diff --git a/Makefile b/Makefile index 3dca6a9..859b65f 100644 --- a/Makefile +++ b/Makefile @@ -21,13 +21,13 @@ init: # 格式化api文件 api-format: goctl api format --dir doc/api/ - goctl api plugin -plugin goctl-swagger="swagger -filename nova-task.json" -api doc/api/nova-task.api -dir doc/swagger + goctl api plugin -plugin goctl-swagger="swagger -filename nova.json" -api doc/api/nova.api -dir doc/swagger .PHONY: api # 根据api文件生成代码 api: make api-format - goctl api go -api doc/api/nova-task.api --dir . --style go_zero + goctl api go -api doc/api/nova.api --dir . --style go_zero .PHONY: build # 编译二进制可执行文件 diff --git a/doc/api/admin.api b/doc/api/admin.api new file mode 100644 index 0000000..683b212 --- /dev/null +++ b/doc/api/admin.api @@ -0,0 +1,23 @@ +syntax = "v1" + +@server ( + prefix: /gapi/admin + middleware: AdminSecretCheck + group: admin +) +service novatask { + @doc "每日钱包签到任务" + @handler AddEmailReward + post /email_reward (EmailReward) + + @doc "执行发放奖励操作" + @handler SendEmailReward + get /email_reward +} + +type EmailReward { + Email string `json:"email"` + RewardType string `json:"reward_type"` + Value float64 `json:"value"` +} + diff --git a/doc/api/carv.api b/doc/api/carv.api new file mode 100644 index 0000000..a068d82 --- /dev/null +++ b/doc/api/carv.api @@ -0,0 +1,50 @@ +syntax = "v1" + +@server ( + prefix: /gapi/carv + middleware: ApiKeyCheck + group: carv +) +service novatask { + @doc "注册绑定钱包任务" + @handler BindWallet + get /bind_wallet (EmailKey) returns (CarvResult) + + @doc "每日钱包签到任务" + @handler WalletCheckIn + get /check_in_wallet (EmailKey) returns (CarvResult) + + @doc "下载并绑定Castile游戏角色" + @handler DownloadAndBindRole + get /bind_role (EmailKey) returns (CarvResult) + + @doc "游戏主线解锁第x章节" + @handler UnlockChapter + get /unlock_chapter (UnlockChapterReq) returns (CarvResult) +} + +type Result { + IsValid bool `json:"isValid"` +} + +type Error { + Code int `json:"code"` + Message string `json:"message"` +} + +type CarvResult { + Result *Result `json:"result"` + Error *Error `json:"error"` +} + +type EmailKey { + Email string `form:"email"` + ApiKey string `Header:"x-api-key"` +} + +type UnlockChapterReq { + Email string `form:"email"` + Chapter int `form:"chapter"` + ApiKey string `Header:"x-api-key"` +} + diff --git a/doc/api/nova.api b/doc/api/nova.api new file mode 100644 index 0000000..ee80d7d --- /dev/null +++ b/doc/api/nova.api @@ -0,0 +1,12 @@ +syntax = "v1" + +import "task.api" +import "carv.api" +import "admin.api" + +info ( + desc: "nova api" + author: "jager" + email: "lhj168os@gmail.com" +) + diff --git a/doc/api/nova-task.api b/doc/api/task.api similarity index 98% rename from doc/api/nova-task.api rename to doc/api/task.api index 9f30a87..0ef37db 100644 --- a/doc/api/nova-task.api +++ b/doc/api/task.api @@ -132,7 +132,7 @@ type UserNftList { // StakeNftList 质押请求参数NFT列表 type StakeNftList { - RoleId uint64 `json:"role_id"` // 角色id + RoleId uint64 `json:"role_id,optional"` // 角色id TokenIds []string `json:"token_ids"` // nft列表 } diff --git a/doc/sql/novatask.sql b/doc/sql/novatask.sql index 031fac5..0418926 100644 --- a/doc/sql/novatask.sql +++ b/doc/sql/novatask.sql @@ -39,31 +39,28 @@ CREATE TABLE `nh_nft_holder_change_log` CREATE TABLE `nh_task_nft_stake` ( - `id` int unsigned NOT NULL AUTO_INCREMENT, - `uid` int unsigned NOT NULL COMMENT '用户钱包', + `id` int unsigned NOT NULL AUTO_INCREMENT, + `uid` int unsigned NOT NULL COMMENT '用户钱包', `role_id` bigint unsigned NOT NULL COMMENT '角色id', - `token_id` varchar(32) NOT NULL COMMENT 'token id', - `state` tinyint NOT NULL DEFAULT 0 COMMENT '状态:1质押中, 0已取消质押', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `token_id` varchar(32) NOT NULL COMMENT 'token id', + `state` tinyint NOT NULL DEFAULT 0 COMMENT '状态:1质押中, 0已取消质押', + `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`), - UNIQUE KEY (`uid`, `token_id`) + UNIQUE KEY (`token_id`) ) COMMENT ='nft质押表'; CREATE TABLE `nh_task_nft_stake_log` ( - `id` int unsigned NOT NULL AUTO_INCREMENT, - `uid` int unsigned NOT NULL COMMENT '用户钱包', + `id` int unsigned NOT NULL AUTO_INCREMENT, + `uid` int unsigned NOT NULL COMMENT '用户钱包', `role_id` bigint unsigned NOT NULL COMMENT '角色id', - `address` varchar(80) NOT NULL COMMENT '钱包地址', - `token_id` varchar(32) NOT NULL COMMENT 'token id', - `award_seq` int NOT NULL COMMENT '派奖序列号', - `balance` int(11) NOT NULL COMMENT '余额', - `stake` tinyint NOT NULL DEFAULT 0 COMMENT '状态:1质押中, 0未质押', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `token_id` varchar(32) NOT NULL COMMENT 'token id', + `operate` tinyint NOT NULL DEFAULT 0 COMMENT '状态: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`) -) COMMENT ='派奖时nft质押日志表'; +) COMMENT ='nft质押日志表'; CREATE TABLE `nh_task_nft_stake_reward` ( @@ -79,4 +76,18 @@ CREATE TABLE `nh_task_nft_stake_reward` `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY (`uid`, `award_seq`) -) COMMENT ='nft质押派奖表'; \ No newline at end of file +) COMMENT ='nft质押派奖表'; + +CREATE TABLE `nh_email_reward` +( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `email` varchar(80) NOT NULL COMMENT '邮箱', + `uid` int unsigned NOT NULL DEFAULT 0 COMMENT '用户id', + `reward_type` varchar(32) NOT NULL COMMENT '奖励类型:[points,elite_points,castile,keys]', + `value` decimal(18, 6) NOT NULL DEFAULT 0 COMMENT '需要发送的积分', + `remark` varchar(256) NOT NULL DEFAULT '' COMMENT '备注', + `sent_at` int NOT NULL DEFAULT 0 COMMENT '发放时间戳', + `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`) +) COMMENT ='需要发放积分的'; \ No newline at end of file diff --git a/internal/consts/consts.go b/internal/consts/consts.go index cc652f7..78a26d8 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -5,4 +5,6 @@ const ( DailyPayConf = "daily_pay_conf" NftStakeTaskDate = "nft_stake_task_date" NftStakeTaskConf = "nft_stake_task_conf" + CarvApiKey = "carv_api_key" + AdminSecret = "admin_secret" ) diff --git a/internal/handler/admin/add_email_reward_handler.go b/internal/handler/admin/add_email_reward_handler.go new file mode 100644 index 0000000..c263eb8 --- /dev/null +++ b/internal/handler/admin/add_email_reward_handler.go @@ -0,0 +1,29 @@ +package admin + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "nova_task/internal/logic/admin" + "nova_task/internal/svc" + "nova_task/internal/types" +) + +// 每日钱包签到任务 +func AddEmailRewardHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.EmailReward + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := admin.NewAddEmailRewardLogic(r.Context(), svcCtx) + err := l.AddEmailReward(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.Ok(w) + } + } +} diff --git a/internal/handler/admin/add_emial_reward_handler.go b/internal/handler/admin/add_emial_reward_handler.go new file mode 100644 index 0000000..440d650 --- /dev/null +++ b/internal/handler/admin/add_emial_reward_handler.go @@ -0,0 +1,29 @@ +package admin + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "nova_task/internal/logic/admin" + "nova_task/internal/svc" + "nova_task/internal/types" +) + +// 每日钱包签到任务 +func AddEmialRewardHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.EmailReward + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := admin.NewAddEmialRewardLogic(r.Context(), svcCtx) + err := l.AddEmialReward(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.Ok(w) + } + } +} diff --git a/internal/handler/admin/send_email_reward_handler.go b/internal/handler/admin/send_email_reward_handler.go new file mode 100644 index 0000000..9d7bb9f --- /dev/null +++ b/internal/handler/admin/send_email_reward_handler.go @@ -0,0 +1,22 @@ +package admin + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "nova_task/internal/logic/admin" + "nova_task/internal/svc" +) + +// 执行发放奖励操作 +func SendEmailRewardHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := admin.NewSendEmailRewardLogic(r.Context(), svcCtx) + err := l.SendEmailReward() + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.Ok(w) + } + } +} diff --git a/internal/handler/carv/bind_wallet_handler.go b/internal/handler/carv/bind_wallet_handler.go new file mode 100644 index 0000000..bc8c96d --- /dev/null +++ b/internal/handler/carv/bind_wallet_handler.go @@ -0,0 +1,29 @@ +package carv + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "nova_task/internal/logic/carv" + "nova_task/internal/svc" + "nova_task/internal/types" +) + +// 注册绑定钱包任务 +func BindWalletHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.EmailKey + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := carv.NewBindWalletLogic(r.Context(), svcCtx) + resp, err := l.BindWallet(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/internal/handler/carv/download_and_bind_role_handler.go b/internal/handler/carv/download_and_bind_role_handler.go new file mode 100644 index 0000000..216a0dd --- /dev/null +++ b/internal/handler/carv/download_and_bind_role_handler.go @@ -0,0 +1,29 @@ +package carv + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "nova_task/internal/logic/carv" + "nova_task/internal/svc" + "nova_task/internal/types" +) + +// 下载并绑定Castile游戏角色 +func DownloadAndBindRoleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.EmailKey + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := carv.NewDownloadAndBindRoleLogic(r.Context(), svcCtx) + resp, err := l.DownloadAndBindRole(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/internal/handler/carv/unlock_chapter_handler.go b/internal/handler/carv/unlock_chapter_handler.go new file mode 100644 index 0000000..b246b44 --- /dev/null +++ b/internal/handler/carv/unlock_chapter_handler.go @@ -0,0 +1,29 @@ +package carv + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "nova_task/internal/logic/carv" + "nova_task/internal/svc" + "nova_task/internal/types" +) + +// 游戏主线解锁第x章节 +func UnlockChapterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UnlockChapterReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := carv.NewUnlockChapterLogic(r.Context(), svcCtx) + resp, err := l.UnlockChapter(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/internal/handler/carv/wallet_check_in_handler.go b/internal/handler/carv/wallet_check_in_handler.go new file mode 100644 index 0000000..e8aa295 --- /dev/null +++ b/internal/handler/carv/wallet_check_in_handler.go @@ -0,0 +1,29 @@ +package carv + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "nova_task/internal/logic/carv" + "nova_task/internal/svc" + "nova_task/internal/types" +) + +// 每日钱包签到任务 +func WalletCheckInHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.EmailKey + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := carv.NewWalletCheckInLogic(r.Context(), svcCtx) + resp, err := l.WalletCheckIn(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/internal/handler/routes.go b/internal/handler/routes.go index 8a4c404..8e0f36b 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -6,6 +6,8 @@ package handler import ( "net/http" + admin "nova_task/internal/handler/admin" + carv "nova_task/internal/handler/carv" task "nova_task/internal/handler/task" "nova_task/internal/svc" @@ -13,6 +15,60 @@ import ( ) func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminSecretCheck}, + []rest.Route{ + { + // 每日钱包签到任务 + Method: http.MethodPost, + Path: "/email_reward", + Handler: admin.AddEmailRewardHandler(serverCtx), + }, + { + // 执行发放奖励操作 + Method: http.MethodGet, + Path: "/email_reward", + Handler: admin.SendEmailRewardHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/gapi/admin"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.ApiKeyCheck}, + []rest.Route{ + { + // 下载并绑定Castile游戏角色 + Method: http.MethodGet, + Path: "/bind_role", + Handler: carv.DownloadAndBindRoleHandler(serverCtx), + }, + { + // 注册绑定钱包任务 + Method: http.MethodGet, + Path: "/bind_wallet", + Handler: carv.BindWalletHandler(serverCtx), + }, + { + // 每日钱包签到任务 + Method: http.MethodGet, + Path: "/check_in_wallet", + Handler: carv.WalletCheckInHandler(serverCtx), + }, + { + // 游戏主线解锁第x章节 + Method: http.MethodGet, + Path: "/unlock_chapter", + Handler: carv.UnlockChapterHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/gapi/carv"), + ) + server.AddRoutes( []rest.Route{ { diff --git a/internal/job/holder/cron.go b/internal/job/holder/cron.go index e316786..3853ffc 100644 --- a/internal/job/holder/cron.go +++ b/internal/job/holder/cron.go @@ -108,6 +108,15 @@ func (c *Cron) Run() { if err != nil { logx.Errorw("delete nft holder error", logx.Field("error", err), logx.Field("address", nft.Address), logx.Field("token", nft.TokenId)) } + uid, err := c.svcCtx.WalletModel.FindUidByAddress(c.ctx, nft.Address) + if err != nil { + logx.Errorw("find uid by address error", logx.Field("error", err), logx.Field("address", nft.Address)) + continue + } + err = c.svcCtx.StakeNftModel.UnStakeNft(c.ctx, uid, nft.TokenId, true) + if err != nil { + logx.Errorw("un stake nft error", logx.Field("error", err), logx.Field("address", nft.Address), logx.Field("token", nft.TokenId)) + } } err = c.svcCtx.NftHolderModel.DeleteOtherUpdateSeq(c.ctx, updateSeq) if err != nil { diff --git a/internal/logic/admin/add_email_reward_logic.go b/internal/logic/admin/add_email_reward_logic.go new file mode 100644 index 0000000..8b2e528 --- /dev/null +++ b/internal/logic/admin/add_email_reward_logic.go @@ -0,0 +1,42 @@ +package admin + +import ( + "context" + "github.com/shopspring/decimal" + "nova_task/internal/model" + "nova_task/internal/pkg/errs" + + "nova_task/internal/svc" + "nova_task/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AddEmailRewardLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 每日钱包签到任务 +func NewAddEmailRewardLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddEmailRewardLogic { + return &AddEmailRewardLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AddEmailRewardLogic) AddEmailReward(req *types.EmailReward) error { + _, err := l.svcCtx.EmailRewardModel.Insert(l.ctx, &model.NhEmailReward{ + Email: req.Email, + RewardType: req.RewardType, + Value: decimal.NewFromFloat(req.Value), + }) + if err != nil { + l.Errorw("add email reward failed", logx.Field("err", err), logx.Field("email", req.Email)) + return errs.New(errs.ErrDatabaseOperate, err) + } + + return errs.Success() +} diff --git a/internal/logic/admin/send_email_reward_logic.go b/internal/logic/admin/send_email_reward_logic.go new file mode 100644 index 0000000..3aba71f --- /dev/null +++ b/internal/logic/admin/send_email_reward_logic.go @@ -0,0 +1,84 @@ +package admin + +import ( + "context" + "github.com/zeromicro/go-zero/core/threading" + "nova_task/internal/model" + "nova_task/internal/pkg/errs" + "strings" + "time" + + "github.com/zeromicro/go-zero/core/logx" + "nova_task/internal/svc" +) + +type SendEmailRewardLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 执行发放奖励操作 +func NewSendEmailRewardLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendEmailRewardLogic { + return &SendEmailRewardLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *SendEmailRewardLogic) SendEmailReward() error { + rewards, err := l.svcCtx.EmailRewardModel.AllRequireSend(l.ctx) + if err != nil { + l.Errorw("get email reward failed", logx.Field("err", err)) + return errs.New(errs.ErrDatabaseOperate, err) + } + + if len(rewards) <= 0 { + return errs.Success() + } + + threading.GoSafe(func() { + ctx := context.Background() + for _, rw := range rewards { + u, err := l.svcCtx.UserModel.FindOneByEmail(ctx, rw.Email) + if err != nil { + l.Errorw("find user failed", logx.Field("err", err), logx.Field("email", rw.Email)) + continue + } + + err = l.svcCtx.EmailRewardModel.SetSent(ctx, rw.Id, u.Id) + if err != nil { + l.Errorw("set sent failed", logx.Field("err", err), logx.Field("id", rw.Id), logx.Field("uid", u.Id)) + continue + } + + // points,elite_points,castile,keys + switch strings.ToLower(rw.RewardType) { + case "points": + err = l.svcCtx.TaskAssetModel.AddPoint(ctx, u.Id, rw.Value) + case "elite_points": + err = l.svcCtx.TaskAssetModel.AddElitePoints(ctx, u.Id, rw.Value) + case "castile": + err = l.svcCtx.TaskAssetModel.AddCastile(ctx, u.Id, rw.Value) + case "keys": + err = l.svcCtx.TaskAssetModel.AddKeys(ctx, u.Id, int(rw.Value.IntPart())) + } + if err != nil { + l.Errorw("add asset failed", logx.Field("err", err), logx.Field("uid", u.Id), logx.Field("rewardType", rw.RewardType), logx.Field("value", rw.Value)) + } + _, err = l.svcCtx.TaskAssetRecordModel.Insert(ctx, &model.NhTaskAssetRecord{ + Uid: int(u.Id), + AssetField: rw.RewardType, + Count: rw.Value.InexactFloat64(), + Remark: rw.Remark, + CreateTime: int(time.Now().Unix()), + }) + if err != nil { + l.Errorw("insert task asset record failed", logx.Field("err", err), logx.Field("uid", u.Id), logx.Field("rewardType", rw.RewardType), logx.Field("value", rw.Value)) + } + } + }) + + return nil +} diff --git a/internal/logic/carv/bind_wallet_logic.go b/internal/logic/carv/bind_wallet_logic.go new file mode 100644 index 0000000..3ef4f50 --- /dev/null +++ b/internal/logic/carv/bind_wallet_logic.go @@ -0,0 +1,31 @@ +package carv + +import ( + "context" + + "nova_task/internal/svc" + "nova_task/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type BindWalletLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 注册绑定钱包任务 +func NewBindWalletLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindWalletLogic { + return &BindWalletLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *BindWalletLogic) BindWallet(req *types.EmailKey) (resp *types.CarvResult, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/internal/logic/carv/download_and_bind_role_logic.go b/internal/logic/carv/download_and_bind_role_logic.go new file mode 100644 index 0000000..ee6e80b --- /dev/null +++ b/internal/logic/carv/download_and_bind_role_logic.go @@ -0,0 +1,31 @@ +package carv + +import ( + "context" + + "nova_task/internal/svc" + "nova_task/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DownloadAndBindRoleLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 下载并绑定Castile游戏角色 +func NewDownloadAndBindRoleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DownloadAndBindRoleLogic { + return &DownloadAndBindRoleLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DownloadAndBindRoleLogic) DownloadAndBindRole(req *types.EmailKey) (resp *types.CarvResult, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/internal/logic/carv/unlock_chapter_logic.go b/internal/logic/carv/unlock_chapter_logic.go new file mode 100644 index 0000000..b8a9c6b --- /dev/null +++ b/internal/logic/carv/unlock_chapter_logic.go @@ -0,0 +1,31 @@ +package carv + +import ( + "context" + + "nova_task/internal/svc" + "nova_task/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UnlockChapterLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 游戏主线解锁第x章节 +func NewUnlockChapterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnlockChapterLogic { + return &UnlockChapterLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UnlockChapterLogic) UnlockChapter(req *types.UnlockChapterReq) (resp *types.CarvResult, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/internal/logic/carv/wallet_check_in_logic.go b/internal/logic/carv/wallet_check_in_logic.go new file mode 100644 index 0000000..176b5fb --- /dev/null +++ b/internal/logic/carv/wallet_check_in_logic.go @@ -0,0 +1,31 @@ +package carv + +import ( + "context" + + "nova_task/internal/svc" + "nova_task/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type WalletCheckInLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 每日钱包签到任务 +func NewWalletCheckInLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WalletCheckInLogic { + return &WalletCheckInLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *WalletCheckInLogic) WalletCheckIn(req *types.EmailKey) (resp *types.CarvResult, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/internal/logic/task/stake_nft_logic.go b/internal/logic/task/stake_nft_logic.go index 8989099..ca0cbe1 100644 --- a/internal/logic/task/stake_nft_logic.go +++ b/internal/logic/task/stake_nft_logic.go @@ -29,13 +29,24 @@ func NewStakeNftLogic(ctx context.Context, svcCtx *svc.ServiceContext) *StakeNft func (l *StakeNftLogic) StakeNft(req *types.StakeNftList) error { uid := utils.GetUidUint(l.ctx) - err := l.svcCtx.StakeNftModel.StakeNft(l.ctx, uid, req.RoleId, req.TokenIds) - if err != nil { - l.Errorw("stake nft failed", logx.Field("err", err), logx.Field("uid", uid), logx.Field("tokenIds", req.TokenIds)) - return errs.New(errs.ErrDatabaseOperate, err) - } for _, tokenId := range req.TokenIds { + tkHolder, err := l.svcCtx.NftHolderModel.FindOneByTokenId(l.ctx, tokenId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + l.Errorw("find nft holder not exist", logx.Field("err", err), logx.Field("uid", uid), logx.Field("tokenId", tokenId)) + continue + } + l.Errorw("find nft holder failed", logx.Field("err", err), logx.Field("uid", uid), logx.Field("tokenId", tokenId)) + return errs.New(errs.ErrDatabaseOperate, err) + } + l.svcCtx.WalletModel.FindUidByAddress(l.ctx, tkHolder.Address) + err = l.svcCtx.StakeNftModel.StakeNft(l.ctx, uid, req.RoleId, tokenId) + if err != nil { + l.Errorw("stake nft failed", logx.Field("err", err), logx.Field("uid", uid), logx.Field("tokenIds", req.TokenIds)) + return errs.New(errs.ErrDatabaseOperate, err) + } + var sns []int8 var propertyId string if utils.IsBigTarot(tokenId) { @@ -50,7 +61,7 @@ func (l *StakeNftLogic) StakeNft(req *types.StakeNftList) error { Uid: uid, RoleId: int64(req.RoleId), TokenId: cast.ToUint(tokenId), - PropertyId: "", + PropertyId: propertyId, Sn: sn, }) if err != nil { diff --git a/internal/logic/task/un_stake_nft_logic.go b/internal/logic/task/un_stake_nft_logic.go index 34ef892..e3b4b19 100644 --- a/internal/logic/task/un_stake_nft_logic.go +++ b/internal/logic/task/un_stake_nft_logic.go @@ -27,7 +27,7 @@ func NewUnStakeNftLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnStak func (l *UnStakeNftLogic) UnStakeNft(req *types.UnStakeNftReq) error { uid := utils.GetUidUint(l.ctx) - err := l.svcCtx.StakeNftModel.UnStakeNft(l.ctx, uid, req.TokenId) + err := l.svcCtx.StakeNftModel.UnStakeNft(l.ctx, uid, req.TokenId, false) if err != nil { l.Errorw("unstake nft failed", logx.Field("err", err), logx.Field("uid", uid), logx.Field("tokenIds", req.TokenId)) return errs.New(errs.ErrDatabaseOperate, err) diff --git a/internal/middleware/adminsecretcheck_middleware.go b/internal/middleware/adminsecretcheck_middleware.go new file mode 100644 index 0000000..6440049 --- /dev/null +++ b/internal/middleware/adminsecretcheck_middleware.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "errors" + "net/http" + "nova_task/internal/model" +) + +type AdminSecretCheckMiddleware struct { + conf model.NhSystemConfigModel +} + +func NewAdminSecretCheckMiddleware(conf model.NhSystemConfigModel) *AdminSecretCheckMiddleware { + return &AdminSecretCheckMiddleware{conf: conf} +} + +func (m *AdminSecretCheckMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if key, err := m.conf.GetAdminSecret(r.Context()); err != nil { + if !errors.Is(err, model.ErrNotFound) { + http.Error(w, "system error", http.StatusInternalServerError) + return + } + } else { + apiKey := r.Header.Get("x-admin-secret") + if apiKey != key { + http.Error(w, "Invalid API key", http.StatusUnauthorized) + return + } + } + next(w, r) + } +} diff --git a/internal/middleware/apikeycheck_middleware.go b/internal/middleware/apikeycheck_middleware.go new file mode 100644 index 0000000..1563759 --- /dev/null +++ b/internal/middleware/apikeycheck_middleware.go @@ -0,0 +1,34 @@ +package middleware + +import ( + "errors" + "net/http" + "nova_task/internal/model" +) + +type ApiKeyCheckMiddleware struct { + conf model.NhSystemConfigModel +} + +func NewApiKeyCheckMiddleware(conf model.NhSystemConfigModel) *ApiKeyCheckMiddleware { + return &ApiKeyCheckMiddleware{conf: conf} +} + +func (m *ApiKeyCheckMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if key, err := m.conf.GetCarvApiKey(r.Context()); err != nil { + if !errors.Is(err, model.ErrNotFound) { + http.Error(w, "system error", http.StatusInternalServerError) + return + } + } else { + apiKey := r.Header.Get("x-api-key") + if apiKey != key { + http.Error(w, "Invalid API key", http.StatusUnauthorized) + return + } + } + + next(w, r) + } +} diff --git a/internal/model/nh_email_reward_model.go b/internal/model/nh_email_reward_model.go new file mode 100755 index 0000000..91c9d06 --- /dev/null +++ b/internal/model/nh_email_reward_model.go @@ -0,0 +1,63 @@ +package model + +import ( + "context" + "errors" + "fmt" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "time" +) + +var _ NhEmailRewardModel = (*customNhEmailRewardModel)(nil) + +type ( + // NhEmailRewardModel is an interface to be customized, add more methods here, + // and implement the added methods in customNhEmailRewardModel. + NhEmailRewardModel interface { + nhEmailRewardModel + withSession(session sqlx.Session) NhEmailRewardModel + AllRequireSend(ctx context.Context) ([]NhEmailReward, error) + SetSent(ctx context.Context, id, uid uint) error + } + + customNhEmailRewardModel struct { + *defaultNhEmailRewardModel + } +) + +func (m *customNhEmailRewardModel) SetSent(ctx context.Context, id, uid uint) error { + upate := fmt.Sprintf("update %s set `uid` = ?, `sent_at` = ? where `id` = ? and `sent_at` = 0", m.table) + result, err := m.conn.ExecCtx(ctx, upate, uid, time.Now().Unix(), id) + if err != nil { + return err + } + rows, err := result.RowsAffected() + if err != nil { + return err + } + if rows == 0 { + return ErrNoRowUpdate + } + return nil +} + +func (m *customNhEmailRewardModel) AllRequireSend(ctx context.Context) ([]NhEmailReward, error) { + query := fmt.Sprintf("select %s from %s where `sent_at` = 0", nhEmailRewardRows, m.table) + var result []NhEmailReward + err := m.conn.QueryRowsCtx(ctx, &result, query) + if err != nil && !errors.Is(err, sqlx.ErrNotFound) { + return nil, err + } + return result, nil +} + +// NewNhEmailRewardModel returns a model for the database table. +func NewNhEmailRewardModel(conn sqlx.SqlConn) NhEmailRewardModel { + return &customNhEmailRewardModel{ + defaultNhEmailRewardModel: newNhEmailRewardModel(conn), + } +} + +func (m *customNhEmailRewardModel) withSession(session sqlx.Session) NhEmailRewardModel { + return NewNhEmailRewardModel(sqlx.NewSqlConnFromSession(session)) +} diff --git a/internal/model/nh_email_reward_model_gen.go b/internal/model/nh_email_reward_model_gen.go new file mode 100755 index 0000000..856f64b --- /dev/null +++ b/internal/model/nh_email_reward_model_gen.go @@ -0,0 +1,95 @@ +// 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" + + "github.com/shopspring/decimal" +) + +var ( + nhEmailRewardFieldNames = builder.RawFieldNames(&NhEmailReward{}) + nhEmailRewardRows = strings.Join(nhEmailRewardFieldNames, ",") + nhEmailRewardRowsExpectAutoSet = strings.Join(stringx.Remove(nhEmailRewardFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + nhEmailRewardRowsWithPlaceHolder = strings.Join(stringx.Remove(nhEmailRewardFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + nhEmailRewardModel interface { + Insert(ctx context.Context, data *NhEmailReward) (sql.Result, error) + FindOne(ctx context.Context, id uint) (*NhEmailReward, error) + Update(ctx context.Context, data *NhEmailReward) error + Delete(ctx context.Context, id uint) error + } + + defaultNhEmailRewardModel struct { + conn sqlx.SqlConn + table string + } + + NhEmailReward struct { + Id uint `db:"id"` + Email string `db:"email"` // 邮箱 + Uid uint `db:"uid"` // 用户id + RewardType string `db:"reward_type"` // 奖励类型:[points,elite_points,castile,keys] + Value decimal.Decimal `db:"value"` // 需要发送的积分 + Remark string `db:"remark"` // 备注 + SentAt int `db:"sent_at"` // 发放时间戳 + CreatedAt time.Time `db:"created_at"` // 创建时间 + UpdatedAt time.Time `db:"updated_at"` // 修改时间 + } +) + +func newNhEmailRewardModel(conn sqlx.SqlConn) *defaultNhEmailRewardModel { + return &defaultNhEmailRewardModel{ + conn: conn, + table: "`nh_email_reward`", + } +} + +func (m *defaultNhEmailRewardModel) Delete(ctx context.Context, id uint) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultNhEmailRewardModel) FindOne(ctx context.Context, id uint) (*NhEmailReward, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", nhEmailRewardRows, m.table) + var resp NhEmailReward + 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 *defaultNhEmailRewardModel) Insert(ctx context.Context, data *NhEmailReward) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?)", m.table, nhEmailRewardRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.Email, data.Uid, data.RewardType, data.Value, data.Remark, data.SentAt) + return ret, err +} + +func (m *defaultNhEmailRewardModel) Update(ctx context.Context, data *NhEmailReward) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, nhEmailRewardRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, data.Email, data.Uid, data.RewardType, data.Value, data.Remark, data.SentAt, data.Id) + return err +} + +func (m *defaultNhEmailRewardModel) tableName() string { + return m.table +} diff --git a/internal/model/nh_nft_stake_model.go b/internal/model/nh_nft_stake_model.go new file mode 100755 index 0000000..014a85e --- /dev/null +++ b/internal/model/nh_nft_stake_model.go @@ -0,0 +1,44 @@ +package model + +import ( + "context" + "errors" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ NhNftStakeModel = (*customNhNftStakeModel)(nil) + +type ( + // NhNftStakeModel is an interface to be customized, add more methods here, + // and implement the added methods in customNhNftStakeModel. + NhNftStakeModel interface { + nhNftStakeModel + withSession(session sqlx.Session) NhNftStakeModel + AllStakeNft(ctx context.Context) ([]NhNftStake, error) + } + + customNhNftStakeModel struct { + *defaultNhNftStakeModel + } +) + +func (m *customNhNftStakeModel) AllStakeNft(ctx context.Context) ([]NhNftStake, error) { + query := "SELECT * FROM `nh_nft_stake` WHERE `status` = 'staked'" + var result []NhNftStake + err := m.conn.QueryRowsCtx(ctx, &result, query) + if err != nil && !errors.Is(err, sqlx.ErrNotFound) { + return nil, err + } + return result, nil +} + +// NewNhNftStakeModel returns a model for the database table. +func NewNhNftStakeModel(conn sqlx.SqlConn) NhNftStakeModel { + return &customNhNftStakeModel{ + defaultNhNftStakeModel: newNhNftStakeModel(conn), + } +} + +func (m *customNhNftStakeModel) withSession(session sqlx.Session) NhNftStakeModel { + return NewNhNftStakeModel(sqlx.NewSqlConnFromSession(session)) +} diff --git a/internal/model/nh_nft_stake_model_gen.go b/internal/model/nh_nft_stake_model_gen.go new file mode 100755 index 0000000..5b6de0e --- /dev/null +++ b/internal/model/nh_nft_stake_model_gen.go @@ -0,0 +1,139 @@ +// 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 ( + nhNftStakeFieldNames = builder.RawFieldNames(&NhNftStake{}) + nhNftStakeRows = strings.Join(nhNftStakeFieldNames, ",") + nhNftStakeRowsExpectAutoSet = strings.Join(stringx.Remove(nhNftStakeFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + nhNftStakeRowsWithPlaceHolder = strings.Join(stringx.Remove(nhNftStakeFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + nhNftStakeModel interface { + Insert(ctx context.Context, data *NhNftStake) (sql.Result, error) + FindOne(ctx context.Context, id uint) (*NhNftStake, error) + FindOneByStakeHashTokenId(ctx context.Context, stakeHash string, tokenId uint) (*NhNftStake, error) + FindOneByUnstakeHashTokenId(ctx context.Context, unstakeHash string, tokenId uint) (*NhNftStake, error) + Update(ctx context.Context, data *NhNftStake) error + Delete(ctx context.Context, id uint) error + } + + defaultNhNftStakeModel struct { + conn sqlx.SqlConn + table string + } + + NhNftStake struct { + Id uint `db:"id"` + UserId uint `db:"user_id"` // 用户ID + RoleId uint64 `db:"role_id"` // 角色ID + Email string `db:"email"` // email + UserAddress string `db:"user_address"` // 用户地址 + NftAddress string `db:"nft_address"` // nft合约地址 + TokenId uint `db:"token_id"` // NFT tokenid + TarotType int8 `db:"tarot_type"` // 塔罗类型:1=大塔罗,2=小塔罗 + LockedGoodsId uint `db:"locked_goods_id"` // 提取前锁定游戏内的道具ID + StakeHash string `db:"stake_hash"` // 质押hash + UnstakeHash string `db:"unstake_hash"` // 提取hash + Status string `db:"status"` // 质押状态:pending待质押,staked已质押,unstaking提取中,unstaked已提取 + StakeTime sql.NullTime `db:"stake_time"` // 质押时间 + UnstakeTime sql.NullTime `db:"unstake_time"` // 提取时间 + Remark sql.NullString `db:"remark"` // 备注 + CallbackStatus int8 `db:"callback_status"` // 质押成功通知状态:0未通知,1已通知,2通知异常 + CallbackNum int `db:"callback_num"` // 质押成功发送通知次数 + CallbackAt sql.NullTime `db:"callback_at"` // 质押通知最新时间 + CallbackRemark string `db:"callback_remark"` // 质押通知回调备注 + CallbackStatus2 int8 `db:"callback_status2"` // 提取成功通知状态:0未通知,1已通知,2通知异常 + CallbackNum2 int `db:"callback_num2"` // 提取成功发送通知次数 + CallbackAt2 sql.NullTime `db:"callback_at2"` // 提取发送通知最新时间 + CallbackRemark2 string `db:"callback_remark2"` // 提取通知回调备注 + CreatedAt time.Time `db:"created_at"` // 创建时间 + UpdatedAt time.Time `db:"updated_at"` // 修改时间 + } +) + +func newNhNftStakeModel(conn sqlx.SqlConn) *defaultNhNftStakeModel { + return &defaultNhNftStakeModel{ + conn: conn, + table: "`nh_nft_stake`", + } +} + +func (m *defaultNhNftStakeModel) Delete(ctx context.Context, id uint) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultNhNftStakeModel) FindOne(ctx context.Context, id uint) (*NhNftStake, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", nhNftStakeRows, m.table) + var resp NhNftStake + 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 *defaultNhNftStakeModel) FindOneByStakeHashTokenId(ctx context.Context, stakeHash string, tokenId uint) (*NhNftStake, error) { + var resp NhNftStake + query := fmt.Sprintf("select %s from %s where `stake_hash` = ? and `token_id` = ? limit 1", nhNftStakeRows, m.table) + err := m.conn.QueryRowCtx(ctx, &resp, query, stakeHash, tokenId) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultNhNftStakeModel) FindOneByUnstakeHashTokenId(ctx context.Context, unstakeHash string, tokenId uint) (*NhNftStake, error) { + var resp NhNftStake + query := fmt.Sprintf("select %s from %s where `unstake_hash` = ? and `token_id` = ? limit 1", nhNftStakeRows, m.table) + err := m.conn.QueryRowCtx(ctx, &resp, query, unstakeHash, tokenId) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultNhNftStakeModel) Insert(ctx context.Context, data *NhNftStake) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, nhNftStakeRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.UserId, data.RoleId, data.Email, data.UserAddress, data.NftAddress, data.TokenId, data.TarotType, data.LockedGoodsId, data.StakeHash, data.UnstakeHash, data.Status, data.StakeTime, data.UnstakeTime, data.Remark, data.CallbackStatus, data.CallbackNum, data.CallbackAt, data.CallbackRemark, data.CallbackStatus2, data.CallbackNum2, data.CallbackAt2, data.CallbackRemark2) + return ret, err +} + +func (m *defaultNhNftStakeModel) Update(ctx context.Context, newData *NhNftStake) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, nhNftStakeRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, newData.UserId, newData.RoleId, newData.Email, newData.UserAddress, newData.NftAddress, newData.TokenId, newData.TarotType, newData.LockedGoodsId, newData.StakeHash, newData.UnstakeHash, newData.Status, newData.StakeTime, newData.UnstakeTime, newData.Remark, newData.CallbackStatus, newData.CallbackNum, newData.CallbackAt, newData.CallbackRemark, newData.CallbackStatus2, newData.CallbackNum2, newData.CallbackAt2, newData.CallbackRemark2, newData.Id) + return err +} + +func (m *defaultNhNftStakeModel) tableName() string { + return m.table +} diff --git a/internal/model/nh_system_config_model.go b/internal/model/nh_system_config_model.go index 8510976..5ee573b 100755 --- a/internal/model/nh_system_config_model.go +++ b/internal/model/nh_system_config_model.go @@ -3,6 +3,7 @@ package model import ( "context" "encoding/json" + "errors" "github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/sqlx" "nova_task/internal/consts" @@ -18,6 +19,8 @@ type ( nhSystemConfigModel GetNftStakeTaskOpenDate(ctx context.Context) (start, end time.Time, err error) GetNftStakeTaskConf(ctx context.Context) (conf NftStakeTaskConf, err error) + GetCarvApiKey(ctx context.Context) (apiKey string, err error) + GetAdminSecret(ctx context.Context) (secret string, err error) } customNhSystemConfigModel struct { @@ -25,6 +28,28 @@ type ( } ) +func (m *customNhSystemConfigModel) GetAdminSecret(ctx context.Context) (secret string, err error) { + cf, err := m.FindOneByName(ctx, consts.CarvApiKey) + if err != nil { + if !errors.Is(err, sqlx.ErrNotFound) { + return "", err + } + return "", nil + } + return cf.Value, nil +} + +func (m *customNhSystemConfigModel) GetCarvApiKey(ctx context.Context) (string, error) { + cf, err := m.FindOneByName(ctx, consts.CarvApiKey) + if err != nil { + if !errors.Is(err, sqlx.ErrNotFound) { + return "", err + } + return "", nil + } + return cf.Value, nil +} + // NewNhSystemConfigModel returns a model for the database table. func NewNhSystemConfigModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) NhSystemConfigModel { return &customNhSystemConfigModel{ diff --git a/internal/model/nh_task_asset_model.go b/internal/model/nh_task_asset_model.go index 1e5d4a6..1fb57c3 100755 --- a/internal/model/nh_task_asset_model.go +++ b/internal/model/nh_task_asset_model.go @@ -16,8 +16,10 @@ type ( NhTaskAssetModel interface { nhTaskAssetModel WithSession(session sqlx.Session) NhTaskAssetModel - AddPoint(ctx context.Context, uid int, points int) error + AddPoint(ctx context.Context, uid uint, points decimal.Decimal) error AddCastile(ctx context.Context, uid uint, castile decimal.Decimal) error + AddElitePoints(ctx context.Context, uid uint, points decimal.Decimal) error + AddKeys(ctx context.Context, uid uint, keys int) error } customNhTaskAssetModel struct { @@ -25,6 +27,18 @@ type ( } ) +func (m *customNhTaskAssetModel) AddKeys(ctx context.Context, uid uint, keys int) error { + insertOrUpdate := fmt.Sprintf("INSERT INTO %s (`uid`, `keys`, `create_time`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `keys` = `keys` + ?", m.table) + _, err := m.conn.ExecCtx(ctx, insertOrUpdate, uid, keys, time.Now().Unix(), keys) + return err +} + +func (m *customNhTaskAssetModel) AddElitePoints(ctx context.Context, uid uint, elitePoints decimal.Decimal) error { + insertOrUpdate := fmt.Sprintf("INSERT INTO %s (`uid`, `elite_points`, `create_time`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `elite_points` = `elite_points` + ?", m.table) + _, err := m.conn.ExecCtx(ctx, insertOrUpdate, uid, elitePoints, time.Now().Unix(), elitePoints) + return err +} + func (m *customNhTaskAssetModel) AddCastile(ctx context.Context, uid uint, castile decimal.Decimal) error { insertOrUpdate := fmt.Sprintf("INSERT INTO %s (`uid`, `castile`, `create_time`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `castile` = `castile` + ?", m.table) _, err := m.conn.ExecCtx(ctx, insertOrUpdate, uid, castile, time.Now().Unix(), castile) @@ -42,7 +56,7 @@ func (m *customNhTaskAssetModel) WithSession(session sqlx.Session) NhTaskAssetMo return NewNhTaskAssetModel(sqlx.NewSqlConnFromSession(session)) } -func (m *customNhTaskAssetModel) AddPoint(ctx context.Context, uid int, points int) error { +func (m *customNhTaskAssetModel) AddPoint(ctx context.Context, uid uint, points decimal.Decimal) 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_nft_stake_log_model_gen.go b/internal/model/nh_task_nft_stake_log_model_gen.go index 6a0a961..364c28f 100755 --- a/internal/model/nh_task_nft_stake_log_model_gen.go +++ b/internal/model/nh_task_nft_stake_log_model_gen.go @@ -40,11 +40,8 @@ type ( Id uint `db:"id"` Uid uint `db:"uid"` // 用户钱包 RoleId uint64 `db:"role_id"` // 角色id - Address string `db:"address"` // 钱包地址 TokenId string `db:"token_id"` // token id - AwardSeq int `db:"award_seq"` // 派奖序列号 - Balance int `db:"balance"` // 余额 - Stake int8 `db:"stake"` // 状态:1质押中, 0未质押 + Operate int8 `db:"operate"` // 状态:1质押, 2取消质押, 3转出 CreatedAt time.Time `db:"created_at"` // 创建时间 UpdatedAt time.Time `db:"updated_at"` // 修改时间 } @@ -78,14 +75,14 @@ func (m *defaultNhTaskNftStakeLogModel) FindOne(ctx context.Context, id uint) (* } func (m *defaultNhTaskNftStakeLogModel) Insert(ctx context.Context, data *NhTaskNftStakeLog) (sql.Result, error) { - query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?)", m.table, nhTaskNftStakeLogRowsExpectAutoSet) - ret, err := m.conn.ExecCtx(ctx, query, data.Uid, data.RoleId, data.Address, data.TokenId, data.AwardSeq, data.Balance, data.Stake) + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?)", m.table, nhTaskNftStakeLogRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.Uid, data.RoleId, data.TokenId, data.Operate) return ret, err } func (m *defaultNhTaskNftStakeLogModel) Update(ctx context.Context, data *NhTaskNftStakeLog) error { query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, nhTaskNftStakeLogRowsWithPlaceHolder) - _, err := m.conn.ExecCtx(ctx, query, data.Uid, data.RoleId, data.Address, data.TokenId, data.AwardSeq, data.Balance, data.Stake, data.Id) + _, err := m.conn.ExecCtx(ctx, query, data.Uid, data.RoleId, data.TokenId, data.Operate, data.Id) return err } diff --git a/internal/model/nh_task_nft_stake_model.go b/internal/model/nh_task_nft_stake_model.go index 3a53e9f..15a63ca 100755 --- a/internal/model/nh_task_nft_stake_model.go +++ b/internal/model/nh_task_nft_stake_model.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/stores/sqlx" ) @@ -15,14 +16,15 @@ type ( NhTaskNftStakeModel interface { nhTaskNftStakeModel withSession(session sqlx.Session) NhTaskNftStakeModel - StakeNft(ctx context.Context, uid uint, roleId uint64, tokens []string) error - UnStakeNft(ctx context.Context, uid uint, token string) error + StakeNft(ctx context.Context, uid uint, roleId uint64, tokenId string) error + UnStakeNft(ctx context.Context, uid uint, token string, isTransferOut bool) error FindByUid(ctx context.Context, uid uint) ([]NhTaskNftStake, error) AllStakeNft(ctx context.Context) ([]NhTaskNftStake, error) } customNhTaskNftStakeModel struct { *defaultNhTaskNftStakeModel + log NhTaskNftStakeLogModel } ) @@ -46,19 +48,46 @@ func (m *customNhTaskNftStakeModel) FindByUid(ctx context.Context, uid uint) ([] return list, nil } -func (m *customNhTaskNftStakeModel) UnStakeNft(ctx context.Context, uid uint, token string) error { +func (m *customNhTaskNftStakeModel) UnStakeNft(ctx context.Context, uid uint, token string, isTransferOut bool) error { update := fmt.Sprintf("UPDATE %s SET `state` = 0 WHERE `uid` = ? AND `token_id` = ?", m.table) - _, err := m.conn.ExecCtx(ctx, update, uid, token) + result, err := m.conn.ExecCtx(ctx, update, uid, token) + if err != nil { + return err + } + row, err := result.RowsAffected() + if err != nil { + return err + } + if row > 0 { + var operate int8 + if isTransferOut { + operate = 3 + } else { + operate = 2 + } + _, err = m.log.Insert(ctx, &NhTaskNftStakeLog{ + Uid: uid, + TokenId: token, + Operate: operate, + }) + } return err } -func (m *customNhTaskNftStakeModel) StakeNft(ctx context.Context, uid uint, roleId uint64, tokens []string) error { +func (m *customNhTaskNftStakeModel) StakeNft(ctx context.Context, uid uint, roleId uint64, tokenId string) error { insertOrUpdate := fmt.Sprintf("INSERT INTO %s (`uid`, `token_id`, `role_id`, `state`) VALUES (?, ?, ?, 1) ON DUPLICATE KEY UPDATE `role_id` = ?, `state` = 1", m.table) - for _, token := range tokens { - _, err := m.conn.ExecCtx(ctx, insertOrUpdate, uid, token, roleId, roleId) - if err != nil { - return err - } + _, err := m.conn.ExecCtx(ctx, insertOrUpdate, uid, tokenId, roleId, roleId) + if err != nil { + return err + } + _, err = m.log.Insert(ctx, &NhTaskNftStakeLog{ + Uid: uid, + RoleId: roleId, + TokenId: tokenId, + Operate: 1, + }) + if err != nil { + logx.Errorw("insert stake log failed", logx.Field("err", err), logx.Field("uid", uid), logx.Field("tokenId", token), logx.Field("role", roleId)) } return nil } @@ -67,6 +96,7 @@ func (m *customNhTaskNftStakeModel) StakeNft(ctx context.Context, uid uint, role func NewNhTaskNftStakeModel(conn sqlx.SqlConn) NhTaskNftStakeModel { return &customNhTaskNftStakeModel{ defaultNhTaskNftStakeModel: newNhTaskNftStakeModel(conn), + log: NewNhTaskNftStakeLogModel(conn), } } diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go index ef03cc9..6e338e6 100644 --- a/internal/svc/service_context.go +++ b/internal/svc/service_context.go @@ -5,7 +5,9 @@ import ( "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/rest" "nova_task/internal/config" + "nova_task/internal/middleware" "nova_task/internal/model" ) @@ -26,10 +28,15 @@ type ServiceContext struct { NftHolderModel model.NhNftHolderModel NftHolderChangeLogModel model.NhNftHolderChangeLogModel StakeNftModel model.NhTaskNftStakeModel + OldStakeNftModel model.NhNftStakeModel StakeNftLogModel model.NhTaskNftStakeLogModel StakeRewardModel model.NhTaskNftStakeRewardModel GamePitModel model.NhGamePitModel StakePropertyModel model.NhNftStakePropertyModel + EmailRewardModel model.NhEmailRewardModel + + ApiKeyCheck rest.Middleware + AdminSecretCheck rest.Middleware Earn *ea.Client DBConn sqlx.SqlConn @@ -38,6 +45,7 @@ type ServiceContext struct { func NewServiceContext(c config.Config) *ServiceContext { dbConn := c.MySql.Conn() + configModel := model.NewNhSystemConfigModel(dbConn, c.Cache) return &ServiceContext{ Config: c, @@ -51,14 +59,18 @@ func NewServiceContext(c config.Config) *ServiceContext { UserModel: model.NewNhUserModel(dbConn), TouristBindModel: model.NewNhTouristBindModel(dbConn), WalletModel: model.NewNhWalletModel(dbConn), - ConfigModel: model.NewNhSystemConfigModel(dbConn, c.Cache), + ConfigModel: configModel, NftHolderModel: model.NewNhNftHolderModel(dbConn), NftHolderChangeLogModel: model.NewNhNftHolderChangeLogModel(dbConn), StakeNftModel: model.NewNhTaskNftStakeModel(dbConn), - StakeNftLogModel: model.NewNhTaskNftStakeLogModel(dbConn), + OldStakeNftModel: model.NewNhNftStakeModel(dbConn), StakeRewardModel: model.NewNhTaskNftStakeRewardModel(dbConn), GamePitModel: model.NewNhGamePitModel(dbConn), StakePropertyModel: model.NewNhNftStakePropertyModel(dbConn), + EmailRewardModel: model.NewNhEmailRewardModel(dbConn), + + ApiKeyCheck: middleware.NewApiKeyCheckMiddleware(configModel).Handle, + AdminSecretCheck: middleware.NewAdminSecretCheckMiddleware(configModel).Handle, Earn: c.Earn.BuildEarnClient(), DBConn: dbConn, diff --git a/internal/types/types.go b/internal/types/types.go index 51cb98c..00215ec 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -3,6 +3,11 @@ package types +type CarvResult struct { + Result *Result `json:"result"` + Error *Error `json:"error"` +} + type Community struct { Id uint `json:"id"` // 社区ID Title string `json:"title"` // 社区标题 @@ -12,6 +17,22 @@ type Community struct { EndAt int64 `json:"end_at"` // 结束时间 } +type EmailKey struct { + Email string `form:"email"` + ApiKey string `Header:"x-api-key"` +} + +type EmailReward struct { + Email string `json:"email"` + RewardType string `json:"reward_type"` + Value float64 `json:"value"` +} + +type Error struct { + Code int `json:"code"` + Message string `json:"message"` +} + type GetCommunityListResp struct { CommunityList []Community `json:"community_list"` // 社区列表 } @@ -32,9 +53,13 @@ type GetTaskRewardResp struct { Points int `json:"points"` // 积分 } +type Result struct { + IsValid bool `json:"isValid"` +} + type StakeNftList struct { - RoleId uint64 `json:"role_id"` // 角色id - TokenIds []string `json:"token_ids"` // nft列表 + RoleId uint64 `json:"role_id,optional"` // 角色id + TokenIds []string `json:"token_ids"` // nft列表 } type StakeTaskDetail struct { @@ -73,6 +98,12 @@ type UnStakeNftReq struct { TokenId string `json:"token_id"` // nftID } +type UnlockChapterReq struct { + Email string `form:"email"` + Chapter int `form:"chapter"` + ApiKey string `Header:"x-api-key"` +} + type UserNft struct { TokenId string `json:"token_id"` // nftID Image string `json:"image"` // nft图片