Commit 24af7eae authored by 贾浩@五瓣科技's avatar 贾浩@五瓣科技

update

parent 6a9e6011
......@@ -8,19 +8,19 @@ import (
type Config struct {
Debug bool `toml:"debug"`
MySQL MysqlConfig `toml:"mysql"`
PGSQL PGSQLConfig `toml:"pgsql"`
Server ServerConfig `toml:"server"`
TGBot TGBotConfig `toml:"tg_bot"`
Supabase SupabaseConfig `toml:"supabase"`
TGTask TGTaskConfig `toml:"tg_task"`
}
type SupabaseConfig struct {
URL string `toml:"url"`
APIKey string `toml:"api_key"`
Email string `toml:"email"`
Password string `toml:"password"`
}
type MysqlConfig struct {
type PGSQLConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
User string `toml:"user"`
......@@ -31,12 +31,12 @@ type MysqlConfig struct {
EnableLog bool `toml:"enable_log"`
}
type ServerConfig struct {
Listen string `toml:"listen"`
type TGTaskConfig struct {
URL string `toml:"url"`
}
type TGBotConfig struct {
Token string `toml:"token"`
type ServerConfig struct {
Listen string `toml:"listen"`
}
var confPath = flag.String("c", "config.toml", "config file path")
......
......@@ -58,3 +58,9 @@ func IsValidAction(action string) bool {
_, ok := validActions[action]
return ok
}
var TaskAction = map[string][]string{
TaskPlatformApp: {TaskActionActive, TaskActionInvite},
TaskPlatformTelegram: {TaskActionJoin, TaskActionActive},
TaskPlatformTwitter: {TaskActionFollow, TaskActionLike, TaskActionReply, TaskActionRetweet},
}
package dao
import (
"fmt"
"sdk_api/config"
dbModel "sdk_api/model/db"
"time"
"github.com/supabase-community/supabase-go"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
type Dao struct {
......@@ -19,39 +25,40 @@ func New(_c *config.Config) (dao *Dao, err error) {
}
// dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True",
// _c.MySQL.User, _c.MySQL.Password, _c.MySQL.Host, _c.MySQL.Port, _c.MySQL.Database)
// dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=postgres port=5432 sslmode=disable search_path=taskcenter",
// "43.198.54.207", "postgres", "wuban-password",
// )
// fmt.Println(dsn)
//
// lgr := logger.Default
// if _c.MySQL.EnableLog {
// lgr = logger.Default.LogMode(logger.Info)
// }
//
// dao.db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
// NamingStrategy: schema.NamingStrategy{
// SingularTable: true,
// },
// DisableForeignKeyConstraintWhenMigrating: true, // 停用外键约束
// Logger: lgr,
// })
// if err != nil {
// return
// }
// sqlDB, err := dao.db.DB()
// if err != nil {
// return
// }
// sqlDB.SetMaxOpenConns(_c.MySQL.MaxConn)
// sqlDB.SetMaxIdleConns(_c.MySQL.MaxIdleConn)
// sqlDB.SetConnMaxIdleTime(time.Hour)
// err = dao.db.AutoMigrate(&dbModel.Project{}, &dbModel.TaskGroup{}, &dbModel.Task{}, &dbModel.TaskHistory{})
// if err != nil {
// return
// }
// _c.PGSQL.User, _c.PGSQL.Password, _c.PGSQL.Host, _c.PGSQL.Port, _c.PGSQL.Database)
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=verify-ca sslrootcert=ca.crt",
_c.PGSQL.Host, _c.PGSQL.User, _c.PGSQL.Password, _c.PGSQL.Database, _c.PGSQL.Port,
)
lgr := logger.Default
if _c.PGSQL.EnableLog {
lgr = logger.Default.LogMode(logger.Info)
}
dao.db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
DisableForeignKeyConstraintWhenMigrating: true, // 停用外键约束
Logger: lgr,
})
if err != nil {
return
}
sqlDB, err := dao.db.DB()
if err != nil {
return
}
sqlDB.SetMaxOpenConns(_c.PGSQL.MaxConn)
sqlDB.SetMaxIdleConns(_c.PGSQL.MaxIdleConn)
sqlDB.SetConnMaxIdleTime(time.Hour)
err = dao.db.AutoMigrate(&dbModel.TaskAction{}, &dbModel.Project{}, &dbModel.TaskGroup{}, &dbModel.Task{}, &dbModel.TaskHistory{})
if err != nil {
return
}
if err = dao.InitTaskAction(); err != nil {
panic(err)
}
return dao, nil
}
package dao
import (
"sdk_api/constant"
dbModel "sdk_api/model/db"
"sdk_api/util"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func (d *Dao) InitTaskAction() (err error) {
for platform, actions := range constant.TaskAction {
for _, action := range actions {
t := &dbModel.TaskAction{
Id: util.GenFlakeID(),
Platform: platform,
Action: action,
}
err = d.db.Clauses(clause.OnConflict{DoNothing: true}).Create(t).Error
if err != nil {
return err
}
}
}
return err
}
func (d *Dao) CreateProject(p *dbModel.Project) (err error) {
return d.db.Create(p).Error
}
......@@ -16,7 +37,7 @@ func (d *Dao) GetProjectList(page, pageSize int) (list []*dbModel.Project, err e
func (d *Dao) GetProject(id int) (p *dbModel.Project, err error) {
p = &dbModel.Project{}
err = d.db.Where("`id` = ?", id).First(&p).Error
err = d.db.Where("id = ?", id).First(&p).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
......@@ -33,7 +54,7 @@ func (d *Dao) GetGroupList(page, pageSize int) (list []*dbModel.TaskGroup, err e
func (d *Dao) GetGroup(id int) (g *dbModel.TaskGroup, err error) {
g = &dbModel.TaskGroup{}
err = d.db.Where("`id` = ?", id).First(&g).Error
err = d.db.Where("id = ?", id).First(&g).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
......@@ -41,9 +62,86 @@ func (d *Dao) GetGroup(id int) (g *dbModel.TaskGroup, err error) {
}
func (d *Dao) GetGroupTasks(gid int) (list []*dbModel.Task, err error) {
return list, d.db.Where("`group_id` = ?", gid).Find(&list).Error
return list, d.db.Where("group_id = ?", gid).Find(&list).Error
}
func (d *Dao) CreateGroupTask(gt *dbModel.Task) (err error) {
return d.db.Create(gt).Error
}
func (d *Dao) GetTaskDetail(tid int) (t *dbModel.Task, err error) {
t = &dbModel.Task{}
err = d.db.Where("id = ?", tid).First(&t).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return t, err
}
func (d *Dao) IsDailyTask(tid int) (ok bool, err error) {
var count int64
err = d.db.Model(&dbModel.Task{}).Where("id = ? and daily = ?", tid, 1).Count(&count).Error
if err != nil {
return false, err
}
return count > 0, nil
}
// IsTaskDone 是否任务已完成
func (d *Dao) IsTaskDone(tid int, userId string) (ok bool, err error) {
isDailyTask, err := d.IsDailyTask(tid)
if err != nil {
return false, err
}
var count int64
if !isDailyTask {
err = d.db.Model(&dbModel.TaskHistory{}).Where("task_id = ? and user_id = ?", tid, userId).Count(&count).Error
return count > 0, err
}
err = d.db.Model(&dbModel.TaskHistory{}).
Where("task_id = ? and user_id = ? and created_at >= ?", tid, userId, time.Now().UTC().Truncate(24*time.Hour)).
Count(&count).Error
return count > 0, err
}
func (d *Dao) CreateTaskHistory(taskId int, userId string, isDailyTask bool) (err error) {
// 使用行锁,防止并发
tx := d.db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
temp := &dbModel.TaskHistory{}
if !isDailyTask {
err = tx.Set("gorm:query_option", "FOR UPDATE NOWAIT").Where("task_id = ? and user_id = ?", taskId, userId).First(temp).Error
if err == gorm.ErrRecordNotFound {
err = tx.Create(&dbModel.TaskHistory{
Id: util.GenFlakeID(),
TaskId: taskId,
UserId: userId,
}).Error
if err != nil {
return err
}
}
return err
}
err = tx.Set("gorm:query_option", "FOR UPDATE NOWAIT").Where("task_id = ? and user_id = ? and created_at >= ?", taskId, userId, time.Now().UTC().Truncate(24*time.Hour)).First(temp).Error
if err == gorm.ErrRecordNotFound {
err = tx.Create(&dbModel.TaskHistory{
Id: util.GenFlakeID(),
TaskId: taskId,
UserId: userId,
}).Error
if err != nil {
return err
}
}
return err
}
package dao
import (
"testing"
)
func TestMigrate(t *testing.T) {
}
package dao
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
)
func (d *Dao) CheckTGJoin(userId, chatId int) (ok bool, err error) {
url := fmt.Sprintf("%s/api/v1/user/joined?chatId=%d&userId=%d", strings.TrimSuffix(d.c.TGTask.URL, "/"), chatId, userId)
log.WithField("url", url).Debug("check tg join")
data, err := httpGet(url)
if err != nil {
return false, err
}
retCode := gjson.Get(string(data), "code").Int()
if retCode == 1 {
return false, errors.New(string(data))
}
return gjson.Get(string(data), "data.joined").Bool(), nil
}
func (d *Dao) CheckTGActive(userId, chatId int) (ok bool, err error) {
url := fmt.Sprintf("%s/api/v1/user/active?chatId=%d&userId=%d", strings.TrimSuffix(d.c.TGTask.URL, "/"), chatId, userId)
log.WithField("url", url).Debug("check tg active")
data, err := httpGet(url)
if err != nil {
return false, err
}
retCode := gjson.Get(string(data), "code").Int()
if retCode == 1 {
return false, errors.New(string(data))
}
return gjson.Get(string(data), "data.msgCount").Int() > 0, nil
}
func httpGet(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
......@@ -2,8 +2,7 @@ package api_model
type CreateProjectRequest struct {
ProjectName string `json:"projectName" binding:"required"`
TelegramChatId int `json:"telegramChatId" binding:"required"`
TwitterHandle string `json:"twitterHandle" binding:"required"`
Description string `json:"description" binding:"required"`
}
type GetProjectResponse struct {
......@@ -23,10 +22,14 @@ type Task struct {
Platform string `json:"platform" binding:"required"`
Action string `json:"action" binding:"required"`
Url string `json:"url" binding:"required"`
TwitterUserId int `json:"twitterUserId"`
TelegramChatId int `json:"telegramChatId"`
TweetId int `json:"tweetId"`
Description string `json:"description" binding:"required"`
Reward int `json:"reward" binding:"required"`
Start int `json:"start" binding:"required"`
End int `json:"end" binding:"required"`
Daily bool `json:"daily" binding:"required"`
}
type GetGroupResponse CreateGroupRequest
package db_model
import (
"gorm.io/gorm"
)
type Project struct {
Id int `gorm:"primaryKey;autoIncrement:false"`
Name string `gorm:"type:text;not null;comment:项目名称"`
TelegramChatId int `gorm:"type:int;not null;comment:telegram群id"`
TwitterHandle string `gorm:"type:text;not null;comment:twitter用户名"`
gorm.Model
Groups []*TaskGroup
}
type TaskGroup struct {
Id int `gorm:"primaryKey;autoIncrement:false"`
ProjectId int `gorm:"type:int;not null;comment:项目id"`
Description string `gorm:"type:text;not null;comment:任务组描述"`
gorm.Model
}
type Task struct {
Id int `gorm:"primaryKey;autoIncrement:false"`
GroupId int `gorm:"type:int;index;not null;comment:任务组id"`
Platform string `gorm:"type:text;not null;comment:任务平台"`
Action string `gorm:"type:text;not null;comment:任务动作"`
Url string `gorm:"type:text;not null;comment:任务地址"`
Description string `gorm:"type:text;not null;comment:任务描述"`
Reward int `gorm:"type:int;not null;comment:任务奖励"`
Start int `gorm:"type:int;not null;comment:任务开始时间"`
End int `gorm:"type:int;not null;comment:任务结束时间"`
gorm.Model
}
type TaskHistory struct {
Id int `gorm:"primaryKey"`
TaskId int `gorm:"type:int;index;not null;comment:任务id"`
User string `gorm:"type:text;not null;comment:用户"`
gorm.Model
}
package db_model
import (
"gorm.io/gorm"
)
type TaskAction struct {
Id int `gorm:"primaryKey;autoIncrement:false"`
Platform string `gorm:"type:text;uniqueIndex:uidx_platform_action;not null;comment:任务平台"`
Action string `gorm:"type:text;uniqueIndex:uidx_platform_action;not null;comment:任务动作"`
gorm.Model
}
func (t *TaskAction) TableName() string {
return "taskcenter.task_action"
}
type Project struct {
Id int `gorm:"primaryKey;autoIncrement:false"`
Name string `gorm:"type:text;not null;comment:项目名称"`
Description string `gorm:"type:text;not null;comment:项目描述"`
gorm.Model
Groups []*TaskGroup
}
func (p *Project) TableName() string {
return "taskcenter.project"
}
type TaskGroup struct {
Id int `gorm:"primaryKey;autoIncrement:false"`
ProjectId int `gorm:"type:int;not null;comment:项目id"`
Description string `gorm:"type:text;not null;comment:任务组描述"`
gorm.Model
}
func (t *TaskGroup) TableName() string {
return "taskcenter.task_group"
}
type Task struct {
Id int `gorm:"primaryKey;autoIncrement:false"`
GroupId int `gorm:"type:int;index;not null;comment:任务组id"`
Platform string `gorm:"type:text;not null;comment:任务平台"`
Action string `gorm:"type:text;not null;comment:任务动作"`
Url string `gorm:"type:text;not null;comment:任务链接"`
TweeterUserId int `gorm:"type:int;not null;comment:tweet用户id,用于关注"`
TweetId int `gorm:"type:int;not null;comment:推文id,用于转发点赞等"`
TelegramChatId int `gorm:"type:int;not null;comment:telegram群id"`
Description string `gorm:"type:text;not null;comment:任务描述"`
Reward int `gorm:"type:int;not null;comment:任务奖励"`
Start int `gorm:"type:int;not null;comment:任务开始时间"`
End int `gorm:"type:int;not null;comment:任务结束时间"`
Daily int `gorm:"type:int;not null;comment:是否是每日任务"`
gorm.Model
}
func (t *Task) TableName() string {
return "taskcenter.task"
}
type TaskHistory struct {
Id int `gorm:"primaryKey;autoIncrement:false"`
TaskId int `gorm:"type:int;index;not null;comment:任务id"`
UserId string `gorm:"type:uuid;index;not null;comment:用户id"`
IsReward int `gorm:"type:int;not null;comment:是否已发放奖励"`
gorm.Model
}
func (t *TaskHistory) TableName() string {
return "taskcenter.task_history"
}
package db_model
......@@ -36,6 +36,14 @@ func createGroup(c *gin.Context) {
c.JSON(200, withError(constant.InvalidParam))
return
}
if task.Platform == constant.TaskPlatformTelegram && task.TelegramChatId == 0 {
c.JSON(200, withError(constant.InvalidParam))
return
}
if task.Platform == constant.TaskPlatformTwitter && task.TweetId == 0 && task.TwitterUserId == 0 {
c.JSON(200, withError(constant.InvalidParam))
return
}
}
gid, err := srv.CreateGroup(req)
......@@ -61,9 +69,4 @@ func getGroup(c *gin.Context) {
return
}
c.JSON(200, withSuccess(resp))
}
func checkTask(c *gin.Context) {
}
......@@ -21,8 +21,12 @@ func initRouter(e *gin.Engine) {
{
group := v1.Group("/group")
group.GET("/:gid", getGroup) // 获取任务组任务详情
group.GET("/:gid/:tid/check", checkTask) // 检查任务是否完成
group.POST("/create", createGroup) // 创建任务组
}
{
task := v1.Group("/task")
task.GET("/check/:tid", checkTask) // 检查任务是否完成
}
}
package server
import (
"sdk_api/constant"
"strconv"
"github.com/gin-gonic/gin"
)
func checkTask(c *gin.Context) {
_taskId := c.Param("tid")
taskId, _ := strconv.Atoi(_taskId)
userId := c.Query("userId")
if taskId == 0 || userId == "" {
c.JSON(200, withError(constant.InvalidParam))
return
}
done, expired, err := srv.CheckTask(taskId, userId)
if err != nil {
c.JSON(200, withError(constant.InternalError))
return
}
if done || expired {
c.JSON(200, withSuccess(gin.H{"done": done}))
}
done, err = srv.SyncTask(taskId, userId)
if err != nil {
c.JSON(200, withError(constant.InternalError))
return
}
c.JSON(200, withSuccess(gin.H{"done": done}))
}
......@@ -21,6 +21,10 @@ func (s *Service) CreateGroup(req *apiModel.CreateGroupRequest) (gid int, err er
}
for _, task := range req.Tasks {
daily := 0
if task.Daily {
daily = 1
}
gt := &dbModel.Task{
Id: util.GenFlakeID(),
GroupId: g.Id,
......@@ -31,6 +35,10 @@ func (s *Service) CreateGroup(req *apiModel.CreateGroupRequest) (gid int, err er
Reward: task.Reward,
Start: task.Start,
End: task.End,
Daily: daily,
TweeterUserId: task.TwitterUserId,
TweetId: task.TweetId,
TelegramChatId: task.TelegramChatId,
}
err = s.d.CreateGroupTask(gt)
if err != nil {
......@@ -69,6 +77,7 @@ func (s *Service) GetGroup(gid int) (resp *apiModel.GetGroupResponse, err error)
Reward: task.Reward,
Start: task.Start,
End: task.End,
Daily: task.Daily == 1,
})
}
return
......
......@@ -12,8 +12,6 @@ func (s *Service) CreateProject(req *apiModel.CreateProjectRequest) (pid int, er
p := &dbModel.Project{
Id: util.GenFlakeID(),
Name: req.ProjectName,
TelegramChatId: req.TelegramChatId,
TwitterHandle: req.TwitterHandle,
}
err = s.d.CreateProject(p)
if err != nil {
......@@ -33,8 +31,6 @@ func (s *Service) GetProjectList(page, pageSize int) (resp []*apiModel.GetProjec
for _, v := range list {
resp = append(resp, &apiModel.GetProjectResponse{
ProjectName: v.Name,
TelegramChatId: v.TelegramChatId,
TwitterHandle: v.TwitterHandle,
})
}
return
......@@ -53,7 +49,5 @@ func (s *Service) GetProject(pid int) (resp *apiModel.GetProjectResponse, err er
return &apiModel.GetProjectResponse{
ProjectName: p.Name,
TelegramChatId: p.TelegramChatId,
TwitterHandle: p.TwitterHandle,
}, nil
}
package service
import (
"fmt"
"sdk_api/constant"
"time"
log "github.com/sirupsen/logrus"
)
func (s *Service) CheckTask(taskId int, userId string) (done bool, expired bool, err error) {
done, err = s.d.IsTaskDone(taskId, userId)
if err != nil {
log.WithError(err).Error("is task done error")
return
}
if done {
return
}
task, err := s.d.GetTaskDetail(taskId)
if err != nil {
log.WithError(err).Error("get task error")
return
}
if task == nil {
return
}
// 超出时间后只查询不再更新状态
if int64(task.Start) > time.Now().Unix() || int64(task.End) < time.Now().Unix() {
expired = true
}
return
}
func (s *Service) SyncTask(taskId int, userId string) (done bool, err error) {
task, err := s.d.GetTaskDetail(taskId)
if err != nil {
log.WithError(err).Error("get task error")
return
}
if task == nil {
return
}
// todo get tg user id by userId
var tgUserId int = 5428144618
switch task.Platform {
case constant.TaskPlatformTelegram:
switch task.Action {
case constant.TaskActionJoin:
// 加入群组
joined, err := s.d.CheckTGJoin(tgUserId, task.TelegramChatId)
if err != nil {
log.WithError(err).Error("check tg join error")
return false, err
}
if !joined {
return false, nil
}
err = s.d.CreateTaskHistory(taskId, userId, task.Daily == 1)
if err != nil {
log.WithError(err).Error("create task history error")
return false, err
}
return true, nil
case constant.TaskActionActive:
// 今日活跃
activated, err := s.d.CheckTGActive(tgUserId, task.TelegramChatId)
if err != nil {
log.WithError(err).Error("check tg active error")
return false, err
}
if !activated {
return false, nil
}
err = s.d.CreateTaskHistory(taskId, userId, task.Daily == 1)
if err != nil {
log.WithError(err).Error("create task history error")
return false, err
}
return true, nil
default:
return false, fmt.Errorf("unknown task action: %s", task.Action)
}
case constant.TaskPlatformTwitter:
switch task.Action {
default:
return false, fmt.Errorf("unknown task action: %s", task.Action)
}
default:
return false, fmt.Errorf("unknown task platform: %s", task.Platform)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment