SpringBoot看这一篇文章就够了

news2024/12/26 11:11:31

第一章 SpringBoot简介

第1节 SpringBoot是什么

1
2
1.SpringBoot是一个可以快速创建可运行的、独立的、生产级的基于Spring的应用程序
2.SpringBoot采用一种约定优于配置的设计理念,可以快速让用户创建出一个可运行的基于Spring的应用

第2节 SpringBoot的优势

1
2
3
4
5
1.快速构建项目
2.对主流的开发框架无需配置集成,会自动的集成到一起(约定优于配置)
3.项目可独立运行,不需要外部servlet容器(不需要额外配置tomcat或者jetty容器)
4.提供运行时的应用监控(健康检查机制)
5.极大的提高了开发部署效率

第3节 SpringBoot的系统需求

1
2
3
4
5
6
7
8
9
10
11
由于每隔一段时间官网就会提升一次版本,当前授课采用的SpringBoot2.1.18版本(2020年11月)

当前版本的系统需求:
1、JDK8到JDK12(包含)版本
2、Spring框架版本5.1.19.RELEASE或者更高
3、项目构建工具
    - 3.1 Maven版本 3.3+
    - 3.2 Gradle版本 4.x (4.4 and later) and 5.x
4、Servlet Containers
    - 4.1 Tomcat 9.0
    - 4.2 Jetty 9.4

第二章 快速入门

第1节 SpringBoot的脚手架

  • 脚手架的概念

在我们软件开发中的脚手架的概念,类似于我们的maven工具一样,以一种预先定义好的方式生成特定环境,特定的项目目录结构,并且预先定义好了每一个目录文件的具体功能

  • 官网脚手架地址

https://start.spring.io/

  • 阿里云脚手架地址

https://start.aliyun.com

第2节 创建SpringBoot项目的方式

  • 使用官网提供的工具创建(Spring官网下载sts)
  • 使用IDEA创建
  • 使用脚手架在线创建

第3节 SpringBoot项目结构以及依赖

  • SpringBoot的目录结构
  •  

  • SpringBoot的依赖样式
    1
    2
    3
    4
    5
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <!--带有starter样式-->
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    

第4节 快速入门代码编写

  • 使用IDEA创建项目(2.1.14版本)

  • 勾选web依赖(首先创建一个web项目[相当于我们的SpringMVC项目])

  • 编写CRUD方法(API方式)
    • 编写代码需要的POJO类
      1
      2
      3
      4
      5
      
      public class Book {
          private Integer bookId;
          private String bookName;
          private Double price;
      }
      
    • CRUD代码
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      
      @RestController
      public class BookController {
      
          /**
           * 添加图书(方式一)
           * PostMapping: 是SpringBoot新增注解,由RequestMapping和RequestMethod.POST组合而成
           */
          //@RequestMapping(value = "/addBook",method = RequestMethod.POST)
          @PostMapping(value = "/addBook1")
          public Object addBook1(Book book){
              System.out.println("添加图书1...入参为: "+book);
              Map<String,Object> result = new HashMap<>();
              result.put("code",200);
              result.put("msg","OK");
              return result;
          }
      
          /**
           * 添加图书(方式二)
           * 如果后台请求参数使用@RequestBody修饰那么前端发送数据需要设置请求头headers
           * Content-Type: application/json
           */
          @PostMapping(value = "/addBook2")
          public Object addBook2(@RequestBody Book book){
              System.out.println("添加图书2...入参为: "+book);
              Map<String,Object> result = new HashMap<>();
              result.put("code",200);
              result.put("msg","OK");
              return result;
          }
      
          /**
           * 删除图书(方式一)
           */
          @DeleteMapping(value = "/deleteBook1")
          public Object deleteBook1(Integer bookId){
              System.out.println("删除图书1...入参为: "+bookId);
              Map<String,Object> result = new HashMap<>();
              result.put("code",200);
              result.put("msg","OK");
              return result;
          }
          /**
           * 删除图书(方式二)
           */
          @DeleteMapping(value = "/deleteBook2/{bookId}")
          public Object deleteBook2(@PathVariable("bookId") Integer bookId){
              System.out.println("删除图书2...入参为: "+bookId);
              Map<String,Object> result = new HashMap<>();
              result.put("code",200);
              result.put("msg","OK");
              return result;
          }
      
          /**
           * 更新图书(方式一)
           */
          @PutMapping(value = "/updateBook1")
          public Object updateBook1(Book book){
              System.out.println("修改图书1...入参为: "+book);
              Map<String,Object> result = new HashMap<>();
              result.put("code",200);
              result.put("msg","OK");
              return result;
          }
      
          /**
           * 更新图书(方式二)
           */
          @PutMapping(value = "/updateBook2")
          public Object updateBook2(@RequestBody Book book){
              System.out.println("修改图书2...入参为: "+book);
              Map<String,Object> result = new HashMap<>();
              result.put("code",200);
              result.put("msg","OK");
              return result;
          }
      
          /**
           * 查询图书(方式一)
           */
          @GetMapping(value = "/queryBooks1")
          public Object queryBooks1(int pageNo,int pageSize){
              System.out.println("查询图书1...入参为: pageNo:"+pageNo+" pageSize:"+pageSize);
              Book book1 = new Book(1001,"三国演义",66.66);
              Book book2 = new Book(1003,"红楼梦",99.66);
              Book book3 = new Book(1002,"水浒传",22.66);
              List<Book> books = new ArrayList<>();
              books.add(book1);
              books.add(book2);
              books.add(book3);
              return books;
          }
      
          /**
           * 查询图书(方式二)
           */
          @GetMapping(value = "/queryBooks2/{pageNo}/{pageSize}")
          public Object queryBooks2(@PathVariable("pageNo") int pageNo,@PathVariable("pageSize") int pageSize){
              System.out.println("查询图书2...入参为: pageNo:"+pageNo+" pageSize:"+pageSize);
              Book book1 = new Book(1001,"三国演义",66.66);
              Book book2 = new Book(1003,"红楼梦",99.66);
              Book book3 = new Book(1002,"水浒传",22.66);
              List<Book> books = new ArrayList<>();
              books.add(book1);
              books.add(book2);
              books.add(book3);
              return books;
          }
      }
      
    • 文件上传/下载
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      
      /**
       * 单文件上传
       */
      @PostMapping(value = "/uploadFile")
      public Object uploadFile(@RequestParam("file")MultipartFile file) throws IOException {
          System.out.println("文件上传...文件名为: "+file.getOriginalFilename());
          //将文件保存到指定位置 比如保存到D盘
          file.transferTo(new File("d:\\"+file.getOriginalFilename()));
          Map<String,Object> result = new HashMap<>();
          result.put("code",200);
          result.put("msg","OK");
          return result;
      }
      
      /**
       * 多文件文件上传
       */
      @PostMapping(value = "/uploadFiles")
      public Object uploadFiles(@RequestParam("file")MultipartFile[] files) throws IOException {
          for (MultipartFile file : files) {
              System.out.println("文件上传...文件名为: "+file.getOriginalFilename());
              //将文件保存到指定位置 比如保存到D盘
              file.transferTo(new File("d:\\"+file.getOriginalFilename()));
          }
          Map<String,Object> result = new HashMap<>();
          result.put("code",200);
          result.put("msg","OK");
          return result;
      }
      /**
       * 文件下载
       */
      @GetMapping(value = "/downLoad")
      public ResponseEntity<byte[]> downLoad() throws IOException {
          System.out.println("文件下载...");
          //获取下载文件的输入流
          BufferedInputStream in = new BufferedInputStream(new FileInputStream(new File("d:\\logo-footer.png")));
          //创建下载缓冲区
          byte[] body = new byte[in.available()];
          //将输入流数据读入缓冲区
          in.read(body);
          //创建响应头
          HttpHeaders headers = new HttpHeaders();
          //构建文件名称
          String fileName="abc.png";
          headers.add("Content-Disposition", "attachment;filename="+fileName);
          //创建响应状态码
          HttpStatus ok = HttpStatus.OK;
          ResponseEntity<byte[]> response = new ResponseEntity<>(body,headers,ok);
          return response;
      }
      
    • JSON数据交互
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      
      public class User {
          private Integer userId;
          private String userName;
          private Date hireDate;
      }
      
      
      /**
       * JSON数据返回时间格式处理
       * 当前jackson返回时间格式不符合我们中国人的习惯可以修改成我们的习惯格式
       * 当前jackson返回时间和我们国家的东八区区时查了八个小时所以需要将时区修改为东八区(GMT+8)
       */
      @GetMapping(value = "/queryUserJson")
      public List<User> queryUserJson(){
          List<User> list = new ArrayList<>();
          User u1 = new User(1001,"user01",new Date());
          User u2 = new User(1002,"user02",new Date());
          User u3 = new User(1003,"user03",new Date());
          list.add(u1);
          list.add(u2);
          list.add(u3);
          return list;
      }
      
      修改方式:
      1、可以在返回值类型的类中添加jackson注解
      public class User {
          private Integer userId;
          private String userName;
          @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
          private Date hireDate;
      }
      2、采用全局配置方式
      

      第5节 SpringBoot的常用配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
      # 服务器端口号
      server.port=8080
      # 请求上下文地址
      # server.servlet.context-path=/book
      # 单个文件上传大小
      spring.servlet.multipart.max-file-size=10MB
      # 上传文件总大小
      spring.servlet.multipart.max-request-size=100MB
      # 修改jackson格式
      spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
      # 设置时区
      spring.jackson.time-zone=GMT+8
      

第三章 启动方式

第1节 热部署

  • 添加maven依赖
    1
    2
    3
    4
    5
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional><!--设置子项是否依赖,如果不设置为true热加载也不会成功-->
    </dependency>
    
  • 配置maven插件
    1
    2
    3
    4
    5
    6
    7
    
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <fork>true</fork><!-- 如果不配置fork热加载不会成功 -->
        </configuration>
    </plugin>
    
  • IDEA设置
    1
    2
    
    1、打开IDEA 在当前路径下: File->Settings->Build, Execution, Deployment->Compiler 勾选Build project automatically 然后确定
    2、继续在使用快捷键(Ctrl+Shift+Alt+/)打开面板,选择Registry勾选compiler.automake.allow.when.app.running
    

    第2节 启动方式

    1
    2
    3
    4
    
    1. 使用自带的main方法启动
    2. 在当前项目的根目录下(pom文件所在目录)执行 mvn spring-boot:run 命令
    3. 执行 java -jar xxx.jar 命令
    4. 修改端口号 java -jar xxx.jar --server.port=9999
    

第四章 配置文件

第1节 SpringBoot配置文件的两种格式

1
2
3
4
1、application.properties
2、application.yml

上面这是SpringBoot支持的两种配置文件的格式,在开发中使用哪种都可以,最常见的是yml方式.

第2节 配置文件中自定义配置属性的获取

  • @Value(“${名称}”) 获取单个属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    1. 在application.properties自定义配置文件
        # 自定义配置
        com.qianfeng.springboot=hello
        name=Tom
        age=18
    2. 在我们的Java类中获取
    
    @Value("${com.qianfeng.springboot}")
    private String qianfeng;
    @Value("${name}")
    private String name;
    @Value("${age}")
    private Integer age;
    /**
     * 测试配置文件获取
     */
    @GetMapping(value = "/getProperties")
    public String getProperties(){
        System.out.println(qianfeng);
        System.out.println(name);
        System.out.println(age);
        return "ok";
    }
    
  • @ConfigurationProperties 获取实体对象

    • 自定义配置(application.yml)
      1
      2
      3
      4
      
      user:
        userId: 1001
        userName: Tom
        hireDate: 2020-12-12 22:22:22
      
    • 使用实体对象压入配置信息
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      
      @Component  //加入IOC容器进行实例化
      @ConfigurationProperties(prefix = "user") //指定配置前缀
      public class User {
          private Integer userId;
          private String userName;
          @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //时间格式
          private Date hireDate;
      }
      
      注意:当使用ConfigurationProperties注解时会报一个警告,官网解释需添加一个依赖即可
      
      <dependency>
      	<groupId>org.springframework.boot</groupId>
      	<artifactId>spring-boot-configuration-processor</artifactId>
      	<optional>true</optional>
      </dependency>
      
    • 测试
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      @Autowired
      private User user;
      /**
       * 测试配置文件获取
       */
      @GetMapping(value = "/getProperties")
      public String getProperties(){
          System.out.println(user);
          return "ok";
      }
      

      第3节 SpringBoot多环境配置

  • 多环境配置(方式一)

    1
    2
    3
    4
    5
    6
    7
    
    SpringBoot多环境管理必须要遵循SpringBoot官方配置文件命名规则 application-{profile}.properties或者application-{profile}.yml
    
    
    eg: 
    application-dev.yml  开发环境 
    application-test.yml 测试环境 
    application-pro.yml  生产环境
    

    按照这个格式编写好的配置默认不会被SpringBoot执行,在运行时springboot还会默认执行application.yml文件,可以在appliaction.yml配置文件中设置Spring.profiles.active=dev 这样在项目运行时就会走application-dev.yml配置,但是这样还是需要频繁的修改配置,所以SpringBoot还提供了一个方式在不修改配置的情况下动态的进行配置文件修改

  • 多环境配置(方式二)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    在同一个文件中进行多环境配置的时候使用 --- 三个横线方式进行分割,在yml文件中配置 
    
    spring:
      profiles:
        active: dev
    ---
    spring:
      profiles: dev
    server:
      port: 8080
    ---
    spring:
      profiles: test
    server:
      port: 8081
    ---
    spring:
      profiles: pro
    server:
      port: 8082
    
  • 使用命令动态的选择环境

    1
    2
    3
    4
    5
    
    1、使用命令将项目打成jar包,然后使用java -jar xxx.jar 运行
    java -jar thymeleaf01-0.0.1-SNAPSHOT.jar --spring.profiles.active=pro
    
    2、使用maven命令运行(2.x版本)
    mvn spring-boot:run -Dspring-boot.run.profiles=test
    
  • maven命令地址

    1
    
    https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/html/#run-example-active-profiles
    

第五章 异常处理

第1节 单个异常处理

1
2
3
4
5
6
7
8
//设置指定捕获当前类中的哪些异常,当前设置为Exception异常以及其子类都会被捕获到
@ExceptionHandler(value= {Exception.class})
public Object testException(Exception e){
	Map<String,Object> result = new HashMap();
	result.put("code",200);
	result.put("msg":e.getMessage());
	return result;
}

第2节 统一异常处理

1
2
3
4
5
6
7
8
9
10
11
@RestController
@ControllerAdvice
public class CommonException {
    @ExceptionHandler(value = {Exception.class})
    public Object testException(Exception e){
        Map<String,Object> result = new HashMap();
    	result.put("code",200);
    	result.put("msg":e.getMessage());
    	return result;
    }
}
  • 异常的优先级
    1
    
    先查找当前类里面的异常处理,如果不存在在去统一异常处理类中匹配
    

第六章 日志设置

第1节 SpringBoot的日志

1
2
3
4
5
6
7
8
SpringBoot默认使用的是Slf4J+logback的日志

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

//日志的依赖一般都不需要加,因为很多其他依赖都带着此依赖,例如 web依赖

1.1 日志的配置

1
2
3
4
5
6
7
# 修改日志级别
logging.level.root=INFO
# 日志输出路径(会在当前文件夹下生成一个名字为spring.log的日志文件)
logging.path=C:\\log
# 修改生成的文件名称(会在项目的当前路径下生成一个 xxx.log 日志文件)
# logging.path和logging.file不能同时使用,如果同时使用,那么只有logging.file生效
logging.file=abc.log

第2节 SpringBoot的统一日志处理

2.1 使用AOP进行日志处理

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Aspect
@Component
public class LogAspect {
    private final static Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);
    @Pointcut(value = "execution(* cn.ukoko.springbootaop.controller..*.*(..))")
    public void log(){}
    @Around(value = "log()")
    public Object methodAroud(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取主机IP
        LOGGER.info("IP :{}",request.getRemoteAddr());
        //获取请求地址
        LOGGER.info("URL:{}",request.getRequestURL().toString());
        //获取请求方式
        LOGGER.info("HTTP Method:{}",request.getMethod());
        //获取类名和方法名
        LOGGER.info("Class Method:{}.{}",joinPoint.getSignature().getDeclaringTypeName(),joinPoint.getSignature().getName());
        Object proceed = joinPoint.proceed();
        LOGGER.info("Result:{}",proceed);
        return proceed;
    }
}

第七章 Spring5.x与Spring4.x版本的差异化

Spring5.x版本相比较于4.x版本发生了很大的变化

第1节 注解的变化

1
2
3
4
5
1. @RestController          : 组合注解(@ResponseBody + @Controller)
2. @SpringBootApplication   : 组合注解(多用于SpringBoot的启动注解)
3. @ImportResource          : 如果一定要使用xml配置文件可以使用此注解加载
4. @ConfigurationProperties : 获取以xxx作为前缀的数据
5. @value                   : 获取以什么命名的数据

第2节 配置文件的变化

1
传统的配置比如ssm框架采用的xml配置,而SpringBoot官方不建议使用xml配置,而是采用传统的API和注解的方式进行配置

举一个例子(整合一个不遵循SpringBoot约定优于配置的依赖[非start依赖])

  • 添加依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.20</version>
    </dependency>
    
  • 创建配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    applicaton.yml
    
    username1: root
    password1: root
    jdbcurl1: jdbc:mysql:///ssm
    
    
    /**
     * 如果maven的依赖满足start这样的类型,多半是不需要配置的,这样的类型的依赖可以和SpringBoot直接整合到一起
     * 但是有一些框架或者工具没有提供start这样类型的依赖,在加入依赖之后不会自动整合到SpringBoot上面,这时候就需要我们配置
     */
     //@ImportResource(locations = {"classpath:bean.xml"})
    @Configuration
    public class DruidConfiguration {
        
        @Value("${username1}") //在进行配置时候不要取有歧义的名字
        private String username;
        @Value("${password1}")
        private String password;
        @Value("${jdbcurl1}")
        private String jdbcurl;
        /**
         * 配置Druid
         */
        //@Primary //自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
        @Bean //将方法的返回值加入到IOC容器中,和我们的xml配置中的bean标签功能相同,内部有name属性,设置为IOC容器中的对象取别名
        public DataSource createDruid(){
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            dataSource.setUrl(jdbcurl);
            System.out.println("------------"+dataSource);
            return dataSource;
        }
    }
    
  • 测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    @Autowired //从IOC容器中获取对象
    private DataSource dataSource;
    
    
    System.out.println("从ioc容器中获取对象:"+dataSource);
    //获取数据库连接
    Connection connection = dataSource.getConnection();
    Statement statement = connection.createStatement();
    String sql="SELECT * FROM user";
    ResultSet rs = statement.executeQuery(sql);
    while(rs.next()){
        int userId = rs.getInt("user_id");
        System.out.println("userId="+userId);
        System.out.println();
    }
    

第八章 CRUD练习(API版本)

  • 添加依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.17</version>
    </dependency>
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.3.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
  • 基础配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    @Mapper: SpringBoot的Mapper层需要添加此注解
    
    # 数据库配置
    spring.datasource.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=utf-8&useUnicode=true&serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=root
    
    # mybatis配置
    mybatis.mapper-locations=classpath:mapper/*Mapper.xml
    mybatis.type-aliases-package=com.ssm.testdemo.entity
    mybatis.config-location=classpath:mybatis-config.xml
    
    # 中文编码(处理请求)
    spring.http.encoding.charset=UTF-8
    spring.http.encoding.force=true
    spring.http.encoding.enabled=true
    server.tomcat.uri-encoding=UTF-8
    
  • 单元测试(2.1.x版本)
    1
    2
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    

第九章 Thymeleaf模板

Thymeleaf是一个web端的并且独立的Java模板引擎,他能够处理HTML、XML、JavaScript、CSS以及纯文本,Thymeleaf的理念是创建一种优雅和易维护的模板,为了实现这一点,它建立在自然模板之上,将逻辑注入到模板文件中,还不会影响到模板被用作设计原型。Thymeleaf一开始就设计了Web标准,SpringBoot官方推荐使用Thymeleaf 而不是JSP

第1节 快速入门

  • 添加依赖
    1
    2
    3
    4
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  • 配置文件
    1
    2
    3
    4
    5
    
    spring: 
        thymeleaf: 
            mode: HTML 
            cache: false 
            encoding: utf-8
    
  • 创建页面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    在templates目录下创建index.html和result.html
    
    1、index.html
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
        <!--第一个页面请求-->
        <a href="/helloworld">Hello World</a>
    </body>
    </html>
    -------------------------------------------------------------------
    2、result.html
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>结果页</title>
    </head>
    <body>
        <!--结果页面-->
        <h1>结果页面</h1>
    </body>
    </html>
    
  • 创建控制器(@Controller)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    /**
     * @Author 枫桥夜泊1990
     * @BLOG https://hd1611756908.github.io/
     * @BSITE https://space.bilibili.com/514155929/
     * @DATE 2020/8/8
     */
    @Controller
    public class UserController {
    
        /**
         * 快速入门
         */
        @GetMapping(value = "/helloworld")
        public String helloworl(){
            System.out.println("第一个thymeleaf请求...");
            return "result";
        }
    
    }
    
  • 刷新前端页面快捷键
    1
    
    ctrl+F9
    

    第2节 常见语法

  • index.html
    1
    
    <a href="/HelloThymeleaf">HelloThymeleaf</a>
    
  • result.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>结果页</title>
    </head>
    <body>
    <!--结果页面-->
    <h1>结果页面</h1>
    <!--显示普通文本,从域对象中获取-->
    <p th:text="${username}"></p>
    <hr>
    <!--显示带有样式的普通文本-->
    <p th:utext="${desc}"></p>
    <hr>
    <!--显示对象,数据处理(thymeleaf提供了内置对象API可以操作数据,比如对前端显示时间的格式化)-->
    <div>
        <p th:text="${user.userId}"></p>
        <p th:text="${user.userName}"></p>
        <p th:text="${user.createTime}"></p>
        <p th:text="${#dates.format(user.createTime,'yyyy-MM-dd HH:mm:ss')}"></p>
    </div>
    <hr>
    <!--内置域对象-->
    <p th:text="${#httpServletRequest.getAttribute('password')}"></p>
    <hr>
    <!--数据遍历  list集合-->
    <table>
        <tr>
            <td>No.</td>
            <td>UID</td>
            <td>姓名</td>
            <td>创建时间</td>
            <td>偶数</td>
            <td>奇数</td>
        </tr>
        <tr th:each="x,y:${users}">
            <td th:text="${y.index+1}"/>
            <td th:text="${x.userId}"/>
            <td th:text="${x.userName}"/>
            <td th:text="${x.createTime}"/>
            <td th:text="${y.even}"/><!--偶数-->
            <td th:text="${y.odd}"/><!--奇数-->
        </tr>
    </table>
    <hr>
    <!--数据遍历  map集合-->
    <table>
        <tr>
            <td>No.</td>
            <td>UID</td>
            <td>姓名</td>
            <td>创建时间</td>
            <td>偶数</td>
            <td>奇数</td>
        </tr>
        <tr th:each="x,y:${map}">
            <td th:text="${y.index+1}"/>
            <td th:text="${x.value.userId}"/>
            <td th:text="${x.value.userName}"/>
            <td th:text="${x.value.createTime}"/>
            <td th:text="${y.even}"/><!--偶数-->
            <td th:text="${y.odd}"/><!--奇数-->
        </tr>
    </table>
    </body>
    </html>
    
  • 控制器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    @GetMapping(value = "/HelloThymeleaf")
    public String HelloThymeleaf(Model model, HttpServletRequest request){
        //显示普通文本,从域对象中获取
        model.addAttribute("username","Tom");
        //显示带有样式的普通文本
        model.addAttribute("desc","<span style='color:red'>你好,中国</span>");
        //显示对象,数据处理(thymeleaf提供了内置对象API可以操作数据,比如对前端显示时间的格式化)
        User user = new User(1001,"user01",new Date());
        model.addAttribute("user",user);
        //内置域对象
        request.setAttribute("password","123456");
        //数据遍历 list
        User user1 = new User(1002,"user02",new Date());
        User user2 = new User(1003,"user03",new Date());
        User user3 = new User(1004,"user04",new Date());
        List<User> users = new ArrayList<>();
        users.add(user1);
        users.add(user2);
        users.add(user3);
        model.addAttribute("users",users);
        //数据遍历 map
        Map<String,User> map = new HashMap<>();
        map.put("user01",user1);
        map.put("user02",user2);
        map.put("user03",user3);
        model.addAttribute("map",map);
        return "result";
    }
    

    第3节 路径处理

    1
    2
    3
    
    <script type="text/javascript" th:src="@{/js/main.js}"></script> 
    <a th:href="@{/show}">访问controller方法</a> 
    <a th:href="@{/static_index.html}">访问静态页面</a>
    

    第4节 条件语句

    1
    2
    3
    4
    5
    6
    
    th:if="boolean" th:if的表达式需为boolean值。如果为true,则标签显示,如果为false,则标签不显示
    
    th:unless="boolean" th:unless和th:if相反,表达式也需为boolean值。如果为true,则标签不显示,如果为false,则标签显示
    
    <span th:if="${stat}">偶</span>
    <span th:unless="${stat}">奇</span>
    

    第5节 页面引入

  • 页面引入介绍
    1
    
    我们常常需要在一个页面当中引入另一个页面,例如,公用的导航栏以及页脚页面。thymeleaf中提供了两种方式进行页面引入
    
  • 引入方式一[th:replace(替换全部)]
  • 引入方式二[th:include(替换内容)]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    1、新建需要被引入的页面文件,路径为"/templates/footer.html"
    
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
    <footer th:fragment="companyInfo"> 
        <p>设为首页 ©2018 SpringBoot 使用<span th:text="${name}"/>前必读意见反馈京ICP证030173号 </p>
    </footer>
    
    2、在目标页面引入footer.html有两种方式
        - 2.1 <div th:include="footer :: companyInfo" th:with="name=${username}"/> 可以设置参数 
        - 2.2 <div th:replace="footer :: companyInfo"/> 设置参数无效,所以直接引入即可
    

    第6节 使用thymeleaf实现CRUD

    1
    2
    3
    4
    5
    6
    7
    
    请求路径的参数设置
    
    <a th:href="@{/delteUser(userId=${x.userId})}">删除</a>
    删除路径会构建成一个类似于 deleteUser?userId=1000
    
    <a th:href="@{/jumpUser/{userId}(userId=${x.userId})}">更新</a>
    更新路径会构建成一个类似于 jumpUser/1000
    

第十章 Shiro整合(注解与传统xml对比)

Spring5.x版本在SpringBoot中官方推荐使用Java配置的方式来进行与第三方框架的集成
我们这里以springBoot和Shiro的整合方式来讲解Java的配置.

  • 版本介绍
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    SpringBoot版本   : 2.1.14.RELEASE
    shiro-spring版本 : 1.5.0
    
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.5.0</version>
    </dependency>
    
    //如果使用Shiro的注解功能需要添加springboot的aop依赖
    //同时在application.properties中配置spring.aop.proxy-target-class=true,有的版本不需要配置(当前的2.1.17.RELEASE不需要)
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
  • 整合用到的核心注解
    1
    2
    
    @Bean: 可以修饰方法和注解,功能和<bean>标签相同,将对象加入到IOC容器中
    @Configuration : 修饰类,被次注解修饰的类,会被Spring扫描到,被认定为是一个配置类
    
  • 集成Shiro配置代码
    1
    2
    3
    4
    5
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    
    @Configuration
    public class ShiroConfig {
    
        /**
         * 将Realm注入到IOC中
         * @return
         */
        @Bean
        public UserRealm createUserRealm(HashedCredentialsMatcher hashedCredentialsMatcher){
            //创建Realm实例
            UserRealm userRealm = new UserRealm();
            //设置加密规则,HashedCredentialsMatcher对象通过给方法入参注入到此方法中
            userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
            return userRealm;
        }
    
        @Bean
        public HashedCredentialsMatcher createHashedCredentialsMatcher(){
            //创建加密对象
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
            //加密方式
            credentialsMatcher.setHashAlgorithmName("md5");
            //迭代次数
            credentialsMatcher.setHashIterations(1024);
            return credentialsMatcher;
        }
    
        /**
         * 安全管理器
         */
        @Bean
        public SecurityManager createSecurityManager(UserRealm userRealm){
            //创建web的安全管理器
            DefaultWebSecurityManager  securityManager = new DefaultWebSecurityManager();
            //配置realm
            securityManager.setRealm(userRealm);
            return securityManager;
        }
    
    
        /**
         * 将ShiroFilter加入到IOC
         */
        @Bean
        public ShiroFilterFactoryBean createShiroFilter(SecurityManager securityManager){
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            shiroFilter.setSecurityManager(securityManager);
            shiroFilter.setLoginUrl("/unauthentication");//未认证
            shiroFilter.setUnauthorizedUrl("/unauthorized");//未授权控制器
            Map<String,String> map = new HashMap<String, String>();
            map.put("/login","anon");
            map.put("/home1","authc,roles[admin]");
            map.put("/home2","authc,roles[admin],perms[user:update]");
            map.put("/**","authc");
            //如果使用注解就不需要再这里配置拦截规则,可以注释掉
            shiroFilter.setFilterChainDefinitionMap(map);
            return shiroFilter;
        }
    
        /**
         * 注解生效
         * 如果使用Shiro注解,需要配置AuthorizationAttributeSourceAdvisor,否则注解不生效
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }
    
  • 控制器测试方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    
    @RestController
    public class UserController {
    
        /**
         * 登陆请求
         */
        @PostMapping(value = "/login")
        public String login(SysUsers sysUsers){
            UsernamePasswordToken token = new UsernamePasswordToken(sysUsers.getUsername(),sysUsers.getPassword());
            Subject subject = SecurityUtils.getSubject();
            try {
                subject.login(token);
            }catch (AuthenticationException e) {
                e.printStackTrace();
                throw new RuntimeException("用户名错误");
                //return "账号或密码错误!";
            } catch (AuthorizationException e) {
                e.printStackTrace();
                throw new RuntimeException("密码错误");
                //return "没有权限";
            }
            return "success";
        }
    
        /**
         * 测试请求
         */
        @RequiresRoles({"admin"})
        @GetMapping(value = "/home1")
        public String index(){
            System.out.println("我是home1,需要认证用户的角色为admin才能访问");
            return "我是home1...";
        }
    
        /**
         * 测试请求
         */
        @RequiresRoles({"admin"})
        @RequiresPermissions({"user:update"})
        @GetMapping(value = "/home2")
        public String index1(){
            System.out.println("我是home2,需要认证用户的角色是admin并且权限为user:update才可以访问");
            return "我是home2...";
        }
    
        /**
         * 处理未认证的请求
         */
        @GetMapping("/unauthentication")
        public void unAuthorization(){
            System.out.println("当前请求未认证...");
            throw new RuntimeException("当前请求未认证...");
        }
    
        /**
         * 处理未授权的请求
         */
        @GetMapping("/unauthorized")
        public void unauthorized(){
            System.out.println("当前请求未授权...");
            throw new RuntimeException("当前请求未授权...");
        }
    }
    
  • 统一异常处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    @Component
    @ControllerAdvice
    public class ShiroExceptionHandler {
        
        @ResponseBody
        @ExceptionHandler(value = {AuthorizationException.class})
        public Object processShiroAuthorizationException(){
            return "授权失败...";
        }
    
        @ResponseBody
        @ExceptionHandler(value = {AuthenticationException.class})
        public Object processShiroAuthenticationException(){
            return "认证失败...";
        }
    }
    
  • 自定义的Realm
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    
    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        private SysUsersService sysUsersService;
        @Autowired
        private SysUsersRolesService sysUsersRolesService;
        @Autowired
        private SysRolesService sysRolesService;
        @Autowired
        private SysRolesPermissionsService sysRolesPermissionsService;
        @Autowired
        private SysPermissionsService sysPermissionsService;
    
    
        /**
         * 获取授权数据
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
            //获取用户名
            String username = principalCollection.getPrimaryPrincipal().toString();
            System.out.println("username:"+username);
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            //通过用户名查询当前用户的所有角色
            SysUsers user = sysUsersService.getUserByUsername(username);
            //根据用户ID查询角色信息
            List<SysUsersRoles> usersRoles = sysUsersRolesService.getSysUsersRolesByUserId(user.getUserId());
            //根据角色ID查询角色名称
            Set<String> roleNames = new HashSet<>();
            for (SysUsersRoles usersRole : usersRoles) {
                SysRoles roles = sysRolesService.getSysRolesByRoleId(usersRole.getRoleId());
                roleNames.add(roles.getRoleName());
                //通过角色ID查询权限列表
                List<SysRolesPermissions> sysRolesPermissionss = sysRolesPermissionsService.getSysRolesPermissionsByRoleId(roles.getRoleId());
                //根据权限ID查询权限名称
                Set<String > permissionsNames = new HashSet<>();
                for (SysRolesPermissions rolesPermissionss : sysRolesPermissionss) {
                    SysPermissions permissions = sysPermissionsService.getSysPermissionsByPermissionsId(rolesPermissionss.getPermissionId());
                    if (null!=permissions){
                        permissionsNames.add(permissions.getPermissionName());
                    }
                }
                //将权限信息封装进info
                info.addStringPermissions(permissionsNames);
            }
            info.setRoles(roleNames);
            return info;
        }
    
        /**
         * 获取认证数据
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
            //获取用户名
            String username = authenticationToken.getPrincipal().toString();
            System.out.println("realm: username:"+username);
            if(username==null || username.length()<=0){
                throw new UnknownAccountException("当前用户不存在");
            }
            //通过用户名查询数据库
            SysUsers user = sysUsersService.getUserByUsername(username);
    
            if(user==null){
                throw new UnknownAccountException("当前用户不存在");
            }
            System.out.println("====="+user);
            //从数据库中获取数据,并且将查询出来的认证数据封装进alt
            ByteSource salt = ByteSource.Util.bytes(user.getSalt());
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),salt,getName());
            return info;
        }
    }
    
  • 测试(postman)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/175189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nacos源码解析==SPI和spring.factories机制-服务注册-心跳发送-服务拉取-服务调用

Spring.Factories这种机制实际上是仿照java中的SPI扩展机制实现的 springboot核心基础之spring.factories机制 - 知乎 SpringBoot1IDEA编写一个自己的starter_一个java开发的博客-CSDN博客_idea创建spring starter spring-cloud-starter-alibaba-nacos-discovery 将要注册到…

know sth. new 大话C#的进阶必知点解析第1章 第5节 名贵中药材程序WPF显示图片报错,找不到资源? 什么原因

1 Ui布局代码&#xff1b; 布局方面&#xff0c;主要还是继承了原先的布局方式。包括图片的展示&#xff0c;也是用了最外层border边框的方式&#xff0c;边框加入背景颜色方式的图片展示&#xff1b; 去把目标图片进行显示出来&#xff0c;这个没有太多技术含量。 至于图片的…

Spring Boot操作数据库学习之整合Druid

文章目录一 Druid 简介二 配置数据源创建项目步骤及数据库内容三 整合操作3.1 添加Druid数据源依赖3.2 编写配置文件3.3 测试3.4 自定义绑定数据源设置3.5 导入Log4j的依赖&配置日志输出3.6 添加DruidDataSource组件3.7 测试3.8 配置 Druid 数据源监控3.9 配置过滤器一 Dru…

SAPIEN PowerShell Studio 介绍

PowerShell Studio是一款优秀的基于PowerShell研发的脚本编辑器&#xff0c;它拥有全新的代码分析、智能预选、xaml支持功能&#xff0c;能够给用户提供一套完整的软件开发环境&#xff0c;让用户能够更加轻松的工作&#xff0c;这样一来大家开发项目的效率就会大大提升。创建模…

Day866.binlogredoLog -MySQL实战

日志系统 Hi&#xff0c;我是阿昌&#xff0c;今天学习的是关于MySql的binlog&redoLog的内容。 一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块&#xff0c;最后到达存储引擎。 那么&#xff0c;一条更新语句的执行流程又是怎样的呢&#x…

人大金仓数据库的用户与角色

创建用户 create user 用户名 授予用户创建数据库权限 alter user 用户名 要给的权限 然后查看用户信息 \du 用户名 设置用户密码 没有口令不能登录 alter user 用户名 password ‘kingbase’; 修改用户的并发连接数 alter user 用户 connection limit 要设置的连接数; 修改…

as-if-serialhappens-before

一、as-if-serialas-if-serial语义的意思是&#xff1a;不管怎么重排序&#xff08;编译器和处理器为了提高并行度&#xff09;&#xff0c;&#xff08;单线程&#xff09;程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。 为了遵守as-if-seri…

java类成员/final/static都涉及到了2023025

类成员&#xff1a; 在Java类里只能包含成员变量、方法、构造器、初始化块、内部类&#xff08;包括接口、枚举&#xff09;这5种成员&#xff0c;目前已经介绍了前面4种&#xff0c;其中static可以修饰成员变量、方法、初始化块、内部类&#xff08;包括接口&#xff0c;枚举&…

显示器的相关知识

目录 显示器的作用 显示器的尺寸 人眼的可视角度 显示器的分辨率 显示器的刷新率 显示器的灰阶响应时间 显示器的色域 显示器的色深 显示器的色准 显示器的HDR参数 显示器的面板 画面撕裂 前言 导致画面撕裂的原因 防画面撕裂技术 视频的码率 显示器的作用 把…

Golang 多模块开发

Golang 多模块开发 今天学习下Golang中多模块的基础知识&#xff0c;学习多模块的运行原理&#xff0c;使用多模块的方式&#xff0c;可以让开发者的代码在其他多个模块中构建、运行。提高代码的复用&#xff0c;从而提高开发效率。 在今天的学习中&#xff0c;将在工作工作空…

bfs入门教程(广度优先搜索)(含图解)

源自《啊哈算法》 目录 bfs正文 题目 思路 完整代码1 完整代码2 再解炸弹人 题目 思路 完整代码1 完整代码2 总结 bfs正文 第四章--深度优先搜索中&#xff0c;我们用dfs找到了寻找小哈的最短路径 接下来&#xff0c;我们要用bfs&#xff08;Breadth First Sear…

Zookeeper的本地安装部署和分布式安装部署

文章目录一. 本地模式安装部署1&#xff09;安装前准备2&#xff09;配置修改3&#xff09;操作Zookeeper1.2 配置参数解读二. 分布式安装部署1&#xff09;集群规划2&#xff09;解压安装3&#xff09;配置服务器编号4&#xff09;配置zoo.cfg文件5&#xff09;集群操作客户端…

Leetcode.126 单词接龙 II

题目链接 Leetcode.126 单词接龙 II 题目描述 按字典 wordList完成从单词 beginWord到单词 endWord转化&#xff0c;一个表示此过程的 转换序列 是形式上像 beginWord -> s1 -> s2 -> ... -> sk这样的单词序列&#xff0c;并满足&#xff1a; 每对相邻的单词之间…

《高效能团队模式》读书笔记2

如果我们将团队类型的数量缩减为四类基本团队拓扑&#xff0c;这个问题就迎刃而解了。 流动式团队 赋能团队 复杂子系统团队 平台团队只要使用得当&#xff0c;这四类团队拓扑能够满足构建和运行现代软件系统的需要。结合有效的软件边界&#xff08;第6章&#xff09;和团队交互…

Java注解,元注解,自定义注解的使用

Java注解&#xff0c;元注解&#xff0c;自定义注解的使用Java注解基本的注解1.Override2.Deprecated3.SuppressWarnings4.SafeVarargs5.FunctionalInterfaceJava提供的元注解1.Retention2.Target3.Documented4.Inherited自定义注解自定义注解的使用Java注解 从JDK5开始,Java增…

从旺店通·企业奇门到用友U8通过接口集成数据

接入系统&#xff1a;旺店通企业奇门慧策&#xff08;原旺店通&#xff09;是一家技术驱动型智能零售服务商&#xff0c;基于云计算PaaS、SaaS模式&#xff0c;以一体化智能零售解决方案&#xff0c;帮助零售企业数字化智能化升级&#xff0c;实现企业规模化发展。对接系统&…

本地数仓项目(四)—— 即席查询

1 背景 本文描述本地数仓项目即席查询相关内容&#xff0c;主要涉及即席查询工具包括Presto、Druid、Kylin。 本文基于文章《本地数据仓库项目(一) —— 本地数仓搭建详细流程》 和《本地数仓项目(二)——搭建系统业务数仓详细流程》以及《本地数仓项目(三&#xff09;—— 数…

金蝶云星辰和旺店通企业版奇门单据接口集成

金蝶云星辰V1和旺店通企业奇门单据接口集成对接源平台:旺店通企业奇门慧策&#xff08;原旺店通&#xff09;是一家技术驱动型智能零售服务商&#xff0c;基于云计算PaaS、SaaS模式&#xff0c;以一体化智能零售解决方案&#xff0c;帮助零售企业数字化智能化升级&#xff0c;实…

图的基本概念以及表示方法(链式前向星重点理解,简单易懂版)

图表示一个集合中元素之间存在的多对多关系的一种数据结构。 图的一些定义 &#xff1a; 1.图由顶点和连接顶点的边构成&#xff0c;即G ( V , E ) &#xff0c;其中V为顶点集合&#xff0c;E为边的集合。2.边表示两个顶点之间存在某种关系&#xff0c;边表示为&#xff08;…

Elasticsearch7.8.0版本高级查询—— 单字段排序文档

目录一、初始化文档数据二、单字段排序文档2.1、概述2.2、示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; { "name":"zhangsan", "…