主题
数据库表结构与数据模型
全部业务状态落在单库里 45 张表:
nc_*承载验证码产品与验证过程,sys_*承载后台权限与动态配置,外加 2 张无前缀的运维表;ES 只做行为日志,不做状态。
1. 交付形态与建库顺序
仓库不带 Flyway/Liquibase,建表与初始数据是纯脚本,且是部署硬前置:
| 脚本 | 行数量级 | 目标库 | 说明 |
|---|---|---|---|
build/db_init/mysql/mysql.sql | 1028 行 / 45 张表 | MySQL 8(默认) | 权威版本,含注释、索引与 utf8mb4_0900_ai_ci |
build/db_init/mysql/init-data.sql | 4560+ 行 | MySQL 8 | 初始数据,nc_icon 949 行、nc_space_elm 3225 行、sys_permission 107 行 |
build/db_init/panwei/panweiddl.sql | 44 张表 | PanWeiDB / openGauss | create table if not exists 语法,bigserial 主键 |
build/db_init/panwei/opengauss-init.sql | — | PanWeiDB | 初始化数据 |
build/db_init/oracle/oracle.sql | 30 张表 | Oracle | 表子集,按需裁剪 |
配套 build/db_init/lib/panweidb-jdbc-1.0.0.jar 是国产库驱动,切库时需同步 db.driver 与 db.url(captcha-base/src/main/resources/base.properties:11-12)。
三套 DDL 不完全等价:PanWeiDB 脚本缺
account_operate_info(MySQL 45 张 vs PanWeiDB 44 张),Oracle 只有 30 张。切库前必须逐表比对,否则后台登录失败计数、监控告警配置会静默失效。
2. 通用约定
- 主键:
bigint AUTO_INCREMENT(PanWeiDB 为bigserial),业务唯一键另建UNIQUE KEY。 - 时间:
*_time为 bigint 毫秒时间戳(如create_time、stat_time);created_at/updated_at为timestamp DEFAULT CURRENT_TIMESTAMP。两套并存是历史遗留,查询时不要混用。 - 状态:
status1=生效/上线,2=失效/下线;名单类type1=黑、2=白、3=灰。 - 字符集:业务表
utf8mb4,MySQL 8 默认排序规则;MySQL 5.7 需显式降级utf8mb4_general_ci。 - 金额/性能类没有,故无 decimal 精度问题;
nc_product的qps_limit/hourly_limit/id_limit是配额,不是计量。
3. 表清单(按职责分组)
3.1 业务配置域(产品、验证码、版本)
| 表 | 行号 | 用途 | 关键字段 | 索引/约束 |
|---|---|---|---|---|
nc_product | mysql.sql:700 | 产品(客户级)与配额 | product_id、secret_id/secret_key、plan_type、status、qps_limit/hourly_limit/id_limit、valid_from/valid_to | nc_product_uniq_idx_pid(product_id) |
nc_app | mysql.sql:50 | 验证码应用(业务级),一个产品下多条 | captcha_id、secret_id/secret_key、timeout(默认 1200s)、security_model、image_sets、puzzle_image_set、audio、domain/restrict_domain、risk_warn_conf、online_status | uk_cid(captcha_id)、idx_product_id、idx_created_at/idx_updated_at |
nc_product_record | mysql.sql:731 | 产品/应用操作流水 | product_id、captcha_id、operation、note(4096) | idx_product_id、idx_create_time |
nc_click_conf | mysql.sql:194 | 点选类题面参数 | captcha_id(或用哨兵值 DEFAULT_CLICK_CONF)、char_type、font/font_size、easy_level、click_count、produce_count | nc_click_conf_uk_cid(captcha_id) |
nc_version | mysql.sql:870 | 前端 SDK 版本仓库 | version、status(同时只允许一个激活)、resources(4096 JSON)、gray_ratio、version_conf | version_unique(version) |
nc_version_product | mysql.sql:891 | 产品→版本的灰度映射 | product_id、version_id、percentage(1~100)、base_version | uniq_pid(product_id) |
nc_browser_versions | mysql.sql:157 | 浏览器安全版本白名单 | browser_name、version_minor | 无唯一键,靠后台维护 |
nc_app.secret_id/secret_key 与 nc_product 同名字段是两套凭证:nc_product 面向客户账号,nc_app 面向单业务,二次校验 verify 用的是 nc_app 那一对。
3.2 图片与素材域
| 表 | 行号 | 用途 | 关键字段 | 索引 |
|---|---|---|---|---|
nc_image_set | mysql.sql:472 | 图集(底图集合) | name、product_id、label(1 通用 / 19 图片推理)、status | nc_image_set_name_index(name) |
nc_image | mysql.sql:394 | 底图 | nos_key(对象存储 key)、set_id、drag_count/drag_big_count | idx_sid(set_id) |
nc_image_conf | mysql.sql:420 | 单图开关 | image_id、captcha_status | uniq_idx_image(image_id) |
nc_image_output | mysql.sql:438 | 预生成产物(拼图/点击答案) | puzzle(512 JSON)、captcha_id、set_id、image_id、type(2 拖动/3 点击)、status、counter | idx_sid_type、idx_sts_ct、idx_sts_uptime |
nc_icon | mysql.sql:377 | 图标库(图标点选用) | noskey | uniq_idx_pid(noskey) |
nc_space_elm | mysql.sql:849 | 空间推理元素 | type、color、angle_level、image_key | nc_space_elm_uniq_idx_pid(image_key) |
nc_element / nc_element_set | mysql.sql:250 / :271 | 通用元素库与元素 | element_type、value、ext、set_id、advanced_type | 无索引,靠 set_id 全扫 |
nc_image_output 是唯一高频写的大表:每生成一张拼图就插一行、被取用后改 status。它的三个索引全部围绕「按图集+状态+类型取一张可用产物」这一条查询设计。
3.3 名单与策略域
| 表 | 行号 | 用途 | 关键字段 | 唯一键 |
|---|---|---|---|---|
nc_managed_ip | mysql.sql:621 | IP 名单 | ip、type(1 黑/2 白/3 灰)、expire_time(-1 永久)、status | (ip) |
nc_namelist_user | mysql.sql:677 | 用户名单 | user、action_obj、hit_count、last_hit_time | (user, action_obj) |
nc_managed_finger_print | mysql.sql:600 | 设备指纹名单 | finger_print、device_type(1 WEB/2 Android/3 iOS/4 鸿蒙) | (finger_print, device_type) |
nc_freq | mysql.sql:289 | 高频规则主表 | type_id、key_str、frequency、monitor_interval、cal_type(1 计数/2 去重)、action、blacklist_lib_type、blacklist_duration | 无,全表缓存 |
nc_freq_condition | mysql.sql:323 | 规则条件(且/或) | frequency_id、meta_id、condition_type、content | 无 |
nc_freq_meta | mysql.sql:340 | 条件字段字典 | type_id、meta_field、priority_level | 无 |
三张 nc_freq* 表是读多写极少:规则配置后由内存缓存承载判定,改库后需重启或触发重载,不能指望改完立刻生效(与 sys_conf 的 30 秒轮询不同)。
3.4 License 域(双表双代码,务必都写)
| 表 | 行号 | 读取方 | 关键字段 | 语义 |
|---|---|---|---|---|
nc_licence | mysql.sql:516 | LicenceCheckInterceptor(拦 /api/**)+ 后台导入 | licence_content(1024 hex)、status、valid_to | 内容是 RSA 加密 JSON 的 hex,解密后按 JSON 内 e 判过期;5s 轮询 |
nc_license | mysql.sql:531 | LicenseService.canUseCaptcha()(取图校验) | captcha_type_specification、max_business_num、effective_start_time/effective_end_time、pub_key/private_key | 按套餐类型规格放行验证码类型;60s 刷新 |
两表没有任何外键或同步逻辑,各自独立刷新。"只写一张"是本系统最常见的部署事故。
3.5 统计域(唯一会无限膨胀的一组)
| 表 | 行号 | 粒度 | 唯一键 |
|---|---|---|---|
nc_request_stat | mysql.sql:755 | 应用 × 时间窗 × 类型 × 区域 | (captcha_id, begin_time, end_time, captcha_type, country) |
nc_request_stat_daily | mysql.sql:786 | 产品 × 天(销售口径) | (product_id, stat_time) |
nc_captcha_stat_daily | mysql.sql:172 | 应用 × 天 | (captcha_id, stat_time) |
nc_device_stat | mysql.sql:222 | 应用 × 设备类型 × 时间窗 | (captcha_id, type, begin_time) |
nc_ip_stat | mysql.sql:495 | 应用 × IP 哈希 × 时间点(风险 TOP) | (captcha_id, hcode, stat_time) |
nc_location_request_stat | mysql.sql:554 | 应用 × 省市 × 分组 × 场景 | (captcha_id, begin_time, end_time, country, province, group_name, scene) |
nc_hittype_request_stat | mysql.sql:359 | 应用 × 命中类型 × 时间窗 | (captcha_id, begin_time, end_time, hit_type) |
nc_sdk_collect | mysql.sql:814 | 前端异常上报流水 | 仅 idx_ctime、idx_pid |
统计写入由 captcha-schedule 的 CaptchaRequestStatWork(@Scheduled(cron = "0 0 1 * * ?"))与 captcha-server 的定时任务共同完成,多副本会重复累加——这是约束副本数的第二个原因。
3.6 模型、审计与系统域
| 表 | 行号 | 用途 | 备注 |
|---|---|---|---|
nc_model | mysql.sql:641 | 模型二进制(model_file 为 text) | name_unique(name),Spark MLlib 产物 |
nc_model_conf | mysql.sql:659 | 应用→模型绑定 | captcha_id 存 512 长度,可逗号多值 |
nc_lsh_feature | mysql.sql:581 | 轨迹 LSH 特征 | feature(1024),相似轨迹检索 |
nc_audit_workflow | mysql.sql:139 | 审批流定义 | trigger_action 唯一 |
nc_audit_record | mysql.sql:100 | 审批单 | apply_code 唯一,change_map 存字段差异 |
sys_user | mysql.sql:1009 | 后台账号 | 主键是 varchar(64) id,name 唯一 |
sys_role / sys_permission | mysql.sql:963 / :949 | 角色、功能权限点 | 权限点即 spring-security.xml 里的 hasAuthority(...) 字符串 |
sys_role_permission / sys_role_product | mysql.sql:978 / :993 | 角色→权限、角色→数据范围 | 后者 (role_id, product_id) 唯一 |
sys_op_record | mysql.sql:930 | 后台操作审计 | idx_type_time(operator, op_type) |
sys_conf | mysql.sql:911 | 动态配置中心 | key_unique(key_name),value_type 1 数字/2 字符串/3 JSON |
account_operate_info | mysql.sql:1 | 登录失败计数、改密时间 | account_unique(account),仅 MySQL 脚本有 |
metrics_warning_info | mysql.sql:19 | 监控阈值(Prometheus 拉取后判警) | idx_metrics_warning_service_name,labels 区分 Physics/Container |
4. 实体关系(ASCII ER)
+----------------+
| nc_product |
| product_id PK |
+--------+-------+
| 1
| n
+-------------+ +--------v--------+ +-----------------+
| nc_image_set|<-----+ nc_app +----->| nc_click_conf |
| set_id PK | n:1 | captcha_id PK | 1:1 | (captcha_id) |
+------+------+ +---+---------+---+ +-----------------+
| 1 | |
| n | 1 | 1
+------v------+ +----v----+ +-v---------------+
| nc_image | |nc_model_| | nc_audit_record |
| (set_id FK) | | conf | | (target=cid) |
+------+------+ +---------+ +--------+--------+
| 1 | n
| n | 1
+------v----------+ +-------v---------+
| nc_image_output | | nc_audit_workflow|
| (image_id FK) | | (trigger_action) |
+-----------------+ +-----------------+
sys_user --n:1--> sys_role --n:n--> sys_permission
| |
| +--n:n--> sys_role_product --1:1--> nc_product
nc_request_stat / nc_captcha_stat_daily / nc_ip_stat
\---- 全部以 captcha_id 逻辑关联 nc_app(无物理外键)关键点:全库没有一条物理外键。关联全靠 product_id / captcha_id 字符串,删除产品时不会级联清理统计与图片产物,必须由后台业务代码兜底。
5. sys_conf:改库即生效的动态配置机制
SysConfLoader(captcha-base/.../sys/conf/SysConfLoader.java:38)是配置的唯一入口:
- 刷新周期硬编码
INTERVAL_SECOND = 30(SysConfLoader.java:44),@PostConstruct启动即加载一次,之后每 30 秒增量刷新; - “按需刷”而非全量刷——类注释明确:没被代码使用过的 key 不会去查库;
- 缓存字段都是
volatile(cache、ipBlackList、smartCaptchaConf等,:55-67),刷新是整体替换引用,不走细粒度锁; - 没有任何
properties回退:key 缺失时的默认值写死在代码里(如CaptchaProduceCount.genDefaultConf(),:75)。
当前初始化 48 条(init-data.sql:4255-4307),按用途可归四类:
| 类别 | 代表 key | 默认值 | 影响 |
|---|---|---|---|
| 生产量 | captcha.produce.count | {"click":100,...,"voice":100} | 各类型每图集预生成总数 |
| 判定阈值 | min.drag.completed.time / min.drag.trace.limit | 1000 / 20 | 最短完成时间、最少轨迹点 |
| 开关 | frequency.enabled、switchover.enabled、validate.referer.enabled | true | 高频策略、自动切换、referer 校验总闸 |
| 灰度名单 | validate.proxy.enabled.pids、answer.correct.skip.acir、sdk.unavailable.pid.custom.conf | 逗号分隔 cid/pid | 按产品维度做灰度 |
部署含义:
sys_conf是有数据的表,不是空表。只建表不导init-data.sql时,frequency.enabled取不到 → 高频策略整体失效,captcha.produce.count取不到 → 预生成走代码默认值,症状是“后台配了没用”。
6. 初始化数据:部署硬前置
init-data.sql 不是可选种子数据,而是运行必需:
| 数据 | 条数 | 缺失后果 |
|---|---|---|
nc_product(YD00000000000001、DEFAULT_PRODUCT) | 2 | 无法取图,getconf 拿不到产品配置 |
nc_app(10 条 custom_* 私有化应用) | 10 | 客户端无 captchaId 可用 |
nc_image_set(3 个默认图集)+ nc_icon 949 条 + nc_space_elm 3225 条 | — | 题面生成无素材,error=202(缺少图片集) |
nc_click_conf(DEFAULT_CLICK_CONF 哨兵行) | 1 | 点击类无默认参数 |
nc_freq_meta | 24 | 高频规则无法配置条件字段 |
sys_permission / sys_role / sys_role_permission | 107 / 5 / 107 | 后台登录后所有菜单 403 |
sys_user(admin,init-data.sql:4536) | 1 | 无法登录后台 |
sys_conf | 48 | 见上一节 |
nc_licence + nc_license | 各 1 | License 校验失败,取图 100% 失败 |
nc_licence 的初始行(init-data.sql:64-65)带 valid_to = null,但 licence_content 内部 JSON 有 e 字段,过期判定以内容里为准,不是看表字段。
7. 表膨胀与归档建议
| 表 | 膨胀速率 | 现状风险 | 建议 |
|---|---|---|---|
nc_image_output | 与 QPS 同阶(每次生成一行) | 最高。status=1 待取产物与历史产物混在一张表,索引随行数退化 | 按 create_time 分区或按月归档;status IN (2,3) 的历史行定期 DELETE ... LIMIT 分批清理 |
nc_request_stat | 每 5 分钟 × 应用 × 类型 × 区域 | 次高 | 保留 90 天,聚合进 nc_request_stat_daily 后删明细 |
nc_ip_stat | 每时间点 × 应用 × IP | 高风险时爆量 | 保留 30 天即可,TOP N 场景不需要长历史 |
nc_sdk_collect | 前端异常有多少写多少 | 无限增长,仅 message 4096 | 保留 15 天;message 可截断 |
nc_product_record / sys_op_record | 后台操作量级 | 可控 | 保留 1 年 |
nc_lsh_feature | 预生成同步增长 | 中 | 与 nc_image_output 同生命周期清理 |
ES captcha_info_* | 每次 check 一条 | 副本数 1、best_compression、ILM 已挂 | 靠 ES_INDEX_CLEAN(captcha-schedule/.../clean/EsIndexCleanUpWorker.java:37,cron = "0 0 2 * * ?")按 elasticsearch.index.retention.days=180 删索引 |
8. ES 索引 captcha_info_*:行为日志模型
模板 build/es/elasticsearch/captcha_info_template 定义 captcha_info_*(:3-4):3 分片 / 1 副本 / codec: best_compression / 开启 query 与 fetch 慢日志 2000ms、index 慢日志 500ms(:6-36),并挂 ILM captcha_info_ilm_policy。
字段设计要点:
dynamic_templates把所有未声明string映射成keyword(:39-49),因此新增字段默认不可分词,做全文检索必须显式改 mapping。- 明确声明 40 余个字段,覆盖
id/cid/pid/action/captchaType/apiVersion/iv(:51-130)与风控字段hitType/hitRuleName/riskType/modelScore/deviceId/error(:131-167)。 userAgent、message、extraData、browserFeature为text + standard分析器(:75-146),其余字符串均为keyword。trace是object(:88-90),不索引内部结构,只能整体取出后由应用解析。
字段语义以 docs/CaptchaInfo字段说明文档.md 为准,其中最易误用的是 hitType:它不是放行/拦截二值,0 表示正常,其余为分段编码(100xx 上下文异常、20100 高频命中、20200 指纹黑名单),排查时必须查枚举而非猜。
9. 建模约束一览
| 约束 | 事实 | 影响 |
|---|---|---|
| 无外键 | 全库 0 条 FOREIGN KEY | 删除产品/应用不级联,统计与产物成孤儿行 |
| 双 License 表 | nc_licence + nc_license,各自轮询(5s / 60s) | 只写一张 → 取图失败或 /api/** 被拦 |
| 双时间类型 | bigint 毫秒 + timestamp 并存 | 跨表时间比较需换算,统计脚本易错 |
| 动态配置无回退 | sys_conf 缺 key → 用代码默认值 | 空库启动“看起来能跑”,策略静默失效 |
| 定时任务写统计 | @Scheduled 无分布式锁 | 多副本重复累加,统计虚高 |