SpringBoot2快速上手

SpringBoot2

文档

  • Spring 官网:https://spring.io/
  • SpringBoot 官网:https://spring.io/projects/spring-boot
  • 尚硅谷SpringBoot2课程文档地址:https://www.yuque.com/atguigu/springboot

HelloWord

第一步:创建一个Maven工程

第二步:导入pom依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.1</version>
</parent>

第三步:添加web启动器

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

第四步:编写MainApplication方法,核心运行方法

/**
 * @author songzhengxiang
 * @create 2022-07-14 22:30
 *
 * 添加一个 @SpringBootApplication 注解表示这是一个 springboot 项目
 */
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        // 启动服务,固定写法
        SpringApplication.run(MainApplication.class,args);
    }
}

第五步:编写HelloController

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "hello spring boot2";
    }
}

此时的项目结构

Snipaste_2022-07-14_22-35-16.png

第六步:运行MainApplication

Snipaste_2022-07-14_22-36-20.png

启动成功后再浏览器输入 http://localhost:8080/hello,页面成功响应 hello spring boot2

Snipaste_2022-07-14_22-37-23.png

修改默认端口

SpringBoot默认启动的端口号是 8080,我们可通过配置文件修改默认启动的端口号。新建一个 application.properties,里面指定启动端口号

server.port=8081

然后再次启动服务,可以看到在 8081 端口运行我们的项目

Snipaste_2022-07-15_15-55-59.png

此时在浏览器输入 http://localhost:8081/hello,页面成功响应

Snipaste_2022-07-15_15-57-24.png

自动装配组件的注意点

  • SpringBoot 默认自动装配声明了 @SpringBootApplication 注解的同级以及下面的包
  • 如果想要改变这种默认的装配规则,可以在运行类中设置 @SpringBootApplication(scanBasePackages = "com.szx") 修改这种默认规则

底层注解

@Configuration

新建一个 MyConfig 类,作为一个配置类,在配置类中可以注册 bena 到容器中

@Configuration(proxyBeanMethods = false)
public class MyConfig {
    @Bean
    public User user(){
        return new User("张三",18);
    }

    @Bean
    public Pet pet(){
        return new Pet("猫");
    }
}

如果在配置类中声明了 proxyBeanMethods = false,则通过配置类获取bean不会先在容器中判断是否存在,每次返回的都会是一个新的bean

在启动类中可以获取容器中的 bean

@SpringBootApplication(scanBasePackages = "com.szx")
public class MainApplication {
    public static void main(String[] args) {
        // 启动服务,固定写法
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        // 从容器中获取bean,默认获取到的容器是单实例的
        User user = run.getBean("user", User.class);
        User user1 = run.getBean("user", User.class);
        System.out.println(user == user1);

        // 如果在配置类中声明了 proxyBeanMethods = false,则通过配置类获取bean不会先在容器中判断是否存在,每次返回的都会是一个新的bean
        MyConfig myConfig = run.getBean(MyConfig.class);
        User user2 = myConfig.user();
        User user3 = myConfig.user();
        boolean b = user2 == user3;
        System.out.println(b);

    }
}

@Import

处理手动的在配置类中添加 bean 之外,也可在配置类上添加 @Import 注解导入组件

@Import({User.class,Pet.class})
@Configuration
public class MyConfig {
    @Bean
    public User user(){
        return new User("张三",18,new Pet("金毛"));
    }

    @Bean
    public Pet pet(){
        return new Pet("猫");
    }
}

运行结果

Snipaste_2022-07-17_14-58-58.png

使用 @Import 导入的bean名称默认为全类名

@Conditional条件装配

  • @ConditionalOnBean(name = "pet") 当容器中有某个bena时才会注册这个bena,要注意有先后顺序的问题
  • @ConditionalOnMissingBean 当容器中没有某个组件时,就注册那个组件
@Configuration
public class MyConfig {

    @Bean
    public Pet pet(){
        return new Pet("猫");
    }

    // 当容器中有pet时,才会注册 user,注意这里有先后循序问题
    @ConditionalOnBean(name = "pet")
    @Bean
    public User user(){
        return new User("张三",18,new Pet("金毛"));
    }
}

可以通过 run.containsBean(“user”); 来判断是否存在 user bean

@SpringBootApplication
public class MainApplication2 {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication2.class);

        boolean isUser = run.containsBean("user");
        System.out.println("是否存在user:" + isUser);

        boolean isPet = run.containsBean("pet");
        System.out.println("是否存在pet:" + isPet);
    }
}

@ImportResource

@ImportResource 注解允许导入一个Spring配置文件

例如新建一个 bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="haha" class="com.szx.boot.beans.User">
        <property name="name" value="haha"></property>
        <property name="age" value="15"></property>
    </bean>

    <bean id="hehe" class="com.szx.boot.beans.Pet">
        <property name="name" value="dog"></property>
    </bean>
</beans>

然后通过 @ImportResource 注解导入这个配置类

package com.szx.boot.config;

import com.szx.boot.beans.Pet;
import com.szx.boot.beans.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

/**
 * @author songzx
 * @create 2022-07-16 12:05
 */
@ImportResource("classpath:beans.xml")
@Import({User.class,Pet.class})
@Configuration
public class MyConfig {

    @Bean
    public Pet pet(){
        return new Pet("猫");
    }

    // 当容器中有pet时,才会注册 user,注意这里有先后循序问题
    @ConditionalOnBean(name = "pet")

    @Bean
    public User user(){
        return new User("张三",18,new Pet("金毛"));
    }
}

然后判断容易中是否存在 haha

package com.szx.boot;

import com.szx.boot.beans.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @author songzx
 * @create 2022-07-17 15:10
 */
@SpringBootApplication
public class MainApplication2 {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication2.class);

        boolean isUser = run.containsBean("user");
        System.out.println("是否存在user:" + isUser);

        boolean isPet = run.containsBean("pet");
        System.out.println("是否存在pet:" + isPet);

        boolean ishaha = run.containsBean("haha");
        System.out.println("是否存在ishaha:" + ishaha);
    }
}

结果显示为 true

Snipaste_2022-07-17_15-32-15.png

@ConfigurationProperties 配置绑定

在配置文件中配置一个类中属性默认值,可以通过 @ConfigurationProperties 注解绑定这个值

添加 Car 类

package com.szx.boot.beans;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author songzx
 * @create 2022-07-17 15:42
 */

@Component
@ConfigurationProperties(prefix = "car")
public class Car {
    String name;
    Integer price;

    public Car() {
    }

    public Car(String name, Integer price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}
  • 必须是在容器中的类,才能使用 ConfigurationProperties 注解

  • prefix 表示使用配置类中的那个前缀

在 application.properties 配置文件中添加 car 类属性的默认值

car.name=yd
car.price=100000

在控制器中添加 getCar 方法,返回 car

package com.szx.boot.controller;
import com.szx.boot.beans.Car;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


/**
 * @author songzhengxiang
 * @create 2022-07-14 22:32
 */
@RestController
public class HelloController {

    @Autowired
    Car car;

    @GetMapping("/car")
    public Car getCar(){
        return car;
    }
}

启动程序,访问 car

Snipaste_2022-07-17_15-52-29.png

第二种方法:

不在类上添加 @Component 注解,在配置类中添加 @EnableConfigurationProperties(Car.class) 注解。表示在容器中注册 Car 类,并开启从配置文件中读取属性值

修改 Car 类

@ConfigurationProperties(prefix = "car")
public class Car {

配置类中添加 @EnableConfigurationProperties 注解

@EnableConfigurationProperties(Car.class)
@ImportResource("classpath:beans.xml")
@Import({User.class,Pet.class})
@Configuration
public class MyConfig {

开发小技巧

LomBok 简化bean开发

第一步导入依赖

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

第二步:安装idea插件

Snipaste_2022-07-17_21-50-47.png

第三步,使用相关注解

@Data // getter 和 setter 方法
@ToString // toString 方法
@NoArgsConstructor // 无参构造器
@AllArgsConstructor // 全参构造器
public class Car {
    
    String name;
    Integer price;
}

额外的还有 @Slf4j 注解,可以将信息直接输出在控制台中

在 Controller 类上添加 @Slf4j

package com.szx.boot.controller;
import com.szx.boot.beans.Car;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


/**
 * @author songzhengxiang
 * @create 2022-07-14 22:32
 */
@Slf4j
@RestController
public class HelloController {

    @Autowired
    Car car;


    @GetMapping("/car")
    public Car getCar(){
        log.info("请求进来");
        return car;
    }
}

然后浏览器访问 /car ,查看控制台打印

Snipaste_2022-07-17_21-58-18.png

devtools 热部署

修改代码后热部署

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

修改代码后需要按下 Ctrl + F9 进行重新编译

Spring Iintializr 初始化向导

通过 Spring Iintializr 创建项目,我们只需要通过选择的方式,就可以快速的生成一个工程

Snipaste_2022-07-17_22-13-35.png

Snipaste_2022-07-17_22-15-34.png

Snipaste_2022-07-17_22-16-37.png

配置文件

yaml

YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义

数据类型

字面量:单个的、不可再分的值。date、boolean、string、number、null

k: v

对象:键值对的集合。map、hash、set、object

行内写法:  k: {k1:v1,k2:v2,k3:v3}
#或
k: 
  k1: v1
  k2: v2
  k3: v3

数组:一组按次序排列的值。array、list、queue

行内写法:  k: [v1,v2,v3]
#或者
k:
 - v1
 - v2
 - v3

示例

新建一个 User 类和 Pet 类

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author songzx
 * @create 2022-07-17 23:10
 */
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Data
@Component
@ConfigurationProperties(prefix = "user")
public class User {
    String userName;
    boolean boss;
    Date birth;
    Integer age;
    String[] interests;
    List<String> animal;
    Map<String,Object> score;
    Set<Double> salarys;
    Pet pet;
    Map<String,List<Pet>> allPets;
}
/**
 * @author songzx
 * @create 2022-07-18 18:17
 */
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Data
@ConfigurationProperties(prefix = "pet")
@Component
public class Pet {
    String petName;
    Double price;
}

新建 application.yml 文件

server:
  port: 8081

user:
  userName: 张三
  boss: false
  birth: 2020/07/18
  age: 15
#  interests: ["a","b","c"]
#  或者
  interests:
    - a
    - b
    - c
  animal:
    - 羽毛球
    - 乒乓球
    - 篮球
#  score: {weight:60,height:186}
#  或者
  score:
    weight: 60
    height: 186
  salarys:
    - 99.99
    - 88.88
  pet:
    petName: BYD
    price: 100000
  allPets:
    sike:
      - {petName: bwm,price: 99999}
    healh: [{petName: wuling,price: 6666}]

新建 UserController 控制器,返回 user 类

@RestController
public class UserController {
    @Autowired
    User user;
    @GetMapping("/getuser")
    public User user(){
        return user;
    }
}

浏览器访问 /getuser 查看效果

Snipaste_2022-07-18_18-32-25.png

配置提示

在设置自定义类的初始化值时可以自动提示相关属性

添加依赖

<!--开启配置提示功能-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

设置在打包时去掉提示的依赖

<!--设置打包时去掉配置提示的依赖包-->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-configuration-processor</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

web场景

静态资源访问

默认情况下,Spring Boot 将类路径中名为/static(或/public/resources/META-INF/resources)的目录作为静态资源目录,我们只需要将文件放在这些文件夹中就可以访问到这些静态资源

Snipaste_2022-07-18_22-35-32.png

启动服务,直接输入静态资源名称即可访问到

Snipaste_2022-07-18_22-36-35.png

默认情况下,资源映射在 上/**,但您可以使用该spring.mvc.static-path-pattern属性对其进行调整。例如,将所有资源重新定位到/res/**可以实现如下:

在 application.yml 中添加如下配置

spring:
  # 自定义静态资源访问前缀
  mvc:
    static-path-pattern: /res/**

这时访问静态资源应该以 res 开头

Snipaste_2022-07-18_22-39-22.png

您还可以使用该spring.web.resources.static-locations属性自定义静态资源位置(将默认值替换为目录位置列表),示例如下

spring:
  # 自定义静态资源访问前缀
  mvc:
    static-path-pattern: /res/**
  # 自定义静态资源目录
  web:
    resources:
      static-locations: [classpath:/haha/]

设置完之后,其他的静态资源位置将会失效,只有 haha 文件夹会作为静态资源文件夹

欢迎页和favicon.ico图标

只要在静态资源文件夹放一个 index.html 和 favicon.ico图标,然后访问首页时就可以访问页面和显示网站小图标

但是在设置了静态资源访问前缀后这个功能不会生效

Snipaste_2022-07-18_23-00-26.png

Snipaste_2022-07-18_23-00-55.png

Rest映射

通过请求方式的不同,来实现不同业务的接口请求

请求方式接口说明
GET/user获取user信息
POST/user添加user信息
DELETE/user删除user信息
PUT/user修改user信息

添加实现类

@RestController
public class UserRestController {
    
    @GetMapping("/user")
    public String getUser(){
        return "get user";
    }
    
    @PostMapping("/user")
    public String postUser(){
        return "post user";
    }
    
    @PutMapping("/user")
    public String putUser(){
        return "put user";
    }
    
    @DeleteMapping("/user")
    public String deleteUser(){
        return "delete user";
    }
}

通过页面表单访问时,需要开启以下配置

spring:
  mvc:
    # 自定义静态资源访问前缀
    static-path-pattern: /**
    # 开启rest映射表单格式请求转换
    hiddenmethod:
      filter:
        enabled: true

然后通过表单发送 delet 或者 put 请求时需要携带 _method 参数

<form action="/user" method="post">
    <input type="hidden" name="_method" value="delete">
    <input type="submit">
</form>

改变默认的_method参数

添加配置类,调用setMethodParam方法设置属性名为 _m

@Configuration
public class WebConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hhmf = new HiddenHttpMethodFilter();
        hhmf.setMethodParam("_m");
        return hhmf;
    }
}

然后修改表单中发送的参数即可

<form action="/user" method="post">
    <input type="hidden" name="_m" value="delete">
    <input type="submit">
</form>

请求参数

常用参数注解

接口定义:@GetMapping("/getCar/{id}/{name}")

请求地址:/getCar/1/lisi?age=18&inters=篮球&inters=羽毛球

注解含义
@PathVariable(“id”) Integer id获取路径上的id
@PathVariable Map<String,String> argmap以map的形式将路径上所有的参数放在argmap中
键值必须都是String类型
@RequestHeader(“Referer”) String Referer获取请求头信息中的Referer值
@RequestHeader Map<String,String> headerMap获取所有的请求头信息,并放在map集合中
@RequestParam(“age”) Integer age获取请求参数 age
@RequestParam(“inters”) List inters以List集合的方式获取请求参数inters
@CookieValue(“_ga”) String _ga获取Cookie中的_ga值
@CookieValue(“_ga”) Cookie cookie获取Cookie中的_ga值,以Cookie的形式接收
@RequestAttribute(“msg”) String msg获取在请求域中保存的msg属性

定义一个CarController

package com.szx.boot02initializr.controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author songzhengxiang
 * @create 2022-07-20 21:41
 */
@RestController
public class CarController {

    @GetMapping("/getCar/{id}/{name}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("name") String name,
                                     @PathVariable Map<String,String> argmap,
                                     @RequestHeader("Referer") String Referer,
                                     @RequestHeader Map<String,String> headerMap,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){
        Map<String, Object> map = new HashMap<>();
        map.put("id",id);
        map.put("name",name);
        map.put("argmap",argmap);
        map.put("Referer",Referer);
        map.put("headerMap",headerMap);
        map.put("age",age);
        map.put("inters",inters);
        map.put("_ga",_ga);
        System.out.println(cookie.getName());
        System.out.println(cookie.getValue());
        return map;
    }
}

前端发送请求

<a href="/getCar/1/lisi?age=18&inters=篮球&inters=羽毛球">getCar/1/lisi</a>

浏览器响应返回的数据

Snipaste_2022-07-20_22-10-41.png

接收form表单参数

发送form表单请求

<form action="/getFormArgs" method="get">
    <input type="text" name="age">
    <input type="email" name="email">
    <input type="submit">
</form>
方式一:以map形式接收
/**
 * 接收form表单参数
 */
@GetMapping("/getFormArgs")
public Map getFormArgs(@RequestParam Map<String,Object> formarg){
    System.out.println("formarg.get(\"age\") = " + formarg.get("age"));
    System.out.println("formarg.get(\"email\") = " + formarg.get("email"));
    return formarg;
}

接收的数据

Snipaste_2022-07-20_22-23-13.png

方式二:获取单个数据
/**
 * 接收form表单参数
 * 方式二
 */
@GetMapping("/getFormArgs")
public Map getFormArgs(@RequestParam("age") String age,
                       @RequestParam("email") String email){
    System.out.println("age = " + age);
    System.out.println("email = " + email);
    HashMap<String, Object> argMap = new HashMap<>();
    argMap.put("age",age);
    argMap.put("email",email);
    return argMap;
}

接收到的数据

Snipaste_2022-07-20_22-26-38.png

方式三:以对象形式接收

首先简单封装一个对象

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class FormBean {
    Integer age;
    String email;
}

以FormBean的形式接收到传递过来的值

/**
 * 接收form表单参数
 * 方式三
 */
@GetMapping("/getFormArgs")
public FormBean getFormArgs(FormBean formBean){
    System.out.println("formBean.getAge() = " + formBean.getAge());
    System.out.println("formBean.getEmail() = " + formBean.getEmail());
    return formBean;
}

接收到的参数

Snipaste_2022-07-20_22-33-47.png

接收json数据

首先通过apiPost模拟发送post请求

Snipaste_2022-07-20_23-01-36.png

方式一:以Map形式接收
@PostMapping("/getJsonArgs")
public Map getPostArgs(@RequestBody Map<String,Object> jsons){
    System.out.println("jsons = " + jsons);
    return jsons;
}

接收到的数据

Snipaste_2022-07-20_23-03-03.png

方式二:以对象形式接收
@PostMapping("/getJsonArgs")
public FormBean getPostArgs(@RequestBody FormBean formBean){
    System.out.println("formBean = " + formBean);
    return formBean;
}

接收到的数据

Snipaste_2022-07-20_23-05-25.png

获取请求域中的参数

新建 RequestController,访问 goto 然后内部转发请求 success,在转发前往 httpServletRequest 中保存 msg 和 code 两个属性

@Controller
public class RequestController {

    @GetMapping("/goto")
    public String goToSuc(HttpServletRequest httpServletRequest){
        httpServletRequest.setAttribute("msg","成功");
        httpServletRequest.setAttribute("code",200);
        return "forward:/success";
    }

    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute("msg") String msg,
                       @RequestAttribute("code") Integer code,
                       HttpServletRequest request){
        Object request_code = request.getAttribute("code");
        Object request_msg = request.getAttribute("msg");
        HashMap<String, Object> map = new HashMap<>();
        map.put("annotation_msg",msg);
        map.put("annotation_code",code);
        map.put("request_msg",request_msg);
        map.put("request_code",request_code);
        return map;
    }
}

在success方法中 @RequestAttribute("msg") 注解获取在请求与中保存的msg属性

浏览器发送请求查看返回值

Snipaste_2022-07-21_16-13-47.png

获取矩阵变量参数

例如当浏览器发送如下格式的地址:/shell/car;age=12;name=张三,后端可以通过 @MatrixVariable 注解来获取第一个分号后面的参数。我们称这种参数为矩阵变量。

注意:获取矩阵变量的数据必须前提是Rest风格的地址

/**
     * 获取矩阵变量的参数
     * @author Songzx
     * @date 2022/7/21
     * 请求地址:/shell/car;age=12;name=张三
     */
@GetMapping("/shell/{path}")
public Map getMatrix(@MatrixVariable("age") Integer age,
                     @MatrixVariable("name") String name,
                     @PathVariable("path") String path){
    HashMap<String, Object> map = new HashMap<>();
    map.put("age",age);
    map.put("name",name);
    map.put("path",path);
    return map;
}

查看接口返回

Snipaste_2022-07-21_18-06-26.png

除了获取单个矩阵,也可以获取多个。例如:

/boos/zhang;age=20/wang;age=32

/**
     * 获取矩阵变量的参数
     * @author Songzx
     * @date 2022/7/21
     * 请求地址:/boos/zhang;age=20/wang;age=32
     */
@GetMapping("/boos/{boos1}/{boos2}")
public Map getMatrix2(@MatrixVariable(value = "age",pathVar = "boos1") Integer zhangAge,
                      @MatrixVariable(value = "age",pathVar = "boos2") Integer wangAge){
    HashMap<String, Object> map = new HashMap<>();
    map.put("zhangAge",zhangAge);
    map.put("wangAge",wangAge);
    return map;
}

接口返回

Snipaste_2022-07-21_18-10-19.png

请求拦截和静态资源放行

操作步骤:

  1. 添加interceptor拦截器,实现HandlerInterceptor接口
    1. preHandle方法,接口方法之前拦截
    2. postHandle方法,接口方法执行之后
    3. afterCompletion方法,视图渲染器渲染完毕后执行
  2. 重写preHandle方法,从当前请求中获取session,从session中判断是否有用户信息
  3. 添加配置类,将拦截器添加到容器中
  4. 配置类实现WebMvcConfigurer接口,并重写addInterceptors方法,添加要拦截和放行的地址

代码示例

1.添加interceptor拦截器,实现HandlerInterceptor接口

2.重写preHandle方法,从当前请求中获取session,从session中判断是否有用户信息

/**
 * @author songzx
 * @create 2022-07-22
 * 使用拦截器做登录检查
 */
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        // 从session中获取是否有userBean
        Object userBean = session.getAttribute("userBean");
        // 如果有表示已经登录,拦截器放行
        if(userBean != null){
            return true;
        }
        // 拿到转发器,重新转发到登录页面
        request.getRequestDispatcher("/login").forward(request,response);
        // 否则返回false
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

3.添加配置类,将拦截器添加到容器中

4.配置类实现WebMvcConfigurer接口,并重写addInterceptors方法,添加要拦截和放行的地址

  • addInterceptor 表示添加拦截器,传入我们上面定义的拦截器实例
  • addPathPatterns 表示添加要拦截的路径,/** 表示拦截所有请求
  • excludePathPatterns 表示设置不拦截那些路径,/css/** 表示不拦截以css开头的所有请求
/**
 * 拦截器,实现 WebMvcConfigurer 接口
 * @author songzx
 * @create 2022-07-22
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/","/login","/css/**","/js/**","/fonts/**","/images/**");
    }
}

添加完成后,在访问了除了 / 和 /login 页面外,其他页面都会被拦截器拦截,判断是否登录,如果未登录则强制跳转到登录页面

单文件上传和多文件上传

前端页面请求

<form th:action="@{/saveForm}" enctype="multipart/form-data" method="post">
    <label>邮箱</label>
    <input type="email" name="email">
    <label>姓名</label>
    <input type="text" name="name">
    <label>头像</label>
    <input type="file" name="photo">
    <label>生活照</label>
    <input type="file" multiple name="photos">
    <input type="submit">
</form>

添加saveForm接口

package com.szx.boot04webadmin.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

/**
 * @author songzx
 * @create 2022-07-24 16:32
 */
@Slf4j
@Controller
public class SaveFormController {
    @PostMapping("/saveForm")
    public String saveForm(@RequestParam("email") String email,
                           @RequestParam("name") String name,
                           @RequestPart("photo") MultipartFile photo,
                           @RequestPart("photos") MultipartFile[] photos) throws IOException {

        log.info("邮箱=>{},姓名=>{},头像大小=>{},生活照个数=>{}",email,name,photo.getSize(),photos.length);

        // 判断头像是否为空
        if(!photo.isEmpty()){
            // 获取文件名称
            String filename = photo.getOriginalFilename();
            // 使用transferTo方法将文件转存到 D/images 文件夹下
            photo.transferTo(new File("D:\\images\\"+filename));
        }

        if(photos.length > 0){
            for (MultipartFile file : photos) {
                if(!file.isEmpty()){
                    // 获取文件名称
                    String filename = file.getOriginalFilename();
                    // 使用transferTo方法将文件转存到 D/images 文件夹下
                    file.transferTo(new File("D:\\images\\"+filename));
                }
            }
        }

        return "main";
    }
}

查看打印

Snipaste_2022-07-24_16-45-43.png

打开 D/images 文件夹,我们上传的文件都会保存在这个文件夹内

Snipaste_2022-07-24_16-47-44.png

修改默认的文件大小限制,在yml配置文件中添加如下配置即可

spring:
  servlet:
    multipart:
      max-file-size: 10MB # 单个文件的最大大小
      max-request-size: 100MB # 多个文件上传时一共限制的最大文件大小

默认错误处理机制

默认情况下,SpringBoot提供 /error 处理所有错误的映射。对于机器客户端,它将生成一个 JSON 响应,其中包含错误信息,Http状态和异常消息的详细信息。对于浏览器,则响应一个错误页面

默认情况下,提供的404页面是这样的

Snipaste_2022-07-24_17-23-29.png

还有500页面如下

Snipaste_2022-07-24_17-24-44.png

我们可以自定义这些错误页面

在 templates 文件夹下添加 error 文件夹,里面放上两个页面

Snipaste_2022-07-24_17-28-33.png

现在我们的404和5xx页面都会是自定义的页面

Snipaste_2022-07-24_17-27-51.png

Snipaste_2022-07-24_17-29-56.png

定制化的常见方式

  • 修改配置文件
  • xxxxCustimuzer
  • 编写自定义的配置类,xxxConfiguration; + @Bean 替换、增加容器中默认组件,视图解析器
  • Web应用,编写一个配置类实现,WebMvcConfigurer 即可定制化web功能。 + @Bean 给容器中扩展一些组件

数据访问

基本数据访问操作

导入jdbc依赖,导入之后会自动帮我们引入数据源,jdbc,事务

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

这里没有自动帮我们导入数据库驱动,这是因为SpringBoot并不知道我们用的是什么数据库,这里我们用MySQL来作为我们的数据库

打开 spring-boot-dependencies 搜索 mysql.version,发现SpringBoot有帮我们维护了mysql驱动的版本,我们只需引入mysql驱动即可

Snipaste_2022-07-25_08-59-17.png

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

添加配置项,yml中密码要使用双引号括起来

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: "abc123"
    url: jdbc:mysql://localhost:3306/spring_boot_test

然后再 spring_boot_test 库中新建一个 user 表,增加如下数据

Snipaste_2022-07-25_09-08-18.png

编写测试方法

@Slf4j
@SpringBootTest
class Boot04WebadminApplicationTests {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Test
    void contextLoads() {
        Integer total = jdbcTemplate.queryForObject("select count(*) from user", Integer.class);
        log.info("数据总数->{}", total);
    }
}

运行效果

Snipaste_2022-07-27_21-49-50.png

使用Druid数据源

自定义的方式使用Druid

Druid官方文档:https://github.com/alibaba/druid

中文地址:https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

导入依赖

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

自定义我们自己的DataSource,新建一个MyDruidDataSourceController,当我们在容器中定义了一个DataSource类型的bean,则SpringBoot中默认的数据源就不会再生效了

@Configuration
public class MyDruidDataSourceController {
    // 当我们在容器中定义了一个DataSource类型的bean,则SpringBoot中默认的数据源就不会再生效了
    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource driudDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }
}

然后来测试当前容器中的DataSource类型

@Slf4j
@SpringBootTest
class Boot04WebadminApplicationTests {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    DataSource dataSource;
    @Test
    void contextLoads() {
        Integer total = jdbcTemplate.queryForObject("select count(*) from user", Integer.class);
        //=> 2
        log.info("数据总数->{}", total);
        //=> class com.alibaba.druid.pool.DruidDataSource
        log.info("dataSource类型->{}",dataSource.getClass());
    }
}

Snipaste_2022-07-27_22-36-39.png

通过测试结果可以看到当前的数据源已经是我们自己定义的Druid数据源

使用Druid数据源的监控功能

1.打开Druid的监控统计功能

如果你要打开监控统计功能,配置StatFilter

@Bean
@ConfigurationProperties("spring.datasource")
public DataSource driudDataSource() throws SQLException {
    DruidDataSource druidDataSource = new DruidDataSource();
    // 打开Druid的监控统计功能
    druidDataSource.setFilters("stat");
    return druidDataSource;
}

2.使用Druid的内置监控页面

内置监控页面是一个Servlet,配置 StatViewServlet

在自定义DataSource的Controller中添加如下bean,/druid/* 表示我们要在那个路径下映射监控页面,固定写法,不能更改

@Bean
public ServletRegistrationBean statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();
    ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");

    return registrationBean;
}

然后启动SpringBoot项目,访问 /druid,即可看到Druid监控页面

Snipaste_2022-07-27_22-50-48.png

编写一个测试方法,执行一个sql语句

@Autowired
JdbcTemplate jdbcTemplate;

@GetMapping("/druidsql")
public String getUserTotal(){
    Integer total = jdbcTemplate.queryForObject("select count(*) from user", Integer.class);
    return total.toString();
}

浏览器访问 /druidsql 后可以查看SQL的执行情况

Snipaste_2022-07-27_23-02-53.png

开启Web应用监控功能
/**
 * 配置Web应用监控
 * @return
 */
@Bean
public FilterRegistrationBean webStatFilter(){
    WebStatFilter webStatFilter = new WebStatFilter();
    FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(webStatFilter);
    // 设置要拦截的路径
    registrationBean.setUrlPatterns(Arrays.asList("/*"));
    // 设置不拦截的路径
    registrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
    return registrationBean;
}

设置完成后就可以监控我们的页面跳转。启动项目,跳转页面时的状态都可以在这里看到

Snipaste_2022-07-27_23-11-12.png

开启SQL防火墙功能

在DruidDataSource实例中往filters属性上添加一个值wall即可

@Bean
@ConfigurationProperties("spring.datasource")
public DataSource driudDataSource() throws SQLException {
    DruidDataSource druidDataSource = new DruidDataSource();
    // stat:打开Druid的监控统计功能
    // wall:打开Druid的Sql防火墙功能
    druidDataSource.setFilters("stat,wall");
    return druidDataSource;
}

然后我们发送SQL请求时都会在这里记录

Snipaste_2022-07-27_23-17-59.png

添加密码访问

我们不希望每个人都可以随意看到这个监控页面,可以设置账号密码来访问监控页面

在 ServletRegistrationBean 添加两个初始化参数即可

@Bean
public ServletRegistrationBean statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();
    ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
    registrationBean.addInitParameter("loginUsername","admin");
    registrationBean.addInitParameter("loginPassword","admin");
    return registrationBean;
}

重启项目访问监控页面,自动出现登录页面

Snipaste_2022-07-27_23-25-24.png

start方式配置druid

首先导入依赖

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.17</version>
</dependency>

yml配置实例

spring:
  datasource:
    username: root
    password: "abc123"
    url: jdbc:mysql://localhost:3306/spring_boot_test
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      aop-patterns: com.szx.*  #监控SpringBean
      filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)

      stat-view-servlet: # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin
        resetEnable: false

      web-stat-filter: # 监控web
        enabled: true
        urlPattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'

      filter:
        stat: # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          logSlowSql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false # 是否拦截删库操作

配置完成后启动应用,Druid监控功能正常使用

8558467.jpg

整合Mybatis

github文档:https://github.com/mybatis

配置版

导入mybatis官方start

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

在配置文件中添加mybatis配置规则

新建UserBen对应数据库中的user表

@Data
public class User {
    Integer id;
    String name;
}

新建mapper.UserMapper.java接口,定义一个根据id查询user的方法

注意:mapper类必须标注@Mapper注解,才能被SpringBoot扫描并识别

@Mapper
public interface UserMapper {

    User getUser(Integer id);
}

新建UserService,自动注入UserMapper接口

@Service
public class UserServer {
    @Autowired
    UserMapper userMapper;

    public User getUser(Integer id){
        return userMapper.getUser(id);
    }
}

然后在配置文件中新建 mapper.UserMapper.xml,映射UserMapper接口

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.szx.boot04webadmin.mapper.UserMapper">
    
    <!--User getUser(Integer id);-->
    <select id="getUser" resultType="com.szx.boot04webadmin.bean.User">
        select * from user where id = #{id}
    </select>

</mapper>

然后在yml配置文件中添加mybatis的相关属性配置

mybatis:
  mapper-locations: classpath:mapper/*.xml # 声明mapper映射文件的位置
  configuration:
    map-underscore-to-camel-case: true # 开启驼峰式命名

上述配置完成之后,新建一个UserController测试sql

@RestController
public class UserController {
    @Autowired
    UserServer userServer;

    @GetMapping("/getUserById")
    public User getUser(@RequestParam("id") Integer id){
        User user = userServer.getUser(1);
        return user;
    }
}

启动服务,浏览器访问 http://localhost:8080/getUserById?id=1

8558467.jpg

注解版

可以通过注解将SQL语句通过注解的方式添加到接口的方法上

1.新建一个cart表

8558467.jpg

2.编写对应的CartBean

@Data
public class Cart {
    Integer id;
    String cartName;
    Double cartPrice;
}

3.添加mapper接口映射

直接将查询SQL通过@Select注解放在方法上,省略mapper.xml 文件的编写

@Mapper
public interface CartMapper {
    @Select("select * from cart where id = #{id}")
    public Cart getCartById(Integer id);
}

4.添加service使用CartMapper接口返回Cart

@Service
public class CartService {
    @Autowired
    CartMapper cartMapper;

    public Cart getCartById(Integer id){
        return cartMapper.getCartById(id);
    }
}

5.编写CartController,添加get请求

@RestController
public class CartController {
    @Autowired
    CartService cartService;

    @GetMapping("getCartById")
    public Cart getCartById(@RequestParam("id") Integer id){
        return cartService.getCartById(id);
    }
}

6.启动项目,浏览器访问 http://localhost:8080/getCartById?id=1 查看效果

8558467.jpg

混合版

当有写SQL语句过长不能很好的在注解中使用时,可以使用混合方式,将SQL继续写在mapper文件中

例如往Cart表中添加数据

在Mapper接口中添加addCart方法

@Mapper
public interface CartMapper {
    /**
     * 查询数据
     * @param id
     * @return
     */
    @Select("select * from cart where id = #{id}")
    Cart getCartById(Integer id);

    /**
     * 添加Cart数据
     * @param cart
     * @return
     */
    Integer addCart(Cart cart);
}

新建CartMapper.xml接口映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.szx.boot04webadmin.mapper.CartMapper">
    <!--Integer addCart(Cart cart);-->
    
    <insert id="addCart" useGeneratedKeys="true" keyProperty="id">
        insert into cart values(null,#{cartName},#{cartPrice})
    </insert>

</mapper>

在service中调用接口

package com.szx.boot04webadmin.server;

import com.szx.boot04webadmin.bean.Cart;
import com.szx.boot04webadmin.mapper.CartMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author songzhengxiang
 * @create 2022-07-30 11:13
 */
@Service
public class CartService {
    @Autowired
    CartMapper cartMapper;

    public Cart getCartById(Integer id){
        return cartMapper.getCartById(id);
    }

    public Integer addCart(Cart cart){
        return cartMapper.addCart(cart);
    }

}

在controller中增加post请求

@PostMapping("cart")
public Integer addCart(@RequestBody Cart cart){
    cartService.addCart(cart);
    return cart.getId();
}

启动服务,发送post请求

8558467.jpg

注解属性配置

在xml中定义的 useGeneratedKeys,keyProperty 等属性也可以通过注解的方式来实现

@Insert("insert into cart values(null,#{cartName},#{cartPrice})")
@Options(useGeneratedKeys = true,keyProperty = "id")
Integer addCart(Cart cart);

整合mybatis-plus

github地址:https://github.com/baomidou/mybatis-plus

中文文档地址:https://baomidou.com/

idea插件安装

搜索mybatisx安装即可

8558467.jpg

快速上手

引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

引入依赖后,mybatis-plus自动帮我们完成如配置

  • SqlSessionFactory自动配置好。底层是使用容器中默认的数据源
  • mapperLocations自动配置好,有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件下任意路径的所有xml都是SQL映射文件,建议以后的SQL映射文件都放在mapper文件夹下
  • 容器中也自动配置好的SqlSessionTemplate
  • @Mapper 标注的接口也会被自动配置扫描;建议直接@MapperScan(“com.xxx.xxx.mapper”)批量扫描

优点:

  • 只需要我们的mapper继承BaseMapper就可以拥有crud的能力

首先新建一张表,并插入原始数据

DROP TABLE IF EXISTS plus_user;

CREATE TABLE plus_user
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

DELETE FROM plus_user;

INSERT INTO plus_user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

然后新建plususerBean

@Data
public class PlusUser {
    Integer id;
    String name;
    Integer age;
    String email;
}

然后新建PlusUserMapper,继承BaseMapper后会有一些基本的增删改查方法可以直接使用

/**
 * @author songzhengxiang
 * @create 2022-07-30 14:34
 * 继承BaseMapper,添加泛型类型,指明我们要操作的Bean
 */
@Mapper
public interface PlusUserMapper extends BaseMapper<PlusUser> {

}

接着新建PlusUserService,使用selectById根据id查询数据

@Service
public class PlusUserService {
    @Autowired
    PlusUserMapper plusUserMapper;

    public PlusUser getUserInfo(Integer id){
        PlusUser plusUser = plusUserMapper.selectById(id);
        System.out.println(plusUser);
        return plusUser;
    }
}

新建 PlusUserController 新建get请求

@RestController
public class PlusUserController {
    @Autowired
    PlusUserService plusUserService;

    @GetMapping("/plususer")
    public PlusUser getPlusUser(@RequestParam("id") Integer id){
        return plusUserService.getUserInfo(id);
    }
}

启动项目,浏览器访问http://localhost:8080/plususer?id=1查看返回的数据

8558467.jpg

@TableName指定数据库名称

mybatis-plus默认回去数据库中找和类名相同的数据表,另外我们也可以指定数据表名称,使用@TableName来指定表名

@Data
@TableName("plus_user")
public class PlusUser {
    Integer id;
    String name;
    Integer age;
    String email;
}

分页显示数据

开始之前需要添加分页插件,在容器中添加 MybatisPlusInterceptor 类

/**
 * @author songzhengxiang
 * @create 2022-07-30 15:40
 */
@Configuration
public class MyBatisConfig {
    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 指明数据库类型,避免查询之前都去查询一遍数据库类型,从而提高查询效率
        PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        // 设置最大单页的数据限制,500表示一页最多返回500条数据,-1表示不限制
        innerInterceptor.setMaxLimit(500L);
        interceptor.addInnerInterceptor(innerInterceptor);
        return interceptor;
    }
}

正常情况下,我们会先封装一个接口,然后通过service调用接口中的方法。所以接下来我们来规范一下代码的调用逻辑

1.新建一个PlusUserDao接口,让这个接口继承MybatisPlus中的IService接口,IService接口是带泛型的,传递过去我们定义的PlusUser

/**
 * 定义一个接口继承IService,添加一个泛型,声明我们要操作那个Bean
 * @author songzhengxiang
 * @create 2022-07-30 15:07
 */
public interface PlusUserDao extends IService<PlusUser>{
}

2.新建PlusUserImpl的接口实现类,同时继承MybatisPlus的ServiceImpl接口实现类

/**
 * 继承ServiceImpl,并实现PlusUserDao接口
 * ServiceImpl类有两个泛型,第一个是定义的mapper,第二个是我们要操作的bean
 * @author songzhengxiang
 * @create 2022-07-30 15:10
 */
@Service
public class PlusUserImpl extends ServiceImpl<PlusUserMapper, PlusUser> implements PlusUserDao {
}

这样操作继承完后,我们就拥有了操作plus_user这张表的一些基本增删改查方法

3.新建一个Controller,来分页获取表中的数据

/**
 * 分页获取plus_user表的数据
 */
@GetMapping("/plususer/{pageNum}/{pageSize}")
public Page getPlusUserByPage(@PathVariable("pageNum") Integer pageNum,@PathVariable("pageSize") Integer pageSize) {
    // 构造分页函数
    Page<PlusUser> page = new Page<>(pageNum, pageSize);
    // 调用page进行分页
    Page<PlusUser> userPage = plusUserImpl.page(page,null);
    return userPage;
}

启动项目,浏览器访问 http://localhost:8080/plususer/1/2 查看返回的数据

8558467.jpg

删除用户

public Map deletePlusUser(@RequestBody Map map){
    Integer id = (Integer) map.get("id");
    boolean b = plusUserImpl.removeById(id);
    HashMap<String, Object> hashMap = new HashMap<>();
    hashMap.put("code",200);
    hashMap.put("success",b);
    hashMap.put("msg",b ? "删除成功" : "删除失败");
    return hashMap;
}

发送请求,删除id为1的数据

8558467.jpg

查看数据库中的剩余数据,发现id为1的数据已经被成功删除了

8558467.jpg

整合redis

快速上手

导入依赖

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

在配置文件中添加redis访问地址

spring:
    redis:
        url: redis://localhost:6379 # redis 本地连接地址

编写测试方法,通过键值对的方式往redis中存储一个数据,然后读取并返回

@RestController
public class RedisController {
    @Autowired
    StringRedisTemplate redisTemplate;

    @GetMapping("/getredisval")
    public String getRedisVal(){
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        opsForValue.set("hello","word");
        return opsForValue.get("hello");
    }
}

启动项目,浏览器访问 http://localhost:8080/getredisval 查看返回

8558467.jpg

打开redis客户端(Another Redis Desktop Manager),查看保存的数据

8558467.jpg

小案例,统计接口访问次数

首先添加拦截器

package com.szx.boot04webadmin.interceptor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author songzhengxiang
 * @create 2022-07-30 21:53
 */
@Component
public class Redisinlnterceptor implements HandlerInterceptor {
    @Autowired
    StringRedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取本次的请求地址
        String uri = request.getRequestURI();

        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        // 设置这个key自动递增
        opsForValue.increment(uri);

        // 默认全部放行
        return true;
    }
}

然后在 webConfig 中添加新定义的拦截器

/**
 * 拦截器,实现 WebMvcConfigurer 接口
 * @author songzx
 * @create 2022-07-22 14:39
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    Redisinlnterceptor redisinlnterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/","/login","/css/**","/js/**","/fonts/**","/images/**");

        // 添加定义的redis路径拦截器
        registry.addInterceptor(redisinlnterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/","/login","/css/**","/js/**","/fonts/**","/images/**");
    }
}

/main.html 接口中从redis获取请求的接口次数,并添加到model中

// 前提是自动注入redisTemplate
@Autowired
StringRedisTemplate redisTemplate;

@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
    ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
    String mainCount = opsForValue.get("/main.html");
    String sqlCount = opsForValue.get("/druidsql");

    model.addAttribute("mainCount",mainCount);
    model.addAttribute("sqlCount",sqlCount);

    return "main";
}

然后在 main.html 页面中通过 thymeleaf 将数据渲染出来

<div class="state-value">
    <div class="value" th:text="${mainCount}">230</div>
    <div class="title">mainCount</div>
</div>

<div class="state-value">
    <div class="value" th:text="${sqlCount}">3490</div>
    <div class="title">sqlCount</div>
</div>

启动项目,我们来分别访问几次首页和/druidsql,查看页面数据变化

8558467.jpg

通过redis客户端也可以看到数据情况

8558467.jpg

单元测试

常用测试方法

  • @Test 表示方法是测试方法
  • @DisplayName 为测试类或者测试方法添加一个名称,在方法执行后可以看到自定义的方法名称。方便查找
  • @BeforeEach 表示每个测试方法开始之前执行
  • @AfterEach 表示每个测试方法结束后执行
  • @BeforeAll 表示所有测试方法开始之前执行,必须是一个静态方法
  • @AfterAll 表示所有测试方法结束后执行,必须是一个静态方法
  • @RepeatedTest 表示可以重复执行的方法,接收一个参数表示重复执行的次数
  • @Disabled 表示这个测试方法已失效
  • @Timeout(500) 表示这个方法如果运行时间超过500毫秒就抛出异常,要设置unit表示时间类型,MILLISECONDS表示毫秒
@Slf4j
// 添加@SpringBootTset注解表示这个是springboot中整合的注解
@SpringBootTest
// 为测试类或者测试方法设置展示的名称
@DisplayName("测试类001")
public class MySpringBootTset {

    // 只要我们使用了@SpringBootTest注解,就可以获取容器中的相关类
    @Autowired
    JdbcTemplate jdbcTemplate;

    // 普通@Test注解
    @Test
    @DisplayName("测试方法:test001")
    public void test1(){
        System.out.println(1);
        System.out.println(jdbcTemplate);
    }

    // @BeforeEach 表示每个测试方法开始之前执行
    @BeforeEach
    public void testBeforeEach(){
        System.out.println("测试方法开始执行");
    }

    // @AfterEach 表示每个测试方法结束后执行
    @AfterEach
    public void testAfterEach(){
        System.out.println("测试方法结束后执行");
    }

    // @BeforeAll 表示所有测试方法开始之前执行,必须是一个静态方法
    @BeforeAll
    public static void testBeforeAll(){
        System.out.println("所有测试方法开始之前");
    }

    // @AfterAll 表示所有测试方法结束后执行,必须是一个静态方法
    @AfterAll
    public static void testAfterAll(){
        System.out.println("所有测试方法结束后执行");
    }

    // RepeatedTest 表示可以重复执行的方法,接收一个参数表示重复执行的次数
    @RepeatedTest(5)
    public void testRepeatedTest(){
        System.out.println("重复执行的测试方法");
    }

    // @Disabled 表示这个测试方法已失效
    @Disabled
    @Test
    public void testDisabled(){
        System.out.println("这个方法被失效");
    }

    // @Timeout(500) 表示这个方法如果运行时间超过500毫秒就抛出异常
    // 要设置unit表示时间类型,MILLISECONDS表示毫秒
    @Timeout(value = 500,unit = TimeUnit.MILLISECONDS)
    @Test
    public void testTimeout() throws InterruptedException {
        Thread.sleep(501);
        System.out.println("延迟输出"); //=> testTimeout() timed out after 500 milliseconds
    }

}

断言测试

方法说明
assertEquals判断两个对象或者原始类型是否相等
assertNotEquals判断两个对象或者原始类型是否不相等
assertSame判断两个对象指向的是否是同一个地址
assertNotSame判断两个对象指向的不是同一个地址
assertTrue判断给定的运算是否返回true
assertFalse判断给定的运算是否返回true
assertNull判断结果是否返回null
assertNotNull判断结果不是null

代码示例

package com.szx.boot04webadmin;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;

/**
 * 断言测试
 * @author songzhengxiang
 * @create 2022-08-01 22:48
 */
@SpringBootTest
public class AssertionTest {
    int cat(int a,int b){
        return a + b;
    }

    @DisplayName("assertEquals判断两个对象或者原始类型是否相等")
    @Test
    public void testAsserEquals(){
        Assertions.assertEquals(5,cat(3,3),"结果不是5");
    }

    @DisplayName("assertNotEquals判断两个对象或者原始类型是否不相等")
    @Test
    public void testAssertNotEquals(){
        Assertions.assertNotEquals(5,cat(3,3),"结果竟然是5?");
    }

    @DisplayName("assertSame判断两个对象指向的是否是同一个地址")
    @Test
    public void testAssertSame(){
        Assertions.assertSame(new Date(),new Date(),"两个对象指向的不是一个地址");
    }

    @DisplayName("assertNotSame判断两个对象指向的不是同一个地址")
    @Test
    public void testAssertNotSame(){
        Assertions.assertNotSame(new Date(),new Date(),"两个对象指向的竟然是是一个地址");
    }

    @DisplayName("assertTrue判断给定的运算是否返回true")
    @Test
    public void testArrestTrue(){
        Assertions.assertTrue(1>5,"结果不是true");
    }

    @DisplayName("assertFalse判断给定的运算是否返回true")
    @Test
    public void testAssertFalse(){
        Assertions.assertFalse(1>5,"结果不是false");
    }

    @DisplayName("assertNull判断结果是否返回null")
    @Test
    public void testAssertNull(){
        Assertions.assertNull(null,"结果不是null");
    }

    @DisplayName("assertNotNull判断结果不是null")
    @Test
    public void testAssertNotNull(){
        Assertions.assertNotNull(null,"结果是null");
    }
}

前置条件 assumptions

Assumptions.assumeTrue 前置条件判断,如果前置条件不成功,则代码不会继续往下执行

@SpringBootTest
public class TestAssumptions {

    @Test
    void testassumption(){
        Assumptions.assumeTrue(false,"结果返回的不是true");
        System.out.println("如果上面的前置条件没有通过,则不会继续往下执行");
    }
}

参数化测试

首先在方法上标注 @ParameterizedTest 注解表示这是一个参数化测试方法

利用 @ValueSource 等注解,指定参数,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就要新增一个测试方法,省去了很多的冗余代码

注解名称含义
@ValueSource为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource表示为参数化测试方法提供一个null入参
@EnumSource提供一个枚举入参
@CsvFileSource从指定的csv文件中读取内容获取参数作为入参
@MethodSource读取指定的方法的返回值作为入参,注意必须返回一个流,并且是静态方法
@ParameterizedTest
@DisplayName("参数化测试")
@ValueSource(ints = {1,2,3})
void testValueSource(int i){
    System.out.println(i);
}

@ParameterizedTest
@DisplayName("参数化测试")
@MethodSource("getSystem")
void testValueSource2(String str){
    System.out.println(str);
}

指标监控

快速上手

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

导入依赖

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

添加配置文件

management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露

然后可以通过访问如下页面来查看各项监控信息

http://localhost:8080/actuator/configprops

http://localhost:8080/actuator/metrics

http://localhost:8080/actuator/metrics/jvm.gc.pause

Boot Admin Serve

我们可以使用这个框架快速搭建一个可视化的监控面板,github 地址 https://github.com/codecentric/spring-boot-admin

首先新建一个 java web 项目

然后引入依赖

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.5.1</version>
</dependency>

然后将 @EnableAdminServer 注解添加到启动类上

@SpringBootApplication
@EnableAdminServer
public class Boot05AdminServerApplication {

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

}

注册客户端应用程序,将这个依赖添加到要监控的项目中

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.5.1</version>
</dependency>

然后再客户端项目中添加如下配置

spring:
    boot:
      admin:
        client:
          url: http://localhost:8888 # 表示要把我们的监控信息推送给那个地址去显示
          instance:
            prefer-ip: true
    application:
      name: boot04-webadmin

然后分别启动要监控的项目和监控面板项目

这是在浏览器访问 http://localhost:8888/ 就可以查看我们项目的各项信息

8558467.jpg8558467.jpg

profiles的使用

环境切换

在实际开发场景中,我们会有多套环境,例如在本地测试环境连接测试库,生产环境则使用生产库。我们可以使用 profiles 来快速的切换不同环境

首先分别新建如下两个文件

  • application-dev.yml
  • application-uat.yml

写入如下内容

# application-dev.yml
product:
  name: dev-张三
# application-uat.yml
product:
  name: uat-李四

我们可以在 application 配置文件上通过 application-xxx 的方式自定义环境配置

然后再主配置文件中声明要使用的环境名称,没有声明环境名称的配置文件就是主配置文件,主配置文件在任何环境下都会生效

server.port=8888
# 声明当前的环境名称
spring.profiles.active=uat 

编写一个 ProductController

@RestController
public class Product {

    @Value("${product.name}")
    String name;

    @GetMapping("/product")
    public String getName(){
        return name;
    }
}

启动项目,访问 http://localhost:8888/product 查看接口返回

在uat环境下,使用的是uat配置文件

8558467.jpg

然后切换主配置文件中的环境名称

# 声明当前的环境名称
spring.profiles.active=dev 

重启项目再次访问,返回值会不同

8558467.jpg

@Profile注解

首先添加一个user接口

public interface user {
    void setName();
    void setAge();
}

然后编写两个类来实现这个接口

Boos

@Profile("uat")
@ConfigurationProperties(prefix = "product")
@Component
@Data
public class Boos implements user{
    String name;
    Integer age;

    @Override
    public void setName() {

    }

    @Override
    public void setAge() {

    }
}

Student

@Data
@Component
@ConfigurationProperties(prefix = "product")
@Profile("dev")
public class Student implements user{
    private String name;
    private Integer age;

    @Override
    public void setName() {

    }

    @Override
    public void setAge() {

    }
}

修改 Product

@RestController
public class Product {

    @Autowired
    user userimpl;

    @GetMapping("/product")
    public user getName(){
        return userimpl;
    }
}

我们可以根据不同的环境,来实现一个接口返回不同的user实现类

8558467.jpg

8558467.jpg

配置环境组

我们可以让多个环境同时生效

首先新建一个 application-test.yml 文件

product:
  weight: 59kg

然后在主配置文件中添加环境组

# 设置环境组,可以让多个环境同时生效
spring.profiles.group.mygroup[0]=dev
spring.profiles.group.mygroup[1]=test

# 指定让那个配置组生效
spring.profiles.active=mygroup

在 Student 中添加 weight 字段

@Data
@Component
@ConfigurationProperties(prefix = "product")
@Profile("dev")
public class Student implements user{
    private String name;
    private Integer age;
    private String weight;

    @Override
    public void setName() {

    }

    @Override
    public void setAge() {

    }
}

启动项目,查看返回。通过结果可以看出,dev 和 test 两个环境都生效

8558467.jpg

配置文件的优先级

  • 首先会加载主配置文件,然后加载声明环境的配置文件
  • 当配置重复时,指定环境的配置文件优先级高,会覆盖主配置文件的设置

配置文件的查找位置

  • classpath 根目录
  • classpath 根目录下的config 目录
  • jar 包当前目录
  • jar 包当前目录的config目录
  • /config 子目录的直接子目录(仅针对与linux系统)

配置文件的加载顺序

优先级从低到高:

  1. 当前jar包内部的 application 和 application.yml
  2. 当前jar包内部的 application-(profile).properties 和 application-(profile).yml
  3. 引用的外部jar包的 application.properties 和 application.yml
  4. 引用的外部jar包的 application-(profile).properties 和 application-(profile).yml

总结:指定环境优先,外部优先,后面的可以覆盖前面的同名配置项目

自定义start

首先新建两个Module

  • szx-hello-spring-boot-start
  • szx-hello-spring-boot-start-autoconfigure

8558467.jpg

在szx-hello-spring-boot-start中引入szx-hello-spring-boot-start-autoconfigure

8558467.jpg

然后在szx-hello-spring-boot-start-autoconfigure中新建service.HelloService,默认不要放在容器中

package com.example.szxhellospringbootstartautoconfigure.service;

import com.example.szxhellospringbootstartautoconfigure.bean.HelloPropretes;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @author songzx
 * @create 2022-08-15 15:20
 */
public class HelloService {
    @Autowired
    HelloPropretes helloPropretes;

    public String sayHello(String name){
        return helloPropretes.getPrefix() + ": " + name + " >" + helloPropretes.getSuffix();
    }
}

这里用到了 helloPropretes,新建bean.helloPropretes

package com.example.szxhellospringbootstartautoconfigure.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author songzx
 * @create 2022-08-15 15:30
 */
@ConfigurationProperties("hello")
public class HelloPropretes {
    String prefix;
    String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

然后新建 auto.HelloAutoConfiguration

package com.example.szxhellospringbootstartautoconfigure.auto;

import com.example.szxhellospringbootstartautoconfigure.bean.HelloPropretes;
import com.example.szxhellospringbootstartautoconfigure.service.HelloService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author songzx
 * @create 2022-08-15 15:36
 */
@Configuration
@EnableConfigurationProperties(HelloPropretes.class)
public class HelloAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(HelloService.class)
    public HelloService helloService(){
        HelloService helloService = new HelloService();
        return helloService;
    }
}

接着在 resources 文件夹下新建 META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.szxhellospringbootstartautoconfigure.auto.HelloAutoConfiguration

szx-hello-spring-boot-start-autoconfigure 的目录结构如下

8558467.jpg

然后将两个项目安装到本地,分别执行一下 clean,install

8558467.jpg

然后新建一个测试项目 boot07-customer-start-test,引入 szx-hello-spring-boot-start

8558467.jpg

新建 controller.HelloController

package com.example.boot07customerstarttest.controller;

import com.example.szxhellospringbootstartautoconfigure.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author songzx
 * @create 2022-08-15 15:55
 */
@RestController
public class HelloController {

    @Autowired
    HelloService helloService;

    @GetMapping("/hello")
    public String sayHello(){
       return helloService.sayHello("张三");
    }
}

然后再配置文件中声明属性

8558467.jpg

启动项目,访问 http://localhost:8088/hello,结果可以看到成功引入

8558467.jpg

如果我们在容器中注册了一个 HelloService,则不会再使用引入的HelloService

新建 config.HelloConfig

package com.example.boot07customerstarttest.config;


import com.example.szxhellospringbootstartautoconfigure.service.HelloService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author songzx
 * @create 2022-08-15 16:07
 */
@Configuration
public class HelloConfig {

    @Bean
    public HelloService helloService(){
        System.out.println("进入自己的HelloService");
        return new HelloService();
    }
}

重启项目,观察控制台打印

8558467.jpg

至此,我们自定义 start 就完成了。

实战案例

文件上传到腾讯云COS

官方文档:https://cloud.tencent.com/document/product/436/10199

首先导入依赖

<!--腾讯云cos相关依赖-->
<dependency>
    <groupId>com.qcloud</groupId>
    <artifactId>cos_api</artifactId>
    <version>5.6.89</version>
</dependency>
<!--接收文件-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

创建一个 FileServer 类,处理上传逻辑

package com.szx.boot03tengxunyun.server;

import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import com.szx.boot03tengxunyun.bean.CosBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * @author songzx
 * @create 2022-07-19 11:28
 */
@Service
public class FileServer {
    // 1 初始化用户身份信息(secretId, secretKey)。
    // SECRETID和SECRETKEY请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理
    String secretId = "";
    String secretKey = "";
    String filePath = "https://blogimages-1257342648.cos.ap-shanghai.myqcloud.com/"; // 文件基础路径
    String bucketName = "blogimages-1257342648"; // 指定文件将要存放的存储桶

    COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
    // 2 设置 bucket 的地域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
    // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
    Region region = new Region("ap-shanghai"); // ap-shanghai 表示上海
    ClientConfig clientConfig = new ClientConfig(region);

    public String upload(MultipartFile multipartFile) throws IOException {
        String filename = multipartFile.getOriginalFilename();
        // 这里建议设置使用 https 协议
        // 从 5.6.54 版本开始,默认使用了 https
        clientConfig.setHttpProtocol(HttpProtocol.https);
        // 3 生成 cos 客户端。
        COSClient cosClient = new COSClient(cred, clientConfig);

        //这里文件名用了当前时间 防止重复,可以根据自己的需要来写
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String[] fileNames= filename.split("\\.");
        String name = fileNames[0]+ sdf.format(new Date())+ filename.substring(filename.lastIndexOf("."), filename.length());
        // 指定文件上传到 COS 上的路径,即对象键。例如对象键为folder/picture.jpg,则表示将文件 picture.jpg 上传到 folder 路径下
        String key = "javaUpload/" + name;
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, multipartFile.getInputStream(),null);
        // 执行上传方法
        cosClient.putObject(putObjectRequest);
        // 返回线上地址
        return filePath + key;
    }
}

封装一个 msg 类,用来返回上传成功返回的数据信息

package com.szx.boot03tengxunyun.bean;

import java.util.HashMap;

/**
 * @author songzx
 * @create 2022-07-19 15:36
 */
public class Msg {
    int code; // 接口响应状态码,500 异常,200 OK
    String message; // 接口返回的信息
    HashMap<String,Object> data = new HashMap<>(); // 接口实际返回的内容

    /**
     * 接口成功返回方法
     * @author Songzx
     * @date 2022/7/2
     */
    public static Msg success(){
        Msg msg = new Msg();
        msg.setCode(200);
        msg.setMessage("成功");
        return msg;
    }

    /**
     * 接口失败返回方法
     * @author Songzx
     * @date 2022/7/2
     */
    public static Msg error(){
        Msg msg = new Msg();
        msg.setCode(500);
        msg.setMessage("失败");
        return msg;
    }

    /**
     * 可以链式调用的add方法
     * @author Songzx
     * @date 2022/7/2
     */
    public Msg add(String key,Object data){
        this.getData().put(key,data);
        return this;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public HashMap<String, Object> getData() {
        return data;
    }

    public void setData(HashMap<String, Object> data) {
        this.data = data;
    }

    public Msg(int code, String message, HashMap<String, Object> data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public Msg() {
    }
}

编写一个 FileController,调用 FileServer 中的 upload 方法

@RestController
public class FileController {
    @Autowired
    FileServer fileServer;

    @RequestMapping(value = "/upload",method = RequestMethod.POST)
    // 这里接收到的 file 名称是前端传递过来的文件参数名称
    public Msg uploadFile(@RequestParam("file") MultipartFile multipartFile){
        try {
            String filePath = fileServer.upload(multipartFile);
            return Msg.success().add("fileUrl",filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Msg.error();
    }
}

然后前端通过 post 方式调用 /upload 接口,同时传递文件过来。前端代码如下,使用了 ElementUI 中的文件上传组件

<template>
  <div>
    <el-upload class="upload-demo" drag action="/upload" multiple>
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    </el-upload>
  </div>
</template>

运行效果:

Snipaste_2022-07-19_16-04-15.png

登录腾讯云控制台,访问对象存储模块,查看 javaUpload 文件夹下的内容

Snipaste_2022-07-19_16-09-11.png

可以看到文件成功上传到云端

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值