Spring Boot 3.2 / MyBatis-Plus / PostgreSQL,7 个 Maven 模块;含认证鉴权、内容与媒资管理、操作日志,以及客户端 /app/** 接口。
879 lines
39 KiB
Markdown
879 lines
39 KiB
Markdown
# ReelShort 短剧服务 · 后端架构设计
|
||
|
||
> 技术栈:JDK 21(LTS,编译目标 Java 17)+ Spring Boot 3.2 + PostgreSQL 18 + Redis
|
||
>
|
||
> 实现说明:项目已从 MySQL 切换到 PostgreSQL。本文档第 5 章的 DDL 仍按 MySQL 语法书写,
|
||
> 实际建表语句见 `sql/schema.sql`(PostgreSQL 版),两者的字段与索引一一对应;
|
||
> 主要差异是自增列用 IDENTITY、`updated_at` 用触发器维护、索引独立创建。
|
||
> 面向两端:管理后台(ReelShortWeb / Vue3)、客户端(ReelShort / Flutter)
|
||
> 版本:v1(2026-10-14)
|
||
|
||
---
|
||
|
||
## 1. 背景与目标
|
||
|
||
### 1.1 现状
|
||
|
||
| 端 | 目录 | 技术 | 状态 |
|
||
| --- | --- | --- | --- |
|
||
| 管理后台 | `ReelShort/ReelShortWeb` | Vue3 + Vite + element-plus-admin | 页面完成,数据为前端 mock |
|
||
| 客户端 | `ReelShort/ReelShort` | Flutter | 页面完成,数据为本地演示数据 |
|
||
| 服务端 | `FruitServer` | Spring Boot 3 + MyBatis-Plus + PostgreSQL | 已实现 |
|
||
|
||
两端的领域模型已经落在代码里,后端要按它们已有的字段和交互来设计,而不是重新发明一套:
|
||
|
||
- 后台:短剧(`title / genre / status / episodes / views / updatedAt / description / cover`)、剧集(`dramaId / number / title / duration / status / views`)、角色、角色分类、权限点、菜单、管理员、系统设置。
|
||
- 客户端:短剧目录、剧集列表(`isFree` 决定能否播放)、收藏、点赞、评论、观看历史、金币、会员。
|
||
- 后台现在靠 mock 提供登录(`POST /mock/user/login`)与菜单路由树(`GET /mock/role/list`);统一响应壳是 `{ code: 0, message, data }`,鉴权头是 `Authorization`,`code === 401` 触发登出。后端必须能一比一替换掉这两个 mock。
|
||
|
||
### 1.2 设计目标
|
||
|
||
1. 一套服务同时支撑 B 端(管理后台)与 C 端(客户端),共用内容域数据。
|
||
2. 先做模块化单体,边界清晰,规模上来后可按模块拆分,不做超前微服务化。
|
||
3. 内容生产链路(上传 → 转码 → 审核 → 发布)与播放链路(鉴权 → 签名地址 → 防盗链)是两条主线,一次设计到位。
|
||
4. 变现能力(金币 / 会员 / 单集解锁 / 充值订单)从第一天就纳入领域模型,避免后期改表。
|
||
|
||
---
|
||
|
||
## 2. 总体架构
|
||
|
||
```
|
||
┌──────────────────┐ ┌──────────────────┐
|
||
│ Flutter 客户端 │ │ Vue 管理后台 │
|
||
└────────┬─────────┘ └────────┬─────────┘
|
||
│ /app/** │ /admin/**
|
||
└────────────┬──────────────┘
|
||
▼
|
||
┌───────────────┐
|
||
│ Nginx / SLB │ 路由、TLS、限流、静态资源
|
||
└───────┬───────┘
|
||
▼
|
||
┌─────────────────────────────┐
|
||
│ Spring Boot 应用(单体多模块)│
|
||
│ admin-api / app-api / job │
|
||
└───┬───────┬────────┬────────┘
|
||
│ │ │
|
||
┌─────▼──┐ ┌──▼───┐ ┌──▼─────────┐
|
||
│ PgSQL │ │Redis │ │ 对象存储+CDN │
|
||
└────────┘ └──────┘ └─────┬──────┘
|
||
│
|
||
┌────────▼────────┐
|
||
│ 转码 Worker(FFmpeg)│ 消费队列,产出 HLS/MP4
|
||
└─────────────────┘
|
||
```
|
||
|
||
分层原则:
|
||
|
||
- 接入层:`admin-api` 与 `app-api` 是两个独立入口,各自独立的鉴权过滤器、限流策略、参数校验,互不污染。
|
||
- 领域层:按业务域切模块(system / content / media / member / interaction / trade / stat),模块之间只通过 Service 接口调用,禁止跨模块直连 Mapper。
|
||
- 基础设施层:`framework` 收敛所有技术细节(安全、缓存、MyBatis、日志、OSS SDK),业务模块不直接依赖第三方 SDK。
|
||
|
||
---
|
||
|
||
## 3. 技术选型
|
||
|
||
| 领域 | 选型 | 说明 |
|
||
| --- | --- | --- |
|
||
| 语言/运行时 | JDK 21(LTS) | 编译目标 Java 17;Spring Boot 3 要求 17+ |
|
||
| 框架 | Spring Boot 3.2.x + Spring MVC | 前后端分离,纯 REST |
|
||
| 持久层 | MyBatis-Plus 3.5.x | 后台 CRUD 密集,代码生成 + 分页插件省事 |
|
||
| 数据库 | PostgreSQL 18(UTF8) | 逻辑删除 + 乐观锁字段统一规范;DDL 见 sql/schema.sql |
|
||
| 缓存 | Redis 7 | 登录态、验证码、热点目录、播放签名、分布式锁 |
|
||
| 鉴权 | Spring Security + JWT(jjwt) | B 端与 C 端共用签发逻辑,各自独立过滤器 |
|
||
| 对象存储 | 阿里云 OSS / S3 兼容(本地用 MinIO) | 视频与封面,分片直传 |
|
||
| 转码 | FFmpeg + 异步队列 | 产出 HLS(多码率)+ MP4 兜底 + 首帧封面 |
|
||
| 消息队列 | RabbitMQ(起步) | 转码任务、订单超时、统计埋点;量级大再换 Kafka |
|
||
| 接口文档 | springdoc-openapi | 自动生成 OpenAPI 3 |
|
||
| 定时任务 | Spring Scheduling / XXL-Job | 定时发布、日报聚合、支付对账 |
|
||
| 构建 | Maven 多模块 | 见第 4 节 |
|
||
|
||
---
|
||
|
||
## 4. 工程结构(Maven 多模块)
|
||
|
||
```
|
||
java/
|
||
├── pom.xml # 聚合父 POM,统一依赖版本
|
||
├── reelshort-common/ # 统一响应/异常/常量/枚举/工具
|
||
├── reelshort-framework/ # 安全、MyBatis、Redis、日志、限流、OSS、MQ 客户端
|
||
├── reelshort-module-system/ # 管理员、角色、权限、菜单、配置、操作日志
|
||
├── reelshort-module-content/ # 短剧、剧集、分类标签、审核发布
|
||
├── reelshort-module-media/ # 媒资上传、转码、播放地址签名
|
||
├── reelshort-module-member/ # C 端用户、登录、设备、资料
|
||
├── reelshort-module-interaction/ # 收藏、点赞、评论、观看历史
|
||
├── reelshort-module-trade/ # 金币、流水、解锁、订单、VIP
|
||
├── reelshort-module-stat/ # 数据看板、日聚合
|
||
├── reelshort-admin-api/ # B 端接口,/admin/**
|
||
├── reelshort-app-api/ # C 端接口,/app/**
|
||
└── reelshort-job/ # 定时任务与异步消费者
|
||
```
|
||
|
||
起步阶段若不想铺这么开,可先合并为 `reelshort-common` + `reelshort-server`(含 system/content/media)+ `reelshort-app-api`,但包结构仍按域划分(`com.reelshort.content.*`),后续拆模块零成本。
|
||
|
||
模块内部结构保持一致:
|
||
|
||
```
|
||
com.reelshort.content
|
||
├── controller/ # 仅 admin-api / app-api 暴露
|
||
├── service/ # 接口 + impl
|
||
├── dal/
|
||
│ ├── entity/ # 数据库实体
|
||
│ ├── mapper/ # MyBatis-Plus Mapper
|
||
│ └── redis/ # 该域的缓存操作
|
||
├── convert/ # entity <-> VO/DTO 转换(MapStruct)
|
||
└── api/ # 暴露给其它模块的 Service 接口
|
||
```
|
||
|
||
命名约定:
|
||
|
||
- 包名 `com.reelshort.<module>.<layer>`
|
||
- 实体 `Drama`、`DramaEpisode`、`AppUser`
|
||
- 请求对象 `XxxCreateReqVO` / `XxxUpdateReqVO` / `XxxPageReqVO`,响应对象 `XxxRespVO`
|
||
- 表名 `<域前缀>_<业务>`,如 `drama`、`drama_episode`、`sys_role`、`app_user`、`stat_drama_daily`
|
||
|
||
---
|
||
|
||
## 5. 领域模型与数据库设计
|
||
|
||
### 5.1 设计约定
|
||
|
||
- 主键统一 `bigint unsigned`;对外可选用雪花 ID,不暴露自增趋势。
|
||
- 所有表带 `created_at / updated_at`,业务表带 `deleted tinyint default 0`(逻辑删除)。
|
||
- 金额一律用 `int` 分或 `bigint` 微单位,禁用 float;金币用 `bigint`。
|
||
- 状态字段存英文码,中文只用于前端展示,不把界面文案写进数据库。后台现有文案映射:
|
||
|
||
| 前端展示 | 存储码 |
|
||
| --- | --- |
|
||
| 草稿 | `draft` |
|
||
| 审核中 / 待审核 | `reviewing` |
|
||
| 待发布 | `approved` |
|
||
| 已上线 | `published` |
|
||
| 已下线 | `offline` |
|
||
|
||
### 5.2 系统域(B 端权限)
|
||
|
||
```sql
|
||
CREATE TABLE sys_admin_user (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
username VARCHAR(64) NOT NULL COMMENT '登录名',
|
||
password VARCHAR(100) NOT NULL COMMENT 'BCrypt 哈希',
|
||
real_name VARCHAR(64) NOT NULL DEFAULT '',
|
||
mobile VARCHAR(20) NOT NULL DEFAULT '',
|
||
email VARCHAR(128) NOT NULL DEFAULT '',
|
||
avatar VARCHAR(255) NOT NULL DEFAULT '',
|
||
status TINYINT NOT NULL DEFAULT 1 COMMENT '1启用 0停用',
|
||
ip_whitelist VARCHAR(255) NOT NULL DEFAULT '' COMMENT '空=不限',
|
||
two_factor TINYINT NOT NULL DEFAULT 0 COMMENT '1已开启 0未开启',
|
||
last_login_at DATETIME(3) NULL,
|
||
last_login_ip VARCHAR(64) NOT NULL DEFAULT '',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||
deleted TINYINT NOT NULL DEFAULT 0,
|
||
UNIQUE KEY uk_username (username)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE sys_role_category (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
name VARCHAR(64) NOT NULL,
|
||
description VARCHAR(255) NOT NULL DEFAULT '',
|
||
sort INT NOT NULL DEFAULT 0,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||
deleted TINYINT NOT NULL DEFAULT 0,
|
||
UNIQUE KEY uk_name (name)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE sys_role (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
name VARCHAR(64) NOT NULL,
|
||
code VARCHAR(64) NOT NULL COMMENT '如 super_admin / content_operator',
|
||
category_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
level INT NOT NULL DEFAULT 9 COMMENT '越小权限越高',
|
||
description VARCHAR(255) NOT NULL DEFAULT '',
|
||
status TINYINT NOT NULL DEFAULT 1,
|
||
is_builtin TINYINT NOT NULL DEFAULT 0 COMMENT '内置角色不可删',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||
deleted TINYINT NOT NULL DEFAULT 0,
|
||
UNIQUE KEY uk_code (code)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE sys_admin_user_role (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
user_id BIGINT UNSIGNED NOT NULL,
|
||
role_id BIGINT UNSIGNED NOT NULL,
|
||
UNIQUE KEY uk_user_role (user_id, role_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE sys_permission (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
code VARCHAR(128) NOT NULL COMMENT '如 drama:create',
|
||
name VARCHAR(64) NOT NULL COMMENT '如 创建短剧',
|
||
group_name VARCHAR(64) NOT NULL COMMENT '如 短剧管理',
|
||
type VARCHAR(16) NOT NULL DEFAULT 'BUTTON' COMMENT 'MENU/BUTTON',
|
||
sort INT NOT NULL DEFAULT 0,
|
||
UNIQUE KEY uk_code (code)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE sys_menu (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
parent_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
title VARCHAR(64) NOT NULL,
|
||
route_path VARCHAR(128) NOT NULL DEFAULT '',
|
||
route_name VARCHAR(64) NOT NULL DEFAULT '',
|
||
component VARCHAR(255) NOT NULL DEFAULT '' COMMENT '如 views/contentManagement/DramaManagementView',
|
||
icon VARCHAR(64) NOT NULL DEFAULT '',
|
||
permission_code VARCHAR(128) NOT NULL DEFAULT '',
|
||
type VARCHAR(16) NOT NULL DEFAULT 'MENU' COMMENT 'CATALOG/MENU/BUTTON',
|
||
sort INT NOT NULL DEFAULT 0,
|
||
visible TINYINT NOT NULL DEFAULT 1,
|
||
status TINYINT NOT NULL DEFAULT 1,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE sys_role_permission (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
role_id BIGINT UNSIGNED NOT NULL,
|
||
permission_id BIGINT UNSIGNED NOT NULL,
|
||
UNIQUE KEY uk_role_perm (role_id, permission_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE sys_role_menu (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
role_id BIGINT UNSIGNED NOT NULL,
|
||
menu_id BIGINT UNSIGNED NOT NULL,
|
||
UNIQUE KEY uk_role_menu (role_id, menu_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE sys_config (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
config_key VARCHAR(128) NOT NULL,
|
||
config_value VARCHAR(1024) NOT NULL DEFAULT '',
|
||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||
UNIQUE KEY uk_key (config_key)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE sys_operation_log (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
admin_user_id BIGINT UNSIGNED NOT NULL,
|
||
username VARCHAR(64) NOT NULL DEFAULT '',
|
||
module VARCHAR(64) NOT NULL DEFAULT '',
|
||
action VARCHAR(64) NOT NULL DEFAULT '',
|
||
target_type VARCHAR(64) NOT NULL DEFAULT '',
|
||
target_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
detail JSON NULL,
|
||
ip VARCHAR(64) NOT NULL DEFAULT '',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
KEY idx_admin_time (admin_user_id, created_at)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
### 5.3 内容域
|
||
|
||
```sql
|
||
CREATE TABLE drama_category (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
name VARCHAR(64) NOT NULL,
|
||
sort INT NOT NULL DEFAULT 0,
|
||
status TINYINT NOT NULL DEFAULT 1,
|
||
UNIQUE KEY uk_name (name)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE drama (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
code VARCHAR(64) NOT NULL DEFAULT '' COMMENT '对外编号',
|
||
title VARCHAR(128) NOT NULL,
|
||
subtitle VARCHAR(128) NOT NULL DEFAULT '',
|
||
category_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
genre VARCHAR(64) NOT NULL DEFAULT '' COMMENT '展示用题材',
|
||
synopsis VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '简介,对应前端 description',
|
||
cover_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '竖版封面',
|
||
horizontal_cover_url VARCHAR(512) NOT NULL DEFAULT '',
|
||
language VARCHAR(16) NOT NULL DEFAULT 'zh-CN',
|
||
total_episodes INT NOT NULL DEFAULT 0 COMMENT '冗余,由剧集同步',
|
||
free_episodes INT NOT NULL DEFAULT 3 COMMENT '前 N 集免费',
|
||
unlock_coin INT NOT NULL DEFAULT 0 COMMENT '默认单集解锁金币',
|
||
status VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT 'draft/reviewing/approved/published/offline',
|
||
publish_at DATETIME(3) NULL COMMENT '定时上线时间',
|
||
published_at DATETIME(3) NULL,
|
||
view_count BIGINT NOT NULL DEFAULT 0 COMMENT '展示口径播放量',
|
||
play_count BIGINT NOT NULL DEFAULT 0,
|
||
favorite_count BIGINT NOT NULL DEFAULT 0,
|
||
like_count BIGINT NOT NULL DEFAULT 0,
|
||
sort INT NOT NULL DEFAULT 0,
|
||
create_by BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||
deleted TINYINT NOT NULL DEFAULT 0,
|
||
KEY idx_status_publish (status, publish_at),
|
||
KEY idx_category (category_id),
|
||
KEY idx_updated (updated_at)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE drama_episode (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
drama_id BIGINT UNSIGNED NOT NULL,
|
||
ep_no INT NOT NULL COMMENT '第几集,对应前端 number',
|
||
title VARCHAR(128) NOT NULL DEFAULT '',
|
||
duration_sec INT NOT NULL DEFAULT 0 COMMENT '前端展示 mm:ss,存秒',
|
||
cover_url VARCHAR(512) NOT NULL DEFAULT '',
|
||
video_asset_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '指向 media_asset',
|
||
is_free TINYINT NOT NULL DEFAULT 0,
|
||
unlock_coin INT NOT NULL DEFAULT 0,
|
||
status VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT 'draft/approved/published/offline',
|
||
release_at DATETIME(3) NULL COMMENT '预约/定时发布',
|
||
play_count BIGINT NOT NULL DEFAULT 0,
|
||
sort INT NOT NULL DEFAULT 0,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||
deleted TINYINT NOT NULL DEFAULT 0,
|
||
UNIQUE KEY uk_drama_ep (drama_id, ep_no),
|
||
KEY idx_drama_status (drama_id, status)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE drama_review_record (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
biz_type VARCHAR(16) NOT NULL COMMENT 'DRAMA/EPISODE',
|
||
biz_id BIGINT UNSIGNED NOT NULL,
|
||
action VARCHAR(16) NOT NULL COMMENT 'SUBMIT/APPROVE/REJECT/PUBLISH/OFFLINE',
|
||
reason VARCHAR(500) NOT NULL DEFAULT '',
|
||
operator_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
operator_name VARCHAR(64) NOT NULL DEFAULT '',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
KEY idx_biz (biz_type, biz_id, created_at)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
### 5.4 媒资域
|
||
|
||
```sql
|
||
CREATE TABLE media_asset (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
biz_type VARCHAR(16) NOT NULL COMMENT 'EPISODE/DRAMA_COVER/AVATAR',
|
||
file_name VARCHAR(255) NOT NULL DEFAULT '',
|
||
object_key VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'OSS 原始文件 key',
|
||
source_url VARCHAR(512) NOT NULL DEFAULT '',
|
||
size_bytes BIGINT NOT NULL DEFAULT 0,
|
||
md5 VARCHAR(64) NOT NULL DEFAULT '',
|
||
duration_sec INT NOT NULL DEFAULT 0,
|
||
width INT NOT NULL DEFAULT 0,
|
||
height INT NOT NULL DEFAULT 0,
|
||
status VARCHAR(16) NOT NULL DEFAULT 'uploading'
|
||
COMMENT 'uploading/uploaded/transcoding/ready/failed',
|
||
hls_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'm3u8 播放地址',
|
||
mp4_url VARCHAR(512) NOT NULL DEFAULT '',
|
||
poster_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '首帧封面',
|
||
create_by BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||
deleted TINYINT NOT NULL DEFAULT 0,
|
||
KEY idx_status (status)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE transcode_task (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
asset_id BIGINT UNSIGNED NOT NULL,
|
||
spec VARCHAR(32) NOT NULL DEFAULT '720p' COMMENT '360p/540p/720p/1080p',
|
||
status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/running/success/failed',
|
||
progress INT NOT NULL DEFAULT 0,
|
||
error_msg VARCHAR(500) NOT NULL DEFAULT '',
|
||
started_at DATETIME(3) NULL,
|
||
finished_at DATETIME(3) NULL,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
KEY idx_asset (asset_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
### 5.5 会员与互动域(C 端)
|
||
|
||
```sql
|
||
CREATE TABLE app_user (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
nickname VARCHAR(64) NOT NULL DEFAULT '',
|
||
avatar VARCHAR(512) NOT NULL DEFAULT '',
|
||
mobile VARCHAR(20) NOT NULL DEFAULT '',
|
||
email VARCHAR(128) NOT NULL DEFAULT '',
|
||
password VARCHAR(100) NOT NULL DEFAULT '' COMMENT '可空,优先验证码登录',
|
||
gender VARCHAR(16) NOT NULL DEFAULT 'unknown',
|
||
birthday DATE NULL,
|
||
register_channel VARCHAR(32) NOT NULL DEFAULT 'app',
|
||
status TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 0封禁',
|
||
vip_expire_at DATETIME(3) NULL COMMENT '冗余,便于查询',
|
||
last_login_at DATETIME(3) NULL,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||
deleted TINYINT NOT NULL DEFAULT 0,
|
||
UNIQUE KEY uk_mobile (mobile)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE user_watch_history (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
user_id BIGINT UNSIGNED NOT NULL,
|
||
drama_id BIGINT UNSIGNED NOT NULL,
|
||
episode_id BIGINT UNSIGNED NOT NULL,
|
||
position_sec INT NOT NULL DEFAULT 0,
|
||
duration_sec INT NOT NULL DEFAULT 0,
|
||
finished TINYINT NOT NULL DEFAULT 0,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uk_user_episode (user_id, episode_id),
|
||
KEY idx_user_time (user_id, updated_at)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE user_favorite (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
user_id BIGINT UNSIGNED NOT NULL,
|
||
drama_id BIGINT UNSIGNED NOT NULL,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uk_user_drama (user_id, drama_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE user_like (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
user_id BIGINT UNSIGNED NOT NULL,
|
||
drama_id BIGINT UNSIGNED NOT NULL,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uk_user_drama (user_id, drama_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE drama_comment (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
drama_id BIGINT UNSIGNED NOT NULL,
|
||
episode_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
user_id BIGINT UNSIGNED NOT NULL,
|
||
parent_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
content VARCHAR(500) NOT NULL,
|
||
like_count INT NOT NULL DEFAULT 0,
|
||
status TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 0隐藏',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
deleted TINYINT NOT NULL DEFAULT 0,
|
||
KEY idx_drama_time (drama_id, created_at)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
### 5.6 交易域(金币 / 会员 / 解锁)
|
||
|
||
```sql
|
||
CREATE TABLE user_coin_account (
|
||
user_id BIGINT UNSIGNED PRIMARY KEY,
|
||
balance BIGINT NOT NULL DEFAULT 0,
|
||
total_recharge BIGINT NOT NULL DEFAULT 0,
|
||
total_consume BIGINT NOT NULL DEFAULT 0,
|
||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁',
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE coin_transaction (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
user_id BIGINT UNSIGNED NOT NULL,
|
||
type VARCHAR(16) NOT NULL COMMENT 'RECHARGE/UNLOCK/REFUND/GIFT/ADJUST',
|
||
amount BIGINT NOT NULL COMMENT '正数入账,负数出账',
|
||
balance_after BIGINT NOT NULL,
|
||
biz_type VARCHAR(32) NOT NULL DEFAULT '',
|
||
biz_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
idempotent_key VARCHAR(128) NOT NULL DEFAULT '',
|
||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uk_idempotent (idempotent_key),
|
||
KEY idx_user_time (user_id, created_at)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE episode_unlock (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
user_id BIGINT UNSIGNED NOT NULL,
|
||
drama_id BIGINT UNSIGNED NOT NULL,
|
||
episode_id BIGINT UNSIGNED NOT NULL,
|
||
unlock_type VARCHAR(16) NOT NULL COMMENT 'COIN/VIP',
|
||
coin_cost BIGINT NOT NULL DEFAULT 0,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uk_user_episode (user_id, episode_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE vip_plan (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
name VARCHAR(64) NOT NULL,
|
||
days INT NOT NULL,
|
||
price_cent INT NOT NULL,
|
||
original_price_cent INT NOT NULL DEFAULT 0,
|
||
status TINYINT NOT NULL DEFAULT 1,
|
||
sort INT NOT NULL DEFAULT 0
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE app_order (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
order_no VARCHAR(64) NOT NULL,
|
||
user_id BIGINT UNSIGNED NOT NULL,
|
||
biz_type VARCHAR(16) NOT NULL COMMENT 'RECHARGE/VIP',
|
||
product_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||
subject VARCHAR(128) NOT NULL DEFAULT '',
|
||
amount_cent INT NOT NULL,
|
||
coin_amount BIGINT NOT NULL DEFAULT 0,
|
||
channel VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'alipay/wechat/apple/stripe',
|
||
channel_trade_no VARCHAR(128) NOT NULL DEFAULT '',
|
||
status VARCHAR(16) NOT NULL DEFAULT 'CREATED' COMMENT 'CREATED/PAID/CLOSED/REFUNDED',
|
||
paid_at DATETIME(3) NULL,
|
||
expire_at DATETIME(3) NULL COMMENT '超时关单时间',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uk_order_no (order_no),
|
||
UNIQUE KEY uk_channel_trade (channel, channel_trade_no),
|
||
KEY idx_user_status (user_id, status)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE vip_membership (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
user_id BIGINT UNSIGNED NOT NULL,
|
||
plan_id BIGINT UNSIGNED NOT NULL,
|
||
order_id BIGINT UNSIGNED NOT NULL,
|
||
start_at DATETIME(3) NOT NULL,
|
||
expire_at DATETIME(3) NOT NULL,
|
||
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
KEY idx_user (user_id, expire_at)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
### 5.7 统计域
|
||
|
||
```sql
|
||
CREATE TABLE stat_drama_daily (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
stat_date DATE NOT NULL,
|
||
drama_id BIGINT UNSIGNED NOT NULL,
|
||
play_count BIGINT NOT NULL DEFAULT 0,
|
||
uv BIGINT NOT NULL DEFAULT 0,
|
||
new_favorite BIGINT NOT NULL DEFAULT 0,
|
||
unlock_count BIGINT NOT NULL DEFAULT 0,
|
||
coin_income BIGINT NOT NULL DEFAULT 0,
|
||
vip_income_cent BIGINT NOT NULL DEFAULT 0,
|
||
UNIQUE KEY uk_date_drama (stat_date, drama_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
CREATE TABLE stat_episode_daily (
|
||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
stat_date DATE NOT NULL,
|
||
episode_id BIGINT UNSIGNED NOT NULL,
|
||
drama_id BIGINT UNSIGNED NOT NULL,
|
||
play_count BIGINT NOT NULL DEFAULT 0,
|
||
uv BIGINT NOT NULL DEFAULT 0,
|
||
avg_watch_sec INT NOT NULL DEFAULT 0,
|
||
UNIQUE KEY uk_date_episode (stat_date, episode_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 接口设计
|
||
|
||
### 6.1 统一约定
|
||
|
||
- 基础路径:B 端 `/admin/**`,C 端 `/app/**`。
|
||
- 响应壳(与后台 axios 拦截器一致):`{ "code": 0, "message": "ok", "data": ... }`;失败 `code != 0`;未登录 `code = 401`。
|
||
- 分页:请求 `pageIndex`(从 1 开始)、`pageSize`;响应 `{ "total": n, "list": [...] }`。
|
||
- 鉴权:请求头 `Authorization: Bearer <token>`;B 端与 C 端 token 独立签发、独立校验。
|
||
- 时间:`yyyy-MM-dd HH:mm:ss` 或毫秒时间戳,全项目统一一种。
|
||
|
||
### 6.2 B 端接口
|
||
|
||
必须与现有 mock 结构兼容的两个接口:
|
||
|
||
| 方法 | 路径 | 说明 | 兼容要求 |
|
||
| --- | --- | --- | --- |
|
||
| POST | `/admin/auth/login` | 管理员登录 | 返回 `data` 至少含 `username / role / roleId / token`,字段名与 `mock/user/index.mock.ts` 一致;`permissions` 换为真实权限码数组 |
|
||
| GET | `/admin/auth/menus` | 当前用户菜单路由树 | 结构与 `mock/role/index.mock.ts` 的 `adminList` 完全一致(`path / component / name / meta.title / meta.icon`),`component` 仍是前端组件字符串 |
|
||
|
||
其余 B 端接口:
|
||
|
||
| 模块 | 接口 |
|
||
| --- | --- |
|
||
| 认证 | `POST /admin/auth/login`、`POST /admin/auth/logout`、`GET /admin/auth/profile`、`POST /admin/auth/password` |
|
||
| 管理员 | `GET /admin/admin-users/page`、`POST /admin/admin-users`、`PUT /admin/admin-users/{id}`、`DELETE /admin/admin-users/{id}`、`PUT /admin/admin-users/{id}/status`、`PUT /admin/admin-users/{id}/password` |
|
||
| 角色 | `GET /admin/roles/page`、`POST /admin/roles`、`PUT /admin/roles/{id}`、`DELETE /admin/roles/{id}`、`GET /admin/roles/{id}/permissions`、`PUT /admin/roles/{id}/permissions` |
|
||
| 角色分类 | `GET /admin/role-categories`、`POST /admin/role-categories`、`PUT /admin/role-categories/{id}`、`DELETE /admin/role-categories/{id}` |
|
||
| 权限点 | `GET /admin/permissions`(按 group 分组,直接喂权限配置页) |
|
||
| 菜单 | `GET /admin/menus/tree`、`POST /admin/menus`、`PUT /admin/menus/{id}`、`DELETE /admin/menus/{id}` |
|
||
| 短剧 | `GET /admin/dramas/page`、`GET /admin/dramas/{id}`、`POST /admin/dramas`、`PUT /admin/dramas/{id}`、`DELETE /admin/dramas/{id}`、`PUT /admin/dramas/{id}/status` |
|
||
| 剧集 | `GET /admin/dramas/{dramaId}/episodes`、`POST /admin/episodes`、`PUT /admin/episodes/{id}`、`DELETE /admin/episodes/{id}`、`PUT /admin/episodes/sort` |
|
||
| 审核 | `POST /admin/dramas/{id}/submit`、`/approve`、`/reject`、`/publish`、`/offline` |
|
||
| 媒资 | `POST /admin/media/upload-token`(分片直传凭证)、`POST /admin/media/upload`(小文件)、`GET /admin/media/{id}`(转码进度) |
|
||
| 字典 | `GET /admin/dicts/{type}`(题材、状态等) |
|
||
| 设置 | `GET /admin/configs`、`PUT /admin/configs` |
|
||
| 统计 | `GET /admin/stat/overview`、`GET /admin/stat/dramas`、`GET /admin/stat/trend` |
|
||
|
||
短剧列表响应示例(字段名对齐前端 `src/data/dramas.js`,前端只需换数据源):
|
||
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"title": "替嫁新娘:总裁的掌心宠",
|
||
"genre": "都市情感",
|
||
"status": "published",
|
||
"statusText": "已上线",
|
||
"episodes": 68,
|
||
"views": 2400000,
|
||
"cover": "https://cdn.example.com/drama/1/cover.jpg",
|
||
"description": "一场意外替嫁,让她成为冷面总裁的契约新娘。",
|
||
"updatedAt": "2025-06-12 10:00:00"
|
||
}
|
||
```
|
||
|
||
### 6.3 C 端接口
|
||
|
||
| 模块 | 接口 | 说明 |
|
||
| --- | --- | --- |
|
||
| 认证 | `POST /app/auth/sms-code` | 发送验证码(Redis 限流防刷) |
|
||
| | `POST /app/auth/login` | 手机号+验证码 / 邮箱+密码,返回 `accessToken`、`refreshToken`、用户资料 |
|
||
| | `POST /app/auth/refresh` | 刷新 token |
|
||
| | `POST /app/auth/logout` | 注销当前设备 |
|
||
| 用户 | `GET /app/user/profile`、`PUT /app/user/profile`、`POST /app/user/avatar` | 对应客户端「我的」页 |
|
||
| 目录 | `GET /app/home/feed` | 首页分区流(推荐、热播、新上线) |
|
||
| | `GET /app/discover/sections` | 发现页分区/轮播 |
|
||
| | `GET /app/categories` | 题材分类 |
|
||
| | `GET /app/dramas/{id}` | 详情:简介、总集数、免费集数、收藏/点赞数、当前用户是否收藏/点赞 |
|
||
| | `GET /app/dramas/{id}/episodes` | 剧集列表(含 `isFree`、`locked`、`unlockCoin`) |
|
||
| 播放 | `POST /app/episodes/{id}/play` | 播放鉴权,返回签名播放地址与续播位置 |
|
||
| | `POST /app/history` | 上报观看进度(播放中每 10~15s 节流上报一次) |
|
||
| | `GET /app/history` | 观看历史列表 |
|
||
| 互动 | `GET/POST /app/dramas/{id}/favorites`、`GET/POST /app/dramas/{id}/likes`、`GET/POST /app/dramas/{id}/comments`、`POST /app/comments/{id}/like`、`DELETE /app/comments/{id}` | 对应 `savedDramaIds / likedDramaIds / commentsByDrama` |
|
||
| 钱包 | `GET /app/wallet` | 金币余额、会员状态、到期时间 |
|
||
| | `GET /app/wallet/plans` | 充值档位与 VIP 套餐 |
|
||
| | `POST /app/orders` | 创建充值/会员订单 |
|
||
| | `POST /app/pay/callback/{channel}` | 支付回调(公开接口,验签 + 幂等) |
|
||
| 解锁 | `POST /app/episodes/{id}/unlock` | 金币解锁单集;VIP 用户直接放行 |
|
||
| 其他 | `GET /app/configs` | 客户端启动配置(公告、分享文案、开关) |
|
||
|
||
播放接口响应示例:
|
||
|
||
```json
|
||
{
|
||
"episodeId": 101,
|
||
"dramaId": 1,
|
||
"epNo": 4,
|
||
"duration": 138,
|
||
"accessible": true,
|
||
"accessType": "FREE",
|
||
"playUrl": "https://cdn.example.com/hls/1/101/index.m3u8?auth_key=...&expires=1730000000",
|
||
"posterUrl": "https://cdn.example.com/hls/1/101/poster.jpg",
|
||
"resumePosition": 42
|
||
}
|
||
```
|
||
|
||
`accessible = false` 时返回 `accessType = COIN_REQUIRED | VIP_REQUIRED` 与 `unlockCoin`,客户端据此弹出解锁或开通会员面板。
|
||
|
||
---
|
||
|
||
## 7. 关键流程
|
||
|
||
### 7.1 内容生产(后台)
|
||
|
||
```
|
||
创建短剧草稿 → 上传封面 → 批量上传剧集视频(分片直传 OSS)
|
||
→ 发布转码任务(MQ 异步) → FFmpeg 产出 HLS/封面 → 回调更新 media_asset
|
||
→ 填写剧集信息(集号、时长、免费标记、解锁金币)
|
||
→ 提交审核(draft → reviewing) → 审核通过(reviewing → approved)
|
||
→ 定时或手动发布(approved → published,publish_at 到点由 job 自动上线)
|
||
→ 客户端目录可见
|
||
```
|
||
|
||
- 转码异步化,后台轮询 `GET /admin/media/{id}` 拿进度即可,不用长连接。
|
||
- 审核、发布、下线三个动作全部写 `drama_review_record` 与 `sys_operation_log`,可追溯。
|
||
|
||
### 7.2 播放链路(客户端)
|
||
|
||
```
|
||
客户端请求 POST /app/episodes/{id}/play
|
||
→ 校验登录态与用户状态
|
||
→ 判定可见性:剧 / 集是否 published
|
||
→ 判定播放权:is_free 命中,或 episode_unlock 命中,或 VIP 有效
|
||
→ 有权:生成带签名的 CDN 地址(有效期 10~30 分钟)→ 返回 playUrl + 续播位置
|
||
→ 无权:返回 COIN_REQUIRED / VIP_REQUIRED,客户端弹窗引导
|
||
```
|
||
|
||
防盗链三条措施:签名地址短时效、CDN Referer/UA 白名单、HLS 分片加密(AES-128,密钥接口同样鉴权)。
|
||
|
||
### 7.3 解锁扣费(幂等)
|
||
|
||
```
|
||
POST /app/episodes/{id}/unlock
|
||
→ 幂等键 user_id + episode_id:先查 episode_unlock,命中直接返回成功
|
||
→ Redis 分布式锁 lock:unlock:{userId}:{episodeId},挡住连点重复扣费
|
||
→ 一个事务内:扣 user_coin_account(乐观锁 version 校验)
|
||
→ 写 coin_transaction → 写 episode_unlock
|
||
→ 返回最新余额
|
||
```
|
||
|
||
余额不足返回明确业务码,客户端直接跳充值页。
|
||
|
||
### 7.4 支付回调
|
||
|
||
```
|
||
POST /app/pay/callback/{channel}
|
||
→ 验签 → 幂等(channel + channel_trade_no 唯一索引)
|
||
→ 一个事务内:订单置为 PAID → 金币入账(写流水)或开通/续期 VIP
|
||
→ 更新 app_user.vip_expire_at
|
||
→ 返回渠道要求的成功应答
|
||
```
|
||
|
||
配套对账任务:定时拉取渠道账单与本地订单比对,处理「渠道成功、本地漏回调」。
|
||
|
||
---
|
||
|
||
## 8. 非功能设计
|
||
|
||
### 8.1 鉴权与权限
|
||
|
||
- B 端:JWT 载荷含 `userId / username / roleId`,过滤器解析后写入 `SecurityContext`;权限校验用注解 `@PreAuthorize("@ss.hasPermission('drama:create')")`,权限码风格与前端 `src/permissions/permissions.js` 对齐。
|
||
- C 端:`accessToken` 短时效(2h)+ `refreshToken` 长时效(30d),存 Redis 支持主动踢下线;键设计 `user:token:{userId}:{deviceId}`。
|
||
- 后台高危操作(删除、发布、改权限)二次校验密码并写 `sys_operation_log`;`two_factor` 字段已在表结构预留。
|
||
|
||
### 8.2 缓存策略
|
||
|
||
| 数据 | Key | 策略 |
|
||
| --- | --- | --- |
|
||
| 首页/发现页分区 | `cache:home:feed` | 5 分钟过期,发布/上线/下线时主动失效 |
|
||
| 短剧详情 | `cache:drama:{id}` | 10 分钟 + 主动失效 |
|
||
| 分类字典 | `cache:dict:category` | 1 小时 |
|
||
| 后台权限码 | `cache:perm:role:{roleId}` | 30 分钟,改角色权限时失效 |
|
||
| 验证码 | `sms:code:{mobile}` | 5 分钟,发送限流 1 次/分、5 次/天 |
|
||
| 播放签名 | —— | 不缓存,实时生成 |
|
||
|
||
热点 Key 加随机后缀防雪崩;空结果缓存短空值防穿透。
|
||
|
||
### 8.3 限流与防刷
|
||
|
||
- 接口级限流(Redis 令牌桶):登录、验证码、评论、解锁扣费。
|
||
- 播放量与观看时长上报去重(`user_id + episode_id + 时间窗`),防止刷量污染统计。
|
||
- 评论接入敏感词过滤,可扩展到第三方内容安全审核。
|
||
- 客户端接口做版本兼容(`X-App-Version`),避免旧版本升级后崩溃。
|
||
|
||
### 8.4 数据量与扩展
|
||
|
||
- 增长最快的表:`user_watch_history`、`drama_comment`、`coin_transaction`、`stat_*_daily`。
|
||
- 单表超 2000 万行或写入明显变慢时,按 `user_id` 取模分表(ShardingSphere),对业务代码透明。
|
||
- `stat_*_daily` 属聚合表,量级可控,用分区表即可。
|
||
- 图片与视频全部走 CDN,应用服务器不落盘媒体文件。
|
||
|
||
### 8.5 可观测与运维
|
||
|
||
- 日志:JSON 结构化,traceId 贯穿全链路;后台敏感操作另写 `sys_operation_log`。
|
||
- 监控:接口 P99、错误率、转码队列积压、支付回调失败率、Redis 命中率。
|
||
- 健康检查 `/actuator/health` 暴露给负载均衡。
|
||
- 环境:dev / test / prod 三套配置,密钥通过环境变量注入,不写进仓库。
|
||
|
||
---
|
||
|
||
## 9. 部署与演进
|
||
|
||
起步形态(单体):
|
||
|
||
```
|
||
Nginx
|
||
├── reelshort-admin-api (/admin)
|
||
├── reelshort-app-api (/app)
|
||
└── reelshort-job (定时任务、异步消费)
|
||
PostgreSQL(主从) / Redis / OSS(或 MinIO) / 转码 Worker(独立容器,可多实例水平扩容)
|
||
```
|
||
|
||
Docker Compose 足够跑通全链路,上线后按量升级到 K8s。
|
||
|
||
演进路径:单体多模块 → 转码与统计独立进程 → 读写分离 → 内容域/交易域拆为独立服务 → 网关统一入口。拆分只在某模块成为独立瓶颈或有独立迭代节奏时进行,不为拆而拆。
|
||
|
||
---
|
||
|
||
## 10. 与现有前端的对接改造点
|
||
|
||
### 10.1 管理后台(ReelShortWeb)
|
||
|
||
1. `.env.*`:`VITE_USE_MOCK=false`,`VITE_API_BASE_PATH` 指向后端地址。
|
||
2. 登录:`src/api/login/index.ts` 的 `/mock/user/login` → `/admin/auth/login`,返回体保持 `{ username, role, roleId, token, permissions }`,前端改动最小。
|
||
3. 菜单:`/mock/role/list` → `/admin/auth/menus`,返回同样的路由树结构,`src/router/index.ts` 无需改动。
|
||
4. 业务数据源替换:
|
||
- `src/data/dramas.js` → `GET /admin/dramas/page`
|
||
- `src/data/episodes.js` → `GET /admin/dramas/{dramaId}/episodes`
|
||
- `src/data/roles.js` → `GET /admin/roles/page`
|
||
- `src/views/userManagement/data/adminUsers.js` → `GET /admin/admin-users/page`
|
||
- `src/data/customMenus.js`(localStorage)→ `GET/POST /admin/menus`
|
||
- `src/stores/rbac.js` 的本地权限表 → 登录返回的 `permissions` + `GET /admin/permissions`
|
||
5. 展示文案(已上线/审核中/草稿)改由 `status` 码映射,`src/utils/status.js` 可逐步改用后端返回的 `statusText`。
|
||
|
||
### 10.2 客户端(ReelShort / Flutter)
|
||
|
||
1. `DemoCatalogRepository` → HTTP 实现,对接 `/app/home/feed`、`/app/dramas/{id}`、`/app/dramas/{id}/episodes`。
|
||
2. `SharedPreferencesEngagementRepository`(收藏/点赞/评论本地存储)→ HTTP 实现,`EngagementSnapshot` 变为服务端快照。
|
||
3. 观看历史本地存储 → `/app/history`;登录后做一次本地数据合并上传。
|
||
4. `Episode.isFree` 与 `PlaybackSession.canPlay` → 由 `/play` 返回的 `accessible / accessType` 驱动。
|
||
5. 「我的」页金币与会员(当前是 `$ 0` 假数据)→ `/app/wallet`。
|
||
6. 新增登录页,token 用 `flutter_secure_storage` 保存,`refreshToken` 自动续期。
|
||
|
||
---
|
||
|
||
## 11. 迭代建议
|
||
|
||
**M1 · 打通后台**
|
||
系统域(登录、RBAC、菜单、管理员)+ 内容域(短剧/剧集 CRUD)+ 媒资上传(先整文件上传,转码后置)+ 统一响应与鉴权框架。目标:后台 mock 全部替换为真实接口。
|
||
|
||
**M2 · 打通客户端**
|
||
C 端登录、目录/详情/剧集、观看历史、收藏点赞评论、播放鉴权与签名地址。目标:客户端真实播放,前端演示仓库全部退役。
|
||
|
||
**M3 · 变现与质量**
|
||
转码流水线(HLS 多码率)、金币与会员、单集解锁、支付与对账、后台数据看板、限流防刷、监控告警。
|
||
|
||
**M4 · 规模化**
|
||
读写分离、热点缓存优化、大表分表、内容安全、多语言内容(`drama.language` 已预留)。
|
||
|
||
---
|
||
|
||
## 附录 A · 错误码规划
|
||
|
||
| 码 | 含义 |
|
||
| --- | --- |
|
||
| 0 | 成功 |
|
||
| 400 | 参数校验失败 |
|
||
| 401 | 未登录 / token 失效 |
|
||
| 403 | 无权限 |
|
||
| 404 | 资源不存在 |
|
||
| 429 | 请求过于频繁 |
|
||
| 500 | 系统异常 |
|
||
| 100001 | 金币余额不足 |
|
||
| 100002 | 剧集未发布或不存在 |
|
||
| 100003 | 剧集已解锁(幂等返回成功) |
|
||
| 100004 | 订单已支付或已关闭 |
|
||
| 100005 | VIP 已过期 |
|
||
|
||
## 附录 B · 配置骨架(application.yml)
|
||
|
||
```yaml
|
||
server:
|
||
port: 8080
|
||
|
||
spring:
|
||
datasource:
|
||
url: jdbc:postgresql://127.0.0.1:5432/reelshort
|
||
username: ${DB_USER:reelshort}
|
||
password: ${DB_PASSWORD:reelshort}
|
||
data:
|
||
redis:
|
||
host: 127.0.0.1
|
||
port: 6379
|
||
servlet:
|
||
multipart:
|
||
max-file-size: 2GB
|
||
max-request-size: 2GB
|
||
|
||
reelshort:
|
||
jwt:
|
||
admin-secret: ${JWT_ADMIN_SECRET}
|
||
app-secret: ${JWT_APP_SECRET}
|
||
app-access-ttl: 2h
|
||
app-refresh-ttl: 30d
|
||
oss:
|
||
endpoint: ${OSS_ENDPOINT}
|
||
bucket: ${OSS_BUCKET}
|
||
access-key: ${OSS_KEY}
|
||
secret-key: ${OSS_SECRET}
|
||
cdn:
|
||
play-domain: https://cdn.example.com
|
||
sign-ttl-seconds: 1800
|
||
```
|