feat: major update - MySQL, chat, wishlist, PWA, admin overhaul
This commit is contained in:
304
mengyastore-backend/cmd/migrate/main.go
Normal file
304
mengyastore-backend/cmd/migrate/main.go
Normal file
@@ -0,0 +1,304 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user