305 lines
9.0 KiB
Go
305 lines
9.0 KiB
Go
// migrate imports existing JSON data files into the MySQL database.
|
||
// Run once after switching to DB storage:
|
||
//
|
||
// go run ./cmd/migrate/main.go
|
||
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"log"
|
||
"os"
|
||
"strconv"
|
||
"time"
|
||
|
||
"gorm.io/driver/mysql"
|
||
"gorm.io/gorm"
|
||
"gorm.io/gorm/clause"
|
||
"gorm.io/gorm/logger"
|
||
|
||
"mengyastore-backend/internal/config"
|
||
"mengyastore-backend/internal/database"
|
||
)
|
||
|
||
func main() {
|
||
cfg, err := config.Load("data/json/settings.json")
|
||
if err != nil {
|
||
log.Fatalf("load config: %v", err)
|
||
}
|
||
|
||
db, err := gorm.Open(mysql.Open(cfg.DatabaseDSN), &gorm.Config{
|
||
Logger: logger.Default.LogMode(logger.Info),
|
||
})
|
||
if err != nil {
|
||
log.Fatalf("open db: %v", err)
|
||
}
|
||
|
||
// Ensure tables exist
|
||
if err := db.AutoMigrate(
|
||
&database.ProductRow{},
|
||
&database.ProductCodeRow{},
|
||
&database.OrderRow{},
|
||
&database.SiteSettingRow{},
|
||
&database.WishlistRow{},
|
||
&database.ChatMessageRow{},
|
||
); err != nil {
|
||
log.Fatalf("auto migrate: %v", err)
|
||
}
|
||
|
||
log.Println("数据库连接成功,开始导入...")
|
||
migrateProducts(db)
|
||
migrateOrders(db)
|
||
migrateWishlists(db)
|
||
migrateChats(db)
|
||
migrateSite(db)
|
||
log.Println("✅ 数据导入完成!")
|
||
}
|
||
|
||
// ─── Products ─────────────────────────────────────────────────────────────────
|
||
|
||
type jsonProduct struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Price float64 `json:"price"`
|
||
DiscountPrice float64 `json:"discountPrice"`
|
||
Tags []string `json:"tags"`
|
||
CoverURL string `json:"coverUrl"`
|
||
ScreenshotURLs []string `json:"screenshotUrls"`
|
||
Description string `json:"description"`
|
||
Active bool `json:"active"`
|
||
RequireLogin bool `json:"requireLogin"`
|
||
MaxPerAccount int `json:"maxPerAccount"`
|
||
TotalSold int `json:"totalSold"`
|
||
ViewCount int `json:"viewCount"`
|
||
DeliveryMode string `json:"deliveryMode"`
|
||
ShowNote bool `json:"showNote"`
|
||
ShowContact bool `json:"showContact"`
|
||
Codes []string `json:"codes"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
}
|
||
|
||
func migrateProducts(db *gorm.DB) {
|
||
data, err := os.ReadFile("data/json/products.json")
|
||
if err != nil {
|
||
log.Printf("[products] 文件不存在,跳过: %v", err)
|
||
return
|
||
}
|
||
var products []jsonProduct
|
||
if err := json.Unmarshal(data, &products); err != nil {
|
||
log.Printf("[products] JSON 解析失败: %v", err)
|
||
return
|
||
}
|
||
for _, p := range products {
|
||
if p.ID == "" {
|
||
continue
|
||
}
|
||
if p.DeliveryMode == "" {
|
||
p.DeliveryMode = "auto"
|
||
}
|
||
row := database.ProductRow{
|
||
ID: p.ID,
|
||
Name: p.Name,
|
||
Price: p.Price,
|
||
DiscountPrice: p.DiscountPrice,
|
||
Tags: database.StringSlice(p.Tags),
|
||
CoverURL: p.CoverURL,
|
||
ScreenshotURLs: database.StringSlice(p.ScreenshotURLs),
|
||
Description: p.Description,
|
||
Active: p.Active,
|
||
RequireLogin: p.RequireLogin,
|
||
MaxPerAccount: p.MaxPerAccount,
|
||
TotalSold: p.TotalSold,
|
||
ViewCount: p.ViewCount,
|
||
DeliveryMode: p.DeliveryMode,
|
||
ShowNote: p.ShowNote,
|
||
ShowContact: p.ShowContact,
|
||
CreatedAt: p.CreatedAt,
|
||
}
|
||
if row.CreatedAt.IsZero() {
|
||
row.CreatedAt = time.Now()
|
||
}
|
||
result := db.Clauses(clause.OnConflict{DoNothing: true}).Create(&row)
|
||
if result.Error != nil {
|
||
log.Printf("[products] 导入 %s 失败: %v", p.ID, result.Error)
|
||
continue
|
||
}
|
||
// Codes → product_codes
|
||
for _, code := range p.Codes {
|
||
if code == "" {
|
||
continue
|
||
}
|
||
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&database.ProductCodeRow{
|
||
ProductID: p.ID,
|
||
Code: code,
|
||
})
|
||
}
|
||
}
|
||
log.Printf("[products] 导入 %d 条商品", len(products))
|
||
}
|
||
|
||
// ─── Orders ───────────────────────────────────────────────────────────────────
|
||
|
||
type jsonOrder struct {
|
||
ID string `json:"id"`
|
||
ProductID string `json:"productId"`
|
||
ProductName string `json:"productName"`
|
||
UserAccount string `json:"userAccount"`
|
||
UserName string `json:"userName"`
|
||
Quantity int `json:"quantity"`
|
||
DeliveredCodes []string `json:"deliveredCodes"`
|
||
Status string `json:"status"`
|
||
DeliveryMode string `json:"deliveryMode"`
|
||
Note string `json:"note"`
|
||
ContactPhone string `json:"contactPhone"`
|
||
ContactEmail string `json:"contactEmail"`
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
}
|
||
|
||
func migrateOrders(db *gorm.DB) {
|
||
data, err := os.ReadFile("data/json/orders.json")
|
||
if err != nil {
|
||
log.Printf("[orders] 文件不存在,跳过: %v", err)
|
||
return
|
||
}
|
||
var orders []jsonOrder
|
||
if err := json.Unmarshal(data, &orders); err != nil {
|
||
log.Printf("[orders] JSON 解析失败: %v", err)
|
||
return
|
||
}
|
||
for _, o := range orders {
|
||
if o.ID == "" {
|
||
continue
|
||
}
|
||
if o.DeliveryMode == "" {
|
||
o.DeliveryMode = "auto"
|
||
}
|
||
if o.DeliveredCodes == nil {
|
||
o.DeliveredCodes = []string{}
|
||
}
|
||
row := database.OrderRow{
|
||
ID: o.ID,
|
||
ProductID: o.ProductID,
|
||
ProductName: o.ProductName,
|
||
UserAccount: o.UserAccount,
|
||
UserName: o.UserName,
|
||
Quantity: o.Quantity,
|
||
DeliveredCodes: database.StringSlice(o.DeliveredCodes),
|
||
Status: o.Status,
|
||
DeliveryMode: o.DeliveryMode,
|
||
Note: o.Note,
|
||
ContactPhone: o.ContactPhone,
|
||
ContactEmail: o.ContactEmail,
|
||
CreatedAt: o.CreatedAt,
|
||
}
|
||
if row.CreatedAt.IsZero() {
|
||
row.CreatedAt = time.Now()
|
||
}
|
||
if result := db.Clauses(clause.OnConflict{DoNothing: true}).Create(&row); result.Error != nil {
|
||
log.Printf("[orders] 导入 %s 失败: %v", o.ID, result.Error)
|
||
}
|
||
}
|
||
log.Printf("[orders] 导入 %d 条订单", len(orders))
|
||
}
|
||
|
||
// ─── Wishlists ────────────────────────────────────────────────────────────────
|
||
|
||
func migrateWishlists(db *gorm.DB) {
|
||
data, err := os.ReadFile("data/json/wishlists.json")
|
||
if err != nil {
|
||
log.Printf("[wishlists] 文件不存在,跳过: %v", err)
|
||
return
|
||
}
|
||
var wl map[string][]string
|
||
if err := json.Unmarshal(data, &wl); err != nil {
|
||
log.Printf("[wishlists] JSON 解析失败: %v", err)
|
||
return
|
||
}
|
||
count := 0
|
||
for account, productIDs := range wl {
|
||
for _, pid := range productIDs {
|
||
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&database.WishlistRow{
|
||
AccountID: account,
|
||
ProductID: pid,
|
||
})
|
||
count++
|
||
}
|
||
}
|
||
log.Printf("[wishlists] 导入 %d 条收藏记录", count)
|
||
}
|
||
|
||
// ─── Chats ────────────────────────────────────────────────────────────────────
|
||
|
||
type jsonChatMsg struct {
|
||
ID string `json:"id"`
|
||
AccountID string `json:"accountId"`
|
||
AccountName string `json:"accountName"`
|
||
Content string `json:"content"`
|
||
SentAt time.Time `json:"sentAt"`
|
||
FromAdmin bool `json:"fromAdmin"`
|
||
}
|
||
|
||
func migrateChats(db *gorm.DB) {
|
||
data, err := os.ReadFile("data/json/chats.json")
|
||
if err != nil {
|
||
log.Printf("[chats] 文件不存在,跳过: %v", err)
|
||
return
|
||
}
|
||
var convs map[string][]jsonChatMsg
|
||
if err := json.Unmarshal(data, &convs); err != nil {
|
||
log.Printf("[chats] JSON 解析失败: %v", err)
|
||
return
|
||
}
|
||
count := 0
|
||
for _, msgs := range convs {
|
||
for _, m := range msgs {
|
||
if m.ID == "" {
|
||
continue
|
||
}
|
||
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&database.ChatMessageRow{
|
||
ID: m.ID,
|
||
AccountID: m.AccountID,
|
||
AccountName: m.AccountName,
|
||
Content: m.Content,
|
||
SentAt: m.SentAt,
|
||
FromAdmin: m.FromAdmin,
|
||
})
|
||
count++
|
||
}
|
||
}
|
||
log.Printf("[chats] 导入 %d 条聊天消息", count)
|
||
}
|
||
|
||
// ─── Site settings ────────────────────────────────────────────────────────────
|
||
|
||
type jsonSite struct {
|
||
TotalVisits int `json:"totalVisits"`
|
||
Maintenance bool `json:"maintenance"`
|
||
MaintenanceReason string `json:"maintenanceReason"`
|
||
}
|
||
|
||
func migrateSite(db *gorm.DB) {
|
||
data, err := os.ReadFile("data/json/site.json")
|
||
if err != nil {
|
||
log.Printf("[site] 文件不存在,跳过: %v", err)
|
||
return
|
||
}
|
||
var site jsonSite
|
||
if err := json.Unmarshal(data, &site); err != nil {
|
||
log.Printf("[site] JSON 解析失败: %v", err)
|
||
return
|
||
}
|
||
upsert := func(key, value string) {
|
||
db.Clauses(clause.OnConflict{
|
||
Columns: []clause.Column{{Name: "key"}},
|
||
DoUpdates: clause.AssignmentColumns([]string{"value"}),
|
||
}).Create(&database.SiteSettingRow{Key: key, Value: value})
|
||
}
|
||
upsert("totalVisits", strconv.Itoa(site.TotalVisits))
|
||
maintenance := "false"
|
||
if site.Maintenance {
|
||
maintenance = "true"
|
||
}
|
||
upsert("maintenance", maintenance)
|
||
upsert("maintenanceReason", site.MaintenanceReason)
|
||
log.Printf("[site] 站点设置导入完成(访问量: %d)", site.TotalVisits)
|
||
}
|