Files
hera/README.md
T

211 lines
15 KiB
Markdown
Raw 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.
# Hera 后端
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/file/access?token=...`,Nginx 保留路径和查询参数
转发至后端;参考 `deploy/nginx-file.conf.example`。后端校验文件专用短期凭证后,
302 跳转至 COS 官方 HTTPS 签名 URL。入口不要求登录头,必须携带有效文件凭证。
文件链接属于临时访问凭据,不应写入日志或分享给无权访问的人。
部署时通过 `HERA_JWT_SECRET` 配置随机签名密钥,并通过 `TENCENT_COS_PUBLIC_BASE_URL`
配置生产 HTTPS 域名下的 `/file/` 地址。小程序需配置入口和跳转目标的 HTTPS 合法域名。
当前 HTTP IP 地址供联调使用,不满足正式小程序的 HTTPS 域名要求。
### 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/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":3,"content":"祈愿"}`。
- `GET /api/user/virtual-items/offerings?holdingId=1`:本人供奉记录,`holdingId` 可省略。
- `POST /api/user/virtual-items/transfers`:`{"userVirtualItemId":1,"toUserId":2,"content":"祝福"}`。
- `GET /api/user/virtual-items/transfers`:本人发出或收到的回向记录。
一个芯片仅允许一个当前用户,一个微信用户可以绑定多个芯片;要求有效账号已绑定微信和手机号。
重复绑定本人设备只刷新更新时间,不增加绑定次数,也不重复发放资产。
每台芯片按北京时间自然年最多解绑两次,次数跟随芯片,不因换用户而重置,跨年重新计算。
同类虚拟物在一个用户下只有一条资产记录,保留首次获得的 `nfc_uid` 和获得时间。
绑定新设备可恢复该类失效资产。解绑最后一台提供该类虚拟物的设备后,资产状态置为 `3`(失效),
不删除供奉和回向历史;仍有同类设备时保留资产。失效资产不能互动或直接请神,必须重新绑定。
供奉类型 `1` 请神、`2` 上香、`3` 祈愿、`4` 功德、`5` 送神。绑定激活自动记录请神;
送神置状态 `0`,保有对应设备时可通过请神恢复为 `1`。重复请神、重复送神会拒绝。
只有功德请求可填写 `meritValue`,要求 1 至 1000000,仅作互动记录,不代表积分、支付或余额。
回向只写记录,不创建接收方资产、不扣除发起方资产,也不把发起方状态改为已回向。
昵称快照从账号表生成;接收方原本有同类资产时保存其关系 ID,没有则为 `null`。
记录不开放通用修改和删除。UID 校验仅确认已入库设备,不具备防复制能力;
若产品需要防克隆,须增加硬件挑战响应或服务端签发的激活凭证,不能将普通 UID 当作秘密。
`NfcWorkflowTest` 在独立 H2 MySQL 模式测试库验证真实 Mapper XML、HTTP、事务与并发抢绑,
覆盖解绑上限、跨年、同类多设备、失效恢复、越权、供奉和只记账回向,不访问线上业务数据。
`mvn test -Dtest=NfcMySqlIntegrationTest -Dhera.nfc.mysql.integration=true` 显式检查现库列名,
通过只读、零行查询验证 XML 与现表兼容,再在七张连接级临时表中验证读写与回滚。
临时表创建成功并全部覆盖后才允许测试写入;结束时回滚并关闭连接自动清理,
不修改正式表结构和业务记录。测试账号需有 `CREATE TEMPORARY TABLES` 权限。