【技术派后端篇】技术派通用敏感词替换:原理、实现与应用

在当今互联网环境下,数据脱敏对于国内的互联网企业而言已经成为一项标配。这不仅是为了满足合规性要求,更是保障用户信息安全和企业声誉的重要举措。本文将深入探讨技术派中实现数据脱敏的关键技术——通用敏感词替换,从算法原理到具体实现,为你呈现一个全面的技术视角。

1 敏感词校验算法:DFA 算法

敏感词校验算法在数据脱敏中起着至关重要的作用。目前,相对成熟的算法有很多,其中 DFA(Deterministic Finite Automaton,确定有限自动机)算法是一种在敏感词校验等领域常用且高效的算法,其基本原理如下:

  1. 构建树状查找结构(森林)

    • 首先,基于给定的敏感词库来构建一个特殊的树状结构(严格来说,对于更完整的敏感词库构建出的结构,若不考虑根节点,是由多个树结构组成的森林)。
    • 例如,若敏感词库包含“我爱你”“我爱他”“我爱她”“我爱你呀”“我爱他呀”“我爱她呀”“我爱她啊”这些词汇。在构建树状结构时,以这些词汇的字符序列为路径来构建节点关系。从根节点开始,第一个字符作为第一层节点的分支依据,第二个字符作为下一层节点的分支依据,以此类推,直到完整的敏感词路径构建完成。这样,相同前缀的敏感词会共享前面的节点路径,形成一种层次化的树状结构。
      在这里插入图片描述
  2. 字符串遍历与匹配

    • 当有需要校验的字符串输入时,从字符串的第一个字符开始进行遍历。
    • 同时,设置一个指针指向树状结构的根节点。对于字符串中的每个字符,在树状结构中查找对应的子节点。如果能找到匹配的子节点,则将指针移动到该子节点;如果找不到,则表示从当前位置开始的字符序列不是敏感词的一部分,继续处理字符串的下一个字符,指针保持在当前位置(根节点或之前匹配到的节点)。
    • 例如,对于输入字符串“一灰我爱你呀哈哈哈”,从“一”开始遍历,在树状结构中找不到匹配的以“一”开头的敏感词分支,指针保持在根节点;接着处理“灰”,同样找不到匹配分支;当处理到“我”时,在树状结构中找到以“我”开头的敏感词分支,指针移动到对应的子节点;然后处理“爱”,继续沿着匹配的路径移动指针;当处理到“你”时,指针移动到相应节点,此时发现该节点是一个敏感词“我爱你”的结束节点,就表示找到了一个敏感词。然后继续从下一个字符“呀”开始重复上述过程,直到遍历完整个字符串。
  3. 高效性体现

    • DFA 算法的高效性在于,它通过预先构建好的树状结构,在对输入字符串进行一次遍历的过程中,就能快速确定其中是否包含敏感词以及具体的敏感词内容。相比于一些简单的逐字符匹配算法(如暴力匹配算法),不需要对每个位置都进行大量的比较操作,大大减少了时间复杂度,提高了敏感词校验的效率,尤其适用于敏感词库较大、输入字符串较长的场景。

通过以上步骤,DFA 算法实现了对输入字符串中敏感词的快速、准确校验。

2 敏感词服务类

为了在项目中更方便地使用敏感词校验功能,我们可以借助开源库。这里从 GitHub 中选取了一个 star 较多的库:https://github.com/houbb/sensitive-word 。

在技术派的项目中,使用该库的方式如下:

  1. pom.xml文件引入依赖
<dependency><groupId>com.github.houbb</groupId><artifactId>sensitive-word</artifactId><version>${sensitive.version}</version>
</dependency>
  1. 新增一个敏感词配置类,用于处理自定义的敏感词以及白名单。
/*** 敏感词相关配置,db配置表中的配置优先级更高,支持动态刷新*/
@Data
@Component
@ConfigurationProperties(prefix = "paicoding.sensitive")
public class SensitiveProperty {/*** true 表示开启敏感词校验*/private Boolean enable;/*** 自定义的敏感词*/private List<String> deny;/*** 自定义的非敏感词*/private List<String> allow;
}
  1. 结合技术派实现的配置动态变更刷新机制([✅ 技术派实现自定义配置注入与动态刷新] ),封装了支持敏感词动态变更的服务类。
/*** 敏感词服务类** @author YiHui* @date 2023/8/9*/
@Slf4j
@Service
public class SensitiveService {private SensitiveProperty sensitiveConfig;private SensitiveWordBs sensitiveWordBs;public SensitiveService(DynamicConfigContainer dynamicConfigContainer, SensitiveProperty sensitiveConfig) {this.sensitiveConfig = sensitiveConfig;dynamicConfigContainer.registerRefreshCallback(sensitiveConfig, this::refresh);}@PostConstructpublic void refresh() {IWordDeny deny = () -> {List<String> sub = WordDenySystem.getInstance().deny();sub.addAll(sensitiveConfig.getDeny());return sub;};IWordAllow allow = () -> {List<String> sub = WordAllowSystem.getInstance().allow();sub.addAll(sensitiveConfig.getAllow());return sub;};sensitiveWordBs = SensitiveWordBs.newInstance().wordDeny(deny).wordAllow(allow).init();log.info("敏感词初始化完成!");}/*** 判断是否包含敏感词** @param txt* @return*/public boolean contains(String txt) {if (BooleanUtils.isTrue(sensitiveConfig.getEnable())) {return sensitiveWordBs.contains(txt);}return false;}/*** 敏感词替换** @param txt* @return*/public String replace(String txt) {if (BooleanUtils.isTrue(sensitiveConfig.getEnable())) {return sensitiveWordBs.replace(txt);}return txt;}}

目前,敏感词校验主要应用在两个部分:

  • 派聪明的提问:对用户输入的提问内容进行敏感词检测和替换。
    在这里插入图片描述

  • 评价的敏感词替换:基于 Mybatis 的插件机制,直接针对从数据库查询出来的评价字段进行敏感词替换。

3 自定义的数据库敏感词替换方案

在实际生产项目中,为了安全和合规性,数据库中有很多信息不能存储明文,例如身份证、银行卡等敏感信息,需要加密后存储,读取时再解密返回明文。实现这一过程有两种常见方式:

  1. 直接编码实现,每次写和读取数据时手动进行加解密操作。这种方式虽然简单直接,但在实际应用中,尤其是在数据量较大、业务逻辑复杂的情况下,手动加解密容易出错且维护成本高。
  2. 实现一个通用的解决方案,在需要脱敏的字段上加一个标识,然后在实际写入数据库或从数据库读取时,自动实现加解密。我们这里重点介绍的就是第二种方案。

4 基于 mybatis 拦截器的敏感词替换实现方案

整体方案的思路较为清晰,具体步骤如下:

  1. 实现一个自定义注解,将其放置在需要脱敏的数据库实体对象的成员上。通过这个注解来标识哪些字段需要进行敏感词替换或数据脱敏处理。
  2. 实现查询拦截器,当从数据库返回内容到数据库实体对象上时,判断成员上是否有对应注解。如果有,则将该成员的值替换为敏感词替换之后的内容。

在具体实现层面,为了提高性能,我们增加了缓存机制,减少每次都对实体对象的成员进行是否需要脱敏的判定。所有相关代码可以在 com.github.paicoding.forum.core.senstive 下查看。

下面对几个关键的实现进行详细说明:

  1. 自定义注解com.github.paicoding.forum.core.senstive.ano.SensitiveField。通过这个注解来标识哪些字段需要进行敏感词替换或数据脱敏处理。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface SensitiveField {/*** 绑定的db中的哪个字段** @return*/String bind() default "";}
  1. 拦截器实现com.github.paicoding.forum.core.senstive.ibatis.SensitiveReadInterceptor 。核心步骤包括根据返回结果找到对应的实体类,并确定需要进行替换的成员;然后执行具体的敏感词替换操作。
/*** 敏感词替换拦截器,这里主要是针对从db中读取的数据进行敏感词处理 (如果需要在写入db时,进行脱敏如加密,也可以使用类似的方式来实现)*/
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {java.sql.Statement.class})
})
@Component
@Slf4j
public class SensitiveReadInterceptor implements Interceptor {private static final String MAPPED_STATEMENT = "mappedStatement";@Autowiredprivate SensitiveService sensitiveService;@SuppressWarnings("unchecked")@Overridepublic Object intercept(Invocation invocation) throws Throwable {final List<Object> results = (List<Object>) invocation.proceed();if (results.isEmpty()) {return results;}final ResultSetHandler statementHandler = realTarget(invocation.getTarget());final MetaObject metaObject = SystemMetaObject.forObject(statementHandler);final MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(MAPPED_STATEMENT);Optional firstOpt = results.stream().filter(Objects::nonNull).findFirst();if (!firstOpt.isPresent()) {return results;}Object firstObject = firstOpt.get();SensitiveObjectMeta sensitiveObjectMeta = findSensitiveObjectMeta(firstObject);replaceSensitiveResults(results, mappedStatement, sensitiveObjectMeta);return results;}/*** 执行具体的敏感词替换** @param results* @param mappedStatement* @param sensitiveObjectMeta*/private void replaceSensitiveResults(Collection<Object> results, MappedStatement mappedStatement, SensitiveObjectMeta sensitiveObjectMeta) {for (Object obj : results) {if (sensitiveObjectMeta.getSensitiveFieldMetaList() == null) {continue;}final MetaObject objMetaObject = mappedStatement.getConfiguration().newMetaObject(obj);sensitiveObjectMeta.getSensitiveFieldMetaList().forEach(i -> {Object value = objMetaObject.getValue(StringUtils.isBlank(i.getBindField()) ? i.getName() : i.getBindField());if (value == null) {return;} else if (value instanceof String) {String strValue = (String) value;String processVal = sensitiveService.replace(strValue);objMetaObject.setValue(i.getName(), processVal);} else if (value instanceof Collection) {Collection listValue = (Collection) value;if (CollectionUtils.isNotEmpty(listValue)) {Optional firstValOpt = listValue.stream().filter(Objects::nonNull).findFirst();if (firstValOpt.isPresent()) {SensitiveObjectMeta valSensitiveObjectMeta = findSensitiveObjectMeta(firstValOpt.get());if (Boolean.TRUE.equals(valSensitiveObjectMeta.getEnabledSensitiveReplace()) && CollectionUtils.isNotEmpty(valSensitiveObjectMeta.getSensitiveFieldMetaList())) {replaceSensitiveResults(listValue, mappedStatement, valSensitiveObjectMeta);}}}} else if (!ClassUtils.isPrimitiveOrWrapper(value.getClass())) {// 对于非基本类型的,需要对其内部进行敏感词替换SensitiveObjectMeta valSensitiveObjectMeta = findSensitiveObjectMeta(value);if (Boolean.TRUE.equals(valSensitiveObjectMeta.getEnabledSensitiveReplace()) && CollectionUtils.isNotEmpty(valSensitiveObjectMeta.getSensitiveFieldMetaList())) {replaceSensitiveResults(newArrayList(value), mappedStatement, valSensitiveObjectMeta);}}});}}/*** 查询对象中,携带有 @SensitiveField 的成员,进行敏感词替换** @param firstObject 待查询的对象* @return 返回对象的敏感词元数据*/private SensitiveObjectMeta findSensitiveObjectMeta(Object firstObject) {SensitiveMetaCache.computeIfAbsent(firstObject.getClass().getName(), s -> {Optional<SensitiveObjectMeta> sensitiveObjectMetaOpt = SensitiveObjectMeta.buildSensitiveObjectMeta(firstObject);return sensitiveObjectMetaOpt.orElse(null);});return SensitiveMetaCache.get(firstObject.getClass().getName());}@Overridepublic Object plugin(Object o) {return Plugin.wrap(o, this);}@Overridepublic void setProperties(Properties properties) {}public static <T> T realTarget(Object target) {if (Proxy.isProxyClass(target.getClass())) {MetaObject metaObject = SystemMetaObject.forObject(target);return realTarget(metaObject.getValue("h.target"));}return (T) target;}
}
  1. 敏感词替换元数据信息com.github.paicoding.forum.core.senstive.ibatis.SensitiveObjectMeta 。通过反射获取数据库实体对象的所有成员,判断是否有自定义注解 SensitiveField ,如果有则记录相关信息,以便后续进行替换操作。
/*** 敏感词相关配置,db配置表中的配置优先级更高,支持动态刷新*/
@Data
public class SensitiveObjectMeta {private static final String JAVA_LANG_OBJECT = "java.lang.object";/*** 是否启用脱敏*/private Boolean enabledSensitiveReplace;/*** 类名*/private String className;/*** 标注 SensitiveField 的成员*/private List<SensitiveFieldMeta> sensitiveFieldMetaList;public static Optional<SensitiveObjectMeta> buildSensitiveObjectMeta(Object param) {if (isNull(param)) {return Optional.empty();}Class<?> clazz = param.getClass();SensitiveObjectMeta sensitiveObjectMeta = new SensitiveObjectMeta();sensitiveObjectMeta.setClassName(clazz.getName());List<SensitiveFieldMeta> sensitiveFieldMetaList = newArrayList();sensitiveObjectMeta.setSensitiveFieldMetaList(sensitiveFieldMetaList);boolean sensitiveField = parseAllSensitiveFields(clazz, sensitiveFieldMetaList);sensitiveObjectMeta.setEnabledSensitiveReplace(sensitiveField);return Optional.of(sensitiveObjectMeta);}private static boolean parseAllSensitiveFields(Class<?> clazz, List<SensitiveFieldMeta> sensitiveFieldMetaList) {Class<?> tempClazz = clazz;boolean hasSensitiveField = false;while (nonNull(tempClazz) && !JAVA_LANG_OBJECT.equalsIgnoreCase(tempClazz.getName())) {for (Field field : tempClazz.getDeclaredFields()) {SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);if (nonNull(sensitiveField)) {SensitiveFieldMeta sensitiveFieldMeta = new SensitiveFieldMeta();sensitiveFieldMeta.setName(field.getName());sensitiveFieldMeta.setBindField(sensitiveField.bind());sensitiveFieldMetaList.add(sensitiveFieldMeta);hasSensitiveField = true;}}tempClazz = tempClazz.getSuperclass();}return hasSensitiveField;}@Datapublic static class SensitiveFieldMeta {/*** 默认根据字段名,找db中同名的字段*/private String name;/*** 绑定的数据库字段别名*/private String bindField;}
}
  1. 敏感词替换com.github.paicoding.forum.core.senstive.ibatis.SensitiveReadInterceptor#replaceSensitiveResults ,这是具体执行敏感词替换的方法。
/*** 执行具体的敏感词替换** @param results* @param mappedStatement* @param sensitiveObjectMeta*/
private void replaceSensitiveResults(Collection<Object> results, MappedStatement mappedStatement, SensitiveObjectMeta sensitiveObjectMeta) {for (Object obj : results) {if (sensitiveObjectMeta.getSensitiveFieldMetaList() == null) {continue;}final MetaObject objMetaObject = mappedStatement.getConfiguration().newMetaObject(obj);sensitiveObjectMeta.getSensitiveFieldMetaList().forEach(i -> {Object value = objMetaObject.getValue(StringUtils.isBlank(i.getBindField()) ? i.getName() : i.getBindField());if (value == null) {return;} else if (value instanceof String) { // 字符串类型,直接进行替换String strValue = (String) value;String processVal = sensitiveService.replace(strValue);objMetaObject.setValue(i.getName(), processVal);} else if (value instanceof Collection) { // 集合类型,需要对集合中的每个元素进行替换Collection listValue = (Collection) value;if (CollectionUtils.isNotEmpty(listValue)) {Optional firstValOpt = listValue.stream().filter(Objects::nonNull).findFirst();if (firstValOpt.isPresent()) {SensitiveObjectMeta valSensitiveObjectMeta = findSensitiveObjectMeta(firstValOpt.get());if (Boolean.TRUE.equals(valSensitiveObjectMeta.getEnabledSensitiveReplace()) && CollectionUtils.isNotEmpty(valSensitiveObjectMeta.getSensitiveFieldMetaList())) {replaceSensitiveResults(listValue, mappedStatement, valSensitiveObjectMeta);}}}} else if (!ClassUtils.isPrimitiveOrWrapper(value.getClass())) {// 对于非基本类型的,需要对其内部进行敏感词替换SensitiveObjectMeta valSensitiveObjectMeta = findSensitiveObjectMeta(value);if (Boolean.TRUE.equals(valSensitiveObjectMeta.getEnabledSensitiveReplace()) && CollectionUtils.isNotEmpty(valSensitiveObjectMeta.getSensitiveFieldMetaList())) {replaceSensitiveResults(newArrayList(value), mappedStatement, valSensitiveObjectMeta);}}});}
  1. 敏感词缓存com.github.paicoding.forum.core.senstive.ibatis.SensitiveMetaCache,为了提高性能,增加了缓存机制,减少每次都对实体对象的成员进行是否需要脱敏的判定。
/*** 敏感词缓存*/
public class SensitiveMetaCache {private static ConcurrentHashMap<String, SensitiveObjectMeta> CACHE = new ConcurrentHashMap<>();public static SensitiveObjectMeta get(String key) {return CACHE.get(key);}public static void put(String key, SensitiveObjectMeta meta) {CACHE.put(key, meta);}public static void remove(String key) {CACHE.remove(key);}public static boolean contains(String key) {return CACHE.containsKey(key);}public static SensitiveObjectMeta putIfAbsent(String key, SensitiveObjectMeta meta) {return CACHE.putIfAbsent(key, meta);}public static SensitiveObjectMeta computeIfAbsent(String key, Function<String, SensitiveObjectMeta> function) {return CACHE.computeIfAbsent(key, function);}
}

5 实际效果与白名单机制

在实际生产环境中,敏感词会被替换为 * 号。例如,当数据库中存储的敏感词被检测到时,会进行相应的替换。同时,我们还实现了敏感词白名单机制,添加白名单中的词汇不会被当作敏感词处理。白名单的动态维护可以在后台进行全局配置(只有管理员有权限操作),添加白名单后会立即生效,实际效果符合预期。
在这里插入图片描述
在这里插入图片描述

6 小结

本文详细介绍了技术派中通用敏感词替换的相关技术,包括敏感词校验的 DFA 算法、敏感词服务类的使用、基于 Mybatis 拦截器的自定义数据库脱敏方案等。这些知识点在实际项目中具有很高的实用价值,虽然通常由公司的基础部门负责支撑,且只有新开项目才有机会亲自实践,但作为合格的研发人员,我们应该多思考如何将一些直接代码实现的场景抽象为通用的基础服务能力,同时要时刻牢记安全与合规的重要性,确保自己的系统具备足够的安全性,防止被恶意攻击。

7 参考链接

  1. 技术派通用敏感词替换
  2. 技术派自定义配置注入&动态刷新
  3. 项目仓库(GitHub)
  4. 项目仓库(码云)
  5. 分支:origin/feature/sensitive_word

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

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

相关文章

Android RK356X TVSettings USB调试开关

Android RK356X TVSettings USB调试开关 平台概述操作-打开USB调试实现源码补充说明 平台 RK3568 Android 11 概述 RK3568 是瑞芯微&#xff08;Rockchip&#xff09;推出的一款高性能处理器&#xff0c;支持 USB OTG&#xff08;On-The-Go&#xff09;和 USB Host 功能。US…

Microsoft Edge for linux debian

下载地址 https://www.microsoft.com/en-us/edge/download?formMA13FJ 安装 # 下载安装包 wget https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/microsoft-edge-stable_135.0.3179.85-1_amd64.deb?brandM102 # 安装 sudo dpkg -i microsoft…

typedef MVS_API CLISTDEF0IDX(ViewScore, IIndex) ViewScoreArr;

查找 MVS_API 定义 我们没有在 List.h 文件中找到 MVS_API 的定义。MVS_API 很可能在 MVS 库的其他地方定义。一般来说&#xff0c;MVS_API 是控制 OpenMVS 库导入导出的宏&#xff0c;通常会出现在 MVS 的头文件中。为了回答这个问题&#xff0c;我可以提供 MVS 代码中常见的…

5.4/Q1,GBD数据库最新文章解读

文章题目&#xff1a;The global burden of high BMI among adolescents between 1990 and 2021 DOI&#xff1a;10.1038/s43856-025-00838-2 中文标题&#xff1a;1990 年至 2021 年青少年高 BMI 的全球负担 发表杂志&#xff1a;Commun Med 影响因子&#xff1a;1区&#xff…

【形式化验证基础】活跃属性Liveness Property和安全性质(Safety Property)介绍

文章目录 一、Liveness Property1、概念介绍2、形式化定义二、Safety Property1. 定义回顾2. 核心概念解析3. 为什么强调“有限前缀”4. 示例说明4.1 示例1:交通信号灯系统4.2 示例2:银行账户管理系统5. 实际应用的意义三. 总结一、Liveness Property 1、概念介绍 在系统的…

Redis面试——常用命令

一、String &#xff08;1&#xff09;设置值相关命令 1.1.1 SET 功能&#xff1a;设置一个键值对&#xff0c;如果键已存在则覆盖旧值语法&#xff1a; SET key value [EX seconds] [PX milliseconds] [NX|XX]EX seconds&#xff1a;设置键的过期时间为 seconds 秒 PX milli…

【Unity】使用Cinemachine+CharacterController实现第三人称视角下的角色视角、移动和跳跃控制

1.初始配置 安装Cinemachine插件给角色添加CharacterConroller创建Cinemachine-->Free Look Camera在Free Look Camera中调整参数&#xff0c;Y Axis勾选Inver&#xff0c;X Axis取消勾选InverFree Look Camera要看向角色 跟随角色&#xff08;自行设置&#xff0c;我就不…

深入理解 DML 和 DQL:SQL 数据操作与查询全解析

深入理解 DML 和 DQL&#xff1a;SQL 数据操作与查询全解析 在数据库管理中&#xff0c;SQL&#xff08;结构化查询语言&#xff09;是操作和查询数据的核心工具。其中&#xff0c;DML&#xff08;Data Manipulation Language&#xff0c;数据操作语言&#xff09; 和 DQL&…

MongoDB数据库的安装到入门使用详细讲解

本篇文章主要讲解MongoDB的安装使用教程及基础的数据库管理和操作能力的讲解,通过本篇文章您可以快速的掌握对MongDB数据库的基本认识及,基础开发能力。 一、MongoDB介绍 MongoDB是一款免费开源的非关系型数据库,该数据库适应于复杂关系的存储和管理,非常适合数据结构复杂…

git提交实现文件或目录忽略

前言 开发中使用git下载项目代码开发,存在不需要提交文件或目录&#xff0c;这里记录下ideajava项目开发添加以下配置可忽略不需要提交文件,以方便我们提交代码时&#xff0c;查看及提交文件只涉及项目代码修改文件。 git提交实现文件或目录忽略 .gitignore 文件的内容列出了在…

go语言的八股文

1.go语言触发异常的场景有哪些 运行时错误 1.空指针解引用&#xff1a;尝试访问一个未初始化的指针指向的内存&#xff0c;会导致程序崩溃并触发异常。 2.数组越界访问&#xff1a;试图访问数组中不存在的索引&#xff0c;比如数组长度为5&#xff0c;却尝试访问索引为10的元素…

Ubuntu安装MySQL步骤及注意事项

一、安装前准备 1. 系统更新&#xff1a;在安装 MySQL 之前&#xff0c;确保你的 Ubuntu 系统软件包是最新的&#xff0c;这能避免因软件包版本问题导致的安装错误&#xff0c;并获取最新的安全补丁。打开终端&#xff0c;执行以下两条命令&#xff1a; sudo apt update sudo …

【愚公系列】《Python网络爬虫从入门到精通》054-Scrapy 文件下载

&#x1f31f;【技术大咖愚公搬代码&#xff1a;全栈专家的成长之路&#xff0c;你关注的宝藏博主在这里&#xff01;】&#x1f31f; &#x1f4e3;开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主&#xff01; &#x1f…

2025最新︱中国信通院静态应用程序安全测试(SAST)工具能力评估,悬镜安全灵脉AI通过评估!

背景 研发运营安全&#xff08;DevSecOps&#xff09;从研发运营&#xff08;DevOps&#xff09;的概念延伸和演变而来&#xff0c;其核心理念是将安全贯穿从开发到运营的软件开发生命周期的每一个环节&#xff0c;在每个阶段自动实施安全措施&#xff0c;从而实现快速开发交付…

辛格迪客户案例 | 浙江高跖医药委托生产质量管理协同(OWL MAH)项目

一、案例概述 浙江高跖医药科技股份有限公司是一家集“研、产、销”为一体的专业化药品持证企业。高跖医药自成立之初就建立并运行着一套相对完善的质量管理体系&#xff0c;涵盖了药品的研发、生产监管及销售。高跖医药于2022年选择实施了辛格迪的“委托生产质量管理协同解决…

【NLP 65、实践 ⑯ 基于Agent优化文章】

羁绊由我而起&#xff0c;痛苦也由我承担 —— 25.4.18 一、⭐【核心函数】定义大模型调用函数 call_large_model prompt&#xff1a;用户传入的提示词&#xff08;如 “请分析这篇作文的主题”&#xff09;&#xff0c;指导模型执行任务 client&#xff1a;Zhipu…

【锂电池SOH估计】BP神经网络锂电池健康状态估计,锂电池SOH估计(Matlab完整源码和数据)

目录 效果一览程序获取程序内容研究内容基于BP神经网络的锂电池健康状态估计研究摘要关键词1. 引言1.1 研究背景1.2 研究意义1.3 研究目标2. 文献综述2.1 锂电池SOH估计理论基础2.2 传统SOH估计方法2.3 基于BP神经网络的SOH估计研究进展2.4 研究空白与创新点3. BP神经网络原理3…

2025第十六届蓝桥杯python B组满分题解(详细)

目录 前言 A: 攻击次数 解题思路&#xff1a; 代码&#xff1a; B: 最长字符串 解题思路&#xff1a; 代码&#xff1a; C: LQ图形 解题思路&#xff1a; 代码&#xff1a; D: 最多次数 解题思路&#xff1a; 代码&#xff1a; E: A * B Problem 解题思路&…

第十二节:原理深挖-React Fiber架构核心思想

链表结构、时间切片&#xff08;Time Slicing&#xff09; 优先级调度实现&#xff08;如用户输入>网络请求&#xff09; React Fiber架构深度解析&#xff1a;从链表到优先级调度的革命性升级 一、Fiber架构核心设计思想 React Fiber是React 16的底层协调算法重构&#x…

你学会了些什么211201?--http基础知识

概念 HTTP–Hyper Text Transfer Protocol&#xff0c;超文本传输协议&#xff1b;是一种建立在TCP上的无状态连接&#xff08;短连接&#xff09;。 整个基本的工作流程是&#xff1a;客户端发送一个HTTP请求&#xff08;Request &#xff09;&#xff0c;这个请求说明了客户端…