reset backend
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS users(
|
||||
id bigserial PRIMARY KEY,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
deleted_at timestamptz,
|
||||
username varchar(255) NOT NULL UNIQUE,
|
||||
password varchar(255) NOT NULL,
|
||||
roles text[] NOT NULL DEFAULT ARRAY['user'],
|
||||
status varchar(50) NOT NULL DEFAULT 'active',
|
||||
metas jsonb NOT NULL DEFAULT '{}',
|
||||
balance bigint NOT NULL DEFAULT 0,
|
||||
balance_frozen bigint NOT NULL DEFAULT 0,
|
||||
verified_at timestamptz
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN users.balance IS '全局可用余额:分/最小货币单位;用户在所有已加入租户内共享该余额;默认 0';
|
||||
COMMENT ON COLUMN users.balance_frozen IS '全局冻结余额:分/最小货币单位;用于下单冻结等;默认 0';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_users_balance ON users(balance);
|
||||
CREATE INDEX IF NOT EXISTS ix_users_balance_frozen ON users(balance_frozen);
|
||||
|
||||
-- +goose StatementEnd
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
-- +goose StatementEnd
|
||||
@@ -1,29 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS tenants(
|
||||
id bigserial PRIMARY KEY,
|
||||
user_id bigint NOT NULL,
|
||||
code varchar(64) NOT NULL,
|
||||
uuid uuid NOT NULL,
|
||||
name varchar(128) NOT NULL DEFAULT '',
|
||||
status varchar(64) NOT NULL DEFAULT '',
|
||||
config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
expired_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_tenants_code_lower ON tenants(lower(code));
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_tenants_uuid ON tenants(uuid);
|
||||
|
||||
-- +goose StatementEnd
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ux_tenants_uuid;
|
||||
|
||||
DROP INDEX IF EXISTS ux_tenants_code_lower;
|
||||
|
||||
DROP TABLE IF EXISTS tenants;
|
||||
|
||||
-- +goose StatementEnd
|
||||
@@ -1,19 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS tenant_users(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
role TEXT[] NOT NULL DEFAULT ARRAY['member'],
|
||||
status varchar(50) NOT NULL DEFAULT 'active',
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (tenant_id, user_id)
|
||||
);
|
||||
|
||||
-- +goose StatementEnd
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS tenant_users;
|
||||
|
||||
-- +goose StatementEnd
|
||||
@@ -1,190 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS media_assets(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
type varchar(32) NOT NULL DEFAULT 'video',
|
||||
status varchar(32) NOT NULL DEFAULT 'uploaded',
|
||||
provider varchar(64) NOT NULL DEFAULT '',
|
||||
bucket varchar(128) NOT NULL DEFAULT '',
|
||||
object_key varchar(512) NOT NULL DEFAULT '',
|
||||
meta jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
deleted_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- media_assets:媒体资源表(视频/音频/图片),用于承载实际文件对象及其处理状态
|
||||
COMMENT ON TABLE media_assets IS '媒体资源:存储对象的抽象(video/audio/image),关联租户与上传用户,记录处理状态与元数据';
|
||||
COMMENT ON COLUMN media_assets.id IS '主键ID:自增;仅用于内部关联';
|
||||
COMMENT ON COLUMN media_assets.tenant_id IS '租户ID:多租户隔离关键字段;所有查询/写入必须限定 tenant_id';
|
||||
COMMENT ON COLUMN media_assets.user_id IS '用户ID:资源上传者;用于审计与权限控制';
|
||||
COMMENT ON COLUMN media_assets.type IS '资源类型:video/audio/image;决定后续处理流程(转码/缩略图/封面等)';
|
||||
COMMENT ON COLUMN media_assets.status IS '处理状态:uploaded/processing/ready/failed/deleted;ready 才可被内容引用对外提供';
|
||||
COMMENT ON COLUMN media_assets.provider IS '存储提供方:例如 s3/minio/oss;便于多存储扩展';
|
||||
COMMENT ON COLUMN media_assets.bucket IS '存储桶:对象所在 bucket;与 provider 组合确定存储定位';
|
||||
COMMENT ON COLUMN media_assets.object_key IS '对象键:对象在 bucket 内的 key;不得暴露可长期复用的直链(通过签名URL/token下发)';
|
||||
COMMENT ON COLUMN media_assets.meta IS '元数据:JSON;包含 hash、duration、width、height、bitrate、codec 等;用于展示与计费/风控';
|
||||
COMMENT ON COLUMN media_assets.deleted_at IS '软删除时间:非空表示已删除;对外接口需过滤';
|
||||
COMMENT ON COLUMN media_assets.created_at IS '创建时间:默认 now();用于审计与排序';
|
||||
COMMENT ON COLUMN media_assets.updated_at IS '更新时间:默认 now();更新状态/元数据时写入';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_media_assets_tenant_id ON media_assets(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_media_assets_tenant_user_id ON media_assets(tenant_id, user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_media_assets_tenant_status ON media_assets(tenant_id, status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS contents(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
title varchar(255) NOT NULL DEFAULT '',
|
||||
description text NOT NULL DEFAULT '',
|
||||
status varchar(32) NOT NULL DEFAULT 'draft',
|
||||
visibility varchar(32) NOT NULL DEFAULT 'tenant_only',
|
||||
preview_seconds int NOT NULL DEFAULT 60,
|
||||
preview_downloadable boolean NOT NULL DEFAULT false,
|
||||
published_at timestamptz,
|
||||
deleted_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- contents:内容表(可发布/可售卖/可试看)
|
||||
COMMENT ON TABLE contents IS '内容:可发布的媒体内容实体,承载标题描述、可见性、试看配置、发布状态等';
|
||||
COMMENT ON COLUMN contents.id IS '主键ID:自增;用于内容引用';
|
||||
COMMENT ON COLUMN contents.tenant_id IS '租户ID:多租户隔离关键字段;所有查询/写入必须限定 tenant_id';
|
||||
COMMENT ON COLUMN contents.user_id IS '用户ID:内容创建者/发布者;用于权限与审计(例如私有内容仅作者可见)';
|
||||
COMMENT ON COLUMN contents.title IS '标题:用于列表展示与搜索;建议限制长度(由业务校验)';
|
||||
COMMENT ON COLUMN contents.description IS '描述:用于详情页展示;可为空字符串';
|
||||
COMMENT ON COLUMN contents.status IS '状态:draft/reviewing/published/unpublished/blocked;published 才对外展示';
|
||||
COMMENT ON COLUMN contents.visibility IS '可见性:public/tenant_only/private;仅控制详情可见,正片资源仍需按价格/权益校验';
|
||||
COMMENT ON COLUMN contents.preview_seconds IS '试看秒数:默认 60;只对 preview 资源生效;必须为正整数';
|
||||
COMMENT ON COLUMN contents.preview_downloadable IS '试看是否允许下载:默认 false;当前策略固定为不允许下载(仅 streaming)';
|
||||
COMMENT ON COLUMN contents.published_at IS '发布时间:首次发布时写入;用于时间窗与排序';
|
||||
COMMENT ON COLUMN contents.deleted_at IS '软删除时间:非空表示已删除;对外接口需过滤';
|
||||
COMMENT ON COLUMN contents.created_at IS '创建时间:默认 now();用于审计与排序';
|
||||
COMMENT ON COLUMN contents.updated_at IS '更新时间:默认 now();编辑内容时写入';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_contents_tenant_id ON contents(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_contents_tenant_user_id ON contents(tenant_id, user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_contents_tenant_status ON contents(tenant_id, status);
|
||||
CREATE INDEX IF NOT EXISTS ix_contents_tenant_visibility ON contents(tenant_id, visibility);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS content_assets(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
content_id bigint NOT NULL,
|
||||
asset_id bigint NOT NULL,
|
||||
role varchar(32) NOT NULL DEFAULT 'main',
|
||||
sort int NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (tenant_id, content_id, asset_id)
|
||||
);
|
||||
|
||||
-- content_assets:内容与媒体资源的关联(区分 main/cover/preview)
|
||||
COMMENT ON TABLE content_assets IS '内容-资源关联:将 media_assets 以角色(main/cover/preview)绑定到 contents,支持排序';
|
||||
COMMENT ON COLUMN content_assets.id IS '主键ID:自增';
|
||||
COMMENT ON COLUMN content_assets.tenant_id IS '租户ID:多租户隔离;必须与 content_id、asset_id 所属租户一致';
|
||||
COMMENT ON COLUMN content_assets.user_id IS '用户ID:操作人/绑定人;用于审计(通常为租户管理员或作者)';
|
||||
COMMENT ON COLUMN content_assets.content_id IS '内容ID:关联 contents.id;用于查询内容下资源列表';
|
||||
COMMENT ON COLUMN content_assets.asset_id IS '资源ID:关联 media_assets.id;用于查询资源归属内容';
|
||||
COMMENT ON COLUMN content_assets.role IS '资源角色:main/cover/preview;preview 必须为独立资源以满足禁下载与防绕过';
|
||||
COMMENT ON COLUMN content_assets.sort IS '排序:同一 role 下的展示顺序,数值越小越靠前';
|
||||
COMMENT ON COLUMN content_assets.created_at IS '创建时间:默认 now();用于审计';
|
||||
COMMENT ON COLUMN content_assets.updated_at IS '更新时间:默认 now();更新 sort/role 时写入';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_content_assets_tenant_content ON content_assets(tenant_id, content_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_content_assets_tenant_asset ON content_assets(tenant_id, asset_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_content_assets_tenant_role ON content_assets(tenant_id, content_id, role);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS content_prices(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
content_id bigint NOT NULL,
|
||||
currency varchar(16) NOT NULL DEFAULT 'CNY',
|
||||
price_amount bigint NOT NULL DEFAULT 0,
|
||||
discount_type varchar(16) NOT NULL DEFAULT 'none',
|
||||
discount_value bigint NOT NULL DEFAULT 0,
|
||||
discount_start_at timestamptz,
|
||||
discount_end_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (tenant_id, content_id)
|
||||
);
|
||||
|
||||
-- content_prices:内容定价与折扣(仅 CNY 分)
|
||||
COMMENT ON TABLE content_prices IS '内容定价:为内容设置价格与折扣(订单需记录成交快照,避免改价影响历史)';
|
||||
COMMENT ON COLUMN content_prices.id IS '主键ID:自增';
|
||||
COMMENT ON COLUMN content_prices.tenant_id IS '租户ID:多租户隔离;与内容归属一致';
|
||||
COMMENT ON COLUMN content_prices.user_id IS '用户ID:设置/更新价格的操作人(通常为 tenant_admin);用于审计';
|
||||
COMMENT ON COLUMN content_prices.content_id IS '内容ID:唯一约束 (tenant_id, content_id);一个内容在一个租户内仅一份定价';
|
||||
COMMENT ON COLUMN content_prices.currency IS '币种:当前固定 CNY;金额单位为分';
|
||||
COMMENT ON COLUMN content_prices.price_amount IS '基础价格:分;0 表示免费(可直接访问正片资源)';
|
||||
COMMENT ON COLUMN content_prices.discount_type IS '折扣类型:none/percent/amount;仅影响下单时成交价,需写入订单快照';
|
||||
COMMENT ON COLUMN content_prices.discount_value IS '折扣值:percent=0-100(按业务校验);amount=分;none 时忽略';
|
||||
COMMENT ON COLUMN content_prices.discount_start_at IS '折扣开始时间:可为空;为空表示立即生效(由业务逻辑解释)';
|
||||
COMMENT ON COLUMN content_prices.discount_end_at IS '折扣结束时间:可为空;为空表示长期有效(由业务逻辑解释)';
|
||||
COMMENT ON COLUMN content_prices.created_at IS '创建时间:默认 now();用于审计';
|
||||
COMMENT ON COLUMN content_prices.updated_at IS '更新时间:默认 now();更新价格/折扣时写入';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_content_prices_tenant_id ON content_prices(tenant_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS content_access(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
content_id bigint NOT NULL,
|
||||
order_id bigint,
|
||||
status varchar(16) NOT NULL DEFAULT 'active',
|
||||
revoked_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (tenant_id, user_id, content_id)
|
||||
);
|
||||
|
||||
-- content_access:购买权益/访问权限(退款后撤销)
|
||||
COMMENT ON TABLE content_access IS '内容权益:记录用户在租户内对内容的访问资格;退款后应立即 revoked';
|
||||
COMMENT ON COLUMN content_access.id IS '主键ID:自增';
|
||||
COMMENT ON COLUMN content_access.tenant_id IS '租户ID:多租户隔离;与内容、用户归属一致';
|
||||
COMMENT ON COLUMN content_access.user_id IS '用户ID:权益所属用户;用于访问校验';
|
||||
COMMENT ON COLUMN content_access.content_id IS '内容ID:权益对应内容;唯一约束 (tenant_id, user_id, content_id)';
|
||||
COMMENT ON COLUMN content_access.order_id IS '订单ID:产生该权益的订单;可为空(例如后台补发/迁移)';
|
||||
COMMENT ON COLUMN content_access.status IS '权益状态:active/revoked/expired;revoked 表示立即失效(例如退款/违规)';
|
||||
COMMENT ON COLUMN content_access.revoked_at IS '撤销时间:当 status=revoked 时写入;用于审计与追责';
|
||||
COMMENT ON COLUMN content_access.created_at IS '创建时间:默认 now();用于审计';
|
||||
COMMENT ON COLUMN content_access.updated_at IS '更新时间:默认 now();更新 status 时写入';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_content_access_tenant_user ON content_access(tenant_id, user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_content_access_tenant_content ON content_access(tenant_id, content_id);
|
||||
|
||||
-- +goose StatementEnd
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ix_content_access_tenant_content;
|
||||
DROP INDEX IF EXISTS ix_content_access_tenant_user;
|
||||
DROP TABLE IF EXISTS content_access;
|
||||
|
||||
DROP INDEX IF EXISTS ix_content_prices_tenant_id;
|
||||
DROP TABLE IF EXISTS content_prices;
|
||||
|
||||
DROP INDEX IF EXISTS ix_content_assets_tenant_role;
|
||||
DROP INDEX IF EXISTS ix_content_assets_tenant_asset;
|
||||
DROP INDEX IF EXISTS ix_content_assets_tenant_content;
|
||||
DROP TABLE IF EXISTS content_assets;
|
||||
|
||||
DROP INDEX IF EXISTS ix_contents_tenant_visibility;
|
||||
DROP INDEX IF EXISTS ix_contents_tenant_status;
|
||||
DROP INDEX IF EXISTS ix_contents_tenant_user_id;
|
||||
DROP INDEX IF EXISTS ix_contents_tenant_id;
|
||||
DROP TABLE IF EXISTS contents;
|
||||
|
||||
DROP INDEX IF EXISTS ix_media_assets_tenant_status;
|
||||
DROP INDEX IF EXISTS ix_media_assets_tenant_user_id;
|
||||
DROP INDEX IF EXISTS ix_media_assets_tenant_id;
|
||||
DROP TABLE IF EXISTS media_assets;
|
||||
|
||||
-- +goose StatementEnd
|
||||
@@ -1,138 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS orders(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
type varchar(32) NOT NULL DEFAULT 'content_purchase',
|
||||
status varchar(32) NOT NULL DEFAULT 'created',
|
||||
currency varchar(16) NOT NULL DEFAULT 'CNY',
|
||||
amount_original bigint NOT NULL DEFAULT 0,
|
||||
amount_discount bigint NOT NULL DEFAULT 0,
|
||||
amount_paid bigint NOT NULL DEFAULT 0,
|
||||
snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
idempotency_key varchar(128) NOT NULL DEFAULT '',
|
||||
paid_at timestamptz,
|
||||
refunded_at timestamptz,
|
||||
refund_forced boolean NOT NULL DEFAULT false,
|
||||
refund_operator_user_id bigint,
|
||||
refund_reason varchar(255) NOT NULL DEFAULT '',
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- orders:订单主表(租户内购买等业务单据)
|
||||
COMMENT ON TABLE orders IS '订单:租户内的业务交易单据;记录成交金额快照、状态流转与退款信息;所有查询/写入必须限定 tenant_id';
|
||||
COMMENT ON COLUMN orders.id IS '主键ID:自增;用于关联订单明细、账本流水、权益等';
|
||||
COMMENT ON COLUMN orders.tenant_id IS '租户ID:多租户隔离关键字段;所有查询/写入必须限定 tenant_id';
|
||||
COMMENT ON COLUMN orders.user_id IS '用户ID:下单用户(buyer);余额扣款与权益归属以该 user_id 为准';
|
||||
COMMENT ON COLUMN orders.type IS '订单类型:content_purchase(购买内容)等;当前默认 content_purchase';
|
||||
COMMENT ON COLUMN orders.status IS '订单状态:created/paid/refunding/refunded/canceled/failed;状态变更需与账本/权益保持一致';
|
||||
COMMENT ON COLUMN orders.currency IS '币种:当前固定 CNY;金额单位为分';
|
||||
COMMENT ON COLUMN orders.amount_original IS '原价金额:分;未折扣前金额(用于展示与对账)';
|
||||
COMMENT ON COLUMN orders.amount_discount IS '优惠金额:分;amount_paid = amount_original - amount_discount(下单时快照)';
|
||||
COMMENT ON COLUMN orders.amount_paid IS '实付金额:分;从租户内余额扣款的金额(下单时快照)';
|
||||
COMMENT ON COLUMN orders.snapshot IS '订单快照:JSON;建议包含 content 标题/定价/折扣、请求来源等,避免改价影响历史展示';
|
||||
COMMENT ON COLUMN orders.idempotency_key IS '幂等键:同一租户同一用户同一业务请求可用;用于防重复下单/重复扣款(建议由客户端生成)';
|
||||
COMMENT ON COLUMN orders.paid_at IS '支付/扣款完成时间:余额支付在 debit_purchase 成功后写入';
|
||||
COMMENT ON COLUMN orders.refunded_at IS '退款完成时间:退款落账成功后写入';
|
||||
COMMENT ON COLUMN orders.refund_forced IS '是否强制退款:true 表示租户管理侧绕过时间窗执行退款(需审计)';
|
||||
COMMENT ON COLUMN orders.refund_operator_user_id IS '退款操作人用户ID:租户管理员/系统;用于审计与追责';
|
||||
COMMENT ON COLUMN orders.refund_reason IS '退款原因:后台/用户发起退款的原因说明;用于审计';
|
||||
COMMENT ON COLUMN orders.created_at IS '创建时间:默认 now();用于审计与排序';
|
||||
COMMENT ON COLUMN orders.updated_at IS '更新时间:默认 now();状态变更/退款写入时更新';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_orders_tenant_user ON orders(tenant_id, user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_orders_tenant_status ON orders(tenant_id, status);
|
||||
CREATE INDEX IF NOT EXISTS ix_orders_tenant_paid_at ON orders(tenant_id, paid_at);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_orders_tenant_idempotency_key ON orders(tenant_id, user_id, idempotency_key) WHERE idempotency_key <> '';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS order_items(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
order_id bigint NOT NULL,
|
||||
content_id bigint NOT NULL,
|
||||
content_user_id bigint NOT NULL DEFAULT 0,
|
||||
amount_paid bigint NOT NULL DEFAULT 0,
|
||||
snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (tenant_id, order_id, content_id)
|
||||
);
|
||||
|
||||
-- order_items:订单明细(购买内容通常一单一内容,但保留扩展能力)
|
||||
COMMENT ON TABLE order_items IS '订单明细:记录订单购买的具体内容及金额/快照;支持后续扩展为一单多内容';
|
||||
COMMENT ON COLUMN order_items.id IS '主键ID:自增';
|
||||
COMMENT ON COLUMN order_items.tenant_id IS '租户ID:多租户隔离关键字段;必须与 orders.tenant_id 一致';
|
||||
COMMENT ON COLUMN order_items.user_id IS '用户ID:下单用户(buyer);冗余字段用于查询加速与审计';
|
||||
COMMENT ON COLUMN order_items.order_id IS '订单ID:关联 orders.id;用于聚合订单明细';
|
||||
COMMENT ON COLUMN order_items.content_id IS '内容ID:关联 contents.id;用于生成/撤销 content_access';
|
||||
COMMENT ON COLUMN order_items.content_user_id IS '内容作者用户ID:用于后续分成/对账扩展;当前可为 0 或写入内容创建者';
|
||||
COMMENT ON COLUMN order_items.amount_paid IS '该行实付金额:分;通常等于订单 amount_paid(单内容场景)';
|
||||
COMMENT ON COLUMN order_items.snapshot IS '内容快照:JSON;建议包含 title/price/discount 等,用于历史展示与审计';
|
||||
COMMENT ON COLUMN order_items.created_at IS '创建时间:默认 now()';
|
||||
COMMENT ON COLUMN order_items.updated_at IS '更新时间:默认 now()';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_order_items_tenant_order ON order_items(tenant_id, order_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_order_items_tenant_content ON order_items(tenant_id, content_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tenant_ledgers(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
order_id bigint,
|
||||
type varchar(32) NOT NULL,
|
||||
amount bigint NOT NULL DEFAULT 0,
|
||||
balance_before bigint NOT NULL DEFAULT 0,
|
||||
balance_after bigint NOT NULL DEFAULT 0,
|
||||
frozen_before bigint NOT NULL DEFAULT 0,
|
||||
frozen_after bigint NOT NULL DEFAULT 0,
|
||||
idempotency_key varchar(128) NOT NULL DEFAULT '',
|
||||
remark varchar(255) NOT NULL DEFAULT '',
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- tenant_ledgers:租户内余额账本流水(必须可审计、可幂等)
|
||||
COMMENT ON TABLE tenant_ledgers IS '账本流水:记录租户内用户余额的每一次变化(冻结/扣款/退款/调账等);用于审计与对账回放';
|
||||
COMMENT ON COLUMN tenant_ledgers.id IS '主键ID:自增';
|
||||
COMMENT ON COLUMN tenant_ledgers.tenant_id IS '租户ID:多租户隔离关键字段;必须与 tenant_users.tenant_id 一致';
|
||||
COMMENT ON COLUMN tenant_ledgers.user_id IS '用户ID:余额账户归属用户;对应 tenant_users.user_id';
|
||||
COMMENT ON COLUMN tenant_ledgers.order_id IS '关联订单ID:购买/退款类流水应关联 orders.id;非订单类可为空';
|
||||
COMMENT ON COLUMN tenant_ledgers.type IS '流水类型:debit_purchase/credit_refund/freeze/unfreeze/adjustment;不同类型决定余额/冻结余额的变更方向';
|
||||
COMMENT ON COLUMN tenant_ledgers.amount IS '流水金额:分/最小货币单位;通常为正数,方向由 type 决定(由业务层约束)';
|
||||
COMMENT ON COLUMN tenant_ledgers.balance_before IS '变更前可用余额:用于审计与对账回放';
|
||||
COMMENT ON COLUMN tenant_ledgers.balance_after IS '变更后可用余额:用于审计与对账回放';
|
||||
COMMENT ON COLUMN tenant_ledgers.frozen_before IS '变更前冻结余额:用于审计与对账回放';
|
||||
COMMENT ON COLUMN tenant_ledgers.frozen_after IS '变更后冻结余额:用于审计与对账回放';
|
||||
COMMENT ON COLUMN tenant_ledgers.idempotency_key IS '幂等键:同一租户同一用户同一业务操作固定;用于防止重复落账(建议由业务层生成)';
|
||||
COMMENT ON COLUMN tenant_ledgers.remark IS '备注:业务说明/后台操作原因等;用于审计';
|
||||
COMMENT ON COLUMN tenant_ledgers.created_at IS '创建时间:默认 now()';
|
||||
COMMENT ON COLUMN tenant_ledgers.updated_at IS '更新时间:默认 now()';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_tenant_ledgers_tenant_user ON tenant_ledgers(tenant_id, user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_tenant_ledgers_tenant_order ON tenant_ledgers(tenant_id, order_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_tenant_ledgers_tenant_type ON tenant_ledgers(tenant_id, type);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_tenant_ledgers_tenant_idempotency_key ON tenant_ledgers(tenant_id, user_id, idempotency_key) WHERE idempotency_key <> '';
|
||||
|
||||
-- +goose StatementEnd
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ux_tenant_ledgers_tenant_idempotency_key;
|
||||
DROP INDEX IF EXISTS ix_tenant_ledgers_tenant_type;
|
||||
DROP INDEX IF EXISTS ix_tenant_ledgers_tenant_order;
|
||||
DROP INDEX IF EXISTS ix_tenant_ledgers_tenant_user;
|
||||
DROP TABLE IF EXISTS tenant_ledgers;
|
||||
|
||||
DROP INDEX IF EXISTS ix_order_items_tenant_content;
|
||||
DROP INDEX IF EXISTS ix_order_items_tenant_order;
|
||||
DROP TABLE IF EXISTS order_items;
|
||||
|
||||
DROP INDEX IF EXISTS ux_orders_tenant_idempotency_key;
|
||||
DROP INDEX IF EXISTS ix_orders_tenant_paid_at;
|
||||
DROP INDEX IF EXISTS ix_orders_tenant_status;
|
||||
DROP INDEX IF EXISTS ix_orders_tenant_user;
|
||||
DROP TABLE IF EXISTS orders;
|
||||
|
||||
-- +goose StatementEnd
|
||||
@@ -1,11 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- orders.amount_paid:为“按金额区间筛选订单”提供索引(租户内隔离维度)
|
||||
CREATE INDEX IF NOT EXISTS ix_orders_tenant_amount_paid ON orders(tenant_id, amount_paid);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ix_orders_tenant_amount_paid;
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- tenant_users.status:历史上默认值为 'active',但代码枚举使用 UserStatus(pending_verify/verified/banned)。
|
||||
-- 为避免新增成员落入未知状态,这里将默认值调整为 'verified',并修正存量 'active' -> 'verified'。
|
||||
ALTER TABLE tenant_users
|
||||
ALTER COLUMN status SET DEFAULT 'verified';
|
||||
|
||||
UPDATE tenant_users
|
||||
SET status = 'verified'
|
||||
WHERE status = 'active';
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
-- 回滚:恢复默认值为 'active'(不回滚数据修正)。
|
||||
ALTER TABLE tenant_users
|
||||
ALTER COLUMN status SET DEFAULT 'active';
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS tenant_invites(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
code varchar(64) NOT NULL,
|
||||
status varchar(32) NOT NULL DEFAULT 'active',
|
||||
max_uses int NOT NULL DEFAULT 0,
|
||||
used_count int NOT NULL DEFAULT 0,
|
||||
expires_at timestamptz,
|
||||
disabled_at timestamptz,
|
||||
disabled_operator_user_id bigint,
|
||||
remark varchar(255) NOT NULL DEFAULT '',
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (tenant_id, code)
|
||||
);
|
||||
|
||||
-- tenant_invites:租户邀请(用于用户通过邀请码加入租户)
|
||||
COMMENT ON TABLE tenant_invites IS '租户邀请:租户管理员生成的邀请码;用户可通过 code 加入租户;支持禁用、过期、使用次数限制;所有查询/写入必须限定 tenant_id';
|
||||
COMMENT ON COLUMN tenant_invites.id IS '主键ID:自增';
|
||||
COMMENT ON COLUMN tenant_invites.tenant_id IS '租户ID:多租户隔离关键字段;所有查询/写入必须限定 tenant_id';
|
||||
COMMENT ON COLUMN tenant_invites.user_id IS '创建人用户ID:生成邀请码的租户管理员(审计用)';
|
||||
COMMENT ON COLUMN tenant_invites.code IS '邀请码:用户加入租户时提交;同一租户内唯一';
|
||||
COMMENT ON COLUMN tenant_invites.status IS '邀请状态:active/disabled/expired;expired 也可由 expires_at 推导,业务侧需保持一致';
|
||||
COMMENT ON COLUMN tenant_invites.max_uses IS '最大可使用次数:0 表示不限制;>0 时 used_count 达到该值后视为失效';
|
||||
COMMENT ON COLUMN tenant_invites.used_count IS '已使用次数:每次成功加入时 +1;需事务保证并发下不超发';
|
||||
COMMENT ON COLUMN tenant_invites.expires_at IS '过期时间:到期后不可再使用(UTC);为空表示不过期';
|
||||
COMMENT ON COLUMN tenant_invites.disabled_at IS '禁用时间:租户管理员禁用该邀请的时间(UTC)';
|
||||
COMMENT ON COLUMN tenant_invites.disabled_operator_user_id IS '禁用操作人用户ID:租户管理员(审计用)';
|
||||
COMMENT ON COLUMN tenant_invites.remark IS '备注:生成/禁用原因等(审计用)';
|
||||
COMMENT ON COLUMN tenant_invites.created_at IS '创建时间:默认 now()';
|
||||
COMMENT ON COLUMN tenant_invites.updated_at IS '更新时间:默认 now()';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_tenant_invites_tenant_status ON tenant_invites(tenant_id, status);
|
||||
CREATE INDEX IF NOT EXISTS ix_tenant_invites_tenant_expires_at ON tenant_invites(tenant_id, expires_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tenant_join_requests(
|
||||
id bigserial PRIMARY KEY,
|
||||
tenant_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
status varchar(32) NOT NULL DEFAULT 'pending',
|
||||
reason varchar(255) NOT NULL DEFAULT '',
|
||||
decided_at timestamptz,
|
||||
decided_operator_user_id bigint,
|
||||
decided_reason varchar(255) NOT NULL DEFAULT '',
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- tenant_join_requests:加入租户申请(用于无邀请码场景的人工审核)
|
||||
COMMENT ON TABLE tenant_join_requests IS '加入申请:用户申请加入租户,租户管理员审核通过/拒绝;所有查询/写入必须限定 tenant_id';
|
||||
COMMENT ON COLUMN tenant_join_requests.id IS '主键ID:自增';
|
||||
COMMENT ON COLUMN tenant_join_requests.tenant_id IS '租户ID:多租户隔离关键字段;所有查询/写入必须限定 tenant_id';
|
||||
COMMENT ON COLUMN tenant_join_requests.user_id IS '申请人用户ID:发起加入申请的用户';
|
||||
COMMENT ON COLUMN tenant_join_requests.status IS '申请状态:pending/approved/rejected;状态变更需记录 decided_at 与 decided_operator_user_id';
|
||||
COMMENT ON COLUMN tenant_join_requests.reason IS '申请原因:用户填写的加入说明(可选)';
|
||||
COMMENT ON COLUMN tenant_join_requests.decided_at IS '处理时间:审核通过/拒绝时记录(UTC)';
|
||||
COMMENT ON COLUMN tenant_join_requests.decided_operator_user_id IS '处理人用户ID:租户管理员(审计用)';
|
||||
COMMENT ON COLUMN tenant_join_requests.decided_reason IS '处理说明:管理员通过/拒绝的原因(可选,审计用)';
|
||||
COMMENT ON COLUMN tenant_join_requests.created_at IS '创建时间:默认 now()';
|
||||
COMMENT ON COLUMN tenant_join_requests.updated_at IS '更新时间:默认 now()';
|
||||
|
||||
-- 约束:同一用户同一租户同一时间仅允许存在一个 pending 申请,避免重复提交淹没审核队列。
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_tenant_join_requests_tenant_user_pending ON tenant_join_requests(tenant_id, user_id) WHERE status = 'pending';
|
||||
CREATE INDEX IF NOT EXISTS ix_tenant_join_requests_tenant_status ON tenant_join_requests(tenant_id, status);
|
||||
CREATE INDEX IF NOT EXISTS ix_tenant_join_requests_tenant_created_at ON tenant_join_requests(tenant_id, created_at);
|
||||
|
||||
-- +goose StatementEnd
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ix_tenant_join_requests_tenant_created_at;
|
||||
DROP INDEX IF EXISTS ix_tenant_join_requests_tenant_status;
|
||||
DROP INDEX IF EXISTS ux_tenant_join_requests_tenant_user_pending;
|
||||
DROP TABLE IF EXISTS tenant_join_requests;
|
||||
|
||||
DROP INDEX IF EXISTS ix_tenant_invites_tenant_expires_at;
|
||||
DROP INDEX IF EXISTS ix_tenant_invites_tenant_status;
|
||||
DROP TABLE IF EXISTS tenant_invites;
|
||||
-- +goose StatementEnd
|
||||
@@ -1,11 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- orders 列表查询索引补齐:租户管理端常用按 created_at/type 过滤 + 排序。
|
||||
CREATE INDEX IF NOT EXISTS ix_orders_tenant_created_at ON orders(tenant_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS ix_orders_tenant_type ON orders(tenant_id, type);
|
||||
-- +goose StatementEnd
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ix_orders_tenant_type;
|
||||
DROP INDEX IF EXISTS ix_orders_tenant_created_at;
|
||||
-- +goose StatementEnd
|
||||
@@ -1,36 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE media_assets
|
||||
ADD COLUMN IF NOT EXISTS variant varchar(32) NOT NULL DEFAULT 'main';
|
||||
|
||||
-- 回填历史数据:老数据一律视为 main。
|
||||
UPDATE media_assets
|
||||
SET variant = 'main'
|
||||
WHERE variant IS NULL OR variant = '';
|
||||
|
||||
-- 约束:只允许 main/preview
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'ck_media_assets_variant'
|
||||
) THEN
|
||||
ALTER TABLE media_assets
|
||||
ADD CONSTRAINT ck_media_assets_variant
|
||||
CHECK (variant IN ('main', 'preview'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMENT ON COLUMN media_assets.variant IS '产物类型:main/preview;用于强制试看资源必须绑定独立产物,避免用正片绕过';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_media_assets_tenant_variant ON media_assets (tenant_id, variant);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ix_media_assets_tenant_variant;
|
||||
ALTER TABLE media_assets DROP CONSTRAINT IF EXISTS ck_media_assets_variant;
|
||||
ALTER TABLE media_assets DROP COLUMN IF EXISTS variant;
|
||||
-- +goose StatementEnd
|
||||
@@ -1,16 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE media_assets
|
||||
ADD COLUMN IF NOT EXISTS source_asset_id bigint;
|
||||
|
||||
COMMENT ON COLUMN media_assets.source_asset_id IS '派生来源资源ID:preview 产物可指向对应 main 资源;用于建立 preview/main 的 1:1 追溯关系';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_media_assets_tenant_source_asset_id ON media_assets (tenant_id, source_asset_id);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ix_media_assets_tenant_source_asset_id;
|
||||
ALTER TABLE media_assets DROP COLUMN IF EXISTS source_asset_id;
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE tenant_ledgers
|
||||
ADD COLUMN IF NOT EXISTS operator_user_id bigint,
|
||||
ADD COLUMN IF NOT EXISTS biz_ref_type varchar(32),
|
||||
ADD COLUMN IF NOT EXISTS biz_ref_id bigint;
|
||||
|
||||
-- tenant_ledgers.operator_user_id:操作者(谁触发该流水)
|
||||
-- 用途:用于审计与风控追溯(例如后台代退款/调账等)。
|
||||
COMMENT ON COLUMN tenant_ledgers.operator_user_id IS '操作者用户ID:谁触发该流水(admin/buyer/system);用于审计与追责;可为空(历史数据或无法识别时)';
|
||||
|
||||
-- tenant_ledgers.biz_ref_type/biz_ref_id:业务引用(幂等与追溯)
|
||||
-- 用途:在 idempotency_key 之外提供结构化引用(例如 order/refund 等),便于报表与按业务对象追溯。
|
||||
COMMENT ON COLUMN tenant_ledgers.biz_ref_type IS '业务引用类型:order/refund/etc;与 biz_ref_id 组成可选的结构化幂等/追溯键';
|
||||
COMMENT ON COLUMN tenant_ledgers.biz_ref_id IS '业务引用ID:与 biz_ref_type 配合使用(例如 orders.id);用于对账与审计';
|
||||
|
||||
-- 索引:按操作者检索敏感操作流水(后台审计用)。
|
||||
CREATE INDEX IF NOT EXISTS ix_tenant_ledgers_tenant_operator ON tenant_ledgers(tenant_id, operator_user_id);
|
||||
|
||||
-- 索引:按业务引用快速定位同一业务对象的流水集合。
|
||||
CREATE INDEX IF NOT EXISTS ix_tenant_ledgers_tenant_biz_ref ON tenant_ledgers(tenant_id, biz_ref_type, biz_ref_id);
|
||||
|
||||
-- 结构化幂等(可选):同一业务引用在同一流水类型下只能出现一条。
|
||||
-- 说明:biz_ref_* 允许为空;仅当两者都非空时才参与唯一性约束。
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_tenant_ledgers_tenant_biz_ref_type_id_type
|
||||
ON tenant_ledgers(tenant_id, biz_ref_type, biz_ref_id, type)
|
||||
WHERE biz_ref_type IS NOT NULL AND biz_ref_id IS NOT NULL;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ux_tenant_ledgers_tenant_biz_ref_type_id_type;
|
||||
DROP INDEX IF EXISTS ix_tenant_ledgers_tenant_biz_ref;
|
||||
DROP INDEX IF EXISTS ix_tenant_ledgers_tenant_operator;
|
||||
|
||||
ALTER TABLE tenant_ledgers
|
||||
DROP COLUMN IF EXISTS biz_ref_id,
|
||||
DROP COLUMN IF EXISTS biz_ref_type,
|
||||
DROP COLUMN IF EXISTS operator_user_id;
|
||||
-- +goose StatementEnd
|
||||
@@ -1,22 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- 修正:biz_ref_type/biz_ref_id 在 Go 模型侧为 string/int64(非指针),空值会写入 ''/0,
|
||||
-- 若唯一索引仅判断 NOT NULL,会导致大量流水写入冲突。
|
||||
-- 约束策略:仅当 biz_ref_type 非空 且 biz_ref_id > 0 时才参与唯一性约束。
|
||||
DROP INDEX IF EXISTS ux_tenant_ledgers_tenant_biz_ref_type_id_type;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_tenant_ledgers_tenant_biz_ref_type_id_type
|
||||
ON tenant_ledgers(tenant_id, biz_ref_type, biz_ref_id, type)
|
||||
WHERE biz_ref_type IS NOT NULL AND biz_ref_type <> '' AND biz_ref_id IS NOT NULL AND biz_ref_id <> 0;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ux_tenant_ledgers_tenant_biz_ref_type_id_type;
|
||||
|
||||
-- Down 回滚为“仅判断 NOT NULL”的版本(不建议在线上使用该版本)。
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_tenant_ledgers_tenant_biz_ref_type_id_type
|
||||
ON tenant_ledgers(tenant_id, biz_ref_type, biz_ref_id, type)
|
||||
WHERE biz_ref_type IS NOT NULL AND biz_ref_id IS NOT NULL;
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- 清理“充值”遗留描述:当前项目已移除租户充值与 per-tenant 余额。
|
||||
|
||||
COMMENT ON COLUMN orders.type IS '订单类型:content_purchase(购买内容)等;当前默认 content_purchase';
|
||||
|
||||
COMMENT ON TABLE tenant_ledgers IS '账本流水:记录租户内用户余额的每一次变化(冻结/扣款/退款/调账等);用于审计与对账回放';
|
||||
COMMENT ON COLUMN tenant_ledgers.type IS '流水类型:debit_purchase/credit_refund/freeze/unfreeze/adjustment;不同类型决定余额/冻结余额的变更方向';
|
||||
|
||||
COMMENT ON COLUMN tenant_ledgers.operator_user_id IS '操作者用户ID:谁触发该流水(admin/buyer/system);用于审计与追责;可为空(历史数据或无法识别时)';
|
||||
COMMENT ON COLUMN tenant_ledgers.biz_ref_type IS '业务引用类型:order/refund/etc;与 biz_ref_id 组成可选的结构化幂等/追溯键';
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
-- 新项目不需要依赖 Down 做历史回滚;保持与 Up 一致,避免引入已移除特性的遗留描述。
|
||||
COMMENT ON COLUMN orders.type IS '订单类型:content_purchase(购买内容)等;当前默认 content_purchase';
|
||||
|
||||
COMMENT ON TABLE tenant_ledgers IS '账本流水:记录租户内用户余额的每一次变化(冻结/扣款/退款/调账等);用于审计与对账回放';
|
||||
COMMENT ON COLUMN tenant_ledgers.type IS '流水类型:debit_purchase/credit_refund/freeze/unfreeze/adjustment;不同类型决定余额/冻结余额的变更方向';
|
||||
|
||||
COMMENT ON COLUMN tenant_ledgers.operator_user_id IS '操作者用户ID:谁触发该流水(admin/buyer/system);用于审计与追责;可为空(历史数据或无法识别时)';
|
||||
COMMENT ON COLUMN tenant_ledgers.biz_ref_type IS '业务引用类型:order/refund/etc;与 biz_ref_id 组成可选的结构化幂等/追溯键';
|
||||
-- +goose StatementEnd
|
||||
@@ -1,21 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- contents:补齐“简介/标签”字段,用于内容发布与列表展示
|
||||
ALTER TABLE contents
|
||||
ADD COLUMN IF NOT EXISTS summary varchar(256) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS tags jsonb NOT NULL DEFAULT '[]'::jsonb;
|
||||
|
||||
COMMENT ON COLUMN contents.summary IS '简介:用于列表/卡片展示的短文本;建议 <= 256 字符(由业务校验)';
|
||||
COMMENT ON COLUMN contents.tags IS '标签:JSON 数组(字符串列表);用于分类/检索与聚合展示';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_contents_tenant_tags ON contents(tenant_id);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS ix_contents_tenant_tags;
|
||||
ALTER TABLE contents
|
||||
DROP COLUMN IF EXISTS tags,
|
||||
DROP COLUMN IF EXISTS summary;
|
||||
-- +goose StatementEnd
|
||||
|
||||
9
backend/database/migrations/20251227112605_init.sql
Normal file
9
backend/database/migrations/20251227112605_init.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
SELECT 'up SQL query';
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
SELECT 'down SQL query';
|
||||
-- +goose StatementEnd
|
||||
Reference in New Issue
Block a user