前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微服务[学成在线] day03:CMS页面管理开发

微服务[学成在线] day03:CMS页面管理开发

作者头像
LCyee
发布2020-08-05 17:37:12
2.1K0
发布2020-08-05 17:37:12
举报

? 知识点概览

为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。

本章节为【学成在线】项目的 day03 的内容

  •  使用 Spring data 提供的 PageRequest 模块进行分页查询的应用
  •  使用 Spring data 提供 MongoDB 的dao接口进行前后端联调的 CRUD 操作
  •  基于 VUE.JS 的前端模块化开发
  •  使用统一的响应模型、状态码进行 RESTful 风格的API开发
  •  熟悉使用 Swagger 进行接口文档的生成与测试
  •  异常处理以及如何自定义异常,根据不同的异常自定义返回的消息格式。

一、自定义条件查询

0x01 需求分析

在页面输入查询条件,查询符合条件的页面信息。

查询条件如下:

站点Id:精确匹配

模板Id:精确匹配

页面别名:模糊匹配

0x02 服务端

Dao层

使用 CmsPageRepository 中的 findAll(Example<S> var1, Pageable var2) 方法实现,无需定义。

单元测试

下边测试findAll方法实现自定义条件查询:

1、指定站点id、模板id作为查询条件

代码语言:javascript
复制
//自定义条件查询
@Test
public void testDiyFindAll(){
    //精确匹配条件值
    CmsPage cmsPage = new CmsPage();
    cmsPage.setSiteId("5a751fab6abb5044e0d19ea1");
    cmsPage.setTemplateId("5a925be7b00ffc4b3c1578b5");
    
    //条件匹配器,用于模糊匹配
    ExampleMatcher matching = ExampleMatcher.matching();
    
    //条件查询实例
    Example<CmsPage> example = Example.of(cmsPage, matching);

    //分页参数
    int page = 0;
    int size = 20;
    Pageable pageable = PageRequest.of(page,size);

    //调用dao
    Page<CmsPage> all = cmsPageRepository.findAll(example, pageable);
    List<CmsPage> content = all.getContent();
    System.out.println(content);
}

查询结果

在上面的代码基础上,增加 ExampleMatcher 实例的一些属性作为模糊查询的参数,增加的代码如下

代码语言:javascript
复制
cmsPage.setPageAliase("详细");
//条件匹配器,用于模糊匹配
ExampleMatcher matching = ExampleMatcher.matching()
       .withMatcher("pageAliase",ExampleMatcher.GenericPropertyMatchers.contains());

.withMatcher 的第一个参数表示要将哪个字段进行匹配,第二个则是要使用的匹配器;

ExampleMatcher.GenericPropertyMatchers 有多个匹配器,这里我们用 .contains() 进行模糊匹配

Service层
代码语言:javascript
复制
@Autowired
CmsPageRepository cmsPageRepository;

/**
 * 分页查询
 * @param page 页号
 * @param size 每页大小
 * @param queryPageRequest 查询条件
 * @return
 */
public QueryResponseResult findList(int page,int size,QueryPageRequest queryPageRequest) {
    //判断条件对象是否为空
    if(queryPageRequest == null){
        queryPageRequest = new QueryPageRequest();
    }

    //匹配条件值
    CmsPage cmsPage = new CmsPage();

    //设置条件值
    //站点ID
    if(!StringUtil.isNullOrEmpty(queryPageRequest.getSiteId())){
        cmsPage.setSiteId(queryPageRequest.getSiteId());
    }
    //模板ID
    if(!StringUtil.isNullOrEmpty(queryPageRequest.getTemplateId())){
        cmsPage.setTemplateId(queryPageRequest.getTemplateId());
    }

    //站点别名
    if(!StringUtil.isNullOrEmpty(queryPageRequest.getPageAliase())){
        cmsPage.setPageAliase(queryPageRequest.getPageAliase());
    }

    //条件匹配器,用于模糊查询
    ExampleMatcher exampleMatcher = ExampleMatcher.matching()
            .withMatcher("pageAliase", ExampleMatcher.GenericPropertyMatchers.contains());


    //条件查询实例
    Example<CmsPage> example = Example.of(cmsPage, exampleMatcher);

    //过滤条件
    if(page <= 0){
        page = 1;
    }
    if(size <= 0){
        size = 10;
    }
    page = page - 1;

    //创建分页查询参数
    PageRequest pageable = PageRequest.of(page, size);

    //分页查询数据
    Page<CmsPage> all = cmsPageRepository.findAll(example, pageable);

    //整理查询到的数据
    QueryResult queryResult = new QueryResult();
    queryResult.setList(all.getContent());
    queryResult.setTotal(all.getTotalElements());

    //返回结果
    return new QueryResponseResult(CommonCode.SUCCESS,queryResult);
}

Controller层无需修改

使用SwaggerUI测试

参数

查询结果

从查询结果中我们可以看出,根据我们输入的条件,查询到了指定 sizeId 并且 pageAliase 包含预览的信息。

0x03 前端

页面

1、增加查询表单

在el-table上方添加该表单

代码语言:javascript
复制
<!--查询表单-->
<el-form :model="params">
  <el-select v-model="params.siteId" placeholder="请选择站点">
    <el-option
    v-for="item in siteList"
    :key="item.siteId"
    :label="item.siteName"
    :value="item.siteId">
    </el-option>
  </el-select>
  页面别名: <el-input v-model="params.pageAliase" style="width: 100px"></el-input>
<el-button type="primary" v-on:click="query" size="small">查询</el-button>
</el-form>

2、新增数据模型对象

代码语言:javascript
复制
data() {
  return {
    siteList:[],//站点列表
    list:[],
    total:0,
    params:{
      siteId:'',
      pageAliase:'',
      page:1,//页码
      size:10//每页显示个数
    }
  }
},

3、在钩子方法中 获取 siteList 站点列表(这里暂时用静态数据代替)

代码语言:javascript
复制
mounted() {
  //默认查询页面
  this.query()
  //初始化站点列表
  this.siteList = [
    {
      siteId:'5a751fab6abb5044e0d19ea1',
      siteName:'门户主站'
    },
    {
      siteId:'102',
      siteName:'测试站'
    }
  ]
},

API调用

1、向服务端传递查询条件,修改 cms.js,如下:

代码语言:javascript
复制
//public是对axios的工具类封装,定义了http请求方法
import http from './../../../base/api/public'  //ES6 导入
import querystring from "querystring"
let sysConfig = require('@/../config/sysConfig')
let apiUrl = sysConfig.xcApiUrlPre
//页面查询
export const page_list = (page,size,params) => {

  //将json对象转成key/value对
  let queryString = querystring.stringify(params);
  //定义方法,请求服务端查询接口
  return http.requestQuickGet(apiUrl + '/cms/page/list/'+page+'/'+ size +'?' + queryString)
}

2、页面调用api方法

代码语言:javascript
复制
//查询
query:function () {
  //调用服务端接口
  cmsApi.page_list(this.params.page, this.params.size, this.params).then((res) =>{
    console.log(res)
    //将res结果数据赋值给数据模型对象
    this.list = res.queryResult.list
    this.total = res.queryResult.total
  })
}

3、测试

二、新增页面

0x01 准备工作,站点和模板API

在配置新增页面的功能之前,我们先配置两个接口,用于获取站点和模板的信息

Dao层

CmsSizeRepository

代码语言:javascript
复制
/**
 * 继承MongoDB自带的Repository
 */
public interface CmsSizeRepository extends MongoRepository<CmsSite,String> {
}

CmsTemplateRepository

代码语言:javascript
复制
/**
 * 继承MongoDB自带的Repository
 */
public interface CmsTemplateRepository extends MongoRepository<CmsTemplate,String> {

}

定义查询模型

QuerySizeRequest

代码语言:javascript
复制
package com.xuecheng.framework.domain.cms.request;

import com.xuecheng.framework.model.request.RequestData;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.Date;

@Data
public class QuerySizeRequest extends RequestData {
    //站点ID
    @ApiModelProperty("站点ID")
    private String siteId;
    //站点名称
    @ApiModelProperty("站点名称")
    private String siteName;
    //站点域
    @ApiModelProperty("站点域")
    private String siteDomain;
    //站点端口
    @ApiModelProperty("站点端口")
    private String sitePort;
    //站点访问地址
    @ApiModelProperty("站点访问地址")
    private String siteWebPath;
    //创建时间
    @ApiModelProperty("创建时间")
    private Date siteCreateTime;
}

QueryTemplateRequest

代码语言:javascript
复制
package com.xuecheng.framework.domain.cms.request;

import com.xuecheng.framework.model.request.RequestData;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
public class QueryTemplateRequest extends RequestData {
    //站点ID
    @ApiModelProperty("站点ID")
    private String siteId;
    //模版ID
    @ApiModelProperty("模版ID")
    private String templateId;
    //模版名称
    @ApiModelProperty("模版名称")
    private String templateName;
    //模版参数
    @ApiModelProperty("模版参数")
    private String templateParameter;
    //模版文件Id
    @ApiModelProperty("模版文件Id")
    private String templateFileId;
}

定义响应模型

CmsSizeResult

代码语言:javascript
复制
@Data
public class CmsSizeResult extends ResponseResult {
    CmsSite cmsSite;
    public CmsSizeResult(ResultCode resultCode, CmsSite cmsSite) {
        super(resultCode);
        this.cmsSite = cmsSite;
    }
}

CmsTemplateResult

代码语言:javascript
复制
@Data
public class CmsTemplateResult extends ResponseResult {
    CmsTemplate cmsTemplate;
    public CmsTemplateResult(ResultCode resultCode, CmsTemplate cmsTemplate) {
        super(resultCode);
        this.cmsTemplate = cmsTemplate;
    }
}

Service层

SiteService

代码语言:javascript
复制
@Service
public class SiteService {
    @Autowired
    CmsSiteRepository cmsSiteRepository;

    /**
     * 查询所有的站点信息
     * @return
     */
    public QueryResponseResult findList(){
        //获取所有的站点信息
        List<CmsSite> all = cmsSiteRepository.findAll();
        if(all == null){
            return new QueryResponseResult(CommonCode.FAIL, null);
        }
        //查询响应模板
        QueryResult<CmsSite> queryResult = new QueryResult<CmsSite>();
        queryResult.setList(all);
        queryResult.setTotal(all.size());
        //根据指定模板响应数据
        return new QueryResponseResult(CommonCode.SUCCESS, queryResult);
    }
}

单元测试,测试是否能正常拿到数据

代码语言:javascript
复制
@SpringBootTest
@RunWith(SpringRunner.class)
public class CmsSizeRespositoryTest {
    @Autowired
    SiteService siteService;

    /**
     * 测试查询所有站点数据
     */
    @Test
    public void testFindAll(){
        QueryResponseResult list = siteService.findList();
        System.out.println(list);
    }
}

TemplateService

代码语言:javascript
复制
@Service
public class TemplateService {
    @Autowired
    CmsTemplateRepository cmsTemplateRepository;

    /**
     * 获取所有模板信息
     * @return
     */
    public QueryResponseResult findAll(){
        //调用MongoDB提供的dao接口
        List<CmsTemplate> all = cmsTemplateRepository.findAll();
        if(all == null){
            return new QueryResponseResult(CommonCode.FAIL, null);
        }

        //拼装返回信息
        QueryResult<CmsTemplate> cmsTemplateQueryResult = new QueryResult<>();
        cmsTemplateQueryResult.setList(all);
        cmsTemplateQueryResult.setTotal(all.size());
        return new QueryResponseResult(CommonCode.SUCCESS,cmsTemplateQueryResult);
    }
}

Controller层

定义站点相关操作的api

代码语言:javascript
复制
package com.xuecheng.api.cms;

import com.xuecheng.framework.model.response.QueryResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(value="CMS站点管理接口",description = "CMS站点管理接口,提供CMS站点的CRUD")
public interface CmsSiteControllerApi {
    @ApiOperation("查询所有站点信息")
    public QueryResponseResult findList();
}

定义模板相关操作的api

代码语言:javascript
复制
package com.xuecheng.api.cms;


import com.xuecheng.framework.model.response.QueryResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(value="CMS页面模板",description = "CMS页面模板,提供CMS页面模板的CRUD")
public interface CmsTemplateControllerApi {
    @ApiOperation("查询所有站点信息")
    public QueryResponseResult findList();
}

CmsSiteController

代码语言:javascript
复制
package com.xuecheng.manage_cms.controller;

import com.xuecheng.api.cms.CmsSiteControllerApi;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.manage_cms.service.SiteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cms/site")
public class CmsSiteController implements CmsSiteControllerApi {
    @Autowired
    SiteService siteService;

    @GetMapping("/list")
    @Override
    public QueryResponseResult findList() {
        QueryResponseResult queryResponseResult = siteService.findList();
        return queryResponseResult;
    }
}

CmsTemplateController

代码语言:javascript
复制
package com.xuecheng.manage_cms.controller;

import com.xuecheng.api.cms.CmsTemplateControllerApi;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.manage_cms.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cms/template")
public class CmsTemplateController implements CmsTemplateControllerApi {
    @Autowired
    TemplateService templateService;

    @GetMapping("/list")
    @Override
    public QueryResponseResult findList() {
        QueryResponseResult queryResponseResult = templateService.findList();
        return queryResponseResult;
    }
}

测试

0x02 后端接口定义

1、定义响应模型

用于接口的响应规范,继承于项目中的ResponseResult

代码语言:javascript
复制
@Data
public class CmsPageResult extends ResponseResult {
    CmsPage cmsPage;
    public CmsPageResult(ResultCode resultCode,CmsPage cmsPage) {
        super(resultCode);
        this.cmsPage = cmsPage;
	}
}

2、定义添加Api

CmsPageControllerApi 中新增 addCmsPage 接口

代码语言:javascript
复制
/**
 * 添加页面数据
 */
@ApiOperation("添加页面数据")
@ApiImplicitParams({
        @ApiImplicitParam(name="cmsPage",value = "请提交json形式的页面数据",required=true,paramType="CmsPage",dataType="CmsPage"),
})
public CmsPageResult addCmsPage(CmsPage cmsPage);

0x03 服务端开发

1、页面唯一值索引

为了保证站点的唯一性,所以我们要根据 sizeId、pageName、pageWebPath 这三个字段来决定站点的数据是否唯一。

右键 cms_page 集合,选择 Add Index 添加一个索引

根据下图所示,点击 Add Field 按钮,选择 sizeId、pageName、pageWebPath 这三个字段,然后点击添加,选择第三步所示的 Unique,最后点击右边的 Create Index 创建索引。

创建成功后

2、Dao层

1、添加根据页面名称、站点Id、页面webpath查询页面方法,此方法用于校验页面是否存在

代码语言:javascript
复制
/**
 * 根据站点id、站点名称、站点路径来查询站点信息
 * @return
 * @param siteId
 * @param pageName
 * @param pageWebPath
 */
CmsPage findBySiteIdAndPageNameAndPageWebPath(String siteId, String pageName, String pageWebPath);

2、使用 CmsPageRepository提供的save方法 。

3、Service层

代码语言:javascript
复制
/**
 * 添加页面数据
 */
public CmsPageResult addCmsPage(CmsPage cmsPage){
    //验证数据唯一性:sizeId、pageName、pageWebPath
    CmsPage cmsPage1 = cmsPageRepository.findBySiteIdAndPageNameAndPageWebPath(cmsPage.getSiteId(), cmsPage.getPageName(), cmsPage.getPageWebPath());
    if(cmsPage1 == null){
        //站点id由mongoDB自动生成,防止前端传值
        cmsPage.setPageId(null);
        CmsPage save = cmsPageRepository.save(cmsPage);
        return new CmsPageResult(CommonCode.SUCCESS,save);
    }
    //添加失败
    return new CmsPageResult(CommonCode.FAIL,cmsPage);
}

4、Controller层

代码语言:javascript
复制
@Override
@PostMapping("/add")
public CmsPageResult addCmsPage(@RequestBody CmsPage cmsPage) {
    return pageService.addCmsPage(cmsPage);
}

5、接口测试

我们在 swagger 自动生成的文档接口中进行测试

第一次添加,添加成功

第二次添加重复的内容,由于唯一性的效验,返回添加失败

0x04 前端开发

构建新增页面

1、页面创建

使用Element-UI的form组件编写添加表单内容,页面效果如下:

1)创建页面

创建page_add.vue页面

2)配置路由

在cms模块的路由文件中配置“添加页面”的路由:

代码语言:javascript
复制
{path:'/cms/page/add',name:'新增页面',component: page_add,hidden:true}

注意:由于 “添加页面” 不需要显示为一个菜单,这里 hidden 设置为 true 隐藏菜单。

测试,在浏览器地址栏输入http://localhost:11000/#/cms/page/add

3)“添加页面” 的按钮

实际情况是用户进入页面查询列表,点击“新增页面”按钮进入新增页面窗口。

在查询按钮的旁边添加:

代码语言:javascript
复制
<router‐link class="mui‐tab‐item" :to="{path:'/cms/page/add/'}">
	<el‐button type="primary" size="small">新增页面</el‐button>
</router‐link>

router-link是vue提供的路由功能,用于在页面生成路由链接,最终在html渲染后就是<a标签。 to:目标路由地址

4)完善页面内容

代码语言:javascript
复制
  <el-form :model="pageForm" label-width="80px">
    <el-form-item label="所属站点" prop="siteId">
      <el-select v-model="pageForm.siteId" placeholder="请选择站点">
        <el-option
          v-for="item in siteList"
          :key="item.siteId"
          :label="item.siteName"
          :value="item.siteId">
        </el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="选择模版" prop="templateId">
      <el-select v-model="pageForm.templateId" placeholder="请选择">
        <el-option
          v-for="item in templateList"
          :key="item.templateId"
          :label="item.templateName"
          :value="item.templateId">
        </el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="页面名称" prop="pageName">
      <el-input v-model="pageForm.pageName" auto-complete="off"></el-input>
    </el-form-item>
    <el-form-item label="别名" prop="pageAliase">
      <el-input v-model="pageForm.pageAliase" auto-complete="off"></el-input>
    </el-form-item>
    <el-form-item label="访问路径" prop="pageWebPath">
      <el-input v-model="pageForm.pageWebPath" auto-complete="off"></el-input>
    </el-form-item>
    <el-form-item label="物理路径" prop="pagePhysicalPath">
      <el-input v-model="pageForm.pagePhysicalPath" auto-complete="off"></el-input>
    </el-form-item>
    <el-form-item label="类型">
      <el-radio-group v-model="pageForm.pageType">
        <el-radio label="0">静态</el-radio>
        <el-radio label="1">动态</el-radio>
      </el-radio-group>
    </el-form-item>
    <el-form-item label="创建时间">
      <el-date-picker type="datetime" placeholder="创建时间" v-model="pageForm.pageCreateTime">
      </el-date-picker>
    </el-form-item>
  </el-form>
  <div slot="footer" class="dialog-footer">
    <el-button type="primary" @click="addSubmit">提交</el-button>
  </div>

Form Attributes说明:

  • model 表单数据对象
  • rules 表单验证规则

Form-Item Attributes说明:

  • prop 表单域 model 字段,在使用 validate、resetFields 方法的情况下,该属性是必填的
  • label 标签文本

详情属性及事件参考 http://element.eleme.io/#/zh-CN/component/form

5)数据对象

代码语言:javascript
复制
  //站点列表
    siteList: [],
    //模版列表
    templateList: [],
    //新增界面数据
    pageForm: {
      siteId: '',
      templateId: '',
      pageName: '',
      pageAliase: '',
      pageWebPath: '',
      pageParameter: '',
      pagePhysicalPath: '',
      pageType: '0',  //默认选中0
      pageCreateTime: new Date()
    }
  }
},
methods: {
  addSubmit() {
    alert("提交")
  },
}

6)站点及模板数据

定义请求api

代码语言:javascript
复制
//获取所有站点信息
export const site_list = () =>{
  return http.requestGet(apiUrl + "/cms/site/list")
}


//获取所有模板信息
export const template_list = () =>{
  return http.requestGet(apiUrl + "/cms/template/list")
}

在created钩子中定义,原讲义内使用的是静态数据

代码语言:javascript
复制
created: function () {
    //初始化站点数据
    cmsApi.site_list().then((res) => {
        if (res.success) {
            console.log("站点数据",res)
            //赋值给站点信息列表
            this.siteList = res.queryResult.list
        } else {
            console.log("获取站点信息时发生了错误", res)
        }
    })

    //初始化模板数据
    cmsApi.template_list().then((res) => {
        if (res.success) {
            console.log("模板数据",res)
            //赋值给模板列表
            this.templateList = res.queryResult.list
        } else {
            console.log("获取模板信息时发生了错误", res)
        }
    })
},

7)测试预览

2、添加返回

进入新增页面后只能通过菜单再次进入页面列表,可以在新增页面添加“返回”按钮,点击返回按钮返回到页面列表。

  1. 新增页面按钮带上参数
代码语言:javascript
复制
<router-link class="mui-tab-item"
             :to="{path:'/cms/page/add/', query:{ page:this.params.page,siteId:this.params.siteId }}">
  <el-button type="primary" size="small">新增页面</el-button>
</router-link>

说明:query表示在路由url上带上参数

2)定义返回方法

在page_add.vue上定义返回按钮

代码语言:javascript
复制
<el‐button type="primary" @click="go_back" >返回</el‐button>

在page_add.vue上定义返回方法

代码语言:javascript
复制
go_back(){
    this.$router.push({
        path: '/cms/page/list', query: {
        page: this.$route.query.page,
        siteId:this.$route.query.siteId
    }
    })
}

说明:this.$route.query 表示取出路由上的参数列表,有两个取路由参数的方法:

a、通过在路由上添加 key/value 串使用 this.route.query 来取参数,例如:/router1?id=123 , /router1?id=456 可以通过 this.route.query.id 获取参数id的值。 b、通过将参数作为路由一部分进行传参数使用 this.route.params 来获取,例如:定义的路由为 /router1/:id ,请求 /router1/123 时可以通过 this.route.params.id 来获取,此种情况用 this.

3)查询列表支持回显

进入查询列表,从url中获取页码和站点id并赋值给数据模型对象,从而实现页面回显。

url例子:http://localhost:12000/#/cms/page/list?page=2&siteId=5a751fab6abb5044e0d19ea1

代码语言:javascript
复制
created() {
    //从路由上获取参数
    this.params.page = Number.parseInt(this.$route.query.page||1);
    this.params.siteId = this.$route.query.siteId||'';
    .....
}

小技巧:使用 ||返回第一个有效值

1.3、表单验证

1)配置校验规则

Element-UI的Form组件提供表单校验的方法:

在form属性上配置rules(表单验证规则)

代码语言:javascript
复制
<el‐form :model="pageForm" :rules="pageFormRules" label‐width="80px" >

在数据模型中配置校验规则:

代码语言:javascript
复制
data() {
    return {
        pageFormRules: {
            siteId:[
                {required: true, message: '请选择站点', trigger: 'blur'}
            ],
            templateId:[
                {required: true, message: '请选择模版', trigger: 'blur'}
            ],
            pageName: [
                {required: true, message: '请输入页面名称', trigger: 'blur'}
            ],
            pageAliase: [
                {required: true, message: '请输入页面别名', trigger: 'blur'}
            ],
            pageWebPath: [
                {required: true, message: '请输入访问路径', trigger: 'blur'}
            ],
            pagePhysicalPath: [
                {required: true, message: '请输入物理路径', trigger: 'blur'}
            ]
        },
    }
}

更多的校验规则参考 http://element.eleme.io/#/zh-CN/component/form 中“表单验证”的例子

2)点击提交按钮触发校验

在form表单上添加 ref属性(ref="pageForm")在校验时引用此表单对象

代码语言:javascript
复制
<el‐form :model="pageForm" :rules="pageFormRules" label‐width="80px" ref="pageForm">

在提交表单时执行校验

代码语言:javascript
复制
addSubmit() {
    this.$refs.pageForm.validate((valid) => {
        if (valid) {
            alert('提交');
        } else {
            alert('校验失败');
            return false;
        }
    })
},

测试

API调用

1、在cms.js中定义page_add方法
代码语言:javascript
复制
export const page_add = params =>{
  return http.requestPost(apiUrl + "/cms/page/add",params)
}
2、添加事件

完整的代码如下:

代码语言:javascript
复制
//提交表单
addSubmit(){
  this.$refs.pageForm.validate((valid) => {
    if (valid) {
      this.$confirm('确认提交吗?', '提示', {}).then(() => {
        cmsApi.page_add(this.pageForm).then((res) => {
          console.log(res);
          if(res.success){
            this.$message({
              message: '提交成功',
              type: 'success'
          });
          this.$refs['pageForm'].resetFields();
          }else{
            this.$message.error('提交失败');
          }
        });
      });
    }
  })
},

本功能使用到两个UI组件:

3、测试

三、修改页面

修改页面用户操作流程:

1、用户进入修改页面,在页面上显示了修改页面的信息

2、用户修改页面的内容,点击“提交”,提示“修改成功”或“修改失败”

0x01 后端接口定义

修改页面需要定义的API如下:

代码语言:javascript
复制
/**
     * 通过ID查询页面
     */
@ApiOperation("通过ID查询页面")
public CmsPageResult findById(String id);


/**
     * 修改页面
     * @param id
     * @param cmsPage
     * @return
     */
@ApiOperation("修改页面")
public CmsPageResult update(String id, CmsPage cmsPage);

我们从前面定义的 findList 用的是 QueryResponseResult 作为响应模型,但是这里我们定义的 CmsPageResult 作为响应模型,两者的区别是什么? 我个人的理解是,findList 是分页查询并且返回了多个对象的信息,而 findById 则是查询单个对象的信息,所以 CmsPageResult 作为操作或查询单个对象时的响应模型,而 QueryResponseResult 则作为操作多个对象时的响应模型。

说明:提交数据使用post、put都可以,只是根据http方法的规范,put方法是对服务器指定资源进行修改,所以这里使用put方法对页面修改进行修改。

0x02 服务端开发

Dao层

使用 Spring Data提供的findById方法完成根据主键查询 。 使用 Spring Data提供的save方法完成数据保存 。

Service层

代码语言:javascript
复制
/**
 * 根据id获取页面数据
 */
public CmsPage cmsPageQueryById(String id) {
    Optional<CmsPage> optional = cmsPageRepository.findById(id);
    if (optional.isPresent()) {
        CmsPage cmsPage = optional.get();
        return cmsPage;
    }
    return null;
}

/**
 * 修改页面数据
 */
public CmsPageResult updateCmsPage(String id, CmsPage cmsPage) {
    //判断该页面是否存在
    CmsPage one = this.cmsPageQueryById(id);
    if (one != null) {
        //修改数据为了安全性,这里还是建议每个字段单独设置
        //更新模板id
        one.setTemplateId(cmsPage.getTemplateId());
        //更新所属站点
        one.setSiteId(cmsPage.getSiteId());
        //更新页面别名
        one.setPageAliase(cmsPage.getPageAliase());
        //更新页面名称
        one.setPageName(cmsPage.getPageName());
        //更新访问路径
        one.setPageWebPath(cmsPage.getPageWebPath());
        //更新物理路径
        one.setPagePhysicalPath(cmsPage.getPagePhysicalPath());
        CmsPage save = cmsPageRepository.save(one);
        if(save != null){
            return new CmsPageResult(CommonCode.SUCCESS, save);
        }
    }
    return new CmsPageResult(CommonCode.FAIL, cmsPage);
}

Controller层

代码语言:javascript
复制
/**
* 根据id获取页面数据
*/
public CmsPageResult cmsPageQueryById(String id) {
    Optional<CmsPage> optional = cmsPageRepository.findById(id);
    if (optional.isPresent()) {
        CmsPage cmsPage = optional.get();
        return new CmsPageResult(CommonCode.SUCCESS, cmsPage);
    }
    return new CmsPageResult(CommonCode.FAIL, null);
}


/**
* 修改页面数据
*/
public CmsPageResult updateCmsPage(String id, CmsPage cmsPage) {
    //判断该页面是否存在
    CmsPageResult cmsPageResult = this.cmsPageQueryById(id);
    CmsPage one = cmsPageResult.getCmsPage();
    if (one != null) {
        //修改数据为了安全性,这里还是建议每个字段单独设置
        //更新模板id
        one.setTemplateId(cmsPage.getTemplateId());
        //更新所属站点
        one.setSiteId(cmsPage.getSiteId());
        //更新页面别名
        one.setPageAliase(cmsPage.getPageAliase());
        //更新页面名称
        one.setPageName(cmsPage.getPageName());
        //更新访问路径
        one.setPageWebPath(cmsPage.getPageWebPath());
        //更新物理路径
        one.setPagePhysicalPath(cmsPage.getPagePhysicalPath());
        CmsPage save = cmsPageRepository.save(one);
        if(save != null){
            return new CmsPageResult(CommonCode.SUCCESS, save);
        }
    }
    return new CmsPageResult(CommonCode.FAIL, cmsPage);
}

0x03 修改:前端开发

页面处理流程

可以参考新增的逻辑

  1. 进入页面,通过钩子方法请求服务端获取页面信息,并赋值给数据模型对象。
  2. 页面信息通过数据绑定在表单中显示。
  3. 用户修改信息点击 "提交" 请求服务端修改页面信息接口。

前端HTTP API构建

代码语言:javascript
复制
//查询单个页面信息
export const page_query = (id) =>{
  return http.requestPut(apiUrl + "/cms/page/get/" + id)
}

//更新接口
export const page_update = (id,params) =>{
  return http.requestPut(apiUrl + "/cms/page/edit/" + id,params)
}

//获取站点和模板列表

添加页面路由

代码语言:javascript
复制
import page_update from '@/module/cms/page/page_update.vue';
//子菜单
children: [
    {
        path: '/cms/page/edit/:pageId', name:"编辑页面信息",component: page_edit, 	hidden:true
    },
]

path中的 :pageId 表示在路由url中定义了一个pageId变量

构建前端页面

page_list 页面新增编辑按钮

代码语言:javascript
复制
<el-table-column label="编辑" width="75" fixed="right">
    <template slot-scope="scope">
<el-button size="small" type="info" @click="toEdit(scope.row.pageId)">编辑</el-button>
    </template>
</el-table-column>

增加跳转edit页面的函数

代码语言:javascript
复制
//跳转至编辑页面
toEdit(pageId){
  this.$router.push({
    path:"/cms/page/edit/" + pageId,
    query:{
      page:this.params.page,
      siteId:this.params.siteId
    }
  })
},

使用 scope 获取当前行数据中的 pageId 传入 toEdit 函数进行页面跳转

完成page_edit.vue 页面的基本构造

代码语言:javascript
复制
<template>
  <div>
    <el-form :model="pageForm" label-width="80px" :rules="pageFormRules" ref="pageForm">
      <el-form-item label="所属站点" prop="siteId">
        <el-select v-model="pageForm.siteId" placeholder="请选择站点">
          <el-option
            v-for="item in siteList"
            :key="item.siteId"
            :label="item.siteName"
            :value="item.siteId">
          </el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="选择模版" prop="templateId">
        <el-select v-model="pageForm.templateId" placeholder="请选择">
          <el-option
            v-for="item in templateList"
            :key="item.templateId"
            :label="item.templateName"
            :value="item.templateId">
          </el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="页面名称" prop="pageName">
        <el-input v-model="pageForm.pageName" auto-complete="off"></el-input>
      </el-form-item>
      <el-form-item label="别名" prop="pageAliase">
        <el-input v-model="pageForm.pageAliase" auto-complete="off"></el-input>
      </el-form-item>
      <el-form-item label="访问路径" prop="pageWebPath">
        <el-input v-model="pageForm.pageWebPath" auto-complete="off"></el-input>
      </el-form-item>
      <el-form-item label="物理路径" prop="pagePhysicalPath">
        <el-input v-model="pageForm.pagePhysicalPath" auto-complete="off"></el-input>
      </el-form-item>
      <el-form-item label="类型">
        <el-radio-group v-model="pageForm.pageType">
          <el-radio class="radio" label="0" >静态</el-radio>
          <el-radio class="radio" label="1" >动态</el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item label="创建时间">
        <el-date-picker type="datetime" placeholder="创建时间" v-model="pageForm.pageCreateTime">
        </el-date-picker>
      </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <el-button type="primary" @click="addSubmit">提交</el-button>
      <el-button type="info" @click="goBack">返回</el-button>
    </div>
  </div>
</template>
<script>
  import * as cmsApi from '../api/cms'

  export default {
    created: function () {
    
    },
    mounted() {

    },

    data() {
      return {
        pageFormRules: {
          siteId: [
            {required: true, message: '请选择站点', trigger: 'blur'}
          ],
          templateId: [
            {required: true, message: '请选择模版', trigger: 'blur'}
          ],
          pageName: [
            {required: true, message: '请输入页面名称', trigger: 'blur'}
          ],
          pageAliase: [
            {required: true, message: '请输入页面别名', trigger: 'blur'}
          ],
          pageWebPath: [
            {required: true, message: '请输入访问路径', trigger: 'blur'}
          ],
          pagePhysicalPath: [
            {required: true, message: '请输入物理路径', trigger: 'blur'}
          ]
        },
        //站点列表
        siteList: [],
        //模版列表
        templateList: [],
        //新增界面数据
        pageForm: {
          siteId: '',
          templateId: '',
          pageName: '',
          pageAliase: '',
          pageWebPath: '',
          pageParameter: '',
          pagePhysicalPath: '',
          pageType: '',
          pageCreateTime: new Date()
        }
      }
    },
    methods: {
      //返回上一页
      goBack() {
        this.$router.push({
          path: "/cms/page/list",
          query: {
            page: this.$route.query.page,
            siteId: this.$route.query.siteId
          }
        })
      }
    }
  }
</script>
1、页面初始化

在钩子函数 created 中进行一些数据的初始化请求,例如站点、模板的信息,用于下拉框的选择,以及在打开编辑页面之前,用户需要获取当前编辑的页面原有的数据,所以我们需要使用 page_list 页面通过的 pageId 来获取当前编辑的页面的数据。

代码语言:javascript
复制
created: function () { 
    //初始化站点数据
    cmsApi.site_list().then((res) => {
        if (res.success) {
            console.log("站点数据", res)
            this.siteList = res.queryResult.list
        } else {
            console.log("获取站点信息时发生了错误", res)
        }
    })

    //初始化模板数据
    cmsApi.template_list().then((res) => {
        if (res.success) {
            console.log("模板数据", res)
            this.templateList = res.queryResult.list
        } else {
            console.log("获取模板信息时发生了错误", res)
        }
    })

    //初始化页面的数据
    cmsApi.page_query(this.$route.params.pageId).then((res) =>{

        if(res.success){
            console.log("初始化页面数据",res.cmsPage)
            this.pageForm = res.cmsPage
        }else{
            console.log("初始化页面数据失败",res)
        }
    })
},

我们在页面路由定义了一个:pageId的参数,所以在初始化页面的数据部分从 this.$route.params 取出了pageId

2、提交更新表单
代码语言:javascript
复制
//提交表单
addSubmit() {
    this.$refs.pageForm.validate((valid) => {
        if (valid) {
            this.$confirm('确认提交修改吗?', '提示', {}).then(() => {
                cmsApi.page_edit(this.$route.params.pageId,this.pageForm).then((res) => {
                    if (res.success) {
                        this.$message({
                            message: '修改提交成功',
                            type: 'success'
                        });
                    } else {
                        this.$message.error('提交失败');
                    }
                });
            });
        }
    })
},

四、删除页面

修改页面用户操作流程:

1、用户进入修改页面,在页面上显示了修改页面的信息

2、用户修改页面的内容,点击“提交”,提示“修改成功”或“修改失败”

0x01 后端接口定义

代码语言:javascript
复制
/**
 * 删除接口
 * @param id 页面id
 * @return
 */
@ApiOperation("删除页面")
public ResponseResult delete(String id);

0x02 服务端开发

Dao层

dao层 使用 MongoDB 提供的dao接口来实现

Service层

代码语言:javascript
复制
/**
 * 根据id删除
 * @param id
 * @return
 */
public ResponseResult deleteCmsPage(String id){
    //检索该页面id是否存在
    Optional<CmsPage> optional = cmsPageRepository.findById(id);
    if(optional.isPresent()){
        //删除并返回结果
        cmsPageRepository.deleteById(id);
        return new ResponseResult(CommonCode.SUCCESS);
    }
    return new ResponseResult(CommonCode.FAIL);
}

Controller层

代码语言:javascript
复制
/**
 * 删除页面
 * @param id 页面id
 * @return
 */
@DeleteMapping("/delete/{id}")
@Override
public ResponseResult delete(@PathVariable("id") String id) {
    return pageService.deleteCmsPage(id);
}

接口测试

0x03 前端开发

页面编写

1、在 page_list 页面内新增一个删除按钮

代码语言:javascript
复制
<el-table-column label="删除" width="75" fixed="right">
  <template slot-scope="scope">
    <el-button
      size="mini"
      type="danger"
      @click="deletePage(scope.row.pageId)">删除
    </el-button>
  </template>
</el-table-column>

2、在 cms.js 定义删除的api

代码语言:javascript
复制
//删除接口
export const page_delete = (id) =>{
  return http.requestDelete(apiUrl + "/cms/page/delete/" + id)
}

3、创建 deletePage 函数,接收到 当前行的 pageId

代码语言:javascript
复制
//删除页面
deletePage(pageId) {
  this.$confirm("此操作将永久的删除该页面", "提示", {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    cmsApi.page_delete(pageId).then(res => {
      if (res.success) {
        this.$message({
          type: 'success',
          message: '删除成功!'
        })
        this.query()
      } else {
        this.$message({
          type: 'warning',
          message: '删除失败'
        });
      }
    })
  }).catch(() => {
    this.$message({
      type: 'info',
      message: '已取消删除'
    });
  });
},

测试

五、异常处理

0x01 异常处理的问题分析

从添加页面的service方法中找问题:

代码语言:javascript
复制
/**
* 添加页面数据
*/
public CmsPageResult addCmsPage(CmsPage cmsPage) {
    //验证数据唯一性:sizeId、pageName、pageWebPath
    CmsPage cmsPage1 = cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
    if (cmsPage1 == null) {
        //站点id由mongoDB自动生成,防止前端传值
        cmsPage.setPageId(null);
        CmsPage save = cmsPageRepository.save(cmsPage);
        return new CmsPageResult(CommonCode.SUCCESS, save);
    }
    //添加失败
    return new CmsPageResult(CommonCode.FAIL, cmsPage);
}

问题

1、上边的代码只要操作不成功仅向用户返回“错误代码:11111,失败信息:操作失败”,无法区别具体的错误信息。

2、service 方法在执行过程出现异常在哪捕获?在 service 中需要都加 try/catch,如果在controller 也需要添加 try/catch,代码冗余严重且不易维护。

解决方案

1、在 Service 方法中的编码顺序是先校验判断,有问题则抛出具体的异常信息,最后执行具体的业务操作,返回成功信息。

2、在统一异常处理类中去捕获异常,无需controller捕获异常,向用户返回统一规范的响应信息。

我们的代码应该是这样的

代码语言:javascript
复制
/**
 * 添加页面数据
 */
public CmsPageResult addCmsPage(CmsPage cmsPage) {
    //效验cmsPage是否为空
    if(cmsPage == null){
        //抛出异常,非法参数

    }
    
    //验证数据唯一性:sizeId、pageName、pageWebPath
    CmsPage cmsPage1 = cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
    
    //检验页面是否已存在
    if (cmsPage1 != null) {
        //抛出异常

    }

    //站点id由mongoDB自动生成,防止前端传值
    cmsPage.setPageId(null);
    CmsPage save = cmsPageRepository.save(cmsPage);
    return new CmsPageResult(CommonCode.SUCCESS, save);
    //添加失败
}

在执行正常的逻辑之前,要把已知的异常进行验证,验证全部通过后才会去执行正常的逻辑代码。

0x02 异常处理流程

系统对异常的处理使用统一的异常处理流程:

1、自定义异常类型。

2、自定义错误代码及错误信息。

3、对于可预知的异常由程序员在代码中主动抛出,由 SpringMVC 统一捕获。

可预知异常是程序员在代码中手动抛出本系统定义的特定异常类型,由于是程序员抛出的异常,通常异常信息比较齐全,程序员在抛出时会指定错误代码及错误信息,获取异常信息也比较方便。

4、对于不可预知的异常(运行时异常)由SpringMVC统一捕获 Exception 类型的异常。

不可预知异常通常是由于系统出现bug、或一些不要抗拒的错误(比如网络中断、服务器宕机等),异常类型为 RuntimeException类型(运行时异常)。

5、可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。

异常抛出及处理流程:

1、在 controllerservicedao 中程序员抛出自定义异常;springMVC 框架抛出框架异常类型

2、统一由异常捕获类捕获异常,并进行处理

3、捕获到自定义异常则直接取出错误代码及错误信息,响应给用户

4、捕获到非自定义异常类型首先从 Map 中找该异常类型是否对应具体的错误代码,如果有则取出错误代码和错误信息并响应给用户,如果从 Map 中找不到异常类型所对应的错误代码则统一为 99999 错误代码并响应给用户。

5、将错误代码及错误信息以 Json 格式响应给用户。

0x03 可预知异常处理

自定义异常类

在common工程定义异常类型。

代码语言:javascript
复制
package com.xuecheng.framework.exception;

import com.xuecheng.framework.model.response.ResultCode;

public class CustomException extends RuntimeException {

    private ResultCode resultCode;

    public CustomException(ResultCode resultCode){
        //异常信息为错误代码+异常信息
        super("错误代码: " + resultCode.code() + " 错误信息: " + resultCode.message());
        this.resultCode = resultCode;
    }

    public ResultCode getResultCode(){
        return this.resultCode;
    }
}

上面的代码中我们自定义了一个名为 CustomException 的异常,并且继承了 RuntimeException 异常类,有的人可能会问,我们为什么不直接继承 Exception 类?因为如果继承了 Exception 类,我们在抛出异常时对代码会有一定的侵入性,例如我们需要在抛出该异常的方法前加入 throws Exception ,例如

代码语言:javascript
复制
public CmsPageResult addCmsPage(CmsPage cmsPage) throws Exception{
    
}

或者使用

代码语言:javascript
复制
try {
    //抛出异常,非法参数
} catch (Exception e) {
    e.printStackTrace();
}

而我们如果使用 RuntimeException ,至需要在抛出异常的地方写入下面代码就能抛出我们自定义的异常

代码语言:javascript
复制
throw new CustomException(resultCode);

抛出异常类

代码语言:javascript
复制
package com.xuecheng.framework.exception;

import com.xuecheng.framework.model.response.ResultCode;

public class ExceptionCast {

    //使用此静态方法抛出自定义异常
    public static void cast(ResultCode resultCode) {
        throw new CustomException(resultCode);
    }
}

异常捕获类

使用 @ControllerAdvice和@ExceptionHandler注解来捕获指定类型的异常

代码语言:javascript
复制
package com.xuecheng.framework.exception;

import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.model.response.ResultCode;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ControllerAdvice
public class ExceptionCatch {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);

    //捕获 CustomException 异常
    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public ResponseResult customException(CustomException e){
        LOGGER.error("catch exception:{}\r\nException:",e.getMessage(),e);
        ResultCode resultCode = e.getResultCode();
        ResponseResult responseResult = new ResponseResult(resultCode);
        return responseResult;
    }
}

异常处理测试

1)定义错误代码

每个业务操作的异常使用异常代码去标识。

代码语言:javascript
复制
package com.xuecheng.framework.model.response;
import lombok.ToString;

@ToString
public enum CmsCode implements ResultCode {

    CMS_ADDPAGE_EXISTS(false,24001,"页面已存在!");

    //操作结果
    boolean success;

    //操作代码
    int code;

    //提示信息
    String message;

    private CmsCode(boolean success, int code, String message){
        this.success = success;
        this.code = code;
        this.message = message;
    }

    @Override
    public boolean success() {
        return success;
    }

    @Override
    public int code() {
        return code;
    }

    @Override
    public String message() {
        return message;
    }
}
2)异常处理测试

1、抛出异常

回到我们 PageService 中的 addCmsPage 抛出异常

代码语言:javascript
复制
//检验页面是否已存在
if (cmsPage1 != null) {
    //抛出异常
    ExceptionCast.cast(CmsCode.CMS_ADDPAGE_EXISTS);
}

2、在启动工程中扫描异常捕获类

代码语言:javascript
复制
@ComponentScan(basePackages = {"com.xuecheng.framework"})  // 扫描framework下的异常捕获类
public class ManageCmsApplication {
    public static void main(String[] args) {
        SpringApplication.run(ManageCmsApplication.class,args);
    }
}

3、前端展示异常信息

在前端新增页面的表单提交中增加判断

代码语言:javascript
复制
else if(res.message){
	this.$message.error(res.message)
}

全部代码如下

代码语言:javascript
复制
//提交表单
addSubmit() {
  this.$refs.pageForm.validate((valid) => {
    if (valid) {
      this.$confirm('确认提交吗?', '提示', {}).then(() => {
        cmsApi.page_add(this.pageForm).then((res) => {
          console.log(res);
          if (res.success) {
            this.$message({
              message: '提交成功',
              type: 'success'
            });
            this.$refs['pageForm'].resetFields();
          } else if(res.message){
            this.$message.error(res.message)
          }
          else {
            this.$message.error('提交失败');
          }
        });
      });
    }
  })
},

测试

0x04 不可预知的异常处理

1)定义异常捕获方法
抛出异常测试

使用postman测试添加页面,不输入cmsPost信息,提交,报错信息如下:

代码语言:javascript
复制
org.springframework.http.converter.HttpMessageNotReadableException
此异常是springMVC在进行参数转换时报的错误。

{
    "timestamp": 1528712906727,
    "status": 400,
    "error": "Bad Request",
    "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
    "message": "Required request body is missing: public
    com.xuecheng.framework.domain.cms.response.CmsPageResult
    com.xuecheng.manage_cms.web.controller.CmsPageController.add(com.xuecheng.framework.domain.cms.C
    msPage)",
    "path": "/cms/page/add"
}

上边的响应信息在客户端是无法解析的。

在异常捕获类 ExceptionCatch 中添加对Exception异常的捕获,下面大致的代码结构:

代码语言:javascript
复制
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseResult exception(Exception exception){
    //记录日志
    LOGGER.error("catch exception:{}",exception.getMessage());
    return null;
}
异常捕获方法

针对上边的问题其解决方案是:

  1. 我们在map中配置HttpMessageNotReadableException和错误代码。
  2. 在异常捕获类中对Exception异常进行捕获,并从map中获取异常类型对应的错误代码,如果存在错误代码则返回此错误,否则统一返回99999错误。

具体的开发实现如下:

1、在通用错误代码类CommCode中配置非法参数异常

代码语言:javascript
复制
@ToString
public enum CommonCode implements ResultCode{
    INVALID_PARAM(false,10003,"非法参数!"),
    
    /**其他代码省略**/
}

2、在异常捕获类中配置 HttpMessageNotReadableException 为非法参数异常。

具体的说明我都写在注释当中了,就不多做解释,直接看代码

代码语言:javascript
复制
package com.xuecheng.framework.exception;

import com.google.common.collect.ImmutableMap;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.model.response.ResultCode;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.constraints.Null;

@ControllerAdvice
public class ExceptionCatch {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);

    //使用EXCEPTIONS存放异常的错误代码和映射,ImmutableMap的特点是一旦创建则不可改变,并且线程安全
    private static ImmutableMap<Class<? extends Throwable>, ResultCode> EXCEPTIONS;
    //使用 Builder 来构建一个异常类型和错误代码的异常
    protected static ImmutableMap.Builder<Class<? extends Throwable>, ResultCode> builder = ImmutableMap.builder();

    //捕获Exception异常
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public ResponseResult exception(Exception e){
        LOGGER.error("catch exception : {}\r\nexception: ",e.getMessage(), e);
        if(EXCEPTIONS == null){
            EXCEPTIONS = builder.build();
        }
        //从 EXCEPTIONS 中取出对于的预定义错误码,如果不存在则为null
        final ResultCode resultCode = EXCEPTIONS.get(e.getClass());
        final ResponseResult responseResult;
        if(resultCode != null){
            responseResult = new ResponseResult(resultCode);
        }else{
            //如果非预定义的错误,则返回服务器错误
            responseResult = new ResponseResult(CommonCode.SERVER_ERROR);
        }
        return responseResult;
    }

    //捕获 customException 自定义异常
    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public ResponseResult customException(CustomException e){
        LOGGER.error("catch exception:{}\r\nException:",e.getMessage(),e);
        ResultCode resultCode = e.getResultCode();
        ResponseResult responseResult = new ResponseResult(resultCode);
        return responseResult;
    }

    static{
        //在这里加入一些基础异常类型的判断
        builder.put(HttpMessageNotReadableException.class, CommonCode.INVALID_PARAM);
    }
}

我们来测试一下,是否能成功捕抓到该异常

我们改成 GET 请求进行测试,由于我们没有预定这种异常的错误代码,所以统一返回99999错误代码

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ? 知识点概览
  • 一、自定义条件查询
    • 0x01 需求分析
      • 0x02 服务端
        • Dao层
        • 单元测试
        • 使用SwaggerUI测试
      • 0x03 前端
        • 页面
        • API调用
    • 二、新增页面
      • 0x01 准备工作,站点和模板API
        • Dao层
        • 定义查询模型
        • 定义响应模型
        • Service层
        • Controller层
      • 0x02 后端接口定义
        • 1、定义响应模型
        • 2、定义添加Api
      • 0x03 服务端开发
        • 1、页面唯一值索引
        • 2、Dao层
        • 3、Service层
        • 4、Controller层
        • 5、接口测试
      • 0x04 前端开发
        • 构建新增页面
        • API调用
    • 三、修改页面
      • 0x01 后端接口定义
        • 0x02 服务端开发
          • Dao层
          • Service层
          • Controller层
        • 0x03 修改:前端开发
          • 页面处理流程
          • 前端HTTP API构建
          • 添加页面路由
          • 构建前端页面
      • 四、删除页面
        • 0x01 后端接口定义
          • 0x02 服务端开发
            • Dao层
            • Service层
            • Controller层
            • 接口测试
          • 0x03 前端开发
            • 页面编写
            • 测试
        • 五、异常处理
          • 0x01 异常处理的问题分析
            • 问题
            • 解决方案
          • 0x02 异常处理流程
            • 0x03 可预知异常处理
              • 自定义异常类
              • 抛出异常类
              • 异常捕获类
              • 异常处理测试
            • 0x04 不可预知的异常处理
            相关产品与服务
            云数据库 MongoDB
            腾讯云数据库 MongoDB(TencentDB for MongoDB)是腾讯云基于全球广受欢迎的 MongoDB 打造的高性能 NoSQL 数据库,100%完全兼容 MongoDB 协议,支持跨文档事务,提供稳定丰富的监控管理,弹性可扩展、自动容灾,适用于文档型数据库场景,您无需自建灾备体系及控制管理系统。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档