Files
meijiaka-zy/python-api/alembic/versions/c3a0e1c71ce6_initial_schema.py
T
小鱼开发 cbd4068776 fix(db): unify table name prefix to mjk_ for update tables
- Rename app_releases → mjk_app_releases
- Rename release_packages → mjk_release_packages
- Update ForeignKey reference and migration file
- Add pre-commit hook: check_table_prefix.py to prevent future violations
2026-05-15 18:28:07 +08:00

216 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""initial_schema
Revision ID: c3a0e1c71ce6
Revises:
Create Date: 2026-05-15 17:31:52.560351
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'c3a0e1c71ce6'
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('mjk_app_releases',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('version', sa.String(length=20), nullable=False),
sa.Column('release_date', sa.DateTime(timezone=True), nullable=False),
sa.Column('notes', sa.Text(), nullable=False),
sa.Column('mandatory', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_mjk_app_releases_version'), 'mjk_app_releases', ['version'], unique=True)
op.create_table('mjk_broll_categories',
sa.Column('slug', sa.String(length=128), nullable=False, comment='分类标识符,URL友好格式'),
sa.Column('name', sa.String(length=256), nullable=False, comment='分类中文名称,三级分类直接对应 scene 标准化后的值'),
sa.Column('parent_id', sa.BigInteger(), nullable=True, comment='父分类ID,NULL 表示根分类(一级)'),
sa.Column('level', sa.BigInteger(), nullable=False, comment='层级:1=一级(大阶段),2=二级(工序),3=三级(场景)'),
sa.Column('sort_order', sa.BigInteger(), nullable=False, comment='排序权重,装修流程有先后顺序'),
sa.Column('status', sa.String(length=16), nullable=False, comment='状态:active(启用)/ disabled(停用)/ deleted(软删除)'),
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['parent_id'], ['mjk_broll_categories.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('slug')
)
op.create_table('mjk_broll_tags',
sa.Column('name', sa.String(length=64), nullable=False, comment='标签名称,如 近景、白天、水管'),
sa.Column('category', sa.String(length=32), nullable=True, comment='标签维度:scene(场景)/ element(元素)/ style(风格)/ mood(情绪)/ time(时间)'),
sa.Column('sort_order', sa.Integer(), nullable=False, comment='排序权重'),
sa.Column('status', sa.String(length=16), nullable=False, comment='状态:active(启用)/ disabled(停用)'),
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('mjk_cover_backgrounds',
sa.Column('script_code', sa.String(length=64), nullable=False, comment='关联脚本大类 code,如 bk(装修避坑)'),
sa.Column('title', sa.String(length=256), nullable=False, comment='背景图名称,运营识别用'),
sa.Column('url', sa.String(length=1024), nullable=False, comment='七牛云 CDN 图片地址'),
sa.Column('sort_order', sa.BigInteger(), nullable=False, comment='排序权重,数字越小越靠前'),
sa.Column('status', sa.String(length=16), nullable=False, comment='状态:active(启用)/ disabled(停用)/ deleted(软删除)'),
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('mjk_point_batches',
sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID'),
sa.Column('amount', sa.Integer(), nullable=False, comment='初始积分'),
sa.Column('remaining', sa.Integer(), nullable=False, comment='剩余可用积分'),
sa.Column('expired_at', sa.DateTime(timezone=True), nullable=False, comment='过期时间(created_at + 180 天)'),
sa.Column('source', sa.String(length=32), nullable=False, comment='来源:wxpay / invite / gift / compensation'),
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('mjk_point_recharge_orders',
sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID'),
sa.Column('points', sa.Integer(), nullable=False, comment='充值积分数'),
sa.Column('amount_rmb', sa.Integer(), nullable=False, comment='人民币金额(单位:分,如 500 = 5 元)'),
sa.Column('out_trade_no', sa.String(length=64), nullable=True, comment='商户订单号(传给微信的 out_trade_no'),
sa.Column('prepay_id', sa.String(length=64), nullable=True, comment='微信预支付会话标识(统一下单返回)'),
sa.Column('wx_order_no', sa.String(length=64), nullable=True, comment='微信支付订单号(微信侧唯一标识)'),
sa.Column('openid', sa.String(length=64), nullable=True, comment='用户微信 OpenID(统一下单必需)'),
sa.Column('client_ip', sa.String(length=45), nullable=True, comment='用户下单时的 IP 地址'),
sa.Column('trade_type', sa.String(length=16), nullable=True, comment='交易类型:JSAPI / NATIVE / APP'),
sa.Column('status', sa.String(length=20), nullable=False, comment='订单状态:pending / paid / failed / closed'),
sa.Column('paid_at', sa.DateTime(timezone=True), nullable=True, comment='支付成功时间'),
sa.Column('closed_at', sa.DateTime(timezone=True), nullable=True, comment='订单关闭时间(超时未支付)'),
sa.Column('request_params', sa.Text(), nullable=True, comment='统一下单请求参数(JSON 格式,用于排查请求侧问题)'),
sa.Column('request_response', sa.Text(), nullable=True, comment='统一下单响应内容(JSON 格式,用于排查微信返回)'),
sa.Column('notify_raw', sa.Text(), nullable=True, comment='微信回调原始内容(XML/JSON,用于排查回调问题)'),
sa.Column('notify_verified', sa.Boolean(), nullable=False, comment='回调签名是否验证通过'),
sa.Column('query_result', sa.Text(), nullable=True, comment='主动查询订单结果(JSON 格式,用于二次确认)'),
sa.Column('error_code', sa.String(length=32), nullable=True, comment='错误码(微信返回或系统异常)'),
sa.Column('error_msg', sa.Text(), nullable=True, comment='错误描述(用于快速定位问题)'),
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('out_trade_no')
)
op.create_table('mjk_point_transactions',
sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID'),
sa.Column('type', sa.String(length=20), nullable=False, comment='变动类型:recharge / consume / expire / refund'),
sa.Column('amount', sa.Integer(), nullable=False, comment='变动数量(正数)'),
sa.Column('balance_before', sa.Integer(), nullable=False, comment='变动前总余额'),
sa.Column('balance_after', sa.Integer(), nullable=False, comment='变动后总余额'),
sa.Column('source_type', sa.String(length=32), nullable=True, comment='消费来源类型:script / polish / voice_clone / tts / video'),
sa.Column('source_id', sa.String(length=64), nullable=True, comment='关联的任务 ID 或订单 ID'),
sa.Column('batch_id', sa.BigInteger(), nullable=True, comment='关联的积分批次 ID(消费时记录从哪个批次扣)'),
sa.Column('duration', sa.Float(), nullable=True, comment='时长(秒),按秒计费业务记录'),
sa.Column('category', sa.String(length=32), nullable=True, comment='业务分类:脚本生成 / 配音合成 / 视频生成 / 压制成片 / 字幕烧录 / 封面设计 / 充值'),
sa.Column('description', sa.Text(), nullable=True, comment='描述'),
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('mjk_user_devices',
sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID(唯一约束,强制单设备登录)'),
sa.Column('device_id', sa.String(length=64), nullable=False, comment='设备唯一标识(前端生成)'),
sa.Column('device_name', sa.String(length=128), nullable=True, comment="设备名称(如 'MacBook Pro'"),
sa.Column('os_info', sa.String(length=128), nullable=True, comment='操作系统信息'),
sa.Column('app_version', sa.String(length=32), nullable=True, comment='应用版本号'),
sa.Column('refresh_token_hash', sa.String(length=64), nullable=True, comment='Refresh Token SHA256 哈希(用于校验和撤销)'),
sa.Column('last_active_at', sa.DateTime(timezone=True), nullable=False, comment='最后活跃时间'),
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('user_id')
)
op.create_table('mjk_user_points',
sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID'),
sa.Column('balance', sa.Integer(), nullable=False, comment='当前积分余额(允许欠费为负)'),
sa.Column('total_recharged', sa.Integer(), nullable=False, comment='累计充值积分'),
sa.Column('total_consumed', sa.Integer(), nullable=False, comment='累计消费积分'),
sa.Column('total_expired', sa.Integer(), nullable=False, comment='累计过期积分'),
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('user_id')
)
op.create_table('mjk_users',
sa.Column('mobile', sa.String(length=20), nullable=False, comment='手机号,登录账号'),
sa.Column('password_hash', sa.String(length=255), nullable=True, comment='密码哈希(bcrypt),预留字段'),
sa.Column('status', sa.String(length=20), nullable=False, comment='账号状态'),
sa.Column('nickname', sa.String(length=64), nullable=True, comment='用户昵称'),
sa.Column('avatar_url', sa.Text(), nullable=True, comment='头像 URL'),
sa.Column('source', sa.String(length=32), nullable=False, comment='注册来源'),
sa.Column('invited_by', sa.String(length=36), nullable=True, comment='邀请人 user_id'),
sa.Column('last_login_at', sa.DateTime(timezone=True), nullable=True, comment='最后登录时间'),
sa.Column('last_login_ip', sa.String(length=45), nullable=True, comment='最后登录 IP(IPv6 最大 45 字符)'),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True, comment='注销时间(软删除标记)'),
sa.Column('extra', postgresql.JSONB(astext_type=sa.Text()), nullable=False, comment='冗余字段,备用'),
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('mobile')
)
op.create_table('mjk_broll_materials',
sa.Column('category_id', sa.BigInteger(), nullable=False, comment='所属三级分类ID,关联 mjk_broll_categories'),
sa.Column('title', sa.String(length=256), nullable=False, comment='素材标题/文件名,运营后台识别用'),
sa.Column('url', sa.String(length=1024), nullable=False, comment='七牛云 CDN 访问地址,FFmpeg合成和前端播放直接使用'),
sa.Column('duration', sa.Float(), nullable=False, comment='视频时长(秒),FFmpeg probe 提取,入库时必须大于0'),
sa.Column('usage_count', sa.BigInteger(), nullable=False, comment='累计使用次数,驱动加权随机算法'),
sa.Column('status', sa.String(length=16), nullable=False, comment='状态:active(可用)/ disabled(下架)/ deleted(软删除)'),
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['category_id'], ['mjk_broll_categories.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('mjk_release_packages',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('release_id', sa.Integer(), nullable=False),
sa.Column('platform', sa.String(length=20), nullable=False),
sa.Column('architecture', sa.String(length=20), nullable=False),
sa.Column('filename', sa.String(length=255), nullable=False),
sa.Column('file_url', sa.String(length=500), nullable=False),
sa.Column('file_size', sa.BigInteger(), nullable=False),
sa.Column('signature', sa.Text(), nullable=False),
sa.Column('download_count', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['release_id'], ['mjk_app_releases.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('release_id', 'platform', 'architecture', name='uix_pkg_platform_arch')
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('mjk_release_packages')
op.drop_table('mjk_broll_materials')
op.drop_table('mjk_users')
op.drop_table('mjk_user_points')
op.drop_table('mjk_user_devices')
op.drop_table('mjk_point_transactions')
op.drop_table('mjk_point_recharge_orders')
op.drop_table('mjk_point_batches')
op.drop_table('mjk_cover_backgrounds')
op.drop_table('mjk_broll_tags')
op.drop_table('mjk_broll_categories')
op.drop_index(op.f('ix_mjk_app_releases_version'), table_name='mjk_app_releases')
op.drop_table('mjk_app_releases')
# ### end Alembic commands ###