project(6)

续 学生问题发布功能

学生问题发布功能的收尾

控制层调用业务逻辑层

在上次课完成user(讲师)和问题关系的业务逻辑层代码之后

附:QuestionServiceImpl中saveQuestion方法的完整代码

@Autowired
    QuestionTagMapper questionTagMapper;

    @Autowired
    UserQuestionMapper userQuestionMapper;

    @Override
    public void saveQuestion(QuestionVo questionVo) {
        log.debug("收到问题数据{}",questionVo);
        // 获取当前登录用户信息(可以验证登录情况)
        String username=userService.currentUsername();
        User user=userMapper.findUserByUsername(username);
        // 将该问题包含的标签拼接成字符串以","分割 以便添加tag_names列
        StringBuilder bud=new StringBuilder();
        for(String tag : questionVo.getTagNames()){
            bud.append(tag).append(",");
        }
        //删除最后一个","
        bud.deleteCharAt(bud.length()-1);
        String tagNames=bud.toString();

        // 构造Question对象
        Question question=new Question()
                .setTitle(questionVo.getTitle())
                .setContent(questionVo.getContent())
                .setUserId(user.getId())
                .setUserNickName(user.getNickname())
                .setTagNames(tagNames)
                .setCreatetime(LocalDateTime.now())
                .setStatus(0)
                .setPageViews(0)
                .setPublicStatus(0)
                .setDeleteStatus(0);
        // 新增Question对象
        int num=questionMapper.insert(question);
        if(num!=1){
            throw  new ServiceException("服务器忙!");
        }
        log.debug("保存了对象:{}",question);
        // 处理新增的Question和对应Tag的关系
        Map<String,Tag> name2TagMap=tagService.getName2TagMap();
        for(String tagName : questionVo.getTagNames()){
            //根据本次循环的标签名称获得对应的标签对象
            Tag tag=name2TagMap.get(tagName);
            //构建QuestionTag实体类对象
            QuestionTag questionTag=new QuestionTag()
                    .setQuestionId(question.getId())
                    .setTagId(tag.getId());
            //执行新增
            num=questionTagMapper.insert(questionTag);
            if(num!=1){
                throw new ServiceException("数据库忙!");
            }
            log.debug("新增了问题和标签的关系:{}",questionTag);
        }

下面我们就要开始进行控制层的调用了

我们已经写好的控制层的基本代码

重构这个方法调用业务逻辑层方法即可

QuestionController代码如下

//学生发布问题的控制器方法
    @PostMapping
    public R createQuestion(
            @Validated QuestionVo questionVo,
            BindingResult result){
        if(result.hasErrors()){
            String message=result.getFieldError()
                    .getDefaultMessage();
            log.warn(message);
            return R.unproecsableEntity(message);
        }
        if(questionVo.getTagNames().length==0){
            log.warn("必须选择至少一个标签");
            return R.unproecsableEntity("必须选择至少一个标签");
        }
        if(questionVo.getTeacherNickNames().length==0){
            log.warn("必须选择至少一个老师");
            return R.unproecsableEntity("必须选择至少一个老师");
        }
        //这里应该将vo对象交由service层去新增
        log.debug("接收到表单数据{}",questionVo);
        try {
            questionService.saveQuestion(questionVo);
            return R.ok("发布成功!");
        }catch (ServiceException e){
            log.error("发布失败",e);
            return R.failed(e);
        }
    }

进行测试…

声明式事务

如果上面章节中新增过程中发生了异常

那么已经新增到数据库的数据不会删除,还没新增到数据库的数据就不会进入数据库了

就会造成数据的不完整

为了保证事务的完整性我们需要学习Spring的声明式事务

什么是事务

事务是数据库管理系统执行过程的一个最小逻辑单位

转账操作对数据库的影响分两步:

  1. 转出账户金额减少
  2. 转入账户金额增加

如果转出账户成功,转入账户失败,那么转出账户金额的减少操作应该撤销

即这两个操作要么都执行要么多不执行

事务的出现就是为了保证数据完整性的

面试常见题:

数据库事务拥有的四个特性

简称ACID

  1. 原子性(Atomicity):事务是执行数据库操作的最小逻辑,不可再分

  2. 一致性(Consistency):事务中对数据库操作的命令状态应该是一致的,

    ​ 即要么都执行要么都不执行

  3. 隔离性(Isolation):一个事务的执行,不影响其他事务

  4. 持久性(Durability):事务操作如果提交,多数据库的影响是持久的

Spring的声明式事务

SpringBoot提供了对事务的支持

相较于我们自己控制JDBC或Mybatis来实现事务,明显SpringBoot实现的方式更简单

只需要在Service层方法上加@Transactional即可

加上这个注解的效果就是:

这个方法对数据库的操作要么都成功,要么都失败

只要发生异常,在发生异常之前的数据库操作会自动撤销!

@Transactional
public void  saveXXX(){
   //...
}

使用Spring异常增强来统一异常处理

我们现在编写的代码

在控制器方法中多数都需要使用try-catch结构来处理异常

而这个异常的处理又不能省略,每个catch代码又都是相似的,造成了代码冗余

如果想解决上面的问题,就可以使用Spring提供的异常增强处理功能

来统一处理控制层方法的异常

处理原理

我们可以定义一个异常增强类

这个异常增强类可以声明为自动处理控制层发生的异常,这样我们就不必每个方法都处理了

在controller包中新建类ExceptionControllerAdvice

代码如下

//@RestControllerAdvice表示对控制器方法的异常增强处理
@RestControllerAdvice
@Slf4j
public class ExceptionControllerAdvice {

    //@ExceptionHandler表示这个方法时用来出处理异常的
    @ExceptionHandler
    public R handlerServiceException(ServiceException e){
        log.error("业务异常",e);
        return R.failed(e);
    }

    @ExceptionHandler
    public R handlerException(Exception e) {
        log.error("其它异常", e);
        return R.failed(e);
    }

}

说明

  1. @RestControllerAdvice表示对控制器方法的异常增强处理
  2. @ExceptionHandler表示这个方法是用来出处理异常的

控制器发生异常时,会自动匹配合适的异常类型,运行方法

在这里插入图片描述

文件上传(上载)

什么是文件上传

在Http协议的标准上

实现将客户端本地文件复制到服务器硬盘中的过程

http协议规定了一些上传文件时的标准

  1. 表单提交的方式必须是post

  2. 表单提交的编码方式必须修改为 multipart/form-data(二进制)

  3. 要上传的文件使用<input type=“file” name=“xxx”> 来表示

  4. HTTP请求头中必须包含 Content-type: multipart/form-data, boundary=AaB03x;

    4中的描述了解即可

  5. 允许上传多个文件

文件上传流程

在这里插入图片描述

编写页面代码

只是为了测试,所以我们编写在static文件夹中即可

upload.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post" enctype="multipart/form-data"
        action="/upload/file">
        <input type="file" name="imageFile"><br>
        <input type="submit">
    </form>
</body>
</html>

提交到SystemController控制器中的方法代码如下

需要保证f:盘中有upload文件夹

 //接收表单上传的文件
    @PostMapping("/upload/file")
    public R<String> upload(MultipartFile imageFile) throws IOException {

        //获得文件名
        String name=imageFile.getOriginalFilename();
        File f=new File("F:/upload/"+name);

        imageFile.transferTo(f);
        return R.ok("上载完成!");
    }

其中MultipartFile imageFile参数imageFile的名字必须和表单中file控件的name属性一致

getOriginalFilename获得原始文件名

transferTo将这个文件写入到指定的file对象中

Ajax上传文件

我们在实际的开发中,也是使用ajax提交的情况较多

所以我们需要学习ajax如何上传文件

重构upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="bower_components/jquery/dist/jquery.min.js"></script>
</head>
<body>
<form id="demoForm">
    <input type="file" id="imageFile" name="imageFile"><br>
    <input type="submit">
</form>
</body>
<script src="js/utils.js"></script>
<script>
    //在页面加载完毕之后运行!
    $(function () {
        //先绑定一个方法,当用户点击提交时,验证是否选中了图片
        $("#demoForm").submit(function(){
            //获得用户选择的文件(js是获得用户选择的文件数组)
            let files=document.getElementById("imageFile").files;
            //判断文件数组是不是长度>0
            if(files.length>0){
                //有文件,做上传
                let file=files[0];//将文件从数组中取出
                console.log(file);
                //调用专门上传文件的方法
                uploadImage(file);
            }else{
                //没文件,直接结束
                alert("请选择文件")
            }
            return false;//阻止表单提交
        })
        //完成文件上传的方法
        function uploadImage(file){
            //构建表单
            let form=new FormData();
            form.append("imageFile",file);
            $.ajax({
                url:"/upload/file",
                method:"post",
                data:form,//发送的是我们构建的表单中的数据
                //下面有两个特殊参数,需要在文件上传时设置
                contentType:false,
                processData:false,
                success:function(r){
                    if(r.code==OK){
                        console.log(r);
                        alert(r.message);
                    }else{
                        alert(r.message);
                    }
                }
            });
        }
    })
</script>
</html>

无需修改控制器代码

直接提交文件测试,成功即可

完成稻草项目中富文本编辑器中文件的上传

搭建一个静态资源服务器

使用SpringBoot聚合项目的子项目完成搭建任务即可

创建straw-resource子项目

创建过程中不需要选中任何依赖

创建成功后执行父子相认

子项目pom.xml 文件代码如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.tedu</groupId>
        <artifactId>straw</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>straw-resource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>straw-resource</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

创建一个文件夹作为图片服务器的资源路径

F:/resource

在这个文件夹中适当保存若干测试图片

在straw-resource项目的application.properties文件中配置如下

server.port=8899
spring.web.resources.static-locations=file:F:/resource

启动服务,输入url

http://localhost:8899/1.jpg

能够看到保存的图片即可,其中1.jpg是我们创建的resource文件夹中真是存在的文件名

将图片上传到静态资源服务器

我们需要将静态资源服务器的ip地址和端口号以及存放文件的路径名保存到当前配置文件中

straw-portal项目的application.properties配置添加代码如下

straw.resource.path=file:F:/resource
straw.resource.host=http://localhost:8899

重构SystemController中上传文件的代码

//下面两个属性值来自application.properties配置文件
    @Value("${straw.resource.path}")
    private File resourcePath;
    @Value("${straw.resource.host}")
    private String resourceHost;

    //接收表单上传的文件
    @PostMapping("/upload/file")
    public R<String> upload(MultipartFile imageFile) throws IOException {
        /*
            我们需要保证任何用户上传的文件的文件名都不能重复
            我们为了尽量避免文件名的重复,采用以下策略
            1.将原有文件名修改为使用UUID生成的字符串
            2.不同的日期创建不同的文件夹
            3.保留文件的扩展名,还能方便文件识别
         */
        //按照当前日期创建文件夹
        String path= DateTimeFormatter.ofPattern("yyyy/MM/dd")
                .format(LocalDate.now());
        //path="2020/12/16"
        File folder=new File(resourcePath,path);
        //folder->F:/resource/2020/12/16
        folder.mkdirs();//创建一串文件夹带s的!!!!
        log.debug("上传的文件夹为:{}",folder.getAbsolutePath());
        //按照上传文件的原始文件名,保留扩展名xx.xx.jpg
        //                              012345678
        String fileName=imageFile.getOriginalFilename();
        String ext=fileName.substring(fileName.lastIndexOf("."));
        //使用UUID生成文件名
        String name= UUID.randomUUID().toString()+ext;
        log.debug("生成的文件名:{}",name);
        //F:/resource/2020/12/16/uuid.jpg
        File file=new File(folder,name);
        //向硬盘写入文件
        imageFile.transferTo(file);
        //直接返回路径方便调用测试
        String url=resourceHost+"/"+path+"/"+name;
        log.debug("访问这个文件的路径为:{}",url);
        return R.ok(url);
    }

如果希望我们上传文件之后能立即显示在页面上

需要在页面上定一个img标签

并在ajax接收到上传图片的url后,将这个ur赋值给img标签的src属性

upload.html代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="bower_components/jquery/dist/jquery.min.js"></script>
</head>
<body>
<form id="demoForm">
    <input type="file" id="imageFile" name="imageFile"><br>
    <input type="submit">
</form>
<img src="" id="image">
</body>
<script src="js/utils.js"></script>
<script>
    //在页面加载完毕之后运行!
    $(function () {
        //先绑定一个方法,当用户点击提交时,验证是否选中了图片
        $("#demoForm").submit(function(){
            //获得用户选择的文件(js是获得用户选择的文件数组)
            let files=document.getElementById("imageFile").files;
            //判断文件数组是不是长度>0
            if(files.length>0){
                //有文件,做上传
                let file=files[0];//将文件从数组中取出
                console.log(file);
                //调用专门上传文件的方法
                uploadImage(file);
            }else{
                //没文件,直接结束
                alert("请选择文件")
            }
            return false;//阻止表单提交
        })
        //完成文件上传的方法
        function uploadImage(file){
            //构建表单
            let form=new FormData();
            form.append("imageFile",file);
            $.ajax({
                url:"/upload/file",
                method:"post",
                data:form,//发送的是我们构建的表单中的数据
                //下面有两个特殊参数,需要在文件上传时设置
                contentType:false,
                processData:false,
                success:function(r){
                    if(r.code==OK){
                        console.log(r);
                        //alert(r.message);
                        $("#image").attr("src",r.message);
                    }else{
                        alert(r.message);
                    }
                }
            });
        }
    })
</script>
</html>

将富文本编辑器中用户选择的文件上传

上传流程

在这里插入图片描述

重构create.html中的代码

代码如下

<!--底部-->
<footer class="container-fluid  bg-light mt-2 py-3">
  <p class="text-center font-weight-light">达内教育-Java教研部 版权所有<br><a href="http://tedu.cn" rel="nofollow" target="_blank">ICP16053980-3</a>
  </p>
</footer>
<script src="../plugins/summernote/summernote.min.js"></script>
<script src="../plugins/summernote/summernote-zh-CN.min.js"></script>
<script>
  $(document).ready(function() {
    $('#summernote').summernote({
      height: 300,
      lang: 'zh-CN',
      placeholder: '请输入问题的详细描述...',
      callbacks:{
        //在执行指定操作后自动调用下面的方法
        //onImageUpload方法就会在用户选中图片之后立即运行
        onImageUpload:function(files) {
          //参数是一个file数组取出第一个,因为我们只会选中一个
          let file =files[0];
          //构建表单
          let form=new FormData();
          form.append("imageFile",file);
          $.ajax({
            url:"/upload/file",
            method:"post",
            data:form,//发送的是我们构建的表单中的数据
            //下面有两个特殊参数,需要在文件上传时设置
            cache:false,
            contentType:false,
            processData:false,
            success:function(r){
              if(r.code==OK){
                console.log(r);
                //将刚刚上传成功的图片显示在summernote富文本编辑器中
                var img=new Image();//实例化了一个img标签
                img.src=r.message;//将img标签的src属性赋值为刚上传的图片
                //summernote方法中提供了插入标签的功能
                //支持使用"insertNode"表示要向富文本编辑器中添加标签内容
                $("#summernote").summernote(
                        "insertNode",img
                )
              }else{
                alert(r.message);
              }
            }
          });
        }
      }
    });
    $('select').select2({placeholder:'请选择...'});
  });
</script>
</body>
<script src="../js/utils.js"></script>
<script src="../js/tags_nav.js"></script>
<script src="../js/createQuestion.js"></script>
</html>

显示问题状态

index.html中通过v-show来控制span的显示或隐藏即可

<div class="col-md-12 col-lg-2">
    <!-- v-show="[boolean类型表达式]" -->
    <!-- 只要boolean类型表达式值为真,这个元素就会显示
    反之就不会显示-->
    <span class="badge badge-pill badge-warning"
    	style="display: none"
    	v-show="question.status==0">未回复</span>
    <span class="badge badge-pill badge-info"
    	style="display: none"
    	v-show="question.status==1">已回复</span>
    <span class="badge badge-pill badge-success"
    	v-show="question.status==2">已解决</span>
</div>

随笔

如果电脑系统是linux

资源路径推荐

file:/home/soft01/resource

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值