192 lines
10 KiB
Go
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[:] }
|