IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> SpringCloud + Vue 视频管理系统第五章(大章的开发) -> 正文阅读

[JavaScript知识库]SpringCloud + Vue 视频管理系统第五章(大章的开发)

SpringCloud + Vue 视频管理系统第五章(大章的开发)

第一节、大章查询功能开发(前端和后端)

1、新建business模块,建立chapter表

新增maven子项目business,和以前一样把system模块下的依赖全部导入到business模块中。

新建com.course.business包,然后把system中的配置文件以及类文件都复制到business中,修改相应的内容(端口号改成了9002)。

大章开发在数据库中加入大章表(chapter),在all.sql中执行如下代码。

drop table if exists `chapter`;
create table `chapter` (
  `id` char(8) not null comment 'ID',
  `course_id` char(8) comment '课程ID',
  `name` varchar(50) comment '名称',
  primary key (`id`)
) engine=innodb default charset=utf8mb4 comment='大章';

2、使用代码生成器生成持久层代码

以前在server模块中创建过generatorConfig.xml文件,这次还是使用这个文件,修改tableName字段为Chapter,注意为了方便以后复用,最好不要直接在原位置修改,建议复制一行把原来的注释掉。运行mybatis-generator。

<!--<table tableName="test" domainObjectName="Test"/>-->
<table tableName="chapter" domainObjectName="Chapter"/>

3、编写其他层代码

service层先写,因为business中引入了TestService,那么把TestService和TestController的Test和test,替换为Chapter和chapter即可(当然需要复制再替换,保留原来的TestXXXX的文件)。

再看一下controller,这里的controller是给控台用的,但是business中的controller有可能是给控台使用,也可能是给网站使用,所以最好再加一层目录。

在controller包下创建admin(给控台使用的)目录,把ChapterController放入。这时还要注意一个问题,一般创建一个项目,请求的访问地址,应该和包目录要一致,所以要在请求中再加一层/admin才行。

@RestController
@RequestMapping("/admin")
public class ChapterController {
    @Resource
    private ChapterService chapterService;

    @RequestMapping("/chapter")
    public List<Chapter> chapter(){
        return chapterService.list();
    }
}

访问127.0.0.1:9002/business/admin/chapte可以访问到相应数据。

数据传输也有点问题,一般开发中数据库到持久层和业务层到视图层采用的实体类不一样,因为可能在传递的过程中要加上其他数据。所以普遍要给业务层和视图层之间加一层dto类。这里就采用dto的方式,创建dto目录,把Chapter类复制到dto包中,命名为ChapterDto。这里还要修改service和controller层,不再使用以前的实体类传输,而是dto进行传输。

ChapterService类代码:也就是把chapter转成chapterDto。

@Service
public class ChapterService {
    @Resource
    private ChapterMapper chapterMapper;

    public List<ChapterDto> list(){
        ChapterExample chapterExample = new ChapterExample();
        List<Chapter> chapterList = chapterMapper.selectByExample(chapterExample);
        List<ChapterDto> chapterDtoList = new ArrayList<>();
        for(int i = 0,l = chapterList.size();i < l;i++){
            Chapter chapter = chapterList.get(i);
            ChapterDto chapterDto = new ChapterDto();
            BeanUtils.copyProperties(chapter,chapterDto);
            chapterDtoList.add(chapterDto);
        }
        return chapterDtoList;
    }
}

ChapterController类代码:

@RestController
@RequestMapping("/admin")
public class ChapterController {
    @Resource
    private ChapterService chapterService;

    @RequestMapping("/chapter")
    public List<ChapterDto> chapter(){
        return chapterService.list();
    }
}

4、前端页面开发(使用假数据)

这里为了方便展示查询出来的数据,使用ace模板中的tables.html页面。

image-20210820151344344

现在admin目录下的src下的views下的admin目录创建chapter.vue。这里为了方便绑定路由,先简单写一个模板。

<template>
  <div>
    <h1>大章</h1>
  </div>
</template>

在router下加上子路由。

import Vue from 'vue'
import Router from 'vue-router'
import Login from './views/login.vue'
import Admin from './views/admin.vue'
import Welcome from './views/admin/welcome.vue'
import Chapter from './views/admin/chapter.vue'

Vue.use(Router);

export default new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [{
        path: '*',
        redirect: "/login",
    }, {
        path: '/login',
        component: Login
    }, {
        path: '/admin',
        component: Admin,
        children: [{
            path: 'welcome',
            component: Welcome,
        }, {
            path: 'chapter',
            component: Chapter,
        }]
    }]
})

访问http://localhost:8080/admin/chapter可以访问到。这时就需要tables.html中的代码了。

在该页面的代码中搜索PAGE BEGIN就能找到,收起代码,复制到chapter.vue中。代码太多了…

image-20210820152132450

这时自动刷新可以看到页面效果。

image-20210820152206099

因为这个是router-vue引入的,router-vue外部已经有两行样式了。

<div class="row">
  <div class="col-xs-12">
    <!-- PAGE CONTENT BEGINS -->
    <router-view/>
    <!-- PAGE CONTENT ENDS -->
  </div><!-- /.col -->
</div><!-- /.row -->

而刚才引入的代码前两行也有这个样式,那么就得去掉刚才引入重复的两行,还有结尾两行。这样假数据页面就完成了。

5、点击sidebar菜单实现页面跳转

找到系统管理代码,合并然后复制粘贴一份,改成业务管理。其中某个子模块改成大章管理。也就是变成了下面这样。

<li class="active open">
  <a href="#" class="dropdown-toggle">
    <i class="menu-icon fa fa-list"></i>
    <span class="menu-text"> 业务管理 </span>

    <b class="arrow fa fa-angle-down"></b>
  </a>

  <b class="arrow"></b>

  <ul class="submenu">
    <li class="">
      <a href="tables.html">
        <i class="menu-icon fa fa-caret-right"></i>
        大章管理
      </a>

      <b class="arrow"></b>
    </li>
  </ul>
</li>

系统管理因为目前也没啥用,所以把它的active open删除掉,不让他默认打开。也就变成了下面这样。

image-20210820153257535

当前访问的路径是http://localhost:8080/admin/chapter,为了更好的展示,最好让它也是访问/business路径找到的。访问http://localhost:8080/admin/business/chapter路径。那么就需要修改router.js。

{
    path: 'business/chapter',
    component: Chapter,
}

为了让chapter页面sidebar默认开启状态,可以修改它的二级状态,也就是加上active样式。这样就变为下面这个样式。

image-20210820153755936

image-20210820153721179

下面就该实现怎么点击欢迎跳到欢迎页,点击大章按钮跳到大章管理,并且还关闭或激活相应的一级、二级样式。

这里需要加入方法,为了更好的追踪到相应的dom,这里加上了一些id,这些id和路由的路径是相互对应的,方便后面通过id匹配相应的请求。

  • 欢迎页面:id=“welcome-sidebar”;
  • 大章管理:id=“business-chapter-sidebar”;
activeSidebar: function (id) {
    // 兄弟菜单去掉active样式,自身增加active样式
    $("#" + id).siblings().removeClass("active");
    $("#" + id).siblings().find("li").removeClass("active");
    $("#" + id).addClass("active");

    // 如果有父菜单,父菜单的兄弟菜单去掉open active,父菜单增加open active
    let parentLi = $("#" + id).parents("li");
    if (parentLi) {
        parentLi.siblings().removeClass("open active");
        parentLi.addClass("open active");
    }
}

上面是admin.vue定义的activeSidebar方法,可以用在其他页面上。

chapter.vue:

<script>
  export default {
    name: "chapter",
    mounted: function() {
      this.$parent.activeSidebar("business-chapter-sidebar");
    },
    methods: {
    }
  }
</script>

welcome.vue:

<script>
  export default {
    name: "welcome",
    mounted: function() {
      this.$parent.activeSidebar("welcome-sidebar");
    },
    methods: {
    }
  }
</script>

样式变化的方法有了,怎么实现跳转呢?需要使用router-link来实现。vue的router-link相当于跳转,to哪个路由就跳哪个页面。

<li class="" id="welcome-sidebar">
  <router-link to="/admin/welcome">
    <i class="menu-icon fa fa-tachometer"></i>
    <span class="menu-text"> 欢迎 </span>
  </router-link>

  <b class="arrow"></b>
</li>
----------------------------------------------------
<ul class="submenu">
    <li class="active" id="business-chapter-sidebar">
        <router-link to="/admin/business/chapter">
            <i class="menu-icon fa fa-caret-right"></i>
            大章管理
        </router-link>

        <b class="arrow"></b>
    </li>

</ul>

这样就实现了点击跳转。

这里还需要调整请求的路由,刚登录直接就进入业务管理了,实际上登陆进去,应该进入welcome页面,而且/admin好像有点多余,因为进入了以后哪个网址都是以它为开头的。那就不如不加了。

首先login页面跳转到admin页面时,代码修改成这样:

login(){  //原来的代码:this.$router.push("/admin/welcome")  this.$router.push("/welcome")}

router.js也是:

//原来的代码:path: "/admin",
path: "/",
name: "admin",
component: Admin,

admin下的欢迎页和大章页面也是:

<!--<router-link to="/admin/welcome">-->
<router-link to="/welcome">
    <i class="menu-icon fa fa-tachometer"></i>
    <span class="menu-text"> 欢迎 </span>
</router-link>

<!--<router-link to="/adminbusiness/chapter">-->
<router-link to="/business/chapter">
    <i class="menu-icon fa fa-caret-right"></i>
    大章管理
</router-link>

为了后面方便router.js给每个子路由加个名字:

import Vue from "vue"
import Router from "vue-router"
import Login from "./views/login.vue"
import Admin from "./views/admin.vue"
import Welcome from "./views/admin/welcome.vue"
import Chapter from "./views/admin/chapter.vue"

Vue.use(Router);

export default new Router({
    mode: "history",
    base: process.env.BASE_URL,
    routes: [{
        path: "*",
        redirect: "/login",
    }, {
        path: "/login",
        component: Login
    }, {
        path: "/",
        name: "admin",
        component: Admin,
        children: [{
            path: "welcome",
            name: "welcome",
            component: Welcome,
        }, {
            path: "business/chapter",
            name: "business/chapter",
            component: Chapter,
        }]
    }]
})

6、通用sidebar激活方法

当前这个sidebar,每创建一个siderbar,就需要修改很多代码,不如直接写个通用的方法。

admin下写了个watch函数,监听的时$route路由的变化,只要变化就进行相应的响应。根据刚才设置的路由名称,修改它的路由把它变为id,通过activeSidebar方法激活样式,这下每个页面的激活方法就不再需要了。

<script>
  export default {
    name: "admin",
    mounted: function() {
      let _this = this;
      $("body").removeClass("login-layout light-login");
      $("body").attr("class", "no-skin");
      // console.log("admin");
      // sidebar激活样式方法二
      _this.activeSidebar(_this.$route.name.replace("/", "-") + "-sidebar");
    },
    watch: {
      $route: {
        handler:function(val, oldVal){
          // sidebar激活样式方法二
          console.log("---->页面跳转:", val, oldVal);
          let _this = this;
          _this.$nextTick(function(){  //页面加载完成后执行
            _this.activeSidebar(_this.$route.name.replace("/", "-") + "-sidebar");
          })
        }
      }
    },
    methods: {
      login () {
        this.$router.push("/admin")
      },

      /**
       * 菜单激活样式,id是当前点击的菜单的id
       * @param id
       */
      activeSidebar: function (id) {
        // 兄弟菜单去掉active样式,自身增加active样式
        $("#" + id).siblings().removeClass("active");
        $("#" + id).siblings().find("li").removeClass("active");
        $("#" + id).addClass("active");

        // 如果有父菜单,父菜单的兄弟菜单去掉open active,父菜单增加open active
        let parentLi = $("#" + id).parents("li");
        if (parentLi) {
          parentLi.siblings().removeClass("open active");
          parentLi.addClass("open active");
        }
      }
    }
  }
</script>

第二节、大章查询功能开发(前后端交互)

1、集成axios

terminal窗口,跳到admin目录下。

执行如下指令:

npm install axios --save

引入到main.js中。加上这两句:

import axios from 'axios'

Vue.prototype.$ajax = axios;

使用前后端交互:

list() {    let _this = this;    _this.$ajax.get('http://127.0.0.1:9002/business/admin/chapter/list').then((response)=>{        console.log("查询大章列表结果: ",response);    })}

这个方法应该在页面初始化的时候执行。

mounted: function() {    let _this = this;    _this.list();},

看前端的路径时/business/admin/chapter/list,但controller路径不对,修改一下。

@RestController@RequestMapping("/admin/chapter")public class ChapterController {    @Resource    private ChapterService chapterService;    @RequestMapping("/list")    public List<ChapterDto> list() {        return chapterService.list();    }}

2、解决跨域问题

我们发现,网页报错了,主要时CORS跨域问题。前端后端交互就容易产生跨域问题。

image-20210820164812646

增加跨域配置,在server下的config包下,创建这个CorsConfig类,把下面代码放入:

@Configurationpublic class CorsConfig implements WebMvcConfigurer {    @Override    public void addCorsMappings(CorsRegistry registry) {        registry.addMapping("/**")                .allowedOriginPatterns("*")                .allowedHeaders(CorsConfiguration.ALL)                .allowedMethods(CorsConfiguration.ALL)                .allowCredentials(true)                .maxAge(3600); // 1小时内不需要再预检(发OPTIONS请求)    }}

这个时候发现console控制台能够接收到数据了。

image-20210820165413142

实际上更好的方式是在gateway中启动跨域配置。在GatewayApplication中配置代码:

/**
* 配置跨域
* @return
*/
@Bean
public CorsWebFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();

    config.setAllowCredentials(Boolean.TRUE);
    config.addAllowedMethod("*");
    config.addAllowedOriginPattern("*");
    config.addAllowedHeader("*");
    config.setMaxAge(3600L);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
    source.registerCorsConfiguration("/**", config);

    return new CorsWebFilter(source);
}

取消config中的CorsConfig取消掉。

3、接收数据并显示

首先调整chapter.vue中的表格,只留最基本的即可,其他的到时候使用循环显示。这个比较困难到时候直接拷代码即可。

image-20210820161841366

这是修改后的。

<tr>
    <td>123</td>
    <td>test</td>
    <td>1111</td>

    <td>
        <div class="hidden-sm hidden-xs btn-group">
            <button class="btn btn-xs btn-success">
                <i class="ace-icon fa fa-check bigger-120"></i>
            </button>

            <button class="btn btn-xs btn-info">
                <i class="ace-icon fa fa-pencil bigger-120"></i>
            </button>

            <button class="btn btn-xs btn-danger">
                <i class="ace-icon fa fa-trash-o bigger-120"></i>
            </button>

            <button class="btn btn-xs btn-warning">
                <i class="ace-icon fa fa-flag bigger-120"></i>
            </button>
        </div>

        <div class="hidden-md hidden-lg">
            <div class="inline pos-rel">
                <button class="btn btn-minier btn-primary dropdown-toggle" data-toggle="dropdown" data-position="auto">
                    <i class="ace-icon fa fa-cog icon-only bigger-110"></i>
                </button>

                <ul class="dropdown-menu dropdown-only-icon dropdown-yellow dropdown-menu-right dropdown-caret dropdown-close">
                    <li>
                        <a href="#" class="tooltip-info" data-rel="tooltip" title="View">
                            <span class="blue">
                                <i class="ace-icon fa fa-search-plus bigger-120"></i>
                            </span>
                        </a>
                    </li>

                    <li>
                        <a href="#" class="tooltip-success" data-rel="tooltip" title="Edit">
                            <span class="green">
                                <i class="ace-icon fa fa-pencil-square-o bigger-120"></i>
                            </span>
                        </a>
                    </li>

                    <li>
                        <a href="#" class="tooltip-error" data-rel="tooltip" title="Delete">
                            <span class="red">
                                <i class="ace-icon fa fa-trash-o bigger-120"></i>
                            </span>
                        </a>
                    </li>
                </ul>
            </div>
        </div>
    </td>
</tr>

这时复制一行,作为一个副本。先写前端script。定义一个chapters变量,使用方法list()来调用前后端交互。

<script>
export default {
  name: "chapter",
  data: function(){
    return {
      chapters: []
    }
  },
  mounted: function() {
    //this.$parent.activeSidebar("business-chapter-sidebar");
    let _this = this;
    _this.list();
  },
  methods: {
    list() {
      let _this = this;
      _this.$ajax.get('http://127.0.0.1:9002/business/admin/chapter/list').then((response)=>{
        console.log("查询大章列表结果: ",response);
        _this.chapters = response.data;
      })
    }
  }
}
</script>

页面展示使用v-for循环显示数据。

<tr v-for="chapter in chapters">
  <td>{{chapter.id}}</td>
  <td>{{chapter.name}}</td>
  <td>{{chapter.courseId}}</td>
  ......
</tr>

这回发现显示了代码。

image-20210820170009381

那么可以把假数据删除了。

4、使用gateway实现路由转发

我们希望网页访问的都是一个路径,而不是一会儿9001、一会儿9002的。所以需要使用gateway,进入配置文件,添加下面代码。

spring.cloud.gateway.routes[1].id=business
spring.cloud.gateway.routes[1].uri=http://127.0.0.1:9002
spring.cloud.gateway.routes[1].predicates[0].name=Path
spring.cloud.gateway.routes[1].predicates[0].args[0]=/business/**

这时就可以把请求的地址改成9000了。重启gatewayApplication。

list() {
      let _this = this;
      _this.$ajax.get('http://127.0.0.1:9000/business/admin/chapter/list').then((response)=>{
        console.log("查询大章列表结果: ",response);
        _this.chapters = response.data;
      })
    }

这时发现确实可以查到。

再思考一个问题,生产环境和测试环境下,如果每次都要修改下面的路径太麻烦了。

 _this.$ajax.get('http://127.0.0.1:9000/business/admin/chapter/list')

所以采用SpringCloud的loadBalance功能:

spring.cloud.gateway.routes[1].id=business
#spring.cloud.gateway.routes[1].uri=http://127.0.0.1:9002
spring.cloud.gateway.routes[1].uri=lb://business
spring.cloud.gateway.routes[1].predicates[0].name=Path
spring.cloud.gateway.routes[1].predicates[0].args[0]=/business/**

第三节、大章分页功能开发

1、集成PageHelper(后端分页)

course的pom文件引入依赖,server下引入不带版本的依赖。

<!-- mybatis分页插件pagehelper -->
<dependency>
   <groupId>com.github.pagehelper</groupId>
   <artifactId>pagehelper-spring-boot-starter</artifactId>
   <version>1.2.10</version>
</dependency>

可以测试一下:直接用PageHelper查询哪页。

PageHelper.startPage(1,1);
//相当于执行了limit...

2、分页参数前后端交互

前后端数据交互是个问题,这里采用加一层dto的方式,加入PageDto,页码和每行数量由前端获取,总条数和总数据由后端获取到,最终形成了PageDto。为了方便每种数据都能取到,采用了泛型。

package com.course.server.dto;

import java.util.List;

public class PageDto<T> {

    /**
     * 当前页码
     */
    protected int page;

    /**
     * 每页条数
     */
    protected int size;

    /**
     * 总条数
     */
    protected long total;

    protected List<T> list;

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public long getTotal() {
        return total;
    }

    public void setTotal(long total) {
        this.total = total;
    }

    public List<T> getList() {
        return list;
    }

    public void setList(List<T> list) {
        this.list = list;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("PageDto{");
        sb.append("page=").append(page);
        sb.append(", size=").append(size);
        sb.append(", total=").append(total);
        sb.append(", list=").append(list);

        sb.append('}');
        return sb.toString();
    }
}

chapterService代码:

public void list(PageDto pageDto){
    PageHelper.startPage(pageDto.getPage(),pageDto.getSize());
    ChapterExample chapterExample = new ChapterExample();
    List<Chapter> chapterList = chapterMapper.selectByExample(chapterExample);
    PageInfo<Chapter> pageInfo = new PageInfo<>(chapterList);
    pageDto.setTotal(pageInfo.getTotal());
    List<ChapterDto> chapterDtoList = new ArrayList<>();
    for(int i = 0,l = chapterList.size();i < l;i++){
        Chapter chapter = chapterList.get(i);
        ChapterDto chapterDto = new ChapterDto();
        BeanUtils.copyProperties(chapter,chapterDto);
        chapterDtoList.add(chapterDto);
    }
    pageDto.setList(chapterDtoList);
}

把相关数据直接放入PageDto对象中。这样就拿到了全部的数据。

chaperMapper代码:

@RequestMapping("/list")
public PageDto list(PageDto pageDto) {
    chapterService.list(pageDto);
    return pageDto;
}

这时发现查出的数据有问题,前端找不到数据。是因为是data下的list。所以需要在chapter.vue文件中的list方法修改一下。

image-20210820172919241

修改成下面这样:

list() {
    let _this = this;
    _this.$ajax.get('http://127.0.0.1:9000/business/admin/chapter/list').then((response)=>{
        console.log("查询大章列表结果: ",response);
        //_this.chapters = response.data;
        _this.chapters = response.data.list;
    })
}

这样就查到了,但是还是有问题,请求传递参数一般都是post请求这里get请求不太合适,那么先测试一下简单的。

list() {
  let _this = this;
  _this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/list',{
    page: 1,
    size: 1
  }).then((response)=>{
    console.log("查询大章列表结果: ",response);
    _this.chapters = response.data.list;
  })
}

先测试传递page=1和size=1看能不能查到,发现不能。经过检查发现这样传递,controller是接收不到的。post常见的传递方式有两种,一种以表单的方式,一种是以流的方式(json),默认是流的方式,前端的数据传递给后端,但后端没接收到,说明controller这样写有点问题。其实只需要在方法参数前加上@RequestBody即可。

@RequestMapping("/list")
public PageDto list(@RequestBody PageDto pageDto) {
    chapterService.list(pageDto);
    return pageDto;
}

这次测试发现分页添加就生效了。

3、添加分页组件

首先添加个刷新按钮。在chapter.vue前面添加如下代码。不过注意下只能有一个父标签。点击后会执行list方法,进行刷新。

<p>
  <button v-on:click="list()" class="btn btn-white btn-default btn-round">
    <i class="ace-icon fa fa-refresh"></i>
    刷新
  </button>
</p>

image-20210820174344001

添加分页组件pagination.vue。因为是组件,要放在components目录下。

<template>
  <div class="pagination" role="group" aria-label="分页">
    <button type="button" class="btn btn-default btn-white btn-round" 
            v-bind:disabled="page === 1"
            v-on:click="selectPage(1)">
      1
    </button>
    <button type="button" class="btn btn-default btn-white btn-round" 
            v-bind:disabled="page === 1"
            v-on:click="selectPage(page - 1)">
      上一页
    </button>
    <button v-for="p in pages" v-bind:id="'page-' + p" 
            type="button" class="btn btn-default btn-white btn-round"
            v-bind:class="{'btn-primary active':page == p}" 
            v-on:click="selectPage(p)">
      {{p}}
    </button>
    <button type="button" class="btn btn-default btn-white btn-round" 
            v-bind:disabled="page === pageTotal"
            v-on:click="selectPage(page + 1)">
      下一页
    </button>
    <button type="button" class="btn btn-default btn-white btn-round"
            v-bind:disabled="page === pageTotal"
            v-on:click="selectPage(pageTotal)">
      {{pageTotal||1}}
    </button>
    &nbsp;
    <span class="m--padding-10">
        每页
        <select v-model="size">
            <option value="1">1</option>
            <option value="5">5</option>
            <option value="10">10</option>
            <option value="20">20</option>
            <option value="50">50</option>
            <option value="100">100</option>
        </select>
        条,共【{{total}}】条
    </span>
  </div>
</template>

<script>
  export default {
    name: 'pagination',
    props: {
      list: {
        type: Function,
        default: null
      },
      itemCount: Number // 显示的页码数,比如总共有100页,只显示10页,其它用省略号表示
    },
    data: function () {
      return {
        total: 0, // 总行数
        size: 10, // 每页条数
        page: 0, // 当前页码
        pageTotal: 0, // 总页数
        pages: [], // 显示的页码数组
      }
    },
    methods: {
      /**
       * 渲染分页组件
       * @param page
       * @param total
       */
      render(page, total) {
        let _this = this;
        _this.page = page;
        _this.total = total;
        _this.pageTotal = Math.ceil(total / _this.size);
        _this.pages = _this.getPageItems(_this.pageTotal, page, _this.itemCount || 5);
      },

      /**
       * 查询某一页
       * @param page
       */
      selectPage(page) {
        let _this = this;
        if (page < 1) {
          page = 1;
        }
        if (page > _this.pageTotal) {
          page = _this.pageTotal;
        }
        if (this.page !== page) {
          _this.page = page;
          if (_this.list) {
            _this.list(page);
          }
        }
      },

      /**
       * 当前要显示在页面上的页码
       * @param total
       * @param current
       * @param length
       * @returns {Array}
       */
      getPageItems(total, current, length) {
        let items = [];
        if (length >= total) {
          for (let i = 1; i <= total; i++) {
            items.push(i);
          }
        } else {
          let base = 0;
          // 前移
          if (current - 0 > Math.floor((length - 1) / 2)) {
            // 后移
            base = Math.min(total, current - 0 + Math.ceil((length - 1) / 2)) - length;
          }
          for (let i = 1; i <= length; i++) {
            items.push(base + i);
          }
        }
        return items;
      }
    }
  }
</script>

<style scoped>
  .pagination {
    vertical-align: middle !important;
    font-size: 16px;
    margin-top: 0;
    margin-bottom: 10px;
  }

  .pagination button {
    margin-right: 5px;
  }

  .btn-primary.active {
    background-color: #2f7bba !important;
    border-color: #27689d !important;
    color: white !important;
    font-weight: 600;
  }

  /*.pagination select {*/
  /*vertical-align: middle !important;*/
  /*font-size: 16px;*/
  /*margin-top: 0;*/
  /*}*/
</style>

在chapter中表格上,刷新按钮下放至这个分页的组件。

image-20210820174936141

然后在chapter.vue中要引入pagination.vue组件。然后使用它。

<script>
import Pagination from "../../components/pagination";
export default {
  components: {Pagination},
}

这里首先要注意,添加分页组件以后,list就不是查询所有了,肯定是按页查询,那么list()中肯定要加上页码参数。

mounted: function() {
  let _this = this;
  _this.$refs.pagination.size = 5;	//默认页码
  _this.list(1);
  // sidebar激活样式方法一
  // this.$parent.activeSidebar("business-chapter-sidebar");

},
methods: {
  list(page) {
    let _this = this;
    _this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/list', {
      page: page,	//获取页码
      size: _this.$refs.pagination.size,	//获取size
    }).then((response)=>{
      console.log("查询大章列表结果:", response);
      _this.chapters = response.data.list;
      _this.$refs.pagination.render(page, response.data.total);	

    })
  }
}

这样就完成了分页组件的开发。

第四节、大章新增功能

1、使用模态框

使用BootStrap模态框。加入如下代码:

<div class="modal fade" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>		</button>
                <h4 class="modal-title">表单</h4>
            </div>
            <div class="modal-body">
                <form class="form-horizontal">
                    <div class="form-group">
                        <label class="col-sm-2 control-label">名称</label>
                        <div class="col-sm-10">
                            <input class="form-control" placeholder="名称">
                        </div>
                    </div>
                    <div class="form-group">
                        <label class="col-sm-2 control-label">课程ID</label>
                        <div class="col-sm-10">
                            <input class="form-control" placeholder="课程ID">
                        </div>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                <button type="button" class="btn btn-primary">保存</button>
            </div>
        </div><!-- /.modal-content -->
    </div><!-- /.modal-dialog -->
</div><!-- /.modal -->

然后复制上一节chapter.vue中的刷新按钮,把它改成新增。

<p>
  <button v-on:click="add()" class="btn btn-white btn-default btn-round">
    <i class="ace-icon fa fa-edit"></i>
    新增
  </button>
  <button v-on:click="list()" class="btn btn-white btn-default btn-round">
    <i class="ace-icon fa fa-refresh"></i>
    刷新
  </button>
</p>

这时就需要新增一个add()方法。添加时希望弹出模态框。

add() {
  let _this = this;
  $(".modal").modal("show");
},

但是这时又弹出一个错误,说_this错误。这还是上面说的那种自动检查错误。

image-20210820181039336

还是在.eslintrc.js中修改,在rules下新增一个检验关闭:

'no-unused-vars': 'off'

代码如下:

module.exports = {
    root: true,
    env: {
        node: true
    },
    'extends': [
        'plugin:vue/essential',
        'eslint:recommended'
    ],
    rules: {
        'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
        'no-undef': 'off',
        'vue/no-unused-vars': 'off',
        'vue/require-v-for-key': 'off',
        'no-unused-vars': 'off'
    },
    parserOptions: {
        parser: 'babel-eslint'
    }
};

点击新增弹出模态框。

image-20210820181415578

2、数据库id的选用

数据库id最好选择Uuid,不要选用自增id,因为自增id容易破解。

UuidUtil的代码如下:

import java.util.UUID;
public class UuidUtil {

    public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f",
            "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
            "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
            "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
            "W", "X", "Y", "Z" };


    /**
     * 获取短UUID
     * @return
     */
    public static String getShortUuid() {
        StringBuffer shortBuffer = new StringBuffer();
        String uuid = UuidUtil.getUuid();
        for (int i = 0; i < 8; i++) {
            String str = uuid.substring(i * 4, i * 4 + 4);
            int x = Integer.parseInt(str, 16);
            shortBuffer.append(chars[x % 0x3E]); // 对62取余
        }
        return shortBuffer.toString();

    }

    /**
     * 获得32位UUID
     */
    public static String getUuid(){
        String uuid = UUID.randomUUID().toString();
        //去掉“-”符号
        return uuid.replaceAll("-", "");
    }

    public static void main(String[] args) {
        System.out.println(getShortUuid());
    }
}

那么先在controller和service层添加save方法,这里需要使用uuid。

service层:

public void save(ChapterDto chapterDto){
    Chapter chapter = new Chapter();
    chapterDto.setId(UuidUtil.getShortUuid());
    BeanUtils.copyProperties(chapterDto,chapter);
    chapterMapper.insert(chapter);
}

controller层:

@RequestMapping("/save")
public ChapterDto save(@RequestBody ChapterDto chapterDto) {
    LOG.info("chapterDto:{}",chapterDto);
    chapterService.save(chapterDto);
    return chapterDto;
}

然后需要给前端设置save()方法。data中设置一个参数chapter,然后把这个chapter保存到后端。

data: function(){
    return {
    chapter: {},
    chapters: []
},
save() {
  let _this = this;
  _this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/save', this.chapter).then((response)=>{
    console.log("保存大章列表结果:", response);
  })
}

在chapter.vue中还要给div设置响应的对应参数,以保证可以和后端保存。

<div class="modal-body">
  <form class="form-horizontal">
    <div class="form-group">
      <label class="col-sm-2 control-label">名称</label>
      <div class="col-sm-10">
        <input v-model="chapter.name" class="form-control" placeholder="名称">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">课程ID</label>
      <div class="col-sm-10">
        <input v-model="chapter.courseId" class="form-control" placeholder="课程ID">
      </div>
    </div>
  </form>
</div>

还需要在保存按钮位置加上点击保存事件:

<button v-on:click="save" type="button" class="btn btn-primary">保存</button>

3、CopyUtil的使用

我们之前用了大量的代码来写两个类对象的复制,那么不如写一个通用的CopyUtil类,以简化后续的代码:

package com.course.server.util;

import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

public class CopyUtil {

    public static <T> List<T> copyList(List source, Class<T> clazz) {
        List<T> target = new ArrayList<>();
        if (!CollectionUtils.isEmpty(source)){
            if (!CollectionUtils.isEmpty(source)){
                for (Object c: source) {
                    T obj = copy(c, clazz);
                    target.add(obj);
                }
            }
        }
        return target;
    }

    public static <T> T copy(Object source, Class<T> clazz) {
        if (source == null) {
            return null;
        }
        T obj = null;
        try {
            obj = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        BeanUtils.copyProperties(source, obj);
        return obj;
    }
}

service层使用CopyUtil代码:

public void list(PageDto pageDto){
	PageHelper.startPage(pageDto.getPage(),pageDto.getSize());
	ChapterExample chapterExample = new ChapterExample();
	List<Chapter> chapterList = chapterMapper.selectByExample(chapterExample);
	PageInfo<Chapter> pageInfo = new PageInfo<>(chapterList);
	pageDto.setTotal(pageInfo.getTotal());
	//List<ChapterDto> chapterDtoList = new ArrayList<>();
	//for(int i = 0,l = chapterList.size();i < l;i++){
	//Chapter chapter = chapterList.get(i);
	//ChapterDto chapterDto = new ChapterDto();
	//BeanUtils.copyProperties(chapter,chapterDto);
	//chapterDtoList.add(chapterDto);
	// }
	List<ChapterDto> chapterDtoList = CopyUtil.copyList(chapterList, ChapterDto.class);
	pageDto.setList(chapterDtoList);
}

public void save(ChapterDto chapterDto){
	//Chapter chapter = new Chapter();
    chapterDto.setId(UuidUtil.getShortUuid());
	//BeanUtils.copyProperties(chapterDto,chapter);
    Chapter chapter = CopyUtil.copy(chapterDto, Chapter.class);
    chapterMapper.insert(chapter);
}

4、增加统一返回的ResponseDto

看上面这两个方法一个list一个save,他们的返回参数不相同,看着很别扭,而且前后端应用最好返回一样的参数。那么创建一个ResponseDto类来接收所有类型的对象。

ResponseDto类:

package com.course.server.dto;

public class ResponseDto<T> {

    /**
     * 业务上的成功或失败
     */
    private boolean success = true;

    /**
     * 返回码
     */
    private String code;

    /**
     * 返回信息
     */
    private String message;

    /**
     * 返回泛型数据,自定义类型
     */
    private T content;

    public String getCode() {
        return code;
    }

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

    public boolean getSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

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

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("ResponseDto{");
        sb.append("success=").append(success);
        sb.append(", code='").append(code).append('\'');
        sb.append(", message='").append(message).append('\'');
        sb.append(", content=").append(content);
        sb.append('}');
        return sb.toString();
    }
}

修改controller类。

@RequestMapping("/list")
public ResponseDto list(@RequestBody PageDto pageDto) {
    ResponseDto responseDto = new ResponseDto();
    chapterService.list(pageDto);
    responseDto.setContent(pageDto);
    return responseDto;
}

@RequestMapping("/save")
public ResponseDto save(@RequestBody ChapterDto chapterDto) {
    LOG.info("chapterDto:{}",chapterDto);
    ResponseDto responseDto = new ResponseDto();
    chapterService.save(chapterDto);
    responseDto.setContent(chapterDto);
    return responseDto;
}

前端注意修改:

list(page) {
    let _this = this;
    _this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/list', {
        page: page,
        size: _this.$refs.pagination.size,
    }).then((response)=>{
        console.log("查询大章列表结果:", response);
        let resp = response.data;
        _this.chapters = resp.content.list;
        _this.$refs.pagination.render(page, resp.content.total);

    })
},

response.data现在不能查到结果了,其中还有一层content,那么就加上这层。

save方法有可能成功也有可能不成功,先判断。如果成功了关闭模态框,并且从第一页刷新list()。

save() {
  let _this = this;
  _this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/save', this.chapter).then((response)=>{
    console.log("保存大章列表结果:", response);
    if (resp.success){
      $(".modal").modal("hide");
      _this.list(1);
    }
  })
}

这样就可以新增了。

修改大章功能也可以使用save方法,但是修改是包含id字段值的。

public void save(ChapterDto chapterDto) {
    Chapter chapter = CopyUtil.copy(chapterDto, Chapter.class);
    if (StringUtils.isEmpty(chapterDto.getId())) {
        this.insert(chapter);
    } else {
        this.update(chapter);
    }
}

private void insert(Chapter chapter) {
    chapter.setId(UuidUtil.getShortUuid());
    chapterMapper.insert(chapter);
}

private void update(Chapter chapter) {
    chapterMapper.updateByPrimaryKey(chapter);
}

第五节、大章删除功能

1、删除代码

先在删除按键位置写个删除点击事件。

del(id) {
  let _this = this;
  _this.$ajax.delete('http://127.0.0.1:9000/business/admin/chapter/delete/' + id).then((response)=>{
    console.log("删除大章列表结果:", response);
    let resp = response.data;
    if (resp.success) {
      _this.list(1);
    }
  })
}

controller和service层。

@RequestMapping("/delete/{id}")
public ResponseDto delete(@PathVariable String id) {
    LOG.info("id:{}",id);
    ResponseDto responseDto = new ResponseDto();
    chapterService.delete(id);
    return responseDto;
}
public void delete(String id) {
    chapterMapper.deleteByPrimaryKey(id);
}

确实删除了。

2、引入sweetalert和消息提示框

如果没有提示框,删除没有任何提示,是不是有误删我们也不知道。

那么先在index.html中引入sweetalert所需要的js文件。

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@9"></script>

在del方法中引入sweetalert提示消息:

del(id) {
    let _this = this;
    Swal.fire({
        title: '确认删除?',
        text: "删除后不可恢复,确认删除?",
        icon: 'warning',
        showCancelButton: true,
        confirmButtonColor: '#3085d6',
        cancelButtonColor: '#d33',
        confirmButtonText: '确认!'
    }).then((result) => {
        if (result.value) {
            _this.$ajax.delete('http://127.0.0.1:9000/business/admin/chapter/delete/' + id).then((response)=>{
                console.log("删除大章列表结果:", response);
                let resp = response.data;
                if (resp.success) {
                    _this.list(1);
                    Swal.fire(
                        '删除成功!',
                        '删除成功!',
                        'success'
                    )
                }
            })

        }
    })
}
image-20210820220410124 image-20210820220445107

删除提示和成功提示见上面。

这个消息提示框需要点击ok,这个提示框才能关闭,我们需要一个可以自动关闭的。

需要引入js到index.html中。

<script src="<%= BASE_URL %>static/js/toast.js"></script>

而且为了方便,避免过程中大量重复的代码,在public/static/js下创建一个toast.js当作通用组件。

const Toast = Swal.mixin({
  toast: true,
  position: 'top-end',
  showConfirmButton: false,
  timer: 3000,
  timerProgressBar: true,
  onOpen: (toast) => {
    toast.addEventListener('mouseenter', Swal.stopTimer)
    toast.addEventListener('mouseleave', Swal.resumeTimer)
  }
});

toast = {
  success: function (message) {
    Toast.fire({
      icon: 'success',
      title: message
    });
  },

  error: function (message) {
    Toast.fire({
      icon: 'error',
      title: message
    });
  },

  warning: function (message) {
    Toast.fire({
      icon: 'warning',
      title: message
    });
  }
};

这时在执行完方法后启动这个代码即可。如:

save(page) {
  let _this = this;
  _this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/save', _this.chapter).then((response)=>{
    console.log("保存大章列表结果:", response);
    let resp = response.data;
    if (resp.success) {
      $("#form-modal").modal("hide");
      _this.list(1);
      toast.success("保存成功!");
    }
  })
},
    
del(id) {
    let _this = this;
    Swal.fire({
        title: '确认删除?',
        text: "删除后不可恢复,确认删除?",
        icon: 'warning',
        showCancelButton: true,
        confirmButtonColor: '#3085d6',
        cancelButtonColor: '#d33',
        confirmButtonText: '确认!'
    }).then((result) => {
        if (result.value) {
            _this.$ajax.delete('http://127.0.0.1:9000/business/admin/chapter/delete/' + id).then((response)=>{
                console.log("删除大章列表结果:", response);
                let resp = response.data;
                if (resp.success) {
                    _this.list(1);
                    toast.success("删除成功!");
                }
            })

        }
    })
}

这里确实提示了保存成功的消息提示框。

image-20210820221616433

3、等待框的使用

等待框会让页面看上去更舒服,尤其是保存删除等操作,有这个等待框可以更好的了解什么时候代码结束了。

先引入js。

<!--  loading等待框  -->
<script src="https://cdn.bootcss.com/jquery.blockUI/2.70.0-2014.11.23/jquery.blockUI.min.js"></script>
<script src="<%= BASE_URL %>static/js/loading.js"></script>

增加个自定义的loading.js,保存在static/js目录下。

Loading = {
  show: function () {
    $.blockUI({
      message: '<img src="/static/image/loading.gif" />',
      css: {
        padding: "10px",
        left: "50%",
        width: "80px",
        marginLeft: "-40px",
      }
    });
  },
  hide: function () {
    // 本地查询速度太快,loading显示一瞬间,故意做个延迟
    setTimeout(function () {
      $.unblockUI();
    }, 500)
  }
};

在保存和删除前端代码最关键的代码前后添加开启和隐藏等待框的代码。

list(page) {
    let _this = this;
    Loading.show();
    _this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/list', {
      page: page,
      size: _this.$refs.pagination.size,
    }).then((response)=>{
      Loading.hide();
      console.log("查询大章列表结果:", response);
      let resp = response.data;
      _this.chapters = resp.content.list;
      _this.$refs.pagination.render(page, resp.content.total);

    })
  },

  save(page) {
    let _this = this;
    Loading.show();
    _this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/save', _this.chapter).then((response)=>{
      Loading.hide();
      console.log("保存大章列表结果:", response);
      let resp = response.data;
      if (resp.success) {
        $("#form-modal").modal("hide");
        _this.list(1);
        toast.success("保存成功!");
      }
    })
  },

  del(id) {
    let _this = this;
    Swal.fire({
      title: '确认删除?',
      text: "删除后不可恢复,确认删除?",
      icon: 'warning',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      cancelButtonColor: '#d33',
      confirmButtonText: '确认!'
    }).then((result) => {
      if (result.value) {
        Loading.show();
        _this.$ajax.delete('http://127.0.0.1:9000/business/admin/chapter/delete/' + id).then((response)=>{
          Loading.hide();
          console.log("删除大章列表结果:", response);
          let resp = response.data;
          if (resp.success) {
            _this.list(1);
            toast.success("删除成功!");
          }
        })

      }
    })
  }
}

换一个消息提示框,之前的太小了。引入confirm.js

<script src="<%= BASE_URL %>static/js/confirm.js"></script>
Confirm = {
  show: function (message, callback) {
    Swal.fire({
      title: '确认?',
      text: message,
      icon: 'warning',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      cancelButtonColor: '#d33',
      confirmButtonText: '确认!'
    }).then((result) => {
      if (result.value) {
        if (callback) {
          callback()
        }
      }
    })
  }
}

修改toast.js。

Toast = {
  success: function (message) {
    Swal.fire({
      position: 'top-end',
      icon: 'success',
      title: message,
      showConfirmButton: false,
      timer: 3000
    })
  },

  error: function (message) {
    Swal.fire({
      position: 'top-end',
      icon: 'error',
      title: message,
      showConfirmButton: false,
      timer: 3000
    })
  },

  warning: function (message) {
    Swal.fire({
      position: 'top-end',
      icon: 'warning',
      title: message,
      showConfirmButton: false,
      timer: 3000
    })
  }
};

4、校验代码

前端代码最好还是校验一下,因为传入为空或者本身值不符合格式最好是不要保存到数据库的。

这里先写两个js,一个tool.js,一个validator.js。都存在了static/js/下了。

Tool = {
  /**
   * 空校验 null或""都返回true
   */
  isEmpty: function (obj) {
    if ((typeof obj == 'string')) {
      return !obj || obj.replace(/\s+/g, "") == ""
    } else {
      return (!obj || JSON.stringify(obj) === "{}" || obj.length === 0);
    }
  },

  /**
   * 非空校验
   */
  isNotEmpty: function (obj) {
    return !this.isEmpty();
  },

  /**
   * 长度校验
   */
  isLength: function (str, min, max) {
    return $.trim(str).length >= min && $.trim(str).length <= max;
  }
};
Validator = {
  require: function (value, text) {
    if (Tool.isEmpty(value)) {
      Toast.warning(text + "不能为空");
      return false;
    } else {
      return true
    }
  },

  length: function (value, text, min, max) {
    if (!Tool.isLength(value, min, max)) {
      Toast.warning(text + "长度" + min + "~" + max + "位");
      return false;
    } else {
      return true
    }
  }
};

然后引入到index.html。

<!--  通用工具类  -->
<script src="<%= BASE_URL %>static/js/tool.js"></script>
<!--  校验类  -->
<script src="<%= BASE_URL %>static/js/validator.js"></script>

chapter.vue只需要在方法前写上这段代码即可。

// 保存校验
if (!Validator.require(_this.chapter.name, "名称")
  || !Validator.require(_this.chapter.courseId, "课程ID")
  || !Validator.length(_this.chapter.courseId, "课程ID", 1, 8)) {
  return;
}

然后开始后端校验。

后端需要创建一个ValidatorUtil工具类。

package com.course.server.util;

import com.course.server.exception.ValidatorException;
import org.springframework.util.StringUtils;

public class ValidatorUtil {

    /**
     * 空校验(null or "")
     */
    public static void require(String str, String fieldName) {
        if (StringUtils.isEmpty(str)) {
            throw new ValidatorException(fieldName + "不能为空");
        }
    }

    /**
     * 长度校验
     */
    public static void length(String str, String fieldName, int min, int max) {
        int length = 0;
        if (!StringUtils.isEmpty(str)) {
            length = str.length();
        }
        if (length < min || length > max) {
            throw new ValidatorException(fieldName + "长度" + min + "~" + max + "位");
        }
    }
}

如果执行有问题可以抛出异常,那么自定义一个异常类。

package com.course.server.exception;

public class ValidatorException extends RuntimeException{

    public ValidatorException(String message) {
        super(message);
    }
}

controller的save方法添加校验。

// 保存校验
ValidatorUtil.require(chapterDto.getName(), "名称");
ValidatorUtil.require(chapterDto.getCourseId(), "课程ID");
ValidatorUtil.length(chapterDto.getCourseId(), "课程ID", 1, 8);

还需要一个处理异常的Handler。可以省的每次都要写大量代码,直接Handler看到抛异常就处理就行。

package com.course.business.controller;

import com.course.server.dto.ResponseDto;
import com.course.server.exception.ValidatorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class ControllerExceptionHandler {

    private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class);

    @ExceptionHandler(value = ValidatorException.class)
    @ResponseBody
    public ResponseDto validatorExceptionHandler(ValidatorException e) {
        ResponseDto responseDto = new ResponseDto();
        responseDto.setSuccess(false);
        LOG.warn(e.getMessage());
        responseDto.setMessage("请求参数异常!");
        return responseDto;
    }
}

前端如果有问题也可以在控制台提示。

} else {
  Toast.warning(resp.message)

5、优化功能

当前控制台还有网页日志输出不太好,优化日志输出可以更好的开发。

父项目pom引入依赖。

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.60</version>
</dependency>

server下引入依赖。

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

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

server目录下创建LogAspect类。然后所有controller都作为切点使用当前这个方法。

package com.course.server.config;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.course.server.util.UuidUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;

@Aspect
@Component
public class LogAspect {

    private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);

    /** 定义一个切点 */
    @Pointcut("execution(public * com.course.*.controller..*Controller.*(..))")
    public void controllerPointcut() {}

    @Before("controllerPointcut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 日志编号
        MDC.put("UUID", UuidUtil.getShortUuid());

        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();

        // 打印业务操作
        String nameCn = "";
        if (name.contains("list") || name.contains("query")) {
            nameCn = "查询";
        } else if (name.contains("save")) {
            nameCn = "保存";
        } else if (name.contains("delete")) {
            nameCn = "删除";
        } else {
            nameCn = "操作";
        }

        // 使用反射,获取业务名称
        Class clazz = signature.getDeclaringType();
        Field field;
        String businessName = "";
        try {
            field = clazz.getField("BUSINESS_NAME");
            if (!StringUtils.isEmpty(field)) {
                businessName = (String) field.get(clazz);
            }
        } catch (NoSuchFieldException e) {
            LOG.error("未获取到业务名称");
        } catch (SecurityException e) {
            LOG.error("获取业务名称失败", e);
        }

        // 打印请求信息
        LOG.info("------------- 【{}】{}开始 -------------", businessName, nameCn);
        LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
        LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);
        LOG.info("远程地址: {}", request.getRemoteAddr());

        // 打印请求参数
        Object[] args = joinPoint.getArgs();
        Object[] arguments  = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ServletRequest
                    || args[i] instanceof ServletResponse
                    || args[i] instanceof MultipartFile) {
                continue;
            }
            arguments[i] = args[i];
        }
        // 排除字段,敏感字段或太长的字段不显示
        String[] excludeProperties = {};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
        excludefilter.addExcludes(excludeProperties);
        LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter)); // 为空的会不打印,但是像图片等长字段也会打印
    }

    @Around("controllerPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        // 排除字段,敏感字段或太长的字段不显示
        String[] excludeProperties = {"password"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
        excludefilter.addExcludes(excludeProperties);
        LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter));
        LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
        return result;
    }
}

前端日志输出。

/**
 * axios拦截器
 */
axios.interceptors.request.use(function (config) {
  console.log("请求:", config);
  return config;
}, error => {});
axios.interceptors.response.use(function (response) {
  console.log("返回结果:", response);
  return response;
}, error => {});
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-21 15:16:46  更:2021-08-21 15:17:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 6:46:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计