feat: major update - MySQL, chat, wishlist, PWA, admin overhaul

This commit is contained in:
2026-03-21 20:22:00 +08:00
committed by 树萌芽
parent 48fb818b8c
commit 84874707f5
71 changed files with 13457 additions and 2031 deletions

View 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)
}