update
This commit is contained in:
34
scripts/fashionapi.service
Normal file
34
scripts/fashionapi.service
Normal file
@ -0,0 +1,34 @@
|
||||
# ============================================================
|
||||
# Fashion API —— systemd 服务单元(可选,用于服务器常驻运行)
|
||||
# ------------------------------------------------------------
|
||||
# 用法(在服务器上):
|
||||
# sudo cp fashionapi.service /etc/systemd/system/
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable --now fashionapi
|
||||
# 查看日志:sudo journalctl -u fashionapi -f
|
||||
#
|
||||
# 敏感项(JWT_SECRET / DB_PASSWORD 等)建议用 Environment 注入,
|
||||
# 不要写进 config.yml,也不要提交到仓库。
|
||||
# ============================================================
|
||||
|
||||
[Unit]
|
||||
Description=Fashion API (public readonly endpoints)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/fashionapi
|
||||
ExecStart=/opt/fashionapi/server-linux-amd64
|
||||
# 生产环境变量覆盖(按需修改)
|
||||
Environment=CONFIG_PATH=/opt/fashionapi/config.yml
|
||||
Environment=GIN_MODE=release
|
||||
# Environment=DB_HOST=127.0.0.1
|
||||
# Environment=DB_PASSWORD=your_strong_password
|
||||
# Environment=JWT_SECRET=change-me-to-a-random-long-string
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
# 如需指定运行账户,取消下一行注释并确保该用户对 /opt/fashionapi 有读权限
|
||||
# User=www-data
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
106
scripts/i18n/main.go
Normal file
106
scripts/i18n/main.go
Normal file
@ -0,0 +1,106 @@
|
||||
// Command i18n_migrate 落地方案 A 的 i18n 列(加列 + 回填)。
|
||||
//
|
||||
// 项目已移除 AutoMigrate,表结构由外部 SQL 托管(见 scripts/sql/002_i18n_columns.sql,
|
||||
// 那是给人 review 的规范底稿)。本程序是「没有 mysql 客户端时」的执行器:
|
||||
// - ALTER 走 ensureColumn:先查 information_schema,缺才加,可安全重跑(幂等)。
|
||||
// - 回填 UPDATE 内联且幂等,重跑无害。
|
||||
//
|
||||
// 不写任何业务逻辑。
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const dsn = "root:root@tcp(127.0.0.1:3306)/db?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
const han = "[一-鿿]"
|
||||
|
||||
func main() {
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("open:", err)
|
||||
}
|
||||
|
||||
// 1) 幂等加列(与 002_i18n_columns.sql 对齐)
|
||||
ensureColumn(db, "brand", "name_en", "VARCHAR(255) NOT NULL DEFAULT '' AFTER name")
|
||||
ensureColumn(db, "brand", "name_cn", "VARCHAR(255) NOT NULL DEFAULT '' AFTER name_en")
|
||||
ensureColumn(db, "brand_runway", "title_en", "VARCHAR(255) NOT NULL DEFAULT '' AFTER title")
|
||||
ensureColumn(db, "brand_runway", "title_cn", "VARCHAR(255) NOT NULL DEFAULT '' AFTER title_en")
|
||||
ensureColumn(db, "brand_runway", "description_en", "VARCHAR(1024) NOT NULL DEFAULT '' AFTER description")
|
||||
ensureColumn(db, "brand_runway", "description_cn", "VARCHAR(1024) NOT NULL DEFAULT '' AFTER description_en")
|
||||
|
||||
// 2) 回填品牌名:从 name / show_name 拆拉丁与中文(幂等)
|
||||
db.Exec(fmt.Sprintf(`
|
||||
UPDATE brand SET
|
||||
name_en = CASE
|
||||
WHEN TRIM(REGEXP_REPLACE(name, '%s', '')) <> '' THEN TRIM(REGEXP_REPLACE(name, '%s', ''))
|
||||
WHEN show_name LIKE '%%/%%' THEN TRIM(SUBSTRING_INDEX(show_name, '/', 1))
|
||||
ELSE ''
|
||||
END,
|
||||
name_cn = CASE
|
||||
WHEN show_name LIKE '%%/%%' AND SUBSTRING_INDEX(show_name, '/', -1) REGEXP '%s'
|
||||
THEN TRIM(SUBSTRING_INDEX(show_name, '/', -1))
|
||||
WHEN show_name REGEXP '%s' THEN TRIM(show_name)
|
||||
WHEN name REGEXP '%s' THEN TRIM(name)
|
||||
ELSE ''
|
||||
END`, han, han, han, han, han))
|
||||
|
||||
// 3) 回填走秀:title / description 当前全英文,直接进 *_en(幂等)
|
||||
db.Exec(`UPDATE brand_runway SET title_en = title, description_en = description, title_cn = '', description_cn = ''`)
|
||||
|
||||
// 4) 校验
|
||||
printCounts(db)
|
||||
fmt.Println("migration done.")
|
||||
}
|
||||
|
||||
func ensureColumn(db *gorm.DB, table, col, def string) {
|
||||
var n int64
|
||||
db.Raw("SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='db' AND TABLE_NAME=? AND COLUMN_NAME=?", table, col).Scan(&n)
|
||||
if n > 0 {
|
||||
fmt.Printf(" skip %s.%s (exists)\n", table, col)
|
||||
return
|
||||
}
|
||||
stmt := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s", table, col, def)
|
||||
fmt.Println(" +", stmt)
|
||||
if r := db.Exec(stmt); r.Error != nil {
|
||||
log.Fatalf("alter %s.%s failed: %v", table, col, r.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func printCounts(db *gorm.DB) {
|
||||
type c struct {
|
||||
Tot, En, Cn, Both, Dcn int64
|
||||
}
|
||||
var b c
|
||||
db.Raw(`SELECT COUNT(*) tot,
|
||||
SUM(CASE WHEN name_en<>'' THEN 1 ELSE 0 END) en,
|
||||
SUM(CASE WHEN name_cn<>'' THEN 1 ELSE 0 END) cn,
|
||||
SUM(CASE WHEN name_en<>'' AND name_cn<>'' THEN 1 ELSE 0 END) both_cnt
|
||||
FROM brand`).Scan(&b)
|
||||
fmt.Printf(" brand: total=%d name_en=%d name_cn=%d both=%d\n", b.Tot, b.En, b.Cn, b.Both)
|
||||
|
||||
var r c
|
||||
db.Raw(`SELECT COUNT(*) tot,
|
||||
SUM(CASE WHEN title_en<>'' THEN 1 ELSE 0 END) en,
|
||||
SUM(CASE WHEN title_cn<>'' THEN 1 ELSE 0 END) cn,
|
||||
SUM(CASE WHEN description_cn<>'' THEN 1 ELSE 0 END) dcn
|
||||
FROM brand_runway WHERE is_deleted=0`).Scan(&r)
|
||||
fmt.Printf(" runway: total=%d title_en=%d title_cn=%d desc_cn=%d\n", r.Tot, r.En, r.Cn, r.Dcn)
|
||||
|
||||
type s struct {
|
||||
Name string
|
||||
ShowName string
|
||||
NameEn string
|
||||
NameCn string
|
||||
}
|
||||
var samples []s
|
||||
db.Raw("SELECT name, show_name, name_en, name_cn FROM brand WHERE name REGEXP ? OR show_name REGEXP ? ORDER BY id LIMIT 8", han, han).Scan(&samples)
|
||||
fmt.Println(" brand samples:")
|
||||
for _, x := range samples {
|
||||
fmt.Printf(" name=%-18q show=%-26q -> en=%-18q cn=%-18q\n", x.Name, x.ShowName, x.NameEn, x.NameCn)
|
||||
}
|
||||
}
|
||||
49
scripts/sql/002_i18n_columns.sql
Normal file
49
scripts/sql/002_i18n_columns.sql
Normal file
@ -0,0 +1,49 @@
|
||||
-- ============================================================================
|
||||
-- 002_i18n_columns.sql
|
||||
-- 方案 A(独立列):把品牌名 / 走秀标题 / 描述 拆分为 en / cn 两列。
|
||||
--
|
||||
-- 设计前提(已用探查脚本核对线上数据):
|
||||
-- * brand.name 多为拉丁名,但 266 个含中文;show_name 形如 "拉丁/中文"(2025 个含中文)。
|
||||
-- * brand_runway.title / description 校验过 han=0,当前全为英文。
|
||||
-- * 排序 / A-Z 索引 / 关键词搜索 统一基于 name_en(拉丁),中文品牌(name_en 为空)归入 OTHER 桶。
|
||||
--
|
||||
-- 回填策略:
|
||||
-- name_en = name 去掉中文后的部分;若 name 全中文则取 show_name 斜杠前部分。
|
||||
-- name_cn = show_name 斜杠后含中文的部分;否则 show_name 本身(含中文);否则 name(含中文)。
|
||||
-- title_en / description_en = 原 title / description;title_cn / description_cn = ''(当前无中文)。
|
||||
--
|
||||
-- 旧列 name / show_name / title / description 保留不删,作为回退与对比;
|
||||
-- 代码全部改用 *_en / *_cn 后,可择机在后续迁移中 DROP。
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE brand
|
||||
ADD COLUMN name_en VARCHAR(255) NOT NULL DEFAULT '' AFTER name,
|
||||
ADD COLUMN name_cn VARCHAR(255) NOT NULL DEFAULT '' AFTER name_en;
|
||||
|
||||
ALTER TABLE brand_runway
|
||||
ADD COLUMN title_en VARCHAR(255) NOT NULL DEFAULT '' AFTER title,
|
||||
ADD COLUMN title_cn VARCHAR(255) NOT NULL DEFAULT '' AFTER title_en,
|
||||
ADD COLUMN description_en VARCHAR(1024) NOT NULL DEFAULT '' AFTER description,
|
||||
ADD COLUMN description_cn VARCHAR(1024) NOT NULL DEFAULT '' AFTER description_en;
|
||||
|
||||
-- 回填品牌名:拆拉丁 / 中文
|
||||
UPDATE brand SET
|
||||
name_en = CASE
|
||||
WHEN TRIM(REGEXP_REPLACE(name, '[一-鿿]', '')) <> '' THEN TRIM(REGEXP_REPLACE(name, '[一-鿿]', ''))
|
||||
WHEN show_name LIKE '%/%' THEN TRIM(SUBSTRING_INDEX(show_name, '/', 1))
|
||||
ELSE ''
|
||||
END,
|
||||
name_cn = CASE
|
||||
WHEN show_name LIKE '%/%' AND SUBSTRING_INDEX(show_name, '/', -1) REGEXP '[一-鿿]'
|
||||
THEN TRIM(SUBSTRING_INDEX(show_name, '/', -1))
|
||||
WHEN show_name REGEXP '[一-鿿]' THEN TRIM(show_name)
|
||||
WHEN name REGEXP '[一-鿿]' THEN TRIM(name)
|
||||
ELSE ''
|
||||
END;
|
||||
|
||||
-- 回填走秀:当前 title / description 全为英文,直接进 *_en
|
||||
UPDATE brand_runway SET
|
||||
title_en = title,
|
||||
description_en = description,
|
||||
title_cn = '',
|
||||
description_cn = '';
|
||||
8
scripts/sql/003_drop_brand_alias.sql
Normal file
8
scripts/sql/003_drop_brand_alias.sql
Normal file
@ -0,0 +1,8 @@
|
||||
-- 003_drop_brand_alias.sql
|
||||
-- 删除 brand_alias 孤儿表。
|
||||
-- 背景:i18n 方案 A 落地后,brand.name_cn / name_en 已成为品牌名权威来源;
|
||||
-- brand_alias 全代码库零引用,且 99.9% 中文记录与 brand.name_cn 完全重复,
|
||||
-- 保留它只会成为名字的第二来源、后期悄然分叉。故删除。
|
||||
-- 注意:本文件为审阅底稿,实际执行由 scripts/i18n/drop_alias/main.go 驱动(无 mysql 客户端环境)。
|
||||
|
||||
DROP TABLE IF EXISTS brand_alias;
|
||||
Reference in New Issue
Block a user