游戏机器人
This commit is contained in:
parent
83b82b8e30
commit
bb79845b6b
@ -2,8 +2,10 @@ package gamemanagement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
@ -12,6 +14,7 @@ import (
|
||||
type RobotProfile struct {
|
||||
Nickname string
|
||||
Avatar string
|
||||
Gender string
|
||||
}
|
||||
|
||||
type RobotProfileSource interface {
|
||||
@ -29,7 +32,7 @@ func NewLikeiRobotProfileSource(db *sql.DB) RobotProfileSource {
|
||||
return &likeiRobotProfileSource{db: db}
|
||||
}
|
||||
|
||||
// WithRobotProfileSource 注入机器人资料池;创建机器人时只消费这里返回的线上 likei 用户昵称和头像。
|
||||
// WithRobotProfileSource 注入机器人资料池;创建机器人优先消费线上 likei 用户昵称和头像,未配置时再走本地随机资料兜底。
|
||||
func WithRobotProfileSource(source RobotProfileSource) Option {
|
||||
return func(h *Handler) {
|
||||
h.robotProfiles = source
|
||||
@ -81,6 +84,50 @@ func (s *likeiRobotProfileSource) RandomRobotProfiles(ctx context.Context, count
|
||||
return profiles, nil
|
||||
}
|
||||
|
||||
func robotProfilesForGenerate(ctx context.Context, source RobotProfileSource, count int) ([]RobotProfile, error) {
|
||||
if source != nil {
|
||||
// 已配置 likei 时继续使用真实资料池;查询失败不能静默降级,否则线上数据源故障会被批量造号掩盖。
|
||||
return source.RandomRobotProfiles(ctx, count)
|
||||
}
|
||||
profiles := make([]RobotProfile, 0, count)
|
||||
for index := 0; index < count; index++ {
|
||||
profiles = append(profiles, randomRobotProfile())
|
||||
}
|
||||
return profiles, nil
|
||||
}
|
||||
|
||||
func randomRobotProfile() RobotProfile {
|
||||
seed := randomRobotHex(8)
|
||||
nameSeed := randomRobotHex(3)
|
||||
if seed == "fallback" {
|
||||
seed = fmt.Sprintf("fallback-%d", randomRobotInt(1000000))
|
||||
}
|
||||
// 本地随机头像只保存绝对 URL,不在 admin-server 里下载或转存图片;user-service 仍负责资料字段校验。
|
||||
return RobotProfile{
|
||||
Nickname: fmt.Sprintf("Robot %s", strings.ToUpper(nameSeed)),
|
||||
Avatar: "https://api.dicebear.com/9.x/adventurer/png?seed=hyapp-robot-" + url.QueryEscape(seed),
|
||||
Gender: randomRobotGender(),
|
||||
}
|
||||
}
|
||||
|
||||
func randomRobotGender() string {
|
||||
if randomRobotInt(2) == 0 {
|
||||
return "male"
|
||||
}
|
||||
return "female"
|
||||
}
|
||||
|
||||
func randomRobotInt(max int64) int64 {
|
||||
if max <= 0 {
|
||||
return 0
|
||||
}
|
||||
value, err := rand.Int(rand.Reader, big.NewInt(max))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return value.Int64()
|
||||
}
|
||||
|
||||
func normalizeRobotProfile(profile RobotProfile) (RobotProfile, bool) {
|
||||
profile.Nickname = strings.TrimSpace(profile.Nickname)
|
||||
profile.Avatar = strings.TrimSpace(profile.Avatar)
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"hyapp-admin-server/internal/integration/gameclient"
|
||||
@ -104,3 +105,46 @@ func TestGenerateDiceRobotsUsesLikeiProfiles(t *testing.T) {
|
||||
t.Fatalf("registered user ids = %#v", games.registered)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateDiceRobotsFallsBackToRandomProfilesWhenLikeiMissing(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
users := &fakeRobotUserClient{}
|
||||
games := &fakeRobotGameClient{}
|
||||
handler := New(games, users, nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
c.Set(middleware.ContextRequestID, "req-robot-random")
|
||||
c.Set(middleware.ContextUserID, uint(7))
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/games/self/dice/robots:generate", bytes.NewBufferString(`{
|
||||
"count": 3,
|
||||
"country": "TH"
|
||||
}`))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
handler.GenerateDiceRobots(c)
|
||||
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
if len(users.created) != 3 {
|
||||
t.Fatalf("created users = %d, want 3", len(users.created))
|
||||
}
|
||||
for index, created := range users.created {
|
||||
if !strings.HasPrefix(created.Username, "Robot ") {
|
||||
t.Fatalf("created[%d] username should be generated, got %#v", index, created)
|
||||
}
|
||||
if !strings.HasPrefix(created.Avatar, "https://api.dicebear.com/9.x/adventurer/png?seed=hyapp-robot-") {
|
||||
t.Fatalf("created[%d] avatar should be generated, got %#v", index, created)
|
||||
}
|
||||
if created.Gender != "male" && created.Gender != "female" {
|
||||
t.Fatalf("created[%d] gender should be random male/female, got %#v", index, created)
|
||||
}
|
||||
if created.Country != "TH" || created.Source != "game_robot" {
|
||||
t.Fatalf("created[%d] metadata = %#v", index, created)
|
||||
}
|
||||
}
|
||||
if len(games.registered) != 3 || games.registered[0] != 9001 || games.registered[2] != 9003 {
|
||||
t.Fatalf("registered user ids = %#v", games.registered)
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,19 +220,14 @@ func (h *Handler) GenerateDiceRobots(c *gin.Context) {
|
||||
if country == "" {
|
||||
country = "SA"
|
||||
}
|
||||
if h.robotProfiles == nil {
|
||||
response.ServerError(c, "likei 机器人资料源未配置")
|
||||
return
|
||||
}
|
||||
// 新机器人资料只从 likei 线上用户库随机抽取;后台不再使用本地昵称池、随机头像站或 COS 转存,
|
||||
// 这样每次批量创建都面对真实用户资料池,避免少量固定预设反复出现在匹配列表里。
|
||||
profiles, err := h.robotProfiles.RandomRobotProfiles(c.Request.Context(), int(req.Count))
|
||||
// likei 是优先资料源;测试服或本地未配置时用随机资料兜底,避免运维为了造机器人账号被外部历史库阻塞。
|
||||
profiles, err := robotProfilesForGenerate(c.Request.Context(), h.robotProfiles, int(req.Count))
|
||||
if err != nil {
|
||||
response.BadRequest(c, "获取 likei 机器人资料失败: "+err.Error())
|
||||
response.BadRequest(c, "获取机器人资料失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if len(profiles) < int(req.Count) {
|
||||
response.BadRequest(c, "likei 机器人资料数量不足")
|
||||
response.BadRequest(c, "机器人资料数量不足")
|
||||
return
|
||||
}
|
||||
userIDs := make([]int64, 0, req.Count)
|
||||
@ -240,13 +235,21 @@ func (h *Handler) GenerateDiceRobots(c *gin.Context) {
|
||||
accountLanguage := robotAccountLanguage(req.NicknameLanguage)
|
||||
for index := int32(0); index < req.Count; index++ {
|
||||
profile := profiles[index]
|
||||
gender := strings.TrimSpace(req.Gender)
|
||||
if gender == "" {
|
||||
gender = strings.TrimSpace(profile.Gender)
|
||||
}
|
||||
if gender == "" {
|
||||
// likei 当前只提供昵称和头像;没有显式性别时保持旧的均匀分布,随机兜底资料会在 profile 中给出真实随机值。
|
||||
gender = robotGender(int(index), "")
|
||||
}
|
||||
created, err := h.user.QuickCreateAccount(c.Request.Context(), userclient.QuickCreateAccountRequest{
|
||||
RequestID: requestID,
|
||||
Caller: "admin-server",
|
||||
Password: randomRobotPassword(),
|
||||
Username: profile.Nickname,
|
||||
Avatar: profile.Avatar,
|
||||
Gender: robotGender(int(index), req.Gender),
|
||||
Gender: gender,
|
||||
Country: country,
|
||||
DeviceID: "game-robot-" + randomRobotHex(8),
|
||||
Source: "game_robot",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user