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

基于 Redis + JWT 的跨系统身份共享方案

单点登录(SSO)的核心目标是**“一次登录,多系统访问”**,需通过“统一认证中心”串联多个业务系统,实现身份凭证的跨系统共享与验证。以下基于 NestJS + Redis + JWT 实现完整的 SSO 流程,包含“统一认证中心(Auth Server)”和“业务系统(Client Server)”两端代码,覆盖“登录→凭证共享→跨系统验证→登出”全链路。

一、SSO 整体架构与核心逻辑

在开始代码实现前,需明确 SSO 的核心组件与数据流转:

  1. 统一认证中心(Auth Server):唯一的身份验证入口,负责用户登录、JWT 令牌生成、SSO 会话管理(Redis 存储);
  2. 业务系统(Client Server):如订单系统、会员系统等,自身不处理登录,依赖 Auth Server 验证用户身份;
  3. Redis:存储 SSO 全局会话(关联用户 ID 与所有登录系统)、JWT 黑名单(登出/失效令牌);
  4. JWT 令牌:分为两种——
    • SSO 令牌(SSO Token):由 Auth Server 生成,全局唯一,代表用户的 SSO 会话;
    • 业务令牌(Client Token):业务系统接收 SSO Token 后,生成自身系统的 JWT(可选,减少跨系统请求 Auth Server 的频率)。

核心流程

flowchart LRA[用户] -->|1. 访问业务系统A| B[业务系统A]B -->|2. 未登录,重定向到Auth Server| C[统一认证中心]A -->|3. 输入账号密码登录| CC -->|4. 验证通过,生成SSO Token+用户信息| D[Redis存储SSO会话<br>(key: SSO_TOKEN, value: 用户ID+系统列表)]C -->|5. 重定向回业务系统A,携带SSO Token| BB -->|6. 向Auth Server验证SSO Token| CC -->|7. 验证通过,返回用户信息| BB -->|8. 生成业务令牌(可选),允许访问| AA -->|9. 访问业务系统B| E[业务系统B]E -->|10. 未登录,重定向到Auth Server| CC -->|11. 检测到SSO会话已存在| EE -->|12. 向Auth Server验证SSO Token| CC -->|13. 验证通过,允许访问| AA -->|14. 登出(任意系统)| F[删除Redis中的SSO会话]F -->|15. 所有业务系统同步登出| A

二、前置准备:环境与依赖

1. 技术栈

  • 后端框架:NestJS(Auth Server 与 Client Server 均基于此);
  • 数据库:Redis(存储 SSO 会话、JWT 黑名单)、MySQL/PostgreSQL(存储用户基础信息);
  • 核心依赖:@nestjs/jwt(JWT 生成/验证)、ioredis(Redis 操作)、passport-jwt(身份验证)、cookie-parser(处理 Cookie,可选)。

2. 安装依赖(Auth Server 与 Client Server 均需安装)

# 核心依赖
pnpm add @nestjs/jwt @nestjs/passport passport-jwt ioredis @nestjs-modules/ioredis bcryptjs
# 开发依赖
pnpm add -D @types/passport-jwt @types/ioredis @types/bcryptjs

三、统一认证中心(Auth Server)代码实现

Auth Server 是 SSO 的核心,需实现“用户登录”“SSO Token 生成”“跨系统令牌验证”“SSO 会话管理(登出/失效)”四大功能。

1. 配置 Redis 与 JWT 模块

首先在 Auth Server 中配置 Redis(存储 SSO 会话)和 JWT(生成 SSO Token):

// src/app.module.ts(Auth Server)
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { RedisModule } from '@nestjs-modules/ioredis';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './auth/auth.module';
import { User } from './users/entities/user.entity'; // 用户实体(id/username/password/role)@Module({imports: [// 配置环境变量(存储JWT密钥、Redis地址、业务系统白名单等)ConfigModule.forRoot({ isGlobal: true }),// 配置Redis(存储SSO会话、JWT黑名单)RedisModule.forRootAsync({inject: [ConfigService],useFactory: (config: ConfigService) => ({config: {host: config.get('REDIS_HOST', 'localhost'),port: config.get('REDIS_PORT', 6379),password: config.get('REDIS_PASSWORD', ''),db: 0,},}),}),// 配置JWT(生成SSO Token)JwtModule.registerAsync({global: true,inject: [ConfigService],useFactory: (config: ConfigService) => ({secret: config.get('SSO_JWT_SECRET', 'sso-prod-secret-2024'), // SSO专用密钥(需复杂)signOptions: { expiresIn: '8h' }, // SSO Token有效期(8小时,可根据需求调整)}),}),// 配置数据库(存储用户信息)TypeOrmModule.forRootAsync({inject: [ConfigService],useFactory: (config: ConfigService) => ({type: 'mysql',host: config.get('DB_HOST'),port: config.get('DB_PORT'),username: config.get('DB_USERNAME'),password: config.get('DB_PASSWORD'),database: config.get('DB_NAME'),entities: [User],synchronize: false, // 生产环境关闭自动同步}),}),AuthModule,],
})
export class AppModule {}

2. 实现 SSO 核心服务(AuthService)

封装“用户登录验证”“SSO Token 生成”“SSO Token 验证”“SSO 会话登出”等核心逻辑:

// src/auth/auth.service.ts(Auth Server)
import { Injectable, UnauthorizedException, ForbiddenException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { InjectRedis } from '@nestjs-modules/ioredis';
import { Repository } from 'typeorm';
import { JwtService } from '@nestjs/jwt';
import { Redis } from 'ioredis';
import { compareSync } from 'bcryptjs';
import { ConfigService } from '@nestjs/config';
import { User } from '../users/entities/user.entity';// SSO会话在Redis中的key前缀(避免key冲突)
const SSO_SESSION_PREFIX = 'sso:session:';
// JWT黑名单在Redis中的key前缀(登出/失效的Token)
const JWT_BLACKLIST_PREFIX = 'sso:jwt:blacklist:';@Injectable()
export class AuthService {// 业务系统白名单(仅允许白名单内的系统访问SSO验证接口,防止恶意请求)private readonly clientWhitelist: string[];constructor(@InjectRepository(User) private userRepo: Repository<User>,private jwtService: JwtService,@InjectRedis() private redis: Redis,private configService: ConfigService,) {// 从环境变量读取业务系统白名单(如"https://order-system.com,https://member-system.com")this.clientWhitelist = this.configService.get('SSO_CLIENT_WHITELIST', '').split(',').filter(Boolean);}/*** 1. 用户登录:验证账号密码,生成SSO Token与SSO会话* @param username 用户名* @param password 密码* @param clientId 业务系统标识(如"order-system",用于记录哪个系统登录)*/async ssoLogin(username: string, password: string, clientId: string) {// ① 验证业务系统是否在白名单内if (!this.clientWhitelist.includes(clientId)) {throw new ForbiddenException('非法业务系统,禁止访问SSO');}// ② 验证用户账号密码const user = await this.userRepo.createQueryBuilder('user').addSelect('user.password') // 显式查询加密后的密码.where('user.username = :username', { username }).getOne();if (!user || !compareSync(password, user.password)) {throw new UnauthorizedException('账号或密码错误');}// ③ 生成SSO Token(Payload包含用户ID、角色、业务系统ID)const ssoToken = this.jwtService.sign({sub: user.id, // 用户IDusername: user.username,role: user.role,clientId, // 当前登录的业务系统标识});// ④ 存储SSO会话到Redis(key: SSO_TOKEN, value: 包含用户ID+已登录系统列表的JSON)const ssoSessionKey = `${SSO_SESSION_PREFIX}${ssoToken}`;// 查询当前用户是否已有SSO会话(若有,追加当前业务系统)const existingSession = await this.redis.get(ssoSessionKey);const loginClients = existingSession ? [...JSON.parse(existingSession).loginClients, clientId] : [clientId];// 存储SSO会话(有效期与SSO Token一致,8小时)await this.redis.set(ssoSessionKey,JSON.stringify({userId: user.id,loginClients: [...new Set(loginClients)], // 去重,避免重复添加同一系统createdAt: Date.now(),}),'EX',8 * 60 * 60, // 8小时(与JWT有效期同步));// ⑤ 返回SSO Token与用户基础信息(不含敏感字段)const { password: _, ...userInfo } = user;return {ssoToken,userInfo,expiresIn: 8 * 60 * 60, // 有效期(秒)};}/*** 2. 验证SSO Token:供业务系统调用,确认Token有效性* @param ssoToken 业务系统传递的SSO Token* @param clientId 业务系统标识(验证Token是否属于该系统)*/async validateSsoToken(ssoToken: string, clientId: string) {// ① 验证业务系统白名单if (!this.clientWhitelist.includes(clientId)) {throw new ForbiddenException('非法业务系统,禁止验证SSO Token');}// ② 检查Token是否在黑名单(已登出/失效)const isBlacklisted = await this.redis.get(`${JWT_BLACKLIST_PREFIX}${ssoToken}`);if (isBlacklisted) {throw new UnauthorizedException('SSO Token已失效,请重新登录');}// ③ 验证JWT签名与过期时间let payload: any;try {payload = this.jwtService.verify(ssoToken);} catch (error) {throw new UnauthorizedException('SSO Token无效或已过期');}// ④ 验证Token中的业务系统标识与当前请求系统一致if (payload.clientId !== clientId) {throw new UnauthorizedException('SSO Token不属于当前业务系统');}// ⑤ 验证Redis中的SSO会话是否存在(防止Token未过期但会话被主动删除)const ssoSessionKey = `${SSO_SESSION_PREFIX}${ssoToken}`;const ssoSession = await this.redis.get(ssoSessionKey);if (!ssoSession) {throw new UnauthorizedException('SSO会话已失效,请重新登录');}// ⑥ 返回用户信息(供业务系统使用)const { userId } = JSON.parse(ssoSession);const user = await this.userRepo.findOne({ where: { id: userId } });if (!user) {throw new UnauthorizedException('用户不存在');}const { password: _, ...userInfo } = user;return { userInfo, ssoToken };}/*** 3. SSO登出:删除Redis中的SSO会话与Token黑名单,实现全系统同步登出* @param ssoToken 待登出的SSO Token*/async ssoLogout(ssoToken: string) {// ① 查询SSO会话(确认会话存在)const ssoSessionKey = `${SSO_SESSION_PREFIX}${ssoToken}`;const ssoSession = await this.redis.get(ssoSessionKey);if (!ssoSession) {throw new UnauthorizedException('SSO会话不存在');}// ② 将Token加入黑名单(防止登出后继续使用)const payload = this.jwtService.decode(ssoToken) as any;await this.redis.set(`${JWT_BLACKLIST_PREFIX}${ssoToken}`,'invalid','EX',payload.exp - Math.floor(Date.now() / 1000), // 黑名单有效期=Token剩余有效期);// ③ 删除SSO会话(全系统同步登出)await this.redis.del(ssoSessionKey);return { message: 'SSO登出成功,所有系统已同步登出' };}
}

3. 实现 SSO 接口(AuthController)

提供“登录”“Token验证”“登出”三个对外接口,供前端和业务系统调用:

// src/auth/auth.controller.ts(Auth Server)
import { Controller, Post, Get, Query, Body, HttpCode } from '@nestjs/common';
import { AuthService } from './auth.service';@Controller('sso')
export class AuthController {constructor(private authService: AuthService) {}/*** 接口1:用户登录(前端调用,如业务系统的登录页重定向到Auth Server的登录接口)* @param body { username, password, clientId }*/@Post('login')@HttpCode(200)async ssoLogin(@Body() body: { username: string; password: string; clientId: string },) {return this.authService.ssoLogin(body.username, body.password, body.clientId);}/*** 接口2:验证SSO Token(业务系统后端调用,确认用户身份)* @param ssoToken 业务系统传递的SSO Token* @param clientId 业务系统标识*/@Get('validate-token')async validateSsoToken(@Query('ssoToken') ssoToken: string,@Query('clientId') clientId: string,) {return this.authService.validateSsoToken(ssoToken, clientId);}/*** 接口3:SSO登出(任意系统的前端调用,实现全系统同步登出)* @param ssoToken 待登出的SSO Token*/@Post('logout')@HttpCode(200)async ssoLogout(@Body('ssoToken') ssoToken: string) {return this.authService.ssoLogout(ssoToken);}
}

四、业务系统(Client Server)代码实现

业务系统(如订单系统、会员系统)自身不处理用户登录,仅需实现“重定向到 Auth Server 登录”“验证 SSO Token”“生成业务令牌”三个核心逻辑,确保用户身份合法后允许访问。

1. 配置业务系统与 Auth Server 通信

首先在业务系统中配置 Auth Server 的地址、自身标识(clientId)等信息:

// src/app.module.ts(Client Server,以订单系统为例)
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { HttpModule } from '@nestjs/axios'; // 用于调用Auth Server的HTTP接口
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';@Module({imports: [ConfigModule.forRoot({ isGlobal: true }),// 配置HTTP模块(调用Auth Server的接口)HttpModule.registerAsync({inject: [ConfigService],useFactory: (config: ConfigService) => ({baseURL: config.get('SSO_AUTH_SERVER_URL', 'https://sso-auth-server.com'), // Auth Server基础地址timeout: 5000, // 超时时间}),}),// 配置业务系统自身的JWT(可选,用于生成业务令牌,减少调用Auth Server的频率)JwtModule.registerAsync({global: true,inject: [ConfigService],useFactory: (config: ConfigService) => ({secret: config.get('CLIENT_JWT_SECRET', 'order-system-secret'), // 业务系统专用密钥signOptions: { expiresIn: '2h' }, // 业务令牌有效期(短于SSO Token)}),}),UserModule,AuthModule,],
})
export class AppModule {}

2. 实现业务系统的 SSO 适配服务(ClientAuthService)

封装“重定向到 Auth Server 登录”“验证 SSO Token”“生成业务令牌”等逻辑:

// src/auth/client-auth.service.ts(Client Server)
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs';@Injectable()
export class ClientAuthService {// 业务系统自身标识(需在Auth Server的白名单中)private readonly clientId: string;// 业务系统的回调地址(Auth Server登录成功后重定向回此地址)private readonly redirectUri: string;constructor(private httpService: HttpService,private jwtService: JwtService,private configService: ConfigService,) {this.clientId = this.configService.get('SSO_CLIENT_ID', 'order-system'); // 如"order-system"this.redirectUri = this.configService.get('SSO_REDIRECT_URI','https://order-system.com/sso/callback',);}/*** 1. 生成重定向到Auth Server的登录地址* @param currentPath 当前访问的业务系统路径(登录成功后跳转回此路径)*/getSsoLoginUrl(currentPath: string) {// 构造Auth Server的登录地址,携带业务系统标识和回调地址const authServerLoginUrl = new URL('/sso/login',this.configService.get('SSO_AUTH_SERVER_URL'),);authServerLoginUrl.searchParams.append('clientId', this.clientId);authServerLoginUrl.searchParams.append('redirectUri', this.redirectUri);authServerLoginUrl.searchParams.append('state', currentPath); // 记录当前路径,用于登录后跳转return authServerLoginUrl.toString();}/*** 2. 验证SSO Token(调用Auth Server的验证接口)* @param ssoToken 从Auth Server获取的SSO Token*/async validateSsoToken(ssoToken: string) {try {// 调用Auth Server的验证接口const response = await firstValueFrom(this.httpService.get('/sso/validate-token', {params: {ssoToken,clientId: this.clientId,},}),);return response.data; // { userInfo, ssoToken }} catch (error) {// 验证失败(如Token无效/过期),抛出未授权异常throw new UnauthorizedException(error.response?.data?.message || 'SSO身份验证失败,请重新登录',);}}/*** 3. 生成业务系统自身的令牌(可选,减少重复调用Auth Server)* @param userInfo 从Auth Server获取的用户信息*/generateClientToken(userInfo: any) {return this.jwtService.sign({sub: userInfo.id,username: userInfo.username,role: userInfo.role,ssoToken: userInfo.ssoToken, // 携带SSO Token,用于后续刷新或登出});}/*** 4. 业务系统登出(调用Auth Server的登出接口,实现全系统同步登出)* @param ssoToken SSO Token*/async logout(ssoToken: string) {try {await firstValueFrom(this.httpService.post('/sso/logout', { ssoToken }),);return { message: '登出成功,已同步退出所有系统' };} catch (error) {throw new UnauthorizedException('登出失败,请重试');}}
}

3. 实现业务系统的 SSO 回调与接口保护

业务系统需提供“SSO 回调接口”(接收 Auth Server 重定向的 SSO Token)和“受保护接口”(验证用户身份):

// src/auth/client-auth.controller.ts(Client Server)
import { Controller, Get, Query, Res, UseGuards, Req, Post } from '@nestjs/common';
import { Response, Request } from 'express';
import { ClientAuthService } from './client-auth.service';
import { JwtAuthGuard } from './jwt-auth.guard'; // 业务系统自身的JWT守卫@Controller('sso')
export class ClientAuthController {constructor(private clientAuthService: ClientAuthService) {}/*** 接口1:SSO回调接口(Auth Server登录成功后重定向到这里)* 接收SSO Token,生成业务令牌,存储到Cookie或返回给前端*/@Get('callback')async ssoCallback(@Query('ssoToken') ssoToken: string,@Query('state') state: string, // 登录前的访问路径@Res() res: Response,) {if (!ssoToken) {return res.redirect('/login?error=缺少SSO令牌');}try {// ① 验证SSO Token有效性const { userInfo } = await this.clientAuthService.validateSsoToken(ssoToken);// ② 生成业务系统自身的令牌(可选)const clientToken = this.clientAuthService.generateClientToken({...userInfo,ssoToken,});// ③ 将业务令牌存储到httpOnly Cookie(推荐,防XSS)res.cookie('client_token', clientToken, {httpOnly: true,secure: process.env.NODE_ENV === 'production', // 生产环境启用HTTPSmaxAge: 2 * 60 * 60 * 1000, // 2小时(与业务令牌有效期一致)path: '/',});// ④ 重定向回登录前的路径(如用户原本访问的订单详情页)return res.redirect(state || '/');} catch (error) {return res.redirect(`/login?error=${error.message}`);}}/*** 接口2:业务系统登出接口*/@Post('logout')@UseGuards(JwtAuthGuard) // 验证业务令牌async logout(@Req() req: Request, @Res() res: Response) {// 从请求中获取SSO Token(业务令牌的Payload中携带)const ssoToken = req.user.ssoToken;await this.clientAuthService.logout(ssoToken);// 清除业务令牌Cookieres.clearCookie('client_token');return res.send({ message: '登出成功' });}
}

4. 业务系统的接口保护(JWT守卫)

使用业务系统自身的 JWT 守卫保护接口,验证用户身份:

// src/auth/jwt-auth.guard.ts(Client Server)
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';@Injectable()
export class JwtAuthGuard implements CanActivate {constructor(private jwtService: JwtService) {}canActivate(context: ExecutionContext): boolean {const request = context.switchToHttp().getRequest<Request>();// 从Cookie或请求头获取业务令牌const token = request.cookies.client_token || (request.headers.authorization?.split(' ')[1] || '');if (!token) {throw new UnauthorizedException('未登录,请先通过SSO登录');}try {// 验证业务令牌const payload = this.jwtService.verify(token);// 将用户信息附加到req.userrequest.user = payload;} catch (error) {throw new UnauthorizedException('登录已过期,请重新登录');}return true;}
}

5. 业务系统的受保护接口示例

// src/user/user.controller.ts(Client Server,订单系统的用户接口)
import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';@Controller('user')
export class UserController {// 需SSO登录才能访问的接口:获取当前用户的订单列表@UseGuards(JwtAuthGuard)@Get('orders')getUserOrders(@Req() req) {// req.user包含从SSO获取的用户信息return {userId: req.user.sub,username: req.user.username,orders: [], // 业务逻辑:查询该用户的订单message: '订单列表查询成功',};}
}

五、SSO 前端适配逻辑(通用方案)

前端需实现“未登录时重定向到 Auth Server”“接收 SSO Token 并存储”“请求时携带业务令牌”三大逻辑,以下是简化示例(基于 Vue/React 通用逻辑):

// sso-utils.js(前端工具函数)
import { getToken, setToken, removeToken } from './storage'; // 封装localStorage/Cookie操作// 1. 初始化:检查是否已登录,未登录则重定向到Auth Server
export function initSso() {const currentPath = window.location.pathname;const isLoggedIn = !!getToken('client_token');if (!isLoggedIn && !currentPath.includes('/sso/callback')) {// 未登录且不在回调页,重定向到Auth Server登录const clientAuthService = new ClientAuthService(); // 封装的API服务const loginUrl = clientAuthService.getSsoLoginUrl(currentPath);window.location.href = loginUrl;}
}// 2. 处理SSO回调(在/sso/callback页面调用)
export async function handleSsoCallback() {const urlParams = new URLSearchParams(window.location.search);const ssoToken = urlParams.get('ssoToken');const state = urlParams.get('state');if (ssoToken) {try {// 调用业务系统的回调接口,验证Token并获取业务令牌await api.post('/sso/callback', { ssoToken });// 跳转回登录前的路径window.location.href = state || '/';} catch (error) {// 处理错误(如Token无效)window.location.href = `/login?error=${error.message}`;}}
}// 3. 请求拦截器:为所有API请求附加业务令牌
export function setupRequestInterceptor(axiosInstance) {axiosInstance.interceptors.request.use((config) => {const token = getToken('client_token');if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;});// 响应拦截器:处理401(登录过期)axiosInstance.interceptors.response.use((response) => response,(error) => {if (error.response?.status === 401) {// 登录过期,清除令牌并重定向到登录页removeToken('client_token');window.location.href = '/login';}return Promise.reject(error);},);
}

六、SSO 关键安全与扩展说明

1. 安全加固

  • 白名单机制:Auth Server 严格校验业务系统的 clientId,仅允许白名单内的系统访问,防止恶意系统滥用 SSO;
  • Token 传输安全:SSO Token 和业务令牌必须通过 HTTPS 传输,避免中间人攻击;
  • 令牌存储安全:前端优先使用 httpOnly Cookie 存储令牌(防 XSS 攻击),配合 SameSite=Strict 防 CSRF;
  • 会话有效期:SSO Token 有效期不宜过长(如 8 小时),业务令牌可更短(如 2 小时),降低令牌泄露风险。

2. 扩展场景

  • 多端适配:移动端(App)可通过 WebView 集成 SSO,或使用 OAuth 2.0 扩展(如授权码模式);
  • 刷新令牌机制:为 SSO Token 添加刷新令牌(存储在 Redis),避免用户频繁登录;
  • 权限同步:当用户权限在 Auth Server 变更时,通过 Redis 发布订阅机制通知所有业务系统刷新权限缓存。

通过以上实现,多个业务系统可共享统一的用户身份,用户只需一次登录即可访问所有关联系统,大幅提升用户体验,同时通过 Redis 实现会话集中管理,确保登出时全系统同步失效,兼顾安全性与易用性。

http://www.xdnf.cn/news/18618.html

相关文章:

  • Vue2+Vue3前端开发笔记合集
  • 【运维进阶】case、for、while、until语句大合集
  • VSCode+Qt+CMake详细地讲解
  • 嵌入式系统bringup通用流程
  • halcon(一)一维码解码
  • 目标检测数据集 第007期-基于yolo标注格式的茶叶病害检测数据集(含免费分享)
  • MATLAB 入门:从变量定义到基础绘图的完整上手指南
  • 05-ArkUI界面开发
  • 前端漏洞(上)- CSRF漏洞
  • C++ Core Guidelines: 最佳实践与深入解析
  • .net9 解析 jwt 详解
  • Go语言 Hello World 实例
  • RabbitMQ--消费端异常处理与 Spring Retry
  • 2025最新ncm转MP3,网易云ncm转mp3格式,ncm转mp3工具!
  • ThinkPHP8学习篇(四):请求和响应
  • VSCode无权访问扩展市场
  • 【数据结构】-5- 顺序表 (下)
  • 【JavaEE】了解synchronized
  • Java 基础学习总结(211)—— Apache Commons ValidationUtils:让参数校验从 “体力活“ 变 “优雅事“
  • 电动车运行原理与最新人工智能驾驶技术在电动车上的应用展望:从基础动力系统到L5级完全自动驾驶的技术深度解析
  • 大语言模型的自动驾驶 LMDrive/DriveVLM-Dual
  • Kubernetes部署Prometheus+Grafana 监控系统NFS存储方案
  • Spark04-MLib library01-机器学习的介绍
  • Spring创建的方式
  • 在 Ubuntu 24.04 或 22.04 LTS 服务器上安装、配置和使用 Fail2ban
  • 【LLM】DeepSeek-V3.1-Think模型相关细节
  • Android - 用Scrcpy 将手机投屏到Windows电脑上
  • MySQL学习记录-基础知识及SQL语句
  • SSRF的学习笔记
  • React useState 全面深入解析