SpringBoot聚合项目:达内知道(七)-声明式事务、统一异常处理、文件上传

1.声明式事务

1.1 新增问题的程序漏洞

  如果在新增问题的业务逻辑层运行过程中发生了异常,就会导致问题可能新增完毕,但是问题和关系(标签、讲师)新增失败的情况,这样就可能出现一个问题没有关联任何标签,或者没有关联任何讲师的情况。这种情况是不好的,在企业开发过程中,必须避免这种情况的发生。因为这样会引发数据安全性问题,可能导致程序运行过程中发生异常,要想避免这个问题就需要使用到事务。

1.2 什么是事务?

事务一般指:"数据库事务"

定义:是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

常见面试题

数据库事务的四个特性,简称ACID特性

  • 原子性(Atomicity):事务作为一个整体被执行,事务中的操作要么都成功,要么都失败(要执行都执行,要不执行都不执行);

  • 一致性(Consistency):事务运行前后,一致状态保持不变,例如:转账前后银行中的总金额不变;

  • 隔离性(Isolation):数据库支持多个事务并发执行,互不干扰;

  • 持久性(Durability):已经提交的事务对数据库的影响是持久的,不可临时退回。

1.3 Spring Boot 声明式事务

  Spring Boot框架中整合了Spring事务的管理功能,允许在业务逻辑层方法上添加事务管理的注解,一旦添加该注解,此方法中的所有数据库操作被管理为一个事务,即要么都执行,要么都不执行,就能解决上面的问题了。在这个方法运行过程中,如果没有发生异常,事务中数据库的操作就会正常提交生效;如果发生了异常,该方法中所有数据库操作全部取消,回滚(恢复)为运行之前的状态。

在实现类QuestionServiceImpl的saveQuestion方法上添加@Transactional注解:

 //新增用户发布的问题
 @Override
 //开启这个方法的事务管理,效果是该方法中所有数据库操作管理为一个事务
 // 如果运行正常,则所有操作生效;如果发生异常;则回滚为运行前状态
 @Transactional
 public void saveQuestion(QuestionVo questionVo, String username) {
  //代码略....
 }

为了安全起见,可以在前面接收注册表单数据的vo类RegisterVo上也加上事务管理的注解。

 

2.统一异常处理

2.1 为什么需要统一异常处理?

  在注册和发布问题的控制层代码中,需要对业务逻辑层进行异常处理,这样的异常处理在以后的功能中会不断出现,这样异常处理的代码就会形成冗余,代码显得臃肿,不方便维护。

2.2 Spring Mvc的统一异常处理

  由于开发过程中大多是由控制器承担异常处理的,所以SpringMvc框架提供了一套异常处理的机制,能够自动处理控制层中发生的各种异常,在控制层代码中就不需要编写try-catch了。我们只需要根据SpringMvc提供的格式编写一个类即可,在controller包中创建类ExceptionControllerAdvice,代码如下:

 package cn.tedu.knows.portal.controller;
 
 import cn.tedu.knows.portal.exception.ServiceException;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 //@RestControllerAdvice:表示当前类是给其他控制器类新增功能的
 //我们这里的功能指异常处理
 @RestControllerAdvice
 @Slf4j //输出日志显示异常信息
 public class ExceptionControllerAdvice {
     //当前类的目标是统一处理控制器中的各种异常
     //控制器中无需再编写任何try-catch结构,减少代码冗余,专心处理业务
     @ExceptionHandler //表示是处理异常的方法
     public String handlerServiceException(ServiceException e){
         //这个方法来处理控制器方法发生ServiceException类型异常(自动运行)
         log.error("业务异常",e);//不需要写{}
         return e.getMessage();
    }
 
     @ExceptionHandler //此处相当于多重catch捕获异常
     public String handlerException(Exception e){
         log.error("其它异常",e);
         return e.getMessage();
    }
 }
 

统一异常处理类运行流程:

 

3.文件上传

3.1 什么是文件上传?

  即客户端将文件复制到服务器的过程,我们在static文件夹下创建一个可以上传文件的页面如下:upload.html

 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>文件上载演示</title>
 </head>
 <body>
     <form id="demoForm" method="post"
           enctype="multipart/form-data"
           action="/upload/file" >
         <div>
             <label>上传文件
                 <input id="imageFile" type="file" name="imageFile">
             </label>
         </div>
         <button type="submit">上传文件</button>
     </form>
     <img id="image" src=""  alt="">
 </body>
 </html>

3.2 实现简单的同步上传

在SystemController类中添加如下方法:

 @PostMapping("/upload/file")
 //MultipartFile表示要上传文件
 public String uploadFile(MultipartFile imageFile)
                                     throws IOException {
     //确定上传的文件夹,该路径在电脑中一定要存在
     File folder=new File("F:/upload");
     //创建这个目录
     folder.mkdirs();//mkdirsssssssss!!!!!可以创建多级目录
     //获得用户上传的文件名(原始)
     String fileName=imageFile.getOriginalFilename();
     log.debug("获得的文件名为:{}",fileName);//类上添加了@Slf4j注解
     //创建文件,代表要保存的文件路径和名称
     //F:/upload/a.jpg
     File file=new File(folder,fileName);//folder表示路径,fileName表示文件名,调用File的构造方法进行拼接出完整路径:路径/文件名。
     log.debug("复制到路径:{}",file.getAbsolutePath());//file类型:文件路径,String类型:文件名,联合起来就是一个完整路径
     //执行上传
     imageFile.transferTo(file);
     return "upload success!";
 }

启动服务,访问路径:http://localhost:8888/upload.html,按照学生身份进行登录,登录后显示效果如下:

选择文件上传后效果如下:

(1)浏览器

(2)控制台

3.3 异步的文件上传

修改upload.html代码如下:

 <!DOCTYPE html>
 <html lang="en">
 <head>
    <meta charset="UTF-8">
    <title>文件上载演示</title>
    <script src="bower_components/jquery/dist/jquery.min.js"></script>
    <!--引入axios框架-->
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
 </head>
 <body>
 <form id="demoForm">
    <div>
        <label>上传文件
            <input id="imageFile" type="file" name="imageFile">
        </label>
    </div>
    <button type="submit">上传文件</button>
 </form>
 <img id="image" src="" alt="">
 <script src="js/utils.js"></script>
 <script>
 $("#demoForm").submit(function () {
    console.log("上传文件方法运行");
    return false;//阻止表单提交,测试时点击提交按钮后无反应,不跳转页面
 })
 </script>
 </body>
 </html>

  启动服务进行测试,登录后,访问http://localhost:8888/upload.html,上传图像文件,上传时页面无跳转,打开F12控制台输出“上传文件方法运行”,表示上传文件方法运行了。

注意:点击提交后页面跳转了,或者浏览器控制台没有输出都不行。

在上面的基础上继续编写代码,进行异步表单提交数据:

 <!DOCTYPE html>
 <html lang="en">
 <head>
    <meta charset="UTF-8">
    <title>文件上载演示</title>
    <!--添加引用-->
    <script src="bower_components/jquery/dist/jquery.min.js"></script>
    <!--引入axios框架-->
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
 </head>
 <body>
 <form id="demoForm">
    <div>
        <label>上传文件
            <input id="imageFile" type="file" name="imageFile"><!--对应下面上传文件的名字-->
        </label>
    </div>
    <button type="submit">上传文件</button>
 </form>
 <img id="image" src="" alt="">
 <!--引入js文件-->
 <script src="js/utils.js"></script>
 <script>
 //提交文件的方法
 $("#demoForm").submit(function () {
    console.log("上传文件方法运行");
    //根据用户设置的id获得用户选中的文件(数组),files表示文件域,代表所有文件
    //可以一次选多个文件,但是现在还未设置,每次只能选中一个文件,故上传文件返回值类型files是数组
    let files = document.getElementById("imageFile").files;//获取选中所有标签赋值到files中
    //判断是否得到了选择的文件
    if(files.length>0){
        //对用户选中的文件执行上传,单独编写uploadImage方法执行上传
        uploadImage(files[0]);//每次只能上传数组中的第一张图片
    }else{
        //没有得到文件,提示上传
        alert("请选择文件");
    }
    //阻止表单提交
    return false;
 })
 
 //上传文件的方法
 function uploadImage(file) {
    //上面阻止了表单提交,此处构建新的表单
    let form = new FormData();
    //在表单中添加文件
    form.append("imageFile",file);//两个参数分别对应文件名key和值value
    //利用axios异步提交数据
    axios({
        url:"/upload/image",
        method:"post", //上传文件要用post
        data:form //提交的内容不要加上“ ”,否则变成字符串
    }).then(function (response) {
        if(response.data=="success"){
            console.log("上传成功!");
        }else{
            console.log("上传失败!");
        }
    })
 }
 </script>
 </body>
 </html>

先在application.properties配置文件中配置上传文件保存的路径,后续会调用:

 # 配置上传文件保存的路径
 knows.resource.path=file:C:/upload
 
 # 配置当前项目访问路径,后续子项目用
 knows.resource.host=http://localhost:8899

在SystemController控制器中新增一个方法用于异步上传数据:

  // 这个属性的值会从application.properties文件中获得
  // 获得的是knows.resource.path对应的值,注意此处是{}而不是()
  @Value("${knows.resource.path}")
  private File resourcePath;//File类型
  // 获得的是knows.resource.host对应的值
  @Value("${knows.resource.host}")
  private String resourceHost;//String类型
 
  //接收上传图片的表单数据
  @PostMapping("/upload/image")
  public String uploadImage(MultipartFile imageFile) throws IOException {//与form表单中imageFile对应
      //按日期创建文件夹的路径 LocalDatetime对应年月日时分秒 LocalDatetime对应年月日
      String path = DateTimeFormatter.ofPattern("yyyy/MM/dd")
                  .format(LocalDate.now());//年月日
      // path: 2021/08/31
      File folder=new File(resourcePath,path);//C:/upload, 2021/08/31,resourcePath已经在配置文件中配置,不用直接写绝对路径,路径可在配置文件中直接修改
      // folder: F:/upload/2021/08/31
      folder.mkdirs(); //mkdirsssssssssssssss!!!!!
      log.debug("上传的文件夹为:{}",folder.getAbsolutePath());
      //获得用户上传文件的原始后缀
      // a.gif     ->   uuid.gif
      // 01234
      //获取文件名
      String fileName=imageFile.getOriginalFilename();
      //截取文件后缀名
      String ext=fileName.substring(fileName.lastIndexOf("."));
      // ext: .gif
      //生成uuid确定文件名称
      String name= UUID.randomUUID().toString() + ext;
      //生成文件完整路径:name: jkha-sdjf-haj-sdhfj.gif
      File file=new File(folder,name);//C:/upload/2021/08/31,xxx.xxx
      // F:/upload/2021/08/31/jkha-sdjf-haj-sdhfj.gif
      log.debug("最终上传的路径:{}",file.getAbsolutePath());
      //执行上传
      imageFile.transferTo(file);
      return "success";
  }

  重启服务进行测试,访问路径为:http://localhost:8888/upload.html,登录后,进行上传图片,上传后没有反应,在浏览器上F12打开控制台,可以看到“上传成功!”,电脑磁盘指定文件夹内保存该图片,表示上传成功。

(1)浏览器

(2)IDEA

(3)电脑磁盘保存路径

如果报错,检查浏览器控制台和IDEA控制台报错信息,进行修改。

 

posted @ 2021-08-30 22:33  Coder_Cui  阅读(224)  评论(0编辑  收藏  举报