This commit is contained in:
toom1996
2026-09-17 22:51:00 +08:00
parent 766c691eb3
commit f7ae917603
33 changed files with 8181 additions and 3094 deletions

View File

@ -3,14 +3,14 @@ package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
const dsn = "root:root@tcp(127.0.0.1:3306)/db?charset=utf8mb4&parseTime=True&loc=Local"
const dsn = "postgres://fashion:fashion_dev_2026@127.0.0.1:5432/fashion?sslmode=disable"
func main() {
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}

View File

@ -5,14 +5,14 @@ package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
const dsn = "root:root@tcp(127.0.0.1:3306)/db?charset=utf8mb4&parseTime=True&loc=Local"
const dsn = "postgres://fashion:fashion_dev_2026@127.0.0.1:5432/fashion?sslmode=disable"
func main() {
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}

View File

@ -1,7 +1,7 @@
// Command seed_users 预置内部账号(运营/编辑用,不开放公开注册)。
//
// 项目已移除 AutoMigrate,表结构由 scripts/sql/005_create_users.sql 托管;
// 本脚本用 CREATE TABLE IF NOT EXISTS 兜底建表,再 upsert 一个内部账号。
// 项目表结构由 GORM AutoMigrate 托管(服务启动时创建);
// 本脚本仅在独立运行、尚未跑过服务时兜底建表,再 upsert 一个内部账号。
//
// 用法:
// go run ./scripts/seed_users
@ -43,19 +43,19 @@ func main() {
}
}()
// 1. 兜底建表(与 scripts/sql/005_create_users.sql 完全一致)
// 1. 兜底建表(与 User 模型保持一致;正式结构由服务启动时 AutoMigrate 创建,此处仅作独立运行兜底)
createSQL := `CREATE TABLE IF NOT EXISTS users (
id int unsigned NOT NULL AUTO_INCREMENT,
created_at int unsigned NOT NULL DEFAULT 0,
updated_at int unsigned NOT NULL DEFAULT 0,
id bigserial PRIMARY KEY,
created_at bigint NOT NULL DEFAULT 0,
updated_at bigint NOT NULL DEFAULT 0,
username varchar(64) NOT NULL,
email varchar(191) NOT NULL,
password_hash varchar(255) NOT NULL DEFAULT '',
is_deleted tinyint unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`
is_deleted smallint NOT NULL DEFAULT 0,
tier varchar(16) NOT NULL DEFAULT 'free',
UNIQUE (username),
UNIQUE (email)
);`
if e := db.Exec(createSQL).Error; e != nil {
log.Fatalf("✗ 建表失败: %v", e)
}

View File

@ -1,79 +0,0 @@
-- ============================================================================
-- 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 = '';
-- ============================================================================
-- 第 5 步(依赖「分类字段回填迁移」之后执行):title_cn 规则生成
--
-- 前提:collection_type / season / year 已由历史迁移解析回填(rtw/menswear/
-- couture/resort/pre_fall × spring/fall/空)。title 本质是「季节/品类描述」,
-- 品牌名在 JOIN 的 brand_name 里单独给,因此 title_cn 可纯规则生成,
-- 完全不依赖外部翻译服务,结果稳定可重跑。
--
-- 映射:resort→「{year} 度假系列」、pre_fall→「{year} 早秋系列」、
-- rtw→「{year} {春夏|秋冬}成衣」、menswear→「{year} {春夏|秋冬}男装」、
-- couture→「{year} {春夏|秋冬}高定」。
--
-- 执行器:scripts/backfill_title_cn/main.go(幂等,整表覆盖重跑无害)。
-- 该步骤与上方 i18n 加列/回填相互独立,可单独重跑。
-- ============================================================================
UPDATE brand_runway
SET title_cn = CASE
WHEN collection_type = 'resort' THEN CONCAT(year, ' 度假系列')
WHEN collection_type = 'pre_fall' THEN CONCAT(year, ' 早秋系列')
WHEN collection_type = 'rtw' AND season = 'spring' THEN CONCAT(year, ' 春夏成衣')
WHEN collection_type = 'rtw' AND season = 'fall' THEN CONCAT(year, ' 秋冬成衣')
WHEN collection_type = 'rtw' THEN CONCAT(year, ' 成衣')
WHEN collection_type = 'menswear' AND season = 'spring' THEN CONCAT(year, ' 春夏男装')
WHEN collection_type = 'menswear' AND season = 'fall' THEN CONCAT(year, ' 秋冬男装')
WHEN collection_type = 'couture' AND season = 'spring' THEN CONCAT(year, ' 春夏高定')
WHEN collection_type = 'couture' AND season = 'fall' THEN CONCAT(year, ' 秋冬高定')
ELSE CONCAT(year, ' 系列')
END
WHERE is_deleted = 0;

View File

@ -1,8 +0,0 @@
-- 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;

View File

@ -1,4 +0,0 @@
-- 街拍地区筛选:street_snap 新增 city 列(从 title 第一段 \t 解析回填)
-- 对应执行器 scripts/backfill_city/main.go(幂等,可重跑)
-- city 存「地点」原文(如 Paris / 巴黎 / New York),'' 表示未归类,不参与地区筛选
ALTER TABLE street_snap ADD COLUMN city VARCHAR(128) NOT NULL DEFAULT '' AFTER year;

View File

@ -1,14 +0,0 @@
-- 账号体系用户表(内部运营/编辑账号,不开放公开注册)。
-- 与 scripts/seed_users/main.go 中内置的建表语句保持一致。
CREATE TABLE IF NOT EXISTS `users` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`created_at` int unsigned NOT NULL DEFAULT 0,
`updated_at` int unsigned NOT NULL DEFAULT 0,
`username` varchar(64) NOT NULL,
`email` varchar(191) NOT NULL,
`password_hash` varchar(255) NOT NULL DEFAULT '',
`is_deleted` tinyint unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,15 +0,0 @@
-- 006: refresh_tokens —— 刷新令牌表(双令牌续期 + 踢下线的服务端状态)
-- 与项目约定一致:表结构由 SQL 托管,不依赖 GORM AutoMigrate。
CREATE TABLE IF NOT EXISTS refresh_tokens (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id INT UNSIGNED NOT NULL,
token_hash VARCHAR(64) NOT NULL,
expires_at INT UNSIGNED NOT NULL DEFAULT 0,
revoked TINYINT UNSIGNED NOT NULL DEFAULT 0,
user_agent VARCHAR(255) NOT NULL DEFAULT '',
ip VARCHAR(64) NOT NULL DEFAULT '',
created_at INT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY uk_token_hash (token_hash),
KEY idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,17 +0,0 @@
-- 007: favorites —— 用户收藏(服务端持久化,按用户隔离)
-- 与项目约定一致:表结构由 SQL 托管,不依赖 GORM AutoMigrate。
-- target_uid 存前端卡片的 it.id(编码串),与前端直接对齐;title/cover/brand 为收藏时快照。
CREATE TABLE IF NOT EXISTS `favorites` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`created_at` int unsigned NOT NULL DEFAULT 0,
`user_id` int unsigned NOT NULL,
`target_type` varchar(16) NOT NULL DEFAULT '',
`target_uid` varchar(64) NOT NULL DEFAULT '',
`title` varchar(255) NOT NULL DEFAULT '',
`cover` varchar(512) NOT NULL DEFAULT '',
`brand` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_target` (`user_id`, `target_uid`),
KEY `idx_user` (`user_id`),
KEY `idx_user_created` (`user_id`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -1,21 +0,0 @@
-- 008: histories —— 用户浏览历史(服务端持久化,按用户隔离,跨设备一致)
-- 与 favorites 同理:表结构由 SQL 托管,不依赖 GORM AutoMigrate。
--
-- 2026-09-02 收敛为「文章级」浏览历史:
-- 1) 触发点:点开某篇走秀/街拍详情(/item/xxx)时记一条;进入列表页不再记。
-- 2) 仅存 (user_id, target_uid, viewed_at):target_uid 为带类型前缀的对外编码串
-- (runway r=xxx / street s=xxx,见后端 hashid.EncodeTyped),前缀本身携带类型,
-- 无需 kind / target_type 列。
-- 3) title/cover/brand 快照已移除:账户页展示时按 target_uid 回查公开详情接口,
-- 保持表最简、展示数据新鲜。
-- 唯一键 (user_id, target_uid):同一篇文章反复打开只 upsert 刷新 viewed_at,绝不新增行。
-- 每用户上限 HISTORY_CAP(应用层维护,默认 2000),超出 FIFO 删最旧。
CREATE TABLE IF NOT EXISTS `histories` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`viewed_at` int unsigned NOT NULL DEFAULT 0,
`user_id` int unsigned NOT NULL,
`target_uid` varchar(64) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_target` (`user_id`, `target_uid`),
KEY `idx_user_viewed` (`user_id`, `viewed_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;