MyBatis参数加解密

news/2025/12/8 21:58:50/文章来源:https://www.cnblogs.com/ciel717/p/19316864

一、概述

MyBatis中通过拦截器实现SQL参数加密/结果集解密是数据安全的常见场景,核心是拦截参数处理环节(加密入参)和结果集处理环节(解密出参)。

适配Spring Boot3 + MyBatis环境。

核心思路

拦截点 作用 拦截接口 拦截方法
参数加密 执行SQL前加密敏感参数 ParameterHandler setParameters(PreparedStatement)
结果集解密 查询后解密敏感字段 ResultSetHandler handleResultSets(Statement)

二、实现步骤

2.1 定义加密注解

通过自定义注解标记实体类中需要加密/解密的字段,让拦截器识别目标字段:

import java.lang.annotation.*;/*** 敏感字段加解密注解*/
@Target(ElementType.FIELD) // 作用于字段
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Documented
public @interface EncryptField {// 可扩展:指定加密算法类型,默认 AESString algorithm() default "AES";
}import java.lang.annotation.*;/*** 标记需要解密的字段*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {// 可扩展:指定加密算法类型,默认AESString algorithm() default "AES";
}

2.2 加密工具类

封装通用的加密/解密方法(实际项目建议使用更安全的算法,如RSA+AES混合):

import org.apache.commons.codec.binary.Base64;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;/*** 加解密工具类(AES示例)*/
public class EncryptUtils {// 密钥(生产环境需从配置中心/环境变量读取,避免硬编码)private static final String AES_KEY = "1234567890123456"; //AES密钥长度必须16/24/32位private static final String AES_ALGORITHM = "AES";/*** AES加密*/public static String aesEncrypt(String content) {if (content == null || content.isEmpty()) {return content;}try {KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");secureRandom.setSeed(AES_KEY.getBytes());keyGenerator.init(128, secureRandom);SecretKey secretKey = keyGenerator.generateKey();byte[] enCodeFormat = secretKey.getEncoded();SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, AES_ALGORITHM);Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);byte[] encryptedBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));return Base64.encodeBase64String(encryptedBytes);} catch (Exception e) {throw new RuntimeException("AES加密失败", e);}}/*** AES解密*/public static String aesDecrypt(String encryptedContent) {if (encryptedContent == null || encryptedContent.isEmpty()) {return encryptedContent;}try {byte[] encryptedBytes = Base64.decodeBase64(encryptedContent);KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");secureRandom.setSeed(AES_KEY.getBytes());keyGenerator.init(128, secureRandom);SecretKey secretKey = keyGenerator.generateKey();byte[] enCodeFormat = secretKey.getEncoded();SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, AES_ALGORITHM);Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);byte[] decryptedBytes = cipher.doFinal(encryptedBytes);return new String(decryptedBytes, StandardCharsets.UTF_8);} catch (Exception e) {throw new RuntimeException("AES解密失败", e);}}
}

2.3 加密拦截器

拦截ParameterHandlersetParameters方法,对标记@EncryptField的字段加密:


import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Properties;/*** 参数加密拦截器:拦截入参,对@EncryptField字段加密*/
@Intercepts({@Signature(type = ParameterHandler.class,method = "setParameters",args = {PreparedStatement.class})
})
public class EncryptParameterInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 1. 获取ParameterHandlerParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();// 2. 通过反射获取参数对象MetaObject metaObject = SystemMetaObject.forObject(parameterHandler);Object parameterObject = metaObject.getValue("parameterObject");if (parameterObject == null) {return invocation.proceed();}// 3. 遍历参数对象的字段,对@EncryptField字段加密encryptField(parameterObject);// 4. 执行原方法return invocation.proceed();}/*** 加密对象中标记@EncryptField的字段*/private void encryptField(Object obj) throws IllegalAccessException {if (obj == null) {return;}Class<?> clazz = obj.getClass();// 处理基本类型包装类/字符串(若参数是单个值)if (obj instanceof String) {return; // 单个字符串参数需特殊处理,此处示例针对实体类}// 遍历所有字段Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {// 检查是否有@EncryptField注解if (field.isAnnotationPresent(EncryptField.class)) {field.setAccessible(true);Object value = field.get(obj);if (value instanceof String) {// 加密字段值String encryptedValue = EncryptUtils.aesEncrypt((String) value);field.set(obj, encryptedValue);}}}}@Overridepublic Object plugin(Object target) {// 仅对ParameterHandler类型创建代理if (target instanceof ParameterHandler) {// 生成代理对象return Plugin.wrap(target, this);}return target;}@Overridepublic void setProperties(Properties properties) {// 可配置拦截器参数,此处暂不处理}
}

2.4 解密拦截器

拦截ResultSetHandlerhandleResultSets方法,对标记@DecryptField的字段解密:

import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;/*** 结果集解密拦截器:拦截出参,对@DecryptField字段解密*/
@Intercepts({@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class})
})
public class DecryptResultSetInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 1. 执行原方法,获取结果集Object result = invocation.proceed();if (result == null) {return null;}// 2. 对结果集解密(支持单对象/列表)if (result instanceof List) {List<?> list = (List<?>) result;for (Object obj : list) {decryptField(obj);}} else {decryptField(result);}return result;}/*** 解密对象中标记@DecryptField的字段*/private void decryptField(Object obj) throws IllegalAccessException {if (obj == null) {return;}Class<?> clazz = obj.getClass();Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(DecryptField.class)) {field.setAccessible(true);Object value = field.get(obj);if (value instanceof String) {// 解密字段值String decryptedValue = EncryptUtils.aesDecrypt((String) value);field.set(obj, decryptedValue);}}}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 可配置拦截器参数}
}

2.5 注册拦截器

Spring Boot 3 配置拦截器

将拦截器注册到MyBatisSqlSessionFactory中:

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import javax.sql.DataSource;@Configuration
@MapperScan("com.yourpackage.mapper") // 替换Mapper包路径
public class MyBatisConfig {/*** 注册参数加密拦截器*/@Beanpublic EncryptParameterInterceptor encryptParameterInterceptor() {return new EncryptParameterInterceptor();}/*** 注册结果集解密拦截器*/@Beanpublic DecryptResultSetInterceptor decryptResultSetInterceptor() {return new DecryptResultSetInterceptor();}/*** 配置SqlSessionFactory,添加拦截器*/@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource,EncryptParameterInterceptor encryptInterceptor,DecryptResultSetInterceptor decryptInterceptor) throws Exception {SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();sessionFactoryBean.setDataSource(dataSource);// 加载Mapper.xml(可选,注解版Mapper可省略)sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml"));// 核心:注册拦截器sessionFactoryBean.setPlugins(encryptInterceptor, decryptInterceptor);// 可选:MyBatis全局配置org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();configuration.setMapUnderscoreToCamelCase(true); // 下划线转驼峰sessionFactoryBean.setConfiguration(configuration);return sessionFactoryBean.getObject();}
}

2.6 实战使用

在实体类中标记需要加密/解密的字段:

public class User {private Long id;private String userName;// 加密存储:入参加密,数据库存密文@EncryptField@DecryptField // 查询时解密private String phone;// 加密存储:入参加密,数据库存密文@EncryptField@DecryptFieldprivate String idCard;// getter/setter 省略
}

三、关键注意事项

3.1 性能优化

  • 避免递归处理大量嵌套对象,可限制递归深度;
  • 拦截器仅对标记注解的字段处理,减少反射开销;
  • 加密算法选择:AES性能优于RSA,建议敏感字段用AES加密,密钥用RSA加密存储。

3.2 特殊场景处理

  • 批量操作:拦截器已兼容List类型结果集,批量插入/查询无需额外处理;
  • 参数为Map:需修改encryptField方法,支持遍历Mapkey-value(判断value是否为标记注解的实体);
  • 空值处理:加密工具类已兼容空值,避免空指针。

3.3 安全增强

  • 加密密钥不要硬编码,建议从application.yml读取(可结合Spring Cloud Config/阿里云KMS);
  • 生产环境禁用ECB模式,改用CBC/GCM模式(需添加初始化向量IV);
  • 对加密后的字段建立索引时,需用密文查询(拦截器会自动加密入参,无需手动处理)。

3.4 调试技巧

  • 在拦截器的intercept方法中加日志/断点,确认参数是否加密、结果集是否解密;
  • 开启MyBatis SQL日志,查看执行的SQL参数是否为密文。

四、扩展场景

4.1 多算法支持

在注解中指定加密算法(如@EncryptField(algorithm="AES"));

封装支持多种算法的加密/解密方法,统一入口,根据算法标识分发逻辑:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;/*** 多算法加密工具类*/
public class MultiAlgEncryptUtils {// 算法密钥配置(实际项目建议从配置中心读取,此处为示例)private static final Map<String, String> ALG_KEY_MAP = new HashMap<>();static {ALG_KEY_MAP.put("AES", "1234567890123456"); // AES-128密钥(16位)ALG_KEY_MAP.put("SM4", "8765432109876543"); // SM4密钥(16位)ALG_KEY_MAP.put("RSA", "MIGfMA0GCSqGSIb3DQEBBQUAA4GNADCBiQKBgQC..."); // RSA公钥(示例)}// 加密函数映射(算法标识 -> 加密逻辑)private static final Map<String, BiFunction<String, String, String>> ENCRYPT_FUNC = new HashMap<>();// 解密函数映射(算法标识 -> 解密逻辑)private static final Map<String, BiFunction<String, String, String>> DECRYPT_FUNC = new HashMap<>();static {// 注册AES算法ENCRYPT_FUNC.put("AES", MultiAlgEncryptUtils::aesEncrypt);DECRYPT_FUNC.put("AES", MultiAlgEncryptUtils::aesDecrypt);// 注册SM4算法ENCRYPT_FUNC.put("SM4", MultiAlgEncryptUtils::sm4Encrypt);DECRYPT_FUNC.put("SM4", MultiAlgEncryptUtils::sm4Decrypt);// 注册RSA算法(示例,实际需处理密钥对)ENCRYPT_FUNC.put("RSA", MultiAlgEncryptUtils::rsaEncrypt);DECRYPT_FUNC.put("RSA", MultiAlgEncryptUtils::rsaDecrypt);}/*** 统一加密入口* * @param content 待加密内容* @param algorithm 加密算法* @return 加密后内容(Base64编码)*/public static String encrypt(String content, String algorithm) {if (content == null || content.isEmpty()) return content;// 校验算法是否支持if (!ENCRYPT_FUNC.containsKey(algorithm)) {throw new UnsupportedOperationException("不支持的加密算法:" + algorithm);}// 获取对应算法密钥(可通过config参数扩展密钥选择)String key = ALG_KEY_MAP.get(algorithm);// 执行加密return ENCRYPT_FUNC.get(algorithm).apply(content, key);}/*** 统一解密入口* * @param content 待解密内容(Base64编码)* @param algorithm 解密算法* @return 解密后内容*/public static String decrypt(String content, String algorithm) {if (content == null || content.isEmpty()) return content;if (!DECRYPT_FUNC.containsKey(algorithm)) {throw new UnsupportedOperationException("不支持的解密算法:" + algorithm);}String key = ALG_KEY_MAP.get(algorithm);return DECRYPT_FUNC.get(algorithm).apply(content, key);}// ------------------------------ 各算法具体实现 ------------------------------/*** AES加密(ECB模式,PKCS5Padding填充)*/private static String aesEncrypt(String content, String key) {try {SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");cipher.init(Cipher.ENCRYPT_MODE, keySpec);byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(encrypted);} catch (Exception e) {throw new RuntimeException("AES加密失败", e);}}/*** AES解密*/private static String aesDecrypt(String content, String key) {try {SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");cipher.init(Cipher.DECRYPT_MODE, keySpec);byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(content));return new String(decrypted, StandardCharsets.UTF_8);} catch (Exception e) {throw new RuntimeException("AES解密失败", e);}}/*** SM4加密(ECB模式,PKCS5Padding填充)*/private static String sm4Encrypt(String content, String key) {try {// 实际项目需引入SM4算法依赖(如BouncyCastle)SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "SM4");Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding");cipher.init(Cipher.ENCRYPT_MODE, keySpec);byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(encrypted);} catch (Exception e) {throw new RuntimeException("SM4加密失败", e);}}/*** SM4解密*/private static String sm4Decrypt(String content, String key) {try {SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "SM4");Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding");cipher.init(Cipher.DECRYPT_MODE, keySpec);byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(content));return new String(decrypted, StandardCharsets.UTF_8);} catch (Exception e) {throw new RuntimeException("SM4解密失败", e);}}/*** RSA加密(示例,实际需使用公钥加密,私钥解密)*/private static String rsaEncrypt(String content, String publicKey) {// 实际实现需解析公钥、处理分块加密(RSA加密长度有限制)try {// 简化示例:直接返回Base64编码的内容(实际需替换为真实RSA加密逻辑)return Base64.getEncoder().encodeToString((content + "_RSA_ENC").getBytes(StandardCharsets.UTF_8));} catch (Exception e) {throw new RuntimeException("RSA加密失败", e);}}/*** RSA解密*/private static String rsaDecrypt(String content, String privateKey) {try {// 简化示例:直接解码并去除加密标识String decrypted = new String(Base64.getDecoder().decode(content), StandardCharsets.UTF_8);return decrypted.replace("_RSA_ENC", "");} catch (Exception e) {throw new RuntimeException("RSA解密失败", e);}}
}

改造参数加密拦截器

 /*** 递归加密字段,根据注解指定的算法选择加密方式*/
private void encryptField(Object obj) throws IllegalAccessException {if (obj == null) return;Class<?> clazz = obj.getClass();// 跳过基础类型和字符串if (clazz.isPrimitive() || clazz == String.class || obj instanceof Number) {return;}Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {field.setAccessible(true);// 仅处理标记@EncryptField的字段if (field.isAnnotationPresent(EncryptField.class)) {EncryptField annotation = field.getAnnotation(EncryptField.class);String algorithm = annotation.algorithm(); // 获取注解指定的算法Object value = field.get(obj);if (value != null && value instanceof String) {// 调用多算法工具类加密String encryptedValue = MultiAlgEncryptUtils.encrypt((String) value, algorithm);field.set(obj, encryptedValue);}}// 递归处理嵌套对象encryptField(field.get(obj));}
}

4.2 其他

  • 动态加密字段:通过配置文件指定需要加密的字段,避免硬编码注解;
  • 脱敏+加密:解密后对敏感字段脱敏(如手机号显示为138****1234)。

通过以上实现,即可在MyBatis中全自动完成SQL参数加密和结果集解密,无需在业务代码中手动处理加密逻辑,保证数据安全的同时降低耦合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/993610.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Hadoop+数据可视化+机器学习随机森林预测算法+智能AI大模型+协同过滤推荐算法的青少年饮食习惯数据分析与可视化平台的设计与实现(精品源码+精品论文+上万材料集+答辩PPT)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

PyTorch推理扩展实战:用Ray Data轻松实现多机多卡并行

单机 PyTorch 模型跑推理没什么问题,但数据量一旦上到万级、百万级,瓶颈就暴露出来了:内存不够、GPU 利用率低、I/O 拖后腿,更别说还要考虑容错和多机扩展。 传统做法是自己写多线程 DataLoader、管理批次队列、手…

成膜助剂出口厂商有哪些?有出口资质的成膜助剂供应商名单推荐

成膜助剂作为涂料、胶粘剂等行业的核心功能性辅料,其供应稳定性、产品质量及出口服务能力直接影响下游产业发展。2025年,随着环保政策升级与国际贸易格局优化,具备出口资质、合规性强的成膜助剂厂商与贸易公司愈发受…

CF1994G

CF1994G因为异或每个位是独立的,只需要处理进位即可。 因此,考虑从低到高枚举每一位,记录进位个数,令 \(dp(i, j)\) 表示到第 \(i\) 位,进了 \(j\) 位的方案数。暴力枚举 \(0/1\) 转移即可。 时间复杂度:\(O(nk)…

2025婴儿车性价比排行榜首选:UPPAbaby MINU V3如何以轻便全能理念重新定义价值标准(附权威认证)

一、轻便婴儿车市场的价值认知革新 根据《2024中国母婴消费白皮书》(中国家庭教育学会发布)数据显示,在选购婴儿车时,87.3%的消费者将"轻便性"列为首要考量因素,但同时也有76.8%的用户担心轻便设计会牺…

陈阅视觉摄影培训机构发展历程

陈阅视觉摄影培训机构,成立于2009年,10多年来,陈阅视觉秉承“让摄影更加简单”的办学使命。 引进国外先进视觉传达课程体系,并结合国内特点,独立研发PLTA摄影教学系统,大力培养摄影与视频制作人才,有力推动国内…

hive ddl dml hivesql命令大全

SELECT T.sname,T.ctfid,T.gender,t.address,count(*) OVER(PARTITION BY T.sname) AS FM_CNTFROM test_db.room3 T WHERE T.address like "%北京%" AND instr(T.ctfid,310)>0 ORDER BY FM_CNT DESC;sel…

杭州刑事案件法律咨询找谁?刑事律师推荐

遇到刑事案件,时间就是生命。杭州刑事案件法律咨询找谁?今天给大家推荐一家专业的刑事辩护律所。 刑事案件为什么要尽早找律师? 黄金37天很关键,刑事拘留期:最长37天这个阶段律师能做什么:会见当事人了解案情;申…

【AI】第一篇:语言模型的前世 n-gram的简单介绍

1. N-gram 是什么?核心逻辑与“N”的含义 N-gram 是自然语言处理(NLP)中一种基于统计的语言模型,其核心思想是:一个词的出现概率,可以由它前面 N-1 个词来预测。它把文本按照连续的 N 个词(或字符)切分成片段(…

【12.11 直播】时序数据库 IoTDB FAQ 全面解答|下一期聊什么?你来决定!

集中解答你最想知道的问题,你还想了解什么?请告诉我们!🤔你在用时序数据库 IoTDB 时,是否也遇到过这些“灵魂拷问”: 🚀为什么我的查询无法执行? 💡树模型、表模型到底该如何选? 🔑如何与现有系统进行高…

12/8

今天满课,全是专业课,写了个Java系统,后端打不开,气死我了,晚上还要写统一建模语言

洛谷 P8189

洛谷 P8189有 \(n\) 个礼物分配给 \(n\) 个人,第 \(i\) 个人原本拥有第 \(i\) 个礼物,每个人都要一个喜欢程度的列表,现在他们可以交换礼物,但每个人最后得到的礼物的喜欢程度不能低于原本的礼物。 \(T\) 组询问,…

计算机毕业设计springboot图书销售框架设计与构建 基于 SpringBoot 的在线书城营销平台构建与实战 SpringBoot 驱动的数字化图书商城系统研发

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

12月8日

你永远无法想象未来会发生什么!

2025常州会计师事务所实力榜:汇丰所以审计创新与税务筹划优势领跑,江苏八城专业服务机构深度解析

2025常州会计师事务所实力榜:汇丰所以审计创新与税务筹划优势领跑,江苏八城专业服务机构深度解析 在长三角经济一体化与产业升级的宏观背景下,江苏省内企业的财务合规、战略咨询与风险管理需求日益精细化、专业化。…

题解:P14666 [KenOI 2025] 游走题

很好的数数题。 思路 观察样例,猜一个结论:游走的终点只可能是节点 \(1\)。考虑证明,一个节点如果往儿子走最终显然是可以再走回父亲的,但如果走到了父亲就不能再走回去了。所以只有走到一个没有父亲的点且把这个点…

你在用什么免费ip库?

事情是这样的,这几天我们站一直被不明ip频繁抓取数据! 6bdd3bee5195c13f7f6b00464f4fcda7 看了下都是越南的ip,你们遇到这种情况是怎么处理的? 导致我们的ip138经常使用过量。 老板让我找免费的ip库,找来找去找到了…

Python核心容器类型教程:列表、字典、元组、集合用法与实战

容器类型是Python处理数据的核心工具,掌握列表(List)、字典(Dict)、元组(Tuple)、集合(Set)这四类核心容器,能让你高效存储、遍历、操作数据——从简单的数据分析到复杂的业务逻辑开发,都离不开这些基础容器…

doc-llm-autotest 基于大模型的文档自动化测试平台:worker服务的可靠性增强

一、可靠性分析 从架构图上,我们可以看出worker调用大模型服务过程中,会发生阻塞等待,如果此时worker异常容器挂掉了,那么此次任务状态会一直为processing,并且因为redis关联task_id的消息已经被消费了,那么这个…

个人电脑本地私有知识库:访答知识库深度解析

个人电脑本地私有知识库:访答知识库深度解析 什么是本地私有知识库? 本地私有知识库是一种将知识数据存储在个人电脑本地的管理工具,与云端存储不同,它提供了更高的数据安全性和隐私保护。用户可以在不依赖网络的情…