一、CMS前端页面查询(分页)开发
1.1 页面原型
1.1.1 创建页面
1.1.1.1 页面结构
在model目录创建cms模块的目录结构

在page目录创建pageList.vue:
<template>
<textarea>测试</textarea>
</template>
<script>
export default {
name: 'page_list'
}
</script>
<style scoped>
</style>
1.1.1.2 页面路由
在cms的router下配置路由
import Home from '@/module/home/page/home.vue'
import pageList from '@/module/cms/page/pageList.vue'
export default [{
path: '/',
component: Home,
name: 'CMS内容管理',
hidden: false,
children: [
{path: '/cms/page/list', name: '页面列表', component: pageList, hidden: false}
]
}]
在base目录下的router导入cms模块的路由
import Vue from 'vue'
import Router from 'vue-router'
// 导入路由规则
import HomeRouter from '@/module/home/router'
import CMSRouter from '@/module/cms/router'
Vue.use(Router)
// 定义路由配置
let routes = []
let concat = (router) => {
routes = routes.concat(router)
}
// 合并路由规则
concat(HomeRouter) // 加入home模块的路由
concat(CMSRouter) // 加入CMS模块的路由
export default routes
测试:

1.1.2 页面搭建
1.1.2.1 Element-UI介绍
本项目使用Element-UI来构建界面,Element是一套为开发者、设计师和产品经理准备的基于Vue2.0的桌面端组件库。
1.1.2.2 页面UI构建
需要一个data table和分页条:
<template>
<div>
<el-button type="primary" v-on:click="query" size="small">查询</el-button>
<!--data table-->
<el-table :data="list" highlight-current-row v-loading="listLoading" style="width: 100%;">
<el-table-column type="index" width="60">
</el-table-column>
<el-table-column prop="pageName" label="页面名称" width="120">
</el-table-column>
<el-table-column prop="pageAliase" label="别名" width="120">
</el-table-column>
<el-table-column prop="pageType" label="类型(静态、动态)" width="180">
</el-table-column>
<el-table-column prop="pageWebPath" label="访问路径" width="250">
</el-table-column>
<el-table-column prop="pagePhysicalPath" label="物理路径" width="250">
</el-table-column>
<el-table-column prop="pageCreateTime" label="创建时间" width="180">
</el-table-column>
<el-table-column label="编辑" width="80">
<template>
<el-button
size="small" type="primary"
>编辑
</el-button>
</template>
</el-table-column>
<el-table-column label="删除" width="80">
<template>
<el-button
size="mini"
type="danger"
>删除
</el-button>
</template>
</el-table-column>
<el-table-column label="静态化" width="100">
<template >
<el-button
size="small" type="primary" plain >静态化
</el-button>
</template>
</el-table-column>
<el-table-column label="发布" width="80">
<template >
<el-button
size="small" type="primary" plain >发布
</el-button>
</template>
</el-table-column>
</el-table>
<!--分页条-->
<el-col :span="24" class="toolbar">
<el-pagination background layout="prev, pager, next" :page-size="this.params.size"
:total="total" :current-page="this.params.page" @current-change="changePage"
style="float:right;">
</el-pagination>
</el-col>
</div>
</template>
<script>
export default {
name: 'pageList',
data () {
return {
params: {
page: 1, // 页码
size: 2, // 每页显示个数
siteId: ''// 站点id
},
listLoading: false,
list: [],
total: 10,
siteList: [] // 站点列表
}
},
methods: {
query () {
console.log('123')
},
changePage () {
this.query()
}
}
}
</script>
<style scoped>
</style>
效果:

1.2 API调用
1.2.1 API方法定义
在cms模块的api目录下定义cms.js
在cms.js中定义如下js方法,此方法实现http请求服务端页面查询接口。
// public是对axios的工具类封装,定义了http请求方法
import http from './../../../base/api/public'
export const pageList = (page, size, params) => {
return http.requestQuickGet('http://localhost:31001/cms/page/list/' + page + '/' + size)
}
axios实现了http方法的封装,vue.js官方不再继续维护vue-resource,推荐使用 axios。
1.2.2 API调用
前端页面导入cms.js,调用js方法请求服务端页面查询接口。
import * as cmsApi from '../api/cms'
query方法中调用:
query () {
cmsApi.pageList(this.params.page,this.params.size,this.params).then((resp) => {
console.log(resp)
this.total = resp.total
this.list = resp.list
})
},
测试:

跨域问题!!!!!!!!!!!
1.3 跨域问题解决
使用proxyTable解决
vue-cli提供的解决vue开发环境下跨域问题的方法,proxyTable的底层使用了http-proxy-middleware(https://github.com/chimurai/http-proxy-middleware),它是http代理中间件,它依赖node.js,基本原理就是使用服务端代理解决浏览器跨域,通过proxyTable由node服务器代理请求(服务端不存在跨域问题)。具体配置如下:
1.修改api方法中url的定义
请求前加/api前缀:
// public是对axios的工具类封装,定义了http请求方法
import http from './../../../base/api/public'
let sysConfig = require('@/../config/sysConfig')
let apiUrl = sysConfig.xcApiUrlPre
export const pageList = (page, size, params) => {
return http.requestQuickGet(apiUrl + '/cms/page/list/' + page + '/' + size)
}
在config下的index.js中配置proxyTable
所有以/api/cms开头的请求,全部代理到http://localhost:31001
'/api/cms/': {
target: 'http://localhost:31001',
pathRewrite: {
'^/api': '' //实际请求的时候去掉/api
}
}
结果:


1.4 分页查询
页面切换,立即请求数据
在分页条中添加监听事件:
<el-col :span="30" class="toolbar">
<el-pagination background layout="prev, pager, next" :page-size="this.params.size"
:total="total" :current-page="this.params.page" @current-change="changePage"
style="float:right;">
</el-pagination>
</el-col>
在changePage方法中接收当前页码:
changePage (page) {
this.params.page = page
this.query()
},
1.5 页面加载时查询
利用vue的钩子函数
mounted () {
this.query()
},
1.6 前后端请求流程小结

二、CMS前端页面查询(自定义)开发
2.1 需求分析
在页面输入查询条件,查询符合条件的页面信息
查询条件如下:
站点Id:精确匹配
模板Id:精确匹配
页面别名:模糊匹配
2.2 服务端
2.1.1 Dao
使用 CmsPageRepository中的findAll(Example var1, Pageable var2)方法实现,无需定义。
测试:
@Test
public void testFindAll(){
int page = 0;
int size = 10;
Pageable pageable = PageRequest.of(page, size);
//条件值对象
CmsPage cmsPage = new CmsPage();
//查询条件
//cmsPage.setSiteId("5a751fab6abb5044e0d19ea1");
cmsPage.setPageAliase("轮播");
//定义匹配器
ExampleMatcher matcher = ExampleMatcher.matching();
matcher = matcher.withMatcher("pageAliase", ExampleMatcher.GenericPropertyMatchers.contains());
//模糊查询匹配规则
// ExampleMatcher.GenericPropertyMatchers.contains(); //包含
// ExampleMatcher.GenericPropertyMatchers.startsWith(); //前缀匹配
// ExampleMatcher.GenericPropertyMatchers.endsWith(); //末尾匹配
Example<CmsPage> example = Example.of(cmsPage,matcher);
Page<CmsPage> cmsPages = cmsPageRepository.findAll(example, pageable);
List<CmsPage> content = cmsPages.getContent();
System.out.println(content);
}
自定义查询直接使用Spring Data JPA提供的Example来完成,模糊查询使用ExampleMatcher,具体的匹配规则在ExampleMatcher.GenericPropertyMatchers下。
2.1.2 Service
/**
*
* @param page 页码
* @param size 页大小
* @param queryPageRequest 具体请求参数
* @return 分页列表
*/
@Override
public QueryResponseResult queryByPage(int page, int size, QueryPageRequest queryPageRequest) {
if (queryPageRequest == null){
queryPageRequest = new QueryPageRequest();
}
//1.自定义查询条件匹配器
//1.1 页面别名模糊查询
ExampleMatcher matcher = ExampleMatcher.matching().withMatcher("pageAliase", ExampleMatcher.GenericPropertyMatchers.contains())
.withMatcher("pageType", ExampleMatcher.GenericPropertyMatchers.exact())
.withMatcher("pageName", ExampleMatcher.GenericPropertyMatchers.contains());
//1.2 封装自定义查询对象
CmsPage cmsPage = new CmsPage();
//站点Id
if (StringUtils.isNotEmpty(queryPageRequest.getSiteId())){
cmsPage.setSiteId(queryPageRequest.getSiteId());
}
//页面别名
if (StringUtils.isNotEmpty(queryPageRequest.getPageAlias())){
cmsPage.setPageAliase(queryPageRequest.getPageAlias());
}
//模板Id
if (StringUtils.isNotEmpty(queryPageRequest.getTemplateId())){
cmsPage.setTemplateId(queryPageRequest.getTemplateId());
}
//页面名称
if (StringUtils.isNotEmpty(queryPageRequest.getPageName())){
cmsPage.setPageName(queryPageRequest.getPageName());
}
//页面Id
if (StringUtils.isNotEmpty(queryPageRequest.getPageId())){
cmsPage.setPageId(queryPageRequest.getPageId());
}
//2.构造分页对象
if (page <= 0){
page = 1;
}
//mongodb的页数从0开始
page -= 1;
if (size <= 0){
size = 10;
}
Pageable pageable = PageRequest.of(page, size);
//3.创建条件实例
Example<CmsPage> example = Example.of(cmsPage, matcher);
//4.查询
Page<CmsPage> pages = this.cmsPageRepository.findAll(example, pageable);
//5.返回结果组装
QueryResult<CmsPage> queryResult = new QueryResult<>();
queryResult.setList(pages.getContent());
queryResult.setTotal(pages.getTotalElements());
return new QueryResponseResult(CommonCode.SUCCESS, queryResult);
}
2.1.3 Controller
无需改动
2.1.4 接口测试


2.3 前端
2.3.1 页面
新增查询表单:
<!--查询表单-->
<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.pageAlias" style="width: 100px"></el-input>
<el-button type="primary" v-on:click="query" size="small">查询</el-button>
</el-form>
一个多选框和一个输入框
数据模型:
data () {
return {
params: {
page: 1, // 页码
size: 5, // 每页显示个数
siteId: '', // 站点id
pageAlias: '' // 站点别名
},
listLoading: false,
list: [],
total: 10,
siteList: [] // 站点列表
}
}
在钩子函数中构建siteList站点列表:
mounted () {
// 初始查询
this.query()
// 初始化站点列表
this.siteList = [
{
siteId: '5a751fab6abb5044e0d19ea1',
siteName: '门户主站'
},
{
siteId: '102',
siteName: '测试站'
}
]
}
2.3.2 API调用
传递自定义查询参数:
// public是对axios的工具类封装,定义了http请求方法
import http from './../../../base/api/public'
import queryString from 'querystring'
let sysConfig = require('@/../config/sysConfig')
let apiUrl = sysConfig.xcApiUrlPre
export const pageList = (page, size, params) => {
// 将json对象转换成键值对
let query = queryString.stringify(params)
return http.requestQuickGet(apiUrl + '/cms/page/list/' + page + '/' + size + '/?' + query)
}
需要注意的是,要对表单数据进行转换,因为表单数据是json格式的,要转换成键值对
测试:

三、新增页面
3.1 接口定义
3.1.1 定义响应模型
@Data
public class CmsPageResult extends ResponseResult {
CmsPage cmsPage;
public CmsPageResult(ResultCode resultCode,CmsPage cmsPage) {
super(resultCode);
this.cmsPage = cmsPage;
}
}
3.1.2 新增接口
在api工程中添加接口:
/**
* 页面新增
* @param cmsPage
* @return
*/
@ApiOperation("页面添加")
@PostMapping("/add")
@ApiResponses({
@ApiResponse(code = 10000,message = "操作成功"),
@ApiResponse(code = 11111,message = "操作失败")
})
CmsPageResult add(@RequestBody CmsPage cmsPage);
3.2 新增页面服务端开发
3.2.1 Dao
使用CmsPageRepository提供的save方法。
如何确定一个页面?通过页面名称、站点Id、页面webpath来确定,所以需要在cms_page集中创建唯一索引。

所以在新增一个页面之前要根据页面名称、站点Id、页面webpath查询页面,校验页面是否存在。
/**
* 页面新增时根据页面名称、站点Id、页面pageWebPath进行校验
* @param pageName 页面名称
* @param siteId 站点ID
* @param pageWebPath 页面路径
* @return
*/
CmsPage findByPageNameAndSiteIdAndPageWebPath(String pageName,String siteId,String pageWebPath);
3.2.2 Service
接口:
/**
* 新增页面
* @param cmsPage cms对象
* @return 操作结果
*/
CmsPageResult add(CmsPage cmsPage);
实现:
/**
* @param cmsPage cms对象
* @return
*/
@Override
public CmsPageResult add(CmsPage cmsPage) {
//1.首先校验页面是否存在
CmsPage temp = this.cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
if (temp == null){
cmsPage.setPageId(null);
this.cmsPageRepository.save(cmsPage);
//2.返回结果
return new CmsPageResult(CommonCode.SUCCESS, cmsPage);
}
return new CmsPageResult(CommonCode.FAIL, null);
}
3.2.3 Controller
@Override
@PostMapping("/add")
public CmsPageResult add(@RequestBody CmsPage cmsPage) {
return cmsService.add(cmsPage);
}
3.2.4 接口测试
Swagger测试:

返回:

数据库:

操作失败(插入相同页面):

3.3 新增页面前端开发
3.3.1 新增页面
3.3.1.1 编写pageAdd.vue页面
使用Element-UI的from组件编写添加表单内容
1、先创建pageAdd.vue页面,预期效果如下

2、配置路由

注意:因为添加页面不需要单独显示为一个菜单,这里面的hidden就设置为true。
3、测试,浏览器输入http://localhost:11000/#/cms/page/add

4、在页面列表添加“添加页面”按钮
实际情况是用户进入页面查询列表,点击新增页面按钮进入新增页面窗口
在查询按钮的旁边添加:
<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渲染后就是标签。
to:目标路由地址
5、创建表单
<template>
<div>
<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="数据Url" prop="dataUrl">
<el-input v-model="pageForm.dataUrl" 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.native="addSubmit" :loading="addLoading">提交</el-button>
</div>
</div>
</template>
<script>
export default {
name: 'pageAdd',
data () {
return {
// 站点列表
siteList: [],
// 模版列表
templateList: [],
// 新增界面数据
pageForm: {
siteId: '',
templateId: '',
pageName: '',
pageAliase: '',
pageWebPath: '',
dataUrl: '',
pageParameter: '',
pagePhysicalPath: '',
pageType: '',
pageCreateTime: new Date()
}
}
},
methods: {
addSubmit () {
alert('提交')
}
}
}
</script>
<style scoped>
</style>
基本表单+表单数据对象
6、站点及模板数据(静态)
在created钩子方法中定义,created是在html渲染之前执行,这里推荐使用created。
created: function () {
// 初始化站点列表
this.siteList = [
{
siteId: '5a751fab6abb5044e0d19ea1',
siteName: '门户主站'
},
{
siteId: '102',
siteName: '测试站'
}
]
// 模板列表
this.templateList = [
{
templateId: '5a962b52b00ffc514038faf7',
templateName: '首页'
},
{
templateId: '5a962bf8b00ffc514038fafa',
templateName: '轮播图'
}
]
}
7、测试

点击新增页面,跳转到表单:

3.3.1.2 添加返回按钮
进入新增页面后只能通过菜单再次进入页面列表,可以在新增页面添加“返回”按钮,点击返回按钮返回到页面列表。而且要返回到用户上次操作时页面列表的状态,所以在页面跳转的时候需要携带两个参数:siteId和page。
1)新增页面按钮带上参数
<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)定义返回方法
添加返回按钮:
goBack () {
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.$route.query.id是拿不到的。
3)查询列表支持回显
进入查询列表,从url中获取页码和站点id并赋值给数据模型对象,从而实现页面回显。在created方法中进行:
created () {
// 从路由上获取参数
this.params.page = Number.parseInt(this.$route.query.page || 1)
this.params.siteId = this.$route.query.siteId || ''
},
created和mounted的区别:
created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
3.3.1.3 表单校验
1、配置校验规则:
Element-UI的Form组件提供表单校验的方法,在form属性上配置rules(表单验证规则)
<el-form :model="pageForm" label-width="80px" :rules="pageFormRules">
在数据模型中匹配校验规则:
// 表单校验规则
pageFormRules: {
siteId: [
{required: true, message: '请选择站点', trigger: 'blur'}
],
templateId: [
{required: true, message: '请选择模版', trigger: 'blur'}
],
pageName: [
{required: true, message: '请输入页面名称', trigger: 'blur'}
],
pageWebPath: [
{required: true, message: '请输入访问路径', trigger: 'blur'}
],
pagePhysicalPath: [
{required: true, message: '请输入物理路径', trigger: 'blur'}
]
}
2、点击提交的时候触发校验
1)在form表单上添加 ref属性(ref=“pageForm”)在校验时引用此表单对象
<el-form :model="pageForm" label-width="100px" :rules="pageFormRules" ref="pageForm">
2)提交前执行校验
addSubmit () {
this.$refs.pageForm.validate((valid) => {
if (valid) {
alert('提交')
}
})
}
3.3.2 API调用
3.3.2.1 方法完善
1、在cms.js中定义pageAdd方法
// 页面添加
export const pageAdd = params => {
return http.requestPost(apiUrl + '/cms/page/add', params)
}
2、修改pageAdd.vue中的addSubmit方法
提交前对表单进行校验,校验成功后弹是否确认提交出对话框,如果点击是则调用API发送请求,如何返回结果中success为true,则弹出对话框提示用户添加成功,重置表单;否则提示添加失败。
使用两个组件:
this.$confirm('确认提交吗?', '提示', {}).then(() => {})
this.$message({
message: '提交成功',
type: 'success'
})
完整代码:
addSubmit () {
this.$refs.pageForm.validate((valid) => {
if (valid) {
this.$confirm('确认提交吗?', '提示', {}).then(() => {
cmsApi.pageAdd(this.pageForm).then((res) => {
console.log(res)
if (res.success) {
this.$message.success('提交成功')
this.$refs['pageForm'].resetFields() // 提交成功后重置表单
} else {
this.$message.error('提交失败')
}
})
})
}
})
},
3.3.2.2 测试

点击提交:

点击确定:

表单清空

查看数据库:

四、修改页面
修改页面的流程:
1、用户进入修改页面,在页面上显示了修改页面的信息
2、用户修改页面的内容,点击“提交”按钮,提示“修改成功”或“修改失败”
4.1 接口定义
CmsPageControllerApi中添加方法:
/**
* 根据id查询页面
* @param id
* @return
*/
@ApiOperation("根据Id查询页面")
@ApiImplicitParams({
@ApiImplicitParam(name = "id",value = "页面Id",required = true,paramType = "path",dataType = "String")
})
@GetMapping("/get/{id}")
@ApiResponses({
@ApiResponse(code = 10000,message = "操作成功"),
@ApiResponse(code = 11111,message = "操作失败")
})
CmsPageResult findById(@PathVariable String id);
/**
* 根据id修改页面
* @param id
* @param cmsPage
* @return
*/
@ApiOperation("修改页面")
@ApiImplicitParams({
@ApiImplicitParam(name = "id",value = "页面Id",required = true,paramType = "path",dataType = "String")
})
@PutMapping("/update/{id}")
@ApiResponses({
@ApiResponse(code = 10000,message = "操作成功"),
@ApiResponse(code = 11111,message = "操作失败")
})
CmsPageResult update(@PathVariable String id,@RequestBody CmsPage cmsPage);
4.2 修改页面服务端开发
4.2.1 Dao
使用Spring Data提供的findById方法完成主键查询
使用Spring Data提供的save方法完成数据保存
4.2.2 Service
接口:
/**
* 根据id查询页面
* @param id
* @return
*/
CmsPage findById(String id);
/**
* 更新页面
* @param id 页面id
* @param cmsPage 页面对象
* @return
*/
CmsPageResult update(String id,CmsPage cmsPage);
实现:
/**
* 根据页面id查询
* @param id
* @return
*/
@Override
public CmsPage findById(String id) {
Optional<CmsPage> optional = cmsPageRepository.findById(id);
return optional.orElse(null);
}
/**
* 更新页面
* @param id 页面id
* @param cmsPage 页面对象
* @return
*/
@Override
public CmsPageResult update(String id, CmsPage cmsPage) {
CmsPage temp = findById(id);
if (temp != null){
//更新页面:模板id、所属站点、页面别名、页面名称、访问路径、物理路径
temp.setTemplateId(cmsPage.getTemplateId());
temp.setSiteId(cmsPage.getSiteId());
temp.setPageAliase(cmsPage.getPageAliase());
temp.setPageName(cmsPage.getPageName());
temp.setPageWebPath(cmsPage.getPageWebPath());
temp.setPagePhysicalPath(cmsPage.getPagePhysicalPath());
temp.setDataUrl(cmsPage.getDataUrl());
CmsPage save = this.cmsPageRepository.save(temp);
return new CmsPageResult(CommonCode.SUCCESS, save);
}
return null;
}
4.2.3 Controller
根据id查询页面
@Override
@GetMapping("/get/{id}")
public CmsPageResult findById(@PathVariable String id) {
CmsPage cmsPage = cmsService.findById(id);
if (cmsPage != null){
return new CmsPageResult(CommonCode.SUCCESS, cmsPage);
}else {
return new CmsPageResult(CommonCode.FAIL, null);
}
}
保存页面信息
@Override
@PutMapping("/update/{id}")
public CmsPageResult update(@PathVariable String id, @RequestBody CmsPage cmsPage) {
return cmsService.update(id,cmsPage);
}
4.2.4 接口测试

4.2.4.1 根据id查询页面
查询测试页面:

id:5c975896591f425e08be9e91

结果:

4.2.4.2 修改页面
修改id为5c975896591f425e08be9e91

结果:

数据库:

4.3 修改页面前端开发
4.3.1 页面处理流程
页面处理流程如下:
1、进入页面,通过钩子方法请求服务端获取页面信息,并赋值给数据模型对象
2、页面信息通过数据绑定在表单显示
3、用户修改信息后点击提交按钮请求服务端修改页面接口
4.3.2 修改页面
4.3.2.1 编写pageEdit页面
与添加页面基本一致,稍作修改
1、配置路由
页面进入的时候传入pageId

通过:pageId的方式传入参数
2、为页面列表中的编辑按钮添加方法
edit (pageId) {
this.$router.push({
path: '/cms/page/edit/' + pageId,
query: {
page: this.params.page,
siteId: this.params.siteId
}
})
},
因为修改完成后要返回,那么在页面跳转的时候就需要携带参数
4.3.2.2 页面内容显示
进入修改页面后立即显示要修改的数据
1、定义api方法
// 根据id查询页面
export const pageGet = id => {
return http.requestQuickGet(apiUrl + '/cms/page/get/' + id)
}
2、接收页面跳转时传入的pageId

3、在created方法中查询页面信息

获取页面跳转时传入的参数,然后调用api,根据页面Id进行查询
4、效果预览

4.3.3 API调用
1、定义api方法
// 页面修改
export const pageEdit = (id, params) => {
return http.requestPut(apiUrl + '/cms/page/update/' + id, params)
}
2、修改提交按钮的方法
editSubmit () {
this.$refs.pageForm.validate((valid) => {
if (valid) {
this.$confirm('确认修改吗?', '提示', {}).then(() => {
cmsApi.pageEdit(this.pageId, this.pageForm).then((res) => {
console.log(res)
if (res.success) {
this.$message.success('修改成功')
this.goBack() // 修改成功后返回
} else {
this.$message.error('修改失败')
}
})
})
}
})
},
3、测试
五、删除页面
用户操作流程:
1、用户进入页面列表,点击删除按钮
2、执行删除操作,提示“删除成功”或“删除失败”
5.1 接口定义
/**
* 删除页面
* @param id
* @return
*/
@ApiOperation("删除页面")
@DeleteMapping("/delete/{id}")
@ApiImplicitParams({
@ApiImplicitParam(name = "id",value = "页面Id",required = true,paramType = "path",dataType = "String")
})
@ApiResponses({
@ApiResponse(code = 10000,message = "操作成功"),
@ApiResponse(code = 11111,message = "操作失败")
})
ResponseResult delete(@PathVariable String id);
5.2 删除页面服务端开发
5.2.1 Dao
使用 Spring Data提供的deleteById方法完成删除操作 。
5.2.2 Service
接口:
/**
* 删除页面
* @param id
* @return
*/
ResponseResult delete(String id);
实现:
/**
* 删除页面
* @param id
* @return
*/
@Override
public ResponseResult delete(String id) {
CmsPage cmsPage = findById(id);
if (cmsPage != null){
// 删除页面
this.cmsPageRepository.deleteById(id);
return ResponseResult.SUCCESS();
}
return ResponseResult.FAIL();
}
5.2.3 Controller
@Override
@DeleteMapping("/delete/{id}")
public ResponseResult delete(@PathVariable String id) {
return cmsService.delete(id);
}
5.2.4 接口测试
先添加一条测试数据:

根据Id删除:


结果:

5.3 删除页面前端开发
5.3.1 定义api方法
// 删除页面
export const pageDel = id => {
return http.requestDelete(apiUrl + '/cms/page/delete/' + id)
}
5.3.2 前端页面
1、在pageList.vue页面添加删除按钮

传入页面Id
2、添加del删除方法
del (pageId) {
this.$confirm('确认删除此页面吗?', '提示', {}).then(() => {
console.log(pageId)
cmsApi.pageDel(pageId).then((res) => {
if (res.success) {
this.$message.success('删除成功!')
this.query()
} else {
this.$message.error('删除失败!')
}
})
})
}
六、异常处理
6.1 异常处理的问题分析
从添加页面的service方法中找问题:
/**
* @param cmsPage cms对象
* @return
*/
@Override
public CmsPageResult add(CmsPage cmsPage) {
//1.首先校验页面是否存在
CmsPage temp = this.cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
if (temp == null){
cmsPage.setPageId(null);
this.cmsPageRepository.save(cmsPage);
//2.返回结果
return new CmsPageResult(CommonCode.SUCCESS, cmsPage);
}
return new CmsPageResult(CommonCode.FAIL, null);
}
存在的问题:
1、上面的代码只要操作不成功仅向用户返回“错误代码”:11111,失败信息:操作失败,无法区别具体的错误信息。

2、service方法在执行过程出现异常在哪捕获?在service中需要都加try/catch,那么在controller也需要添加try/catch,代码冗余严重且不易维护。
解决方案:
1、在Service方法中的编码顺序是先校验判断,有问题则抛出具体的异常信息,最后执行具体的业务操作,返回成功信息。
2、在统一异常处理类中去捕获异常,无需controller捕获异常,向用户返回统一规范的响应信息
代码模板如下:
/**
* @param cmsPage cms对象
* @return
*/
@Override
public CmsPageResult add(CmsPage cmsPage) {
//1.校验cmsPage是否为空
if (cmsPage == null){
//抛出异常,非法请求
}
//2.根据页面名称查询(页面名称已在mongodb创建了唯一索引)
CmsPage temp = this.cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
//3.校验页面是否存在,已存在则抛出异常
if (temp != null){
//抛出异常,已存在相同的页面名称
}
//4.添加页面主键由spring data自动生成
cmsPage.setPageId(null);
CmsPage save = cmsPageRepository.save(cmsPage);
//5.返回结果
return new CmsPageResult(CommonCode.SUCCESS, save);
}
6.2 异常处理流程
系统对异常的处理使用统一的异常处理流程:
1、自定义异常类型。
2、自定义错误代码及错误信息。
3、对于可预知的异常由程序员在代码中主动抛出,由SpringMVC统一捕获。(可预知异常是程序员在代码中手动抛出本系统定义的特定异常类型,由于是程序员抛出的异常,通常异常信息比较齐全,程序员在抛出时会指定错误代码及错误信息,获取异常信息也比较方便。)
4、对于不可预知的异常(运行时异常)由SpringMVC统一捕获Exception类型的异常。不可预知异常通常是由于系统出现bug、或一些不要抗拒的错误(比如网络中断、服务器宕机等),异常类型为RuntimeException类型(运行时异常)。
5、可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。
异常抛出及处理流程:

1、在controller、service、dao中抛出自定义异常;springMVC框架抛出框架异常类型
2、统一由异常捕获类捕获异常,并进行处理
3、捕获到自定义异常则直接取出错误代码及错误信息,响应给用户。
4、捕获到非自定义异常类型首先从Map中找该异常类型是否对应具体的错误代码,如果有则取出错误代码和错误信息并响应给用户,如果从Map中找不到异常类型所对应的错误代码则统一为99999错误代码并响应给用户。
5、将错误代码及错误信息以Json格式响应给用户。
6.3 可预知异常处理
6.3.1 自定义异常类
在common工程中定义异常类型
package com.xuecheng.framework.exception;
import com.xuecheng.framework.model.response.ResultCode;
/**
* @Author: 98050
* @Time: 2019-03-25 21:13
* @Feature:
*/
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;
}
}
6.3.2 异常抛出类
package com.xuecheng.framework.exception;
import com.xuecheng.framework.model.response.ResultCode;
/**
* @Author: 98050
* @Time: 2019-03-25 21:19
* @Feature: 异常抛出类
*/
public class ExceptionCast {
/**
* 使用此静态方法抛出自定义异常
*/
public static void cast(ResultCode resultCode){
throw new CustomException(resultCode);
}
}
6.3.3 异常捕获类
使用 @ControllerAdvice和@ExceptionHandler注解来捕获指定类型的异常
package com.xuecheng.framework.exception;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.model.response.ResultCode;
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;
/**
* @Author: 98050
* @Time: 2019-03-25 21:22
* @Feature: 异常捕获类
*/
@ControllerAdvice
public class ExceptionCatch {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);
/**
* 捕获CustomException异常
* @param e
* @return
*/
@ExceptionHandler(CustomException.class)
@ResponseBody
public ResponseResult customException(CustomException e){
LOGGER.error("catch exception : {} \r\nexception:",e.getMessage(),e);
ResultCode resultCode = e.getResultCode();
return new ResponseResult(resultCode);
}
}
对于@ControllerAdvice,我们比较熟知的用法是结合@ExceptionHandler用于全局异常的处理,但其作用不仅限于此。ControllerAdvice拆分开来就是Controller Advice,关于Advice,在Spring Aop中其用于封装一个切面所有属性的,包括切入点和需要织入的切面逻辑。这里ContrllerAdvice也可以这么理解,其抽象级别应该是用于对Controller进行“切面”环绕的,而具体的业务织入方式则是通过结合其他的注解来实现的。@ControllerAdvice是在类上声明的注解,其用法主要有三点:
- 结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的;
- 结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的;
- 结合方法型注解@ModelAttribute,表示其标注的方法将会在目标Controller方法执行之前执行。
@ControllerAdvice的用法基本是将其声明在某个bean上,然后在该bean的方法上使用其他的注解来指定不同的织入逻辑。不过这里@ControllerAdvice并不是使用AOP的方式来织入业务逻辑的,而是Spring内置对其各个逻辑的织入方式进行了内置支持。
6.3.4 异常处理测试
6.3.4.1 定义错误代码
package com.xuecheng.framework.domain.cms.response;
import com.xuecheng.framework.model.response.ResultCode;
import lombok.ToString;
/**
* CMS异常错误代码
* @author 98050
*/
@ToString
public enum CmsCode implements ResultCode {
/**
* 页面已经存在
*/
CMS_ADDPAGE_EXISTSNAME(false,24001,"页面名称已存在!"),
/**
* 从页面信息中找不到获取数据的url
*/
CMS_GENERATEHTML_DATAURLISNULL(false,24002,"从页面信息中找不到获取数据的url!"),
/**
* 根据页面的数据url获取不到数据
*/
CMS_GENERATEHTML_DATAISNULL(false,24003,"根据页面的数据url获取不到数据!"),
/**
* 页面模板为空
*/
CMS_GENERATEHTML_TEMPLATEISNULL(false,24004,"页面模板为空!"),
/**
* 生成的静态html为空
*/
CMS_GENERATEHTML_HTMLISNULL(false,24005,"生成的静态html为空!"),
/**
* 保存静态html出错
*/
CMS_GENERATEHTML_SAVEHTMLERROR(false,24005,"保存静态html出错!"),
/**
* 预览页面为空
*/
CMS_COURSE_PERVIEWISNULL(false,24007,"预览页面为空!");
/**
* 操作结果
*/
boolean success;
/**
* 操作结果
*/
int code;
/**
* 提示信息
*/
String message;
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;
}
}
6.3.4.2 异常处理测试
1、抛出异常
在controller、service、dao中都可以抛出异常。
修改CmsServiceImpl中的add方法,添加抛出异常的代码。
public CmsPageResult add(CmsPage cmsPage) {
//1.校验cmsPage是否为空
if (cmsPage == null){
//抛出异常,非法请求
}
//2.根据页面名称查询(页面名称已在mongodb创建了唯一索引)
CmsPage temp = this.cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
//3.校验页面是否存在,已存在则抛出异常
if (temp != null){
//抛出异常,已存在相同的页面名称
ExceptionCast.cast(CmsCode.CMS_ADDPAGE_EXISTSNAME);
}
//4.添加页面主键由spring data自动生成
cmsPage.setPageId(null);
CmsPage save = cmsPageRepository.save(cmsPage);
//5.返回结果
return new CmsPageResult(CommonCode.SUCCESS, save);
}
2、启动工程,扫描到异常捕获的类ExceptionCatch在
springBoot的启动类中添加扫描的包:

3、前端异常信息展示
先添加一个测试页面:

添加相同页面:

前端错误信息处理:

再次提交:

6.4 不可预知异常处理
6.4.1 定义异常捕获方法
6.4.1.1 异常抛出测试
使用PostMan测试添加页面接口,但是不输入cmsPage信息:

结果:

上边的响应信息在客户端是无法解析的。
在异常捕获类中添加对Exception异常的捕获:
/**
* 捕获不可预知异常
* @param e
* @return
*/
@ExceptionHandler(CustomException.class)
@ResponseBody
public ResponseResult exception(Exception e){
LOGGER.error("catch exception : {}",e.getMessage());
return null;
}
6.4.1.2 异常捕获方法
针对上边的问题其解决方案是:
1、我们在map中配置HttpMessageNotReadableExcept
2、在异常捕获类中对Exception异常进行捕获,并从map回此错误,否则统一返回99999错误。
具体的开发实现如下:
1、在通用错误代码类CommCode中配置非法参数异常捕获方法
/**
* 非法参数
*/
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
/**
* @Author: 98050
* @Time: 2019-03-25 21:22
* @Feature: 异常捕获类
*/
@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();
static{
//在这里加入一些基础的异常类型判断
builder.put(HttpMessageNotReadableException.class,CommonCode.INVALID_PARAM);
}
/**
* 捕获CustomException异常
* @param e
* @return
*/
@ExceptionHandler(CustomException.class)
@ResponseBody
public ResponseResult customException(CustomException e){
LOGGER.error("catch exception : {} \r\nexception:",e.getMessage(),e);
ResultCode resultCode = e.getResultCode();
return new ResponseResult(resultCode);
}
/**
* 捕获不可预知异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseResult exception(Exception e){
LOGGER.error("catch exception : {}",e.getMessage());
if (EXCEPTIONS == null){
EXCEPTIONS = builder.build();
}
final ResultCode resultCode = EXCEPTIONS.get(e.getClass());
final ResponseResult result;
if (resultCode != null){
result = new ResponseResult(resultCode);
}else {
result = new ResponseResult(CommonCode.SERVER_ERROR);
}
return result;
}
}
这里面使用了ImmutableMap,线程安全的、不可改变的集合
6.4.2 异常处理测试
再次使用PostMan进行测试:

七、分支合并




277

被折叠的 条评论
为什么被折叠?



