WorkBuddy fd7195ecb0 fix(safety): Phase 10 复验 —— 修复图片上传魔数校验死代码(生产故障)/ 高风险词写入日志
对 Phase 10(Image & Chat Safety)执行 KEEP-VERIFY 复验:不采信历史 ✅,逐条核对
图片私有访问、类型/大小/魔数校验、审核与水印、风险识别/举报/违规处理、日志脱敏。
Gate 判定通过,发现并修复 2 处缺陷,并推翻旧报告 2 项安全声明。

D1(Critical,生产故障)图片上传的类型与魔数校验整条是死代码
  - PhotoService.uploadAndSave 用 `header = new byte[Math.min(12, stream.available())]`
    仅分配零值缓冲,从不调用 stream.read(...) → validateMagicNumber 对任何文件都收到
    12 个 0x00 → 恒返回 false → 恒抛"无效的图片文件头"。
  - 影响链(已确认线上触发):小程序 local-eligibility-supplement.ts 以
    photoType='local_eligibility' 调 POST /photos/upload → 本地资格佐证上传 100% 失败。
  - 次生问题:isAllowedMimeType(image-only 白名单)在 local_eligibility 分支之后
    无条件执行,即使魔数修好,application/pdf 仍被挡,而 FeatureFlagConfig
    的 localEvidenceAllowedTypes 明确允许 pdf;另有未 mark 的 stream.reset() 无效调用。
  - 测试为何没发现:PhotoServiceTest 只对 validateMagicNumber 传手写字节做单测,
    从不经过 uploadAndSave,该路径缺陷完全无覆盖。
  - 修复:改用 try(InputStream){ header = s.readNBytes(12); } 真正读取;
    MIME 校验按 photoType 分流(local_eligibility 走自己的白名单含 pdf);
    魔数校验仅对图片执行、PDF 跳过;移除死 reset();上传用全新流与校验流隔离;
    generateFileKey 重载按 Content-Type 推断扩展名,PDF 不再拿到误导性 .jpg key。

D2(Low)高风险词写入普通日志
  - ChatSafetyService.scanContent 把命中的高风险词原样写入 log.warn,
    与同文件 CONTACT 分支"只记 pattern 不记内容"矛盾,且违反本阶段 Gate。
  - 修复:改为 log.warn("Chat content matched HIGH_RISK keyword (blocked)")。

推翻的旧声明(docs/Phase-10-Completion-Report.md)
  - 「伪装恶意图片被拒(Magic Number校验)」不成立:该校验运行时恒失败,
    连合法图片也一律被拒(fail-closed 到不可用),安全属性并非由它提供。
  - 「EXIF清理预留(PhotoService.stripExif占位)」不存在:全仓库无该符号。
    且 getUploadToken 客户端直传路径服务端无法介入清理 EXIF。
  - 已在旧报告顶部加状态注记,指向本次复验报告。

测试
  - 新增 PhotoUploadValidationTest(6 用例),用 MockMultipartFile 真正走一遍
    uploadAndSave:合法 JPEG/PNG 通过且确实调用 putObject、伪造 Content-Type 被魔数挡、
    PDF 佐证不被 image-only 挡且 key 以 .pdf 结尾、非法 MIME 被拒、超大被拒。
  - 负向验证:临时把魔数守卫改为 if(false && ...) → 恰好 spoofedContentTypeRejectedByMagic
    一个用例变红,其余 5 个仍绿;随后还原并 grep 确认无 NEGATIVE-CHECK-TEMP 残留。
  - 全量回归(离线容器 danyang-match-backend-test,mvn -o test):
    334 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS(基线 328,+6)。

登记 gap(不自动实现,待产品确认)
  - 图片水印:全仓库无实现;主计划未列硬需求,仅"开发执行计划修订版"提"高级动态水印"。
  - EXIF 清理:旧声明不存在;客户端直传路径服务端无法介入,需改为接收侧重编码。
  - 直传凭证路径(getUploadToken)无服务端内容校验且不落 DB 记录 → 后续 getSignedUrl
    归属校验会查不到 UserPhoto 行,属契约层缺口,超出本阶段安全 Gate 范围。
  - 违规自动升级:ChatSafetyService 注释明确属 Phase 17 范围,当前 high 仅拦截不落库。

文档:新增 Phase-10-V11-Recovery-Completion-Report-2026-09-16.md;
旧报告加状态注记;主计划 Phase 10 复选框置 ✅;STATUS / JIRA_BACKLOG 同步(334 tests)。

Phase 11 未启动,按计划每阶段出报告后停止等待确认。
2026-09-16 21:00:52 +08:00

善缘丹阳公益婚恋服务平台 V1.1 增量恢复中

面向丹阳市成年单身人士,由公益组织参与运营的实名制公益婚恋服务平台。

真实 · 公益 · 本地 · 安全 · 高效

当前基线: V1.0.0-RC.2 + V1.1 客户需求增量 · 状态:INCREMENTAL RECOVERY / NO-GO

当前代码包含 V1.1 部分实现,但尚未通过 Admin、Miniapp、权限和 E2E 发布门禁。请勿依据历史 Completion Report 或 Jira 勾选项判断已经上线。唯一执行计划见 docs/善缘丹阳 V1.1 客户需求基线增量开发与修复计划.md。


项目简介

本平台帮助丹阳本地单身人士通过实名认证、人工审核、智能匹配、双向喜欢、在线沟通、公益红娘和线下相亲活动,安全、高效、真实地寻找合适伴侣。

用户永久免费。


技术栈

组件 技术
后端 Java 21 + Spring Boot 3.x
数据库 PostgreSQL
缓存/Session Redis
对象存储 S3 兼容(MinIO / 国内云存储)
实时通信 WebSocket
数据库迁移 Flyway
小程序 微信原生 + TypeScript
管理后台 Vue 3 + TypeScript + Vite + Pinia + Element Plus
容器化 Docker + Docker Compose + Nginx
CI/CD GitHub Actions

项目结构

├── backend/          # Spring Boot 后端服务
├── miniapp/          # 微信小程序用户端
├── admin-web/        # 运营管理后台(Web)
├── infrastructure/   # Docker Compose 编排
├── scripts/          # 运维脚本
├── tests/            # 测试代码
│   ├── unit/
│   ├── integration/
│   ├── api/
│   ├── permission/
│   ├── concurrency/
│   ├── e2e/
│   └── invariants/   # 核心不变量测试(每次CI必跑)
├── docs/             # 项目文档
│   ├── product/
│   ├── architecture/
│   ├── api/
│   ├── database/
│   ├── security/
│   ├── testing/
│   ├── ops/
│   └── decisions/
├── AGENTS.md         # 项目最高执行规则摘要
├── CHANGELOG.md
└── README.md

快速开始(开发环境)

前置要求

  • Docker & Docker Compose
  • JDK 21
  • Node.js 18+
  • npm / pnpm

一键启动基础设施

# 进入基础设施目录
cd infrastructure

# 启动 PostgreSQL / Redis / MinIO
docker compose up -d

# 等待服务就绪(约30秒)
# 可运行:docker compose logs -f

启动后端

cd backend

# 复制环境变量
cp src/main/resources/application-dev.yml.example src/main/resources/application-dev.yml
# 编辑 application-dev.yml,设置数据库连接等

# 构建并启动
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev

启动管理后台

cd admin-web

# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

启动小程序

使用微信开发者工具打开 miniapp/ 目录,配置 AppID 后即可预览。


环境变量

所有环境变量参见各模块的 .env.example 文件。

关键变量:

# 数据库
DB_HOST=localhost
DB_PORT=5432
DB_NAME=danyang_match
DB_USER=app_rw
DB_PASSWORD=<secret>

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379

# 对象存储
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=<secret>
MINIO_SECRET_KEY=<secret>
MINIO_BUCKET_PUBLIC=public
MINIO_BUCKET_PRIVATE=private

# 微信
WECHAT_APP_ID=<secret>
WECHAT_APP_SECRET=<secret>

Secret 不进 Git。


数据库迁移

cd backend
./mvnw flyway:migrate -Dflyway.url=jdbc:postgresql://localhost:5432/danyang_match

运行测试

Phase 01 一键基线检查

PowerShell:

.\scripts\verify-phase01.ps1

Bash:

./scripts/verify-phase01.sh

脚本依次检查 Git、Backend tests、Admin type-check/build、Miniapp TypeScript 和 V1.1 E2E。任一门禁失败时脚本返回非零退出码,并在结尾列出失败项。Windows 未安装 JDK/Maven 时,Backend 检查使用 Docker 中的 JDK 21 与仓库内 Maven 包;不会永久修改系统环境。

首次执行 Admin/Miniapp 检查前,在 admin-web 目录运行 npm ci。E2E 使用 python -m pytest,不要依赖全局 pytest 命令。

分项命令

# 后端单元测试
cd backend
./mvnw test

# 后端集成测试
./mvnw verify

# 管理后台构建检查
cd admin-web
pnpm build

# 小程序编译检查
cd miniapp
# 在微信开发者工具中查看编译结果

构建部署

# 构建后端镜像
docker build -t danyang-match-backend:latest ./backend

# 构建管理后台
cd admin-web && pnpm build

# 完整部署
cd infrastructure && docker compose up -d

核心业务闭环

微信登录 → 绑定手机 → 实名认证 → 填写资料 → 设置择偶 → 提交审核
    ↓
审核通过 → 系统推荐 → 用户Like → 双向Match → 安全聊天
    ↓
公益红娘辅助 / 参加线下活动 → 进一步了解 → 双方确认成功牵手

开发阶段

项目按 Phase 01~Phase 26 顺序推进。详见 docs/STATUS.md。


许可证

本项目为公益项目,服务于丹阳市单身人士婚恋匹配。

S
Description
善缘丹阳公益婚恋平台
Readme Unlicense
29 MiB
Languages
Java 53.1%
Vue 14.9%
JavaScript 12.5%
HTML 9.6%
TypeScript 3.8%
Other 6.1%