package com.dms.service; import com.dms.common.DmsException; import com.dms.common.Errors; import com.dms.security.Passwords; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; /** * 角色密码分配解析与校验(契约1.53 密码即角色):创建/编辑用户与门户 * 设密三入口共用。请求形态 [{roleId, password}]——password 为 null/缺省 * 表示该角色使用用户级密码。硬约束(消除登录匹配歧义): * ①必须为一个角色选择使用用户密码(恰一个 null); * ②其余独立密码两两互异; * ③独立密码不等于用户级密码(创建/设密比对明文,编辑用旧哈希 verify)。 */ public final class RolePasswords { private RolePasswords() { } /** * 解析并校验角色密码分配。 * * @param raw 请求体 rolePasswords 数组 * @param targetRoleIds 本次挂载的角色ID全集(调用方保证 ≥2) * @param userPasswordPlaintext 用户级密码明文(创建/设密场景提供) * @param userPasswordHash 用户级密码哈希(编辑未改用户密码场景提供) * @return roleId → 独立密码明文;使用用户密码的角色映射值为 null */ public static Map parse(Object raw, Set targetRoleIds, String userPasswordPlaintext, String userPasswordHash) { if (!(raw instanceof List list)) { throw Errors.invalidArgument("rolePasswords必须是数组"); } Map result = new LinkedHashMap<>(); for (Object item : list) { if (!(item instanceof Map entry) || !(entry.get("roleId") instanceof String roleIdText)) { throw Errors.invalidArgument("rolePasswords每项须为含roleId的对象"); } long roleId; try { roleId = Long.parseLong(roleIdText); } catch (NumberFormatException ex) { throw Errors.invalidArgument("roleId必须是数字字符串"); } if (!targetRoleIds.contains(roleId)) { throw Errors.invalidArgument("roleId必须指向本次挂载的角色"); } if (result.containsKey(roleId)) { throw Errors.invalidArgument("rolePasswords存在重复角色"); } Object password = entry.get("password"); if (password instanceof String text && !text.isEmpty()) { try { Passwords.validatePolicy(text); } catch (IllegalArgumentException ex) { throw Errors.invalidArgument(ex.getMessage()); } result.put(roleId, text); } else { result.put(roleId, null); } } if (!result.keySet().equals(targetRoleIds)) { throw Errors.invalidArgument("rolePasswords必须覆盖全部挂载角色"); } long nullCount = result.values().stream().filter(Objects::isNull).count(); if (nullCount != 1) { throw Errors.invalidArgument("必须为一个角色选择使用用户密码"); } List independents = new ArrayList<>(); for (String password : result.values()) { if (password == null) { continue; } if ((userPasswordPlaintext != null && password.equals(userPasswordPlaintext)) || (userPasswordHash != null && Passwords.verify(userPasswordHash, password))) { throw conflict(); } for (String prev : independents) { if (prev.equals(password)) { throw conflict(); } } independents.add(password); } return result; } /** 互异约束冲突:登录按密码匹配角色,同密码会产生进入歧义。 */ public static DmsException conflict() { return new DmsException("PASSWORD_CONFLICT", 400, "各角色密码不能相同,且不能与用户密码相同", null); } }