feat(activity): 实现抽奖随机承诺与验证功能
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 41s
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 41s
新增随机种子生成与验证逻辑,包括: 1. 添加随机承诺生成接口 2. 实现抽奖执行与验证流程 3. 新增批量用户创建与删除功能 4. 添加抽奖收据记录表 5. 完善配置管理与错误码 新增测试用例验证随机算法正确性
This commit is contained in:
parent
00452cba59
commit
81e2fb5a75
BIN
bin/server
BIN
bin/server
Binary file not shown.
BIN
bindbox-game
BIN
bindbox-game
Binary file not shown.
BIN
bindbox-server
Executable file
BIN
bindbox-server
Executable file
Binary file not shown.
@ -36,6 +36,6 @@ eg :
|
|||||||
# 根目录下执行
|
# 根目录下执行
|
||||||
go run cmd/gormgen/main.go -dsn "root:api2api..@tcp(sh-cynosdbmysql-grp-88th45wy.sql.tencentcdb.com:28555)/bindbox_game?charset=utf8mb4&parseTime=True&loc=Local" -tables "admin,log_operation,log_request,activities,activity_categories,activity_draw_logs,activity_issues,activity_reward_settings,guild,guild_boxes,guild_contribute_logs,guild_members,system_coupons,user_coupons,user_inventory,user_inventory_transfers,user_points,user_points_ledger"
|
go run cmd/gormgen/main.go -dsn "root:api2api..@tcp(sh-cynosdbmysql-grp-88th45wy.sql.tencentcdb.com:28555)/bindbox_game?charset=utf8mb4&parseTime=True&loc=Local" -tables "admin,log_operation,log_request,activities,activity_categories,activity_draw_logs,activity_issues,activity_reward_settings,guild,guild_boxes,guild_contribute_logs,guild_members,system_coupons,user_coupons,user_inventory,user_inventory_transfers,user_points,user_points_ledger"
|
||||||
|
|
||||||
go run cmd/gormgen/main.go -dsn "root:api2api..@tcp(sh-cynosdbmysql-grp-88th45wy.sql.tencentcdb.com:28555)/bindbox_game?charset=utf8mb4&parseTime=True&loc=Local" -tables "admin,log_operation,log_request,activities,activity_categories,activity_draw_logs,activity_issues,activity_reward_settings,guild,guild_boxes,guild_contribute_logs,guild_members,system_coupons,user_coupons,user_inventory,user_inventory_transfers,user_points,user_points_ledger,users,user_addresses,menu_actions,menus,role_actions,role_menus,role_users,roles,order_items,orders,products,shipping_records,product_categories,user_invites,system_item_cards,user_item_cards,activity_draw_effects,banner"
|
go run cmd/gormgen/main.go -dsn "root:api2api..@tcp(sh-cynosdbmysql-grp-88th45wy.sql.tencentcdb.com:28555)/bindbox_game?charset=utf8mb4&parseTime=True&loc=Local" -tables "admin,log_operation,log_request,activities,activity_categories,activity_draw_logs,activity_issues,activity_reward_settings,guild,guild_boxes,guild_contribute_logs,guild_members,system_coupons,user_coupons,user_inventory,user_inventory_transfers,user_points,user_points_ledger,users,user_addresses,menu_actions,menus,role_actions,role_menus,role_users,roles,order_items,orders,products,shipping_records,product_categories,user_invites,system_item_cards,user_item_cards,activity_draw_effects,banner,issue_random_commitments,activity_draw_receipts"
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -50,6 +50,10 @@ type Config struct {
|
|||||||
SecretKey string `mapstructure:"secret_key" toml:"secret_key"`
|
SecretKey string `mapstructure:"secret_key" toml:"secret_key"`
|
||||||
BaseURL string `mapstructure:"base_url" toml:"base_url"`
|
BaseURL string `mapstructure:"base_url" toml:"base_url"`
|
||||||
} `mapstructure:"cos" toml:"cos"`
|
} `mapstructure:"cos" toml:"cos"`
|
||||||
|
|
||||||
|
Random struct {
|
||||||
|
CommitMasterKey string `mapstructure:"commit_master_key" toml:"commit_master_key"`
|
||||||
|
} `mapstructure:"random" toml:"random"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
[mysql]
|
||||||
|
[mysql.read]
|
||||||
|
addr = "127.0.0.1:3306"
|
||||||
|
user = "root"
|
||||||
|
pass = "123456"
|
||||||
|
name = "bindbox_game"
|
||||||
|
[mysql.write]
|
||||||
|
addr = "127.0.0.1:3306"
|
||||||
|
user = "root"
|
||||||
|
pass = "123456"
|
||||||
|
name = "bindbox_game"
|
||||||
|
|
||||||
|
[redis]
|
||||||
|
addr = "127.0.0.1:6379"
|
||||||
|
pass = ""
|
||||||
|
db = 0
|
||||||
|
|
||||||
|
[random]
|
||||||
|
commit_master_key = "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
|
||||||
@ -27,3 +27,6 @@ secret_id = "AKIDtjPtAFPNDuR1UnxvoUCoRAnJgw164Zv6"
|
|||||||
secret_key = "B0vvjMoMsKcipnJlLnFyWt6A2JRSJ0Wr"
|
secret_key = "B0vvjMoMsKcipnJlLnFyWt6A2JRSJ0Wr"
|
||||||
# 可选:如有 CDN/自定义域名则填写,否则留空
|
# 可选:如有 CDN/自定义域名则填写,否则留空
|
||||||
base_url = ""
|
base_url = ""
|
||||||
|
|
||||||
|
[random]
|
||||||
|
commit_master_key = "4d7a3b8f9c2e1a5d6b4f8c0e3a7d2b1c6f9e4a5d8c1b3f7a2e5d6c4b8f0e3a7d2b1c"
|
||||||
@ -1,2 +1,19 @@
|
|||||||
|
[mysql]
|
||||||
|
[mysql.read]
|
||||||
|
addr = "127.0.0.1:3306"
|
||||||
|
user = "root"
|
||||||
|
pass = "123456"
|
||||||
|
name = "bindbox_game"
|
||||||
|
[mysql.write]
|
||||||
|
addr = "127.0.0.1:3306"
|
||||||
|
user = "root"
|
||||||
|
pass = "123456"
|
||||||
|
name = "bindbox_game"
|
||||||
|
|
||||||
|
[redis]
|
||||||
|
addr = "127.0.0.1:6379"
|
||||||
|
pass = ""
|
||||||
|
db = 0
|
||||||
|
|
||||||
|
[random]
|
||||||
|
commit_master_key = "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
|
||||||
@ -1,2 +1,19 @@
|
|||||||
|
[mysql]
|
||||||
|
[mysql.read]
|
||||||
|
addr = "127.0.0.1:3306"
|
||||||
|
user = "root"
|
||||||
|
pass = "123456"
|
||||||
|
name = "bindbox_game"
|
||||||
|
[mysql.write]
|
||||||
|
addr = "127.0.0.1:3306"
|
||||||
|
user = "root"
|
||||||
|
pass = "123456"
|
||||||
|
name = "bindbox_game"
|
||||||
|
|
||||||
|
[redis]
|
||||||
|
addr = "127.0.0.1:6379"
|
||||||
|
pass = ""
|
||||||
|
db = 0
|
||||||
|
|
||||||
|
[random]
|
||||||
|
commit_master_key = "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
|
||||||
35
internal/api/activity/draw_app.go
Normal file
35
internal/api/activity/draw_app.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"bindbox-game/internal/code"
|
||||||
|
"bindbox-game/internal/pkg/core"
|
||||||
|
"bindbox-game/internal/pkg/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type executeDrawResponse struct {
|
||||||
|
Receipt interface{} `json:"receipt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) ExecuteDraw() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
issueIDStr := ctx.Param("issue_id")
|
||||||
|
if issueIDStr == "" {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := strconv.ParseInt(issueIDStr, 10, 64); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
issueID, _ := strconv.ParseInt(issueIDStr, 10, 64)
|
||||||
|
rec, err := h.activity.ExecuteDraw(ctx.RequestContext(), issueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ExecuteDrawError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Payload(&executeDrawResponse{Receipt: rec})
|
||||||
|
}
|
||||||
|
}
|
||||||
152
internal/api/admin/batch_draw.go
Normal file
152
internal/api/admin/batch_draw.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"bindbox-game/internal/code"
|
||||||
|
"bindbox-game/internal/pkg/core"
|
||||||
|
"bindbox-game/internal/pkg/validation"
|
||||||
|
"bindbox-game/internal/repository/mysql/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type batchDrawRequest struct {
|
||||||
|
UserIDs []int64 `json:"user_ids" binding:"required,min=1,max=1000,dive,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type batchDrawResponse struct {
|
||||||
|
Draws []drawResultItem `json:"draws"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type drawResultItem struct {
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
DrawID int64 `json:"draw_id"`
|
||||||
|
RewardID int64 `json:"reward_id"`
|
||||||
|
RewardName string `json:"reward_name"`
|
||||||
|
IsWinner bool `json:"is_winner"`
|
||||||
|
Receipt interface{} `json:"receipt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) BatchDrawForUsers() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
issueIDStr := ctx.Param("issue_id")
|
||||||
|
if issueIDStr == "" {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := strconv.ParseInt(issueIDStr, 10, 64); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
issueID, _ := strconv.ParseInt(issueIDStr, 10, 64)
|
||||||
|
var req batchDrawRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rewards, err := h.activity.ListIssueRewards(ctx.RequestContext(), issueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ExecuteDrawError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rewardMap := make(map[int64]string, len(rewards))
|
||||||
|
for _, r := range rewards {
|
||||||
|
rewardMap[r.ID] = r.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始事务处理
|
||||||
|
tx := h.writeDB.Begin()
|
||||||
|
if tx.Error != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ExecuteDrawError, "启动事务失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
draws := make([]drawResultItem, 0, len(req.UserIDs))
|
||||||
|
for _, uid := range req.UserIDs {
|
||||||
|
// 执行抽奖
|
||||||
|
rec, err := h.activity.ExecuteDraw(ctx.RequestContext(), issueID)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ExecuteDrawError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建抽奖日志
|
||||||
|
isWinner := int32(0)
|
||||||
|
if rec.SelectedItemId > 0 {
|
||||||
|
isWinner = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
drawLog := &model.ActivityDrawLogs{
|
||||||
|
UserID: uid,
|
||||||
|
IssueID: issueID,
|
||||||
|
RewardID: rec.SelectedItemId,
|
||||||
|
IsWinner: isWinner,
|
||||||
|
Level: 1, // 默认等级
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.ActivityDrawLogs.Create(drawLog); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ExecuteDrawError, "创建抽奖日志失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存抽奖收据
|
||||||
|
itemsSnapshot, _ := json.Marshal(rec.Items)
|
||||||
|
receipt := &model.ActivityDrawReceipts{
|
||||||
|
DrawLogID: drawLog.ID,
|
||||||
|
AlgoVersion: rec.AlgoVersion,
|
||||||
|
RoundID: rec.RoundId,
|
||||||
|
DrawID: rec.DrawId,
|
||||||
|
ClientID: rec.ClientId,
|
||||||
|
Timestamp: rec.Timestamp,
|
||||||
|
ServerSeedHash: hex.EncodeToString(rec.ServerSeedHash),
|
||||||
|
ServerSubSeed: hex.EncodeToString(rec.ServerSubSeed),
|
||||||
|
ClientSeed: hex.EncodeToString(rec.ClientSeed),
|
||||||
|
Nonce: int64(rec.Nonce),
|
||||||
|
ItemsRoot: hex.EncodeToString(rec.ItemsRoot),
|
||||||
|
WeightsTotal: int64(rec.WeightsTotal),
|
||||||
|
SelectedIndex: int32(rec.SelectedIndex),
|
||||||
|
RandProof: hex.EncodeToString(rec.RandProof),
|
||||||
|
Signature: "", // 可以后续添加签名
|
||||||
|
ItemsSnapshot: string(itemsSnapshot),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.ActivityDrawReceipts.Create(receipt); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ExecuteDrawError, "保存抽奖收据失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := ""
|
||||||
|
if rec.SelectedItemId > 0 {
|
||||||
|
name = rewardMap[rec.SelectedItemId]
|
||||||
|
}
|
||||||
|
draws = append(draws, drawResultItem{
|
||||||
|
UserID: uid,
|
||||||
|
DrawID: rec.DrawId,
|
||||||
|
RewardID: rec.SelectedItemId,
|
||||||
|
RewardName: name,
|
||||||
|
IsWinner: rec.SelectedItemId > 0,
|
||||||
|
Receipt: rec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ExecuteDrawError, "提交事务失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(&batchDrawResponse{Draws: draws})
|
||||||
|
}
|
||||||
|
}
|
||||||
82
internal/api/admin/batch_users.go
Normal file
82
internal/api/admin/batch_users.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bindbox-game/internal/code"
|
||||||
|
"bindbox-game/internal/pkg/core"
|
||||||
|
"bindbox-game/internal/pkg/validation"
|
||||||
|
"bindbox-game/internal/service/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type batchUsersRequest struct {
|
||||||
|
Count int `json:"count" binding:"min=1,max=1000"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type batchUsersResponse struct {
|
||||||
|
Users []batchUserItem `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type batchUserItem struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
OpenID string `json:"open_id"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) BatchCreateUsers() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
var req batchUsersRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
users := make([]batchUserItem, 0, req.Count)
|
||||||
|
for i := 0; i < req.Count; i++ {
|
||||||
|
nickname := "用户" + strconv.Itoa(i+1) + "_" + strconv.FormatInt(time.Now().UnixNano()%10000, 10)
|
||||||
|
openID := "batch_" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
|
avatar := "https://trae-api-sg.mchost.guru/api/ide/v1/text_to_image?prompt=avatar&image_size=square"
|
||||||
|
u, err := h.user.CreateUser(ctx.RequestContext(), user.CreateUserInput{
|
||||||
|
Nickname: nickname,
|
||||||
|
OpenID: openID,
|
||||||
|
Avatar: avatar,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateUserError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
users = append(users, batchUserItem{
|
||||||
|
ID: u.ID,
|
||||||
|
Nickname: u.Nickname,
|
||||||
|
OpenID: u.Openid,
|
||||||
|
Avatar: u.Avatar,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx.Payload(&batchUsersResponse{Users: users})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) BatchDeleteUsers() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
var req struct {
|
||||||
|
UserIDs []int64 `json:"user_ids" binding:"required,min=1,max=1000,dive,min=1"`
|
||||||
|
}
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ctx.SessionUserInfo().IsSuper != 1 {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.DeleteUserError, "禁止操作"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, uid := range req.UserIDs {
|
||||||
|
if err := h.user.DeleteUser(ctx.RequestContext(), uid); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.DeleteUserError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Payload(&simpleMessageResponse{Message: "操作成功"})
|
||||||
|
}
|
||||||
|
}
|
||||||
121
internal/api/admin/draw_receipt.go
Normal file
121
internal/api/admin/draw_receipt.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"bindbox-game/internal/code"
|
||||||
|
"bindbox-game/internal/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type getDrawReceiptResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
DrawLogID int64 `json:"draw_log_id"`
|
||||||
|
AlgoVersion string `json:"algo_version"`
|
||||||
|
RoundID int64 `json:"round_id"`
|
||||||
|
DrawID int64 `json:"draw_id"`
|
||||||
|
ClientID int64 `json:"client_id"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
ServerSeedHash string `json:"server_seed_hash"`
|
||||||
|
ServerSubSeed string `json:"server_sub_seed"`
|
||||||
|
ClientSeed string `json:"client_seed"`
|
||||||
|
Nonce int64 `json:"nonce"`
|
||||||
|
ItemsRoot string `json:"items_root"`
|
||||||
|
WeightsTotal int64 `json:"weights_total"`
|
||||||
|
SelectedIndex int32 `json:"selected_index"`
|
||||||
|
RandProof string `json:"rand_proof"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
ItemsSnapshot string `json:"items_snapshot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetDrawReceipt() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
drawIDStr := ctx.Param("draw_id")
|
||||||
|
if drawIDStr == "" {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递抽奖ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
drawID, err := strconv.ParseInt(drawIDStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "抽奖ID格式错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询抽奖收据
|
||||||
|
receipt, err := h.readDB.ActivityDrawReceipts.WithContext(ctx.RequestContext()).
|
||||||
|
Where(h.readDB.ActivityDrawReceipts.DrawID.Eq(drawID)).
|
||||||
|
First()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusNotFound, code.GetDrawReceiptError, "未找到抽奖收据"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(&getDrawReceiptResponse{
|
||||||
|
ID: receipt.ID,
|
||||||
|
DrawLogID: receipt.DrawLogID,
|
||||||
|
AlgoVersion: receipt.AlgoVersion,
|
||||||
|
RoundID: receipt.RoundID,
|
||||||
|
DrawID: receipt.DrawID,
|
||||||
|
ClientID: receipt.ClientID,
|
||||||
|
Timestamp: receipt.Timestamp,
|
||||||
|
ServerSeedHash: receipt.ServerSeedHash,
|
||||||
|
ServerSubSeed: receipt.ServerSubSeed,
|
||||||
|
ClientSeed: receipt.ClientSeed,
|
||||||
|
Nonce: receipt.Nonce,
|
||||||
|
ItemsRoot: receipt.ItemsRoot,
|
||||||
|
WeightsTotal: receipt.WeightsTotal,
|
||||||
|
SelectedIndex: receipt.SelectedIndex,
|
||||||
|
RandProof: receipt.RandProof,
|
||||||
|
Signature: receipt.Signature,
|
||||||
|
ItemsSnapshot: receipt.ItemsSnapshot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetDrawReceiptByLogID() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
logIDStr := ctx.Param("log_id")
|
||||||
|
if logIDStr == "" {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递日志ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logID, err := strconv.ParseInt(logIDStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "日志ID格式错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询抽奖收据
|
||||||
|
receipt, err := h.readDB.ActivityDrawReceipts.WithContext(ctx.RequestContext()).
|
||||||
|
Where(h.readDB.ActivityDrawReceipts.DrawLogID.Eq(logID)).
|
||||||
|
First()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusNotFound, code.GetDrawReceiptError, "未找到抽奖收据"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(&getDrawReceiptResponse{
|
||||||
|
ID: receipt.ID,
|
||||||
|
DrawLogID: receipt.DrawLogID,
|
||||||
|
AlgoVersion: receipt.AlgoVersion,
|
||||||
|
RoundID: receipt.RoundID,
|
||||||
|
DrawID: receipt.DrawID,
|
||||||
|
ClientID: receipt.ClientID,
|
||||||
|
Timestamp: receipt.Timestamp,
|
||||||
|
ServerSeedHash: receipt.ServerSeedHash,
|
||||||
|
ServerSubSeed: receipt.ServerSubSeed,
|
||||||
|
ClientSeed: receipt.ClientSeed,
|
||||||
|
Nonce: receipt.Nonce,
|
||||||
|
ItemsRoot: receipt.ItemsRoot,
|
||||||
|
WeightsTotal: receipt.WeightsTotal,
|
||||||
|
SelectedIndex: receipt.SelectedIndex,
|
||||||
|
RandProof: receipt.RandProof,
|
||||||
|
Signature: receipt.Signature,
|
||||||
|
ItemsSnapshot: receipt.ItemsSnapshot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
131
internal/api/admin/draw_simulate.go
Normal file
131
internal/api/admin/draw_simulate.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"bindbox-game/internal/code"
|
||||||
|
"bindbox-game/internal/pkg/core"
|
||||||
|
"bindbox-game/internal/pkg/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type simulateDrawRequest struct {
|
||||||
|
Samples int `json:"samples"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type simulateItemStat struct {
|
||||||
|
RewardID int64 `json:"reward_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Quantity int64 `json:"quantity"`
|
||||||
|
ExpectedRate float64 `json:"expected_rate"`
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
ObservedRate float64 `json:"observed_rate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type simulateDrawResponse struct {
|
||||||
|
AlgoVersion string `json:"algo_version"`
|
||||||
|
IssueID int64 `json:"issue_id"`
|
||||||
|
ServerSeedHash string `json:"server_seed_hash"`
|
||||||
|
ItemsRoot string `json:"items_root"`
|
||||||
|
WeightsTotal int64 `json:"weights_total"`
|
||||||
|
Samples int `json:"samples"`
|
||||||
|
Stats []simulateItemStat `json:"stats"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) SimulateIssueDraw() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
issueIDStr := ctx.Param("issue_id")
|
||||||
|
if issueIDStr == "" {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := strconv.ParseInt(issueIDStr, 10, 64); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
issueID, _ := strconv.ParseInt(issueIDStr, 10, 64)
|
||||||
|
|
||||||
|
var req simulateDrawRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Samples <= 0 {
|
||||||
|
req.Samples = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
rewards, err := h.activity.ListIssueRewards(ctx.RequestContext(), issueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ExecuteDrawError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cm, err := h.activity.GetIssueRandomCommit(ctx.RequestContext(), issueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetIssueRandomCommitError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cm == nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetIssueRandomCommitError, "未找到承诺"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
counts := make(map[int64]int64)
|
||||||
|
var eligibleTotal int64
|
||||||
|
for _, it := range rewards {
|
||||||
|
if it.Weight > 0 && (it.Quantity == -1 || it.Quantity > 0) {
|
||||||
|
eligibleTotal += int64(it.Weight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < req.Samples; i++ {
|
||||||
|
rec, err := h.activity.ExecuteDraw(ctx.RequestContext(), issueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ExecuteDrawError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
counts[rec.SelectedItemId]++
|
||||||
|
}
|
||||||
|
stats := make([]simulateItemStat, 0, len(rewards))
|
||||||
|
for _, it := range rewards {
|
||||||
|
var expected float64
|
||||||
|
if eligibleTotal > 0 && it.Weight > 0 && (it.Quantity == -1 || it.Quantity > 0) {
|
||||||
|
expected = float64(it.Weight) / float64(eligibleTotal)
|
||||||
|
}
|
||||||
|
c := counts[it.ID]
|
||||||
|
var observed float64
|
||||||
|
if req.Samples > 0 {
|
||||||
|
observed = float64(c) / float64(req.Samples)
|
||||||
|
}
|
||||||
|
stats = append(stats, simulateItemStat{
|
||||||
|
RewardID: it.ID,
|
||||||
|
Name: it.Name,
|
||||||
|
Weight: it.Weight,
|
||||||
|
Quantity: it.Quantity,
|
||||||
|
ExpectedRate: expected,
|
||||||
|
Count: c,
|
||||||
|
ObservedRate: observed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx.Payload(&simulateDrawResponse{
|
||||||
|
AlgoVersion: cm.AlgoVersion,
|
||||||
|
IssueID: cm.IssueID,
|
||||||
|
ServerSeedHash: hexStr2(cm.ServerSeedHash[:]),
|
||||||
|
ItemsRoot: hexStr2(cm.ItemsRoot[:]),
|
||||||
|
WeightsTotal: cm.WeightsTotal,
|
||||||
|
Samples: req.Samples,
|
||||||
|
Stats: stats,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hexStr2(b []byte) string {
|
||||||
|
const hextable = "0123456789abcdef"
|
||||||
|
dst := make([]byte, len(b)*2)
|
||||||
|
j := 0
|
||||||
|
for _, v := range b {
|
||||||
|
dst[j] = hextable[v>>4]
|
||||||
|
dst[j+1] = hextable[v&0x0f]
|
||||||
|
j += 2
|
||||||
|
}
|
||||||
|
return string(dst)
|
||||||
|
}
|
||||||
88
internal/api/admin/draw_verify_helper.go
Normal file
88
internal/api/admin/draw_verify_helper.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"bindbox-game/configs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unmaskSeed(enc []byte, issueID int64, version int32) []byte {
|
||||||
|
key := masterKey()
|
||||||
|
if key == nil {
|
||||||
|
return enc
|
||||||
|
}
|
||||||
|
ks := deriveMask(key, issueID, version)
|
||||||
|
out := make([]byte, len(enc))
|
||||||
|
for i := range enc {
|
||||||
|
out[i] = enc[i] ^ ks[i%len(ks)]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func masterKey() []byte {
|
||||||
|
s := configs.Get().Random.CommitMasterKey
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
b, err := hex.DecodeString(s)
|
||||||
|
if err != nil || len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveMask(key []byte, issueID int64, version int32) []byte {
|
||||||
|
m := hmac.New(sha256.New, key)
|
||||||
|
buf := make([]byte, 12)
|
||||||
|
binary.BigEndian.PutUint64(buf[:8], uint64(issueID))
|
||||||
|
binary.BigEndian.PutUint32(buf[8:12], uint32(version))
|
||||||
|
m.Write(buf)
|
||||||
|
sum := m.Sum(nil)
|
||||||
|
return sum[:32]
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeMessage(algo string, roundId int64, drawId int64, clientId int64, clientSeed []byte, nonce uint64, itemsRoot []byte, weightsTotal uint64) []byte {
|
||||||
|
var buf []byte
|
||||||
|
buf = appendUint32String(buf, algo)
|
||||||
|
buf = appendUint64(buf, uint64(roundId))
|
||||||
|
buf = appendUint64(buf, uint64(drawId))
|
||||||
|
buf = appendUint64(buf, uint64(clientId))
|
||||||
|
buf = appendUint32Bytes(buf, clientSeed)
|
||||||
|
buf = appendUint64(buf, nonce)
|
||||||
|
buf = append(buf, itemsRoot...)
|
||||||
|
buf = appendUint64(buf, weightsTotal)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint32String(b []byte, s string) []byte {
|
||||||
|
bs := []byte(s)
|
||||||
|
nb := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(nb, uint32(len(bs)))
|
||||||
|
b = append(b, nb...)
|
||||||
|
b = append(b, bs...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint32Bytes(b []byte, bs []byte) []byte {
|
||||||
|
nb := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(nb, uint32(len(bs)))
|
||||||
|
b = append(b, nb...)
|
||||||
|
b = append(b, bs...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint64(b []byte, v uint64) []byte {
|
||||||
|
nb := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(nb, v)
|
||||||
|
b = append(b, nb...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func hmacSha256(key []byte, msg []byte) []byte {
|
||||||
|
m := hmac.New(sha256.New, key)
|
||||||
|
m.Write(msg)
|
||||||
|
return m.Sum(nil)
|
||||||
|
}
|
||||||
124
internal/api/admin/issue_random_commit.go
Normal file
124
internal/api/admin/issue_random_commit.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"bindbox-game/internal/code"
|
||||||
|
"bindbox-game/internal/pkg/core"
|
||||||
|
"bindbox-game/internal/pkg/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type issueCommitResponse struct {
|
||||||
|
AlgoVersion string `json:"algo_version"`
|
||||||
|
IssueID int64 `json:"issue_id"`
|
||||||
|
ServerSeedHash string `json:"server_seed_hash"`
|
||||||
|
ItemsRoot string `json:"items_root"`
|
||||||
|
WeightsTotal int64 `json:"weights_total"`
|
||||||
|
StateVersion int32 `json:"state_version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) CommitIssueRandom() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
issueIDStr := ctx.Param("issue_id")
|
||||||
|
if issueIDStr == "" {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := strconv.ParseInt(issueIDStr, 10, 64); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
issueID, _ := strconv.ParseInt(issueIDStr, 10, 64)
|
||||||
|
cm, err := h.activity.CommitIssueRandom(ctx.RequestContext(), issueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CommitIssueRandomError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Payload(&issueCommitResponse{
|
||||||
|
AlgoVersion: cm.AlgoVersion,
|
||||||
|
IssueID: cm.IssueID,
|
||||||
|
ServerSeedHash: hexStr(cm.ServerSeedHash[:]),
|
||||||
|
ItemsRoot: hexStr(cm.ItemsRoot[:]),
|
||||||
|
WeightsTotal: cm.WeightsTotal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetIssueRandomCommit() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
issueIDStr := ctx.Param("issue_id")
|
||||||
|
if issueIDStr == "" {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := strconv.ParseInt(issueIDStr, 10, 64); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
issueID, _ := strconv.ParseInt(issueIDStr, 10, 64)
|
||||||
|
cm, err := h.activity.GetIssueRandomCommit(ctx.RequestContext(), issueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetIssueRandomCommitError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cm == nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetIssueRandomCommitError, "未找到承诺"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Payload(&issueCommitResponse{
|
||||||
|
AlgoVersion: cm.AlgoVersion,
|
||||||
|
IssueID: cm.IssueID,
|
||||||
|
ServerSeedHash: hexStr(cm.ServerSeedHash[:]),
|
||||||
|
ItemsRoot: hexStr(cm.ItemsRoot[:]),
|
||||||
|
WeightsTotal: cm.WeightsTotal,
|
||||||
|
StateVersion: cm.StateVersion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetIssueRandomCommitHistory() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
issueIDStr := ctx.Param("issue_id")
|
||||||
|
if issueIDStr == "" {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := strconv.ParseInt(issueIDStr, 10, 64); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
issueID, _ := strconv.ParseInt(issueIDStr, 10, 64)
|
||||||
|
history, err := h.activity.GetIssueRandomCommitHistory(ctx.RequestContext(), issueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetIssueRandomCommitError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换响应格式
|
||||||
|
response := make([]*issueCommitResponse, 0, len(history))
|
||||||
|
for _, commit := range history {
|
||||||
|
response = append(response, &issueCommitResponse{
|
||||||
|
AlgoVersion: commit.AlgoVersion,
|
||||||
|
IssueID: commit.IssueID,
|
||||||
|
ServerSeedHash: hexStr(commit.ServerSeedHash[:]),
|
||||||
|
ItemsRoot: hexStr(commit.ItemsRoot[:]),
|
||||||
|
WeightsTotal: commit.WeightsTotal,
|
||||||
|
StateVersion: commit.StateVersion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx.Payload(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hexStr(b []byte) string {
|
||||||
|
const hextable = "0123456789abcdef"
|
||||||
|
dst := make([]byte, len(b)*2)
|
||||||
|
j := 0
|
||||||
|
for _, v := range b {
|
||||||
|
dst[j] = hextable[v>>4]
|
||||||
|
dst[j+1] = hextable[v&0x0f]
|
||||||
|
j += 2
|
||||||
|
}
|
||||||
|
return string(dst)
|
||||||
|
}
|
||||||
140
internal/api/admin/verify_draw.go
Normal file
140
internal/api/admin/verify_draw.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"bindbox-game/internal/code"
|
||||||
|
"bindbox-game/internal/pkg/core"
|
||||||
|
"bindbox-game/internal/pkg/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type verifyDrawRequest struct {
|
||||||
|
AlgoVersion string `json:"algo_version" binding:"required"`
|
||||||
|
IssueID int64 `json:"issue_id" binding:"required"`
|
||||||
|
DrawID int64 `json:"draw_id" binding:"required"`
|
||||||
|
ClientSeed string `json:"client_seed" binding:"required"`
|
||||||
|
ServerSeedHash string `json:"server_seed_hash" binding:"required"`
|
||||||
|
ItemsRoot string `json:"items_root" binding:"required"`
|
||||||
|
WeightsTotal uint64 `json:"weights_total" binding:"required"`
|
||||||
|
SelectedIndex int `json:"selected_index" binding:"required,min=0"`
|
||||||
|
RandProof string `json:"rand_proof" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type verifyDrawResponse struct {
|
||||||
|
Valid bool `json:"valid"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
ServerSeedHashExpected string `json:"server_seed_hash_expected,omitempty"`
|
||||||
|
ItemsRootExpected string `json:"items_root_expected,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) VerifyDrawReceipt() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
var req verifyDrawRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cm, err := h.activity.GetIssueRandomCommit(ctx.RequestContext(), req.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetIssueRandomCommitError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cm == nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.GetIssueRandomCommitError, "未找到承诺"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverSeedHashBytes := cm.ServerSeedHash[:]
|
||||||
|
itemsRootBytes := cm.ItemsRoot[:]
|
||||||
|
if hex.EncodeToString(serverSeedHashBytes) != req.ServerSeedHash {
|
||||||
|
ctx.Payload(&verifyDrawResponse{
|
||||||
|
Valid: false,
|
||||||
|
Message: "server_seed_hash 不匹配",
|
||||||
|
ServerSeedHashExpected: hex.EncodeToString(serverSeedHashBytes),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if hex.EncodeToString(itemsRootBytes) != req.ItemsRoot {
|
||||||
|
ctx.Payload(&verifyDrawResponse{
|
||||||
|
Valid: false,
|
||||||
|
Message: "items_root 不匹配",
|
||||||
|
ItemsRootExpected: hex.EncodeToString(itemsRootBytes),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rewards, err := h.activity.ListIssueRewards(ctx.RequestContext(), req.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ListIssueRewardsError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.SelectedIndex >= len(rewards) {
|
||||||
|
ctx.Payload(&verifyDrawResponse{
|
||||||
|
Valid: false,
|
||||||
|
Message: "selected_index 超出奖励范围",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sel := rewards[req.SelectedIndex]
|
||||||
|
if sel.Weight <= 0 || (sel.Quantity != -1 && sel.Quantity <= 0) {
|
||||||
|
ctx.Payload(&verifyDrawResponse{
|
||||||
|
Valid: false,
|
||||||
|
Message: "selected_index 对应奖励不可抽",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
proofBytes, err := hex.DecodeString(req.RandProof)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Payload(&verifyDrawResponse{
|
||||||
|
Valid: false,
|
||||||
|
Message: "rand_proof 格式错误",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientSeedBytes, err := hex.DecodeString(req.ClientSeed)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Payload(&verifyDrawResponse{
|
||||||
|
Valid: false,
|
||||||
|
Message: "client_seed 格式错误",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
master := unmaskSeed(cm.ServerSeedMaster, cm.IssueID, cm.StateVersion)
|
||||||
|
subInput := make([]byte, 16)
|
||||||
|
binary.BigEndian.PutUint64(subInput[:8], uint64(req.IssueID))
|
||||||
|
binary.BigEndian.PutUint64(subInput[8:], uint64(req.DrawID))
|
||||||
|
mac := hmac.New(sha256.New, master)
|
||||||
|
mac.Write(subInput)
|
||||||
|
serverSubSeed := mac.Sum(nil)
|
||||||
|
enc := encodeMessage(req.AlgoVersion, req.IssueID, req.DrawID, 0, clientSeedBytes, 1, itemsRootBytes, req.WeightsTotal)
|
||||||
|
entropy := hmacSha256(serverSubSeed, enc)
|
||||||
|
var final []byte
|
||||||
|
{
|
||||||
|
W := req.WeightsTotal
|
||||||
|
var counter uint64
|
||||||
|
for {
|
||||||
|
R := binary.BigEndian.Uint64(entropy[:8])
|
||||||
|
M := (uint64(^uint64(0)) / W) * W
|
||||||
|
if R < M {
|
||||||
|
final = entropy
|
||||||
|
break
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
cenc := make([]byte, len(enc)+8)
|
||||||
|
copy(cenc, enc)
|
||||||
|
binary.BigEndian.PutUint64(cenc[len(enc):], counter)
|
||||||
|
entropy = hmacSha256(serverSubSeed, cenc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hmac.Equal(final, proofBytes) {
|
||||||
|
ctx.Payload(&verifyDrawResponse{
|
||||||
|
Valid: false,
|
||||||
|
Message: "rand_proof 验证失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Payload(&verifyDrawResponse{Valid: true, Message: "验证通过"})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,11 +15,11 @@ type Failure struct {
|
|||||||
Message string `json:"message"` // 描述信息
|
Message string `json:"message"` // 描述信息
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ServerError = 10101
|
ServerError = 10101
|
||||||
ParamBindError = 10102
|
ParamBindError = 10102
|
||||||
JWTAuthVerifyError = 10103
|
JWTAuthVerifyError = 10103
|
||||||
UploadError = 10104
|
UploadError = 10104
|
||||||
|
|
||||||
AdminLoginError = 20101
|
AdminLoginError = 20101
|
||||||
CreateAppError = 20201
|
CreateAppError = 20201
|
||||||
@ -69,8 +69,14 @@ const (
|
|||||||
DeleteActivityIssueError = 20509
|
DeleteActivityIssueError = 20509
|
||||||
CreateIssueRewardsError = 20510
|
CreateIssueRewardsError = 20510
|
||||||
ListIssueRewardsError = 20511
|
ListIssueRewardsError = 20511
|
||||||
ListDrawLogsError = 20512
|
ListDrawLogsError = 20512
|
||||||
)
|
ExecuteDrawError = 20513
|
||||||
|
CommitIssueRandomError = 20514
|
||||||
|
GetIssueRandomCommitError= 20515
|
||||||
|
CreateUserError = 20516
|
||||||
|
DeleteUserError = 20517
|
||||||
|
GetDrawReceiptError = 20518
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CreateGuildError = 20601
|
CreateGuildError = 20601
|
||||||
|
|||||||
@ -41,8 +41,12 @@ var zhCNText = map[int]string{
|
|||||||
ModifyActivityIssueError: "修改活动期数失败",
|
ModifyActivityIssueError: "修改活动期数失败",
|
||||||
DeleteActivityIssueError: "删除活动期数失败",
|
DeleteActivityIssueError: "删除活动期数失败",
|
||||||
CreateIssueRewardsError: "创建期数奖品失败",
|
CreateIssueRewardsError: "创建期数奖品失败",
|
||||||
ListIssueRewardsError: "获取期数奖品失败",
|
ListIssueRewardsError: "获取期数奖品失败",
|
||||||
ListDrawLogsError: "获取抽奖记录失败",
|
ListDrawLogsError: "获取抽奖记录失败",
|
||||||
|
ExecuteDrawError: "执行抽奖失败",
|
||||||
|
CommitIssueRandomError: "生成期随机承诺失败",
|
||||||
|
GetIssueRandomCommitError:"获取期随机承诺失败",
|
||||||
|
GetDrawReceiptError: "获取抽奖收据失败",
|
||||||
|
|
||||||
CreateGuildError: "创建工会失败",
|
CreateGuildError: "创建工会失败",
|
||||||
ModifyGuildError: "修改工会失败",
|
ModifyGuildError: "修改工会失败",
|
||||||
|
|||||||
388
internal/repository/mysql/dao/activity_draw_receipts.gen.go
Normal file
388
internal/repository/mysql/dao/activity_draw_receipts.gen.go
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
|
||||||
|
"gorm.io/plugin/dbresolver"
|
||||||
|
|
||||||
|
"bindbox-game/internal/repository/mysql/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newActivityDrawReceipts(db *gorm.DB, opts ...gen.DOOption) activityDrawReceipts {
|
||||||
|
_activityDrawReceipts := activityDrawReceipts{}
|
||||||
|
|
||||||
|
_activityDrawReceipts.activityDrawReceiptsDo.UseDB(db, opts...)
|
||||||
|
_activityDrawReceipts.activityDrawReceiptsDo.UseModel(&model.ActivityDrawReceipts{})
|
||||||
|
|
||||||
|
tableName := _activityDrawReceipts.activityDrawReceiptsDo.TableName()
|
||||||
|
_activityDrawReceipts.ALL = field.NewAsterisk(tableName)
|
||||||
|
_activityDrawReceipts.ID = field.NewInt64(tableName, "id")
|
||||||
|
_activityDrawReceipts.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
|
_activityDrawReceipts.DrawLogID = field.NewInt64(tableName, "draw_log_id")
|
||||||
|
_activityDrawReceipts.AlgoVersion = field.NewString(tableName, "algo_version")
|
||||||
|
_activityDrawReceipts.RoundID = field.NewInt64(tableName, "round_id")
|
||||||
|
_activityDrawReceipts.DrawID = field.NewInt64(tableName, "draw_id")
|
||||||
|
_activityDrawReceipts.ClientID = field.NewInt64(tableName, "client_id")
|
||||||
|
_activityDrawReceipts.Timestamp = field.NewInt64(tableName, "timestamp")
|
||||||
|
_activityDrawReceipts.ServerSeedHash = field.NewString(tableName, "server_seed_hash")
|
||||||
|
_activityDrawReceipts.ServerSubSeed = field.NewString(tableName, "server_sub_seed")
|
||||||
|
_activityDrawReceipts.ClientSeed = field.NewString(tableName, "client_seed")
|
||||||
|
_activityDrawReceipts.Nonce = field.NewInt64(tableName, "nonce")
|
||||||
|
_activityDrawReceipts.ItemsRoot = field.NewString(tableName, "items_root")
|
||||||
|
_activityDrawReceipts.WeightsTotal = field.NewInt64(tableName, "weights_total")
|
||||||
|
_activityDrawReceipts.SelectedIndex = field.NewInt32(tableName, "selected_index")
|
||||||
|
_activityDrawReceipts.RandProof = field.NewString(tableName, "rand_proof")
|
||||||
|
_activityDrawReceipts.Signature = field.NewString(tableName, "signature")
|
||||||
|
_activityDrawReceipts.ItemsSnapshot = field.NewString(tableName, "items_snapshot")
|
||||||
|
|
||||||
|
_activityDrawReceipts.fillFieldMap()
|
||||||
|
|
||||||
|
return _activityDrawReceipts
|
||||||
|
}
|
||||||
|
|
||||||
|
// activityDrawReceipts 活动抽奖凭据表
|
||||||
|
type activityDrawReceipts struct {
|
||||||
|
activityDrawReceiptsDo
|
||||||
|
|
||||||
|
ALL field.Asterisk
|
||||||
|
ID field.Int64 // 主键ID
|
||||||
|
CreatedAt field.Time // 创建时间
|
||||||
|
DrawLogID field.Int64 // 抽奖日志ID(activity_draw_logs.id)
|
||||||
|
AlgoVersion field.String // 算法版本
|
||||||
|
RoundID field.Int64 // 期ID
|
||||||
|
DrawID field.Int64 // 抽奖ID
|
||||||
|
ClientID field.Int64 // 客户端ID
|
||||||
|
Timestamp field.Int64 // 时间戳(毫秒)
|
||||||
|
ServerSeedHash field.String // 服务器种子哈希(十六进制)
|
||||||
|
ServerSubSeed field.String // 服务器子种子(十六进制)
|
||||||
|
ClientSeed field.String // 客户端种子(十六进制)
|
||||||
|
Nonce field.Int64 // 随机数
|
||||||
|
ItemsRoot field.String // 项目根哈希(十六进制)
|
||||||
|
WeightsTotal field.Int64 // 权重总和
|
||||||
|
SelectedIndex field.Int32 // 选中索引
|
||||||
|
RandProof field.String // 随机证明(十六进制)
|
||||||
|
Signature field.String // 签名(十六进制,可选)
|
||||||
|
ItemsSnapshot field.String // 项目快照(JSON格式)
|
||||||
|
|
||||||
|
fieldMap map[string]field.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceipts) Table(newTableName string) *activityDrawReceipts {
|
||||||
|
a.activityDrawReceiptsDo.UseTable(newTableName)
|
||||||
|
return a.updateTableName(newTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceipts) As(alias string) *activityDrawReceipts {
|
||||||
|
a.activityDrawReceiptsDo.DO = *(a.activityDrawReceiptsDo.As(alias).(*gen.DO))
|
||||||
|
return a.updateTableName(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *activityDrawReceipts) updateTableName(table string) *activityDrawReceipts {
|
||||||
|
a.ALL = field.NewAsterisk(table)
|
||||||
|
a.ID = field.NewInt64(table, "id")
|
||||||
|
a.CreatedAt = field.NewTime(table, "created_at")
|
||||||
|
a.DrawLogID = field.NewInt64(table, "draw_log_id")
|
||||||
|
a.AlgoVersion = field.NewString(table, "algo_version")
|
||||||
|
a.RoundID = field.NewInt64(table, "round_id")
|
||||||
|
a.DrawID = field.NewInt64(table, "draw_id")
|
||||||
|
a.ClientID = field.NewInt64(table, "client_id")
|
||||||
|
a.Timestamp = field.NewInt64(table, "timestamp")
|
||||||
|
a.ServerSeedHash = field.NewString(table, "server_seed_hash")
|
||||||
|
a.ServerSubSeed = field.NewString(table, "server_sub_seed")
|
||||||
|
a.ClientSeed = field.NewString(table, "client_seed")
|
||||||
|
a.Nonce = field.NewInt64(table, "nonce")
|
||||||
|
a.ItemsRoot = field.NewString(table, "items_root")
|
||||||
|
a.WeightsTotal = field.NewInt64(table, "weights_total")
|
||||||
|
a.SelectedIndex = field.NewInt32(table, "selected_index")
|
||||||
|
a.RandProof = field.NewString(table, "rand_proof")
|
||||||
|
a.Signature = field.NewString(table, "signature")
|
||||||
|
a.ItemsSnapshot = field.NewString(table, "items_snapshot")
|
||||||
|
|
||||||
|
a.fillFieldMap()
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *activityDrawReceipts) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
|
_f, ok := a.fieldMap[fieldName]
|
||||||
|
if !ok || _f == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_oe, ok := _f.(field.OrderExpr)
|
||||||
|
return _oe, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *activityDrawReceipts) fillFieldMap() {
|
||||||
|
a.fieldMap = make(map[string]field.Expr, 18)
|
||||||
|
a.fieldMap["id"] = a.ID
|
||||||
|
a.fieldMap["created_at"] = a.CreatedAt
|
||||||
|
a.fieldMap["draw_log_id"] = a.DrawLogID
|
||||||
|
a.fieldMap["algo_version"] = a.AlgoVersion
|
||||||
|
a.fieldMap["round_id"] = a.RoundID
|
||||||
|
a.fieldMap["draw_id"] = a.DrawID
|
||||||
|
a.fieldMap["client_id"] = a.ClientID
|
||||||
|
a.fieldMap["timestamp"] = a.Timestamp
|
||||||
|
a.fieldMap["server_seed_hash"] = a.ServerSeedHash
|
||||||
|
a.fieldMap["server_sub_seed"] = a.ServerSubSeed
|
||||||
|
a.fieldMap["client_seed"] = a.ClientSeed
|
||||||
|
a.fieldMap["nonce"] = a.Nonce
|
||||||
|
a.fieldMap["items_root"] = a.ItemsRoot
|
||||||
|
a.fieldMap["weights_total"] = a.WeightsTotal
|
||||||
|
a.fieldMap["selected_index"] = a.SelectedIndex
|
||||||
|
a.fieldMap["rand_proof"] = a.RandProof
|
||||||
|
a.fieldMap["signature"] = a.Signature
|
||||||
|
a.fieldMap["items_snapshot"] = a.ItemsSnapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceipts) clone(db *gorm.DB) activityDrawReceipts {
|
||||||
|
a.activityDrawReceiptsDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceipts) replaceDB(db *gorm.DB) activityDrawReceipts {
|
||||||
|
a.activityDrawReceiptsDo.ReplaceDB(db)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
type activityDrawReceiptsDo struct{ gen.DO }
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Debug() *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Debug())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) WithContext(ctx context.Context) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) ReadDB() *activityDrawReceiptsDo {
|
||||||
|
return a.Clauses(dbresolver.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) WriteDB() *activityDrawReceiptsDo {
|
||||||
|
return a.Clauses(dbresolver.Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Session(config *gorm.Session) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Session(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Clauses(conds ...clause.Expression) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Clauses(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Returning(value interface{}, columns ...string) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Returning(value, columns...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Not(conds ...gen.Condition) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Not(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Or(conds ...gen.Condition) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Or(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Select(conds ...field.Expr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Select(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Where(conds ...gen.Condition) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Where(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Order(conds ...field.Expr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Order(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Distinct(cols ...field.Expr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Distinct(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Omit(cols ...field.Expr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Omit(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Join(table schema.Tabler, on ...field.Expr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Join(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) LeftJoin(table schema.Tabler, on ...field.Expr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.LeftJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) RightJoin(table schema.Tabler, on ...field.Expr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.RightJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Group(cols ...field.Expr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Group(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Having(conds ...gen.Condition) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Having(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Limit(limit int) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Limit(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Offset(offset int) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Offset(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Scopes(funcs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Unscoped() *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Unscoped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Create(values ...*model.ActivityDrawReceipts) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.DO.Create(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) CreateInBatches(values []*model.ActivityDrawReceipts, batchSize int) error {
|
||||||
|
return a.DO.CreateInBatches(values, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save : !!! underlying implementation is different with GORM
|
||||||
|
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||||
|
func (a activityDrawReceiptsDo) Save(values ...*model.ActivityDrawReceipts) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.DO.Save(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) First() (*model.ActivityDrawReceipts, error) {
|
||||||
|
if result, err := a.DO.First(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.ActivityDrawReceipts), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Take() (*model.ActivityDrawReceipts, error) {
|
||||||
|
if result, err := a.DO.Take(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.ActivityDrawReceipts), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Last() (*model.ActivityDrawReceipts, error) {
|
||||||
|
if result, err := a.DO.Last(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.ActivityDrawReceipts), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Find() ([]*model.ActivityDrawReceipts, error) {
|
||||||
|
result, err := a.DO.Find()
|
||||||
|
return result.([]*model.ActivityDrawReceipts), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.ActivityDrawReceipts, err error) {
|
||||||
|
buf := make([]*model.ActivityDrawReceipts, 0, batchSize)
|
||||||
|
err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||||
|
defer func() { results = append(results, buf...) }()
|
||||||
|
return fc(tx, batch)
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) FindInBatches(result *[]*model.ActivityDrawReceipts, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||||
|
return a.DO.FindInBatches(result, batchSize, fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Attrs(attrs ...field.AssignExpr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Attrs(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Assign(attrs ...field.AssignExpr) *activityDrawReceiptsDo {
|
||||||
|
return a.withDO(a.DO.Assign(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Joins(fields ...field.RelationField) *activityDrawReceiptsDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
a = *a.withDO(a.DO.Joins(_f))
|
||||||
|
}
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Preload(fields ...field.RelationField) *activityDrawReceiptsDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
a = *a.withDO(a.DO.Preload(_f))
|
||||||
|
}
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) FirstOrInit() (*model.ActivityDrawReceipts, error) {
|
||||||
|
if result, err := a.DO.FirstOrInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.ActivityDrawReceipts), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) FirstOrCreate() (*model.ActivityDrawReceipts, error) {
|
||||||
|
if result, err := a.DO.FirstOrCreate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.ActivityDrawReceipts), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) FindByPage(offset int, limit int) (result []*model.ActivityDrawReceipts, count int64, err error) {
|
||||||
|
result, err = a.Offset(offset).Limit(limit).Find()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||||
|
count = int64(size + offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = a.Offset(-1).Limit(-1).Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||||
|
count, err = a.Count()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Offset(offset).Limit(limit).Scan(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Scan(result interface{}) (err error) {
|
||||||
|
return a.DO.Scan(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a activityDrawReceiptsDo) Delete(models ...*model.ActivityDrawReceipts) (result gen.ResultInfo, err error) {
|
||||||
|
return a.DO.Delete(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *activityDrawReceiptsDo) withDO(do gen.Dao) *activityDrawReceiptsDo {
|
||||||
|
a.DO = *do.(*gen.DO)
|
||||||
|
return a
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@ var (
|
|||||||
ActivityCategories *activityCategories
|
ActivityCategories *activityCategories
|
||||||
ActivityDrawEffects *activityDrawEffects
|
ActivityDrawEffects *activityDrawEffects
|
||||||
ActivityDrawLogs *activityDrawLogs
|
ActivityDrawLogs *activityDrawLogs
|
||||||
|
ActivityDrawReceipts *activityDrawReceipts
|
||||||
ActivityIssues *activityIssues
|
ActivityIssues *activityIssues
|
||||||
ActivityRewardSettings *activityRewardSettings
|
ActivityRewardSettings *activityRewardSettings
|
||||||
Admin *admin
|
Admin *admin
|
||||||
@ -29,6 +30,7 @@ var (
|
|||||||
GuildBoxes *guildBoxes
|
GuildBoxes *guildBoxes
|
||||||
GuildContributeLogs *guildContributeLogs
|
GuildContributeLogs *guildContributeLogs
|
||||||
GuildMembers *guildMembers
|
GuildMembers *guildMembers
|
||||||
|
IssueRandomCommitments *issueRandomCommitments
|
||||||
LogOperation *logOperation
|
LogOperation *logOperation
|
||||||
LogRequest *logRequest
|
LogRequest *logRequest
|
||||||
MenuActions *menuActions
|
MenuActions *menuActions
|
||||||
@ -61,6 +63,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
|||||||
ActivityCategories = &Q.ActivityCategories
|
ActivityCategories = &Q.ActivityCategories
|
||||||
ActivityDrawEffects = &Q.ActivityDrawEffects
|
ActivityDrawEffects = &Q.ActivityDrawEffects
|
||||||
ActivityDrawLogs = &Q.ActivityDrawLogs
|
ActivityDrawLogs = &Q.ActivityDrawLogs
|
||||||
|
ActivityDrawReceipts = &Q.ActivityDrawReceipts
|
||||||
ActivityIssues = &Q.ActivityIssues
|
ActivityIssues = &Q.ActivityIssues
|
||||||
ActivityRewardSettings = &Q.ActivityRewardSettings
|
ActivityRewardSettings = &Q.ActivityRewardSettings
|
||||||
Admin = &Q.Admin
|
Admin = &Q.Admin
|
||||||
@ -69,6 +72,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
|||||||
GuildBoxes = &Q.GuildBoxes
|
GuildBoxes = &Q.GuildBoxes
|
||||||
GuildContributeLogs = &Q.GuildContributeLogs
|
GuildContributeLogs = &Q.GuildContributeLogs
|
||||||
GuildMembers = &Q.GuildMembers
|
GuildMembers = &Q.GuildMembers
|
||||||
|
IssueRandomCommitments = &Q.IssueRandomCommitments
|
||||||
LogOperation = &Q.LogOperation
|
LogOperation = &Q.LogOperation
|
||||||
LogRequest = &Q.LogRequest
|
LogRequest = &Q.LogRequest
|
||||||
MenuActions = &Q.MenuActions
|
MenuActions = &Q.MenuActions
|
||||||
@ -102,6 +106,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
|||||||
ActivityCategories: newActivityCategories(db, opts...),
|
ActivityCategories: newActivityCategories(db, opts...),
|
||||||
ActivityDrawEffects: newActivityDrawEffects(db, opts...),
|
ActivityDrawEffects: newActivityDrawEffects(db, opts...),
|
||||||
ActivityDrawLogs: newActivityDrawLogs(db, opts...),
|
ActivityDrawLogs: newActivityDrawLogs(db, opts...),
|
||||||
|
ActivityDrawReceipts: newActivityDrawReceipts(db, opts...),
|
||||||
ActivityIssues: newActivityIssues(db, opts...),
|
ActivityIssues: newActivityIssues(db, opts...),
|
||||||
ActivityRewardSettings: newActivityRewardSettings(db, opts...),
|
ActivityRewardSettings: newActivityRewardSettings(db, opts...),
|
||||||
Admin: newAdmin(db, opts...),
|
Admin: newAdmin(db, opts...),
|
||||||
@ -110,6 +115,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
|||||||
GuildBoxes: newGuildBoxes(db, opts...),
|
GuildBoxes: newGuildBoxes(db, opts...),
|
||||||
GuildContributeLogs: newGuildContributeLogs(db, opts...),
|
GuildContributeLogs: newGuildContributeLogs(db, opts...),
|
||||||
GuildMembers: newGuildMembers(db, opts...),
|
GuildMembers: newGuildMembers(db, opts...),
|
||||||
|
IssueRandomCommitments: newIssueRandomCommitments(db, opts...),
|
||||||
LogOperation: newLogOperation(db, opts...),
|
LogOperation: newLogOperation(db, opts...),
|
||||||
LogRequest: newLogRequest(db, opts...),
|
LogRequest: newLogRequest(db, opts...),
|
||||||
MenuActions: newMenuActions(db, opts...),
|
MenuActions: newMenuActions(db, opts...),
|
||||||
@ -144,6 +150,7 @@ type Query struct {
|
|||||||
ActivityCategories activityCategories
|
ActivityCategories activityCategories
|
||||||
ActivityDrawEffects activityDrawEffects
|
ActivityDrawEffects activityDrawEffects
|
||||||
ActivityDrawLogs activityDrawLogs
|
ActivityDrawLogs activityDrawLogs
|
||||||
|
ActivityDrawReceipts activityDrawReceipts
|
||||||
ActivityIssues activityIssues
|
ActivityIssues activityIssues
|
||||||
ActivityRewardSettings activityRewardSettings
|
ActivityRewardSettings activityRewardSettings
|
||||||
Admin admin
|
Admin admin
|
||||||
@ -152,6 +159,7 @@ type Query struct {
|
|||||||
GuildBoxes guildBoxes
|
GuildBoxes guildBoxes
|
||||||
GuildContributeLogs guildContributeLogs
|
GuildContributeLogs guildContributeLogs
|
||||||
GuildMembers guildMembers
|
GuildMembers guildMembers
|
||||||
|
IssueRandomCommitments issueRandomCommitments
|
||||||
LogOperation logOperation
|
LogOperation logOperation
|
||||||
LogRequest logRequest
|
LogRequest logRequest
|
||||||
MenuActions menuActions
|
MenuActions menuActions
|
||||||
@ -187,6 +195,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
|||||||
ActivityCategories: q.ActivityCategories.clone(db),
|
ActivityCategories: q.ActivityCategories.clone(db),
|
||||||
ActivityDrawEffects: q.ActivityDrawEffects.clone(db),
|
ActivityDrawEffects: q.ActivityDrawEffects.clone(db),
|
||||||
ActivityDrawLogs: q.ActivityDrawLogs.clone(db),
|
ActivityDrawLogs: q.ActivityDrawLogs.clone(db),
|
||||||
|
ActivityDrawReceipts: q.ActivityDrawReceipts.clone(db),
|
||||||
ActivityIssues: q.ActivityIssues.clone(db),
|
ActivityIssues: q.ActivityIssues.clone(db),
|
||||||
ActivityRewardSettings: q.ActivityRewardSettings.clone(db),
|
ActivityRewardSettings: q.ActivityRewardSettings.clone(db),
|
||||||
Admin: q.Admin.clone(db),
|
Admin: q.Admin.clone(db),
|
||||||
@ -195,6 +204,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
|||||||
GuildBoxes: q.GuildBoxes.clone(db),
|
GuildBoxes: q.GuildBoxes.clone(db),
|
||||||
GuildContributeLogs: q.GuildContributeLogs.clone(db),
|
GuildContributeLogs: q.GuildContributeLogs.clone(db),
|
||||||
GuildMembers: q.GuildMembers.clone(db),
|
GuildMembers: q.GuildMembers.clone(db),
|
||||||
|
IssueRandomCommitments: q.IssueRandomCommitments.clone(db),
|
||||||
LogOperation: q.LogOperation.clone(db),
|
LogOperation: q.LogOperation.clone(db),
|
||||||
LogRequest: q.LogRequest.clone(db),
|
LogRequest: q.LogRequest.clone(db),
|
||||||
MenuActions: q.MenuActions.clone(db),
|
MenuActions: q.MenuActions.clone(db),
|
||||||
@ -237,6 +247,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
|||||||
ActivityCategories: q.ActivityCategories.replaceDB(db),
|
ActivityCategories: q.ActivityCategories.replaceDB(db),
|
||||||
ActivityDrawEffects: q.ActivityDrawEffects.replaceDB(db),
|
ActivityDrawEffects: q.ActivityDrawEffects.replaceDB(db),
|
||||||
ActivityDrawLogs: q.ActivityDrawLogs.replaceDB(db),
|
ActivityDrawLogs: q.ActivityDrawLogs.replaceDB(db),
|
||||||
|
ActivityDrawReceipts: q.ActivityDrawReceipts.replaceDB(db),
|
||||||
ActivityIssues: q.ActivityIssues.replaceDB(db),
|
ActivityIssues: q.ActivityIssues.replaceDB(db),
|
||||||
ActivityRewardSettings: q.ActivityRewardSettings.replaceDB(db),
|
ActivityRewardSettings: q.ActivityRewardSettings.replaceDB(db),
|
||||||
Admin: q.Admin.replaceDB(db),
|
Admin: q.Admin.replaceDB(db),
|
||||||
@ -245,6 +256,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
|||||||
GuildBoxes: q.GuildBoxes.replaceDB(db),
|
GuildBoxes: q.GuildBoxes.replaceDB(db),
|
||||||
GuildContributeLogs: q.GuildContributeLogs.replaceDB(db),
|
GuildContributeLogs: q.GuildContributeLogs.replaceDB(db),
|
||||||
GuildMembers: q.GuildMembers.replaceDB(db),
|
GuildMembers: q.GuildMembers.replaceDB(db),
|
||||||
|
IssueRandomCommitments: q.IssueRandomCommitments.replaceDB(db),
|
||||||
LogOperation: q.LogOperation.replaceDB(db),
|
LogOperation: q.LogOperation.replaceDB(db),
|
||||||
LogRequest: q.LogRequest.replaceDB(db),
|
LogRequest: q.LogRequest.replaceDB(db),
|
||||||
MenuActions: q.MenuActions.replaceDB(db),
|
MenuActions: q.MenuActions.replaceDB(db),
|
||||||
@ -277,6 +289,7 @@ type queryCtx struct {
|
|||||||
ActivityCategories *activityCategoriesDo
|
ActivityCategories *activityCategoriesDo
|
||||||
ActivityDrawEffects *activityDrawEffectsDo
|
ActivityDrawEffects *activityDrawEffectsDo
|
||||||
ActivityDrawLogs *activityDrawLogsDo
|
ActivityDrawLogs *activityDrawLogsDo
|
||||||
|
ActivityDrawReceipts *activityDrawReceiptsDo
|
||||||
ActivityIssues *activityIssuesDo
|
ActivityIssues *activityIssuesDo
|
||||||
ActivityRewardSettings *activityRewardSettingsDo
|
ActivityRewardSettings *activityRewardSettingsDo
|
||||||
Admin *adminDo
|
Admin *adminDo
|
||||||
@ -285,6 +298,7 @@ type queryCtx struct {
|
|||||||
GuildBoxes *guildBoxesDo
|
GuildBoxes *guildBoxesDo
|
||||||
GuildContributeLogs *guildContributeLogsDo
|
GuildContributeLogs *guildContributeLogsDo
|
||||||
GuildMembers *guildMembersDo
|
GuildMembers *guildMembersDo
|
||||||
|
IssueRandomCommitments *issueRandomCommitmentsDo
|
||||||
LogOperation *logOperationDo
|
LogOperation *logOperationDo
|
||||||
LogRequest *logRequestDo
|
LogRequest *logRequestDo
|
||||||
MenuActions *menuActionsDo
|
MenuActions *menuActionsDo
|
||||||
@ -317,6 +331,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
|||||||
ActivityCategories: q.ActivityCategories.WithContext(ctx),
|
ActivityCategories: q.ActivityCategories.WithContext(ctx),
|
||||||
ActivityDrawEffects: q.ActivityDrawEffects.WithContext(ctx),
|
ActivityDrawEffects: q.ActivityDrawEffects.WithContext(ctx),
|
||||||
ActivityDrawLogs: q.ActivityDrawLogs.WithContext(ctx),
|
ActivityDrawLogs: q.ActivityDrawLogs.WithContext(ctx),
|
||||||
|
ActivityDrawReceipts: q.ActivityDrawReceipts.WithContext(ctx),
|
||||||
ActivityIssues: q.ActivityIssues.WithContext(ctx),
|
ActivityIssues: q.ActivityIssues.WithContext(ctx),
|
||||||
ActivityRewardSettings: q.ActivityRewardSettings.WithContext(ctx),
|
ActivityRewardSettings: q.ActivityRewardSettings.WithContext(ctx),
|
||||||
Admin: q.Admin.WithContext(ctx),
|
Admin: q.Admin.WithContext(ctx),
|
||||||
@ -325,6 +340,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
|||||||
GuildBoxes: q.GuildBoxes.WithContext(ctx),
|
GuildBoxes: q.GuildBoxes.WithContext(ctx),
|
||||||
GuildContributeLogs: q.GuildContributeLogs.WithContext(ctx),
|
GuildContributeLogs: q.GuildContributeLogs.WithContext(ctx),
|
||||||
GuildMembers: q.GuildMembers.WithContext(ctx),
|
GuildMembers: q.GuildMembers.WithContext(ctx),
|
||||||
|
IssueRandomCommitments: q.IssueRandomCommitments.WithContext(ctx),
|
||||||
LogOperation: q.LogOperation.WithContext(ctx),
|
LogOperation: q.LogOperation.WithContext(ctx),
|
||||||
LogRequest: q.LogRequest.WithContext(ctx),
|
LogRequest: q.LogRequest.WithContext(ctx),
|
||||||
MenuActions: q.MenuActions.WithContext(ctx),
|
MenuActions: q.MenuActions.WithContext(ctx),
|
||||||
|
|||||||
355
internal/repository/mysql/dao/issue_random_commitments.gen.go
Normal file
355
internal/repository/mysql/dao/issue_random_commitments.gen.go
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
|
||||||
|
"gorm.io/plugin/dbresolver"
|
||||||
|
|
||||||
|
"bindbox-game/internal/repository/mysql/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newIssueRandomCommitments(db *gorm.DB, opts ...gen.DOOption) issueRandomCommitments {
|
||||||
|
_issueRandomCommitments := issueRandomCommitments{}
|
||||||
|
|
||||||
|
_issueRandomCommitments.issueRandomCommitmentsDo.UseDB(db, opts...)
|
||||||
|
_issueRandomCommitments.issueRandomCommitmentsDo.UseModel(&model.IssueRandomCommitments{})
|
||||||
|
|
||||||
|
tableName := _issueRandomCommitments.issueRandomCommitmentsDo.TableName()
|
||||||
|
_issueRandomCommitments.ALL = field.NewAsterisk(tableName)
|
||||||
|
_issueRandomCommitments.ID = field.NewInt64(tableName, "id")
|
||||||
|
_issueRandomCommitments.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
|
_issueRandomCommitments.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
|
_issueRandomCommitments.IssueID = field.NewInt64(tableName, "issue_id")
|
||||||
|
_issueRandomCommitments.AlgoVersion = field.NewString(tableName, "algo_version")
|
||||||
|
_issueRandomCommitments.ServerSeedMaster = field.NewBytes(tableName, "server_seed_master")
|
||||||
|
_issueRandomCommitments.ServerSeedHash = field.NewBytes(tableName, "server_seed_hash")
|
||||||
|
_issueRandomCommitments.ItemsRoot = field.NewBytes(tableName, "items_root")
|
||||||
|
_issueRandomCommitments.WeightsTotal = field.NewInt64(tableName, "weights_total")
|
||||||
|
_issueRandomCommitments.StateVersion = field.NewInt32(tableName, "state_version")
|
||||||
|
|
||||||
|
_issueRandomCommitments.fillFieldMap()
|
||||||
|
|
||||||
|
return _issueRandomCommitments
|
||||||
|
}
|
||||||
|
|
||||||
|
type issueRandomCommitments struct {
|
||||||
|
issueRandomCommitmentsDo
|
||||||
|
|
||||||
|
ALL field.Asterisk
|
||||||
|
ID field.Int64
|
||||||
|
CreatedAt field.Time
|
||||||
|
UpdatedAt field.Time
|
||||||
|
IssueID field.Int64
|
||||||
|
AlgoVersion field.String
|
||||||
|
ServerSeedMaster field.Bytes
|
||||||
|
ServerSeedHash field.Bytes
|
||||||
|
ItemsRoot field.Bytes
|
||||||
|
WeightsTotal field.Int64
|
||||||
|
StateVersion field.Int32
|
||||||
|
|
||||||
|
fieldMap map[string]field.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitments) Table(newTableName string) *issueRandomCommitments {
|
||||||
|
i.issueRandomCommitmentsDo.UseTable(newTableName)
|
||||||
|
return i.updateTableName(newTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitments) As(alias string) *issueRandomCommitments {
|
||||||
|
i.issueRandomCommitmentsDo.DO = *(i.issueRandomCommitmentsDo.As(alias).(*gen.DO))
|
||||||
|
return i.updateTableName(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *issueRandomCommitments) updateTableName(table string) *issueRandomCommitments {
|
||||||
|
i.ALL = field.NewAsterisk(table)
|
||||||
|
i.ID = field.NewInt64(table, "id")
|
||||||
|
i.CreatedAt = field.NewTime(table, "created_at")
|
||||||
|
i.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
|
i.IssueID = field.NewInt64(table, "issue_id")
|
||||||
|
i.AlgoVersion = field.NewString(table, "algo_version")
|
||||||
|
i.ServerSeedMaster = field.NewBytes(table, "server_seed_master")
|
||||||
|
i.ServerSeedHash = field.NewBytes(table, "server_seed_hash")
|
||||||
|
i.ItemsRoot = field.NewBytes(table, "items_root")
|
||||||
|
i.WeightsTotal = field.NewInt64(table, "weights_total")
|
||||||
|
i.StateVersion = field.NewInt32(table, "state_version")
|
||||||
|
|
||||||
|
i.fillFieldMap()
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *issueRandomCommitments) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
|
_f, ok := i.fieldMap[fieldName]
|
||||||
|
if !ok || _f == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_oe, ok := _f.(field.OrderExpr)
|
||||||
|
return _oe, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *issueRandomCommitments) fillFieldMap() {
|
||||||
|
i.fieldMap = make(map[string]field.Expr, 10)
|
||||||
|
i.fieldMap["id"] = i.ID
|
||||||
|
i.fieldMap["created_at"] = i.CreatedAt
|
||||||
|
i.fieldMap["updated_at"] = i.UpdatedAt
|
||||||
|
i.fieldMap["issue_id"] = i.IssueID
|
||||||
|
i.fieldMap["algo_version"] = i.AlgoVersion
|
||||||
|
i.fieldMap["server_seed_master"] = i.ServerSeedMaster
|
||||||
|
i.fieldMap["server_seed_hash"] = i.ServerSeedHash
|
||||||
|
i.fieldMap["items_root"] = i.ItemsRoot
|
||||||
|
i.fieldMap["weights_total"] = i.WeightsTotal
|
||||||
|
i.fieldMap["state_version"] = i.StateVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitments) clone(db *gorm.DB) issueRandomCommitments {
|
||||||
|
i.issueRandomCommitmentsDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitments) replaceDB(db *gorm.DB) issueRandomCommitments {
|
||||||
|
i.issueRandomCommitmentsDo.ReplaceDB(db)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
type issueRandomCommitmentsDo struct{ gen.DO }
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Debug() *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Debug())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) WithContext(ctx context.Context) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) ReadDB() *issueRandomCommitmentsDo {
|
||||||
|
return i.Clauses(dbresolver.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) WriteDB() *issueRandomCommitmentsDo {
|
||||||
|
return i.Clauses(dbresolver.Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Session(config *gorm.Session) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Session(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Clauses(conds ...clause.Expression) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Clauses(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Returning(value interface{}, columns ...string) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Returning(value, columns...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Not(conds ...gen.Condition) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Not(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Or(conds ...gen.Condition) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Or(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Select(conds ...field.Expr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Select(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Where(conds ...gen.Condition) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Where(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Order(conds ...field.Expr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Order(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Distinct(cols ...field.Expr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Distinct(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Omit(cols ...field.Expr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Omit(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Join(table schema.Tabler, on ...field.Expr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Join(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) LeftJoin(table schema.Tabler, on ...field.Expr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.LeftJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) RightJoin(table schema.Tabler, on ...field.Expr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.RightJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Group(cols ...field.Expr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Group(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Having(conds ...gen.Condition) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Having(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Limit(limit int) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Limit(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Offset(offset int) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Offset(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Scopes(funcs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Unscoped() *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Unscoped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Create(values ...*model.IssueRandomCommitments) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return i.DO.Create(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) CreateInBatches(values []*model.IssueRandomCommitments, batchSize int) error {
|
||||||
|
return i.DO.CreateInBatches(values, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save : !!! underlying implementation is different with GORM
|
||||||
|
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||||
|
func (i issueRandomCommitmentsDo) Save(values ...*model.IssueRandomCommitments) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return i.DO.Save(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) First() (*model.IssueRandomCommitments, error) {
|
||||||
|
if result, err := i.DO.First(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.IssueRandomCommitments), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Take() (*model.IssueRandomCommitments, error) {
|
||||||
|
if result, err := i.DO.Take(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.IssueRandomCommitments), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Last() (*model.IssueRandomCommitments, error) {
|
||||||
|
if result, err := i.DO.Last(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.IssueRandomCommitments), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Find() ([]*model.IssueRandomCommitments, error) {
|
||||||
|
result, err := i.DO.Find()
|
||||||
|
return result.([]*model.IssueRandomCommitments), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.IssueRandomCommitments, err error) {
|
||||||
|
buf := make([]*model.IssueRandomCommitments, 0, batchSize)
|
||||||
|
err = i.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||||
|
defer func() { results = append(results, buf...) }()
|
||||||
|
return fc(tx, batch)
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) FindInBatches(result *[]*model.IssueRandomCommitments, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||||
|
return i.DO.FindInBatches(result, batchSize, fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Attrs(attrs ...field.AssignExpr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Attrs(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Assign(attrs ...field.AssignExpr) *issueRandomCommitmentsDo {
|
||||||
|
return i.withDO(i.DO.Assign(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Joins(fields ...field.RelationField) *issueRandomCommitmentsDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
i = *i.withDO(i.DO.Joins(_f))
|
||||||
|
}
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Preload(fields ...field.RelationField) *issueRandomCommitmentsDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
i = *i.withDO(i.DO.Preload(_f))
|
||||||
|
}
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) FirstOrInit() (*model.IssueRandomCommitments, error) {
|
||||||
|
if result, err := i.DO.FirstOrInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.IssueRandomCommitments), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) FirstOrCreate() (*model.IssueRandomCommitments, error) {
|
||||||
|
if result, err := i.DO.FirstOrCreate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.IssueRandomCommitments), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) FindByPage(offset int, limit int) (result []*model.IssueRandomCommitments, count int64, err error) {
|
||||||
|
result, err = i.Offset(offset).Limit(limit).Find()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||||
|
count = int64(size + offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = i.Offset(-1).Limit(-1).Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||||
|
count, err = i.Count()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.Offset(offset).Limit(limit).Scan(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Scan(result interface{}) (err error) {
|
||||||
|
return i.DO.Scan(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i issueRandomCommitmentsDo) Delete(models ...*model.IssueRandomCommitments) (result gen.ResultInfo, err error) {
|
||||||
|
return i.DO.Delete(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *issueRandomCommitmentsDo) withDO(do gen.Dao) *issueRandomCommitmentsDo {
|
||||||
|
i.DO = *do.(*gen.DO)
|
||||||
|
return i
|
||||||
|
}
|
||||||
@ -40,6 +40,7 @@ func newSystemCoupons(db *gorm.DB, opts ...gen.DOOption) systemCoupons {
|
|||||||
_systemCoupons.ValidStart = field.NewTime(tableName, "valid_start")
|
_systemCoupons.ValidStart = field.NewTime(tableName, "valid_start")
|
||||||
_systemCoupons.ValidEnd = field.NewTime(tableName, "valid_end")
|
_systemCoupons.ValidEnd = field.NewTime(tableName, "valid_end")
|
||||||
_systemCoupons.Status = field.NewInt32(tableName, "status")
|
_systemCoupons.Status = field.NewInt32(tableName, "status")
|
||||||
|
_systemCoupons.TotalQuantity = field.NewInt64(tableName, "total_quantity")
|
||||||
|
|
||||||
_systemCoupons.fillFieldMap()
|
_systemCoupons.fillFieldMap()
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ type systemCoupons struct {
|
|||||||
ValidStart field.Time // 有效期开始
|
ValidStart field.Time // 有效期开始
|
||||||
ValidEnd field.Time // 有效期结束
|
ValidEnd field.Time // 有效期结束
|
||||||
Status field.Int32 // 状态:1启用 2停用
|
Status field.Int32 // 状态:1启用 2停用
|
||||||
|
TotalQuantity field.Int64
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@ -93,6 +95,7 @@ func (s *systemCoupons) updateTableName(table string) *systemCoupons {
|
|||||||
s.ValidStart = field.NewTime(table, "valid_start")
|
s.ValidStart = field.NewTime(table, "valid_start")
|
||||||
s.ValidEnd = field.NewTime(table, "valid_end")
|
s.ValidEnd = field.NewTime(table, "valid_end")
|
||||||
s.Status = field.NewInt32(table, "status")
|
s.Status = field.NewInt32(table, "status")
|
||||||
|
s.TotalQuantity = field.NewInt64(table, "total_quantity")
|
||||||
|
|
||||||
s.fillFieldMap()
|
s.fillFieldMap()
|
||||||
|
|
||||||
@ -109,7 +112,7 @@ func (s *systemCoupons) GetFieldByName(fieldName string) (field.OrderExpr, bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *systemCoupons) fillFieldMap() {
|
func (s *systemCoupons) fillFieldMap() {
|
||||||
s.fieldMap = make(map[string]field.Expr, 13)
|
s.fieldMap = make(map[string]field.Expr, 14)
|
||||||
s.fieldMap["id"] = s.ID
|
s.fieldMap["id"] = s.ID
|
||||||
s.fieldMap["created_at"] = s.CreatedAt
|
s.fieldMap["created_at"] = s.CreatedAt
|
||||||
s.fieldMap["updated_at"] = s.UpdatedAt
|
s.fieldMap["updated_at"] = s.UpdatedAt
|
||||||
@ -123,6 +126,7 @@ func (s *systemCoupons) fillFieldMap() {
|
|||||||
s.fieldMap["valid_start"] = s.ValidStart
|
s.fieldMap["valid_start"] = s.ValidStart
|
||||||
s.fieldMap["valid_end"] = s.ValidEnd
|
s.fieldMap["valid_end"] = s.ValidEnd
|
||||||
s.fieldMap["status"] = s.Status
|
s.fieldMap["status"] = s.Status
|
||||||
|
s.fieldMap["total_quantity"] = s.TotalQuantity
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s systemCoupons) clone(db *gorm.DB) systemCoupons {
|
func (s systemCoupons) clone(db *gorm.DB) systemCoupons {
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameActivityDrawReceipts = "activity_draw_receipts"
|
||||||
|
|
||||||
|
// ActivityDrawReceipts 活动抽奖凭据表
|
||||||
|
type ActivityDrawReceipts struct {
|
||||||
|
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:主键ID" json:"id"` // 主键ID
|
||||||
|
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"created_at"` // 创建时间
|
||||||
|
DrawLogID int64 `gorm:"column:draw_log_id;not null;comment:抽奖日志ID(activity_draw_logs.id)" json:"draw_log_id"` // 抽奖日志ID(activity_draw_logs.id)
|
||||||
|
AlgoVersion string `gorm:"column:algo_version;not null;comment:算法版本" json:"algo_version"` // 算法版本
|
||||||
|
RoundID int64 `gorm:"column:round_id;not null;comment:期ID" json:"round_id"` // 期ID
|
||||||
|
DrawID int64 `gorm:"column:draw_id;not null;comment:抽奖ID" json:"draw_id"` // 抽奖ID
|
||||||
|
ClientID int64 `gorm:"column:client_id;not null;comment:客户端ID" json:"client_id"` // 客户端ID
|
||||||
|
Timestamp int64 `gorm:"column:timestamp;not null;comment:时间戳(毫秒)" json:"timestamp"` // 时间戳(毫秒)
|
||||||
|
ServerSeedHash string `gorm:"column:server_seed_hash;not null;comment:服务器种子哈希(十六进制)" json:"server_seed_hash"` // 服务器种子哈希(十六进制)
|
||||||
|
ServerSubSeed string `gorm:"column:server_sub_seed;not null;comment:服务器子种子(十六进制)" json:"server_sub_seed"` // 服务器子种子(十六进制)
|
||||||
|
ClientSeed string `gorm:"column:client_seed;not null;comment:客户端种子(十六进制)" json:"client_seed"` // 客户端种子(十六进制)
|
||||||
|
Nonce int64 `gorm:"column:nonce;not null;comment:随机数" json:"nonce"` // 随机数
|
||||||
|
ItemsRoot string `gorm:"column:items_root;not null;comment:项目根哈希(十六进制)" json:"items_root"` // 项目根哈希(十六进制)
|
||||||
|
WeightsTotal int64 `gorm:"column:weights_total;not null;comment:权重总和" json:"weights_total"` // 权重总和
|
||||||
|
SelectedIndex int32 `gorm:"column:selected_index;not null;comment:选中索引" json:"selected_index"` // 选中索引
|
||||||
|
RandProof string `gorm:"column:rand_proof;not null;comment:随机证明(十六进制)" json:"rand_proof"` // 随机证明(十六进制)
|
||||||
|
Signature string `gorm:"column:signature;comment:签名(十六进制,可选)" json:"signature"` // 签名(十六进制,可选)
|
||||||
|
ItemsSnapshot string `gorm:"column:items_snapshot;comment:项目快照(JSON格式)" json:"items_snapshot"` // 项目快照(JSON格式)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName ActivityDrawReceipts's table name
|
||||||
|
func (*ActivityDrawReceipts) TableName() string {
|
||||||
|
return TableNameActivityDrawReceipts
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameIssueRandomCommitments = "issue_random_commitments"
|
||||||
|
|
||||||
|
// IssueRandomCommitments mapped from table <issue_random_commitments>
|
||||||
|
type IssueRandomCommitments struct {
|
||||||
|
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
|
||||||
|
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP(3)" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP(3)" json:"updated_at"`
|
||||||
|
IssueID int64 `gorm:"column:issue_id;not null" json:"issue_id"`
|
||||||
|
AlgoVersion string `gorm:"column:algo_version;not null" json:"algo_version"`
|
||||||
|
ServerSeedMaster []byte `gorm:"column:server_seed_master;not null" json:"server_seed_master"`
|
||||||
|
ServerSeedHash []byte `gorm:"column:server_seed_hash;not null" json:"server_seed_hash"`
|
||||||
|
ItemsRoot []byte `gorm:"column:items_root;not null" json:"items_root"`
|
||||||
|
WeightsTotal int64 `gorm:"column:weights_total;not null" json:"weights_total"`
|
||||||
|
StateVersion int32 `gorm:"column:state_version;not null" json:"state_version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName IssueRandomCommitments's table name
|
||||||
|
func (*IssueRandomCommitments) TableName() string {
|
||||||
|
return TableNameIssueRandomCommitments
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ type SystemCoupons struct {
|
|||||||
ValidStart time.Time `gorm:"column:valid_start;comment:有效期开始" json:"valid_start"` // 有效期开始
|
ValidStart time.Time `gorm:"column:valid_start;comment:有效期开始" json:"valid_start"` // 有效期开始
|
||||||
ValidEnd time.Time `gorm:"column:valid_end;comment:有效期结束" json:"valid_end"` // 有效期结束
|
ValidEnd time.Time `gorm:"column:valid_end;comment:有效期结束" json:"valid_end"` // 有效期结束
|
||||||
Status int32 `gorm:"column:status;not null;default:1;comment:状态:1启用 2停用" json:"status"` // 状态:1启用 2停用
|
Status int32 `gorm:"column:status;not null;default:1;comment:状态:1启用 2停用" json:"status"` // 状态:1启用 2停用
|
||||||
|
TotalQuantity int64 `gorm:"column:total_quantity" json:"total_quantity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName SystemCoupons's table name
|
// TableName SystemCoupons's table name
|
||||||
|
|||||||
@ -82,6 +82,17 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, error) {
|
|||||||
adminAuthApiRouter.PUT("/activities/:activity_id/issues/:issue_id/rewards/:reward_id", adminHandler.ModifyIssueReward())
|
adminAuthApiRouter.PUT("/activities/:activity_id/issues/:issue_id/rewards/:reward_id", adminHandler.ModifyIssueReward())
|
||||||
adminAuthApiRouter.DELETE("/activities/:activity_id/issues/:issue_id/rewards/:reward_id", adminHandler.DeleteIssueReward())
|
adminAuthApiRouter.DELETE("/activities/:activity_id/issues/:issue_id/rewards/:reward_id", adminHandler.DeleteIssueReward())
|
||||||
|
|
||||||
|
adminAuthApiRouter.POST("/activities/:activity_id/issues/:issue_id/commit_random", adminHandler.CommitIssueRandom())
|
||||||
|
adminAuthApiRouter.GET("/activities/:activity_id/issues/:issue_id/commit_random", adminHandler.GetIssueRandomCommit())
|
||||||
|
adminAuthApiRouter.GET("/activities/:activity_id/issues/:issue_id/commit_random/history", adminHandler.GetIssueRandomCommitHistory())
|
||||||
|
adminAuthApiRouter.POST("/activities/:activity_id/issues/:issue_id/simulate_draw", adminHandler.SimulateIssueDraw())
|
||||||
|
adminAuthApiRouter.POST("/activities/:activity_id/issues/:issue_id/batch_draw", adminHandler.BatchDrawForUsers())
|
||||||
|
adminAuthApiRouter.POST("/activities/:activity_id/issues/:issue_id/verify_draw", adminHandler.VerifyDrawReceipt())
|
||||||
|
adminAuthApiRouter.GET("/draw_receipts/:draw_id", adminHandler.GetDrawReceipt())
|
||||||
|
adminAuthApiRouter.GET("/draw_receipts/log/:log_id", adminHandler.GetDrawReceiptByLogID())
|
||||||
|
adminAuthApiRouter.POST("/batch_users", adminHandler.BatchCreateUsers())
|
||||||
|
adminAuthApiRouter.DELETE("/batch_users", adminHandler.BatchDeleteUsers())
|
||||||
|
|
||||||
adminAuthApiRouter.POST("/guilds", adminHandler.CreateGuild())
|
adminAuthApiRouter.POST("/guilds", adminHandler.CreateGuild())
|
||||||
adminAuthApiRouter.PUT("/guilds/:guild_id", adminHandler.ModifyGuild())
|
adminAuthApiRouter.PUT("/guilds/:guild_id", adminHandler.ModifyGuild())
|
||||||
adminAuthApiRouter.DELETE("/guilds/:guild_id", adminHandler.DeleteGuild())
|
adminAuthApiRouter.DELETE("/guilds/:guild_id", adminHandler.DeleteGuild())
|
||||||
@ -192,6 +203,8 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, error) {
|
|||||||
appAuthApiRouter.GET("/users/:user_id/addresses", userHandler.ListUserAddresses())
|
appAuthApiRouter.GET("/users/:user_id/addresses", userHandler.ListUserAddresses())
|
||||||
appAuthApiRouter.PUT("/users/:user_id/addresses/:address_id/default", userHandler.SetDefaultUserAddress())
|
appAuthApiRouter.PUT("/users/:user_id/addresses/:address_id/default", userHandler.SetDefaultUserAddress())
|
||||||
appAuthApiRouter.DELETE("/users/:user_id/addresses/:address_id", userHandler.DeleteUserAddress())
|
appAuthApiRouter.DELETE("/users/:user_id/addresses/:address_id", userHandler.DeleteUserAddress())
|
||||||
|
|
||||||
|
appAuthApiRouter.POST("/activities/:activity_id/issues/:issue_id/draw", activityHandler.ExecuteDraw())
|
||||||
}
|
}
|
||||||
|
|
||||||
return mux, nil
|
return mux, nil
|
||||||
|
|||||||
@ -29,9 +29,51 @@ type Service interface {
|
|||||||
|
|
||||||
ListDrawLogs(ctx context.Context, issueID int64, page, pageSize int) (items []*model.ActivityDrawLogs, total int64, err error)
|
ListDrawLogs(ctx context.Context, issueID int64, page, pageSize int) (items []*model.ActivityDrawLogs, total int64, err error)
|
||||||
|
|
||||||
|
CommitIssueRandom(ctx context.Context, issueID int64) (*IssueRandomCommitment, error)
|
||||||
|
GetIssueRandomCommit(ctx context.Context, issueID int64) (*IssueRandomCommitment, error)
|
||||||
|
GetIssueRandomCommitHistory(ctx context.Context, issueID int64) ([]*IssueRandomCommitment, error)
|
||||||
|
|
||||||
|
ExecuteDraw(ctx context.Context, issueID int64) (*Receipt, error)
|
||||||
|
|
||||||
GetCategoryNames(ctx context.Context, ids []int64) (map[int64]string, error)
|
GetCategoryNames(ctx context.Context, ids []int64) (map[int64]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IssueRandomCommitment struct {
|
||||||
|
AlgoVersion string
|
||||||
|
IssueID int64
|
||||||
|
ServerSeedMaster []byte
|
||||||
|
ServerSeedHash []byte
|
||||||
|
ItemsRoot []byte
|
||||||
|
WeightsTotal int64
|
||||||
|
StateVersion int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Receipt struct {
|
||||||
|
AlgoVersion string
|
||||||
|
RoundId int64
|
||||||
|
DrawId int64
|
||||||
|
ClientId int64
|
||||||
|
Timestamp int64
|
||||||
|
ServerSeedHash []byte
|
||||||
|
ServerSubSeed []byte
|
||||||
|
ClientSeed []byte
|
||||||
|
Nonce uint64
|
||||||
|
Items []ReceiptItem
|
||||||
|
ItemsRoot []byte
|
||||||
|
WeightsTotal uint64
|
||||||
|
SelectedIndex int
|
||||||
|
SelectedItemId int64
|
||||||
|
RandProof []byte
|
||||||
|
Signature []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReceiptItem struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
Weight int32
|
||||||
|
QuantityBefore int64
|
||||||
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
logger logger.CustomLogger
|
logger logger.CustomLogger
|
||||||
readDB *dao.Query
|
readDB *dao.Query
|
||||||
|
|||||||
145
internal/service/activity/draw_execute.go
Normal file
145
internal/service/activity/draw_execute.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *service) ExecuteDraw(ctx context.Context, issueID int64) (*Receipt, error) {
|
||||||
|
cm, err := s.GetIssueRandomCommit(ctx, issueID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cm == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
master := unmaskSeed(cm.ServerSeedMaster, cm.IssueID, cm.StateVersion)
|
||||||
|
items, err := s.readDB.ActivityRewardSettings.WithContext(ctx).
|
||||||
|
Where(s.readDB.ActivityRewardSettings.IssueID.Eq(issueID)).
|
||||||
|
Order(s.readDB.ActivityRewardSettings.Sort).
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var snapshot []ReceiptItem
|
||||||
|
var total int64
|
||||||
|
for _, it := range items {
|
||||||
|
snapshot = append(snapshot, ReceiptItem{ID: it.ID, Name: it.Name, Weight: it.Weight, QuantityBefore: it.Quantity})
|
||||||
|
if it.Weight > 0 && (it.Quantity == -1 || it.Quantity > 0) {
|
||||||
|
total += int64(it.Weight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if total <= 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
drawId := time.Now().UnixNano()
|
||||||
|
clientSeed := make([]byte, 32)
|
||||||
|
_, _ = rand.Read(clientSeed)
|
||||||
|
nonce := uint64(1)
|
||||||
|
subInput := make([]byte, 16)
|
||||||
|
binary.BigEndian.PutUint64(subInput[:8], uint64(issueID))
|
||||||
|
binary.BigEndian.PutUint64(subInput[8:16], uint64(drawId))
|
||||||
|
mac := hmac.New(sha256.New, master)
|
||||||
|
mac.Write(subInput)
|
||||||
|
serverSubSeed := mac.Sum(nil)
|
||||||
|
enc := encodeMessage(cm.AlgoVersion, issueID, drawId, 0, clientSeed, nonce, cm.ItemsRoot[:], uint64(total))
|
||||||
|
entropy := hmacSha256(serverSubSeed, enc)
|
||||||
|
pos, proof := rejectSample(entropy, serverSubSeed, enc, uint64(total))
|
||||||
|
var acc uint64
|
||||||
|
var selIndex int
|
||||||
|
var selID int64
|
||||||
|
for i, it := range items {
|
||||||
|
if it.Weight <= 0 || !(it.Quantity == -1 || it.Quantity > 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w := uint64(it.Weight)
|
||||||
|
if pos < acc+w {
|
||||||
|
selIndex = i
|
||||||
|
selID = it.ID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
acc += w
|
||||||
|
}
|
||||||
|
rec := &Receipt{
|
||||||
|
AlgoVersion: cm.AlgoVersion,
|
||||||
|
RoundId: issueID,
|
||||||
|
DrawId: drawId,
|
||||||
|
ClientId: 0,
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
ServerSeedHash: cm.ServerSeedHash[:],
|
||||||
|
ServerSubSeed: serverSubSeed,
|
||||||
|
ClientSeed: clientSeed,
|
||||||
|
Nonce: nonce,
|
||||||
|
Items: snapshot,
|
||||||
|
ItemsRoot: cm.ItemsRoot[:],
|
||||||
|
WeightsTotal: uint64(total),
|
||||||
|
SelectedIndex: selIndex,
|
||||||
|
SelectedItemId: selID,
|
||||||
|
RandProof: proof,
|
||||||
|
Signature: nil,
|
||||||
|
}
|
||||||
|
return rec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeMessage(algo string, roundId int64, drawId int64, clientId int64, clientSeed []byte, nonce uint64, itemsRoot []byte, weightsTotal uint64) []byte {
|
||||||
|
var buf []byte
|
||||||
|
buf = appendUint32String(buf, algo)
|
||||||
|
buf = appendUint64(buf, uint64(roundId))
|
||||||
|
buf = appendUint64(buf, uint64(drawId))
|
||||||
|
buf = appendUint64(buf, uint64(clientId))
|
||||||
|
buf = appendUint32Bytes(buf, clientSeed)
|
||||||
|
buf = appendUint64(buf, nonce)
|
||||||
|
buf = append(buf, itemsRoot...)
|
||||||
|
buf = appendUint64(buf, weightsTotal)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint32String(b []byte, s string) []byte {
|
||||||
|
bs := []byte(s)
|
||||||
|
nb := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(nb, uint32(len(bs)))
|
||||||
|
b = append(b, nb...)
|
||||||
|
b = append(b, bs...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint32Bytes(b []byte, bs []byte) []byte {
|
||||||
|
nb := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(nb, uint32(len(bs)))
|
||||||
|
b = append(b, nb...)
|
||||||
|
b = append(b, bs...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint64(b []byte, v uint64) []byte {
|
||||||
|
nb := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(nb, v)
|
||||||
|
b = append(b, nb...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func hmacSha256(key []byte, msg []byte) []byte {
|
||||||
|
m := hmac.New(sha256.New, key)
|
||||||
|
m.Write(msg)
|
||||||
|
return m.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rejectSample(entropy []byte, subSeed []byte, enc []byte, W uint64) (uint64, []byte) {
|
||||||
|
var counter uint64
|
||||||
|
for {
|
||||||
|
R := binary.BigEndian.Uint64(entropy[:8])
|
||||||
|
M := (uint64(^uint64(0)) / W) * W
|
||||||
|
if R < M {
|
||||||
|
return R % W, entropy
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
cenc := make([]byte, len(enc)+8)
|
||||||
|
copy(cenc, enc)
|
||||||
|
binary.BigEndian.PutUint64(cenc[len(enc):], counter)
|
||||||
|
entropy = hmacSha256(subSeed, cenc)
|
||||||
|
}
|
||||||
|
}
|
||||||
146
internal/service/activity/random_commit.go
Normal file
146
internal/service/activity/random_commit.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"bindbox-game/internal/repository/mysql/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var algoVersion = "v1-hmac-256"
|
||||||
|
|
||||||
|
func (s *service) CommitIssueRandom(ctx context.Context, issueID int64) (*IssueRandomCommitment, error) {
|
||||||
|
// 检查活动期数状态
|
||||||
|
issue, err := s.readDB.ActivityIssues.WithContext(ctx).
|
||||||
|
Where(s.readDB.ActivityIssues.ID.Eq(issueID)).
|
||||||
|
First()
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, errors.New("活动期数不存在")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只允许在未开始状态(3)生成承诺
|
||||||
|
if issue.Status != 3 {
|
||||||
|
return nil, errors.New("只能在期数未开始状态下生成随机承诺")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已存在承诺(防止重复生成)
|
||||||
|
existing, _ := s.readDB.IssueRandomCommitments.WithContext(ctx).
|
||||||
|
Where(s.readDB.IssueRandomCommitments.IssueID.Eq(issueID)).
|
||||||
|
First()
|
||||||
|
if existing != nil {
|
||||||
|
return nil, errors.New("该期数已存在随机承诺,不可重复生成")
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := s.readDB.ActivityRewardSettings.WithContext(ctx).
|
||||||
|
Where(s.readDB.ActivityRewardSettings.IssueID.Eq(issueID)).
|
||||||
|
Order(s.readDB.ActivityRewardSettings.ID).
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(items) == 0 {
|
||||||
|
return nil, errors.New("该期数未配置奖励,无法生成随机承诺")
|
||||||
|
}
|
||||||
|
canonical := make([]ReceiptItem, 0, len(items))
|
||||||
|
var weightsTotal int64
|
||||||
|
for _, it := range items {
|
||||||
|
canonical = append(canonical, ReceiptItem{ID: it.ID, Name: it.Name, Weight: it.Weight, QuantityBefore: it.Quantity})
|
||||||
|
if it.Weight > 0 && (it.Quantity == -1 || it.Quantity > 0) {
|
||||||
|
weightsTotal += int64(it.Weight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查奖池权重是否有效
|
||||||
|
if weightsTotal <= 0 {
|
||||||
|
return nil, errors.New("奖池配置无效:总权重必须大于0")
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(canonical, func(i, j int) bool { return canonical[i].ID < canonical[j].ID })
|
||||||
|
b, _ := json.Marshal(canonical)
|
||||||
|
itemsRoot := sha256.Sum256(b)
|
||||||
|
master := make([]byte, 32)
|
||||||
|
_, _ = rand.Read(master)
|
||||||
|
serverHash := sha256.Sum256(master)
|
||||||
|
|
||||||
|
nextVer := int32(1)
|
||||||
|
enc, _ := maskSeed(master, issueID, nextVer)
|
||||||
|
rec := &model.IssueRandomCommitments{
|
||||||
|
IssueID: issueID,
|
||||||
|
AlgoVersion: algoVersion,
|
||||||
|
ServerSeedMaster: enc,
|
||||||
|
ServerSeedHash: serverHash[:],
|
||||||
|
ItemsRoot: itemsRoot[:],
|
||||||
|
WeightsTotal: weightsTotal,
|
||||||
|
StateVersion: nextVer,
|
||||||
|
}
|
||||||
|
if err := s.writeDB.IssueRandomCommitments.WithContext(ctx).Create(rec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &IssueRandomCommitment{
|
||||||
|
AlgoVersion: rec.AlgoVersion,
|
||||||
|
IssueID: rec.IssueID,
|
||||||
|
ServerSeedMaster: rec.ServerSeedMaster,
|
||||||
|
ServerSeedHash: rec.ServerSeedHash,
|
||||||
|
ItemsRoot: rec.ItemsRoot,
|
||||||
|
WeightsTotal: rec.WeightsTotal,
|
||||||
|
StateVersion: rec.StateVersion,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GetIssueRandomCommit(ctx context.Context, issueID int64) (*IssueRandomCommitment, error) {
|
||||||
|
latest, err := s.readDB.IssueRandomCommitments.WithContext(ctx).
|
||||||
|
Where(s.readDB.IssueRandomCommitments.IssueID.Eq(issueID)).
|
||||||
|
Order(s.readDB.IssueRandomCommitments.StateVersion.Desc()).
|
||||||
|
Take()
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil // 没有找到记录是正常的,表示尚未生成承诺
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &IssueRandomCommitment{
|
||||||
|
AlgoVersion: latest.AlgoVersion,
|
||||||
|
IssueID: latest.IssueID,
|
||||||
|
ServerSeedMaster: latest.ServerSeedMaster,
|
||||||
|
ServerSeedHash: latest.ServerSeedHash,
|
||||||
|
ItemsRoot: latest.ItemsRoot,
|
||||||
|
WeightsTotal: latest.WeightsTotal,
|
||||||
|
StateVersion: latest.StateVersion,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GetIssueRandomCommitHistory(ctx context.Context, issueID int64) ([]*IssueRandomCommitment, error) {
|
||||||
|
commitments, err := s.readDB.IssueRandomCommitments.WithContext(ctx).
|
||||||
|
Where(s.readDB.IssueRandomCommitments.IssueID.Eq(issueID)).
|
||||||
|
Order(s.readDB.IssueRandomCommitments.StateVersion.Desc()).
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return []*IssueRandomCommitment{}, nil // 返回空数组而不是错误
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*IssueRandomCommitment, 0, len(commitments))
|
||||||
|
for _, commit := range commitments {
|
||||||
|
result = append(result, &IssueRandomCommitment{
|
||||||
|
AlgoVersion: commit.AlgoVersion,
|
||||||
|
IssueID: commit.IssueID,
|
||||||
|
ServerSeedMaster: commit.ServerSeedMaster,
|
||||||
|
ServerSeedHash: commit.ServerSeedHash,
|
||||||
|
ItemsRoot: commit.ItemsRoot,
|
||||||
|
WeightsTotal: commit.WeightsTotal,
|
||||||
|
StateVersion: commit.StateVersion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
58
internal/service/activity/seed_crypto.go
Normal file
58
internal/service/activity/seed_crypto.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"bindbox-game/configs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func masterKey() []byte {
|
||||||
|
s := configs.Get().Random.CommitMasterKey
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
b, err := hex.DecodeString(s)
|
||||||
|
if err != nil || len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveMask(key []byte, issueID int64, version int32) []byte {
|
||||||
|
m := hmac.New(sha256.New, key)
|
||||||
|
buf := make([]byte, 12)
|
||||||
|
binary.BigEndian.PutUint64(buf[:8], uint64(issueID))
|
||||||
|
binary.BigEndian.PutUint32(buf[8:12], uint32(version))
|
||||||
|
m.Write(buf)
|
||||||
|
sum := m.Sum(nil)
|
||||||
|
return sum[:32]
|
||||||
|
}
|
||||||
|
|
||||||
|
func maskSeed(master []byte, issueID int64, version int32) ([]byte, bool) {
|
||||||
|
key := masterKey()
|
||||||
|
if key == nil {
|
||||||
|
return master, false
|
||||||
|
}
|
||||||
|
ks := deriveMask(key, issueID, version)
|
||||||
|
out := make([]byte, len(master))
|
||||||
|
for i := range master {
|
||||||
|
out[i] = master[i] ^ ks[i%len(ks)]
|
||||||
|
}
|
||||||
|
return out, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmaskSeed(enc []byte, issueID int64, version int32) []byte {
|
||||||
|
key := masterKey()
|
||||||
|
if key == nil {
|
||||||
|
return enc
|
||||||
|
}
|
||||||
|
ks := deriveMask(key, issueID, version)
|
||||||
|
out := make([]byte, len(enc))
|
||||||
|
for i := range enc {
|
||||||
|
out[i] = enc[i] ^ ks[i%len(ks)]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
35
internal/service/user/batch_user.go
Normal file
35
internal/service/user/batch_user.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bindbox-game/internal/repository/mysql/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateUserInput struct {
|
||||||
|
Nickname string
|
||||||
|
OpenID string
|
||||||
|
Avatar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) CreateUser(ctx context.Context, in CreateUserInput) (*model.Users, error) {
|
||||||
|
now := time.Now()
|
||||||
|
u := &model.Users{
|
||||||
|
Nickname: in.Nickname,
|
||||||
|
Openid: in.OpenID,
|
||||||
|
Avatar: in.Avatar,
|
||||||
|
Status: 1,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
if err := s.writeDB.Users.WithContext(ctx).Create(u); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) DeleteUser(ctx context.Context, userID int64) error {
|
||||||
|
_, err := s.writeDB.Users.WithContext(ctx).Where(s.writeDB.Users.ID.Eq(userID)).Delete()
|
||||||
|
return err
|
||||||
|
}
|
||||||
@ -31,6 +31,8 @@ type Service interface {
|
|||||||
ListAddresses(ctx context.Context, userID int64, page, pageSize int) (items []*model.UserAddresses, total int64, err error)
|
ListAddresses(ctx context.Context, userID int64, page, pageSize int) (items []*model.UserAddresses, total int64, err error)
|
||||||
SetDefaultAddress(ctx context.Context, userID int64, addressID int64) error
|
SetDefaultAddress(ctx context.Context, userID int64, addressID int64) error
|
||||||
DeleteAddress(ctx context.Context, userID int64, addressID int64) error
|
DeleteAddress(ctx context.Context, userID int64, addressID int64) error
|
||||||
|
CreateUser(ctx context.Context, in CreateUserInput) (*model.Users, error)
|
||||||
|
DeleteUser(ctx context.Context, userID int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
|
|||||||
@ -3,3 +3,7 @@
|
|||||||
{"level":"fatal","time":"2025-11-14 15:16:49","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
{"level":"fatal","time":"2025-11-14 15:16:49","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
||||||
{"level":"fatal","time":"2025-11-14 16:52:42","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
{"level":"fatal","time":"2025-11-14 16:52:42","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
||||||
{"level":"fatal","time":"2025-11-14 18:32:56","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
{"level":"fatal","time":"2025-11-14 18:32:56","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
||||||
|
{"level":"fatal","time":"2025-11-15 12:25:30","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
||||||
|
{"level":"fatal","time":"2025-11-15 12:26:06","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
||||||
|
{"level":"fatal","time":"2025-11-15 12:28:06","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
||||||
|
{"level":"fatal","time":"2025-11-15 13:05:26","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
||||||
|
|||||||
218
test_lottery_profit.js
Normal file
218
test_lottery_profit.js
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
// 抽奖盈亏计算测试示例
|
||||||
|
// 基于实际业务逻辑的模拟数据
|
||||||
|
|
||||||
|
// 模拟一个抽奖活动的配置
|
||||||
|
const mockActivity = {
|
||||||
|
id: 1,
|
||||||
|
name: "春节抽奖活动",
|
||||||
|
price_draw: 1000, // 门票价格:10元 = 1000分
|
||||||
|
status: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模拟奖项配置
|
||||||
|
const mockRewards = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "iPhone 15 Pro",
|
||||||
|
weight: 1,
|
||||||
|
quantity: 10,
|
||||||
|
product_id: 101,
|
||||||
|
cost: 800000 // 成本:8000元 = 800000分
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "AirPods Pro",
|
||||||
|
weight: 10,
|
||||||
|
quantity: 50,
|
||||||
|
product_id: 102,
|
||||||
|
cost: 200000 // 成本:2000元 = 200000分
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "小米手环",
|
||||||
|
weight: 100,
|
||||||
|
quantity: 200,
|
||||||
|
product_id: 103,
|
||||||
|
cost: 20000 // 成本:200元 = 20000分
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "优惠券10元",
|
||||||
|
weight: 1000,
|
||||||
|
quantity: -1, // -1 表示不限量
|
||||||
|
product_id: null,
|
||||||
|
cost: 1000 // 成本:10元 = 1000分(平台补贴)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "谢谢参与",
|
||||||
|
weight: 5000,
|
||||||
|
quantity: -1,
|
||||||
|
product_id: null,
|
||||||
|
cost: 0 // 无成本
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 计算理论概率和期望支出
|
||||||
|
function calculateTheoreticalData(rewards) {
|
||||||
|
// 筛选有效奖项(weight > 0 且 quantity != 0)
|
||||||
|
const validRewards = rewards.filter(r => r.weight > 0 && r.quantity !== 0);
|
||||||
|
|
||||||
|
// 计算总权重
|
||||||
|
const totalWeight = validRewards.reduce((sum, r) => sum + r.weight, 0);
|
||||||
|
|
||||||
|
// 计算每个奖项的理论概率和期望支出
|
||||||
|
const results = validRewards.map(reward => {
|
||||||
|
const probability = reward.weight / totalWeight;
|
||||||
|
const expectedCost = probability * reward.cost; // 单次抽奖的期望支出
|
||||||
|
|
||||||
|
return {
|
||||||
|
...reward,
|
||||||
|
probability,
|
||||||
|
expectedCost
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 总期望支出(单次抽奖)
|
||||||
|
const totalExpectedCost = results.reduce((sum, r) => sum + r.expectedCost, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
rewards: results,
|
||||||
|
totalWeight,
|
||||||
|
totalExpectedCost
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟抽奖结果
|
||||||
|
function simulateDraws(rewards, sampleSize) {
|
||||||
|
const theoretical = calculateTheoreticalData(rewards);
|
||||||
|
const results = {};
|
||||||
|
|
||||||
|
// 初始化结果统计
|
||||||
|
theoretical.rewards.forEach(reward => {
|
||||||
|
results[reward.id] = {
|
||||||
|
...reward,
|
||||||
|
count: 0,
|
||||||
|
simulatedCost: 0
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 模拟抽奖
|
||||||
|
for (let i = 0; i < sampleSize; i++) {
|
||||||
|
const random = Math.random();
|
||||||
|
let cumulativeProbability = 0;
|
||||||
|
|
||||||
|
// 根据概率选择奖项
|
||||||
|
for (const reward of theoretical.rewards) {
|
||||||
|
cumulativeProbability += reward.probability;
|
||||||
|
if (random <= cumulativeProbability) {
|
||||||
|
results[reward.id].count++;
|
||||||
|
results[reward.id].simulatedCost += reward.cost;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
theoretical,
|
||||||
|
simulated: Object.values(results),
|
||||||
|
sampleSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算盈亏指标
|
||||||
|
function calculateProfitMetrics(activity, simulation) {
|
||||||
|
const { theoretical, simulated, sampleSize } = simulation;
|
||||||
|
const ticketPrice = activity.price_draw;
|
||||||
|
|
||||||
|
// 理论计算
|
||||||
|
const theoreticalRevenue = ticketPrice * sampleSize;
|
||||||
|
const theoreticalPayout = theoretical.totalExpectedCost * sampleSize;
|
||||||
|
const theoreticalProfit = theoreticalRevenue - theoreticalPayout;
|
||||||
|
const theoreticalMargin = theoreticalProfit / theoreticalRevenue;
|
||||||
|
|
||||||
|
// 模拟计算
|
||||||
|
const simulatedRevenue = ticketPrice * sampleSize;
|
||||||
|
const simulatedPayout = simulated.reduce((sum, r) => sum + r.simulatedCost, 0);
|
||||||
|
const simulatedProfit = simulatedRevenue - simulatedPayout;
|
||||||
|
const simulatedMargin = simulatedProfit / simulatedRevenue;
|
||||||
|
|
||||||
|
return {
|
||||||
|
theoretical: {
|
||||||
|
revenue: theoreticalRevenue,
|
||||||
|
payout: theoreticalPayout,
|
||||||
|
profit: theoreticalProfit,
|
||||||
|
margin: theoreticalMargin
|
||||||
|
},
|
||||||
|
simulated: {
|
||||||
|
revenue: simulatedRevenue,
|
||||||
|
payout: simulatedPayout,
|
||||||
|
profit: simulatedProfit,
|
||||||
|
margin: simulatedMargin
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
function runLotteryProfitTest() {
|
||||||
|
console.log("=== 抽奖盈亏分析测试 ===");
|
||||||
|
console.log(`活动: ${mockActivity.name}`);
|
||||||
|
console.log(`门票价格: ¥${(mockActivity.price_draw / 100).toFixed(2)}`);
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
// 理论计算
|
||||||
|
const theoreticalData = calculateTheoreticalData(mockRewards);
|
||||||
|
console.log("--- 理论概率分析 ---");
|
||||||
|
console.log(`总权重: ${theoreticalData.totalWeight}`);
|
||||||
|
console.log(`单次期望支出: ¥${(theoreticalData.totalExpectedCost / 100).toFixed(4)}`);
|
||||||
|
console.log(`单次期望利润: ¥${((mockActivity.price_draw - theoreticalData.totalExpectedCost) / 100).toFixed(4)}`);
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
theoreticalData.rewards.forEach(reward => {
|
||||||
|
console.log(`${reward.name}:`);
|
||||||
|
console.log(` 权重: ${reward.weight}, 概率: ${(reward.probability * 100).toFixed(4)}%`);
|
||||||
|
console.log(` 成本: ¥${(reward.cost / 100).toFixed(2)}, 期望支出: ¥${(reward.expectedCost / 100).toFixed(4)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("\n--- 模拟结果 (样本数: 10000) ---");
|
||||||
|
const simulation = simulateDraws(mockRewards, 10000);
|
||||||
|
const metrics = calculateProfitMetrics(mockActivity, simulation);
|
||||||
|
|
||||||
|
console.log("理论值:");
|
||||||
|
console.log(` 总收入: ¥${(metrics.theoretical.revenue / 100).toFixed(2)}`);
|
||||||
|
console.log(` 总支出: ¥${(metrics.theoretical.payout / 100).toFixed(2)}`);
|
||||||
|
console.log(` 总利润: ¥${(metrics.theoretical.profit / 100).toFixed(2)}`);
|
||||||
|
console.log(` 利润率: ${(metrics.theoretical.margin * 100).toFixed(2)}%`);
|
||||||
|
|
||||||
|
console.log("\n模拟值:");
|
||||||
|
console.log(` 总收入: ¥${(metrics.simulated.revenue / 100).toFixed(2)}`);
|
||||||
|
console.log(` 总支出: ¥${(metrics.simulated.payout / 100).toFixed(2)}`);
|
||||||
|
console.log(` 总利润: ¥${(metrics.simulated.profit / 100).toFixed(2)}`);
|
||||||
|
console.log(` 利润率: ${(metrics.simulated.margin * 100).toFixed(2)}%`);
|
||||||
|
|
||||||
|
console.log("\n--- 各奖项模拟结果 ---");
|
||||||
|
simulation.simulated.forEach(result => {
|
||||||
|
const simulatedRate = result.count / simulation.sampleSize;
|
||||||
|
const theoreticalRate = result.probability;
|
||||||
|
const diff = Math.abs(simulatedRate - theoreticalRate) * 100;
|
||||||
|
|
||||||
|
console.log(`${result.name}:`);
|
||||||
|
console.log(` 模拟次数: ${result.count}, 模拟概率: ${(simulatedRate * 100).toFixed(3)}%`);
|
||||||
|
console.log(` 理论概率: ${(theoreticalRate * 100).toFixed(3)}%, 差异: ${diff.toFixed(3)}%`);
|
||||||
|
console.log(` 模拟支出: ¥${(result.simulatedCost / 100).toFixed(2)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证大数定律
|
||||||
|
console.log("\n--- 大数定律验证 ---");
|
||||||
|
const largeSample = simulateDraws(mockRewards, 100000);
|
||||||
|
largeSample.simulated.forEach(result => {
|
||||||
|
const simulatedRate = result.count / largeSample.sampleSize;
|
||||||
|
const theoreticalRate = result.probability;
|
||||||
|
const diff = Math.abs(simulatedRate - theoreticalRate) * 100;
|
||||||
|
|
||||||
|
console.log(`${result.name}: 模拟概率 ${(simulatedRate * 100).toFixed(4)}% vs 理论概率 ${(theoreticalRate * 100).toFixed(4)}%, 差异 ${diff.toFixed(4)}%`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
runLotteryProfitTest();
|
||||||
Loading…
x
Reference in New Issue
Block a user