Files
kefu/im/backend/internal/app/seed.go
T
2026-09-03 08:38:17 +08:00

192 lines
10 KiB
Go

package app
import (
"context"
"crypto/sha256"
"database/sql"
"encoding/json"
"fmt"
"time"
)
type demoUser struct {
Phone, Nickname, Avatar, Cover, City, Bio string
Gender, Age, VIP int
Lat, Lng float64
}
var demoUsers = []demoUser{
{"13800138000", "小甜心", "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=600&auto=format&fit=crop", "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?w=1200&auto=format&fit=crop", "上海", "热爱生活,喜欢记录美好瞬间", 2, 23, 2, 31.2304, 121.4737},
{"13800138001", "小鹿心", "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=600&auto=format&fit=crop", "https://images.unsplash.com/photo-1519608487953-e999c86e7455?w=1200&auto=format&fit=crop", "上海", "摄影、旅行和一切浪漫的事", 2, 23, 1, 31.2310, 121.4750},
{"13800138002", "爱笑的眼睛", "https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=600&auto=format&fit=crop", "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=1200&auto=format&fit=crop", "上海", "愿每一天都有新的故事", 2, 24, 1, 31.2289, 121.4701},
{"13800138003", "一只可爱喵", "https://images.unsplash.com/photo-1517841905240-472988babdf9?w=600&auto=format&fit=crop", "https://images.unsplash.com/photo-1497436072909-f5e4be1713c0?w=1200&auto=format&fit=crop", "上海", "咖啡重度爱好者", 2, 23, 0, 31.2260, 121.4690},
{"13800138004", "星辰大海", "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=600&auto=format&fit=crop", "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?w=1200&auto=format&fit=crop", "上海", "周末去爬山吧", 1, 25, 0, 31.2248, 121.4810},
{"13800138005", "南音不渝", "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=600&auto=format&fit=crop", "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?w=1200&auto=format&fit=crop", "上海", "听歌、跑步、看展", 1, 24, 0, 31.2204, 121.4760},
{"13800138006", "温柔的风", "https://images.unsplash.com/photo-1531123897727-8f129e1688ce?w=600&auto=format&fit=crop", "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=1200&auto=format&fit=crop", "上海", "想遇见同频的人", 2, 24, 1, 31.2184, 121.4860},
{"13800138007", "月亮邮递员", "https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=600&auto=format&fit=crop", "https://images.unsplash.com/photo-1470252649378-9c29740c9fa8?w=1200&auto=format&fit=crop", "上海", "收集晚霞和好心情", 2, 24, 1, 31.2154, 121.4710},
}
func (a *App) Seed() error {
var tableCount int
if err := a.db.QueryRow(`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'users'`).Scan(&tableCount); err != nil {
return err
}
if tableCount == 0 {
return fmt.Errorf("数据库尚未初始化,请先运行 scripts/migrate.ps1")
}
if err := a.encryptLegacyPhones(context.Background()); err != nil {
return fmt.Errorf("encrypt legacy phone data: %w", err)
}
adminUsername := a.config.BootstrapAdminUsername
adminPassword := a.config.BootstrapAdminPassword
adminRealName := a.config.BootstrapAdminRealName
if a.config.SeedDemo && adminPassword == "" {
adminUsername = "admin"
adminPassword = "Admin@123"
}
if adminPassword != "" {
if adminUsername == "" {
return fmt.Errorf("已配置管理员密码,但 IM_BOOTSTRAP_ADMIN_USERNAME 为空")
}
adminHash, hashErr := hashPassword(adminPassword)
if hashErr != nil {
return hashErr
}
_, err := a.db.Exec(`INSERT INTO admin_users (username,password_hash,real_name,avatar_url,status)
VALUES (?,?,?,?,1) ON DUPLICATE KEY UPDATE real_name=VALUES(real_name)`, adminUsername, adminHash, adminRealName, demoUsers[0].Avatar)
if err != nil {
return err
}
_, err = a.db.Exec(`INSERT IGNORE INTO admin_user_roles(admin_user_id,role_id)
SELECT a.id,r.id FROM admin_users a JOIN admin_roles r ON r.role_code='super_admin' WHERE a.username=?`, adminUsername)
if err != nil {
return fmt.Errorf("assign bootstrap administrator role: %w", err)
}
}
var adminCount int
if err := a.db.QueryRow(`SELECT COUNT(*) FROM admin_users WHERE status=1`).Scan(&adminCount); err != nil {
return err
}
if adminCount == 0 {
return fmt.Errorf("没有可用管理员,请配置 IM_BOOTSTRAP_ADMIN_USERNAME 和 IM_BOOTSTRAP_ADMIN_PASSWORD")
}
if !a.config.SeedDemo {
return nil
}
var users int
if err := a.db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&users); err != nil {
return err
}
if users > 0 {
return nil
}
passwordHash, err := hashPassword("123456")
if err != nil {
return err
}
tx, err := a.db.Begin()
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
ids := make([]int64, 0, len(demoUsers))
for index, item := range demoUsers {
birthday := time.Now().AddDate(-item.Age, 0, 0).Format("2006-01-02")
phoneCipher, encryptErr := a.encryptPhone(item.Phone)
if encryptErr != nil {
return encryptErr
}
result, execErr := tx.Exec(`INSERT INTO users (public_id,country_code,phone_hash,phone_cipher,password_hash,status,risk_level)
VALUES (?,?,?,?,?,1,0)`, fmt.Sprintf("XY%08d", index+10001), "+86", phoneHash(item.Phone), phoneCipher, passwordHash)
if execErr != nil {
return execErr
}
id, _ := result.LastInsertId()
ids = append(ids, id)
_, execErr = tx.Exec(`INSERT INTO user_profiles
(user_id,nickname,avatar_url,cover_url,gender,birthday,height_cm,city_code,city_name,occupation,bio,profile_score,is_vip,vip_level,last_active_at)
VALUES (?,?,?,?,?,?,?,?,'上海',?,?,95,?,?,?)`, id, item.Nickname, item.Avatar, item.Cover, item.Gender, birthday, 163+index%12, "310100", "创意行业", item.Bio, btoi(item.VIP > 0), item.VIP, time.Now().Add(-time.Duration(index*4)*time.Minute))
if execErr != nil {
return execErr
}
_, _ = tx.Exec(`INSERT INTO user_privacy_settings (user_id) VALUES (?)`, id)
_, _ = tx.Exec(`INSERT INTO user_location_states (user_id,city_code,location_cell,latitude,longitude,source) VALUES (?,'310100','wx4g',?,?,'seed')`, id, item.Lat, item.Lng)
_, _ = tx.Exec(`INSERT INTO user_risk_profiles (user_id,risk_score,risk_level) VALUES (?, ?, ?)`, id, index*3, btoi(index == 7))
}
mediaSets := [][]string{
{"https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?w=900&auto=format&fit=crop", "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=900&auto=format&fit=crop", "https://images.unsplash.com/photo-1470770841072-f978cf4d019e?w=900&auto=format&fit=crop"},
{"https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?w=900&auto=format&fit=crop", "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?w=900&auto=format&fit=crop", "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=900&auto=format&fit=crop"},
{"https://images.unsplash.com/photo-1470252649378-9c29740c9fa8?w=900&auto=format&fit=crop"},
}
contents := []string{"今天的天空很美,心情也很好~", "周末去爬山啦", "晚霞也太治愈了吧"}
for index, content := range contents {
result, execErr := tx.Exec(`INSERT INTO posts (user_id,content,city_code,location_text,like_count,comment_count) VALUES (?,?, '310100','上海',?,?)`, ids[index+1], content, 23+index*13, 8+index*2)
if execErr != nil {
return execErr
}
postID, _ := result.LastInsertId()
for order, url := range mediaSets[index] {
_, _ = tx.Exec(`INSERT INTO post_media (post_id,media_url,media_type,sort_order) VALUES (?,?,'image',?)`, postID, url, order)
}
}
for i := 1; i < len(ids); i++ {
_, _ = tx.Exec(`INSERT INTO user_follows (user_id,target_user_id) VALUES (?,?)`, ids[0], ids[i])
if i < 5 {
_, _ = tx.Exec(`INSERT INTO user_likes (user_id,target_user_id,source) VALUES (?,?, 'seed')`, ids[0], ids[i])
}
}
conversationResult, err := tx.Exec(`INSERT INTO im_conversations (conversation_type,last_seq,last_message_at) VALUES (1,3,NOW(3))`)
if err != nil {
return err
}
conversationID, _ := conversationResult.LastInsertId()
_, _ = tx.Exec(`INSERT INTO im_direct_conversations (conversation_id,user1_id,user2_id) VALUES (?,?,?)`, conversationID, ids[0], ids[1])
_, _ = tx.Exec(`INSERT INTO im_conversation_members (conversation_id,user_id,read_seq,delivered_seq) VALUES (?,?,3,3),(?,?,1,3)`, conversationID, ids[0], conversationID, ids[1])
messages := []struct {
sender int64
text string
}{{ids[1], "今天的晚霞好美呀~"}, {ids[0], "阳光正好,想和你去看一次日落"}, {ids[1], "好呀好呀,我也正想去看呢!"}}
for index, message := range messages {
body, _ := json.Marshal(map[string]string{"text": message.text})
result, execErr := tx.Exec(`INSERT INTO im_messages (conversation_id,seq,sender_id,client_msg_id,message_type,body) VALUES (?,?,?,?,1,?)`, conversationID, index+1, message.sender, fmt.Sprintf("01JDEMO%019d", index+1), body)
if execErr != nil {
return execErr
}
if index == len(messages)-1 {
messageID, _ := result.LastInsertId()
_, _ = tx.Exec(`UPDATE im_conversations SET last_message_id=? WHERE id=?`, messageID, conversationID)
}
}
_, _ = tx.Exec(`INSERT INTO notifications (user_id,type,title,content,biz_type,biz_id) VALUES
(?,'follow','新的关注','爱笑的眼睛关注了你','user',?),
(?,'like','新的喜欢','小鹿心喜欢了你','user',?),
(?,'system','欢迎来到星遇','完善资料可以获得更多推荐','',NULL)`, ids[0], ids[2], ids[0], ids[1], ids[0])
_, _ = tx.Exec(`INSERT INTO reports (reporter_user_id,target_type,target_id,reason_code,description,status) VALUES (?, 'user', ?, 'advertising', '频繁发送广告链接', 'PENDING')`, ids[2], ids[7])
_, _ = tx.Exec(`INSERT INTO risk_events (user_id,event_type,score_delta,device_id,ip,metadata) VALUES (?, 'rapid_messages', 12, 'demo-device', '127.0.0.1', JSON_OBJECT('count', 32))`, ids[7])
_, _ = tx.Exec(`INSERT INTO orders (order_no,user_id,product_type,product_id,amount_cent,status,channel,paid_at) VALUES ('XYDEMO202608240001', ?, 'membership', 2, 6800, 'PAID', 'alipay', NOW(3))`, ids[0])
return tx.Commit()
}
func btoi(value bool) int {
if value {
return 1
}
return 0
}
func scanNullableString(value sql.NullString) string {
if value.Valid {
return value.String
}
return ""
}
func sha(value string) []byte { sum := sha256.Sum256([]byte(value)); return sum[:] }