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.rhkb.cn/news/219004.html

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

相关文章

【Linux】多线程编程

目录 1. 线程基础知识 2. 线程创建 3. 线程ID&#xff08;TID&#xff09; 4. 线程终止 5. 线程取消 6. 线程等待 7. 线程分离 8. 线程互斥 8.1 初始化互斥量 8.2 销毁互斥量 8.3 互斥量加锁和解锁 9. 可重入和线程安全 10. 线程同步之条件变量 10.1 初始化条件变…

一文了解Tomcat

文章目录 1、Tomcat介绍2、Tomcat使用配置2.1、Tomcat下载启动2.2、Tomcat启动乱码2.3、Tomcat端口号修改 3、Tomcat项目部署4、IDEA中使用Tomcat方式 1、Tomcat介绍 什么是Tomcat ​ Tomcat是Apache软件基金会一个核心项目&#xff0c;是一个开源免费的轻量级web服务器&#x…

【算法Hot100系列】最长回文子串

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【机器学习】应用KNN实现鸢尾花种类预测

目录 前言 一、K最近邻&#xff08;KNN&#xff09;介绍 二、鸢尾花数据集介绍 三、鸢尾花数据集可视化 四、鸢尾花数据分析 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Fil…

js基础入门

先来一点js基础&#xff0c;其实js大部分的时候都在处理对象或者数组。 对象四个基本操作&#xff1a;增删改查 掌握元素的增删改查&#xff0c;了解如何拷贝&#xff0c;深拷贝和浅拷贝的区别。详情见代码 <script>//创建对象一共有三种赋值声明的语法let obj{} //赋值…

大数据存储技术(3)—— HBase分布式数据库

目录 一、HBase简介 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;特点 &#xff08;三&#xff09;HBase架构 二、HBase原理 &#xff08;一&#xff09;读流程 &#xff08;二&#xff09;写流程 &#xff08;三&#xff09;数据 flush 过程 &#xf…

Java stream 进阶版

1、Stream 概述 Java 8 引入了 Stream API,它是一种用于简化集合和数组操作的强大工具。Stream API 允许我们将集合或数组视为流,并在流上进行各种操作,如筛选、排序、聚合等。 Stream API 的核心概念是 Stream 流,它代表了一个数据流,其中包含了一系列的元素。这些元素…

【LeetCode刷题-排序】--179.最大数

179.最大数 思路&#xff1a; 方法&#xff1a;自定义排序 class Solution {public String largestNumber(int[] nums) {if(nums null || nums.length 0){return "";}//将每个数字转换成字符串String[] strs new String[nums.length];for(int i 0;i < nums.l…

怎样长时间保持SSH会话连接不断开?

操作场景 使用SSH方式登录CentOS Stream操作系统的云服务器时&#xff0c;过一段时间就会自动断开连接。 该文档适用于CentOS/EulerOS系统。 操作方法 编辑/etc/ssh/sshd_config文件设置心跳&#xff0c;保持连接。 编辑/etc/ssh/sshd_config&#xff0c;添加配置项&#x…

【C语言】——认识指针变量和地址,以及指针变量类型的意义

&#x1f3a5; 岁月失语唯石能言的个人主页 &#x1f525;个人栏专&#xff1a;秒懂C语言 ⭐若在许我少年时&#xff0c;一两黄金一两风 目录 前言 一、指针变量和地址 1.1 取地址操作符&#xff08;&&#xff09; 1.2 指针变量和解引用操作符&#xff…

scrapy post请求——百度翻译(十四)

scrapy处理 post 请求 爬取百度翻译界面 目录 1.创建项目及爬虫文件 2.发送post请求 1.创建项目及爬虫文件 scrapy startproject scrapy_104 scrapy genspider translate fanyi.baidu.com 2.发送请求 post请求需要传递参数&#xff0c;所以就不能用start_urls和parse函数了&…

飞天使-docker知识点6-容器dockerfile各项名词解释

文章目录 docker的小技巧dockerfile容器为什么会出现启动了不暂停查看docker 网桥相关信息 docker 数据卷 docker的小技巧 [rootlight-test playbook-vars[]# docker inspect -f "{{.NetworkSettings.IPAddress}}" d3a9ae03ae5f 172.17.0.4docker d3a9ae03ae5f:/etc…

QT----第三天,Visio stdio自定义封装控件,鼠标事件,定时器,事件分发器过滤器,绘图事件

目录 第三天1 自定义控件封装2 QT鼠标事件3 定时器4 event事件分发器5 事件过滤器6 绘图事件Qpainter 源码&#xff1a;CPP学习代码 第三天 1 自定义控件封装 新建一个QT widgetclass&#xff0c;同时生成ui,h,cpp文件 在smallWidget.ui里添加上你想要的控件并调试大小 回到…

《Linux C编程实战》笔记:一些系统调用

目录 dup和dup2函数 fcntl函数 示例程序1 示例程序2 ioctl函数 dup和dup2函数 #include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd): dup 函数复制 oldfd 参数所指向的文件描述符。 参数&#xff1a; oldfd&#xff1a;要复制的文件描述符的…

zookeeper4==zookeeper源码阅读,FOLLOWER收到了需要LEADER执行的命令后各节点会执行什么

上面已经阅读并观察了节点确定自己的身份后会做些什么&#xff0c;大致就是比对双方信息然后完成同步。 本篇阅读&#xff0c; FOLLOWER收到了需要LEADER执行的命令后&#xff0c;怎么同步给LEADER的&#xff0c;并且LEADER会执行什么操作。 源码启动zkCli用于测试 将原本的代…

技术阅读周刊第十期

技术阅读周刊&#xff0c;每周更新。 周四加了个班&#xff0c;周五没缓过来&#xff0c;就推迟到今天更新了 历史更新 20231117&#xff1a;第六期20231124&#xff1a;第七期20231201&#xff1a;第八期20231215&#xff1a;第九期 Golang: 14 Shorthand Tricks You Might No…

LLM中的Prompt提示

简介 在LLM中&#xff0c;prompt&#xff08;提示&#xff09;是一个预先设定的条件&#xff0c;它可以限制模型自由发散&#xff0c;而是围绕提示内容进行展开。输入中添加prompt&#xff0c;可以强制模型关注特定的信息&#xff0c;从而提高模型在特定任务上的表现。 结构 …

hive数据仓库工具

1、hive是一套操作数据仓库的应用工具&#xff0c;通过这个工具可实现mapreduce的功能 2、hive的语言是hql[hive query language] 3、官网hive.apache.org 下载hive软件包地址 Welcome! - The Apache Software Foundationhttps://archive.apache.org/ 4、hive在管理数据时分为元…

『踩坑记录』IDEA Spring initialzr新建Spring项目不能选择jdk8的解决方法

问题描述 Spring initializr新建Spring项目不能选低版本java 解决方法 默认官方start.spring.io已不支持自动生成低版本jkd的Spring项目&#xff0c;自定义用阿里云的starter即可 用阿里云的就能支持低版本jdk了 完 欢迎关注我的CSDN博客 &#xff1a;Ho1aAs 版权属于&a…

【C++】 C++11 新特性探索:decltype 和 auto

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ decltype推导变量类型推导函数返回类型 2️⃣ auto自动推导变量类型迭代器和范围循环 3️⃣ decltype 和 auto 同时使用&#x1f6ec; 结论&#x1f4d6; 参考资料 &#x1f6eb; 问题 描述 C11 引入了一些强大的新特性&#xff…