Springboot笔记

一,springboot项目的文件目录

  • xxxAppliaction:springboot的朱入库

  • application.properties:springboot的配置文件 但是大多以后使用yml文件格式

  • 启动直接点run

  • 建包只能是统计目录下建立,否则不会生效

    pom.xml
    • 有一个父组件

    • 还有依赖

    • 所有的springboot的组件都是springboot开头的

    •   <?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>
            
            <!--依赖的父组件-->
            <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.7.8</version>
                <relativePath/> <!-- lookup parent from repository -->
            </parent>
            
            <groupId>com.ljh</groupId>
            <artifactId>SpringBoot_quick2</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <name>SpringBoot_quick2</name>
            <description>SpringBoot_quick2</description>
            <properties>
                <java.version>1.8</java.version>
            </properties>
        
            <!--web依赖-->
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
        
                <!--junit的单元测试-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                    <scope>test</scope>
                </dependency>
        
                <!--@configuartionProperties的执行器配置-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-configuration-processor</artifactId>
                    <optional>true</optional>
                </dependency>
            </dependencies>
        
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                    </plugin>
                </plugins>
            </build>
        
        </project>
        
      

二,第一个SpringBoot项目

package com.ljh.Controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class user {

    @RequestMapping("/caixu")
    public String kuang(){
        return "蔡旭傻逼";
    }
}

三,原理初探

自动配置

pom.xml

  • spring-boot-dependencies:核心依赖在父工程中
  • 我们在写或者引入一些SpringBoot以来的时候,不需要制定一些版本,就是因为这个

启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
  • 启动器:说白了就是SpringBoot的应用场景
  • 后面-什么就是带入环境
    • 例:-web
  • 在官网上面可以找到

主程序

@SpringBootApplication //标志这个类是SpringBoot的应用
public class ZSpringBoot01Application {

    public static void main(String[] args) {
        //通过反射将SpringBoot启动
        SpringApplication.run(ZSpringBoot01Application.class, args);
    }

}
  • 注解

  •   @SpringBootConfiguration
      	//SpringBoot的配置
      @EnableAutoConfiguration
      	//自动配置
    

四,yaml的语法和SpringBoot配置

yaml语法

# 官方的配置太多了,理解记忆
# 对空格的要求十分高
# key和value之间有一个空格

# 注入到我们的配置类中

# 普通的key和value
name: qinjiang

# 存一个对象
student:
	name: qingjiang
	age: 3

student: {name: qinjiang,age: 2}

# 存一个数组
pets:
	-cat
	-dog
	
pets:[cat,dog]

#yaml可以村对象,但是properties不可以存对象

SpringBoot配置

	@Value("蔡旭")
    private String name;
    @Value("2")
    private Integer age;
# 可以有@ConfigurationProperties(prefix = "Person")注解
# 这个可以直接引用一个对象注入,在使用这个注解的时候,就会有错误提示如果不配置使用注解就会爆红,但是没有什么影响(添加依赖即可)

@Component
//@ConfigurationProperties(prefix = "person")
@PropertySource(value = "classpath:name.properties")
public class Person2 {

    @Value("${name}")
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Pet pet;

可以使用properties和yml配置,但是properties会乱码,并且properties不可以一次性全部进入

也可以在yml文件中直接使用JSTEL表达式例如${random.uuid}等等

//松散注入:在yml文件里面是first-name 但是在java'中是firstName.但是也可以匹配成功
Dof{firstName='cai', age=12}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTiJpdO6-1677419918887)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230216232911734.png)]

JSR303检验

@Component
@ConfigurationProperties(prefix = "person")
//这个是JSR303检验,就好比表单检验
@Validated
public class Person {
    //如果格式有无的话不可以正常的注入的
    @Email()
    @Email(message="邮箱格式有无")
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Pet pet;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ucjuj9xJ-1677419918888)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230216234053868.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jVTYI2zK-1677419918888)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230216234103508.png)]

多环境配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3iKJNzgD-1677419918888)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230217142003806.png)]

  • SpringBoot的yml配置: —可以分割多个文件.如果使用哪一个就active:dev就行

五,SpringBoot Web开发

静态资源

0,代码分析
@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
            
            // 这个是自定义配置,如果发现自定义配置直接返回(spring.mvc.static-path-pattern)
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
            
            //这个是利用webjars导用数据
			Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
			CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
			if (!registry.hasMappingForPattern("/webjars/**")) {
				customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
						.addResourceLocations("classpath:/META-INF/resources/webjars/")
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
            
            //放在static,public,resources中,但是resources>static(默认)>public,但是public和resources得自己创建
			String staticPathPattern = this.mvcProperties.getStaticPathPattern();
			if (!registry.hasMappingForPattern(staticPathPattern)) {
				customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
						.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
            
		}

1.webjars下面可以访问
2.放在static,public,resources中,但是resources>static(默认)>public
  • 一般在static中放一些静态资源
  • 在public放一些公共资源
3.也可以在如此配置
  • spring.mvc.static-path-pattern=/ 这个可以改变文件路径,但是系统默认的静态文件就会失效

首页如何定制

1.可以将首页放在static,public,resources中,index.html
2.可以将首页放在templates下面,但是需要controller来跳转,这个也需要thymeleaf的支持

thymeleaf

maven

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
  • 在使用时,导入pom.xml坐标,并且在html中导入html标签

  • 在使用的时候,必须导入相应的pom坐标.,可以在一下链接里面查看文档

  • https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

  • thymeleaf的官方文档是:https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#using-texts

  •   <!DOCTYPE html>
      
      <html xmlns:th="http://www.thymeleaf.org">
      
        <head>
          <title>Good Thymes Virtual Grocery</title>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
          <link rel="stylesheet" type="text/css" media="all" 
                href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
        </head>
      
        <body>
        
          <p th:text="#{home.welcome}">Welcome to our grocery store!</p>
        
        </body>
      
      </html>
    
  •   <html xmlns:th="http://www.thymeleaf.org">
          # 这个是thymeleaf的约束
    
thymeleaf的语法
  • Simple expressions:
    	Variable Expressions: ${...}  # 变量
    	Selection Variable Expressions: *{...} # 选择变量表达式
    	Message Expressions: #{...}  # 消息表达式
    	Link URL Expressions: @{...}  # 链接URL表达式
    	Fragment Expressions: ~{...}  # 片段表达式
    Literals
    	Text literals: 'one text', 'Another one!',Number literals: 0, 34, 3.0, 12.3,Boolean literals: true, false
    	Null literal: null
    	Literal tokens: one, sometext, main,Text operations:
    	String concatenation: +
    	Literal substitutions: |The name is ${name}|
    Arithmetic operations:
    	Binary operators: +, -, *, /, %
    	Minus sign (unary operator): -
    Boolean operations:
    	Binary operators: and, or
    	Boolean negation (unary operator): !, not
    Comparisons and equality:
    	Comparators: >, <, >=, <= (gt, lt, ge, le)
    	Equality operators: ==, != (eq, ne)
    Conditional operators:
    	If-then: (if) ? (then)
    	If-then-else: (if) ? (then) : (else)
    	Default: (value) ?: (defaultvalue)
    
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xKaJjjjC-1677419918889)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230217230239289.png)]

  • 第一个thymeleaf的页面

    •   <!DOCTYPE html>
        <html xmlns:th="http://www.thymeleaf.org">
        <head>
            <meta charset="UTF-8">
            <title>$Title$</title>
        </head>
        <body>
        <!--所有的html页面都可以用thymeleaf所接管 th:元素名 th:text,th:class-->
            <div th:text="${nihao}"></div>
        </body>
        </html>
      
    •   //在templates目录下的所有页面,都需要Controller来进行跳转
        //这个跳转需要模板引擎的支持 thymeleaf
        @Controller
        public class IndexController {
        
            @RequestMapping("/test")
            public String index(Model model){
                String name = "caixu";
                model.addAttribute("nihao",name);
                return "test";
            }
        }
        
      
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBiJFFn3-1677419918889)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230217230426090.png)]

  • 语法
    •   <!DOCTYPE html>
        <html xmlns:th="http://www.thymeleaf.org">
        <head>
            <meta charset="UTF-8">
            <title>$Title$</title>
        </head>
        <body>
        <!--所有的html页面都可以用thymeleaf所接管 th:元素名 th:text,th:class-->
            <div th:text="${nihao}"></div> <!--th:text不能自动的识别表达式中的html标签,但是utext可以识别-->
            <div th:utext="${nihao}"></div>
        
        <hr>
        <!--${users里面的值是model里面传过来的值,user是${users}里面的值,${user}的值是来自于遍历的user-->
            <h3 th:each="user:${users}" th:text="${user}"></h3>
        </body>
        </html>
      
    •   @Controller
        public class IndexController {
        
            @RequestMapping("/test")
            public String index(Model model){
                String name = "<h1>caixu</h1>";
                model.addAttribute("nihao",name);
                model.addAttribute("users", Arrays.asList("傻逼","狗日的"));
                return "test";
            }
        }
      

SpringBoot的MVC配置

  • 如果想要加入一些mvc中的特性的话,那就需要我们写一个config类,加@Configuration注解,然后实现WebMvcConfigurer接口.如果要用里面的内容的话可以直接实现里面的方法

  • 文件文章:

    • If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
  • 注,不能加@EnableMvc 如果加了就会全面接管

    可以实现的方法
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-96pgDyN7-1677419918889)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230218231813718.png)]

  • 例如addInterceptors 加一个拦截器

  •   //在springboot中,如果我们要扩展springmvc的话,官方建议我们这么做.写一个config然后加@Configuration
      @Configuration
      public class MyMvcConfig implements WebMvcConfigurer {
      
          //视图跳转
          @Override
          public void addViewControllers(ViewControllerRegistry registry) {
              registry.addViewController("/kuang").setViewName("test");
          }
      }
      
    
  • 在springboot中,有非常多的xxx configuration 帮助我们进行拓展设置,只要看见了这个东西,我们就要注意了.

    自定义配置类
        //自定义国际化组件
        @Bean
        public LocaleResolver localeResolver(){
            return new MyLocaleResolver();
        }
    

六,作业测试

基础准备

  • 模板资源地址:https://getbootstrap.com/docs/4.0/examples/

  • 在进行首页配置时,所有的页面静态资源都需要使用thymeleaf来接管,例:th:href="@{/asserts/css/dashboard.css}

国际化

  • 1,在resources的目录下面建立一个i18n 里面加入login.properties,在这个里面放入login_en_US.propertieslogin_zh_CN.properties分别对应中文和英文

  • 2,在application.properties中加入一个国际化配置

    •   #国际化 我们配置文件的真实位置 国际化的message用#{}
        spring.messages.basename=i18n.login
      
  • 3,将html页面更改,message的消息使用@{}

    •   	<a class="btn btn-sm" th:href="@{/index.html(I='zh_CN')}">中文</a>
        			<a class="btn btn-sm" th:href="@{/index.html(I='en_US')}">English</a>
      
  • 4,加入一个配置类

    •   public class MyLocaleResolver implements LocaleResolver {
            @Override
            public Locale resolveLocale(HttpServletRequest request) {
                //获取请求中的参数
                String language = request.getParameter("I");
                System.out.println(language);
                Locale locale = Locale.getDefault(); //如果没有就使用默认的
                //如果请求携带了国际化的参数
                if(!StringUtils.isEmpty(language)){
                    //zh_CN
                    String[] s = language.split("_");
                    locale = new Locale(s[0], s[1]);
                }
                return locale;
            }
        
            @Override
            public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        
            }
        }
      
  • 5,将组件配置到MvcConfig扩展里面

    •       //自定义国际化组件
            @Bean
            public LocaleResolver localeResolver(){
                return new MyLocaleResolver();
            }
      

拦截器

  • 1,在登陆的时候写一个参数 HttpSession session

    •   session.setAttribute("loginUser", username);
      
  • 2.写一个拦截器的类

    • 登录拦截器原理:拦截器原理,在登录的时候,如果登录成功之后就会发出一个session,所以我们只需要检查时候存在登录session即可如果存在就可以访问,如果没有就不可以

    •   //登录拦截器 重写方法.返回true放行,false不放
        public class LoginHandlerIntercepter implements HandlerInterceptor {
        
            //拦截器原理,在登录的时候,如果登录成功之后就会发出一个session,所以我们只需要检查时候存在登录session即可如果存在就可以访问,如果没有就不可以
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                //登录成功之后就会存在Session
                Object loginUser = request.getSession().getAttribute("loginUser");
                if(loginUser==null){
                    request.setAttribute("msg", "没有权限,请先登录");
                    request.getRequestDispatcher("/index.html").forward(request,response);
                    return false;
                }else {
                    return true;
                }
            }
        }
      
  • 3,在Config里面配置拦截器,重写public void addInterceptors(InterceptorRegistry registry)方法

    •       @Override
            public void addInterceptors(InterceptorRegistry registry) {  //加入一个拦截器,拦截全部页面,除过"/index.html","/","/user/login"
                registry.addInterceptor(new LoginHandlerIntercepter())
                        .addPathPatterns("/**")
                        .excludePathPatterns("/index.html","/","/user/login","/asserts/css/*","/asserts/js/*","/asserts/img/*");
            }
      
      • addInterceptor:加入拦截器
      • addPathPatterns:需要拦截的东西 /** 全部
      • excludePathPatterns:这些文件排除在外,不进行拦截

抽取与复用

  • 使用th:fragment 抽取 th:fragment="sidebar"
  • 使用th:replace替换 th:replace="~{commons/commons::topbar}"
  • 如果要传参数,可以直接使用()传参,接收判断

员工列表展示

  • 前端
							<thead>
								<tr>
									<th>序号</th>
									<th>姓名</th>
									<th>邮箱</th>
									<th>性别</th>
									<th>部门</th>
									<th>生日</th>
									<th>操作</th>
								</tr>
							</thead>
							<tbody>
								<tr th:each="emp:${emp}">
									<td th:text="${emp.id}"></td>
									<td th:text="${emp.lastName}"></td>
									<td th:text="${emp.email}"></td>
									<td th:text="${emp.gender==1?'男':'女'}"></td>
									<td th:text="${emp.department.departmentName}"></td>
									<td th:text="${emp.birth}"></td>
									<td>
										<button class="btn btn-sm btn-primary">编辑</button>
										<button class="btn btn-sm btn-danger">删除</button>
									</td>
								</tr>
							</tbody>
  • 后端
    @RequestMapping("/findEmployee")
    public String findEmployee(Model model){
        Collection<Employee> all = employeeDao.findAll();
        model.addAttribute("emp",all);
        return "emp/list";
    }

新增/添加员工

1,按钮提交
2.跳转到添加界面
3.添加员工成功然后跳转回首页

CRUD完成

404

  • 只需要在templates文件夹下面建立一个error文件夹,如果报了错之后,会自动的跳转到404.html’

七,网站开发

前端:

  • 模板:别人买好的,我们拿来改

  • 框架:组件:自己手动组合! BootStrap,Layui,semantic-ui

    • 格栅系统
    • 导航栏
    • 侧边栏
    • 表单
  • 1.前端搞定:页面

  • 2.设计数据库

  • 3,前端让他能够自动运行,独立化工程

  • 4.数据接口如何对接:json

  • 5.前后端联调测试

八,以下内容

  • JDBC
  • Mybatis: 重点
  • Druid: 重点
  • Shiro: 安全
  • SpringSecurity: 安全,重点
  • 异步任务~,邮件发送,定时任务
  • Swagger
  • Dubbo + Zookeeper

9,整合数据库

1,JDBC数据库

1.1 创建项目

  • 选用关系型数据库里面的JDBC和Mysql Driver

  • 所需要的pom坐标

    •       <dependency>
                <!--jdbc-->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <!--mysql数据库-->
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <scope>runtime</scope>
            </dependency>
      

1.2 配置

  •   # 应用服务 WEB 访问端口
      server.port=8080
      # 数据库驱动:
      spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
      # 数据库连接地址(时区配置)
      spring.datasource.url=jdbc:mysql://localhost:3306/ljh02?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
      # 数据库用户名&密码:
      spring.datasource.username=root
      spring.datasource.password=021012
    
  • 他会直接生成DataSource,直接注入即可

  • 如果出现时区报错,就加入一个serverTimezone=UTC

1.3 运用

  • 在Springboot中会有xxxx Template:Springboot已经配置好了,直接使用即可

  •   package com.ljh.Controller;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.List;
      import java.util.Map;
      
      @RestController
      public class JDBCController {
      
          @Autowired
          JdbcTemplate jdbcTemplate;
      
          //查询数据库的全部信息
          @RequestMapping("/userlist")
          public List<Map<String,Object>> userList(){
              String sql = "select * from student";
              List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
              return maps;
          }
      
          //添加用户
          @RequestMapping("/addUser")
          public String addUser(){
              String sql = "insert into student (id,name,telephoneNumber) values (111,'shabi','123457')";
              jdbcTemplate.execute(sql);
              return "redirect:/userlist";
          }
      
          //更新用户
          @RequestMapping("/update/{id}")
          public int updateUser(@PathVariable("id")Integer id){
              String sql = "update student set id = ?,name=?,telephoneNumber=? where id = "+id;
              Object[] o = new Object[3];
              o[0] = 1;
              o[1] = "diaosi";
              o[2] = "19090";
              int update = jdbcTemplate.update(sql, o);
              return update;
          }
      
          //删除用户
          @RequestMapping("/delete/{id}")
          public int deleteUser(@PathVariable("id")Integer id){
              String sql = "delete from student where id = ?";
              int update = jdbcTemplate.update(sql, id);
              return update;
          }
      }
      
    

2,整合Druid数据源

2.1 配置`

2.2 配置文件的配置

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.21</version>
    </dependency>
    
  • 在Application.properties中配置

    •   # 配置数据源
        spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
      
  • 直接使用即可

2.2 内部的配置

  •   #数据库连接信息
      spring:
        datasource:
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 021012
          url: jdbc:mysql://localhost:3306/ljh02?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
          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=5
          druid:
            stat-view-servlet:
              # 是否启用StatViewServlet默认值true
              enabled: true
              # 访问路径为/druid时,跳转到StatViewServlet
              url-pattern: /druid/*
              # 是否能够重置数据
              reset-enable: false
              # 需要账号密码才能访问控制台,默认为root
              login-username: druid
              login-password: druid
              # IP白名单
              allow: 127.0.0.1
              # IP黑名单(共同存在时,deny优先于allow)
              deny:
    

2.3 log4j在druid配置中的整合

  • 配置一个config

  •   @Configuration
      public class DruidConfig {
      
          @ConfigurationProperties(prefix = "spring.datasource")
          @Bean
          public DataSource druidDataSource(){
              return new DruidDataSource();
          }
      
          //后台监控
          //因为Springboot中内置了Servlet容器,所有没有web.xml文件,替代方法:ServletRegistrationBean
          @Bean
          public ServletRegistrationBean statViewServlet(){
              ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*");
      
              HashMap<String,String> init = new HashMap<>();
              //增加配置
              init.put("loginUsername", "admin"); //登录key的值是固定的 loginUsername和loginPassword
              init.put("loginPassword", "123456");
      
              //允许谁能访问
              init.put("allow", "");//如果是个空值,那么说谁都可以访问
      
              //后台有人登录
              bean.setInitParameters(init); //设置初始化
      
              return bean;
          }
      }
    
  • 注: 因为Springboot中内置了Servlet容器,所有没有web.xml文件,替代方法:ServletRegistrationBean

    • Spring Boot 源码的都支持,Spring boot 默认为我们提供了注册 Servlet 三大组件 Servlet、Filter、Listener 的接口。我们只需按需配置和添加少量的代码即可实现添加 Servlet 的功能。
  • 注: 在初始化数据的时候我们只能用登录key的值是固定的 loginUsername和loginPassword两个值来登录,否则会出现错误

  • 注:如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j

  • 在运行的时候,我们可以使用localhost:8080/druid 来访问druid页面,在页面里面可以发现sql监控(日志信息)

    2.4 整合filter过滤器

  •       //filter过滤
          @Bean
          public FilterRegistrationBean webStatFilter(){
              FilterRegistrationBean bean = new FilterRegistrationBean();
              bean.setFilter(new WebStatFilter());
      
              Map<String,String> init = new HashMap<>();
              //可以过滤那些请求
              //这些东西不进行统计
              init.put("exclusions","*.js,*.css,/druid/*");
      
              //初始化
              bean.setInitParameters(init);
      
              return bean;
          }
    
  • FilterRegistrationBean:通过实现javax.servlet.Filter接口,覆盖其doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)方法,决定拦截或放行

3,整合Mybatis框架(重点)

  • 导入依赖
  • 配置文件
  • @mapper注解

10,SpringSecurity(安全)

在web开发中,安全第一位,过滤器和拦截器

  • https://www.freesion.com/article/98931326949/#1__4 SpringSecurity优秀笔记

功能性需求:否

做网站应该在设计网站之初就考虑安全

  • 漏洞:隐私泄露
  • 架构

shiro,SpringSecurity:两个框架很像,除了类不一样,名字不一样

认证,授权(vip1,vip2,vip3)

  • 功能权限
  • 访问权限
  • 菜单权限
  • … 拦截器,过滤器

10,1 准备

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13HI5gm4-1677419918889)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225175944631.png)]

  • 在这个页面,我们想让他登录只有,有的人不访问level2或者level3等,就可以采用这些方法
  • 在结合SpringSecurity时候,我们会采用Aop的思想,横切

10.2 介绍

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式
    • @Enablexxx 开启某个功能

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

参考官网:https://spring.io/projects/spring-security

查看我们自己项目中的版本,找到对应的帮助文档:

https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4

10.3 用户认证和授权

目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能

  • https://www.cnblogs.com/magic-sea/p/12824718.html 优秀总结(一)

10.3.1引入 Spring Security 模块

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

10.3.2 写配置类

  • https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/#jc这个页面里面有Java Config的配置[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JEz0PSrc-1677419918890)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225181014014.png)]

  •   //1,加注解 @EnableWebSecurity; 2,写继承 WebSecurityConfigurerAdapter
      
      @EnableWebSecurity
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              super.configure(http);
          }
      }
    
10.3.2.1 SpringSecurity中的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wCyXEoE6-1677419918890)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225181202796.png)]

10.3.2.2 http安全
  •       @Override //链式编程思想 http安全
          protected void configure(HttpSecuity http) throws Exception {
              //请求授权规则
              //首页所有人可以访问,功能页只有对应有权限的人才能访问
              //authorizeRequests() 认证请求
              //antMatchers("/") 添加页面
              //permitAll() 所有人都可以访问
              //hasRole("vip1") 谁能够访问
              http.authorizeRequests()
                      .antMatchers("/").permitAll()
                      .antMatchers("/level1/**").hasRole("vip1")
                      .antMatchers("/level2/**").hasRole("vip2")
                      .antMatchers("/level3/**").hasRole("vip3");
      
              //没有权限会跳入到登录页面,需要开启登录页面
              //login页面
              http.formLogin();
          }
    
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i3ftyASZ-1677419918890)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225182650218.png)]

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h9lBXU1u-1677419918890)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225182747057.png)]

10.3.3 认证
  •       //认证 springboot 2.1.x可以直接使用
          //密码编码,PasswordEncoder
          //在Spring Security 5.0+ 新增加密
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              //withUser 是虚拟数据 传入username  password:密码 roles:是权限(角色)
              //passwordEncoder 是对于密码的加密,如果不加密数据是不能用的
              //new BCryptPasswordEncoder().encode("123") 设置密码
              //正常是再说数据库里面读取
              auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                      .withUser("ljh").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2")
                      .and()
                      .withUser("zzt").password(new BCryptPasswordEncoder().encode("234")).roles("vip2","vip3");
          }
    
  • 这个给一个用户给了一个角色,这个角色可以访问http安全里面的hasRole()访问的人

  • 在登录ljh的时候,只能对level1和level2进行访问,在登录zzt的时候,只能对level2和level3里面的人进行访问

  • 在访问无权限的时候系统会弹出

    •   There was an unexpected error (type=Forbidden, status=403).
        Forbidden (被禁止的,即,无权访问)
      
  • 如果采用数据库认证,则需

    •   @Autowired
        private DataSource dataSource;
        
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            // ensure the passwords are encoded properly
            UserBuilder users = User.withDefaultPasswordEncoder();
            auth
                .jdbcAuthentication() //调用jdbc
                    .dataSource(dataSource) //定义数据源
                    .withDefaultSchema()
                    .withUser(users.username("user").password("password").roles("USER"))
                    .withUser(users.username("admin").password("password").roles("USER","ADMIN"));
        }
      
10.3.4 注销
  • 1,在HttpSecuity中添加http.logout();方法即可
  • 2.注销之后跳转页面http.logout().logoutSuccessUrl("/");
10.3.5 记住我功能
  • 1,与注销类似,在HttpSecuity中添加http.rememberMe()方法即可;
  • 2,如果自定义就可以看10.3.6
10.3.6 自定义登录
  •   //login页面 loginPage("/toLogin")登录页面    
      // usernameParameter("user"):前端提交表单的name可以自定义      
      // passwordParameter("pwd")通前一个一样    
      // loginProcessingUrl("/login");实际登录页面
      http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
    
    • 关于.loginProcessingUrl("/login")的理解
      • 前端里面的<form th:action="@{/login}" method="post">,但是登录页面是loginPage("/toLogin")所以就会存在差异,所以就需要loginProcessingUrl("/login")来进行更改
      • 但是前端是<form th:action="@{/toLogin}" method="post">,登录页面也是loginPage("/toLogin"),就不需要加
  • 自定义页面的记住我功能

    •       //记住我功能 实际就是发送了一个cookie 默认保持两周,在自定义页面里面,有一个单选框,我们直接获取单选框的name即可
            http.rememberMe().rememberMeParameter("remember");
      
    • 前端里面

      •   <input type="checkbox" name="remember"> 记住我
        
      • 我们需要的是rememberMeParameter("remember");name="remember"相互对应

11.Shiro

11.1 简介

  • Apache Shiro 是一个Java 的安全 (权限) 框架
  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在]avaSE环境,也可以用在JavaEE环境.
  • Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。
  • 下载地址: http://shiro.apache.org/

11.2 功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZhvBB0PO-1677419918891)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225210500480.png)]

  • Authentication :认证、验证
  • Authorization :授权
  • Session Management :会话管理
  • Crytography :加密机制

11.3 shiro架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xb11Tzru-1677419918891)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225210353680.png)]

  • 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
      
  • 总结:

    •   应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager
        我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断
      

11.4 代码分析(入门案例(官网提供))

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;

public class Quickstart {

    //集成log4j框架
  private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);

  public static void main(String[] args) {
    // 工厂模式,通过shiro.ini 配置文件中的信息,生成一个工厂实例
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);

    // 通过SecurityUtils 获取当前执行的用户Subject:
    Subject currentUser = SecurityUtils.getSubject();

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

    // 让我们登录当前用户,以便我们可以检查角色和权限:
    // 这里和SpringSecurity 使用了类似的代码,判断用户是否被认证
    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 (AuthenticationException ae) {
        //意外情况?错误?
      }
    }

    //currentUser用法
    //打印其标识主体(在这种情况下,为用户名) : 
    log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

    // 检查角色是否存在
    if (currentUser.hasRole("schwartz")) {
      log.info("May the Schwartz be with you!");
    } else {
      log.info("Hello, mere mortal.");
    }

    //粗粒度
    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.");
    }

    //细粒度
    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!");
    }

    //注销
    currentUser.logout();

    //结束
    System.exit(0);
  }
}
  • 主要流程

    •   //获取当前对象
        Subject currentUser = SecurityUtils.getSubject();
        //根据当前对象获取对应的Session
        Session session = currentUser.getSession();
        //判断用户是否被认证,根据用户名和密码生成令牌
        UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
        //设置记住我功能
        token.setRememberMe(true);
        //拿到令牌进行登录
        currentUser.login(token);
        //打印其标识主体
        currentUser.getPrincipal()
        //注销
        currentUser.logout();
      

11.5 Shiro整合Springboot

11.5.1 准备工作

shiro的架构

//ShiroFilterFactoryBean 第三步
//DefaultWebSecurityManager 第二部
//创建realm对象 需要自定义 由下往上配置
  • Realm对象创建

    •   //自定义的UserRealm  让他extends AuthorizingRealm
        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;
            }
        }
      
  • ShiroConfig类的创建

    •   @Configuration
        public class ShiroConfig {
        
            //ShiroFilterFactoryBean 第三步
            @Bean(name = "filterShiroFilterRegistrationBean")
            public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager){
                ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
                //设置安全管理器
                bean.setSecurityManager(defaultWebSecurityManager);
                return bean;
            }
            //DefaultWebSecurityManager 第二部
            @Bean                                                           //将上面的userRealm拿下来,让其关联 @Qualifier(方法名)
            public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
                DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
                //关联UserRealm
                securityManager.setRealm(userRealm);
                return securityManager;
            }
        
        
            //创建realm对象 需要自定义 由下往上配置
            @Bean //被spring托管  第一步
            public UserRealm userRealm(){
                return new UserRealm();
            }
        
        }
      
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bL2RwJm8-1677419918891)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225222235318.png)]

11.5.2 实现登陆拦截

  •       //ShiroFilterFactoryBean 第三步
          @Bean(name = "filterShiroFilterRegistrationBean")
          public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager){
              ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
              //设置安全管理器
              bean.setSecurityManager(defaultWebSecurityManager);
      
              /*
              * anon:无需认证就可以访问
              * authc:必须认证了才能访问
              * user:必须拥有 记住我 功能之后才能访问
              * perms:拥有对某个资源的权限才能访问
              * role: 拥有某个角色才能访问
              * */
      
              //登录拦截
              Map<String,String> filterMap = new LinkedHashMap<>();
              filterMap.put("/user/add", "anon");
              filterMap.put("/user/update", "authc");
      
              bean.setFilterChainDefinitionMap(filterMap);
      
              //设置登录请求
              bean.setLoginUrl("/toLogin");
      
              
             
              return bean;
          }
    
  • 对于某一个连接可以访问,都赋予权限

  • 也可以使用通配符 filterMap.put("/user/*", "authc"); user下的全部路径都是需要认证之后才能登录的

  • 在登录点击之后会跳到认证里面,即Realm中的认证

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nuNQluIA-1677419918891)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226082418256.png)]

11.5.3 授权和认证

11.5.3.1 认证
无数据库

当我们定义public class UserRealm extends AuthorizingRealm之后,会重写doGetAuthenticationInfo方法,即认证方法

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

        //用户名,密码  数据库中取一般
         String name = "root";
         String password = "123";

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        if(!token.getUsername().equals(name)){
            return null; //当返回值为null时,他会抛出一个授权的异常 UnknownAccountException
        }

        //密码认证 shiro做,没有getPassword方法,防止密码泄露 所需要的参数 Object principal, Object credentials, String realmName
        return new SimpleAuthenticationInfo("",password,"");
    }
  • 在11.5.2 中,点击登录之后,他会将数据自动跳转到 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException方法,即(认证方法)
  • return new SimpleAuthenticationInfo("",password,"");没有getPassword方法,防止密码泄露
有数据库
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("执行了->认证doGetAuthenticationInfo");

    //用户名,密码  数据库中取一般


    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

    User user = userService.queryUserByName(token.getUsername());

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


    //密码认证 shiro做,没有getPassword方法,防止密码泄露 所需要的参数 Object principal, Object credentials, String realmName
    //shiro里面会进行加密对密码
    //可以采用加密方式,md5加密和md5盐值加密
    return new SimpleAuthenticationInfo("",user.getPassword(),"");
}
  • 在连接数据库时,**纯数字密码需要加单引号 **
11.5.3.2 授权
设置页面的权限
  • 在shiro配置类中的public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager)方法中配置权限

  •       //授权 第二个参数表示,需要user用户的add权限才能访问
      		//所有的权限必须有user:add这个字符串而授权
          filterMap.put("/user/add", "perms[user:add]");
    
    • 在登录之后,如果没有权限,我们去访问add,结果是type=Unauthorized, status=401(无权限操作)
设置用户的权限
  •       //授权
          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
              System.out.println("执行了->授权doGetAuthorizationInfo"); //在我们进入配了权限的页面时,我们就会授权
      
              SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
              //只要经过这个地方,就给加一个user:add权限
              //但是这个问题就是,全部人员都会进入这个方法,然后全部人都会被授权
              info.addStringPermission("user:add");
              return info;
          }
    
    • 但是这个样子会给全部的人加上user:add的权限
  • 改进:在数据库内部添加user:add

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4zkWjC9-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226100550887.png)]
  • 使用数据库来完成查询权限控制

    •       //授权
            @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 principal = (User) subject.getPrincipal(); //拿到当前的user对象
                //设置了用户的权限
                info.addStringPermission(principal.getPerms());
                return info;
            }
      
    •   //doGetAuthenticationInfo(AuthenticationToken authenticationToken)中的
        //user是通过在登录页面查到的人
        User user = userService.queryUserByName(token.getUsername());
        //把user放到principal中,以供SecurityUtils.getSubject()中的subject调用
        return new SimpleAuthenticationInfo(user,user.getPassword(),"");
      
    • image-20230226102733859
    • doGetAuthenticationInfo中的这行代码表示将user对象存到principal中,如果调用了当前登录对象,subject,就可以通过subject.getPrincipal()取出内部的值

    • 然后我们就可以将这个值得perms取出,通过addStringPermission方法,加给info.返回权限

  • 例如:用户名ljh 只能进入到add页面进行访问,如果进入到update里面,会进入无权操作页面

    ​ 用户名lx,只能进入到update页面进行访问,如果进入到add里面,就会进入无权操作页面

    • bean.setUnauthorizedUrl("/noauth"); //没有授权的页面

    •       @RequestMapping("/noauth")
            @ResponseBody
            public String unauthorized(){
                return "未经过授权,无权访问";
            }
      

登出操作

  • 获取Subject然后调用subject.logout();

    •       @RequestMapping("/logout")
            @ResponseBody
            public String logout(){
                Subject subject = SecurityUtils.getSubject();
                subject.logout();
                return "退出登录";
            }
      

真正的登录操作执行

    @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";
        }
    }
  • subject.login(token)的这一行,真正的完成了登入,如果需要加一些登录session的话,就需要在这个地方或者认证的地方放入session

12 Swagger

12.1 介绍

  • 后端:后端控制处,服务层,数据访问层
  • 前端:前端控制层,视图层
    • 伪造后端数据:自己写,没有使用后端
  • 前后端交互:=>API
  • 前后端相互独立,松耦合
  • 前后端甚至可以不是不同服务器
  • 前后端分离
    • 前端测试后端接口
    • 后端提供接口,需要实时更新的消息及改动

Swagger

  • 号称世界上最流行的Api框架.
  • RestFul Api 文档在线自动生成工具=>Api文档与API定义同步更新
  • 可以在线测试API接口

官网:https://swagger.io/

12.2 在项目中使用Swagger

  • 需要springbox
    • swagger2
    • ui

12.3 Springboot集成Swagger

  • 新建一个Springboot项目

  • 导入相关依赖

    •       <!--3.0.0 只导入-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-boot-starter</artifactId>
                <version>3.0.0</version>
            </dependency>
      
  • 配置文件配置

    •   spring:
          mvc:
            pathmatch:
              matching-strategy: ant_path_matcher
      
  • 编写一个hello工程

  • 配置Swagger =>Config

    •   @Configuration //配置
        @EnableSwagger2 //开启swagger
        @EnableOpenApi  //3.0.0 版本
        public class SwaggerConfig {
        
        }
      
  • // 3.0.0 以上访问这个页面
    http://localhost:8080/swagger-ui/index.html

  • Swagger 3.0 配置参考: https://cloud.tencent.com/developer/article/1936862

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HBcirwLe-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226121144337.png)]

12.4 配置Swagger

12.4.1 Swagger的bean实例Docket;

  •   @Configuration //配置
      @EnableSwagger2 //开启swagger
      @EnableOpenApi  //3.0.0 版本
      public class SwaggerConfig {
      
          @Bean
          public Docket docket(){
              return new Docket(DocumentationType.SWAGGER_2)//配置了Swagger的bean实例
                      .apiInfo(apiInfo());
          }
      
          //配置Swagger信息
          public ApiInfo apiInfo(){
              //name url email 作者信息
              Contact contact = new Contact("ljh","https://blog.kuangstudy.com","1196915419@qq.com");
      
              return new ApiInfo("LJH Api Documentation",
                      "LJH Api Documentation",
                      "1.0",
                      "https://blog.kuangstudy.com",
                      contact, "" +
                      "Apache 2.0",
                      "http://www.apache.org/licenses/LICENSE-2.0",
                      new ArrayList());
          }
      }
      
    

12.4.2 配置扫描接口

Docket.select();
    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)//配置了Swagger的bean实例
                .apiInfo(apiInfo())
                .select()
                //RequestHandlerSelectors 配置要扫描的接口的方法
                    //basePackage 指出要扫描的包
                    //ang():扫描全部
                    //none:一个都不扫描
                    //withClassAnnotation: 扫描类上的注解
                    //withMethodAnnotation: 扫描方法上的注解
                // .apis(RequestHandlerSelectors.withMethodAnnotation(GetMapping.class))
                .apis(RequestHandlerSelectors.basePackage("com.ljh.Controller"))
                .paths(PathSelectors.ant("/ljh"))//过滤什么路径
                //参数:
                    //ant : 路径
                    //none:一个也不
                    //any:全部
                    //regex:正则
                .build();
    }
配置是否启动Swagger
                .apiInfo(apiInfo())
                .enable(true)
  • 是否启动,如果为false,则Swagger不能在浏览器中访问]

  • 通过环境来判断是否进行Swagger

    •       //设置要显示的Swagger的环境
            Profiles profiles = Profiles.of("dev");
        
            //获取环境 environment.acceptsProfiles判断是否处于这个环境中
            boolean b = environment.acceptsProfiles(profiles);
            //把b传给enable,自动判断是否进行Swagger
      
  • 然后将b传入到enable中即可

    • 如果环境是dev(开发环境),就可以直接打开enable
配置Api文档的分组
.groupName("ljh")
  • 定义多个Docket就可以实现多个分组

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5cTRIbrP-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226155433552.png)]

    •   @Bean
        public Docket B(){
            return new Docket(DocumentationType.SWAGGER_2).groupName("B");
        }
           
        @Bean
         public Docket A(){
             return new Docket(DocumentationType.SWAGGER_2).groupName("A");
         }
        
         @Bean
         public Docket docket(Environment environment){
         	return new Docket(DocumentationType.SWAGGER_2).groupName("ljh");
         }
      

14.4.3 实体类配置

  • 只要我们的接口中,只要返回的有实体类,就会被扫描到Swagger

    •       //只要我们的接口中,只要返回的有实体类,就会被扫描到Swagger
            @PostMapping("/user")
            public User user(){
                return new User();
            }
      
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1I3NP7QO-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226160347916.png)]
    • 加Api注解的意思为,可以在Swagger页面里面显示中文

      @ApiModel("用户实体类")
      public class User {
      
          @ApiModelProperty("用户名")
          private String username;
          @ApiModelProperty("密码")
          private String password;	
      
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBwoqU7C-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226160307950.png)]
  • Api注解的样式(目的就是为了给文档加注解)

    •   @ApiModel("用户实体类")  //加在类上面
      
    •   @ApiModelProperty("用户名")  //加在成员变量的属性上面
      
    •       @ApiOperation("Hello控制类") //接口
            @PostMapping("/hello2")
            public String hello2(@ApiParam("用户名") String username){  //参数Api
                return "hello"+username;
            }
      
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zMIf6vfZ-1677419918893)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226161004284.png[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zc4UmY1N-1677419919936)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226161122971.png)]]

12.5 总结

1.我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
2.接口文档实时更新
3.可以在线测试

13 任务

13.1 异步任务

13.1.1 问题

  • 在存在异步时,如果不采取措施,那么会导致前端页面转圈,影响用户体验
  • 在异步开启后,不是异步的会先行加载,留有异步的操作进行加载

13.1.2 解决措施

  • 1.在异步的方法上面加异步注解

    •       @Async
            public void hello(){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("数据正在发送");
            }
      
  • 2.在Springboot的启动类上面打开异步的注解

    •   @EnableAsync //开启异步
        @SpringBootApplication
        public class ZSpringBoot10TestApplication {
        
            public static void main(String[] args) {
                SpringApplication.run(ZSpringBoot10TestApplication.class, args);
            }
        
        }
      

13.2 定义任务

介绍

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

@EnableScheduling //开启定时功能的注解
@Scheduled //什么时候执行

使用

  •   @Service
      public class ScheduledService {
      
          //在一个特定的时间执行这个方法
          //cron表达式:
          //秒 分 时 日 月 周几
      //    @Scheduled(cron = "0 * * * * 0-7") //在周一到周日的每天的每小时的每分钟的第0秒执行一次
          @Scheduled(cron = "30 24 17 * * ?")
          public void hello(){
              System.out.println("hello,你被执行了");
          }
      }
    
  •   @EnableAsync //开启异步
      @EnableScheduling //开启定时功能的注解
      @SpringBootApplication
      public class ZSpringBoot10TestApplication {
          public static void main(String[] args) {
              SpringApplication.run(ZSpringBoot10TestApplication.class, args);
          }
      }
    
  • 在线cron的解析器: https://www.pppet.net/

13.3 邮件发送

13.3.1 配置

  • 1,导入依赖

    •   <!--邮件依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
      
  • 2,导入配置

    •   spring.mail.host=smtp.qq.com
        spring.mail.username=1196915419@qq.com
        spring.mail.password=ekarnpjvpagegebe
        # QQ需要开启加密验证
        spring.mail.properties.smtp.ssl.enable=true
      
  • 3,写测试类,发送邮件

    •   @SpringBootTest
        class ZSpringBoot10TestApplicationTests {
        
            @Autowired
            JavaMailSenderImpl mailSender;
        
            @Test
            void contextLoads() {
                //一个简单的邮件
                SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
                simpleMailMessage.setSubject("你好");
                simpleMailMessage.setText("秀秀");
                simpleMailMessage.setTo("1196915419@qq.com");
                simpleMailMessage.setFrom("1196915419@qq.com");
                mailSender.send(simpleMailMessage);
            }
        
            @Test
            void contextLoads2() throws MessagingException {
                //一个复杂的邮件
                MimeMessage mimeMessage = mailSender.createMimeMessage();
                //组装
                MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true); //多文件需要加true
                //正文
                helper.setSubject("不再年轻了");
                helper.setText("<p style=‘color:red’>谢谢你放弃了我</p>",true); //可以发送文本,但是如果要解析html的话就需要加true
        
                //附件
                helper.addAttachment("1.JPG", new File("E:\\桌面目录(C盘)\\1.JPG"));
        
                helper.setTo("3241016337@qq.com");
                helper.setFrom("1196915419@qq.com");
                mailSender.send(mimeMessage);
            }
        
        }
        
      
    • 该方法在Springboot的测试类中,直接点击就可以发送

14 Springboot集成Redis

  • 源码分析

    •   @Bean
        @ConditionalonMissinaBean(name ="redisTempate") // 我可以自己定义一redisTemplate来替换这个默的
        public RedisTemplate<object, object> redisTemplate(RedisConnectionFactory redisConnectionFactorythrows UnknownHostException {
        // 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化!// 两个泛型都是 object,object 的类型,我们后使用需要强制转换 <string,object>
        	RedisTemplate<object, object> template = new RedisTemplate<>0);
            template,setConnectionFactory(redisConnectionFactory);
            return template;
        }
                                                           
        @Bean
        @conditionalonMissingBean // 由于 string 是redis中最常使用的类型,所以说单独提出来了一个bean
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {
        	stringRedisTemplate template = new stringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
      

整合

1.导入依赖

```xml
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
```

2.配置连接

```properties
spring.redis.host=localhost
spring.redis.port=6379
# spring.redis.lettuce.pool 配置redis采用lettuce连接池即可
```

3.测试

  •     
      @SpringBootTest
      class ZSpringBoot11RedisApplicationTests {
      
          @Autowired
          private RedisTemplate redisTemplate;
      
          @Test
          void contextLoads() {
              //redisTemplate  操作不同的数据类型,api和我们的指令是一样的
              //opsForValue 操作字符串
              //opsForList 操作list集合
              //opsForHash
              //....
      //        redisTemplate.opsForValue();
      
              //除了基本操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD
              //获取redis的连接对象
      //        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
      //        connection.flushDb();
      //        connection.flushAll();
      
              redisTemplate.opsForValue().set("mykey", "ljh");
              System.out.println(redisTemplate.opsForValue().get("mykey"));
          }
      
      }
      
    

自定义配置类

package com.ljh.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.net.UnknownHostException;

@Configuration
public class RedisConfig {
    /**
     *  编写自定义的 redisTemplate
     *  这是一个比较固定的模板
     */
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 为了开发方便,直接使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

        // Json 配置序列化
        // 使用 jackson 解析任意的对象
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        // 使用 objectMapper 进行转义
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key 采用 String 的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // Hash 的 key 采用 String 的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value 采用 jackson 的序列化方式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // Hash 的 value 采用 jackson 的序列化方式
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        // 把所有的配置 set 进 template
        template.afterPropertiesSet();

        return template;
    }
}

可以很好的序列化,可以更好的设置编码

RedisUtil 的工具类

package com.ljh.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入并设置时间
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * 递减
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }


    // ================================Map=================================

    /**
     * HashGet
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * HashSet 并设置时间
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }


    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }


    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将list放入缓存
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */

    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */

    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }

    }

}

  • 调用代码

    •       @Test
            public void test2(){
                redisUtil.set("diaosi", "zzt");
                System.out.println(redisUtil.get("diaosi"));
            }
      

15 分布式 Dubbo+Zookeeper+Springboot

  • 笔记:https://mp.weixin.qq.com/s/sKu9-vH7NEpUd8tbxLRLVQ

15.1 什么是分布式系统

  • 在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;
  • 分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据
  • 分布式系统(distributed system)是建立在网络之上的软件系统.
  • 首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。

https://cn.dubbo.apache.org/zh-cn/index.html Dubbo官网

15.2 Dubbo

15.2.1 介绍

  • 下载:https://www.apache.org/dyn/closer.lua/dubbo/3.1.4/apache-dubbo-3.1.4-src.zip

  • https://github.com/apache/dubbo-admin/tree/master-0.2.0

  • Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

    dubbo官网 http://dubbo.apache.org/zh-cn/index.html

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ztGHeMI8-1677419918893)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226195608427.png)]

服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

调用关系说明

l 服务容器负责启动,加载,运行服务提供者。

l 服务提供者在启动时,向注册中心注册自己提供的服务。

l 服务消费者在启动时,向注册中心订阅自己所需的服务。

l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

15.3 Zookeeper

下载:https://zookeeper.apache.org/releases.html#download

  • zookeeper:注册中心
  • dubbo-admin:是一个额监控管理后台 查看我们注册了那些服务,哪些服务被消费了
  • Dubbo : jar包

15.4 整合Springboot

  • 不整合了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值