shiro+redis并发登陆控制

主要实现用户同时在线人数控制

1.项目结构


我们在application.yml 实现对用户同时在线人数和踢出之前还是之后的控制。

1.1 application.yml

spring:
  freemarker:
    suffix: .html

  redis:
    host: 127.0.0.1
    database: 0
    port: 6379
    jedis:
      pool:
        max-idle: 8
        min-idle: 1
        max-wait: -1
        max-active: 8

shiro:
  #踢出之前用户还是之后用户
  kickoutAfter: true
  # 最大同时在线人数,-1 表示无限制
  maxSession: 1

1.2 pom.xml

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.7.1</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.7</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <!--Redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--大佬写的shiro整合redis-->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>
    </dependencies>

1.3 User实体类

此处注意实体类要序列化,否则session会报错

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

    private Integer userId;
    private String userName;
    private String password;

}

1.4 ShiroConfig配置

package com.zjl.config;

import com.zjl.realm.MyRealm;
import com.zjl.filter.KickoutSessionFilter;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Value("${shiro.kickoutAfter}")
    private boolean kickoutAfter;

    @Value("${shiro.maxSession}")
    private Integer maxSession;

    //@Autowired
    //private RedisTemplate redisTemplate;

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // shiro核心安全接口
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登陆地址
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 未授权地址
        shiroFilterFactoryBean.setUnauthorizedUrl("/login");
        // 过滤地址
        LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        // shiro过滤链配置
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/loginUser","anon");
        filterChainDefinitionMap.put("/index","anon");
        //自定义过滤器
        Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("kickout",kickoutSessionFilter());
        shiroFilterFactoryBean.setFilters(filters);

        filterChainDefinitionMap.put("/**","kickout,authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager(MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setCacheManager(redisCacheManager());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }


    /**
     * Redis缓存管理器
     *
     * @return {@link RedisCacheManager}
     */
    @Bean
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setExpire(1800);
        // 配置缓存的话要求放在session里面的实体类必须有个id标识 注:这里id为用户表中的主键,否-> 报:User must has getter for field: xx
        redisCacheManager.setPrincipalIdFieldName("userId");
        return redisCacheManager;
    }

    /**
     * Redis管理器
     * @return {@link RedisManager}
     */
    @Bean
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setTimeout(2000);
        return redisManager;
    }

    /**
     * Redis SessionDao
     * @return {@link RedisSessionDAO}
     */
    @Bean
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        return redisSessionDAO;
    }

    @Bean
    public MyRealm myRealm(){
        return new MyRealm();
    }

    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(this.redisSessionDAO());
        //session过期时间 1800000为半小时
//        sessionManager.setGlobalSessionTimeout(1800000);
        return sessionManager;
    }

    @Bean
    public KickoutSessionFilter kickoutSessionFilter(){
        KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();
        kickoutSessionFilter.setMaxSession(maxSession);
        kickoutSessionFilter.setKickoutUrl("/login?kickout=1");
        kickoutSessionFilter.setKickoutBefore(kickoutAfter);
        kickoutSessionFilter.setSessionManager(sessionManager());
        kickoutSessionFilter.setCacheManager(redisCacheManager());
        return kickoutSessionFilter;
    }

    /**
     * Session ID生成管理器
     *
     * @return
     */
    @Bean(name = "sessionIdGenerator")
    public JavaUuidSessionIdGenerator sessionIdGenerator() {
        JavaUuidSessionIdGenerator sessionIdGenerator = new JavaUuidSessionIdGenerator();
        return sessionIdGenerator;
    }


}

1.5 KickoutSessionFilter过滤器

主要实现踢人功能。

package com.zjl.filter;


import com.zjl.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.*;

@Slf4j
public class KickoutSessionFilter extends AccessControlFilter {

    // 用户被踢出后重定向的地址
    private String kickoutUrl = "/";
    // 使用RedisCacheManager 存储的cache前缀名
    public static String ONLINE_USER = "online_user";
    // 踢出当前登录用户还是之前用户
    private boolean kickoutAfter = true;
    // 同一个账号最大同时在线人数
    private int maxSession = -1;
    // SessionManager session管理器
    private SessionManager sessionManager;
    // 缓存
    private Cache<String, LinkedList<Serializable>> cache;

    public void setCacheManager(CacheManager cacheManager) {
        this.cache = cacheManager.getCache(ONLINE_USER);
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

    public void setKickoutUrl(String kickoutUrl) {
        this.kickoutUrl = kickoutUrl;
    }

    public void setKickoutBefore(boolean kickoutAfter) {
        this.kickoutAfter = kickoutAfter;
    }

    /**
     * 是否允许访问,true表示允许访问
     * @param servletRequest
     * @param servletResponse
     * @param o
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        return false;
    }

    /**
     * 访问拒绝时是否自己处理,return false表示已经自己处理,true 表示自己不处理,继续由下一个拦截器执行
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        //如果用户未登录,跳过此过程
        if(!subject.isAuthenticated() && !subject.isRemembered()) {
            return true;
        }
        // -1表示无人数限制
        if (maxSession == -1){
            return true;
        }

        // 获取当前登录的sessionId
        Session session = subject.getSession();
        Serializable sessionId = session.getId();

        // 获取当前登录用户信息
        User user = (User) subject.getPrincipal();
        String username = user.getUserName();

        // 读取当前用户的Redis缓存,没有就创建一个空队列
        LinkedList<Serializable> deque = cache.get(username);
        if(deque==null){
            deque = new LinkedList<>();
        }

        //如果队列里没有此sessionId,且用户没有被踢出;放入队列
        if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
            //将sessionId存入队列
            deque.add(sessionId);
            //将用户的sessionId队列缓存
            cache.put(username, deque);
        }

        //如果队列里的sessionId数超出最大会话数,开始踢人
        while(deque.size() > maxSession) {
            // 保存待踢出用户的sessionId
            Serializable kickoutSessionId = null;
            //如果踢出后登录者,更新队列
            if(kickoutAfter) {
                kickoutSessionId = deque.removeFirst();
                cache.put(username, deque);
            } else {
                //否则踢出前者,更新队列
                kickoutSessionId = deque.removeLast();
                cache.put(username, deque);
            }
            // 对即将踢出的用户标记
            try {
                Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
                if(kickoutSession != null) {
                    //设置会话的kickout属性表示踢出
                    kickoutSession.setAttribute("kickout", true);
                }
            } catch (Exception e) {
                log.error("踢出用户异常!");
            }
        }

        //如果被踢出了,直接退出,重定向到踢出后的地址
        if (session.getAttribute("kickout")!=null && (Boolean)session.getAttribute("kickout")) {
            try {
                //退出登录
                subject.logout();
            } catch (Exception e) { //ignore
                log.error("用户退出异常!");
            }
            // 保存请求
            saveRequest(request);
            //若为Ajax请求
            if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) {
                // 自定义ajax返回结果
                try {
                    response.setCharacterEncoding("UTF-8");
                    PrintWriter out = response.getWriter();
                    out.println("您已经在其他地方登录,请重新登录!");
                    out.flush();
                    out.close();
                } catch (Exception e) {
                    log.error("未知异常,请联系管理员,或刷新重试!");
                }
            }else{
                //重定向到踢出地址
                WebUtils.issueRedirect(request, response, kickoutUrl);
            }
            return false;
        }
        return true;
    }

}

此处值得注意的点主要如下:

  • kickoutUrl: 踢出用户后,重定向到的地址;此处使用 /login?kickout=1 后续便于js判断用户是否被踢出
  • maxSession: 同时最大在线人数;
  • ONLINE_USER : redisCache 的前缀名
    在这里插入图片描述
  • kickoutAfter: 控制踢出前一个还是后一个。

1.6 MyRealm

package com.zjl.realm;

import com.zjl.entity.User;
import org.apache.shiro.SecurityUtils;
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.subject.Subject;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

public class MyRealm extends AuthorizingRealm {

    private static final List<User> users = new ArrayList<>();{
        users.add(new User(1,"zhangsan","123456"));
        users.add(new User(2,"lisi","123456"));
        users.add(new User(3,"wangwu","123456"));
    }

    /**
     * 授权
     * @param principalCollection
     * @return {@link AuthorizationInfo}
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        return info;
    }

    /**
     * 认证
     * @param authenticationToken 身份验证令牌
     * @return {@link AuthenticationInfo}
     * @throws AuthenticationException 身份验证异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        String password = "";
        if (token.getPassword() != null){
            password = new String(token.getPassword());
        }
        // 登陆操作
        User user = login(username,password);
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,password,getName());
        return info;
    }

    /**
     * 登录
     *
     * @param userName 用户名
     * @param password 密码
     */
    public User login(String userName ,String password){
        //判断用户名
        for(User user:users){
            if (userName.equals(user.getUserName())){
                //判断密码
                if (password.equals(user.getPassword())){
                    return user;
                }else {
                    throw new IncorrectCredentialsException();
                }
            }
        }
        throw new UnknownAccountException();
    }
}

1.7 LoginController

此处简单写了几个方法,不再过多阐述,后续测试页面有效果。

package com.zjl.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * 登录控制器
 * @author zjl
 * @date 2021/07/09
 */
@Controller
public class LoginController {

    @GetMapping("/login")
    public String login()
    {
        return "index";
    }

    @GetMapping("/success")
    public String success()
    {
        return "success";
    }

    @PostMapping("/loginUser")
    @ResponseBody
    public Map<String,Object> login(String userName, String password) {
        Map<String,Object> map = new HashMap<>();
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password,false);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            map.put("msg","登陆成功");
            map.put("code",0);
        } catch (AuthenticationException e) {
            e.printStackTrace();
            map.put("msg","账号或密码错误");
        }
        return map;
    }

    @PostMapping("/logout")
    @ResponseBody
    public Map<String,Object> logout(String userName, String password) {
        Map<String,Object> map = new HashMap<>();
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        map.put("msg","退出成功");
        map.put("code",0);
        return map;
    }

    @RequestMapping("/toInfo")
    public ModelAndView info(){
        ModelAndView view = new ModelAndView();
        view.setViewName("/info");
        return view;
    }

}

1.8 html页面

1.8.1 index.html (登录页)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆页</title>
    <!-- CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
    <div class="container">
        <#--登陆框-->
        <div style="width: 500px;margin:200px auto;vertical-align: middle">
            <form>
                <div class="form-group">
                    <label for="userName">用户名</label>
                    <input type="text" class="form-control" id="userName" aria-describedby="emailHelp">
                </div>
                <div class="form-group">
                    <label for="password">密码</label>
                    <input type="password" class="form-control" id="password">
                </div>
                <button type="button" class="btn btn-primary" style="float: right" onclick="login()">登陆</button>
            </form>
        </div>
    </div>
</body>
<script>
    $(function () {
        var href=location.href;
        if(href.indexOf("kickout")>0){
            alert("您的账号在另一台设备上登录,如非本人操作,请立即修改密码!");
        }
    })
    function login() {
        let userName = $('#userName').val();
        let password = $('#password').val();
        console.log()
        $.ajax({
            type : 'POST',
            url: "/loginUser",
            dataType:"json",
            data : {
                userName:userName,
                password:password,
            },
            success: function(res) {
                console.log(res)
                alert(res.msg)
                if (res.code === 0){
                    location.href = "/success"
                }
            }
        });
    }
</script>
</html>

此段代码:判断当前用户是否重复登陆:
在这里插入图片描述

1.8.2 success.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆页</title>
    <!-- CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
    <div class="container">
        <h1>登陆成功</h1>
        <button type="button" class="btn btn-primary" style="float: right" onclick="logout()">退出登陆</button>
        <a href="/toInfo">账号多人登陆提示</a>
    </div>
</body>
<script>
    function logout() {
        $.ajax({
            type : 'POST',
            url: "/logout",
            dataType:"json",
            data : {
            },
            success: function(res) {
                if (res.code === 0){
                    location.href = "/"
                }
            }
        });
    }
</script>
</html>

1.8.3 info.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆页</title>
    <!-- CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
    <div class="container">
        <h1>测试页面</h1>
    </div>
</body>
</html>

2.测试

启动项目,访问 localhost:8080 测试账号:zhangsan/123456
在这里插入图片描述
我们先打开一个浏览器登陆。再重新打开一个不同的浏览器。或再打开此浏览器,开启无痕窗口。
在这里插入图片描述

在这里插入图片描述
此时点击第一个浏览器的 账号多人登陆提示 提示重复登陆!
在这里插入图片描述
至此,shiro加redis的踢人功能完成!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Spring Boot是一种用于快速开发Java应用程序的框架,它提供了许多便捷的功能和特性,如自动配置、简化的部署等。MyBatis Plus是一个MyBatis的增强工具,可以更便捷地操作数据库。Shiro是一个强大的Java安全框架,可以提供身份认证、授权、会话管理等安全相关的功能。Redis是一个高性能的键值对存储系统,常用于缓存、分布式锁等场景。Template是Spring框架中用于渲染视图的模板引擎。 综上所述,Spring Boot与MyBatis Plus、ShiroRedis Template一起使用可以构建一个功能强大、高效、安全的应用程序。Spring Boot提供了便捷的开发环境和配置,使得整个项目的搭建和部署更加简单。MyBatis Plus提供了简洁的API,可以更方便地操作数据库,减少了开发人员的工作量。Shiro可以提供安全相关的功能,保护应用程序的数据和资源安全。Redis作为缓存可以提高应用程序的访问速度,使用分布式锁等功能可以保证数据一致性和并发控制。Template可以方便地渲染视图,使得前端页面开发更加简单。 总之,Spring Boot与MyBatis Plus、ShiroRedis Template的集成可以帮助开发人员快速构建功能完善、高效、安全的应用程序。它们各自的特性和功能相互配合,提供了一种快速开发的解决方案,为开发人员提供了更好的开发体验。 ### 回答2: Spring Boot是一个用于简化Spring应用程序开发的框架,它提供了自动配置和快速开发的特性。MyBatis Plus是基于MyBatis的增强工具,它简化了与数据库的交互,提供了很多便捷的方法和功能。Shiro是一个用于身份认证和授权的安全框架,它可以帮助我们实现用户身份认证、权限控制和会话管理的功能。Redis是一个开源的内存数据库,它提供了对数据的高速缓存和持久化存储的功能。Redis Template是Spring对Redis进行操作的一个封装工具,它提供了一系列的方法用于对Redis进行增删改查的操作。 使用Spring Boot可以简化项目的搭建和配置,通过自动配置可以省去很多繁琐的步骤。使用MyBatis Plus可以不用编写繁琐的SQL语句,只需定义实体类和Mapper接口即可完成数据库的操作。使用Shiro可以轻松实现用户的身份认证和权限控制,保障系统的安全性。使用Redis可以提高系统的性能,通过缓存机制减少数据库的访问次数。 结合起来使用,可以构建一个高效、安全和可靠的Web应用程序。Spring Boot提供了集成MyBatis Plus和Shiro的插件,可以方便地使用这两个框架。Redis Template可以与Spring Boot的缓存框架一起使用,实现高速缓存。通过这些技术的使用,我们可以快速开发出功能完善的Web应用,提高开发效率和系统性能。 ### 回答3: SpringBoot是Java中一个开源的应用程序框架,它可以简化开发过程,提供了许多开箱即用的功能和库,使得开发者能够更快速地构建应用程序。 MyBatisPlus是一个基于MyBatis的增强工具,它提供了更方便、更强大的操作数据库的功能,大大简化了数据库操作的代码。 Shiro是Java中一个功能强大且易于使用的安全框架,它提供了身份验证、授权、加密、会话管理等功能,可以帮助开发者实现应用程序的安全控制Redis是一个开源的内存数据库,它可以用作缓存、消息队列等,具有高性能、持久化、分布式等特点。 Template是Spring框架中的一个模板引擎,它支持HTML、XML、JSON等多种模板语言,用于将动态数据渲染到模板中,生成最终的静态页面或其他格式的文件。 综合以上技术,可以构建一个高效、安全、可靠的Web应用。使用SpringBoot可以简化项目的搭建和配置,MyBatisPlus可以方便地操作数据库,Shiro可以保护应用程序的安全,Redis可以提高系统的性能和可扩展性,Template可以方便地生成动态页面。 例如,我们可以使用SpringBoot搭建一个基于MyBatisPlus的后台管理系统,使用Shiro完成用户的身份验证和权限控制,使用Redis作为缓存存储用户的会话信息,使用Template将动态数据渲染到页面中。这样的系统具有良好的性能和安全性,提供了友好的用户界面和丰富的功能。 总之,SpringBoot、MyBatisPlus、ShiroRedis和Template等技术可以共同协作,帮助我们构建出高质量、高效率的应用程序。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值