AuthzService.java 6.49 KB
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)。
 *
 * <p>后端逐用户表单权限被关(只验登录 + 租户 + 行级),所以在 agent 侧按用户实际授权把可碰的
 * 表单/菜单限制住。数据源与后端一致(`sysjurisdiction`,即 `getsAuthsIdNew` 的逻辑)——不新造。
 *
 * <p>管理员(sysadmin/admin) = 全部权限(返回 null);否则 = 该用户有权的 id 集合(菜单/表单/按钮,
 * 由 `sysjurisdiction.sKey` 按 '-' 拆出)。当前本地 dev-login 以 admin 身份运行,因此实际全通;
 * 生产透传用户 token 时即按各用户真实授权收紧。Read / Query / Write 共用此边界。
 */
@Service
public class AuthzService {

    private final JdbcTemplate jdbc;

    @Value("${erp.dev-login.username:admin}")
    private String devUserNo;
    @Value("${erp.dev-login.brand:1111111111}")
    private String devBrand;
    @Value("${erp.dev-login.subsidiary:1111111111}")
    private String devSub;
    @Value("${erp.dev-login.usertype:sysadmin}")
    private String devUserType;

    @Value("${erp.dev-login.userid:}")
    private String devUserIdOverride;

    public AuthzService(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }

    /** 当前有效用户(本地=dev-login)能否访问该菜单/表单 id。 */
    public boolean canAccessModule(String moduleId) {
        Set<String> granted = grantedIds(resolveDevUserId(), devUserType, devBrand, devSub);
        return isAllowed(granted, moduleId);
    }

    /**
     * 构造 dev-login(本地开发)身份:token 为空 → ErpClient 回退 dev-login;权限集按 dev 账号解析
     * (admin → null = 全部)。
     */
    public com.xly.agent.AgentIdentity devIdentity() {
        String uid = devUserIdOverride != null && !devUserIdOverride.isBlank() ? devUserIdOverride : resolveDevUserId();
        Set<String> granted = grantedIds(uid, devUserType, devBrand, devSub);
        return new com.xly.agent.AgentIdentity(null, uid, devUserNo, devBrand, devSub, devUserType, granted);
    }

    /**
     * 构造透传的真实用户身份:token = 用户浏览器里的 ERP 登录 token(转发给 ERP),权限集按该用户
     * 真实授权({@code sAuthsId})解析。用于生产环境按各用户真实权限收紧。
     */
    public com.xly.agent.AgentIdentity userIdentity(String token, String userId, String userNo,
                                                    String brandsId, String subsidiaryId, String userType) {
        Set<String> granted = grantedIds(userId, userType, brandsId, subsidiaryId);
        return new com.xly.agent.AgentIdentity(token, userId, userNo, brandsId, subsidiaryId, userType, granted);
    }

    /** null = 全部(管理员);否则 = 有权的 id 集合。 */
    public Set<String> grantedIds(String userId, String userType, String brandsId, String subsidiaryId) {
        if (isAdmin(userType)) {
            return null; // 超管全部权限
        }
        Set<String> ids = new HashSet<>();
        if (userId == null) {
            return ids;
        }
        List<Map<String, Object>> rows;
        Integer direct = safeCount(
                "SELECT COUNT(*) FROM sysjurisdiction WHERE sBrandsId=? AND sSubsidiaryId=? AND sUserId=?",
                brandsId, subsidiaryId, userId);
        if (direct != null && direct > 0) {
            rows = jdbc.queryForList(
                    "SELECT J.sKey AS sKey FROM sysjurisdiction J WHERE J.sBrandsId=? AND J.sSubsidiaryId=? AND J.sUserId=?",
                    brandsId, subsidiaryId, userId);
        } else {
            String groups = queryStr(
                    "SELECT GROUP_CONCAT(sJurisdictionClassifyId) FROM sftlogininfojurisdictiongroup " +
                            "WHERE sBrandsId=? AND sSubsidiaryId=? AND sParentId=? " +
                            "AND sJurisdictionClassifyId IN (SELECT sId FROM sisjurisdictionclassify WHERE bLogininfoShow=0)",
                    brandsId, subsidiaryId, userId);
            if (groups == null || groups.isBlank()) {
                return ids;
            }
            List<String> gl = Arrays.asList(groups.split(","));
            String in = gl.stream().map(g -> "?").collect(Collectors.joining(","));
            List<Object> args = new ArrayList<>();
            args.add(brandsId);
            args.add(subsidiaryId);
            args.addAll(gl);
            rows = jdbc.queryForList(
                    "SELECT J.sKey AS sKey FROM sysjurisdiction J WHERE J.sBrandsId=? AND J.sSubsidiaryId=? " +
                            "AND J.sJurisdictionClassifyId IN (" + in + ")", args.toArray());
        }
        for (Map<String, Object> r : rows) {
            Object k = r.get("sKey");
            if (k != null) {
                for (String part : k.toString().split("-")) {
                    if (!part.isBlank()) {
                        ids.add(part);
                    }
                }
            }
        }
        return ids;
    }

    public boolean isAllowed(Set<String> granted, String... ids) {
        if (granted == null) {
            return true; // 管理员
        }
        for (String id : ids) {
            if (id != null && granted.contains(id)) {
                return true;
            }
        }
        return false;
    }

    private String resolveDevUserId() {
        return queryStr("SELECT sId FROM gdslogininfo WHERE sUserNo=? AND sBrandsId=? LIMIT 1", devUserNo, devBrand);
    }

    private boolean isAdmin(String userType) {
        return userType != null && (userType.equalsIgnoreCase("sysadmin") || userType.equalsIgnoreCase("admin"));
    }

    private Integer safeCount(String sql, Object... args) {
        try {
            return jdbc.queryForObject(sql, Integer.class, args);
        } catch (Exception e) {
            return 0;
        }
    }

    private String queryStr(String sql, Object... args) {
        try {
            List<Map<String, Object>> r = jdbc.queryForList(sql, args);
            if (!r.isEmpty()) {
                Object v = r.get(0).values().iterator().next();
                return v == null ? null : v.toString();
            }
        } catch (Exception ignore) {
        }
        return null;
    }
}