feat(deploy): production deployment setup and fixes

- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma
  binaryTargets for openssl 3.0
- nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal
  cron, http->https redirect
- prisma: add origin_goods.delisted migration, sync missing schema
  (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz
- api: CORS wildcard reflection, helmet CORP cross-origin, price
  backfill in persistProductDetail, categoryIcon ancestor fallback,
  mediaByColor per-color gallery in public goods detail
- admin: /admin base path (vite + router)
- import-data.mjs: udt_name casting, serial sequence advance fix
This commit is contained in:
yeuimu
2026-08-26 14:23:09 +08:00
parent be0b90e68f
commit 6c61a4e871
982 changed files with 74156 additions and 179393 deletions
+172 -172
View File
@@ -1,173 +1,173 @@
-- 1. 原商品表(外部来源商品)
CREATE TABLE origin_goods (
origin_good_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sds_good_id TEXT NOT NULL, -- SDS商品ID,假设为外部系统标识
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE origin_goods IS '原商品表,记录外部来源商品';
COMMENT ON COLUMN origin_goods.origin_good_id IS '原商品ID(主键)';
COMMENT ON COLUMN origin_goods.sds_good_id IS 'SDS商品ID';
-- 2. 国家表
CREATE TABLE countries (
country_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
country_name TEXT NOT NULL UNIQUE, -- 国家名称唯一
country_icon TEXT, -- 图标URL或路径
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE countries IS '国家表';
COMMENT ON COLUMN countries.country_id IS '国家ID(主键)';
COMMENT ON COLUMN countries.country_name IS '国家名称';
COMMENT ON COLUMN countries.country_icon IS '国家图标';
-- 3. 品类表(支持层级)
CREATE TABLE categories (
category_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
parent_category_id BIGINT REFERENCES categories(category_id) ON DELETE RESTRICT,
category_name TEXT NOT NULL,
category_icon TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE categories IS '品类表,支持自关联层级';
COMMENT ON COLUMN categories.category_id IS '品类ID(主键)';
COMMENT ON COLUMN categories.parent_category_id IS '父品类IDNULL表示顶级品类';
COMMENT ON COLUMN categories.category_name IS '品类名称';
COMMENT ON COLUMN categories.category_icon IS '品类图标';
-- 索引:外键列
CREATE INDEX idx_categories_parent_id ON categories(parent_category_id);
-- 4. 标签表
CREATE TABLE tags (
tag_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tag_name TEXT NOT NULL UNIQUE,
tag_color TEXT, -- 十六进制颜色码,如 '#FF0000'
timing TEXT, -- 时机(如促销时段)
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE tags IS '标签表';
COMMENT ON COLUMN tags.tag_id IS '标签ID(主键)';
COMMENT ON COLUMN tags.tag_name IS '标签名';
COMMENT ON COLUMN tags.tag_color IS '标签颜色(前端展示用)';
COMMENT ON COLUMN tags.timing IS '时机(例如节日促销、季节限定)';
-- 5. 位置表(展示位置定义)
-- 原设计中的 Indexes 表,为避免与数据库索引关键字混淆,重命名为 positions
CREATE TABLE positions (
position_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
index_val INTEGER NOT NULL, -- 位置排序值或权重
country_id BIGINT REFERENCES countries(country_id) ON DELETE CASCADE,
category_id BIGINT REFERENCES categories(category_id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE positions IS '展示位置表,定义商品在特定国家/品类下的展示排序';
COMMENT ON COLUMN positions.position_id IS '位置ID(主键)';
COMMENT ON COLUMN positions.index_val IS '位置值(如排序序号、优先级权重)';
COMMENT ON COLUMN positions.country_id IS '关联的国家IDNULL表示不限国家';
COMMENT ON COLUMN positions.category_id IS '关联的品类IDNULL表示不限品类';
-- 索引:外键列
CREATE INDEX idx_positions_country_id ON positions(country_id);
CREATE INDEX idx_positions_category_id ON positions(category_id);
-- 复合索引用于快速查询某个国家+品类下的位置列表
CREATE INDEX idx_positions_country_category ON positions(country_id, category_id);
-- 6. 商品表(核心表)
CREATE TABLE goods (
good_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
origin_good_id BIGINT REFERENCES origin_goods(origin_good_id) ON DELETE RESTRICT,
country_id BIGINT NOT NULL REFERENCES countries(country_id) ON DELETE RESTRICT,
category_id BIGINT NOT NULL REFERENCES categories(category_id) ON DELETE RESTRICT,
tag_id BIGINT REFERENCES tags(tag_id) ON DELETE SET NULL,
position_id BIGINT REFERENCES positions(position_id) ON DELETE SET NULL,
good_name TEXT NOT NULL,
good_priority INTEGER NOT NULL DEFAULT 0 CHECK (good_priority >= 0), -- 优先级,数值越高越靠前
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE goods IS '商品主表';
COMMENT ON COLUMN goods.good_id IS '商品ID(主键)';
COMMENT ON COLUMN goods.origin_good_id IS '原商品ID(关联外部来源)';
COMMENT ON COLUMN goods.country_id IS '国家ID(必填)';
COMMENT ON COLUMN goods.category_id IS '品类ID(必填)';
COMMENT ON COLUMN goods.tag_id IS '标签ID(可为空)';
COMMENT ON COLUMN goods.position_id IS '展示位置ID(可为空)';
COMMENT ON COLUMN goods.good_name IS '商品名称';
COMMENT ON COLUMN goods.good_priority IS '商品优先级(全局排序,越大越靠前)';
-- 外键索引(必须手动创建)
CREATE INDEX idx_goods_origin_good_id ON goods(origin_good_id);
CREATE INDEX idx_goods_country_id ON goods(country_id);
CREATE INDEX idx_goods_category_id ON goods(category_id);
CREATE INDEX idx_goods_tag_id ON goods(tag_id);
CREATE INDEX idx_goods_position_id ON goods(position_id);
-- 常用查询索引
CREATE INDEX idx_goods_priority ON goods(good_priority DESC); -- 按优先级排序
CREATE INDEX idx_goods_country_priority ON goods(country_id, good_priority DESC); -- 按国家+优先级
CREATE INDEX idx_goods_category_country ON goods(category_id, country_id); -- 品类+国家过滤
-- 可选:部分索引,仅索引活跃商品(如果有软删除或状态字段)
-- 当前设计无状态字段,暂不添加
-- 7. (可选)商品与标签的多对多关联表
-- 原设计中 goods 表仅有单一 tag_id,限制了多标签场景。
-- 若业务需要多标签,建议增加关联表,并移除 goods.tag_id 字段。
-- 以下为扩展设计(可根据实际需求决定是否启用):
/*
CREATE TABLE goods_tags (
goods_id BIGINT NOT NULL REFERENCES goods(good_id) ON DELETE CASCADE,
tag_id BIGINT NOT NULL REFERENCES tags(tag_id) ON DELETE CASCADE,
PRIMARY KEY (goods_id, tag_id)
);
CREATE INDEX idx_goods_tags_tag_id ON goods_tags(tag_id);
*/
-- 注意:若使用 goods_tags 关联表,则应删除 goods 表中的 tag_id 列。
-- 保留原单标签设计还是采用多标签,请根据业务需求选择。
-- 8. 自动更新 updated_at 的触发器函数(可选)
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 为每个表应用自动更新 updated_at 的触发器
CREATE TRIGGER trigger_origin_goods_updated_at
BEFORE UPDATE ON origin_goods
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_countries_updated_at
BEFORE UPDATE ON countries
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_categories_updated_at
BEFORE UPDATE ON categories
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_tags_updated_at
BEFORE UPDATE ON tags
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_positions_updated_at
BEFORE UPDATE ON positions
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_goods_updated_at
BEFORE UPDATE ON goods
-- 1. 原商品表(外部来源商品)
CREATE TABLE origin_goods (
origin_good_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sds_good_id TEXT NOT NULL, -- SDS商品ID,假设为外部系统标识
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE origin_goods IS '原商品表,记录外部来源商品';
COMMENT ON COLUMN origin_goods.origin_good_id IS '原商品ID(主键)';
COMMENT ON COLUMN origin_goods.sds_good_id IS 'SDS商品ID';
-- 2. 国家表
CREATE TABLE countries (
country_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
country_name TEXT NOT NULL UNIQUE, -- 国家名称唯一
country_icon TEXT, -- 图标URL或路径
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE countries IS '国家表';
COMMENT ON COLUMN countries.country_id IS '国家ID(主键)';
COMMENT ON COLUMN countries.country_name IS '国家名称';
COMMENT ON COLUMN countries.country_icon IS '国家图标';
-- 3. 品类表(支持层级)
CREATE TABLE categories (
category_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
parent_category_id BIGINT REFERENCES categories(category_id) ON DELETE RESTRICT,
category_name TEXT NOT NULL,
category_icon TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE categories IS '品类表,支持自关联层级';
COMMENT ON COLUMN categories.category_id IS '品类ID(主键)';
COMMENT ON COLUMN categories.parent_category_id IS '父品类IDNULL表示顶级品类';
COMMENT ON COLUMN categories.category_name IS '品类名称';
COMMENT ON COLUMN categories.category_icon IS '品类图标';
-- 索引:外键列
CREATE INDEX idx_categories_parent_id ON categories(parent_category_id);
-- 4. 标签表
CREATE TABLE tags (
tag_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tag_name TEXT NOT NULL UNIQUE,
tag_color TEXT, -- 十六进制颜色码,如 '#FF0000'
timing TEXT, -- 时机(如促销时段)
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE tags IS '标签表';
COMMENT ON COLUMN tags.tag_id IS '标签ID(主键)';
COMMENT ON COLUMN tags.tag_name IS '标签名';
COMMENT ON COLUMN tags.tag_color IS '标签颜色(前端展示用)';
COMMENT ON COLUMN tags.timing IS '时机(例如节日促销、季节限定)';
-- 5. 位置表(展示位置定义)
-- 原设计中的 Indexes 表,为避免与数据库索引关键字混淆,重命名为 positions
CREATE TABLE positions (
position_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
index_val INTEGER NOT NULL, -- 位置排序值或权重
country_id BIGINT REFERENCES countries(country_id) ON DELETE CASCADE,
category_id BIGINT REFERENCES categories(category_id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE positions IS '展示位置表,定义商品在特定国家/品类下的展示排序';
COMMENT ON COLUMN positions.position_id IS '位置ID(主键)';
COMMENT ON COLUMN positions.index_val IS '位置值(如排序序号、优先级权重)';
COMMENT ON COLUMN positions.country_id IS '关联的国家IDNULL表示不限国家';
COMMENT ON COLUMN positions.category_id IS '关联的品类IDNULL表示不限品类';
-- 索引:外键列
CREATE INDEX idx_positions_country_id ON positions(country_id);
CREATE INDEX idx_positions_category_id ON positions(category_id);
-- 复合索引用于快速查询某个国家+品类下的位置列表
CREATE INDEX idx_positions_country_category ON positions(country_id, category_id);
-- 6. 商品表(核心表)
CREATE TABLE goods (
good_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
origin_good_id BIGINT REFERENCES origin_goods(origin_good_id) ON DELETE RESTRICT,
country_id BIGINT NOT NULL REFERENCES countries(country_id) ON DELETE RESTRICT,
category_id BIGINT NOT NULL REFERENCES categories(category_id) ON DELETE RESTRICT,
tag_id BIGINT REFERENCES tags(tag_id) ON DELETE SET NULL,
position_id BIGINT REFERENCES positions(position_id) ON DELETE SET NULL,
good_name TEXT NOT NULL,
good_priority INTEGER NOT NULL DEFAULT 0 CHECK (good_priority >= 0), -- 优先级,数值越高越靠前
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE goods IS '商品主表';
COMMENT ON COLUMN goods.good_id IS '商品ID(主键)';
COMMENT ON COLUMN goods.origin_good_id IS '原商品ID(关联外部来源)';
COMMENT ON COLUMN goods.country_id IS '国家ID(必填)';
COMMENT ON COLUMN goods.category_id IS '品类ID(必填)';
COMMENT ON COLUMN goods.tag_id IS '标签ID(可为空)';
COMMENT ON COLUMN goods.position_id IS '展示位置ID(可为空)';
COMMENT ON COLUMN goods.good_name IS '商品名称';
COMMENT ON COLUMN goods.good_priority IS '商品优先级(全局排序,越大越靠前)';
-- 外键索引(必须手动创建)
CREATE INDEX idx_goods_origin_good_id ON goods(origin_good_id);
CREATE INDEX idx_goods_country_id ON goods(country_id);
CREATE INDEX idx_goods_category_id ON goods(category_id);
CREATE INDEX idx_goods_tag_id ON goods(tag_id);
CREATE INDEX idx_goods_position_id ON goods(position_id);
-- 常用查询索引
CREATE INDEX idx_goods_priority ON goods(good_priority DESC); -- 按优先级排序
CREATE INDEX idx_goods_country_priority ON goods(country_id, good_priority DESC); -- 按国家+优先级
CREATE INDEX idx_goods_category_country ON goods(category_id, country_id); -- 品类+国家过滤
-- 可选:部分索引,仅索引活跃商品(如果有软删除或状态字段)
-- 当前设计无状态字段,暂不添加
-- 7. (可选)商品与标签的多对多关联表
-- 原设计中 goods 表仅有单一 tag_id,限制了多标签场景。
-- 若业务需要多标签,建议增加关联表,并移除 goods.tag_id 字段。
-- 以下为扩展设计(可根据实际需求决定是否启用):
/*
CREATE TABLE goods_tags (
goods_id BIGINT NOT NULL REFERENCES goods(good_id) ON DELETE CASCADE,
tag_id BIGINT NOT NULL REFERENCES tags(tag_id) ON DELETE CASCADE,
PRIMARY KEY (goods_id, tag_id)
);
CREATE INDEX idx_goods_tags_tag_id ON goods_tags(tag_id);
*/
-- 注意:若使用 goods_tags 关联表,则应删除 goods 表中的 tag_id 列。
-- 保留原单标签设计还是采用多标签,请根据业务需求选择。
-- 8. 自动更新 updated_at 的触发器函数(可选)
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 为每个表应用自动更新 updated_at 的触发器
CREATE TRIGGER trigger_origin_goods_updated_at
BEFORE UPDATE ON origin_goods
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_countries_updated_at
BEFORE UPDATE ON countries
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_categories_updated_at
BEFORE UPDATE ON categories
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_tags_updated_at
BEFORE UPDATE ON tags
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_positions_updated_at
BEFORE UPDATE ON positions
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_goods_updated_at
BEFORE UPDATE ON goods
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();