1 parent
329e27b515
commit
8ffeb59dd4
33 files changed
+3427
-111
No files matched your search
@@ -0,0 +1,22 @@
|
||||
# 本地环境变量模板(可复制到 .env 后填写,.env 已被 .gitignore 排除)
|
||||
# 说明:本文件只放占位符,禁止提交任何真实口令。
|
||||
|
||||
APT_DB_HOST=127.0.0.1
|
||||
APT_DB_PORT=3306
|
||||
APT_DB_NAME=apt_rev01
|
||||
APT_DB_USER=iotlocks
|
||||
APT_DB_PASSWORD=<从本机安全位置填入,勿提交>
|
||||
|
||||
APT_REDIS_HOST=127.0.0.1
|
||||
APT_REDIS_PORT=6379
|
||||
APT_REDIS_DB=1
|
||||
APT_REDIS_PASSWORD=
|
||||
|
||||
APT_TOKEN_SECRET=<32 位以上随机串,可用 openssl rand -hex 32 生成>
|
||||
APT_GY_ID=APT-REV01-001
|
||||
APT_HTTP_PORT=8080
|
||||
APT_PROBE_ENABLED=true
|
||||
APT_PROBE_TIMEOUT_MS=2000
|
||||
APT_DRUID_CONSOLE=false
|
||||
APT_DRUID_USER=aptadmin
|
||||
APT_DRUID_PASSWORD=
|
||||
@@ -54,6 +54,13 @@
|
||||
<artifactId>ruoyi-generator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- IT-0 / IR-04:测试骨架(JUnit5 + MockMvc + Mockito) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -72,6 +79,12 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- IT-0 / IR-04:JUnit5 需 Surefire 3.x,默认 2.12.4 会静默漏跑全部用例 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.5.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package com.ruoyi.apt.ops.controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.ruoyi.apt.common.AptContext;
|
||||
import com.ruoyi.apt.common.AptErrorCode;
|
||||
import com.ruoyi.apt.common.AptException;
|
||||
import com.ruoyi.apt.framework.config.AptProperties;
|
||||
import com.ruoyi.apt.infra.connectivity.AptConnectivityService;
|
||||
import com.ruoyi.apt.infra.connectivity.ProbeResult;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* IT-0 基座自检接口(运维自检,非业务 Controller)。
|
||||
*
|
||||
* <p>按 R-02「冻结前不写 Controller」,本轮不提供任何入住 / 叫乘 / 账务业务接口;
|
||||
* 本接口只用于验证数据库与 Redis 的连接、读写与异常链路。匿名访问受双重限制:
|
||||
* 必须 apt.probe.enabled=true 且来源为回环地址。</p>
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/apt/ops/connectivity", produces = "application/json;charset=UTF-8")
|
||||
public class AptConnectivityController
|
||||
{
|
||||
private final AptConnectivityService connectivityService;
|
||||
private final AptProperties properties;
|
||||
|
||||
public AptConnectivityController(AptConnectivityService connectivityService,
|
||||
AptProperties properties)
|
||||
{
|
||||
this.connectivityService = connectivityService;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /apt/ops/connectivity —— 只读连通性检查(SELECT 1 + Redis PING + database 校验)。
|
||||
* 请求参数:无。
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping
|
||||
public AjaxResult check(HttpServletRequest request)
|
||||
{
|
||||
assertAllowed(request);
|
||||
List<ProbeResult> results = new ArrayList<>();
|
||||
results.add(connectivityService.checkDb());
|
||||
results.add(connectivityService.checkRedis());
|
||||
return assemble(results, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /apt/ops/connectivity/probe —— 读写闭环自检。
|
||||
* 请求体:{ "target": "DB|REDIS|ALL", "strict": true }
|
||||
* strict=true(默认):任一依赖失败即抛出统一错误码;strict=false 返回结构化降级结果。
|
||||
*/
|
||||
@Anonymous
|
||||
@PostMapping("/probe")
|
||||
public AjaxResult probe(@RequestBody(required = false) ProbeRequest body,
|
||||
HttpServletRequest request)
|
||||
{
|
||||
assertAllowed(request);
|
||||
ProbeRequest req = (body == null) ? new ProbeRequest() : body;
|
||||
String target = (req.getTarget() == null) ? "ALL" : req.getTarget().trim().toUpperCase();
|
||||
if (!"DB".equals(target) && !"REDIS".equals(target) && !"ALL".equals(target))
|
||||
{
|
||||
throw new AptException(AptErrorCode.BAD_REQUEST, "INVALID_TARGET",
|
||||
"target 仅支持 DB / REDIS / ALL");
|
||||
}
|
||||
List<ProbeResult> results = new ArrayList<>();
|
||||
if ("DB".equals(target) || "ALL".equals(target))
|
||||
{
|
||||
results.add(connectivityService.probeDb());
|
||||
}
|
||||
if ("REDIS".equals(target) || "ALL".equals(target))
|
||||
{
|
||||
results.add(connectivityService.probeRedis());
|
||||
}
|
||||
return assemble(results, req.isStrict());
|
||||
}
|
||||
|
||||
private AjaxResult assemble(List<ProbeResult> results, boolean strict)
|
||||
{
|
||||
boolean allOk = true;
|
||||
boolean anyOk = false;
|
||||
long totalMs = 0;
|
||||
List<Map<String, Object>> resultMaps = new ArrayList<>();
|
||||
ProbeResult firstFailure = null;
|
||||
for (ProbeResult result : results)
|
||||
{
|
||||
allOk &= result.isOk();
|
||||
anyOk |= result.isOk();
|
||||
totalMs += result.getElapsedMs();
|
||||
resultMaps.add(result.toMap());
|
||||
if (!result.isOk() && firstFailure == null)
|
||||
{
|
||||
firstFailure = result;
|
||||
}
|
||||
}
|
||||
if (strict && firstFailure != null)
|
||||
{
|
||||
throw new AptException(AptErrorCode.fromCode(firstFailure.getCode()),
|
||||
firstFailure.getReason(), "连通性自检失败:" + firstFailure.getTarget());
|
||||
}
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("overall", allOk ? "OK" : (anyOk ? "DEGRADED" : "FAILED"));
|
||||
data.put("gyid", AptContext.getGyId());
|
||||
data.put("traceId", AptContext.getTraceId());
|
||||
data.put("elapsedMs", totalMs);
|
||||
data.put("results", resultMaps);
|
||||
return new AjaxResult(AptErrorCode.SUCCESS.getCode(), "success", data);
|
||||
}
|
||||
|
||||
private void assertAllowed(HttpServletRequest request)
|
||||
{
|
||||
if (!properties.getProbe().isEnabled())
|
||||
{
|
||||
throw new AptException(AptErrorCode.FORBIDDEN_PERM, "PROBE_DISABLED",
|
||||
"连通性自检接口已关闭");
|
||||
}
|
||||
String remote = request.getRemoteAddr();
|
||||
if (!isLoopback(remote))
|
||||
{
|
||||
throw new AptException(AptErrorCode.FORBIDDEN_PERM, "NON_LOOPBACK_FORBIDDEN",
|
||||
"自检接口仅允许本机回环地址访问");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLoopback(String remote)
|
||||
{
|
||||
if (remote == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return remote.startsWith("127.")
|
||||
|| "0:0:0:0:0:0:0:1".equals(remote)
|
||||
|| "::1".equals(remote);
|
||||
}
|
||||
|
||||
/** POST /probe 请求体 */
|
||||
public static class ProbeRequest
|
||||
{
|
||||
private String target = "ALL";
|
||||
private boolean strict = true;
|
||||
|
||||
public String getTarget()
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(String target)
|
||||
{
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public boolean isStrict()
|
||||
{
|
||||
return strict;
|
||||
}
|
||||
|
||||
public void setStrict(boolean strict)
|
||||
{
|
||||
this.strict = strict;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,54 @@
|
||||
# 数据源配置
|
||||
# 数据源配置 —— IT-0 第一轮(apt_rev01)
|
||||
# 凭据一律外置到环境变量(IR-05 / 表 13-10 验收②:仓库内不得出现口令)
|
||||
# 缺失 APT_DB_PASSWORD 时应用启动即失败(fail-fast),不回退到任何默认口令
|
||||
spring:
|
||||
datasource:
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: password
|
||||
# 从库数据源
|
||||
url: jdbc:mysql://${APT_DB_HOST:127.0.0.1}:${APT_DB_PORT:3306}/${APT_DB_NAME:apt_rev01}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&connectTimeout=5000&socketTimeout=30000&rewriteBatchedStatements=true
|
||||
username: ${APT_DB_USER:iotlocks}
|
||||
password: ${APT_DB_PASSWORD}
|
||||
slave:
|
||||
# 从数据源开关/默认关闭
|
||||
enabled: false
|
||||
url:
|
||||
username:
|
||||
password:
|
||||
# 初始连接数
|
||||
initialSize: 5
|
||||
# 最小连接池数量
|
||||
minIdle: 10
|
||||
# 最大连接池数量
|
||||
url:
|
||||
username:
|
||||
password:
|
||||
# ---- 连接池与超时(连接失败 / 超时策略)----
|
||||
initialSize: 2
|
||||
minIdle: 2
|
||||
maxActive: 20
|
||||
# 配置获取连接等待超时的时间
|
||||
maxWait: 60000
|
||||
# 配置连接超时时间
|
||||
connectTimeout: 30000
|
||||
# 配置网络超时时间
|
||||
socketTimeout: 60000
|
||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||
minEvictableIdleTimeMillis: 300000
|
||||
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||
maxEvictableIdleTimeMillis: 900000
|
||||
# 配置检测连接是否有效
|
||||
validationQuery: SELECT 1 FROM DUAL
|
||||
# 取连接等待上限:5s 快速失败,避免线程长期挂起
|
||||
maxWait: 5000
|
||||
connectTimeout: 5000
|
||||
socketTimeout: 30000
|
||||
# 连接有效性校验:借出时校验,坏连接立即被剔除
|
||||
validationQuery: SELECT 1
|
||||
testWhileIdle: true
|
||||
testOnBorrow: false
|
||||
testOnBorrow: true
|
||||
testOnReturn: false
|
||||
webStatFilter:
|
||||
enabled: true
|
||||
statViewServlet:
|
||||
enabled: true
|
||||
# 设置白名单,不填则允许所有访问
|
||||
allow:
|
||||
url-pattern: /druid/*
|
||||
# 控制台管理用户名和密码
|
||||
login-username: ruoyi
|
||||
login-password: 123456
|
||||
timeBetweenEvictionRunsMillis: 30000
|
||||
minEvictableIdleTimeMillis: 60000
|
||||
maxEvictableIdleTimeMillis: 900000
|
||||
# 连接初始化字符集
|
||||
connection-init-sqls: SET NAMES utf8mb4
|
||||
# 慢 SQL 与防火墙
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
# 慢SQL记录
|
||||
log-slow-sql: true
|
||||
slow-sql-millis: 1000
|
||||
merge-sql: true
|
||||
wall:
|
||||
config:
|
||||
multi-statement-allow: true
|
||||
multi-statement-allow: false
|
||||
webStatFilter:
|
||||
enabled: false
|
||||
statViewServlet:
|
||||
# 控制台默认关闭;确需开启时由环境变量提供口令,不落仓库
|
||||
enabled: ${APT_DRUID_CONSOLE:false}
|
||||
allow: 127.0.0.1
|
||||
url-pattern: /druid/*
|
||||
login-username: ${APT_DRUID_USER:aptadmin}
|
||||
login-password: ${APT_DRUID_PASSWORD:}
|
||||
@@ -1,122 +1,88 @@
|
||||
# 项目相关配置
|
||||
ruoyi:
|
||||
# 名称
|
||||
name: RuoYi
|
||||
# 版本
|
||||
name: AptRev01
|
||||
version: 3.9.2
|
||||
# 版权年份
|
||||
copyrightYear: 2026
|
||||
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
|
||||
profile: D:/ruoyi/uploadPath
|
||||
# 获取ip地址开关
|
||||
# 相对路径,避免硬编码本机绝对路径
|
||||
profile: ${APT_UPLOAD_PATH:./uploadPath}
|
||||
addressEnabled: false
|
||||
# 验证码类型 math 数字计算 char 字符验证
|
||||
captchaType: math
|
||||
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8080
|
||||
port: ${APT_HTTP_PORT:8080}
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
encoding:
|
||||
charset: UTF-8
|
||||
enabled: true
|
||||
force-response: true
|
||||
tomcat:
|
||||
# tomcat的URI编码
|
||||
uri-encoding: UTF-8
|
||||
# 连接数满后的排队数,默认为100
|
||||
accept-count: 1000
|
||||
threads:
|
||||
# tomcat最大线程数,默认为200
|
||||
max: 800
|
||||
# Tomcat启动初始化的线程数,默认值10
|
||||
min-spare: 100
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.ruoyi: debug
|
||||
com.ruoyi.apt: info
|
||||
org.springframework: warn
|
||||
|
||||
# 用户配置
|
||||
user:
|
||||
password:
|
||||
# 密码最大错误次数
|
||||
maxRetryCount: 5
|
||||
# 密码锁定时间(默认10分钟)
|
||||
lockTime: 10
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
# 资源信息
|
||||
messages:
|
||||
# 国际化资源文件路径
|
||||
basename: i18n/messages
|
||||
profiles:
|
||||
active: druid
|
||||
# 文件上传
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 10MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 20MB
|
||||
jackson:
|
||||
time-zone: GMT+8
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
# 热部署开关
|
||||
enabled: true
|
||||
enabled: false
|
||||
data:
|
||||
# redis 配置
|
||||
redis:
|
||||
# 地址
|
||||
host: localhost
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 0
|
||||
# 密码
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
host: ${APT_REDIS_HOST:127.0.0.1}
|
||||
port: ${APT_REDIS_PORT:6379}
|
||||
# 第一轮要求:使用本机 Redis 的 database 1
|
||||
database: ${APT_REDIS_DB:1}
|
||||
password: ${APT_REDIS_PASSWORD:}
|
||||
# 读写/连接超时 3s,超时快速失败并交由统一异常处理映射
|
||||
timeout: 3s
|
||||
connect-timeout: 3s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池的最大数据库连接数
|
||||
max-active: 8
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
max-active: 16
|
||||
max-wait: 2s
|
||||
shutdown-timeout: 100ms
|
||||
|
||||
# token配置
|
||||
# token 密钥外置,不提供任何默认值
|
||||
token:
|
||||
# 令牌自定义标识
|
||||
header: Authorization
|
||||
# 令牌密钥
|
||||
secret: abcdefghijklmnopqrstuvwxyz
|
||||
# 令牌有效期(默认30分钟)
|
||||
secret: ${APT_TOKEN_SECRET}
|
||||
expireTime: 30
|
||||
|
||||
# MyBatis配置
|
||||
mybatis:
|
||||
# 搜索指定包别名
|
||||
typeAliasesPackage: com.ruoyi.**.domain
|
||||
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
# 加载全局的配置文件
|
||||
configLocation: classpath:mybatis/mybatis-config.xml
|
||||
|
||||
# PageHelper分页插件
|
||||
pagehelper:
|
||||
helperDialect: mysql
|
||||
supportMethodsArguments: true
|
||||
params: count=countSql
|
||||
|
||||
# Springdoc配置
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /v3/api-docs
|
||||
@@ -125,23 +91,32 @@ springdoc:
|
||||
path: /swagger-ui.html
|
||||
tags-sorter: alpha
|
||||
group-configs:
|
||||
- group: 'default'
|
||||
display-name: '测试模块'
|
||||
paths-to-match: '/**'
|
||||
packages-to-scan: com.ruoyi.web.controller.tool
|
||||
- group: 'apt'
|
||||
display-name: '公寓端基座'
|
||||
paths-to-match: '/apt/**'
|
||||
packages-to-scan: com.ruoyi.apt.**
|
||||
|
||||
# 防盗链配置
|
||||
referer:
|
||||
# 防盗链开关
|
||||
enabled: false
|
||||
# 允许的域名列表
|
||||
allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
|
||||
allowed-domains: localhost,127.0.0.1
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
# 排除链接(多个用逗号分隔)
|
||||
excludes: /system/notice
|
||||
# 匹配链接
|
||||
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||
|
||||
# ---- 公寓端基座自定义配置(IT-0 / IT-1)----
|
||||
apt:
|
||||
# 一进程一 gyid(D5):只从部署环境注入,不接受请求参数
|
||||
gy-id: ${APT_GY_ID:APT-REV01-001}
|
||||
probe:
|
||||
enabled: ${APT_PROBE_ENABLED:true}
|
||||
# 单次探针超时(毫秒),超过即判定 TIMEOUT
|
||||
timeout-ms: ${APT_PROBE_TIMEOUT_MS:2000}
|
||||
redis-key-prefix: ${APT_REDIS_KEY_PREFIX:apt:rev01:}
|
||||
# 探针数据存活时间(秒),避免残留脏数据
|
||||
redis-ttl-seconds: 60
|
||||
# 探针表名(只写探针专用表,不触碰任何业务表)
|
||||
table: apt_ops_probe
|
||||
page:
|
||||
max-size: 100
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.ruoyi.apt.common;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** D5 gyid 隔离与 traceId 上下文的基线用例(不依赖数据库/Redis)。 */
|
||||
class AptContextTest
|
||||
{
|
||||
@AfterEach
|
||||
void tearDown()
|
||||
{
|
||||
AptContext.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingGyIdIsRejected()
|
||||
{
|
||||
AptException e = assertThrows(AptException.class, AptContext::getGyId);
|
||||
assertEquals(40302, e.getErrorCode().getCode());
|
||||
assertEquals("GY_ID_MISSING", e.getReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
void gyIdAndTraceIdAreStableWithinThread()
|
||||
{
|
||||
AptContext.setGyId("APT-REV01-001");
|
||||
AptContext.initTraceId();
|
||||
assertEquals("APT-REV01-001", AptContext.getGyId());
|
||||
String traceId = AptContext.getTraceId();
|
||||
assertNotNull(traceId);
|
||||
assertEquals(traceId, AptContext.getTraceId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearRemovesAllThreadLocals()
|
||||
{
|
||||
AptContext.setGyId("APT-REV01-001");
|
||||
AptContext.initTraceId();
|
||||
AptContext.clear();
|
||||
assertNull(AptContext.getGyIdOrNull());
|
||||
assertThrows(AptException.class, AptContext::getGyId);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.ruoyi.apt.framework;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import java.util.Map;
|
||||
import com.ruoyi.apt.common.AptErrorCode;
|
||||
import com.ruoyi.apt.common.AptException;
|
||||
import com.ruoyi.apt.framework.web.AptInfraExceptionHandler;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.dao.QueryTimeoutException;
|
||||
import org.springframework.jdbc.CannotGetJdbcConnectionException;
|
||||
|
||||
/** 连接失败 / 超时 / 数据异常 -> 统一错误码映射的基线用例。 */
|
||||
class AptInfraExceptionHandlerTest
|
||||
{
|
||||
private final AptInfraExceptionHandler handler = new AptInfraExceptionHandler();
|
||||
private final HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
|
||||
@Test
|
||||
void dbConnectFailureMapsTo50301()
|
||||
{
|
||||
AjaxResult result = handler.handleDbConnectFailure(
|
||||
new CannotGetJdbcConnectionException("connection refused"), request);
|
||||
assertError(result, 50301, "DB_CONNECT_FAILED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void timeoutMapsTo50301()
|
||||
{
|
||||
AjaxResult result = handler.handleTimeout(new QueryTimeoutException("slow query"), request);
|
||||
assertError(result, 50301, "DEPENDENCY_TIMEOUT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateKeyMapsTo40901()
|
||||
{
|
||||
AjaxResult result = handler.handleDuplicateKey(new DuplicateKeyException("dup"), request);
|
||||
assertError(result, 40901, "DUPLICATE_KEY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void aptExceptionKeepsCodeAndReason()
|
||||
{
|
||||
AjaxResult result = handler.handleAptException(
|
||||
new AptException(AptErrorCode.DATA_ANOMALY, "SCHEMA_MISMATCH", "表结构不匹配"), request);
|
||||
assertError(result, 50304, "SCHEMA_MISMATCH");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void assertError(AjaxResult result, int code, String reason)
|
||||
{
|
||||
assertEquals(code, result.get(AjaxResult.CODE_TAG));
|
||||
Map<String, Object> data = (Map<String, Object>) result.get(AjaxResult.DATA_TAG);
|
||||
assertEquals(reason, data.get("reason"));
|
||||
assertNotNull(data.get("traceId"), "错误响应必须带 traceId");
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package com.ruoyi.apt.ops;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import com.ruoyi.apt.common.AptContext;
|
||||
import com.ruoyi.apt.common.AptErrorCode;
|
||||
import com.ruoyi.apt.framework.config.AptProperties;
|
||||
import com.ruoyi.apt.framework.web.AptInfraExceptionHandler;
|
||||
import com.ruoyi.apt.infra.connectivity.AptConnectivityService;
|
||||
import com.ruoyi.apt.infra.connectivity.ProbeResult;
|
||||
import com.ruoyi.apt.ops.controller.AptConnectivityController;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
/** 自检接口的请求参数、返回结构与错误码约定回归基线(不依赖真实 DB/Redis)。 */
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AptConnectivityControllerTest
|
||||
{
|
||||
@Mock
|
||||
private AptConnectivityService connectivityService;
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@BeforeEach
|
||||
void setUp()
|
||||
{
|
||||
AptProperties properties = new AptProperties();
|
||||
properties.setGyId("APT-REV01-001");
|
||||
AptConnectivityController controller =
|
||||
new AptConnectivityController(connectivityService, properties);
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(controller)
|
||||
.setControllerAdvice(new AptInfraExceptionHandler())
|
||||
.build();
|
||||
AptContext.setGyId("APT-REV01-001");
|
||||
AptContext.initTraceId();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown()
|
||||
{
|
||||
AptContext.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkReturnsOverallOk() throws Exception
|
||||
{
|
||||
when(connectivityService.checkDb()).thenReturn(new ProbeResult("DB"));
|
||||
when(connectivityService.checkRedis()).thenReturn(new ProbeResult("REDIS"));
|
||||
mockMvc.perform(get("/apt/ops/connectivity"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.overall").value("OK"))
|
||||
.andExpect(jsonPath("$.data.gyid").value("APT-REV01-001"))
|
||||
.andExpect(jsonPath("$.data.results.length()").value(2))
|
||||
.andExpect(jsonPath("$.data.traceId").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidTargetReturns40001() throws Exception
|
||||
{
|
||||
mockMvc.perform(post("/apt/ops/connectivity/probe")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"target\":\"UNKNOWN\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(40001))
|
||||
.andExpect(jsonPath("$.data.reason").value("INVALID_TARGET"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void strictFailureReturns50301() throws Exception
|
||||
{
|
||||
when(connectivityService.probeDb()).thenReturn(new ProbeResult("DB")
|
||||
.fail(AptErrorCode.DEPENDENCY_UNAVAILABLE, "DB_CONNECT_FAILED", "connection refused"));
|
||||
mockMvc.perform(post("/apt/ops/connectivity/probe")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"target\":\"DB\",\"strict\":true}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(50301))
|
||||
.andExpect(jsonPath("$.data.reason").value("DB_CONNECT_FAILED"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonStrictFailureReturnsDegradedStructure() throws Exception
|
||||
{
|
||||
when(connectivityService.probeDb()).thenReturn(new ProbeResult("DB"));
|
||||
when(connectivityService.probeRedis()).thenReturn(new ProbeResult("REDIS")
|
||||
.fail(AptErrorCode.DEPENDENCY_UNAVAILABLE, "REDIS_TIMEOUT", "timeout"));
|
||||
mockMvc.perform(post("/apt/ops/connectivity/probe")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"target\":\"ALL\",\"strict\":false}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.overall").value("DEGRADED"))
|
||||
.andExpect(jsonPath("$.data.results[1].reason").value("REDIS_TIMEOUT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonLoopbackIsForbidden() throws Exception
|
||||
{
|
||||
mockMvc.perform(get("/apt/ops/connectivity")
|
||||
.with(req -> { req.setRemoteAddr("10.0.0.9"); return req; }))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(40301))
|
||||
.andExpect(jsonPath("$.data.reason").value("NON_LOOPBACK_FORBIDDEN"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.ruoyi.apt.common;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 公寓端运行时上下文:gyid 与 traceId。
|
||||
*
|
||||
* <p>D5「一进程一 gyid」:gyid 只从部署环境注入(拦截器写入),
|
||||
* 绝不接受请求参数;所有业务 SQL 必须带 gyid。traceId 用于错误响应
|
||||
* 与日志关联,避免把堆栈外泄给调用方。</p>
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
public final class AptContext
|
||||
{
|
||||
private static final ThreadLocal<String> GY_ID = new ThreadLocal<>();
|
||||
private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
|
||||
|
||||
private AptContext()
|
||||
{
|
||||
}
|
||||
|
||||
public static void setGyId(String gyId)
|
||||
{
|
||||
GY_ID.set(gyId);
|
||||
}
|
||||
|
||||
/** 取 gyid;未注入视为隔离失效,直接拒绝(40302) */
|
||||
public static String getGyId()
|
||||
{
|
||||
String value = GY_ID.get();
|
||||
if (value == null || value.isEmpty())
|
||||
{
|
||||
throw new AptException(AptErrorCode.FORBIDDEN_GYID, "GY_ID_MISSING", "公寓标识未注入");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String getGyIdOrNull()
|
||||
{
|
||||
return GY_ID.get();
|
||||
}
|
||||
|
||||
public static void initTraceId()
|
||||
{
|
||||
TRACE_ID.set(UUID.randomUUID().toString().replace("-", ""));
|
||||
}
|
||||
|
||||
public static String getTraceId()
|
||||
{
|
||||
String value = TRACE_ID.get();
|
||||
if (value == null)
|
||||
{
|
||||
value = UUID.randomUUID().toString().replace("-", "");
|
||||
TRACE_ID.set(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void clear()
|
||||
{
|
||||
GY_ID.remove();
|
||||
TRACE_ID.remove();
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.ruoyi.apt.common;
|
||||
|
||||
/**
|
||||
* 公寓端统一错误码(沿用 公寓端/04 表 4-4 分段,不新造号段)。
|
||||
*
|
||||
* <p>第一轮新增 50304「数据异常」落在既有 503xx 段内(与 CR-02 在同一分段内
|
||||
* 追加 50303 的做法一致),需阶段二契约冻结评审登记后方可对外启用。</p>
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
public enum AptErrorCode
|
||||
{
|
||||
SUCCESS(200, "success"),
|
||||
BAD_REQUEST(40001, "参数错误"),
|
||||
UNAUTHORIZED(40101, "未登录"),
|
||||
FORBIDDEN_PERM(40301, "缺少权限点"),
|
||||
FORBIDDEN_GYID(40302, "gyid 越界或缺失"),
|
||||
NOT_FOUND(40401, "资源不存在"),
|
||||
DUPLICATE_KEY(40901, "业务键重复"),
|
||||
DEPENDENCY_UNAVAILABLE(50301, "依赖不可用"),
|
||||
/** 数据异常:完整性约束、读写结果不符、序列化失败 */
|
||||
DATA_ANOMALY(50304, "数据异常"),
|
||||
INTERNAL_ERROR(500, "内部错误");
|
||||
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
||||
AptErrorCode(int code, String message)
|
||||
{
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
public static AptErrorCode fromCode(int code)
|
||||
{
|
||||
for (AptErrorCode value : values())
|
||||
{
|
||||
if (value.code == code)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.apt.common;
|
||||
|
||||
/**
|
||||
* 带错误码与 reason 的公寓端异常,由统一异常处理器映射为 HTTP 响应。
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
public class AptException extends RuntimeException
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final AptErrorCode errorCode;
|
||||
private final String reason;
|
||||
|
||||
public AptException(AptErrorCode errorCode, String reason, String message)
|
||||
{
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public AptException(AptErrorCode errorCode, String reason, String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
this.errorCode = errorCode;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public AptErrorCode getErrorCode()
|
||||
{
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public String getReason()
|
||||
{
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package com.ruoyi.apt.framework.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 公寓端基座配置(application.yml 的 apt.* 段)。
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "apt")
|
||||
public class AptProperties
|
||||
{
|
||||
/** 一进程一 gyid(D5),只从部署环境注入 */
|
||||
private String gyId;
|
||||
|
||||
private Probe probe = new Probe();
|
||||
private Page page = new Page();
|
||||
|
||||
public String getGyId()
|
||||
{
|
||||
return gyId;
|
||||
}
|
||||
|
||||
public void setGyId(String gyId)
|
||||
{
|
||||
this.gyId = gyId;
|
||||
}
|
||||
|
||||
public Probe getProbe()
|
||||
{
|
||||
return probe;
|
||||
}
|
||||
|
||||
public void setProbe(Probe probe)
|
||||
{
|
||||
this.probe = probe;
|
||||
}
|
||||
|
||||
public Page getPage()
|
||||
{
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(Page page)
|
||||
{
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public static class Probe
|
||||
{
|
||||
private boolean enabled = true;
|
||||
/** 单次探针超时判定阈值(毫秒) */
|
||||
private long timeoutMs = 2000;
|
||||
private String redisKeyPrefix = "apt:rev01:";
|
||||
private int redisTtlSeconds = 60;
|
||||
/** 探针专用表,不触碰任何业务表 */
|
||||
private String table = "apt_ops_probe";
|
||||
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public long getTimeoutMs()
|
||||
{
|
||||
return timeoutMs;
|
||||
}
|
||||
|
||||
public void setTimeoutMs(long timeoutMs)
|
||||
{
|
||||
this.timeoutMs = timeoutMs;
|
||||
}
|
||||
|
||||
public String getRedisKeyPrefix()
|
||||
{
|
||||
return redisKeyPrefix;
|
||||
}
|
||||
|
||||
public void setRedisKeyPrefix(String redisKeyPrefix)
|
||||
{
|
||||
this.redisKeyPrefix = redisKeyPrefix;
|
||||
}
|
||||
|
||||
public int getRedisTtlSeconds()
|
||||
{
|
||||
return redisTtlSeconds;
|
||||
}
|
||||
|
||||
public void setRedisTtlSeconds(int redisTtlSeconds)
|
||||
{
|
||||
this.redisTtlSeconds = redisTtlSeconds;
|
||||
}
|
||||
|
||||
public String getTable()
|
||||
{
|
||||
return table;
|
||||
}
|
||||
|
||||
public void setTable(String table)
|
||||
{
|
||||
this.table = table;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Page
|
||||
{
|
||||
private int maxSize = 100;
|
||||
|
||||
public int getMaxSize()
|
||||
{
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
public void setMaxSize(int maxSize)
|
||||
{
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.ruoyi.apt.framework.web;
|
||||
|
||||
import com.ruoyi.apt.common.AptContext;
|
||||
import com.ruoyi.apt.framework.config.AptProperties;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
/**
|
||||
* 把部署环境注入的 gyid 与 traceId 放入线程上下文。
|
||||
* 仅作用于 /apt/**,且只从配置读取,绝不采信请求参数。
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
@Component
|
||||
public class AptContextInterceptor implements HandlerInterceptor
|
||||
{
|
||||
private final AptProperties properties;
|
||||
|
||||
public AptContextInterceptor(AptProperties properties)
|
||||
{
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
{
|
||||
AptContext.initTraceId();
|
||||
AptContext.setGyId(properties.getGyId());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception ex)
|
||||
{
|
||||
AptContext.clear();
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package com.ruoyi.apt.framework.web;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.ruoyi.apt.common.AptContext;
|
||||
import com.ruoyi.apt.common.AptErrorCode;
|
||||
import com.ruoyi.apt.common.AptException;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.dao.QueryTimeoutException;
|
||||
import org.springframework.data.redis.RedisConnectionFailureException;
|
||||
import org.springframework.data.redis.RedisSystemException;
|
||||
import org.springframework.jdbc.BadSqlGrammarException;
|
||||
import org.springframework.jdbc.CannotGetJdbcConnectionException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 基础设施异常统一映射:连接失败 / 超时 / 数据异常 -> 约定错误码 + reason + traceId。
|
||||
* 不向调用方外泄堆栈,只写日志(公寓端/04 表 4-4、表 13-11 M0 验收)。
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class AptInfraExceptionHandler
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(AptInfraExceptionHandler.class);
|
||||
|
||||
@ExceptionHandler(AptException.class)
|
||||
public AjaxResult handleAptException(AptException e, HttpServletRequest request)
|
||||
{
|
||||
log.error("apt infra error, uri={}, code={}, reason={}, traceId={}",
|
||||
request.getRequestURI(), e.getErrorCode().getCode(), e.getReason(),
|
||||
AptContext.getTraceId(), e);
|
||||
return build(e.getErrorCode(), e.getReason(), e.getMessage(), request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DuplicateKeyException.class)
|
||||
public AjaxResult handleDuplicateKey(DuplicateKeyException e, HttpServletRequest request)
|
||||
{
|
||||
return handle(AptErrorCode.DUPLICATE_KEY, "DUPLICATE_KEY", "业务键重复", e, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(CannotGetJdbcConnectionException.class)
|
||||
public AjaxResult handleDbConnectFailure(CannotGetJdbcConnectionException e, HttpServletRequest request)
|
||||
{
|
||||
return handle(AptErrorCode.DEPENDENCY_UNAVAILABLE, "DB_CONNECT_FAILED", "数据库连接失败", e, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(QueryTimeoutException.class)
|
||||
public AjaxResult handleTimeout(QueryTimeoutException e, HttpServletRequest request)
|
||||
{
|
||||
return handle(AptErrorCode.DEPENDENCY_UNAVAILABLE, "DEPENDENCY_TIMEOUT", "依赖访问超时", e, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(BadSqlGrammarException.class)
|
||||
public AjaxResult handleSchemaMismatch(BadSqlGrammarException e, HttpServletRequest request)
|
||||
{
|
||||
return handle(AptErrorCode.DATA_ANOMALY, "SCHEMA_MISMATCH", "表结构与代码不匹配", e, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public AjaxResult handleIntegrity(DataIntegrityViolationException e, HttpServletRequest request)
|
||||
{
|
||||
return handle(AptErrorCode.DATA_ANOMALY, "DATA_INTEGRITY_VIOLATION", "数据完整性约束被破坏", e, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(RedisConnectionFailureException.class)
|
||||
public AjaxResult handleRedisConnectFailure(RedisConnectionFailureException e, HttpServletRequest request)
|
||||
{
|
||||
return handle(AptErrorCode.DEPENDENCY_UNAVAILABLE, "REDIS_CONNECT_FAILED", "Redis 连接失败", e, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(RedisSystemException.class)
|
||||
public AjaxResult handleRedisSystem(RedisSystemException e, HttpServletRequest request)
|
||||
{
|
||||
return handle(AptErrorCode.DEPENDENCY_UNAVAILABLE, "REDIS_ERROR", "Redis 命令执行异常", e, request);
|
||||
}
|
||||
|
||||
/** JDBC / Redis 的兜底数据访问异常 */
|
||||
@ExceptionHandler(DataAccessException.class)
|
||||
public AjaxResult handleDataAccess(DataAccessException e, HttpServletRequest request)
|
||||
{
|
||||
return handle(AptErrorCode.DEPENDENCY_UNAVAILABLE, "DEPENDENCY_ERROR", "依赖访问异常", e, request);
|
||||
}
|
||||
|
||||
private AjaxResult handle(AptErrorCode code, String reason, String message,
|
||||
Exception e, HttpServletRequest request)
|
||||
{
|
||||
log.error("apt infra error, uri={}, code={}, reason={}, traceId={}",
|
||||
request.getRequestURI(), code.getCode(), reason, AptContext.getTraceId(), e);
|
||||
return build(code, reason, message, request);
|
||||
}
|
||||
|
||||
private AjaxResult build(AptErrorCode code, String reason, String message, HttpServletRequest request)
|
||||
{
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("reason", reason);
|
||||
data.put("traceId", AptContext.getTraceId());
|
||||
data.put("path", request.getRequestURI());
|
||||
return new AjaxResult(code.getCode(), message, data);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.apt.framework.web;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* /apt/** 前缀的基座拦截器注册(四前缀拦截器最小集,IT-1 再补 /duty/** 与 /open/**)。
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
@Configuration
|
||||
public class AptWebMvcConfig implements WebMvcConfigurer
|
||||
{
|
||||
private final AptContextInterceptor aptContextInterceptor;
|
||||
|
||||
public AptWebMvcConfig(AptContextInterceptor aptContextInterceptor)
|
||||
{
|
||||
this.aptContextInterceptor = aptContextInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry)
|
||||
{
|
||||
registry.addInterceptor(aptContextInterceptor).addPathPatterns("/apt/**");
|
||||
}
|
||||
}
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
package com.ruoyi.apt.infra.connectivity;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
import javax.sql.DataSource;
|
||||
import com.ruoyi.apt.common.AptContext;
|
||||
import com.ruoyi.apt.common.AptErrorCode;
|
||||
import com.ruoyi.apt.common.AptException;
|
||||
import com.ruoyi.apt.framework.config.AptProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.dao.QueryTimeoutException;
|
||||
import org.springframework.data.redis.RedisConnectionFailureException;
|
||||
import org.springframework.data.redis.RedisSystemException;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.jdbc.BadSqlGrammarException;
|
||||
import org.springframework.jdbc.CannotGetJdbcConnectionException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 数据库 / Redis 连通性与读写闭环探针(IT-0 基座,不触碰任何业务表)。
|
||||
*
|
||||
* <p>异常分类:连接失败 -> 50301 + DB_CONNECT_FAILED / REDIS_CONNECT_FAILED;
|
||||
* 超时 -> 50301 + *_TIMEOUT;重复键 -> 40901;完整性 / 读写结果不符 -> 50304。
|
||||
* JDBC 查询超时由 JdbcTemplate#setQueryTimeout 从 apt.probe.timeout-ms 推导,
|
||||
* Redis 命令超时由 spring.data.redis.timeout 统一兜底,探针再按 elapsedMs 二次判定。</p>
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
@Service
|
||||
public class AptConnectivityService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(AptConnectivityService.class);
|
||||
private static final String PROBE_VALUE = "it0-probe";
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
private final StringRedisTemplate redis;
|
||||
private final AptProperties properties;
|
||||
|
||||
public AptConnectivityService(DataSource dataSource, StringRedisTemplate redis,
|
||||
AptProperties properties)
|
||||
{
|
||||
this.jdbc = new JdbcTemplate(dataSource);
|
||||
int timeoutSeconds = (int) Math.max(1, properties.getProbe().getTimeoutMs() / 1000);
|
||||
this.jdbc.setQueryTimeout(timeoutSeconds);
|
||||
this.redis = redis;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/** 只读连通性检查:SELECT 1 + 探针表计数 */
|
||||
public ProbeResult checkDb()
|
||||
{
|
||||
ProbeResult result = new ProbeResult("DB");
|
||||
long start = System.nanoTime();
|
||||
try
|
||||
{
|
||||
Integer one = jdbc.queryForObject("SELECT 1", Integer.class);
|
||||
if (one == null || one != 1)
|
||||
{
|
||||
return fail(result, AptErrorCode.DATA_ANOMALY, "DB_READ_MISMATCH", "SELECT 1 返回值异常");
|
||||
}
|
||||
Long rows = jdbc.queryForObject("SELECT COUNT(*) FROM " + probeTable(), Long.class);
|
||||
result.detail("operation", "SELECT");
|
||||
result.detail("select1", one);
|
||||
result.detail("probeRows", rows);
|
||||
result.detail("gyid", gyId());
|
||||
}
|
||||
catch (CannotGetJdbcConnectionException e)
|
||||
{
|
||||
classifyDb(result, "DB_CONNECT_FAILED", AptErrorCode.DEPENDENCY_UNAVAILABLE, e);
|
||||
}
|
||||
catch (QueryTimeoutException e)
|
||||
{
|
||||
classifyDb(result, "DB_TIMEOUT", AptErrorCode.DEPENDENCY_UNAVAILABLE, e);
|
||||
}
|
||||
catch (BadSqlGrammarException e)
|
||||
{
|
||||
classifyDb(result, "SCHEMA_MISMATCH", AptErrorCode.DATA_ANOMALY, e);
|
||||
}
|
||||
catch (DataIntegrityViolationException e)
|
||||
{
|
||||
classifyDb(result, "DATA_INTEGRITY_VIOLATION", AptErrorCode.DATA_ANOMALY, e);
|
||||
}
|
||||
catch (DataAccessException e)
|
||||
{
|
||||
classifyDb(result, "DB_ERROR", AptErrorCode.DEPENDENCY_UNAVAILABLE, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
finish(result, start);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 读写闭环:INSERT -> SELECT -> DELETE,失败不残留脏数据 */
|
||||
public ProbeResult probeDb()
|
||||
{
|
||||
ProbeResult result = new ProbeResult("DB");
|
||||
String key = "db-" + shortId();
|
||||
String table = probeTable();
|
||||
long start = System.nanoTime();
|
||||
try
|
||||
{
|
||||
int inserted = jdbc.update(
|
||||
"INSERT INTO " + table + " (gyid, probe_key, payload) VALUES (?, ?, ?)",
|
||||
gyId(), key, PROBE_VALUE);
|
||||
String read = jdbc.queryForObject(
|
||||
"SELECT payload FROM " + table + " WHERE gyid = ? AND probe_key = ?",
|
||||
String.class, gyId(), key);
|
||||
int deleted = jdbc.update(
|
||||
"DELETE FROM " + table + " WHERE gyid = ? AND probe_key = ?", gyId(), key);
|
||||
if (inserted != 1 || !PROBE_VALUE.equals(read) || deleted != 1)
|
||||
{
|
||||
return fail(result, AptErrorCode.DATA_ANOMALY, "DB_READ_WRITE_MISMATCH",
|
||||
"写入/读取/删除结果不一致");
|
||||
}
|
||||
result.detail("operation", "INSERT/SELECT/DELETE");
|
||||
result.detail("inserted", inserted);
|
||||
result.detail("readBack", read);
|
||||
result.detail("deleted", deleted);
|
||||
result.detail("gyid", gyId());
|
||||
}
|
||||
catch (CannotGetJdbcConnectionException e)
|
||||
{
|
||||
classifyDb(result, "DB_CONNECT_FAILED", AptErrorCode.DEPENDENCY_UNAVAILABLE, e);
|
||||
}
|
||||
catch (QueryTimeoutException e)
|
||||
{
|
||||
classifyDb(result, "DB_TIMEOUT", AptErrorCode.DEPENDENCY_UNAVAILABLE, e);
|
||||
}
|
||||
catch (DuplicateKeyException e)
|
||||
{
|
||||
classifyDb(result, "DUPLICATE_KEY", AptErrorCode.DUPLICATE_KEY, e);
|
||||
}
|
||||
catch (BadSqlGrammarException e)
|
||||
{
|
||||
classifyDb(result, "SCHEMA_MISMATCH", AptErrorCode.DATA_ANOMALY, e);
|
||||
}
|
||||
catch (DataIntegrityViolationException e)
|
||||
{
|
||||
classifyDb(result, "DATA_INTEGRITY_VIOLATION", AptErrorCode.DATA_ANOMALY, e);
|
||||
}
|
||||
catch (DataAccessException e)
|
||||
{
|
||||
classifyDb(result, "DB_ERROR", AptErrorCode.DEPENDENCY_UNAVAILABLE, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
cleanupDbQuietly(table, key);
|
||||
finish(result, start);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 只读连通性检查:PING + database 索引校验 */
|
||||
public ProbeResult checkRedis()
|
||||
{
|
||||
ProbeResult result = new ProbeResult("REDIS");
|
||||
long start = System.nanoTime();
|
||||
try
|
||||
{
|
||||
String pong = redis.execute((RedisCallback<String>) connection -> connection.ping());
|
||||
int database = redisDatabase();
|
||||
if (pong == null || !"PONG".equalsIgnoreCase(pong))
|
||||
{
|
||||
return fail(result, AptErrorCode.DATA_ANOMALY, "REDIS_READ_MISMATCH", "PING 返回值异常");
|
||||
}
|
||||
if (database != 1)
|
||||
{
|
||||
return fail(result, AptErrorCode.DATA_ANOMALY, "REDIS_DB_MISMATCH",
|
||||
"Redis database 应为 1,实际为 " + database);
|
||||
}
|
||||
result.detail("operation", "PING");
|
||||
result.detail("ping", pong);
|
||||
result.detail("database", database);
|
||||
}
|
||||
catch (RedisConnectionFailureException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_CONNECT_FAILED", e);
|
||||
}
|
||||
catch (QueryTimeoutException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_TIMEOUT", e);
|
||||
}
|
||||
catch (RedisSystemException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_ERROR", e);
|
||||
}
|
||||
catch (DataAccessException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_ERROR", e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_ERROR", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
finish(result, start);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 读写闭环:SET(带 TTL) -> GET -> DEL,失败不残留 key */
|
||||
public ProbeResult probeRedis()
|
||||
{
|
||||
ProbeResult result = new ProbeResult("REDIS");
|
||||
String key = properties.getProbe().getRedisKeyPrefix() + "probe:" + shortId();
|
||||
long start = System.nanoTime();
|
||||
try
|
||||
{
|
||||
redis.opsForValue().set(key, PROBE_VALUE,
|
||||
Duration.ofSeconds(Math.max(1, properties.getProbe().getRedisTtlSeconds())));
|
||||
String read = redis.opsForValue().get(key);
|
||||
Boolean deleted = redis.delete(key);
|
||||
if (!PROBE_VALUE.equals(read) || !Boolean.TRUE.equals(deleted))
|
||||
{
|
||||
return fail(result, AptErrorCode.DATA_ANOMALY, "REDIS_READ_WRITE_MISMATCH",
|
||||
"SET/GET/DEL 结果不一致");
|
||||
}
|
||||
result.detail("operation", "SET/GET/DEL");
|
||||
result.detail("key", key);
|
||||
result.detail("readBack", read);
|
||||
result.detail("deleted", deleted);
|
||||
result.detail("database", redisDatabase());
|
||||
result.detail("ttlSeconds", properties.getProbe().getRedisTtlSeconds());
|
||||
}
|
||||
catch (RedisConnectionFailureException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_CONNECT_FAILED", e);
|
||||
}
|
||||
catch (QueryTimeoutException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_TIMEOUT", e);
|
||||
}
|
||||
catch (RedisSystemException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_ERROR", e);
|
||||
}
|
||||
catch (DataAccessException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_ERROR", e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
classifyRedis(result, "REDIS_ERROR", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
cleanupRedisQuietly(key);
|
||||
finish(result, start);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void classifyDb(ProbeResult result, String reason, AptErrorCode code, Exception e)
|
||||
{
|
||||
log.warn("db probe failed, reason={}, traceId={}, cause={}", reason,
|
||||
AptContext.getTraceId(), e.toString());
|
||||
result.fail(code, reason, e.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
private void classifyRedis(ProbeResult result, String reason, Exception e)
|
||||
{
|
||||
log.warn("redis probe failed, reason={}, traceId={}, cause={}", reason,
|
||||
AptContext.getTraceId(), e.toString());
|
||||
result.fail(AptErrorCode.DEPENDENCY_UNAVAILABLE, reason, e.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
private ProbeResult fail(ProbeResult result, AptErrorCode code, String reason, String message)
|
||||
{
|
||||
result.fail(code, reason, message);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 超过配置阈值但底层未报超时时,仍按超时归类 */
|
||||
private void finish(ProbeResult result, long startNanos)
|
||||
{
|
||||
result.elapsed(startNanos);
|
||||
if (result.isOk() && result.getElapsedMs() > properties.getProbe().getTimeoutMs())
|
||||
{
|
||||
result.fail(AptErrorCode.DEPENDENCY_UNAVAILABLE,
|
||||
result.getTarget() + "_TIMEOUT", "探针耗时超过阈值");
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupDbQuietly(String table, String key)
|
||||
{
|
||||
try
|
||||
{
|
||||
jdbc.update("DELETE FROM " + table + " WHERE gyid = ? AND probe_key = ?", gyId(), key);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("db probe cleanup skipped, traceId={}, cause={}", AptContext.getTraceId(), e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupRedisQuietly(String key)
|
||||
{
|
||||
try
|
||||
{
|
||||
redis.delete(key);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("redis probe cleanup skipped, traceId={}, cause={}", AptContext.getTraceId(), e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private int redisDatabase()
|
||||
{
|
||||
RedisConnectionFactory factory = redis.getConnectionFactory();
|
||||
if (factory instanceof LettuceConnectionFactory)
|
||||
{
|
||||
return ((LettuceConnectionFactory) factory).getDatabase();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String gyId()
|
||||
{
|
||||
String value = AptContext.getGyIdOrNull();
|
||||
return (value == null || value.isEmpty()) ? properties.getGyId() : value;
|
||||
}
|
||||
|
||||
/** 表名来自配置,做白名单校验,避免配置注入 */
|
||||
private String probeTable()
|
||||
{
|
||||
String table = properties.getProbe().getTable();
|
||||
if (table == null || !table.matches("[A-Za-z0-9_]+"))
|
||||
{
|
||||
throw new AptException(AptErrorCode.BAD_REQUEST, "INVALID_PROBE_TABLE", "探针表名非法");
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
private String shortId()
|
||||
{
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package com.ruoyi.apt.infra.connectivity;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.ruoyi.apt.common.AptErrorCode;
|
||||
|
||||
/**
|
||||
* 单个依赖(DB / REDIS)的探针结果。
|
||||
* 失败时不抛异常,先落成结构化结果;控制器按 strict 决定是否交给统一异常处理器。
|
||||
*
|
||||
* @author apt-rev01
|
||||
*/
|
||||
public class ProbeResult
|
||||
{
|
||||
private final String target;
|
||||
private final Map<String, Object> details = new LinkedHashMap<>();
|
||||
private boolean ok = true;
|
||||
private int code = AptErrorCode.SUCCESS.getCode();
|
||||
private String reason = "OK";
|
||||
private String message = "ok";
|
||||
private long elapsedMs;
|
||||
|
||||
public ProbeResult(String target)
|
||||
{
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public String getTarget()
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
public boolean isOk()
|
||||
{
|
||||
return ok;
|
||||
}
|
||||
|
||||
public int getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getReason()
|
||||
{
|
||||
return reason;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
public long getElapsedMs()
|
||||
{
|
||||
return elapsedMs;
|
||||
}
|
||||
|
||||
public Map<String, Object> getDetails()
|
||||
{
|
||||
return details;
|
||||
}
|
||||
|
||||
public ProbeResult detail(String key, Object value)
|
||||
{
|
||||
this.details.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProbeResult fail(AptErrorCode errorCode, String reason, String message)
|
||||
{
|
||||
this.ok = false;
|
||||
this.code = errorCode.getCode();
|
||||
this.reason = reason;
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProbeResult elapsed(long startNanos)
|
||||
{
|
||||
this.elapsedMs = (System.nanoTime() - startNanos) / 1_000_000L;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> toMap()
|
||||
{
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("target", target);
|
||||
map.put("ok", ok);
|
||||
map.put("code", code);
|
||||
map.put("reason", reason);
|
||||
map.put("message", message);
|
||||
map.put("elapsedMs", elapsedMs);
|
||||
map.put("details", details);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# 本地启动脚本:从被 .gitignore 排除的 .env 加载凭据,仓库内不含任何真实口令。
|
||||
# 用法:复制 dev/.env.example 为 dev/.env 并填写,然后执行本脚本。
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$envFile = Join-Path $PSScriptRoot '.env'
|
||||
if (-not (Test-Path $envFile)) {
|
||||
throw "缺少 $envFile,请先从 .env.example 复制并填写本机凭据"
|
||||
}
|
||||
|
||||
Get-Content -LiteralPath $envFile | ForEach-Object {
|
||||
$line = $_.Trim()
|
||||
if ($line -eq '' -or $line.StartsWith('#')) { return }
|
||||
if ($line -match '^([A-Za-z0-9_]+)\s*=\s*(.*)$') {
|
||||
[Environment]::SetEnvironmentVariable($Matches[1], $Matches[2], 'Process')
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($required in @('APT_DB_PASSWORD', 'APT_TOKEN_SECRET')) {
|
||||
if (-not [Environment]::GetEnvironmentVariable($required, 'Process')) {
|
||||
throw "缺少必需环境变量 $required(请在 .env 中填写)"
|
||||
}
|
||||
}
|
||||
|
||||
$backend = Join-Path (Split-Path $PSScriptRoot -Parent) 'RuoYi-Vue-master'
|
||||
Set-Location $backend
|
||||
& mvn -pl ruoyi-admin -am spring-boot:run
|
||||
@@ -0,0 +1,50 @@
|
||||
-- =============================================================================
|
||||
-- IT-0 / DV-0 分域增量 DDL —— 公共部分(幂等辅助 + 连通性探针表)
|
||||
-- 目标库:apt_rev01 @ 127.0.0.1:3306 / MySQL 9.5
|
||||
-- 设计口径:CR-02 表 3-25(59 张)、表 3-33(PK/UK/IDX/迁移顺序)、
|
||||
-- 公寓端/03 表 3-8~3-15、公寓端/13 表 13-10 验收③(二次执行幂等)
|
||||
-- 通用规则:
|
||||
-- 1) InnoDB / utf8mb4 / utf8mb4_0900_ai_ci
|
||||
-- 2) 代理主键 id bigint AUTO_INCREMENT;业务幂等键一律 UK 且以 gyid 打头
|
||||
-- 3) 不建外键(docs/10 表 5-6:跨端/分库场景用应用层约束 + 对账兜底)
|
||||
-- 4) 状态用 varchar 大写下划线枚举 + CHECK,不用 MySQL ENUM、不用数字码
|
||||
-- 5) 金额 decimal(12,2)、比率 decimal(6,4)、时间 datetime(3)、禁止浮点
|
||||
-- 6) deleted tinyint NOT NULL DEFAULT 0;create_time/update_time 审计列
|
||||
-- 7) 所有 CREATE TABLE 带 IF NOT EXISTS,可重复执行
|
||||
-- =============================================================================
|
||||
SET NAMES utf8mb4;
|
||||
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';
|
||||
|
||||
-- 幂等补列存储过程:供 07_patch_columns.sql 与后续迭代增量迁移使用
|
||||
DROP PROCEDURE IF EXISTS apt_add_column_if_missing;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE apt_add_column_if_missing(
|
||||
IN p_table VARCHAR(64),
|
||||
IN p_column VARCHAR(64),
|
||||
IN p_ddl TEXT)
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = p_table
|
||||
AND COLUMN_NAME = p_column
|
||||
) THEN
|
||||
SET @apt_ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN ', p_ddl);
|
||||
PREPARE apt_stmt FROM @apt_ddl;
|
||||
EXECUTE apt_stmt;
|
||||
DEALLOCATE PREPARE apt_stmt;
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
-- 连通性探针专用表(不计入 59 张业务表):只做 INSERT/SELECT/DELETE 闭环验证
|
||||
CREATE TABLE IF NOT EXISTS apt_ops_probe (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '代理主键',
|
||||
gyid VARCHAR(32) NOT NULL COMMENT '公寓隔离标识',
|
||||
probe_key VARCHAR(64) NOT NULL COMMENT '探针幂等键',
|
||||
payload VARCHAR(256) NULL COMMENT '探针载荷',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '写入时刻',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_probe_gyid_key (gyid, probe_key),
|
||||
KEY idx_probe_gyid_time (gyid, created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='IT-0 连通性探针表(非业务表)';
|
||||
@@ -0,0 +1,260 @@
|
||||
-- =============================================================================
|
||||
-- 域 1:主数据快照 + 计划任务 + 本地建单(11 张)
|
||||
-- 来源:公寓端/03 表 3-1(7)、表 3-2(3)、CR-01 表 1-17(apt_local_task)
|
||||
-- 列级定义:apt_local_task 见 CR-01 表 1-17 + 公寓端/11 表 11-7;
|
||||
-- 其余 10 张文档只给「表名/owner/写入口」,字段按接口契约与
|
||||
-- 公寓端/03 表 3-9/3-15 通用规则推断(标注 [推断]),待阶段二冻结评审。
|
||||
-- =============================================================================
|
||||
|
||||
-- 1 主数据版本(platform-down,只读快照;Mock 时 source='LOCAL')
|
||||
CREATE TABLE IF NOT EXISTS apt_master_version (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '代理主键',
|
||||
gyid VARCHAR(32) NOT NULL COMMENT '公寓隔离标识',
|
||||
domain VARCHAR(32) NOT NULL COMMENT '快照域 APARTMENT/ROOM/PERSON/ROUTE/RULE/HOLIDAY',
|
||||
source_version BIGINT NOT NULL DEFAULT 0 COMMENT '平台版本号,单调递增',
|
||||
source VARCHAR(20) NOT NULL DEFAULT 'PLATFORM' COMMENT 'PLATFORM / LOCAL(Mock 数据标 LOCAL)',
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'APPLIED' COMMENT 'PENDING/APPLIED/FAILED',
|
||||
applied_at DATETIME(3) NULL COMMENT '生效时刻',
|
||||
remark VARCHAR(256) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_mv_gyid_domain (gyid, domain),
|
||||
KEY idx_mv_gyid_state (gyid, state, applied_at),
|
||||
CONSTRAINT ck_mv_source CHECK (source IN ('PLATFORM','LOCAL')),
|
||||
CONSTRAINT ck_mv_state CHECK (state IN ('PENDING','APPLIED','FAILED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='主数据版本快照 [推断]';
|
||||
|
||||
-- 2 公寓档案
|
||||
CREATE TABLE IF NOT EXISTS apt_master_apartment (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
apartment_code VARCHAR(64) NOT NULL COMMENT '公寓编码',
|
||||
apartment_name VARCHAR(128) NULL,
|
||||
city_code VARCHAR(32) NULL,
|
||||
address VARCHAR(256) NULL,
|
||||
contact_ref VARCHAR(128) NULL COMMENT '联系方式只存引用',
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
source_version BIGINT NOT NULL DEFAULT 0,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_ma_gyid_code (gyid, apartment_code),
|
||||
CONSTRAINT ck_ma_state CHECK (state IN ('ACTIVE','DISABLED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='公寓档案 [推断]';
|
||||
|
||||
-- 3 房间主数据(含 CR-02 表 3-32 补列 building;Q-20 未决前列存在但不启用)
|
||||
CREATE TABLE IF NOT EXISTS apt_master_room (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
room_id VARCHAR(64) NOT NULL COMMENT '房间主键(平台下发)',
|
||||
room_no VARCHAR(32) NOT NULL COMMENT '房号',
|
||||
room_floor VARCHAR(32) NULL COMMENT '楼层(Q-20 未决前的筛选落点)',
|
||||
building VARCHAR(64) NULL COMMENT '楼栋;Q-20 未决,保留列不启用(表 3-32)',
|
||||
bed_count INT NOT NULL DEFAULT 0,
|
||||
orientation VARCHAR(32) NULL,
|
||||
room_type VARCHAR(32) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
state_version BIGINT NOT NULL DEFAULT 1 COMMENT '房态乐观锁,与 source_version 语义不同',
|
||||
source_version BIGINT NOT NULL DEFAULT 0 COMMENT '平台快照版本;改快照字段返回 40904',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_mr_gyid_room (gyid, room_id),
|
||||
KEY idx_mr_gyid_floor_no (gyid, room_floor, room_no),
|
||||
CONSTRAINT ck_mr_state CHECK (state IN ('ACTIVE','DISABLED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='房间主数据(platform-down 只读)[推断+补列]';
|
||||
|
||||
-- 4 人员主数据(含 公寓端/10 表 10-6 人脸补列:媒体只存引用与校验和)
|
||||
CREATE TABLE IF NOT EXISTS apt_master_person (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
ryid VARCHAR(64) NOT NULL COMMENT '人员 ID',
|
||||
xm VARCHAR(64) NOT NULL COMMENT '姓名(快照字段,改则返回 40904)',
|
||||
work_no VARCHAR(64) NULL COMMENT '工号,平台侧校验唯一性',
|
||||
dept_code VARCHAR(64) NULL,
|
||||
person_category VARCHAR(20) NOT NULL DEFAULT 'TRANSIENT' COMMENT 'TRANSIENT / LONG_TERM',
|
||||
photo_ref VARCHAR(512) NULL COMMENT '照片引用,不存原图二进制',
|
||||
photo_checksum VARCHAR(128) NULL,
|
||||
feature_ref VARCHAR(512) NULL COMMENT '特征值引用,不存特征大对象',
|
||||
feature_checksum VARCHAR(128) NULL,
|
||||
feature_algo VARCHAR(32) NULL,
|
||||
feature_version BIGINT NOT NULL DEFAULT 1,
|
||||
face_sync_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/SYNCED/FAILED/STALE',
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
source_version BIGINT NOT NULL DEFAULT 0,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_mp_gyid_ryid (gyid, ryid),
|
||||
KEY idx_mp_gyid_workno (gyid, work_no),
|
||||
KEY idx_mp_gyid_name (gyid, xm),
|
||||
CONSTRAINT ck_mp_category CHECK (person_category IN ('TRANSIENT','LONG_TERM')),
|
||||
CONSTRAINT ck_mp_face CHECK (face_sync_state IN ('PENDING','SYNCED','FAILED','STALE'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='人员主数据(含 §12 补列)';
|
||||
|
||||
-- 5 交路主数据
|
||||
CREATE TABLE IF NOT EXISTS apt_master_route (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
jlid VARCHAR(64) NOT NULL COMMENT '交路 ID;重复新增返回 40901',
|
||||
cc VARCHAR(64) NOT NULL COMMENT '车次(必填,缺则 40001)',
|
||||
kcsj DATETIME(3) NOT NULL COMMENT '开车时刻(必填)',
|
||||
route_name VARCHAR(128) NULL,
|
||||
start_station VARCHAR(64) NULL,
|
||||
end_station VARCHAR(64) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
stop_reason VARCHAR(256) NULL COMMENT '停用原因(必填,审计留痕)',
|
||||
source_version BIGINT NOT NULL DEFAULT 0,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_mroute_gyid_jlid (gyid, jlid),
|
||||
KEY idx_mroute_gyid_cc_time (gyid, cc, kcsj),
|
||||
CONSTRAINT ck_mroute_state CHECK (state IN ('ACTIVE','STOPPED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='交路主数据 [推断]';
|
||||
|
||||
-- 6 叫乘规则(规则体用 JSON,避免频繁改表)
|
||||
CREATE TABLE IF NOT EXISTS apt_master_rule (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
rule_id VARCHAR(64) NOT NULL,
|
||||
rule_type VARCHAR(32) NOT NULL COMMENT 'CALL_TIME / HOLIDAY / TIMEOUT',
|
||||
rule_version BIGINT NOT NULL DEFAULT 1,
|
||||
rule_body JSON NULL COMMENT '规则体 JSON',
|
||||
effective_from DATETIME(3) NULL,
|
||||
effective_to DATETIME(3) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
source_version BIGINT NOT NULL DEFAULT 0,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_mrule_gyid_id_ver (gyid, rule_id, rule_version),
|
||||
KEY idx_mrule_gyid_type (gyid, rule_type, state)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='叫乘规则快照 [推断]';
|
||||
|
||||
-- 7 节假日
|
||||
CREATE TABLE IF NOT EXISTS apt_holiday (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
holiday_date DATE NOT NULL,
|
||||
holiday_name VARCHAR(64) NULL,
|
||||
holiday_type VARCHAR(20) NOT NULL DEFAULT 'HOLIDAY',
|
||||
source_version BIGINT NOT NULL DEFAULT 0,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_holiday_gyid_date (gyid, holiday_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='节假日表 [推断]';
|
||||
|
||||
-- 8 平台计划下行落库(jcsj 仅基线,权威值恒由公寓复算 CR-C06)
|
||||
CREATE TABLE IF NOT EXISTS gy_syn_jcxx (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
jcid VARCHAR(64) NOT NULL COMMENT '叫乘计划 ID',
|
||||
jlid VARCHAR(64) NULL,
|
||||
cc VARCHAR(64) NULL,
|
||||
ryid VARCHAR(64) NULL,
|
||||
xm VARCHAR(64) NULL,
|
||||
fjh VARCHAR(32) NULL COMMENT '房间号',
|
||||
kcsj DATETIME(3) NULL,
|
||||
jcsj DATETIME(3) NULL COMMENT '平台计划基线(非权威)',
|
||||
computed_jcsj DATETIME(3) NULL COMMENT '公寓复算值(权威)',
|
||||
plan_version BIGINT NOT NULL DEFAULT 1,
|
||||
sync_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/SYNCED/FAILED/DRIFT',
|
||||
source_version BIGINT NOT NULL DEFAULT 0,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_syn_gyid_jcid (gyid, jcid),
|
||||
KEY idx_syn_gyid_jlid_time (gyid, jlid, jcsj),
|
||||
CONSTRAINT ck_syn_state CHECK (sync_state IN ('PENDING','SYNCED','FAILED','DRIFT'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台计划下行 [推断]';
|
||||
|
||||
-- 9 计划同步状态
|
||||
CREATE TABLE IF NOT EXISTS gy_syn_jcxx_status (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
jcid VARCHAR(64) NOT NULL,
|
||||
sync_state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
last_sync_at DATETIME(3) NULL,
|
||||
error_code VARCHAR(64) NULL,
|
||||
error_msg VARCHAR(512) NULL,
|
||||
retry_count INT NOT NULL DEFAULT 0,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_synst_gyid_jcid (gyid, jcid),
|
||||
KEY idx_synst_gyid_state (gyid, sync_state, last_sync_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='计划同步状态 [推断]';
|
||||
|
||||
-- 10 叫乘任务租约锁(防止重复生成任务)
|
||||
CREATE TABLE IF NOT EXISTS gy_call_duty_match_lock (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
lock_key VARCHAR(128) NOT NULL COMMENT '租约键',
|
||||
jcid VARCHAR(64) NULL,
|
||||
owner_ref VARCHAR(64) NULL COMMENT '持锁实例',
|
||||
lease_until DATETIME(3) NOT NULL COMMENT '租约到期',
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'HELD',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_lock_gyid_key (gyid, lock_key),
|
||||
KEY idx_lock_gyid_lease (gyid, lease_until),
|
||||
CONSTRAINT ck_lock_state CHECK (state IN ('HELD','RELEASED','EXPIRED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='叫乘任务租约锁 [推断]';
|
||||
|
||||
-- 11 本地建单 / 入住申请(CR-01 表 1-17 + 公寓端/11 表 11-7 补列)
|
||||
-- Q-40 未冻结前:扩展要素落 request_payload JSON 兜底,不新增列(表 13-10)
|
||||
CREATE TABLE IF NOT EXISTS apt_local_task (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
task_id VARCHAR(64) NOT NULL COMMENT '本地任务 ID(幂等键)',
|
||||
jcid VARCHAR(64) NULL COMMENT '预分配叫乘 ID,与 apply_id 一对一',
|
||||
apply_id VARCHAR(64) NULL COMMENT '入住申请 ID',
|
||||
origin VARCHAR(20) NOT NULL DEFAULT 'LOCAL' COMMENT 'LOCAL/PLATFORM/DEVICE',
|
||||
local_version BIGINT NOT NULL DEFAULT 1,
|
||||
biz_version BIGINT NOT NULL DEFAULT 1,
|
||||
match_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/MATCHED/UNMATCHED/LOCKED/REJECTED',
|
||||
checkin_state VARCHAR(24) NOT NULL DEFAULT 'SUBMITTED' COMMENT 'SUBMITTED/AUDITED/CHECKED_IN/REJECTED',
|
||||
sync_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/SENT/FAILED/DEAD/RETRIED',
|
||||
fjh VARCHAR(32) NULL,
|
||||
fjh_final VARCHAR(32) NULL COMMENT '审核后房间',
|
||||
room_adjust_seq INT NOT NULL DEFAULT 0,
|
||||
face_verify_state VARCHAR(20) NULL COMMENT 'VERIFIED/UNVERIFIED/SKIPPED',
|
||||
face_device_code VARCHAR(64) NULL,
|
||||
face_verified_at DATETIME(3) NULL,
|
||||
ryid VARCHAR(64) NULL,
|
||||
xm VARCHAR(64) NULL,
|
||||
jlid VARCHAR(64) NULL,
|
||||
cc VARCHAR(64) NULL,
|
||||
jcsj DATETIME(3) NULL,
|
||||
request_payload JSON NULL COMMENT 'Q-40 冻结前的 JSON 兜底留痕',
|
||||
audited_by VARCHAR(64) NULL,
|
||||
audited_at DATETIME(3) NULL,
|
||||
checked_in_at DATETIME(3) NULL,
|
||||
reject_reason VARCHAR(256) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_lt_gyid_task (gyid, task_id),
|
||||
UNIQUE KEY uk_lt_gyid_jcid (gyid, jcid),
|
||||
KEY idx_lt_gyid_apply (gyid, apply_id),
|
||||
KEY idx_lt_gyid_state (gyid, match_state, checkin_state),
|
||||
CONSTRAINT ck_lt_match CHECK (match_state IN ('PENDING','MATCHED','UNMATCHED','LOCKED','REJECTED')),
|
||||
CONSTRAINT ck_lt_checkin CHECK (checkin_state IN ('SUBMITTED','AUDITED','CHECKED_IN','REJECTED')),
|
||||
CONSTRAINT ck_lt_sync CHECK (sync_state IN ('PENDING','SENT','FAILED','DEAD','RETRIED')),
|
||||
CONSTRAINT ck_lt_face CHECK (face_verify_state IS NULL OR face_verify_state IN ('VERIFIED','UNVERIFIED','SKIPPED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='本地建单/入住申请(申请阶段不建 gy_jcxx 行)';
|
||||
@@ -0,0 +1,254 @@
|
||||
-- =============================================================================
|
||||
-- 域 2:执行域(7 张)+ 设备域(4 张)= 11 张
|
||||
-- 来源:公寓端/03 表 3-3 / 3-4;字段口径见 公寓端/03 表 3-9、表 3-11、表 3-15
|
||||
-- 说明:gy_jcxx 存量 70 列(docs/11 表 2-1),此处按接口契约给出核心列 + CR-02 表 3-32
|
||||
-- 两处补列;其余存量列在阶段二冻结后由 07_patch_columns.sql 幂等补齐。
|
||||
-- 硬约束:ryzt / finish_jc / finish_ly / stoped 只有 apt.duty.state 一个写入口,
|
||||
-- SQL 必须带 gyid 与条件更新(where id=? and ryzt=?),禁止直接 UPDATE。
|
||||
-- =============================================================================
|
||||
|
||||
-- 12 叫乘主表(执行事实 + 业务终态)
|
||||
CREATE TABLE IF NOT EXISTS gy_jcxx (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
jcid VARCHAR(64) NOT NULL COMMENT '叫乘 ID',
|
||||
jlid VARCHAR(64) NULL,
|
||||
cc VARCHAR(64) NULL,
|
||||
ryid VARCHAR(64) NULL,
|
||||
xm VARCHAR(64) NULL,
|
||||
fjh VARCHAR(32) NULL,
|
||||
kcsj DATETIME(3) NULL COMMENT '开车时刻',
|
||||
jcsj DATETIME(3) NULL COMMENT '叫乘时刻(公寓复算,权威)',
|
||||
jcsj_platform DATETIME(3) NULL COMMENT '平台计划基线(仅对照)',
|
||||
ryzt VARCHAR(8) NOT NULL DEFAULT '0' COMMENT '叫乘状态 0~5(存量数字码,不跨端)',
|
||||
finish_jc VARCHAR(2) NOT NULL DEFAULT '0' COMMENT '0 未完成 / 1 已完成',
|
||||
finish_ly VARCHAR(2) NOT NULL DEFAULT '0' COMMENT '0 在寓 / 1 已离寓',
|
||||
stoped VARCHAR(2) NOT NULL DEFAULT '0' COMMENT '人工停止',
|
||||
sjlysj DATETIME(3) NULL COMMENT '实际离寓时间;与 finish_ly 共同判「已入住」',
|
||||
call_mode VARCHAR(20) NOT NULL DEFAULT 'SIP' COMMENT 'CR-02 表 3-32 补列:SIP / TABLET',
|
||||
dispatch_seq BIGINT NULL COMMENT 'CR-02 表 3-32 补列:平板派发序号',
|
||||
biz_version BIGINT NOT NULL DEFAULT 1 COMMENT '条件更新乐观锁',
|
||||
occurred_at DATETIME(3) NULL COMMENT '业务发生时刻(不判日切)',
|
||||
received_at DATETIME(3) NULL COMMENT '本地入库时刻',
|
||||
platform_recv_at DATETIME(3) NULL COMMENT '平台接收时刻(归日以此为准)',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_jcxx_gyid_jcid (gyid, jcid),
|
||||
KEY idx_jcxx_gyid_time (gyid, jcsj),
|
||||
KEY idx_jcxx_gyid_state (gyid, ryzt, finish_ly),
|
||||
KEY idx_jcxx_gyid_seq (gyid, dispatch_seq),
|
||||
CONSTRAINT ck_jcxx_ryzt CHECK (ryzt IN ('0','1','2','3','4','5')),
|
||||
CONSTRAINT ck_jcxx_finish_jc CHECK (finish_jc IN ('0','1')),
|
||||
CONSTRAINT ck_jcxx_finish_ly CHECK (finish_ly IN ('0','1')),
|
||||
CONSTRAINT ck_jcxx_stoped CHECK (stoped IN ('0','1')),
|
||||
CONSTRAINT ck_jcxx_mode CHECK (call_mode IN ('SIP','TABLET'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='叫乘主表;状态字段单写入口 apt.duty.state';
|
||||
|
||||
-- 13 叫乘归档(只读,#13 ARCHIVE 摘要来源)
|
||||
CREATE TABLE IF NOT EXISTS gy_jcxx_his (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
jcid VARCHAR(64) NOT NULL,
|
||||
jlid VARCHAR(64) NULL,
|
||||
cc VARCHAR(64) NULL,
|
||||
ryid VARCHAR(64) NULL,
|
||||
xm VARCHAR(64) NULL,
|
||||
fjh VARCHAR(32) NULL,
|
||||
jcsj DATETIME(3) NULL,
|
||||
ryzt VARCHAR(8) NULL,
|
||||
finish_jc VARCHAR(2) NULL,
|
||||
finish_ly VARCHAR(2) NULL,
|
||||
sjlysj DATETIME(3) NULL,
|
||||
archive_batch VARCHAR(64) NULL,
|
||||
archived_at DATETIME(3) NULL,
|
||||
summary_payload JSON NULL COMMENT '§15 分组计数 satisfaction{} / roomOccupancy{}',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_jcxxhis_gyid_jcid (gyid, jcid),
|
||||
KEY idx_jcxxhis_gyid_date (gyid, archived_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='叫乘归档历史 [推断]';
|
||||
|
||||
-- 14 离寓/入住事件(设备与人工登记事实)
|
||||
CREATE TABLE IF NOT EXISTS gygl_ly_event (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
event_id VARCHAR(64) NOT NULL COMMENT '事件幂等键',
|
||||
event_type VARCHAR(20) NOT NULL COMMENT 'CHECKIN / CHECKOUT',
|
||||
event_source VARCHAR(20) NOT NULL COMMENT 'DEVICE / FACE / MANUAL',
|
||||
jcid VARCHAR(64) NULL,
|
||||
ryid VARCHAR(64) NULL,
|
||||
fjh VARCHAR(32) NULL,
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
received_at DATETIME(3) NULL,
|
||||
platform_recv_at DATETIME(3) NULL,
|
||||
report_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/SENT/FAILED/DEAD/RETRIED',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_lye_gyid_event (gyid, event_id),
|
||||
KEY idx_lye_gyid_type_time (gyid, event_type, occurred_at),
|
||||
CONSTRAINT ck_lye_type CHECK (event_type IN ('CHECKIN','CHECKOUT')),
|
||||
CONSTRAINT ck_lye_source CHECK (event_source IN ('DEVICE','FACE','MANUAL')),
|
||||
CONSTRAINT ck_lye_report CHECK (report_state IN ('PENDING','SENT','FAILED','DEAD','RETRIED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='离寓/入住事件 [推断+§13]';
|
||||
|
||||
-- 15 房态(唯一写入口 apt-stay,条件更新带 state_version)
|
||||
CREATE TABLE IF NOT EXISTS gy_rooms (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
room_id VARCHAR(64) NOT NULL,
|
||||
room_no VARCHAR(32) NOT NULL,
|
||||
room_floor VARCHAR(32) NULL,
|
||||
room_status VARCHAR(8) NOT NULL DEFAULT '1' COMMENT '1 空闲/2 住满/3 待清扫/4 不能入住/5 未满/6 保洁中',
|
||||
in_person_count INT NOT NULL DEFAULT 0,
|
||||
state_version BIGINT NOT NULL DEFAULT 1 COMMENT '乐观锁:where id=? and state_version=?',
|
||||
disabled_reason VARCHAR(256) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_rooms_gyid_room (gyid, room_id),
|
||||
KEY idx_rooms_gyid_status (gyid, room_status, room_floor),
|
||||
CONSTRAINT ck_rooms_status CHECK (room_status IN ('1','2','3','4','5','6'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='房间与房态(apt-stay 写入)';
|
||||
|
||||
-- 16 保洁任务
|
||||
CREATE TABLE IF NOT EXISTS gy_clean_task (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
room_id VARCHAR(64) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/PROCESSING/DONE/CANCELLED',
|
||||
assignee_ref VARCHAR(64) NULL,
|
||||
scheduled_at DATETIME(3) NULL,
|
||||
finished_at DATETIME(3) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_cleantask_gyid_task (gyid, task_id),
|
||||
KEY idx_cleantask_gyid_state (gyid, state, scheduled_at),
|
||||
CONSTRAINT ck_cleantask_state CHECK (state IN ('PENDING','PROCESSING','DONE','CANCELLED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='保洁任务 [推断]';
|
||||
|
||||
-- 17 报警记录
|
||||
CREATE TABLE IF NOT EXISTS gy_bjxx (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
alarm_id VARCHAR(64) NOT NULL,
|
||||
jcid VARCHAR(64) NULL,
|
||||
room_id VARCHAR(64) NULL,
|
||||
alarm_type VARCHAR(32) NOT NULL,
|
||||
severity VARCHAR(20) NOT NULL DEFAULT 'WARN',
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
handled_at DATETIME(3) NULL,
|
||||
handled_by VARCHAR(64) NULL,
|
||||
handled_result VARCHAR(256) NULL,
|
||||
biz_version BIGINT NOT NULL DEFAULT 1,
|
||||
report_state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_bjxx_gyid_alarm (gyid, alarm_id),
|
||||
KEY idx_bjxx_gyid_type_time (gyid, alarm_type, occurred_at),
|
||||
KEY idx_bjxx_gyid_handled (gyid, handled_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='报警记录 [推断]';
|
||||
|
||||
-- 18 报警日汇总(#13 摘要来源)
|
||||
CREATE TABLE IF NOT EXISTS gy_bjcstj (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
stat_date DATE NOT NULL,
|
||||
alarm_count INT NOT NULL DEFAULT 0,
|
||||
handled_count INT NOT NULL DEFAULT 0,
|
||||
report_state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_bjcstj_gyid_date (gyid, stat_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='报警日汇总 [推断]';
|
||||
|
||||
-- 19 设备档案(含 §12 人脸设备范围补列)
|
||||
CREATE TABLE IF NOT EXISTS gy_sb (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
device_code VARCHAR(64) NOT NULL COMMENT '设备编码,入库大写',
|
||||
device_type VARCHAR(32) NOT NULL COMMENT 'TABLET / FACE / SIP / BROADCAST',
|
||||
room_id VARCHAR(64) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
face_scope_type VARCHAR(20) NULL COMMENT '§12:设备人脸范围类型',
|
||||
face_scope_ref VARCHAR(128) NULL COMMENT '§12:设备人脸范围引用',
|
||||
last_seen_at DATETIME(3) NULL,
|
||||
biz_version BIGINT NOT NULL DEFAULT 1,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_sb_gyid_code (gyid, device_code),
|
||||
KEY idx_sb_gyid_type_state (gyid, device_type, state),
|
||||
CONSTRAINT ck_sb_state CHECK (state IN ('ACTIVE','DISABLED','OFFLINE'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='设备档案(含 §12 补列)';
|
||||
|
||||
-- 20 设备心跳与在线状态
|
||||
CREATE TABLE IF NOT EXISTS gy_sb_xt (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
device_code VARCHAR(64) NOT NULL,
|
||||
online_state VARCHAR(20) NOT NULL DEFAULT 'OFFLINE',
|
||||
app_version VARCHAR(32) NULL,
|
||||
ip_ref VARCHAR(64) NULL COMMENT '只存引用,不落内网地址',
|
||||
heartbeat_at DATETIME(3) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_sbxt_gyid_code (gyid, device_code),
|
||||
KEY idx_sbxt_gyid_state (gyid, online_state, heartbeat_at),
|
||||
CONSTRAINT ck_sbxt_state CHECK (online_state IN ('ONLINE','OFFLINE','UNKNOWN'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='设备心跳 [推断]';
|
||||
|
||||
-- 21 设备历史指令(存量兼容,只读,不新增写入)
|
||||
CREATE TABLE IF NOT EXISTS gy_sb_zl (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
command_id VARCHAR(64) NOT NULL,
|
||||
device_code VARCHAR(64) NULL,
|
||||
command_type VARCHAR(32) NULL,
|
||||
state VARCHAR(20) NULL,
|
||||
occurred_at DATETIME(3) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_sbzl_gyid_cmd (gyid, command_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='设备历史指令(只读存量兼容)[推断]';
|
||||
|
||||
-- 22 设备指令队列(只入队 PENDING,无心跳不伪造 ACKED)
|
||||
CREATE TABLE IF NOT EXISTS apt_device_command (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
command_id VARCHAR(64) NOT NULL COMMENT '命令幂等键',
|
||||
device_code VARCHAR(64) NOT NULL,
|
||||
command_type VARCHAR(32) NOT NULL COMMENT 'FACE_ISSUE/FACE_ERASE/FACE_FULL_SYNC/PLAY_TASK',
|
||||
payload_json JSON NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/SENT/ACKED/EXPIRED/FAILED',
|
||||
protocol_confirmed TINYINT NOT NULL DEFAULT 0 COMMENT '默认 0,设备 ACK 前不置 1',
|
||||
face_scope_ref VARCHAR(128) NULL COMMENT '§12 补列',
|
||||
sent_at DATETIME(3) NULL,
|
||||
acked_at DATETIME(3) NULL,
|
||||
expire_at DATETIME(3) NULL,
|
||||
last_error VARCHAR(512) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_devcmd_gyid_cmd (gyid, command_id),
|
||||
KEY idx_devcmd_gyid_state (gyid, state, create_time),
|
||||
CONSTRAINT ck_devcmd_state CHECK (state IN ('PENDING','SENT','ACKED','EXPIRED','FAILED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='设备指令队列(§12 补列)';
|
||||
@@ -0,0 +1,227 @@
|
||||
-- =============================================================================
|
||||
-- 域 3:语音域(11 张)+ §10 补遗 T-17 apt_sip_reg_status(1 张)= 12 张
|
||||
-- 来源:公寓端/03 表 3-5;docs/10 表 5-3(PK/UK/IDX/关键字段)、表 5-4(MySQL 类型适配);
|
||||
-- 公寓端/00 表 0-6~0-8(endpoint / session / recording 逐列)、表 0-9(T-17 逐列);
|
||||
-- CR-02 表 3-40(T-17 关键约束:只追加、无 update_time)
|
||||
-- 约束:分机号不可原地改号(改号返回 40906);sync_state 仅 4 值 + CHECK;
|
||||
-- 录音只存引用与校验和,不落二进制;表上不建外键。
|
||||
-- =============================================================================
|
||||
|
||||
-- 23 分机台账
|
||||
CREATE TABLE IF NOT EXISTS apt_sip_endpoint (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
endpoint_id VARCHAR(64) NOT NULL,
|
||||
extension VARCHAR(8) NOT NULL COMMENT '分机号 char(8),入库统一大写',
|
||||
room_id VARCHAR(64) NULL,
|
||||
display_name VARCHAR(64) NULL,
|
||||
config_version BIGINT NOT NULL DEFAULT 1 COMMENT '分机配置版本(区别于 state_version/biz_version)',
|
||||
sync_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '仅 PENDING/SYNCED/FAILED/DRIFT',
|
||||
enabled TINYINT NOT NULL DEFAULT 1,
|
||||
last_qualify_at DATETIME(3) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_endpoint_gyid_id (gyid, endpoint_id),
|
||||
UNIQUE KEY uk_endpoint_gyid_ext (gyid, extension),
|
||||
KEY idx_endpoint_gyid_sync (gyid, sync_state),
|
||||
CONSTRAINT ck_endpoint_sync CHECK (sync_state IN ('PENDING','SYNCED','FAILED','DRIFT'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='SIP 分机台账(先库后 Asterisk + 异步收敛)';
|
||||
|
||||
-- 24 分机-房间绑定
|
||||
CREATE TABLE IF NOT EXISTS apt_sip_binding (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
binding_id VARCHAR(64) NOT NULL,
|
||||
endpoint_id VARCHAR(64) NOT NULL,
|
||||
room_id VARCHAR(64) NOT NULL,
|
||||
effective_from DATETIME(3) NOT NULL,
|
||||
effective_to DATETIME(3) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_binding_gyid_id (gyid, binding_id),
|
||||
KEY idx_binding_gyid_room (gyid, room_id, state)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='分机绑定 [推断]';
|
||||
|
||||
-- 25 中继
|
||||
CREATE TABLE IF NOT EXISTS apt_trunk (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
trunk_id VARCHAR(64) NOT NULL,
|
||||
trunk_name VARCHAR(64) NULL,
|
||||
host_ref VARCHAR(128) NULL COMMENT '只存引用,不落内网地址',
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
config_version BIGINT NOT NULL DEFAULT 1,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_trunk_gyid_id (gyid, trunk_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='中继 [推断]';
|
||||
|
||||
-- 26 通话会话
|
||||
CREATE TABLE IF NOT EXISTS apt_call_session (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
session_id VARCHAR(64) NOT NULL,
|
||||
jcid VARCHAR(64) NULL,
|
||||
endpoint_id VARCHAR(64) NULL,
|
||||
conf_no VARCHAR(64) NULL COMMENT '单 Conf-01 兼容值,不加唯一约束',
|
||||
call_mode VARCHAR(20) NOT NULL DEFAULT 'SIP',
|
||||
call_state VARCHAR(20) NOT NULL DEFAULT 'INIT',
|
||||
started_at DATETIME(3) NULL,
|
||||
answered_at DATETIME(3) NULL,
|
||||
ended_at DATETIME(3) NULL,
|
||||
hangup_cause VARCHAR(64) NULL,
|
||||
biz_version BIGINT NOT NULL DEFAULT 1,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_callsession_gyid_id (gyid, session_id),
|
||||
KEY idx_callsession_gyid_jcid (gyid, jcid),
|
||||
KEY idx_callsession_gyid_state (gyid, call_state, started_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='通话会话(ConfbridgeJoin 判接通)';
|
||||
|
||||
-- 27 AMI 事件(只追加,明细不上报)
|
||||
CREATE TABLE IF NOT EXISTS apt_call_event (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
event_seq BIGINT NOT NULL COMMENT '同会话单调',
|
||||
event_id VARCHAR(64) NOT NULL,
|
||||
session_id VARCHAR(64) NULL,
|
||||
event_type VARCHAR(32) NOT NULL,
|
||||
event_payload JSON NULL,
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_callevent_gyid_id (gyid, event_id),
|
||||
UNIQUE KEY uk_callevent_gyid_seq (gyid, session_id, event_seq),
|
||||
KEY idx_callevent_gyid_time (gyid, occurred_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AMI 事件(只追加)[推断]';
|
||||
|
||||
-- 28 AMI 事件归档(只读)
|
||||
CREATE TABLE IF NOT EXISTS apt_call_event_his (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
event_id VARCHAR(64) NOT NULL,
|
||||
event_seq BIGINT NOT NULL,
|
||||
session_id VARCHAR(64) NULL,
|
||||
event_type VARCHAR(32) NULL,
|
||||
event_payload JSON NULL,
|
||||
occurred_at DATETIME(3) NULL,
|
||||
archived_at DATETIME(3) NULL,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_calleventhis_gyid_id (gyid, event_id),
|
||||
KEY idx_calleventhis_gyid_date (gyid, archived_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AMI 事件归档 [推断]';
|
||||
|
||||
-- 29 录音索引(只存引用,PURGED 返回 41001)
|
||||
CREATE TABLE IF NOT EXISTS apt_recording (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
recording_id VARCHAR(64) NOT NULL,
|
||||
session_id VARCHAR(64) NULL,
|
||||
file_ref VARCHAR(512) NULL COMMENT '签名地址引用,不落二进制',
|
||||
checksum VARCHAR(128) NULL,
|
||||
duration_ms BIGINT NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'RECORDING' COMMENT 'RECORDING/AVAILABLE/MISSING/PURGED',
|
||||
expire_at DATETIME(3) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_recording_gyid_id (gyid, recording_id),
|
||||
KEY idx_recording_gyid_session (gyid, session_id),
|
||||
CONSTRAINT ck_recording_state CHECK (state IN ('RECORDING','AVAILABLE','MISSING','PURGED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='录音索引(§12:录音不出公寓)';
|
||||
|
||||
-- 30 TTS 播报记录
|
||||
CREATE TABLE IF NOT EXISTS apt_tts_utterance (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
utterance_id VARCHAR(64) NOT NULL,
|
||||
text_ref VARCHAR(256) NULL,
|
||||
audio_ref VARCHAR(512) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_tts_gyid_id (gyid, utterance_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='TTS 播报 [推断]';
|
||||
|
||||
-- 31 Asterisk 配置版本
|
||||
CREATE TABLE IF NOT EXISTS apt_ast_config_version (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
version_id VARCHAR(64) NOT NULL,
|
||||
config_type VARCHAR(32) NOT NULL,
|
||||
config_version BIGINT NOT NULL DEFAULT 1,
|
||||
checksum VARCHAR(128) NULL,
|
||||
applied_at DATETIME(3) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_astcfg_gyid_ver (gyid, version_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Asterisk 配置版本 [推断]';
|
||||
|
||||
-- 32 配置漂移对账
|
||||
CREATE TABLE IF NOT EXISTS apt_ast_drift (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
drift_id VARCHAR(64) NOT NULL,
|
||||
endpoint_id VARCHAR(64) NULL,
|
||||
drift_type VARCHAR(32) NOT NULL,
|
||||
expected_ref VARCHAR(256) NULL,
|
||||
actual_ref VARCHAR(256) NULL,
|
||||
detected_at DATETIME(3) NOT NULL,
|
||||
resolved_at DATETIME(3) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'OPEN',
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_astdrift_gyid_id (gyid, drift_id),
|
||||
KEY idx_astdrift_gyid_state (gyid, state, detected_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='配置漂移 [推断]';
|
||||
|
||||
-- 33 AMI 会话
|
||||
CREATE TABLE IF NOT EXISTS apt_ast_ami_session (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
ami_session_id VARCHAR(64) NOT NULL,
|
||||
connected_at DATETIME(3) NULL,
|
||||
disconnected_at DATETIME(3) NULL,
|
||||
reconnect_count INT NOT NULL DEFAULT 0,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'DOWN',
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_amisession_gyid_id (gyid, ami_session_id),
|
||||
KEY idx_amisession_gyid_state (gyid, state, connected_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AMI 会话 [推断]';
|
||||
|
||||
-- 34 T-17 分机注册状态流水(CR-02 §10 补遗 / 公寓端/00 表 0-9)
|
||||
-- 只追加、无 update_time;event_seq 同分机单调;恢复只追加不清空
|
||||
CREATE TABLE IF NOT EXISTS apt_sip_reg_status (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '代理主键',
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
endpoint_id VARCHAR(64) NOT NULL,
|
||||
event_seq BIGINT NOT NULL COMMENT '同分机单调递增',
|
||||
state VARCHAR(20) NOT NULL COMMENT 'REGISTERED/UNREGISTERED/UNREACHABLE/UNKNOWN',
|
||||
prev_state VARCHAR(20) NULL,
|
||||
source VARCHAR(20) NOT NULL COMMENT 'AMI_EVENT/QUALIFY/PROBE/MANUAL/RECONCILE',
|
||||
alarm_id VARCHAR(64) NULL COMMENT '关联 apt_device_alarm',
|
||||
alarm_event_key VARCHAR(128) NULL COMMENT 'hash(gyid,endpoint_id,SIP_OFFLINE,offline_since)',
|
||||
offline_since DATETIME(3) NULL,
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
biz_version BIGINT NOT NULL DEFAULT 1,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_sipreg_gyid_seq (gyid, endpoint_id, event_seq),
|
||||
KEY idx_sipreg_gyid_state_time (gyid, state, occurred_at),
|
||||
KEY idx_sipreg_gyid_ep_time (gyid, endpoint_id, occurred_at),
|
||||
CONSTRAINT ck_sipreg_state CHECK (state IN ('REGISTERED','UNREGISTERED','UNREACHABLE','UNKNOWN')),
|
||||
CONSTRAINT ck_sipreg_source CHECK (source IN ('AMI_EVENT','QUALIFY','PROBE','MANUAL','RECONCILE'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-17 分机注册状态流水(只追加)';
|
||||
@@ -0,0 +1,145 @@
|
||||
-- =============================================================================
|
||||
-- 域 4:账务本地域(5 张)+ T-11 apt_ledger_offline(1 张)= 6 张
|
||||
-- 来源:公寓端/03 表 3-6;公寓端/12 表 12-7~12-9(双额度 / 状态链 / 补列);
|
||||
-- CR-02 表 3-30(T-11 列级定义);CR-C34 金额口径、CR-C37 现金充值计收入
|
||||
-- 重要:本域属「延后域」(IT-9~IT-11 / P2)。按 公寓端/13 表 13-29,
|
||||
-- 第一轮**只建表结构、预置错误码与权限点,不开放任何读写、不建业务代码**;
|
||||
-- 平台统计账务三类指标按 NO_DATA / BLOCKED 输出,不推断不补零。
|
||||
-- =============================================================================
|
||||
|
||||
-- 35 余额主数据(公寓本地缓存;platform-ledger 为余额主账唯一写者)
|
||||
CREATE TABLE IF NOT EXISTS gy_ykt_remainder (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
ryid VARCHAR(64) NOT NULL,
|
||||
xm VARCHAR(64) NULL,
|
||||
knye DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '卡内/补贴额度',
|
||||
xjye DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '现金额度(充值只增此列)',
|
||||
xfze DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '消费总额',
|
||||
bzze DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '补助总额',
|
||||
xfxjje DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '现金消费额',
|
||||
xfbzje DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '补助消费额',
|
||||
balance_version BIGINT NOT NULL DEFAULT 1 COMMENT '平台主账版本',
|
||||
local_balance_version BIGINT NOT NULL DEFAULT 1 COMMENT '本地 CAS 版本',
|
||||
last_reconciled_on DATE NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_remainder_gyid_ryid (gyid, ryid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='双额度余额缓存(延后域,本期不读写)';
|
||||
|
||||
-- 36 消费流水(一笔消费只一条 flow_id)
|
||||
CREATE TABLE IF NOT EXISTS gy_ykt_consume (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
flow_id VARCHAR(64) NOT NULL COMMENT '消费流水幂等键',
|
||||
ryid VARCHAR(64) NOT NULL,
|
||||
amount DECIMAL(12,2) NOT NULL,
|
||||
allowance_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '补贴部分(CR-C38 统计取此列)',
|
||||
cash_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '现金补足部分',
|
||||
recipe_version VARCHAR(64) NULL,
|
||||
device_code VARCHAR(64) NULL,
|
||||
device_event_id VARCHAR(64) NULL COMMENT '设备事件幂等键',
|
||||
ledger_state VARCHAR(24) NOT NULL DEFAULT 'SUBMITTED' COMMENT 'SUBMITTED/DEDUCTED/REPORTED/RECONCILED/REJECTED/REVERSED',
|
||||
offline_scope VARCHAR(20) NOT NULL DEFAULT 'LOCAL_ONLY' COMMENT 'LOCAL_ONLY/SYNCED/RECONCILED',
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
received_at DATETIME(3) NULL,
|
||||
platform_recv_at DATETIME(3) NULL COMMENT '归日以此为准',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_consume_gyid_flow (gyid, flow_id),
|
||||
UNIQUE KEY uk_consume_gyid_devevt (gyid, device_event_id),
|
||||
KEY idx_consume_gyid_time (gyid, occurred_at),
|
||||
CONSTRAINT ck_consume_ledger CHECK (ledger_state IN ('SUBMITTED','DEDUCTED','REPORTED','RECONCILED','REJECTED','REVERSED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='消费流水(延后域,本期不读写)';
|
||||
|
||||
-- 37 补贴发放
|
||||
CREATE TABLE IF NOT EXISTS gy_ykt_allowance (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
allowance_id VARCHAR(64) NOT NULL,
|
||||
ryid VARCHAR(64) NOT NULL,
|
||||
amount DECIMAL(12,2) NOT NULL,
|
||||
period VARCHAR(20) NULL,
|
||||
issue_at DATETIME(3) NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ISSUED',
|
||||
report_state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_allowance_gyid_id (gyid, allowance_id),
|
||||
KEY idx_allowance_gyid_ryid (gyid, ryid, issue_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='补贴发放(延后域,本期不读写)[推断]';
|
||||
|
||||
-- 38 线下充值申请(申请表不存金额,金额由管理员核对后写入记录)
|
||||
CREATE TABLE IF NOT EXISTS gy_ykt_cash_request (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
request_id VARCHAR(64) NOT NULL,
|
||||
ryid VARCHAR(64) NOT NULL,
|
||||
request_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/COMPLETED/CANCELLED',
|
||||
confirmed_by VARCHAR(64) NULL,
|
||||
confirmed_at DATETIME(3) NULL,
|
||||
cancel_reason VARCHAR(256) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_cashreq_gyid_id (gyid, request_id),
|
||||
KEY idx_cashreq_gyid_state (gyid, request_state, create_time),
|
||||
CONSTRAINT ck_cashreq_state CHECK (request_state IN ('PENDING','COMPLETED','CANCELLED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='线下充值申请(申请表不存金额)';
|
||||
|
||||
-- 39 现金充值记录(计收入,不与消费抵销 CR-C37)
|
||||
CREATE TABLE IF NOT EXISTS gy_ykt_xjczjl (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
flow_id VARCHAR(64) NOT NULL,
|
||||
request_id VARCHAR(64) NULL,
|
||||
ryid VARCHAR(64) NOT NULL,
|
||||
amount DECIMAL(12,2) NOT NULL COMMENT '正数,计期间收入',
|
||||
recharge_channel VARCHAR(20) NOT NULL DEFAULT 'OFFLINE_CASH',
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
platform_recv_at DATETIME(3) NULL,
|
||||
report_state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_xjczjl_gyid_flow (gyid, flow_id),
|
||||
KEY idx_xjczjl_gyid_time (gyid, occurred_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='现金充值记录(延后域,本期不读写)';
|
||||
|
||||
-- 40 T-11 断网本地流水(CR-02 表 3-30 列级定义 + §14 补列)
|
||||
CREATE TABLE IF NOT EXISTS apt_ledger_offline (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
flow_id VARCHAR(64) NOT NULL,
|
||||
type VARCHAR(30) NOT NULL COMMENT 'CONSUME / RECHARGE',
|
||||
ryid VARCHAR(64) NOT NULL,
|
||||
card_no VARCHAR(64) NULL,
|
||||
device_code VARCHAR(64) NULL,
|
||||
amount DECIMAL(12,2) NOT NULL COMMENT '扣减记负数,充值记正数',
|
||||
allowance_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '§14 补列:补贴部分',
|
||||
cash_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '§14 补列:现金部分',
|
||||
balance_before DECIMAL(12,2) NOT NULL COMMENT 'CAS 前值',
|
||||
balance_after DECIMAL(12,2) NOT NULL COMMENT 'CAS 后值',
|
||||
offline_scope VARCHAR(20) NOT NULL DEFAULT 'LOCAL_ONLY' COMMENT 'LOCAL_ONLY/SYNCED/RECONCILED',
|
||||
local_balance_version BIGINT NOT NULL DEFAULT 1,
|
||||
quota_ref VARCHAR(64) NULL COMMENT '#1 离线额度批次',
|
||||
idempotency_key VARCHAR(128) NULL COMMENT 'hash(gyId+bizType+bizId+bizVersion)',
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
synced_at DATETIME(3) NULL,
|
||||
platform_recv_at DATETIME(3) NULL,
|
||||
reconcile_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/RECONCILED/DIFF/REVERSED',
|
||||
last_error VARCHAR(512) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_ledgeroff_gyid_type_flow (gyid, type, flow_id),
|
||||
KEY idx_ledgeroff_gyid_state (gyid, reconcile_state, occurred_at),
|
||||
CONSTRAINT ck_ledgeroff_type CHECK (type IN ('CONSUME','RECHARGE')),
|
||||
CONSTRAINT ck_ledgeroff_scope CHECK (offline_scope IN ('LOCAL_ONLY','SYNCED','RECONCILED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-11 断网本地账务流水(延后域)';
|
||||
@@ -0,0 +1,215 @@
|
||||
-- =============================================================================
|
||||
-- 域 5:上报 / 值班 / 运维(7 张)+ T-09 / T-10 / T-12 / T-13(4 张)= 11 张
|
||||
-- 来源:公寓端/03 表 3-7;CR-02 表 3-29(T-12/T-13)、表 3-30(T-09/T-10)、表 3-32(补列)
|
||||
-- 关键区分:apt_report_outbox 是「队列」(本地状态机 PENDING/SENDING/SENT/FAILED/DEAD,
|
||||
-- 见 公寓端/03 表 3-11),apt_report_history 是「终态流水」,两者不互换(CR-C10)。
|
||||
-- 跨端信封 reportState 正式五值 PENDING/SENT/FAILED/DEAD/RETRIED(CR-02 表 3-21)。
|
||||
-- =============================================================================
|
||||
|
||||
-- 41 上报 outbox 队列(含 CR-02 表 3-32 补列 retry_batch_id)
|
||||
CREATE TABLE IF NOT EXISTS apt_report_outbox (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
idempotency_key VARCHAR(128) NOT NULL COMMENT 'hash(gyId+bizType+bizId+bizVersion)',
|
||||
biz_type VARCHAR(64) NOT NULL COMMENT '既有 bizType,不新增枚举',
|
||||
biz_id VARCHAR(64) NOT NULL,
|
||||
biz_version BIGINT NOT NULL DEFAULT 1,
|
||||
payload JSON NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '本地队列态(表 3-11)',
|
||||
retry_batch_id VARCHAR(64) NULL COMMENT 'CR-02 表 3-32 补列',
|
||||
retry_count INT NOT NULL DEFAULT 0,
|
||||
watermark BIGINT NOT NULL DEFAULT 0 COMMENT '对账三维 max(watermark)',
|
||||
next_retry_at DATETIME(3) NULL,
|
||||
last_error VARCHAR(512) NULL,
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
received_at DATETIME(3) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_outbox_gyid_idem (gyid, idempotency_key),
|
||||
KEY idx_outbox_gyid_state_wm (gyid, state, watermark),
|
||||
KEY idx_outbox_gyid_biz (gyid, biz_type, biz_id),
|
||||
CONSTRAINT ck_outbox_state CHECK (state IN ('PENDING','SENDING','SENT','FAILED','DEAD'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='上报 outbox 队列(非终态流水)';
|
||||
|
||||
-- 42 上报水位
|
||||
CREATE TABLE IF NOT EXISTS apt_report_watermark (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
current_watermark BIGINT NOT NULL DEFAULT 0,
|
||||
acked_watermark BIGINT NOT NULL DEFAULT 0,
|
||||
last_heartbeat_at DATETIME(3) NULL,
|
||||
clock_skew_ms BIGINT NULL COMMENT '#14 心跳时钟偏差',
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_watermark_gyid (gyid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='上报水位 [推断]';
|
||||
|
||||
-- 43 值班操作审计(所有写操作经审计切面;Q-40 冻结前也作 JSON 兜底载体)
|
||||
CREATE TABLE IF NOT EXISTS duty_operation_log (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
log_id VARCHAR(64) NOT NULL,
|
||||
operator_ref VARCHAR(64) NULL,
|
||||
module VARCHAR(64) NULL,
|
||||
action VARCHAR(64) NULL,
|
||||
detail JSON NULL COMMENT 'Q-40 / Q-48 冻结前的 JSON 兜底留痕',
|
||||
trace_id VARCHAR(64) NULL,
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_oplog_gyid_log (gyid, log_id),
|
||||
KEY idx_oplog_gyid_time (gyid, occurred_at),
|
||||
KEY idx_oplog_gyid_trace (gyid, trace_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='值班操作审计 [推断]';
|
||||
|
||||
-- 44 人工命令(只提交命令,由 apt.duty.state 落状态)
|
||||
CREATE TABLE IF NOT EXISTS duty_manual_action (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
action_id VARCHAR(64) NOT NULL COMMENT '与 command_id 对齐,幂等键',
|
||||
jcid VARCHAR(64) NULL,
|
||||
action_type VARCHAR(32) NOT NULL COMMENT 'STOP / RE_CALL / CORRECT',
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
duplicated TINYINT NOT NULL DEFAULT 0 COMMENT '重复命令返回 duplicated=true',
|
||||
result_code VARCHAR(64) NULL,
|
||||
requested_by VARCHAR(64) NULL,
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_manual_gyid_action (gyid, action_id),
|
||||
KEY idx_manual_gyid_jcid (gyid, jcid, occurred_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='人工命令(不直写业务状态)[推断]';
|
||||
|
||||
-- 45 交班日志
|
||||
CREATE TABLE IF NOT EXISTS duty_shift_handover (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
handover_id VARCHAR(64) NOT NULL,
|
||||
shift_id VARCHAR(64) NULL,
|
||||
from_operator VARCHAR(64) NULL,
|
||||
to_operator VARCHAR(64) NULL,
|
||||
content JSON NULL,
|
||||
handed_at DATETIME(3) NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_handover_gyid_id (gyid, handover_id),
|
||||
KEY idx_handover_gyid_time (gyid, handed_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='交班日志 [推断]';
|
||||
|
||||
-- 46 运维自检(12 项)
|
||||
CREATE TABLE IF NOT EXISTS apt_ops_selfcheck (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
check_id VARCHAR(64) NOT NULL,
|
||||
check_item VARCHAR(64) NOT NULL,
|
||||
result VARCHAR(20) NOT NULL DEFAULT 'UNKNOWN',
|
||||
call_ready TINYINT NOT NULL DEFAULT 0,
|
||||
detail VARCHAR(512) NULL,
|
||||
checked_at DATETIME(3) NOT NULL,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_selfcheck_gyid_id (gyid, check_id),
|
||||
KEY idx_selfcheck_gyid_time (gyid, checked_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='运维自检 12 项 [推断]';
|
||||
|
||||
-- 47 人员账号映射
|
||||
CREATE TABLE IF NOT EXISTS apt_person_account (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
ryid VARCHAR(64) NOT NULL,
|
||||
sys_user_id BIGINT NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
source_version BIGINT NOT NULL DEFAULT 0,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_personacct_gyid_ryid (gyid, ryid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='人员账号映射 [推断]';
|
||||
|
||||
-- 48 T-09 上报终态流水(CR-02 表 3-30;不是队列)
|
||||
CREATE TABLE IF NOT EXISTS apt_report_history (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
idempotency_key VARCHAR(128) NOT NULL,
|
||||
biz_type VARCHAR(64) NOT NULL,
|
||||
biz_id VARCHAR(64) NOT NULL,
|
||||
biz_version BIGINT NOT NULL DEFAULT 1,
|
||||
batch_id VARCHAR(64) NULL,
|
||||
retry_batch_id VARCHAR(64) NULL,
|
||||
state VARCHAR(20) NOT NULL COMMENT 'SENT/FAILED/DEAD/RETRIED',
|
||||
result_code VARCHAR(64) NULL,
|
||||
last_error VARCHAR(512) NULL,
|
||||
occurred_at DATETIME(3) NULL,
|
||||
sent_at DATETIME(3) NULL,
|
||||
platform_recv_at DATETIME(3) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_reporthis_gyid_idem (gyid, idempotency_key),
|
||||
KEY idx_reporthis_gyid_state (gyid, state, sent_at),
|
||||
CONSTRAINT ck_reporthis_state CHECK (state IN ('SENT','FAILED','DEAD','RETRIED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-09 上报终态流水';
|
||||
|
||||
-- 49 T-10 续报批次(CR-02 表 3-30)
|
||||
CREATE TABLE IF NOT EXISTS apt_report_retry_batch (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
batch_id VARCHAR(64) NOT NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'RUNNING',
|
||||
trigger_type VARCHAR(20) NOT NULL DEFAULT 'MANUAL' COMMENT 'MANUAL / AUTO',
|
||||
item_count INT NOT NULL DEFAULT 0,
|
||||
success_count INT NOT NULL DEFAULT 0,
|
||||
from_watermark BIGINT NOT NULL DEFAULT 0,
|
||||
to_watermark BIGINT NOT NULL DEFAULT 0,
|
||||
started_at DATETIME(3) NULL,
|
||||
finished_at DATETIME(3) NULL,
|
||||
created_by VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_retrybatch_gyid_id (gyid, batch_id),
|
||||
CONSTRAINT ck_retrybatch_trigger CHECK (trigger_type IN ('MANUAL','AUTO'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-10 续报批次(复用原幂等键)';
|
||||
|
||||
-- 50 T-12 排班(CR-02 表 3-29)
|
||||
CREATE TABLE IF NOT EXISTS duty_roster (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
shift_id VARCHAR(64) NOT NULL COMMENT '日期 + 班次',
|
||||
ryid VARCHAR(64) NOT NULL,
|
||||
shift_date DATE NOT NULL,
|
||||
shift_type VARCHAR(20) NOT NULL DEFAULT 'DAY' COMMENT 'DAY / NIGHT',
|
||||
start_at DATETIME(3) NOT NULL,
|
||||
end_at DATETIME(3) NOT NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'PLANNED' COMMENT 'PLANNED/CONFIRMED/CANCELLED',
|
||||
created_by VARCHAR(64) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_roster_gyid_shift_ryid (gyid, shift_id, ryid),
|
||||
KEY idx_roster_gyid_date (gyid, shift_date),
|
||||
CONSTRAINT ck_roster_state CHECK (state IN ('PLANNED','CONFIRMED','CANCELLED')),
|
||||
CONSTRAINT ck_roster_type CHECK (shift_type IN ('DAY','NIGHT'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-12 排班';
|
||||
|
||||
-- 51 T-13 盯控质量考核(CR-02 表 3-29;本地算、平台汇总)
|
||||
CREATE TABLE IF NOT EXISTS duty_performance (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
ryid VARCHAR(64) NOT NULL,
|
||||
shift_id VARCHAR(64) NULL,
|
||||
stat_date DATE NOT NULL,
|
||||
response_sec_avg DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
alarm_handled_rate DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
call_success_rate DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
manual_action_count INT NOT NULL DEFAULT 0,
|
||||
alarm_missed_count INT NOT NULL DEFAULT 0,
|
||||
calc_version BIGINT NOT NULL DEFAULT 1,
|
||||
report_state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
calculated_at DATETIME(3) NULL,
|
||||
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_perf_gyid_date_ryid (gyid, stat_date, ryid),
|
||||
KEY idx_perf_gyid_date (gyid, stat_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-13 盯控质量考核';
|
||||
@@ -0,0 +1,196 @@
|
||||
-- =============================================================================
|
||||
-- 域 6:CR-02 新增业务表 T-01~T-08(8 张)
|
||||
-- 列级定义来源:CR-02 表 3-27(T-01~T-03)、表 3-28(T-04/T-05/T-08)、
|
||||
-- 表 3-29(T-06/T-07);§12 表 10-6 的补列已合并进 T-04
|
||||
-- 迁移顺序:按 CR-02 表 3-33,本域在存量 44 张之后执行;DDL 与回填分离。
|
||||
-- =============================================================================
|
||||
|
||||
-- 52 T-01 平板设备
|
||||
CREATE TABLE IF NOT EXISTS apt_tablet_device (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
device_code VARCHAR(64) NOT NULL COMMENT '设备编码,入库大写',
|
||||
room_id VARCHAR(64) NULL,
|
||||
activation_code_ref VARCHAR(128) NULL COMMENT '激活码只存引用,不存明文',
|
||||
register_status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'ACTIVE/DISABLED/PENDING',
|
||||
app_version VARCHAR(32) NULL,
|
||||
last_seen_at DATETIME(3) NULL,
|
||||
last_cursor VARCHAR(128) 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),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_tabletdev_gyid_code (gyid, device_code),
|
||||
CONSTRAINT ck_tabletdev_status CHECK (register_status IN ('ACTIVE','DISABLED','PENDING'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-01 平板设备';
|
||||
|
||||
-- 53 T-02 平板任务(按 jcsj 顺序派发,dispatch_seq 单调递增)
|
||||
CREATE TABLE IF NOT EXISTS apt_tablet_task (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
jcid VARCHAR(64) NOT NULL,
|
||||
dispatch_seq BIGINT NOT NULL,
|
||||
call_mode VARCHAR(20) NOT NULL DEFAULT 'TABLET',
|
||||
jcsj DATETIME(3) NULL COMMENT '叫乘基准时刻(本地复算值)',
|
||||
device_code VARCHAR(64) NULL,
|
||||
pull_state VARCHAR(20) NOT NULL DEFAULT 'WAITING' COMMENT 'WAITING/PULLED/EXPIRED/DONE',
|
||||
pulled_at DATETIME(3) NULL,
|
||||
pulled_by VARCHAR(64) NULL,
|
||||
expire_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),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_tablettask_gyid_task (gyid, task_id),
|
||||
KEY idx_tablettask_gyid_seq (gyid, dispatch_seq),
|
||||
KEY idx_tablettask_gyid_time (gyid, jcsj),
|
||||
CONSTRAINT ck_tablettask_pull CHECK (pull_state IN ('WAITING','PULLED','EXPIRED','DONE'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-02 平板派发任务';
|
||||
|
||||
-- 54 T-03 平板叫乘结果(result_id 幂等;平板链路不发 #24)
|
||||
CREATE TABLE IF NOT EXISTS apt_tablet_call_result (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
result_id VARCHAR(64) NOT NULL COMMENT '幂等键',
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
jcid VARCHAR(64) NOT NULL,
|
||||
face_verify_state VARCHAR(20) NOT NULL COMMENT 'VERIFIED/UNVERIFIED/SKIPPED',
|
||||
verified_ryid VARCHAR(64) NULL,
|
||||
result_type VARCHAR(20) NOT NULL COMMENT 'ANSWERED/NO_ANSWER/FAILED/CANCELLED',
|
||||
person_count INT NOT NULL DEFAULT 0,
|
||||
device_code VARCHAR(64) NOT NULL,
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
received_at DATETIME(3) NOT NULL,
|
||||
applied_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/APPLIED/IGNORED',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_tabletresult_gyid_id (gyid, result_id),
|
||||
KEY idx_tabletresult_gyid_task (gyid, task_id),
|
||||
KEY idx_tabletresult_gyid_state (gyid, applied_state, occurred_at),
|
||||
CONSTRAINT ck_tabletresult_face CHECK (face_verify_state IN ('VERIFIED','UNVERIFIED','SKIPPED')),
|
||||
CONSTRAINT ck_tabletresult_type CHECK (result_type IN ('ANSWERED','NO_ANSWER','FAILED','CANCELLED')),
|
||||
CONSTRAINT ck_tabletresult_applied CHECK (applied_state IN ('PENDING','APPLIED','IGNORED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-03 平板叫乘结果';
|
||||
|
||||
-- 55 T-04 人脸注册申请(生效点在公寓;平台回执不决定本地生效)
|
||||
-- 合并 公寓端/10 表 10-6 补列:双路状态链、媒体引用、person_category
|
||||
CREATE TABLE IF NOT EXISTS apt_person_face_reg (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
reg_id VARCHAR(64) NOT NULL,
|
||||
work_no VARCHAR(64) NULL,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
face_feature_ref VARCHAR(256) NOT NULL COMMENT '不存特征大对象',
|
||||
photo_ref VARCHAR(512) NULL COMMENT '不存原图二进制',
|
||||
photo_checksum VARCHAR(128) NULL,
|
||||
feature_checksum VARCHAR(128) NULL,
|
||||
feature_algo VARCHAR(32) NULL,
|
||||
feature_version BIGINT NOT NULL DEFAULT 1,
|
||||
person_category VARCHAR(20) NOT NULL DEFAULT 'TRANSIENT' COMMENT 'TRANSIENT / LONG_TERM',
|
||||
reg_state VARCHAR(20) NOT NULL DEFAULT 'APPLIED' COMMENT 'APPLIED/CONFIRMED/REJECTED/ARCHIVED',
|
||||
local_only TINYINT(1) NOT NULL DEFAULT 1,
|
||||
verdict VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '平台同步回执 ACCEPTED/REJECTED',
|
||||
confirmed_ryid VARCHAR(64) NULL,
|
||||
reject_reason VARCHAR(256) NULL,
|
||||
verdict_version BIGINT NULL,
|
||||
apply_device_code VARCHAR(64) NULL,
|
||||
applied_at DATETIME(3) NULL,
|
||||
confirmed_by VARCHAR(64) NULL,
|
||||
confirmed_at DATETIME(3) NULL,
|
||||
upload_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '上传平台状态链(独立)',
|
||||
dispatch_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '下发设备状态链(独立)',
|
||||
face_sync_state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
registered_by VARCHAR(64) NULL,
|
||||
device_code VARCHAR(64) NULL,
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
report_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/SENT/FAILED/DEAD/RETRIED',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_facereg_gyid_reg (gyid, reg_id),
|
||||
KEY idx_facereg_gyid_state (gyid, reg_state, occurred_at),
|
||||
KEY idx_facereg_gyid_workno (gyid, work_no),
|
||||
CONSTRAINT ck_facereg_state CHECK (reg_state IN ('APPLIED','CONFIRMED','REJECTED','ARCHIVED')),
|
||||
CONSTRAINT ck_facereg_verdict CHECK (verdict IN ('PENDING','ACCEPTED','REJECTED')),
|
||||
CONSTRAINT ck_facereg_category CHECK (person_category IN ('TRANSIENT','LONG_TERM')),
|
||||
CONSTRAINT ck_facereg_upload CHECK (upload_state IN ('PENDING','SENT','FAILED','DEAD','ACKED')),
|
||||
CONSTRAINT ck_facereg_dispatch CHECK (dispatch_state IN ('PENDING','ISSUED','FAILED','DEAD','ACKED')),
|
||||
CONSTRAINT ck_facereg_report CHECK (report_state IN ('PENDING','SENT','FAILED','DEAD','RETRIED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-04 人脸注册申请(§12 补列已合并)';
|
||||
|
||||
-- 56 T-05 换乘单(CR-02 表 3-28)
|
||||
CREATE TABLE IF NOT EXISTS apt_transfer_order (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
transfer_id VARCHAR(64) NOT NULL,
|
||||
jcid VARCHAR(64) NOT NULL,
|
||||
jlid VARCHAR(64) NOT NULL,
|
||||
old_room_id VARCHAR(64) NULL,
|
||||
new_room_id VARCHAR(64) NULL,
|
||||
new_dep_time DATETIME(3) NOT NULL,
|
||||
new_call_time DATETIME(3) NOT NULL,
|
||||
recomputed_call_time DATETIME(3) NOT NULL COMMENT '与 new_call_time 不等时告警,不静默覆盖',
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
|
||||
command_id VARCHAR(64) NULL,
|
||||
reason VARCHAR(256) NULL,
|
||||
biz_version BIGINT NOT NULL DEFAULT 1,
|
||||
applied_at DATETIME(3) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_transfer_gyid_id (gyid, transfer_id),
|
||||
KEY idx_transfer_gyid_jcid (gyid, jcid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-05 换乘单';
|
||||
|
||||
-- 57 T-06 保洁人员
|
||||
CREATE TABLE IF NOT EXISTS apt_clean_staff (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
staff_id VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
phone_ref VARCHAR(128) NULL COMMENT '联系方式只存引用',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_cleanstaff_gyid_id (gyid, staff_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-06 保洁人员';
|
||||
|
||||
-- 58 T-07 保洁记录
|
||||
CREATE TABLE IF NOT EXISTS apt_clean_record (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
record_id VARCHAR(64) NOT NULL,
|
||||
staff_id VARCHAR(64) NOT NULL,
|
||||
room_id VARCHAR(64) NOT NULL,
|
||||
clean_type VARCHAR(20) NOT NULL,
|
||||
state VARCHAR(20) NOT NULL,
|
||||
task_id VARCHAR(64) NULL,
|
||||
started_at DATETIME(3) NULL,
|
||||
finished_at DATETIME(3) NULL,
|
||||
remark VARCHAR(256) NULL,
|
||||
report_state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_cleanrecord_gyid_id (gyid, record_id),
|
||||
KEY idx_cleanrecord_gyid_room (gyid, room_id, started_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-07 保洁记录';
|
||||
|
||||
-- 59 T-08 设备报警(CR-02 表 3-28;alarm_event_key 与 /ws/duty 去重一致)
|
||||
CREATE TABLE IF NOT EXISTS apt_device_alarm (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
gyid VARCHAR(32) NOT NULL,
|
||||
alarm_id VARCHAR(64) NOT NULL,
|
||||
device_code VARCHAR(64) NOT NULL,
|
||||
alarm_source VARCHAR(20) NOT NULL DEFAULT 'DEVICE',
|
||||
alarm_type VARCHAR(20) NOT NULL,
|
||||
severity VARCHAR(20) NOT NULL DEFAULT 'WARN',
|
||||
alarm_event_key VARCHAR(128) NOT NULL,
|
||||
occurred_at DATETIME(3) NOT NULL,
|
||||
handled_at DATETIME(3) NULL,
|
||||
handled_by VARCHAR(64) NULL,
|
||||
handled_result VARCHAR(256) NULL,
|
||||
biz_version BIGINT NOT NULL DEFAULT 1,
|
||||
report_state VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_devalarm_gyid_id (gyid, alarm_id),
|
||||
UNIQUE KEY uk_devalarm_gyid_key (gyid, alarm_event_key),
|
||||
KEY idx_devalarm_gyid_state (gyid, alarm_type, occurred_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='T-08 设备报警';
|
||||
@@ -0,0 +1,41 @@
|
||||
-- =============================================================================
|
||||
-- 增量补列(幂等):CR-02 表 3-32 / §12 表 10-6 / §13 表 11-7 / §14 表 12-7~12-9
|
||||
-- 用法:先执行 00_common.sql 建立 apt_add_column_if_missing,再执行本文件。
|
||||
-- 说明:本轮 apt_rev01 为空库,01~06 已把补列直接建在表内,本文件为空操作(no-op);
|
||||
-- 它服务于「已有旧库的后续迭代」与阶段二冻结评审后的增量迁移,重复执行不报错。
|
||||
-- Q-20 / Q-40 / Q-43 / Q-48 未决项:一律不新增列,按表 13-10 用 JSON 兜底
|
||||
-- (duty_operation_log.detail / apt_local_task.request_payload / PL_JOB_*)。
|
||||
-- =============================================================================
|
||||
CALL apt_add_column_if_missing('gy_jcxx', 'call_mode',
|
||||
"call_mode VARCHAR(20) NOT NULL DEFAULT 'SIP' COMMENT 'SIP / TABLET'");
|
||||
CALL apt_add_column_if_missing('gy_jcxx', 'dispatch_seq',
|
||||
'dispatch_seq BIGINT NULL COMMENT ''平板派发序号''');
|
||||
CALL apt_add_column_if_missing('apt_report_outbox', 'retry_batch_id',
|
||||
'retry_batch_id VARCHAR(64) NULL COMMENT ''续报批次''');
|
||||
CALL apt_add_column_if_missing('apt_master_room', 'building',
|
||||
'building VARCHAR(64) NULL COMMENT ''楼栋;Q-20 未决前列存在但不启用''');
|
||||
|
||||
CALL apt_add_column_if_missing('apt_master_person', 'photo_ref',
|
||||
'photo_ref VARCHAR(512) NULL COMMENT ''照片引用''');
|
||||
CALL apt_add_column_if_missing('apt_master_person', 'feature_ref',
|
||||
'feature_ref VARCHAR(512) NULL COMMENT ''特征值引用''');
|
||||
CALL apt_add_column_if_missing('apt_master_person', 'face_sync_state',
|
||||
"face_sync_state VARCHAR(20) NOT NULL DEFAULT 'PENDING'");
|
||||
|
||||
CALL apt_add_column_if_missing('apt_local_task', 'apply_id',
|
||||
'apply_id VARCHAR(64) NULL COMMENT ''入住申请 ID''');
|
||||
CALL apt_add_column_if_missing('apt_local_task', 'checkin_state',
|
||||
"checkin_state VARCHAR(24) NOT NULL DEFAULT 'SUBMITTED'");
|
||||
CALL apt_add_column_if_missing('apt_local_task', 'request_payload',
|
||||
'request_payload JSON NULL COMMENT ''Q-40 冻结前 JSON 兜底''');
|
||||
|
||||
CALL apt_add_column_if_missing('gy_ykt_remainder', 'local_balance_version',
|
||||
'local_balance_version BIGINT NOT NULL DEFAULT 1');
|
||||
CALL apt_add_column_if_missing('gy_sb', 'face_scope_type',
|
||||
'face_scope_type VARCHAR(20) NULL COMMENT ''§12 人脸设备范围''');
|
||||
|
||||
-- 未决项显式留痕(不做 DDL):
|
||||
-- Q-20 楼栋字段启用 -> apt_master_room.building 已建但不参与筛选
|
||||
-- Q-40 apt_local_task 补列冻结 -> request_payload JSON 兜底
|
||||
-- Q-43 账务补列冻结 -> 04_domain_04_ledger.sql 只建结构,不开放读写
|
||||
-- Q-48 平台统计表冻结 -> 平台库 pl_apt_stat_* 本轮不建(延后域)
|
||||
@@ -0,0 +1,44 @@
|
||||
-- =============================================================================
|
||||
-- 可重放回填种子(DDL 与回填分离;CR-02 表 3-33 / 表 13-10 验收③)
|
||||
-- 所有 INSERT 均为「不存在才插入」,重复执行不产生重复数据。
|
||||
-- 执行前可用以下方式覆盖公寓标识(需与环境变量 APT_GY_ID 保持一致):
|
||||
-- mysql ... -e "SET @apt_gyid='YOUR_GYID'; SOURCE 08_seed_replayable.sql"
|
||||
-- =============================================================================
|
||||
SET @apt_gyid := COALESCE(NULLIF(@apt_gyid, ''), 'APT-REV01-001');
|
||||
|
||||
-- 主数据版本基线:Mock 阶段显式标 LOCAL、source_version=0
|
||||
INSERT INTO apt_master_version (gyid, domain, source_version, source, state, applied_at, remark)
|
||||
SELECT @apt_gyid, d.domain_name, 0, 'LOCAL', 'APPLIED', NOW(3), 'IT-0 基线种子(非平台下发)'
|
||||
FROM (
|
||||
SELECT 'APARTMENT' AS domain_name UNION ALL SELECT 'ROOM' UNION ALL SELECT 'PERSON'
|
||||
UNION ALL SELECT 'ROUTE' UNION ALL SELECT 'RULE' UNION ALL SELECT 'HOLIDAY'
|
||||
) d
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM apt_master_version v WHERE v.gyid = @apt_gyid AND v.domain = d.domain_name
|
||||
);
|
||||
|
||||
-- 上报水位初始行
|
||||
INSERT INTO apt_report_watermark (gyid, current_watermark, acked_watermark)
|
||||
SELECT @apt_gyid, 0, 0
|
||||
WHERE NOT EXISTS (SELECT 1 FROM apt_report_watermark w WHERE w.gyid = @apt_gyid);
|
||||
|
||||
-- 12 项自检项定义(只登记检查项,结果由 IT-7 落库;本轮不写业务结果)
|
||||
INSERT INTO apt_ops_selfcheck (gyid, check_id, check_item, result, call_ready, checked_at)
|
||||
SELECT @apt_gyid, c.check_id, c.check_item, 'UNKNOWN', 0, NOW(3)
|
||||
FROM (
|
||||
SELECT 'IT0-01' AS check_id, 'DB_CONNECT' AS check_item UNION ALL
|
||||
SELECT 'IT0-02', 'DB_READ_WRITE' UNION ALL
|
||||
SELECT 'IT0-03', 'REDIS_CONNECT' UNION ALL
|
||||
SELECT 'IT0-04', 'REDIS_DB_INDEX' UNION ALL
|
||||
SELECT 'IT0-05', 'REDIS_READ_WRITE' UNION ALL
|
||||
SELECT 'IT0-06', 'DDL_TABLE_COUNT' UNION ALL
|
||||
SELECT 'IT0-07', 'DDL_PK_UK_IDX' UNION ALL
|
||||
SELECT 'IT0-08', 'GYID_ISOLATION' UNION ALL
|
||||
SELECT 'IT0-09', 'CREDENTIAL_SCAN' UNION ALL
|
||||
SELECT 'IT0-10', 'QUARTZ_STORE' UNION ALL
|
||||
SELECT 'IT0-11', 'AMI_REACHABLE' UNION ALL
|
||||
SELECT 'IT0-12', 'CALL_READY'
|
||||
) c
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM apt_ops_selfcheck s WHERE s.gyid = @apt_gyid AND s.check_id = c.check_id
|
||||
);
|
||||
@@ -0,0 +1,64 @@
|
||||
-- =============================================================================
|
||||
-- IT-0 验收校验脚本(表 13-10 验收③ / MS-0)
|
||||
-- 预期:business_table_count = 59;missing_pk = 0;missing_gyid = 0
|
||||
-- 口径:apt_* 36 + gy_*/gygl_* 18 + duty_* 5 = 59(CR-02 表 3-25、公寓端/03 表 3-8)
|
||||
-- apt_ops_probe 为连通性探针表,显式排除,不计入 59 张。
|
||||
-- =============================================================================
|
||||
SELECT '=== 1. 业务表总数(应为 59)===' AS section;
|
||||
SELECT COUNT(*) AS business_table_count
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_TYPE = 'BASE TABLE'
|
||||
AND (TABLE_NAME LIKE 'apt\_%' OR TABLE_NAME LIKE 'gy\_%'
|
||||
OR TABLE_NAME LIKE 'gygl\_%' OR TABLE_NAME LIKE 'duty\_%')
|
||||
AND TABLE_NAME <> 'apt_ops_probe';
|
||||
|
||||
SELECT '=== 2. 分前缀分布(应为 apt 36 / gy 17 / gygl 1 / duty 5)===' AS section;
|
||||
SELECT CASE
|
||||
WHEN TABLE_NAME LIKE 'apt\_%' THEN 'apt'
|
||||
WHEN TABLE_NAME LIKE 'gygl\_%' THEN 'gygl'
|
||||
WHEN TABLE_NAME LIKE 'gy\_%' THEN 'gy'
|
||||
ELSE 'duty'
|
||||
END AS prefix, COUNT(*) AS cnt
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND (TABLE_NAME LIKE 'apt\_%' OR TABLE_NAME LIKE 'gy\_%'
|
||||
OR TABLE_NAME LIKE 'gygl\_%' OR TABLE_NAME LIKE 'duty\_%')
|
||||
AND TABLE_NAME <> 'apt_ops_probe'
|
||||
GROUP BY prefix ORDER BY prefix;
|
||||
|
||||
SELECT '=== 3. 缺主键的业务表(应为 0 行)===' AS section;
|
||||
SELECT t.TABLE_NAME
|
||||
FROM information_schema.TABLES t
|
||||
WHERE t.TABLE_SCHEMA = DATABASE()
|
||||
AND (t.TABLE_NAME LIKE 'apt\_%' OR t.TABLE_NAME LIKE 'gy\_%'
|
||||
OR t.TABLE_NAME LIKE 'gygl\_%' OR t.TABLE_NAME LIKE 'duty\_%')
|
||||
AND t.TABLE_NAME <> 'apt_ops_probe'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.TABLE_CONSTRAINTS c
|
||||
WHERE c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME
|
||||
AND c.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||
);
|
||||
|
||||
SELECT '=== 4. 缺 gyid 列的业务表(应为 0 行)===' AS section;
|
||||
SELECT t.TABLE_NAME
|
||||
FROM information_schema.TABLES t
|
||||
WHERE t.TABLE_SCHEMA = DATABASE()
|
||||
AND (t.TABLE_NAME LIKE 'apt\_%' OR t.TABLE_NAME LIKE 'gy\_%'
|
||||
OR t.TABLE_NAME LIKE 'gygl\_%' OR t.TABLE_NAME LIKE 'duty\_%')
|
||||
AND t.TABLE_NAME <> 'apt_ops_probe'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS c
|
||||
WHERE c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME
|
||||
AND c.COLUMN_NAME = 'gyid'
|
||||
);
|
||||
|
||||
SELECT '=== 5. 外键数量(应为 0:docs/10 表 5-6 明确不建外键)===' AS section;
|
||||
SELECT COUNT(*) AS foreign_key_count
|
||||
FROM information_schema.TABLE_CONSTRAINTS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND CONSTRAINT_TYPE = 'FOREIGN KEY';
|
||||
|
||||
SELECT '=== 6. 探针表与基线种子 ===' AS section;
|
||||
SELECT (SELECT COUNT(*) FROM apt_ops_probe) AS probe_rows,
|
||||
(SELECT COUNT(*) FROM apt_master_version WHERE source = 'LOCAL') AS local_baseline_rows,
|
||||
(SELECT COUNT(*) FROM apt_ops_selfcheck) AS selfcheck_item_rows;
|
||||
Reference in new issue
Block a user