Spring Security 如何使用@PreAuthorize注解


🧱 第一步:環境準備

? 1. 創建數據庫(MySQL)

-- 創建數據庫,使用 utf8mb4 字符集支持 emoji 和多語言
CREATE DATABASE security_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;-- 使用該數據庫
USE security_demo;-- 用戶表結構
-- id: 主鍵自增
-- username: 唯一,不允許重復
-- password: 存儲 BCrypt 加密后的密碼(明文不可逆)
-- role: 存儲用戶角色,如 ROLE_ADMIN、ROLE_USER
CREATE TABLE user (id BIGINT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50) UNIQUE NOT NULL,password VARCHAR(100) NOT NULL,  -- BCrypt 加密后長度約 60role VARCHAR(50) NOT NULL
);-- 插入測試數據
-- 注意:密碼 '123456' 已通過 BCrypt 加密(強度為 10)
-- 生成工具:https://www.devglan.com/online-tools/bcrypt-hash-generator
INSERT INTO user (username, password, role) VALUES 
('admin', '$2a$10$RRLCewx/5eYR60ZJ6y6U7eM8V6a8y6U7eM8V6a8y6U7eM8V6a8y6U7', 'ROLE_ADMIN'),
('alice', '$2a$10$RRLCewx/5eYR60ZJ6y6U7eM8V6a8y6U7eM8V6a8y6U7eM8V6a8y6U7', 'ROLE_USER'),
('bob', '$2a$10$RRLCewx/5eYR60ZJ6y6U7eM8V6a8y6U7eM8V6a8y6U7eM8V6a8y6U7', 'ROLE_USER');

🔐 安全提示

  • 不要將明文密碼存入數據庫。
  • BCrypt?是 Spring Security 推薦的密碼加密算法,自帶鹽值(salt),防彩虹表攻擊。
  • $2a$10$...?中的?10?是加密強度(log rounds),值越大越安全但越慢。

? 2. Redis

# 確保 Redis 正在運行
redis-server

💡 用途說明

  • 緩存用戶角色信息,避免每次請求都查詢數據庫。
  • 提升系統性能,尤其在高并發場景下。
  • 鍵名格式:user_role:用戶名

📁 第二步:Spring Boot 項目結構

src/main/java/com/example/demo/
├── DemoApplication.java              # 主啟動類
├── config/
│   ├── SecurityConfig.java           # 安全核心配置
│   └── MyBatisConfig.java            # MyBatis 配置(可選)
├── controller/
│   └── UserController.java           # 用戶操作接口
├── entity/
│   └── User.java                     # 用戶實體類
├── mapper/
│   └── UserMapper.java               # 數據訪問接口
├── service/
│   ├── CustomUserDetailsService.java # 自定義用戶認證邏輯
│   └── RedisService.java             # Redis 操作封裝
└── util/└── PasswordUtil.java             # 密碼工具類(未使用,建議補全)

📦?pom.xml?依賴詳解

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.3</version> <!-- 使用最新穩定版 Spring Boot --><relativePath/> <!-- 查找父 POM 從本地開始 --></parent><groupId>com.example</groupId><artifactId>security-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>security-demo</name><properties><java.version>17</java.version> <!-- 推薦使用 LTS 版本 --></properties><dependencies><!-- Web 支持:Tomcat + Spring MVC --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 安全框架:Spring Security --><!-- 提供認證、授權、CSRF、Session 等功能 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- MyBatis 啟動器 --><!-- 簡化 MyBatis 配置,自動掃描 Mapper --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><!-- MySQL 驅動 --><!-- 運行時依賴,編譯時不需要 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- Redis 支持 --><!-- 用于緩存用戶權限信息 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Lombok --><!-- 自動生成 getter/setter/toString 等方法 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><!-- 打包時排除 Lombok,避免運行時報錯 --><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>

? 第三步:代碼實現

1.?DemoApplication.java?- 主啟動類

package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** Spring Boot 主啟動類* @SpringBootApplication 注解 = @Configuration + @EnableAutoConfiguration + @ComponentScan* 自動掃描 com.example.demo 包下所有組件*/
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

2.?User.java?- 實體類

package com.example.demo.entity;import lombok.Data;/*** 用戶實體類* 對應數據庫 user 表* 使用 Lombok @Data 自動生成:* - getter/setter* - toString()* - equals()/hashCode()* - requiredArgsConstructor*/
@Data
public class User {private Long id;private String username;private String password;private String role; // 如 ROLE_ADMIN
}

3.?UserMapper.java?- MyBatis Mapper

package com.example.demo.mapper;import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;/*** 數據訪問接口(DAO)* @Mapper 注解:讓 Spring 能掃描到該接口并創建代理對象* SQL 注解方式:@Select 直接寫 SQL,適合簡單查詢*/
@Mapper
public interface UserMapper {/*** 根據用戶名查詢用戶信息* #{username} 是預編譯參數,防止 SQL 注入* @param username 用戶名* @return 用戶對象,不存在返回 null*/@Select("SELECT * FROM user WHERE username = #{username}")User findByUsername(String username);
}

4.?RedisService.java?- Redis 工具類

package com.example.demo.service;import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;/*** Redis 操作服務類* 封裝常用操作,便于業務調用* 使用 StringRedisTemplate(只處理字符串),適合緩存簡單鍵值對*/
@Service
public class RedisService {private final StringRedisTemplate redisTemplate;/*** 構造器注入 RedisTemplate* Spring 自動注入 RedisConnectionFactory 創建的模板*/public RedisService(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}/*** 設置字符串值,并設置過期時間(分鐘)* @param key 鍵* @param value 值* @param timeout 過期時間(分鐘)*/public void set(String key, String value, long timeout) {redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MINUTES);}/*** 獲取字符串值* @param key 鍵* @return 值,不存在返回 null*/public String get(String key) {return redisTemplate.opsForValue().get(key);}/*** 刪除指定鍵* @param key 鍵*/public void delete(String key) {redisTemplate.delete(key);}
}

5.?CustomUserDetailsService.java?- 自定義用戶詳情服務

package com.example.demo.service;import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.Collection;
import java.util.Collections;/*** 自定義用戶詳情服務* Spring Security 通過此服務加載用戶信息用于認證* 實現 UserDetailsService 接口是必須的*/
@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisService redisService;/*** 根據用戶名加載用戶詳情* 調用時機:用戶登錄時(/login)* 流程:*  1. 先查 Redis 緩存*  2. 緩存命中 → 返回*  3. 未命中 → 查數據庫 → 寫入緩存* @param username 用戶名* @return UserDetails(Spring Security 用戶模型)* @throws UsernameNotFoundException 用戶不存在時拋出*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1. 嘗試從 Redis 獲取角色String cachedRole = redisService.get("user_role:" + username);if (cachedRole != null) {System.out.println("? Redis 緩存命中: " + username);return buildUserDetails(username, "******", cachedRole);}// 2. 緩存未命中,查詢數據庫User user = userMapper.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("? 用戶不存在: " + username);}// 3. 將角色寫入 Redis,有效期 30 分鐘redisService.set("user_role:" + username, user.getRole(), 30);System.out.println("🔥 數據庫查詢并緩存: " + username);return buildUserDetails(user.getUsername(), user.getPassword(), user.getRole());}/*** 構建 Spring Security 的 UserDetails 對象* @param username 用戶名* @param password 加密后的密碼* @param role 角色(如 ROLE_ADMIN)* @return UserDetails 實例*/private UserDetails buildUserDetails(String username, String password, String role) {// 將角色封裝為 GrantedAuthority(權限對象)Collection<? extends GrantedAuthority> authorities =Collections.singletonList(new SimpleGrantedAuthority(role));// 創建 Spring Security 內置用戶對象// 參數:用戶名、密碼、權限集合return new org.springframework.security.core.userdetails.User(username,password,authorities);}
}

6.?SecurityConfig.java?- 安全配置

package com.example.demo.config;import com.example.demo.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;/*** Spring Security 配置類* 控制認證、授權、密碼編碼、會話等行為*/
@Configuration
@EnableWebSecurity  // 啟用 Web 安全
@EnableGlobalMethodSecurity(prePostEnabled = true) // 啟用方法級安全(支持 @PreAuthorize)
public class SecurityConfig {@Autowiredprivate CustomUserDetailsService userDetailsService;/*** 密碼編碼器 Bean* 用于比對用戶輸入密碼與數據庫加密密碼* @return BCryptPasswordEncoder 實例*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 安全過濾鏈配置* 定義哪些請求需要認證、使用何種認證方式等*/@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf().disable() // 禁用 CSRF(適合無狀態 API).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 無狀態會話.and().authorizeHttpRequests(authz -> authz.requestMatchers("/login").permitAll() // 登錄接口放行.anyRequest().authenticated()         // 其他請求需認證).httpBasic(); // 使用 HTTP Basic 認證(測試用)return http.build();}/*** Redis Template Bean* 用于操作 Redis* Spring Boot 自動配置 RedisConnectionFactory*/@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {return new StringRedisTemplate(factory);}
}

7.?UserController.java?- 控制器

package com.example.demo.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;/*** 用戶操作控制器* 演示 @PreAuthorize 方法級權限控制*/
@RestController
@RequestMapping("/api/users")
public class UserController {/*** 刪除用戶接口* @PreAuthorize("hasRole('ROLE_ADMIN')")* 只有擁有 ROLE_ADMIN 角色的用戶才能調用* 注意:hasRole() 會自動添加 ROLE_ 前綴,所以寫 'ADMIN' 也可以*/@PreAuthorize("hasRole('ROLE_ADMIN')")@DeleteMapping("/{username}")public String deleteUser(@PathVariable String username) {return "🗑? 用戶 " + username + " 已刪除";}/*** 查看用戶信息* @PreAuthorize("authentication.principal.username == #username")* 表達式含義:*   當前登錄用戶名(authentication.principal.username)*   必須等于路徑參數 #username* 實現“只能查看自己信息”的業務邏輯*/@PreAuthorize("authentication.principal.username == #username")@GetMapping("/{username}")public String getUserInfo(@PathVariable String username) {return "👤 用戶信息: " + username;}
}

8.?application.yml?- 配置文件

server:port: 8080  # 服務端口spring:datasource:url: jdbc:mysql://localhost:3306/security_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: yourpassword  # 請替換為真實密碼driver-class-name: com.mysql.cj.jdbc.Driverredis:host: localhostport: 6379  # Redis 服務地址mybatis:type-aliases-package: com.example.demo.entity  # 別名包,SQL 中可用類名代替全路徑configuration:map-underscore-to-camel-case: true  # 數據庫下劃線字段自動映射到 Java 駝峰屬性logging:level:com.example.demo.mapper: debug  # 顯示 MyBatis 執行的 SQL

?? 第四步:運行與測試

1. 啟動項目

服務啟動后,訪問 http://localhost:8080 會跳轉登錄頁(Basic Auth)。


2. 測試命令

? 測試1:管理員查看自己
curl -u admin:123456 http://localhost:8080/api/users/admin
# 響應:👤 用戶信息: admin
# 說明:用戶名匹配,授權通過
? 測試2:管理員刪除用戶
curl -X DELETE -u admin:123456 http://localhost:8080/api/users/alice
# 響應:🗑? 用戶 alice 已刪除
# 說明:admin 擁有 ROLE_ADMIN,權限通過
? 測試3:普通用戶刪別人
curl -X DELETE -u alice:123456 http://localhost:8080/api/users/bob
# 響應:403 Forbidden
# 說明:alice 是 ROLE_USER,不滿足 hasRole('ROLE_ADMIN')

? 查看 Redis 緩存

redis-cli
> KEYS user_role:*
# 輸出:
# "user_role:admin"
# "user_role:alice"
# "user_role:bob"
> GET user_role:admin
# "ROLE_ADMIN"

🏁 總結:核心知識點

技術作用
@PreAuthorize方法級權限控制,支持 SpEL 表達式
hasRole()檢查角色(自動加?ROLE_?前綴)
authentication.principal.username獲取當前登錄用戶名
#param引用方法參數
Redis 緩存提升性能,避免重復查庫
BCrypt安全存儲密碼
HTTP Basic簡單認證方式(適合測試)

KEY: user_role:admin
VAL: ROLE_ADMINKEY: user_role:alice
VAL: ROLE_USER

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/97741.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/97741.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/97741.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JVM中產生OOM(內存溢出)的8種典型情況及解決方案

Java中的OutOfMemoryError&#xff08;OOM&#xff09;是當JVM內存不足時拋出的錯誤。本文將全面剖析JVM中產生OOM的各種情況&#xff0c;包括堆內存溢出、方法區溢出、棧溢出等&#xff0c;并提供詳細的診斷方法和解決方案。 一、OOM基礎概念 1.1 OOM錯誤類型 Java中的OOM是…

【IEEE出版、EI檢索、往屆會后3個月檢索】第四屆信號處理、計算機網絡與通信國際學術會議(SPCNC 2025)

第四屆信號處理、計算機網絡與通信國際學術會議&#xff08;SPCNC 2025&#xff09;將于2025年12月5-7日于中國武漢召開&#xff08;線上同步&#xff09;。為本次會議旨在齊聚海內外信號處理、計算機網絡與通信等計算機領域的專家學者&#xff0c;為相關領域研究和從業人員提供…

Spring boot注解介紹

1. Spring 核心注解Spring Boot 是基于 Spring 框架的&#xff0c;所以核心注解依然適用。? 常見核心注解Component表示一個通用組件&#xff0c;Spring 會自動掃描并注入到容器中。Component public class MyComponent {public void sayHello() {System.out.println("He…

撤銷回退 情況?:已經 add ,但沒有 commit

撤銷回退 情況?&#xff1a;已經 add &#xff0c;但沒有 commit add 后還是保存到了暫存區呢&#xff1f;怎么撤銷呢&#xff1f; 1 # 向ReadMe中新增??代碼 2 hyb139-159-150-152:~/gitcode$ vim ReadMe 3 hyb139-159-150-152:~/gitcode$ cat ReadMe 4 hello bit 5 hell…

【Linux筆記】命令行與vim基礎

一、Linux命令行基礎 1. 基本語法命令空格參數&#xff08;可寫可不寫&#xff09;空格文件&#xff0c;文件夾&#xff08;可寫可不寫&#xff09;ls列出文件夾中的內容/opt 根目錄下的opt文件夾ls-a all顯示出所有文件以及隱藏文件/optls-a如果不寫則輸出一個點&#xff0c;當…

Redis 的整數集合:像分類收納盒一樣的整數專屬存儲

目錄 一、先懂定位&#xff1a;為什么需要整數集合&#xff1f;&#xff08;銜接哈希表&#xff09; 二、整數集合的結構&#xff1a;像 “貼了規格標簽的收納盒” 1. encoding&#xff1a;收納盒的 “規格標簽”&#xff08;核心&#xff1a;決定格子大小&#xff09; 2. …

Linux 進程狀態 — 僵尸進程

&#x1f381;個人主頁&#xff1a;工藤新一 &#x1f50d;系列專欄&#xff1a;C面向對象&#xff08;類和對象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;終會照亮我前方的路 &#x1f389;歡迎大家點贊&#x1f44d;評論&#x1f4dd;收藏?文章 文章目錄進…

React 中 key 的作用

React 中 key 的作用是什么&#xff1f; Date: August 31, 2025 Area: 原理key 概念 在 React 中&#xff0c;key 用于識別哪些元素是變化、添加或刪除的。 在列表渲染中&#xff0c;key 尤其重要&#xff0c;因為它能提高渲染性能和確保組件狀態的一致性。key 的作用 1&#x…

wpf之附加屬性

前言 附加屬性是 WPF 中一個非常強大和獨特的概念。簡單來說&#xff0c;它允許一個對象為另一個在其本身類定義中未定義的屬性賦值。 1、定義附加屬性 定義一個Watermark的附加屬性&#xff0c;該屬性的作用是將TextBox的附加屬性改變時&#xff0c;TextBox的字體顏色改成灰…

深入淺出 RabbitMQ-消息可靠性投遞

大家好&#xff0c;我是工藤學編程 &#x1f989;一個正在努力學習的小博主&#xff0c;期待你的關注實戰代碼系列最新文章&#x1f609;C實現圖書管理系統&#xff08;Qt C GUI界面版&#xff09;SpringBoot實戰系列&#x1f437;【SpringBoot實戰系列】SpringBoot3.X 整合 Mi…

數字化時代,中小企業如何落地數字化轉型

大數據時代&#xff0c;各行各業的行業龍頭和大型集團都已經開始了數據管理&#xff0c;讓數據成為數據資產。但是在我國&#xff0c;中小企業的數量巨大&#xff0c;很多管理者忽視了這一點&#xff0c;今天我們就來聊一聊中小企業的數字化轉型。中小企業需要數字化轉型首先要…

Unity筆記(九)——畫線功能Linerenderer、范圍檢測、射線檢測

寫在前面&#xff1a;寫本系列(自用)的目的是回顧已經學過的知識、記錄新學習的知識或是記錄心得理解&#xff0c;方便自己以后快速復習&#xff0c;減少遺忘。這里只記錄代碼知識。十一、畫線功能Linerenderer畫線功能Linerenderer是Unity提供的畫線腳本&#xff0c;創建一個空…

刷題記錄(8)string類操作使用

一、僅反轉字母 917. 僅僅反轉字母 - 力扣&#xff08;LeetCode&#xff09; 簡單來說輸入字符串&#xff0c;要求你返回所有僅字母位置反轉后的字符串。 簡單看一個樣例加深理解&#xff1a; 前后互換&#xff0c;我想思路基本很明顯了&#xff0c;雙指針&#xff0c;或者說…

用好AI,從提示詞工程到上下文工程

前言 隨著 AI 大模型的爆發,提示詞工程(prompt engineering ) 一度是用戶應用 AI ,發揮 AI 能力最重要、也最應該掌握的技術。 但現在,在 “提示詞工程”的基礎上,一個更寬泛也更強力的演化概念被提出,也就是本文我們要介紹的 “上下文工程(Context Engineering)” …

計算機Python畢業設計推薦:基于Django+Vue用戶評論挖掘旅游系統

精彩專欄推薦訂閱&#xff1a;在下方主頁&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主頁&#xff1a;計算機畢設木哥&#x1f525; &#x1f496; 文章目錄 一、項目介紹二、…

? 肆 ? ? 默認安全:安全建設方案 ? a.信息安全基線

&#x1f44d;點「贊」&#x1f4cc;收「藏」&#x1f440;關「注」&#x1f4ac;評「論」 在金融科技深度融合的背景下&#xff0c;信息安全已從單純的技術攻防擴展至架構、合規、流程與創新的系統工程。作為一名從業十多年的老兵&#xff0c;將系統闡述數字銀行安全體系的建設…

如何用AI視頻增強清晰度軟件解決畫質模糊問題

在視頻制作和分享過程中&#xff0c;畫質模糊、細節丟失等問題常常影響觀看體驗。無論是老舊視頻的修復還是低分辨率素材的優化&#xff0c;清晰度提升都成為用戶關注的重點。借助專業的AI技術&#xff0c;這些問題可以得到有效解決。目前市面上存在多種解決方案&#xff0c;能…

Linux92 shell:倒計時,用戶分類

問題 while IFS read -r line;doootweb kk]# tail -6 /etc/passwd user1r4:x:1040:1040::/home/user1r4:/bin/bash useros20:x:1041:1041::/home/useros20:/bin/bash useros21:x:1042:1042::/home/useros21:/bin/bash useros22:x:1043:1043::/home/useros22:/bin/bash useros23…

LinkedList源碼解析

1. 數據結構設計 (1) 節點結構 LinkedList 的核心是雙向鏈表節點 Node&#xff1a; private static class Node<E> {E item; // 存儲的元素Node<E> next; // 后繼節點Node<E> prev; // 前驅節點Node(Node<E> prev, E element, Node<E&g…

語雀批量導出知識庫

使用工具&#xff1a;yuque-dl 參考文檔&#xff1a; GitHub - gxr404/yuque-dl: yuque 語雀知識庫下載 Yuque-DL&#xff1a;一款強大的語雀資源下載工具_語雀文檔怎么下載-CSDN博客