Files
hera/README.md

271 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 祈运小程序后端
Spring Boot 后端,提供微信小程序登录、手机号验证码绑定、用户资料、协议和关注我们接口。
命理计算使用 `cn.6tail:lunar` 根据公历生日和时辰生成四柱八字,再计算五行分布、日主、喜用和忌用。
元宝页提供今日卡牌和每日生活小贴士接口。
## 本地启动
```bash
export WECHAT_APP_ID=wxa76a2ef9e8ba612e
export WECHAT_APP_SECRET='set-via-environment'
export DB_URL='jdbc:mysql://101.43.139.56:3306/hera?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true'
export DB_USERNAME=hera_user
export DB_PASSWORD='请通过部署环境配置'
mvn spring-boot:run
```
默认地址:`http://localhost:8080`
本机敏感配置可放在仓库根目录的 `.hera-local.yml`,使用与 `application.yml` 相同的 YAML
层级。默认启用 `local`,从当前启动目录读取该文件;此文件已加入 Git 忽略清单。
## 环境与部署
- `application.yml`:公共参数、上传限制与日志策略。
- `application-local.yml`:本地环境,MySQL/Redis 默认使用公网 `101.43.139.56`,允许创建缺失表。
- `application-prod.yml`:线上环境,MySQL/Redis 默认使用内网 `10.0.8.9`,不自动建表或改表。
- `.hera-local.yml`:本机私有配置,仅 `local` 环境加载。
- `.hera-prod.yml`:线上私有配置,仅 `prod` 环境加载,不会误读本地配置。
两套环境不能同时启用。地址和端口可通过 `DB_URL`、`REDIS_HOST`、`REDIS_PORT`、
`SERVER_PORT` 覆盖,其他参数仍使用现有环境变量。线上必须配置至少 32 字节、非开发默认值的
`hera.jwt-secret`(或 `HERA_JWT_SECRET`),不满足时拒绝启动。短信仍为 mock,正式上线前需接入厂商。
环境隔离只隔离配置文件,不会自动创建两套数据库;需要数据隔离时应分别指定数据库和 Redis 逻辑库。
服务器使用 Java 8,将 JAR 与私有 `.hera-prod.yml` 放在同一启动目录,执行:
```bash
chmod 600 .hera-prod.yml
java -jar hera-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
```
私有文件字段结构参考 `deploy/hera-prod.yml.example`;可以直接填写真实值,也可以使用环境变量占位符。
本机已按现有密钥准备私有 `.hera-prod.yml`,未复制本地的公网连接地址覆盖线上内网地址。
这两个私有文件均不进入 Git 或 JAR,不应放在 Nginx 可下载目录中。
需要将私有文件置于其他目录时,可设置 `HERA_PROD_CONFIG=optional:file:/etc/hera/prod.yml`
或对应的 `HERA_LOCAL_CONFIG`,默认仍从启动目录查找。
环境变量占位符只在未被私有文件的同名直接值覆盖时生效;若要通过环境变量管理某字段,
请删除私有文件中的直接值或将该值改成对应占位符。
## 日志
控制台与文件同时输出,时间使用北京时间。默认路径为启动目录的 `logs/`,
可设置 `LOG_PATH=/var/log/hera`,启动用户必须具有目录写权限:
- `hera.log`:启动信息、请求摘要及异常等全部应用日志。
- `hera-error.log`:仅 ERROR 级别日志,错误同时保留在 `hera.log`。
- `archive/`:按日期和大小滚动后的 gzip 压缩日志。
默认每文件 20MB、保留 30 天,每个文件序列独立限制总量为 2GB(两个序列合计最高约 4GB,
不含当前活动文件);支持 `LOG_MAX_FILE_SIZE`、`LOG_MAX_HISTORY`、`LOG_TOTAL_SIZE_CAP` 覆盖。
应用日志不代替 Nginx 访问日志、指标和告警,磁盘仍需运维监控。
每个请求返回 `X-Request-Id`,对应日志中的 `requestId`。请求摘要记录方法、路由模板、
状态码和耗时,不记录查询串、请求或响应正文、手机号、验证码、Token、Cookie、文件凭证等。
未知路由只记 `<unmapped>`,不打印可能包含敏感信息的原始路径。异常日志保留类型、原因链类型和
堆栈位置,但不打印异常消息,以免泄露 SQL 参数或包含微信密钥的 URL。
默认不启用 SQL 参数与 HTTP wire DEBUG 日志,不建议在线上自行打开此类日志。
```bash
tail -f logs/hera.log
tail -f logs/hera-error.log
```
## Redis
使用 Spring Data Redis 和 Lettuce,依赖版本由 Spring Boot 2.7.18 管理。
本地默认地址为 `101.43.139.56:6379`,线上为 `10.0.8.9:6379`,逻辑库为 `0`,连接与命令超时均为 3 秒。
支持 `REDIS_HOST`、`REDIS_PORT`、`REDIS_USERNAME`、`REDIS_PASSWORD`、`REDIS_DATABASE`、
`REDIS_SSL` 环境变量;本机密码配置在被 Git 忽略的 `.hera-local.yml` 中。
业务代码可注入 `StringRedisTemplate`。Key、Value 和 Hash 均采用字符串序列化;
结构化数据使用现有 `ObjectMapper` 显式转换为 JSON。建议 Key 使用 `hera:{业务}:` 前缀,
缓存和临时数据写入时必须指定合理的 TTL。当前接入连接能力,登录会话、mock 短信验证码
仍由现有 MySQL 链路保存,未启用用户资料自动缓存。
`mvn test` 包含不访问真实 Redis 的配置测试。
`mvn test -Dtest=RedisIntegrationTest -Dhera.redis.integration=true` 显式验证真实连接、
认证、PING、字符串读写与 TTL,使用 `hera:test:redis:{随机值}` 测试键并在结束后删除,
不清空 Redis。该测试独立于 MySQL,不执行用户表迁移。
## 腾讯云 COS 文件存储
存储桶为 `hera-1319913268`,地域为 `ap-beijing`,桶权限保持私有读。
运行环境必须配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`。
推荐仅授权该桶 `hera/avatar/*`、`hera/files/*` 路径的 `cos:PutObject`、`cos:GetObject` 权限。
密钥只通过环境变量或被忽略的私有配置提供,不进入受版本管理的配置或 Git。
未配置 COS 密钥时允许服务启动,文件操作会返回配置错误。
- `POST /api/user/avatar`:上传 JPG、PNG、WebP 头像,最大 5MB。
- `POST /api/files`:通过 multipart 字段 `file` 上传普通文件,最大 5MB。
- `GET /api/files/url?reference=cos%3A%2F%2Fhera%2Ffiles%2F...`:为本人文件续签。
所有上述接口需要 Bearer JWT。对象分别存储在 `hera/avatar/{userId}/`、
`hera/files/{userId}/`,数据库保存 `cos://` 对象标识;不要保存临时签名 URL。
资料查询返回的头像 URL 和文件上传返回的 URL 有效期为 900 秒;过期后重新获取。
历史 `/uploads/avatars/` 路径继续按原有本地静态资源配置访问,旧文件没有自动迁移。
访问入口默认使用 `http://101.43.139.56/hera/file/access?token=...`,Nginx 将外部
`/hera/file/access` 转发至后端 `/file/access`,保留查询参数;参考
`deploy/nginx-file.conf.example`。不要将携带应用文件凭证的入口直接代理至 COS。后端校验文件专用短期凭证后,
302 跳转至 COS 官方 HTTPS 签名 URL。入口不要求登录头,必须携带有效文件凭证。
文件链接属于临时访问凭据,不应写入日志或分享给无权访问的人。
部署时通过 `HERA_JWT_SECRET` 配置随机签名密钥,并通过 `TENCENT_COS_PUBLIC_BASE_URL`
配置生产 HTTPS 域名下的 `/file/` 地址。小程序需配置入口和跳转目标的 HTTPS 合法域名。
当前 HTTP IP 地址供联调使用,不满足正式小程序的 HTTPS 域名要求。
如果部署环境或私有配置仍将 `TENCENT_COS_PUBLIC_BASE_URL`/`hera.cos.public-base-url`
设为旧的 `/file/` 地址,需要一并改为 `http://101.43.139.56/hera/file/`,否则会覆盖默认值。
更新配置并重启服务后,重新获取资料或天宫列表生成新链接;已有缓存链接不会自动改变。
### COS 验证
`mvn test` 执行本地签名、目录归属和模拟上传测试,不访问真实 COS。
`mvn test -Dhera.cos.integration=true` 额外启用真实 COS 联调:从运行配置读取密钥,
在 `hera/avatar/9223372036854775807/` 和 `hera/files/9223372036854775807/`
创建本次随机命名的测试对象,校验匿名访问为 403、签名下载内容一致,以及后端文件入口的跳转。
测试结束后删除本次创建的对象,因此联调账号还需要这两个测试目录的 `cos:DeleteObject` 权限。
此测试不会操作数据库,也不会验证远端 Nginx 是否已部署。
## 开发说明
- 当前已默认关闭微信 mock,并接入微信 `jscode2session`。
- 数据持久层使用 MyBatis Mapper XML,本地启动会执行 `schema.sql` 创建缺失的数据表,线上默认不执行。
- 本地数据库连接默认指向 `101.43.139.56:3306/hera`,线上默认内网 `10.0.8.9:3306/hera`,可通过环境参数覆盖。
- `WECHAT_APP_SECRET` 请优先使用服务器环境变量配置,不建议提交到公开仓库。
- `/api/auth/sms` 开发阶段返回 `mockCode`,接入短信厂商后移除返回值。
- 正式上线前需要替换 `hera.jwt-secret`、短信服务实现和协议正文。
## 数据表 CRUD
小程序用户业务已统一到 `user_account` 与 `user_profile`:微信身份、昵称和手机号
保存在账号表;性别、生日、出生时辰、头像、现居住地区与资料修改额度保存在资料表。
`User` 仅作为联合查询模型;业务接口不再读写 `hera_user`。
当前系统尚未上线,以当前数据库与 `schema.sql` 为准,不执行历史用户迁移、
启动时补列或修改旧表结构,也不读取或维护历史迁移版本表。
`schema.sql` 仅通过 `CREATE TABLE IF NOT EXISTS` 初始化缺失的当前业务表;
已有表结构若发生变化,应先更新代码与建表定义,不自动兼容历史结构。
纯微信账号的 `password_hash` 可空;资料修改周期为首次修改起六个月,最多五次。
历史迁移源码与对应测试已移除,但本次清理不会删除数据库中已有的表或记录。
`POST /api/auth/wx-login` 对已绑定手机号的用户额外返回 `token` 和 `user`;未绑定时,
继续通过 `sessionKey` 和 mock 短信验证码调用 `/api/auth/bind-phone` 完成注册或绑定。
绑定成功会核销会话与验证码。禁用账号不能登录或使用个人资料接口。
个人资料接口的字段名保持不变;资料尚未填写时,生日、时辰及五行报告可能为空,不填充虚构资料。
原 `/api/data/**` 通用 CRUD 控制器已撤下,避免普通登录用户改写设备归属、资产、
账号状态或他人资料。Mapper 与内部 CRUD Service 保留,不作为小程序 HTTP 接口。
账号注册和资料修改继续使用 `/api/auth/**` 与 `/api/user/profile`,尚未提供管理员接口。
## NFC 与虚拟物
全部接口需要 `Authorization: Bearer <token>`,用户身份取 JWT,不接收操作者用户 ID。
列表接受 `offset=0&limit=20`,单页最大 100。芯片和虚拟物字典由可信后台或数据库预先入库,
不允许小程序自行注册硬件、指定设备奖励或修改流水。
代码以现库结构为准:绑定流水通过 `nfc_uid` 关联设备,芯片不再使用 `last_login_at`。
`nfc_chip.sn` 为设备序列号,按现库定义使用 `VARCHAR(64) NOT NULL`,无默认值、无唯一索引。
硬件入库和更新时必须提供 SN;扫描、绑定及本人设备列表返回 `sn`,客户端不能通过绑定请求修改它。
设备归属和绑定历史仍以唯一 UID 关联,不将 SN 当作唯一标识或绑定凭证。
新库由 `schema.sql` 创建该字段;旧库若尚未新增,应使用真实序列号补录已有设备后再设置非空约束,
不自动生成序列号或用 UID 代填,本服务不在启动时自动修改已有表。
旧库如缺少芯片归属、解绑额度或绑定流水表,可先备份,再手工执行
`src/main/resources/db/nfc-schema-update.sql`;该脚本不由应用自动执行。新库由 `schema.sql` 建表。
若已有同名表但字段结构不同,部署前应核对,不会通过删表自动适配。
- `POST /api/nfc/scan`:`{"uid":"设备UID"}`,查看绑定状态,不返回他人身份或 NDEF。
- `POST /api/nfc/bind`:同上,绑定设备、写绑定流水、激活预置虚拟物,整体事务提交。
- `POST /api/nfc/unbind`:同上,解绑本人设备并写解绑时间。
- `GET /api/nfc/devices`:本人当前设备。
- `GET /api/nfc/bindings`:本人绑定与解绑历史。
- `GET /api/virtual-items`:启用的虚拟物字典,按字典排序。
- `GET /api/tiangong/items`:本人神仙持有状态、上香计数及展示图片。
- `GET /api/user/virtual-items`:本人资产,含失效与送神状态。
- `GET /api/user/virtual-items/{id}`:本人持有关系详情。
- `PUT /api/user/virtual-items/sort`:`{"ids":[持有关系ID,...]}`,按提交顺序设置排序值。
- `POST /api/user/virtual-items/offerings`:`{"userVirtualItemId":1,"offeringType":2,"prayerContent":"愿家人平安"}`。
- `POST /api/user/virtual-items/lifecycle`:`{"userVirtualItemId":1,"actionType":1}`,`1` 请神、`2` 送神。
- `GET /api/user/virtual-items/lifecycle?holdingId=1`:本人请神/送神流水,`holdingId` 可省略。
- `GET /api/user/virtual-items/offerings?holdingId=1`:本人供奉记录,`holdingId` 可省略。
- `POST /api/user/virtual-items/dedications`:`{"userVirtualItemId":1,"dedicationTarget":"家人","dedicationContent":"愿平安顺遂"}`。
- `GET /api/user/virtual-items/dedications`:本人发起的回向记录。
- `/api/user/virtual-items/transfers` 保留为上述回向接口的路径别名,使用相同请求和响应,不再接收接收账号 ID。
`GET /api/tiangong/items` 还会为每位神仙返回 `latestPrayer`(`prayerContent`)和
`latestDedication`(`dedicationTarget`、可空的 `dedicationContent`),供祈愿、回向表单回显。
仅匹配当前登录用户与该神仙字典 ID 的 `status=1` 且存在对应明细的记录;
两类分别按 `offered_at DESC, id DESC` 取最新一条,没有可用记录时对应字段为 `null`。
虚拟物字典与天宫列表均返回 `iconUrl`(列表图标)和 `iconDetailUrl`(神仙详情页大图),
两种图片独立配置,不互相覆盖。`virtual_item.icon_detail_url` 按现库使用
`VARCHAR(255) NOT NULL`、无默认值,后台入库和更新字典时必须提供,已有表不由启动流程自动改列。
私有桶图片与头像一致:数据库保存 `cos://hera/virtual-items/{itemId}/...` 持久标识,
上述两个业务接口实时返回有效期 900 秒的文件访问入口,不向数据库写入临时签名 URL。
已有普通 HTTP/HTTPS 图片地址保持原样。文件网关允许签名访问该专用目录,但不将它视为用户个人文件。
COS 身份需具有该目录下的 `PutObject`、`GetObject` 权限;不改变存储桶私有读权限。
`VirtualItemImageImportTest` 是显式授权才会执行的真实数据导入,默认跳过。
启用参数为 `-Dhera.virtual-item.image-import=true`,并且必须同时指定
`hera.import.item-id`、`hera.import.item-name`、`hera.import.icon-path`、`hera.import.detail-path`。
它校验目标名称、PNG/WebP 文件头与 5MB 大小限制,按 SHA-256 命名对象并验证签名下载内容,
先在 `target/` 备份旧图片值,再通过旧值条件同时更新两个图片字段;不修改其他字典项。
`-Dhera.import.inspect-only=true` 只列出 ID 与名称,不上传或写库;新增缺失字典项必须显式设置
`-Dhera.import.create-if-missing=true`,不允许将已有同名神仙用其他 ID 重复创建。
批量导入可通过 `-Dhera.import.manifest=/absolute/path/manifest.json` 指定 JSON 数组,
每项包含 `id`、`name`、`iconPath`、`detailPath`;批次内 ID 和名称不可重复。
WebP 应在上传前用支持该格式的工具完整解码,核对尺寸、透明通道与压缩质量,
导入器不会将不支持 WebP 的 JDK ImageIO 当作解码器;原始文件不应被压缩结果覆盖。
不要在普通构建、CI 或无人审核的自动化中启用此参数。
一个芯片仅允许一个当前用户,一个微信用户可以绑定多个芯片;要求有效账号已绑定微信和手机号。
重复绑定本人设备只刷新更新时间,不增加绑定次数,也不重复发放资产。
每台芯片按北京时间自然年最多解绑两次,次数跟随芯片,不因换用户而重置,跨年重新计算。
同类虚拟物在一个用户下只有一条资产记录,保留首次获得的 `nfc_uid` 和获得时间。
绑定新设备可恢复该类失效资产。解绑最后一台提供该类虚拟物的设备后,资产状态置为 `3`(失效),
不删除供奉和回向历史;仍有同类设备时保留资产。失效资产不能互动或直接请神,必须重新绑定。
供奉类型与最新表设计一致:`1` 上香、`2` 祈愿、`3` 回向。原来的 `1` 请神、`5` 送神
必须改为调用独立的 `/lifecycle` 接口,客户端需随本次后端适配同步升级,不能沿用旧供奉编码。
请神/送神写 `user_virtual_item_lifecycle`,其中 `action_type` 为 `1` 请神、`2` 送神。
绑定首次发放或恢复资产也写请神流水;重复绑定本人设备或添加同类设备不重复写请神流水。
送神置状态 `0`,保有对应设备时可通过请神恢复为 `1`。重复请神、重复送神会拒绝。
供奉主记录只保留用户、虚拟物、类型、状态和时间,明细分别写入
`user_virtual_item_offering_incense`、`user_virtual_item_offering_prayer`、
`user_virtual_item_offering_dedication`,主记录与明细在同一事务提交。
上香可传 `incenseCount`(3、6、9,默认3)、`incenseType`(可省略,1至127)、`remark`(最多255字)。
每次请求由服务端记录 `incenseTimes=1`;客户端不能通过该字段扩大次数。
祈愿必须填写 `prayerContent`(非空,最多500字,兼容旧字段名 `content`)。
通过供奉入口回向必须填写 `dedicationTarget`(非空,最多255字),`dedicationContent` 可为空、最多500字。
同一请求只允许填写所选类型的明细,`meritValue` 仍为禁用参数,填写即拒绝。
响应中按类型返回嵌套 `incense`、`prayer`、`dedication` 明细,其余两个为 `null`。
`holdingId` 历史筛选先校验归属,再按当前用户和虚拟物查询,不依赖主表已删除的持有关系字段。
天宫的 `incenseCount` 是本人该虚拟物有效上香主记录所关联明细的 `SUM(incense_times)`,
不按香支数或记录条数计算,无记录时返回0;祈愿、回向和无效主记录不参与累计。
回向只保存发起用户、虚拟物、对象文本和可选的回向文;不关联接收账号,不转移资产。
专用回向接口兼容 `name`/`content` 字段别名,返回统一的供奉主记录和 `dedication` 明细。
回向列表仅返回当前用户发起的记录,不通过对象文本推测“接收人”。
记录不开放通用修改和删除。UID 校验仅确认已入库设备,不具备防复制能力;
若产品需要防克隆,须增加硬件挑战响应或服务端签发的激活凭证,不能将普通 UID 当作秘密。
`NfcWorkflowTest` 在独立 H2 MySQL 模式测试库验证真实 Mapper XML、HTTP、事务与并发抢绑,
覆盖解绑上限、跨年、同类多设备、失效恢复、越权、供奉和只记账回向,不访问线上业务数据。
`mvn test -Dtest=NfcMySqlIntegrationTest -Dhera.nfc.mysql.integration=true` 显式检查现库列名,
通过只读、零行查询验证 XML 与现有供奉拆分表兼容,再在十张连接级临时表中验证读写与回滚。
新增请神/送神流水表在临时表中验证,不要求联调前向正式库建表。
临时表创建成功并全部覆盖后才允许测试写入;结束时回滚并关闭连接自动清理,
不修改正式表结构和业务记录。测试账号需有 `CREATE TEMPORARY TABLES` 权限。
### 供奉拆分后的部署
本次适配以已经存在的供奉主表及三张明细表为前提;部署新代码前,执行
`src/main/resources/db/virtual-item-lifecycle-update.sql` 创建独立的请神/送神流水表。
脚本可重复执行,不更新已有业务记录、不推测回填历史。新环境由 `schema.sql` 创建全套表。
旧的 `named-dedication-update.sql` 只适用于历史回向表,不能在当前已拆分的数据库上重放。
从更早的旧供奉结构升级时,需另行制定历史数据迁移,不能只重命名类型编码或通过删表重建处理。