chore: align db spec with migrations

This commit is contained in:
2026-01-08 16:24:51 +08:00
parent 4cda51539f
commit 26215e8e47

View File

@@ -1,151 +1,339 @@
-- 新项目数据库 DDLPostgreSQL -- QuyUn v2 数据库 DDLPostgreSQL
-- 目标多租户tenant_id 全表隔离)+ tenant_uuid 用于 OSS Keyquyun/<tenant_uuid>/<md5>.<ext> -- 说明:本文件用于规格参考,需与 backend/database/migrations 保持一致。
-- 注意tenant_uuid 由业务代码生成写入(不使用 DB 扩展默认值)
BEGIN; BEGIN;
-- 1) 租户 -- Users
CREATE TABLE IF NOT EXISTS tenants ( CREATE TABLE IF NOT EXISTS users(
id BIGSERIAL PRIMARY KEY, id bigserial PRIMARY KEY, -- 主键ID自增
tenant_code VARCHAR(64) NOT NULL, username varchar(255) NOT NULL UNIQUE, -- 用户名:唯一,用于登录
tenant_uuid UUID NOT NULL, password varchar(255) NOT NULL, -- 密码:加密存储
name VARCHAR(128) NOT NULL DEFAULT '', roles text[] DEFAULT '{user}', -- 角色:用户角色列表,如 {user, super_admin}
status INT2 NOT NULL DEFAULT 0, status varchar(50) DEFAULT 'active', -- 状态active/inactive/banned
config JSONB NOT NULL DEFAULT '{}'::jsonb, metas jsonb DEFAULT '{}', -- 元数据:额外扩展信息
created_at TIMESTAMPTZ NOT NULL DEFAULT now(), balance bigint DEFAULT 0, -- 全局可用余额:分/最小货币单位
updated_at TIMESTAMPTZ NOT NULL DEFAULT now() balance_frozen bigint DEFAULT 0, -- 全局冻结余额:分/最小货币单位
verified_at timestamp with time zone, -- 实名认证时间
nickname varchar(255) DEFAULT '', -- 昵称:用户显示名称
avatar varchar(512) DEFAULT '', -- 头像URL地址
gender varchar(32) DEFAULT 'secret', -- 性别male/female/secret
bio varchar(512) DEFAULT '', -- 简介:用户个人简介
birthday date, -- 生日YYYY-MM-DD
location jsonb DEFAULT '{}', -- 位置:省市区信息 {province: "...", city: "..."}
points bigint DEFAULT 0, -- 积分:用户积分
phone varchar(32) DEFAULT '', -- 手机号:用于登录/验证
is_real_name_verified boolean DEFAULT FALSE, -- 是否实名认证true/false
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间:默认 now()
updated_at timestamp with time zone DEFAULT NOW(), -- 更新时间:默认 now()
deleted_at timestamp with time zone -- 删除时间:软删除
); );
-- tenant_code不区分大小写写入/查询均 lower并限制字符集 a-z0-9_- -- Tenants
ALTER TABLE tenants CREATE TABLE IF NOT EXISTS tenants(
ADD CONSTRAINT tenants_tenant_code_format id bigserial PRIMARY KEY, -- 主键ID自增
CHECK (tenant_code ~ '^[A-Za-z0-9_-]+$'); user_id bigint NOT NULL, -- 创建者ID关联 users.id
code varchar(64) NOT NULL UNIQUE, -- 租户代码:唯一标识,用于 URL 等
CREATE UNIQUE INDEX IF NOT EXISTS ux_tenants_tenant_code_lower uuid uuid NOT NULL, -- UUID全局唯一标识
ON tenants (lower(tenant_code)); name varchar(128) NOT NULL, -- 租户名称
status varchar(64) NOT NULL, -- 状态pending_verify/verified/banned
CREATE UNIQUE INDEX IF NOT EXISTS ux_tenants_tenant_uuid config jsonb DEFAULT '{}', -- 配置:租户配置信息
ON tenants (tenant_uuid); expired_at timestamp with time zone, -- 过期时间:租户有效期
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间:默认 now()
-- 2) 租户后台账号 updated_at timestamp with time zone DEFAULT NOW() -- 更新时间:默认 now()
CREATE TABLE IF NOT EXISTS admin_users (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
username VARCHAR(64) NOT NULL,
password_hash TEXT NOT NULL,
role VARCHAR(32) NOT NULL DEFAULT 'admin',
status INT2 NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
); );
CREATE UNIQUE INDEX IF NOT EXISTS ux_admin_users_tenant_username CREATE INDEX IF NOT EXISTS idx_tenants_user_id ON tenants(user_id);
ON admin_users (tenant_id, lower(username));
-- 3) 用户(微信 openid 用户) -- TenantUsers
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS tenant_users(
id BIGSERIAL PRIMARY KEY, id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id BIGINT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, tenant_id bigint NOT NULL, -- 租户ID关联 tenants.id
created_at TIMESTAMPTZ NOT NULL DEFAULT now(), user_id bigint NOT NULL, -- 用户ID关联 users.id
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), role text[] DEFAULT '{member}', -- 角色member/tenant_admin
deleted_at TIMESTAMPTZ, status varchar(50) DEFAULT 'verified', -- 状态pending_verify/verified/banned
status INT2 NOT NULL DEFAULT 0, created_at timestamp with time zone DEFAULT NOW(), -- 创建时间:默认 now()
open_id VARCHAR(128) NOT NULL, updated_at timestamp with time zone DEFAULT NOW(), -- 更新时间:默认 now()
username VARCHAR(128) NOT NULL DEFAULT '', UNIQUE (tenant_id, user_id)
avatar TEXT,
metas JSONB NOT NULL DEFAULT '{}'::jsonb,
auth_token JSONB NOT NULL DEFAULT '{}'::jsonb,
balance BIGINT NOT NULL DEFAULT 0
); );
CREATE UNIQUE INDEX IF NOT EXISTS ux_users_tenant_openid -- Contents
ON users (tenant_id, open_id); CREATE TABLE IF NOT EXISTS contents(
id bigserial PRIMARY KEY, -- 主键ID自增用于内容引用
CREATE INDEX IF NOT EXISTS ix_users_tenant_id tenant_id bigint NOT NULL, -- 租户ID多租户隔离关键字段
ON users (tenant_id, id); user_id bigint NOT NULL, -- 用户ID内容创建者/发布者
title varchar(255) NOT NULL, -- 标题:用于列表展示与搜索
-- 4) 媒体 description text NOT NULL, -- 描述:用于详情页展示
CREATE TABLE IF NOT EXISTS medias ( status varchar(32) DEFAULT 'draft', -- 状态draft/reviewing/published/unpublished/blocked
id BIGSERIAL PRIMARY KEY, visibility varchar(32) DEFAULT 'tenant_only', -- 可见性public/tenant_only/private
tenant_id BIGINT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, preview_seconds int DEFAULT 60, -- 试看秒数:默认 60
created_at TIMESTAMPTZ NOT NULL DEFAULT now(), preview_downloadable boolean DEFAULT FALSE, -- 试看是否允许下载
name VARCHAR(255) NOT NULL DEFAULT '', published_at timestamp with time zone, -- 发布时间
mime_type VARCHAR(128) NOT NULL DEFAULT '', summary varchar(256) DEFAULT '', -- 简介:用于列表/卡片展示
size BIGINT NOT NULL DEFAULT 0, tags jsonb DEFAULT '[]', -- 标签JSON 数组
path VARCHAR(512) NOT NULL DEFAULT '', body text DEFAULT '', -- 内容主体:文章内容/详细介绍
metas JSONB NOT NULL DEFAULT '{}'::jsonb, genre varchar(64) DEFAULT '', -- 类型/流派
hash VARCHAR(64) NOT NULL DEFAULT '' views int DEFAULT 0, -- 浏览量
likes int DEFAULT 0, -- 点赞数
key varchar(32) DEFAULT '', -- 音乐调性/主音
is_pinned boolean DEFAULT FALSE, -- 是否置顶
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW(), -- 更新时间
deleted_at timestamp with time zone -- 软删除时间
); );
-- 租户内按 md5 去重 CREATE INDEX IF NOT EXISTS idx_contents_tenant_id ON contents(tenant_id);
CREATE UNIQUE INDEX IF NOT EXISTS ux_medias_tenant_hash
ON medias (tenant_id, hash);
CREATE INDEX IF NOT EXISTS ix_medias_tenant_id -- MediaAssets
ON medias (tenant_id, id); CREATE TABLE IF NOT EXISTS media_assets(
id bigserial PRIMARY KEY, -- 主键ID自增
-- 5) 曲谱/内容 tenant_id bigint NOT NULL, -- 租户ID多租户隔离关键字段
CREATE TABLE IF NOT EXISTS posts ( user_id bigint NOT NULL, -- 用户ID资源上传者
id BIGSERIAL PRIMARY KEY, type varchar(32) DEFAULT 'video', -- 资源类型video/audio/image
tenant_id BIGINT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, status varchar(32) DEFAULT 'uploaded', -- 处理状态uploaded/processing/ready/failed/deleted
created_at TIMESTAMPTZ NOT NULL DEFAULT now(), provider varchar(64) NOT NULL, -- 存储提供方s3/minio/oss/local
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), bucket varchar(128) NOT NULL, -- 存储桶
deleted_at TIMESTAMPTZ, object_key varchar(512) NOT NULL, -- 对象键
status INT2 NOT NULL DEFAULT 0, meta jsonb DEFAULT '{}', -- 元数据JSON
title VARCHAR(128) NOT NULL, variant varchar(32) DEFAULT 'main', -- 产物类型main/preview
head_images JSONB NOT NULL DEFAULT '[]'::jsonb, source_asset_id bigint DEFAULT 0, -- 派生来源资源ID
description VARCHAR(256) NOT NULL DEFAULT '', hash varchar(64) DEFAULT '', -- 文件 MD5 哈希
content TEXT NOT NULL DEFAULT '', created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
price BIGINT NOT NULL DEFAULT 0, updated_at timestamp with time zone DEFAULT NOW(), -- 更新时间
discount INT2 NOT NULL DEFAULT 100, deleted_at timestamp with time zone -- 软删除时间
views BIGINT NOT NULL DEFAULT 0,
likes BIGINT NOT NULL DEFAULT 0,
tags JSONB NOT NULL DEFAULT '[]'::jsonb,
assets JSONB NOT NULL DEFAULT '[]'::jsonb
); );
CREATE INDEX IF NOT EXISTS ix_posts_tenant_status_deleted CREATE INDEX IF NOT EXISTS idx_media_assets_hash ON media_assets (hash);
ON posts (tenant_id, status, deleted_at);
-- 6) 授权关系(购买/赠送) -- ContentAssets
CREATE TABLE IF NOT EXISTS user_posts ( CREATE TABLE IF NOT EXISTS content_assets(
id BIGSERIAL PRIMARY KEY, id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id BIGINT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, tenant_id bigint NOT NULL, -- 租户ID
created_at TIMESTAMPTZ NOT NULL DEFAULT now(), user_id bigint NOT NULL, -- 用户ID
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), content_id bigint NOT NULL, -- 内容ID
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, asset_id bigint NOT NULL, -- 资源ID
post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE, role varchar(32) DEFAULT 'main', -- 资源角色main/cover/preview
price BIGINT NOT NULL DEFAULT 0 sort int DEFAULT 0, -- 排序
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW() -- 更新时间
); );
CREATE UNIQUE INDEX IF NOT EXISTS ux_user_posts_tenant_user_post -- ContentPrices
ON user_posts (tenant_id, user_id, post_id); CREATE TABLE IF NOT EXISTS content_prices(
id bigserial PRIMARY KEY, -- 主键ID自增
CREATE INDEX IF NOT EXISTS ix_user_posts_tenant_user tenant_id bigint NOT NULL, -- 租户ID
ON user_posts (tenant_id, user_id, id); user_id bigint NOT NULL, -- 用户ID
content_id bigint NOT NULL, -- 内容ID
-- 7) 订单(仅余额) currency varchar(16) DEFAULT 'CNY', -- 币种
CREATE TABLE IF NOT EXISTS orders ( price_amount bigint NOT NULL, -- 基础价格:分
id BIGSERIAL PRIMARY KEY, discount_type varchar(16) DEFAULT 'none', -- 折扣类型none/percent/amount
tenant_id BIGINT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, discount_value bigint DEFAULT 0, -- 折扣值
created_at TIMESTAMPTZ NOT NULL DEFAULT now(), discount_start_at timestamp with time zone, -- 折扣开始时间
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), discount_end_at timestamp with time zone, -- 折扣结束时间
order_no VARCHAR(64) NOT NULL, created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
price BIGINT NOT NULL DEFAULT 0, updated_at timestamp with time zone DEFAULT NOW(), -- 更新时间
discount INT2 NOT NULL DEFAULT 100, UNIQUE (tenant_id, content_id)
currency VARCHAR(10) NOT NULL DEFAULT 'CNY',
payment_method VARCHAR(50) NOT NULL DEFAULT 'balance',
post_id BIGINT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status INT2 NOT NULL DEFAULT 0,
meta JSONB NOT NULL DEFAULT '{}'::jsonb
); );
CREATE UNIQUE INDEX IF NOT EXISTS ux_orders_tenant_order_no -- ContentAccess
ON orders (tenant_id, order_no); CREATE TABLE IF NOT EXISTS content_access(
id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id bigint NOT NULL, -- 租户ID
user_id bigint NOT NULL, -- 用户ID
content_id bigint NOT NULL, -- 内容ID
order_id bigint DEFAULT 0, -- 订单ID
status varchar(16) DEFAULT 'active', -- 权益状态
revoked_at timestamp with time zone, -- 撤销时间
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW(), -- 更新时间
UNIQUE (tenant_id, user_id, content_id)
);
CREATE INDEX IF NOT EXISTS ix_orders_tenant_user -- Orders
ON orders (tenant_id, user_id, id); CREATE TABLE IF NOT EXISTS orders(
id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id bigint NOT NULL, -- 租户ID
user_id bigint NOT NULL, -- 用户ID
type varchar(32) DEFAULT 'content_purchase', -- 订单类型
status varchar(32) DEFAULT 'created', -- 订单状态
currency varchar(16) DEFAULT 'CNY', -- 币种
amount_original bigint NOT NULL, -- 原价金额
amount_discount bigint NOT NULL, -- 优惠金额
amount_paid bigint NOT NULL, -- 实付金额
snapshot jsonb DEFAULT '{}', -- 订单快照
idempotency_key varchar(128) NOT NULL, -- 幂等键
paid_at timestamp with time zone, -- 支付时间
refunded_at timestamp with time zone, -- 退款时间
refund_forced boolean DEFAULT FALSE, -- 是否强制退款
refund_operator_user_id bigint DEFAULT 0, -- 退款操作人
refund_reason varchar(255) DEFAULT '', -- 退款原因
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW(), -- 更新时间
coupon_id bigint DEFAULT 0 -- 关联优惠券ID (0表示未使用)
);
-- OrderItems
CREATE TABLE IF NOT EXISTS order_items(
id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id bigint NOT NULL, -- 租户ID
user_id bigint NOT NULL, -- 用户ID
order_id bigint NOT NULL, -- 订单ID
content_id bigint NOT NULL, -- 内容ID
content_user_id bigint NOT NULL, -- 内容作者用户ID
amount_paid bigint NOT NULL, -- 该行实付金额
snapshot jsonb DEFAULT '{}', -- 内容快照
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW() -- 更新时间
);
-- TenantLedgers
CREATE TABLE IF NOT EXISTS tenant_ledgers(
id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id bigint NOT NULL, -- 租户ID
user_id bigint NOT NULL, -- 用户ID
order_id bigint DEFAULT 0, -- 关联订单ID
type varchar(32) NOT NULL, -- 流水类型
amount bigint NOT NULL, -- 流水金额
balance_before bigint NOT NULL, -- 变更前可用余额
balance_after bigint NOT NULL, -- 变更后可用余额
frozen_before bigint NOT NULL, -- 变更前冻结余额
frozen_after bigint NOT NULL, -- 变更后冻结余额
idempotency_key varchar(128) NOT NULL, -- 幂等键
remark varchar(255) NOT NULL, -- 备注
operator_user_id bigint DEFAULT 0, -- 操作者用户ID
biz_ref_type varchar(32) DEFAULT '', -- 业务引用类型
biz_ref_id bigint DEFAULT 0, -- 业务引用ID
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW() -- 更新时间
);
-- TenantInvites
CREATE TABLE IF NOT EXISTS tenant_invites(
id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id bigint NOT NULL, -- 租户ID
user_id bigint NOT NULL, -- 创建人用户ID
code varchar(64) NOT NULL, -- 邀请码
status varchar(32) DEFAULT 'active', -- 邀请状态
max_uses int NOT NULL, -- 最大可使用次数
used_count int DEFAULT 0, -- 已使用次数
expires_at timestamp with time zone, -- 过期时间
disabled_at timestamp with time zone, -- 禁用时间
disabled_operator_user_id bigint DEFAULT 0, -- 禁用操作人用户ID
remark varchar(255) DEFAULT '', -- 备注
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW() -- 更新时间
);
-- TenantJoinRequests
CREATE TABLE IF NOT EXISTS tenant_join_requests(
id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id bigint NOT NULL, -- 租户ID
user_id bigint NOT NULL, -- 申请人用户ID
status varchar(32) DEFAULT 'pending', -- 申请状态
reason varchar(255) NOT NULL, -- 申请原因
decided_at timestamp with time zone, -- 处理时间
decided_operator_user_id bigint DEFAULT 0, -- 处理人用户ID
decided_reason varchar(255) DEFAULT '', -- 处理说明
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW() -- 更新时间
);
-- Comments
CREATE TABLE IF NOT EXISTS comments(
id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id bigint NOT NULL, -- 租户ID
user_id bigint NOT NULL, -- 用户ID
content_id bigint NOT NULL, -- 内容ID
reply_to bigint DEFAULT 0, -- 回复评论ID0表示一级评论
content text NOT NULL, -- 评论内容
likes int DEFAULT 0, -- 点赞数
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW(), -- 更新时间
deleted_at timestamp with time zone -- 软删除时间
);
CREATE INDEX IF NOT EXISTS idx_comments_content_id ON comments(content_id);
CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id);
-- User Content Actions (Like, Favorite)
CREATE TABLE IF NOT EXISTS user_content_actions(
id bigserial PRIMARY KEY, -- 主键ID自增
user_id bigint NOT NULL, -- 用户ID
content_id bigint NOT NULL, -- 内容ID
type varchar(32) NOT NULL, -- 类型like, favorite
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
UNIQUE (user_id, content_id, type)
);
-- User Comment Actions (Like)
CREATE TABLE IF NOT EXISTS user_comment_actions(
id bigserial PRIMARY KEY, -- 主键ID自增
user_id bigint NOT NULL, -- 用户ID
comment_id bigint NOT NULL, -- 评论ID
type varchar(32) NOT NULL, -- 类型like
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
UNIQUE (user_id, comment_id, type)
);
-- Payout Accounts
CREATE TABLE IF NOT EXISTS payout_accounts(
id bigserial PRIMARY KEY, -- 主键ID自增
tenant_id bigint NOT NULL, -- 租户ID
user_id bigint NOT NULL, -- 用户ID
type varchar(32) NOT NULL, -- 类型bank, alipay
name varchar(128) NOT NULL, -- 账户名称/开户行
account varchar(128) NOT NULL, -- 账号
realname varchar(128) NOT NULL, -- 真实姓名
created_at timestamp with time zone DEFAULT NOW(), -- 创建时间
updated_at timestamp with time zone DEFAULT NOW() -- 更新时间
);
CREATE INDEX IF NOT EXISTS idx_payout_accounts_tenant_id ON payout_accounts(tenant_id);
-- Notifications
CREATE TABLE IF NOT EXISTS notifications(
id bigserial PRIMARY KEY, -- 主键ID自增
user_id bigint NOT NULL, -- 接收用户ID
tenant_id bigint DEFAULT 0, -- 来源租户ID
type varchar(32) NOT NULL, -- 类型system, order, audit, interaction
title varchar(255) NOT NULL, -- 标题
content text NOT NULL, -- 内容
is_read boolean DEFAULT FALSE, -- 是否已读
created_at timestamp with time zone DEFAULT NOW() -- 创建时间
);
CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications(user_id);
-- Coupons
CREATE TABLE IF NOT EXISTS coupons(
id bigserial PRIMARY KEY, -- 主键ID
tenant_id bigint NOT NULL DEFAULT 0, -- 租户ID
title varchar(255) NOT NULL, -- 优惠券标题
description text, -- 优惠券描述
type varchar(32) NOT NULL, -- 优惠券类型: fix_amount/discount
value bigint NOT NULL, -- 优惠券面值
min_order_amount bigint NOT NULL DEFAULT 0, -- 最低订单金额门槛
max_discount bigint, -- 最高抵扣金额
total_quantity integer NOT NULL DEFAULT 0, -- 发行总量
used_quantity integer NOT NULL DEFAULT 0, -- 已使用数量
start_at timestamptz, -- 开始生效时间
end_at timestamptz, -- 过期失效时间
created_at timestamptz NOT NULL DEFAULT NOW(),
updated_at timestamptz NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_coupons_tenant_id ON coupons(tenant_id);
CREATE TABLE IF NOT EXISTS user_coupons(
id bigserial PRIMARY KEY,
user_id bigint NOT NULL, -- 用户ID
coupon_id bigint NOT NULL, -- 优惠券ID
order_id bigint, -- 使用该优惠券的订单ID
status varchar(32) NOT NULL DEFAULT 'unused', -- 状态: unused/used/expired
used_at timestamptz, -- 使用时间
created_at timestamptz NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_user_coupons_user_id ON user_coupons(user_id);
CREATE INDEX IF NOT EXISTS idx_user_coupons_coupon_id ON user_coupons(coupon_id);
COMMIT; COMMIT;