干燥机配套车间生产管理系统/云平台服务端
zhuguifei
2025-11-14 bf718dd6b7a5bafbbec6feaf507470f84d30531c
新增功能

1.sso登录
2.部门同步
3.用户同步
已添加3个文件
已修改12个文件
1727 ■■■■■ 文件已修改
jeecg-boot-base-core/pom.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-boot-base-core/src/main/java/org/jeecg/common/sign/SM2Utils.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-boot-base-core/src/main/java/org/jeecg/common/sign/Sm2Lib.java 882 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DepartmentSortUtil.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/cas/controller/CasClientController.java 271 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/cas/util/CasServiceUtil.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartController.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysUser.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/ISysDepartService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/ISysUserService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysDepartServiceImpl.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java 147 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jeecg-boot-base-core/pom.xml
@@ -258,7 +258,17 @@
      <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
      <version>1.2.5</version>
    </dependency>
        <!--  Bouncy Castle åР坆 -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>1.78</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15to18</artifactId>
            <version>1.78</version>
        </dependency>
  </dependencies>
</project>
jeecg-boot-base-core/src/main/java/org/jeecg/common/sign/SM2Utils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
package org.jeecg.common.sign;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
 *  å›½å¯†2 åŠ ç­¾/验签, åŠ å¯†è§£å¯†æŽ¥å£
 */
public class SM2Utils {
    //单例模式
    private static SM2Utils instance;
    public static SM2Utils getInstance() {
        if (instance == null) {
            instance = new SM2Utils();
        }
        return instance;
    }
    /**
     * åŠ ç­¾ï¼š åŠ å¯†ç­¾å (使用 ç­¾åç§é’¥)
     * @param raw å¾…加签的数据
     * @param privateKey ç­¾åç§é’¥
     * @return åŠ ç­¾åŽçš„æ•°æ®, hex
     */
    public String sign(String raw, String privateKey) {
        try {
            return Sm2Lib.sign(raw, privateKey);
        }catch (Exception e){
            log("Sm2Util.sign Exception:"+e.getMessage());
            return null;
        }
    }
    /**
     * éªŒç­¾ï¼š ç­¾åæ ¡éªŒ (使用 å…¬é’¥)
     * @param raw  æ˜Žæ–‡æ•°æ®
     * @param sign å¯†æ–‡æ•°æ®ï¼Œhex æ ¼å¼
     * @param publicKey éªŒç­¾å…¬é’¥
     * @return éªŒç­¾ç»“æžœ true/false
     */
    public boolean verify(String raw, String sign, String publicKey) {
        try {
            return Sm2Lib.verify(raw, publicKey, sign);
        } catch (Exception e) {
            log("Sm2Util.verify Exception:"+e.getMessage());
        }
        return false;
    }
    /**
     * ç”Ÿæˆç§˜é’¥å¯¹(工具函数)
     * @return 0 å…¬é’¥ï¼Œ 1私钥
     */
    public String[] genKey() {
        return Sm2Lib.genKey();
    }
    /**
     * æ•°æ®åР坆(使用 å…¬é’¥)
     * @param data å¾…加密数据
     * @param publicKey æ•°æ®åР坆公钥
     * @return åŠ å¯†åŽçš„å¯†æ–‡æ•°æ®
     */
    public String encrypt(String data, String publicKey) {
        if(StringUtils.isEmpty(data)|| "null".equals(data)){
            return null;
        }
        try{
            return  Sm2Lib.encrypt(data, publicKey);
        }catch(Exception e){
            log("Sm2Util.encrypt Exception: "+ e);
            e.printStackTrace();
            return null;
        }
    }
    /**
     * æ•°æ®è§£å¯† (默认  ç§é’¥)
     * @param data å¯†æ–‡æ•°æ®
     * @param privateKey æ•°æ®è§£å¯†ç§é’¥
     * @return è§£å¯†åŽçš„æ˜Žæ–‡æ•°æ®
     */
    public String decrypt(String data, String privateKey)  {
        try {
            return Sm2Lib.decrypt(data, privateKey);
        }catch(Exception e){
            log("Sm2Util.decrypt Exception: "+ e);
            return null;
        }
    }
    public static String getSignContent(Map<String, String> sortedParams) {
        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<String>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = sortedParams.get(key);
            if (areNotEmpty(key, value)) {
                content.append(index == 0 ? "" : "&").append(key).append("=").append(value);
                index++;
            }
        }
        return content.toString();
    }
    private static boolean areNotEmpty(String... values) {
        boolean result = true;
        if (values == null || values.length == 0) {
            result = false;
        } else {
            for (String value : values) {
                result &= !StringUtils.isEmpty(value);
            }
        }
        return result;
    }
    private void log(String msg){
        System.out.println(msg);
    }
    public static void main(String[] args) {
        String[] strings = getInstance().genKey();
        for (String string : strings) {
            System.out.println(string);
        }
    }
}
jeecg-boot-base-core/src/main/java/org/jeecg/common/sign/Sm2Lib.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,882 @@
package org.jeecg.common.sign;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X962Parameters;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9ECPoint;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.pqc.legacy.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import javax.crypto.Cipher;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.util.*;
import java.util.regex.Pattern;
//SM2 åŸºç¡€åº“
public class Sm2Lib
{
    //SM2算法默认用户ID,目前开放平台不会使用非默认用户ID
    public static String DEFAULT_USER_ID = "1234567812345678";
    public static String DEFAULT_CHARSET = "utf8";
    public static final String SM2_ECC_GY_VAL = "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0";
    public static final String SM2_ECC_GX_VAL = "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7";
    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    public final static BigInteger SM2_ECC_N = CURVE.getOrder();
    public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
    public final static BigInteger SM2_ECC_GX = new BigInteger(SM2_ECC_GX_VAL, 16);
    public final static BigInteger SM2_ECC_GY = new BigInteger(SM2_ECC_GY_VAL, 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
    public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);
    public final static BigInteger SM2_ECC_P = CURVE.getQ();
    public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
    public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
    //public static final int CURVE_LEN = BCECUtils.getCurveLength(DOMAIN_PARAMS);
    public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
    public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
            G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
    public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
            JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());
    private static BouncyCastleProvider provider;
    static {
        provider = new BouncyCastleProvider();
        Security.addProvider(provider);
    }
    public static String decrypt(String cipherTextBase64, String privateKey) throws Exception {
        return decrypt(cipherTextBase64, privateKey, DEFAULT_CHARSET);
    }
    public static String decrypt(String cipherTextBase64, String privateKey, String charset) throws Exception {
        byte[] cipher = Base64.getDecoder().decode(cipherTextBase64);
        boolean isMatch = Pattern.matches("^[0-9a-f]+$", privateKey.toLowerCase());
        if (isMatch) {
            ECPrivateKeyParameters ecPrivateKeyParameters = BCECUtils.createECPrivateKeyParameters(privateKey, DOMAIN_PARAMS);
            byte[] buf = decrypt(ecPrivateKeyParameters, cipher);
            //将解密后的明文按指定字符集编码后返回
            try {
                String strContent = new String(buf, charset);
                return strContent;
            } catch (UnsupportedEncodingException e) {
                throw new Exception(e);
            }
        } else {
            byte[] privateKeyByte = Base64.getDecoder().decode(privateKey);
            // è§£æžX509格式SM2私钥
            PrivateKey sm2PrivateKey = parsePKCS8PrivateKey(privateKeyByte);
            // ä½¿ç”¨SM2私钥解密
            byte[] buf = sm2Decrypt(cipher, sm2PrivateKey);
            //将解密后的明文按指定字符集编码后返回
            try {
                String strContent = new String(buf, charset);
                return strContent;
            } catch (UnsupportedEncodingException e) {
                throw new Exception(e);
            }
        }
    }
    public static String encrypt(String plainText, String publicKey) throws Exception {
        return encrypt(plainText, publicKey, DEFAULT_CHARSET);
    }
    public static String encrypt(String plainText, String publicKey, String charset) throws Exception {
        byte[] plain = plainText.getBytes(charset);
        boolean isMatch = Pattern.matches("^[0-9a-f]+$", publicKey.toLowerCase());
        if (isMatch) {
            ECPoint point = CURVE.decodePoint(ByteUtils.fromHexString(publicKey));
            ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(point, DOMAIN_PARAMS);
            byte[] cipher = encrypt(ecPublicKeyParameters, plain);
            //将密文Base64编码后返回
            String strContent = Base64.getEncoder().encodeToString(cipher);
            return strContent;
        } else {
            // è§£æžPKCS8格式SM2私钥
            byte[] publicKeyByte = Base64.getDecoder().decode(publicKey);
            PublicKey sm2PublicKey = parseX509PublicKey(publicKeyByte);
            byte[] cipher = sm2Encrypt(plain, sm2PublicKey);
            //将密文Base64编码后返回
            String strContent = Base64.getEncoder().encodeToString(cipher);
            return strContent;
        }
    }
    public static String sign(String content, String privateKey) throws Exception {
        return sign(content, privateKey, null, DEFAULT_CHARSET);
    }
    public static String sign(String content, String privateKey, String userId, String charset) throws Exception {
        if (StringUtils.isEmpty(userId)) {
            userId = DEFAULT_USER_ID;
        }
        byte[] message = content.getBytes(charset);
        boolean isMatch = Pattern.matches("^[0-9a-f]+$", privateKey.toLowerCase());
        if (isMatch) {
            ECPrivateKeyParameters ecPrivateKeyParameters = BCECUtils.createECPrivateKeyParameters(privateKey, DOMAIN_PARAMS);
            byte[] signature = sign(ecPrivateKeyParameters, Strings.toByteArray(userId), message);
            try {
                String sign = Base64.getEncoder().encodeToString(signature);
                return sign;
            } catch (Exception e) {
                throw new Exception(e);
            }
        } else {
            byte[] privateKeyByte = Base64.getDecoder().decode(privateKey);
            PrivateKey sm2PrivateKey = parsePKCS8PrivateKey(privateKeyByte);
            byte[] signature = sm2Sign(message, sm2PrivateKey, userId);
            String sign = Base64.getEncoder().encodeToString(signature);
            return sign;
        }
    }
    public static boolean verify(String content, String publicKey, String sign) throws Exception {
        return verify(content, publicKey, sign, null, DEFAULT_CHARSET);
    }
    public static boolean verify(String content, String publicKey, String sign, String userId, String charset) throws Exception {
        if (StringUtils.isEmpty(userId)) {
            userId = DEFAULT_USER_ID;
        }
        byte[] message = content.getBytes(charset);
        byte[] signature = Base64.getDecoder().decode(sign);
        boolean isMatch = Pattern.matches("^[0-9a-f]+$", publicKey.toLowerCase());
        if (isMatch) {
            ECPoint point = CURVE.decodePoint(ByteUtils.fromHexString(publicKey));
            ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(point, DOMAIN_PARAMS);
            boolean valid = verify(ecPublicKeyParameters, Strings.toByteArray(userId), message, signature);
            return valid;
        } else {
            byte[] publicKeyByte = Base64.getDecoder().decode(publicKey);
            PublicKey sm2PublicKey = parseX509PublicKey(publicKeyByte);
            boolean valid = sm2Verify(signature, message, sm2PublicKey, userId);
            return valid;
        }
    }
    private static byte[] sm2Encrypt(byte[] plain, PublicKey sm2PublicKey) throws Exception {
        try {
            Cipher sm2CipherEngine = Cipher.getInstance("SM2", "BC");
            sm2CipherEngine.init(Cipher.ENCRYPT_MODE, sm2PublicKey);
            return sm2CipherEngine.doFinal(plain);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }
    private static byte[] sm2Decrypt(byte[] cipher, PrivateKey sm2PrivateKey) throws Exception {
        try {
            Cipher sm2CipherEngine = Cipher.getInstance("SM2", "BC");
            sm2CipherEngine.init(Cipher.DECRYPT_MODE, sm2PrivateKey);
            return sm2CipherEngine.doFinal(cipher);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }
    private static byte[] sm2Sign(byte[] message, PrivateKey sm2PrivateKey, String sm2UserId) throws Exception {
        try {
            Signature sm2SignEngine = Signature.getInstance("SM3withSM2");
            sm2SignEngine.setParameter(new SM2ParameterSpec(
                    Strings.toByteArray(sm2UserId)));
            sm2SignEngine.initSign(sm2PrivateKey);
            sm2SignEngine.update(message);
            return sm2SignEngine.sign();
        } catch (Exception e) {
            throw new Exception(e);
        }
    }
    private static boolean sm2Verify(byte[] signature, byte[] message, PublicKey publicKey, String sm2UserId) {
        try {
            Signature sm2SignEngine = Signature.getInstance("SM3withSM2");
            sm2SignEngine.setParameter(new SM2ParameterSpec(Strings.toByteArray(sm2UserId)));
            sm2SignEngine.initVerify(publicKey);
            sm2SignEngine.update(message);
            return sm2SignEngine.verify(signature);
        } catch (Exception e) {
            return false;
        }
    }
    private static PublicKey parseX509PublicKey(byte[] x509PublicKey) throws Exception {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(x509PublicKey);
            return keyFactory.generatePublic(keySpec);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }
    private static PrivateKey parsePKCS8PrivateKey(byte[] pkcs8PriateKey) throws Exception {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8PriateKey);
            return keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }
    public static String getSignContent(Map<String, String> sortedParams) {
        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = sortedParams.get(key);
            if (areNotEmpty(key, value)) {
                content.append((index == 0 ? "" : "&") + key + "=" + value);
                index++;
            }
        }
        return content.toString();
    }
    private static boolean areNotEmpty(String... values) {
        boolean result = true;
        if (values == null || values.length == 0) {
            result = false;
        } else {
            for (String value : values) {
                result &= !isEmpty(value);
            }
        }
        return result;
    }
    private static boolean isEmpty(String value) {
        int strLen;
        if (value == null || (strLen = value.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if ((Character.isWhitespace(value.charAt(i)) == false)) {
                return false;
            }
        }
        return true;
    }
    /**
     * @param pubKeyParams å…¬é’¥
     * @param srcData      åŽŸæ–‡
     * @return é»˜è®¤è¾“出C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    private static byte[] encrypt(final ECPublicKeyParameters pubKeyParams, final byte[] srcData) throws InvalidCipherTextException {
        return encrypt(SM2Engine.Mode.C1C3C2, pubKeyParams, srcData);
    }
    /**
     * @param priKeyParams ç§é’¥
     * @param sm2Cipher    é»˜è®¤è¾“å…¥C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return åŽŸæ–‡ã€‚SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    private static byte[] decrypt(final ECPrivateKeyParameters priKeyParams, final byte[] sm2Cipher)
            throws InvalidCipherTextException {
        return decrypt(SM2Engine.Mode.C1C3C2, priKeyParams, sm2Cipher);
    }
    /**
     * @param mode         æŒ‡å®šå¯†æ–‡ç»“构,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param pubKeyParams å…¬é’¥
     * @param srcData      åŽŸæ–‡
     * @return æ ¹æ®mode不同,输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    private static byte[] encrypt(final SM2Engine.Mode mode, final ECPublicKeyParameters pubKeyParams, final byte[] srcData)
            throws InvalidCipherTextException {
        final SM2Engine engine = new SM2Engine(mode);
        final ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParams, new SecureRandom());
        engine.init(true, pwr);
        return engine.processBlock(srcData, 0, srcData.length);
    }
    /**
     * @param mode         æŒ‡å®šå¯†æ–‡ç»“构,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param priKeyParams ç§é’¥
     * @param sm2Cipher    æ ¹æ®mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return åŽŸæ–‡ã€‚SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    private static byte[] decrypt(final SM2Engine.Mode mode, final ECPrivateKeyParameters priKeyParams, final byte[] sm2Cipher)
            throws InvalidCipherTextException {
        final SM2Engine engine = new SM2Engine(mode);
        engine.init(false, priKeyParams);
        return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
    }
    /**
     * ç­¾å
     *
     * @param priKeyParams ç§é’¥
     * @param withId       å¯ä»¥ä¸ºnull,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
     * @param srcData      æºæ•°æ®
     * @return DER编码后的签名值
     * @throws CryptoException
     */
    private static byte[] sign(final ECPrivateKeyParameters priKeyParams, final byte[] withId, final byte[] srcData)
            throws CryptoException {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = null;
        ParametersWithRandom pwr = new ParametersWithRandom(priKeyParams, new SecureRandom());
        if (withId != null) {
            param = new ParametersWithID(pwr, withId);
        } else {
            param = pwr;
        }
        signer.init(true, param);
        signer.update(srcData, 0, srcData.length);
        return signer.generateSignature();
    }
    /**
     * éªŒç­¾
     *
     * @param pubKeyParams å…¬é’¥
     * @param withId       å¯ä»¥ä¸ºnull,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
     * @param srcData      åŽŸæ–‡
     * @param sign         DER编码的签名值
     * @return éªŒç­¾æˆåŠŸè¿”å›žtrue,失败返回false
     */
    private static boolean verify(final ECPublicKeyParameters pubKeyParams, final byte[] withId, final byte[] srcData,
                                  final byte[] sign) {
        final SM2Signer signer = new SM2Signer();
        CipherParameters param;
        if (withId != null) {
            param = new ParametersWithID(pubKeyParams, withId);
        } else {
            param = pubKeyParams;
        }
        signer.init(false, param);
        signer.update(srcData, 0, srcData.length);
        return signer.verifySignature(sign);
    }
    /**
     * ç”Ÿæˆç§˜é’¥å¯¹(工具函数)
     * @return 0 å…¬é’¥ï¼Œ 1私钥
     */
    public static String[] genKey()
    {
        AsymmetricCipherKeyPair kPair = BCECUtils.generateKeyPairParameter(DOMAIN_PARAMS, new SecureRandom());
        ECPrivateKeyParameters ecPriv = (ECPrivateKeyParameters)kPair.getPrivate();
        ECPublicKeyParameters ecPub = (ECPublicKeyParameters)kPair.getPublic();
        BigInteger privateKey = ecPriv.getD();
        ECPoint publicKey = ecPub.getQ();
        byte[] priv = privateKey.toByteArray();
        byte[] pub = publicKey.getEncoded(false);
        if (priv.length == 33)
        {
            byte[] newPriv = new byte[32];
            System.arraycopy(priv, 1, newPriv, 0, 32);
            priv = newPriv;
        }
        String[] keyPairs = new String[2];
        keyPairs[0] = ByteUtils.toHexString(pub);
        keyPairs[1] = ByteUtils.toHexString(priv);
        return keyPairs;
    }
}
/**
 * è¿™ä¸ªå·¥å…·ç±»çš„æ–¹æ³•,也适用于其他基于BC库的ECC算法
 */
class BCECUtils {
    private static final String ALGO_NAME_EC = "EC";
    private static final String PEM_STRING_PUBLIC = "PUBLIC KEY";
    private static final String PEM_STRING_ECPRIVATEKEY = "EC PRIVATE KEY";
    /**
     * ç”ŸæˆECC密钥对
     *
     * @return ECC密钥对
     */
    public static AsymmetricCipherKeyPair generateKeyPairParameter(
            ECDomainParameters domainParameters, SecureRandom random) {
        ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters,
                random);
        ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
        keyGen.init(keyGenerationParams);
        return keyGen.generateKeyPair();
    }
    public static KeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random)
            throws NoSuchProviderException, NoSuchAlgorithmException,
            InvalidAlgorithmParameterException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        ECParameterSpec parameterSpec = new ECParameterSpec(domainParameters.getCurve(), domainParameters.getG(),
                domainParameters.getN(), domainParameters.getH());
        kpg.initialize(parameterSpec, random);
        return kpg.generateKeyPair();
    }
    public static int getCurveLength(ECKeyParameters ecKey) {
        return getCurveLength(ecKey.getParameters());
    }
    public static int getCurveLength(ECDomainParameters domainParams) {
        return (domainParams.getCurve().getFieldSize() + 7) / 8;
    }
    public static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) {
        if (src.length == curveLength) {
            return src;
        }
        byte[] result = new byte[curveLength];
        if (src.length > curveLength) {
            System.arraycopy(src, src.length - result.length, result, 0, result.length);
        } else {
            System.arraycopy(src, 0, result, result.length - src.length, src.length);
        }
        return result;
    }
    /**
     * @param dHex             åå…­è¿›åˆ¶å­—符串形式的私钥d值,如果是SM2算法,Hex字符串长度应该是64(即32字节)
     * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPrivateKeyParameters createECPrivateKeyParameters(
            String dHex, ECDomainParameters domainParameters) {
        return createECPrivateKeyParameters(ByteUtils.fromHexString(dHex), domainParameters);
    }
    /**
     * @param dBytes           å­—节数组形式的私钥d值,如果是SM2算法,应该是32字节
     * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPrivateKeyParameters createECPrivateKeyParameters(
            byte[] dBytes, ECDomainParameters domainParameters) {
        return createECPrivateKeyParameters(new BigInteger(1, dBytes), domainParameters);
    }
    /**
     * @param d                å¤§æ•°å½¢å¼çš„私钥d值
     * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPrivateKeyParameters createECPrivateKeyParameters(
            BigInteger d, ECDomainParameters domainParameters) {
        return new ECPrivateKeyParameters(d, domainParameters);
    }
    /**
     * æ ¹æ®EC私钥构造EC公钥
     *
     * @param priKey ECC私钥参数对象
     * @return
     */
    public static ECPublicKeyParameters buildECPublicKeyByPrivateKey(ECPrivateKeyParameters priKey) {
        ECDomainParameters domainParameters = priKey.getParameters();
        ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), priKey.getD());
        return new ECPublicKeyParameters(q, domainParameters);
    }
    /**
     * @param x                å¤§æ•°å½¢å¼çš„公钥x分量
     * @param y                å¤§æ•°å½¢å¼çš„公钥y分量
     * @param curve            EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link Sm2Lib#CURVE}
     * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            BigInteger x, BigInteger y, ECCurve curve, ECDomainParameters domainParameters) {
        return createECPublicKeyParameters(x.toByteArray(), y.toByteArray(), curve, domainParameters);
    }
    /**
     * @param xHex             åå…­è¿›åˆ¶å½¢å¼çš„公钥x分量,如果是SM2算法,Hex字符串长度应该是64(即32字节)
     * @param yHex             åå…­è¿›åˆ¶å½¢å¼çš„公钥y分量,如果是SM2算法,Hex字符串长度应该是64(即32字节)
     * @param curve            EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link Sm2Lib#CURVE}
     * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
        return createECPublicKeyParameters(ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex),
                curve, domainParameters);
    }
    /**
     * @param xBytes           åå…­è¿›åˆ¶å½¢å¼çš„公钥x分量,如果是SM2算法,应该是32字节
     * @param yBytes           åå…­è¿›åˆ¶å½¢å¼çš„公钥y分量,如果是SM2算法,应该是32字节
     * @param curve            EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link Sm2Lib#CURVE}
     * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) {
        final byte uncompressedFlag = 0x04;
        int curveLength = getCurveLength(domainParameters);
        xBytes = fixToCurveLengthBytes(curveLength, xBytes);
        yBytes = fixToCurveLengthBytes(curveLength, yBytes);
        byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
        encodedPubKey[0] = uncompressedFlag;
        System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
        System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
        return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
    }
    public static ECPrivateKeyParameters convertPrivateKeyToParameters(BCECPrivateKey ecPriKey) {
        ECParameterSpec parameterSpec = ecPriKey.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
                parameterSpec.getN(), parameterSpec.getH());
        return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters);
    }
    public static ECPublicKeyParameters convertPublicKeyToParameters(BCECPublicKey ecPubKey) {
        ECParameterSpec parameterSpec = ecPubKey.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
                parameterSpec.getN(), parameterSpec.getH());
        return new ECPublicKeyParameters(ecPubKey.getQ(), domainParameters);
    }
    public static BCECPublicKey createPublicKeyFromSubjectPublicKeyInfo(SubjectPublicKeyInfo subPubInfo)
            throws NoSuchProviderException,
            NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        return BCECUtils.convertX509ToECPublicKey(subPubInfo.toASN1Primitive().getEncoded(ASN1Encoding.DER));
    }
    /**
     * å°†ECC私钥转换为PKCS8标准的字节流
     *
     * @param priKey
     * @param pubKey å¯ä»¥ä¸ºç©ºï¼Œä½†æ˜¯å¦‚果为空的话得到的结果OpenSSL可能解析不了
     * @return
     */
    public static byte[] convertECPrivateKeyToPKCS8(
            ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
        ECDomainParameters domainParams = priKey.getParameters();
        ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
                domainParams.getN(), domainParams.getH());
        BCECPublicKey publicKey = null;
        if (pubKey != null) {
            publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
                    BouncyCastleProvider.CONFIGURATION);
        }
        BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey,
                spec, BouncyCastleProvider.CONFIGURATION);
        return privateKey.getEncoded();
    }
    /**
     * å°†PKCS8标准的私钥字节流转换为私钥对象
     *
     * @param pkcs8Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     */
    public static BCECPrivateKey convertPKCS8ToECPrivateKey(byte[] pkcs8Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
        PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(pkcs8Key);
        KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPrivateKey) kf.generatePrivate(peks);
    }
    /**
     * å°†PKCS8标准的私钥字节流转换为PEM
     *
     * @param encodedKey
     * @return
     * @throws IOException
     */
    public static String convertECPrivateKeyPKCS8ToPEM(byte[] encodedKey) throws IOException {
        return convertEncodedDataToPEM(PEM_STRING_ECPRIVATEKEY, encodedKey);
    }
    /**
     * å°†PEM格式的私钥转换为PKCS8标准字节流
     *
     * @param pemString
     * @return
     * @throws IOException
     */
    public static byte[] convertECPrivateKeyPEMToPKCS8(String pemString) throws IOException {
        return convertPEMToEncodedData(pemString);
    }
    /**
     * å°†ECC私钥转换为SEC1标准的字节流
     * openssl d2i_ECPrivateKey函数要求的DER编码的私钥也是SEC1标准的,
     * è¿™ä¸ªå·¥å…·å‡½æ•°çš„主要目的就是为了能生成一个openssl可以直接“识别”的ECC私钥.
     * ç›¸å¯¹RSA私钥的PKCS1标准,ECC私钥的标准为SEC1
     *
     * @param priKey
     * @param pubKey
     * @return
     * @throws IOException
     */
    public static byte[] convertECPrivateKeyToSEC1(
            ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) throws IOException {
        byte[] pkcs8Bytes = convertECPrivateKeyToPKCS8(priKey, pubKey);
        PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes);
        ASN1Encodable encodable = pki.parsePrivateKey();
        ASN1Primitive primitive = encodable.toASN1Primitive();
        byte[] sec1Bytes = primitive.getEncoded();
        return sec1Bytes;
    }
    /**
     * å°†SEC1标准的私钥字节流恢复为PKCS8标准的字节流
     *
     * @param sec1Key
     * @return
     * @throws IOException
     */
    public static byte[] convertECPrivateKeySEC1ToPKCS8(byte[] sec1Key) throws IOException {
        /**
         * å‚考org.bouncycastle.asn1.pkcs.PrivateKeyInfo和
         * org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey,逆向拼装
         */
        X962Parameters params = getDomainParametersFromName(Sm2Lib.JDK_EC_SPEC, false);
        ASN1OctetString privKey = new DEROctetString(sec1Key);
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(0)); //版本号
        v.add(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params)); //算法标识
        v.add(privKey);
        DERSequence ds = new DERSequence(v);
        return ds.getEncoded(ASN1Encoding.DER);
    }
    /**
     * å°†SEC1标准的私钥字节流转为BCECPrivateKey对象
     *
     * @param sec1Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws IOException
     */
    public static BCECPrivateKey convertSEC1ToBCECPrivateKey(byte[] sec1Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(convertECPrivateKeySEC1ToPKCS8(sec1Key));
        KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPrivateKey) kf.generatePrivate(peks);
    }
    /**
     * å°†SEC1标准的私钥字节流转为ECPrivateKeyParameters对象
     * openssl i2d_ECPrivateKey函数生成的DER编码的ecc私钥是:SEC1标准的、带有EC_GROUP、带有公钥的,
     * è¿™ä¸ªå·¥å…·å‡½æ•°çš„主要目的就是为了使Java程序能够“识别”openssl生成的ECC私钥
     *
     * @param sec1Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     */
    public static ECPrivateKeyParameters convertSEC1ToECPrivateKey(byte[] sec1Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        BCECPrivateKey privateKey = convertSEC1ToBCECPrivateKey(sec1Key);
        return convertPrivateKeyToParameters(privateKey);
    }
    /**
     * å°†ECC公钥对象转换为X509标准的字节流
     *
     * @param pubKey
     * @return
     */
    public static byte[] convertECPublicKeyToX509(ECPublicKeyParameters pubKey) {
        ECDomainParameters domainParams = pubKey.getParameters();
        ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
                domainParams.getN(), domainParams.getH());
        BCECPublicKey publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
                BouncyCastleProvider.CONFIGURATION);
        return publicKey.getEncoded();
    }
    /**
     * å°†X509标准的公钥字节流转为公钥对象
     *
     * @param x509Bytes
     * @return
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static BCECPublicKey convertX509ToECPublicKey(byte[] x509Bytes) throws NoSuchProviderException,
            NoSuchAlgorithmException, InvalidKeySpecException {
        X509EncodedKeySpec eks = new X509EncodedKeySpec(x509Bytes);
        KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPublicKey) kf.generatePublic(eks);
    }
    /**
     * å°†X509标准的公钥字节流转为PEM
     *
     * @param encodedKey
     * @return
     * @throws IOException
     */
    public static String convertECPublicKeyX509ToPEM(byte[] encodedKey) throws IOException {
        return convertEncodedDataToPEM(PEM_STRING_PUBLIC, encodedKey);
    }
    /**
     * å°†PEM格式的公钥转为X509标准的字节流
     *
     * @param pemString
     * @return
     * @throws IOException
     */
    public static byte[] convertECPublicKeyPEMToX509(String pemString) throws IOException {
        return convertPEMToEncodedData(pemString);
    }
    /**
     * copy from BC
     *
     * @param genSpec
     * @return
     */
    public static X9ECParameters getDomainParametersFromGenSpec(ECGenParameterSpec genSpec) {
        return getDomainParametersFromName(genSpec.getName());
    }
    /**
     * copy from BC
     *
     * @param curveName
     * @return
     */
    public static X9ECParameters getDomainParametersFromName(String curveName) {
        X9ECParameters domainParameters;
        try {
            if (curveName.charAt(0) >= '0' && curveName.charAt(0) <= '2') {
                ASN1ObjectIdentifier oidID = new ASN1ObjectIdentifier(curveName);
                domainParameters = ECUtil.getNamedCurveByOid(oidID);
            } else {
                if (curveName.indexOf(' ') > 0) {
                    curveName = curveName.substring(curveName.indexOf(' ') + 1);
                    domainParameters = ECUtil.getNamedCurveByName(curveName);
                } else {
                    domainParameters = ECUtil.getNamedCurveByName(curveName);
                }
            }
        } catch (IllegalArgumentException ex) {
            domainParameters = ECUtil.getNamedCurveByName(curveName);
        }
        return domainParameters;
    }
    /**
     * copy from BC
     *
     * @param ecSpec
     * @param withCompression
     * @return
     */
    public static X962Parameters getDomainParametersFromName(
            java.security.spec.ECParameterSpec ecSpec, boolean withCompression) {
        X962Parameters params;
        if (ecSpec instanceof ECNamedCurveSpec) {
            ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec) ecSpec).getName());
            if (curveOid == null) {
                curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec) ecSpec).getName());
            }
            params = new X962Parameters(curveOid);
        } else if (ecSpec == null) {
            params = new X962Parameters(DERNull.INSTANCE);
        } else {
            ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
            X9ECParameters ecP = new X9ECParameters(
                    curve,
                    new X9ECPoint(EC5Util.convertPoint(curve, ecSpec.getGenerator()), withCompression),
                    ecSpec.getOrder(),
                    BigInteger.valueOf(ecSpec.getCofactor()),
                    ecSpec.getCurve().getSeed());
            //// å¦‚果是1.62或更低版本的bcprov-jdk15on应该使用以下这段代码,因为高版本的EC5Util.convertPoint没有向下兼容
            /*
            X9ECParameters ecP = new X9ECParameters(
                curve,
                EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
                ecSpec.getOrder(),
                BigInteger.valueOf(ecSpec.getCofactor()),
                ecSpec.getCurve().getSeed());
            */
            params = new X962Parameters(ecP);
        }
        return params;
    }
    private static String convertEncodedDataToPEM(String type, byte[] encodedData) throws IOException {
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
        try {
            PemObject pemObj = new PemObject(type, encodedData);
            pWrt.writeObject(pemObj);
        } finally {
            pWrt.close();
        }
        return new String(bOut.toByteArray());
    }
    private static byte[] convertPEMToEncodedData(String pemString) throws IOException {
        ByteArrayInputStream bIn = new ByteArrayInputStream(pemString.getBytes());
        PemReader pRdr = new PemReader(new InputStreamReader(bIn));
        try {
            PemObject pemObject = pRdr.readPemObject();
            return pemObject.getContent();
        } finally {
            pRdr.close();
        }
    }
}
jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java
@@ -36,6 +36,10 @@
    @SensitiveField
    private String username;
    @SensitiveField
    private String ssouserid;
    /**
     * ç™»å½•人名字
     */
jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DepartmentSortUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
package org.jeecg.common.util;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.*;
/**
 * éƒ¨é—¨æŽ’序
 * desc: æŽ¥å£èŽ·å–çš„éƒ¨é—¨åˆ—è¡¨éœ€è¦æŠŠçˆ¶èŠ‚ç‚¹æŽ’åœ¨å‰é¢ï¼Œå¦åˆ™å­èŠ‚ç‚¹æ— æ³•æ’å…¥
 */
public class DepartmentSortUtil {
    /**
     * ç®€å•排序方法 - æŒ‰ç…§æ‚¨çš„要求处理JSON数据
     * @param data JSONArray数据
     * @return æŽ’序后的JSONArray
     */
    public static JSONArray sortDepartments(JSONArray data) {
        if (data == null || data.isEmpty()) {
            return new JSONArray();
        }
        // 1. åˆ†ç¦»æ ¹èŠ‚ç‚¹ï¼ˆæ²¡æœ‰parentId的)和子节点
        List<JSONObject> rootNodes = new ArrayList<>();
        List<JSONObject> childNodes = new ArrayList<>();
        for (int i = 0; i < data.size(); i++) {
            JSONObject item = data.getJSONObject(i);
            if(item.getString("name").endsWith("公司")){
                item.put("parentId",null);
            }
            if (item.getString("parentId") == null) {
                rootNodes.add(item);
            } else {
                childNodes.add(item);
            }
        }
        // 2. æ ¹èŠ‚ç‚¹æŒ‰orderNum排序
        rootNodes.sort((a, b) -> {
            int orderA = a.getInteger("orderNum") != null ? a.getInteger("orderNum") : Integer.MAX_VALUE;
            int orderB = b.getInteger("orderNum") != null ? b.getInteger("orderNum") : Integer.MAX_VALUE;
            return Integer.compare(orderA, orderB);
        });
        // 3. å­èŠ‚ç‚¹æŒ‰parentId分组,然后每组按orderNum排序
        Map<String, List<JSONObject>> childrenByParent = new HashMap<>();
        for (JSONObject child : childNodes) {
            String parentId = child.getString("parentId");
            childrenByParent.computeIfAbsent(parentId, k -> new ArrayList<>()).add(child);
        }
        // å¯¹æ¯ä¸ªçˆ¶èŠ‚ç‚¹ä¸‹çš„å­èŠ‚ç‚¹æŽ’åº
        for (List<JSONObject> children : childrenByParent.values()) {
            children.sort((a, b) -> {
                int orderA = a.getInteger("orderNum") != null ? a.getInteger("orderNum") : Integer.MAX_VALUE;
                int orderB = b.getInteger("orderNum") != null ? b.getInteger("orderNum") : Integer.MAX_VALUE;
                return Integer.compare(orderA, orderB);
            });
        }
        // 4. æž„建最终结果:根节点 + æŒ‰parentId顺序的子节点
        JSONArray result = new JSONArray();
        // å…ˆæ·»åŠ æ‰€æœ‰æ ¹èŠ‚ç‚¹
        result.addAll(rootNodes);
        // ç„¶åŽæŒ‰æ ¹èŠ‚ç‚¹çš„é¡ºåºæ·»åŠ å„è‡ªçš„å­èŠ‚ç‚¹
        for (JSONObject root : rootNodes) {
            String rootId = root.getString("id");
            List<JSONObject> children = childrenByParent.get(rootId);
            if (children != null) {
                result.addAll(children);
            }
        }
        // 5. å¤„理可能存在的其他子节点(parentId对应的根节点不在当前数据中)
        Set<String> processedParents = new HashSet<>();
        for (JSONObject root : rootNodes) {
            processedParents.add(root.getString("id"));
        }
        for (String parentId : childrenByParent.keySet()) {
            if (!processedParents.contains(parentId)) {
                List<JSONObject> children = childrenByParent.get(parentId);
                result.addAll(children);
            }
        }
        return result;
    }
}
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
@@ -77,6 +77,7 @@
        }
        // é…ç½®ä¸ä¼šè¢«æ‹¦æˆªçš„链接 é¡ºåºåˆ¤æ–­
        filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录
        filterChainDefinitionMap.put("/sys/cas/client/validateLogout", "anon"); //cas注销
        filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
        filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除
        filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/cas/controller/CasClientController.java
@@ -1,21 +1,38 @@
package org.jeecg.modules.cas.controller;
import java.util.LinkedHashMap;
import java.util.List;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CacheConstant;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.sign.SM2Utils;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.Md5Util;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.base.service.BaseCommonService;
import org.jeecg.modules.cas.util.CasServiceUtil;
import org.jeecg.modules.cas.util.XmlUtils;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysDepartService;
import org.jeecg.modules.system.service.ISysDictService;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.system.service.impl.SysBaseApiImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
@@ -45,67 +62,239 @@
    private ISysUserService sysUserService;
    @Autowired
    private ISysDepartService sysDepartService;
    @Resource
    private BaseCommonService baseCommonService;
    @Autowired
    private SysBaseApiImpl sysBaseApi;
    @Autowired
    private RedisUtil redisUtil;
    
    @Value("${cas.prefixUrl}")
    private String prefixUrl;
    @GetMapping("/validateLogin")
    @Value("${cas.systemCode}")
    private String systemCode;
    @Value("${cas.DATA_PUBLIC_KEY}")
    private String DATA_PUBLIC_KEY;
    @Value("${cas.DATA_PRIVATE_KEY}")
    private String DATA_PRIVATE_KEY;
    @Value("${cas.SIGN_PUBLIC_KEY}")
    private String SIGN_PUBLIC_KEY;
    @Value("${cas.SIGN_PRIVATE_KEY}")
    private String SIGN_PRIVATE_KEY;
    @Value("${cas.SSO_KEY}")
    private String SSO_KEY;
    @Autowired
    private ISysDictService sysDictService;
    @GetMapping("/validateLogin")
    public Object validateLogin(@RequestParam(name="ticket") String ticket,
                                @RequestParam(name="service") String service,
                                HttpServletRequest request,
                                HttpServletResponse response) throws Exception {
        Result<JSONObject> result = new Result<JSONObject>();
        log.info("Rest api login.");
        log.info("sso login.");
        try {
            String validateUrl = prefixUrl+"/p3/serviceValidate";
            String res = CasServiceUtil.getStValidate(validateUrl, ticket, service);
            log.info("res."+res);
            final String error = XmlUtils.getTextForElement(res, "authenticationFailure");
            if(StringUtils.isNotEmpty(error)) {
                throw new Exception(error);
            }
            final String principal = XmlUtils.getTextForElement(res, "user");
            if (StringUtils.isEmpty(principal)) {
                throw new Exception("No principal was found in the response from the CAS server.");
            }
            String timestamp = String.valueOf(System.currentTimeMillis());
            String validateUrl = prefixUrl+"/sso/checkTicket?";
            String params ="client="+systemCode + "&nonce="+RandomStringUtils.randomAlphabetic(6) +
                    "&ssoLogoutCall=http://localhost/" +"&ticket="+ ticket +"&timestamp="+timestamp;
            String raw = params + "&key="+SSO_KEY;
            String sign = Md5Util.md5Encode(raw,"utf-8");
            validateUrl += params +"&sign="+sign;
            String res = CasServiceUtil.getStValidate(validateUrl, ticket, service);
             if(StringUtils.isEmpty(res)){
                 return  Result.error("登录失败,sso返回异常");
             }
            JSONObject jsonObject = JSON.parseObject(res);
            //æ­¤ ticket æŒ‡å‘çš„ loginId
            String userId = jsonObject.getString("data");
            if(!jsonObject.containsKey("code") || jsonObject.getInteger("code") != 200 || StringUtils.isEmpty(userId)){
                 return  Result.error("登录失败,sso返回异常");
             }
            //此账号在 sso-server ç«¯çš„会话剩余有效期(单位:s)
           Integer remainSessionTimeout =  jsonObject.getInteger("remainSessionTimeout");
             // èŽ·å–ç™»å½•ç”¨æˆ·ä¿¡æ¯
            String userRan = RandomStringUtils.randomAlphabetic(6);
            String userStamp = String.valueOf(System.currentTimeMillis());
            String userRaw = "apiType=userinfo"+"&client="+systemCode +"&loginId="+userId  +
                    "&nonce="+userRan +"&timestamp="+userStamp + "&key="+SSO_KEY;
            String userSign = Md5Util.md5Encode(userRaw,"utf-8");
            String userUrl = prefixUrl+"/sso/getData?";
            String userParams ="apiType=userinfo"+"&loginId="+userId +"&timestamp="+userStamp +
                    "&nonce="+userRan+ "&sign=" + userSign  + "&client="+systemCode;
            userUrl += userParams ;
            String loginRes = CasServiceUtil.getRemote(userUrl);
            JSONObject userRes = JSON.parseObject(loginRes);
            JSONObject userData = userRes.getJSONObject("data").getJSONObject("user");
            if(!userRes.containsKey("code") || userRes.getInteger("code") != 200 || userData.isEmpty()){
                return  Result.error("登录失败,sso用户信息返回异常");
            }
//            final String error = XmlUtils.getTextForElement(res, "authenticationFailure");
//            if(StringUtils.isNotEmpty(error)) {
//                throw new Exception(error);
//            }
//            final String principal = XmlUtils.getTextForElement(res, "user");
//            if (StringUtils.isEmpty(principal)) {
//                throw new Exception("No principal was found in the response from the CAS server.");
//            }
            String principal = userData.getString("workNo");
            log.info("-------token----username---"+principal);
            //1. æ ¡éªŒç”¨æˆ·æ˜¯å¦æœ‰æ•ˆ
              SysUser sysUser = sysUserService.getUserByName(principal);
            sysUser.setSsouserid(userId);
              result = sysUserService.checkUserIsEffective(sysUser);
              if(!result.isSuccess()) {
                  // TODO  sso é€€å‡º
                  return result;
              }
             String token = JwtUtil.sign(sysUser.getUsername(), sysUser.getPassword());
             // è®¾ç½®è¶…æ—¶æ—¶é—´
             redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
             redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME*2 / 1000);
            userInfo(sysUser,result,remainSessionTimeout);
             //获取用户部门信息
            JSONObject obj = new JSONObject();
            List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
            obj.put("departs", departs);
            if (departs == null || departs.size() == 0) {
                obj.put("multi_depart", 0);
            } else if (departs.size() == 1) {
                sysUserService.updateUserDepart(principal, departs.get(0).getOrgCode(),null);
                obj.put("multi_depart", 1);
            } else {
                obj.put("multi_depart", 2);
            }
            obj.put("token", token);
            obj.put("userInfo", sysUser);
            result.setResult(obj);
            result.success("登录成功");
        } catch (Exception e) {
            //e.printStackTrace();
            LoginUser loginUser = new LoginUser();
            BeanUtils.copyProperties(sysUser, loginUser);
            baseCommonService.addLog("用户名: " + sysUser.getRealname() + ",sso登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
            // åŒæ­¥æ›´æ–°ç”¨æˆ·ç¼“存,确保ShiroRealm读取到带有sso用户ID的LoginUser
            redisUtil.set(String.format("%s::%s", CacheConstant.SYS_USERS_CACHE, loginUser.getUsername()), loginUser);
            // ä½¿ç”¨JWT再次触发Shiro登录,使当前请求的Subject持有包含ssouserid的Principal
            String token = result.getResult().getString("token");
            try {
                SecurityUtils.getSubject().login(new org.jeecg.config.shiro.JwtToken(token));
                LoginUser u = (LoginUser) SecurityUtils.getSubject().getPrincipal();
                System.err.println(u);
                log.info("Shiro JWT登录成功");
            } catch (AuthenticationException e) {
                log.error("Shiro认证失败: {}", e.getMessage());
                return Result.error("Shiro认证失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            result.error500(e.getMessage());
            ssoLogOut();
        }
        return new HttpEntity<>(result);
    }
    /**
     * ç”¨æˆ·ä¿¡æ¯
     *
     * @param sysUser
     * @param result
     * @return
     */
    private Result<JSONObject> userInfo(SysUser sysUser, Result<JSONObject> result,int timeout) {
        String username = sysUser.getUsername();
        String syspassword = sysUser.getPassword();
        // èŽ·å–ç”¨æˆ·éƒ¨é—¨ä¿¡æ¯
        JSONObject obj = new JSONObject(new LinkedHashMap<>());
        //1.生成token
        String token = JwtUtil.sign(username, syspassword);
        // è®¾ç½®token缓存有效时间
        redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
        redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, timeout);
        obj.put("token", token);
        //2.设置登录租户
        Result<JSONObject> loginTenantError = sysUserService.setLoginTenant(sysUser, obj, username,result);
        if (loginTenantError != null) {
            return loginTenantError;
        }
        //3.设置登录用户信息
        obj.put("userInfo", sysUser);
        //4.设置登录部门
        List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
        obj.put("departs", departs);
        if (departs == null || departs.size() == 0) {
            obj.put("multi_depart", 0);
        } else if (departs.size() == 1) {
            sysUserService.updateUserDepart(username, departs.get(0).getOrgCode(),null);
            obj.put("multi_depart", 1);
        } else {
            //查询当前是否有登录部门
            // update-begin--Author:wangshuai Date:20200805 for:如果用戶为选择部门,数据库为存在上一次登录部门,则取一条存进去
            SysUser sysUserById = sysUserService.getById(sysUser.getId());
            if(oConvertUtils.isEmpty(sysUserById.getOrgCode())){
                sysUserService.updateUserDepart(username, departs.get(0).getOrgCode(),null);
            }
            // update-end--Author:wangshuai Date:20200805 for:如果用戶为选择部门,数据库为存在上一次登录部门,则取一条存进去
            obj.put("multi_depart", 2);
        }
        obj.put("sysAllDictItems", sysDictService.queryAllDictItems());
        //SecurityUtils.getSubject().login(new org.jeecg.config.shiro.JwtToken(token));
        result.setResult(obj);
        result.success("登录成功");
        return result;
    }
    @GetMapping("/validateLogout")
    public Result<Object> logout(HttpServletRequest request,HttpServletResponse response) {
        String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
        if(oConvertUtils.isEmpty(token)) {
            return Result.error("退出登录失败!");
        }
        try {
            SecurityUtils.getSubject().login(new org.jeecg.config.shiro.JwtToken(token));
        } catch (AuthenticationException e) {
        }
         JSONObject jsonObject =  ssoLogOut();
        if(!jsonObject.containsKey("code") || jsonObject.getInteger("code") != 200 ){
            return  Result.error("退出登录失败,sso注销异常");
        }
        //本系统退出逻辑
        //用户退出逻辑
        if(oConvertUtils.isEmpty(token)) {
            return Result.error("退出登录失败!");
        }
        String username = JwtUtil.getUsername(token);
        LoginUser sysUser = sysBaseApi.getUserByName(username);
        if(sysUser!=null) {
            //update-begin--Author:wangshuai  Date:20200714  for:登出日志没有记录人员
            baseCommonService.addLog("用户名: "+sysUser.getRealname()+",退出成功!", CommonConstant.LOG_TYPE_1, null,sysUser);
            //update-end--Author:wangshuai  Date:20200714  for:登出日志没有记录人员
            log.info(" ç”¨æˆ·å:  "+sysUser.getRealname()+",退出成功! ");
            //清空用户登录Token缓存
            redisUtil.del(CommonConstant.PREFIX_USER_TOKEN + token);
            //清空用户登录Shiro权限缓存
            redisUtil.del(CommonConstant.PREFIX_USER_SHIRO_CACHE + sysUser.getId());
            //清空用户的缓存信息(包括部门信息),例如sys:cache:user::<username>
            redisUtil.del(String.format("%s::%s", CacheConstant.SYS_USERS_CACHE, sysUser.getUsername()));
            //调用shiro的logout
            SecurityUtils.getSubject().logout();
            return Result.ok("退出登录成功!");
        }else {
            return Result.error("Token无效!");
        }    }
    private JSONObject ssoLogOut(){
        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        //sso退出逻辑
        String userId = loginUser.getSsouserid();
        String outRan = RandomStringUtils.randomAlphabetic(6);
        String outStamp = String.valueOf(System.currentTimeMillis());
        String outRaw ="client="+systemCode + "&loginId="+userId + "&nonce="+outRan + "&timestamp="+outStamp +  "&key="+SSO_KEY;
        String outSign = Md5Util.md5Encode(outRaw,"utf-8");
        String outUrl = prefixUrl+"/sso/signout?";
        String outParams ="loginId="+userId +"&timestamp="+outStamp +
                "&nonce="+outRan+ "&sign=" + outSign  + "&client="+systemCode;
        outUrl += outParams ;
        String outRes = CasServiceUtil.getRemote(outUrl);
        return  JSON.parseObject(outRes);
    }
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/cas/util/CasServiceUtil.java
@@ -11,8 +11,10 @@
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
@@ -37,7 +39,6 @@
     */
    public static String getStValidate(String url, String st, String service){
        try {
            url = url+"?service="+service+"&ticket="+st;
            CloseableHttpClient httpclient = createHttpClientWithNoSsl();
            HttpGet httpget = new HttpGet(url);
            HttpResponse response = httpclient.execute(httpget);
@@ -49,7 +50,44 @@
        return "";
    }
    /**
     * ä¸‰æ–¹get请求
     * @param url
     * @return
     */
    public static String getRemote(String url){
        try {
            CloseableHttpClient httpclient = createHttpClientWithNoSsl();
            HttpGet httpget = new HttpGet(url);
            HttpResponse response = httpclient.execute(httpget);
            String res = readResponse(response);
            return res == null ? null : (res == "" ? null : res);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
    /**
     * ä¸‰æ–¹post请求
     * @param url
     * @return
     */
    public static String postRemote(String url,String sign){
        try {
            CloseableHttpClient httpclient = createHttpClientWithNoSsl();
            HttpPost httpost = new HttpPost(url);
            httpost.setHeader("sign", sign);
            HttpResponse response = httpclient.execute(httpost);
            String res = readResponse(response);
            return res == null ? null : (res == "" ? null : res);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
    /**
     * è¯»å– response body å†…容为字符串
     *
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartController.java
@@ -193,7 +193,19 @@
        return result;
    }
    /**
    @RequestMapping(value = "/syncTenantDept", method = RequestMethod.GET)
    public Result<?> syncTenantDept(){
        try {
            return sysDepartService.syncTenantDept();
        }catch (Exception e){
            e.printStackTrace();
        }
        return Result.error("操作失败,请联系管理员!");
    }
    /**
     * ç¼–辑数据 ç¼–辑部门的部分数据,并保存到数据库
     * 
     * @param sysDepart
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserController.java
@@ -135,8 +135,7 @@
    @RequestMapping(value = "/syncTenantUser", method = RequestMethod.GET)
    public Result<?> syncTenantUser(){
       try {
           String message = sysUserService.syncTenantUser();
           return Result.ok(message);
           return sysUserService.syncTenantUser();
       }catch (Exception e){
           e.printStackTrace();
       }
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysUser.java
@@ -209,4 +209,10 @@
     * æµç¨‹çŠ¶æ€
     */
    private String bpmStatus;
    /**
     * sso
     */
    @TableField(exist = false)
    private String ssouserid;
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/ISysDepartService.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.model.DepartIdModel;
import org.jeecg.modules.system.model.SysDepartTreeModel;
@@ -34,6 +35,12 @@
     */
    List<SysDepartTreeModel> queryTreeList();
    /**
     * åŒæ­¥ç§Ÿæˆ·éƒ¨é—¨
     * @return
     */
    Result<Object> syncTenantDept();
    /**
     * æŸ¥è¯¢æ‰€æœ‰éƒ¨é—¨ä¿¡æ¯,并分节点进行显示
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/ISysUserService.java
@@ -390,5 +390,9 @@
     */
    void editTenantUser(SysUser sysUser, String tenantId, String departs, String roles);
    String syncTenantUser();
    /**
     * åŒæ­¥ç§Ÿæˆ·ç”¨æˆ·
     * @return
     */
    Result<Object> syncTenantUser();
}
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysDepartServiceImpl.java
@@ -1,5 +1,6 @@
package org.jeecg.modules.system.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -11,15 +12,19 @@
import io.netty.util.internal.StringUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.FillRuleConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.sign.SM2Utils;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.DepartmentSortUtil;
import org.jeecg.common.util.FillRuleUtil;
import org.jeecg.common.util.YouBianCodeUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.jeecg.modules.cas.util.CasServiceUtil;
import org.jeecg.modules.system.entity.*;
import org.jeecg.modules.system.mapper.*;
import org.jeecg.modules.system.model.DepartIdModel;
@@ -27,7 +32,9 @@
import org.jeecg.modules.system.service.ISysDepartService;
import org.jeecg.modules.system.util.FindsDepartsChildrenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -60,6 +67,18 @@
    private SysUserMapper sysUserMapper;
    @Autowired
    private SysDepartMapper departMapper;
    @Lazy
    @Autowired
    private ISysDepartService departService;
    @Value("${cas.prefixUrl}")
    private String prefixUrl;
    @Value("${cas.systemCode}")
    private String systemCode;
    @Value("${cas.SIGN_PRIVATE_KEY}")
    private String SIGN_PRIVATE_KEY;
    @Override
    public List<SysDepartTreeModel> queryMyDeptTreeList(String departIds) {
@@ -118,7 +137,85 @@
        return listResult;
    }
    /**
    @Override
    public Result<Object> syncTenantDept() {
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        int tenantId = oConvertUtils.getInt(TenantContext.getTenant(), 0);
        if(tenantId == 0){
            return  Result.error("请登录租户管理员后同步部门!");
        }
        //组织机构
        String orgStamp = String.valueOf(System.currentTimeMillis());
        String orgRaw =     "systemCode="+ systemCode +  "&timestamp="+ orgStamp;
        String orgSign =   SM2Utils.getInstance().sign(orgRaw,SIGN_PRIVATE_KEY);
        String orgUrl = prefixUrl+"/api/org/list?";
        String orgParams = "systemCode="+ systemCode +"&timestamp="+ orgStamp;
        orgUrl += orgParams;
        String orgRes = CasServiceUtil.postRemote(orgUrl,orgSign);
        if(orgRes == null){
            return  Result.error("同步接口未返回数据!");
        }
        JSONObject jsonObject = JSON.parseObject(orgRes);
        JSONArray data = jsonObject.getJSONArray("data");
        if(jsonObject.getInteger("code") != 200 || data.isEmpty()){
            return  Result.error("同步接口返回空数据!");
        }
        LambdaQueryWrapper<SysDepart> queryWrapper  = new LambdaQueryWrapper<SysDepart>();
        queryWrapper.eq(SysDepart::getTenantId, tenantId);
        List<SysDepart> existDeptList = departService.list(queryWrapper);
        Map<String, SysDepart> deptMap = existDeptList.stream()
                .collect(Collectors.toMap(SysDepart::getId, dept -> dept));
        JSONArray jsonArray = DepartmentSortUtil.sortDepartments(data);
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject obj = jsonArray.getJSONObject(i);
            SysDepart depart = new SysDepart();
            String name = obj.getString("name");
            String parentId = obj.getString("parentId");
            if(!StringUtils.isEmpty(name) && name.endsWith("公司")){
                depart.setOrgCategory("1");
                depart.setParentId(null);
            }else {
                depart.setOrgCategory("2");
                depart.setParentId(parentId);
            }
            depart.setDepartName(name);
            depart.setId(obj.getString("id"));
            JSONObject formData = new JSONObject();
            formData.put("parentId",parentId);
            String[] codeArray = (String[]) FillRuleUtil.executeRule(FillRuleConstant.DEPART,formData);
            //有parentId但数据库找不到parenId的子节点无法插入
            if(codeArray == null){
                continue;
            }
            //update-end--Author:baihailong  Date:20191209 for:部门编码规则生成器做成公用配置
            depart.setOrgCode(codeArray[0]);
            depart.setCreateBy(sysUser.getUsername());
            String orgType = codeArray[1];
            depart.setOrgType(String.valueOf(orgType));
            depart.setCreateTime(new Date());
            depart.setDelFlag(CommonConstant.DEL_FLAG_0.toString());
            depart.setDepartOrder(obj.getInteger("orderNum"));
            depart.setTenantId(tenantId);
            depart.setIzLeaf(1);
            if(!deptMap.containsKey(depart.getId())){
                departMapper.insert(depart);
                if (StringUtils.isNotBlank(parentId)) {
                    //将父部门的设成不是叶子结点
                    departMapper.setMainLeaf(depart.getParentId(),CommonConstant.NOT_LEAF);
                }
            }
        }
        return Result.ok("操作成功!");
    }
    /**
     * queryTreeList æ ¹æ®éƒ¨é—¨id查询,前端回显调用
     */
    @Override
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java
@@ -24,11 +24,13 @@
import org.jeecg.common.constant.enums.RoleIndexConfigEnum;
import org.jeecg.common.desensitization.annotation.SensitiveEncode;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.sign.SM2Utils;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.system.vo.SysUserCacheInfo;
import org.jeecg.common.util.*;
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.jeecg.modules.base.service.BaseCommonService;
import org.jeecg.modules.cas.util.CasServiceUtil;
import org.jeecg.modules.system.bo.TenantUserBo;
import org.jeecg.modules.system.entity.*;
import org.jeecg.modules.system.mapper.*;
@@ -42,6 +44,7 @@
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
@@ -99,6 +102,15 @@
    private SysUserTenantMapper relationMapper;
    @Autowired
    private SysUserTenantMapper userTenantMapper;
    @Value("${cas.prefixUrl}")
    private String prefixUrl;
    @Value("${cas.systemCode}")
    private String systemCode;
    @Value("${cas.SIGN_PRIVATE_KEY}")
    private String SIGN_PRIVATE_KEY;
    @Override
    public Result<IPage<SysUser>> queryPageList(HttpServletRequest req, QueryWrapper<SysUser> queryWrapper, Integer pageSize, Integer pageNo) {
@@ -647,14 +659,14 @@
        //update-begin---author:wangshuai ---date:20230112  for:用户创建的时候增加临时角色 test------------
        //开启租户saas模式
        if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
            String testRoleId = "ee8626f80f7c2619917b6236f3a7f02b";
            //如果前台没有传递角色或者传过来的劫色没有临时角色,那么默认临时角色 test
            if (oConvertUtils.isEmpty(selectedRoles) || !selectedRoles.contains(testRoleId)) {
                SysUserRole userRole = new SysUserRole(user.getId(), testRoleId);
                sysUserRoleMapper.insert(userRole);
            }
        }
//        if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
//            String testRoleId = "ee8626f80f7c2619917b6236f3a7f02b";
//            //如果前台没有传递角色或者传过来的劫色没有临时角色,那么默认临时角色 test
//            if (oConvertUtils.isEmpty(selectedRoles) || !selectedRoles.contains(testRoleId)) {
//                SysUserRole userRole = new SysUserRole(user.getId(), testRoleId);
//                sysUserRoleMapper.insert(userRole);
//            }
//        }
        //update-end---author:wangshuai ---date:20230112  for:用户创建的时候增加临时角色 test------------
        //step.3 ä¿å­˜æ‰€å±žéƒ¨é—¨
@@ -1232,24 +1244,54 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String syncTenantUser() {
    public Result<Object> syncTenantUser() {
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        int tenantId = oConvertUtils.getInt(TenantContext.getTenant(), 0);
        if(tenantId == 0){
            return "请登录租户管理员后同步用户!";
            return  Result.error("请登录租户管理员后同步用户!");
        }
        // æ¨¡æ‹ŸæŽ¥å£æ•°æ® TODO使用接口数据
        String json = "{\"code\":200,\"msg\":\"处理成功\",\"data\":[{\"id\":\"621466726\",\"name\":\"石一\",\"birthday\":\"1992-08-19\",\"rank\":\"bad0f40d-6ed9-4d7a-82f8-95af7d9f47d5\",\"firstOrgName\":\"信息部\",\"firstOrgId\":\"1632356\",\"orgName\":\"信息部\",\"orgId\":\"1632356\",\"post\":null,\"postId\":null,\"status\":\"\",\"jobStatus\":\"\",\"workNo\":\"6518\",\"mobile\":\"13658340793\",\"leaderId\":\"619347944\",\"leader\":\"程亚飞\"},{\"id\":\"621466727\",\"name\":\"张三\",\"birthday\":\"1993-05-15\",\"rank\":\"bad0f40d-6ed9-4d7a-82f8-95af7d9f47d6\",\"firstOrgName\":\"技术部\",\"firstOrgId\":\"1632357\",\"orgName\":\"技术部\",\"orgId\":\"1632357\",\"post\":\"工程师\",\"postId\":\"1001\",\"status\":\"在职\",\"jobStatus\":\"正常\",\"workNo\":\"6519\",\"mobile\":\"13658340794\",\"leaderId\":\"619347945\",\"leader\":\"李四\"},{\"id\":\"621466728\",\"name\":\"王五\",\"birthday\":\"1991-12-20\",\"rank\":\"bad0f40d-6ed9-4d7a-82f8-95af7d9f47d7\",\"firstOrgName\":\"市场部\",\"firstOrgId\":\"1632358\",\"orgName\":\"市场部\",\"orgId\":\"1632358\",\"post\":\"经理\",\"postId\":\"1002\",\"status\":\"在职\",\"jobStatus\":\"正常\",\"workNo\":\"6520\",\"mobile\":\"13658340795\",\"leaderId\":\"619347946\",\"leader\":\"赵六\"}]}";
        JSONObject jsonObject = JSON.parseObject(json);
        // æ¨¡æ‹ŸæŽ¥å£æ•°æ®
//        String json = "{\"code\":200,\"msg\":\"处理成功\",\"data\":[{\"id\":\"621466726\",\"name\":\"石一\",\"birthday\":\"1992-08-19\",\"rank\":\"bad0f40d-6ed9-4d7a-82f8-95af7d9f47d5\",\"firstOrgName\":\"信息部\",\"firstOrgId\":\"1632356\",\"orgName\":\"信息部\",\"orgId\":\"1632356\",\"post\":null,\"postId\":null,\"status\":\"\",\"jobStatus\":\"\",\"workNo\":\"6518\",\"mobile\":\"13658340793\",\"leaderId\":\"619347944\",\"leader\":\"程亚飞\"},{\"id\":\"621466727\",\"name\":\"张三\",\"birthday\":\"1993-05-15\",\"rank\":\"bad0f40d-6ed9-4d7a-82f8-95af7d9f47d6\",\"firstOrgName\":\"技术部\",\"firstOrgId\":\"1632357\",\"orgName\":\"技术部\",\"orgId\":\"1632357\",\"post\":\"工程师\",\"postId\":\"1001\",\"status\":\"在职\",\"jobStatus\":\"正常\",\"workNo\":\"6519\",\"mobile\":\"13658340794\",\"leaderId\":\"619347945\",\"leader\":\"李四\"},{\"id\":\"621466728\",\"name\":\"王五\",\"birthday\":\"1991-12-20\",\"rank\":\"bad0f40d-6ed9-4d7a-82f8-95af7d9f47d7\",\"firstOrgName\":\"市场部\",\"firstOrgId\":\"1632358\",\"orgName\":\"市场部\",\"orgId\":\"1632358\",\"post\":\"经理\",\"postId\":\"1002\",\"status\":\"在职\",\"jobStatus\":\"正常\",\"workNo\":\"6520\",\"mobile\":\"13658340795\",\"leaderId\":\"619347946\",\"leader\":\"赵六\"}]}";
        //用户列表
        String ulStamp = String.valueOf(System.currentTimeMillis());
        String ulRaw = "systemCode="+ systemCode + "&timestamp="+ ulStamp;
        String ulSign =   SM2Utils.getInstance().sign(ulRaw,SIGN_PRIVATE_KEY);
        String ulUrl = prefixUrl+"/api/user/list?";
        String ulParams = "systemCode="+ systemCode + "&timestamp="+ ulStamp;
        ulUrl += ulParams;
        String ulRes = CasServiceUtil.postRemote(ulUrl, ulSign);
        if(ulRes == null){
            return  Result.error("同步接口未返回数据!");
        }
        JSONObject jsonObject = JSON.parseObject(ulRes);
        JSONArray data = jsonObject.getJSONArray("data");
        List<TenantUserBo> remoteTenantUserList = data.toJavaList(TenantUserBo.class);
        System.err.println(remoteTenantUserList);
        if(jsonObject.getInteger("code") != 200 || data.isEmpty()){
            return  Result.error("同步接口返回空数据!");
        }
        List<TenantUserBo> remoteTenantUserList = data.toJavaList(TenantUserBo.class)
                .stream()
                .filter(user -> {
                    String workNo = user.getWorkNo();
                    return workNo != null && !workNo.trim().isEmpty();
                })
                .collect(Collectors.toMap(
                        user -> user.getWorkNo().trim().toUpperCase(),  // åŽ»é™¤é¦–å°¾ç©ºæ ¼
                        user -> user,
                        (existing, replacement) -> {
                            // å¯ä»¥åœ¨è¿™é‡Œæ·»åŠ æ—¥å¿—æŸ¥çœ‹é‡å¤æƒ…å†µ
                            System.out.println("发现重复workNo: " + replacement.getWorkNo());
                            return replacement;
                        }
                ))
                .values()
                .stream()
                .collect(Collectors.toList());
        // æŸ¥è¯¢ç§Ÿæˆ·ä¸‹ç”¨æˆ·
        List<String> tenantUserIds = userTenantMapper.getUserIdsByTenantId(tenantId);
        LambdaQueryWrapper<SysUser> userQueryWrapper = new LambdaQueryWrapper<>();
        userQueryWrapper.in(SysUser::getId, tenantUserIds);
        userQueryWrapper.in(SysUser::getTenantId, tenantId);
        List<SysUser> tenantUserList = this.baseMapper.selectList(userQueryWrapper);
@@ -1269,40 +1311,57 @@
                .collect(Collectors.toList());
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failedCount = new AtomicInteger(0);
        for (int i = 0; i <remoteNotInTenant.size() ; i++) {
            TenantUserBo tenantUserBo = remoteNotInTenant.get(i);
            addUser(tenantUserBo,tenantId,successCount,failedCount);
        }
        addUser(remoteNotInTenant,tenantId,successCount,failedCount);
        if(remoteTenantUserList.size()>0){
           return  "同步"+remoteTenantUserList.size()+"条数据,已存在"+remoteInTenant.size() +"条;成功新增"+successCount.get()+"条,失败"+failedCount.get()+"条";
           return  Result.ok("查到"+remoteTenantUserList.size()+"条数据,已存在"+remoteInTenant.size() +"条;成功新增"+successCount.get()+"条;");
        }
        return "同步接口未返回数据!";
        return Result.error("同步接口未返回数据!");
    }
    private void addUser(TenantUserBo tenantUserBo,Integer tenantId,AtomicInteger successCount, AtomicInteger failedCount) {
        SysUser user = new SysUser();
        user.setLoginTenantId(tenantId);
        user.setTenantId(tenantId);
        user.setCreateTime(new Date());// è®¾ç½®åˆ›å»ºæ—¶é—´
        String salt = oConvertUtils.randomGen(8);
        String passwordEncode = PasswordUtil.encrypt(tenantUserBo.getWorkNo(), "123456", salt);
        user.setSalt(salt);
        user.setUsername(tenantUserBo.getWorkNo());
        user.setRealname(tenantUserBo.getName());
        user.setPassword(passwordEncode);
        user.setPhone(tenantUserBo.getMobile());
        user.setStatus(CommonConstant.USER_UNFREEZE);
        user.setDelFlag(CommonConstant.DEL_FLAG_0);
        user.setActivitiSync(CommonConstant.ACT_SYNC_0);
        boolean save = this.save(user);
    private void addUser(List<TenantUserBo> remoteNotInTenantList,Integer tenantId,AtomicInteger successCount, AtomicInteger failedCount) {
        if(save){
            this.saveUserTenant(user.getId(),String.valueOf(tenantId));
        LambdaQueryWrapper<SysDepart> departQueryWrapper = new LambdaQueryWrapper<>();
        departQueryWrapper.eq(SysDepart::getTenantId, tenantId);
        List<SysDepart> departList = sysDepartMapper.selectList(departQueryWrapper);
        // æ‰€æœ‰éƒ¨é—¨
        Map<String, String> departMap = departList.stream()
                .collect(Collectors.toMap(
                        SysDepart::getDepartName,
                        SysDepart::getId,
                        (existing, replacement) -> existing
                ));
        LambdaQueryWrapper<SysRole> roleLambdaQueryWrapper = new LambdaQueryWrapper<>();
        roleLambdaQueryWrapper.eq(SysRole::getRoleCode, "TJT-PT");
        roleLambdaQueryWrapper.eq(SysRole::getTenantId, tenantId);
        SysRole  role =  sysRoleMapper.selectOne(roleLambdaQueryWrapper);
        for (int i = 0; i <remoteNotInTenantList.size() ; i++) {
            TenantUserBo tenantUserBo = remoteNotInTenantList.get(i);
            SysUser user = new SysUser();
            user.setId(tenantUserBo.getId());
            user.setLoginTenantId(tenantId);
            user.setTenantId(tenantId);
            user.setCreateTime(new Date());// è®¾ç½®åˆ›å»ºæ—¶é—´
            String salt = oConvertUtils.randomGen(8);
            String passwordEncode = PasswordUtil.encrypt(tenantUserBo.getWorkNo(), "123456", salt);
            user.setSalt(salt);
            user.setUsername(tenantUserBo.getWorkNo());
            user.setRealname(tenantUserBo.getName());
            user.setPassword(passwordEncode);
            user.setPhone(tenantUserBo.getMobile());
            user.setStatus(CommonConstant.USER_UNFREEZE);
            user.setDelFlag(CommonConstant.DEL_FLAG_0);
            user.setActivitiSync(CommonConstant.ACT_SYNC_0);
            user.setWorkNo(tenantUserBo.getWorkNo());
            successCount.incrementAndGet();
        }else {
            failedCount.incrementAndGet();
            String depart =  departMap.get(tenantUserBo.getOrgName()) ;
            this.saveUser(user,role.getId(),depart,tenantId+"");
        }
    }
    /**