package com.xly.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* xlyAi 侧身份与授权层(架构 §7)。
*
*
身份唯一来源 = {@link #resolveIdentity}:透传的用户 token 经 ERP {@code /ai/whoami}
* **服务端内省**换取真实的 userId/租户/用户类型——客户端自报的 userid/brandsid/usertype 一概不信。
* token 缺失或无效时:仅本地开发({@code erp.dev-login.enabled=true})回落 dev 身份;
* 生产一律返回 null,调用方必须拒绝(fail-closed,绝不降级成 dev/管理员)。
*
*
授权 = 表单级白名单:后端逐用户表单权限被关(只验登录 + 租户 + 行级),所以在 agent 侧按用户
* 实际授权(`sysjurisdiction`,即 `getsAuthsIdNew` 的逻辑)限制可碰的表单/菜单。
* 管理员(sysadmin/admin) = 全部权限(返回 null);Read / Write 共用此边界。
*/
@Service
public class AuthzService {
private final JdbcTemplate jdbc;
private final ErpClient erp;
@Value("${erp.dev-login.username:}")
private String devUserNo;
@Value("${erp.dev-login.brand:}")
private String devBrand;
@Value("${erp.dev-login.subsidiary:}")
private String devSub;
@Value("${erp.dev-login.usertype:sysadmin}")
private String devUserType;
@Value("${erp.dev-login.userid:}")
private String devUserIdOverride;
public AuthzService(JdbcTemplate jdbc, ErpClient erp) {
this.jdbc = jdbc;
this.erp = erp;
}
/**
* 服务端身份解析(所有端点的唯一入口)。token 有效 → 内省出的真实身份;
* token 无效/过期 → null(**不**回落 dev,防止过期用户被静默提权);
* token 缺失 → dev-login 开启时 dev 身份,否则 null。
*/
public com.xly.agent.AgentIdentity resolveIdentity(String authorization) {
String token = authorization == null ? "" : authorization.trim();
if (!token.isBlank()) {
com.fasterxml.jackson.databind.JsonNode w = erp.whoami(token);
if (w == null) {
return null;
}
String userId = w.path("sId").asText("");
String brandsId = w.path("sBrandsId").asText("");
String subsidiaryId = w.path("sSubsidiaryId").asText("");
String userType = w.path("sType").asText("");
Set granted = grantedIds(userId, userType, brandsId, subsidiaryId);
return new com.xly.agent.AgentIdentity(token, userId, brandsId, subsidiaryId, granted);
}
return erp.devLoginEnabled() ? devIdentity() : null;
}
/**
* dev-login(仅本地开发)身份:token 为空 → ErpClient 回退 dev-login;权限集按 dev 账号解析
* (admin → null = 全部)。生产不可达(resolveIdentity 已按开关拦截)。
*/
public com.xly.agent.AgentIdentity devIdentity() {
String uid = devUserIdOverride != null && !devUserIdOverride.isBlank() ? devUserIdOverride : resolveDevUserId();
Set granted = grantedIds(uid, devUserType, devBrand, devSub);
return new com.xly.agent.AgentIdentity(null, uid, devBrand, devSub, granted);
}
/** null = 全部(管理员);否则 = 有权的 id 集合。 */
private Set grantedIds(String userId, String userType, String brandsId, String subsidiaryId) {
if (isAdmin(userType)) {
return null; // 超管全部权限
}
Set ids = new HashSet<>();
if (userId == null) {
return ids;
}
List