ab test and notification

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
hahwu 2026-04-24 17:23:55 +08:00
parent b926f1aab0
commit ddae026231
30 changed files with 2033 additions and 801 deletions

View File

@ -11,115 +11,187 @@ import (
"google.golang.org/grpc/credentials/insecure"
)
const backendRPCTimeout = 3 * time.Second
type backendCallMeta struct {
AppID int
ServerID int
Method string
Timeout time.Duration
}
type backendInvoker[T any] func(ctx context.Context, client msg.BackendClient) (T, error)
type backendMiddleware[T any] func(meta backendCallMeta, next backendInvoker[T]) backendInvoker[T]
type backendCodeResponse interface {
GetCode() int32
GetMsg() string
}
func executeBackendCall[T any](appID, serverID int, method string, invoke backendInvoker[T], middlewares ...backendMiddleware[T]) (T, error) {
var zero T
serverConfig, err := util.GetServerConfig(appID, serverID)
if err != nil {
return zero, fmt.Errorf("failed to get server config: %w", err)
}
if serverConfig == nil {
return zero, fmt.Errorf("server config not found for AppId %d and ServerId %d", appID, serverID)
}
addr := fmt.Sprintf("%s:%d", serverConfig.Host, serverConfig.GrpcPort)
// TODO 测试
//addr = ":50051"
conn, err := grpc.NewClient(
addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return zero, fmt.Errorf("failed to connect: %w", err)
}
defer conn.Close()
meta := backendCallMeta{
AppID: appID,
ServerID: serverID,
Method: method,
Timeout: backendRPCTimeout,
}
wrapped := invoke
for index := len(middlewares) - 1; index >= 0; index-- {
wrapped = middlewares[index](meta, wrapped)
}
ctx, cancel := context.WithTimeout(context.Background(), meta.Timeout)
defer cancel()
return wrapped(ctx, msg.NewBackendClient(conn))
}
func wrapRPCError[T any](meta backendCallMeta, next backendInvoker[T]) backendInvoker[T] {
return func(ctx context.Context, client msg.BackendClient) (T, error) {
var zero T
resp, err := next(ctx, client)
if err != nil {
return zero, fmt.Errorf("%s failed: %w", meta.Method, err)
}
return resp, nil
}
}
func ensureBackendCode[T backendCodeResponse](meta backendCallMeta, next backendInvoker[T]) backendInvoker[T] {
return func(ctx context.Context, client msg.BackendClient) (T, error) {
var zero T
resp, err := next(ctx, client)
if err != nil {
return zero, fmt.Errorf("%s failed: %w", meta.Method, err)
}
if resp.GetCode() != 0 {
return zero, fmt.Errorf("%s failed with code: %d, msg: %s", meta.Method, resp.GetCode(), resp.GetMsg())
}
return resp, nil
}
}
func OrderShipping(AppId, ServerId int, req *msg.ReqOrderShipping) error {
ServerConfig, err := util.GetServerConfig(AppId, ServerId)
if err != nil {
return fmt.Errorf("failed to get server config: %v", err)
}
if ServerConfig == nil {
return fmt.Errorf("server config not found for AppId %d and ServerId %d", AppId, ServerId)
}
conn, err := grpc.NewClient(fmt.Sprintf("%s:%d", ServerConfig.Host, ServerConfig.GrpcPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("failed to connect: %v", err)
}
defer conn.Close()
client := msg.NewBackendClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resp, err := client.OrderShipping(ctx, req)
if err != nil {
return fmt.Errorf("OrderShipping failed: %v", err)
}
if resp.GetCode() != 0 {
return fmt.Errorf("OrderShipping failed with code: %d, msg: %s", resp.GetCode(), resp.GetMsg())
}
return nil
_, err := executeBackendCall(
AppId,
ServerId,
"OrderShipping",
func(ctx context.Context, client msg.BackendClient) (*msg.ResOrderShipping, error) {
return client.OrderShipping(ctx, req)
},
ensureBackendCode[*msg.ResOrderShipping],
)
return err
}
func ActivityCfgReload(AppId, ServerId int, req *msg.ReqActivityCfgReload) error {
ServerConfig, err := util.GetServerConfig(AppId, ServerId)
if err != nil {
return fmt.Errorf("failed to get server config: %v", err)
}
if ServerConfig == nil {
return fmt.Errorf("server config not found for AppId %d and ServerId %d", AppId, ServerId)
}
conn, err := grpc.NewClient(fmt.Sprintf("%s:%d", ServerConfig.Host, ServerConfig.GrpcPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("failed to connect: %v", err)
}
defer conn.Close()
client := msg.NewBackendClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resp, err := client.ReloadActivity(ctx, req)
if err != nil {
return fmt.Errorf("ReloadActivity failed: %v", err)
}
if resp.GetCode() != 0 {
return fmt.Errorf("ReloadActivity failed with code: %d, msg: %s", resp.GetCode(), resp.GetMsg())
}
return nil
func ReloadActivity(AppId, ServerId int, req *msg.ReqActivityCfgReload) error {
_, err := executeBackendCall(
AppId,
ServerId,
"ReloadActivity",
func(ctx context.Context, client msg.BackendClient) (*msg.ResActivityCfgReload, error) {
return client.ReloadActivity(ctx, req)
},
ensureBackendCode[*msg.ResActivityCfgReload],
)
return err
}
func GetUserInfo(AppId, ServerId int, req *msg.UserDetailParam) (*msg.ResUserDetailInfo, error) {
ServerConfig, err := util.GetServerConfig(AppId, ServerId)
func UserDetail(AppId, ServerId int, req *msg.UserDetailParam) (*msg.ResUserDetailInfo, error) {
resp, err := executeBackendCall(
AppId,
ServerId,
"UserDetail",
func(ctx context.Context, client msg.BackendClient) (*msg.ResUserDetail, error) {
return client.UserDetail(ctx, req)
},
ensureBackendCode[*msg.ResUserDetail],
)
if err != nil {
return nil, fmt.Errorf("failed to get server config: %v", err)
}
if ServerConfig == nil {
return nil, fmt.Errorf("server config not found for AppId %d and ServerId %d", AppId, ServerId)
}
conn, err := grpc.NewClient(fmt.Sprintf("%s:%d", ServerConfig.Host, ServerConfig.GrpcPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
// TODO : 目前先连接本地,后续改为连接对应服务器
// conn, err := grpc.NewClient("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, fmt.Errorf("failed to connect: %v", err)
}
defer conn.Close()
client := msg.NewBackendClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resp, err := client.UserDetail(ctx, req)
if err != nil {
return nil, fmt.Errorf("GetUserInfo failed: %v", err)
}
if resp.GetCode() != 0 {
return nil, fmt.Errorf("GetUserInfo failed with code: %d, msg: %s", resp.GetCode(), resp.GetMsg())
return nil, err
}
return resp.GetInfo(), nil
}
func GetServerInfo(AppId, ServerId int, req *msg.ReqServerInfo) (*msg.ResServerInfo, error) {
ServerConfig, err := util.GetServerConfig(AppId, ServerId)
if err != nil {
return nil, fmt.Errorf("failed to get server config: %v", err)
}
if ServerConfig == nil {
return nil, fmt.Errorf("server config not found for AppId %d and ServerId %d", AppId, ServerId)
}
conn, err := grpc.NewClient(fmt.Sprintf("%s:%d", ServerConfig.Host, ServerConfig.GrpcPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
// TODO : 目前先连接本地,后续改为连接对应服务器
//conn, err := grpc.NewClient("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, fmt.Errorf("failed to connect: %v", err)
}
defer conn.Close()
client := msg.NewBackendClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resp, err := client.ServerInfo(ctx, req)
if err != nil {
return nil, fmt.Errorf("ServerInfo failed: %v", err)
}
return resp, nil
return executeBackendCall(
AppId,
ServerId,
"ServerInfo",
func(ctx context.Context, client msg.BackendClient) (*msg.ResServerInfo, error) {
return client.ServerInfo(ctx, req)
},
wrapRPCError[*msg.ResServerInfo],
)
}
func PlayerGm(AppId, ServerId int, req *msg.ReqAdminGm) (*msg.ResAdminGm, error) {
return executeBackendCall(
AppId,
ServerId,
"PlayerGm",
func(ctx context.Context, client msg.BackendClient) (*msg.ResAdminGm, error) {
return client.PlayerGm(ctx, req)
},
wrapRPCError[*msg.ResAdminGm],
)
}
func ServerReload(AppId, ServerId int, req *msg.ReqReload) (*msg.ResBackend, error) {
return executeBackendCall(
AppId,
ServerId,
"ServerReload",
func(ctx context.Context, client msg.BackendClient) (*msg.ResBackend, error) {
return client.ServerReload(ctx, req)
},
wrapRPCError[*msg.ResBackend],
)
}
func BanPlayer(AppId, ServerId int, req *msg.ReqAdminBan) (*msg.ResBackend, error) {
return executeBackendCall(
AppId,
ServerId,
"BanPlayer",
func(ctx context.Context, client msg.BackendClient) (*msg.ResBackend, error) {
return client.BanPlayer(ctx, req)
},
wrapRPCError[*msg.ResBackend],
)
}
func ReloadMail(AppId, ServerId int, req *msg.ReqReloadServerMail) (*msg.ResBackend, error) {
return executeBackendCall(
AppId,
ServerId,
"ReloadMail",
func(ctx context.Context, client msg.BackendClient) (*msg.ResBackend, error) {
return client.ReloadMail(ctx, req)
},
wrapRPCError[*msg.ResBackend],
)
}

687
controller/experiment.go Normal file
View File

@ -0,0 +1,687 @@
package controller
import (
"backend/util"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"
abtestdao "backend/sdk/abtest/dao"
abtestmodel "backend/sdk/abtest/model"
abtestservice "backend/sdk/abtest/service"
"github.com/gin-gonic/gin"
)
var (
experimentServiceOnce sync.Once
experimentService *abtestservice.Service
experimentServiceErr error
)
type experimentCreateVariantRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Weight int `json:"weight"`
Params json.RawMessage `json:"params"`
}
type experimentCreateWhitelistRequest struct {
UserID string `json:"user_id" binding:"required"`
VariantName string `json:"variant_name" binding:"required"`
}
type experimentCreateRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
Status *int `json:"status"`
Variants []experimentCreateVariantRequest `json:"variants"`
Whitelist []experimentCreateWhitelistRequest `json:"whitelist"`
}
type optionalStringField struct {
Set bool
Value *string
}
func (f *optionalStringField) UnmarshalJSON(data []byte) error {
f.Set = true
if string(data) == "null" {
f.Value = nil
return nil
}
var value string
if err := json.Unmarshal(data, &value); err != nil {
return err
}
f.Value = &value
return nil
}
type experimentUpdateRequest struct {
Name *string `json:"name"`
Description *string `json:"description"`
StartTime optionalStringField `json:"start_time"`
EndTime optionalStringField `json:"end_time"`
Status *int `json:"status"`
Variants []experimentCreateVariantRequest `json:"variants"`
Whitelist []experimentCreateWhitelistRequest `json:"whitelist"`
}
func getExperimentService() (*abtestservice.Service, error) {
experimentServiceOnce.Do(func() {
db, err := util.ConnectMysql("log", "abtest")
if err != nil {
experimentServiceErr = err
return
}
experimentService = abtestservice.New(abtestdao.New(db))
})
return experimentService, experimentServiceErr
}
func ExperimentList(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
var statusPtr *int
if statusRaw := c.Query("status"); statusRaw != "" {
status, convErr := strconv.Atoi(statusRaw)
if convErr != nil {
failed(c, "invalid status")
return
}
statusPtr = &status
}
list, total, err := svc.ListExperiments(statusPtr, page, pageSize)
if err != nil {
failed(c, err.Error())
return
}
success(c, gin.H{"list": list, "total": total, "page": page, "page_size": pageSize})
}
func ExperimentCreate(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
var req experimentCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
failed(c, err.Error())
return
}
result, err := createExperimentAggregate(svc, &req)
if err != nil {
failed(c, err.Error())
return
}
util.AddAdminLog(c, "创建AB实验", req)
success(c, result)
}
func createExperimentAggregate(svc *abtestservice.Service, req *experimentCreateRequest) (gin.H, error) {
name := strings.TrimSpace(req.Name)
if name == "" {
return nil, fmt.Errorf("name is required")
}
startTime, err := parseExperimentTime(req.StartTime)
if err != nil {
return nil, fmt.Errorf("invalid start_time: %w", err)
}
endTime, err := parseExperimentTime(req.EndTime)
if err != nil {
return nil, fmt.Errorf("invalid end_time: %w", err)
}
if startTime != nil && endTime != nil && startTime.After(*endTime) {
return nil, fmt.Errorf("start_time must be earlier than end_time")
}
experiment, err := svc.CreateExperiment(&abtestmodel.CreateExperimentReq{
Name: name,
Description: req.Description,
})
if err != nil {
return nil, err
}
shouldRollback := true
defer func() {
if shouldRollback {
_ = svc.DeleteExperiment(experiment.ID)
}
}()
if req.Status != nil || startTime != nil || endTime != nil {
experiment, err = svc.UpdateExperiment(experiment.ID, &abtestmodel.UpdateExperimentReq{
Status: req.Status,
StartTime: startTime,
EndTime: endTime,
})
if err != nil {
return nil, err
}
}
variants := make([]*abtestmodel.Variant, 0, len(req.Variants))
variantsByName := make(map[string]*abtestmodel.Variant, len(req.Variants))
for _, variantReq := range req.Variants {
variantName := strings.TrimSpace(variantReq.Name)
if variantName == "" {
return nil, fmt.Errorf("variant name is required")
}
if _, exists := variantsByName[variantName]; exists {
return nil, fmt.Errorf("duplicate variant name: %s", variantName)
}
variant, createErr := svc.CreateVariant(experiment.ID, &abtestmodel.CreateVariantReq{
Name: variantName,
Description: variantReq.Description,
Weight: variantReq.Weight,
Params: variantReq.Params,
})
if createErr != nil {
return nil, createErr
}
variants = append(variants, variant)
variantsByName[variant.Name] = variant
}
whitelist := make([]*abtestmodel.Whitelist, 0, len(req.Whitelist))
for _, whitelistReq := range req.Whitelist {
userID := strings.TrimSpace(whitelistReq.UserID)
variantName := strings.TrimSpace(whitelistReq.VariantName)
if userID == "" {
return nil, fmt.Errorf("whitelist user_id is required")
}
variant, exists := variantsByName[variantName]
if !exists {
return nil, fmt.Errorf("whitelist variant_name not found: %s", variantName)
}
entry, addErr := svc.AddWhitelist(experiment.ID, &abtestmodel.CreateWhitelistReq{
UserID: userID,
VariantID: variant.ID,
})
if addErr != nil {
return nil, addErr
}
whitelist = append(whitelist, entry)
}
shouldRollback = false
return gin.H{
"experiment": experiment,
"variants": variants,
"whitelist": whitelist,
}, nil
}
func parseExperimentTime(raw string) (*time.Time, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil, nil
}
layouts := []string{
"2006-01-02 15:04:05",
time.RFC3339,
"2006-01-02T15:04:05",
}
for _, layout := range layouts {
parsed, err := time.ParseInLocation(layout, raw, time.Local)
if err == nil {
return &parsed, nil
}
}
return nil, fmt.Errorf("unsupported time format")
}
func ExperimentUpdate(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
failed(c, "invalid id")
return
}
var req experimentUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
failed(c, err.Error())
return
}
experiment, err := updateExperimentAggregate(svc, id, &req)
if err != nil {
status := http.StatusBadRequest
if err == abtestservice.ErrExperimentNotFound {
status = http.StatusNotFound
}
c.JSON(status, gin.H{"code": 1, "message": err.Error()})
return
}
util.AddAdminLog(c, "更新AB实验", gin.H{"id": id, "payload": req})
success(c, experiment)
}
func updateExperimentAggregate(svc *abtestservice.Service, experimentID int64, req *experimentUpdateRequest) (*abtestmodel.Experiment, error) {
if _, err := svc.GetExperiment(experimentID); err != nil {
return nil, err
}
updateReq := &abtestmodel.UpdateExperimentReq{
Name: req.Name,
Description: req.Description,
Status: req.Status,
}
if req.StartTime.Set {
if req.StartTime.Value == nil {
updateReq.ClearStartTime = true
} else {
parsed, err := parseExperimentTime(*req.StartTime.Value)
if err != nil {
return nil, fmt.Errorf("invalid start_time: %w", err)
}
updateReq.StartTime = parsed
}
}
if req.EndTime.Set {
if req.EndTime.Value == nil {
updateReq.ClearEndTime = true
} else {
parsed, err := parseExperimentTime(*req.EndTime.Value)
if err != nil {
return nil, fmt.Errorf("invalid end_time: %w", err)
}
updateReq.EndTime = parsed
}
}
if updateReq.StartTime != nil && updateReq.EndTime != nil && updateReq.StartTime.After(*updateReq.EndTime) {
return nil, fmt.Errorf("start_time must be earlier than end_time")
}
experiment, err := svc.UpdateExperiment(experimentID, updateReq)
if err != nil {
return nil, err
}
variantsProvided := req.Variants != nil
whitelistProvided := req.Whitelist != nil
if !variantsProvided && !whitelistProvided {
return experiment, nil
}
var variants []*abtestmodel.Variant
variantsByName := make(map[string]*abtestmodel.Variant)
if variantsProvided {
if err := svc.ResetExperimentRelations(experimentID); err != nil {
return nil, err
}
for _, variantReq := range req.Variants {
variantName := strings.TrimSpace(variantReq.Name)
if variantName == "" {
return nil, fmt.Errorf("variant name is required")
}
if _, exists := variantsByName[variantName]; exists {
return nil, fmt.Errorf("duplicate variant name: %s", variantName)
}
variant, createErr := svc.CreateVariant(experimentID, &abtestmodel.CreateVariantReq{
Name: variantName,
Description: variantReq.Description,
Weight: variantReq.Weight,
Params: variantReq.Params,
})
if createErr != nil {
return nil, createErr
}
variants = append(variants, variant)
variantsByName[variant.Name] = variant
}
} else if whitelistProvided {
variants, err = svc.ListVariants(experimentID)
if err != nil {
return nil, err
}
for _, variant := range variants {
variantsByName[variant.Name] = variant
}
if err := svc.ClearWhitelist(experimentID); err != nil {
return nil, err
}
}
if whitelistProvided {
for _, whitelistReq := range req.Whitelist {
userID := strings.TrimSpace(whitelistReq.UserID)
variantName := strings.TrimSpace(whitelistReq.VariantName)
if userID == "" {
return nil, fmt.Errorf("whitelist user_id is required")
}
variant, exists := variantsByName[variantName]
if !exists {
return nil, fmt.Errorf("whitelist variant_name not found: %s", variantName)
}
if _, err := svc.AddWhitelist(experimentID, &abtestmodel.CreateWhitelistReq{
UserID: userID,
VariantID: variant.ID,
}); err != nil {
return nil, err
}
}
}
return svc.GetExperiment(experimentID)
}
func ExperimentDelete(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
failed(c, "invalid id")
return
}
if err := svc.DeleteExperiment(id); err != nil {
status := http.StatusBadRequest
if err == abtestservice.ErrExperimentNotFound {
status = http.StatusNotFound
}
c.JSON(status, gin.H{"code": 1, "message": err.Error()})
return
}
util.AddAdminLog(c, "删除AB实验", gin.H{"id": id})
success(c, nil)
}
func ExperimentVariantList(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
failed(c, "invalid id")
return
}
list, err := svc.ListVariants(id)
if err != nil {
failed(c, err.Error())
return
}
success(c, list)
}
func ExperimentVariantCreate(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
failed(c, "invalid id")
return
}
var req abtestmodel.CreateVariantReq
if err := c.ShouldBindJSON(&req); err != nil {
failed(c, err.Error())
return
}
variant, err := svc.CreateVariant(id, &req)
if err != nil {
status := http.StatusBadRequest
if err == abtestservice.ErrExperimentNotFound {
status = http.StatusNotFound
}
c.JSON(status, gin.H{"code": 1, "message": err.Error()})
return
}
util.AddAdminLog(c, "创建AB实验变体", gin.H{"experiment_id": id, "payload": req})
success(c, variant)
}
func ExperimentVariantUpdate(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
variantID, err := strconv.ParseInt(c.Param("variantId"), 10, 64)
if err != nil {
failed(c, "invalid variantId")
return
}
var req abtestmodel.UpdateVariantReq
if err := c.ShouldBindJSON(&req); err != nil {
failed(c, err.Error())
return
}
variant, err := svc.UpdateVariant(variantID, &req)
if err != nil {
status := http.StatusBadRequest
if err == abtestservice.ErrVariantNotFound {
status = http.StatusNotFound
}
c.JSON(status, gin.H{"code": 1, "message": err.Error()})
return
}
util.AddAdminLog(c, "更新AB实验变体", gin.H{"variant_id": variantID, "payload": req})
success(c, variant)
}
func ExperimentVariantDelete(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
variantID, err := strconv.ParseInt(c.Param("variantId"), 10, 64)
if err != nil {
failed(c, "invalid variantId")
return
}
if err := svc.DeleteVariant(variantID); err != nil {
status := http.StatusBadRequest
if err == abtestservice.ErrVariantNotFound {
status = http.StatusNotFound
}
c.JSON(status, gin.H{"code": 1, "message": err.Error()})
return
}
util.AddAdminLog(c, "删除AB实验变体", gin.H{"variant_id": variantID})
success(c, nil)
}
func ExperimentWhitelistList(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
failed(c, "invalid id")
return
}
list, err := svc.ListWhitelist(id)
if err != nil {
failed(c, err.Error())
return
}
success(c, list)
}
func ExperimentWhitelistCreate(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
failed(c, "invalid id")
return
}
var req abtestmodel.CreateWhitelistReq
if err := c.ShouldBindJSON(&req); err != nil {
failed(c, err.Error())
return
}
entry, err := svc.AddWhitelist(id, &req)
if err != nil {
status := http.StatusBadRequest
if err == abtestservice.ErrExperimentNotFound || err == abtestservice.ErrVariantNotFound {
status = http.StatusNotFound
}
c.JSON(status, gin.H{"code": 1, "message": err.Error()})
return
}
util.AddAdminLog(c, "新增AB白名单", gin.H{"experiment_id": id, "payload": req})
success(c, entry)
}
func ExperimentWhitelistBatchCreate(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
failed(c, "invalid id")
return
}
var req abtestmodel.BatchCreateWhitelistReq
if err := c.ShouldBindJSON(&req); err != nil {
failed(c, err.Error())
return
}
list, err := svc.BatchAddWhitelist(id, &req)
if err != nil {
status := http.StatusBadRequest
if err == abtestservice.ErrExperimentNotFound || err == abtestservice.ErrVariantNotFound {
status = http.StatusNotFound
}
c.JSON(status, gin.H{"code": 1, "message": err.Error()})
return
}
util.AddAdminLog(c, "批量新增AB白名单", gin.H{"experiment_id": id, "payload": req})
success(c, list)
}
func ExperimentWhitelistDelete(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
failed(c, "invalid id")
return
}
userID := c.Param("userId")
if userID == "" {
failed(c, "invalid userId")
return
}
if err := svc.RemoveWhitelist(id, userID); err != nil {
failed(c, err.Error())
return
}
util.AddAdminLog(c, "删除AB白名单", gin.H{"experiment_id": id, "user_id": userID})
success(c, nil)
}
func ExperimentResult(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
failed(c, "invalid id")
return
}
result, err := svc.GetExperimentResult(id)
if err != nil {
status := http.StatusBadRequest
if err == abtestservice.ErrExperimentNotFound {
status = http.StatusNotFound
}
c.JSON(status, gin.H{"code": 1, "message": err.Error()})
return
}
success(c, result)
}
func UserExperimentGroups(c *gin.Context) {
svc, err := getExperimentService()
if err != nil {
failed(c, err.Error())
return
}
userID := strings.TrimSpace(c.Param("userId"))
if userID == "" {
failed(c, "invalid userId")
return
}
groups, err := svc.GetUserGroups(userID)
if err != nil {
failed(c, err.Error())
return
}
success(c, groups)
}

View File

@ -0,0 +1,48 @@
package controller
import (
"strings"
"backend/middleware/alibaba"
"github.com/gin-gonic/gin"
)
const notificationTemplatePath = `D:\Github\common\sdk\tuyou\notification.json`
type notificationConfigUpdateRequest struct {
Config string `json:"config" binding:"required"`
}
func NotificationConfigGet(c *gin.Context) {
ossConfig, err := alibaba.GetNotificationConfig()
if err != nil {
failed(c, "获取通知配置失败: "+err.Error())
return
}
success(c, gin.H{
"config": ossConfig.Config,
"updated_at": ossConfig.LastModified,
})
}
func NotificationConfigSave(c *gin.Context) {
var req notificationConfigUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
failed(c, "Invalid input: "+err.Error())
return
}
raw := strings.TrimSpace(req.Config)
if raw == "" {
failed(c, "config is required")
return
}
alibaba.Osscreate(req.Config)
success(c, gin.H{
"code": 0,
})
}

View File

@ -390,4 +390,7 @@ func ClientImageGitPull(c *gin.Context) {
scritp_fail(c, "执行更新翻译图标失败: "+err.Error()+": "+output)
return
}
success(c, map[string]interface{}{
"code": 0,
})
}

54
go.mod
View File

@ -4,6 +4,7 @@ go 1.25.5
require (
github.com/Ullaakut/nmap v2.0.2+incompatible
github.com/abtest v0.0.0
github.com/alibabacloud-go/cloudauth-20190307/v4 v4.9.2
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9
github.com/alibabacloud-go/dingtalk v1.6.97
@ -13,8 +14,8 @@ require (
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
github.com/aliyun/credentials-go v1.4.6
github.com/elastic/go-elasticsearch/v8 v8.19.1
github.com/gin-gonic/gin v1.10.0
github.com/go-sql-driver/mysql v1.8.1
github.com/gin-gonic/gin v1.12.0
github.com/go-sql-driver/mysql v1.9.3
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/jmoiron/sqlx v1.4.0
@ -25,18 +26,24 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/xuri/excelize/v2 v2.10.0
go.etcd.io/bbolt v1.4.3
golang.org/x/crypto v0.46.0
golang.org/x/crypto v0.48.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect
google.golang.org/grpc v1.67.3
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v2 v2.4.0
)
require github.com/nacos-group/nacos-sdk-go/v2 v2.3.5
require (
gitea.bywaystudios.com/pet_home/nacos v0.0.0-20260422063809-d6268b6aa6e3
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.4.1
github.com/joho/godotenv v1.5.1
github.com/nacos-group/nacos-sdk-go/v2 v2.3.5
)
replace github.com/abtest => ./sdk/abtest
require (
filippo.io/edwards25519 v1.1.0 // indirect
gitea.bywaystudios.com/pet_home/nacos v0.0.0-20260422063809-d6268b6aa6e3 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/darabonba-array v0.1.0 // indirect
@ -58,34 +65,33 @@ require (
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 // indirect
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1 // indirect
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8 // indirect
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.4.1 // indirect
github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/deckarep/golang-set v1.7.1 // indirect
github.com/elastic/elastic-transport-go/v8 v8.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lestrrat-go/strftime v1.1.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@ -94,20 +100,23 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc // indirect
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect
github.com/tiendc/go-deepcopy v1.7.1 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/xuri/efp v0.0.1 // indirect
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
@ -115,13 +124,12 @@ require (
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.4.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

95
go.sum
View File

@ -146,10 +146,12 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -162,10 +164,8 @@ github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -180,12 +180,12 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -206,13 +206,16 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@ -307,10 +310,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -359,8 +360,8 @@ github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNs
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -389,6 +390,10 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
@ -418,9 +423,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
@ -430,8 +434,8 @@ github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
@ -447,6 +451,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -466,13 +472,14 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -491,8 +498,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -576,8 +583,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -656,8 +663,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -671,8 +678,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -687,14 +694,12 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -866,8 +871,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

26
main.go
View File

@ -106,6 +106,26 @@ func main() {
alibabaApi.POST("/game/notify", controller.AlibabaGameNotify) // 游戏报警
alibabaApi.POST("/notify/order", controller.AlibabaNotifyOrder) // 订单通知
}
abtestApi := r.Group("/api/v1", middleware.ValidateToken())
{
experiments := abtestApi.Group("/experiments")
{
experiments.GET("", controller.ExperimentList)
experiments.POST("", controller.ExperimentCreate)
experiments.PUT("/:id", controller.ExperimentUpdate)
experiments.DELETE("/:id", controller.ExperimentDelete)
experiments.GET("/:id/variants", controller.ExperimentVariantList)
experiments.POST("/:id/variants", controller.ExperimentVariantCreate)
experiments.PUT("/:id/variants/:variantId", controller.ExperimentVariantUpdate)
experiments.DELETE("/:id/variants/:variantId", controller.ExperimentVariantDelete)
experiments.GET("/:id/whitelist", controller.ExperimentWhitelistList)
experiments.POST("/:id/whitelist", controller.ExperimentWhitelistCreate)
experiments.POST("/:id/whitelist/batch", controller.ExperimentWhitelistBatchCreate)
experiments.DELETE("/:id/whitelist/:userId", controller.ExperimentWhitelistDelete)
experiments.GET("/:id/results", controller.ExperimentResult)
}
abtestApi.GET("/users/:userId/groups", controller.UserExperimentGroups)
}
api := r.Group("/api", middleware.ValidateToken())
{
// 账号管理
@ -164,8 +184,10 @@ func main() {
api.POST("/language/list", controller.Language)
api.POST("/language/export", controller.LanguageExport)
api.POST("/language/save", controller.LanguageSave)
api.POST("language/add", controller.LanguageAdd)
api.POST("language/delete", controller.LanguageDelete)
api.POST("/language/add", controller.LanguageAdd)
api.POST("/language/delete", controller.LanguageDelete)
api.GET("/config/notification", controller.NotificationConfigGet)
api.PUT("/config/notification/update", controller.NotificationConfigSave)
}
// 自动化脚本

View File

@ -1,26 +1,25 @@
package alibaba
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"strconv"
"strings"
"sync"
"time"
"gitea.bywaystudios.com/pet_home/nacos"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
"github.com/joho/godotenv"
)
const (
defaultOSSRegion = "cn-hangzhou"
defaultOSSBucket = "merge-pet-elastic"
defaultOSSObjectKey = "example-object.txt"
defaultOSSRegion = "us-west-1"
defaultOSSBucket = "merge-pet-us"
defaultOSSObjectKey = "static/notification.json"
defaultNacosDataID = "oss.json"
defaultNacosGroup = "server"
defaultNacosLogDir = "./log/nacos"
@ -45,9 +44,20 @@ var (
globalOSSRuntime ossRuntime
ossInitOnce sync.Once
dotenvLoadOnce sync.Once
ossCnf ossConfig
)
func Osscreate() {
type OSSObjectContent struct {
Bucket string `json:"bucket"`
Key string `json:"key"`
Region string `json:"region"`
Config map[string]interface{} `json:"config"`
ContentType string `json:"contentType,omitempty"`
ETag string `json:"etag,omitempty"`
LastModified string `json:"lastModified,omitempty"`
}
func Osscreate(content string) {
config, err := initOSSConfig()
if err != nil {
log.Fatalf("加载 OSS 配置失败: %v", err)
@ -59,7 +69,7 @@ func Osscreate() {
client := oss.NewClient(cfg)
body := strings.NewReader("Hello OSS from Go SDK V2!")
body := strings.NewReader(content)
request := &oss.PutObjectRequest{
Bucket: oss.Ptr(config.Bucket),
@ -75,225 +85,95 @@ func Osscreate() {
log.Printf("上传成功! ETag: %s\n", *result.ETag)
}
func initOSSConfig() (ossConfig, error) {
loadDotEnv()
var initErr error
ossInitOnce.Do(func() {
config, err := loadOSSConfig()
if err != nil {
initErr = err
return
}
globalOSSRuntime.set(config)
if err := watchOSSConfigChanges(); err != nil {
log.Printf("监听 Nacos OSS 配置失败,继续使用当前配置: %v", err)
}
})
if initErr != nil {
return ossConfig{}, initErr
}
config, ok := globalOSSRuntime.get()
if !ok {
return ossConfig{}, fmt.Errorf("oss config is not initialized")
}
return config, nil
}
func loadDotEnv() {
dotenvLoadOnce.Do(func() {
if err := godotenv.Load(); err != nil {
if !os.IsNotExist(err) {
log.Printf("加载 .env 失败,继续使用现有环境变量: %v", err)
}
}
})
}
func loadOSSConfig() (ossConfig, error) {
baseConfig := loadOSSConfigFromEnv()
config := baseConfig
nacosConfig, err := loadOSSConfigFromNacos()
func GetNotificationConfig() (*OSSObjectContent, error) {
config, err := initOSSConfig()
if err != nil {
log.Printf("从 Nacos 读取 OSS 配置失败,回退环境变量: %v", err)
return nil, fmt.Errorf("加载 OSS 配置失败: %w", err)
}
cfg := oss.LoadDefaultConfig().
WithCredentialsProvider(credentials.NewStaticCredentialsProvider(config.AccessKeyID, config.AccessKeySecret, config.SessionToken)).
WithRegion(config.Region)
client := oss.NewClient(cfg)
request := &oss.GetObjectRequest{
Bucket: oss.Ptr(config.Bucket),
Key: oss.Ptr(config.ObjectKey),
}
result, err := client.GetObject(context.TODO(), request)
if err != nil {
return nil, fmt.Errorf("获取 OSS 文件失败: %w", err)
}
defer result.Body.Close()
body, err := io.ReadAll(result.Body)
if err != nil {
return nil, fmt.Errorf("读取 OSS 文件内容失败: %w", err)
}
content := bytes.TrimSpace(body)
log.Printf("获取 OSS 文件成功: bucket=%s key=%s size=%d", config.Bucket, config.ObjectKey, len(content))
var data map[string]interface{}
if err := json.Unmarshal(content, &data); err != nil {
log.Printf("解析 OSS 文件内容失败: %v", err)
} else {
config = mergeOSSConfig(baseConfig, nacosConfig)
log.Printf("OSS 文件内容解析成功: %v", data)
}
return normalizeOSSConfig(config)
response := &OSSObjectContent{
Bucket: config.Bucket,
Key: config.ObjectKey,
Region: config.Region,
Config: data,
}
if result.ContentType != nil {
response.ContentType = *result.ContentType
}
if result.ETag != nil {
response.ETag = *result.ETag
}
if result.LastModified != nil {
response.LastModified = result.LastModified.Format(time.RFC3339)
}
return response, nil
}
func loadOSSConfigFromEnv() ossConfig {
return ossConfig{
Region: strings.TrimSpace(os.Getenv("OSS_REGION")),
Bucket: strings.TrimSpace(os.Getenv("OSS_BUCKET")),
ObjectKey: strings.TrimSpace(os.Getenv("OSS_OBJECT_KEY")),
AccessKeyID: strings.TrimSpace(os.Getenv("OSS_ACCESS_KEY_ID")),
AccessKeySecret: strings.TrimSpace(os.Getenv("OSS_ACCESS_KEY_SECRET")),
SessionToken: strings.TrimSpace(os.Getenv("OSS_SESSION_TOKEN")),
}
}
func loadOSSConfigFromNacos() (ossConfig, error) {
client, err := newNacosClient()
if err != nil {
return ossConfig{}, err
}
var config ossConfig
dataID := getEnvOrDefault("OSS_NACOS_DATA_ID", defaultNacosDataID)
group := getEnvOrDefault("OSS_NACOS_GROUP", getEnvOrDefault("NACOS_GROUP", defaultNacosGroup))
if err := client.GetJSON(dataID, group, &config); err != nil {
return ossConfig{}, err
}
return config, nil
}
func watchOSSConfigChanges() error {
client, err := newNacosClient()
if err != nil {
return err
}
dataID := getEnvOrDefault("OSS_NACOS_DATA_ID", defaultNacosDataID)
group := getEnvOrDefault("OSS_NACOS_GROUP", getEnvOrDefault("NACOS_GROUP", defaultNacosGroup))
return client.Listen(dataID, group, func(event nacos.ChangeEvent) {
var nacosConfig ossConfig
if err := json.Unmarshal([]byte(event.Content), &nacosConfig); err != nil {
log.Printf("解析 Nacos OSS 配置变更失败: %v", err)
return
func initOSSConfig() (ossConfig, error) {
ossInitOnce.Do(func() {
nacosClient, err := nacos.GetNacosClient()
ossCnf = ossConfig{
Region: defaultOSSRegion,
Bucket: defaultOSSBucket,
ObjectKey: defaultOSSObjectKey,
}
mergedConfig, err := normalizeOSSConfig(mergeOSSConfig(loadOSSConfigFromEnv(), nacosConfig))
if err != nil {
log.Printf("Nacos OSS 配置变更无效,保留旧配置: %v", err)
log.Printf("创建 Nacos 客户端失败: %v", err)
return
}
globalOSSRuntime.set(mergedConfig)
log.Printf("OSS 配置已从 Nacos 热更新: group=%s dataId=%s", event.Group, event.DataID)
})
}
func newNacosClient() (*nacos.Client, error) {
serverAddrs := strings.TrimSpace(os.Getenv("NACOS_SERVER_ADDRS"))
if serverAddrs == "" {
return nil, fmt.Errorf("NACOS_SERVER_ADDRS is empty")
}
return nacos.NewClient(nacos.Options{
Servers: parseNacosServers(serverAddrs),
Username: strings.TrimSpace(os.Getenv("NACOS_USERNAME")),
Password: strings.TrimSpace(os.Getenv("NACOS_PASSWORD")),
NamespaceID: strings.TrimSpace(os.Getenv("NACOS_NAMESPACE_ID")),
TimeoutMs: parseUintEnv("NACOS_TIMEOUT_MS", 5000),
LogDir: getEnvOrDefault("NACOS_LOG_DIR", defaultNacosLogDir),
CacheDir: getEnvOrDefault("NACOS_CACHE_DIR", defaultNacosCacheDir),
LogLevel: getEnvOrDefault("NACOS_LOG_LEVEL", "info"),
NotLoadCacheAtStart: true,
DefaultGroup: getEnvOrDefault("NACOS_GROUP", defaultNacosGroup),
})
}
func normalizeOSSConfig(config ossConfig) (ossConfig, error) {
if strings.TrimSpace(config.Region) == "" {
config.Region = defaultOSSRegion
}
if strings.TrimSpace(config.Bucket) == "" {
config.Bucket = defaultOSSBucket
}
if strings.TrimSpace(config.ObjectKey) == "" {
config.ObjectKey = defaultOSSObjectKey
}
if strings.TrimSpace(config.AccessKeyID) == "" || strings.TrimSpace(config.AccessKeySecret) == "" {
return ossConfig{}, fmt.Errorf("oss access key is empty after nacos and environment fallback")
}
return config, nil
}
func mergeOSSConfig(base ossConfig, override ossConfig) ossConfig {
if strings.TrimSpace(override.Region) != "" {
base.Region = strings.TrimSpace(override.Region)
}
if strings.TrimSpace(override.Bucket) != "" {
base.Bucket = strings.TrimSpace(override.Bucket)
}
if strings.TrimSpace(override.ObjectKey) != "" {
base.ObjectKey = strings.TrimSpace(override.ObjectKey)
}
if strings.TrimSpace(override.AccessKeyID) != "" {
base.AccessKeyID = strings.TrimSpace(override.AccessKeyID)
}
if strings.TrimSpace(override.AccessKeySecret) != "" {
base.AccessKeySecret = strings.TrimSpace(override.AccessKeySecret)
}
if strings.TrimSpace(override.SessionToken) != "" {
base.SessionToken = strings.TrimSpace(override.SessionToken)
}
return base
}
func parseNacosServers(raw string) []nacos.Server {
parts := strings.Split(raw, ",")
servers := make([]nacos.Server, 0, len(parts))
for _, part := range parts {
item := strings.TrimSpace(part)
if item == "" {
continue
err = nacosClient.GetJSON(defaultNacosDataID, defaultNacosGroup, &ossCnf)
if err != nil {
log.Printf("从 Nacos 获取 OSS 配置失败: %v", err)
}
server := nacos.Server{IPAddr: item}
if strings.Contains(item, ":") {
host, port, found := strings.Cut(item, ":")
if found {
server.IPAddr = strings.TrimSpace(host)
if parsedPort, convErr := strconv.ParseUint(strings.TrimSpace(port), 10, 64); convErr == nil {
server.Port = parsedPort
}
} else {
server.IPAddr = item
if ossCnf.Region == "" {
ossCnf.Region = defaultOSSRegion
}
if ossCnf.Bucket == "" {
ossCnf.Bucket = defaultOSSBucket
}
if ossCnf.ObjectKey == "" {
ossCnf.ObjectKey = defaultOSSObjectKey
}
nacosClient.Listen(defaultNacosDataID, defaultNacosGroup, func(event nacos.ChangeEvent) {
err = nacosClient.GetJSON(defaultNacosDataID, defaultNacosGroup, &ossCnf)
if err != nil {
log.Printf("从 Nacos 获取 OSS 配置失败: %v", err)
return
}
}
servers = append(servers, server)
}
return servers
}
log.Printf("Nacos 配置已更新: %v", ossCnf)
})
})
func (r *ossRuntime) set(config ossConfig) {
r.mu.Lock()
defer r.mu.Unlock()
r.config = config
}
func (r *ossRuntime) get() (ossConfig, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
if strings.TrimSpace(r.config.AccessKeyID) == "" || strings.TrimSpace(r.config.AccessKeySecret) == "" {
return ossConfig{}, false
}
return r.config, true
}
func parseUintEnv(key string, defaultValue uint64) uint64 {
raw := strings.TrimSpace(os.Getenv(key))
if raw == "" {
return defaultValue
}
value, err := strconv.ParseUint(raw, 10, 64)
if err != nil {
return defaultValue
}
return value
}
func getEnvOrDefault(key string, defaultValue string) string {
value := strings.TrimSpace(os.Getenv(key))
if value == "" {
return defaultValue
}
return value
return ossCnf, nil
}

View File

@ -23,8 +23,10 @@ func ValidateToken() gin.HandlerFunc {
c.Set("admin", tokenInfo.UserName)
}
// 获取请求路径和方法
path := c.Request.URL.Path
path := c.FullPath()
if path == "" {
path = c.Request.URL.Path
}
// 验证用户权限
if tokenInfo != nil {

View File

@ -55,7 +55,7 @@ func GetUserList(AppId, ServerId, PageSize, CurrentPage, Uid, StartTime, EndTime
}
func UserDetail2(appId, uid, node int) (*msg.ResUserDetailInfo, error) {
r, err := client.GetUserInfo(appId, node, &msg.UserDetailParam{
r, err := client.UserDetail(appId, node, &msg.UserDetailParam{
Uid: int64(uid),
})
if err != nil {
@ -64,24 +64,6 @@ func UserDetail2(appId, uid, node int) (*msg.ResUserDetailInfo, error) {
return r, nil
}
func UserDetail(appId, uid, node int) (map[string]interface{}, error) {
req := &msg.ReqAdminInfo{
Uid: int64(uid),
}
ws, err := util.GetWebsocket(appId, node)
if err != nil {
return nil, fmt.Errorf("failed to get websocket: %v", err)
}
defer ws.Close()
r, err := util.SendAdminMsg(ws, req)
if err != nil {
return nil, fmt.Errorf("failed to send admin message: %v", err)
}
return r, nil
}
func UserGM(AppId, ServerId, Uid int, Command string) (interface{}, error) {
req := &msg.ReqAdminGm{
Uid: int64(Uid),
@ -93,17 +75,11 @@ func UserGM(AppId, ServerId, Uid int, Command string) (interface{}, error) {
defer db.Close()
node := 0
err = db.Get(&node, "SELECT `node` FROM t_player_baseinfo WHERE dwUin = ? ", Uid)
ws, err := util.GetWebsocket(AppId, node)
if err != nil {
return nil, fmt.Errorf("failed to get websocket: %v", err)
}
defer ws.Close()
r, err := util.SendAdminMsg(ws, req)
resp, err := client.PlayerGm(AppId, node, req)
if err != nil {
return nil, fmt.Errorf("failed to send admin message: %v", err)
}
return r, nil
return resp, nil
}
func UserBan(AppId, ServerId, Uid int, Day int, Reason string) (interface{}, error) {
@ -114,15 +90,9 @@ func UserBan(AppId, ServerId, Uid int, Day int, Reason string) (interface{}, err
Reason: Reason,
}
AppId, ServerId = util.ParseUid(Uid)
ws, err := util.GetWebsocket(AppId, ServerId)
if err != nil {
return nil, fmt.Errorf("failed to get websocket: %v", err)
}
defer ws.Close()
r, err := util.SendAdminMsg(ws, req)
resp, err := client.BanPlayer(AppId, ServerId, req)
if err != nil {
return nil, fmt.Errorf("failed to send admin message: %v", err)
}
return r, nil
return resp, nil
}

View File

@ -142,7 +142,7 @@ func (a *ActivityMod) reloadCfg() error {
ServerList := util.GetServerInfo(a.AppId, 0)
for _, server := range ServerList {
go func(serverInfo util.ServerConfig) {
err := client.ActivityCfgReload(serverInfo.AppId, serverInfo.ServerId, nil)
err := client.ReloadActivity(serverInfo.AppId, serverInfo.ServerId, nil)
if err != nil {
log.Printf("failed to sync activity cfg: %v", err)
}

View File

@ -194,36 +194,46 @@ func SendPhoneCode(phone string) error {
}
var PermissionList = map[string][]string{
"/api/admin/list": {"super", "admin"},
"/api/admin/add": {"super"},
"/api/admin/log/list": {"super", "admin"},
"/api/log/user": {"super", "admin", "user"},
"/api/log/asset": {"super", "admin", "user"},
"/api/log/event": {"super", "admin", "user"},
"/api/log/order": {"super", "admin", "user"},
"/api/user/list": {"super", "admin", "user"},
"/api/user/gm": {"super", "admin"},
"/api/user/ban": {"super", "admin"},
"/api/server/list": {"super", "admin"},
"/api/server/serverList": {"super", "admin"},
"/api/server/addServer": {"super"},
"/api/server/nodeList": {"super"},
"/api/server/addNode": {"super"},
"/api/server/updateApp": {"super", "admin"},
"/api/server/restart": {"super", "admin"},
"/api/server/reload": {"super", "admin"},
"/api/statistics/level": {"super", "admin"},
"/api/statistics/order": {"super", "admin"},
"/api/statistics/info": {"super", "admin"},
"/api/statistics/heat": {"super", "admin"},
"/api/mail/send": {"super", "admin"},
"/api/mail/list": {"super", "admin"},
"/api/mail/delete": {"super", "admin"},
"/api/language/list": {"super", "admin", "wb_transfer"},
"/api/language/export": {"super", "admin", "wb_transfer"},
"/api/language/save": {"super", "admin", "wb_transfer"},
"/api/language/add": {"super", "admin", "wb_transfer"},
"/api/language/delete": {"super", "admin"},
"/api/admin/list": {"super", "admin"},
"/api/admin/add": {"super"},
"/api/admin/log/list": {"super", "admin"},
"/api/log/user": {"super", "admin", "user"},
"/api/log/asset": {"super", "admin", "user"},
"/api/log/event": {"super", "admin", "user"},
"/api/log/order": {"super", "admin", "user"},
"/api/user/list": {"super", "admin", "user"},
"/api/user/gm": {"super", "admin"},
"/api/user/ban": {"super", "admin"},
"/api/server/list": {"super", "admin"},
"/api/server/serverList": {"super", "admin"},
"/api/server/addServer": {"super"},
"/api/server/nodeList": {"super"},
"/api/server/addNode": {"super"},
"/api/server/updateApp": {"super", "admin"},
"/api/server/restart": {"super", "admin"},
"/api/server/reload": {"super", "admin"},
"/api/statistics/level": {"super", "admin"},
"/api/statistics/order": {"super", "admin"},
"/api/statistics/info": {"super", "admin"},
"/api/statistics/heat": {"super", "admin"},
"/api/mail/send": {"super", "admin"},
"/api/mail/list": {"super", "admin"},
"/api/mail/delete": {"super", "admin"},
"/api/language/list": {"super", "admin", "wb_transfer"},
"/api/language/export": {"super", "admin", "wb_transfer"},
"/api/language/save": {"super", "admin", "wb_transfer"},
"/api/language/add": {"super", "admin", "wb_transfer"},
"/api/language/delete": {"super", "admin"},
"/api/client/notification": {"super", "admin"},
"/api/v1/experiments": {"super", "admin"},
"/api/v1/experiments/:id": {"super", "admin"},
"/api/v1/experiments/:id/variants": {"super", "admin"},
"/api/v1/experiments/:id/variants/:variantId": {"super", "admin"},
"/api/v1/experiments/:id/whitelist": {"super", "admin"},
"/api/v1/experiments/:id/whitelist/batch": {"super", "admin"},
"/api/v1/experiments/:id/whitelist/:userId": {"super", "admin"},
"/api/v1/experiments/:id/results": {"super", "admin"},
"/api/v1/users/:userId/groups": {"super", "admin"},
}
func CheckUserPermission(username, path string) bool {

View File

@ -1,6 +1,7 @@
package model
import (
"backend/client"
"backend/msg"
util "backend/util"
"fmt"
@ -96,18 +97,12 @@ func (m *Mail) SendMail() error {
ServerList := util.GetServerInfo(m.AppId, 0)
for _, server := range ServerList {
go func(serverInfo util.ServerConfig) {
ws, err := util.GetWebsocket(m.AppId, server.ServerId)
if err != nil {
log.Printf("failed to get websocket: %v", err)
}
defer ws.Close()
req := &msg.ReqReloadServerMail{}
_, err = util.SendAdminMsg(ws, req)
_, err := client.ReloadMail(serverInfo.AppId, serverInfo.ServerId, req)
if err != nil {
log.Printf("failed to send admin message: %v", err)
}
log.Printf("sent reload mail message to server %d", server.ServerId)
log.Printf("sent reload mail message to server %d", serverInfo.ServerId)
}(server)
}

View File

@ -2,6 +2,7 @@ package model
import (
"backend/Type"
"backend/client"
"backend/middleware/alibaba"
"backend/middleware/feishu"
"backend/msg"
@ -365,14 +366,8 @@ func (s *Server) ReloadServer() (string, error) {
if err != nil {
return "", err
}
ws, err := util.GetWebsocket(s.AppId, s.ServerId)
if err != nil {
return "", fmt.Errorf("failed to get websocket: %v", err)
}
defer ws.Close()
req := &msg.ReqReload{}
_, err = util.SendAdminMsg(ws, req)
_, err = client.ServerReload(s.AppId, s.ServerId, req)
if err != nil {
log.Printf("failed to send admin message: %v", err)
}

View File

@ -30648,6 +30648,110 @@ func (x *ResServerInfo) GetVersion() string {
return ""
}
type ResAdminGm struct {
state protoimpl.MessageState `protogen:"open.v1"`
Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"`
Msg string `protobuf:"bytes,2,opt,name=Msg,proto3" json:"Msg,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResAdminGm) Reset() {
*x = ResAdminGm{}
mi := &file_proto_Gameapi_proto_msgTypes[518]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResAdminGm) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResAdminGm) ProtoMessage() {}
func (x *ResAdminGm) ProtoReflect() protoreflect.Message {
mi := &file_proto_Gameapi_proto_msgTypes[518]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResAdminGm.ProtoReflect.Descriptor instead.
func (*ResAdminGm) Descriptor() ([]byte, []int) {
return file_proto_Gameapi_proto_rawDescGZIP(), []int{518}
}
func (x *ResAdminGm) GetCode() int32 {
if x != nil {
return x.Code
}
return 0
}
func (x *ResAdminGm) GetMsg() string {
if x != nil {
return x.Msg
}
return ""
}
type ResBackend struct {
state protoimpl.MessageState `protogen:"open.v1"`
Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"`
Msg string `protobuf:"bytes,2,opt,name=Msg,proto3" json:"Msg,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResBackend) Reset() {
*x = ResBackend{}
mi := &file_proto_Gameapi_proto_msgTypes[519]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResBackend) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResBackend) ProtoMessage() {}
func (x *ResBackend) ProtoReflect() protoreflect.Message {
mi := &file_proto_Gameapi_proto_msgTypes[519]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResBackend.ProtoReflect.Descriptor instead.
func (*ResBackend) Descriptor() ([]byte, []int) {
return file_proto_Gameapi_proto_rawDescGZIP(), []int{519}
}
func (x *ResBackend) GetCode() int32 {
if x != nil {
return x.Code
}
return 0
}
func (x *ResBackend) GetMsg() string {
if x != nil {
return x.Msg
}
return ""
}
var File_proto_Gameapi_proto protoreflect.FileDescriptor
const file_proto_Gameapi_proto_rawDesc = "" +
@ -32927,7 +33031,15 @@ const file_proto_Gameapi_proto_rawDesc = "" +
"\fNumGoroutine\x18\t \x01(\x05R\fNumGoroutine\x12\x1a\n" +
"\bUsageMem\x18\n" +
" \x01(\x04R\bUsageMem\x12\x18\n" +
"\aVersion\x18\v \x01(\tR\aVersion*\xab\f\n" +
"\aVersion\x18\v \x01(\tR\aVersion\"2\n" +
"\n" +
"ResAdminGm\x12\x12\n" +
"\x04Code\x18\x01 \x01(\x05R\x04Code\x12\x10\n" +
"\x03Msg\x18\x02 \x01(\tR\x03Msg\"2\n" +
"\n" +
"ResBackend\x12\x12\n" +
"\x04Code\x18\x01 \x01(\x05R\x04Code\x12\x10\n" +
"\x03Msg\x18\x02 \x01(\tR\x03Msg*\xab\f\n" +
"\x0eITEM_POP_LABEL\x12\f\n" +
"\bPlayroom\x10\x00\x12\r\n" +
"\tPiggyBank\x10\x01\x12\n" +
@ -33175,14 +33287,19 @@ const file_proto_Gameapi_proto_rawDesc = "" +
"\x1cFRIEND_REPLY_HANDLE_ERR_TYPE\x12\b\n" +
"\x04NONE\x10\x00\x12\n" +
"\n" +
"\x06CATNIP\x10\x012\xa6\x02\n" +
"\x06CATNIP\x10\x012\x96\x04\n" +
"\aBackend\x12P\n" +
"\x0eReloadActivity\x12\x1e.tutorial.ReqActivityCfgReload\x1a\x1e.tutorial.ResActivityCfgReload\x12G\n" +
"\rOrderShipping\x12\x1a.tutorial.ReqOrderShipping\x1a\x1a.tutorial.ResOrderShipping\x12@\n" +
"\n" +
"UserDetail\x12\x19.tutorial.UserDetailParam\x1a\x17.tutorial.ResUserDetail\x12>\n" +
"\n" +
"ServerInfo\x12\x17.tutorial.ReqServerInfo\x1a\x17.tutorial.ResServerInfoB\bZ\x06../msgb\x06proto3"
"ServerInfo\x12\x17.tutorial.ReqServerInfo\x1a\x17.tutorial.ResServerInfo\x126\n" +
"\bPlayerGm\x12\x14.tutorial.ReqAdminGm\x1a\x14.tutorial.ResAdminGm\x128\n" +
"\tBanPlayer\x12\x15.tutorial.ReqAdminBan\x1a\x14.tutorial.ResBackend\x12A\n" +
"\n" +
"ReloadMail\x12\x1d.tutorial.ReqReloadServerMail\x1a\x14.tutorial.ResBackend\x129\n" +
"\fServerReload\x12\x13.tutorial.ReqReload\x1a\x14.tutorial.ResBackendB\bZ\x06../msgb\x06proto3"
var (
file_proto_Gameapi_proto_rawDescOnce sync.Once
@ -33197,7 +33314,7 @@ func file_proto_Gameapi_proto_rawDescGZIP() []byte {
}
var file_proto_Gameapi_proto_enumTypes = make([]protoimpl.EnumInfo, 14)
var file_proto_Gameapi_proto_msgTypes = make([]protoimpl.MessageInfo, 594)
var file_proto_Gameapi_proto_msgTypes = make([]protoimpl.MessageInfo, 596)
var file_proto_Gameapi_proto_goTypes = []any{
(ITEM_POP_LABEL)(0), // 0: tutorial.ITEM_POP_LABEL
(HANDLE_TYPE)(0), // 1: tutorial.HANDLE_TYPE
@ -33731,120 +33848,122 @@ var file_proto_Gameapi_proto_goTypes = []any{
(*UserDetailOrderInfoChess)(nil), // 529: tutorial.UserDetailOrderInfoChess
(*ResUserDetailInfo)(nil), // 530: tutorial.ResUserDetailInfo
(*ResServerInfo)(nil), // 531: tutorial.ResServerInfo
nil, // 532: tutorial.ResChessColorData.MChessColorDataEntry
nil, // 533: tutorial.UpdateBaseItemInfo.MUpdateItemEntry
nil, // 534: tutorial.ResPlayerChessData.MChessDataEntry
nil, // 535: tutorial.ReqPutPartInBag.MChessDataEntry
nil, // 536: tutorial.UpdatePlayerChessData.MChessDataEntry
nil, // 537: tutorial.ReqSeparateChess.MChessDataEntry
nil, // 538: tutorial.ReqUpgradeChess.MChessDataEntry
nil, // 539: tutorial.ReqGetChessFromBuff.MChessDataEntry
nil, // 540: tutorial.ReqChessEx.MChessDataEntry
nil, // 541: tutorial.ReqSourceChest.MChessDataEntry
nil, // 542: tutorial.ReqPlayroomOutline.MChessDataEntry
nil, // 543: tutorial.ReqPutChessInBag.MChessDataEntry
nil, // 544: tutorial.ReqTakeChessOutBag.MChessDataEntry
nil, // 545: tutorial.ResPlayerBriefProfileData.SetEmojiEntry
nil, // 546: tutorial.UserInfo.SetEmojiEntry
nil, // 547: tutorial.ReqRewardOrder.MChessDataEntry
nil, // 548: tutorial.ResCardInfo.AllCardEntry
nil, // 549: tutorial.ResCardInfo.HandbookEntry
nil, // 550: tutorial.ResGuideInfo.RewardEntry
nil, // 551: tutorial.ResGuideTask.TaskEntry
nil, // 552: tutorial.ResDailyTask.WeekRewardEntry
nil, // 553: tutorial.ResDailyTask.DailyTaskEntry
nil, // 554: tutorial.ResLimitEvent.LimitEventListEntry
nil, // 555: tutorial.ResLimitEventProgress.ProgressRewardEntry
nil, // 556: tutorial.LimitEvent.ParamEntry
nil, // 557: tutorial.ReqLimitEventLuckyCat.MChessDataEntry
nil, // 558: tutorial.ResFriendPlayerSimple.EmojiEntry
nil, // 559: tutorial.ResFriendPlayerSimple.PlayroomEntry
nil, // 560: tutorial.ResFriendPlayerSimple.DressSetEntry
nil, // 561: tutorial.ResFriendPlayerSimple.PhysiologyEntry
nil, // 562: tutorial.ResPlayerSimple.EmojiEntry
nil, // 563: tutorial.ResPlayerRank.PlayroomSetEntry
nil, // 564: tutorial.ResPlayerRank.DressSetEntry
nil, // 565: tutorial.ResKv.KvEntry
nil, // 566: tutorial.ResRank.RankListEntry
nil, // 567: tutorial.ResMailList.MailListEntry
nil, // 568: tutorial.ResCharge.SpecialShopEntry
nil, // 569: tutorial.ResCharge.ChessShopEntry
nil, // 570: tutorial.ResCharge.GiftEntry
nil, // 571: tutorial.ResCharge.WeeklyDiscountEntry
nil, // 572: tutorial.ReqBuyChessShop2.MChessDataEntry
nil, // 573: tutorial.ResEndless.EndlessListEntry
nil, // 574: tutorial.ResChampshipRank.RankListEntry
nil, // 575: tutorial.ResChampshipPreRank.RankListEntry
nil, // 576: tutorial.ResNotifyCard.CardEntry
nil, // 577: tutorial.ResNotifyCard.MasterEntry
nil, // 578: tutorial.ResNotifyCard.HandbookEntry
nil, // 579: tutorial.ResMining.MapEntry
nil, // 580: tutorial.ResMining.PassRewardEntry
nil, // 581: tutorial.ReqMiningTake.MapEntry
nil, // 582: tutorial.ResActRed.RedEntry
nil, // 583: tutorial.ResItem.ItemEntry
nil, // 584: tutorial.ItemNotify.ItemEntry
nil, // 585: tutorial.ResGuessColor.OMapEntry
nil, // 586: tutorial.ReqGuessColorTake.OMapEntry
nil, // 587: tutorial.GuessColorInfo.MapEntry
nil, // 588: tutorial.ResPlayroom.PlayroomEntry
nil, // 589: tutorial.ResPlayroom.MoodEntry
nil, // 590: tutorial.ResPlayroom.PhysiologyEntry
nil, // 591: tutorial.ResPlayroom.DressEntry
nil, // 592: tutorial.ResPlayroom.DressSetEntry
nil, // 593: tutorial.ResPlayroom.WeeklyDiscountEntry
nil, // 594: tutorial.ReqPlayroomDressSet.DressSetEntry
nil, // 595: tutorial.NotifyPlayroomMood.MoodEntry
nil, // 596: tutorial.NotifyPlayroomMood.PhysiologyEntry
nil, // 597: tutorial.ResPlayroomInfo.PlayroomEntry
nil, // 598: tutorial.ResPlayroomInfo.ItemsEntry
nil, // 599: tutorial.ResPlayroomInfo.FlipEntry
nil, // 600: tutorial.ResPlayroomInfo.EmojiEntry
nil, // 601: tutorial.ResPlayroomInfo.DressSetEntry
nil, // 602: tutorial.ResPlayroomGame.ItemsEntry
nil, // 603: tutorial.ReqPlayroomSetRoom.PlayroomEntry
nil, // 604: tutorial.MiningCfg.GemEntry
nil, // 605: tutorial.MiningCfg.JackpotEntry
nil, // 606: tutorial.MiningCfg.PassEntry
nil, // 607: tutorial.ResUserDetailInfo.ChessMapEntry
(*ResAdminGm)(nil), // 532: tutorial.ResAdminGm
(*ResBackend)(nil), // 533: tutorial.ResBackend
nil, // 534: tutorial.ResChessColorData.MChessColorDataEntry
nil, // 535: tutorial.UpdateBaseItemInfo.MUpdateItemEntry
nil, // 536: tutorial.ResPlayerChessData.MChessDataEntry
nil, // 537: tutorial.ReqPutPartInBag.MChessDataEntry
nil, // 538: tutorial.UpdatePlayerChessData.MChessDataEntry
nil, // 539: tutorial.ReqSeparateChess.MChessDataEntry
nil, // 540: tutorial.ReqUpgradeChess.MChessDataEntry
nil, // 541: tutorial.ReqGetChessFromBuff.MChessDataEntry
nil, // 542: tutorial.ReqChessEx.MChessDataEntry
nil, // 543: tutorial.ReqSourceChest.MChessDataEntry
nil, // 544: tutorial.ReqPlayroomOutline.MChessDataEntry
nil, // 545: tutorial.ReqPutChessInBag.MChessDataEntry
nil, // 546: tutorial.ReqTakeChessOutBag.MChessDataEntry
nil, // 547: tutorial.ResPlayerBriefProfileData.SetEmojiEntry
nil, // 548: tutorial.UserInfo.SetEmojiEntry
nil, // 549: tutorial.ReqRewardOrder.MChessDataEntry
nil, // 550: tutorial.ResCardInfo.AllCardEntry
nil, // 551: tutorial.ResCardInfo.HandbookEntry
nil, // 552: tutorial.ResGuideInfo.RewardEntry
nil, // 553: tutorial.ResGuideTask.TaskEntry
nil, // 554: tutorial.ResDailyTask.WeekRewardEntry
nil, // 555: tutorial.ResDailyTask.DailyTaskEntry
nil, // 556: tutorial.ResLimitEvent.LimitEventListEntry
nil, // 557: tutorial.ResLimitEventProgress.ProgressRewardEntry
nil, // 558: tutorial.LimitEvent.ParamEntry
nil, // 559: tutorial.ReqLimitEventLuckyCat.MChessDataEntry
nil, // 560: tutorial.ResFriendPlayerSimple.EmojiEntry
nil, // 561: tutorial.ResFriendPlayerSimple.PlayroomEntry
nil, // 562: tutorial.ResFriendPlayerSimple.DressSetEntry
nil, // 563: tutorial.ResFriendPlayerSimple.PhysiologyEntry
nil, // 564: tutorial.ResPlayerSimple.EmojiEntry
nil, // 565: tutorial.ResPlayerRank.PlayroomSetEntry
nil, // 566: tutorial.ResPlayerRank.DressSetEntry
nil, // 567: tutorial.ResKv.KvEntry
nil, // 568: tutorial.ResRank.RankListEntry
nil, // 569: tutorial.ResMailList.MailListEntry
nil, // 570: tutorial.ResCharge.SpecialShopEntry
nil, // 571: tutorial.ResCharge.ChessShopEntry
nil, // 572: tutorial.ResCharge.GiftEntry
nil, // 573: tutorial.ResCharge.WeeklyDiscountEntry
nil, // 574: tutorial.ReqBuyChessShop2.MChessDataEntry
nil, // 575: tutorial.ResEndless.EndlessListEntry
nil, // 576: tutorial.ResChampshipRank.RankListEntry
nil, // 577: tutorial.ResChampshipPreRank.RankListEntry
nil, // 578: tutorial.ResNotifyCard.CardEntry
nil, // 579: tutorial.ResNotifyCard.MasterEntry
nil, // 580: tutorial.ResNotifyCard.HandbookEntry
nil, // 581: tutorial.ResMining.MapEntry
nil, // 582: tutorial.ResMining.PassRewardEntry
nil, // 583: tutorial.ReqMiningTake.MapEntry
nil, // 584: tutorial.ResActRed.RedEntry
nil, // 585: tutorial.ResItem.ItemEntry
nil, // 586: tutorial.ItemNotify.ItemEntry
nil, // 587: tutorial.ResGuessColor.OMapEntry
nil, // 588: tutorial.ReqGuessColorTake.OMapEntry
nil, // 589: tutorial.GuessColorInfo.MapEntry
nil, // 590: tutorial.ResPlayroom.PlayroomEntry
nil, // 591: tutorial.ResPlayroom.MoodEntry
nil, // 592: tutorial.ResPlayroom.PhysiologyEntry
nil, // 593: tutorial.ResPlayroom.DressEntry
nil, // 594: tutorial.ResPlayroom.DressSetEntry
nil, // 595: tutorial.ResPlayroom.WeeklyDiscountEntry
nil, // 596: tutorial.ReqPlayroomDressSet.DressSetEntry
nil, // 597: tutorial.NotifyPlayroomMood.MoodEntry
nil, // 598: tutorial.NotifyPlayroomMood.PhysiologyEntry
nil, // 599: tutorial.ResPlayroomInfo.PlayroomEntry
nil, // 600: tutorial.ResPlayroomInfo.ItemsEntry
nil, // 601: tutorial.ResPlayroomInfo.FlipEntry
nil, // 602: tutorial.ResPlayroomInfo.EmojiEntry
nil, // 603: tutorial.ResPlayroomInfo.DressSetEntry
nil, // 604: tutorial.ResPlayroomGame.ItemsEntry
nil, // 605: tutorial.ReqPlayroomSetRoom.PlayroomEntry
nil, // 606: tutorial.MiningCfg.GemEntry
nil, // 607: tutorial.MiningCfg.JackpotEntry
nil, // 608: tutorial.MiningCfg.PassEntry
nil, // 609: tutorial.ResUserDetailInfo.ChessMapEntry
}
var file_proto_Gameapi_proto_depIdxs = []int32{
532, // 0: tutorial.ResChessColorData.mChessColorData:type_name -> tutorial.ResChessColorData.MChessColorDataEntry
534, // 0: tutorial.ResChessColorData.mChessColorData:type_name -> tutorial.ResChessColorData.MChessColorDataEntry
5, // 1: tutorial.ReqLogin.type:type_name -> tutorial.LOGIN_TYPE
2, // 2: tutorial.ResId2Verify.ResultCode:type_name -> tutorial.RES_CODE
533, // 3: tutorial.UpdateBaseItemInfo.mUpdateItem:type_name -> tutorial.UpdateBaseItemInfo.MUpdateItemEntry
534, // 4: tutorial.ResPlayerChessData.mChessData:type_name -> tutorial.ResPlayerChessData.MChessDataEntry
535, // 3: tutorial.UpdateBaseItemInfo.mUpdateItem:type_name -> tutorial.UpdateBaseItemInfo.MUpdateItemEntry
536, // 4: tutorial.ResPlayerChessData.mChessData:type_name -> tutorial.ResPlayerChessData.MChessDataEntry
74, // 5: tutorial.ResPlayerChessInfo.ChessBag:type_name -> tutorial.ChessBag
55, // 6: tutorial.ResPlayerChessInfo.PartBag:type_name -> tutorial.PartBag
2, // 7: tutorial.ResGetChessRetireReward.code:type_name -> tutorial.RES_CODE
56, // 8: tutorial.PartBag.PartBagGrids:type_name -> tutorial.PartBagGrid
535, // 9: tutorial.ReqPutPartInBag.mChessData:type_name -> tutorial.ReqPutPartInBag.MChessDataEntry
537, // 9: tutorial.ReqPutPartInBag.mChessData:type_name -> tutorial.ReqPutPartInBag.MChessDataEntry
2, // 10: tutorial.ResPutPartInBag.code:type_name -> tutorial.RES_CODE
1, // 11: tutorial.ChessHandle.type:type_name -> tutorial.HANDLE_TYPE
536, // 12: tutorial.UpdatePlayerChessData.mChessData:type_name -> tutorial.UpdatePlayerChessData.MChessDataEntry
538, // 12: tutorial.UpdatePlayerChessData.mChessData:type_name -> tutorial.UpdatePlayerChessData.MChessDataEntry
59, // 13: tutorial.UpdatePlayerChessData.mChessHandle:type_name -> tutorial.ChessHandle
2, // 14: tutorial.ResUpdatePlayerChessData.code:type_name -> tutorial.RES_CODE
537, // 15: tutorial.ReqSeparateChess.mChessData:type_name -> tutorial.ReqSeparateChess.MChessDataEntry
539, // 15: tutorial.ReqSeparateChess.mChessData:type_name -> tutorial.ReqSeparateChess.MChessDataEntry
2, // 16: tutorial.ResSeparateChess.code:type_name -> tutorial.RES_CODE
538, // 17: tutorial.ReqUpgradeChess.mChessData:type_name -> tutorial.ReqUpgradeChess.MChessDataEntry
540, // 17: tutorial.ReqUpgradeChess.mChessData:type_name -> tutorial.ReqUpgradeChess.MChessDataEntry
2, // 18: tutorial.ResUpgradeChess.code:type_name -> tutorial.RES_CODE
539, // 19: tutorial.ReqGetChessFromBuff.mChessData:type_name -> tutorial.ReqGetChessFromBuff.MChessDataEntry
541, // 19: tutorial.ReqGetChessFromBuff.mChessData:type_name -> tutorial.ReqGetChessFromBuff.MChessDataEntry
2, // 20: tutorial.ResGetChessFromBuff.code:type_name -> tutorial.RES_CODE
7, // 21: tutorial.ReqChessEx.Type:type_name -> tutorial.CHESS_EX_TYPE
540, // 22: tutorial.ReqChessEx.mChessData:type_name -> tutorial.ReqChessEx.MChessDataEntry
542, // 22: tutorial.ReqChessEx.mChessData:type_name -> tutorial.ReqChessEx.MChessDataEntry
2, // 23: tutorial.ResChessEx.code:type_name -> tutorial.RES_CODE
541, // 24: tutorial.ReqSourceChest.mChessData:type_name -> tutorial.ReqSourceChest.MChessDataEntry
543, // 24: tutorial.ReqSourceChest.mChessData:type_name -> tutorial.ReqSourceChest.MChessDataEntry
2, // 25: tutorial.ResSourceChest.code:type_name -> tutorial.RES_CODE
542, // 26: tutorial.ReqPlayroomOutline.mChessData:type_name -> tutorial.ReqPlayroomOutline.MChessDataEntry
544, // 26: tutorial.ReqPlayroomOutline.mChessData:type_name -> tutorial.ReqPlayroomOutline.MChessDataEntry
2, // 27: tutorial.ResPlayroomOutline.code:type_name -> tutorial.RES_CODE
75, // 28: tutorial.ChessBag.ChessBagGrids:type_name -> tutorial.ChessBagGrid
543, // 29: tutorial.ReqPutChessInBag.mChessData:type_name -> tutorial.ReqPutChessInBag.MChessDataEntry
545, // 29: tutorial.ReqPutChessInBag.mChessData:type_name -> tutorial.ReqPutChessInBag.MChessDataEntry
2, // 30: tutorial.ResPutChessInBag.code:type_name -> tutorial.RES_CODE
544, // 31: tutorial.ReqTakeChessOutBag.mChessData:type_name -> tutorial.ReqTakeChessOutBag.MChessDataEntry
546, // 31: tutorial.ReqTakeChessOutBag.mChessData:type_name -> tutorial.ReqTakeChessOutBag.MChessDataEntry
2, // 32: tutorial.ResTakeChessOutBag.code:type_name -> tutorial.RES_CODE
2, // 33: tutorial.ResTakeChessOutBagToHonor.code:type_name -> tutorial.RES_CODE
2, // 34: tutorial.ResBuyChessBagGrid.code:type_name -> tutorial.RES_CODE
545, // 35: tutorial.ResPlayerBriefProfileData.SetEmoji:type_name -> tutorial.ResPlayerBriefProfileData.SetEmojiEntry
547, // 35: tutorial.ResPlayerBriefProfileData.SetEmoji:type_name -> tutorial.ResPlayerBriefProfileData.SetEmojiEntry
2, // 36: tutorial.ResSetEnergyMul.ResultCode:type_name -> tutorial.RES_CODE
8, // 37: tutorial.ReqLang.Lang:type_name -> tutorial.LANG_TYPE
2, // 38: tutorial.ResLang.ResultCode:type_name -> tutorial.RES_CODE
@ -33852,7 +33971,7 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
195, // 40: tutorial.UserInfo.AvatarList:type_name -> tutorial.AvatarInfo
191, // 41: tutorial.UserInfo.FaceList:type_name -> tutorial.FaceInfo
198, // 42: tutorial.UserInfo.EmojiList:type_name -> tutorial.EmojiInfo
546, // 43: tutorial.UserInfo.SetEmoji:type_name -> tutorial.UserInfo.SetEmojiEntry
548, // 43: tutorial.UserInfo.SetEmoji:type_name -> tutorial.UserInfo.SetEmojiEntry
2, // 44: tutorial.ResSetName.ResultCode:type_name -> tutorial.RES_CODE
2, // 45: tutorial.ResSetPetName.ResultCode:type_name -> tutorial.RES_CODE
2, // 46: tutorial.ResBuyEnergy.Code:type_name -> tutorial.RES_CODE
@ -33860,7 +33979,7 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
2, // 48: tutorial.ResGetHandbookReward.Code:type_name -> tutorial.RES_CODE
105, // 49: tutorial.Handbook.Handbooks:type_name -> tutorial.HandbookInfo
2, // 50: tutorial.ResHandbookAllReward.Code:type_name -> tutorial.RES_CODE
547, // 51: tutorial.ReqRewardOrder.mChessData:type_name -> tutorial.ReqRewardOrder.MChessDataEntry
549, // 51: tutorial.ReqRewardOrder.mChessData:type_name -> tutorial.ReqRewardOrder.MChessDataEntry
2, // 52: tutorial.ResRewardOrder.Code:type_name -> tutorial.RES_CODE
2, // 53: tutorial.ResDelOrder.Code:type_name -> tutorial.RES_CODE
171, // 54: tutorial.Order.Items:type_name -> tutorial.ItemInfo
@ -33871,8 +33990,8 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
2, // 59: tutorial.ResDecorateAll.Code:type_name -> tutorial.RES_CODE
2, // 60: tutorial.ResAreaReward.Code:type_name -> tutorial.RES_CODE
127, // 61: tutorial.ResCardInfo.CardList:type_name -> tutorial.Card
548, // 62: tutorial.ResCardInfo.AllCard:type_name -> tutorial.ResCardInfo.AllCardEntry
549, // 63: tutorial.ResCardInfo.Handbook:type_name -> tutorial.ResCardInfo.HandbookEntry
550, // 62: tutorial.ResCardInfo.AllCard:type_name -> tutorial.ResCardInfo.AllCardEntry
551, // 63: tutorial.ResCardInfo.Handbook:type_name -> tutorial.ResCardInfo.HandbookEntry
2, // 64: tutorial.ResCardSeasonFirstReward.Code:type_name -> tutorial.RES_CODE
2, // 65: tutorial.ResCardHandbookReward.Code:type_name -> tutorial.RES_CODE
2, // 66: tutorial.ResMasterCard.Code:type_name -> tutorial.RES_CODE
@ -33891,16 +34010,16 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
2, // 79: tutorial.ResGetFriendCard.Code:type_name -> tutorial.RES_CODE
2, // 80: tutorial.ResGuideReward.Code:type_name -> tutorial.RES_CODE
2, // 81: tutorial.ResGuidePlayroom.Code:type_name -> tutorial.RES_CODE
550, // 82: tutorial.ResGuideInfo.Reward:type_name -> tutorial.ResGuideInfo.RewardEntry
552, // 82: tutorial.ResGuideInfo.Reward:type_name -> tutorial.ResGuideInfo.RewardEntry
171, // 83: tutorial.ResItemPop.Items:type_name -> tutorial.ItemInfo
173, // 84: tutorial.ResItemPop.CardPacks:type_name -> tutorial.CardPack
171, // 85: tutorial.ItemList.List:type_name -> tutorial.ItemInfo
551, // 86: tutorial.ResGuideTask.Task:type_name -> tutorial.ResGuideTask.TaskEntry
553, // 86: tutorial.ResGuideTask.Task:type_name -> tutorial.ResGuideTask.TaskEntry
183, // 87: tutorial.GuideTask.Progress:type_name -> tutorial.QuestProgress
2, // 88: tutorial.ResGetGuideTaskReward.Code:type_name -> tutorial.RES_CODE
2, // 89: tutorial.ResGetGuideActiveReward.Code:type_name -> tutorial.RES_CODE
552, // 90: tutorial.ResDailyTask.WeekReward:type_name -> tutorial.ResDailyTask.WeekRewardEntry
553, // 91: tutorial.ResDailyTask.DailyTask:type_name -> tutorial.ResDailyTask.DailyTaskEntry
554, // 90: tutorial.ResDailyTask.WeekReward:type_name -> tutorial.ResDailyTask.WeekRewardEntry
555, // 91: tutorial.ResDailyTask.DailyTask:type_name -> tutorial.ResDailyTask.DailyTaskEntry
171, // 92: tutorial.DailyWeek.Items:type_name -> tutorial.ItemInfo
183, // 93: tutorial.DailyTask.Progress:type_name -> tutorial.QuestProgress
171, // 94: tutorial.DailyTask.Items:type_name -> tutorial.ItemInfo
@ -33922,33 +34041,33 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
208, // 110: tutorial.ResActivity.ActiveList:type_name -> tutorial.ActivityInfo
2, // 111: tutorial.ResActivityReward.Code:type_name -> tutorial.RES_CODE
2, // 112: tutorial.ResAddGiftReward.Code:type_name -> tutorial.RES_CODE
554, // 113: tutorial.ResLimitEvent.LimitEventList:type_name -> tutorial.ResLimitEvent.LimitEventListEntry
555, // 114: tutorial.ResLimitEventProgress.ProgressReward:type_name -> tutorial.ResLimitEventProgress.ProgressRewardEntry
556, // 113: tutorial.ResLimitEvent.LimitEventList:type_name -> tutorial.ResLimitEvent.LimitEventListEntry
557, // 114: tutorial.ResLimitEventProgress.ProgressReward:type_name -> tutorial.ResLimitEventProgress.ProgressRewardEntry
2, // 115: tutorial.ResLimitEventReward.Code:type_name -> tutorial.RES_CODE
2, // 116: tutorial.ResSelectLimitEvent.Code:type_name -> tutorial.RES_CODE
556, // 117: tutorial.LimitEvent.Param:type_name -> tutorial.LimitEvent.ParamEntry
557, // 118: tutorial.ReqLimitEventLuckyCat.mChessData:type_name -> tutorial.ReqLimitEventLuckyCat.MChessDataEntry
558, // 117: tutorial.LimitEvent.Param:type_name -> tutorial.LimitEvent.ParamEntry
559, // 118: tutorial.ReqLimitEventLuckyCat.mChessData:type_name -> tutorial.ReqLimitEventLuckyCat.MChessDataEntry
2, // 119: tutorial.ResLimitEventLuckyCat.Code:type_name -> tutorial.RES_CODE
2, // 120: tutorial.ResLimitSenceReward.Code:type_name -> tutorial.RES_CODE
171, // 121: tutorial.ResChessRainReward.Items:type_name -> tutorial.ItemInfo
2, // 122: tutorial.ResFastProduceReward.Code:type_name -> tutorial.RES_CODE
2, // 123: tutorial.ResCatTrickReward.Code:type_name -> tutorial.RES_CODE
237, // 124: tutorial.ResSearchPlayer.List:type_name -> tutorial.ResPlayerSimple
558, // 125: tutorial.ResFriendPlayerSimple.Emoji:type_name -> tutorial.ResFriendPlayerSimple.EmojiEntry
559, // 126: tutorial.ResFriendPlayerSimple.Playroom:type_name -> tutorial.ResFriendPlayerSimple.PlayroomEntry
560, // 127: tutorial.ResFriendPlayerSimple.DressSet:type_name -> tutorial.ResFriendPlayerSimple.DressSetEntry
560, // 125: tutorial.ResFriendPlayerSimple.Emoji:type_name -> tutorial.ResFriendPlayerSimple.EmojiEntry
561, // 126: tutorial.ResFriendPlayerSimple.Playroom:type_name -> tutorial.ResFriendPlayerSimple.PlayroomEntry
562, // 127: tutorial.ResFriendPlayerSimple.DressSet:type_name -> tutorial.ResFriendPlayerSimple.DressSetEntry
238, // 128: tutorial.ResFriendPlayerSimple.Last:type_name -> tutorial.ActLog
561, // 129: tutorial.ResFriendPlayerSimple.Physiology:type_name -> tutorial.ResFriendPlayerSimple.PhysiologyEntry
562, // 130: tutorial.ResPlayerSimple.Emoji:type_name -> tutorial.ResPlayerSimple.EmojiEntry
563, // 131: tutorial.ResPlayerRank.PlayroomSet:type_name -> tutorial.ResPlayerRank.PlayroomSetEntry
564, // 132: tutorial.ResPlayerRank.DressSet:type_name -> tutorial.ResPlayerRank.DressSetEntry
563, // 129: tutorial.ResFriendPlayerSimple.Physiology:type_name -> tutorial.ResFriendPlayerSimple.PhysiologyEntry
564, // 130: tutorial.ResPlayerSimple.Emoji:type_name -> tutorial.ResPlayerSimple.EmojiEntry
565, // 131: tutorial.ResPlayerRank.PlayroomSet:type_name -> tutorial.ResPlayerRank.PlayroomSetEntry
566, // 132: tutorial.ResPlayerRank.DressSet:type_name -> tutorial.ResPlayerRank.DressSetEntry
238, // 133: tutorial.ResPlayerRank.Last:type_name -> tutorial.ActLog
237, // 134: tutorial.ResFriendLog.Player:type_name -> tutorial.ResPlayerSimple
240, // 135: tutorial.NotifyFriendLog.info:type_name -> tutorial.ResFriendLog
242, // 136: tutorial.NotifyFriendLog.Bubble:type_name -> tutorial.FriendBubbleInfo
171, // 137: tutorial.FriendBubbleInfo.Items:type_name -> tutorial.ItemInfo
244, // 138: tutorial.NotifyFriendCard.Info:type_name -> tutorial.ResFriendCard
565, // 139: tutorial.ResKv.kv:type_name -> tutorial.ResKv.KvEntry
567, // 139: tutorial.ResKv.kv:type_name -> tutorial.ResKv.KvEntry
2, // 140: tutorial.ResFriendByCode.Code:type_name -> tutorial.RES_CODE
237, // 141: tutorial.ResFriendByCode.Player:type_name -> tutorial.ResPlayerSimple
237, // 142: tutorial.ResFriendRecommend.List:type_name -> tutorial.ResPlayerSimple
@ -33977,27 +34096,27 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
237, // 165: tutorial.ResAgreeFriend.Player:type_name -> tutorial.ResPlayerSimple
2, // 166: tutorial.ResRefuseFriend.Code:type_name -> tutorial.RES_CODE
2, // 167: tutorial.ResDelFriend.Code:type_name -> tutorial.RES_CODE
566, // 168: tutorial.ResRank.RankList:type_name -> tutorial.ResRank.RankListEntry
567, // 169: tutorial.ResMailList.MailList:type_name -> tutorial.ResMailList.MailListEntry
568, // 168: tutorial.ResRank.RankList:type_name -> tutorial.ResRank.RankListEntry
569, // 169: tutorial.ResMailList.MailList:type_name -> tutorial.ResMailList.MailListEntry
171, // 170: tutorial.MailInfo.Items:type_name -> tutorial.ItemInfo
293, // 171: tutorial.MailNotify.Info:type_name -> tutorial.MailInfo
2, // 172: tutorial.ResReadMail.Code:type_name -> tutorial.RES_CODE
2, // 173: tutorial.ResGetMailReward.Code:type_name -> tutorial.RES_CODE
2, // 174: tutorial.ResDeleteMail.Code:type_name -> tutorial.RES_CODE
568, // 175: tutorial.ResCharge.SpecialShop:type_name -> tutorial.ResCharge.SpecialShopEntry
569, // 176: tutorial.ResCharge.ChessShop:type_name -> tutorial.ResCharge.ChessShopEntry
570, // 177: tutorial.ResCharge.Gift:type_name -> tutorial.ResCharge.GiftEntry
570, // 175: tutorial.ResCharge.SpecialShop:type_name -> tutorial.ResCharge.SpecialShopEntry
571, // 176: tutorial.ResCharge.ChessShop:type_name -> tutorial.ResCharge.ChessShopEntry
572, // 177: tutorial.ResCharge.Gift:type_name -> tutorial.ResCharge.GiftEntry
304, // 178: tutorial.ResCharge.Wish:type_name -> tutorial.WishList
571, // 179: tutorial.ResCharge.WeeklyDiscount:type_name -> tutorial.ResCharge.WeeklyDiscountEntry
573, // 179: tutorial.ResCharge.WeeklyDiscount:type_name -> tutorial.ResCharge.WeeklyDiscountEntry
2, // 180: tutorial.ResAddWish.Code:type_name -> tutorial.RES_CODE
2, // 181: tutorial.ResGetWish.Code:type_name -> tutorial.RES_CODE
2, // 182: tutorial.ResSendWishBeg.Code:type_name -> tutorial.RES_CODE
2, // 183: tutorial.ResFreeShop.Code:type_name -> tutorial.RES_CODE
2, // 184: tutorial.ResBuyChessShop.Code:type_name -> tutorial.RES_CODE
572, // 185: tutorial.ReqBuyChessShop2.mChessData:type_name -> tutorial.ReqBuyChessShop2.MChessDataEntry
574, // 185: tutorial.ReqBuyChessShop2.mChessData:type_name -> tutorial.ReqBuyChessShop2.MChessDataEntry
2, // 186: tutorial.ResBuyChessShop2.Code:type_name -> tutorial.RES_CODE
2, // 187: tutorial.ResRefreshChessShop.Code:type_name -> tutorial.RES_CODE
573, // 188: tutorial.ResEndless.EndlessList:type_name -> tutorial.ResEndless.EndlessListEntry
575, // 188: tutorial.ResEndless.EndlessList:type_name -> tutorial.ResEndless.EndlessListEntry
171, // 189: tutorial.ResEndlessInfo.Items:type_name -> tutorial.ItemInfo
2, // 190: tutorial.ResEndlessReward.Code:type_name -> tutorial.RES_CODE
2, // 191: tutorial.ResPiggyBankReward.Code:type_name -> tutorial.RES_CODE
@ -34010,12 +34129,12 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
171, // 198: tutorial.ChampionshipCfgRank.Items:type_name -> tutorial.ItemInfo
2, // 199: tutorial.ResChampshipReward.Code:type_name -> tutorial.RES_CODE
2, // 200: tutorial.ResChampshipRankReward.Code:type_name -> tutorial.RES_CODE
574, // 201: tutorial.ResChampshipRank.RankList:type_name -> tutorial.ResChampshipRank.RankListEntry
575, // 202: tutorial.ResChampshipPreRank.RankList:type_name -> tutorial.ResChampshipPreRank.RankListEntry
576, // 201: tutorial.ResChampshipRank.RankList:type_name -> tutorial.ResChampshipRank.RankListEntry
577, // 202: tutorial.ResChampshipPreRank.RankList:type_name -> tutorial.ResChampshipPreRank.RankListEntry
337, // 203: tutorial.ResChampshipPreRank.Cfg:type_name -> tutorial.ChampionshipCfg
576, // 204: tutorial.ResNotifyCard.Card:type_name -> tutorial.ResNotifyCard.CardEntry
577, // 205: tutorial.ResNotifyCard.Master:type_name -> tutorial.ResNotifyCard.MasterEntry
578, // 206: tutorial.ResNotifyCard.Handbook:type_name -> tutorial.ResNotifyCard.HandbookEntry
578, // 204: tutorial.ResNotifyCard.Card:type_name -> tutorial.ResNotifyCard.CardEntry
579, // 205: tutorial.ResNotifyCard.Master:type_name -> tutorial.ResNotifyCard.MasterEntry
580, // 206: tutorial.ResNotifyCard.Handbook:type_name -> tutorial.ResNotifyCard.HandbookEntry
2, // 207: tutorial.ResSetFacebookUrl.Code:type_name -> tutorial.RES_CODE
364, // 208: tutorial.CatReturnGiftCfg.RewardList:type_name -> tutorial.CatReturnGiftCfgReward
171, // 209: tutorial.CatReturnGiftCfgReward.Reward:type_name -> tutorial.ItemInfo
@ -34023,22 +34142,22 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
2, // 211: tutorial.ResCatReturnGiftScore.Code:type_name -> tutorial.RES_CODE
2, // 212: tutorial.ResCatReturnGiftReward.Code:type_name -> tutorial.RES_CODE
2, // 213: tutorial.ResCatReturnGiftRewardGift.Code:type_name -> tutorial.RES_CODE
579, // 214: tutorial.ResMining.Map:type_name -> tutorial.ResMining.MapEntry
580, // 215: tutorial.ResMining.PassReward:type_name -> tutorial.ResMining.PassRewardEntry
581, // 216: tutorial.ReqMiningTake.Map:type_name -> tutorial.ReqMiningTake.MapEntry
581, // 214: tutorial.ResMining.Map:type_name -> tutorial.ResMining.MapEntry
582, // 215: tutorial.ResMining.PassReward:type_name -> tutorial.ResMining.PassRewardEntry
583, // 216: tutorial.ReqMiningTake.Map:type_name -> tutorial.ReqMiningTake.MapEntry
2, // 217: tutorial.ResMiningTake.Code:type_name -> tutorial.RES_CODE
2, // 218: tutorial.ResMiningReward.Code:type_name -> tutorial.RES_CODE
2, // 219: tutorial.ResActPassReward.Code:type_name -> tutorial.RES_CODE
582, // 220: tutorial.ResActRed.Red:type_name -> tutorial.ResActRed.RedEntry
584, // 220: tutorial.ResActRed.Red:type_name -> tutorial.ResActRed.RedEntry
208, // 221: tutorial.ActivityNotify.Info:type_name -> tutorial.ActivityInfo
583, // 222: tutorial.ResItem.Item:type_name -> tutorial.ResItem.ItemEntry
584, // 223: tutorial.ItemNotify.Item:type_name -> tutorial.ItemNotify.ItemEntry
585, // 222: tutorial.ResItem.Item:type_name -> tutorial.ResItem.ItemEntry
586, // 223: tutorial.ItemNotify.Item:type_name -> tutorial.ItemNotify.ItemEntry
391, // 224: tutorial.ResGuessColor.MapList:type_name -> tutorial.GuessColorInfo
585, // 225: tutorial.ResGuessColor.OMap:type_name -> tutorial.ResGuessColor.OMapEntry
587, // 225: tutorial.ResGuessColor.OMap:type_name -> tutorial.ResGuessColor.OMapEntry
389, // 226: tutorial.ResGuessColor.Opponent:type_name -> tutorial.opponent
391, // 227: tutorial.ReqGuessColorTake.Map:type_name -> tutorial.GuessColorInfo
586, // 228: tutorial.ReqGuessColorTake.OMap:type_name -> tutorial.ReqGuessColorTake.OMapEntry
587, // 229: tutorial.GuessColorInfo.Map:type_name -> tutorial.GuessColorInfo.MapEntry
588, // 228: tutorial.ReqGuessColorTake.OMap:type_name -> tutorial.ReqGuessColorTake.OMapEntry
589, // 229: tutorial.GuessColorInfo.Map:type_name -> tutorial.GuessColorInfo.MapEntry
2, // 230: tutorial.ResGuessColorTake.Code:type_name -> tutorial.RES_CODE
2, // 231: tutorial.ResGuessColorReward.Code:type_name -> tutorial.RES_CODE
397, // 232: tutorial.ResRace.Opponent:type_name -> tutorial.raceopponent
@ -34049,19 +34168,19 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
171, // 237: tutorial.ResPlayroom.Items:type_name -> tutorial.ItemInfo
438, // 238: tutorial.ResPlayroom.Opponent:type_name -> tutorial.RoomOpponent
437, // 239: tutorial.ResPlayroom.Friend:type_name -> tutorial.FriendRoom
588, // 240: tutorial.ResPlayroom.Playroom:type_name -> tutorial.ResPlayroom.PlayroomEntry
590, // 240: tutorial.ResPlayroom.Playroom:type_name -> tutorial.ResPlayroom.PlayroomEntry
423, // 241: tutorial.ResPlayroom.collect:type_name -> tutorial.PlayroomCollectInfo
589, // 242: tutorial.ResPlayroom.Mood:type_name -> tutorial.ResPlayroom.MoodEntry
591, // 242: tutorial.ResPlayroom.Mood:type_name -> tutorial.ResPlayroom.MoodEntry
171, // 243: tutorial.ResPlayroom.LoseItem:type_name -> tutorial.ItemInfo
433, // 244: tutorial.ResPlayroom.Chip:type_name -> tutorial.ChipInfo
590, // 245: tutorial.ResPlayroom.Physiology:type_name -> tutorial.ResPlayroom.PhysiologyEntry
591, // 246: tutorial.ResPlayroom.Dress:type_name -> tutorial.ResPlayroom.DressEntry
592, // 247: tutorial.ResPlayroom.DressSet:type_name -> tutorial.ResPlayroom.DressSetEntry
592, // 245: tutorial.ResPlayroom.Physiology:type_name -> tutorial.ResPlayroom.PhysiologyEntry
593, // 246: tutorial.ResPlayroom.Dress:type_name -> tutorial.ResPlayroom.DressEntry
594, // 247: tutorial.ResPlayroom.DressSet:type_name -> tutorial.ResPlayroom.DressSetEntry
422, // 248: tutorial.ResPlayroom.PetAir:type_name -> tutorial.PlayroomAirInfo
182, // 249: tutorial.ResPlayroom.DailyTask:type_name -> tutorial.DailyTask
435, // 250: tutorial.ResPlayroom.AdItem:type_name -> tutorial.AdItem
437, // 251: tutorial.ResPlayroom.Target:type_name -> tutorial.FriendRoom
593, // 252: tutorial.ResPlayroom.WeeklyDiscount:type_name -> tutorial.ResPlayroom.WeeklyDiscountEntry
595, // 252: tutorial.ResPlayroom.WeeklyDiscount:type_name -> tutorial.ResPlayroom.WeeklyDiscountEntry
182, // 253: tutorial.NotifyPlayroomTask.DailyTask:type_name -> tutorial.DailyTask
237, // 254: tutorial.NotifyPlayroomBroken.Player:type_name -> tutorial.ResPlayerSimple
2, // 255: tutorial.ResPlayroomTask.Code:type_name -> tutorial.RES_CODE
@ -34069,28 +34188,28 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
2, // 257: tutorial.ResPlayroomUnlock.Code:type_name -> tutorial.RES_CODE
2, // 258: tutorial.ResPlayroomUpvote.Code:type_name -> tutorial.RES_CODE
421, // 259: tutorial.PlayroomDress.List:type_name -> tutorial.PlayroomDressInfo
594, // 260: tutorial.ReqPlayroomDressSet.DressSet:type_name -> tutorial.ReqPlayroomDressSet.DressSetEntry
596, // 260: tutorial.ReqPlayroomDressSet.DressSet:type_name -> tutorial.ReqPlayroomDressSet.DressSetEntry
2, // 261: tutorial.ResPlayroomDressSet.Code:type_name -> tutorial.RES_CODE
2, // 262: tutorial.ResPlayroomPetAirSet.Code:type_name -> tutorial.RES_CODE
2, // 263: tutorial.ResPlayroomWorkOutline.Code:type_name -> tutorial.RES_CODE
171, // 264: tutorial.NotifyPlayroomLose.LoseItem:type_name -> tutorial.ItemInfo
433, // 265: tutorial.NotifyPlayroomLose.Chip:type_name -> tutorial.ChipInfo
595, // 266: tutorial.NotifyPlayroomMood.Mood:type_name -> tutorial.NotifyPlayroomMood.MoodEntry
596, // 267: tutorial.NotifyPlayroomMood.Physiology:type_name -> tutorial.NotifyPlayroomMood.PhysiologyEntry
597, // 266: tutorial.NotifyPlayroomMood.Mood:type_name -> tutorial.NotifyPlayroomMood.MoodEntry
598, // 267: tutorial.NotifyPlayroomMood.Physiology:type_name -> tutorial.NotifyPlayroomMood.PhysiologyEntry
435, // 268: tutorial.NotifyPlayroomMood.AdItem:type_name -> tutorial.AdItem
597, // 269: tutorial.ResPlayroomInfo.Playroom:type_name -> tutorial.ResPlayroomInfo.PlayroomEntry
598, // 270: tutorial.ResPlayroomInfo.Items:type_name -> tutorial.ResPlayroomInfo.ItemsEntry
599, // 271: tutorial.ResPlayroomInfo.flip:type_name -> tutorial.ResPlayroomInfo.FlipEntry
600, // 272: tutorial.ResPlayroomInfo.Emoji:type_name -> tutorial.ResPlayroomInfo.EmojiEntry
601, // 273: tutorial.ResPlayroomInfo.DressSet:type_name -> tutorial.ResPlayroomInfo.DressSetEntry
599, // 269: tutorial.ResPlayroomInfo.Playroom:type_name -> tutorial.ResPlayroomInfo.PlayroomEntry
600, // 270: tutorial.ResPlayroomInfo.Items:type_name -> tutorial.ResPlayroomInfo.ItemsEntry
601, // 271: tutorial.ResPlayroomInfo.flip:type_name -> tutorial.ResPlayroomInfo.FlipEntry
602, // 272: tutorial.ResPlayroomInfo.Emoji:type_name -> tutorial.ResPlayroomInfo.EmojiEntry
603, // 273: tutorial.ResPlayroomInfo.DressSet:type_name -> tutorial.ResPlayroomInfo.DressSetEntry
2, // 274: tutorial.ResPlayroomFlip.Code:type_name -> tutorial.RES_CODE
2, // 275: tutorial.ResPlayroomGuide.Code:type_name -> tutorial.RES_CODE
2, // 276: tutorial.ResPlayroomFlipReward.Code:type_name -> tutorial.RES_CODE
2, // 277: tutorial.ResPlayroomGame.Code:type_name -> tutorial.RES_CODE
602, // 278: tutorial.ResPlayroomGame.Items:type_name -> tutorial.ResPlayroomGame.ItemsEntry
604, // 278: tutorial.ResPlayroomGame.Items:type_name -> tutorial.ResPlayroomGame.ItemsEntry
171, // 279: tutorial.ResPlayroomGameShowReward.Items:type_name -> tutorial.ItemInfo
2, // 280: tutorial.ResPlayroomInteract.Code:type_name -> tutorial.RES_CODE
603, // 281: tutorial.ReqPlayroomSetRoom.Playroom:type_name -> tutorial.ReqPlayroomSetRoom.PlayroomEntry
605, // 281: tutorial.ReqPlayroomSetRoom.Playroom:type_name -> tutorial.ReqPlayroomSetRoom.PlayroomEntry
2, // 282: tutorial.ResPlayroomSetRoom.Code:type_name -> tutorial.RES_CODE
2, // 283: tutorial.ResPlayroomSelectReward.Code:type_name -> tutorial.RES_CODE
2, // 284: tutorial.ResPlayroomLose.Code:type_name -> tutorial.RES_CODE
@ -34121,16 +34240,16 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
2, // 309: tutorial.ResCatnipGrandReward.Code:type_name -> tutorial.RES_CODE
2, // 310: tutorial.ResCatnipEmoji.Code:type_name -> tutorial.RES_CODE
171, // 311: tutorial.MiningCfg.itemCost:type_name -> tutorial.ItemInfo
604, // 312: tutorial.MiningCfg.Gem:type_name -> tutorial.MiningCfg.GemEntry
605, // 313: tutorial.MiningCfg.Jackpot:type_name -> tutorial.MiningCfg.JackpotEntry
606, // 314: tutorial.MiningCfg.Pass:type_name -> tutorial.MiningCfg.PassEntry
606, // 312: tutorial.MiningCfg.Gem:type_name -> tutorial.MiningCfg.GemEntry
607, // 313: tutorial.MiningCfg.Jackpot:type_name -> tutorial.MiningCfg.JackpotEntry
608, // 314: tutorial.MiningCfg.Pass:type_name -> tutorial.MiningCfg.PassEntry
171, // 315: tutorial.MiningCfgJackpot.Items:type_name -> tutorial.ItemInfo
171, // 316: tutorial.MiningCfgPass.Items:type_name -> tutorial.ItemInfo
521, // 317: tutorial.ReqActivityCfgReload.List:type_name -> tutorial.ActivityCfg
530, // 318: tutorial.ResUserDetail.info:type_name -> tutorial.ResUserDetailInfo
529, // 319: tutorial.UserDetailOrderInfo.Chess:type_name -> tutorial.UserDetailOrderInfoChess
529, // 320: tutorial.UserDetailOrderInfo.ChessId:type_name -> tutorial.UserDetailOrderInfoChess
607, // 321: tutorial.ResUserDetailInfo.ChessMap:type_name -> tutorial.ResUserDetailInfo.ChessMapEntry
609, // 321: tutorial.ResUserDetailInfo.ChessMap:type_name -> tutorial.ResUserDetailInfo.ChessMapEntry
238, // 322: tutorial.ResUserDetailInfo.ActLog:type_name -> tutorial.ActLog
527, // 323: tutorial.ResUserDetailInfo.FriendList:type_name -> tutorial.UserDetailFriendInfo
528, // 324: tutorial.ResUserDetailInfo.Order:type_name -> tutorial.UserDetailOrderInfo
@ -34158,12 +34277,20 @@ var file_proto_Gameapi_proto_depIdxs = []int32{
523, // 346: tutorial.Backend.OrderShipping:input_type -> tutorial.ReqOrderShipping
525, // 347: tutorial.Backend.UserDetail:input_type -> tutorial.UserDetailParam
515, // 348: tutorial.Backend.ServerInfo:input_type -> tutorial.ReqServerInfo
522, // 349: tutorial.Backend.ReloadActivity:output_type -> tutorial.ResActivityCfgReload
524, // 350: tutorial.Backend.OrderShipping:output_type -> tutorial.ResOrderShipping
526, // 351: tutorial.Backend.UserDetail:output_type -> tutorial.ResUserDetail
531, // 352: tutorial.Backend.ServerInfo:output_type -> tutorial.ResServerInfo
349, // [349:353] is the sub-list for method output_type
345, // [345:349] is the sub-list for method input_type
517, // 349: tutorial.Backend.PlayerGm:input_type -> tutorial.ReqAdminGm
518, // 350: tutorial.Backend.BanPlayer:input_type -> tutorial.ReqAdminBan
514, // 351: tutorial.Backend.ReloadMail:input_type -> tutorial.ReqReloadServerMail
516, // 352: tutorial.Backend.ServerReload:input_type -> tutorial.ReqReload
522, // 353: tutorial.Backend.ReloadActivity:output_type -> tutorial.ResActivityCfgReload
524, // 354: tutorial.Backend.OrderShipping:output_type -> tutorial.ResOrderShipping
526, // 355: tutorial.Backend.UserDetail:output_type -> tutorial.ResUserDetail
531, // 356: tutorial.Backend.ServerInfo:output_type -> tutorial.ResServerInfo
532, // 357: tutorial.Backend.PlayerGm:output_type -> tutorial.ResAdminGm
533, // 358: tutorial.Backend.BanPlayer:output_type -> tutorial.ResBackend
533, // 359: tutorial.Backend.ReloadMail:output_type -> tutorial.ResBackend
533, // 360: tutorial.Backend.ServerReload:output_type -> tutorial.ResBackend
353, // [353:361] is the sub-list for method output_type
345, // [345:353] is the sub-list for method input_type
345, // [345:345] is the sub-list for extension type_name
345, // [345:345] is the sub-list for extension extendee
0, // [0:345] is the sub-list for field type_name
@ -34180,7 +34307,7 @@ func file_proto_Gameapi_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_Gameapi_proto_rawDesc), len(file_proto_Gameapi_proto_rawDesc)),
NumEnums: 14,
NumMessages: 594,
NumMessages: 596,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -23,6 +23,10 @@ const (
Backend_OrderShipping_FullMethodName = "/tutorial.Backend/OrderShipping"
Backend_UserDetail_FullMethodName = "/tutorial.Backend/UserDetail"
Backend_ServerInfo_FullMethodName = "/tutorial.Backend/ServerInfo"
Backend_PlayerGm_FullMethodName = "/tutorial.Backend/PlayerGm"
Backend_BanPlayer_FullMethodName = "/tutorial.Backend/BanPlayer"
Backend_ReloadMail_FullMethodName = "/tutorial.Backend/ReloadMail"
Backend_ServerReload_FullMethodName = "/tutorial.Backend/ServerReload"
)
// BackendClient is the client API for Backend service.
@ -33,6 +37,10 @@ type BackendClient interface {
OrderShipping(ctx context.Context, in *ReqOrderShipping, opts ...grpc.CallOption) (*ResOrderShipping, error)
UserDetail(ctx context.Context, in *UserDetailParam, opts ...grpc.CallOption) (*ResUserDetail, error)
ServerInfo(ctx context.Context, in *ReqServerInfo, opts ...grpc.CallOption) (*ResServerInfo, error)
PlayerGm(ctx context.Context, in *ReqAdminGm, opts ...grpc.CallOption) (*ResAdminGm, error)
BanPlayer(ctx context.Context, in *ReqAdminBan, opts ...grpc.CallOption) (*ResBackend, error)
ReloadMail(ctx context.Context, in *ReqReloadServerMail, opts ...grpc.CallOption) (*ResBackend, error)
ServerReload(ctx context.Context, in *ReqReload, opts ...grpc.CallOption) (*ResBackend, error)
}
type backendClient struct {
@ -83,6 +91,46 @@ func (c *backendClient) ServerInfo(ctx context.Context, in *ReqServerInfo, opts
return out, nil
}
func (c *backendClient) PlayerGm(ctx context.Context, in *ReqAdminGm, opts ...grpc.CallOption) (*ResAdminGm, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResAdminGm)
err := c.cc.Invoke(ctx, Backend_PlayerGm_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backendClient) BanPlayer(ctx context.Context, in *ReqAdminBan, opts ...grpc.CallOption) (*ResBackend, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResBackend)
err := c.cc.Invoke(ctx, Backend_BanPlayer_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backendClient) ReloadMail(ctx context.Context, in *ReqReloadServerMail, opts ...grpc.CallOption) (*ResBackend, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResBackend)
err := c.cc.Invoke(ctx, Backend_ReloadMail_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backendClient) ServerReload(ctx context.Context, in *ReqReload, opts ...grpc.CallOption) (*ResBackend, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResBackend)
err := c.cc.Invoke(ctx, Backend_ServerReload_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// BackendServer is the server API for Backend service.
// All implementations must embed UnimplementedBackendServer
// for forward compatibility.
@ -91,6 +139,10 @@ type BackendServer interface {
OrderShipping(context.Context, *ReqOrderShipping) (*ResOrderShipping, error)
UserDetail(context.Context, *UserDetailParam) (*ResUserDetail, error)
ServerInfo(context.Context, *ReqServerInfo) (*ResServerInfo, error)
PlayerGm(context.Context, *ReqAdminGm) (*ResAdminGm, error)
BanPlayer(context.Context, *ReqAdminBan) (*ResBackend, error)
ReloadMail(context.Context, *ReqReloadServerMail) (*ResBackend, error)
ServerReload(context.Context, *ReqReload) (*ResBackend, error)
mustEmbedUnimplementedBackendServer()
}
@ -113,6 +165,18 @@ func (UnimplementedBackendServer) UserDetail(context.Context, *UserDetailParam)
func (UnimplementedBackendServer) ServerInfo(context.Context, *ReqServerInfo) (*ResServerInfo, error) {
return nil, status.Error(codes.Unimplemented, "method ServerInfo not implemented")
}
func (UnimplementedBackendServer) PlayerGm(context.Context, *ReqAdminGm) (*ResAdminGm, error) {
return nil, status.Error(codes.Unimplemented, "method PlayerGm not implemented")
}
func (UnimplementedBackendServer) BanPlayer(context.Context, *ReqAdminBan) (*ResBackend, error) {
return nil, status.Error(codes.Unimplemented, "method BanPlayer not implemented")
}
func (UnimplementedBackendServer) ReloadMail(context.Context, *ReqReloadServerMail) (*ResBackend, error) {
return nil, status.Error(codes.Unimplemented, "method ReloadMail not implemented")
}
func (UnimplementedBackendServer) ServerReload(context.Context, *ReqReload) (*ResBackend, error) {
return nil, status.Error(codes.Unimplemented, "method ServerReload not implemented")
}
func (UnimplementedBackendServer) mustEmbedUnimplementedBackendServer() {}
func (UnimplementedBackendServer) testEmbeddedByValue() {}
@ -206,6 +270,78 @@ func _Backend_ServerInfo_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler)
}
func _Backend_PlayerGm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReqAdminGm)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BackendServer).PlayerGm(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Backend_PlayerGm_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BackendServer).PlayerGm(ctx, req.(*ReqAdminGm))
}
return interceptor(ctx, in, info, handler)
}
func _Backend_BanPlayer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReqAdminBan)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BackendServer).BanPlayer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Backend_BanPlayer_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BackendServer).BanPlayer(ctx, req.(*ReqAdminBan))
}
return interceptor(ctx, in, info, handler)
}
func _Backend_ReloadMail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReqReloadServerMail)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BackendServer).ReloadMail(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Backend_ReloadMail_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BackendServer).ReloadMail(ctx, req.(*ReqReloadServerMail))
}
return interceptor(ctx, in, info, handler)
}
func _Backend_ServerReload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReqReload)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BackendServer).ServerReload(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Backend_ServerReload_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BackendServer).ServerReload(ctx, req.(*ReqReload))
}
return interceptor(ctx, in, info, handler)
}
// Backend_ServiceDesc is the grpc.ServiceDesc for Backend service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -229,6 +365,22 @@ var Backend_ServiceDesc = grpc.ServiceDesc{
MethodName: "ServerInfo",
Handler: _Backend_ServerInfo_Handler,
},
{
MethodName: "PlayerGm",
Handler: _Backend_PlayerGm_Handler,
},
{
MethodName: "BanPlayer",
Handler: _Backend_BanPlayer_Handler,
},
{
MethodName: "ReloadMail",
Handler: _Backend_ReloadMail_Handler,
},
{
MethodName: "ServerReload",
Handler: _Backend_ServerReload_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proto/Gameapi.proto",

Binary file not shown.

View File

@ -0,0 +1,4 @@
name: "Admin Backend"
version: "1.0.0"
debug: true
port: 5240

View File

@ -0,0 +1,74 @@
system:
nmap: false
feishu_url: 'https://open.feishu.cn/open-apis/bot/v2/hook/70e24a79-b019-434a-b4d1-4592bbf7c311'
notice_url: 'https://open.feishu.cn/open-apis/bot/v2/hook/64bad1f3-3a41-4dca-9037-399067ffb252'
operation_url: 'https://open.feishu.cn/open-apis/bot/v2/hook/64bad1f3-3a41-4dca-9037-399067ffb252'
operation_chat_id: 'oc_f6e10a55f28f31e2a5677bdcb6aed599'
us_operation_chat_id: 'oc_a3d1268f63573563fdaea3dedb3bf78d'
client_chat_id: 'oc_f6e10a55f28f31e2a5677bdcb6aed599'
ssh: true
mysqls:
- host: '127.0.0.1'
name: 'merge_pet_test'
port: 3306
user: 'root'
password: 'GWFj1cHaqjpzvcsHBWTFtLWtm8MUZKROx_wvbV6jPg=='
database: 'pet_home_%d'
idleTimeout: Infinity
- host: '127.0.0.1'
name: 'merge_pet_audit'
port: 3306
user: 'root'
password: 'GWFj1cHaqjpzvcsHBWTFtLWtm8MUZKROx_wvbV6jPg=='
database: 'merge_pet_audit_%d'
idleTimeout: Infinity
- host: '127.0.0.1'
name: 'merge_pet_sdk'
port: 3306
user: 'root'
password: 'GWFj1cHaqjpzvcsHBWTFtLWtm8MUZKROx_wvbV6jPg=='
database: 'Merge_Pet_sdk'
idleTimeout: Infinity
- host: '127.0.0.1'
name: 'merge_pet_london'
port: 3306
user: 'root'
password: 'GWFj1cHaqjpzvcsHBWTFtLWtm8MUZKROx_wvbV6jPg=='
database: 'merge_pet_london_%d'
idleTimeout: Infinity
- host: '127.0.0.1'
name: 'merge_pet_online'
port: 3306
user: 'root'
password: 'pUf9tft9GOPjV1z855Jf2DpB5NUpGEc_'
database: 'merge_pet_%d'
idleTimeout: Infinity
- host: 'rm-f8zd2030feam53n43io.mysql.rds.aliyuncs.com'
name: 'log'
port: 3306
user: 'root'
password: 'GWFj1cHaqjpzvcsHBWTFtLWtm8MUZKROx_wvbV6jPg=='
database: 'game'
idleTimeout: Infinity
servers:
- name: 'server1'
host: '1.15.182.107'
port: 22
username: 'root'
password: 'r_vcLRGHPm1bEZDKU1L5TTr_-u_expNKgsCBuXNHmA=='
- name: 'server2'
host: '47.254.83.25'
port: 22
username: 'root'
password: 'ynAMWcS4YpHozPHuudywcIZuBnTiSSaWF2iLoSlRVA=='
- name: 'london'
host: '8.208.47.208'
port: 22
username: 'root'
password: 'dhzyiIMG0LkzSmY0lVEukdbM1uIt8OY8qljhgUJnBT8='
- name: 'log'
host: '8.155.14.94'
port: 22
username: 'root'
password: 'gDcfdjgyq60A-8q_cZ_CbSQnTlY0YakpiYVyTmFTEkk='

View File

@ -0,0 +1,2 @@
cd /data/admin_backend
GOOS=linux GOARCH=amd64 go build -o /data/admin_backend/release/abtest_service/abtest_service sdk/abtest/main.go

View File

@ -30,7 +30,7 @@ func (m *MySQLConfig) DSN() string {
func Load() *Config {
return &Config{
Server: ServerConfig{
Port: getEnv("SERVER_PORT", "8080"),
Port: getEnv("SERVER_PORT", "5360"),
},
MySQL: MySQLConfig{
Host: getEnv("MYSQL_HOST", "ga-bp1s4q4jo935fmnxs474f.aliyunga0018.com"),

View File

@ -1,19 +1,64 @@
package dao
import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/abtest/model"
"backend/sdk/abtest/model"
"github.com/jmoiron/sqlx"
)
type DAO struct {
db *sql.DB
type scannedTime struct {
Time time.Time
Valid bool
}
func New(db *sql.DB) *DAO {
func (t *scannedTime) Scan(value interface{}) error {
if value == nil {
t.Valid = false
t.Time = time.Time{}
return nil
}
switch v := value.(type) {
case time.Time:
t.Time = v
t.Valid = true
return nil
case []byte:
return t.parse(string(v))
case string:
return t.parse(v)
default:
return fmt.Errorf("unsupported time scan type %T", value)
}
}
func (t *scannedTime) parse(raw string) error {
layouts := []string{
"2006-01-02 15:04:05",
time.RFC3339,
"2006-01-02T15:04:05",
}
for _, layout := range layouts {
parsed, err := time.ParseInLocation(layout, raw, time.Local)
if err == nil {
t.Time = parsed
t.Valid = true
return nil
}
}
return fmt.Errorf("invalid time value %q", raw)
}
type DAO struct {
db *sqlx.DB
}
func New(db *sqlx.DB) *DAO {
return &DAO{db: db}
}
@ -35,9 +80,9 @@ func (d *DAO) GetExperiment(id int64) (*model.Experiment, error) {
"SELECT id, name, description, status, start_time, end_time, created_at, updated_at FROM experiments WHERE id = ?", id,
)
exp := &model.Experiment{}
var startTime, endTime model.NullTime
var startTime, endTime, createdAt, updatedAt scannedTime
if err := row.Scan(&exp.ID, &exp.Name, &exp.Description, &exp.Status,
&startTime, &endTime, &exp.CreatedAt, &exp.UpdatedAt); err != nil {
&startTime, &endTime, &createdAt, &updatedAt); err != nil {
return nil, err
}
if startTime.Valid {
@ -46,6 +91,12 @@ func (d *DAO) GetExperiment(id int64) (*model.Experiment, error) {
if endTime.Valid {
exp.EndTime = &endTime.Time
}
if createdAt.Valid {
exp.CreatedAt = createdAt.Time
}
if updatedAt.Valid {
exp.UpdatedAt = updatedAt.Time
}
return exp, nil
}
@ -74,9 +125,9 @@ func (d *DAO) ListExperiments(status *int, offset, limit int) ([]*model.Experime
var list []*model.Experiment
for rows.Next() {
exp := &model.Experiment{}
var startTime, endTime model.NullTime
var startTime, endTime, createdAt, updatedAt scannedTime
if err := rows.Scan(&exp.ID, &exp.Name, &exp.Description, &exp.Status,
&startTime, &endTime, &exp.CreatedAt, &exp.UpdatedAt); err != nil {
&startTime, &endTime, &createdAt, &updatedAt); err != nil {
return nil, 0, err
}
if startTime.Valid {
@ -85,6 +136,12 @@ func (d *DAO) ListExperiments(status *int, offset, limit int) ([]*model.Experime
if endTime.Valid {
exp.EndTime = &endTime.Time
}
if createdAt.Valid {
exp.CreatedAt = createdAt.Time
}
if updatedAt.Valid {
exp.UpdatedAt = updatedAt.Time
}
list = append(list, exp)
}
return list, total, nil
@ -105,10 +162,16 @@ func (d *DAO) UpdateExperiment(id int64, req *model.UpdateExperimentReq) error {
sets = append(sets, "status = ?")
args = append(args, *req.Status)
}
if req.ClearStartTime {
sets = append(sets, "start_time = NULL")
}
if req.StartTime != nil {
sets = append(sets, "start_time = ?")
args = append(args, *req.StartTime)
}
if req.ClearEndTime {
sets = append(sets, "end_time = NULL")
}
if req.EndTime != nil {
sets = append(sets, "end_time = ?")
args = append(args, *req.EndTime)
@ -123,6 +186,34 @@ func (d *DAO) UpdateExperiment(id int64, req *model.UpdateExperimentReq) error {
return err
}
func (d *DAO) ResetExperimentRelations(experimentID int64) error {
tx, err := d.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.Exec("DELETE FROM events WHERE experiment_id = ?", experimentID); err != nil {
return err
}
if _, err := tx.Exec("DELETE FROM user_assignments WHERE experiment_id = ?", experimentID); err != nil {
return err
}
if _, err := tx.Exec("DELETE FROM whitelist WHERE experiment_id = ?", experimentID); err != nil {
return err
}
if _, err := tx.Exec("DELETE FROM variants WHERE experiment_id = ?", experimentID); err != nil {
return err
}
return tx.Commit()
}
func (d *DAO) DeleteWhitelistByExperiment(experimentID int64) error {
_, err := d.db.Exec("DELETE FROM whitelist WHERE experiment_id = ?", experimentID)
return err
}
func (d *DAO) DeleteExperiment(id int64) error {
tx, err := d.db.Begin()
if err != nil {
@ -171,13 +262,20 @@ func (d *DAO) GetVariant(id int64) (*model.Variant, error) {
)
v := &model.Variant{}
var params []byte
var createdAt, updatedAt scannedTime
if err := row.Scan(&v.ID, &v.ExperimentID, &v.Name, &v.Description, &v.Weight,
&params, &v.CreatedAt, &v.UpdatedAt); err != nil {
&params, &createdAt, &updatedAt); err != nil {
return nil, err
}
if params != nil {
v.Params = json.RawMessage(params)
}
if createdAt.Valid {
v.CreatedAt = createdAt.Time
}
if updatedAt.Valid {
v.UpdatedAt = updatedAt.Time
}
return v, nil
}
@ -194,13 +292,20 @@ func (d *DAO) ListVariantsByExperiment(experimentID int64) ([]*model.Variant, er
for rows.Next() {
v := &model.Variant{}
var params []byte
var createdAt, updatedAt scannedTime
if err := rows.Scan(&v.ID, &v.ExperimentID, &v.Name, &v.Description, &v.Weight,
&params, &v.CreatedAt, &v.UpdatedAt); err != nil {
&params, &createdAt, &updatedAt); err != nil {
return nil, err
}
if params != nil {
v.Params = json.RawMessage(params)
}
if createdAt.Valid {
v.CreatedAt = createdAt.Time
}
if updatedAt.Valid {
v.UpdatedAt = updatedAt.Time
}
list = append(list, v)
}
return list, nil
@ -248,9 +353,13 @@ func (d *DAO) GetAssignment(experimentID int64, userID string) (*model.UserAssig
experimentID, userID,
)
a := &model.UserAssignment{}
if err := row.Scan(&a.ID, &a.ExperimentID, &a.UserID, &a.VariantID, &a.AssignedAt); err != nil {
var assignedAt scannedTime
if err := row.Scan(&a.ID, &a.ExperimentID, &a.UserID, &a.VariantID, &assignedAt); err != nil {
return nil, err
}
if assignedAt.Valid {
a.AssignedAt = assignedAt.Time
}
return a, nil
}
@ -310,9 +419,9 @@ func (d *DAO) ListRunningExperiments() ([]*model.Experiment, error) {
var list []*model.Experiment
for rows.Next() {
exp := &model.Experiment{}
var startTime, endTime model.NullTime
var startTime, endTime, createdAt, updatedAt scannedTime
if err := rows.Scan(&exp.ID, &exp.Name, &exp.Description, &exp.Status,
&startTime, &endTime, &exp.CreatedAt, &exp.UpdatedAt); err != nil {
&startTime, &endTime, &createdAt, &updatedAt); err != nil {
return nil, err
}
if startTime.Valid {
@ -321,6 +430,12 @@ func (d *DAO) ListRunningExperiments() ([]*model.Experiment, error) {
if endTime.Valid {
exp.EndTime = &endTime.Time
}
if createdAt.Valid {
exp.CreatedAt = createdAt.Time
}
if updatedAt.Valid {
exp.UpdatedAt = updatedAt.Time
}
list = append(list, exp)
}
return list, nil
@ -400,9 +515,13 @@ func (d *DAO) GetWhitelistEntry(experimentID int64, userID string) (*model.White
experimentID, userID,
)
w := &model.Whitelist{}
if err := row.Scan(&w.ID, &w.ExperimentID, &w.UserID, &w.VariantID, &w.CreatedAt); err != nil {
var createdAt scannedTime
if err := row.Scan(&w.ID, &w.ExperimentID, &w.UserID, &w.VariantID, &createdAt); err != nil {
return nil, err
}
if createdAt.Valid {
w.CreatedAt = createdAt.Time
}
return w, nil
}
@ -438,9 +557,13 @@ func (d *DAO) ListWhitelistByExperiment(experimentID int64) ([]*model.Whitelist,
var list []*model.Whitelist
for rows.Next() {
w := &model.Whitelist{}
if err := rows.Scan(&w.ID, &w.ExperimentID, &w.UserID, &w.VariantID, &w.CreatedAt); err != nil {
var createdAt scannedTime
if err := rows.Scan(&w.ID, &w.ExperimentID, &w.UserID, &w.VariantID, &createdAt); err != nil {
return nil, err
}
if createdAt.Valid {
w.CreatedAt = createdAt.Time
}
list = append(list, w)
}
return list, nil

View File

@ -1,38 +0,0 @@
module github.com/abtest
go 1.25.5
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/gin-gonic/gin v1.12.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
)

View File

@ -1,82 +0,0 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -5,8 +5,9 @@ import (
"net/http"
"strconv"
"github.com/abtest/model"
"github.com/abtest/service"
"backend/sdk/abtest/model"
"backend/sdk/abtest/service"
"github.com/gin-gonic/gin"
)

View File

@ -1,24 +1,26 @@
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
"github.com/abtest/config"
"github.com/abtest/dao"
"github.com/abtest/handler"
"github.com/abtest/router"
"github.com/abtest/service"
"backend/common"
"backend/util"
"backend/sdk/abtest/config"
"backend/sdk/abtest/dao"
"backend/sdk/abtest/handler"
"backend/sdk/abtest/router"
"backend/sdk/abtest/service"
)
func main() {
cfg := config.Load()
// 连接 MySQL
db, err := sql.Open("mysql", cfg.MySQL.DSN())
common.Init()
db, err := util.ConnectMysql("log", "abtest")
if err != nil {
log.Fatalf("failed to open mysql: %v", err)
}

View File

@ -67,11 +67,13 @@ type CreateExperimentReq struct {
}
type UpdateExperimentReq struct {
Name *string `json:"name"`
Description *string `json:"description"`
Status *int `json:"status"`
StartTime *time.Time `json:"start_time"`
EndTime *time.Time `json:"end_time"`
Name *string `json:"name"`
Description *string `json:"description"`
Status *int `json:"status"`
StartTime *time.Time `json:"start_time"`
EndTime *time.Time `json:"end_time"`
ClearStartTime bool
ClearEndTime bool
}
type CreateVariantReq struct {

View File

@ -1,7 +1,8 @@
package router
import (
"github.com/abtest/handler"
"backend/sdk/abtest/handler"
"github.com/gin-gonic/gin"
)
@ -18,17 +19,17 @@ func Setup(h *handler.Handler) *gin.Engine {
// 实验管理
experiments := api.Group("/experiments")
{
experiments.POST("", h.CreateExperiment)
experiments.GET("", h.ListExperiments)
experiments.GET("/:id", h.GetExperiment)
experiments.PUT("/:id", h.UpdateExperiment)
experiments.DELETE("/:id", h.DeleteExperiment)
// experiments.POST("", h.CreateExperiment)
// experiments.GET("", h.ListExperiments)
// experiments.GET("/:id", h.GetExperiment)
// experiments.PUT("/:id", h.UpdateExperiment)
// experiments.DELETE("/:id", h.DeleteExperiment)
// 变体管理
experiments.POST("/:id/variants", h.CreateVariant)
experiments.GET("/:id/variants", h.ListVariants)
experiments.PUT("/:id/variants/:variantId", h.UpdateVariant)
experiments.DELETE("/:id/variants/:variantId", h.DeleteVariant)
// // 变体管理
// experiments.POST("/:id/variants", h.CreateVariant)
// experiments.GET("/:id/variants", h.ListVariants)
// experiments.PUT("/:id/variants/:variantId", h.UpdateVariant)
// experiments.DELETE("/:id/variants/:variantId", h.DeleteVariant)
// 用户分流
experiments.POST("/:id/assign", h.AssignUser)

View File

@ -7,8 +7,8 @@ import (
"errors"
"fmt"
"github.com/abtest/dao"
"github.com/abtest/model"
"backend/sdk/abtest/dao"
"backend/sdk/abtest/model"
)
var (
@ -80,6 +80,20 @@ func (s *Service) DeleteExperiment(id int64) error {
return s.dao.DeleteExperiment(id)
}
func (s *Service) ResetExperimentRelations(experimentID int64) error {
if _, err := s.GetExperiment(experimentID); err != nil {
return err
}
return s.dao.ResetExperimentRelations(experimentID)
}
func (s *Service) ClearWhitelist(experimentID int64) error {
if _, err := s.GetExperiment(experimentID); err != nil {
return err
}
return s.dao.DeleteWhitelistByExperiment(experimentID)
}
// ==================== Variant ====================
func (s *Service) CreateVariant(experimentID int64, req *model.CreateVariantReq) (*model.Variant, error) {

View File

@ -140,9 +140,9 @@ func TestEncrypt(t *testing.T) {
}
func TestDecrypt(t *testing.T) {
str := "Z4rf7eZZe500dxa"
str := "linyiling123"
enc, _ := util.Encrypt(str)
dec, _ := util.Decrypt("XGXZ_ID17eupWm0sVAarKaFjwAI_2Ve7dMb3")
dec, _ := util.Decrypt("mz-AJj2wjQ5F6MY5hbxiqsfqcwjRj8P85w==")
fmt.Printf("enc:%s\ndec:%s\n", enc, dec)
}
@ -534,5 +534,160 @@ func TestAlibabaOss(t *testing.T) {
err := common.Init()
if err != nil {
fmt.Println("Error initializing common:", err)
return
}
content := `
{
"schedules": [
{
"id": 10001,
"scheduleType": 0,
"deltaTime": {
"ticks": 1728000000000
},
"repeats": false,
"fireTime": {
"ticks": 639086752456801235
},
"repeatInterval": 0,
"cancelType": 1,
"cancelValue": 3
},
{
"id": 10002,
"scheduleType": 1,
"deltaTime": {
"ticks": 0
},
"repeats": false,
"fireTime": {
"ticks": 639337539300000000
},
"repeatInterval": 0,
"cancelType": 0,
"cancelValue": 0
},
{
"id": 10000,
"scheduleType": 0,
"deltaTime": {
"ticks": 1200000000
},
"repeats": true,
"fireTime": {
"ticks": 639086752456846041
},
"repeatInterval": 0,
"cancelType": 1,
"cancelValue": 0
}
],
"presentations": [
{
"id": 90001,
"titles": [
{
"language": 10,
"locText": "Merry Christmas",
"param": 0
},
{
"language": 40,
"locText": "圣诞快乐",
"param": 0
}
],
"infos": [
{
"language": 10,
"locText": "Merry Christmas!",
"param": 0
},
{
"language": 40,
"locText": "圣诞快乐!",
"param": 0
}
]
},
{
"id": 90002,
"titles": [
{
"language": 10,
"locText": "{0} is waiting for you to come home",
"param": 1
},
{
"language": 40,
"locText": "{0}正在等你回家",
"param": 1
}
],
"infos": [
{
"language": 10,
"locText": "{0} is hungry and misses you - come back soon!",
"param": 1
},
{
"language": 40,
"locText": "{0}肚子饿了,很想你,快回来看看吧",
"param": 1
}
]
},
{
"id": 90000,
"titles": [
{
"language": 10,
"locText": "Test",
"param": 0
},
{
"language": 40,
"locText": "测试",
"param": 0
}
],
"infos": [
{
"language": 10,
"locText": "UserName: {0} is testing",
"param": 2
},
{
"language": 40,
"locText": "用户{0}测试中",
"param": 2
}
]
}
],
"activeNotis": [
{
"id": 60000000,
"scheduleId": 10000,
"conditionId": 40001,
"presentationId": 90000
}
],
"inactiveNotis": [
{
"id": 60000001,
"scheduleId": 10001,
"conditionId": 40002,
"presentationId": 90002
},
{
"id": 60000002,
"scheduleId": 10002,
"conditionId": 40001,
"presentationId": 90001
}
]
}
`
alibaba.Osscreate(content)
}