- pkg/jwt: 删 public SetSecret; 加 MustInit(secret string) 启动时强制注入, 缺/为空/等于弱默认值时返回 error(运行期不可再改); 密钥用 atomic.Value 存 []byte,所有读走 mustSecret() 原子 Load,消除 SetSecret/ParseToken 并发 data race(go test -race 零告警)。 - gateway main + auth_provider: 启动时 MustInit 读 JWT_SECRET env,失败 fatal。 - scripts/loadgen/seed/tokens: 同步 MustInit。 - .env.example: JWT_SECRET 改为 ≥32 字节 base64 示例(原为空,被 MustInit 立即拒);注释提示生产 MUST replace。 - 测试: 4 个 MustInit 行为 + 1 个 50-goroutine race 覆盖。 - 行为变更: 任何 .env 缺 JWT_SECRET 或用占位 secret 的服务,启动会 panic (这是 fail-fast 期望行为);其余 4 个 .env 文件占位由 ops 单独轮换。 Co-Authored-By: Claude <noreply@anthropic.com>
102 lines
2.4 KiB
Go
102 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/lib/pq"
|
|
"github.com/topfans/backend/pkg/jwt"
|
|
)
|
|
|
|
type TestUser struct {
|
|
UserID int64
|
|
Mobile string
|
|
AssetIDs pq.Int64Array
|
|
ExhibitionIDs pq.Int64Array
|
|
}
|
|
|
|
func GenerateTokensForLoadtest(cfg *Config) error {
|
|
if err := jwt.MustInit(cfg.JWTSecret); err != nil {
|
|
return fmt.Errorf("jwt.MustInit failed: %w", err)
|
|
}
|
|
|
|
db, err := openDB(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
|
|
rows, err := db.Query(`
|
|
SELECT u.id, u.mobile,
|
|
COALESCE(array_agg(DISTINCT a.id) FILTER (WHERE a.id IS NOT NULL), '{}'::int8[]),
|
|
COALESCE(array_agg(DISTINCT e.id) FILTER (WHERE e.id IS NOT NULL), '{}'::int8[])
|
|
FROM users u
|
|
LEFT JOIN assets a ON a.owner_uid = u.id AND a.star_id = $1
|
|
LEFT JOIN fan_profiles fp ON fp.user_id = u.id AND fp.star_id = $1
|
|
LEFT JOIN booth_slots bs ON bs.host_profile_id = fp.id AND bs.slot_index IN (1, 2)
|
|
LEFT JOIN exhibitions e ON e.slot_id = bs.slot_id AND e.occupier_star_id = $1
|
|
WHERE u.id BETWEEN $2 AND $3
|
|
GROUP BY u.id, u.mobile
|
|
ORDER BY u.id
|
|
`, LoadtestStarID, LoadtestUserMin, LoadtestUserMax)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var users []TestUser
|
|
for rows.Next() {
|
|
var u TestUser
|
|
if err := rows.Scan(&u.UserID, &u.Mobile, &u.AssetIDs, &u.ExhibitionIDs); err != nil {
|
|
return err
|
|
}
|
|
users = append(users, u)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := os.Create("users.csv")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
w := csv.NewWriter(f)
|
|
defer w.Flush()
|
|
if err := w.Write([]string{"phone", "password", "user_id", "star_id", "jwt_token", "asset_ids", "exhibition_ids"}); err != nil {
|
|
return err
|
|
}
|
|
|
|
now := time.Now().UnixMilli()
|
|
for _, u := range users {
|
|
token, err := jwt.GenerateToken(u.UserID, LoadtestStarID, now)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := w.Write([]string{
|
|
u.Mobile, "Test@123",
|
|
strconv.FormatInt(u.UserID, 10),
|
|
"999900", token,
|
|
joinInt64([]int64(u.AssetIDs)),
|
|
joinInt64([]int64(u.ExhibitionIDs)),
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
fmt.Printf("✅ users.csv written: %d rows\n", len(users))
|
|
return nil
|
|
}
|
|
|
|
func joinInt64(s []int64) string {
|
|
parts := make([]string, len(s))
|
|
for i, v := range s {
|
|
parts[i] = strconv.FormatInt(v, 10)
|
|
}
|
|
return strings.Join(parts, ";")
|
|
}
|