a3c54249dc
密码找回(匿名接口,见 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。
55 lines
2.4 KiB
Java
55 lines
2.4 KiB
Java
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);
|
||
}
|