Spring Boot 整合 Shiro 实现单用户与多用户认证授权指南

一、引言:为什么选择 Shiro?

Apache Shiro 是一个强大且易用的 Java 安全框架,它提供了身份认证、授权、会话管理和加密等功能。相比其他安全框架,Shiro具有以下优势:

  • 简单易用:API 设计简洁直观,学习成本低
  • 全面的安全功能:一站式解决认证、授权、会话管理问题
  • 灵活可扩展:易于与 Spring Boot等主流框架整合
  • 轻量级:核心包体积小,不依赖其他框架 在现代 Web 应用中,无论是单用户系统还是多用户并发系统,Shiro都能提供可靠的安全保障。

二、环境准备与项目搭建

2.1 开发环境要求

组件 版本要求

JDK 1.8 及以上

Maven 3.6 及以上

Spring Boot 2.7.x(本文中使用 2.7.5 版本)

Shiro 1.10.0

2.2 创建 Spring Boot 项目

可以通过 Spring Initializr 快速创建项目,也可使用 IDE 内置的项目创建功能。

2.2.1 添加核心依赖

在 pom.xml 文件中添加以下依赖:

java 复制代码
<dependencies>
    <!-- Spring Boot Web 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Shiro 整合 Spring Boot 依赖 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-starter</artifactId>
        <version>1.10.0</version>
    </dependency>
    
    <!-- Lombok 依赖(可选,用于简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    <!-- Spring Boot 测试依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2.2.2 项目结构设计

推荐的项目结构如下:

bash 复制代码
com.example.shirodemo
├── ShiroDemoApplication.java       // 应用程序入口
├── config
│   └── ShiroConfig.java            // Shiro 核心配置类
├── controller
│   ├── LoginController.java        // 登录相关控制器
│   ├── UserController.java         // 普通用户控制器
│   └── AdminController.java        // 管理员控制器
├── entity
│   └── User.java                   // 用户实体类
├── realm
│   └── UserRealm.java              // 自定义 Realm 实现
├── service
│   └── UserService.java            // 用户服务类
└── listener
    └── MySessionListener.java      // 会话监听器(多用户扩展)

三、单用户认证授权实现

3.1 核心概念解析

在开始编码前,先了解 Shiro 的几个核心概念:

  • Subject:当前用户的安全操作对象,代表与系统交互的用户
  • SecurityManager:Shiro 的核心,管理所有用户的安全操作
  • Realm:充当数据源角色,负责用户认证和授权信息的获取
  • Authentication:身份认证,验证用户是否为合法用户
  • Authorization:授权,验证合法用户是否有权限执行某个操作

3.2 实体类设计

创建 User.java 实体类,存储用户基本信息:

java 复制代码
package com.example.shirodemo.entity;

import lombok.Data;

/**
 * 用户实体类
 */
@Data
public class User {
    private Integer id;          // 用户ID
    private String username;     // 用户名
    private String password;     // 密码(加密存储)
    private String salt;         // 盐值(用于加密)
    private String nickname;     // 昵称
    
    // 构造方法
    public User(Integer id, String username, String password, String salt, String nickname) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.salt = salt;
        this.nickname = nickname;
    }
}

注意:实际项目中密码应加密存储,这里使用 MD5 加密示例,密码 "123456" 加密后为 "202cb962ac59075b964b07152d234b70"

3.3 Shiro 核心配置

创建 ShiroConfig.java 配置类,配置 Shiro 的核心组件:

java 复制代码
package com.example.shirodemo.config;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.shirodemo.realm.UserRealm;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro 配置类
 */
@Configuration
public class ShiroConfig {

    /**
     * 自定义 Realm
     */
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    /**
     * 安全管理器
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置自定义 Realm
        securityManager.setRealm(userRealm());
        return securityManager;
    }

    /**
     * Shiro 过滤器工厂
     * 负责拦截所有请求并进行安全控制
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        
        // 配置登录页面和未授权页面
        factoryBean.setLoginUrl("/loginPage");  // 未认证时跳转的登录页面
        factoryBean.setUnauthorizedUrl("/unauthorized");  // 未授权时跳转的页面
        
        // 配置过滤器链,LinkedHashMap 保证顺序
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 公开访问的路径,无需认证
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/loginPage", "anon");
        filterChainDefinitionMap.put("/logout", "logout");  // 退出登录过滤器
        
        // 角色控制:管理员角色才能访问的路径
        filterChainDefinitionMap.put("/admin/**", "roles[admin]");
        
        // 角色控制:普通用户角色才能访问的路径
        filterChainDefinitionMap.put("/user/**", "roles[user]");
        
        // 其他所有路径需要认证
        filterChainDefinitionMap.put("/**", "authc");
        
        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return factoryBean;
    }
}

过滤器说明

Shiro 提供了多种内置过滤器,常用的有:

  • anon:匿名访问,无需认证
  • authc:需要认证才能访问
  • roles[角色名]:需要特定角色才能访问
  • perms[权限名]:需要特定权限才能访问
  • logout:退出登录过滤器

3.4 自定义 Realm 实现

Realm 是 Shiro 与数据交互的桥梁,负责认证和授权逻辑的实现。创建 UserRealm.java:

java 复制代码
package com.example.shirodemo.realm;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.shirodemo.entity.User;
import com.example.shirodemo.service.UserService;
import java.util.Set;

/**
 * 自定义 Realm,实现认证和授权逻辑
 */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 授权逻辑:获取用户的角色和权限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 从 principals 中获取用户名
        String username = (String) principals.getPrimaryPrincipal();
        
        // 从服务层获取用户角色
        Set<String> roles = userService.findRolesByUsername(username);
        
        // 创建授权信息对象并设置角色
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roles);
        
        // 实际项目中还可以添加权限信息
        // authorizationInfo.setStringPermissions(permissions);
        
        return authorizationInfo;
    }

    /**
     * 认证逻辑:验证用户身份
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 
            throws AuthenticationException {
        // 从 token 中获取用户名
        String username = (String) token.getPrincipal();
        
        // 查询用户信息
        User user = userService.findByUsername(username);
        
        // 用户不存在
        if (user == null) {
            throw new UnknownAccountException("用户名不存在");
        }
        
        // 返回认证信息,Shiro 会自动进行密码比对
        return new SimpleAuthenticationInfo(
                user.getUsername(),       // 身份标识(通常为用户名)
                user.getPassword(),       // 数据库中的密码(加密后)
                ByteSource.Util.bytes(user.getSalt()),  // 盐值
                getName()                 // Realm 名称
        );
    }
}

3.5 用户服务实现

创建 UserService.java 提供用户数据访问服务:

java 复制代码
package com.example.shirodemo.service;

import com.example.shirodemo.entity.User;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 用户服务类,提供用户信息和角色查询
 */
@Service
public class UserService {

    // 模拟数据库存储用户信息
    private static final Map<String, User> users = new HashMap<>();
    
    // 初始化用户数据
    static {
        // 管理员用户,密码123456(已加密存储)
        users.put("admin", new User(1, "admin", "202cb962ac59075b964b07152d234b70", "admin_salt", "管理员"));
        // 普通用户
        users.put("user", new User(2, "user", "202cb962ac59075b964b07152d234b70", "user_salt", "普通用户"));
    }
    
    /**
     * 根据用户名查询用户
     */
    public User findByUsername(String username) {
        return users.get(username);
    }
    
    /**
     * 根据用户名查询角色
     */
    public Set<String> findRolesByUsername(String username) {
        Set<String> roles = new HashSet<>();
        
        if ("admin".equals(username)) {
            roles.add("admin");
        }
        
        if ("user".equals(username)) {
            roles.add("user");
        }
        
        return roles;
    }
}

3.6 控制器实现

3.6.1 登录控制器

创建 LoginController.java 处理登录、登出等请求:

java 复制代码
package com.example.shirodemo.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 登录相关控制器
 */
@RestController
public class LoginController {

    /**
     * 登录页面提示
     */
    @GetMapping("/loginPage")
    public String loginPage() {
        return "请登录,访问 /login 进行登录(POST方法,参数:username和password)";
    }
    
    /**
     * 登录处理
     */
    @PostMapping("/login")
    public String login(String username, String password) {
        // 获取当前用户
        Subject subject = SecurityUtils.getSubject();
        
        // 创建用户名密码令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        
        try {
            // 执行登录
            subject.login(token);
            return "登录成功!当前用户:" + username;
        } catch (AuthenticationException e) {
            return "登录失败:" + e.getMessage();
        }
    }
    
    /**
     * 退出登录
     */
    @GetMapping("/logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "退出登录成功!";
    }
    
    /**
     * 未授权页面
     */
    @GetMapping("/unauthorized")
    public String unauthorized() {
        return "未授权,无法访问!";
    }
    
    /**
     * 首页
     */
    @GetMapping("/")
    public String index() {
        // 获取当前登录用户
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        return "欢迎访问首页,当前登录用户:" + username;
    }
}

3.6.2 用户控制器

创建 UserController.java 处理普通用户请求:

java 复制代码
package com.example.shirodemo.controller;

import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 普通用户控制器
 * 需拥有 user 角色才能访问
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/info")
    @RequiresRoles("user")  // 要求 user 角色
    public String userInfo() {
        return "这是普通用户可以看到的信息";
    }
    
    @GetMapping("/profile")
    @RequiresRoles("user")  // 要求 user 角色
    public String userProfile() {
        return "用户个人资料页面";
    }
}

3.6.3 管理员控制器

创建 AdminController.java 处理管理员请求:

java 复制代码
package com.example.shirodemo.controller;

import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 管理员控制器
 * 需拥有 admin 角色才能访问
 */
@RestController
@RequestMapping("/admin")
public class AdminController {

    @GetMapping("/info")
    @RequiresRoles("admin")  // 要求 admin 角色
    public String adminInfo() {
        return "这是管理员可以看到的信息";
    }
    
    @GetMapping("/manage")
    @RequiresRoles("admin")  // 要求 admin 角色
    public String manageUsers() {
        return "用户管理页面";
    }
}

3.7 单用户功能测试

3.7.1 启动应用

创建主类 ShiroDemoApplication.java:

java 复制代码
package com.example.shirodemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ShiroDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShiroDemoApplication.class, args);
    }
}

运行主类启动应用,默认端口为 8080。

3.7.2 测试步骤

  1. 未登录访问受保护资源 访问 http://localhost:8080/user/info 结果:会被重定向到登录提示页面
  2. 登录管理员账号 使用 POST 方法访问
    http://localhost:8080/login?username=admin\&password=123456 结果:返回
    "登录成功!当前用户:admin"
  3. 管理员访问权限测试 访问 http://localhost:8080/admin/info → 成功访问 访问
    http://localhost:8080/user/info → 访问失败(未授权)
  4. 登录普通用户账号 使用 POST 方法访问
    http://localhost:8080/login?username=user\&password=123456 结果:返回 "登录成功!当前用户:user"
  5. 普通用户访问权限测试 访问 http://localhost:8080/user/info → 成功访问 访问
    http://localhost:8080/admin/info → 访问失败(未授权)
  6. 退出登录 访问 http://localhost:8080/logout 结果:返回 "退出登录成功!"

四、扩展到多用户会话管理

在实际应用中,我们需要支持多个用户同时登录,并对在线用户进行管理。下面扩展上述示例以支持多用户场景。

4.1 会话管理配置

修改 ShiroConfig.java,添加会话管理器配置:

java 复制代码
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import java.util.Collections;

// ... 其他代码保持不变

/**
 * 会话DAO,负责会话的CRUD操作
 */
@Bean
public SessionDAO sessionDAO() {
    // 内存会话DAO,适合单机环境
    return new MemorySessionDAO();
}

/**
 * 会话管理器
 */
@Bean
public SessionManager sessionManager() {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    // 设置会话DAO
    sessionManager.setSessionDAO(sessionDAO());
    // 设置会话超时时间,单位毫秒(30分钟)
    sessionManager.setGlobalSessionTimeout(1800000);
    // 配置会话ID Cookie
    SimpleCookie cookie = new SimpleCookie("sid");
    cookie.setHttpOnly(true);  // 防止JavaScript访问
    cookie.setPath("/");       // 所有路径都有效
    sessionManager.setSessionIdCookie(cookie);
    // 启用会话ID Cookie
    sessionManager.setSessionIdCookieEnabled(true);
    // 配置会话监听
    sessionManager.setSessionListeners(Collections.singletonList(new MySessionListener()));
    return sessionManager;
}


// 更新安全管理器配置,添加会话管理器
@Bean
public SecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(userRealm());
    securityManager.setSessionManager(sessionManager());  // 添加会话管理器
    return securityManager;
} 

4.2 实现会话监听器

创建 MySessionListener.java 监听会话的创建、销毁等事件:

java 复制代码
package com.example.shirodemo.listener;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 会话监听器,监听会话的创建、停止和过期
 */
public class MySessionListener implements SessionListener {
    private static final Logger logger = LoggerFactory.getLogger(MySessionListener.class);
    // 在线用户数量,使用原子类保证线程安全
    private final AtomicInteger onlineCount = new AtomicInteger(0);

    /**
     * 会话创建时触发
     */
    @Override
    public void onStart(Session session) {
        onlineCount.incrementAndGet();
        logger.info("用户登录,当前在线人数:{}", onlineCount.get());
    }

    /**
     * 会话停止时触发(用户退出登录)
     */
    @Override
    public void onStop(Session session) {
        onlineCount.decrementAndGet();
        logger.info("用户退出,当前在线人数:{}", onlineCount.get());
    }

    /**
     * 会话过期时触发
     */
    @Override
    public void onExpiration(Session session) {
        onlineCount.decrementAndGet();
        logger.info("会话过期,当前在线人数:{}", onlineCount.get());
    }
    
    /**
     * 获取当前在线人数
     */
    public int getOnlineCount() {
        return onlineCount.get();
    }
}

4.3 扩展用户服务

更新 UserService.java,添加更多测试用户:

java 复制代码
// 初始化用户数据
static {
    // 管理员用户
    users.put("admin", new User(1, "admin", "202cb962ac59075b964b07152d234b70", "admin_salt", "管理员"));
    
    // 普通用户1
    users.put("user1", new User(2, "user1", "202cb962ac59075b964b07152d234b70", "user1_salt", "用户1"));
    
    // 普通用户2
    users.put("user2", new User(3, "user2", "202cb962ac59075b964b07152d234b70", "user2_salt", "用户2"));
    
    // 同时拥有admin和user角色的用户
    users.put("super", new User(4, "super", "202cb962ac59075b964b07152d234b70", "super_salt", "超级用户"));
}

// 更新角色查询方法
public Set<String> findRolesByUsername(String username) {
    Set<String> roles = new HashSet<>();
    
    if ("admin".equals(username) || "super".equals(username)) {
        roles.add("admin");
    }
    
    if ("user1".equals(username) || "user2".equals(username) || "super".equals(username)) {
        roles.add("user");
    }
    
    return roles;
}

4.4 实现在线用户管理功能

扩展 AdminController.java,添加在线用户管理接口:

java 复制代码
package com.example.shirodemo.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/admin")
public class AdminController {

    @Autowired
    private SessionDAO sessionDAO;

    // ... 原有代码保持不变
    
    /**
     * 获取在线用户列表
     */
    @GetMapping("/onlineUsers")
    @RequiresRoles("admin")
    public List<Map<String, Object>> getOnlineUsers() {
        // 获取所有活跃会话
        Collection<Session> sessions = sessionDAO.getActiveSessions();
        List<Map<String, Object>> onlineUsers = new ArrayList<>();
        
        for (Session session : sessions) {
            Map<String, Object> userInfo = new HashMap<>();
            userInfo.put("sessionId", session.getId());
            // 从会话中获取用户名
            userInfo.put("username", session.getAttribute(
                "org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY"));
            userInfo.put("host", session.getHost());  // 客户端IP
            userInfo.put("startTime", session.getStartTimestamp());  // 登录时间
            userInfo.put("lastAccessTime", session.getLastAccessTime());  // 最后活动时间
            userInfo.put("timeout", session.getTimeout());  // 超时时间(毫秒)
            
            onlineUsers.add(userInfo);
        }
        
        return onlineUsers;
    }
    
    /**
     * 强制用户登出
     */
    @GetMapping("/forceLogout/{sessionId}")
    @RequiresRoles("admin")
    public String forceLogout(@PathVariable String sessionId) {
        Session session = sessionDAO.readSession(sessionId);
        if (session != null) {
            sessionDAO.delete(session);  // 删除会话,强制用户登出
            return "已强制用户登出,会话ID:" + sessionId;
        }
        return "会话不存在或已过期,会话ID:" + sessionId;
    }
}

4.5 多用户功能测试

4.5.1 多用户登录测试

使用不同浏览器登录不同用户

浏览器 1:登录 admin/123456

浏览器 2:登录 user1/123456

浏览器 3:登录 user2/123456

浏览器 4:登录 super/123456

验证权限隔离

user1 访问 http://localhost:8080/admin/info → 未授权 admin 访问
http://localhost:8080/user/info → 未授权 super 访问
http://localhost:8080/admin/infohttp://localhost:8080/user/info

都能访问

4.5.2 在线用户管理测试

  1. 查看在线用户列表

    使用 admin 账号访问 http://localhost:8080/admin/onlineUsers

    结果:返回所有当前登录用户的会话信息

  2. 强制用户登出

    从在线用户列表中获取 user1 的 sessionId

    访问 http://localhost:8080/admin/forceLogout/{sessionId}

    在登录 user1 的浏览器中刷新页面 → 会被要求重新登录

    五、进阶扩展建议

五、总结

本文详细介绍了 Spring Boot 整合 Shiro 的完整过程,从单用户认证授权到多用户会话管理,涵盖了核心配置、代码实现和测试方法。通过本文的学习,你可以:

  • 理解 Shiro 的核心概念和工作原理
  • 掌握 Spring Boot 整合 Shiro 的基本配置
  • 实现用户认证和基于角色的授权
  • 管理多用户会话和在线用户

Shiro 作为一款优秀的安全框架,能够满足大多数 Web

应用的安全需求。在实际项目中,可根据具体需求进行扩展和优化,构建更安全、更可靠的认证授权系统。
shiro官网

相关推荐
BD_Marathon2 小时前
MyBatis:配置文件完成增删改查_添加
java·mybatis
程序员JerrySUN2 小时前
深度理解 KVM:Linux 内核系统学习的重要角度
java·linux·学习
midsummer_woo2 小时前
基于springboot+vue+mysql的中药实验管理系统设计与实现(源码+论文+开题报告)
vue.js·spring boot·mysql
编程小白gogogo3 小时前
Student后台管理系统查询接口
java·spring·mybatis
007php0073 小时前
使用LNMP一键安装包安装PHP、Nginx、Redis、Swoole、OPcache
java·开发语言·redis·python·nginx·php·swoole
Mr_Xuhhh3 小时前
Qt窗口(2)-工具栏
java·c语言·开发语言·数据库·c++·qt·算法
ai小鬼头4 小时前
AIStarter教你快速打包GPT-SoVITS-v2,解锁AI应用市场新玩法
前端·后端·github
续亮~4 小时前
基于Spring AI Alibaba的智能知识助手系统:从零到一的RAG实战开发
java·人工智能·spring·springaialibaba
paopaokaka_luck4 小时前
基于SpringBoot+Vue的汽车租赁系统(协同过滤算法、腾讯地图API、支付宝沙盒支付、WebsSocket实时聊天、ECharts图形化分析)
vue.js·spring boot·后端·websocket·算法·汽车·echarts