第二天CMS前端页面查询开发

文章目录

一、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、测试

1553418610296

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

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 接口测试

1553496033299

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

1553505356576

通过: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,失败信息:操作失败,无法区别具体的错误信息。

1553516756882

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进行测试:

七、分支合并

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值