当前位置: 首页 > news >正文

Web安全实战:用户输入处理中的转义、验证与清理机制详解

最近在开发一个需要处理用户上传内容的项目时,遇到了一个棘手的问题:如何安全、高效地处理用户提交的文本,特别是当文本中包含一些特殊字符或潜在风险内容时。这让我深入研究了字符串处理中的转义、验证和清理机制。本文将围绕这个主题,分享一套从基础概念到实战落地的完整解决方案,涵盖核心原理、代码实现、常见陷阱以及生产环境的最佳实践。无论你是刚接触Web安全的新手,还是需要优化现有过滤逻辑的开发者,都能从中找到可复用的思路和代码。

1. 背景与核心概念:为什么需要处理特殊内容?

在日常的Web开发中,用户输入是不可控的。用户可能会无意或有意地输入一些包含特殊字符的文本,例如HTML标签(<script>)、SQL片段(' OR '1'='1)、文件路径(../../../etc/passwd)等。如果不对这些输入进行处理,直接用于渲染页面、拼接SQL或执行系统命令,就会导致严重的安全漏洞,如跨站脚本攻击(XSS)、SQL注入、路径遍历等。

因此,“处理特殊内容”的核心目标有两个:

  1. 安全性:防止恶意输入破坏应用逻辑、窃取数据或攻击服务器。
  2. 功能性:确保用户输入的内容能够按照预期正确显示和存储,不会因为特殊字符而出现乱码、格式错误或功能异常。

这里需要区分几个容易混淆的概念:

  • 转义(Escaping):将字符转换为另一种形式,使其失去原有的特殊含义。例如,将HTML中的<转义为&lt;,这样浏览器就不会将其解析为标签的开始。
  • 编码(Encoding):将数据从一种形式转换为另一种形式,通常是为了传输或存储。例如,URL编码将空格转为%20
  • 验证(Validation):检查输入是否符合预期的格式、类型、长度等规则。例如,验证邮箱地址是否包含“@”。
  • 清理/过滤(Sanitization/Filtering):直接移除或替换输入中不安全的部分。例如,移除所有HTML标签,只保留纯文本。

一个重要的原则是:永远不要相信用户输入。处理应该在多个层面进行:前端做初步验证提升用户体验,后端做严格校验和转义保证安全,数据库层使用参数化查询防止注入。

2. 环境准备与版本说明

本文的示例代码将主要使用PythonJava (Spring Boot)两种常见的后端语言进行演示,同时会涉及前端(HTML/JavaScript)和数据库(SQL)的相关知识。你可以根据你的技术栈选择对应的部分进行参考。

环境与版本建议:

  • Python环境
    • 语言版本:Python 3.8+
    • 关键库:html(标准库),bleach(用于HTML清理)
    • IDE:PyCharm, VSCode 或任意文本编辑器。
  • Java环境
    • JDK版本:JDK 11 或 17
    • 框架:Spring Boot 2.7+
    • 构建工具:Maven 或 Gradle
    • IDE:IntelliJ IDEA, Eclipse。
  • 前端环境:现代浏览器即可,无特殊要求。
  • 数据库:示例中使用通用SQL语法,适用于MySQL、PostgreSQL等。

示例项目结构(Python Flask示例):

user-input-demo/ ├── app.py # 主应用文件 ├── requirements.txt # Python依赖 ├── static/ │ └── style.css # 静态样式文件 └── templates/ ├── index.html # 主页模板 └── result.html # 结果显示模板

版本兼容性说明:本文重点在于演示处理逻辑和核心API的使用。部分库的API在不同版本间可能有细微变化,请根据你实际使用的版本查阅官方文档进行调整。核心思想是相通的。

3. 核心处理策略与语法拆解

3.1 HTML内容转义

当需要将用户输入的内容显示在HTML页面上时,必须进行转义,以防止XSS攻击。

Python示例(使用标准库html):

import html # 用户输入的原始内容 user_input = '<script>alert("XSS")</script> 这是一段正常文本。' # 进行HTML转义 escaped_output = html.escape(user_input) print(escaped_output) # 输出:&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt; 这是一段正常文本。 # 在现代模板引擎(如Jinja2)中,通常会自动转义 # {{ user_input }} 在Jinja2中默认是安全的

关键点

  • html.escape()默认会转义&,<,>, 以及引号("')。
  • 如果你需要将转义后的内容还原,可以使用html.unescape(),但务必确保还原的内容来源可信

Java示例(使用Spring Boot + Thymeleaf):Thymeleaf模板引擎默认会对所有表达式进行HTML转义。

<!-- 在Thymeleaf模板中 --> <p th:text="${userInput}">这里会显示转义后的内容</p> <!-- 如果userInput是 <script>alert(1)</script>, 页面上会显示为文本,而不是执行脚本 --> <!-- 如果你确信内容安全,需要输出原始HTML,可以使用 th:utext (慎用!) --> <p th:utext="${trustedHtml}">这里会渲染HTML</p>

手动转义工具类(Java通用):

import org.springframework.web.util.HtmlUtils; public class HtmlEscapeDemo { public static void main(String[] args) { String raw = "<div>Hello & 'World'</div>"; String escaped = HtmlUtils.htmlEscape(raw); System.out.println(escaped); // 输出:&lt;div&gt;Hello &amp; &#39;World&#39;&lt;/div&gt; } }

3.2 富文本内容清理(Sanitization)

有时我们需要允许用户输入一些简单的格式(如加粗、斜体、链接),但又不能允许脚本。这时需要使用专门的清理库。

Python示例(使用bleach库):首先安装:pip install bleach

import bleach from bleach.sanitizer import ALLOWED_TAGS, ALLOWED_ATTRIBUTES # 用户输入的富文本 rich_input = ''' <p>这是一段<strong>加粗</strong>文本和一个<a href="https://example.com" onclick="stealCookie()">链接</a>。</p> <script>alert('bad')</script> ''' # 1. 使用默认允许的标签和属性(相对安全) cleaned_default = bleach.clean(rich_input) print(cleaned_default) # 输出:<p>这是一段<strong>加粗</strong>文本和一个<a href="https://example.com">链接</a>。</p> # <script> 标签及其内容被移除,onclick属性被移除。 # 2. 自定义允许的标签和属性 allowed_tags = ALLOWED_TAGS + ['p', 'span', 'div'] allowed_attrs = {**ALLOWED_ATTRIBUTES, 'a': ['href', 'title', 'class']} cleaned_custom = bleach.clean(rich_input, tags=allowed_tags, attributes=allowed_attrs) print(cleaned_custom)

Java示例(使用Jsoup库):Jsoup通常用于解析HTML,但其清理功能也非常强大。

<!-- Maven 依赖 --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.17.2</version> <!-- 请使用最新版本 --> </dependency>
import org.jsoup.Jsoup; import org.jsoup.safety.Safelist; public class HtmlSanitizerDemo { public static void main(String[] args) { String dirtyInput = "<p><a href='http://example.com/' onclick='steal()'>Link</a><script>alert('xss')</script></p>"; // 1. 使用基本白名单(允许基本的文本格式标签) String basicClean = Jsoup.clean(dirtyInput, Safelist.basic()); System.out.println("Basic: " + basicClean); // 输出:<p><a href="http://example.com/" rel="nofollow">Link</a></p> // script被移除,onclick被移除,自动添加了rel="nofollow" // 2. 使用宽松白名单并自定义(允许图片、特定CSS类) Safelist relaxed = Safelist.relaxed() .addTags("section", "article") .addAttributes("span", "class") .addProtocols("img", "src", "http", "https", "data"); String relaxedClean = Jsoup.clean(dirtyInput, relaxed); System.out.println("Relaxed: " + relaxedClean); } }

3.3 SQL注入防护

绝对不要使用字符串拼接来构造SQL语句!必须使用参数化查询(Prepared Statements)。

Python示例(使用sqlite3):

import sqlite3 # 错误做法:字符串拼接,极易导致SQL注入 user_id = "1' OR '1'='1" sql_bad = f"SELECT * FROM users WHERE id = '{user_id}'" # 执行的SQL会是: SELECT * FROM users WHERE id = '1' OR '1'='1', 会返回所有用户! # 正确做法:使用参数化查询 conn = sqlite3.connect('example.db') cursor = conn.cursor() # 使用 ? 作为占位符 sql_good = "SELECT * FROM users WHERE id = ?" cursor.execute(sql_good, (user_id,)) # 第二个参数是一个元组 # 数据库驱动会正确处理参数,将 user_id 的值安全地传递给查询。

Java示例(使用JDBC或JPA):

// 使用JDBC PreparedStatement String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; try (PreparedStatement pstmt = connection.prepareStatement(sql)) { pstmt.setString(1, usernameInput); pstmt.setString(2, passwordInput); ResultSet rs = pstmt.executeQuery(); // ... 处理结果 } // 使用Spring Data JPA(更推荐) public interface UserRepository extends JpaRepository<User, Long> { @Query("SELECT u FROM User u WHERE u.username = :username") User findByUsername(@Param("username") String username); // 框架会自动处理参数化,防止注入。 }

3.4 文件路径与命令注入防护

当用户输入被用作文件路径或系统命令的一部分时,必须进行严格限制和校验。

基本原则:

  1. 白名单校验:只允许特定的、预期的字符集(如字母、数字、短横线、下划线)。
  2. 路径规范化与限制:使用API获取规范路径,并检查是否在允许的目录范围内。
  3. 避免直接调用系统命令:如果必须,使用数组形式传递参数,而不是拼接字符串。

Python示例(安全地拼接文件路径):

import os import re def safe_file_access(base_dir, user_filename): # 1. 白名单校验文件名(只允许字母、数字、点、下划线、短横线) if not re.match(r'^[a-zA-Z0-9_.-]+$', user_filename): raise ValueError("Invalid filename") # 2. 拼接路径 full_path = os.path.join(base_dir, user_filename) # 3. 获取绝对路径并检查是否仍在基础目录内(防止目录遍历攻击如 ../../../etc/passwd) abs_base = os.path.abspath(base_dir) abs_path = os.path.abspath(full_path) # 检查目标路径是否以基础路径开头 if not abs_path.startswith(abs_base): raise ValueError("Access denied: path traversal attempt") # 4. 现在可以安全地操作文件了 with open(abs_path, 'r') as f: return f.read()

Java示例(使用ProcessBuilder执行命令):

// 错误做法 String cmd = "ping " + userInput; // 如果userInput是“127.0.0.1 && rm -rf /”,就灾难了 Runtime.getRuntime().exec(cmd); // 正确做法:使用ProcessBuilder并分隔参数 ProcessBuilder pb = new ProcessBuilder("ping", "-c", "4", userInput); // userInput作为独立参数 // 即使userInput包含特殊字符,它也会被当作一个整体参数传递给ping命令,不会被shell解析。 Process p = pb.start();

4. 完整实战案例:构建一个安全的用户评论系统

让我们构建一个简单的Web应用,演示如何安全地接收、存储和显示用户评论。

4.1 项目结构与依赖(Python Flask版)

文件:requirements.txt

Flask==2.3.3 bleach==6.0.0

文件:app.py

from flask import Flask, render_template, request, redirect, url_for import html import bleach from bleach.sanitizer import ALLOWED_TAGS, ALLOWED_ATTRIBUTES import sqlite3 import os app = Flask(__name__) DATABASE = 'comments.db' # 初始化数据库 def init_db(): conn = sqlite3.connect(DATABASE) c = conn.cursor() c.execute(''' CREATE TABLE IF NOT EXISTS comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, content TEXT NOT NULL, -- 存储原始内容(清理后) content_html TEXT NOT NULL, -- 存储用于安全显示的HTML created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') conn.commit() conn.close() # 定义允许的富文本标签和属性 ALLOWED_TAGS_CUSTOM = ALLOWED_TAGS + ['p', 'br', 'h3', 'h4', 'ul', 'ol', 'li'] ALLOWED_ATTRS_CUSTOM = {**ALLOWED_ATTRIBUTES, 'a': ['href', 'title', 'rel']} @app.route('/') def index(): conn = sqlite3.connect(DATABASE) c = conn.cursor() # 从数据库直接读取已处理好的安全HTML内容 c.execute('SELECT username, content_html, created_at FROM comments ORDER BY created_at DESC') comments = c.fetchall() conn.close() return render_template('index.html', comments=comments) @app.route('/add', methods=['POST']) def add_comment(): username = request.form['username'] raw_content = request.form['content'] # 1. 基础验证(非空、长度限制) if not username or not raw_content: return "用户名和内容不能为空", 400 if len(username) > 50 or len(raw_content) > 1000: return "输入内容过长", 400 # 2. 对用户名进行严格过滤(只允许特定字符) import re if not re.match(r'^[a-zA-Z0-9_\u4e00-\u9fa5]{1,50}$', username): return "用户名包含非法字符", 400 # 3. 对评论内容进行清理(允许简单的富文本) # 先进行HTML转义,防止XSS escaped_content = html.escape(raw_content) # 然后使用bleach进行清理,只允许安全的标签和属性 cleaned_html = bleach.clean( escaped_content, tags=ALLOWED_TAGS_CUSTOM, attributes=ALLOWED_ATTRS_CUSTOM, strip=True ) # 注意:我们存储了原始清理后的文本和安全的HTML两个版本 # content 字段可以用于纯文本场景(如搜索),content_html用于显示 safe_content_for_db = cleaned_html # 或者也可以存储 raw_content 用于其他处理 # 4. 使用参数化查询插入数据库 conn = sqlite3.connect(DATABASE) c = conn.cursor() c.execute(''' INSERT INTO comments (username, content, content_html) VALUES (?, ?, ?) ''', (username, raw_content, cleaned_html)) # 这里存储了原始内容和安全HTML conn.commit() conn.close() return redirect(url_for('index')) if __name__ == '__main__': init_db() app.run(debug=True)

4.2 前端模板

文件:templates/index.html

<!DOCTYPE html> <html> <head> <title>安全评论系统</title> <style> .comment { border: 1px solid #ccc; margin: 10px; padding: 15px; } .meta { color: #666; font-size: 0.9em; } .content { margin-top: 10px; } </style> </head> <body> <h1>用户评论</h1> <form action="/add" method="post"> <div> <label>用户名:</label> <input type="text" name="username" required maxlength="50"> </div> <div> <label>评论内容(支持简单HTML):</label><br> <textarea name="content" rows="4" cols="50" required maxlength="1000"></textarea> <p><small>支持:&lt;strong&gt;, &lt;em&gt;, &lt;a href="..."&gt;, &lt;p&gt;, &lt;br&gt;, &lt;ul&gt;, &lt;li&gt; 等标签。</small></p> </div> <button type="submit">提交评论</button> </form> <hr> <h2>所有评论</h2> {% for comment in comments %} <div class="comment"> <div class="meta"> <strong>{{ comment[0] }}</strong> 于 {{ comment[2] }} </div> <div class="content"> {# 注意:这里直接输出数据库中的 content_html,它是已经过清理的安全HTML #} {{ comment[1] | safe }} </div> </div> {% else %} <p>暂无评论。</p> {% endfor %} </body> </html>

4.3 运行与验证

  1. 安装依赖:pip install -r requirements.txt
  2. 运行应用:python app.py
  3. 访问http://127.0.0.1:5000
  4. 尝试输入以下内容进行测试:
    • 正常文本你好,世界!
    • 简单HTML这是一个<strong>加粗</strong>的<a href="https://www.csdn.net">链接</a>。
    • 恶意脚本<script>alert('xss');</script><img src=x onerror=alert(1)>
    • SQL注入尝试' OR '1'='1

预期结果:

  • 正常文本和简单HTML会正确显示。
  • 恶意脚本标签(<script>)和危险属性(onerror)会被bleach库过滤掉,只留下安全的文本或部分安全的标签。
  • 由于使用了参数化查询,SQL注入尝试会被当作普通的文本数据存入数据库,不会影响数据库查询逻辑。
  • 在前端显示时,因为使用了| safe过滤器,且内容在入库前已被清理,所以是安全的。但请注意,在Jinja2中直接对未经验证的数据使用| safe是极度危险的,我们这里的安全前提是数据在入库时已经过严格清理。

4.4 关键逻辑解析

  1. 分层防御

    • 前端:通过HTMLmaxlengthrequired进行基础验证和体验优化。
    • 后端验证:对用户名进行正则白名单校验,对内容进行长度校验。
    • 内容清理:使用bleach.clean()对富文本进行基于白名单的清理。
    • 数据库安全:使用参数化查询防止SQL注入。
    • 输出转义:在模板中,对于已清理的HTML使用| safe,对于其他动态数据,Jinja2默认会转义。
  2. 数据存储策略:我们存储了原始内容 (raw_content) 和清理后的HTML (cleaned_html)。这是一个权衡。存储原始内容有利于未来更换清理策略或进行文本分析,但需要确保任何使用原始内容的地方都经过处理。存储清理后的HTML则更安全、显示效率高,但失去了原始信息的灵活性。生产环境中需要根据业务需求决定。

5. 常见问题与排查思路

问题现象可能原因解决思路
用户提交的HTML标签全部被显示为文本(例如,显示<strong>而不是加粗)。1. 后端没有进行富文本清理,而是直接进行了HTML转义。
2. 前端模板在输出时使用了自动转义(如Jinja2未使用| safe)。
1. 检查后端处理逻辑:是否在存储或传递给模板前,对需要富文本的内容调用了清理函数(如bleach.clean()),而不是转义函数(如html.escape())。
2. 检查前端模板:对于已清理的安全HTML内容,输出时是否需要使用安全过滤器(如{{ content | safe }})。
允许的某个HTML标签或属性不起作用。1. 清理库(如bleachJsoup)的白名单配置未包含该标签或属性。
2. 标签或属性的写法不符合规范(如属性值缺少引号)。
1. 检查清理函数的tagsattributes参数配置,确保目标标签和属性已正确添加。
2. 检查用户输入的HTML片段是否格式良好。清理库可能对格式错误的HTML处理行为不一致。
应用在处理特定Unicode字符或emoji时出现乱码或错误。1. 数据库、后端代码、前端页面的字符编码不统一(如不是UTF-8)。
2. 清理库可能错误地处理了某些特殊字符。
1. 确保整个数据流(数据库连接、HTTP响应头、HTML meta标签)都使用UTF-8编码。
2. 测试清理库对emoji的支持情况,考虑在清理前或后对内容进行规范化。
发现存储的评论内容中仍包含潜在的恶意脚本。1. 清理白名单过于宽松。
2. 存在绕过清理的XSS新变种(如基于SVG、data:URI的XSS)。
3. 清理过程被绕过(例如,内容在清理后被二次修改)。
1. 收紧白名单,只开放业务必须的标签和属性。遵循最小权限原则。
2. 保持清理库更新到最新版本,以应对最新的威胁。
3. 审计代码,确保用户内容在最终展示前,有且仅有一次严格的清理过程。
性能下降,尤其是在处理大量或复杂的HTML内容时。1. 清理操作(如bleach.clean)是CPU密集型操作,内容过长或过于复杂会导致耗时增加。
2. 在每次页面请求时都进行清理,而不是在入库时清理一次。
1. 对用户输入的内容长度做合理限制。
2.强烈建议在内容入库(或缓存)时进行清理,而不是在每次读取展示时清理。将清理后的安全HTML存储起来,展示时直接读取,用空间换时间。

6. 最佳实践与工程建议

  1. 实施纵深防御

    • 不要依赖单一防护措施。结合前端验证、后端验证、输入清理、参数化查询、输出转义等多层防护。
    • 在数据边界进行处理:在数据进入核心业务逻辑前(入库、调用外部API前)进行验证和清理;在数据离开系统前(输出到HTML、JSON、日志前)进行适当的编码或转义。
  2. 使用权威的安全库

    • 不要自己编写复杂的正则表达式来过滤HTML或脚本,极易出错并被绕过。
    • 使用经过广泛测试和社区维护的库,如 Python 的bleach、Java 的Jsoup(用于清理) 和OWASP Java Encoder(用于编码)。
  3. 遵循“最小权限”原则

    • 对于富文本:定义尽可能严格的白名单。只开放业务绝对需要的标签和属性。例如,如果不需要<iframe>,就永远不要允许它。
    • 对于文件操作:将用户文件限制在特定的、非系统目录下,并使用白名单校验文件扩展名和内容类型。
    • 对于数据库:为应用数据库用户分配最小的必要权限(通常是SELECT,INSERT,UPDATE,DELETE,而不是DROPGRANT)。
  4. 安全的默认配置

    • 模板引擎应默认开启HTML转义。在需要输出原始HTML的地方,要显式地、谨慎地标记(如Jinja2的|safe,Thymeleaf的th:utext)。
    • HTTP响应头应设置安全相关的头部,如Content-Security-Policy (CSP)。CSP可以极大地缓解XSS的影响,即使有恶意脚本被注入,CSP也能限制其执行。
  5. 日志与监控

    • 记录所有验证失败、清理过程中被移除的内容的日志(注意日志本身也要对用户输入进行转义,防止日志注入攻击)。
    • 监控这些日志,可以发现攻击尝试或清理规则是否存在问题。
    • 对异常大量的提交、包含大量特殊字符的请求进行告警。
  6. 代码审查与安全测试

    • 在代码审查中,将用户输入处理作为重点审查项。检查是否所有用户可控的数据都经过了适当的处理。
    • 定期进行安全测试,包括手动测试和使用自动化工具(如OWASP ZAP、Burp Suite)进行漏洞扫描。
    • 对清理库的白名单配置进行定期复审,确保其仍然符合业务需求且足够安全。

处理用户输入是Web开发安全的基石。它没有一劳永逸的银弹,需要开发者根据具体的业务场景,选择合适的策略和工具,并始终保持警惕。从简单的HTML转义到复杂的富文本清理,从参数化查询到文件路径校验,每一步都关乎着应用和数据的安全。建议将本文中的示例代码作为起点,融入到你自己的项目中,并持续关注OWASP等安全组织发布的最新最佳实践和漏洞信息。

http://www.jsqmd.com/news/1380079/

相关文章:

  • 2026年优选西安专业的楼盘地产服务商 - 装修教育财税推荐2026
  • 深度图与视差图伪彩色可视化:原理、实现与工程实践
  • 2026年浙江碳钢镀镍厂家哪家靠谱?放心厂家甄选 - 商业新知
  • eNSP交换机Telnet远程登录完整实验教程(真机Cloud桥接方案)
  • 游戏剧情与过场怎么测:分支条件、跳过、恢复、字幕与资源版本
  • 一体化智能制造方案,生产自动化与管理 AI 能分开采购实施吗?
  • PTA基础编程题目集 7-30字符串的冒泡排序(C++语言实现)
  • Java接入大模型的三层路径从API调用到智能体编排
  • Java 开源商城系统怎么选?先回答这 4 个问题,再去看代码
  • 中小企业必看:上海本地搜索引擎优化公司怎么选?2026甄选测评推荐 - 商业新知
  • 【8.22截稿提醒】CPNN 2026计算机感知与神经网络国际会议|深圳线下 EI/Scopus双检索
  • Android开发进阶:贝塞尔曲线原理、绘制与动画实战
  • 2026年荃净环保和希望树除醛效果哪家好?家用除醛选购实用指南 - 亚东说
  • FSearch:Linux文件搜索终极指南,让你的文件查找速度提升10倍
  • ARP静态绑定与解绑全攻略:从原理到实战,防御ARP欺骗攻击
  • 佛山家具购买渠道怎么选? - GrowthUME
  • 2026年安平市政护栏选购参考 中庭护栏核心信息及行业优质企业盘点 - 小范同学a
  • Linux双网卡配置与故障排查:从物理层到防火墙的完整指南
  • CSSO 1.3 Beta 4 更新详解:修复媒体查询与字体去重,优化前端构建CSS压缩
  • AI Agent开发中测试驱动开发(TDD)的实践指南:从交互契约到工程化落地
  • ArcGIS栅格计算器在水文分析中的应用:从水位数据到水力梯度与年际变化
  • Python FDTD仿真:3D电磁场计算的终极指南
  • 终极Real-ESRGAN图像超分辨率实战指南:如何让模糊图片瞬间变高清
  • Nginx服务器安全加固实战:从配置到防护的完整指南
  • 2026年秦皇岛东方皮肤医学门诊部-斑秃相关就诊 - 小范同学a
  • Windows HEIC 缩略图扩展:让 iPhone 照片在资源管理器中一目了然
  • Dify 高级实验(07):智能审批——如何让机器自动完成流程审批?
  • 营业执照翻译怎么办理?线上办理翻译的流程是什么,一文详解! - 指上通
  • 浙江靠谱合金钢镀镍厂家有哪些?2026靠谱厂家整理推荐 - 商业新知
  • ESP-SR语音识别框架终极指南:为嵌入式设备打造智能语音交互系统