spring boot整合shiro_Spring框架介绍及使用

spring boot整合shiro_Spring框架介绍及使用目录1、Shiro简介1.1、Shiro是什么?1.2、有哪些功能?1.3、Shiro架构(外部)2、快速入门(QuickStartShiro)2.1、项目结构2.2、导入shiro依赖,这是我的pom所有依赖。(pom.xml)2.3、相关配置文件(1)log4j.properties——官网(2)shiro.ini——官网(3)启动类Quickstart——官网3、SpringBoot集成Shiro框架3.1SpringBoot整合Shir…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

目录

1、Shiro简介

1.1、Shiro是什么?

1.2、有哪些功能?

1.3、Shiro架构(外部)

2、快速入门(QuickStartShiro)

 2.1、项目结构

 2.2、导入shiro依赖,这是我的pom所有依赖。(pom.xml)

 2.3、相关配置文件

(1)log4j.properties——官网

(2)shiro.ini——官网

(3)启动类 Quickstart——官网

3、SpringBoot集成Shiro框架

3.1SpringBoot整合Shiro环境搭建

1、项目结构

2、新建一个项目或模块,勾选依赖。(省略…….)

3、数据库(Mysql)

3、pom.xml依赖。(这是我pom所有依赖)

4、ShiroConfig(ShiroConfig配置)

5、UserRealm(自定义UserRealm)

6、MyController(用户控制层)

7、UserMapper(用户映射层)

8、User(用户实体类)

9、UserServiceImpl(业务逻辑)

10、UserService(用户接口实现层)

11、SpringbootshiroApplication(启动类)

12、UserMapper.xml

13、style.css

14、img  —> background.jpg

15、add.html

16、update.html

17、index.html

18、login.html(登录页)

19、application.properties(配置)

20、application.yml(配置)


1、Shiro简介

1.1、Shiro是什么?

  • Apache Shiro 是 Java 的一个安全(权限)框架。

  • Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。

  • Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。

  • 下载地址

1.2、有哪些功能?

image-20200729114647110

  • Authentication:身份认证/登录,验证用户是不是拥有相应的身份

  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限

  • Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web 环境的

  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储

  • Web Support:Web 支持,可以非常容易的集成到Web 环境

  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率

  • Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去

  • Testing:提供测试支持

  • “Run As”:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问

  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

1.3、Shiro架构(外部)

从外部来看Shiro,即从应用程序角度的来观察如何使用Shiro完成工作

image-20200729114702566

  • Subject:应用代码直接交互的对象是Subject,也就是说Shiro的对外API 核心就是Subject。Subject 代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;与Subject 的所有交互都会委托给SecurityManager;Subject 其实是一个门面,SecurityManager才是实际的执行者
     
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且其管理着所有Subject;可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色

  • Realm:Shiro从Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm 看成DataSource

1.4、Shiro架构(内部)

image-20200729114720578

  • Subject:任何可以与应用交互的“用户”;
  • SecurityManager:相当于SpringMVC中的DispatcherServlet;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证、授权、会话及缓存的管理。
  • Authenticator:负责Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  • Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  • Realm:可以有1 个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的Realm;
  • SessionManager:管理Session 生命周期的组件;而Shiro并不仅仅可以用在Web 环境,也可以用在如普通的JavaSE环境
    CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密。

2、快速入门(QuickStartShiro)

2.1、项目结构

spring boot整合shiro_Spring框架介绍及使用

2.2、导入shiro依赖,这是我的pom所有依赖。(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.quickstartshiro</groupId>
    <artifactId>shiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shiro</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

    <dependencies>

            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.5.3</version>
            </dependency>

            <!-- configure logging -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>jcl-over-slf4j</artifactId>
                <version>1.7.26</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <version>1.7.26</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>


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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.4.3</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.quickstartshiro.ShiroApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

 这段是关键代码:

spring boot整合shiro_Spring框架介绍及使用

 2.3、相关配置文件

(1)log4j.properties——官网

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

(2)shiro.ini——官网

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

(3)启动类 Quickstart——官网

package com.quickstartshiro;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 * 简单入门Shiro使用API
 *
 * @since 0.9 RC2
 */
public class QuickStart {

    private static final transient Logger log = LoggerFactory.getLogger(QuickStart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        // 读取配置文件:
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        // 获取当前的用户对象 Subject
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        //通过当前用户拿到Shiro的Session 可以脱离web存值取值
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //判断当前的用户是否被认证
        if (!currentUser.isAuthenticated()) {
            //Token 令牌
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            //设置记住我
            token.setRememberMe(true);
            try {
                //执行登录操作
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        // 检查角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        //粗粒度
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        //细粒度
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        //注销
        currentUser.logout();

        //结束
        System.exit(0);
    }
}

 spring boot整合shiro_Spring框架介绍及使用

3、SpringBoot集成Shiro框架

3.1SpringBoot整合Shiro环境搭建

1、项目结构

spring boot整合shiro_Spring框架介绍及使用

2、新建一个项目或模块,勾选依赖。(省略…….)

      Web  —>  Spring Web   

      Template Engines   —>  Thymeleaf

3、数据库(Mysql)

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `username` varchar(255) DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `perms` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

spring boot整合shiro_Spring框架介绍及使用

3、pom.xml依赖。(这是我pom所有依赖)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.springbootshiro</groupId>
    <artifactId>springbootshiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootshiro</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

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


        <!--shiro-thymeleaf整合-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>


        <!--
      Subject  用户
      SecurityManager 管理所有用户
      Realm 连接数据库
-->
<!--        <dependency>-->
<!--            <groupId>org.projectlombok</groupId>-->
<!--            <artifactId>lombok</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.23</version>
        </dependency>

        <!--引入mybatis,这是MyBatis官方提供的适配spring Boot的,而不是spring Boot自己的-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>


        <!--shiro整合spring的包-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.springbootshiro.SpringbootshiroApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

4、ShiroConfig(ShiroConfig配置)

package com.springbootshiro.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //1、创建realm对象,需要自定义类
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

    //2、DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getdefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联userRealm
        securityManager.setRealm(userRealm);
        return securityManager;

    }

    //3、shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getdefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

        //实现shiro的内置过滤器
        /**
         * anon:无需认证就可以访问
         * authc:必须认证了才能访问
         * user:必须拥有记住我功能才能用
         * perms:拥有对某个资源的权限才能访问
         * role:拥有某个角色权限
         */

        //拦截
        Map<String, String> filter = new LinkedHashMap<>();

        //授权,正常的情况下,未授权跳转到未授权页面
        filter.put("/user/add","perms[user:add]");
        filter.put("/user/update","perms[user:update]");


        filter.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filter);

        //设置登录的请求
        bean.setLoginUrl("/toLogin");

        //未授权页面
        bean.setUnauthorizedUrl("/noauth");

        return bean;
    }

    // 整合ShiroDialect: 用来整合 Shiro thymeleaf
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }

}

5、UserRealm(自定义UserRealm)

package com.springbootshiro.config;

import com.springbootshiro.pojo.User;
import com.springbootshiro.service.UserService;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

//自定义的UserRealm
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
        System.out.println("执行了=>授权doGetAuthorizationInfo");

        //SimpleAuthorizationInfo
        SimpleAuthorizationInfo info =  new SimpleAuthorizationInfo();

        //拿到当前登录的这个对象
        Subject subject = SecurityUtils.getSubject();
        //拿到User
        User currentUser = (User)subject.getPrincipal();
        //设置当前用户的权限
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了=>认证doGetAuthorizationInfo");

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //连接真实数据库
        User user = userService.queryUserByName(userToken.getUsername());

        //没有这个人
        if (user == null){
            //UnknownAccountException
            return null;
        }

//        Subject currentSubject = SecurityUtils.getSubject();
//        Session session = currentSubject.getSession();
//        session.setAttribute("loginUser",user);

        //可以加密,MD5:e10adc3949ba59abbe56e057f20f883e  MD5:盐值加密
        //密码认证,shiro做
        return new SimpleAuthenticationInfo(user,user.getPassword(),"");
    }
}

6、MyController(用户控制层)

package com.springbootshiro.controller;

import org.apache.shiro.SecurityUtils;
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.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MyController {

//    @RequestMapping({"/","/index"})
//    public String toIndex(Model model){
//        model.addAttribute("msg","hello,Shiro");
//        return "index";
//    }

    @RequestMapping("/user/add")
    public String add() {
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update() {
        return "user/update";
    }

    @RequestMapping({"/","/toLogin"})
    public String toLogin() {
        return "login";
    }

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取一个用户
        Subject subject = SecurityUtils.getSubject();

        //封装用户登录的数据
        UsernamePasswordToken token = new UsernamePasswordToken(username,password );

        try{
            subject.login(token);
            return "index";
        }catch (UnknownAccountException e){//用户名不存在
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){//密码不存在
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }

    @RequestMapping("/noauth")
    @ResponseBody
    public String unauthorized(){
        return "未经授权无法访问此页面";
    }


}

7、UserMapper(用户映射层)

package com.springbootshiro.mapper;

import com.springbootshiro.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface UserMapper {
    public User queryUserByName(String username);
}

8、User(用户实体类)

package com.springbootshiro.pojo;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 用户密码实体类
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String username;
    private String password;
    private String perms;
}

9、UserServiceImpl(业务逻辑)

package com.springbootshiro.service.impl;

import com.springbootshiro.mapper.UserMapper;
import com.springbootshiro.pojo.User;
import com.springbootshiro.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 业务逻辑
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUserByName(String username) {
        return userMapper.queryUserByName(username);
    }
}

10、UserService(用户接口实现层)

package com.springbootshiro.service;

import com.springbootshiro.pojo.User;

/**
 * 用户接口实现层
 */
public interface UserService {
    public User queryUserByName(String username);
}

11、SpringbootshiroApplication(启动类)

package com.springbootshiro;

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

@SpringBootApplication
public class SpringbootshiroApplication {

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

}

12、UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.springbootshiro.mapper.UserMapper">

<!--    <select id="queryUserList" resultType="com.springbootshiro.pojo.User">-->
<!--        select * from user;-->
<!--    </select>-->

<!--    <select id="queryUserById" resultType="com.springbootshiro.pojo.User">-->
<!--        select * from user where id = #{id};-->
<!--    </select>-->

<!--    <insert id="addUser" parameterType="com.springbootshiro.pojo.User">-->
<!--        insert into user (id, username, password) values (#{id},#{username},#{password});-->
<!--    </insert>-->

<!--    <update id="updateUser" parameterType="com.springbootshiro.pojo.User">-->
<!--        update user set username=#{username},password = #{password} where id = #{id};-->
<!--    </update>-->

<!--    <delete id="deleteUser" parameterType="int">-->
<!--        delete from user where id = #{id}-->
<!--    </delete>-->

    <select id="queryUserByName" parameterType="String" resultType="com.springbootshiro.pojo.User">
        select * from user where username = #{username}
    </select>
</mapper>

13、style.css

、
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,dl,dt,dd,ol,nav ul,nav li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}
article, aside, details, figcaption, figure,footer, header, hgroup, menu, nav, section {display: block;}
ol,ul{list-style:none;margin:0px;padding:0px;}
blockquote,q{quotes:none;}
blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
table{border-collapse:collapse;border-spacing:0;}

a{text-decoration:none;}
.txt-rt{text-align:right;}
.txt-lt{text-align:left;}
.txt-center{text-align:center;}
.float-rt{float:right;}
.float-lt{float:left;}
.clear{clear:both;}
.pos-relative{position:relative;}
.pos-absolute{position:absolute;}
.vertical-base{vertical-align:baseline;}
.vertical-top{vertical-align:top;}
nav.vertical ul li{display:block;}
nav.horizontal ul li{display: inline-block;}
img{max-width:100%;}

body {
    background:url('../img/background.jpg') no-repeat 0px 0px;
    background-size: cover;
    font-family: 'Open Sans', sans-serif;
    background-attachment: fixed;
    background-position: center;
}

body a {
    transition: 0.5s all;
    -webkit-transition: 0.5s all;
    -moz-transition: 0.5s all;
    -o-transition: 0.5s all;
    -ms-transition: 0.5s all;
}

h1 {
    color: #FFF;
    text-align: center;
    letter-spacing: 6px;
    font-size: 40px;
    margin-top: 75px;
}

.container {
    width: 32%;
    margin: 50px auto;
    text-align: center;
    background:rgba(0, 0, 0, 0.43);
    -webkit-box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.75);
    -moz-box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.75);
    box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.75);
}

h2 {
    font-size:	30px;
    color: #FFF;
    padding-top: 45px;
    letter-spacing:3px;
}
form {
    padding: 65px;
}

form span {
    width: 23%;
    float: left;
    background: #fed14e;
    padding: 10.2px 10px;
    position: relative;
    color: #005377;
    font-size: 15px;
    letter-spacing: 1px;
}

form span:after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    right: -11px;
    border-top: 6px solid rgba(0, 0, 0, 0);
    border-left: 11px solid #fed14e;
    border-bottom: 6px solid rgba(0, 0, 0, 0);
    top: 14px;
}
input.name {
    width: 65%;
    padding:10px 10px 10px 15px;
    border: none;
    outline: none;
    font-size: 17px;
    letter-spacing: 1px;
    margin-bottom: 35px;
    float:left;
}

input.password {
    width: 65%;
    padding:10px 10px 10px 15px;
    border: none;
    outline: none;
    font-size: 17px;
    letter-spacing: 1px;
    margin-bottom: 35px;
    float:left;
}
.rem-for-agile{
    width:50%;
    float:left;
    text-align:left;
    font-size:13px;
    color:rgb(238, 223, 179);
}
.rem-for-agile a{
    color:rgb(238, 223, 179);
    margin-top:3px;
    display:inline-block;
    padding-left:18px;
}
.rem-for-agile a:hover{
    color: #fff;
}
input[type="checkbox"] {
    margin: 10px 5px 0px 0px;
    vertical-align: sub;
}

.login-w3{
    width:50%;
    float:right;
}
input[type="submit"]{
    background-color:#005377;
    color:#fff;
    padding:11px;
    outline: none;
    border:none;
    font-size: 17px;
    width:50%;
    cursor:pointer;
    margin-top:5px;
    transition: 0.5s all;
    -webkit-transition: 0.5s all;
    -moz-transition: 0.5s all;
    -o-transition: 0.5s all;
    -ms-transition: 0.5s all;
}
input[type="submit"]:hover{
    background:#fed14e;
    color:#000;

}
.footer-w3l{
    margin-top: 150px;
    margin-bottom: 20px;
}
.footer-w3l p {
    color:white;
    text-align:center;
    font-size:13px;
    letter-spacing:1px;
}
.footer-w3l a{
    color:white;
    text-decoration:none;
}
.footer-w3l a:hover{
    text-decoration:underline;
}
@media screen and (max-width: 1440px) {
    form span {
        font-size:14px;
        padding:10.5px 10px;
    }
    input.name{
        width: 63%;
    }
    input.password {
        width: 63%;
    }

}
@media screen and (max-width: 1366px) {
    .container {
        width: 37%;
    }

}

@media screen and (max-width: 1080px) {
    .container {
        width: 46%;
    }


}
@media screen and (max-width: 991px) {
    h1{
        margin-top:50px;
    }
    .container {
        width: 52%;
    }

}


@media screen and (max-width: 800px) {
    .container {
        width: 63%;
    }
    form {
        padding: 55px;
    }

}

@media screen and (max-width: 736px) {
    h1 {
        letter-spacing: 4px;
        font-size: 35px;
    }
    h2 {
        font-size: 28px;
        padding-top: 35px;
        letter-spacing: 2px;
    }

}
@media screen and (max-width: 667px) {
    .container {
        width: 66%;
    }
    form {
        padding: 53px;
    }

}
@media screen and (max-width: 640px) {
    form {
        padding: 42px;
    }
}
@media screen and (max-width: 600px) {
    .container {
        width: 70%;
    }
}
@media screen and (max-width: 568px) {
    .container {
        width: 74%;
    }
}
@media screen and (max-width: 480px) {
    h1 {
        letter-spacing: 2px;
        font-size: 31px;
    }
    .container {
        width: 78%;
    }
    form {
        padding: 30px;
    }
    input.name {
        width: 62%;
    }
    input.password{
        width: 62%;
    }
    input[type="submit"] {
        margin-top:8px;
    }
}
@media screen and (max-width: 414px) {
    .container {
        width: 85%;
    }
    form span {
        font-size: 12px;
    }
    input.name {
        width: 58.5%;
        padding: 9px 9px 9px 15px;
    }
    input.password{
        width: 58.5%;
        padding: 9px 9px 9px 15px;
    }
    .footer-w3l p {
        letter-spacing:0;
    }
}

@media screen and (max-width: 384px) {
    form span {
        width: 25%;
    }
    input.name {
        width: 57.5%;
    }
    input.password{
        width: 57.5%;
    }
}

@media screen and (max-width: 375px) {
    h1 {
        font-size: 27px;
    }
    h2 {
        font-size: 24px;
    }
    input.name {
        width: 57%;
    }
    input.password{
        width: 57%;
    }
    form span{
        padding-left:5px;
    }

}
@media screen and (max-width: 320px) {
    h1 {
        font-size: 23px;
        letter-spacing:1px;
    }
    h2 {
        font-size: 19px;
        letter-spacing:1px;
        padding-top:25px;
    }
    form {
        padding: 20px;
    }
    form span {
        font-size: 11px;
        width:27%;
    }
    input.name {
        padding: 8px 9px 8px 15px;
        width:55%;
    }
    input.password {
        padding: 8px 9px 8px 15px;
        width:55%;
    }
    .rem-for-agile{
        font-size:11px;
    }
    input[type="submit"] {
        padding: 9px;
        font-size: 16px;
        width: 60%;
        margin-top:10px;
    }
}

14、img  —> background.jpg

spring boot整合shiro_Spring框架介绍及使用

15、add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>add</h1>
</body>
</html>

16、update.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>update</h1>
</body>
</html>

17、index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>

<div>
    <h1>首页</h1>
    <p th:text="${msg}"></p>

<!--    &lt;!&ndash;用session实现,配合UserRealm中的session实现&ndash;&gt;-->
<!--&lt;!&ndash;    <div th:if="${session.loginUser==null}">&ndash;&gt;-->
<!--&lt;!&ndash;        <a th:href="@{/toLogin}">登录</a>&ndash;&gt;-->
<!--&lt;!&ndash;    </div>&ndash;&gt;-->

<!--    <div shiro:notAuthenticated>-->
<!--        <a th:href="@{/toLogin}">登录</a>-->
<!--    </div>-->

    <hr>

    <div shiro:hasPermission="user:add">
        <a th:href="@{/user/add}">add</a>
    </div>

    <div shiro:hasPermission="user:update">
        <a th:href="@{/user/update}">update</a>
    </div>

</div>
</body>
</html>

18、login.html(登录页)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>某某公司后台登录系统</title>
    <link rel="stylesheet" href="/css/style.css">

    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="author" content="order by dede58.com"/>
    <meta name="keywords" content="Simple Login Form Widget Responsive, Login Form Web Template, Flat Pricing Tables, Flat Drop-Downs, Sign-Up Web Templates, Flat Web Templates, Login Sign-up Responsive Web Template, Smartphone Compatible Web Template, Free Web Designs for Nokia, Samsung, LG, Sony Ericsson, Motorola Web Design" />
    <script type="application/x-javascript"> addEventListener("load", function() { setTimeout(hideURLbar, 0); }, false); function hideURLbar(){ window.scrollTo(0,1); } </script>

</head>

<body>
<h1>某某公司后台登录系统</h1>
<div class="container w3">
    <h2>登录</h2>
    <p th:text="${msg}" style="color: red;"></p>
    <form th:action="@{/login}">
        <div class="username">
            <span class="username" style="height:19px">用户:</span>
            <input type="text" name="username" class="name" placeholder="" required>
            <div class="clear"></div>
        </div>
        <div class="password-agileits">
            <span class="username"style="height:19px">密码:</span>
            <input type="password" name="password" class="password" placeholder="" required>
            <div class="clear"></div>
        </div>
        <div class="rem-for-agile">
            <input type="checkbox" name="remember" class="remember">记得我
            <br>
            <a href="#">忘记了密码</a><br>
        </div>
        <div class="login-w3">
            <input type="submit" class="login" value="Login">
        </div>
        <div class="clear"></div>
    </form>
</div>
<div class="footer-w3l">
    <p> 某某公司后台登录系统</p>
</div>
</body>
</html>

19、application.properties(配置)

# 应用名称
spring.application.name=springbootshiro
#指定freemarker的模板路径和模板的后缀
spring.freemarker.template-loader-path=classpath:/webapp/
spring.freemarker.suffix=.ftl
# 指定字符集
spring.freemarker.charset=utf-8
# 指定是否要启用缓存
spring.freemarker.cache=false
#指定是否要暴露请求和会话属性
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
# spring 静态资源扫描路径
spring.resources.static-locations=classpath:static/
server.port=8080
# THYMELEAF (ThymeleafAutoConfiguration)
# 开启模板缓存(默认值: true )
spring.thymeleaf.cache=true
# 检查模板是否存在,然后再呈现
spring.thymeleaf.check-template=true
# 检查模板位置是否正确(默认值 :true )
spring.thymeleaf.check-template-location=true
#Content-Type 的值(默认值: text/html )
spring.thymeleaf.content-type=text/html
# 开启 MVC Thymeleaf 视图解析(默认值: true )
spring.thymeleaf.enabled=true
# 模板编码
spring.thymeleaf.encoding=UTF-8
# 要被排除在解析之外的视图名称列表,⽤逗号分隔
spring.thymeleaf.excluded-view-names=
# 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
spring.thymeleaf.mode=HTML5
# 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
spring.thymeleaf.prefix=classpath:/templates/
# 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
spring.thymeleaf.suffix=.html


20、application.yml(配置)

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/mybatis_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

mybatis:
  type-aliases-package: nuc.ss.pojo
  mapper-locations: classpath:mapper/*.xml

效果:

spring boot整合shiro_Spring框架介绍及使用

spring boot整合shiro_Spring框架介绍及使用

 spring boot整合shiro_Spring框架介绍及使用

spring boot整合shiro_Spring框架介绍及使用

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/195961.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 二进制与十进制,八进制,十六进制转换_十进制转十六进制算法

    二进制与十进制,八进制,十六进制转换_十进制转十六进制算法进制转换:二进制、八进制、十六进制、十进制之间的转换不同进制之间的转换在编程中经常会用到,尤其是C语言。将二进制、八进制、十六进制转换为十进制二进制、八进制和十六进制向十进制转换都非常容易,就是“按权相加”。所谓“权”,也即“位权”。假设当前数字是N进制,那么:对于整数部分,从右往左看,第i位的位权等于Ni-1对于小数部分,恰好相反,要从左往右看,第j位的位权为N-j。…

    2022年10月18日
  • 计算机组成原理–浅谈计算机性能

    计算机组成原理–浅谈计算机性能本篇文章属于计算机组成原理的开篇之作,主讲影响计算机性能的因素与提升的计算机性能的路径。

  • ajax跨域解除方案,关于Ajax跨域问题及解决方案详析「建议收藏」

    ajax跨域解除方案,关于Ajax跨域问题及解决方案详析「建议收藏」复现Ajax跨域问题做两个简单的小项目复现Ajax跨域问题.后端语言使用Java首先是一个简单的订单系统,通过访问/loadOrderList,最终以json串形式返回订单集合.该项目使用Tomcat发布在7070端口.@RequestMapping(“/loadOrderList”)@ResponseBodypublicListloadOrderList(Stringuid){//…

  • 华为交换机的基本配置命令_华为交换机配置手册

    华为交换机的基本配置命令_华为交换机配置手册华为交换机基础配置命令参考基础配置用户模式登陆设备后,直接进入用户模式,只能执行少量查看配置的命令;视图模式用户模式下,输入system-view进入视图模式,可执行设备全局配置的命令;局部配置模式视图模式下,输入局部配置命令,如interfaceGE1/0/0,进入GE1/0/0端口配置模式,此外局部配置模式有很多种,可根据不同需求进入vla…

  • jsp显示时间的代码_空调代码大全

    jsp显示时间的代码_空调代码大全请参照以下的原代码,注意:下面的JavaScript码可以放置于想要显示时间的任意位置。以下是网页源代码您在本站逗留了varsec=0;varmin=0;varhou=0;flag=0;idt=window.setTimeout(“update();”,1000);functionupdate(){sec++;if(sec==60){sec=0;min+=1;}if(min

  • Javaer 进阶必看的 RocketMQ ,就这篇了

    Javaer 进阶必看的 RocketMQ ,就这篇了

    2020年11月20日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号