Guns 技术文档 v5.1

2023-11-05

@stylefeng 2018-10-17 10:22 字数 27433 阅读 4795

Guns 技术文档 v5.1

stylefeng技术文档


 

 


1. 序言

1.1 文档简介

本文档基于最新的Guns版本,集Guns使用手册Guns开发手册Guns核心思想等于一体,并整理了qq群gitee上用户经常反馈的问题的答疑!本文档最好的阅读方式是从上到下依次阅读(推荐),也可根据需要直接从目录查看相关文档!感谢您对Guns的支持!

1.2 Guns教程

教程采用视频的形式,讲述了Guns作者近年来工作经验的总结,以及自2017年3月份编写Guns的感悟。教程历时两个月精心打造,希望大家多多支持!

点击查看教程详细介绍

教程零售价格:199元

如何获取教程? 
请添加作者(stylefeng)qq 332464581(请备注购买教程)或加入下方qq群联系群主购买

1.3 获取帮助


2. 使用手册

注意:

2.1 下载项目

登录码云平台,打开Guns主页,点击下载按钮下载 
image_1c4hmao6j5bmvv91cg51vqlqvd1s.png-118.4kB

2.2 导入项目

2.2.1 eclipse导入

  1. 导入之前请检查eclipse的maven配置是否本机所安装的maven(一般不用eclipse自带的maven),如下 
    image_1c4hnssmqqcl99t1iq1r4q1vgm29.png-60.2kB

  2. 检查maven安装目录下的settings.xml是否配置了阿里云镜像 
    image_1c4hnv9u49e81sns188p16041n3n2m.png-9.8kB

  3. 再次检查eclipse中maven的配置是否应用了当前maven安装目录的settings.xml配置文件(个人习惯全局和用户配置设置为一个),如下 
    image_1c4ho2i5pkq52e71d3a1il91gld33.png-58.7kB

  4. 以上设置完成,需要重启一下eclipse

  5. 点击eclipse菜单File->import,出现如下界面,选择Existing maven project 
    image_1c4hoa3t0a0o1q1i1gng1qbe1nai3g.png-39.6kB

  6. 找到下载的项目目录,并点击所有模块,之后点击Finish,导入成功

2.2.2 IDEA导入

  1. 同样,导入前检查IDEA的maven配置是否正确 
    image_1c4j6lfd113i71qdeqimt9usf5m.png-228.6kB

  2. 检查maven安装目录下的settings.xml是否配置了阿里云镜像(同2.2.1节2步)

  3. 进入IDEA主界面,点击open,并选择下载好的guns代码的根目录 
    image_1c4j6ssdt1ip35nqsqeb231f7g13.png-40.5kB

2.3 运行项目

运行前的准备:

  • 安装mysql数据库,作者所用mysql版本为5.7
  1. 执行guns-admin模块下的sql/guns.sql脚本,初始化guns的数据库环境

  2. 打开guns-admin/src/main/resources/application.yml配置文件,修改数据连接账号密码,改为您所连接数据库的配置,local为本地开发环境,dev为开发服务器的环境,test为测试服务器的环境,produce为正式上线的环境 
    image_1cprhp62fts184a1m1cei8f6n9.png-37.5kB

  3. 如需修改服务器端口或者context-path,默认的context-path为/,可参考下图 
    image_1ch7lopru1ptj1i9u1smfr5s1ag2m.png-11.2kB

  4. 执行GunsApplication类中的main方法,即可运行Guns系统

  5. 打开浏览器,输入localhost:8080,即可访问到Guns的登录页面,默认登录账号密码: admin/111111

2.4 打包部署

目前Guns支持两种打包方式,即jar包war包

  1. 打包之前修改guns-admin.pom中的packaging节点,改为jar或者war 
    image_1c4j9c41o15jh17psgso3cdspf34.png-142.1kB

  2. 在项目的guns-parent目录执行maven 命令clean package -Dmaven.test.skip=true,即可打包,如下 
    image_1cpri2cmj1a1enr310ptb3k1tfjp.png-95.7kB 
    image_1c4j9fht81l07o7g10bk1e7g109a3h.png-34.1kB

  3. 命令执行成功后,在guns-admin/target目录下即可看到打包好的文件 
    image_1cprj62u51dqp10vd1c27j6cfkl16.png-63.5kB

提示:若打的包为jar包,可通过java -jar guns-admin-1.0.0-SNAPSHOT.jar来启动Guns系统


3. 开发手册

用Guns开发手头常备如下几个工具:

3.1 了解Guns

3.1.1 模块结构

新版的5.1版本的Guns结构,开发环境由多模块变成了单模块,化繁为简,返璞归真, 
image_1cprjb85s1qm2ja91cj111qulf41j.png-66kB

但是pom中还是依赖了作者开发的两个其他模块, 
image_1cprjcdj5vdjtr43fr1h5jum120.png-58.1kB

这俩模块作者已经上传到maven的中央仓库中(https://search.maven.org/search?q=cn.stylefeng

guns-generator为代码生成模块,其中代码生成模块整合了mybatis-plus的代码生成器和guns独有的代码生成器,可以一键生成entity,dao,service,html,js等代码,可减少很多开发新模块的工作量,此模块的gitee地址为https://gitee.com/stylefeng/guns-generator

kernel-core模块为抽象出的核心(通用)模块,以供其他模块调用,此模块主要封装了一些通用的工具类,公共枚举,常量,配置等等,此模块的gitee地址是https://gitee.com/stylefeng-Roses/roses-kernel

3.1.2 包结构说明

image_1cpuek1o0a3p12p81hii1ic31pa69.png-125.4kB

3.2 实战开发

Guns开发三部曲 -> 1.建表 2.代码生成 3.添加菜单 4.适配业务代码

下面以一个订单业务为例,实战演练如何用Guns编写简单的增删改查业务

3.2.1 建表

新建订单表如下: 
image_1c4k94iouk5ep8ttl72011loi9.png-31.5kB

 
  1. DROP TABLE IF EXISTS `biz_order`;
  2. CREATE TABLE `biz_order` (
  3. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  4. `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名称',
  5. `place` varchar(255) DEFAULT NULL COMMENT '下单地点',
  6. `create_time` datetime DEFAULT NULL COMMENT '下单时间',
  7. `user_name` varchar(255) DEFAULT NULL COMMENT '下单用户名称',
  8. `user_phone` varchar(255) DEFAULT NULL COMMENT '下单用户电话',
  9. PRIMARY KEY (`id`) USING BTREE
  10. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='订单表';
  11.  
  12. SET FOREIGN_KEY_CHECKS = 1;

3.2.2 代码生成

登录管理系统,打开代码生成页面,填写如下内容,注意看红线部分内容 
image_1c4k9ladaqbksvfhtlrdb1t9jm.png-90kB

下面详细讲解代码生成使用: 
1. 项目路径: 代码生成的路径,具体到guns-admin模块的绝对路径,一般不需要修改,因为程序会自动计算出guns-admin的绝对路径 
2. 项目的包: 为guns-admin的同GunsApplication类同一目录的包,如下图,一般也不需要修改 
image_1cpuemk8c1pi11dl41m1t47213o8m.png-31.8kB 
3. 核心包: gun-core的包,一般也不需要修改 
4. 作者: 填写代码生成出的注释上的作者 
5. 业务名称: 生成业务的中午名称 
6. 模块名称: 对应代码中modular包下的模块名称,如下图,若模块名称填order,则生成出的业务代码回到order包下 
image_1cpueocmfg721rcphg51dc4afr13.png-35.9kB 
7. 父级菜单: 此项的选择会影响生成sql添加菜单项的切入点,生成出的sql文件执行后可自动增加到sys_menu菜单项,省去手动添加菜单的繁琐 
8. 表前缀: 填写此项会自动移除生成实体,mapper和service类的名称中包含的重复前缀,例如生成订单表业务代码时,填写biz_,则生成的实体中不会包含Biz前缀名称,若不填写,则生成的实体类为BizOrder 
9. 数据表: 选择即为生成该表所对应的实体,dao,service等类 
10. 模板: 选择后生成相应的控制器,实体,service,dao代码等等

生成代码之后需要重启一下管理系统,生成的代码才可以生效!

3.3.3 添加菜单与分配权限

生成代码之后,需要为管理系统添加菜单,才可以让新增加的业务显示到页面上,添加菜单有两种方式: 
第一种为手动添加菜单,依次点击系统管理->菜单管理->点击添加,打开添加页面,如下 
image_1c4kcll604kcn1dd6u1160168b2n.png-54.2kB

这里需要注意如下几点:

  • 请求地址需要和Controller中的RequestMapping的值一致
  • 排序为同层级菜单中显示菜单的顺序
  • 父级编号的选择可以更改菜单插入的位置
  • 图标可以从H+的资源库中获取
  • 因为菜单管理不单单是对管理系统中的菜单管理,也包含权限的管理,所以需要选择是否是菜单这个选项

第二种添加菜单的方式为直接执行代码生成中的sql脚本,默认生成的sql文件在src/main/java目录下,如下所示

 
  1. INSERT INTO `guns`.`sys_menu` (`id`, `code`, `pcode`, `pcodes`, `name`, `icon`, `url`, `num`, `levels`, `ismenu`, `tips`, `status`, `isopen`) VALUES ('956388083570089986', 'order', '0', '[0],', '订单管理', '', '/order', '99', '1', '1', NULL, '1', '0');
  2. INSERT INTO `guns`.`sys_menu` (`id`, `code`, `pcode`, `pcodes`, `name`, `icon`, `url`, `num`, `levels`, `ismenu`, `tips`, `status`, `isopen`) VALUES ('956388083570089987', 'order_list', 'order', '[0],[order],', '订单管理列表', '', '/order/list', '99', '2', '0', NULL, '1', '0');
  3. INSERT INTO `guns`.`sys_menu` (`id`, `code`, `pcode`, `pcodes`, `name`, `icon`, `url`, `num`, `levels`, `ismenu`, `tips`, `status`, `isopen`) VALUES ('956388083570089988', 'order_add', 'order', '[0],[order],', '订单管理添加', '', '/order/add', '99', '2', '0', NULL, '1', '0');
  4. INSERT INTO `guns`.`sys_menu` (`id`, `code`, `pcode`, `pcodes`, `name`, `icon`, `url`, `num`, `levels`, `ismenu`, `tips`, `status`, `isopen`) VALUES ('956388083570089989', 'order_update', 'order', '[0],[order],', '订单管理更新', '', '/order/update', '99', '2', '0', NULL, '1', '0');
  5. INSERT INTO `guns`.`sys_menu` (`id`, `code`, `pcode`, `pcodes`, `name`, `icon`, `url`, `num`, `levels`, `ismenu`, `tips`, `status`, `isopen`) VALUES ('956388083570089990', 'order_delete', 'order', '[0],[order],', '订单管理删除', '', '/order/delete', '99', '2', '0', NULL, '1', '0');
  6. INSERT INTO `guns`.`sys_menu` (`id`, `code`, `pcode`, `pcodes`, `name`, `icon`, `url`, `num`, `levels`, `ismenu`, `tips`, `status`, `isopen`) VALUES ('956388083570089991', 'order_detail', 'order', '[0],[order],', '订单管理详情', '', '/order/detail', '99', '2', '0', NULL, '1', '0');

执行完成后可以看到,菜单管理页面中已经有了新添加的订单相关的菜单和资源,如下 
image_1c4lpa1c41ndm3c31na83oltf79.png-284.2kB

在添加完菜单只有,还需要给角色分配相关的菜单权限,才可以把新增的业务显示到菜单上

打开系统管理->角色管理,给当前的登录的超级管理员,增加刚才新增的权限,如下图 
image_1c4lpcuabtoj1nnc1e4n7l4da1m.png-143.8kB

配置完成刷新页面即可看到,即可看到新增加的菜单,如下图,若看不到请重新登录 
image_1c4lr5pkd1634h5o7t1bcu1jh813.png-22.9kB

到这里,基本的增删改查功能就实现了,如下图 
image_1c4lr9ort199k11vcoq91h621u8p20.png-44.1kB 
image_1c4lrbeu4v9va5u160oevjni22d.png-56kB

3.3.4 编写业务代码

由于Guns的代码生成器还不能实现100%的智能,所以生成之后还需要对生成的代码做一些完善,如果有除了增删改查以外的业务,还需要手动编写。例如,上面编写的添加订单和修改订单里,下单时间默认是text文本框,这里需要手动改为laydate样式的日期框,如下图 
image_1c4mlgs5v46dsf1uadj3a1sna9.png-27.4kB

3.3 权限控制与校验

3.3.1 用户,角色和资源

用户、角色和资源(或者说权限),这三者的关系是用户对应角色角色对应资源,菜单和所有的按钮都可以看做是资源(或权限),把某一个角色赋予相应的资源,那么该角色就会有访问该资源的权限,否则,该角色访问这些被管控的资源就会被服务器返回403 没有权限,当角色绑定资源后还需要给用户赋予角色才可以让登录的用户访问相关服务器接口。

一句话概括: 用户对应角色,角色对应资源

3.3.2 如何对资源进行权限控制

Guns系统中,通过在控制器上加@Permission注解进行权限校验,如下所示,该接口在被访问的时候,就会进行权限校验

image_1c4mn6qsl1be51mq512v18rf1ehdm.png-19.5kB

通过我们查找用户对应的角色,并查找角色对应的资源,可以找到,当前用户(admin)有该资源的权限,如下

image_1c4mnc40e1k5l137o1oj99f6qou13.png-12.1kB

@Permission注解中可以带一个String数组类型的参数,如下,加上该参数,则接口被限制为只有某个或某些角色才可访问

image_1c4p772891jej148n87g1roa1ge89.png-26.9kB

权限的检查是通过AOP拦截@Permission注解完成的,当访问受权限控制的资源时,AOP对当前请求的servletPath和数据库中sys_menu表的url字段进行匹配,如果当前用户所拥有的权限包含当前请求的servletPath,则访问这个接口成功

3.3.3 前端页面对权限资源的显示

在前端页面中,如果增删改查等按钮受权限控制,则我们需要对资源进行一个权限检查,如果有该资源的权限,才能让该按钮显示,通过beetlshiro注册方法即可完成该项的检查

 
  1. @if(shiro.hasPermission("/menu/add")){
  2. <#button name="添加" icon="fa-plus" clickFun="Menu.openAddMenu()"/>
  3. @}
  4. @if(shiro.hasPermission("/menu/edit")){
  5. <#button name="修改" icon="fa-edit" clickFun="Menu.openChangeMenu()" space="true"/>
  6. @}
  7. @if(shiro.hasPermission("/menu/remove")){
  8. <#button name="删除" icon="fa-remove" clickFun="Menu.delMenu()" space="true"/>
  9. @}

其中shiro.hasPermission()起到了权限检查的作用,如果有该资源对应的权限,则被检查的资源显示,若没有该资源的权限,则按钮不显示

若想深入了解shiro和权限控制的实现原理,可参考视频教程第12节 shiro与权限系统,内有70分钟详细的讲解

3.4 多数据源的使用

首先,我们新建一个数据库guns_test,并分别在guns数据库和guns_test数据库中分别新增同样结构的两个表test,sql文件如下,也可以在src/test/sql下找到这个sql文件

 
  1. DROP DATABASE IF EXISTS guns_test;
  2. CREATE DATABASE IF NOT EXISTS guns_test DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
  3.  
  4. use guns_test;
  5.  
  6. SET NAMES utf8mb4;
  7. SET FOREIGN_KEY_CHECKS = 0;
  8.  
  9. -- ----------------------------
  10. -- Table structure for test
  11. -- ----------------------------
  12. DROP TABLE IF EXISTS `test`;
  13. CREATE TABLE `test` (
  14. `aaa` int(11) NOT NULL AUTO_INCREMENT,
  15. `bbb` varchar(255) DEFAULT NULL,
  16. PRIMARY KEY (`aaa`) USING BTREE
  17. ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
  18.  
  19. SET FOREIGN_KEY_CHECKS = 1;

1.对表进行代码生成,方便测试两个数据源

image_1c4mosmth1fp4dv81ai6nbll8u1g.png-92.7kB

2.打开application.yml中的多数据源开关

image_1ci8k9e4l1kmtjmsh4a12rb10q39.png-87.8kB

3.配置application.yml中的多数据源的连接信息

image_1ci8kbvun1m7b1t0g1g7r1k7u1f58m.png-92.2kB

另外注意,如果想开启多数据源,需要关闭kernel-core中mybatis-plus中的自动配置!!重要!!如下!!

image_1cpuhger9l1722v1afo8ab1vns1g.png-81.1kB

4.编写测试多数据源的代码,注意观察@DataSource注解,这些代码都可以在cn.stylefeng.guns.multi包中找到

 
  1. package cn.stylefeng.guns.multi.service;
  2.  
  3. /**
  4. * <p>
  5. * 服务类
  6. * </p>
  7. *
  8. * @author fengshuonan
  9. * @since 2018-07-10
  10. */
  11. public interface TestService {
  12.  
  13. /**
  14. * 测试多数据源的业务
  15. *
  16. * @author stylefeng
  17. * @Date 2017/6/23 23:02
  18. */
  19. void testBiz();
  20.  
  21. /**
  22. * 测试多数据源的业务
  23. *
  24. * @author stylefeng
  25. * @Date 2017/6/23 23:02
  26. */
  27. void testGuns();
  28.  
  29. }
 
  1. package cn.stylefeng.guns.multi.service.impl;
  2.  
  3. import cn.stylefeng.guns.core.common.constant.DatasourceEnum;
  4. import cn.stylefeng.guns.multi.entity.Test;
  5. import cn.stylefeng.guns.multi.mapper.TestMapper;
  6. import cn.stylefeng.guns.multi.service.TestService;
  7. import cn.stylefeng.roses.core.mutidatasource.annotion.DataSource;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.transaction.annotation.Transactional;
  11.  
  12. /**
  13. * <p>
  14. * 服务实现类
  15. * </p>
  16. *
  17. * @author fengshuonan
  18. * @since 2018-07-10
  19. */
  20. @Service
  21. public class TestServiceImpl implements TestService {
  22.  
  23. @Autowired
  24. private TestMapper testMapper;
  25.  
  26. @Override
  27. @DataSource(name = DatasourceEnum.DATA_SOURCE_BIZ)
  28. @Transactional
  29. public void testBiz() {
  30. Test test = new Test();
  31. test.setBbb("bizTest");
  32. testMapper.insert(test);
  33. }
  34.  
  35. @Override
  36. @DataSource(name = DatasourceEnum.DATA_SOURCE_GUNS)
  37. @Transactional
  38. public void testGuns() {
  39. Test test = new Test();
  40. test.setBbb("gunsTest");
  41. testMapper.insert(test);
  42. }
  43. }
 
  1. package cn.stylefeng.guns.multi.test;
  2.  
  3. import cn.stylefeng.guns.base.BaseJunit;
  4. import cn.stylefeng.guns.multi.service.TestService;
  5. import org.junit.Test;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7.  
  8. /**
  9. * 业务测试
  10. *
  11. * @author fengshuonan
  12. * @date 2017-06-23 23:12
  13. */
  14. public class BizTest extends BaseJunit {
  15.  
  16. @Autowired
  17. TestService testService;
  18.  
  19. @Test
  20. public void test() {
  21. testService.testGuns();
  22.  
  23. testService.testBiz();
  24. }
  25. }

5.执行BizTest这个测试类,可以看出,两条数据同时插入了不同的数据库中的两张表中

image_1ci8kfj001dr91gg01l721g781l5f13.png-18.7kB

image_1ci8kg1fo18671fafp4q1k5e186u1g.png-26kB

多数据源的原理就是一个项目同时配置了两个DataSource,并把这两个DataSource放到DynamicDataSource绑定,使用AOP进行动态切换当前操作的数据源。

若想深入了解多数据源的配置和原理可参考MybatisPlusConfig类MultiSourceExAop类,也可参考视频教程第7节 多数据源配置和使用,内有详细的讲解

3.5 如何分页

Guns的分页是通过mybatis-plus的分页插件实现的,大体分如下两种情况

3.5.1 简单查询的分页

如果查询结果为单表查询,例如查询用户列表,则可以调用mybatis plus的自动生成的mapper中的selectPage()或者selectMapsPage()方法,Page类的构造函数中第一个参数为当前查询第几页,第二个参数为每页的记录数。 
image_1c4mrp2brphr1313asoset1bb155.png-21.4kB

3.5.2 复杂查询的分页

若查询结果是关联多个表的操作,则需要用到自定义的mapper,此时的分页操作也很简单,只需要给mapper的第一个参数设置为Page对象即可,例如Guns中LogController中的查询操作日志列表,用的就是复杂查询的分页,我们可以看到在mybatis接口的第一个参数中,传递了Page对象,如下 
image_1c4ms4v6sg343nu1tmq1u1o10j65i.png-28.4kB
当mybatis执行此方法的时候,会被mybatis-plus的分页插件自动拦截到,并且把分页查询的结果返回到这个Page对象中!

3.5.3 获取前端表格插件传值

Guns中前端表格用的Bootstrap Table插件,在前端执行查询时,插件会自动往后台传递分页参数,并且默认的格式如下, 
image_1c4msjlp71qb61b9bqlg9q4154i6v.png-54.5kB
Bootstrap Table会传递order(升序或者降序)offset(每页偏移量)limit(每页条数)sort(排序的字段)这四个参数,与之对应,后台封装了一个通用的接受分页参数的类PageFactory,从而不用每次都来request.getParameter()这样接收参数,如下所示,

 
  1. public class PageFactory<T> {
  2.  
  3. public Page<T> defaultPage() {
  4. HttpServletRequest request = HttpKit.getRequest();
  5. int limit = Integer.valueOf(request.getParameter("limit")); //每页多少条数据
  6. int offset = Integer.valueOf(request.getParameter("offset")); //每页的偏移量(本页当前有多少条)
  7. String sort = request.getParameter("sort"); //排序字段名称
  8. String order = request.getParameter("order"); //asc或desc(升序或降序)
  9. if (ToolUtil.isEmpty(sort)) {
  10. Page<T> page = new Page<>((offset / limit + 1), limit);
  11. page.setOpenSort(false);
  12. return page;
  13. } else {
  14. Page<T> page = new Page<>((offset / limit + 1), limit, sort);
  15. if (Order.ASC.getDes().equals(order)) {
  16. page.setAsc(true);
  17. } else {
  18. page.setAsc(false);
  19. }
  20. return page;
  21. }
  22. }
  23. }

在后台代码中如需接收参数,构建分页Page对象的时候,只需如下这样一调用即可构建分页对象

 
  1. Page<OperationLog> page = new PageFactory<OperationLog>().defaultPage();

3.6 数据范围

3.6.1 介绍

Guns的数据范围是指当前部门的用户可以看到当前部门和子部门的数据,子部门的数据不可以看到上级部门的数据,但超级管理员例外,例如,userAuserB两个用户都有查看用户列表的权限,但是userA在总公司部门,userB在运营部,他们有如下部门关系

image_1c4mu0mfbf48prrac1dv61t1u7s.png-17kB

那么userA在查看用户列表的时候能看到公司所有人的数据,userB只能看到运营部的数据,这就是数据范围!

3.6.2 如何使用

使用时,只需要new一个DataScope,并在构造方法中传递给当前用户用后的部门权限(一般我们用封装好的ShiroKit.getDeptDataScope()方法即可获取到当前用户的部门权限集合),之后,传递给mybatis的dao方法的第一个参数即可,例子如下

 
  1. DataScope dataScope = new DataScope(ShiroKit.getDeptDataScope());
  2. List<Map<String, Object>> users = managerDao.selectUsers(dataScope, name, beginTime, endTime, deptid);

注意: 在使用过程中,原mybatis的dao方法的查询结果中必须包含deptid字段(默认情况),若部门id不叫deptid也可也初始化DateScope对象的时候,修改该对象的scopeName属性,改为自定义的部门id字段名即可

3.6.3 原理

数据范围的原理是利用了mybatis拦截器,类似于mybatis-plus的分页插件,在原查询结果之上包装了一层select筛选查询,如下

 
  1. select (原语句字段) from (原语句) where deptid in (DataScope对象中包含的部门id列表)

若想深入了解数据范围的编写过程和原理可参考视频教程第15节 数据范围使用和原理,内有详细的讲解

3.7 guns-rest模块的使用

guns-rest模块已在Guns 5.1版本中剔除掉了,若想了解jwt相关的使用方法可以参考Guns 4.2版本(https://gitee.com/stylefeng/guns/tree/v4.2/

3.7.1 关于jwt鉴权

在了解guns-rest模块的使用之前,需要了解一下jwt鉴权机制,下面给出一些参考资料

说白了就是如果想请求服务器资源,需要先走服务器的auth接口,用账号和密码换取token,之后每个接口的请求都需要带着token去访问,否则就是鉴权失败.

3.7.2 关于传输数据的签名

签名机制是指客户端向服务端传输数据中,对传输数据进行md5加密,并且加密过程中利用Auth接口返回的随机字符串进行混淆加密,并把md5值同时附带给服务端,服务端通获取数据之后对数据再进行一次md5加密,若加密结果和客户端传来的数据一致,则认定客户端请求的数据是没有被篡改的,若不一致,则认为被加密的数据是被篡改的.

3.7.3 guns-rest模块的运行流程

  1. 执行guns-rest模块下的db文件夹的sql初始化脚本guns_rest.sql
  2. 启动guns-rest模块
  3. 下载postman接口测试工具或者insomnia接口测试工具,下面以insomnia接口测试工具为例,演示rest模块资源访问流程
  4. 访问/auth接口,传递给接口账号密码获取访问接口用的token,如下 
    image_1c4paeghfoa21mmu84jrb113l4m.png-75.2kB
    接口请求成功,auth接口返回给两个属性的json,randomKey的作用是在之后接口的数据传输中对数据做MD5混淆加密用的,token的作用是在之后访问资源的过程中,携带到请求的header中,证明我们是有权限访问资源的
  5. 接着去访问/hello接口,在访问之前,我们需要做两件事: 
    第一 把请求hello接口的请求头Header中带一个Authorization属性,属性的值为Bearertoken值,注意中间用空格隔开 
    image_1c4pb4r481mse1dlj1tdu12lnh213.png-64.1kB
    第二 /hello接口的所需要一个@RequestBody类型的数据,所以我们还需要传给这个接口一个json数据 
    image_1c4pb8kd51kck1lkg4fmdls1sdo1g.png-13.7kB 
    注意 json数据不能直接为如下的形式
 
  1. {"name":"ffff","user":"stylefeng","age":12,"tips":"code"}

为了保证传输的数据的安全性,Guns做了对传输数据的签名,所以传输过程中需要对数据进行签名,我们可以直接运行DecryptTest这个测试类,直接生成签名好的json数据,如下 
image_1c4pbeivc1bq31vhn1f2615ji1681t.png-48.6kB 
这里注意填写md5的加密盐为刚才/auth接口生成的randomKey,运行后生成如下json

 
  1. {"object":"eyJhZ2UiOjEyLCJuYW1lIjoiZmZmZiIsInRpcHMiOiJjb2RlIiwidXNlciI6InN0eWxlZmVuZyJ9","sign":"d737820570c0881e8614272f9792e07d"}

我们填入到接口的请求体里,并点击Send 
image_1c4pbhob317vk1ck4fp61veu89h2a.png-56.3kB

接口访问成功!

3.7.4 运行原理

关于rest模块鉴权运行原理,其实就是一个简单的过滤器AuthFilter类实现的,若想了解运行机制可以查看下auth包下的类的代码(几十行) 
image_1c4pbvvle1bqi85210en1ou9g442n.png-37.5kB

3.8 工作流

工作流在Guns 5.1中也剔除掉了(因为不是必需品),不过如果需要使用工作流的话可以用Guns 3.3版本(https://gitee.com/stylefeng/guns/tree/v3.3

Guns 3.1版本引入了工作流框架flowable 6.2.0,并自带一个报销流程供大家参考,但是为了满足大家的需求,工作流不是绝大多数人都会使用,所以目前不对工作流提供支持,若需要项目集成工作流,可以仿照Guns3.1提供的flowable的配置,作为参考,自行集成一下工作流相关的内容,下面介绍一下之前版本的工作流。

为了不和guns的数据库混淆,guns新建了一个数据库guns_flowable,并配置了一个单独的数据源来连接该数据库,在application.yml中的配置如下 
image_1c4pccdfhep7ii7p684iv1ukb34.png-36.4kB

在guns启动过程中,若guns_flowable数据库没有表,flowable引擎会自动初始化工作流需要的表

在报销管理业务中,一共有三个角色,申请人(账号:admin),经理(账号:manager),老板(账号:boss),他们的密码都是111111,首先申请人填写报销单, 
image_1c4pcrtts1ig0cj11ade1k8d1llh3h.png-19.3kB 
填写之后需要在报销审批菜单中,提交下自己的申请 
image_1c4pctk939n1tjo25g1v1n1cji3u.png-45.4kB
如果报销金额小于500则是经理(manager)审批,我们登录经理的号,可以看到申请记录 
image_1c4pd00lf169q1lvg14q71rf9nof4r.png-51.3kB
这里点击通过,则该流程结束,如果点不通过则还需要申请人重新提交申请

关于工作流的开发,可以参考flowable官方文档

3.9 日志记录

在我们日常开发中,对于某些关键业务,我们通常需要记录该操作的内容,例如修改了什么数据,修改的内容是什么,删除了哪些数据等等,在Guns中有一整套完善的解决方案来完成此项功能

3.9.1 业务日志

我们通过@BussinessLog注解来记录日志,该注解源码如下,

 
  1. /**
  2. * 标记需要做业务日志的方法
  3. *
  4. * @author fengshuonan
  5. * @date 2017-03-31 12:46
  6. */
  7. @Inherited
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Target({ElementType.METHOD})
  10. public @interface BussinessLog {
  11.  
  12. /**
  13. * 业务的名称,例如:"修改菜单"
  14. */
  15. String value() default "";
  16.  
  17. /**
  18. * 被修改的实体的唯一标识,例如:菜单实体的唯一标识为"id"
  19. */
  20. String key() default "id";
  21.  
  22. /**
  23. * 字典(用于查找key的中文名称和字段的中文名称)
  24. */
  25. Class<? extends AbstractDictMap> dict() default SystemDict.class;
  26. }

其中,value为需要记录日志的业务名称,key为修改或删除内容的唯一标识,通过这个唯一标识可以知道具体的修改的哪条记录,删除的哪条记录等等,dict为对修改字段的中文翻译字典,因为程序记录的都是英文的字段名称,这里通过字典,把英文字段和中文名称对应起来,那么日志信息记录到数据库中就可以变为中文的记录

UserDict为例,

 
  1. /**
  2. * 用户的字典
  3. *
  4. * @author fengshuonan
  5. * @date 2017-05-06 15:01
  6. */
  7. public class UserDict extends AbstractDictMap {
  8.  
  9. @Override
  10. public void init() {
  11. put("userId","账号");
  12. put("avatar","头像");
  13. put("account","账号");
  14. put("name","名字");
  15. put("birthday","生日");
  16. put("sex","性别");
  17. put("email","电子邮件");
  18. put("phone","电话");
  19. put("roleid","角色名称");
  20. put("deptid","部门名称");
  21. put("roleIds","角色名称集合");
  22. }
  23.  
  24. @Override
  25. protected void initBeWrapped() {
  26. putFieldWrapperMethodName("sex","getSexName");
  27. putFieldWrapperMethodName("deptid","getDeptName");
  28. putFieldWrapperMethodName("roleid","getSingleRoleName");
  29. putFieldWrapperMethodName("userId","getUserAccountById");
  30. putFieldWrapperMethodName("roleIds","getRoleName");
  31. }
  32. }

翻译字典类中包含两个方法init()initBeWrapped(),其中init()为存放英文字段和中文字段的匹配,initBeWrapped()操作的是把某些字段的数字值翻译为中文直观名称的过程,例如当修改用户信息时,用户修改了一个人性别信息(数据库中1是男,2是女),由1变为了2,程序记录的是数据库中1变为2,但是这句话给业务人员看到他是不知道1和2是什么东西的,所以这里做了一个值的包装,把1包装为对应的中文名称2包装为对应的中文名称,这样,记录到数据库中,信息就变为了,xxx用户操作了修改用户功能,值由变为了
在initBeWrapped()方法中putFieldWrapperMethodName()这个方法的第一参数是被包装的字段名,第二个参数是ConstantFactory中的方法名,因为默认会调用ConstantFactory来包装值属性

下面介绍业务日志记录的具体步骤:

  • 1.在需要被记录日志的接口上添加@BussinessLog注解,并根据需要填写三个属性(value,key,dict)
  • 2.若是添加或者修改业务,往往需要去编写Dict字典类
  • 3.若是修改业务,例如修改用户信息,因为点击更新用户的时候不会提交修改之前的数据,所以在更新用户信息之前需要保存一下用户的旧的信息才可以记录用户修改的内容,这个缓存用户临时信息的地方一般添加在跳转到用户详情接口,用LogObjectHolder.me().set(user);这行代码来缓存用户的旧的信息,具体用法可以参考UserMgrController类中的userEdit()edit()

3.9.2 异常日志

由于Guns有统一的异常拦截器,一般程序的报错,不管是业务异常还是未知的RuntimeException都会拦截并记录到数据库,若是您有自己的异常日志需要记录到数据库或者日志文件,推荐如下做法

  1. 如果记录到数据库,调用Guns的日志记录工具类,如下
 
  1. LogManager.me().executeLog();

该方法为异步记录日志的方法,executeLog()方法中需要传递一个TimerTask对象,TimerTask对象可以用LogTaskFactory类创建,在LogTaskFactory类中,有5个方法,可以分别记录不用的日志,有登录日志退出日志业务日志异常日志等等,可以自行选择调用 
2. 若需要记录日志到文件中,可以采用slf4j的org.slf4j.Logger类记录,具体方法如下

 
  1. //首先在类中初始化
  2. private Logger log = LoggerFactory.getLogger(this.getClass());
  3.  
  4. //再在方法中调用
  5. log.error("业务异常:", e);

3.10 如何使用缓存

在Guns中使用缓存的地方不多,主要在ConstantFactory的查询中用了缓存,在ConstantFactory有高频调用的查询,所以在这些方法上加了缓存,搜索加上缓存后还要注意在修改了相关数据的时候要删除缓存,否则可能导致数据的不一致,在Guns中默认用的是Ehcache缓存,并配合了spring cache使用,用spring cache的好处就是,spring cache是缓存的抽象,如果想换为redis缓存,则不用修改代码,改一下配置即可实现,下面介绍两种操作缓存的方法

3.10.1 用工具类操作

在guns-core中封装了一些常用的操作Ehcache缓存的工具类CacheUtil,此类采用静态方法调用的方式,可以添加,获取,删除缓存,用法非常简单

 
  1. //添加缓存,第一个参数为缓存的名称,是ehcache.xml中<cache>节点的NAME,key为添加缓存的键值,value为缓存的值
  2. public static void put(String cacheName, Object key, Object value);
  3.  
  4. //获取某个缓存名称中的某个键值对应的缓存
  5. public static <T> T get(String cacheName, Object key);
  6.  
  7. //获取某个缓存的所有key
  8. public static List getKeys(String cacheName);
  9.  
  10. //删除某个key对应的缓存
  11. public static void remove(String cacheName, Object key);
  12.  
  13. //删除某个缓存名称下的所有缓存
  14. public static void removeAll(String cacheName);

3.10.2 用spring cache操作缓存

利用spring cache来操作缓存,可以很方便的在redis和ehcache之间切换缓存实现,利用spring cache 的缓存注解,加到方法之上可以很方便的缓存方法的结果,如果参数对应的键值存在了缓存,则下一次走这个方法则会直接返回缓存的结果,spring cache提供了4个注解来操作缓存.

  • 1.@Cacheable表明在调用方法之前,首先应该在缓存中查找方法的返回值,如果这个值能够找到,则会返回缓存的值,否则执行该方法,并将返回值放到缓存中,一般在数据库查询(select)之后调用这个注解
  • 2.@CachePut表明在方法调用前不会检查缓存,方法始终都会被调用,调用之后把结果放到缓存中,一般在数据库操作插入数据(save)的时候调用
  • 3.@CacheEvict表明spring会清除一个或者多个缓存,一般在数据库更新或者删除数据的时候调用(update或者delete)
  • 4.@Caching分组的注解,可以同时应用多个其他缓存注解,可以相同类型或者不同类型

一般在用这些注解的时候,我们需要填写两个参数,一个是value代表缓存的名称,一个是key代表缓存的键值 
image_1c4rro76j1s151juv1l2g16ft1mcc9.png-32.8kB
如上图所示,键值key一般包含两部分组成,一部分是键的标识例如上图中的CacheKey.SINGLE_ROLE_NAME,一部分是参数(一般是参数的值)例如上图中的#roleId

3.11 使用枚举

在Guns中,枚举一般分两类,一种是状态枚举,一种是异常枚举,状态枚举的作用是枚举状态,列出状态的所有值,例如

 
  1. /**
  2. * 菜单的状态
  3. *
  4. * @author fengshuonan
  5. * @Date 2017年1月22日 下午12:14:59
  6. */
  7. public enum MenuStatus {
  8.  
  9. ENABLE(1, "启用"),
  10. DISABLE(0, "禁用");
  11.  
  12. int code;
  13. String message;
  14.  
  15. MenuStatus(int code, String message) {
  16. this.code = code;
  17. this.message = message;
  18. }
  19.  
  20. ...
  21. }

异常枚举的作用是枚举所有出现的业务异常,例如,

 
  1. /**
  2. * 所有业务异常的枚举
  3. *
  4. * @author fengshuonan
  5. * @date 2016年11月12日 下午5:04:51
  6. */
  7. public enum BizExceptionEnum implements ServiceExceptionEnum{
  8.  
  9. /**
  10. * 错误的请求
  11. */
  12. SESSION_TIMEOUT(400, "会话超时"),
  13. SERVER_ERROR(500, "服务器异常");
  14.  
  15. BizExceptionEnum(int code, String message) {
  16. this.code = code;
  17. this.message = message;
  18. }
  19.  
  20. private Integer code;
  21.  
  22. private String message;
  23.  
  24. ...
  25. }

使用枚举可以方便维护一些状态的值和管理所有的业务异常,所以在有状态或者新的业务异常的时候推荐写到枚举里

3.12 spring boot热部署

热部署的两种情况(适用于main方法启动)

3.12.1 重新加载html

如果是eclipse修改html保存后可以自动替换,如果不能请检查server配置 
image_1c4tvurdb1hhnvum1a3rs3i1se362.png-867.5kB

如果是IDEA,可以修改html之后点击这个按钮,或者按快捷键CTRL+F9,即可更新 
image_1c4tvvn0jdcv15et1odl1qbd17qs6f.png-10.3kB

3.12.2 重新加载java类

如果是eclipse,在application.yml中找到配置spring.devtools.restart.enabled改为true即可

如果是在IDEA中: 
第一步 请先修改spring.devtools.restart.enabled=true

第二步 如下idea配置,打上对勾 
输入图片说明

第三步 按下 Shift+Ctrl+Alt+/,选择Registry 
输入图片说明

进去之后,找到如下图所示的选项,打勾 
输入图片说明


4. 扩展与高级配置

4.1 修改项目名和包名

4.1.1 修改项目名

  1. 以guns-admin在idea环境下为例,右击项目,点refactor->Rename 
    image_1cpvo0tm512bd1s343pkjr1do8p.png-136kB
  2. 修改模块名称 
    image_1cpvo27dj4bg8i0f2c1egjv9e16.png-21.1kB
  3. 修改pom的artifactId改为myguns 
    image_1cpvo4r1fredrtu5ep1lvr1f291j.png-214kB

4.1.2 修改包名

下面以把cn.stylefeng.guns改为com.company.project为例

  1. 选择cn.stylefeng.guns包,仍然为右键refactor->Rename
  2. 弹出对话框选择,Rename all,输入project 
    image_1cpvqs2v11i6l1niafbqsk4k229.png-36.5kB
  3. 修改包名称,再次选择cn.stylefeng.project包,右键refactor->Rename,输入 
    image_1cpvr78g920t168214s41qpuc9o2m.png-72.6kB
  4. 改完后项目可能有些类报错,进去把这些类没用的import删掉就好, 
    image_1cpvrlf841nomnpi5qalvtou4t.png-300.6kB
  5. 修改application.yml中的的相关包配置 
    image_1cpvrv8ah1p26162jp3m1no91bfep.png-60.4kB
  6. 修改logback-spring.xml配置文件中的相关配置 
    image_1cpvs23bc1d321jcgaveb6i1eii16.png-246.9kB
  7. 修改mapper扫描相关的包配置,多数据源的也要修改 
    image_1cpvsbpbb12ij11unjjfubv11643.png-133kB
  8. 修改SessionHolderInterceptor类的扫描配置 
    image_1cpvst7jrm4iv411nebdv81clk60.png-147.8kB
  9. 修改WebConfig中的相关配置 
    image_1cpvsugi11jt91l5bn4qpor3s26d.png-227.4kB
  10. 另外,检查aop相关的包扫描,默认可能ide已经帮你改掉了,如果没改得自己改下 
    image_1cpvsvr0f1t8516152n125h9t66q.png-250.2kB

4.2 放过接口权限验证

在日常开发中,我们可能需要放过某个接口的权限验证,即用户不用登录就可以访问接口 
1. 首先我们在BlackboardController这个类中,增加一个接口 
image_1c4rvmgpthklkvn1ccd1rte20a9i.png-58.1kB
2. 在ShiroConfig类中,找到shiroFilter()这个方法,配置上这个接口,注意加到最上面,这个Map是有顺序的,可以用通配符 
image_1c4rvpe7r14d1a2co2l5q11njeaf.png-46.7kB
3. 启动应用,并且不登录系统,我们访问http://localhost:8080/blackboard/test即可看到,这个接口不需要登录也可以访问到

4.3 静态资源和模板位置的变更

由于spring boot默认是把静态资源文件css,js等放到resources/static目录的,默认把前端模板文件放到resources/templates目录,笔者认为前端面页面还是按maven的思想放到webapp目录比较分层清晰,所以做了一个变动,主要变动如下:

yml配置中增加了两个配置 
image_1c4s06qo61f5915c6nf9ea1ue3bp.png-19kB

若想变动资源和模板的位置修改这两个配置即可

4.4 三个或更多数据源如何配置

  1. 新建类似于MutiDataSourceProperties这样的类,用于接收第三个数据源 
    image_1c4s0esme1qeecs91mpg18pq10d7c6.png-77.3kB
  2. MybatisPlusConfig类中,配置类似于如下代码的方法 
    image_1c4s0h0kqtkc199i3k7lbl1ei4cj.png-16.7kB
  3. DynamicDataSource配置中,增加第二步新加的数据源 
    image_1c4s0k7eg1rbf1lonfd1m9eajd0.png-65kB
  4. 同时在DatasourceEnum类中,增加第三个数据源名称 
    image_1c4s0kp51177e3d2km9130uvtadd.png-24.7kB
  5. 使用方法同第二个数据源使用方法相同

4.5 添加登录验证码

Guns系统中内置了登录输入验证码的功能,因为开发方便调试,所以默认是关闭的,若需要开启该功能,只需要在application.yml中配置开启即可,如下 
image_1c4rvvsugsdq1gut1dhv1fnj1tnqbc.png-25.8kB

4.6 spring profile

在实际的生产环境中,往往存在多个环境,例如开发环境(dev),测试环境(test),生产环境(prod),并且不同环境的数据库和日志记录等配置的都不相同,为了每次发布不同环境的包时,不来回的修改这些配置,特引入了spring profile,引入之后,我们只需要把所有环境的配置都预先列出来,在每次发布不同环境的包的时候,只需要选择当前激活的是哪个环境的配置即可快速切换配置,关于spring profile的详细描述可参考这篇博文https://www.jianshu.com/p/948c303b2253

在yml配置中,我们用---来切分不同profile的配置,如下 
image_1c4s14cdf16g8uq017v05181h9bfa.png-29kB

在分割线的下边我们就可以配置不同环境的配置了,profile可以配置多个,只需要用spring.profiles来标记当前节段的profile的名字即可 
image_1c4s1asg31vkknd21jd51momo06fn.png-20.5kB

并用spring.profiles.active来激活当前的profile配置即可 
image_1c4s1efat1odh1j393t310i6q6fg4.png-36.9kB

---把配置切分成了多个节段,其中第一节是所有profile共有的配置,例如guns的配置中的这一大段 
image_1c4s1grk5r3s1j1f1ujn19tr1ftggh.png-165.9kB

第一节段---下方的配置则是不同的profile的配置

4.7 多机器部署开启spring session

多机环境把session托管给redis存储,所以要部署和配置redis,另外需要注意的是开启相关配置 
1.单机环境下不需要依赖spring-session,所以需要把相关依赖的注释打开

 
  1. <dependency>
  2. <groupId>org.springframework.session</groupId>
  3. <artifactId>spring-session-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-data-redis</artifactId>
  8. </dependency>

2.修改application.yml中guns.spring-session-open配置,改为true,打开spring-session

 
  1. guns.spring-session-open=true

3.配置application.yml中,spring.redis.host,spring.redis.port,spring.redis.password

 
  1. spring.redis.host=xxx
  2. spring.redis.port=xxx
  3. spring.redis.password=xxx

4.需要把SpringSessionConfig类中的注释打开

 
  1. @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)

5.如需配置session失效时间,请在SpringSessionConfig类中修改maxInactiveIntervalInSeconds属性值

4.8 使用Redis

默认Guns在部署分布式的环境中使用了Redis作为分布式session的存储,如果想在项目中用redis做缓存或者存储,建议使用RedisTemplate来进行操作

1.首先下载Redis服务端,可以在Guns的qq群里找到redis的可执行包,或者去redis官网下载 
2.在guns-admin项目添加对redis的依赖如下

 
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

3.在application.yml中配置redis的连接属性 
image_1c4teiq7e1ksl1tne1bn6159h1c9c1g.png-11.1kB 
4.在GunsApplication类中,注入RedisTemplate,并编写CommandLineRunner来测试一下Redis的连接,如下

 
  1. @Bean
  2. CommandLineRunner commandLineRunner() {
  3. return new CommandLineRunner() {
  4. @Override
  5. public void run(String... strings) throws Exception {
  6. BoundValueOperations<String, Object> test = redisTemplate.boundValueOps("test");
  7. test.set("test value");
  8.  
  9. Object o = test.get();
  10. System.out.println(o);
  11. }
  12. };
  13. }

4.9 XSS过滤器

4.9.1 介绍

为了抵御XSS攻击,不让用户在录入数据的同时插入恶意js代码,Guns对所有传入数据中带有html标签<script>标签的内容进行转义,转义后的内容并不是乱码,只是为了传入的html片段不会在渲染页面时让浏览器当成真正的脚本去执行。

4.9.2 原理

防止XSS攻击的原理其实就是一个过滤器,对所有请求传来的参数进行正则校验,利用replaceAll把所有的请求中带有html标签的<>等这种标识过滤为了& lt;& gt;这种特殊符号.

4.9.3 放过过滤

WebConfig配置类中,找到XssFilter配置的地方,在setUrlExclusion()这里加上被放过过滤的列表即可 
image_1c4tfno2otcgpqe1lur17j1jec24.png-92.6kB


5. 核心思想

5.1 分包

在日常开发中,业务模块的包结构划分一般划分为三个configcoremodular或者四个commonconfigcoremodular

其中common为模块内通用的注解、常量、枚举、异常和持久化的实体等,若common不单独划分一个包,则可以把common包放到core包下面

config包存放整个模块的配置类,因为项目基于spring boot开发,大部分的spring配置都换成了java bean方式的配置,所以单独分一个包来存放配置,config包中除了存放配置类,还有一些以Properties结尾的类,这些类的作用是启动应用的时候把application.yml中的配置映射到类的属性上,使用时需要注意以下几点 
image_1c4t9rbev13cu136916qj1n6j1fr49.png-48.5kB

modular存放按业务划分的业务代码,若本模块中包含多个模块业务,则在modular中建立多个业务包,在具体的业务包下再建立controllerdaoservicetransferwarpper这几个包,其中transfer为前后端传输数据所用的属性封装,warpper为对返回结果的包装器(下面会介绍到),如果当前模块中只存在一类业务,那么没有必要在modular包下再建立多个业务模块,可直接在modular模块建立controllerdaoservicetransferwarpper

core包存放当前模块所运行的一些核心机制,例如全局的异常拦截器,日志AOP,权限的AOP,项目初始化后的监听器,工具类等,还可以存放一些对某些框架的扩展,例如对beetl模板的扩展配置和工具类,对flowable的扩展类,Shiro的一些拓展类等等

这样拆分的好处在于把业务,配置和运行机制清晰的拆分开,提高项目的可维护性,加快项目的开发效率!

5.2 统一异常拦截

5.2.1 介绍

统一异常拦截指对程序抛出的异常利用@ControllerAdvice在统一的一个类中做catch处理,在Guns中,我们在GlobalExceptionHandler类中做统一异常拦截处理,GlobalExceptionHandler类中可以拦截所有控制器执行过程中抛出的异常,若需要拦截其他包下的异常可以参考SessionInterceptor这个类中AOP的写法,来拦截其他特定包的异常。统一异常拦截的写法注意一下几点 
image_1c4tcbdft270bih3pv1kuf1psh13.png-74.8kB

5.2.2 优点

对异常进行统一处理,不需要再在业务代码中进行try catch操作,尽情写业务,有异常也会被自动拦截到,并且自动处理返回给前端提示

5.2.3 关于性能

有人可能会认为利用异常拦截这种机制,把业务逻辑的错误都用业务异常抛出进入aop的执行器,对性能会有所影响,经过笔者的调研和测试,频繁的抛出异常和try catch不会有性能损耗,主要的性能损耗在catch方法内部,并且在catch内,记录日志比较占用大部分的时间

所以,如果是系统特别注重性能等问题,可以把业务异常分为两类,一类是较为频繁抛出的业务异常,一类是较少出现次数的业务异常,第一类异常可以再@ExceptionHandler中不做日志记录,只进行简单的返回操作,第二类可以着重做异常处理,并做结果返回

5.3 结果包装器

我们在进行列表查询详情查询的过程中,查到的结果中,有些值可能在数据库中存的是一些列数字(一般为状态值等),但是我们要返回给前端,业务人员看的时候不希望直接返回给他们这些不直观的值(例如1,2,3,4),我们更希望返回给前端中文名称(例如启用,冻结,已删除),所以我们应该对这些数值做一下包装,把他们包装成文字描述

5.3.1 如何使用

以查询用户列表的接口为例,不包装的情况下默认的查询结果为这些字段 
image_1c4tmpf94to7oesm17lo14rk9.png-10.7kB 
其中性别,角色,部门,状态都是数值或者id类型,我们需要把他们包装成文字形式返回给前端

1.首先建立UserWarpper类继承BaseControllerWarpper

 
  1. /**
  2. * 用户管理的包装类
  3. *
  4. * @author fengshuonan
  5. * @date 2017年2月13日 下午10:47:03
  6. */
  7. public class UserWarpper extends BaseControllerWarpper {
  8.  
  9. public UserWarpper(List<Map<String, Object>> list) {
  10. super(list);
  11. }
  12.  
  13. @Override
  14. public void warpTheMap(Map<String, Object> map) {
  15. map.put("sexName", ConstantFactory.me().getSexName((Integer) map.get("sex")));
  16. map.put("roleName", ConstantFactory.me().getRoleName((String) map.get("roleid")));
  17. map.put("deptName", ConstantFactory.me().getDeptName((Integer) map.get("deptid")));
  18. map.put("statusName", ConstantFactory.me().getStatusName((Integer) map.get("status")));
  19. }
  20. }

通过查看BaseControllerWarpper类可了解到被包装的参数必须为Map或者List类型

 
  1. /**
  2. * 控制器查询结果的包装类基类
  3. *
  4. * @author fengshuonan
  5. * @date 2017年2月13日 下午10:49:36
  6. */
  7. public abstract class BaseControllerWarpper {
  8.  
  9. public Object obj = null;
  10.  
  11. public BaseControllerWarpper(Object obj) {
  12. this.obj = obj;
  13. }
  14.  
  15. @SuppressWarnings("unchecked")
  16. public Object warp() {
  17. if (this.obj instanceof List) {
  18. List<Map<String, Object>> list = (List<Map<String, Object>>) this.obj;
  19. for (Map<String, Object> map : list) {
  20. warpTheMap(map);
  21. }
  22. return list;
  23. } else if (this.obj instanceof Map) {
  24. Map<String, Object> map = (Map<String, Object>) this.obj;
  25. warpTheMap(map);
  26. return map;
  27. } else {
  28. return this.obj;
  29. }
  30. }
  31.  
  32. protected abstract void warpTheMap(Map<String, Object> map);
  33. }

我们继承BaseControllerWarpper类主要是为了实现warpTheMap()方法,也就是具体的包装过程,warpTheMap()方法的参数map就是被包装的原始数据的每个条目,我们可以在这每个条目中增加一些字段也就是被包装字段的中文名称,如下 
image_1c4tn697k14vjstnce46ge4tlm.png-20.6kB

5.3.2 ConstantFactory

在包装过程中,我们经常会用到ConstantFactory这个类,这个类是连接数据库和包装类的桥梁,我们可以在ConstantFactory中封装一些编辑的查询方法,这些方法通常会被多个包装类多次调用,并且在调用这些方法的时候ConstantFactory.me()的形式静态调用,可以快速的包装一些状态和id,非常方便,在ConstantFactory中我们可以利用spring cache的@Cacheable注解来缓存一些数据,把这些频繁的查询缓存起来

5.4 前端思想

Guns前端采用了beetl模板引擎,beetl包含语法简洁,速度快,文档全,社区活跃等众多优点,所有的beetl语法都以@开头

5.4.1 布局

在用户登录页面后进入的是index.html页面,这个页面加载了整个后台管理系统的框架,我们可以看到index.html源代码中把整个页面分为了三部分,左侧菜单栏,右侧页面和右侧主题栏部分,其实就是利用beetl的@include把整个大的复杂的页面细化了,这样好维护

image_1c4toquv31u2arfe154i9r811sf13.png-19.6kB

左侧菜单和右侧主题栏部分在用户登录后会一直不变,除非刷新浏览器页面,动态变化的是页面右侧这部分,我们打开6个标签页,并打开浏览器F12调试

image_1c4tpunc91mhp1kho9k71cen11b81g.png-281.1kB

新建和切换标签,页面的地址不会变化,变化的是页面右侧的iframe这部分

下面我们分析一下右侧页面的组成,我们打开菜单管理页面,查看他的代码

 
  1. @layout("/common/_container.html"){
  2. <div class="row">
  3. XXXX等html代码...
  4. </div>
  5. <script src="${ctxPath}/static/modular/system/menu/menu.js"></script>
  6. @}

整个页面被@layout所包围,@layout是beetl的引用布局(具体用法文档可以查看beetl的官方文档),Guns中内置了/common/_container.html这样一个布局,可以把/common/_container.html理解为一个html的抽象封装,我们每个页面都继承自这个模板,默认包含了一系列通用的js css引用等,这样写即简化了我们的开发和维护,又使我们的代码简洁有序,在/common/_container.html中的${layoutContent}就代表我们每个页面不同的html

5.4.2 标签

为了把一些重复性的html封装起来,我们使用了beetl的标签,这些标签的本质是把重复性的html代码用一行html标签替代,从而方便使用,易于维护,这些标签都放在common/tags这个文件夹 
image_1c4tr6i1ddgj1n6310on88c17pp2q.png-26.5kB

标签中的一些属性例如${name} ${id}等属性均为掉钱被调用时,从调用体的属性传来<#xxxTag name="xxx" id="xxx">

5.4.3 手动新增标签页

新版Guns提供了手动新增标签页的方法Feng.newCrontab(href,menuName);第一个参数是新打开tab页面的地址,第二个参数是新增tag页面的标签名称。

image_1ch7na7ua1uor16hl1er14461cu2a.png-91.3kB


6. 常见问题答疑

6.1 默认的系统登录账号和密码是多少

账号是admin 密码是111111

6.2 权限异常

6.3 为何分页是前端实现

部分页面因为数据量比较少,就直接用客户端分页了,日志页面的分页是采用服务端分页的,如果其他业务有特别需要,可以手动设置一下

6.4 关于${ctxPath}

这个变量在哪里定义的?这个是beetl自带的具体请看beetl文档

6.5 放过某些url的权限验证

在ShiroConfig类下的shiroFilter方法里配置,参考4.2节

6.6 主页的搜索功能

主页的搜索功能目前没有写实际业务,只是装饰作用

6.7 运行sql报错

在初始化guns.sql过程中,可能会出现

 
  1. [Err] 1067 - Invalid default value for 'createtime'

这样的报错,Guns目前支持mysql 5.7的运行环境,若您的mysql低于此版本,请把sys_expense表的DEFAULT CURRENT_TIMESTAMP这部分语句去掉即可 
image_1c4tuq72p1r1hfm41k9j1ppd8qh41.png-52.2kB

6.8 关于打包

Guns现在是多模块组成,各个模块之间有依赖关系,打包时,先修改guns-admin模块的pom的<packaging>节点,改为jar或者war 
image_1c4tv3vhook81d3r1432jhh9m74e.png-13.9kB

再在guns-parent目录下输入clean package -Dmaven.test.skip=true来打出所有模块的包 
image_1c4tv625p1etf1v9212lb1osbd2e4r.png-19.6kB

执行成功后,在guns-admin目录下即可看到打好的包 
image_1c4tv7fgi3pmjd4e565oucjq58.png-24.3kB

6.9 查询结果的驼峰转化问题

直接参考mp的文档

6.10 为何使用beetl

beetl具有语法简介,性能超高,文档全,社区活跃等特点,所以建议用beetl模板引擎

6.11 为何有的业务没有service层

部分业务比较简单,所以就没写service层,写service是为了让复杂业务更有条理,更清晰。(此项仅供参考)

6.12 为何既有dao,又有mapper

mapper是mybatis-plus自动生成的,里边有许多mybatis-plus增强的方法,dao是自己写的业务,mybatis-plus自动生成代码时会覆盖mapper,所以就把自己写的dao分开了,生成代码的时候不影响。(此项仅供参考)

6.13 提示@spring.active@错误

请使用阿里云的maven仓库,并点击maven的reimport即可


+

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Guns 技术文档 v5.1 的相关文章

  • iphone下拉菜单卡住了_苹果手机怎么下拉菜单 苹果x右上角下拉失灵怎么办

    苹果手机下拉菜单怎么设置 苹果手机下拉菜单的设置步骤如下 1 打开主屏上的设置应用 2 在设置列表中找到控制中心一栏 点击进入 3 接下来请关闭在控制中心列表下的二个选项 4 随后我们再重新打开这二个选项 然后再尝试下拉调出通知中心菜单就好
  • Qt中的信号和槽

    信号和槽的定义和使用 信号和槽的相关概念 用信号和槽 实现一个功能 1 定义teacher和student两个类 继承Qobject 2 teacher类发送信号hungry student类接受信号 用槽函数treat 响应 studen
  • warning: LF will be replaced by CRLF in

    解决LF 和 CRLF的问题 情景 今天在拉取代码的时候发现代码全都报红了 原因就是这个warning LF will be replaced by CRLF in 我用的是windows 同事用的是mac系统 其实是在换行的时候不同的系统
  • 工业物联网协议对比:MQTT Sparkplug vs OPC-UA

    引言 工业系统需要确保数据的无缝交换 因此对于高效 安全的通信协议具有极高的依赖性 MQTT Sparkplug 和 OPC UA 是两个经常被提到的工业协议 本文将全面比较 MQTT Sparkplug 和 OPC UA 以及它们的衍生版
  • 帆软-旋风图或飓风图

    https help fanruan com finereport doc view 1937 html source 1 1 概述 1 1 应用场景 图表可以自定义坐标轴的轴标签格式 例如旋风图的 X 轴的轴标签默认左侧为负值 可以将这些
  • 2020软件测试学科全套上课视频教程网盘免费分享

    软件测试学科全套上课视频教程网盘免费分享 合计50余天 以下是2020软件测试学习路线 为知名的培训机构视频 笔记源码分享给大家 里面可能会有瑕疵 望见谅 是小编自己在官网下载整理出来的 整理不易 分享给大家 请大家按照以下的学习线路学习
  • unity游戏开发日志(一)将mmd模型导入unity,并解决材质丢失的问题

    开发目标 从零开始开发一款音乐手游 类似钢琴块的玩法加QQ炫舞画面 实现一部想玩就可以随时玩的简单音游 该项目着重练手 以及技术交流 欢迎大家指导 开发计划 计划在两个月内完成游戏 该游戏完成后将进行fps游戏的开发 完成角色模型导入 ui
  • JDBC工具类——JDBCUtils类

    文章目录 一 JDBCUtils类设计 二 项目实例 1 用户表 2 项目结构 1 db properties 2 JDBCUtils java 3 JDBCUtilsTest java 一 JDBCUtils类设计 1 静态代码块 读取配
  • Win10下kafka简单安装及使用

    新手教程 老鸟忽略 使用具体参见http kafka apache org quickstart quickstart multibroker文档使用说明 如有问题欢迎交流 kafka依赖于zookeeper 官网下载的kafka内置了zo
  • navicat premium连接Oracle

    1 准备工作 instantclient 12 1 oracle官网可以下 配置环境变量 2个 2 配置环境变量 SQLPATH instantclient 11 2的安装地址 NLS LANG AMERICAN AMERICA UTF8
  • PHP输出1-20之间的奇数,用php输出一个数组中的偶数或奇数的方法

    函数介绍 array filter 函数用回调函数过滤数组中的元素 该函数把输入数组中的每个键值传给回调函数 如果回调函数返回 true 则把输入数组中的当前键值返回给结果数组 数组键名保持不变 在线学习视频推荐 php视频教程 代码示例如
  • 用欧拉公式推导三角函数所有公式包括 倍角公式-半角公式-和差化积-积化和差...

    主要思路 从欧拉公式推证得四条积化和差公式 得到了三角函数中加减乘除的转换基础 之后的证明就非常简单了 1我们首先从欧拉公式推出sinx和cosx 2再推出积化和差的四个基本公式 积化和差的具体推导只是一个非技巧性的推证 3有了积化和差 倍
  • UTC时间如何转换成北京时间—C语言代码

    解析原理 UTC 时区差 本地时间 时区差东为正 西为负 在此 把东八区时区差记为 0800 UTC 0800 本地 北京 时间 1 那么 UTC 本地时间 北京时间 0800 2 0942 0800 0142 即UTC是当天凌晨一点四十二
  • jmeter安装

    今天我们项目经理发我们jmeter安装包 说后期测试会用到 让我们安装一下 然后 我找了安装教程 发现有的说安装前需要配置环境 有的说不用配置 我刚开始配置了环境 但是发现最后打开jmeter窗口 最上面的菜单栏中的 选项 是灰色的不好用
  • 虚拟机安装mariadb后,无法连接数据库

    排查步骤如下 判断mariadb是否已安装 rpm q mariadb 如果未安装 使用 yum install y mariadb server x86 64 进行安装 判断防火墙是否关闭 systemctl status firewal
  • 设计模式-----单例模式

    一 单例模式简介 所谓类的单例设计模式 就是采取一定的方法保证在整个的软件系统中 对某个类只能存在一个对象实例 并且该类只提供一个取得其对象实例的方法 静态方法 比如 Hibernate 的 SessionFactory 它充当数据存储源的
  • 汽车尾气污染检测 尾气烟雾检测

    汽车尾气污染检测 尾气烟雾检测 排放黑烟的汽车 汽车尾气污染检测 尾气烟雾检测 有标注好训练数据集 python tensorflow自研框架
  • 【QT-2】Qt连接及操作MySql数据库

    基础环境 Mysql Navicat 1 安装MySQL的驱动文件 将MySQL安装目录lib下libmysql dll文件复制到Qt的安装路径D softWare Qt 5 9 9 msvc2017 64 bin下即可 2 QT项目中配置

随机推荐

  • markdown 公式_一篇文章教会你如何在Markdown文档中插入数学公式

    我平时用的比较多的是Typora这个markdown编辑器 所以在这里就以Typora为例 介绍如何在Markdown文档中插入数学公式的方法 如果你学会了这个方法 那么你无论使用哪一个markdown编辑器 你都可以很愉快的往里面插入公式
  • 【C++登山之路之语法高山 】—— 命名空间+缺省参数+函数重载(万字详解,图片演示,结构原理)

    目录 命名空间 命名空间定义 命名空间的使用 缺省参数 函数重载 函数重载底层原理 C和C 的相互调用 即使前方的路依旧艰险且看不清方向 也不要停止你的脚步 ps Pexels 上的 Creative Vix 拍摄的图片 命名空间 在C C
  • python基础语法学习一

    大部分参考自菜鸟教程 文中出现的代码 带 gt gt 的代码为交互式命令行 其他为脚本 目录 前言 运行python 正文 1 注释 2 代码块 3 多行语句 4 数据类型 4 1字符串 4 1 1 字符串格式化 4 1 2 f strin
  • 实现圆边框的渐变色

    交流群号 611979698 目前还没啥人 欢迎加入一起探讨学习 微信小程序 可通过伪元素来实现 效果图如下 index wxml代码
  • C++ stack使用方法详细介绍

    更多关于STL文章 STL学习笔记 容器适配器 stack Class stack lt gt 实现出一个 stack 也称为LIFO 后进先出 你可以使用 push 将任意数量的元素放入 stack 也可以使用 pop 将元素依其插入的相
  • js中every()和some()的用法

    1 every 与some 方法都是JS中数组的迭代方法 every 是对数组中每一项运行给定函数 如果该函数对每一项返回true 则返回true some 是对数组中每一项运行给定函数 如果该函数对任一项返回true 则返回true 1
  • 如何实现“点击回到顶部”的功能?

    五种方法 锚点 scrollTop scrollTo scrollBy scrollIntoView 锚点 使用锚点链接是一种简单的返回顶部的功能实现 该实现主要在页面顶部放置一个指定名称的锚点链接 然后在页面下方放置一个返回到该锚点的链接
  • Redis数据类型详解(String/List/Hash/Set/SortedSet)

    String set get set key value nx nx key不存在 设置成功 如果key存在 则设置失败 分布式锁 多个客户端对同一个key设置 设置成功后获得锁 其它获取锁失败 set key value xx xx ke
  • linux下rocketmq安装-单机

    1 环境准备 jdk 这里用的jdk8 maven 3 6 1 这里说一下maven安装 下载maven 3 6 1 root devops 02 wget https archive apache org dist maven maven
  • 关于人工智能若干问题的再思考

    1 有人认为 人工智能就是人类在了解自己 认识自己 实际上 人工智能只是人类试图了解自己而已 因为 我是谁 这个坐标原点远远还没有确定下来 2 我是谁 的问题就是自主的初始问题 也是人所有智能坐标体系框架的坐标原点 记忆是这个坐标系中具有方
  • 作为开发人员,这四类Code Review方法你都知道吗?

    本文翻译自 https dzone com articles 4 types of code reviews any professional developer 转载请注明出处 葡萄城官网 葡萄城为开发者提供专业的开发工具 解决方案和服务
  • 行人重识别(Person Re-Identification) ——Market-1501 数据集介绍、命名说明及pytorch数据类型转化

    开头瞎叭叭 每日一个小知识 pycharm快速注释操作 ctrl Market 1501数据集简介 1 该数据集在清华大学校园中采集 于夏天拍摄 在 2015 年构建并公开 2 包括由6个摄像头 其中5个高清摄像头和1个低清摄像头 拍摄到的
  • 从B树、B+树、B*树谈到R 树

    从B 树 B 树 B 树谈到R 树 作者 July weedge Frankie 编程艺术室出品 说明 本文从B树开始谈起 然后论述B 树 B 树 最后谈到R 树 其中B树 B 树及B 树部分由weedge完成 R 树部分由Frankie完
  • MySQL——MySQL的基础操作部分

    使用命令行登录 mysql u root p 直接敲击回车后输入密码即可 当看到出现 mysql gt 的符号之后 就表示已经进入到了 系统中 就可以输入 的命令对数据库进行操作了 查看数据库 使用命令查看所有的数据库 注意在使用MySQL
  • 【福建事业单位-综合基础知识】03行政法

    福建事业单位 综合基础知识 03行政法 1 行政法概述 原则重点 行政主体范围 行政行为 总结 二 行政处罚 2 1行政处罚的种类 总结 三 行政强制措施 总结 四 行政复讼 总结 五 行政诉讼 总结 行政法框架 1 行政法概述 原则重点
  • Openstack之dashboard服务、云主机管理、cinder块存储服务

    搭建Openstack环境以及Openstack认证服务 Openstack之glance镜像服务 nova计算服务 Openstack之neutron网络服务 启动一个实例 Openstack之dashboard服务 云主机管理 cind
  • UFT的使用

    UFT是一种自动化测试工具 以VBScript为内嵌语言 其支持功能测试和回归测试自动化 可用于软件应用程序和环境的测试 基本功能包括 创建测试 检验数据 增强测试 运行测试脚本 分析测试结果 维护测试 视图分为 关键字视图和专家视图 UF
  • matlab敏感性分析代码,sobol敏感性分析 matlab代码

    sobol 参数敏感性分析 参考 csdn https blog csdn net xiaosebi1111 article details 46517409 wiki https en wikipedia org wiki Varianc
  • spring框架:简介+依赖注入

    目录 一 spring简介 二 创建项目 三 spring创建对象 四 SpringBean管理 1 注入实现 XML 2 注入实现 注解 一 spring简介 spring诞生与2003年 是一个轻量级的 IOC Inversion Of
  • Guns 技术文档 v5.1

    stylefeng 2018 10 17 10 22 字数 27433 阅读 4795 Guns 技术文档 v5 1 stylefeng技术文档 Guns 技术文档 v5 1 1 序言 1 1 文档简介 1 2 Guns教程 1 3 获取帮