【技術派后端篇】技術派通用敏感詞替換:原理、實現與應用

在當今互聯網環境下,數據脫敏對于國內的互聯網企業而言已經成為一項標配。這不僅是為了滿足合規性要求,更是保障用戶信息安全和企業聲譽的重要舉措。本文將深入探討技術派中實現數據脫敏的關鍵技術——通用敏感詞替換,從算法原理到具體實現,為你呈現一個全面的技術視角。

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.pswp.cn/news/902382.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/902382.shtml
英文地址,請注明出處:http://en.pswp.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;這個請求說明了客戶端…