Spring Cloud + Vue前后端分离-第5章 单表管理功能前后端开发

Spring Cloud + Vue前后端分离-第5章 单表管理功能前后端开发

完成单表的增删改查

控台单表增删改查的前后端开发,重点学习前后端数据交互,vue ajax库axios的使用等

通用组件开发:分页、确认框、提示框、等待框等

常用的公共组件:确认框、提示框、等待框,统一日志拦截器等。使用vue自定义组件制作分页组件,mybatis分页插件pagehelper的使用等

5-1 大章列表查询功能开发1

增加maven子项目business

1.增加business模块,并增加初始启动代码

Shift+F6重命名。重命名也是一种重构,会将所有引用到的地方都一起改名,甚至是注释掉的代码也会一起改掉

application.properties

spring.application.name=business
server.servlet.context-path=/business
server.port=9002
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

大章表设计及持久层代码生成

将sql脚本和代码放一起的好处是,可以通用git提交记录来查看sql的变更记录,方便追溯

一般的表结构设计,都会有一个ID字段,作为主键,与业务无关

1.增加大章chapter表sql,生成持久层代码

小技巧:可以将常用的文件放入收藏夹,方便查找

注:每次要生成新表代码时,旧的表不要删除,但要注释掉。(同时生产多个表也可以,但没必要)

自动生成的代码


完成后端列表查询接口

同样,在business里的controller层也是一样的创建方法 

启动注册中心,再启动business服务

1.增加dto层,用于controller和service层 

DTO : Data Transfer Object 数据传输对象,用于数据传输

又是一个约定: domain内的实体,是mybatis generator自动生成的,不允许手动修改。一旦修改,再次生成实体类时, 所做的修改会被覆盖

domain作用于service和mapper;dto作用于controller和service

Ctrl+Alt+V为表达式生成一个变量

拓展:编写自己的for语句代码

for(int $INDEX$ = 0, l = $LIST$.size(); $INDEX$ < l; $INDEX$++) {
    $END$
}

1. 点击按钮弹出变量设置窗口

2.设置这两个变量

BeanUtils是Spring提供的一个工具类,用于实体间的复制。后续我们会对BeanUtils做封闭,简化使用,提高开发效率

2.增加ChapterDto

是chapter复制的
3.修改ChapterService,将返回Chapter改成返回ChapterDto

5-2 大章列表查询功能开发2

从这个地方开始,我换mac了,嘿嘿

前端页面开发

row col-xs-12都是bootstrap栅格系统的内置样式,用于响应式页面的布局,需熟练掌握

选中全部,Shift+Tab,反向缩进

点击sidebar菜单实现页面跳转

二级菜单要显示成激活状态,只需要添加active样式

接下来完成功能:点击左侧菜单,该菜单变成激活状态,并跳到相应的路由页面

siblings,jquery的方法,获取所有兄弟节点

约定:id 的命名要和路由相关。后续我们会用到这个约定。

<router-link to="">,类似于<a href="">,用于链接跳转

为每一个路由都加上一个name属性,后续做通用的sidebar激活样式方法时会用到

 

通用的sidebar 点击激活样式方法

通用的功能,要尽量做个通用的方法,要学会“懒”。

1.通用的sidebar点击激活样式方法,使用watch 监听路由变化

vue 内置的watch,用来监测vue 实例上的数据变动,$route 也是一个变量。

通过name 属性值,得到菜单id 的值。前面有约定:id 的命名要和路由相关。程序开发中有一项设计范式叫:约定大于配置(按约定编程)。

此时如果从login页面点击登录跳到welcome页面,welcome并不会有激活样式。这里的watch,只在admin下面的子组件互相跳转时有效

js中有this 关键字,代表当前执行方法的对象。养成习惯,在方法开头,声明本地变量_this 代替this。后面会介绍直接用this的坑。

5-3 大章列表查询功能开发3

集成axios 完成前后端交互

vue也支持使用jquery ajax 来请求后端借口,推荐使用vue axios

注意:要先进到vue cli 项目,再安装插件

--save:在package.json添加依赖。(不加-- save的话,只是去下载插件,项目中并没有依赖插件)

1.安装axios

npm install axios --save

2.以vue属性的方式使用axios

修改main.js

import axios from 'axios'

Vue.prototype.$ajax = axios;

Vue.prototype.xxx,可以理解为Vue组件的全局变量。可以在任意Vue组件中,使用this.xxx 来获取这个值。$ 是代表Vue 全局属性的一个约定

3.chapter.vue 中使用$ajax

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

/admin 用于控台类的接口,/web 用于网站类的接口。接口设计中,用不同的请求前缀代表不同的入口,做接口隔离,方便做鉴权、统计、监控等

启动serve、注册中心EurekaApplication、BusinessApplication

CORS,Cross-Origin Resource Sharing 跨站点资源分享,属于跨域问题。同个IP的不同端口间访问也属于跨域。前后端分离必然有跨域问题

4.解决跨域的问题

1.集成axios 完成前后端交互

2.增加CorsConfig,解决前后端跨域的问题

增加CorsConfig.java

package com.course.server.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("*").allowedHeaders(CorsConfiguration.ALL).allowedMethods(CorsConfiguration.ALL).allowCredentials(true).maxAge(3600); // 1小时内不需要再预检(发OPTIONS请求)}}

页面改造显示真实数据

1.大章页面显示真实数据

Ctrl+Shift+减号:收起所有节点,包括所有的子节点。

Ctrl+Shift+加号:展开所有的层级。

使用data定义组件内的变量,可用于做双向数据绑定,双向数据绑定是vue 的核心功能之一。

使用this.xxx来访问组件内的变量

使用gateway 路由转发

1.使用gateway 路由转发,vue页面只访问gateway的端口

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

这里的请求地址目前是写死在代码中的,后续我们会做优化,对请求地址做多环境的配置。

 扩展:1.解决gateway 跨域问题

gateway跨域配置

在gateway 启动类里增加

/*** 配置跨域* @return*/@Beanpublic CorsWebFilter corsFilter() {CorsConfiguration config = new CorsConfiguration();config.setAllowCredentials(Boolean.TRUE);config.addAllowedMethod("*");config.addAllowedOrigin("*");config.addAllowedHeader("*");config.setMaxAge(3600L);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());source.registerCorsConfiguration("/**", config);return new CorsWebFilter(source);}

把CorsConfig.java注释掉

把服务重新启动

是否直接访问gateway就不需要跨域配置里呢?需要再验证一下

单个SpringBoot 应用使用CorsConfig 解决跨域问题。使用SpringCloud Gateway的,使用CorsWebFilter解决跨域问题。

扩展:2.使用lb://+注册中心名称作路由转发

lb意思是loadbalance  负载均衡

问题:如果配置的是IP端口,那发布到生产时就可能会访问不到,就算配置了maven多环境,也需要提前知道上线后的IP和端口,提前配好。

#spring.cloud.gateway.routes[1].uri=http://127.0.0.1:9002
spring.cloud.gateway.routes[1].uri=lb://business

5-4 分页功能开发

集成分页插件pagehelper

1.集成分页插件pagehelper,注意页码从1开始

mybatis-generator 生成的代码是不带分页功能的,使用pagehelper插件来扩展分页功能。

父包

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

server子包

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

ChapterService.java

PageHelper.startPage(1,1);

PageHelper 的分页参数:pageNum是从1开始的 

分页功能的关键字:limit。从日志可以看出,该sql 执行的是limit 1,相当于limit 0,1 ,即从第0行开始,查1条。

插件分页语句规则:调用startPage 方法之后,执行的第一个select 语句会进行分页。

limit 1,1  :从行号1(行号是从0开始)开始,查1条

分页查询功能需要两条sql ,一条是查总记录数(通过每页条数计算出总共有多少页),一条是查当前页的记录。

分页参数前后端交互

1.分页参数前后端交互,axios 的post 请求默认是以流的方式传递参数,所以controller 里的参数要加@RequestBody 注解

泛型需要熟练掌握,在写一些通用类,工具类时很好用。

扩展:使用泛型的地方都可以用Object 代替,但是泛型可以在编译期就发现问题,并且避免了代码中写强制类型转换。

PageDto 即用来接收入参,也用来返回结果。

 当传入的分页参数不合法时,比如0,0 ,程序不会报错,而是查全部记录,分页不生效。

经验分享:在开发完代码后,需要进行测试,特别要针对一些边界值做测试。

 

接口请求参数传递,尽量使用post。使用get 请求在url 里拼参数的话,会使url 变得很长,有些浏览器或服务器会对url 长度做限制,导致请求失败。

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

日志输出时,变量使用点位符,比如LOG.info("输出:id={},姓名={}",id,name),而不是LOG.info("输出:id=“+ id +”,姓名=" + name)

post请求有多种参数传递,通过header里的Content-Type来标识,常见的有两种,一种是表单的方式,另一种是json(流)的方式。

jquery 默认是以表单的方式,vue angular 默认是用json 的方式。

5-5 前端分页组件的使用

增加刷新功能

注意:<template>标签只能有一个子标签

 fa 样式是 fontawesome 图标,可以百度搜“fontawesome图标”查看所有的图标样式

前端分页组件的使用

1.增加分页组件pagination.vue

2.大章管理页面使用分页组件,可自定义初始每页10条,最多显示8个按钮

问题:当数据量很大的时候,分页页码很多,这时把所有页码都显示出来,会占用页面的大部分空间,影响体验。所以需要设置显示页码数量

v-bind:list="list",前面的list,是分页组件暴露出来的一个回调方法,后面的list,是chapter组件的list方法

props,定义父组件向子组件传递的参数,可以是一个函数或数据。本组件中暴露了两个参数list 和 itemCount 给外部。

pagination.vue

<template><div class="pagination" role="group" aria-label="分页"><button type="button" class="btn btn-default btn-white btn-round"v-bind:disabled="page === 1"v-on:click="selectPage(1)">1</button><button type="button" class="btn btn-default btn-white btn-round"v-bind:disabled="page === 1"v-on:click="selectPage(page - 1)">上一页</button><button v-for="p in pages" v-bind:id="'page-' + p"type="button" class="btn btn-default btn-white btn-round"v-bind:class="{'btn-primary active':page == p}"v-on:click="selectPage(p)">{{p}}</button><button type="button" class="btn btn-default btn-white btn-round"v-bind:disabled="page === pageTotal"v-on:click="selectPage(page + 1)">下一页</button><button type="button" class="btn btn-default btn-white btn-round"v-bind:disabled="page === pageTotal"v-on:click="selectPage(pageTotal)">{{pageTotal||1}}</button>&nbsp;<span class="m--padding-10">每页<select v-model="size"><option value="1">1</option><option value="5">5</option><option value="10">10</option><option value="20">20</option><option value="50">50</option><option value="100">100</option></select>条,共【{{total}}】条</span></div>
</template><script>
export default {name: 'pagination',//props,定义父组件向子组件传递的参数,可以是一个函数或数据。本组件中暴露了两个参数list 和 itemCount 给外部。props: {list: {type: Function,default: null},itemCount: Number // 显示的页码数,比如总共有100页,只显示10页,其它用省略号表示},data: function () {return {total: 0, // 总行数size: 10, // 每页条数page: 0, // 当前页码pageTotal: 0, // 总页数pages: [], // 显示的页码数组}},methods: {/*** 渲染分页组件* @param page* @param total*/render(page, total) {let _this = this;_this.page = page;_this.total = total;_this.pageTotal = Math.ceil(total / _this.size);_this.pages = _this.getPageItems(_this.pageTotal, page, _this.itemCount || 5);},/*** 查询某一页* @param page*/selectPage(page) {let _this = this;if (page < 1) {page = 1;}if (page > _this.pageTotal) {page = _this.pageTotal;}if (this.page !== page) {_this.page = page;if (_this.list) {_this.list(page);}}},/*** 当前要显示在页面上的页码* @param total* @param current* @param length* @returns {Array}*/getPageItems(total, current, length) {let items = [];if (length >= total) {for (let i = 1; i <= total; i++) {items.push(i);}} else {let base = 0;// 前移if (current - 0 > Math.floor((length - 1) / 2)) {// 后移base = Math.min(total, current - 0 + Math.ceil((length - 1) / 2)) - length;}for (let i = 1; i <= length; i++) {items.push(base + i);}}return items;}}
}
</script><style scoped>
.pagination {vertical-align: middle !important;font-size: 16px;margin-top: 0;margin-bottom: 10px;
}.pagination button {margin-right: 5px;
}.btn-primary.active {background-color: #2f7bba !important;border-color: #27689d !important;color: white !important;font-weight: 600;
}/*.pagination select {*/
/*vertical-align: middle !important;*/
/*font-size: 16px;*/
/*margin-top: 0;*/
/*}*/
</style>

5-6 增加新增大章功能

页面设计与前端代码开发

1.增加新增大章功能,前端代码开发

Bootstrap v3 中文文档 · Bootstrap 是最受欢迎的 HTML、CSS 和 JavaScript 框架,用于开发响应式布局、移动设备优先的 WEB 项目。 | Bootstrap 中文网

新增功能的页面如何设计,需要平时心里有些储备,可以平时浏览bootstrap 文档,看看都有哪些组件,用的时候心里有数。

模态框主要分为三大块:

modal-header 是标题;

modal-body 是主体内容,大章的表单内容就放在这里;

modal-footer 是底部按钮。

小技巧:

1.选中开头,一小部分代码

2.滚轮滚动到结尾鼠标拖动滚动条到结尾

3.按住shift并鼠标点击结尾

这种操作特别适合选中大段文本。

$(".modal")里的modal 是 css 的选择器,模态框代码里有class="modal" 样式;modal() 里的modal 是内置的方法,用于弹出或关闭模态框

启动admin、eureka、gateway、business

可以使用$(".modal").modal({backdrop:"static"});

禁止点空白的地方关闭,某些场景需求会用到这个功能。

&nbsp;

vuecli 会将我们写的html js css 代码编译压缩,空格和换行都会被压缩掉,导致按钮间的间隔没有了

html 有很多转义字符,比如你想在界面显示文本"<text>",但是浏览器会认为<text>是一个标签,这时可以在html中用转义字符:&lt;text&gt;

<label for="id">有个场景会经常用到:点击复选框checkbox时选中,使用lable for 后,点击label 的文字,也能选中复制框

模态框弹出和关闭,可以用js代码,也可以用button属性:data-dismiss="css选择器"关闭;

data-toggle="css选择器"打开

短ID设计与后端代码开发

1.增加新增大章功能,后端代码开发,完成前后端联调,保存成功

面试:为什么不用自增ID?自增ID至少有三个问题:

1.id 是连续,容易被探测;

2.需要+1次查询才能得到id 的值;

3.分布式存储中,id 会出现重复

uuid 是根据机器、时间等多个维度生成的32位16进制数,有生之年不会重复。我在uuid 的基础上,封装了8位短uuid。

短ID 是根据将32位ID,转为62进制8位ID,减少存储空间。

原理是将uuid 转为10进制,再对62取余。也可以再添加两个符号,转为64进制。

xxxx.sout 用到了postfix

目前使用BeanUtil.copyProperties,需要多行代码,后续会对其做封装优化。

chapter变量用于绑定form 表单的数据。

将绑定好数据的chapter 作为前后端交互传参

增加复制工具类CopyUtil

1.增加复制工具类CopyUtil,封装BeanUtils.copyProperties,简化单实体复制和列表复制的代码

该工具类封装了BeanUtils.copyProperties,利用反射,牺牲一点性能(可忽略不计),换取开发效率。

统一返回参数ResponseDto

纯接口应用,一般会规范固定的请求参数,如版本号、请求流水等;再规范固定的返回参数,如返回码、返回描述等。方便调用方统一处理。

1.增加统一返回实体类ResponseDto,前后端代码针对ResponseDto 做修改

2.chapter 保存成功后关闭表单,并刷新列表

3.为modal增加id 属性

ResponseDto.java

ChapterController.java 

response.data 就相当于responseDto

列表查询业务上一般都是成功的(查不到数据也是成功的,所以不需要判断success。保存有可能失败,所以需要判断success)

验证功能:

1.列表查询没问题;

2.保存功能没问题;

3.保存成功后关闭modal,并刷新列表。

css 选择器,可以通过id、class、标签等选择页面元素

问题:同一个页面有多个modal时,用class选择时,会出现重复,所以需要给每个modal增加id属性

需要测试modal相关的操作,点击新增,点击关闭,点击取消,点击保存,点击空白

5-7 修改删除大章功能

增加大章修改功能

1.增加修改大章功能,新增和修改用同一个保存功能,通过传入的参数id 有没有值来判断

新增和编辑功能弹出来的模态框是同一个。vue、controller、service 调用的都是同一个方法,只是到service层再根据id 是否有值来判断是新增还是删除

hidden-md:中等屏幕隐藏,其它可见;
hidden-lg:大屏幕隐藏,其它可见。
相反的有visible-xx,具体可参考https://v3.bootcss.com/css/#responsive-utilities-classes

在响应式页面中,同一个页面在大屏和小屏里显示的内容不太一样,大屏显示的内容更多,hidden-xx和visible-xx会经常用到

 <div class="hidden-md hidden-lg"><div class="inline pos-rel"><button class="btn btn-minier btn-primary dropdown-toggle" data-toggle="dropdown" data-position="auto"><i class="ace-icon fa fa-cog icon-only bigger-110"></i></button><ul class="dropdown-menu dropdown-only-icon dropdown-yellow dropdown-menu-right dropdown-caret dropdown-close"><li><a href="#" class="tooltip-info" data-rel="tooltip" title="View"><span class="blue"><i class="ace-icon fa fa-search-plus bigger-120"></i></span></a></li><li><a href="#" class="tooltip-success" data-rel="tooltip" title="Edit"><span class="green"><i class="ace-icon fa fa-pencil-square-o bigger-120"></i></span></a></li><li><a href="#" class="tooltip-error" data-rel="tooltip" title="Delete"><span class="red"><i class="ace-icon fa fa-trash-o bigger-120"></i></span></a></li></ul></div></div>

1.将表格每一行数据传递到edit中做处理

2.将传递过来的一行数据chapter,赋给vue变量_this.chapter

vue变量_this.chapter会通过v-model属性和form表单做数据绑定

数据显示:将表格行数据显示到表单。反过来,数据修改:修改表单影响表格行数据。

_this.chapter = $.extend({},chapter);

发现问题:对文本框编辑后,点新增弹出文本框,会带出上一次编辑过的值。

_this.chapter = {};

增加大章删除功能

1.增加删除大章功能

delete 是js 的关键字,vue 方法里不能使用js 关键字

restful 是一种请求风格。简单的理解:通过看url 就能知道这个请求是要对什么资源做什么操作

后端的代码还没写,所以你报错404,需要熟记常用的返回码,如:200,301,400,401,403,404,500,503等

5-8 集成前端通用组件

集成sweetalert 用于界面消息确认框

1.集成sweetalert2,删除时弹出确认框

删除是一个有风险的操作,需要有确认的动作。

SweetAlert2 - a beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript's popup boxes

 

制作消息提示框

1.制作toast组件,内部用sweetalert2实现

通过修改timer可以设置弹出的时长,设置icon可以设置成成功、错误、警告等。

养成一种思维,将通用的代码做成组件

如果组件包含html代码,可以用vue组件;如果组件只有js代码,可以用原生的js

toast 是js 全局变量,可以在其它js 文件中使用,也可以在vue 组件中直接使用。

集成blockUI 用于界面等待框

1.制作Loading组件,内部用jquery blockui插件实现

等待框的作用:

1.让用户知道,后端正在处理,耐心等待;

2.防止用户恶意重复点击。

malsup.com/jquery/block/

BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务

本身loading功能不复杂,jquery blockUI 插件已经多年没更新了,也说明很稳定了。

一般使用压缩过的

1.修改Toastr 组件的显示效果,更大气
2.制作Confirm 组件

组件化的好处:只需要修改组件代码,就可以改变组件的样式,使用的地方完全不用动

简单理解 js 回调函数:将一个函数以参数的形式传递到另一个函数里去执行。在自定义组件中经常用到回调函数。

将变化的代码(组件无关的代码)作为回调函数传递进来

原来的代码先注释掉

5-9 代码优化

前端代码校验

1.增加工具类tool.js和校验类validator.js

2.大章保存非空和长度增加校验

validator.js

tool.js

后端代码校验

1.增加后端校验工具类ValidatorUtil

2.增加统一异常处理,ControllerExceptionHandler,关键字:@ControllerAdvice

新增什么都不填写,依旧保存成功

这种数据是不对的

前后端分离的项目,后端接口需要增加和前端一样的校验,防止被绕过前端界面,利用第三方工具如postman,直接访问后端接口

自定义异常可以继承RuntimeException 或 Exception。一般项目内部的业务异常,可以用RuntimeException,不需要try catch。如果是开发一些框架或工具类,明确告诉外部需要做异常处理的,可以用Exception。另外还需要考虑事务中的异常处理,后续介绍

测试一下

刷新

没有新增

说明我们校验生效了

现象:后端出异常,导致前后收不到结果,vue中的.then方法没有执行,等待框没有关闭,导致不能继续任何操作,只能刷新页面

选中代码,ctrl+alt+T,选择try/catch

但是这么做,如果有多个地方都用到,依然比较复杂

@ControllerAdvice 是Controller增强其,可以对Controller 做统一的处理,如异常处理,数据处理等。

前端也需要增加一下

测试

但还有一个安全问题

有时候我们的接口原本是不对外的,或者只跟特定的第三方应用做对接,这时为了内部安全,不应该把参数的校验规则暴露出去,所以需要模糊返回信息。类似登录接口应该返回“用户名或密码错误”,而不是“用户名不存在”,或“密码错误”(容易被探测)

如果开发过程中提示“请求参数异常”,说明后端有校验拦截,前端没有,此时应该把前端校验加上

使用AOP制作统一日志输出

1.增加日志AOP,统一日志输出

2.logback 增加打印日志跟踪号

问题:从打印的日志内容,看不出业务信息。日志不仅开发时有用,生产运维时查看业务日志也很重要,所以需要把日志加上业务信息。

统一日志处理,可以用AOP,也可以用Spring拦截器

package com.course.server.config;import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.course.server.util.UuidUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
@Aspect
@Component
public class LogAspect {private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);/** 定义一个切点 */@Pointcut("execution(public * com.course.*.controller..*Controller.*(..))")public void controllerPointcut() {}@Before("controllerPointcut()")public void doBefore(JoinPoint joinPoint) throws Throwable {// 日志编号MDC.put("UUID", UuidUtil.getShortUuid());// 开始打印请求日志ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();Signature signature = joinPoint.getSignature();String name = signature.getName();// 打印业务操作String nameCn = "";if (name.contains("list") || name.contains("query")) {nameCn = "查询";} else if (name.contains("save")) {nameCn = "保存";} else if (name.contains("delete")) {nameCn = "删除";} else {nameCn = "操作";}// 使用反射,获取业务名称Class clazz = signature.getDeclaringType();Field field;String businessName = "";try {field = clazz.getField("BUSINESS_NAME");if (!StringUtils.isEmpty(field)) {businessName = (String) field.get(clazz);}} catch (NoSuchFieldException e) {LOG.error("未获取到业务名称");} catch (SecurityException e) {LOG.error("获取业务名称失败", e);}// 打印请求信息LOG.info("------------- 【{}】{}开始 -------------", businessName, nameCn);LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);LOG.info("远程地址: {}", request.getRemoteAddr());// 打印请求参数Object[] args = joinPoint.getArgs();Object[] arguments  = new Object[args.length];for (int i = 0; i < args.length; i++) {if (args[i] instanceof ServletRequest|| args[i] instanceof ServletResponse|| args[i] instanceof MultipartFile) {continue;}arguments[i] = args[i];}// 排除字段,敏感字段或太长的字段不显示String[] excludeProperties = {"shard"};PropertyPreFilters filters = new PropertyPreFilters();PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();excludefilter.addExcludes(excludeProperties);LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter)); // 为空的会不打印,但是像图片等长字段也会打印}@Around("controllerPointcut()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = proceedingJoinPoint.proceed();// 排除字段,敏感字段或太长的字段不显示String[] excludeProperties = {"password", "shard"};PropertyPreFilters filters = new PropertyPreFilters();PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();excludefilter.addExcludes(excludeProperties);LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter));LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);return result;}}

约定优于配置。又一个约定:查询类接口以list或query开头,保存用save开头,删除用delete开头

敏感字段时不能明文打印或存储,比如身份证,手机号等。

后续会介绍图片上传,图片会转为base64 文本,太长,没有打印的必要,且占用空间,可以不打印。

一个日志跟踪号用来标识一次请求。生产环境中,往往同时会打印多个请求的日志,通过“grep 日志跟踪号” 可以查找出一次请求的所有日志。

1.前端增加统一日志输出

2.加上注释 

添加了一些注释

 删除了输出的日志

对ChapterController.java也是进行了注释和删除

ChapterService.java也是进行了注释和删除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/228258.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系列九、事务

一、事务 1.1、概述 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或者撤销操作请求&#xff0c;即&#xff1a;这些操作要么同时成功&#xff0c;要么同时失败。 例如: 张三给李四转账1000块钱&…

使用邮件群发平台,轻松实现高效沟通的4大优势!

新媒体带动着众多线上平台的发展&#xff0c;使得流量为企业带来了可观的营收。但是&#xff0c;随着短视频市场的饱和&#xff0c;想要再次获得初始时的流量就变得越发困难。在这个时候&#xff0c;企业不妨将眼光往邮件群发这个传统的营销方式上倾斜&#xff0c;特别是出海、…

数据结构之---- 动态规划

数据结构之---- 动态规划 什么是动态规划&#xff1f; 动态规划是一个重要的算法范式&#xff0c;它将一个问题分解为一系列更小的子问题&#xff0c;并通过存储子问题的解来避免重复计算&#xff0c;从而大幅提升时间效率。 在本节中&#xff0c;我们从一个经典例题入手&am…

盛最多水的容器

给定一个长度为 n 的整数列表 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。 示例1&…

Python基础01-环境搭建与输入输出

零、文章目录 Python基础01-环境搭建与输入输出 1、Python概述 &#xff08;1&#xff09;为什么要学习Python 技术趋势&#xff1a;Python自带明星属性&#xff0c;热度稳居编程语言界前三 简单易学&#xff1a;开发代码少&#xff0c;精确表达需求逻辑&#xff1b;33个关…

什么是Maven?

什么是Maven 1、Maven是依赖管理、项目构建工具。 pom.xml springBoot项目的核心配置文件&#xff0c;pom项目对象模型、Dependency依赖管理模型。 Maven中的GAVP是指&#xff1a; 1、GroupId&#xff1a;当前工程组织id&#xff0c;例如&#xff1a;com.jd.tddl 2、ArtifactI…

IS-IS原理与配置

IS-IS原理与配置 • IS-IS&#xff08;Intermediate System to Intermediate System&#xff0c;中间系统到中间系统&#xff09;是ISO &#xff08;International Organization for Standardization&#xff0c;国际标准化组织&#xff09;为它的CLNP &#xff08;ConnectionL…

[ 8 种有效方法] 如何在没有备份的情况下恢复 Android 上永久删除的照片?

我们生命中最重要的时刻&#xff0c;但这样做有缺点&#xff0c;其中之一就是数据丢失的风险。您可能倾向于定期删除无意义的照片&#xff0c;同时保存可爱的照片&#xff0c;从而使您的 Android 设备井井有条。然而&#xff0c;有些人在删除自己珍视的图像时不小心犯了错误。您…

非递归方式遍历二叉树的原理

一、递归遍历代码 // 先序遍历 void PreOrder(BiTNode *T){if (T!NULL){visit(T); // 最简单的visit就是printf(T->data)PreOrder(T->lChild);PreOrder(T->rChild);} }// 中序遍历 void InOrder(BiTNode *T){if (T!NULL){InOrder(T->lchild);visit(T);InOrder(T-…

Linux---文本搜索命令

1. grep命令的使用 命令说明grep文本搜索 grep命令效果图: 2. grep命令选项的使用 命令选项说明-i忽略大小写-n显示匹配行号-v显示不包含匹配文本的所有行 -i命令选项效果图: -n命令选项效果图: -v命令选项效果图: 3. grep命令结合正则表达式的使用 正则表达式说明^以指…

单片机上位机(串口通讯C#)

一、简介 用C#编写了几个单片机上位机模板。可定制&#xff01;&#xff01;&#xff01; 二、效果图

SCI一区级 | Matlab实现GWO-CNN-GRU-selfAttention多变量多步时间序列预测

SCI一区级 | Matlab实现GWO-CNN-GRU-selfAttention多变量多步时间序列预测 目录 SCI一区级 | Matlab实现GWO-CNN-GRU-selfAttention多变量多步时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现GWO-CNN-GRU-selfAttention灰狼算法优化卷积门控循环…

大数据HCIE成神之路之数据预处理(2)——异常值处理

异常值处理 1 异常值处理1.1 散点图1.1.1 实验任务1.1.1.1 实验背景1.1.1.2 实验目标1.1.1.3 实验数据解析 1.1.2 实验思路1.1.3 实验操作步骤1.1.4 结果验证 1.2 基于分类模型的异常检测1.2.1 实验任务1.2.1.1 实验背景1.2.1.2 实验目标1.2.1.3 实验数据解析 1.2.2 实验思路1.…

深入了解Linux网络配置:常见面试问题及解答

学习目标&#xff1a; 解释Linux网络配置的重要性和作用引入常见的面试问题 学习内容&#xff1a; 如何查看当前系统的IP地址和网关信息&#xff1f; 解答&#xff1a;可以使用ifconfig命令来查看当前系统的IP地址和网关信息。通过运行ifconfig命令&#xff0c;将会列出所有可…

数字基础设施及相关产业链报告:数据要素加快推进、AI终端应用加速发展

今天分享的AI系列深度研究报告&#xff1a;《数字基础设施及相关产业链报告&#xff1a;数据要素加快推进、AI终端应用加速发展》。 &#xff08;报告出品方&#xff1a;长城证券&#xff09; 报告共计&#xff1a;16页 1. 行业观点 在 TMT 各子板块&#xff1a;电子、通信、…

【Spring】Spring中的事务

文章目录 1. Spring事务简介2. Spring事务的案例案例代码代码目录结构数据库pom.xmlResource/jdbc.propertiesconfig/SpringConfig.javaconfig/JdbcConfig.javaconfig/MyBatisConfig.javadao/AccountDao.javaservice/AccountService.javaservice/impl/AccountServiceImpl.java测…

【Mode Management】ComM详细介绍

目录 1. Introduction and functional overview 2.Dependencies to other modules 3.Functional specification 3.1 Partial Network Cluster Management 3.2 ComM channel state machine 3.2.1 Behaviour in state COMM_NO_COMMUNICATION 3.2.1.1 COMM_NO_COM_NO_PENDI…

暂退法(丢弃法)

在深度学习中&#xff0c;丢弃法&#xff08;Dropout&#xff09;是一种常用的正则化技术&#xff0c;旨在减少模型的过拟合现象&#xff0c;可能会比之前的权重衰减(Weight Decay)效果更好。通过在训练过程中随机丢弃一部分神经元&#xff0c;可以有效地减少神经网络中的参数依…

Python实验项目9 :网络爬虫与自动化

实验 1&#xff1a;爬取网页中的数据。 要求&#xff1a;使用 urllib 库和 requests 库分别爬取 http://www.sohu.com 首页的前 360 个字节的数据。 # 要求&#xff1a;使用 urllib 库和 requests 库分别爬取 http://www.sohu.com 首页的前 360 个字节的数据。 import urllib.r…