使用AES加密方法,对Springboot+Vue项目进行前后端数据加密

一、后端代码

  1. 安装maven依赖
java 复制代码
   <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>1.73</version>
        </dependency>
  1. 修复版AES加密工具类
java 复制代码
@Slf4j
public class AESUtil {

    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";

    private static final String KEY = "0123456789abcdef0123456789abcdef";
    private static final String IV = "0123456789abcdef";

    // Base64清理正则
    private static final Pattern BASE64_CLEAN_PATTERN = Pattern.compile("[\\s\\r\\n\\t]");

    static {
//        log.info("🔑 AES配置信息:");
//        log.info("  算法: {}", ALGORITHM);
//        log.info("  模式: CBC");
//        log.info("  填充: PKCS5Padding");
//        log.info("  密钥: {}", KEY);
//        log.info("  IV: {}", IV);
    }

    /**
     * 清理Base64字符串
     */
    private static String cleanBase64(String base64) {
        if (base64 == null) {
            return null;
        }
        return BASE64_CLEAN_PATTERN.matcher(base64).replaceAll("");
    }

    /**
     * AES加密
     */
    public static String encrypt(String data) throws Exception {
        try {
            log.debug("🔐 AES加密 - 原文长度: {}", data.length());

            SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

            byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            String result = Base64.getEncoder().encodeToString(encryptedBytes);

            log.debug("✅ AES加密成功 - 密文长度: {}", result.length());
            return result;
        } catch (Exception e) {
            log.error("❌ AES加密失败: {}", e.getMessage(), e);
            throw new Exception("AES加密失败: " + e.getMessage(), e);
        }
    }

    /**
     * AES解密  增强日志版本
     */
    public static String decrypt(String encryptedData) throws Exception {
        try {
            log.info("🔓 AES解密启动 - 密文长度: {}", encryptedData.length());

            // 记录密文样本
            String preview = encryptedData.length() > 100 ? encryptedData.substring(0, 100) + "..." : encryptedData;
            log.debug("🔓 密文预览: {}", preview);

            // 清理Base64数据
            String cleanedData = cleanBase64(encryptedData);
            log.debug("🔧 清理后密文长度: {}", cleanedData.length());

            SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

            byte[] encryptedBytes;
            try {
                encryptedBytes = Base64.getDecoder().decode(cleanedData);
                log.debug("🔧 Base64解码成功,字节长度: {}", encryptedBytes.length);
            } catch (IllegalArgumentException e) {
                log.error("❌ Base64解码失败: {}", e.getMessage());
                throw new Exception("Base64数据格式错误: " + e.getMessage(), e);
            }

            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            String result = new String(decryptedBytes, StandardCharsets.UTF_8);

            log.info("✅ AES解密成功 - 原文长度: {}", result.length());

            // 🎯 详细记录解密结果
            String resultPreview = result.length() > 200 ? result.substring(0, 200) + "..." : result;
            log.info("🔓 解密结果预览: {}", resultPreview);

            // 检查是否为JSON格式
            String trimmedResult = result.trim();
            boolean isJson = trimmedResult.startsWith("{") || trimmedResult.startsWith("[");
            log.debug("🔍 解密结果JSON检测: {}", isJson ? "是" : "否");

            if (isJson) {
                try {
                    // 验证JSON格式
                    ObjectMapper objectMapper = new ObjectMapper();
                    Object jsonObject = objectMapper.readValue(result, Object.class);
                    log.debug("✅ 解密结果为有效JSON格式");

                    if (jsonObject instanceof Map) {
                        log.debug("📋 JSON对象键: {}", ((Map) jsonObject).keySet());
                    } else if (jsonObject instanceof List) {
                        log.debug("📋 JSON数组长度: {}", ((List) jsonObject).size());
                    }
                } catch (Exception e) {
                    log.warn("⚠️ 解密结果看起来像JSON但解析失败: {}", e.getMessage());
                }
            }

            return result;
        } catch (Exception e) {
            log.error("❌ AES解密失败: {}", e.getMessage());
            log.error("❌ 解密失败详情 - 输入数据长度: {}", encryptedData != null ? encryptedData.length() : 0);
            if (encryptedData != null) {
                log.error("❌ 输入数据预览: {}", encryptedData.length() > 100 ?
                        encryptedData.substring(0, 100) + "..." : encryptedData);
            }
            throw new Exception("AES解密失败: " + e.getMessage(), e);
        }
    }

    /**
     * 测试复杂数据结构
     */
    public static void testComplexData() {
        log.info("🧪 测试AES复杂数据结构处理能力...");

        // 模拟复杂数据结构
        String complexJson = "{\"doctorType\":0,\"isCenterHos\":true,\"shareCheckTimeOutList\":[{\"name\":\"1天内有效\",\"value\":\"1440\"},{\"name\":\"30分钟内有效\",\"value\":\"30\"}],\"checkTypeList\":[{\"name\":\"SR\",\"value\":\"1\"},{\"name\":\"MR\",\"value\":\"2\"}]}";

        try {
            log.info("测试数据长度: {}", complexJson.length());

            String encrypted = encrypt(complexJson);
            log.info("加密成功,密文长度: {}", encrypted);

            String decrypted = decrypt(encrypted);
            log.info("解密成功,原文长度: {}", decrypted);

            boolean success = complexJson.equals(decrypted);
            log.info("复杂数据测试: {}", success ? "✅ 成功" : "❌ 失败");

        } catch (Exception e) {
            log.error("❌ 复杂数据测试失败: {}", e.getMessage(), e);
        }
    }
    public static void main(String[] args) {
        testComplexData();
    }
}
  1. 加密请求包装类
java 复制代码
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class EncryptedRequest implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 加密后的数据
     */
    private String data;

    /**
     * 时间戳
     */
    private Long timestamp;

    public EncryptedRequest() {
    }

    public EncryptedRequest(String data) {
        this.data = data;
        this.timestamp = System.currentTimeMillis();
    }
}
  1. 加密响应包装类
java 复制代码
@Data
public class EncryptedResponse<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 加密后的数据
     */
    private String data;

    /**
     * 时间戳
     */
    private Long timestamp;

    public EncryptedResponse() {
        this.timestamp = System.currentTimeMillis();
    }

    public EncryptedResponse(String data) {
        this();
        this.data = data;
    }

    public static <T> EncryptedResponse<T> success(String encryptedData) {
        return new EncryptedResponse<>(encryptedData);
    }
}
  1. 加密请求包装器
java 复制代码
@Slf4j
public class EncryptHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private String requestBody;
    private String decryptedBody;
    private boolean isEncryptedRequest = false;
    private boolean isDecrypted = false;

    public EncryptHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);

        String isEncrypted = request.getHeader("X-Request-Encrypted");
        this.isEncryptedRequest = "true".equals(isEncrypted);

        logRequestInfo(request);
    }

    /**
     * 记录请求详细信息
     */
    private void logRequestInfo(HttpServletRequest request) {
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String encryptedHeader = request.getHeader("X-Request-Encrypted");
        String contentType = request.getContentType();

        log.info("📥 请求包装器初始化 - {} {} [加密头: {}, Content-Type: {}]",
                method, uri, encryptedHeader, contentType);
        log.debug("📥 原始请求体长度: {}", requestBody.length());

        if (isEncryptedRequest && requestBody != null && !requestBody.trim().isEmpty()) {
            log.debug("📥 加密请求体预览: {}",
                    requestBody.length() > 200 ? requestBody.substring(0, 200) + "..." : requestBody);

            try {
                decryptAndLogRequestBody();
            } catch (Exception e) {
                log.error("❌ 请求解密失败: {}", e.getMessage());
            }
        } else {
            log.debug("📥 普通请求体: {}", requestBody);
            this.decryptedBody = this.requestBody;
        }
    }

    /**
     * 解密并记录请求体
     */
    private void decryptAndLogRequestBody() throws Exception {
        if (isDecrypted) {
            return;
        }

        ObjectMapper objectMapper = new ObjectMapper();

        try {
            // 解析加密请求结构
            Map<String, Object> requestMap = objectMapper.readValue(requestBody, Map.class);
            String encryptedData = (String) requestMap.get("data");

            if (encryptedData != null && !encryptedData.trim().isEmpty()) {
                log.info("🔓 开始解密请求数据 - 密文长度: {}", encryptedData.length());

                // 执行解密
                this.decryptedBody = AESUtil.decrypt(encryptedData);
                this.isDecrypted = true;

                log.info("✅ 请求解密成功 - 原文长度: {}", decryptedBody.length());
                log.info("🔓 解密后请求数据: {}", decryptedBody);

            } else {
                log.warn("⚠️ 加密请求但data字段为空");
                this.decryptedBody = this.requestBody;
            }
        } catch (Exception e) {
            log.error("❌ 解析加密请求结构失败: {}", e.getMessage());
            this.decryptedBody = this.requestBody;
            throw e;
        }
    }

    // 🎯 关键修复:重写getContentType方法
    @Override
    public String getContentType() {
        // 如果是加密请求,解密后的数据是JSON,强制返回application/json
        if (isEncryptedRequest && isDecrypted) {
            log.debug("🔧 强制设置Content-Type为application/json");
            return "application/json";
        }
        return super.getContentType();
    }

    // 🎯 关键修复:重写getHeader方法,确保Content-Type正确
    @Override
    public String getHeader(String name) {
        if ("content-type".equalsIgnoreCase(name) && isEncryptedRequest && isDecrypted) {
            return "application/json";
        }
        return super.getHeader(name);
    }

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream byteArrayInputStream;

        if (decryptedBody != null) {
            byteArrayInputStream = new ByteArrayInputStream(decryptedBody.getBytes(StandardCharsets.UTF_8));
            log.debug("🔓 返回解密后的输入流,长度: {}", decryptedBody.length());
        } else {
            byteArrayInputStream = new ByteArrayInputStream(requestBody.getBytes(StandardCharsets.UTF_8));
            log.debug("📝 返回原始输入流,长度: {}", requestBody.length());
        }

        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return byteArrayInputStream.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
                // 不需要实现
            }

            @Override
            public int read() {
                return byteArrayInputStream.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
    }
}
  1. 加密响应包装器
java 复制代码
public class EncryptHttpServletResponseWrapper extends HttpServletResponseWrapper {

    private final ByteArrayOutputStream buffer;
    private final ServletOutputStream outputStream;
    private final PrintWriter writer;

    public EncryptHttpServletResponseWrapper(HttpServletResponse response) {
        super(response);
        buffer = new ByteArrayOutputStream();
        outputStream = new WrapperOutputStream(buffer);
        writer = new PrintWriter(outputStream);
    }

    @Override
    public ServletOutputStream getOutputStream() {
        return outputStream;
    }

    @Override
    public PrintWriter getWriter() {
        return writer;
    }

    @Override
    public void flushBuffer() throws IOException {
        if (writer != null) {
            writer.flush();
        }
        if (outputStream != null) {
            outputStream.flush();
        }
    }

    public byte[] getResponseData() throws IOException {
        flushBuffer();
        return buffer.toByteArray();
    }

    private static class WrapperOutputStream extends ServletOutputStream {
        private final ByteArrayOutputStream buffer;

        public WrapperOutputStream(ByteArrayOutputStream buffer) {
            this.buffer = buffer;
        }

        @Override
        public void write(int b) {
            buffer.write(b);
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {
            // 不需要实现
        }
    }
}
  1. 全局加解密过滤器
java 复制代码
@Slf4j
public class GlobalEncryptDecryptFilter implements Filter {

    private final ObjectMapper objectMapper = new ObjectMapper();

    // 添加请求标记,避免重复处理
    private static final String FILTER_APPLIED_ATTRIBUTE = GlobalEncryptDecryptFilter.class.getName() + ".APPLIED";
    // 排除加密的接口
    private static final String[] EXCLUDE_ENCRYPT_URIS = {
            "/static",
            "/error",
    };

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String requestURI = httpRequest.getRequestURI();
        String method = httpRequest.getMethod();

        // 🎯 只使用请求属性检查
        if (httpRequest.getAttribute(FILTER_APPLIED_ATTRIBUTE) != null) {
//            log.debug("⏩ 跳过已处理的请求: {} {}", method, requestURI);
            chain.doFilter(request, response);
            return;
        }

        log.info("🔐 全局加解密过滤器处理请求: {} {}", method, requestURI);

        // 标记请求已处理
        httpRequest.setAttribute(FILTER_APPLIED_ATTRIBUTE, Boolean.TRUE);

        try {

            // 🎯 新增:检查是否是Multipart请求(文件上传)
            if (isMultipartRequest(httpRequest)) {
                log.info("📎 跳过Multipart文件上传请求: {}", requestURI);
                chain.doFilter(request, response);
                return;
            }

            // 检查是否在排除列表中
            if (shouldExcludeEncrypt(requestURI)) {
                log.info("✅ 跳过加解密: {}", requestURI);
                chain.doFilter(request, response);
                return;
            }

            // 🎯 重构:根据请求类型选择处理方式
            boolean isEncryptedRequest = "true".equals(httpRequest.getHeader("X-Request-Encrypted"));
            String contentType = httpRequest.getContentType();
            boolean isPostMethod = "POST".equalsIgnoreCase(method);

            log.debug("🔍 请求分析 - 加密头: {}, Content-Type: {}, 方法: {}", isEncryptedRequest, contentType, method);

            if (isEncryptedRequest && isPostMethod) {
                // 🎯 加密的POST请求:使用专门的加密请求处理
                handleEncryptedRequest(httpRequest, httpResponse, chain);
            } else {
                // 🎯 非加密请求或GET请求:使用普通处理
                EncryptHttpServletResponseWrapper responseWrapper = new EncryptHttpServletResponseWrapper(httpResponse);
                chain.doFilter(request, responseWrapper);
                processResponseEncryption(requestURI, responseWrapper, httpResponse);
            }

        } catch (Exception e) {
            log.error("❌ 加解密处理异常, URI: {}", requestURI, e);
            handleEncryptError(response, e);
        }
    }

    /**
     * 处理加密请求 - 执行解密并继续过滤器链
     */
    private void handleEncryptedRequest(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws Exception {

        log.info("🔐 处理加密请求: {} {}", request.getMethod(), request.getRequestURI());

        // 使用深度集成包装器
//        SpringMvcCompatibleRequestWrapper requestWrapper = new SpringMvcCompatibleRequestWrapper(request);
        UniversalRequestWrapper requestWrapper = new UniversalRequestWrapper(request);
        EncryptHttpServletResponseWrapper responseWrapper = new EncryptHttpServletResponseWrapper(response);

        // 🎯 关键:将解密内容存储为请求属性,供后续参数绑定使用
        if (requestWrapper.isEncryptedRequest()) {
            String decryptedContent = requestWrapper.getDecryptedContent();
            // 存储解密内容到请求属性,确保后续流程可以获取
            request.setAttribute("DECRYPTED_REQUEST_BODY", decryptedContent);
            log.info("🎯 解密内容已存储到请求属性,长度: {}", decryptedContent.length());
            log.debug("🔓 解密内容预览: {}",
                    decryptedContent.length() > 200 ? decryptedContent.substring(0, 200) + "..." : decryptedContent);
        }

        // 继续过滤器链
        chain.doFilter(requestWrapper, responseWrapper);

        // 处理响应加密
        processResponseEncryption(request.getRequestURI(), responseWrapper, response);
    }

    /**
     * 🎯 新增:检查是否是Multipart请求(文件上传)
     */
    private boolean isMultipartRequest(HttpServletRequest request) {
        String contentType = request.getContentType();
        boolean isMultipart = contentType != null && contentType.toLowerCase().startsWith("multipart/");

        if (isMultipart) {
            log.debug("📎 检测到Multipart请求,Content-Type: {}", contentType);
        }

        return isMultipart;
    }


    /**
     * 检查是否应该排除加密 - 支持通配符匹配
     */
    private boolean shouldExcludeEncrypt(String uri) {
        for (String excludeUri : EXCLUDE_ENCRYPT_URIS) {
            // 如果排除项以 * 结尾,使用前缀匹配
            if (excludeUri.endsWith("/*")) {
                String prefix = excludeUri.substring(0, excludeUri.length() - 1);
                if (uri.startsWith(prefix)) {
                    log.debug("✅ 前缀匹配排除: {} -> {}", excludeUri, uri);
                    return true;
                }
            }
            // 精确匹配
            else if (uri.equals(excludeUri)) {
                log.debug("✅ 精确匹配排除: {}", uri);
                return true;
            }
            // 路径前缀匹配(原有逻辑保持)
            else if (uri.startsWith(excludeUri)) {
                log.debug("✅ 路径匹配排除: {} -> {}", excludeUri, uri);
                return true;
            }
        }
        return false;
    }

    /**
     * 处理响应加密
     */
    private void processResponseEncryption(String requestURI,
                                           EncryptHttpServletResponseWrapper responseWrapper,
                                           HttpServletResponse originalResponse) throws Exception {

        byte[] responseData = responseWrapper.getResponseData();
        if (responseData.length > 0) {
            String responseBody = new String(responseData, StandardCharsets.UTF_8);

            log.info("🔐 处理响应加密, URI: {}", requestURI);

            if (isJsonResponse(responseWrapper) && !responseBody.trim().isEmpty()) {
                try {
                    // 解析原始响应
                    Map<String, Object> responseMap = objectMapper.readValue(responseBody, Map.class);
                    Integer code = (Integer) responseMap.get("code");
                    Object data = responseMap.get("data");

                    // 🎯 关键修复:检查数据是否已经是加密字符串
                    boolean isAlreadyEncrypted = isEncryptedData(data);

                    // 只对成功的业务响应且未加密的数据进行加密
                    if (code != null && code == 200 && data != null && !isAlreadyEncrypted) {
                        log.info("✅ 开始AES加密响应数据");

                        // 加密数据
                        String dataJson = objectMapper.writeValueAsString(data);
                        String encryptedData = AESUtil.encrypt(dataJson);

                        // 构建加密响应
                        Map<String, Object> encryptedResponse = new HashMap<>();
                        encryptedResponse.put("code", 200);
                        encryptedResponse.put("msg", responseMap.get("msg"));
                        encryptedResponse.put("data", encryptedData);
                        encryptedResponse.put("timestamp", System.currentTimeMillis());

                        String finalResponse = objectMapper.writeValueAsString(encryptedResponse);
                        byte[] finalResponseBytes = finalResponse.getBytes(StandardCharsets.UTF_8);

                        // 设置响应头
                        originalResponse.setHeader("X-Response-Encrypted", "true");
                        originalResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
                        originalResponse.setContentLength(finalResponseBytes.length);

                        // 清空可能已写入的数据
                        originalResponse.resetBuffer();

                        // 写入加密后的数据
                        originalResponse.getOutputStream().write(finalResponseBytes);
                        originalResponse.getOutputStream().flush();

                        log.info("✅ 响应AES加密成功, URI: {}, 数据长度: {}", requestURI, encryptedData.length());
                        return;
                    } else if (isAlreadyEncrypted) {
                        log.info("⚠️ 数据已经是加密状态,跳过重复加密, URI: {}", requestURI);
                    } else {
                        log.debug("响应不符合加密条件, code: {}, data: {}", code, data != null);
                    }
                } catch (Exception e) {
                    log.error("❌ 响应AES加密失败,返回原始数据: {}", e.getMessage(), e);
                }
            }

            // 返回原始数据
            originalResponse.resetBuffer();
            originalResponse.getOutputStream().write(responseData);
            log.info("📝 返回原始响应数据, URI: {}", requestURI);
        }
    }

    /**
     * 判断数据是否已经是加密字符串
     */
    private boolean isEncryptedData(Object data) {
        if (!(data instanceof String)) {
            return false;
        }

        String dataStr = (String) data;

        // 加密数据通常的特征:
        // 1. 长度较长(至少100字符以上)
        // 2. 只包含Base64字符(A-Z, a-z, 0-9, +, /, =)
        // 3. 不包含JSON特征字符({, }, :, "等)
        if (dataStr.length() < 100) {
            return false;
        }

        // 检查是否是Base64格式
        String base64Pattern = "^[A-Za-z0-9+/]*={0,2}$";
        boolean isBase64 = dataStr.matches(base64Pattern);

        // 检查是否包含JSON特征
        boolean hasJsonChars = dataStr.contains("{") || dataStr.contains("}") ||
                dataStr.contains("\"") || dataStr.contains(":");

        log.debug("🔍 加密数据检测 - 长度: {}, Base64格式: {}, 包含JSON字符: {}",
                dataStr.length(), isBase64, hasJsonChars);

        return isBase64 && !hasJsonChars;
    }

    private boolean isJsonResponse(HttpServletResponse response) {
        String contentType = response.getContentType();
        return contentType != null && contentType.contains(MediaType.APPLICATION_JSON_VALUE);
    }

    private void handleEncryptError(ServletResponse response, Exception e) throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);

        Map<String, Object> errorResult = new HashMap<>();
        errorResult.put("code", 500);
        errorResult.put("msg", "系统加解密异常: " + e.getMessage());
        String errorResponse = objectMapper.writeValueAsString(errorResult);

        // 确保错误响应不设置加密头
        response.getWriter().write(errorResponse);

        log.error("💥 加解密异常处理完成");
    }
}
  1. 通用请求包装器 - 自动处理加密请求的参数绑定
java 复制代码
@Slf4j
public class UniversalRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] originalBodyBytes;
    private byte[] decryptedBodyBytes;
    private final boolean isEncryptedRequest;
    private boolean isDecrypted = false;

    // 🎯 关键:参数映射,用于支持参数绑定
    private Map<String, String[]> parameterMap;
    private boolean parametersExtracted = false;
    private final ObjectMapper objectMapper = new ObjectMapper();

    // 🎯 关键:使用CaseInsensitiveMap确保头部不区分大小写
    private final Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

    public UniversalRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);

        // 🎯 保存所有原始头部信息
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            headers.put(headerName, headerValue);
        }

        this.originalBodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
        this.isEncryptedRequest = "true".equals(request.getHeader("X-Request-Encrypted"));

        log.info("🔄 通用请求包装器 - {} {} [加密: {}]",
                request.getMethod(), request.getRequestURI(), isEncryptedRequest);

        if (this.isEncryptedRequest) {
            try {
                decryptRequestBody();
                // 🎯 关键:解密后自动提取参数
                extractParametersFromJson();
            } catch (Exception e) {
                log.error("❌ 请求解密失败: {}", e.getMessage());
                this.decryptedBodyBytes = this.originalBodyBytes;
            }
        } else {
            this.decryptedBodyBytes = this.originalBodyBytes;
        }
    }

    /**
     * 解密请求体
     */
    private void decryptRequestBody() throws Exception {
        if (isDecrypted) {
            return;
        }

        try {
            String originalRequestBody = new String(originalBodyBytes, StandardCharsets.UTF_8);
            log.debug("📥 加密请求体字符串长度: {}", originalRequestBody.length());

            // 解析加密请求结构
            Map<String, Object> requestMap = objectMapper.readValue(originalRequestBody, Map.class);
            String encryptedData = (String) requestMap.get("data");

            if (encryptedData != null && !encryptedData.trim().isEmpty()) {
                log.info("🔓 开始解密请求数据 - 密文长度: {}", encryptedData.length());

                // 执行解密
                String decryptedBody = AESUtil.decrypt(encryptedData);
                this.decryptedBodyBytes = decryptedBody.getBytes(StandardCharsets.UTF_8);
                this.isDecrypted = true;

                log.info("✅ 请求解密成功 - 原文字节长度: {}", decryptedBodyBytes.length);
                log.debug("🔓 解密后请求数据: {}", decryptedBody);

                // 验证JSON格式
                try {
                    objectMapper.readValue(decryptedBody, Object.class);
                    log.debug("✅ 解密数据是有效的JSON格式");
                } catch (Exception e) {
                    log.error("❌ 解密后的数据不是有效的JSON: {}", e.getMessage());
                    throw new RuntimeException("解密后的数据不是有效的JSON格式");
                }
            } else {
                log.warn("⚠️ 加密请求但data字段为空");
                this.decryptedBodyBytes = this.originalBodyBytes;
            }
        } catch (Exception e) {
            log.error("❌ 解析加密请求结构失败: {}", e.getMessage());
            this.decryptedBodyBytes = this.originalBodyBytes;
            throw e;
        }
    }

    /**
     * 🎯 关键方法:从JSON中提取参数
     */
    private void extractParametersFromJson() {
        if (parametersExtracted) {
            return;
        }

        try {
            String decryptedContent = getDecryptedContent();
            if (decryptedContent != null && !decryptedContent.trim().isEmpty()) {
                Map<String, Object> jsonMap = objectMapper.readValue(decryptedContent, Map.class);
                this.parameterMap = new HashMap<>();

                for (Map.Entry<String, Object> entry : jsonMap.entrySet()) {
                    String key = entry.getKey();
                    Object value = entry.getValue();

                    if (value == null) {
                        parameterMap.put(key, new String[]{null});
                    } else if (value instanceof List) {
                        // 处理数组
                        List<?> list = (List<?>) value;
                        String[] array = list.stream()
                                .map(item -> item != null ? item.toString() : null)
                                .toArray(String[]::new);
                        parameterMap.put(key, array);
                    } else {
                        // 处理单个值
                        parameterMap.put(key, new String[]{value.toString()});
                    }
                }

                log.info("🎯 从JSON提取参数: {}", parameterMap.keySet());
                log.debug("📋 参数详情: {}", parameterMap);
            }
        } catch (Exception e) {
            log.warn("⚠️ JSON参数提取失败: {}", e.getMessage());
        } finally {
            parametersExtracted = true;
        }
    }

    // 🎯 关键:重写参数相关方法,支持参数绑定
    @Override
    public String getParameter(String name) {
        // 优先返回从JSON提取的参数
        if (parameterMap != null && parameterMap.containsKey(name)) {
            String[] values = parameterMap.get(name);
            String result = values != null && values.length > 0 ? values[0] : null;
            log.debug("🔍 获取参数 {}: {}", name, result);
            return result;
        }
        return super.getParameter(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        // 合并原始参数和JSON参数(JSON参数优先)
        if (parameterMap != null && !parameterMap.isEmpty()) {
            Map<String, String[]> merged = new HashMap<>(super.getParameterMap());
            merged.putAll(parameterMap);
            return merged;
        }
        return super.getParameterMap();
    }

    @Override
    public Enumeration<String> getParameterNames() {
        if (parameterMap != null && !parameterMap.isEmpty()) {
            Set<String> names = new HashSet<>(Collections.list(super.getParameterNames()));
            names.addAll(parameterMap.keySet());
            return Collections.enumeration(names);
        }
        return super.getParameterNames();
    }

    @Override
    public String[] getParameterValues(String name) {
        if (parameterMap != null && parameterMap.containsKey(name)) {
            String[] values = parameterMap.get(name);
            log.debug("🔍 获取参数值 {}: {}", name, Arrays.toString(values));
            return values;
        }
        return super.getParameterValues(name);
    }

    // 🎯 关键:确保Authorization头正确返回
    @Override
    public String getHeader(String name) {
        if ("content-type".equalsIgnoreCase(name)) {
            return "application/json;charset=UTF-8";
        }

        if ("authorization".equalsIgnoreCase(name)) {
            String authHeader = headers.get(name);
            if (authHeader != null) {
                return authHeader;
            }
        }

        String headerValue = headers.get(name);
        if (headerValue != null) {
            return headerValue;
        }

        return super.getHeader(name);
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        Set<String> headerNames = new HashSet<>(headers.keySet());
        String authHeader = super.getHeader("Authorization");
        if (authHeader != null && !headers.containsKey("Authorization")) {
            headers.put("Authorization", authHeader);
            headerNames.add("Authorization");
        }
        return Collections.enumeration(headerNames);
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        if ("content-type".equalsIgnoreCase(name)) {
            return Collections.enumeration(Collections.singletonList("application/json;charset=UTF-8"));
        }

        String headerValue = headers.get(name);
        if (headerValue != null) {
            return Collections.enumeration(Collections.singletonList(headerValue));
        }

        return super.getHeaders(name);
    }

    // 🎯 关键:重写输入流方法,确保返回解密后的数据
    @Override
    public ServletInputStream getInputStream() throws IOException {
        log.debug("🔓 返回解密后的输入流,字节长度: {}", decryptedBodyBytes.length);

        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptedBodyBytes);

        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return byteArrayInputStream.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
                // 不需要实现
            }

            @Override
            public int read() {
                return byteArrayInputStream.read();
            }

            @Override
            public int read(byte[] b, int off, int len) throws IOException {
                return byteArrayInputStream.read(b, off, len);
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
    }

    // 🎯 关键:重写内容长度相关方法
    @Override
    public int getContentLength() {
        return decryptedBodyBytes.length;
    }

    @Override
    public long getContentLengthLong() {
        return decryptedBodyBytes.length;
    }

    @Override
    public String getCharacterEncoding() {
        return "UTF-8";
    }

    /**
     * 获取解密后的请求体内容(用于调试)
     */
    public String getDecryptedContent() {
        return new String(decryptedBodyBytes, StandardCharsets.UTF_8);
    }

    public boolean isEncryptedRequest() {
        return isEncryptedRequest;
    }
}
  1. 注意,全局过滤器需要配置自己框架所有过滤器的最前面,比如Security配置类configure方法中
  2. 代码只是把data数据加密了,所有全部数据加密,需要根据需要调整响应的响应返回参数,需要根据实际业务操作进行调整,代码仅供参考,可以根据自己的想法和思路进行相应调整

二、前端代码

  1. 安装依赖 npm install crypto-js
  2. 创建AES加密工具类 aes.js
java 复制代码
// src/utils/aes.js
import CryptoJS from 'crypto-js'

/**
 * AES加密工具类 - 解决数据类型问题
 */
class AESUtil {
    constructor() {
        this.key = CryptoJS.enc.Utf8.parse('0123456789abcdef0123456789abcdef')
        this.iv = CryptoJS.enc.Utf8.parse('0123456789abcdef')
        this.mode = CryptoJS.mode.CBC
        this.padding = CryptoJS.pad.Pkcs7
    }

    /**
     * 智能数据类型处理
     */
    processDataForEncryption(data) {
        // 如果是字符串,直接返回(不进行JSON.stringify)
        if (typeof data === 'string') {
            return {
                data: data,
                type: 'string'
            }
        }
        // 如果是对象或数组,进行JSON序列化
        else if (typeof data === 'object' && data !== null) {
            return {
                data: JSON.stringify(data),
                type: 'json'
            }
        }
        // 其他类型转换为字符串
        else {
            return {
                data: String(data),
                type: 'string'
            }
        }
    }

    /**
     * 根据类型处理解密结果
     */
    processDecryptedData(decryptedStr, originalType) {
        if (originalType === 'json') {
            try {
                return JSON.parse(decryptedStr)
            } catch (e) {
                console.warn('⚠️ JSON解析失败,返回原始字符串:', e.message)
                return decryptedStr
            }
        } else {
            return decryptedStr
        }
    }

    /**
     * AES加密
     */
    encrypt(data) {
        try {
            // 智能处理数据类型
            const processed = this.processDataForEncryption(data)
            // console.log('🔐 AES加密 - 数据类型:', processed.type, '原始长度:', data.length, '处理后长度:', processed.data.length)

            const encrypted = CryptoJS.AES.encrypt(processed.data, this.key, {
                iv: this.iv,
                mode: this.mode,
                padding: this.padding
            })

            const result = encrypted.toString()

            // 返回包含类型信息的加密结果
            return {
                encryptedData: result,
                dataType: processed.type
            }
        } catch (error) {
            console.error('❌ AES加密失败:', error)
            throw new Error(`AES加密失败: ${error.message}`)
        }
    }

    /**
     * AES解密 
     */
    decrypt(encryptedData, dataType = 'auto') {
        try {
            if (!encryptedData || typeof encryptedData !== 'string') {
                throw new Error('解密数据必须是字符串')
            }

            // console.log('🔓 AES开始解密,密文长度:', encryptedData.length, '数据类型:', dataType)

            const decrypted = CryptoJS.AES.decrypt(encryptedData, this.key, {
                iv: this.iv,
                mode: this.mode,
                padding: this.padding
            })

            const resultStr = decrypted.toString(CryptoJS.enc.Utf8)

            if (!resultStr) {
                throw new Error('解密结果为空,可能是密钥或数据格式错误')
            }

            // console.log('✅ AES解密成功,原始字符串长度:', resultStr.length)
            // console.log('🔍 解密后的原始内容:', resultStr)

            // 🎯 关键修复:强制尝试JSON解析
            let finalResult
            if (dataType === 'json' || dataType === 'auto') {
                // 对于接口响应数据,强制尝试JSON解析
                try {
                    finalResult = JSON.parse(resultStr)
                    // console.log('✅ JSON解析成功')
                } catch (e) {
                    // JSON解析失败,检查是否真的是JSON格式但有小问题
                    const trimmed = resultStr.trim()
                    if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
                        (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
                        console.warn('⚠️ JSON格式有误,但看起来是JSON,尝试修复...')
                        try {
                            // 尝试修复常见的JSON格式问题
                            const fixedJson = trimmed
                                .replace(/(\w+):/g, '"$1":') // 修复无引号的key
                                .replace(/'/g, '"') // 单引号转双引号
                            finalResult = JSON.parse(fixedJson)
                            // console.log('✅ 修复后JSON解析成功')
                        } catch (fixError) {
                            // console.error('❌ JSON修复失败,返回原始字符串')
                            finalResult = resultStr
                        }
                    } else {
                        // console.log('📝 不是JSON格式,返回字符串')
                        finalResult = resultStr
                    }
                }
            } else {
                finalResult = resultStr
                // console.log('📝 作为字符串返回')
            }

            // console.log('🎯 最终解密结果类型:', typeof finalResult)
            if (typeof finalResult === 'object') {
                // console.log('🎯 解密对象键名:', Object.keys(finalResult))
            }

            return finalResult
        } catch (error) {
            console.error('❌ AES解密失败:', error)
            throw new Error(`AES解密失败: ${error.message}`)
        }
    }
    /**
     * 兼容性封装函数 - 用于HTTP拦截器
     */
    encryptForRequest(data) {
        const result = this.encrypt(data)
        // 对于请求,我们只返回加密字符串,类型信息通过其他方式传递
        return result.encryptedData
    }

    /**
     * 修复的测试用例
     */
    testComplexData() {
        // console.group('🧪 修复版AES兼容性测试')

        const testCases = [
            {
                name: '简单对象',
                data: { test: "简单测试", number: 123 },
                expectedType: 'json'
            },
            {
                name: '复杂对象',
                data: {
                    list: Array(10).fill().map((_, i) => ({ id: i, name: `项目${i}` })),
                    config: { enabled: true, timeout: 5000 }
                },
                expectedType: 'json'
            },
            {
                name: '纯字符串',
                data: "这是一个纯文本字符串,没有JSON格式",
                expectedType: 'string'
            },
            {
                name: 'JSON格式字符串',
                data: '{"name": "test", "value": 123}',
                expectedType: 'string' // 注意:这是字符串,不是对象
            },
            {
                name: '超长字符串',
                data: "这是一个很长的测试字符串".repeat(100),
                expectedType: 'string'
            }
        ]

        let allPassed = true
        const results = []

        for (const testCase of testCases) {
            try {
                // console.log(`\n📋 测试用例: ${testCase.name}`)
                // console.log('输入数据类型:', typeof testCase.data)
                // console.log('输入数据长度:', typeof testCase.data === 'string' ? testCase.data.length : JSON.stringify(testCase.data).length)

                // 加密
                const encryptedResult = this.encrypt(testCase.data)
                // console.log('加密结果长度:', encryptedResult.encryptedData.length)
                // console.log('加密数据类型:', encryptedResult.dataType)

                // 解密 - 根据加密时的类型信息
                const decrypted = this.decrypt(encryptedResult.encryptedData, encryptedResult.dataType)
                // console.log('解密结果类型:', decrypted)

                // 验证
                let expected
                if (testCase.expectedType === 'json') {
                    expected = testCase.data
                } else {
                    expected = testCase.data
                }

                const success = JSON.stringify(decrypted) === JSON.stringify(expected)


                // console.log(`测试结果: ${success ? '✅ 通过' : '❌ 失败'}`)
                // console.log('解密结果类型:', typeof decrypted)

                if (!success) {
                    // console.error('数据不匹配:')
                    // console.error('  预期:', JSON.stringify(expected).substring(0, 100))
                    // console.error('  实际:', JSON.stringify(decrypted).substring(0, 100))
                }

                results.push({
                    name: testCase.name,
                    success: success,
                    inputType: typeof testCase.data,
                    encryptedType: encryptedResult.dataType,
                    decryptedType: typeof decrypted,
                    inputLength: typeof testCase.data === 'string' ? testCase.data.length : JSON.stringify(testCase.data).length,
                    encryptedLength: encryptedResult.encryptedData.length,
                    decryptedLength: typeof decrypted === 'string' ? decrypted.length : JSON.stringify(decrypted).length
                })

                allPassed = allPassed && success

            } catch (error) {
                // console.error(`❌ 测试用例"${testCase.name}"执行失败:`, error.message)
                results.push({
                    name: testCase.name,
                    success: false,
                    error: error.message
                })
                allPassed = false
            }
        }

        // console.log('\n📊 测试总结:')
        results.forEach(result => {
            const status = result.success ? '✅' : '❌'
            const text = result.success ? '通过' : '失败'
            // console.log(`  ${result.name}: ${status} ${text}`)
            // console.log(`    输入: ${result.inputType}, 加密: ${result.encryptedType}, 解密: ${result.decryptedType}`)
            if (result.error) {
                // console.log(`    错误: ${result.error}`)
            }
        })

        // console.log(`\n🎯 总体结果: ${allPassed ? '✅ 所有测试通过' : '❌ 存在失败测试'}`)
        console.groupEnd()

        return allPassed
    }
}

// 创建单例实例
const aesUtil = new AESUtil()

// 导出工具函数 - 保持向后兼容
export const encrypt = (data) => {
    const result = aesUtil.encrypt(data)
    return result.encryptedData
}

export const decrypt = (encryptedData) => aesUtil.decrypt(encryptedData, 'auto')
export const testComplexData = () => aesUtil.testComplexData()

export default aesUtil
  1. 在前端vue统一请求响应方法中添加加密和解密过滤
java 复制代码
//添加aes方法
import { encrypt, decrypt} from '../utils/aes'
  1. 在request 拦截器添加方法
java 复制代码
// request 拦截器
axios.interceptors.request.use(
    (req) => {
        console.log('🔐 请求拦截器 - URL:', req.url, '方法:', req.method)
  // 加密处理逻辑
        const isPostMethod = req.method && req.method.toLowerCase() === 'post'
        const isNotFormData = !(req.data instanceof FormData)
        const shouldEncryptThisRequest = shouldEncrypt(url)

        // console.log('🔐 加密检查 - POST:', isPostMethod, '非FormData:', isNotFormData, '需要加密:', shouldEncryptThisRequest)

        if (isPostMethod && shouldEncryptThisRequest && req.data && isNotFormData) {
            try {
                // 使用修复版的加密
                const encryptedData = encrypt(req.data)

                const encryptedRequest = {
                    data: encryptedData,
                    timestamp: Date.now()
                }
                req.data = encryptedRequest
                req.headers['X-Request-Encrypted'] = 'true'
                req.headers['Content-Type'] = 'application/json'
                // console.log('✅ 请求AES加密成功')
            } catch (error) {
                console.error('❌ 请求AES加密失败:', error)
                req.data = qs.stringify(req.data)
                req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
            }
        } else if (isPostMethod && !shouldEncryptThisRequest && isNotFormData) {
            req.data = qs.stringify(req.data)
            req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
            // console.log('📝 使用表单格式,不加密')
        }

        return req
    },
    (error) => {
        console.error('❌ 请求拦截器错误:', error)
        return Promise.reject(error)
    }
)
 /**
 * 检查URL是否需要加密 - 支持动态参数和通配符
 */
function shouldEncrypt(url) {
    if (!url) return false

    return !EXCLUDE_ENCRYPT_URLS.some(excludeUrl => {
        // 🎯 新增:如果排除项以 /* 结尾,使用前缀匹配
        if (excludeUrl.endsWith('/*')) {
            const prefix = excludeUrl.substring(0, excludeUrl.length - 2); // 去掉 /*
            if (url.startsWith(prefix)) {
                // console.log(`🔕 通配符匹配排除加密: ${url} -> ${excludeUrl}`);
                return true;
            }
        }
        // 如果排除项以 / 结尾,使用前缀匹配(处理动态参数)
        else if (excludeUrl.endsWith('/')) {
            if (url.startsWith(excludeUrl)) {
                // console.log(`🔕 前缀匹配排除加密: ${url} -> ${excludeUrl}`);
                return true;
            }
        }
        // 精确匹配
        else if (url === excludeUrl) {
            // console.log(`🔕 精确匹配排除加密: ${url}`);
            return true;
        }
        // 包含匹配(原有逻辑)
        else if (url.includes(excludeUrl)) {
            // console.log(`🔕 包含匹配排除加密: ${url} -> ${excludeUrl}`);
            return true;
        }
        return false;
    });
}
// response 拦截器 - 使用AES解密
axios.interceptors.response.use(
    (response) => {
      // 如果AES测试失败,禁用解密
        if (window.AES_DISABLED) {
            console.warn('⚠️ AES解密已禁用,直接返回原始数据')
            return handleBusinessLogic(response.data)
        }
                const url = response.config.url
        const isEncryptedResponse = response.headers['x-response-encrypted'] === 'true'
        const shouldEncryptThisResponse = shouldEncrypt(url)
    let finalData = response.data

        // 处理加密响应
        if ((isEncryptedResponse || shouldEncryptThisResponse) && response.data) {
            try {
                console.group('🔐 加密响应处理流程 - URL: ' + url)

                let encryptedData = null
                let responseStructure = null

                if (response.data.data && typeof response.data.data === 'string') {
                    encryptedData = response.data.data
                    responseStructure = 'standard'
                    // console.log('🔓 标准加密格式,加密数据长度:', encryptedData.length)
                } else if (typeof response.data === 'string') {
                    encryptedData = response.data
                    responseStructure = 'direct'
                    // console.log('🔓 直接加密格式,加密数据长度:', encryptedData.length)
                } else {
                    // console.warn('⚠️ 无法识别的加密格式,跳过解密')
                    console.groupEnd()
                }

                if (encryptedData) {
                    // console.log('🔐 原始加密数据预览:', encryptedData.substring(0, 100) + '...')

                    try {
                        // 使用修复版的解密(自动检测类型)
                        const decryptedResult = decrypt(encryptedData)
                        // console.log('✅ AES解密成功!')
                        // console.log('📊 解密结果类型:', typeof decryptedResult)
                        // console.log('📏 解密结果长度:', typeof decryptedResult === 'string' ? decryptedResult.length : JSON.stringify(decryptedResult).length)

                        // 🎯 关键位置:打印每个接口的完整解密数据
                        // console.log('🔓 完整解密数据:')
                        if (typeof decryptedResult === 'string') {
                            // 如果是字符串,直接输出
                            // console.log('  字符串内容:', decryptedResult)
                        } else {
                            // 如果是对象,格式化输出
                            // console.log('  JSON对象:', JSON.stringify(decryptedResult, null, 2))
                        }

                        // 检查数据结构
                        if (typeof decryptedResult === 'object' && decryptedResult !== null) {
                            // console.log('📋 对象键名:', Object.keys(decryptedResult))
                            if (Array.isArray(decryptedResult)) {
                                // console.log('📋 数组长度:', decryptedResult.length)
                                if (decryptedResult.length > 0) {
                                    // console.log('📋 数组第一项:', decryptedResult[0])
                                }
                            }
                        }

                        // 根据响应结构构建最终数据
                        if (responseStructure === 'standard') {
                            finalData = {
                                ...response.data,
                                data: decryptedResult
                            }
                        } else {
                            finalData = decryptedResult
                        }

                        // console.log('🎯 最终返回给业务的数据结构:', typeof finalData)
                        if (typeof finalData === 'object' && finalData !== null) {
                            // console.log('🎯 最终数据包含的字段:', Object.keys(finalData))
                        }

                    } catch (decryptError) {
                        console.error('❌ AES解密失败:', decryptError.message)
                        console.error('❌ 解密失败详情:', {
                            url: url,
                            encryptedDataLength: encryptedData.length,
                            error: decryptError.message,
                            stack: decryptError.stack
                        })
                        // 解密失败时保持原始数据
                    }
                }
                console.groupEnd()
            } catch (error) {
                console.groupEnd()
                console.error('❌ 响应解密处理异常 - URL: ' + url, error)
                console.error('❌ 异常详情:', {
                    url: url,
                    error: error.message,
                    stack: error.stack
                })
            }
        }
    }
)
// 代码仅供参考、需要根据实际业务场景进行判断和使用AES加密方法
  1. 前端效果,可以看到前后端data的请求响应参数就加密完成

相关推荐
㳺三才人子3 小时前
初探 Flask
后端·python·flask·html
星栈独行3 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Java爱好狂.4 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易4 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
装不满的克莱因瓶4 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl5 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
excel6 小时前
为什么我推荐使用 Termius:现代 SSH 工具的完整体验
前端·后端
卷毛的技术笔记6 小时前
Java后端硬核实战:用Spring AI Alibaba+Redis给LLM装上“超强记忆中枢”
java·人工智能·redis·后端·spring·ai·系统架构
IT_陈寒7 小时前
Java的Optional差点让我掉坑里,这几个坑你别踩
前端·人工智能·后端
子兮曰8 小时前
Harness 驾驭工程深度教程:从 AGENTS.md 到全链路 AI 编码基础设施
前端·后端·ai编程