为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。
本章节为【学成在线】项目的 day03
的内容
Spring data
提供的 PageRequest
模块进行分页查询的应用Spring data
提供 MongoDB
的dao接口进行前后端联调的 CRUD 操作VUE.JS
的前端模块化开发RESTful
风格的API开发Swagger
进行接口文档的生成与测试在页面输入查询条件,查询符合条件的页面信息。
查询条件如下:
站点Id:精确匹配
模板Id:精确匹配
页面别名:模糊匹配
使用 CmsPageRepository
中的 findAll(Example<S> var1, Pageable var2)
方法实现,无需定义。
下边测试findAll方法实现自定义条件查询:
1、指定站点id、模板id作为查询条件
//自定义条件查询
@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
实例的一些属性作为模糊查询的参数,增加的代码如下
cmsPage.setPageAliase("详细");
//条件匹配器,用于模糊匹配
ExampleMatcher matching = ExampleMatcher.matching()
.withMatcher("pageAliase",ExampleMatcher.GenericPropertyMatchers.contains());
.withMatcher
的第一个参数表示要将哪个字段进行匹配,第二个则是要使用的匹配器;
ExampleMatcher.GenericPropertyMatchers
有多个匹配器,这里我们用 .contains()
进行模糊匹配
@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层无需修改
参数
查询结果
从查询结果中我们可以看出,根据我们输入的条件,查询到了指定 sizeId
并且 pageAliase
包含预览的信息。
1、增加查询表单
在el-table上方添加该表单
<!--查询表单-->
<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、新增数据模型对象
data() {
return {
siteList:[],//站点列表
list:[],
total:0,
params:{
siteId:'',
pageAliase:'',
page:1,//页码
size:10//每页显示个数
}
}
},
3、在钩子方法中 获取 siteList
站点列表(这里暂时用静态数据代替)
mounted() {
//默认查询页面
this.query()
//初始化站点列表
this.siteList = [
{
siteId:'5a751fab6abb5044e0d19ea1',
siteName:'门户主站'
},
{
siteId:'102',
siteName:'测试站'
}
]
},
1、向服务端传递查询条件,修改 cms.js,如下:
//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方法
//查询
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、测试
在配置新增页面的功能之前,我们先配置两个接口,用于获取站点和模板的信息
CmsSizeRepository
/**
* 继承MongoDB自带的Repository
*/
public interface CmsSizeRepository extends MongoRepository<CmsSite,String> {
}
CmsTemplateRepository
/**
* 继承MongoDB自带的Repository
*/
public interface CmsTemplateRepository extends MongoRepository<CmsTemplate,String> {
}
QuerySizeRequest
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
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
@Data
public class CmsSizeResult extends ResponseResult {
CmsSite cmsSite;
public CmsSizeResult(ResultCode resultCode, CmsSite cmsSite) {
super(resultCode);
this.cmsSite = cmsSite;
}
}
CmsTemplateResult
@Data
public class CmsTemplateResult extends ResponseResult {
CmsTemplate cmsTemplate;
public CmsTemplateResult(ResultCode resultCode, CmsTemplate cmsTemplate) {
super(resultCode);
this.cmsTemplate = cmsTemplate;
}
}
SiteService
@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);
}
}
单元测试,测试是否能正常拿到数据
@SpringBootTest
@RunWith(SpringRunner.class)
public class CmsSizeRespositoryTest {
@Autowired
SiteService siteService;
/**
* 测试查询所有站点数据
*/
@Test
public void testFindAll(){
QueryResponseResult list = siteService.findList();
System.out.println(list);
}
}
TemplateService
@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);
}
}
定义站点相关操作的api
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
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
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
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;
}
}
测试
用于接口的响应规范,继承于项目中的ResponseResult
@Data
public class CmsPageResult extends ResponseResult {
CmsPage cmsPage;
public CmsPageResult(ResultCode resultCode,CmsPage cmsPage) {
super(resultCode);
this.cmsPage = cmsPage;
}
}
在 CmsPageControllerApi
中新增 addCmsPage
接口
/**
* 添加页面数据
*/
@ApiOperation("添加页面数据")
@ApiImplicitParams({
@ApiImplicitParam(name="cmsPage",value = "请提交json形式的页面数据",required=true,paramType="CmsPage",dataType="CmsPage"),
})
public CmsPageResult addCmsPage(CmsPage cmsPage);
为了保证站点的唯一性,所以我们要根据 sizeId、pageName、pageWebPath 这三个字段来决定站点的数据是否唯一。
右键 cms_page
集合,选择 Add Index
添加一个索引
根据下图所示,点击 Add Field
按钮,选择 sizeId、pageName、pageWebPath 这三个字段,然后点击添加,选择第三步所示的 Unique
,最后点击右边的 Create Index
创建索引。
创建成功后
1、添加根据页面名称、站点Id、页面webpath查询页面方法,此方法用于校验页面是否存在
/**
* 根据站点id、站点名称、站点路径来查询站点信息
* @return
* @param siteId
* @param pageName
* @param pageWebPath
*/
CmsPage findBySiteIdAndPageNameAndPageWebPath(String siteId, String pageName, String pageWebPath);
2、使用 CmsPageRepository提供的save方法 。
/**
* 添加页面数据
*/
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);
}
@Override
@PostMapping("/add")
public CmsPageResult addCmsPage(@RequestBody CmsPage cmsPage) {
return pageService.addCmsPage(cmsPage);
}
我们在 swagger
自动生成的文档接口中进行测试
第一次添加,添加成功
第二次添加重复的内容,由于唯一性的效验,返回添加失败
使用Element-UI的form组件编写添加表单内容,页面效果如下:
1)创建页面
创建page_add.vue页面
2)配置路由
在cms模块的路由文件中配置“添加页面”的路由:
{path:'/cms/page/add',name:'新增页面',component: page_add,hidden:true}
注意:由于 “添加页面” 不需要显示为一个菜单,这里 hidden
设置为 true
隐藏菜单。
测试,在浏览器地址栏输入http://localhost:11000/#/cms/page/add
3)“添加页面” 的按钮
实际情况是用户进入页面查询列表,点击“新增页面”按钮进入新增页面窗口。
在查询按钮的旁边添加:
<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)完善页面内容
<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说明:
Form-Item Attributes说明:
详情属性及事件参考 http://element.eleme.io/#/zh-CN/component/form
5)数据对象
//站点列表
siteList: [],
//模版列表
templateList: [],
//新增界面数据
pageForm: {
siteId: '',
templateId: '',
pageName: '',
pageAliase: '',
pageWebPath: '',
pageParameter: '',
pagePhysicalPath: '',
pageType: '0', //默认选中0
pageCreateTime: new Date()
}
}
},
methods: {
addSubmit() {
alert("提交")
},
}
6)站点及模板数据
定义请求api
//获取所有站点信息
export const site_list = () =>{
return http.requestGet(apiUrl + "/cms/site/list")
}
//获取所有模板信息
export const template_list = () =>{
return http.requestGet(apiUrl + "/cms/template/list")
}
在created钩子中定义,原讲义内使用的是静态数据
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)测试预览
进入新增页面后只能通过菜单再次进入页面列表,可以在新增页面添加“返回”按钮,点击返回按钮返回到页面列表。
<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上定义返回按钮
<el‐button type="primary" @click="go_back" >返回</el‐button>
在page_add.vue上定义返回方法
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
created() {
//从路由上获取参数
this.params.page = Number.parseInt(this.$route.query.page||1);
this.params.siteId = this.$route.query.siteId||'';
.....
}
小技巧:使用 ||返回第一个有效值
1)配置校验规则
Element-UI的Form组件提供表单校验的方法:
在form属性上配置rules(表单验证规则)
<el‐form :model="pageForm" :rules="pageFormRules" label‐width="80px" >
在数据模型中配置校验规则:
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")在校验时引用此表单对象
<el‐form :model="pageForm" :rules="pageFormRules" label‐width="80px" ref="pageForm">
在提交表单时执行校验
addSubmit() {
this.$refs.pageForm.validate((valid) => {
if (valid) {
alert('提交');
} else {
alert('校验失败');
return false;
}
})
},
测试
export const page_add = params =>{
return http.requestPost(apiUrl + "/cms/page/add",params)
}
完整的代码如下:
//提交表单
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组件:
element-ui
的 message-box
组件弹出确认提交窗口(http://element.eleme.io/#/zhCN/component/message-box)
message
组件提示操作结果 (http://element.eleme.io/#/zh-CN/component/message)
修改页面用户操作流程:
1、用户进入修改页面,在页面上显示了修改页面的信息
2、用户修改页面的内容,点击“提交”,提示“修改成功”或“修改失败”
修改页面需要定义的API如下:
/**
* 通过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方法对页面修改进行修改。
使用 Spring Data提供的findById方法完成根据主键查询 。 使用 Spring Data提供的save方法完成数据保存 。
/**
* 根据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);
}
/**
* 根据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);
}
可以参考新增的逻辑
//查询单个页面信息
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)
}
//获取站点和模板列表
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
页面新增编辑按钮
<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页面的函数
//跳转至编辑页面
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
页面的基本构造
<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>
在钩子函数 created
中进行一些数据的初始化请求,例如站点、模板的信息,用于下拉框的选择,以及在打开编辑页面之前,用户需要获取当前编辑的页面原有的数据,所以我们需要使用 page_list
页面通过的 pageId
来获取当前编辑的页面的数据。
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
//提交表单
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、用户修改页面的内容,点击“提交”,提示“修改成功”或“修改失败”
/**
* 删除接口
* @param id 页面id
* @return
*/
@ApiOperation("删除页面")
public ResponseResult delete(String id);
dao
层 使用 MongoDB
提供的dao接口来实现
/**
* 根据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);
}
/**
* 删除页面
* @param id 页面id
* @return
*/
@DeleteMapping("/delete/{id}")
@Override
public ResponseResult delete(@PathVariable("id") String id) {
return pageService.deleteCmsPage(id);
}
1、在 page_list
页面内新增一个删除按钮
<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
//删除接口
export const page_delete = (id) =>{
return http.requestDelete(apiUrl + "/cms/page/delete/" + id)
}
3、创建 deletePage
函数,接收到 当前行的 pageId
//删除页面
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: '已取消删除'
});
});
},
从添加页面的service方法中找问题:
/**
* 添加页面数据
*/
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
捕获异常,向用户返回统一规范的响应信息。
我们的代码应该是这样的
/**
* 添加页面数据
*/
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);
//添加失败
}
在执行正常的逻辑之前,要把已知的异常进行验证,验证全部通过后才会去执行正常的逻辑代码。
系统对异常的处理使用统一的异常处理流程:
1、自定义异常类型。
2、自定义错误代码及错误信息。
3、对于可预知的异常由程序员在代码中主动抛出,由 SpringMVC
统一捕获。
可预知异常是程序员在代码中手动抛出本系统定义的特定异常类型,由于是程序员抛出的异常,通常异常信息比较齐全,程序员在抛出时会指定错误代码及错误信息,获取异常信息也比较方便。
4、对于不可预知的异常(运行时异常)由SpringMVC
统一捕获 Exception
类型的异常。
不可预知异常通常是由于系统出现bug、或一些不要抗拒的错误(比如网络中断、服务器宕机等),异常类型为 RuntimeException
类型(运行时异常)。
5、可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。
异常抛出及处理流程:
1、在 controller
、service
、dao
中程序员抛出自定义异常;springMVC
框架抛出框架异常类型
2、统一由异常捕获类捕获异常,并进行处理
3、捕获到自定义异常则直接取出错误代码及错误信息,响应给用户
4、捕获到非自定义异常类型首先从 Map
中找该异常类型是否对应具体的错误代码,如果有则取出错误代码和错误信息并响应给用户,如果从 Map
中找不到异常类型所对应的错误代码则统一为 99999
错误代码并响应给用户。
5、将错误代码及错误信息以 Json
格式响应给用户。
在common工程定义异常类型。
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
,例如
public CmsPageResult addCmsPage(CmsPage cmsPage) throws Exception{
}
或者使用
try {
//抛出异常,非法参数
} catch (Exception e) {
e.printStackTrace();
}
而我们如果使用 RuntimeException
,至需要在抛出异常的地方写入下面代码就能抛出我们自定义的异常
throw new CustomException(resultCode);
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注解来捕获指定类型的异常
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;
}
}
每个业务操作的异常使用异常代码去标识。
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;
}
}
1、抛出异常
回到我们 PageService
中的 addCmsPage
抛出异常
//检验页面是否已存在
if (cmsPage1 != null) {
//抛出异常
ExceptionCast.cast(CmsCode.CMS_ADDPAGE_EXISTS);
}
2、在启动工程中扫描异常捕获类
@ComponentScan(basePackages = {"com.xuecheng.framework"}) // 扫描framework下的异常捕获类
public class ManageCmsApplication {
public static void main(String[] args) {
SpringApplication.run(ManageCmsApplication.class,args);
}
}
3、前端展示异常信息
在前端新增页面的表单提交中增加判断
else if(res.message){
this.$message.error(res.message)
}
全部代码如下
//提交表单
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('提交失败');
}
});
});
}
})
},
测试
使用postman测试添加页面,不输入cmsPost信息,提交,报错信息如下:
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异常的捕获,下面大致的代码结构:
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseResult exception(Exception exception){
//记录日志
LOGGER.error("catch exception:{}",exception.getMessage());
return null;
}
针对上边的问题其解决方案是:
具体的开发实现如下:
1、在通用错误代码类CommCode中配置非法参数异常
@ToString
public enum CommonCode implements ResultCode{
INVALID_PARAM(false,10003,"非法参数!"),
/**其他代码省略**/
}
2、在异常捕获类中配置 HttpMessageNotReadableException 为非法参数异常。
具体的说明我都写在注释当中了,就不多做解释,直接看代码
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错误代码