From 9f4fb0a9d0edd6ce2587732533e4bf995d889968 Mon Sep 17 00:00:00 2001 From: lianghuanjie Date: Wed, 11 Dec 2024 21:15:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=BB=E5=8A=A1=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/api/nova-task.api | 17 ++- doc/sql/novatask.sql | 12 ++ doc/swagger/nova-task.json | 72 ++++++++++-- go.mod | 1 + go.sum | 2 + internal/handler/routes.go | 6 + .../handler/task/get_task_list_handler.go | 5 +- .../handler/task/get_task_reward_handler.go | 2 +- .../task/verify_task_result_handler.go | 29 +++++ internal/logic/task/get_task_list_logic.go | 15 +++ internal/logic/task/get_task_reward_logic.go | 53 ++++++++- .../logic/task/verify_task_result_logic.go | 80 +++++++++++++ internal/model/nh_task_asset_model.go | 41 +++++++ internal/model/nh_task_asset_model_gen.go | 106 ++++++++++++++++++ internal/model/nh_task_asset_record_model.go | 29 +++++ .../model/nh_task_asset_record_model_gen.go | 91 +++++++++++++++ internal/model/nh_task_model.go | 14 +++ internal/model/nh_task_model_gen.go | 9 +- internal/model/nh_task_progress_model.go | 37 ++++++ internal/model/nh_task_progress_model_gen.go | 106 ++++++++++++++++++ internal/pkg/encrypt/jwt/jwt.go | 66 +++++------ internal/pkg/errs/reason.go | 3 + internal/svc/service_context.go | 15 ++- internal/types/types.go | 17 ++- 24 files changed, 765 insertions(+), 63 deletions(-) create mode 100644 doc/sql/novatask.sql create mode 100644 internal/handler/task/verify_task_result_handler.go create mode 100644 internal/logic/task/verify_task_result_logic.go create mode 100755 internal/model/nh_task_asset_model.go create mode 100755 internal/model/nh_task_asset_model_gen.go create mode 100755 internal/model/nh_task_asset_record_model.go create mode 100755 internal/model/nh_task_asset_record_model_gen.go create mode 100755 internal/model/nh_task_progress_model.go create mode 100755 internal/model/nh_task_progress_model_gen.go diff --git a/doc/api/nova-task.api b/doc/api/nova-task.api index d470976..925ae73 100644 --- a/doc/api/nova-task.api +++ b/doc/api/nova-task.api @@ -10,9 +10,13 @@ service novatask { @handler GetTaskList get /tasks (GetTaskListReq) returns (GetTaskListResp) + @doc "校验任务结果" + @handler VerifyTaskResult + get /task/:id (TaskIdPath) returns (VerifyTaskResultResp) + @doc "领取任务奖励" @handler GetTaskReward - get /reward/:id (GetTaskRewardReq) returns (GetTaskRewardResp) + get /reward/:id (TaskIdPath) returns (GetTaskRewardResp) } type GetTaskListReq { @@ -21,25 +25,32 @@ type GetTaskListReq { type Task { Id uint `json:"id"` + CommunityId uint `json:"community_id"` Title string `json:"title"` SubTitle string `json:"sub_title"` Description string `json:"description"` Points int `json:"points"` ButtonText string `json:"button_text"` Type int8 `json:"type"` + Url string `json:"url"` + Status int8 `json:"status"` StartAt string `json:"start_at"` EndAt string `json:"end_at"` - Status int8 `json:"status"` + FinishState int8 `json:"finish_state"` // 0:未完成 1:待校验 2:已完成未领取 3:已领取 } type GetTaskListResp { Tasks []Task `json:"tasks"` } -type GetTaskRewardReq { +type TaskIdPath { ID uint `path:"id"` } +type VerifyTaskResultResp { + Finish bool `json:"finish"` +} + type GetTaskRewardResp { Points int `json:"points"` } diff --git a/doc/sql/novatask.sql b/doc/sql/novatask.sql new file mode 100644 index 0000000..db72932 --- /dev/null +++ b/doc/sql/novatask.sql @@ -0,0 +1,12 @@ +CREATE TABLE `nh_task_progress` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `uid` int(11) NOT NULL, + `task_id` int(11) unsigned NOT NULL COMMENT '任务id', + `task_seq` int(11) NOT NULL COMMENT '用于可重复任务的序列号', + `stage` tinyint NOT NULL DEFAULT 0 COMMENT '任务的阶段, 0:未完成 1:待校验 2:已完成未领取 3:已领取', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uid_task_id_seq` (`uid`, `task_id`, `task_seq`) +) COMMENT='用户任务节点'; \ No newline at end of file diff --git a/doc/swagger/nova-task.json b/doc/swagger/nova-task.json index b3ded41..834f5a9 100644 --- a/doc/swagger/nova-task.json +++ b/doc/swagger/nova-task.json @@ -45,6 +45,36 @@ ] } }, + "/api/task/v1/task/{id}": { + "get": { + "summary": "校验任务结果", + "operationId": "VerifyTaskResult", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/VerifyTaskResultResp" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "task" + ], + "security": [ + { + "apiKey": [] + } + ] + } + }, "/api/task/v1/tasks": { "get": { "summary": "获取任务列表", @@ -109,10 +139,6 @@ "tasks" ] }, - "GetTaskRewardReq": { - "type": "object", - "title": "GetTaskRewardReq" - }, "GetTaskRewardResp": { "type": "object", "properties": { @@ -133,6 +159,10 @@ "type": "integer", "format": "uint32" }, + "community_id": { + "type": "integer", + "format": "uint32" + }, "title": { "type": "string" }, @@ -153,29 +183,57 @@ "type": "integer", "format": "int8" }, + "url": { + "type": "string" + }, + "status": { + "type": "integer", + "format": "int8" + }, "start_at": { "type": "string" }, "end_at": { "type": "string" }, - "status": { + "finish_state": { "type": "integer", - "format": "int8" + "format": "int8", + "description": " 0:未完成 1:待校验 2:已完成未领取 3:已领取" } }, "title": "Task", "required": [ "id", + "community_id", "title", "sub_title", "description", "points", "button_text", "type", + "url", + "status", "start_at", "end_at", - "status" + "finish_state" + ] + }, + "TaskIdPath": { + "type": "object", + "title": "TaskIdPath" + }, + "VerifyTaskResultResp": { + "type": "object", + "properties": { + "finish": { + "type": "boolean", + "format": "boolean" + } + }, + "title": "VerifyTaskResultResp", + "required": [ + "finish" ] } }, diff --git a/go.mod b/go.mod index a51e0e5..2930af5 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.4 require ( github.com/golang-jwt/jwt/v4 v4.5.1 + github.com/shopspring/decimal v1.4.0 github.com/spf13/cast v1.7.0 github.com/zeromicro/go-zero v1.7.4 ) diff --git a/go.sum b/go.sum index 8c0c3c3..ca0949e 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= diff --git a/internal/handler/routes.go b/internal/handler/routes.go index f82d3d8..603cdfe 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -21,6 +21,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/reward/:id", Handler: task.GetTaskRewardHandler(serverCtx), }, + { + // 校验任务结果 + Method: http.MethodGet, + Path: "/task/:id", + Handler: task.VerifyTaskResultHandler(serverCtx), + }, { // 获取任务列表 Method: http.MethodGet, diff --git a/internal/handler/task/get_task_list_handler.go b/internal/handler/task/get_task_list_handler.go index d6c4c12..caffbb1 100644 --- a/internal/handler/task/get_task_list_handler.go +++ b/internal/handler/task/get_task_list_handler.go @@ -1,15 +1,14 @@ package task import ( - "net/http" - "github.com/zeromicro/go-zero/rest/httpx" + "net/http" "nova_task/internal/logic/task" "nova_task/internal/svc" "nova_task/internal/types" ) -// 获取任务列表 +// GetTaskListHandler 获取任务列表 func GetTaskListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req types.GetTaskListReq diff --git a/internal/handler/task/get_task_reward_handler.go b/internal/handler/task/get_task_reward_handler.go index 8f16190..ee0bfc0 100644 --- a/internal/handler/task/get_task_reward_handler.go +++ b/internal/handler/task/get_task_reward_handler.go @@ -12,7 +12,7 @@ import ( // 领取任务奖励 func GetTaskRewardHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req types.GetTaskRewardReq + var req types.TaskIdPath if err := httpx.Parse(r, &req); err != nil { httpx.ErrorCtx(r.Context(), w, err) return diff --git a/internal/handler/task/verify_task_result_handler.go b/internal/handler/task/verify_task_result_handler.go new file mode 100644 index 0000000..e12b835 --- /dev/null +++ b/internal/handler/task/verify_task_result_handler.go @@ -0,0 +1,29 @@ +package task + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "nova_task/internal/logic/task" + "nova_task/internal/svc" + "nova_task/internal/types" +) + +// 校验任务结果 +func VerifyTaskResultHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.TaskIdPath + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := task.NewVerifyTaskResultLogic(r.Context(), svcCtx) + resp, err := l.VerifyTaskResult(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/internal/logic/task/get_task_list_logic.go b/internal/logic/task/get_task_list_logic.go index ccac48d..3443fde 100644 --- a/internal/logic/task/get_task_list_logic.go +++ b/internal/logic/task/get_task_list_logic.go @@ -2,6 +2,8 @@ package task import ( "context" + "github.com/spf13/cast" + "nova_task/internal/model" "nova_task/internal/pkg/errs" "time" @@ -27,6 +29,8 @@ func NewGetTaskListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetTa } func (l *GetTaskListLogic) GetTaskList(req *types.GetTaskListReq) (*types.GetTaskListResp, error) { + uid := cast.ToInt(l.ctx.Value("uid")) + tasks, err := l.svcCtx.TaskModel.FindTasksByCommunity(l.ctx, req.CommunityId) if err != nil { l.Errorw("get task list failed", logx.Field("err", err), logx.Field("communityId", req.CommunityId)) @@ -35,6 +39,15 @@ func (l *GetTaskListLogic) GetTaskList(req *types.GetTaskListReq) (*types.GetTas resp := &types.GetTaskListResp{} for _, t := range tasks { + taskSeq := 0 + if t.Type == model.TASKTYPE_DAILY_PAY { + taskSeq = cast.ToInt(time.Now().Format("20060102")) + } + var finishState int8 + tp, err := l.svcCtx.TaskProgressModel.FindOneByUidTaskIdTaskSeq(l.ctx, uid, t.CommunityId, taskSeq) + if err == nil { + finishState = tp.Stage + } resp.Tasks = append(resp.Tasks, types.Task{ Id: t.Id, Title: t.Title, @@ -43,9 +56,11 @@ func (l *GetTaskListLogic) GetTaskList(req *types.GetTaskListReq) (*types.GetTas Points: t.Points, ButtonText: t.ButtonText, Type: t.Type, + Url: t.Url, StartAt: t.StartAt.Time.Format(time.DateTime), EndAt: t.EndAt.Time.Format(time.DateTime), Status: t.Status, + FinishState: finishState, }) } diff --git a/internal/logic/task/get_task_reward_logic.go b/internal/logic/task/get_task_reward_logic.go index 1a484da..ac0aa8c 100644 --- a/internal/logic/task/get_task_reward_logic.go +++ b/internal/logic/task/get_task_reward_logic.go @@ -2,6 +2,11 @@ package task import ( "context" + "errors" + "github.com/spf13/cast" + "nova_task/internal/model" + "nova_task/internal/pkg/errs" + "time" "nova_task/internal/svc" "nova_task/internal/types" @@ -9,13 +14,13 @@ import ( "github.com/zeromicro/go-zero/core/logx" ) +// GetTaskRewardLogic v领取任务奖励 type GetTaskRewardLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } -// 领取任务奖励 func NewGetTaskRewardLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetTaskRewardLogic { return &GetTaskRewardLogic{ Logger: logx.WithContext(ctx), @@ -24,8 +29,50 @@ func NewGetTaskRewardLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Get } } -func (l *GetTaskRewardLogic) GetTaskReward(req *types.GetTaskRewardReq) (resp *types.GetTaskRewardResp, err error) { - // todo: add your logic here and delete this line +func (l *GetTaskRewardLogic) GetTaskReward(req *types.TaskIdPath) (resp *types.GetTaskRewardResp, err error) { + uid := cast.ToInt(l.ctx.Value("uid")) + task, err := l.svcCtx.TaskModel.FindOne(l.ctx, req.ID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errs.BadRequest(errs.ErrTaskNotFound, "task not found") + } + return nil, errs.InternalServer(errs.ErrDatabaseOperate, err) + } + var taskSeq int + if task.Type == model.TASKTYPE_DAILY_PAY { + taskSeq = cast.ToInt(time.Now().Format("20060102")) + } + tp, err := l.svcCtx.TaskProgressModel.FindOneByUidTaskIdTaskSeq(l.ctx, uid, task.Id, taskSeq) + if err != nil { + if !errors.Is(err, model.ErrNotFound) { + l.Errorw("find task progress error", logx.Field("err", err)) + return nil, errs.InternalServer(errs.ErrDatabaseOperate, err) + } + return nil, errs.BadRequest(errs.ErrTaskNotFinished, "task not finished") + } + switch { + case tp.Stage < model.TASK_PROGRESS_WAIT_REWARD: + return nil, errs.BadRequest(errs.ErrTaskNotFinished, "task not finished") + case tp.Stage > model.TASK_PROGRESS_WAIT_REWARD: + return nil, errs.BadRequest(errs.ErrTaskAlreadyReward, "task already reward") + } + + // 给予用户奖励 + err = l.svcCtx.TaskAssetModel.AddUserPoint(l.ctx, uid, task.Points) + if err != nil { + l.Errorw("add user point error", logx.Field("err", err)) + return nil, errs.InternalServer(errs.ErrDatabaseOperate, err) + } + + // 记录用户获奖记录 + _, err = l.svcCtx.TaskAssetRecordModel.Insert(l.ctx, &model.NhTaskAssetRecord{ + Uid: uid, + EventId: uint64(task.Id), + AssetField: "points", + Count: float64(task.Points), + Remark: task.Title, + CreateTime: int(time.Now().Unix()), + }) return } diff --git a/internal/logic/task/verify_task_result_logic.go b/internal/logic/task/verify_task_result_logic.go new file mode 100644 index 0000000..45c3519 --- /dev/null +++ b/internal/logic/task/verify_task_result_logic.go @@ -0,0 +1,80 @@ +package task + +import ( + "context" + "errors" + "github.com/spf13/cast" + "github.com/zeromicro/go-zero/core/logx" + "nova_task/internal/model" + "nova_task/internal/pkg/errs" + "nova_task/internal/svc" + "nova_task/internal/types" + "time" +) + +// VerifyTaskResultLogic 校验任务结果逻辑 +type VerifyTaskResultLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewVerifyTaskResultLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VerifyTaskResultLogic { + return &VerifyTaskResultLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *VerifyTaskResultLogic) VerifyTaskResult(req *types.TaskIdPath) (*types.VerifyTaskResultResp, error) { + uid := cast.ToInt(l.ctx.Value("uid")) + task, err := l.svcCtx.TaskModel.FindOne(l.ctx, req.ID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errs.BadRequest(errs.ErrTaskNotFound, "task not found") + } + return nil, errs.InternalServer(errs.ErrDatabaseOperate, err) + } + var taskSeq int + if task.Type == model.TASKTYPE_DAILY_PAY { + taskSeq = cast.ToInt(time.Now().Format("20060102")) + } + tp, err := l.svcCtx.TaskProgressModel.FindOneByUidTaskIdTaskSeq(l.ctx, uid, task.Id, taskSeq) + if err != nil { + if !errors.Is(err, model.ErrNotFound) { + l.Errorw("find task progress error", logx.Field("err", err)) + return nil, errs.InternalServer(errs.ErrDatabaseOperate, err) + } + tp = &model.NhTaskProgress{ + Uid: uid, + TaskId: task.Id, + TaskSeq: taskSeq, + Stage: model.TASK_PROGRESS_NOT_FINISHED, + } + } + if tp.Stage == model.TASK_PROGRESS_REWARDED { + return nil, errs.BadRequest(errs.ErrTaskAlreadyReward, "task already reward") + } + // todo: 校验用户是否完成该任务 + switch task.Type { + case model.TASKTYPE_BIND_TWITTER: + case model.TASKTYPE_BIND_DISCORD: + case model.TASKTYPE_DAILY_PAY: + default: + } + + tp.Stage = model.TASK_PROGRESS_WAIT_REWARD + if tp.Id > 0 { + err = l.svcCtx.TaskProgressModel.Update(l.ctx, tp) + } else { + _, err = l.svcCtx.TaskProgressModel.Insert(l.ctx, tp) + } + + if err != nil { + l.Errorw("update task progress error", logx.Field("err", err)) + return nil, errs.InternalServer(errs.ErrDatabaseOperate, err) + } + + return &types.VerifyTaskResultResp{Finish: true}, nil +} diff --git a/internal/model/nh_task_asset_model.go b/internal/model/nh_task_asset_model.go new file mode 100755 index 0000000..3c50cd5 --- /dev/null +++ b/internal/model/nh_task_asset_model.go @@ -0,0 +1,41 @@ +package model + +import ( + "context" + "fmt" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "time" +) + +var _ NhTaskAssetModel = (*customNhTaskAssetModel)(nil) + +type ( + // NhTaskAssetModel is an interface to be customized, add more methods here, + // and implement the added methods in customNhTaskAssetModel. + NhTaskAssetModel interface { + nhTaskAssetModel + withSession(session sqlx.Session) NhTaskAssetModel + AddUserPoint(ctx context.Context, uid int, points int) error + } + + customNhTaskAssetModel struct { + *defaultNhTaskAssetModel + } +) + +// NewNhTaskAssetModel returns a model for the database table. +func NewNhTaskAssetModel(conn sqlx.SqlConn) NhTaskAssetModel { + return &customNhTaskAssetModel{ + defaultNhTaskAssetModel: newNhTaskAssetModel(conn), + } +} + +func (m *customNhTaskAssetModel) withSession(session sqlx.Session) NhTaskAssetModel { + return NewNhTaskAssetModel(sqlx.NewSqlConnFromSession(session)) +} + +func (m *customNhTaskAssetModel) AddUserPoint(ctx context.Context, uid int, points int) error { + insertOrUpdate := fmt.Sprintf("INSERT INTO %s (`uid`, `points`, `create_time`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `points` = `points` + ?", m.table) + _, err := m.conn.ExecCtx(ctx, insertOrUpdate, uid, points, time.Now().Unix(), points) + return err +} diff --git a/internal/model/nh_task_asset_model_gen.go b/internal/model/nh_task_asset_model_gen.go new file mode 100755 index 0000000..1956afd --- /dev/null +++ b/internal/model/nh_task_asset_model_gen.go @@ -0,0 +1,106 @@ +// Code generated by goctl. DO NOT EDIT. +// versions: +// goctl version: 1.7.3 + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" + + "github.com/shopspring/decimal" +) + +var ( + nhTaskAssetFieldNames = builder.RawFieldNames(&NhTaskAsset{}) + nhTaskAssetRows = strings.Join(nhTaskAssetFieldNames, ",") + nhTaskAssetRowsExpectAutoSet = strings.Join(stringx.Remove(nhTaskAssetFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + nhTaskAssetRowsWithPlaceHolder = strings.Join(stringx.Remove(nhTaskAssetFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + nhTaskAssetModel interface { + Insert(ctx context.Context, data *NhTaskAsset) (sql.Result, error) + FindOne(ctx context.Context, id int) (*NhTaskAsset, error) + FindOneByUid(ctx context.Context, uid int) (*NhTaskAsset, error) + Update(ctx context.Context, data *NhTaskAsset) error + Delete(ctx context.Context, id int) error + } + + defaultNhTaskAssetModel struct { + conn sqlx.SqlConn + table string + } + + NhTaskAsset struct { + Id int `db:"id"` + Uid int `db:"uid"` + Points decimal.Decimal `db:"points"` // 积分值 + ElitePoints decimal.Decimal `db:"elite_points"` // 高级积分 + Keys int `db:"keys"` // 宝箱钥匙 + CreateTime int `db:"create_time"` // 创建时间 + } +) + +func newNhTaskAssetModel(conn sqlx.SqlConn) *defaultNhTaskAssetModel { + return &defaultNhTaskAssetModel{ + conn: conn, + table: "`nh_task_asset`", + } +} + +func (m *defaultNhTaskAssetModel) Delete(ctx context.Context, id int) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultNhTaskAssetModel) FindOne(ctx context.Context, id int) (*NhTaskAsset, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", nhTaskAssetRows, m.table) + var resp NhTaskAsset + err := m.conn.QueryRowCtx(ctx, &resp, query, id) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultNhTaskAssetModel) FindOneByUid(ctx context.Context, uid int) (*NhTaskAsset, error) { + var resp NhTaskAsset + query := fmt.Sprintf("select %s from %s where `uid` = ? limit 1", nhTaskAssetRows, m.table) + err := m.conn.QueryRowCtx(ctx, &resp, query, uid) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultNhTaskAssetModel) Insert(ctx context.Context, data *NhTaskAsset) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?)", m.table, nhTaskAssetRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.Uid, data.Points, data.ElitePoints, data.Keys) + return ret, err +} + +func (m *defaultNhTaskAssetModel) Update(ctx context.Context, newData *NhTaskAsset) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, nhTaskAssetRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, newData.Uid, newData.Points, newData.ElitePoints, newData.Keys, newData.Id) + return err +} + +func (m *defaultNhTaskAssetModel) tableName() string { + return m.table +} diff --git a/internal/model/nh_task_asset_record_model.go b/internal/model/nh_task_asset_record_model.go new file mode 100755 index 0000000..0d20cf1 --- /dev/null +++ b/internal/model/nh_task_asset_record_model.go @@ -0,0 +1,29 @@ +package model + +import "github.com/zeromicro/go-zero/core/stores/sqlx" + +var _ NhTaskAssetRecordModel = (*customNhTaskAssetRecordModel)(nil) + +type ( + // NhTaskAssetRecordModel is an interface to be customized, add more methods here, + // and implement the added methods in customNhTaskAssetRecordModel. + NhTaskAssetRecordModel interface { + nhTaskAssetRecordModel + withSession(session sqlx.Session) NhTaskAssetRecordModel + } + + customNhTaskAssetRecordModel struct { + *defaultNhTaskAssetRecordModel + } +) + +// NewNhTaskAssetRecordModel returns a model for the database table. +func NewNhTaskAssetRecordModel(conn sqlx.SqlConn) NhTaskAssetRecordModel { + return &customNhTaskAssetRecordModel{ + defaultNhTaskAssetRecordModel: newNhTaskAssetRecordModel(conn), + } +} + +func (m *customNhTaskAssetRecordModel) withSession(session sqlx.Session) NhTaskAssetRecordModel { + return NewNhTaskAssetRecordModel(sqlx.NewSqlConnFromSession(session)) +} diff --git a/internal/model/nh_task_asset_record_model_gen.go b/internal/model/nh_task_asset_record_model_gen.go new file mode 100755 index 0000000..91f9492 --- /dev/null +++ b/internal/model/nh_task_asset_record_model_gen.go @@ -0,0 +1,91 @@ +// Code generated by goctl. DO NOT EDIT. +// versions: +// goctl version: 1.7.3 + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + nhTaskAssetRecordFieldNames = builder.RawFieldNames(&NhTaskAssetRecord{}) + nhTaskAssetRecordRows = strings.Join(nhTaskAssetRecordFieldNames, ",") + nhTaskAssetRecordRowsExpectAutoSet = strings.Join(stringx.Remove(nhTaskAssetRecordFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + nhTaskAssetRecordRowsWithPlaceHolder = strings.Join(stringx.Remove(nhTaskAssetRecordFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + nhTaskAssetRecordModel interface { + Insert(ctx context.Context, data *NhTaskAssetRecord) (sql.Result, error) + FindOne(ctx context.Context, id int) (*NhTaskAssetRecord, error) + Update(ctx context.Context, data *NhTaskAssetRecord) error + Delete(ctx context.Context, id int) error + } + + defaultNhTaskAssetRecordModel struct { + conn sqlx.SqlConn + table string + } + + NhTaskAssetRecord struct { + Id int `db:"id"` + Uid int `db:"uid"` + EventId uint64 `db:"event_id"` // 日志事件 + AssetField string `db:"asset_field"` // 积分或钥匙字段 + Count float64 `db:"count"` // 数量 正数获得,负数消耗 + Remark string `db:"remark"` // 备注 + ProvideUid uint `db:"provide_uid"` // 贡献人,当事件为返利事件 + CreateTime int `db:"create_time"` // 创建时间 + } +) + +func newNhTaskAssetRecordModel(conn sqlx.SqlConn) *defaultNhTaskAssetRecordModel { + return &defaultNhTaskAssetRecordModel{ + conn: conn, + table: "`nh_task_asset_record`", + } +} + +func (m *defaultNhTaskAssetRecordModel) Delete(ctx context.Context, id int) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultNhTaskAssetRecordModel) FindOne(ctx context.Context, id int) (*NhTaskAssetRecord, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", nhTaskAssetRecordRows, m.table) + var resp NhTaskAssetRecord + err := m.conn.QueryRowCtx(ctx, &resp, query, id) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultNhTaskAssetRecordModel) Insert(ctx context.Context, data *NhTaskAssetRecord) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?)", m.table, nhTaskAssetRecordRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.Uid, data.EventId, data.AssetField, data.Count, data.Remark, data.ProvideUid) + return ret, err +} + +func (m *defaultNhTaskAssetRecordModel) Update(ctx context.Context, data *NhTaskAssetRecord) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, nhTaskAssetRecordRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, data.Uid, data.EventId, data.AssetField, data.Count, data.Remark, data.ProvideUid, data.Id) + return err +} + +func (m *defaultNhTaskAssetRecordModel) tableName() string { + return m.table +} diff --git a/internal/model/nh_task_model.go b/internal/model/nh_task_model.go index ef30533..1ec7f53 100755 --- a/internal/model/nh_task_model.go +++ b/internal/model/nh_task_model.go @@ -9,6 +9,20 @@ import ( var _ NhTaskModel = (*customNhTaskModel)(nil) +const ( + // 0=follow_twitter,1=bind_twitter,2=cast_twitter,3=publish_twitter,4=repost_twitter,5=watch_youtube,6=follow_youtube,7=bind_discord,8=join_telegram,9=daily_pay + TASKTYPE_FOLLOW_TWITTER = 0 + TASKTYPE_BIND_TWITTER = 1 + TASKTYPE_CAST_TWITTER = 2 + TASKTYPE_PUBLISH_TWITTER = 3 + TASKTYPE_REPOST_TWITTER = 4 + TASKTYPE_WATCH_YOUTUBE = 5 + TASKTYPE_FOLLOW_YOUTUBE = 6 + TASKTYPE_BIND_DISCORD = 7 + TASKTYPE_JOIN_TELEGRAM = 8 + TASKTYPE_DAILY_PAY = 9 +) + type ( // NhTaskModel is an interface to be customized, add more methods here, // and implement the added methods in customNhTaskModel. diff --git a/internal/model/nh_task_model_gen.go b/internal/model/nh_task_model_gen.go index acf48a3..b865cb4 100755 --- a/internal/model/nh_task_model_gen.go +++ b/internal/model/nh_task_model_gen.go @@ -44,7 +44,8 @@ type ( Description string `db:"description"` // 描述 Points int `db:"points"` // 积分数量 ButtonText string `db:"button_text"` // 按钮上的文字 - Type int8 `db:"type"` // 0=一次性任务,1=每天任务 + Type int8 `db:"type"` // 0=follow_twitter,1=bind_twitter,2=cast_twitter,3=publish_twitter,4=repost_twitter,5=watch_youtube,6=follow_youtube,7=bind_discord,8=join_telegram,9=daily_pay + Url string `db:"url"` // 跳转链接 Status int8 `db:"status"` // 0=不启用,1=启用 StartAt sql.NullTime `db:"start_at"` // 开始时间 EndAt sql.NullTime `db:"end_at"` // 结束时间 @@ -81,14 +82,14 @@ func (m *defaultNhTaskModel) FindOne(ctx context.Context, id uint) (*NhTask, err } func (m *defaultNhTaskModel) Insert(ctx context.Context, data *NhTask) (sql.Result, error) { - query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, nhTaskRowsExpectAutoSet) - ret, err := m.conn.ExecCtx(ctx, query, data.CommunityId, data.Title, data.SubTitle, data.Description, data.Points, data.ButtonText, data.Type, data.Status, data.StartAt, data.EndAt) + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, nhTaskRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.CommunityId, data.Title, data.SubTitle, data.Description, data.Points, data.ButtonText, data.Type, data.Url, data.Status, data.StartAt, data.EndAt) return ret, err } func (m *defaultNhTaskModel) Update(ctx context.Context, data *NhTask) error { query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, nhTaskRowsWithPlaceHolder) - _, err := m.conn.ExecCtx(ctx, query, data.CommunityId, data.Title, data.SubTitle, data.Description, data.Points, data.ButtonText, data.Type, data.Status, data.StartAt, data.EndAt, data.Id) + _, err := m.conn.ExecCtx(ctx, query, data.CommunityId, data.Title, data.SubTitle, data.Description, data.Points, data.ButtonText, data.Type, data.Url, data.Status, data.StartAt, data.EndAt, data.Id) return err } diff --git a/internal/model/nh_task_progress_model.go b/internal/model/nh_task_progress_model.go new file mode 100755 index 0000000..f57750a --- /dev/null +++ b/internal/model/nh_task_progress_model.go @@ -0,0 +1,37 @@ +package model + +import "github.com/zeromicro/go-zero/core/stores/sqlx" + +var _ NhTaskProgressModel = (*customNhTaskProgressModel)(nil) + +const ( + // 任务的阶段, 0:未完成 1:待校验 2:已完成未领取 3:已领取 + TASK_PROGRESS_NOT_FINISHED = 0 + TASK_PROGRESS_WAIT_VERIFY = 1 + TASK_PROGRESS_WAIT_REWARD = 2 + TASK_PROGRESS_REWARDED = 3 +) + +type ( + // NhTaskProgressModel is an interface to be customized, add more methods here, + // and implement the added methods in customNhTaskProgressModel. + NhTaskProgressModel interface { + nhTaskProgressModel + withSession(session sqlx.Session) NhTaskProgressModel + } + + customNhTaskProgressModel struct { + *defaultNhTaskProgressModel + } +) + +// NewNhTaskProgressModel returns a model for the database table. +func NewNhTaskProgressModel(conn sqlx.SqlConn) NhTaskProgressModel { + return &customNhTaskProgressModel{ + defaultNhTaskProgressModel: newNhTaskProgressModel(conn), + } +} + +func (m *customNhTaskProgressModel) withSession(session sqlx.Session) NhTaskProgressModel { + return NewNhTaskProgressModel(sqlx.NewSqlConnFromSession(session)) +} diff --git a/internal/model/nh_task_progress_model_gen.go b/internal/model/nh_task_progress_model_gen.go new file mode 100755 index 0000000..72ef354 --- /dev/null +++ b/internal/model/nh_task_progress_model_gen.go @@ -0,0 +1,106 @@ +// Code generated by goctl. DO NOT EDIT. +// versions: +// goctl version: 1.7.3 + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + "time" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + nhTaskProgressFieldNames = builder.RawFieldNames(&NhTaskProgress{}) + nhTaskProgressRows = strings.Join(nhTaskProgressFieldNames, ",") + nhTaskProgressRowsExpectAutoSet = strings.Join(stringx.Remove(nhTaskProgressFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + nhTaskProgressRowsWithPlaceHolder = strings.Join(stringx.Remove(nhTaskProgressFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + nhTaskProgressModel interface { + Insert(ctx context.Context, data *NhTaskProgress) (sql.Result, error) + FindOne(ctx context.Context, id int) (*NhTaskProgress, error) + FindOneByUidTaskIdTaskSeq(ctx context.Context, uid int, taskId uint, taskSeq int) (*NhTaskProgress, error) + Update(ctx context.Context, data *NhTaskProgress) error + Delete(ctx context.Context, id int) error + } + + defaultNhTaskProgressModel struct { + conn sqlx.SqlConn + table string + } + + NhTaskProgress struct { + Id int `db:"id"` + Uid int `db:"uid"` + TaskId uint `db:"task_id"` // 任务id + TaskSeq int `db:"task_seq"` // 用于可重复任务的序列号 + Stage int8 `db:"stage"` // 任务的阶段, 0:未完成 1:待校验 2:已完成未领取 3:已领取 + CreatedAt time.Time `db:"created_at"` // 创建时间 + UpdatedAt time.Time `db:"updated_at"` // 修改时间 + } +) + +func newNhTaskProgressModel(conn sqlx.SqlConn) *defaultNhTaskProgressModel { + return &defaultNhTaskProgressModel{ + conn: conn, + table: "`nh_task_progress`", + } +} + +func (m *defaultNhTaskProgressModel) Delete(ctx context.Context, id int) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultNhTaskProgressModel) FindOne(ctx context.Context, id int) (*NhTaskProgress, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", nhTaskProgressRows, m.table) + var resp NhTaskProgress + err := m.conn.QueryRowCtx(ctx, &resp, query, id) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultNhTaskProgressModel) FindOneByUidTaskIdTaskSeq(ctx context.Context, uid int, taskId uint, taskSeq int) (*NhTaskProgress, error) { + var resp NhTaskProgress + query := fmt.Sprintf("select %s from %s where `uid` = ? and `task_id` = ? and `task_seq` = ? limit 1", nhTaskProgressRows, m.table) + err := m.conn.QueryRowCtx(ctx, &resp, query, uid, taskId, taskSeq) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultNhTaskProgressModel) Insert(ctx context.Context, data *NhTaskProgress) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?)", m.table, nhTaskProgressRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.Uid, data.TaskId, data.TaskSeq, data.Stage) + return ret, err +} + +func (m *defaultNhTaskProgressModel) Update(ctx context.Context, newData *NhTaskProgress) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, nhTaskProgressRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, newData.Uid, newData.TaskId, newData.TaskSeq, newData.Stage, newData.Id) + return err +} + +func (m *defaultNhTaskProgressModel) tableName() string { + return m.table +} diff --git a/internal/pkg/encrypt/jwt/jwt.go b/internal/pkg/encrypt/jwt/jwt.go index 9b7739b..86952fd 100644 --- a/internal/pkg/encrypt/jwt/jwt.go +++ b/internal/pkg/encrypt/jwt/jwt.go @@ -70,37 +70,37 @@ func (b *TokenBuilder) ParseUidFromToken(tokenStr string) (string, string, strin return appid, userId, tgId, expiredAt, nil } -//// ParseUid 解析出uid -//func (b *TokenBuilder) ParseUid(r *http.Request) (string, error) { -// parser := token.NewTokenParser() -// tok, err := parser.ParseToken(r, b.config.AccessSecret, "") -// if err != nil { -// return "", err -// } -// if !tok.Valid { -// return "", errors.New("token is invalid") -// } -// if claims, ok := tok.Claims.(jwt.MapClaims); ok { -// if uid, ok := claims["uid"]; ok { -// return cast.ToString(uid), nil -// } -// } -// return "", errors.New("token not exist uid") -//} +// ParseUid 解析出uid +func (b *TokenBuilder) ParseUid(r *http.Request) (string, error) { + parser := token.NewTokenParser() + tok, err := parser.ParseToken(r, b.config.AccessSecret, "") + if err != nil { + return "", err + } + if !tok.Valid { + return "", errors.New("token is invalid") + } + if claims, ok := tok.Claims.(jwt.MapClaims); ok { + if uid, ok := claims["uid"]; ok { + return cast.ToString(uid), nil + } + } + return "", errors.New("token not exist uid") +} -//func ParseUid(r *http.Request, accessSecret string) (string, error) { -// parser := token.NewTokenParser() -// tok, err := parser.ParseToken(r, accessSecret, "") -// if err != nil { -// return "", err -// } -// if !tok.Valid { -// return "", errors.New("token is invalid") -// } -// if claims, ok := tok.Claims.(jwt.MapClaims); ok { -// if uid, ok := claims["uid"]; ok { -// return cast.ToString(uid), nil -// } -// } -// return "", errors.New("token not exist uid") -//} +func ParseUid(r *http.Request, accessSecret string) (string, error) { + parser := token.NewTokenParser() + tok, err := parser.ParseToken(r, accessSecret, "") + if err != nil { + return "", err + } + if !tok.Valid { + return "", errors.New("token is invalid") + } + if claims, ok := tok.Claims.(jwt.MapClaims); ok { + if uid, ok := claims["uid"]; ok { + return cast.ToString(uid), nil + } + } + return "", errors.New("token not exist uid") +} diff --git a/internal/pkg/errs/reason.go b/internal/pkg/errs/reason.go index c5a7b19..d7d5495 100644 --- a/internal/pkg/errs/reason.go +++ b/internal/pkg/errs/reason.go @@ -25,6 +25,9 @@ const ( ErrInvoiceHasPaid Reason = 10004 // 订单已支付 ErrInvalidAppId Reason = 10005 // 应用id无效 ErrUserNotHasInviter Reason = 10006 // 用户没有邀请人 + ErrTaskNotFound Reason = 10007 // 任务不存在 + ErrTaskAlreadyReward Reason = 10008 // 任务已领取 + ErrTaskNotFinished Reason = 10009 // 任务未完成 // ========= admin 业务相关错误码: 30000~39999 ========= ErrUnknownAdminError Reason = 30000 // 未知的admin错误 diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go index a9e2ffd..789bb8c 100644 --- a/internal/svc/service_context.go +++ b/internal/svc/service_context.go @@ -6,13 +6,20 @@ import ( ) type ServiceContext struct { - Config config.Config - TaskModel model.NhTaskModel + Config config.Config + TaskModel model.NhTaskModel + TaskAssetModel model.NhTaskAssetModel + TaskAssetRecordModel model.NhTaskAssetRecordModel + TaskProgressModel model.NhTaskProgressModel } func NewServiceContext(c config.Config) *ServiceContext { + dbConn := c.MySql.Conn() return &ServiceContext{ - Config: c, - TaskModel: model.NewNhTaskModel(c.MySql.Conn()), + Config: c, + TaskModel: model.NewNhTaskModel(dbConn), + TaskAssetModel: model.NewNhTaskAssetModel(dbConn), + TaskAssetRecordModel: model.NewNhTaskAssetRecordModel(dbConn), + TaskProgressModel: model.NewNhTaskProgressModel(dbConn), } } diff --git a/internal/types/types.go b/internal/types/types.go index 7a24bf1..b7238d0 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -11,23 +11,30 @@ type GetTaskListResp struct { Tasks []Task `json:"tasks"` } -type GetTaskRewardReq struct { - ID uint `path:"id"` -} - type GetTaskRewardResp struct { Points int `json:"points"` } type Task struct { Id uint `json:"id"` + CommunityId uint `json:"community_id"` Title string `json:"title"` SubTitle string `json:"sub_title"` Description string `json:"description"` Points int `json:"points"` ButtonText string `json:"button_text"` Type int8 `json:"type"` + Url string `json:"url"` + Status int8 `json:"status"` StartAt string `json:"start_at"` EndAt string `json:"end_at"` - Status int8 `json:"status"` + FinishState int8 `json:"finish_state"` // 0:未完成 1:待校验 2:已完成未领取 3:已领取 +} + +type TaskIdPath struct { + ID uint `path:"id"` +} + +type VerifyTaskResultResp struct { + Finish bool `json:"finish"` }