景点与网站合作方案怎么做,nodejs适合网站开发,做网站贵不,能打开各种网站的浏览器下载合集#x1f4d5;开源风云系列 #x1f34a;本系列将从开源名将若依出发#xff0c;探究优质开源项目脚手架汲取编程之道。 #x1f349;从不分离版本开写到前后端分离版#xff0c;再到微服务版本#xff0c;乃至其中好玩的一系列增强Plus操作。 #x1f348;希望你具备如下…开源风云系列 本系列将从开源名将若依出发探究优质开源项目脚手架汲取编程之道。 从不分离版本开写到前后端分离版再到微服务版本乃至其中好玩的一系列增强Plus操作。 希望你具备如下技术栈 SpringSpringMVCMybatis/Mybatis-plusThymeleafSpringBootShiroSpringSecuritySpringCloud云服务器相关知识 本篇是分离版第三篇 文章同时同步到我的个人站点欢迎来访 国内https://www.linxiaoqin.netlify.app国际https://www.linxiaoqin.vercel.app 目录 开源风云系列1、分页1.1、前端封装分页1.2、后端分页方式 2、MyBatisPlus整合2.1、MP测试 3、多数据源4、事务5、操作日志6、数据权限7、服务监控7.1、服务监控前端7.2、栅格布局 8、Swagger9、XSS脚本攻击10、防止重复提交过滤 1、分页 [!NOTE] 分页主要是后端做前端只需要传给后端我要第几页每页多少条数据。 1.1、前端封装分页
以操作日志页面为例子,前端每次给后端发请求的时候都是带了分页参数的默认都是请求第一页每页10条数据。 我们请求后端接口的时候会带上queryParams对象我们进入日志页面就会执行getList方法进而执行list方法发送请求发送请求的时候会带上queryParams、dateRange 参数这个dateRange参数是日期范围也就是前端搜索框筛选日期的参数为什么这个日期范围参数不放在 queryParams 对象里面一起传参进去呢 其实 dateRange 日期范围是在 data 里面和 element ui 的日期选择器进行了 v-model 双向绑定 若依对 element ui 的分页进行了封装包括分页的页码数等等可以结合 element ui 的 Pagination 分页的参数进行更改。 [!NOTE] 前端只需要维护好数据我需要当前是第几页一共有几条数据每页多少条。 前端只需要维护好这三个数据其他的交给后端就行了。 1.2、后端分页方式
后端是通过pagehelper插件实现的。ry通过pagehelper间接引入了mybatis [!NOTE] 若依在 BaseController中封装了分页方法。也就是说我们自己写的controller只要继承了BaseController直接调用startPage方法即可完成分页。分页的源码调试讲解源码解析 很牛值得一看 我们的Controller继承BaseController只需要在我们的Service的查询方法之前加上startPage()就会默认表示sql需要分页。 2、MyBatisPlus整合
关于若依集成MyBatisPlus我们可以按照步骤如下
在根pom.xml中引入mybatis-plus在common模块中继承 在common模块下删除mybatis冲突依赖 填写 yml 配置
mybatis-plus:# 配置mapper的扫描找到所有的mapper.xml映射文件configLocation: classpath:mybatis/mybatis-config.xml# 搜索指定包别名typeAliasesPackage: com.kuang.**.domain删除MyBatisConfig也就是删除ry自己的配置文件,全部注释掉也可以 然后直接重启项目成功 为什么要排除pagehelper中的mybatis依赖
答因为mybatis-plus的启动器和pagehelper都引入了mybatismybatis-plus的启动器的mybatis版本比较高。pagehelper引入的mybatis的版本比较低mybatis-plus用到了高版本mybatis的方法。所以需要排除pagehelper中的mybatis。否则报错没找到方法。但是我自己的版本如下
Pagehelper V1.4.7
mybatis3.5.13mybatis-spring2.1.1
mybatis-plus V3.5.3
mybatis3.5.10mybatis-spring2.0.7
也就是其实mybatis-plus用的版本都是低的版本那么maven会自动将高的版本排除保留低版本的依赖。所以不需要手动排除Maven会自动排除。 那么我把低版本的依赖排除使用高版本的依赖发现也是OK的。 2.1、MP测试
我们测试一下改造一下登录日志的查询首先给 mapper 继承 BaseMapper 在impl实现类下执行查询 访问日志管理会发现报错 控制台打印的SQL语句是
SELECT info_id,user_name,status,ipaddr,login_location,browser,os,msg,login_time,
search_value,create_by,create_time,update_by,update_time,remark,params
FROM sys_logininfor LIMIT ?可以发现我们的登录日志数据库表中就没有 search_value 后面的字段了那么为什么这个sql语句会带呢 是因为继承关系导致的SysLogininfor 继承了 BaseEntity,BaseEntity的属性也被写入了SQL中 所以我们只需要给BaseEntity的不需要的属性上加上注解TableField(exist false) 完成上述操作后再进行登录日志菜单的点击就可以正常查询显示了。
3、多数据源
多数据源不是必须的需求是让一个项目同时连接操作多个数据库。 对于一个请求假如此请求需要查询数据库我们首先是前端点击然后前端给后端发请求后端controller层拿到请求controller把请求发给service层service层把数据再递交给mapper层mapper层在若依中是mybatis接管。 当mybatis看到数据库查询请求后这时候代理模式就开始了我们的mapper层的接口被代理框架直接通过反射拿到当前的代理对象、方法、相关参数、DefaultSqlSession去执行相关查询。 重点来了怎么查询首先要干嘛当然是得到当前数据库的连接Connection然后通过此数据库连接进行执行相关查询。 我们自己写一个类DynamicDataSource动态数据源的英文实现DataSource接口然后重写getConnection方法我们可以在DynamicDataSource类中准备好几个连接然后getConnection方法中根据情况获得不同的连接。 [!NOTE] 总结如果需要切换多个数据源只需要把DataSource注解加到方法上或者类上切面会把注解中的value读出来并设置到线程私有上下文中然后在getConnection的时候读取线程私有的value来确定用哪个数据源。 我们来玩一下三个MySQL数据源
首先在DruidConfig中增加一个数据源 修改一下数据库的枚举 注入三个数据库到动态数据库
Bean
Primary
public DynamicDataSource dataSource(DataSource masterDataSource,DataSource slaveDataSource,DataSource thirdDataSource) {MapObject, Object targetDataSources new HashMap();targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);targetDataSources.put(DataSourceType.SLAVE.name(),slaveDataSource);targetDataSources.put(DataSourceType.THIRD.name(),thirdDataSource);return new DynamicDataSource(masterDataSource, targetDataSources);
}我们在某个方法上面加上注解DataSource(DataSourceType.THIRD),就会使用第三个数据库就完成了切库的操作。
4、事务
若依-Vue中的事务和SpringBoot中完全一致。
在方法或者类上可以假如Transactional注解只要该注解加上方法中无论有多少个执行SQL的地方只要执行遇到一个异常所有的操作都将会回滚。
Transactional注解默认是捕捉运行时异常如果需要抓更大的异常可以设置参数
Transactional(rollbackFor Exception.class)5、操作日志
操作日志是记录用户的某些操作。
为什么要记录呢 可能后续系统出现问题需要回来找证据的时候会用到。 供管理员查看用户的行为。 记录正确或者错误记录也方便程序员修改程序。
总而言之操作日志有它存在的必要。但是操作日志多少也会消耗一些性能。我们所记录的操作日志一般都是用户对数据库进行增删改的记录。对于查询用户并没有更改数据库所以往往不会去记录。
老生常谈AOP。操作日志是通过下面的类实现的: 切面类会切Log注解意思是要某个方法有操作日志只需要手动加上Log注解。IDEA可以帮我们看到我们切了哪个方法。 以新增部门方法为例处理完请求后执行会拿到切入点、拿到Log()注解、拿到请求返回的结果 以用户修改菜单为例我们只需要加上Log(title 用户管理, businessType BusinessType.UPDATE) 当我们对用户信息进行修改的时候就可以在操作日志菜单中看到 6、数据权限
所谓数据权限就是不同的人对数据的权限不同。举个例子一个学校的校长能看到学生管理系统的全部学生的信息进行管理维护而一个老师只能维护管理系统中自己班的学生别的人根本看不到。
一般来说但凡是一个管理类的系统都有着数据方面的权限。ry中的数据权限是AOP切入修改SQL实现的让SQL中拼接一些关于权限的SQL来实现。 可以看到DataScopeAspect切面类切的是注解DataScope处理请求前执行会拿到切入点、拿到DataScope()注解。 我们来实操一下
首先随便建立一个表注意表字段必须有user_id不然怎么和用户关联不和用户表关联谈什么数据权限
比如说有这样一个需求:
作为一个高校的研究生有可能要每周收青年大学习的截图。那么我现在需要学生自己去我的网站上面上传截图并且用户自己只能看到自己上传的截图但是管理员可以看到所有的截图。这样一个需求我们马上安排。
SQL语句如下
-- 创建kuang_picture表
CREATE TABLE kuangstudy_vue.kuang_picture (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键,uri varchar(500) NULL COMMENT 图片uri,user_id bigint(20) NULL COMMENT 关联用户user_id,create_by varchar(50) NULL COMMENT 创建人,create_time datetime NULL COMMENT 创建时间,update_by varchar(50) NULL COMMENT 更新人,update_time datetime NULL COMMENT 更新时间,remark varchar(500) NULL COMMENT 备注,PRIMARY KEY (id)
);这里我就直接使用若依的代码生成器来生成页面只需要注意图片的URI显示类型是图片上传就好。 然后我们使用两个普通用户分别上传图片发现这俩人竟然可以看到别人的图片。 这就不合理了我们要自己开始编码首先要填充 user_id不然谈什么数据权限填充之前表里面这么显示的 我们开始编码填充 然后重启项目我们再次使用普通角色上传图片就会连带着 user_id 显示 那么我们开始进行数据权限的编码需要在查询的SQL上进行改动我们对查询图片列表的sql进行改动 我们给 kuang_picture 、 sys_user 、 sys_dept 三张表分别起别名为 k、u、d ,由于 sys_user 中也有 create_by等字段所以我们要指定查询的是哪张表的user_id、create_by等字段。 所以我们加上查询指定的表名.id、表名.uri等等 最重要的一步记得在sql最后加上 ${params.dataScope} 最后在方法上加上注解DataScope,d表示部门表的别名u表示用户表的别名。 这样就实现了我们使用 Gitee 登录的用户上传图片只有 Gitee 登录的用户可以看到 ry 登录的用户上传图片只有 ry 用户可以看到管理员可以看到所有人的图片。
7、服务监控
从服务监控的前端页面展示看到有CPU信息、内存信息、服务器信息、Java虚拟机信息和磁盘信息。 后端代码如上就是调用API封装成Server对象将信息返回给前端。 7.1、服务监控前端
当打开服务监控的页面时页面会有一个加载的 loading 可能有小伙伴问为啥先getList后loading的为啥loading还能走到 因为getList里面有Promise的异步请求操作。异步不会阻塞。此外getList方法的接口还睡了一秒钟所以正常来说getList后执行完毕。 7.2、栅格布局
我们自己来仿照若依的服务监控来写一个栅格布局首先新建菜单 之后去views/kuang下新建kuang_server.vue
templatediv classkuang-containel-row :gutter30el-col :span12el-carddiv slotheaderspani classel-icon-cpu/i CPU/spanel-button stylefloat: right; padding: 3px 0 typetext操作按钮/el-button/divdivel-descriptions :column2el-descriptions-item label核心数 v-ifserver.cpuel-tag sizemedium{{ server.cpu.cpuNum }}核/el-tag/el-descriptions-itemel-descriptions-item label系统使用率v-ifserver.cpuel-tag sizesmall{{ server.cpu.sys }}%/el-tag/el-descriptions-itemel-descriptions-item label用户使用率v-ifserver.cpuel-tag sizesmall{{ server.cpu.used }}%/el-tag/el-descriptions-itemel-descriptions-item label当前等待率v-ifserver.cpuel-tag sizesmall{{ server.cpu.wait }}%/el-tag/el-descriptions-itemel-descriptions-item label当前空闲率v-ifserver.cpuel-tag sizesmall{{ server.cpu.free }}%/el-tag/el-descriptions-item/el-descriptions/div/el-card/el-colel-col :span6el-carddiv slotheaderspani classel-icon-tickets/i 服务器内存/spanel-button stylefloat: right; padding: 3px 0 typetext操作按钮/el-button/divdivel-descriptions :column2el-descriptions-item label总内存 v-ifserver.memel-tag sizemedium{{ server.mem.total }}G/el-tag/el-descriptions-itemel-descriptions-item label已用内存v-ifserver.memel-tag sizesmall{{ server.mem.used }}G/el-tag/el-descriptions-itemel-descriptions-item label剩余内存v-ifserver.memel-tag sizesmall{{ server.mem.free }}G/el-tag/el-descriptions-itemel-descriptions-item label使用率v-ifserver.memel-tag sizesmall{{ server.mem.usage }}%/el-tag/el-descriptions-item/el-descriptions/div/el-card/el-colel-col :span6el-carddiv slotheaderspani classel-icon-tickets/i JVM内存/spanel-button stylefloat: right; padding: 3px 0 typetext操作按钮/el-button/divdivel-descriptions :column2el-descriptions-item label总内存 v-ifserver.memel-tag sizemedium{{ server.jvm.total }}M/el-tag/el-descriptions-itemel-descriptions-item label已用内存v-ifserver.memel-tag sizesmall{{ server.jvm.used }}M/el-tag/el-descriptions-itemel-descriptions-item label剩余内存v-ifserver.memel-tag sizesmall{{ server.jvm.free }}M/el-tag/el-descriptions-itemel-descriptions-item label使用率v-ifserver.memel-tag sizesmall{{ server.jvm.usage }}%/el-tag/el-descriptions-item/el-descriptions/div/el-card/el-col/el-rowel-row classkuang-rowel-colel-carddiv slotheaderspani classel-icon-monitor/i 服务器信息/spanel-button stylefloat: right; padding: 3px 0 typetext操作按钮/el-button/divdivel-descriptions :column2el-descriptions-item label服务器IP v-ifserver.sysel-tag sizemedium{{ server.sys.computerIp }}/el-tag/el-descriptions-itemel-descriptions-item label服务器名称v-ifserver.sysel-tag sizesmall{{ server.sys.computerName }}/el-tag/el-descriptions-itemel-descriptions-item label系统架构v-ifserver.sysel-tag sizesmall{{ server.sys.osArch }}/el-tag/el-descriptions-itemel-descriptions-item label操作系统v-ifserver.sysel-tag sizesmall{{ server.sys.osName }}/el-tag/el-descriptions-itemel-descriptions-item label项目路径v-ifserver.sysel-tag sizemedium{{ server.sys.userDir }}/el-tag/el-descriptions-item/el-descriptions/div/el-card/el-col/el-rowel-row classkuang-rowel-colel-carddiv slotheaderspanJava虚拟机/spanel-button stylefloat: right; padding: 3px 0 typetext操作按钮/el-button/divdivel-descriptions :column3el-descriptions-item labelJava名称 v-ifserver.jvmel-tag sizemedium{{ server.jvm.name }}/el-tag/el-descriptions-itemel-descriptions-item label服务启动时间 v-ifserver.jvmel-tag sizesmall{{ server.jvm.startTime }}/el-tag/el-descriptions-itemel-descriptions-item labeljdk安装路径 v-ifserver.jvmel-tag sizesmall{{ server.jvm.home }}/el-tag/el-descriptions-itemel-descriptions-item labeljdk版本 v-ifserver.jvmel-tag sizesmall{{ server.jvm.version }}/el-tag/el-descriptions-itemel-descriptions-item label最大可用内存数 v-ifserver.jvmel-tag sizesmall{{ server.jvm.max }}M/el-tag/el-descriptions-itemel-descriptions-item label运行时长 v-ifserver.jvmel-tag sizemedium{{ server.jvm.runTime }}/el-tag/el-descriptions-item/el-descriptions/div/el-card/el-col/el-row/div
/templatescript
import {getServer} from /api/monitor/server;export default {name: kuang_server,created() {this.getList();this.openLoading();},data(){return {server: [],}},methods: {/** 查询服务器信息 */getList() {getServer().then(response {this.server response.data;console.log(this.server);this.$modal.closeLoading();});},// 打开加载层openLoading() {this.$modal.loading(正在加载服务监控数据请稍候);}}
}
/scriptstyle scoped
.kuang-contain{margin: 30px;
}.kuang-row{margin-top: 30px;
}/style 仿造若依写一个监控页面就完成了。
8、Swagger
原生swagger不但很丑而且参数配置的请求list还有必填项很难绕过所以我们来换个皮肤
在跟pom.xml中引入
!--Swagger增强--
dependencygroupIdcom.github.xiaoymin/groupIdartifactIdknife4j-spring-ui/artifactIdversion${knife4j-spring-ui.version}/version
/dependency在admin的pom.xml中继承
!--Swagger增强--
dependencygroupIdcom.github.xiaoymin/groupIdartifactIdknife4j-spring-ui/artifactId
/dependency编写前端vue文件
templatei-frame :srcurl /
/template
script
import iFrame from /components/iFrame/index;
export default {name: kuang_swagger,components: { iFrame },data() {return {url: process.env.VUE_APP_BASE_API /doc.html};},
};
/script添加菜单 如此便大功告成 我们来看一下SwaggerConfig启用Swagger哪些接口暴露给Swagger展示呢我们可以使用注解也可以指定扫描哪个包中的注解也可以扫描所有的api。最后还设置了一个请求前缀pathMapping在 yml 中配置为 /dev-api意思是说请求项目的/abc接口实际上需要请求/dev-api/abc这一点需要特别注意 同时在类中可以看到Swagger文档的标题、描述设置等。 那么我们如何使用Swagger呢我们想要自己的API展示到Swagger里面该怎么做呢很简单我们在自己包也引入关于Swagger的依赖
!-- swagger3--
dependencygroupIdio.springfox/groupIdartifactIdspringfox-boot-starter/artifactId
/dependency!-- 防止进入swagger页面报类型转换错误排除3.0.0中的引用手动增加1.6.2版本 --
dependencygroupIdio.swagger/groupIdartifactIdswagger-models/artifactIdversion1.6.2/version
/dependency!--Swagger增强--
dependencygroupIdcom.github.xiaoymin/groupIdartifactIdknife4j-spring-ui/artifactId
/dependency然后在自己的方法上加上注解ApiOperation(xxxx) 然后就可以在Swagger中看到啦 9、XSS脚本攻击
以若依系统为例我们在修改用户的时候企图在备注中植入 JavaScript 脚本然后将会被ry的XSS脚本攻击拦截 若依如何抵抗XSS攻击呢
当xss.enabled为true的时候会开启XSS攻击防护
首先new一个过滤注册器FilterRegistrationBean当请求来的时候会触发这个过滤器注册器里面设置拦截路径拦截路径也就是我们在yml中配置的/system/*,/monitor/*,/tool/*,给过滤器设置一个名字叫xssFilter设置过滤器的执行顺序优先级为最高最终将yml中排除的链接初始化进过滤器也就是排除的链接/system/notice不需要开启XSS防护。 过滤器可以根据DispatcherType的类型选择是否执行FilterRegistrationBean这个类用于把我们的过滤器注册到servlet容器中registration.addUrlPatterns用于设置拦截的URLregistration.setOrder用于设置优先级数字越小优先级越高registration.setInitParameters用于设置初始化参数例如可以设置excludes排除的URL然而逻辑需要自己实现默认不支持接受参数类型为MapString, String 接着我们看看XSS过滤器到底干了什么 我们这里直接说结论
init方法主要是为了处理排除的URLhandleExcludeURL方法用于看看URL是不是排除的URL如果是GET和DELETE则直接不过滤另外如果所请求的URL在排除URL也直接不过滤XSS过滤原理是重新包装了Request重写了getParameterValues方法和getInputStream方法。getParameterValues方法用于获取参数值的数组如果存在值那么就进行HTML过滤并且排除前后空格。getInputStream方法用于处理JSON类型如果发现是JSON那么会先HTML过滤然后重新包装成流进行返回。
10、防止重复提交过滤
我们讲述过前端的防止重复提交如果请求数据和请求URL和最近一次请求一致并且请求间隔小于1000ms就进行请求拦截直接拒绝当前请求。
从我上面的描述发现了一个bug总有手快的人喜欢点A按钮然后立刻点B按钮然后又立刻点A按钮。那么对于A按钮是重复提交了但是又不满足前端判断重复请求的条件于是重复请求进入了后端这时候就需要后端再次校验是不是重复请求。
后端过滤重复请求也是通过过滤器并且和XSS过滤惊人的相似。 首先new一个注册器然后注册一个重复过滤器拦截所有路径优先级为最低注入到Spring容器中。