初始化代码

This commit is contained in:
duhao 2026-09-20 11:26:24 +08:00
parent cec5498060
commit 303b60502e
30 changed files with 43 additions and 4555 deletions

View File

@ -1,7 +1,11 @@
# 构建上下文排除项,加速构建并避免敏感文件泄漏 # 构建上下文排除项,加速构建并避免敏感文件泄漏
# 只保留 Dockerfile COPY 需要的文件:Dockerfile、laop-admin.jar
target/ target/
!.mvn/wrapper/maven-wrapper.jar src/
.mvn/wrapper/maven-wrapper.properties pom.xml
.mvn/
mvnw
mvnw.cmd
# IDE & 系统 # IDE & 系统
.idea/ .idea/
@ -22,6 +26,3 @@ logs/
# 文档(不需要进入镜像) # 文档(不需要进入镜像)
*.md *.md
docs/ docs/
# 测试
src/test/

View File

@ -1,38 +1,26 @@
# ===================================================================== # =====================================================================
# 翼云Hub · LAOP-admin 后端镜像 Dockerfile # 翼云Hub · LAOP-admin 后端镜像 Dockerfile
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# 说明: # 设计:本地 mvn package 预先生成 jar,Dockerfile 只 COPY jar 进 JRE 镜像
# - 项目 pom.xml 指定 Java 17(source/target),编译期和运行期版本严格一致, # --------------------------------------------------------------------
# - jar 包最终名为 laop-admin.jar(由 pom.xml finalName 决定)。 # 为什么不用多阶段构建(FROM maven → mvn package → FROM jre)?
# - 多阶段构建在镜像内执行 mvn package,每次都要下载依赖,慢
# - 跨架构构建(Mac arm64 → 服务器 x86_64)需 QEMU 模拟,慢上加慢
# - Java 字节码与 CPU 架构无关,本地 Mac 编译的 jar 可直接跑在 x86_64 JRE 上
# 改进后:
# - 本地 mvn package 秒级完成(原生速度,无跨架构)
# - Dockerfile 只 COPY jar,构建瞬间完成
# - 不需要 QEMU,不需要基础镜像固化依赖
# --------------------------------------------------------------------
# 前置:本地执行过 mvn clean package -DskipTests -Pprod
# 生成 target/laop-admin.jar
# 脚本 build-and-push.sh 会自动完成这步并复制到 build context
# ===================================================================== # =====================================================================
# -------------------------------------------------------------------
# 阶段1:构建期(maven + jdk17,与 pom.xml 严格对齐)
# -------------------------------------------------------------------
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
# 先单独 COPY pom.xml,利用 Docker 缓存层加速依赖下载
COPY pom.xml ./
# 预下载依赖(仅当 pom 变化时重新执行,提升后续构建速度)
RUN mvn dependency:go-offline -B
# 复制源码
COPY src ./src
# 打包:激活 prod profile,让 application.yml 中的 @spring.profiles.active@ 替换为 prod
# -DskipTests:构建镜像时跳过测试,测试应在 CI 流水线单独执行
RUN mvn clean package -DskipTests -Pprod
# -------------------------------------------------------------------
# 阶段2:运行期(JRE 17 LTS)
# -------------------------------------------------------------------
FROM eclipse-temurin:17-jre-jammy FROM eclipse-temurin:17-jre-jammy
LABEL maintainer="laop-admin" LABEL maintainer="laop-admin"
LABEL description="翼云Hub · 无人机信息开放平台 后端服务" LABEL description="无人机信息开放平台 后端服务"
WORKDIR /app WORKDIR /app
@ -47,16 +35,16 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN mkdir -p /data/static RUN mkdir -p /data/static
ENV LAOP_STATIC_DIR=/data/static ENV LAOP_STATIC_DIR=/data/static
# 拷贝构建产物 # 拷贝本地预编译好的 jar 包
COPY --from=builder /build/target/laop-admin.jar app.jar # 脚本 build-and-push.sh 在构建前会执行:
# cp target/laop-admin.jar ./laop-admin.jar
# 把 jar 复制到 build context 根目录(避免把整个 target/ 进入上下文)
COPY laop-admin.jar app.jar
# 容器内暴露端口(与 application.yml server.port=8081 一致) # 容器内暴露端口(与 application.yml server.port=8081 一致)
EXPOSE 8081 EXPOSE 8081
# JVM 参数:容器内内存限制感知 + GC 优化(2核8G 服务器) # JVM 参数:容器内内存限制感知 + GC 优化(2核8G 服务器)
# -XX:+UseContainerSupport:让 JVM 感知 cgroup 限制(JDK10+ 默认开启,显式声明)
# -XX:MaxRAMPercentage=75.0:限制堆内存占容器内存 75%,给非堆留空间
# -Djava.security.egd=file:/dev/./urandom:加速 SecureRandom 启动
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom" ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom"
# 运行:显式指定 prod profile,覆盖任何默认配置 # 运行:显式指定 prod profile,覆盖任何默认配置

View File

@ -65,12 +65,6 @@
<version>${redisson.version}</version> <version>${redisson.version}</version>
</dependency> </dependency>
<!-- Flyway:Spring Boot 3.2 自带 9.22.x,无需显式声明版本 -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>

View File

@ -22,7 +22,7 @@
-- 下级街道/镇在数据源中为 9 位编码, 违反"6 位编码"规约, 按民政部 -- 下级街道/镇在数据源中为 9 位编码, 违反"6 位编码"规约, 按民政部
-- 公布口径仅保留市级一行, 不入库下级 -- 公布口径仅保留市级一行, 不入库下级
-- created_at / updated_at 由 DB DEFAULT 维护, deleted_at 默认 NULL, 均不显式写入 -- created_at / updated_at 由 DB DEFAULT 维护, deleted_at 默认 NULL, 均不显式写入
-- 幂等性: 由 flyway_schema_history 表保证本脚本仅执行一次; 若 region 表已有 -- 幂等性: 本脚本仅手动执行一次; 若 region 表已有
-- 历史数据需重置, 先 TRUNCATE region RESTART IDENTITY CASCADE 再重跑 -- 历史数据需重置, 先 TRUNCATE region RESTART IDENTITY CASCADE 再重跑
-- ============================================================ -- ============================================================

View File

@ -1,14 +1,14 @@
-- ============================================================ -- ============================================================
-- 翼云Hub · 数据库完整 DDL(整理版) -- 翼云Hub · 数据库完整 DDL(整理版)
-- ------------------------------------------------------------ -- ------------------------------------------------------------
-- 本文件合并了 V1 ~ V19 全部 Flyway 迁移的最终效果, -- 本文件合并了 V1 ~ V19 全部迁移 SQL 的最终效果,
-- 按依赖顺序从零建库,供新环境初始化使用。 -- 按依赖顺序从零建库,供新环境初始化使用。
-- --
-- 与 Flyway 迁移脚本的关系: -- 与迁移脚本(同目录 V*.sql)的关系:
-- 现有 V1 ~ V19 保持原样不动(已部署环境正在使用), -- 现有 V1 ~ V19 保持原样不动(已部署环境已按序执行过),
-- 新环境可选: -- 新环境可选:
-- a) 继续走 Flyway(V1~V19 依次执行)——与已部署环境一致 -- a) 按版本号顺序依次执行 V*.sql —— 与已部署环境一致
-- b) 直接执行本 schema.sql + data.sql(跳过 Flyway)——更快 -- b) 直接执行本 schema.sql + data.sql —— 更快(推荐新库用)
-- --
-- 目标数据库:PostgreSQL 14 / 16 -- 目标数据库:PostgreSQL 14 / 16
-- 字符集:UTF8(PG 默认) -- 字符集:UTF8(PG 默认)

View File

@ -13,7 +13,7 @@ import java.time.LocalDateTime;
/** /**
* 课程预约表,承载用户在课程详情页"立即预约"提交的预约信息。 * 课程预约表,承载用户在课程详情页"立即预约"提交的预约信息。
* *
* <p>唯一约束由 Flyway 迁移 V19 重建的部分唯一索引 * <p>唯一约束由迁移 SQL V19 重建的部分唯一索引
* {@code uk_course_appointment_active (user_id, course_id, appointment_date) * {@code uk_course_appointment_active (user_id, course_id, appointment_date)
* WHERE status != 2 AND deleted_at IS NULL}(2 = AppointmentStatusEnum.CANCELLED)保障, * WHERE status != 2 AND deleted_at IS NULL}(2 = AppointmentStatusEnum.CANCELLED)保障,
* 防止同一用户对同一课程同一时段重复提交有效预约;取消后允许重新预约。 * 防止同一用户对同一课程同一时段重复提交有效预约;取消后允许重新预约。

View File

@ -12,7 +12,7 @@ import jakarta.persistence.UniqueConstraint;
/** /**
* 省市区表,承载中国行政区划三级数据(省/市/区县)。 * 省市区表,承载中国行政区划三级数据(省/市/区县)。
* *
* <p>由 Flyway 启动时初始化预置数据(也可对接民政部区划接口定期同步)。 * <p>预置数据由迁移 SQL(sql/V2__seed_region.sql)手动初始化(也可对接民政部区划接口定期同步)。
* 业务侧所有涉及 LBS 的字段(如 {@link Pilot#getServiceCityCodes()})均以本表 {@code code} * 业务侧所有涉及 LBS 的字段(如 {@link Pilot#getServiceCityCodes()})均以本表 {@code code}
* 为值存储,多选用分号 {@code ;} 拼接,查询时通过程序侧 split + IN 或 SQL 模糊匹配筛选。</p> * 为值存储,多选用分号 {@code ;} 拼接,查询时通过程序侧 split + IN 或 SQL 模糊匹配筛选。</p>
* *

View File

@ -21,7 +21,7 @@ import java.util.List;
* *
* <p><b>职责边界:</b> * <p><b>职责边界:</b>
* <ul> * <ul>
* <li>配置项"只能改值"——key 由 Flyway 迁移脚本在部署时一次性 INSERT,不允许运行时新增/删除;</li> * <li>配置项"只能改值"——key 由迁移 SQL 脚本(sql/V10__app_config.sql)在部署时一次性 INSERT,不允许运行时新增/删除;</li>
* <li>修改时按配置键做类型校验(布尔开关只能 0/1,数值参数必须落在约定区间);</li> * <li>修改时按配置键做类型校验(布尔开关只能 0/1,数值参数必须落在约定区间);</li>
* <li>修改时自动刷新 updated_at 和 updated_by,保留操作审计能力。</li> * <li>修改时自动刷新 updated_at 和 updated_by,保留操作审计能力。</li>
* </ul> * </ul>

View File

@ -26,20 +26,19 @@ spring:
minimum-idle: 2 minimum-idle: 2
connection-timeout: 30000 connection-timeout: 30000
flyway:
enabled: false
data: data:
redis: redis:
host: 127.0.0.1 host: 127.0.0.1
port: 6379 port: 6379
database: 0 # 使用独立库 db2,与其他服务(默认 db0)数据隔离
database: 2
redisson: redisson:
config: | config: |
singleServerConfig: singleServerConfig:
address: "redis://127.0.0.1:6379" address: "redis://127.0.0.1:6379"
database: 0 # 与 spring.data.redis.database 保持一致:独立库 db2,与其他服务隔离
database: 2
connectionMinimumIdleSize: 4 connectionMinimumIdleSize: 4
connectionPoolSize: 16 connectionPoolSize: 16

View File

@ -23,14 +23,16 @@ spring:
redis: redis:
host: ${LAOP_REDIS_HOST} host: ${LAOP_REDIS_HOST}
port: ${LAOP_REDIS_PORT:6379} port: ${LAOP_REDIS_PORT:6379}
database: ${LAOP_REDIS_DB:0} # 独立库 db2,与其他服务(默认 db0)数据隔离;.env 可用 LAOP_REDIS_DB 覆盖
database: ${LAOP_REDIS_DB:2}
password: ${LAOP_REDIS_PASSWORD} password: ${LAOP_REDIS_PASSWORD}
redisson: redisson:
config: | config: |
singleServerConfig: singleServerConfig:
address: "redis://${LAOP_REDIS_HOST}:${LAOP_REDIS_PORT:6379}" address: "redis://${LAOP_REDIS_HOST}:${LAOP_REDIS_PORT:6379}"
database: ${LAOP_REDIS_DB:0} # 与 spring.data.redis.database 保持一致:独立库 db2
database: ${LAOP_REDIS_DB:2}
password: "${LAOP_REDIS_PASSWORD}" password: "${LAOP_REDIS_PASSWORD}"
connectionMinimumIdleSize: 8 connectionMinimumIdleSize: 8
connectionPoolSize: 64 connectionPoolSize: 64

View File

@ -28,12 +28,6 @@ spring:
jdbc.lob.non_contextual_creation: true jdbc.lob.non_contextual_creation: true
type: type:
preferred_json_jdbc_type: JSONB preferred_json_jdbc_type: JSONB
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
baseline-version: 0
table: flyway_schema_history
logging: logging:
level: level:

View File

@ -1,88 +0,0 @@
-- ============================================================
-- V10 —— 功能配置表 app_config + 首页轮播图图片补全
-- ------------------------------------------------------------
-- 1. app_config:键值对形式的平台功能开关/参数配置
-- 2. 把 V9 初始化的 3 条轮播图关联到真实图片(sys_file + /static/image/banner-home-*.png)
-- ============================================================
-- ========== 1. 功能配置 KV 表 ==========
CREATE TABLE IF NOT EXISTS app_config (
config_key VARCHAR(64) PRIMARY KEY,
config_value VARCHAR(1000) NOT NULL,
remark VARCHAR(200),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_by VARCHAR(64)
);
COMMENT ON TABLE app_config IS '平台功能配置KV表(开关、参数等)';
COMMENT ON COLUMN app_config.config_key IS '配置键,唯一标识';
COMMENT ON COLUMN app_config.config_value IS '配置值(字符串形式,布尔值用 0/1)';
COMMENT ON COLUMN app_config.remark IS '备注说明';
COMMENT ON COLUMN app_config.created_at IS '创建时间';
COMMENT ON COLUMN app_config.updated_at IS '最后更新时间';
COMMENT ON COLUMN app_config.updated_by IS '最后修改人标识(可选)';
CREATE TRIGGER trg_app_config_updated BEFORE UPDATE ON app_config
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- 微信公众号模板消息推送开关(0关闭 1开启)
INSERT INTO app_config (config_key, config_value, remark)
VALUES ('mp_push_enabled', '0', '微信公众号模板消息推送总开关(0关闭 1开启)')
ON CONFLICT (config_key) DO NOTHING;
-- ========== 2. 首页轮播图图片补全 ==========
-- 本地磁盘文件已由运维放到 static/image/ 目录下:
-- banner-home-01-yiyunhub.png
-- banner-home-02-performance.png
-- banner-home-03-merchant.png
-- 本脚本负责注册 sys_file 记录 + 关联到 app_banner。
-- 注册 3 张图片到 sys_file(file_type = 8 → BANNER_IMAGE)
WITH
reg1 AS (
INSERT INTO sys_file (file_name, file_suffix, file_url, file_size, file_type, uploader_id, status)
VALUES ('banner-home-01-yiyunhub.png', 'png', '/static/image/banner-home-01-yiyunhub.png', NULL, 8, NULL, 1)
ON CONFLICT DO NOTHING
RETURNING id
),
reg2 AS (
INSERT INTO sys_file (file_name, file_suffix, file_url, file_size, file_type, uploader_id, status)
VALUES ('banner-home-02-performance.png', 'png', '/static/image/banner-home-02-performance.png', NULL, 8, NULL, 1)
ON CONFLICT DO NOTHING
RETURNING id
),
reg3 AS (
INSERT INTO sys_file (file_name, file_suffix, file_url, file_size, file_type, uploader_id, status)
VALUES ('banner-home-03-merchant.png', 'png', '/static/image/banner-home-03-merchant.png', NULL, 8, NULL, 1)
ON CONFLICT DO NOTHING
RETURNING id
),
-- 兜底:如果 ON CONFLICT 没返回 id(文件已存在),通过 file_name 反查
f1 AS (SELECT id FROM reg1 UNION ALL SELECT id FROM sys_file WHERE file_name = 'banner-home-01-yiyunhub.png' LIMIT 1),
f2 AS (SELECT id FROM reg2 UNION ALL SELECT id FROM sys_file WHERE file_name = 'banner-home-02-performance.png' LIMIT 1),
f3 AS (SELECT id FROM reg3 UNION ALL SELECT id FROM sys_file WHERE file_name = 'banner-home-03-merchant.png' LIMIT 1)
-- 把图片关联到轮播图(按标题匹配 V9 初始化的 3 条)
UPDATE app_banner
SET
image_file_id = CASE title
WHEN 'CAAC 报名通道开启' THEN (SELECT id FROM f1)
WHEN '无人机表演专场' THEN (SELECT id FROM f2)
WHEN '实力商家招商入驻' THEN (SELECT id FROM f3)
END,
-- 同步更新标题为小程序实际显示的文案
title = CASE title
WHEN 'CAAC 报名通道开启' THEN '翼云Hub'
WHEN '无人机表演专场' THEN '商业表演'
WHEN '实力商家招商入驻' THEN '实力商家'
ELSE title
END,
subtitle = CASE title
WHEN 'CAAC 报名通道开启' THEN '无人机信息开放平台'
WHEN '无人机表演专场' THEN '专业飞手 高客单'
WHEN '实力商家招商入驻' THEN '开放生态 精准流量'
ELSE subtitle
END
WHERE deleted_at IS NULL
AND image_file_id IS NULL;

View File

@ -1,21 +0,0 @@
-- =====================================================================
-- V11: course_appointment 唯一索引改为部分唯一索引
-- ------------------------------------------------------------
-- 背景:
-- V1 创建的全量唯一索引 (user_id, course_id, appointment_date) 不区分
-- 预约状态,导致用户取消预约后无法重新预约同一课程同一时段,
-- 从产品交互角度不合理。
--
-- 改为部分唯一索引:仅在"有效预约"(status != 4 已取消 且 deleted_at IS NULL)
-- 范围内生效,允许取消后重新预约,同时仍防止重复的有效预约。
--
-- status=4 对应 CourseConstants.APPT_STATUS_CANCELLED(已取消)
-- =====================================================================
-- 1. 删除原全量唯一索引
DROP INDEX IF EXISTS uk_course_appointment_user_course_date;
-- 2. 创建部分唯一索引:仅对"有效预约"生效,取消/软删的记录不参与唯一性校验
CREATE UNIQUE INDEX uk_course_appointment_active
ON course_appointment (user_id, course_id, appointment_date)
WHERE status != 4 AND deleted_at IS NULL;

View File

@ -1,53 +0,0 @@
-- ============================================================
-- V12: 门店培训信息表 + 题库种子数据
-- 1. training_store:Web 管理端"培训考试 → 培训"tab 展示的门店介绍信息
-- 2. exam_bank 种子:AOPA / UTC / ASDC / CAAC 四类执照题库
-- 3. exam_question 种子:从公开题库整理的单选/多选/判断题(含答案与解析)
-- ============================================================
-- ------------------------------------------------------------
-- 门店培训信息表 training_store
-- 单条记录设计:管理员编辑门店介绍,Web 端查询返回首条有效记录
-- ------------------------------------------------------------
CREATE TABLE training_store (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL,
address VARCHAR(200),
contact_name VARCHAR(50),
contact_phone VARCHAR(20),
training_content TEXT,
cover_image BIGINT,
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_training_store_deleted ON training_store (deleted_at);
COMMENT ON TABLE training_store IS '门店培训信息表';
COMMENT ON COLUMN training_store.id IS '主键';
COMMENT ON COLUMN training_store.name IS '门店名称';
COMMENT ON COLUMN training_store.address IS '门店地址';
COMMENT ON COLUMN training_store.contact_name IS '联系人';
COMMENT ON COLUMN training_store.contact_phone IS '联系电话';
COMMENT ON COLUMN training_store.training_content IS '培训内容介绍';
COMMENT ON COLUMN training_store.cover_image IS '封面图file_id(sys_file.id)';
COMMENT ON COLUMN training_store.status IS '状态:0禁用 1启用';
COMMENT ON COLUMN training_store.created_at IS '创建时间';
COMMENT ON COLUMN training_store.updated_at IS '更新时间';
COMMENT ON COLUMN training_store.deleted_at IS '软删除时间';
CREATE TRIGGER trg_training_store_updated BEFORE UPDATE ON training_store
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- 默认门店记录(管理员可在 Web 端编辑覆盖)
INSERT INTO training_store (name, address, contact_name, contact_phone, training_content, status)
VALUES ('翼云Hub无人机培训中心', '上海市松江区科技园区翼云路1号', '王教练', '13800138000',
'本中心提供AOPA/UTC/ASDC/CAAC四类无人机执照理论培训与实操指导,涵盖航空法规、飞行原理、气象学、应急处置等核心模块,由资深飞行教官授课,通过率业内领先。', 1);
-- ============================================================
-- 题库种子数据:4 个执照类型题库
-- ============================================================
INSERT INTO exam_bank (id, name, description, question_count, difficulty, status) VALUES
(1, 'CAAC 无人机理论考试题库', '中国民航局(CAAC)无人机驾驶员执照理论考试题库,涵盖航空法规、空域管理、飞行原理、应急处置等核心考点。', 20, 2, 1),
(2, 'AOPA 无人机理论考试题库', '中国航空器拥有者及驾驶员协会(AOPA)无人机驾驶员考试题库,侧重飞行操作与实操规范。', 5, 2, 1),
(3, 'UTC 无人机理论考试题库', '无人驾驶航空器系统驾驶员训练合格证(UTC)考试题库,涵盖航拍、植保等应用场景知识。', 5, 1, 1),
(4, 'ASDC 无人机理论考试题库', '中国航空运动协会(ASDC)无人机驾驶员考试题库,侧重航模与运动类飞行器知识。', 5, 1, 1);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,83 +0,0 @@
-- ============================================================
-- V16: 任务申请-审批流 + 微信服务号模板消息
-- 1) task_record 状态语义升级为"飞手申请 → 商家审批"生命周期,补充审核字段
-- 2) user 表补充微信开放平台 unionid 与服务号 openid(模板消息接收人)
-- 3) 新增 wx_message_log 模板消息发送日志表(Web 端可查)
-- ============================================================
-- ------------------------------------------------------------
-- 1. task_record:申请记录支持商家审批
-- ------------------------------------------------------------
ALTER TABLE task_record ADD COLUMN reject_time TIMESTAMP;
ALTER TABLE task_record ADD COLUMN reject_reason VARCHAR(200);
COMMENT ON TABLE task_record IS '任务申请记录表(飞手申请接单 → 商家审批 → 执行)';
COMMENT ON COLUMN task_record.status IS '状态:1已申请(待商家确认) 2商家已同意(已接单) 3商家已拒绝 4飞手已撤销';
COMMENT ON COLUMN task_record.apply_time IS '飞手申请接单时间';
COMMENT ON COLUMN task_record.accept_time IS '商家同意时间';
COMMENT ON COLUMN task_record.reject_time IS '商家拒绝时间';
COMMENT ON COLUMN task_record.reject_reason IS '商家拒绝原因';
-- ------------------------------------------------------------
-- 2. user:服务号模板消息接收人身份
-- ------------------------------------------------------------
ALTER TABLE "user" ADD COLUMN unionid VARCHAR(64);
ALTER TABLE "user" ADD COLUMN mp_openid VARCHAR(64);
CREATE INDEX idx_user_unionid ON "user" (unionid);
CREATE INDEX idx_user_mp_openid ON "user" (mp_openid);
COMMENT ON COLUMN "user".unionid IS '微信开放平台 unionid(小程序登录返回时写入,跨端身份关联)';
COMMENT ON COLUMN "user".mp_openid IS '微信服务号 openid(关注服务号事件自动绑定),NULL 表示未关注服务号';
-- ------------------------------------------------------------
-- 3. wx_message_log:模板消息发送日志(发送结果由微信回调回写)
-- ------------------------------------------------------------
CREATE TABLE wx_message_log (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id BIGINT,
biz_type SMALLINT NOT NULL,
biz_id BIGINT,
template_id VARCHAR(64) NOT NULL,
open_id VARCHAR(64),
msg_id BIGINT,
send_status SMALLINT NOT NULL,
skip_reason VARCHAR(200),
err_code VARCHAR(32),
err_msg VARCHAR(500),
result_status VARCHAR(64),
content TEXT,
send_time TIMESTAMP,
result_time TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_wx_message_log_user ON wx_message_log (user_id);
CREATE INDEX idx_wx_message_log_msg_id ON wx_message_log (msg_id);
CREATE INDEX idx_wx_message_log_status ON wx_message_log (send_status);
CREATE INDEX idx_wx_message_log_deleted ON wx_message_log (deleted_at);
COMMENT ON TABLE wx_message_log IS '微信模板消息发送日志表';
COMMENT ON COLUMN wx_message_log.id IS '主键';
COMMENT ON COLUMN wx_message_log.user_id IS '接收人用户ID(商家)';
COMMENT ON COLUMN wx_message_log.biz_type IS '业务类型:1任务申请通知';
COMMENT ON COLUMN wx_message_log.biz_id IS '业务单据ID(任务申请通知为 task_record.id)';
COMMENT ON COLUMN wx_message_log.template_id IS '微信模板ID';
COMMENT ON COLUMN wx_message_log.open_id IS '接收人服务号 openid';
COMMENT ON COLUMN wx_message_log.msg_id IS '微信返回的消息ID(发送提交成功后返回,回调送达结果以此关联)';
COMMENT ON COLUMN wx_message_log.send_status IS '发送状态:0已跳过 1已提交 2送达成功 3送达失败';
COMMENT ON COLUMN wx_message_log.skip_reason IS '跳过原因(未绑定服务号等)';
COMMENT ON COLUMN wx_message_log.err_code IS '提交/送达错误码';
COMMENT ON COLUMN wx_message_log.err_msg IS '提交/送达错误信息';
COMMENT ON COLUMN wx_message_log.result_status IS '微信回调送达结果原文(success / failed:user block 等)';
COMMENT ON COLUMN wx_message_log.content IS '模板参数 JSON 快照,便于排查展示问题';
COMMENT ON COLUMN wx_message_log.send_time IS '提交微信发送时间';
COMMENT ON COLUMN wx_message_log.result_time IS '微信回调送达时间';
COMMENT ON COLUMN wx_message_log.created_at IS '创建时间';
COMMENT ON COLUMN wx_message_log.updated_at IS '更新时间';
COMMENT ON COLUMN wx_message_log.deleted_at IS '软删除时间';
CREATE TRIGGER trg_wx_message_log_updated BEFORE UPDATE ON wx_message_log
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

View File

@ -1,70 +0,0 @@
-- ============================================================
-- V17: 模板消息队列持久化(DB 队列替代内存队列)
-- 1) 新建 wx_notify_task 任务表(PENDING → PROCESSING → SUCCESS/FAILED,消费后软删除)
-- 2) wx_message_log 增加 task_id 列,关联发送任务
-- ============================================================
-- ------------------------------------------------------------
-- 1. wx_notify_task:模板消息发送任务表(DB 持久化队列)
-- 替代原 LinkedBlockingQueue 内存队列,解决服务重启未消费任务丢失问题。
-- 消费机制:CAS 占位(status 0→1),成功后发送,完成时软删除。
-- 失败重试:retry_count + next_retry_at 指数退避,超过上限置 FAILED 并软删除。
-- ------------------------------------------------------------
CREATE TABLE wx_notify_task (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL,
biz_type SMALLINT NOT NULL,
biz_id BIGINT,
task_no VARCHAR(64),
task_name VARCHAR(128),
service_type_name VARCHAR(64),
handler VARCHAR(128),
operate_time VARCHAR(32),
template_id VARCHAR(64),
status SMALLINT NOT NULL DEFAULT 0,
retry_count SMALLINT NOT NULL DEFAULT 0,
next_retry_at TIMESTAMP,
skip_reason VARCHAR(200),
err_code VARCHAR(32),
err_msg VARCHAR(500),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 部分索引:仅索引未软删除且待处理行,消费查询高效
CREATE INDEX idx_wx_notify_task_pending ON wx_notify_task (status, next_retry_at)
WHERE deleted_at IS NULL AND status = 0;
CREATE INDEX idx_wx_notify_task_user ON wx_notify_task (user_id);
CREATE INDEX idx_wx_notify_task_biz ON wx_notify_task (biz_type, biz_id);
COMMENT ON TABLE wx_notify_task IS '微信模板消息发送任务表(DB 持久化队列,消费完成后软删除)';
COMMENT ON COLUMN wx_notify_task.id IS '主键';
COMMENT ON COLUMN wx_notify_task.user_id IS '接收人用户ID';
COMMENT ON COLUMN wx_notify_task.biz_type IS '业务类型:1任务申请 2审核结果 3飞手撤销(同 wx_message_log.biz_type)';
COMMENT ON COLUMN wx_notify_task.biz_id IS '业务单据ID(任务动态通知为 task_record.id)';
COMMENT ON COLUMN wx_notify_task.task_no IS '任务编号(模板 character_string14)';
COMMENT ON COLUMN wx_notify_task.task_name IS '任务名称(模板 thing5)';
COMMENT ON COLUMN wx_notify_task.service_type_name IS '服务类型名称(模板 thing12)';
COMMENT ON COLUMN wx_notify_task.handler IS '处理人文案(模板 thing17)';
COMMENT ON COLUMN wx_notify_task.operate_time IS '操作时间 yyyy-MM-dd HH:mm(模板 time18)';
COMMENT ON COLUMN wx_notify_task.template_id IS '使用的模板ID(消费时解析写入,PENDING 阶段为 NULL)';
COMMENT ON COLUMN wx_notify_task.status IS '任务状态:0待处理 1处理中 2成功 3失败';
COMMENT ON COLUMN wx_notify_task.retry_count IS '已重试次数(0=首次,超过上限置 FAILED)';
COMMENT ON COLUMN wx_notify_task.next_retry_at IS '下次重试时间(NULL=立即可处理)';
COMMENT ON COLUMN wx_notify_task.skip_reason IS '跳过原因(未关注服务号等,不实际发送)';
COMMENT ON COLUMN wx_notify_task.err_code IS '最后一次失败的错误码';
COMMENT ON COLUMN wx_notify_task.err_msg IS '最后一次失败的错误信息';
COMMENT ON COLUMN wx_notify_task.created_at IS '创建时间(入队时间)';
COMMENT ON COLUMN wx_notify_task.updated_at IS '更新时间';
COMMENT ON COLUMN wx_notify_task.deleted_at IS '软删除时间(消费完成后置位)';
CREATE TRIGGER trg_wx_notify_task_updated BEFORE UPDATE ON wx_notify_task
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ------------------------------------------------------------
-- 2. wx_message_log 增加 task_id 列(关联发送任务,从日志反查任务)
-- ------------------------------------------------------------
ALTER TABLE wx_message_log ADD COLUMN task_id BIGINT;
COMMENT ON COLUMN wx_message_log.task_id IS '关联的发送任务ID(wx_notify_task.id),从日志反查任务';

View File

@ -1,9 +0,0 @@
-- 研学课程表新增精选开关:开启后在小程序首页"精选研学课程"区块展示
-- 默认全部课程为非精选,管理员在 Web 管理端手动标记
ALTER TABLE course
ADD COLUMN featured SMALLINT NOT NULL DEFAULT 0;
COMMENT ON COLUMN course.featured IS '精选标记:0否 1是,开启后小程序首页精选课程列表展示';
-- 为精选标记建立索引,加速首页查询 WHERE featured = 1
CREATE INDEX idx_course_featured ON course(featured);

View File

@ -1,28 +0,0 @@
-- =====================================================================
-- V19: course_appointment 预约状态二态化 + 部分唯一索引重建
-- ------------------------------------------------------------
-- 背景:
-- 原 V1 设计了四态模型(1已预约/2已确认/3已完成/4已取消),产品侧
-- 简化为二态:1已预约(用户提交即生效,无需商家确认)/ 2已取消
-- (终态)。不再存在"商家确认"和"已完成"的中间状态。
--
-- 旧数据迁移策略:
-- status=2(原已确认)→ 置为 1(已预约),因为不再需要商家确认
-- status=3(原已完成)→ 置为 2(已取消),视作历史终态
-- status=1/4 → 不变
--
-- 同时重建部分唯一索引:取消状态值从 4 变更为 2,索引条件同步更新。
-- =====================================================================
-- 1. 旧数据回填:把原四态折叠为二态
UPDATE course_appointment SET status = 1 WHERE status = 2; -- 已确认 → 已预约
UPDATE course_appointment SET status = 2 WHERE status = 3; -- 已完成 → 已取消(历史终态)
UPDATE course_appointment SET status = 2 WHERE status = 4; -- 原已取消(4) → 新已取消(2)
-- 2. 删除旧的部分唯一索引(V11 创建的,条件 WHERE status != 4)
DROP INDEX IF EXISTS uk_course_appointment_active;
-- 3. 按新二态条件重建:有效预约 = status=1(已预约)且未软删
CREATE UNIQUE INDEX uk_course_appointment_active
ON course_appointment (user_id, course_id, appointment_date)
WHERE status != 2 AND deleted_at IS NULL;

View File

@ -1,552 +0,0 @@
-- ============================================================
-- 翼云Hub · 数据库初始化脚本
-- 目标数据库: PostgreSQL 14(开发) / PostgreSQL 16(生产)
-- 字符集: UTF8 (PG 默认)
-- 命名规范: 表名/字段名一律下划线小写,主键统一为 id
-- 软删除: deleted_at,时间戳由 NULL 标记未删除
-- 时间维护: created_at 由 DEFAULT 维护,updated_at 由触发器维护
-- 多选字段: 以 String 存储,多值之间用英文分号 ; 拼接(如 "1;2;3")
-- 经纬度字段: 统一用 VARCHAR(20) 存储,避免浮点精度问题
-- 类型字段: 统一用 SMALLINT 存储数字编码(Java 侧映射为 Integer)
-- ============================================================
-- ------------------------------------------------------------
-- 公共触发器函数:自动维护 updated_at 字段
-- 任何 UPDATE 命中该函数挂的表,updated_at 都会刷新为当前时间
-- ------------------------------------------------------------
CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS
$$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- ============================================================
-- 1. 公告表 announcement
-- ============================================================
CREATE TABLE announcement (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title VARCHAR(100) NOT NULL,
content TEXT,
valid_start_time TIMESTAMP,
valid_end_time TIMESTAMP,
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_announcement_status ON announcement (status);
CREATE INDEX idx_announcement_valid_period ON announcement (valid_start_time, valid_end_time);
CREATE INDEX idx_announcement_deleted ON announcement (deleted_at);
COMMENT ON TABLE announcement IS '公告表';
COMMENT ON COLUMN announcement.id IS '主键';
COMMENT ON COLUMN announcement.title IS '公告标题';
COMMENT ON COLUMN announcement.content IS '公告正文';
COMMENT ON COLUMN announcement.valid_start_time IS '生效开始时间';
COMMENT ON COLUMN announcement.valid_end_time IS '生效结束时间';
COMMENT ON COLUMN announcement.status IS '状态:0隐藏 1显示';
COMMENT ON COLUMN announcement.created_at IS '创建时间';
COMMENT ON COLUMN announcement.updated_at IS '更新时间';
COMMENT ON COLUMN announcement.deleted_at IS '软删除时间';
CREATE TRIGGER trg_announcement_updated BEFORE UPDATE ON announcement
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 2. 用户表 "user"(user 在 PG 为保留字,需用双引号引用)
-- ============================================================
CREATE TABLE "user" (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
openid VARCHAR(64) NOT NULL,
nickname VARCHAR(50),
avatar_url VARCHAR(500),
phone VARCHAR(20),
gender SMALLINT NOT NULL DEFAULT 0,
bio VARCHAR(200),
role_type SMALLINT NOT NULL DEFAULT 1,
points INT NOT NULL DEFAULT 0,
is_pilot_bound SMALLINT NOT NULL DEFAULT 0,
is_merchant_bound SMALLINT NOT NULL DEFAULT 0,
pilot_id BIGINT,
merchant_id BIGINT,
status SMALLINT NOT NULL DEFAULT 1,
last_login_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE UNIQUE INDEX uk_user_openid ON "user" (openid);
CREATE INDEX idx_user_phone ON "user" (phone);
CREATE INDEX idx_user_deleted ON "user" (deleted_at);
COMMENT ON TABLE "user" IS '用户表';
COMMENT ON COLUMN "user".id IS '主键';
COMMENT ON COLUMN "user".openid IS '微信openId';
COMMENT ON COLUMN "user".nickname IS '用户昵称';
COMMENT ON COLUMN "user".avatar_url IS '头像URL';
COMMENT ON COLUMN "user".phone IS '手机号';
COMMENT ON COLUMN "user".gender IS '性别:0未知 1男 2女';
COMMENT ON COLUMN "user".bio IS '个人简介';
COMMENT ON COLUMN "user".role_type IS '角色类型:1普通用户 2飞手 3商家 4机构';
COMMENT ON COLUMN "user".points IS '积分余额';
COMMENT ON COLUMN "user".is_pilot_bound IS '是否已绑定飞手身份:0否 1是';
COMMENT ON COLUMN "user".is_merchant_bound IS '是否已绑定商家身份:0否 1是';
COMMENT ON COLUMN "user".pilot_id IS '关联飞手ID';
COMMENT ON COLUMN "user".merchant_id IS '关联商家ID';
COMMENT ON COLUMN "user".status IS '账号状态:0禁用 1正常';
COMMENT ON COLUMN "user".last_login_at IS '最后登录时间';
COMMENT ON COLUMN "user".created_at IS '注册时间';
COMMENT ON COLUMN "user".updated_at IS '更新时间';
COMMENT ON COLUMN "user".deleted_at IS '软删除时间';
CREATE TRIGGER trg_user_updated BEFORE UPDATE ON "user"
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 3. 商家表 merchant
-- ============================================================
CREATE TABLE merchant (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL,
name VARCHAR(100) NOT NULL,
address VARCHAR(200),
door_image BIGINT,
license_file_id BIGINT,
contact_name VARCHAR(50),
contact_phone VARCHAR(20),
latitude VARCHAR(20),
longitude VARCHAR(20),
service_types VARCHAR(200),
description TEXT,
cert_status SMALLINT NOT NULL DEFAULT 0,
cert_time TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE UNIQUE INDEX uk_merchant_user ON merchant (user_id);
CREATE INDEX idx_merchant_cert ON merchant (cert_status);
CREATE INDEX idx_merchant_deleted ON merchant (deleted_at);
COMMENT ON TABLE merchant IS '商家表';
COMMENT ON COLUMN merchant.id IS '主键';
COMMENT ON COLUMN merchant.user_id IS '关联用户ID';
COMMENT ON COLUMN merchant.name IS '商家名称';
COMMENT ON COLUMN merchant.address IS '商家地址';
COMMENT ON COLUMN merchant.door_image IS '门头照片file_id';
COMMENT ON COLUMN merchant.license_file_id IS '营业执照file_id';
COMMENT ON COLUMN merchant.contact_name IS '联系人';
COMMENT ON COLUMN merchant.contact_phone IS '联系电话';
COMMENT ON COLUMN merchant.latitude IS '纬度(字符串存储)';
COMMENT ON COLUMN merchant.longitude IS '经度(字符串存储)';
COMMENT ON COLUMN merchant.service_types IS '服务类型编码,多选用分号拼接,如:1;2;3';
COMMENT ON COLUMN merchant.description IS '商家简介';
COMMENT ON COLUMN merchant.cert_status IS '认证状态:0未认证 1认证中 2已认证 3认证失败';
COMMENT ON COLUMN merchant.cert_time IS '认证时间';
COMMENT ON COLUMN merchant.created_at IS '创建时间';
COMMENT ON COLUMN merchant.updated_at IS '更新时间';
COMMENT ON COLUMN merchant.deleted_at IS '软删除时间';
CREATE TRIGGER trg_merchant_updated BEFORE UPDATE ON merchant
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 4. 飞手表 pilot
-- ============================================================
CREATE TABLE pilot (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL,
name VARCHAR(50) NOT NULL,
phone VARCHAR(20) NOT NULL,
birthday DATE,
age SMALLINT,
avatar_file_id BIGINT,
cert_status SMALLINT NOT NULL DEFAULT 0,
cert_time TIMESTAMP,
service_city_codes VARCHAR(500),
bio VARCHAR(200),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE UNIQUE INDEX uk_pilot_user ON pilot (user_id);
CREATE INDEX idx_pilot_cert ON pilot (cert_status);
CREATE INDEX idx_pilot_deleted ON pilot (deleted_at);
COMMENT ON TABLE pilot IS '飞手表';
COMMENT ON COLUMN pilot.id IS '主键';
COMMENT ON COLUMN pilot.user_id IS '关联用户ID';
COMMENT ON COLUMN pilot.name IS '姓名(实名)';
COMMENT ON COLUMN pilot.phone IS '手机号';
COMMENT ON COLUMN pilot.birthday IS '出生年月';
COMMENT ON COLUMN pilot.age IS '年龄(自动计算)';
COMMENT ON COLUMN pilot.avatar_file_id IS '头像file_id';
COMMENT ON COLUMN pilot.cert_status IS '认证状态:0未认证 1认证中 2已认证 3认证失败';
COMMENT ON COLUMN pilot.cert_time IS '认证时间';
COMMENT ON COLUMN pilot.service_city_codes IS '服务城市编码,多选用分号拼接,关联region.code';
COMMENT ON COLUMN pilot.bio IS '个人简介';
COMMENT ON COLUMN pilot.created_at IS '创建时间';
COMMENT ON COLUMN pilot.updated_at IS '更新时间';
COMMENT ON COLUMN pilot.deleted_at IS '软删除时间';
CREATE TRIGGER trg_pilot_updated BEFORE UPDATE ON pilot
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 4.1 飞手执照表 pilot_license(独立一对多表,替代 pilot 上的分号拼接字段)
-- ============================================================
CREATE TABLE pilot_license (
id BIGSERIAL PRIMARY KEY,
pilot_id BIGINT NOT NULL REFERENCES pilot(id),
license_type SMALLINT NOT NULL CHECK (license_type IN (1, 2, 3, 4)),
license_no VARCHAR(50),
license_file_id BIGINT,
license_date VARCHAR(10),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_pilot_license_pilot ON pilot_license(pilot_id);
COMMENT ON TABLE pilot_license IS '飞手执照表(多本执照一对多)';
COMMENT ON COLUMN pilot_license.pilot_id IS '关联飞手ID(pilot.id)';
COMMENT ON COLUMN pilot_license.license_type IS '执照类型编码:1 AOPA 2 UTC 3 ASFC 4 CAAC';
COMMENT ON COLUMN pilot_license.license_no IS '执照编号';
COMMENT ON COLUMN pilot_license.license_file_id IS '执照照片file_id(sys_file.id)';
COMMENT ON COLUMN pilot_license.license_date IS '获证日期,格式 yyyy-MM-dd';
COMMENT ON COLUMN pilot_license.created_at IS '创建时间';
COMMENT ON COLUMN pilot_license.updated_at IS '更新时间';
COMMENT ON COLUMN pilot_license.deleted_at IS '软删除时间';
CREATE TRIGGER trg_pilot_license_updated BEFORE UPDATE ON pilot_license
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 5. 任务表 task
-- ============================================================
CREATE TABLE task (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
task_no VARCHAR(32) NOT NULL,
name VARCHAR(100) NOT NULL,
type SMALLINT NOT NULL,
province VARCHAR(20),
city VARCHAR(20),
district VARCHAR(20),
address VARCHAR(200),
latitude VARCHAR(20),
longitude VARCHAR(20),
scene_images VARCHAR(500),
required_count INT NOT NULL DEFAULT 1,
task_start_time TIMESTAMP NOT NULL,
task_end_time TIMESTAMP,
reward NUMERIC(10, 2) NOT NULL,
insurance_require VARCHAR(100),
contact_name VARCHAR(50),
contact_phone VARCHAR(20) NOT NULL,
remark TEXT,
status SMALLINT NOT NULL DEFAULT 0,
is_premium SMALLINT NOT NULL DEFAULT 0,
premium_cost INT NOT NULL DEFAULT 0,
publisher_id BIGINT NOT NULL,
accepted_pilot_id BIGINT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE UNIQUE INDEX uk_task_task_no ON task (task_no);
CREATE INDEX idx_task_type_status ON task (type, status);
CREATE INDEX idx_task_city_status ON task (city, status);
CREATE INDEX idx_task_publisher ON task (publisher_id);
CREATE INDEX idx_task_pilot ON task (accepted_pilot_id);
CREATE INDEX idx_task_deleted ON task (deleted_at);
COMMENT ON TABLE task IS '任务表';
COMMENT ON COLUMN task.id IS '主键';
COMMENT ON COLUMN task.task_no IS '任务编号(全局唯一)';
COMMENT ON COLUMN task.name IS '任务名称';
COMMENT ON COLUMN task.type IS '任务类型编码:1植保 2吊运 3航拍 4巡检 5清洗 6表演 7测绘';
COMMENT ON COLUMN task.province IS '省份';
COMMENT ON COLUMN task.city IS '城市';
COMMENT ON COLUMN task.district IS '区县';
COMMENT ON COLUMN task.address IS '详细地址';
COMMENT ON COLUMN task.latitude IS '纬度坐标(字符串存储)';
COMMENT ON COLUMN task.longitude IS '经度坐标(字符串存储)';
COMMENT ON COLUMN task.scene_images IS '作业场景图file_id数组,多选用分号拼接';
COMMENT ON COLUMN task.required_count IS '需求人数';
COMMENT ON COLUMN task.task_start_time IS '任务开始时间';
COMMENT ON COLUMN task.task_end_time IS '任务结束时间';
COMMENT ON COLUMN task.reward IS '报酬(元)';
COMMENT ON COLUMN task.insurance_require IS '保险要求编码,多选用分号拼接:1三者险 2飞手险 3机身险';
COMMENT ON COLUMN task.contact_name IS '联系人姓名';
COMMENT ON COLUMN task.contact_phone IS '联系电话';
COMMENT ON COLUMN task.remark IS '备注';
COMMENT ON COLUMN task.status IS '状态:0草稿 1待接单 2已接单 3执行中 4已完成 5已取消';
COMMENT ON COLUMN task.is_premium IS '是否优质任务:0否 1是';
COMMENT ON COLUMN task.premium_cost IS '优质任务积分消耗';
COMMENT ON COLUMN task.publisher_id IS '发布人ID';
COMMENT ON COLUMN task.accepted_pilot_id IS '接单飞手ID';
COMMENT ON COLUMN task.created_at IS '创建时间';
COMMENT ON COLUMN task.updated_at IS '更新时间';
COMMENT ON COLUMN task.deleted_at IS '软删除时间';
CREATE TRIGGER trg_task_updated BEFORE UPDATE ON task
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 6. 任务执行记录表 task_record
-- ============================================================
CREATE TABLE task_record (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
task_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
pilot_id BIGINT,
merchant_id BIGINT,
status SMALLINT NOT NULL DEFAULT 1,
apply_time TIMESTAMP,
accept_time TIMESTAMP,
record_start_time TIMESTAMP,
record_end_time TIMESTAMP,
cancel_time TIMESTAMP,
cancel_reason VARCHAR(200),
insurance_verified SMALLINT NOT NULL DEFAULT 0,
actual_reward NUMERIC(10, 2),
rating SMALLINT,
rating_comment VARCHAR(200),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE UNIQUE INDEX uk_task_record_task_user ON task_record (task_id, user_id);
CREATE INDEX idx_task_record_user ON task_record (user_id, status);
CREATE INDEX idx_task_record_pilot ON task_record (pilot_id, status);
CREATE INDEX idx_task_record_deleted ON task_record (deleted_at);
COMMENT ON TABLE task_record IS '任务执行记录表';
COMMENT ON COLUMN task_record.id IS '主键';
COMMENT ON COLUMN task_record.task_id IS '任务ID';
COMMENT ON COLUMN task_record.user_id IS '关联用户ID';
COMMENT ON COLUMN task_record.pilot_id IS '接单飞手ID';
COMMENT ON COLUMN task_record.merchant_id IS '发单商家ID';
COMMENT ON COLUMN task_record.status IS '状态:1待接单 2已接单 3执行中 4已完成 5已取消';
COMMENT ON COLUMN task_record.apply_time IS '申请/接单时间';
COMMENT ON COLUMN task_record.accept_time IS '飞手接单时间';
COMMENT ON COLUMN task_record.record_start_time IS '实际开始时间';
COMMENT ON COLUMN task_record.record_end_time IS '实际结束时间(完成时间)';
COMMENT ON COLUMN task_record.cancel_time IS '取消时间';
COMMENT ON COLUMN task_record.cancel_reason IS '取消原因';
COMMENT ON COLUMN task_record.insurance_verified IS '保险是否校验:0否 1是';
COMMENT ON COLUMN task_record.actual_reward IS '实际报酬';
COMMENT ON COLUMN task_record.rating IS '用户评分(1-5)';
COMMENT ON COLUMN task_record.rating_comment IS '评价内容';
COMMENT ON COLUMN task_record.created_at IS '创建时间';
COMMENT ON COLUMN task_record.updated_at IS '更新时间';
COMMENT ON COLUMN task_record.deleted_at IS '软删除时间';
CREATE TRIGGER trg_task_record_updated BEFORE UPDATE ON task_record
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 7. 文件表 sys_file(避开 Java 与 PG 的保留字 file 冲突)
-- 文件表只增不改、不更新,因此无 updated_at 字段
-- ============================================================
CREATE TABLE sys_file (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
file_name VARCHAR(200) NOT NULL,
file_suffix VARCHAR(10),
file_url VARCHAR(500),
file_size BIGINT,
file_type SMALLINT,
uploader_id BIGINT,
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_sys_file_type ON sys_file (file_type);
CREATE INDEX idx_sys_file_uploader ON sys_file (uploader_id);
CREATE INDEX idx_sys_file_deleted ON sys_file (deleted_at);
COMMENT ON TABLE sys_file IS '文件表';
COMMENT ON COLUMN sys_file.id IS '主键';
COMMENT ON COLUMN sys_file.file_name IS '原始文件名';
COMMENT ON COLUMN sys_file.file_suffix IS '文件后缀';
COMMENT ON COLUMN sys_file.file_url IS '访问URL';
COMMENT ON COLUMN sys_file.file_size IS '文件大小(字节)';
COMMENT ON COLUMN sys_file.file_type IS '业务类型编码:1营业执照 2飞手执照 3作业场景图 4课程图片 5头像 9其他';
COMMENT ON COLUMN sys_file.uploader_id IS '上传用户ID';
COMMENT ON COLUMN sys_file.status IS '状态:0禁用 1正常';
COMMENT ON COLUMN sys_file.created_at IS '上传时间';
COMMENT ON COLUMN sys_file.deleted_at IS '软删除时间';
-- ============================================================
-- 8. 题库表 exam_bank
-- ============================================================
CREATE TABLE exam_bank (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL,
cover_image BIGINT,
description TEXT,
question_count INT NOT NULL DEFAULT 0,
difficulty SMALLINT NOT NULL DEFAULT 1,
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_exam_bank_deleted ON exam_bank (deleted_at);
COMMENT ON TABLE exam_bank IS '题库表';
COMMENT ON COLUMN exam_bank.id IS '主键';
COMMENT ON COLUMN exam_bank.name IS '题库名称';
COMMENT ON COLUMN exam_bank.cover_image IS '封面图file_id';
COMMENT ON COLUMN exam_bank.description IS '题库描述';
COMMENT ON COLUMN exam_bank.question_count IS '题目数量';
COMMENT ON COLUMN exam_bank.difficulty IS '难度:1简单 2中等 3困难';
COMMENT ON COLUMN exam_bank.status IS '状态:0禁用 1启用';
COMMENT ON COLUMN exam_bank.created_at IS '创建时间';
COMMENT ON COLUMN exam_bank.updated_at IS '更新时间';
COMMENT ON COLUMN exam_bank.deleted_at IS '软删除时间';
CREATE TRIGGER trg_exam_bank_updated BEFORE UPDATE ON exam_bank
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 9. 习题表 exam_question
-- ============================================================
CREATE TABLE exam_question (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
bank_id BIGINT NOT NULL,
content TEXT NOT NULL,
options JSONB,
question_type SMALLINT NOT NULL,
answer VARCHAR(10) NOT NULL,
explanation TEXT,
difficulty SMALLINT NOT NULL DEFAULT 1,
score SMALLINT NOT NULL DEFAULT 5,
sort_order INT NOT NULL DEFAULT 0,
used_count INT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_exam_question_bank ON exam_question (bank_id);
CREATE INDEX idx_exam_question_type ON exam_question (question_type);
CREATE INDEX idx_exam_question_deleted ON exam_question (deleted_at);
COMMENT ON TABLE exam_question IS '习题表';
COMMENT ON COLUMN exam_question.id IS '主键';
COMMENT ON COLUMN exam_question.bank_id IS '所属题库ID';
COMMENT ON COLUMN exam_question.content IS '题目内容';
COMMENT ON COLUMN exam_question.options IS '选项数组([{"key":"A","val":"..."},...])';
COMMENT ON COLUMN exam_question.question_type IS '题型:1单选 2多选 3判断';
COMMENT ON COLUMN exam_question.answer IS '标准答案(如:A 或 AB)';
COMMENT ON COLUMN exam_question.explanation IS '答案解析';
COMMENT ON COLUMN exam_question.difficulty IS '难度:1简单 2中等 3困难';
COMMENT ON COLUMN exam_question.score IS '分值';
COMMENT ON COLUMN exam_question.sort_order IS '排序';
COMMENT ON COLUMN exam_question.used_count IS '被使用次数';
COMMENT ON COLUMN exam_question.created_at IS '创建时间';
COMMENT ON COLUMN exam_question.updated_at IS '更新时间';
COMMENT ON COLUMN exam_question.deleted_at IS '软删除时间';
CREATE TRIGGER trg_exam_question_updated BEFORE UPDATE ON exam_question
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 10. 课程表 course
-- ============================================================
CREATE TABLE course (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL,
tags VARCHAR(200),
category SMALLINT,
target_age VARCHAR(200),
location VARCHAR(200),
latitude VARCHAR(20),
longitude VARCHAR(20),
description TEXT,
images VARCHAR(500),
price NUMERIC(10, 2) NOT NULL,
merchant_id BIGINT,
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_course_category ON course (category);
CREATE INDEX idx_course_status ON course (status);
CREATE INDEX idx_course_merchant ON course (merchant_id);
CREATE INDEX idx_course_deleted ON course (deleted_at);
COMMENT ON TABLE course IS '课程表';
COMMENT ON COLUMN course.id IS '主键';
COMMENT ON COLUMN course.name IS '课程名称';
COMMENT ON COLUMN course.tags IS '标签编码,多选用分号拼接';
COMMENT ON COLUMN course.category IS '分类编码:1认知启蒙 2技能进阶 3创新应用 4竞赛辅导';
COMMENT ON COLUMN course.target_age IS '适合年龄描述';
COMMENT ON COLUMN course.location IS '上课地点';
COMMENT ON COLUMN course.latitude IS '纬度(字符串存储)';
COMMENT ON COLUMN course.longitude IS '经度(字符串存储)';
COMMENT ON COLUMN course.description IS '课程描述';
COMMENT ON COLUMN course.images IS '课程图片file_id数组,多选用分号拼接';
COMMENT ON COLUMN course.price IS '课程价格';
COMMENT ON COLUMN course.merchant_id IS '所属商家ID';
COMMENT ON COLUMN course.status IS '状态:0下架 1上架 2已满员';
COMMENT ON COLUMN course.created_at IS '创建时间';
COMMENT ON COLUMN course.updated_at IS '更新时间';
COMMENT ON COLUMN course.deleted_at IS '软删除时间';
CREATE TRIGGER trg_course_updated BEFORE UPDATE ON course
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 11. 课程预约表 course_appointment
-- ============================================================
CREATE TABLE course_appointment (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL,
course_id BIGINT NOT NULL,
merchant_id BIGINT,
appointment_date TIMESTAMP NOT NULL,
person_count INT NOT NULL DEFAULT 1,
contact_name VARCHAR(50),
contact_phone VARCHAR(20),
remark VARCHAR(200),
status SMALLINT NOT NULL DEFAULT 1,
amount NUMERIC(10, 2) NOT NULL DEFAULT 0.00,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE UNIQUE INDEX uk_course_appointment_user_course_date ON course_appointment (user_id, course_id, appointment_date);
CREATE INDEX idx_course_appointment_course_status ON course_appointment (course_id, status);
CREATE INDEX idx_course_appointment_merchant ON course_appointment (merchant_id);
CREATE INDEX idx_course_appointment_deleted ON course_appointment (deleted_at);
COMMENT ON TABLE course_appointment IS '课程预约表';
COMMENT ON COLUMN course_appointment.id IS '主键';
COMMENT ON COLUMN course_appointment.user_id IS '用户ID';
COMMENT ON COLUMN course_appointment.course_id IS '课程ID';
COMMENT ON COLUMN course_appointment.merchant_id IS '商家ID';
COMMENT ON COLUMN course_appointment.appointment_date IS '预约时间';
COMMENT ON COLUMN course_appointment.person_count IS '预约人数';
COMMENT ON COLUMN course_appointment.contact_name IS '联系人姓名';
COMMENT ON COLUMN course_appointment.contact_phone IS '联系人电话';
COMMENT ON COLUMN course_appointment.remark IS '备注';
COMMENT ON COLUMN course_appointment.status IS '状态:1已预约 2已确认 3已完成 4已取消';
COMMENT ON COLUMN course_appointment.amount IS '支付金额';
COMMENT ON COLUMN course_appointment.created_at IS '预约时间';
COMMENT ON COLUMN course_appointment.updated_at IS '更新时间';
COMMENT ON COLUMN course_appointment.deleted_at IS '软删除时间';
CREATE TRIGGER trg_course_appointment_updated BEFORE UPDATE ON course_appointment
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ============================================================
-- 12. 省市区表 region
-- 业务侧所有涉及 LBS 的字段(如 pilot.service_city_codes)均以本表 code 为值存储,
-- 多选用分号拼接,查询时通过程序侧 split + IN 或 SQL 模糊匹配筛选
-- ============================================================
CREATE TABLE region (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
code VARCHAR(12) NOT NULL,
name VARCHAR(50) NOT NULL,
level SMALLINT NOT NULL,
parent_code VARCHAR(12),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE UNIQUE INDEX uk_region_code ON region (code);
CREATE INDEX idx_region_parent ON region (parent_code);
CREATE INDEX idx_region_level ON region (level);
CREATE INDEX idx_region_deleted ON region (deleted_at);
COMMENT ON TABLE region IS '省市区表';
COMMENT ON COLUMN region.id IS '主键';
COMMENT ON COLUMN region.code IS '行政区划编码(6位数字字符串,如110000北京市)';
COMMENT ON COLUMN region.name IS '区划名称';
COMMENT ON COLUMN region.level IS '级别:1省 2市 3区县';
COMMENT ON COLUMN region.parent_code IS '父级编码,省级为NULL';
COMMENT ON COLUMN region.created_at IS '创建时间';
COMMENT ON COLUMN region.updated_at IS '更新时间';
COMMENT ON COLUMN region.deleted_at IS '软删除时间';
CREATE TRIGGER trg_region_updated BEFORE UPDATE ON region
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

View File

@ -1,33 +0,0 @@
-- ============================================================
-- V2: 积分明细表 + 移除 task.premium_cost
-- 设计说明:积分的每一笔收入/支出统一记录到 points_detail 流水表,
-- 余额仍以 user.points 为准;任务表不再冗余存储 premium_cost,
-- 优质任务的积分消耗以 points_detail 中事件类型=4 的流水为准。
-- ============================================================
-- 1. 积分明细表(流水只增不改,继承 created_at,无 updated_at)
CREATE TABLE points_detail (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL,
change_type SMALLINT NOT NULL,
event_type SMALLINT NOT NULL,
points INT NOT NULL,
related_id BIGINT,
remark VARCHAR(200),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_points_detail_user ON points_detail (user_id, created_at);
COMMENT ON TABLE points_detail IS '积分明细表:记录用户每笔积分增扣流水';
COMMENT ON COLUMN points_detail.id IS '主键';
COMMENT ON COLUMN points_detail.user_id IS '用户ID,关联user.id';
COMMENT ON COLUMN points_detail.change_type IS '变动方向:1收入 2支出';
COMMENT ON COLUMN points_detail.event_type IS '事件类型:1发布任务 2完成任务 3邀请好友注册 4发布优质任务';
COMMENT ON COLUMN points_detail.points IS '积分变动值,收入为正、支出为负';
COMMENT ON COLUMN points_detail.related_id IS '关联业务ID(任务ID/被邀请用户ID等)';
COMMENT ON COLUMN points_detail.remark IS '备注说明';
COMMENT ON COLUMN points_detail.created_at IS '创建时间';
COMMENT ON COLUMN points_detail.deleted_at IS '软删除时间';
-- 2. 任务表移除冗余的优质任务积分消耗字段
ALTER TABLE task DROP COLUMN premium_cost;

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
-- =====================================================================
-- V3: 飞手执照支持多本(执照类型/编号/照片 改为分号拼接的多值存储)
-- 规约依据:AGENTS.md §4 "多选字段统一用 String 分号拼接,值为数字编码"
-- 注意:dev 环境未启用 Flyway,需手动在 PG 执行本脚本
-- =====================================================================
-- 执照类型编码:单本 SMALLINT → 多本拼接串(如 "1;2" 表示 AOPA+UTC)
ALTER TABLE pilot
ALTER COLUMN license_type TYPE VARCHAR(32) USING license_type::VARCHAR;
COMMENT ON COLUMN pilot.license_type IS 'CAAC执照类型编码,多本执照分号拼接(如 1;2 表示 AOPA+UTC):1 AOPA 2 UTC 3 ASFC';
-- 执照编号:多本编号按下标与 license_type 一一对应,分号拼接
ALTER TABLE pilot
ALTER COLUMN license_no TYPE VARCHAR(255);
COMMENT ON COLUMN pilot.license_no IS 'CAAC执照编号,多本执照分号拼接,与license_type按下标一一对应';
-- 执照照片 file_id:多张照片按下标对应,分号拼接(关联 sys_file.id)
ALTER TABLE pilot
ALTER COLUMN license_file_id TYPE VARCHAR(255) USING license_file_id::VARCHAR;
COMMENT ON COLUMN pilot.license_file_id IS '执照照片file_id,多张分号拼接(如 12;13),与license_type按下标一一对应,关联sys_file.id';

View File

@ -1,8 +0,0 @@
-- V4: 飞手新增服务类型字段
-- 服务类型为飞手能力属性(1植保 2吊运 3航拍 4巡检 5清洗 6表演 7测绘),多选分号拼接;
-- 商家认证表单已移除服务类型录入(历史列保留不再使用,避免破坏存量数据)。
ALTER TABLE pilot
ADD COLUMN service_types VARCHAR(200) NULL;
COMMENT ON COLUMN pilot.service_types IS '服务类型编码,多选分号拼接:1植保 2吊运 3航拍 4巡检 5清洗 6表演 7测绘';

View File

@ -1,11 +0,0 @@
-- =====================================================================
-- V5: 飞手执照新增获证日期字段(多本执照分号拼接,按下标对齐)
-- 规约依据:AGENTS.md §4 "多选字段统一用 String 分号拼接"
-- 存储格式:yyyy-MM-dd(如 "2023-06-15"),多本执照如 "2023-06-15;2024-03-20"
-- 注意:dev 环境未启用 Flyway,需手动在 PG 执行本脚本
-- =====================================================================
ALTER TABLE pilot
ADD COLUMN license_date VARCHAR(500);
COMMENT ON COLUMN pilot.license_date IS '执照获证日期,多本执照分号拼接(如 2023-06-15;2024-03-20),与license_type按下标一一对应,格式yyyy-MM-dd';

View File

@ -1,48 +0,0 @@
-- =====================================================================
-- V6: 飞手执照独立表 pilot_license(替代 pilot 表 4 个分号拼接字段)
-- 架构升级:多本执照从 String 分号拼接 → 独立表一对多关系
-- 影响范围:Entity / DTO / VO / CertServiceImpl / HomeServiceImpl
-- 注意:dev 环境未启用 Flyway,需手动在 PG 执行本脚本
-- =====================================================================
-- 1. 建表:继承项目审计字段规约(id BIGSERIAL + created_at/updated_at/deleted_at)
CREATE TABLE pilot_license (
id BIGSERIAL PRIMARY KEY,
pilot_id BIGINT NOT NULL REFERENCES pilot(id),
license_type SMALLINT NOT NULL,
license_no VARCHAR(50),
license_file_id BIGINT,
license_date VARCHAR(10),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMP,
CONSTRAINT ck_license_type CHECK (license_type IN (1, 2, 3, 4))
);
COMMENT ON TABLE pilot_license IS '飞手执照表(多本执照一对多)';
COMMENT ON COLUMN pilot_license.pilot_id IS '关联飞手ID(pilot.id)';
COMMENT ON COLUMN pilot_license.license_type IS '执照类型编码:1 AOPA 2 UTC 3 ASFC 4 CAAC';
COMMENT ON COLUMN pilot_license.license_no IS '执照编号';
COMMENT ON COLUMN pilot_license.license_file_id IS '执照照片file_id(sys_file.id)';
COMMENT ON COLUMN pilot_license.license_date IS '获证日期,格式 yyyy-MM-dd';
CREATE INDEX idx_pilot_license_pilot ON pilot_license(pilot_id);
-- 2. 数据迁移:从 pilot 表 4 个分号串按下标对齐插入已有执照
-- PG 原生 string_to_array + unnest + WITH ORDINALITY 实现"并行数组按下标对齐"
INSERT INTO pilot_license (pilot_id, license_type, license_no, license_file_id, license_date)
SELECT
p.id,
types_arr.elem::SMALLINT,
nos_arr.elem,
file_arr.elem::BIGINT,
date_arr.elem
FROM pilot p
CROSS JOIN LATERAL unnest(string_to_array(COALESCE(p.license_type, ''), ';')) WITH ORDINALITY AS types_arr(elem, ord)
LEFT JOIN LATERAL unnest(string_to_array(COALESCE(p.license_no, ''), ';')) WITH ORDINALITY AS nos_arr (elem, ord) ON nos_arr.ord = types_arr.ord AND nos_arr.elem <> ''
LEFT JOIN LATERAL unnest(string_to_array(COALESCE(p.license_file_id, ''), ';')) WITH ORDINALITY AS file_arr(elem, ord) ON file_arr.ord = types_arr.ord AND file_arr.elem <> ''
LEFT JOIN LATERAL unnest(string_to_array(COALESCE(p.license_date, ''), ';')) WITH ORDINALITY AS date_arr(elem, ord) ON date_arr.ord = types_arr.ord AND date_arr.elem <> ''
WHERE COALESCE(p.license_type, '') <> '';
-- 3. 验证迁移结果(执行完本脚本后可选运行)
-- SELECT pilot_id, COUNT(*) AS license_count FROM pilot_license GROUP BY pilot_id;

View File

@ -1,8 +0,0 @@
-- =====================================================================
-- V7: 删除 Pilot / Merchant 表的 order_count 统计字段
-- 规约依据:AGENTS.md §2.6 + §4 — 统计/派生字段禁止定义在 PO/Entity 中
-- 排序改为 Repository native SQL JOIN task COUNT 实时聚合
-- =====================================================================
ALTER TABLE pilot DROP COLUMN IF EXISTS order_count;
ALTER TABLE merchant DROP COLUMN IF EXISTS order_count;

View File

@ -1,34 +0,0 @@
-- =====================================================================
-- V8: course 表新增"排课 / 宣传图 / 联系人"字段(Web 管理端研学课程管理)
-- 背景:
-- Web 管理端需要维护课程的排课信息与咨询联系人,小程序端课程详情同步展示。
-- 排课支持两种互斥方式(schedule_type 区分):
-- 1=每周重复:每周固定星期几上课,取值存 schedule_week_days(1=周一 ... 7=周日,分号拼接)
-- 2=日期排课:指定具体日期上课,取值存 schedule_dates(yyyy-MM-dd,分号拼接)
-- 遵循项目多选字段规约:多值统一 String + 英文分号拼接,禁止 jsonb(简单标量多值)。
-- =====================================================================
-- 排课方式:1每周重复 2日期排课(必填,历史数据默认"每周重复")
ALTER TABLE course ADD COLUMN schedule_type SMALLINT NOT NULL DEFAULT 1;
-- 每周上课星期编码串(schedule_type=1 时有值,如 "1;3;5" 表示周一/周三/周五)
ALTER TABLE course ADD COLUMN schedule_week_days VARCHAR(20);
-- 排课日期串(schedule_type=2 时有值,如 "2026-09-10;2026-09-12")
ALTER TABLE course ADD COLUMN schedule_dates VARCHAR(500);
-- 课程宣传图片文件ID(关联 sys_file.id,小程序端课程卡片/详情头图)
ALTER TABLE course ADD COLUMN cover_image_id BIGINT;
-- 课程咨询联系人姓名
ALTER TABLE course ADD COLUMN contact_name VARCHAR(50);
-- 课程咨询联系电话
ALTER TABLE course ADD COLUMN contact_phone VARCHAR(20);
COMMENT ON COLUMN course.schedule_type IS '排课方式:1每周重复 2日期排课';
COMMENT ON COLUMN course.schedule_week_days IS '每周上课星期编码串(1=周一...7=周日,分号拼接,schedule_type=1 时有值)';
COMMENT ON COLUMN course.schedule_dates IS '排课日期串(yyyy-MM-dd 分号拼接,schedule_type=2 时有值)';
COMMENT ON COLUMN course.cover_image_id IS '课程宣传图片文件ID(关联 sys_file.id)';
COMMENT ON COLUMN course.contact_name IS '课程咨询联系人姓名';
COMMENT ON COLUMN course.contact_phone IS '课程咨询联系电话';

View File

@ -1,46 +0,0 @@
-- ============================================================
-- V9 —— 首页轮播图表 app_banner
-- 承载小程序首页轮播 Banner 的运营配置,支持图片上传、跳转链接、排序、启停
-- ============================================================
CREATE TABLE app_banner (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title VARCHAR(100) NOT NULL,
subtitle VARCHAR(200),
image_file_id BIGINT,
link_type VARCHAR(32),
link_url VARCHAR(500),
sort_order INT NOT NULL DEFAULT 0,
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX idx_app_banner_status ON app_banner (status);
CREATE INDEX idx_app_banner_sort ON app_banner (sort_order);
CREATE INDEX idx_app_banner_deleted ON app_banner (deleted_at);
COMMENT ON TABLE app_banner IS '首页轮播图表';
COMMENT ON COLUMN app_banner.id IS '主键';
COMMENT ON COLUMN app_banner.title IS '轮播图标题';
COMMENT ON COLUMN app_banner.subtitle IS '轮播图副标题/描述';
COMMENT ON COLUMN app_banner.image_file_id IS '轮播图图片file_id(关联 sys_file.id)';
COMMENT ON COLUMN app_banner.link_type IS '跳转类型:caac/performance/merchant/pilot/course/custom';
COMMENT ON COLUMN app_banner.link_url IS '自定义跳转链接(link_type=custom 时使用)';
COMMENT ON COLUMN app_banner.sort_order IS '排序值,升序,值越小越靠前';
COMMENT ON COLUMN app_banner.status IS '状态:0隐藏 1显示';
COMMENT ON COLUMN app_banner.created_at IS '创建时间';
COMMENT ON COLUMN app_banner.updated_at IS '更新时间';
COMMENT ON COLUMN app_banner.deleted_at IS '软删除时间';
CREATE TRIGGER trg_app_banner_updated BEFORE UPDATE ON app_banner
FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
-- ------------------------------------------------------------
-- 初始化 3 条轮播图数据(对应小程序现有的硬编码 bannerList)
-- image_file_id 暂设 NULL,后续通过 Web 管理端上传真实图片补全
-- ------------------------------------------------------------
INSERT INTO app_banner (title, subtitle, link_type, sort_order, status) VALUES
('CAAC 报名通道开启', '超250g无人机须实名,操控员须持证', 'caac', 1, 1),
('无人机表演专场', '高客单商业订单,专业飞手接单', 'performance', 2, 1),
('实力商家招商入驻', '开放聚合生态,精准流量分发', 'merchant', 3, 1);