springboot进阶

前言

如何写一个网站?
前端方面:

  • 模板: 直接在网上找到整套的前端页面
  • 框架: 自己手动组合拼接导航栏侧边栏等构成页面,bootstrap,layUI,semantic-ui
    构建一个页面的整体流程:
    1,前端搞定,知道页面外观以及需要显示的数据
    2,设计数据库(难点),
    3,如果是前后端分离则同时需要独立化工程
    3,数据接口的对接交互:
    4,前后端联调测试

后期成长:
1,有一套自己熟悉的后台模板:工作必要,这里以xadmin举例:(免费)

http://x.xuebingsi.com/#

2,前端页面方面,至少自己能够通过前端框架组合出来一个网站页面,起码有以下功能

  • 首页
  • 详情页(展示页)
  • 编辑页(交互页)
  • 提交页
  • 用户页
    3,让这个网站能够独立运行

整合JDBC

重头开始建立一个项目:
勾选数据库相关配置:
在这里插入图片描述
之后在application.yaml中配置好相关信息:

spring:
  datasource:
    username: root
    password: "981216"
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

8.0版本不兼容时还需要设置时区serverTimezone=GMT,不然就会报错
在这里插入图片描述
然后简单测试一下:

@SpringBootTest
class SpringbootDataApplicationTests {

    //配置数据库信息之后springboot自动帮我们创建一个类,封装好了数据信息
    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        //查看默认数据源  HikariProxyConnection@157201184 wrapping com.mysql.cj.jdbc.ConnectionImpl@2f521c4
        //这是springboot默认使用的数据源
        System.out.println(dataSource.getClass());
        //获得数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);

        //关闭
        connection.close();
    }
}

关于加载原理:
点开配置文件中的DataSource属性值,就会发现一个DataSourcesproperties.java的类,
这个类配置了设置数据库所必需的属性,比如用户名,密码等
有这个类,根据自动装配原理就可以明白,肯定还有一个DataSourcesAutoConfiguration的类,双击shift搜索后第一眼就可以发现注入了DataSourcesproperties.java
在这里插入图片描述
在往后会发现有大量的XXX Template,这些都是springboot已经配置好的模板bean,拿到就可以直接用,例如在资源库中有一个springbootautoConfiguration,其org目录下的jdbc栏会有一个JDBCTemplate已经被注册了bean,只需要传入一个DataSource和JDBCProperties配置类就可以了,
在这里插入图片描述
这里写一个controller测试一下:

@RestController
//@RestController会将返回值自动转成json字符串
public class JDBCController {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @GetMapping("/addUser")
    public String addUser(){
        String sql="insert into yasuo.user(id,username,password) value (3,'希哥',777)";
        jdbcTemplate.update(sql);
        return "插入数据成功";
    }
    @GetMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id")int id){
        String sql="delete from yasuo.user where id=?";
        jdbcTemplate.update(sql,id);
        return "删除成功";
    }
    
    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id") int id){
        //这里利用一个动态sql来进行演示
        String sql="update yasuo.user set username=?,password=? where id="+id;
        //将所需参数封装,
        Object[] objects = new Object[2];
        objects[0]="盲仔";
        objects[1]="77777";
        //这里的update有一个重写方法,可以进行多参数传递
        jdbcTemplate.update(sql,objects);
        return "修改成功";
    }
    
    //查询数据库的所有信息,这里用到了万能的Map,这个好像在之前老秦的javase里面有讲到
    @GetMapping("/userList")
    public List<Map<String,Object>> userList(){
        String sql="select * from yasuo.user";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
}

整合Druid数据源

首先就是获取依赖:

<!--        Druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>

然后还需要在application中修改数据源配置,否则使用默认的:

spring:
  datasource:
    username: root
    password: "981216"
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    #SpringBoot默认是不注入这些的,需要自己绑定并进行解析
    #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注入,
    #这里才是druid牛逼的地方
    #如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Properity
    #则导入log4j 依赖就行
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

接下来就是druid的过人之处了,首先创建一个Cofig包,写一个druid配置类:
因为springboot 内置了servlet容器 所以没有web.xml
对应的替代方法就是配置ServletRegistrationBean

@Configuration  //所有的配置类都必须有配置注解
public class DruidConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource") //绑定配置文件
    //注意,这里绑定的属性是application.yaml的spring.DataSource属性,而不是其他的什么专有配置文件
    //这样配置文件中设置的DataSource属性就可以拿来用了
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    //后台监控,要记得注册bean到spring容器中才能生效
    @Bean
    public ServletRegistrationBean statViewServlet(){
        //此处用来获取druid的后台监控信息,是死代码
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

        //后台需要有人登陆,查看相关监控信息,这里也需要设置一个账号密码
        HashMap<String,String> initParameters = new HashMap<>();
        //增加配置 此处put的值全都是固定的,如果修改就无效了
        initParameters.put("loginUsername","admin");//登陆key 是固定的loginUsername和loginPassword,不能自定义
        initParameters.put("loginPassword","981216");

        //允许谁可以访问
        initParameters.put("allow","");//如果这里的value设置为空,则谁都可以访问

        //禁止谁能访问 initParameters.put("deny","192.168.0.013");//禁止特定ip访问

        bean.setInitParameters(initParameters);//设置初始化参数
        return bean;

    }
    
}

配置后台监控成功之后,登录http://localhost:8080/druid就可直接到监控的登录页面
在这里插入图片描述
然后根据设定的账号密码,登录之后就可以进入监控页面了:
在这里插入图片描述
内置servlet没有web.xml,那么过滤器应该怎么设置呢?同样也是在ServletRegistrationBean中进行设置的:

//filter
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new WebStatFilter()); //阿里巴巴的过滤器

        //可以过滤哪些请求?
        Map<String,String> initParameters = new HashMap<>();

        //这些格式不进行统计过滤
        initParameters.put("exclusions","*.js,*.css,/druid/*");
        bean.setInitParameters(initParameters);
        return bean;

    }

这里都是通过HashMap的键值对来进行设置属性参数的,所以key的名称也必须固定,这些名称都再源码中声明好了:
在这里插入图片描述

整合Mybatis:

要想在springboot项目中使用mybaits,就需要导入对应的配置依赖:
在这里插入图片描述
之后就在application.yaml中写好配置参数,比如数据库用户名密码,以及URL和驱动:
一定要记得时区设置(serverTimezone=GMT)

spring:
  datasource:
    username: root
    password: "981216"
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

然后就是pojo以及mapper类:
pojo略过,mapper层建好包,写好接口,之前都是在mapper包下面直接写xml配置文件,用来绑定接口对应的sql语句,现在统一放在springboot的resource目录下面:

<?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">
<!--一定要首先绑定好命名空间,否则一切白搭-->
<mapper namespace="com.xige.Mapper.UserMapper">

<!--这里的引用属性要想被识别,就必须设置别名,并且还要能被spring识别-->
    <select id="queryUserList" resultType="User">
        select * from Blog where id = #{id}
    </select>
</mapper>

而要想被springboot识别,则还需要在application中声明属性:
ps:classpath:后面不加"/"是在项目的resource目录下查找,如果加了就是在整个项目目录中查找了

mybatis:
  type-aliases-package: com.xige.POJO
  mapper-locations: classpath:mybatis/mapper/*.xml

接下来就是做一个测试了:
创建pojo,@Data有参无参注解搞一搞,这里直接略过,
然后就是创建一个Mapper借口:

@Mapper  //这个注解表示了,这是一个mybatis的一个mapper类,必须有
//@Repository表示这个类是持久层的,用来操作数据库的,这样此类就被mybatis整合了
//@Component  也可以用此万能类来声明被mybatis整合 但是没有也不会报错
@Repository
public interface UserMapper {
    List<User> queryUserList();
    User queryUserById(int id);
    int addUser(User user);
    int updateUser(User user);
    int deleteUser(int id);
}

紧接着就是绑定接口的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">
<!--一定要首先绑定好命名空间,否则一切白搭-->
<mapper namespace="com.xige.Mapper.UserMapper">

<!--这里的引用属性要想被识别,就必须设置别名,并且还要能被spring识别-->
    <select id="queryUserList" resultType="User">
        select * from yasuo.user
    </select>
    <select id="queryUserById" resultType="User">
        select * from yasuo.user where id = #{id}
    </select>
    <insert id="addUser" parameterType="User">
        insert  into user(id,username,password) value (#{id},#{username},#{password})
    </insert>
    <update id="updateUser" parameterType="User">
        update yasuo.user set username=#{username},password=#{password} where id=#{id}
    </update>
    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>
</mapper>

接下来就是controller层测试了:

@RestController //此注解相当于@Controller+@ResponseBody,都是只返回字符串
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("/queryUserList")
    public List<User> queryUserList(){
        List<User> users = userMapper.queryUserList();
        for (User user : users) {
            System.out.println(user);
        }
        return users;
    }
}

到此为止
需要注意的是项目所有二次创建的源码包一定要在项目主入口方法的同级目录,这个坑踩得真绝望,可能是创建项目的时候没有把com.xige后面的项目名删除,然后其他文件都在xige目录下面导致的,一直报错404,而且没有任何提示

springSecurity(安全)

安全这块一般来说都会用到springsecurity和shiro
其中springsecurity使用来管理用户授权的(vip1,vip2)
shiro是用来认证登录的
一般来说权限分为:
功能权限
访问权限
菜单权限等
之前我们都是用的拦截器,过滤器等原生代码来执行的
而现在,我们就有了springsecurity来帮忙了
接下来就是具体的代码测试了:
网页静态资源在git上面,直接复制到template上面,
然后直接创建controller;

@Controller
//专门用来跳路由器的controller
public class RouterController {

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

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

    //此处利用传递特定权限的参数来进行选择性跳转页面
    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id){
        return "views/level1/"+ id;
    }

    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id){
        return "views/level2/"+ id;
    }
    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id){
        return "views/level3/"+ id;
    }
}

application配置文件设置关闭thymeleaf的缓存

#关闭thymeleaf的缓存,方便随时调试.不然每次都要clear
spring.thymeleaf.cache=false

项目结构图如下:
在这里插入图片描述
页面效果图如下:
在这里插入图片描述
接下来就是具体的权限部署分配了

用户认证和授权

springSecurity是针对spring项目的安全框架,也是spring Boot底层安全模块默认的技术选型,他可以实现强大的web安全控制,对于安全控制,我们仅需要引入spring-boot-starter-security模块,进行少量的配置,基于可以实现强大的安全管理
重点是以下几个类

  • WebSecurityConfigurerAdapter: 自定义Securitycelue
  • AuthenticationManageerBuider; 自定义认证策略
  • @EnableWebSecurity 开启WebSecurity模式

Spring Security两个主要目标就是认证和授权(也就是访问控制)
“认证” (Authentication)
“授权” (Authorization)
这个概念是通用的,而不是只在SpringSecurity中存在
接下来写个配置类实际操作一下:(此处使用了链式编程,一环扣一环)

//AOP 
@EnableWebSecurity//开启
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //链式编程
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人都可以访问,但是功能页只有对应有权限的人才能访问
        http.authorizeRequests()   //认证请求
                .antMatchers("/").permitAll()  //特定路径的访问权限,此行代表首页所有人都可以访问
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
    }
}

这里把链接都设置为特定权限才可以进行访问,但是又没有给权限,所以直接点击链接就会报错403,权限不足
在这里插入图片描述
接下来就是写登录页面的跳转控制了,这里也又特定的方法,如果没有权限就直接返回到登录页

注销以及权限控制

注销登录只需要在首页添加一个超链接 再添加一行代码:
(超链接的图标可以在https://semantic-ui.com/elements/icon.html中查找)

 //注销,开启注销功能,删除cookies,清空session 注销之后自动跳转到首页
http.logout().deleteCookies("remove").invalidateHttpSession(true).logoutSuccessUrl("/");

那么还有一点就是登陆之后显示用户名,权限以及注销按钮等
这里还需要导入一个springsecurity整合thymeleaf的依赖:

<!--        springSecurity-thymeleaf整合包-->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

然后就是根据是否登陆来进行显示登陆按钮以及用户名了:
一定要注意这里的取反标志

<!--如果未登录,就显示登陆按钮
   sec:authorize="isAuthenticated()用来验证是否已经登陆了
   注意这里的方法前面有一个!取反标志,代表没有登陆
-->
      <div sec:authorize="!isAuthenticated()">
          <a class="item" th:href="@{/toLogin}">
              <i class="address card icon"></i> 登录
          </a>
      </div>

<!--                <div th:if="${session.user}"></div>-->

      <!--如果已经登陆  注销 显示用户名-->
      <div sec:authorize="isAuthenticated()">
          <a class="item">
              用户名: <span sec:authentication="name"></span>
              权限: <span sec:authentication="authorities"></span>
          </a>
      </div>
      <div sec:authorize="isAuthenticated()">
          <a class="item" th:href="@{/logout}">
              <i class="sign out alternate icon"></i> 注销
          </a>
      </div>

然后还有对低权限设置不能显示高权限才能点击的内容
只需要添加一个sec:authorize="hasRole(‘vip1’),这里就代表有vip1可以进行展示:

<div>
        <br>
        <div class="ui three column stackable grid">

<!--            菜单栏根据用户的权限进行部分显示-->
            <div class="column" sec:authorize="hasRole('vip1')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 1</h5>
                            <hr>
                            <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                            <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                            <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip2')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip3')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>

效果如下:
在这里插入图片描述

记住我以及首页定制

记住我其实就是将账号密码存储在cookie中,一行代码就可以直接执行

//记住我 (账号) 默认保存两周
http.rememberMe();

定制跳转页面:
只需要把提交页面与登录页面保持一致就行了
提交页面如下:

<form th:action="@{/toLogin}" method="post">
    <div class="field">
        <label>Username</label>
        <div class="ui left icon input">
            <input type="text" placeholder="Username" name="username">
            <i class="user icon"></i>
        </div>
    </div>
    <div class="field">
        <label>Password</label>
        <div class="ui left icon input">
            <input type="password" name="password">
            <i class="lock icon"></i>
        </div>
    </div>
    <div class="field">
        <input type="checkbox" name="remember"> 记住我
    </div>
    <input type="submit" class="ui blue submit button"/>
</form>

原本的http.formLogin()是跳转的默认页面,后缀可重新指定一个登录页面

//也可以进行定制页面   loginPage("/toLogin");
        http.formLogin().loginPage("/toLogin");
        //.loginProcessingUrl("/login")

然后还有特定页面的rememberme(记住我):
首先是页面上的记住我标签:

<div class="field">
   <input type="checkbox" name="remember"> 记住我
</div>

然后就是根据名字绑定到springsecurity中:

//记住我 (账号) 绑定特定页面的记住我
http.rememberMe().rememberMeParameter("remember");

整个配置类如下:

//AOP
@EnableWebSecurity//开启
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //链式编程
    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人都可以访问,但是功能页只有对应有权限的人才能访问
        http.authorizeRequests()   //认证请求
                .antMatchers("/").permitAll()  //特定路径的访问权限,此行代表首页所有人都可以访问
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");

        //没有权限就默认回到登录页面,需要开启登录的页面
        //也可以进行定制页面   loginPage("/toLogin");
        http.formLogin();
        //记住我 (账号) 绑定特定页面的记住我
        http.rememberMe().rememberMeParameter("remember");

        //注销,开启注销功能,删除cookies,清空session 注销之后自动跳转到首页
        http.logout().deleteCookies("remove").invalidateHttpSession(true).logoutSuccessUrl("/");

    }

    //认证
    //既然用到了登录,那用户和密码以及认证就是必须的了:
    //密码编码错误 :passwordEncoder
    //在springSecurity 5.0+版本中新增了许多的加密方式,如果不加密会默认无法使用
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //auth.jdbcAuthentication();   //正常来说应该从数据库中获取

        //从内存中获取
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("xige").password(new BCryptPasswordEncoder().encode("666666")).roles("vip2","vip3")
                .and() //只要使用and连接方法,就可以无限增加用户并部署权限
                .withUser("root").password(new BCryptPasswordEncoder().encode("111111")).roles("vip1","vip2","vip3")
                .and()
                .withUser("lj").password(new BCryptPasswordEncoder().encode("1")).roles("vip1");
    }
}

Shiro

Shiro是一个Apache公司的java安全(权限)框架
Shiro可以完成认证,授权,加密,会话管理,Web继承,缓存,用户伪装等等
Shiro核心架构:
subject: 当前用户,
Shirl SecurityManager: (管理所有的用户)
Realm : 保证数据安全控制

shiro的使用实例:
这些代码以及上面的一些配置文件都是根据老秦在github上面找的
地址:https://github.com/apache/shiro/blob/master/samples/quickstart/
首先创建一个新的maven项目,然后导入依赖:

    <dependencies>
<!--        shiro核心文件-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.7.1</version>
        </dependency>
<!--日志配置文件-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <scope>runtime</scope>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

第二步,配置shiro的配置文件
除此之外还可以再配置一个log4j的配置文件

[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

第三步,代码测试:
代码为GitHub示例代码,仅仅作测试用

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
//原来的路径是 import org.apache.shiro.ini.IniSecurityManagerFactory;
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.lang.util.Factory;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    //日志输出
    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        //通过工厂模式读取配置文件然后创建实例,这三行都是死代码
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

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

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

        //通过当前用户获得shiro的session
        Session session = currentUser.getSession();
        //利用拿到的session存值/取值
        session.setAttribute("someKey", "一个值");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("一个值")) {
            log.info("利用Subject的session取值 [" + value + "]");
        }

        //测试当前用户是否被认证
        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:  验证权限  schwartz在shiro.ini中已经被声明过是一种权限了
        //这里和下面的那个if判断都是用来验证权限的,只不过上面这个是粗粒度,下面那个更细致
        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);
    }
}

输出为:
在这里插入图片描述
需要注意的是,官方默认使用的日志为Commons-logging
如果没有配置log4j,就会默认去匹配Commons-logging
这一点知道就行,一般都是使用的log4j
还有一点就是maven项目赋值java类的时候可能在源码右上角一直显示正在分析,
解决办法就是:
在这里插入图片描述

shiro的subject分析

此处就是拿前面的java源码来进行细化分析的
弹幕上说,逻辑关系大致为,一个用户对应很多角色,一个角色对应不同权限
在shiro中首先拿到subject:

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

然后就是getsession

//通过当前用户获得shiro的session
        Session session = currentUser.getSession();
        //取值/存值
        session.setAttribute("someKey", "一个值");
        String value = (String) session.getAttribute("someKey");

接着判断当前用户是否被认证:

if (!currentUser.isAuthenticated()) {...}

获得当前用户的认证:

currentUser.getPrincipal()

判断当前用户是否拥有对应角色

currentUser.hasRole("schwartz")

获得当前角色的权限

currentUser.isPermitted("lightsaber:wield")

最后就是注销了:

currentUser.logout();

springboot集成shiro环境搭建

首先新建model,创建一个最基本的springboot项目回路,记得勾选web和thymeleaf依赖
model:

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

<p th:text="${msg}"></p>

</body>
</html>

controller

@Controller
public class Mycontroller {
    @RequestMapping({"/","/index"})
    public String Toindex(Model model){
        model.addAttribute("msg", "hello,shiro");
        return "index";
    }
}

经测试无误之后,然后就开始搭建环境了:
首先导入shiro-spring依赖

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

然后编写配置类shiroConfig
编写配置类需要三大步骤: 这三个步骤一环扣一环
//shiroFilterFactoryBean
//DefaultWebSecurityManager
//创建realm对象 需要自定义

创建realm对象需要继承AuthorizingRealm对象,实现方法后注入到config中:

//自定义的Realm对象
public class UserRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权==>doGetAuthorizationInfo");
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证==>doGetAuthenticationInfo");
        return null;
    }
}

config配置类为:

@Configuration//配置类必须有此注解
public class shiroConfig {

    //创建realm对象  需要自定义

    //回顾: 如何把一个bean放在spring项目的configuration中?
    @Bean//可以添加(name ="XXX") 来修改bean的名字,方便其他类传参接收
    public UserRealm userRealm(){
        return new UserRealm();
    }

    //DefaultWebSecurityManager
    @Bean(name ="manager")
    //@Qualifier("userRealm")用于接收bean中指定参数
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联 Realm  只需要根据专门的方法拉进行注入,
        //但是老秦说下面的realm对象已经注入到bean中了,再次调用就需要传参来进行使用了
        securityManager.setRealm(userRealm);

        return securityManager;
    }

    //shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        return bean;
    }
}

写完配置类之后再往controller中添加两个跳转回路:

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

并在index页面写对应的两个超链接,这样环境就搭建好了,
接下来就是控制用户进入的权限了

shiro实现登录拦截

shiro的权限等级分为:

  • anon: 无需认证就可以进行访问
  • authc: 必须认证过才可以进行访问
  • user: 必须拥有记住我功能才能用
  • perms: 拥有对某个资源的权限才能进行访问
  • role: 拥有某个角色权限才能访问
    这些权限部署也必然有对应方法: setFilterChainDefinitionMap()
    不难看出这是一个利用集合统一赋值或者部署权限的方法,
    到此处登录拦截方法整合上面的shiro配置代码如下:
@Configuration
public class shiroConfig {

    //创建realm对象  需要自定义

    //回顾: 如何把一个bean放在spring项目的configuration中?
    @Bean//可以添加(name ="XXX") 来修改bean的名字,方便其他类传参接收
    public UserRealm userRealm(){
        return new UserRealm();
    }
    //DefaultWebSecurityManager
    @Bean(name ="manager")
    //@Qualifier("userRealm")用于接收bean中指定参数
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联 Realm  只需要根据专门的方法拉进行注入,
        //但是老秦说下面的realm对象已经注入到bean中了,再次调用就需要传参来进行使用了
        securityManager.setRealm(userRealm);
        return securityManager;
    }
    //shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro的内置过滤器
        /*
        * anon: 无需认证就可以进行访问
        * authc: 必须认证过才可以进行访问
        * user: 必须拥有记住我功能才能用
        * perms: 拥有对某个资源的权限才能进行访问
        * role: 拥有某个角色权限才能访问
        * */
        Map<String,String> filterMap=new LinkedHashMap<>();
        filterMap.put("/user/add","authc"); //user/add页面需要认证之后才可已经访问都可以进行访问
        filterMap.put("/user/update","anon");
        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }
}

此处设置的add需要登录认证但是此处没有设置登录页面,所以直接报错,而update则可以直接进行访问.
那么接下来就是手动设置一个登录页面作为add页面的跳转链接了
这里只需要在shiro的拦截方法中添加一句:

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

shiro实现用户认证

这里直接在controller层跳转的时候就开始获取用户数据进行登录认证了:

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取当前的用户
        Subject subject = SecurityUtils.getSubject();
        //封装用户的登录数据    UsernamePasswordToken创建令牌,用来验证密码是否正确
        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";
        }
    }

这里进登录页面点击提交的时候会携带token自动跳转到自定义的Realm类中的认证方法中(我搞不懂是怎么进去的)
然后就可以在自定义的方法中进行更深层次的操作了:

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证==>doGetAuthenticationInfo");
        //这里就是用来认证账号密码是否正确的地方了,此处先伪造一个账号密码
        String name= "root";
        String password= "981216";

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        if (!userToken.getUsername().equals(name)){
            return null; //抛出异常 UnknownAccountException
        }
        //密码认证,shiro自己做,不然可能会造成密码泄露,此处只用将密码作为第二个参数就可以了
        return new SimpleAuthenticationInfo("",password,"");
    }

此处只是伪造的用户名和密码.但是已经可以正常运行了,那么接下来就是:

shiro整合Mybatis

首先额外导入相关依赖:
注意,如果含有静态资源导出问题时,需要在静态资源配置中额外声明一个yml的属性,否则报错无法进行加载applicationContext

<!--        连接mysql数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
<!--druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>

接着就是创建基本的pojo,dao,service层的回路,以及相关配置文件,这里程序不做赘述,
注意mapper接口中要使用@Repository以及@Mapper注解,serviceImpl实现类要使用@Service即可,主要是xml配置文件:
首先是mapper.xml配置文件,也就是mapper层的sql语句:

<?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">
<!--一定要首先绑定好命名空间,否则一切白搭-->
<mapper namespace="com.xige.mapper.UserMapper">

    <!--这里的引用属性要想被识别,就必须设置别名,并且还要能被spring识别-->
    <select id="queryUserByName" parameterType="String" resultType="User">
        select * from mybatis.user where name = #{name}
    </select>
</mapper>

然后就是mybatis中DataSource数据源以及绑定mapper配置文件的application文件:

spring:
  datasource:
    username: root
    password: "981216"
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    #SpringBoot默认是不注入这些的,需要自己绑定并进行解析
    #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注入,这里才是druid牛逼的地方
    #如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Properity
    #则导入log4j 依赖就行
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

mybatis:
  type-aliases-package: com.xige.pojo
  mapper-locations: classpath:mapper/*.xml

然后接下来就是利用springboot中的测试方法进行测试了:

@SpringBootTest

class SpringbootApplicationTests {

    @Autowired
    UserServiceImpl userService;

    @Test
    void contextLoads() {
        System.out.println(userService.queryUserByName("adc"));
    }
}

既然数据库测试通过,那么接下来就是通过数据库来获取realm用户数据进行登录了:
改动后如下:(其实也就注入uservice,然后利用接口方法获取对象)

//数据库获取realm对象
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权==>doGetAuthorizationInfo");
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证==>doGetAuthenticationInfo");

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //接下来就是连接真实的数据库了
        User user = userService.queryUserByName(userToken.getUsername());
        if (user==null){ //没有这个用户
            return null;
        }
        //密码认证,shiro自己做,不然可能会造成密码泄露,此处只用将密码作为第二个参数就可以了
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
}

这样就算完成了

Shiro请求权限实现

首先按照真实业务来说的话,权限是和用户数据绑定在数据库中的,所以我们首先可以在表中新增一个权限属性,以下表为例:
在这里插入图片描述
有的用户没有权限,有得用户有部分权限
然后还有在shiroConfig中设置部分内容特定权限可见,

filterMap.put("/user/add","perms[user:add]");
//user/add页面需要url带有user:add字符串才可已经访问
filterMap.put("/user/update","perms[user:update]");

以及权限不足时的固定跳转:(controller那里就不说了,直接就是@ResponseBody)

bean.setUnauthorizedUrl("/noauth");

然后接下来就是在UserRealm中授权了:
首先获取SimpleAuthorizationInfo()对象:

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

如果想要设置基本的通用权限就可以直接:

info.addStringPermission("user:add");

这样一旦进入到这个方法就会自动授权.
如果想要根据不同的用户来授权的话,首先自然就是先得到对象了,然后再获取对象数据库中的perms(权限)属性来进行授权:

//数据库获取realm对象
public class UserRealm extends AuthorizingRealm {

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

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //接下来就是连接真实的数据库了
        User user = userService.queryUserByName(userToken.getUsername());
        if (user==null){ //没有这个用户
            return null;
        }
        //密码认证,shiro自己做,不然可能会造成密码泄露,此处将密码作为第二个参数就可以了
        //先认证再授权,此处如果想要从数据库中获取权限信息就需要把对象也传递到授权方法中
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权==>doGetAuthorizationInfo");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //此时每个用户进来都会被自动授权user:add
        //info.addStringPermission("user:add");

        //拿到当前登录的对象
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal();  //从认证处拿到user对象后才能进行强制类型转换
        //这里因为表中有的权限没有写,直接就是null,所以需要加上一个非空判断,不然就会报错500
        if (currentUser.getPerms()==null){
            return info;
        }
        //拿到user对象之后可以开始设置用户权限,权限也是从user表中拿到的
        info.addStringPermission(currentUser.getPerms());

        return info;
    }
}

这样一来授权管理就完成了

Shiro整合Thymeleaf

此处整合thymeleaf的目的就是用户登录的时候只能显示自己权限范围内的东西,优化用户体验
既然要用到thymeleaf,那么首先要做的就时要导入thymeleaf整合shiro的依赖了:

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

然后根据权限显示内容只需要用含有shiro:hasPermission="XXX"声明的div标签包裹内容就可以了,xxx就代表权限,有了权限才会进行显示
然后更进一步就是应该写一个登录按钮了,还有如果已经有用户登录之后就不用显示登录按钮了,这里直接用shiro:notAuthenticated或者shiro:guest声明的标签包裹一下就可以了

<h1>我是首页</h1>
<p th:text="${msg}"></p>
<!--shiro:notAuthenticated意为没有权限就显示此标签-->
<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>

任务

异步任务

一般项目中难免会有多线程任务,而其中带有嵌套线程反馈的任务有时候会产生响应时间过长,导致用户体验较差的情况这种应该如何调优呢?
以下测试环境为例:
Service层;

@Service
public class AsyncService {
    //告诉spring这是一个异步的方法
    //@Async
    public void hello(){

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("数据正在处理....");

    }
}

Controller层:

@RestController
public class AsyncController {

    @Autowired
    AsyncService asyncService;

    @RequestMapping("/hello")
    public String hello(){
        asyncService.hello();  //停止三秒
        return "OK";
    }
}

按照正常流程来讲,访问页面时会等待三秒后控制台打印输出信息,然后页面才会相应出ok,
但是相应之前都是在转圈加载,这样肯定会影响用户体验的,所以为了提升用户体验,我们可以使用spring的方法进行异步操作,很简单:
只需要在service层的方法上添加一个@Async,告诉spring这是一个异步方法
然后再springboot主启动类上添加@EnableAsync注解,开启异步操作,即可

这样用户访问页面时直接就可以显示ok,然后控制台依旧等待三秒打印输出结果
这样就将用户想看到的部分提前显示,用户操作后无法看到的部分异步执行,进而提升用户体验

邮件任务

首先要想使用邮件发送,就要先导入springboot的邮件依赖:

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

然后以现在使用的qq邮箱为例:
首先在qq邮箱设置中开启smtp服务:
在这里插入图片描述
然后还要在application配置文件中添加以下配置:

spring.mail.username=2045807586@qq.com
spring.mail.password=odimwjhsbjmqdhjd
spring.mail.host=smtp.qq.com

#开启加密验证
spring.mail.properties.mail.smtl.ssl.enable=true

然后此处直接在springboot的test中进行测试:

@SpringBootTest
class SpringbootTaskApplicationTests {

    @Autowired
    JavaMailSenderImpl mailSender; //首先注入自带工具类

    @Test
    void contextLoads() {

        //一个简单的邮件
        SimpleMailMessage message = new SimpleMailMessage();
        message.setSubject("让我访问~"); //标题
        message.setText("艾玛老妹儿~想死你了~~~");  //文本内容
        message.setTo("2045807586@qq.com");  //发送给谁
        message.setFrom("2045807586@qq.com");  //谁发送 注意这里是先发送给服务器,然后服务器再转发,所以可以自己发自己

        mailSender.send(message);
        System.out.println("邮件发送成功~");
    }
}

然后带有附件的复杂邮件发送如下:

@Test
    void contextLoads1() throws MessagingException {

        //一个复杂的邮件
        MimeMessage message = mailSender.createMimeMessage();
        //组装~  组装之后的发送全由helper来进行设置
        MimeMessageHelper helper = new MimeMessageHelper(message,true);
        helper.setSubject("让我康康~");
        //方法支持html代码,但是第二个参数要声明为true
        helper.setText("<p style='color:red'>嘿嘿嘿~</p>",true);
        //附件
        helper.addAttachment("1.jpg",new File("D:\\IDEAproject\\springboot-task\\src\\main\\resources\\static\\1.jpg"));
        helper.addAttachment("2.jpg",new File("D:\\IDEAproject\\springboot-task\\src\\main\\resources\\static\\1.jpg"));

        //收发人
        helper.setTo("2045807586@qq.com");  
        helper.setFrom("2045807586@qq.com");


        mailSender.send(message);
        System.out.println("邮件发送成功~");
    }

定时任务

定时任务一般用到以下两个方法以及注解还有一个表达式:

TaskScheduler  任务调度程序
TaskExecutor   任务执行者

@EnableScheduling //开启对定时功能的支持 放在主启动类上面
@Scheduler   //什么时候执行定时功能

Cron表达式

以下面为例:
首先在主启动类使用@EnableScheduling开启对定时功能的支持,然后就是Service层的业务代码:

@Service
public class ScheduleService {
    
    //在特定时间执行
    //Cron表达式       秒 分 时 日 月 周几(0-7,其中0和7都代表周末)
    @Scheduled(cron = "20 54 21 * * ?")
    public void hello(){
        System.out.println("定时任务已成功执行~~~~");
    }
}

cron表达式的几个参数如上所示,秒分时日月周
更多cron表达式及特殊字符直接百度:
https://cron.qqe2.com

分布式系统理论

什么是分布式系统理论?
分布式系统是一组通过网络进行通信(HTP,RPC),为完成共同的任务二协调工作的计算机节点组成的系统.分布式系统的出现是为了用廉价的普通的机器完成单个计算机无法完成的计算.储存任务,其目的是利用多个的机器组合起来当做一个服务器,处理更多的数据
分布式和集群的区别是什么?
分布式: 一个业务拆分为多个子业务,分布在不同的服务器上面
集群: 同一个业务部署在多个服务器上面
不同的服务器需要使用到一个叫nginx的来进行协调或者负载均衡

RPC

RPC代表远程过程调用,是一种进程间通讯方式,它是一种技术思想,而不是规范,它允许程序调用另外一个地址空间(通常是共享网络的另外一个机器上)的过程及函数,而不是程序员显式编码这个远程调用的细节,即程序员无论是调用本地的还是远程的函数,本质上编写的调用编码基本相同
RPC两个核心模块:通讯.序列化
序列化: 数据传输需要转换

这里也就需要用到Dubbo,一个专注于通讯的框架

Dubbo及Zookeeper安装

Apache Dubbo是一款高性能轻量级的开元java RPC框架
它提供了三大核心能力:面向接口的远程方法调用,智能容错负载均衡,以及服务自动注册和实现
关于Dubbo有这么一张示意图:
在这里插入图片描述

生产者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
个人感觉就像淘宝一样,店家生产产品(生产者),然后放到淘宝平台(注册中心),消费者在平台进行购买,其中生产者和消费者需要收到监控中心的监控

zookeeper环境搭建

接下来就是环境搭建了,环境搭建方面推荐使用zookeeper注册中心:
下载地址: https://zookeeper.apache.org/releases.html
https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.5.9/
下载解压之后可以发现一系列文件夹,按照tomcat的思路首先打开bin文件夹:
在这里插入图片描述
直接管理员权限运行可能会闪退,点开文档在倒数第二行输入pause,cmd就不会闪退,只会显示报错信息,报错信息可以看到是缺少一个zoo.cfg,点开第二个conf文件夹果然没有zoo.cfg文件,此处直接复制下面的那个模板配置(zoo_sample.cfg)然后粘贴并重命名为zoo.cfg即可
在这里插入图片描述
再点开配置文件可以发现这里实际上的配置信息只有端口号2181以及其他的几句
在这里插入图片描述
这样一来再重新启动一下服务就行了:
在这里插入图片描述
此时使用自带的连接测试一下:
在这里插入图片描述
连接成功了,
zookeeper服务中心的节点是使用键值对的形式来储存的,
输入 1s/ 罗列出所有节点,可以发现一开始并没有节点,
我们此时也可以创建一个节点 create /e /节点名 节点
在这里插入图片描述
当然,取值的时候只需要get /节点名 即可
在这里插入图片描述
当然,以后都是使用java代码来进行节点操作了

Dubbo-admin安装测试

GitHub地址:https://github.com/apache/dubbo-admin/tree/master
下载之后打开Dubbo-admin从src翻到最后有个application.properties配置文件,zookeeper的端口号要注意对应:
在这里插入图片描述
然后在项目目录路径使用cmd命令打包dubbo-admin:
直接打开项目文件然后路径左侧cmd➕空格回车就好了

mvn clean package -Dmaven.test.skip=true

如下:
在这里插入图片描述
初次打包需要点时间,运行后出现如下则为打包成功:
在这里插入图片描述
打包成功会在dubbo-admin目录下出现一个target文件夹,里面包含了一个jar包
接下来打开zookeeper的zkserver.cmd运行zookeeper服务之后,在jar包所在目录cmd窗口输入java -jar +文件名运行这个jar包:
运行之后打开localhost:7001会让你输入账号密码,默认都是root之后就进入到dubbo admin页面了:
在这里插入图片描述
zookeeper是一个注册中心
dubbo是一个jar包
dubbo-admin是一个监控管理平台,它可以查看我们注册了哪些服务,其中哪些服务被消费了(可以没有)

服务注册开发实战

介绍完大致原理,接下来就是动手测试了
首先创建一个普通的maven项目,然后在项目中创建两个springboot模块,分别为提供者(provider)和消费者(consumer),提供者中写个getTicket(拿票)的接口和方法,
提供者模块的配置文件还要导入dubbo-springboot-starter的依赖:

<!--        导入dubbo和zookeeper的依赖-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.3</version>
        </dependency>

        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
<!--            排除slf4j-log4j12,不然会报错-->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

接着在生产者的application.properties文件中声明属性:
(消费者的端口为8002)


server.port=8001
#服务应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://localhost:2181
#有哪些服务被注册
dubbo.scan.base-packages=com.xige.service

最后首先启动zookeeper,然后启动提供者者模块,可以在zookeeper的cmd页面或者启动dubbo-admin的缓存jar包后,在http://localhost:7001页面查看是否启动成功
在这里插入图片描述
可以看到在dubbo-admin页面中已经可以查到提供者的服务
那么接下来就是消费者获取提供者的服务了:
首先依然是消费者的pom依赖(一样)以及系统配置文件:

#消费者去哪里拿服务
dubbo.application.name=consumer-server
#注册中心的地址
dubbo.registry.address=zookeeper:localhost:2181

注意,这里的dubbo.application.name属性是自定义的,注册中心的地址是固定的

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值