Files
mp-java/src/main/java/com/gxwebsoft/hjc/service/HjcPasswordApplyService.java
T
weicw1996 a3c54249dc feat(hjc): 密码找回与修改密码(材料审核制 + 双因子)
密码找回(匿名接口,见 ADR 0008「密码重置由平台运维人工执行」)
- POST /api/hjc/auth/password/apply:企业名称 + 纳税人识别号 + 新密码 + 授权委托书。
  用双要素定位企业而不是只用企业名称——hjc_enterprise.name 没有唯一索引(hjc_init.sql 里只有
  user_id 唯一),只用名称会把 A 企业的密码重置落到 B 账号上;加上同样有索引的 credit_code 后
  歧义天然消失,命中多条时告警并拒绝。企业不存在与信息不匹配返回同一句话且 data 为 null,
  避免企业注册状态被枚举;同企业 24h 内二次提交返回已存在那条。
- GET /api/hjc/auth/password/apply/status:同样双要素查进度。
- 状态 0 待审核 / 1 已通过(待重置) / 2 已驳回 / 3 已重置。「已通过」≠「已重置」是刻意的:
  核心实例没有任何 hjc 可用的代改密码通道,最后由运维照单在核心实例后台手工重置,再回后台标记。
- 新表 hjc_password_apply:hjc_init.sql 加建表语句,另给幂等的 hjc_password_apply.sql 作发布步骤
  (只增表,不动任何既有表,对共用本库的其他项目无影响)。新密码明文存储供运维照单重置,
  是 ADR 0008 已显式接受的风险,列注释里写明。

修改密码(登录态,双因子:旧密码 + 账号绑定手机号的短信验证码)
- POST /api/hjc/auth/password/sms 与 PUT /api/hjc/auth/password/change。短信发到**账号绑定手机号**
  (后端从登录态取),前端不能指定号码。
- HjcCoreAuthClient 加两条调用,且两条的认证姿态刻意相反,各自的「为什么与类注释相反」都写在注释里:
  verifyOldPassword 必须转发买家 Authorization(核心实例 PUT /auth/password 按登录态识别改谁),
  resetPassword 必须剥掉 Authorization 且带 userId + tenantId——不传 userId 时核心实例会按手机号
  查出**所有租户**下的账号并逐个改密(其 SQL 无 tenant_id 条件),那会连带改掉同一手机号在其它
  平台的账号密码。

访问控制
- SecurityConfig 只把匿名的 apply 加进 permitAll(status 走既有 GET /** 放行);sms 与 change
  **必须**保持登录态,故刻意不入名单。

后台端点(配套 hjc-vue 的审核页)
- /api/hjc/password-apply 的 page / detail / audit / mark-reset 四个管理员端点。列表不带明文新密码,
  只有详情给(运维要照单重置)。

未验:端到端需真实环境(核心实例授权、审核人、运维重置),见 .scratch/hjc-password/issues/06。
2026-09-17 02:35:02 +08:00

55 lines
2.4 KiB
Java
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.
package com.gxwebsoft.hjc.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.hjc.entity.HjcEnterprise;
import com.gxwebsoft.hjc.entity.HjcPasswordApply;
import com.gxwebsoft.hjc.param.HjcPasswordApplyParam;
/**
* 汇吉采密码找回申请(材料审核制,见 ADR-0008)。
*/
public interface HjcPasswordApplyService extends IService<HjcPasswordApply> {
PageResult<HjcPasswordApply> pageRel(HjcPasswordApplyParam param);
/** 某企业最新一条申请(匿名链路用,显式传租户) */
HjcPasswordApply getLatestByEnterpriseId(Integer enterpriseId, Integer tenantId);
/** 某企业最新一条待审核申请;不为 null 即「重复提交」,直接返回它而不新建 */
HjcPasswordApply getPendingByEnterpriseId(Integer enterpriseId, Integer tenantId);
/** 写入一条待审核申请 */
HjcPasswordApply submit(HjcEnterprise enterprise, String newPassword, String handbookUrl, Integer tenantId);
/**
* 审核:仅允许 {@code 0 → 1}(通过,待运维重置)或 {@code 0 → 2}(驳回)。
*
* <p>用「带状态条件的更新」而不是「先查后改」,避免两个管理员同时点通过/驳回时后者覆盖前者。</p>
*
* @return 错误文案;成功返回 null
*/
String audit(Integer id, Integer status, String rejectReason, Integer auditUserId);
/**
* 标记已重置:仅允许 {@code 1 → 3}。
*
* <p><b>「审核通过」不等于「已重置」</b>:这一步表示运维已经在核心实例管理后台照申请单执行完毕。</p>
*
* @return 错误文案;成功返回 null
*/
String markReset(Integer id, Integer resetUserId);
/**
* 频次配额(Redis 原子自增,键名以 {@code hjc:pwd:} 开头,与核心实例的键区分)。
*
* <p>用 {@code StringRedisTemplate} 而不是项目的 {@code RedisUtil}:后者只包了 set/get/delete
* 没有原子自增,而"先读再写"的计数在并发下会漏放。</p>
*
* @param bucket 配额桶名,例如 {@code apply-ip}
* @param subject 配额主体,例如客户端 IP 或企业 ID
* @return true = 未超限(已占用一次配额)
*/
boolean tryAcquireQuota(String bucket, String subject, int limit, long ttlSeconds);
}