// Package testusers creates explicitly labelled, non-loginable test profiles. // It is only used by an operator-invoked CLI, never during server startup. package testusers import ( "fmt" "net/url" "strings" ) const Batch = "cn-adults-20260831-v1" const Count = 100 type Profile struct { PublicID string `json:"publicId"` IsTest bool `json:"isTest"` TestBatch string `json:"testBatch"` Label string `json:"label"` Nickname string `json:"nickname"` Gender int `json:"gender"` Birthday string `json:"birthday"` Height int `json:"height"` CityCode string `json:"cityCode"` City string `json:"city"` Occupation string `json:"occupation"` Bio string `json:"bio"` AvatarFile string `json:"avatarFile"` Avatar string `json:"avatar"` } // Avatars contains five independently generated adult portraits per gender. // Reuse is intentional for fixtures; these are not 100 real identities. func Avatars() []string { files := make([]string, 0, 10) for gender := 1; gender <= 2; gender++ { for n := 1; n <= 5; n++ { files = append(files, fmt.Sprintf("900028%d%d-1.png", gender, n)) } } return files } func Generate(publicBase string) ([]Profile, error) { base, err := url.Parse(strings.TrimRight(publicBase, "/")) if err != nil || base.Host == "" || base.User != nil || base.RawQuery != "" || base.Fragment != "" || (base.Scheme != "http" && base.Scheme != "https") { return nil, fmt.Errorf("public base must be an absolute HTTP(S) uploads URL without credentials, query or fragment") } if base.Scheme == "http" && base.Hostname() != "127.0.0.1" && base.Hostname() != "localhost" && base.Hostname() != "::1" { return nil, fmt.Errorf("non-local avatar URLs require HTTPS") } surnames := []string{"陈", "林", "周", "许", "苏", "沈", "陆", "顾", "方", "季"} given := [][]string{{"沐川", "知远", "星河", "景行", "予安"}, {"晚晴", "知夏", "语桐", "清禾", "若宁"}} cities := [][2]string{{"310100", "上海"}, {"440100", "广州"}, {"440300", "深圳"}, {"330100", "杭州"}, {"510100", "成都"}, {"420100", "武汉"}, {"320100", "南京"}, {"350200", "厦门"}, {"610100", "西安"}, {"370200", "青岛"}} jobs := []string{"设计师", "工程师", "教师", "摄影师", "产品经理"} hobbies := []string{"摄影与城市漫步", "阅读与咖啡", "跑步与音乐", "旅行与美食", "电影与绘画"} ages := [2][5]int{{27, 30, 24, 33, 28}, {26, 29, 24, 32, 27}} items := make([]Profile, 0, Count) for gender := 1; gender <= 2; gender++ { for i := 0; i < 50; i++ { avatarFile := fmt.Sprintf("900028%d%d-1.png", gender, i%5+1) city := cities[i%len(cities)] items = append(items, Profile{ PublicID: fmt.Sprintf("TESTCN%06d", (gender-1)*50+i+1), IsTest: true, TestBatch: Batch, Label: "测试用户", Nickname: "测试·" + surnames[i/5] + given[gender-1][i%5], Gender: gender, Birthday: fmt.Sprintf("%d-%02d-%02d", 2026-ages[gender-1][i%5], i%6+1, i%27+1), Height: 160 + (2-gender)*12 + i%12, CityCode: city[0], City: city[1], Occupation: jobs[i%len(jobs)], Bio: "【测试数据】虚构资料,AI生成中国成年人头像,仅供功能测试,非真实交友用户。兴趣示例:" + hobbies[i%len(hobbies)] + "。", AvatarFile: avatarFile, Avatar: base.String() + "/" + avatarFile, }) } } return items, nil }