BMS养殖后台管理系统开发文档

简介:

​ BMS是一个后台管理系统,基于经典技术组合(Spring Boot、Apache、Shiro、MyBatis、Thymeleaf)主要目的是通过项目系统的学习运用,将公司以及社会发展需要同专业知识结合,注重专注业务,降低技术难度,从而节省人力成本,缩短项目周期,提高软件安全质量。


技术架构:

​ Spring Boot、Apache、Shiro、MyBatis、Thymeleaf

1、系统环境Java EE 8Servlet 3.0Apache Maven 3
2、主框架Spring Boot 2.2.xSpring Framework 5.2.xApache Shiro 1.7
3、持久层Apache MyBatis 3.5.xHibernate Validation 6.0.xAlibaba Druid 1.2.x
4、视图层Bootstrap 3.3.7Thymeleaf 3.0.x

开发要求:JDK >= 1.8MySQL >= 5.7Maven >= 3.0

开发工具:

​ Idea、Navicat、Typora、Postman、Apifox(国产)、Google


数据库设计版式:

数据库


主要特性:

完全响应式布局(支持电脑、平板、手机等所有主流设备)
强大的一键生成功能(包括控制器、模型、视图、菜单等)
支持多数据源,简单配置即可实现切换。
支持按钮及数据权限,可自定义部门数据权限。
对常用js插件进行二次封装,使js代码变得简洁,更加易维护
完善的XSS防范及脚本过滤,彻底杜绝XSS攻击
Maven多项目依赖,模块及插件分项目,尽量松耦合,方便模块升级、增减模块。
国际化支持,服务端及客户端支持
完善的日志记录体系简单注解即可实现
支持服务监控,数据监控,缓存监控功能。

部署系统

    打包工程文件在主项目的bin目录下执行package.bat打包Web工程,生成war/jar包文件。然后会在项目下生成target文件夹包含war或jar提示多模块版本会生成在admin模块下target文件夹部署工程文件1、jar部署方式使用命令行执行:java –jar XXX.jar 或者执行脚本:run.bat2、war部署方式主项目的pom.xml中的packaging修改为war,放入tomcat服务器webapps<packaging>war</packaging>提示多模块版本在admin模块下修改pom.xmlSpringBoot去除内嵌Tomcat(PS:此步骤不重要,因为不排除也能在容器中部署war)<!-- 多模块排除内置tomcat --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><!-- 单应用排除内置tomcat -->       <exclusions><exclusion><artifactId>spring-boot-starter-tomcat</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions>

相关问题异常解决:

    如果提示当前权限不足,无法写入文件请检查application.yml中的profile路径或logback.xml中的log.path路径是否有可读可写操作权限

项目结构:

com.bms
├── common            // 工具类
│       └── annotation                    // 自定义注解
│       └── config                        // 全局配置
│       └── constant                      // 通用常量
│       └── core                          // 核心控制
│       └── enums                         // 通用枚举
│       └── exception                     // 通用异常
│       └── json                          // JSON数据处理
│       └── utils                         // 通用类处理
│       └── xss                           // XSS过滤处理
├── framework         // 框架核心
│       └── aspectj                       // 注解实现
│       └── config                        // 系统配置
│       └── datasource                    // 数据权限
│       └── interceptor                   // 拦截器
│       └── manager                       // 异步处理
│       └── shiro                         // 权限控制
│       └── web                           // 前端控制
├── bms-generator   // 代码生成(不用可移除)
├── bms-system      // 系统代码
├── bms-admin       // 后台服务
├── bms-xxxxxx      // 其他自定义功能模块

配置文件

  • 通用配置 application.yml
# 项目相关配置
bms:# 名称name: lowkey# 版本version: 4.6.0# 版权年份copyrightYear: 2022# 实例演示开关demoEnabled: true# 文件路径 示例( Windows配置D:/lowkey/uploadPath,Linux配置 /home/lowkey/uploadPath)profile: D:/BMS/uploadPath# 获取ip地址开关addressEnabled: false# 开发环境配置
server:# 服务器的HTTP端口,默认为80port: 80servlet:# 应用的访问路径context-path: /tomcat:# tomcat的URI编码uri-encoding: UTF-8# tomcat最大线程数,默认为200max-threads: 800# Tomcat启动初始化的线程数,默认值25min-spare-threads: 30# 日志配置
logging:level:com.ruoyi: debugorg.springframework: warn# 用户配置
user:password:# 密码错误{maxRetryCount}次锁定10分钟maxRetryCount: 5# Spring配置
spring:# 模板引擎thymeleaf:mode: HTMLencoding: utf-8# 禁用缓存cache: false# 资源信息messages:# 国际化资源文件路径basename: static/i18n/messagesjackson:time-zone: GMT+8date-format: yyyy-MM-dd HH:mm:ssprofiles: active: druid# 文件上传servlet:multipart:# 单个文件大小max-file-size:  10MB# 设置总上传的文件大小max-request-size:  20MB# 服务模块devtools:restart:# 热部署开关enabled: true# MyBatis
mybatis:# 搜索指定包别名typeAliasesPackage: com.bms.domain# 配置mapper的扫描,找到所有的mapper.xml映射文件mapperLocations: classpath*:mapper/**/*Mapper.xml# 加载全局的配置文件configLocation: classpath:mybatis/mybatis-config.xml# PageHelper分页插件
pagehelper: helperDialect: mysqlreasonable: truesupportMethodsArguments: trueparams: count=countSql # Shiro
shiro:user:# 登录地址loginUrl: /login# 权限认证失败地址unauthorizedUrl: /unauth# 首页地址indexUrl: /index# 验证码开关captchaEnabled: true# 验证码类型 math 数组计算 char 字符captchaType: mathcookie:# 设置Cookie的域名 默认空,即当前访问的域名domain: # 设置cookie的有效访问路径path: /# 设置HttpOnly属性httpOnly: true# 设置Cookie的过期时间,天为单位maxAge: 30# 设置密钥,务必保持唯一性(生成方式,直接拷贝到main运行即可)KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecretKey deskey = keygen.generateKey(); System.out.println(Base64.encodeToString(deskey.getEncoded()));cipherKey: zSyK5Kp6PZAAjlT+eeNMlg==session:# Session超时时间,-1代表永不过期(默认30分钟)expireTime: 30# 同步session到数据库的周期(默认1分钟)dbSyncPeriod: 1# 相隔多久检查一次session的有效性,默认就是10分钟validationInterval: 10# 同一个用户最大会话数,比如2的意思是同一个账号允许最多同时两个人登录(默认-1不限制)maxSession: -1# 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户kickoutAfter: false# 防止XSS攻击
xss: # 过滤开关enabled: true# 排除链接(多个用逗号分隔)excludes: /system/notice/*# 匹配链接urlPatterns: /system/*,/monitor/*,/tool/*# Swagger配置
swagger:# 是否开启swaggerenabled: true

数据源配置 application-druid.yml

# 数据源配置
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverdruid:# 主库数据源master:url: jdbc:mysql://localhost:3306/bms?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: rootpassword: 123456# 从库数据源slave:# 从数据源开关/默认关闭enabled: falseurl: username: password: # 初始连接数initialSize: 5# 最小连接池数量minIdle: 10# 最大连接池数量maxActive: 20# 配置获取连接等待超时的时间maxWait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒minEvictableIdleTimeMillis: 300000# 配置一个连接在池中最大生存的时间,单位是毫秒maxEvictableIdleTimeMillis: 900000# 配置检测连接是否有效validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsewebStatFilter: enabled: truestatViewServlet:enabled: true# 设置白名单,不填则允许所有访问allow:url-pattern: /druid/*# 控制台管理用户名和密码login-username: login-password: filter:stat:enabled: true# 慢SQL记录log-slow-sql: trueslow-sql-millis: 1000merge-sql: truewall:config:multi-statement-allow: true

代码生成配置 generator.yml

# 代码生成
gen: # 作者author: lowkey# 默认生成包路径 system 需改成自己的模块名称 如 system monitor toolpackageName: com.bms.system# 自动去除表前缀,默认是falseautoRemovePre: false# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)tablePrefix: sys_

核心技术

SpringBoot框架

1、介绍

Spring Boot是一款开箱即用框架,提供各种默认配置来简化项目配置。让我们的Spring应用变的更轻量化、更快的入门。 在主程序执行main函数就可以运行。你也可以打包你的应用为jar并通过使用java -jar来运行你的Web应用。它遵循"约定优先于配置"的原则, 使用SpringBoot只需很少的配置,大部分的时候直接使用默认的配置即可。同时可以与Spring Cloud的微服务无缝结合。

提示

Spring Boot2.x版本环境要求必须是jdk8或以上版本,服务器Tomcat8或以上版本

2、优点

  • 使编码变得简单: 推荐使用注解。
  • 使配置变得简单: 自动配置、快速集成新技术能力 没有冗余代码生成和XML配置的要求
  • 使部署变得简单: 内嵌Tomcat、Jetty、Undertow等web容器,无需以war包形式部署
  • 使监控变得简单: 提供运行时的应用监控
  • 使集成变得简单: 对主流开发框架的无配置集成。
  • 使开发变得简单: 极大地提高了开发快速构建项目、部署效率。

Shiro安全控制

1、介绍
Apache Shiro是Java的一个安全框架。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。其不仅可以用在 JavaSE环境,也可以用在 JavaEE 环境。

2、优点

  • 易于理解的 Java Security API
  • 简单的身份认证,支持多种数据源
  • 对角色的简单的授权,支持细粒度的授权
  • 不跟任何的框架或者容器捆绑,可以独立运行

3、特性
Authentication身份认证/登录,验证用户是不是拥有相应的身份
Authorization授权,即验证权限,验证某个已认证的用户是否拥有某个权限,即判断用户是否能做事情 SessionManagement会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中
Cryptography加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
Caching缓存,比如用户登录后,其用户信息,拥有的角色/权限不必每次去查,提高效率
ConcurrencyShiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去
Testing提供测试支持
RunAs允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
RememberMe记住我,这是非常常见的功能,即一次登录后,下次再来的话不用登录了

4、架构
Subject主体,代表了当前的“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫, 机器人等;即一个抽象概念;所有Subject都绑定到SercurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者
SecurityManage安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject; 可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互
Realm域,Shiro从Realm获取安全数据(如用户,角色,权限),就是说SecurityManager要验证用户身份, 那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以有1个或多个Realm,我们一般在应用中都需要实现自己的Realm
SessionManager如果写过Servlet就应该知道Session的概念,Session需要有人去管理它的生命周期,这个组件就是SessionManager
SessionDAODAO大家都用过,数据库访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,也可以写入缓存,以提高性能
CacheManager缓存控制器,来管理如用户,角色,权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager; 我们需要给Shrio的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。

Shiro不会去维护用户,维护权限;这些需要自己去设计/提供;然后通过响应的接口注入给Shiro即可

Thymeleaf模板

1、介绍
Thymeleaf是一个用于Web和独立Java环境的模板引擎,能够处理HTML、XML、JavaScript、CSS甚至纯文本。能轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。 与其它模板引擎(比如FreeMaker)相比,Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用(更加方便前后端分离,比如方便类似VUE前端设计页面),抛弃JSP吧。 Thymeleaf 3.0是一个完全彻底重构的模板引擎,极大的减少内存占用和提升性能和并发性,避免v2.1版因大量的输出标记的集合产生的资源占用。 Thymeleaf 3.0放弃了大多数面向DOM的处理机制,变成了一个基于事件的模板处理器,它通过处理模板标记或文本并立即生成其输出,甚至在新事件之前响应模板解析器/缓存事件。Thymeleaf是Spring Boot官方的推荐使用模板。

2、优点

  • 国际化支持非常简单
  • 语法简单,功能强大。内置大量常用功能,使用非常方便
  • 可以很好的和Spring集成
  • 静态html嵌入标签属性,浏览器可以直接打开模板文件,便于前后端联调
  • Spring Boot 官方推荐,用户群广

分页实现

  • 前端采用基于bootstrap的轻量级表格插件bootstrap-table
  • 后端采用基于mybatis的轻量级分页插件pageHelper

前后端分页实现流程

前端调用实现

var options = {url: prefix + "/list",columns: [{field: 'id',title: '主键'},{field: 'name',title: '名称'}]
};
$.table.init(options);

自定义查询条件参数(特殊情况提前设置查询条件下使用)

var options = {url: prefix + "/list",queryParams: queryParams,columns: [{field: 'id',title: '主键'},{field: 'name',title: '名称'}]
};
$.table.init(options);function queryParams(params) {var search = $.table.queryParams(params);search.userName = $("#userName").val();return search;
}

后台逻辑实现

@PostMapping("/list")
@ResponseBody
public TableDataInfo list(User user)
{startPage();  // 此方法配合前端完成自动分页List<User> list = userService.selectUserList(user);return getDataTable(list);
}
  • 常见坑点1:selectPostById莫名其妙的分页。例如下面这段代码
startPage();
List<User> list;
if(user != null){list = userService.selectUserList(user);
} else {list = new ArrayList<User>();
}
Post post = postService.selectPostById(1L);
return getDataTable(list);

原因分析:这种情况下由于user存在null的情况,就会导致pageHelper生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。 当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
上面这个代码,应该写成下面这个样子才能保证安全。

List<User> list;
if(user != null){startPage();list = userService.selectUserList(user);
} else {list = new ArrayList<User>();
}
Post post = postService.selectPostById(1L);
return getDataTable(list);
  • 常见坑点2:添加了startPage方法。也没有正常分页。例如下面这段代码
startPage();
Post post = postService.selectPostById(1L);
List<User> list = userService.selectUserList(user);
return getDataTable(list);

原因分析:只对该语句以后的第一个查询(Select)语句得到的数据进行分页。
上面这个代码,应该写成下面这个样子才能正常分页。

Post post = postService.selectPostById(1L);
startPage();
List<User> list = userService.selectUserList(user);
return getDataTable(list);

注意

如果改为其他数据库需修改配置application.yml文件中的属性helperDialect=你的数据库

导入导出

在实际开发中经常需要使用导入导出功能来加快数据的操作。在项目中可以使用注解来完成此项功能。 在需要被导入导出的实体类属性添加@Excel注解,目前支持参数如下:

注解参数说明

参数 类型 默认值 描述
sort int Integer.MAX_VALUE 导出时在excel中排序,值越小越靠前
name String 导出到Excel中的名字
dateFormat String 日期格式, 如: yyyy-MM-dd
dictType String 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
readConverterExp String 读取内容转表达式 (如: 0=男,1=女,2=未知)
separator String , 分隔符,读取字符串组内容
scale int -1 BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
roundingMode int BigDecimal.ROUND_HALF_EVEN BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
columnType Enum Type.STRING 导出类型(0数字 1字符串 2图片)
height String 14 导出时在excel中每个列的高度 单位为字符
width String 16 导出时在excel中每个列的宽 单位为字符
suffix String 文字后缀,如% 90 变成90%
defaultValue String 当值为空时,字段的默认值
prompt String 提示信息
combo String Null 设置只能选择不能输入的列内容
targetAttr String 另一个类中的属性名称,支持多级获取,以小数点隔开
isStatistics boolean false 是否自动统计数据,在最后追加一行统计数据总和
type Enum Type.ALL 字段类型(0:导出导入;1:仅导出;2:仅导入)
align Enum Type.AUTO 导出字段对齐方式(0:默认;1:靠左;2:居中;3:靠右)
handler Class ExcelHandlerAdapter.class 自定义数据处理器
args String[] {} 自定义数据处理器参数
导出实现流程

1、前端调用封装好的方法$.table.init,传入后台exportUrl

var options = {exportUrl: prefix + "/export",columns: [{field: 'id',title: '主键'},{field: 'name',title: '名称'}]
};
$.table.init(options);

2、添加导出按钮事件

<a class="btn btn-warning" onclick="$.table.exportExcel()"><i class="fa fa-download"></i> 导出
</a>

3、在实体变量上添加@Excel注解

@Excel(name = "用户序号", prompt = "用户编号")
private Long userId;@Excel(name = "用户名称")
private String userName;@Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
private String sex;@Excel(name = "用户头像", cellType = ColumnType.IMAGE)
private String avatar;@Excel(name = "帐号状态", dictType = "sys_normal_disable")
private String status;@Excel(name = "最后登陆时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date loginDate;

4、在Controller添加导出方法

@PostMapping("/export")
@ResponseBody
public AjaxResult export(User user)
{List<User> list = userService.selectUserList(user);ExcelUtil<User> util = new ExcelUtil<User>(User.class);return util.exportExcel(list, "用户数据");
}

提示

导出默认流程是先创建一个临时文件,等待前端请求下载结束后马上删除这个临时文件。如遇到迅雷这种二次请求下载应用可能会导致文件已经被删除,我们也可以改成流的形式返回给前端。 参考实现 - [如何解决导出使用下载插件出现异常(opens new window)]

导入实现流程

1、前端调用封装好的方法$.table.init,传入后台importUrl

var options = {importUrl: prefix + "/importData",columns: [{field: 'id',title: '主键'},{field: 'name',title: '名称'}]
};
$.table.init(options);

2、添加导入按钮事件

<a class="btn btn-info" onclick="$.table.importExcel()"><i class="fa fa-upload"></i> 导入
</a>

3、添加导入前端代码,form默认idimportForm,也可指定importExcel(id)

<!-- 导入区域 -->
<script id="importTpl" type="text/template">
<form enctype="multipart/form-data" class="mt20 mb10"><div class="col-xs-offset-1"><input type="file" id="file" name="file"/><div class="mt10 pt5"><input type="checkbox" id="updateSupport" name="updateSupport" title="如果登录账户已经存在,更新这条数据。"> 是否更新已经存在的用户数据&nbsp; <a onclick="$.table.importTemplate()" class="btn btn-default btn-xs"><i class="fa fa-file-excel-o"></i> 下载模板</a></div><font color="red" class="pull-left mt10">提示:仅允许导入“xls”或“xlsx”格式文件!</font></div>
</form>
</script>

4、在实体变量上添加@Excel注解,默认为导出导入,也可以单独设置仅导入Type.IMPORT

@Excel(name = "用户序号")
private Long id;@Excel(name = "部门编号", type = Type.IMPORT)
private Long deptId;@Excel(name = "用户名称")
private String userName;/** 导出部门多个对象 */
@Excels({@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
})
private SysDept dept;/** 导出部门单个对象 */
@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT)
private SysDept dept;

5、在Controller添加导入方法,updateSupport属性为是否存在则覆盖(可选)

@PostMapping("/importData")
@ResponseBody
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
{ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);List<SysUser> userList = util.importExcel(file.getInputStream());String operName = ShiroUtils.getSysUser().getLoginName();String message = userService.importUser(userList, updateSupport, operName);return AjaxResult.success(message);
}

提示

也可以直接到main运行此方法测试。

InputStream is = new FileInputStream(new File("D:\\test.xlsx"));
ExcelUtil<Entity> util = new ExcelUtil<Entity>(Entity.class);
List<Entity> userList = util.importExcel(is);

自定义标题信息

有时候我们希望导出表格包含标题信息,我们可以这样做。

导出用户管理表格新增标题(用户列表)

public AjaxResult export(SysUser user)
{List<SysUser> list = userService.selectUserList(user);ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);return util.exportExcel(list, "用户数据", "用户列表");
}

导入表格包含标题处理方式,其中1表示标题占用行数,根据实际情况填写。

public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
{ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);List<SysUser> userList = util.importExcel(file.getInputStream(), 1);String operName = SecurityUtils.getUsername();String message = userService.importUser(userList, updateSupport, operName);return AjaxResult.success(message);
}

自定义数据处理器

有时候我们希望数据展现为一个特殊的格式,或者需要对数据进行其它处理。Excel注解提供了自定义数据处理器以满足各种业务场景。而实现一个数据处理器也是非常简单的。如下:

1、在实体类用Excel注解handler属性指定自定义的数据处理器

public class User extends BaseEntity
{@Excel(name = "用户名称", handler = MyDataHandler.class, args = { "aaa", "bbb" })private String userName;
}

2、编写数据处理器MyDataHandler继承ExcelHandlerAdapter,返回值为处理后的值。

public class MyDataHandler implements ExcelHandlerAdapter
{@Overridepublic Object format(Object value, String[] args){// value 为单元格数据值// args 为excel注解args参数组return value;}
}

上传下载

首先创建一张上传文件的表,例如:

drop table if exists sys_file_info;
create table sys_file_info (file_id           int(11)          not null auto_increment       comment '文件id',file_name         varchar(50)      default ''                    comment '文件名称',file_path         varchar(255)     default ''                    comment '文件路径',primary key (file_id)
) engine=innodb auto_increment=1 default charset=utf8 comment = '文件信息表';
上传实现流程

1、代码生成sys_file_info表相关代码并复制到对应目录。

2、参考示例修改代码。

<input id="filePath" name="filePath" class="form-control" type="file">
function submitHandler() {if ($.validate.form()) {uploadFile();}
}function uploadFile() {var formData = new FormData();if ($('#filePath')[0].files[0] == null) {$.modal.alertWarning("请先选择文件路径");return false;}formData.append('fileName', $("#fileName").val());formData.append('file', $('#filePath')[0].files[0]);$.ajax({url: prefix + "/add",type: 'post',cache: false,data: formData,processData: false,contentType: false,dataType: "json",success: function(result) {$.operate.successCallback(result);}});
}

3、在FileInfoController添加对应上传方法

@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@RequestParam("file") MultipartFile file, FileInfo fileInfo) throws IOException
{// 上传文件路径String filePath = bmsConfig.getUploadPath();// 上传并返回新文件名称String fileName = FileUploadUtils.upload(filePath, file);fileInfo.setFilePath(fileName);return toAjax(fileInfoService.insertFileInfo(fileInfo));
}

4、上传成功后需要预览可以对该属性格式化处理

{field : 'filePath', title: '文件预览',formatter: function(value, row, index) {return $.table.imageView(value);}
},

如需对文件格式控制,设置application.yml中的multipart属性

# 文件上传
servlet:multipart:# 单个文件大小max-file-size:  10MB# 设置总上传的文件大小max-request-size:  20MB

注意:如果只是单纯的上传一张图片没有其他参数可以使用通用方法 /common/upload
请求处理方法 com.bms.web.controller.common.CommonController

下载实现流程

1、参考示例代码。

function downloadFile(value){window.location.href = ctx + "common/download/resource?resource=" + value;
}

2、参考Controller下载方法

/*** 本地资源通用下载*/
@GetMapping("/common/download/resource")
public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)throws Exception
{// 本地资源路径String localPath = Global.getProfile();// 数据库资源地址String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);// 下载名称String downloadName = StringUtils.substringAfterLast(downloadPath, "/");response.setCharacterEncoding("utf-8");response.setContentType("multipart/form-data");response.setHeader("Content-Disposition","attachment;fileName=" + FileUtils.setFileDownloadHeader(request, downloadName));FileUtils.writeBytes(downloadPath, response.getOutputStream());
}

权限注解

Shiro注解权限控制

  • @RequiresAuthentication使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。
  • @RequiresGuest使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是gust身份,不需要经过认证或者在原先的session中存在记录。
  • @RequiresPermissions当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
  • @RequiresRoles当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
  • @RequiresUser当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。
@RequiresRoles

@RequiresRoles注解用于配置接口要求用户拥有某(些)角色才可访问,它拥有两个参数

参数 类型 描述
value String[] 角色列表
logical Logical 角色之间的判断关系,默认为Logical.AND

示例1: 以下代码表示必须拥有admin角色才可访问

@RequiresRoles("admin")
public AjaxResult save(...)
{return AjaxResult.success(...);
}

示例2: 以下代码表示必须拥有admincommon角色才可访问

@RequiresRoles({"admin", "common"})
public AjaxResult save(...)
{return AjaxResult.success(...);
}

示例3: 以下代码表示需要拥有admincommon角色才可访问

@RequiresRoles(value = {"admin", "common"}, logical = Logical.OR)
public AjaxResult save(...)
{return AjaxResult.success(...);
}
@RequiresPermissions

@RequiresPermissions注解用于配置接口要求用户拥有某(些)权限才可访问,它拥有两个参数

参数 类型 描述
value String[] 权限列表
logical Logical 权限之间的判断关系,默认为Logical.AND

示例1: 以下代码表示必须拥有system:user:add权限才可访问

@RequiresPermissions("system:user:add")
public AjaxResult save(...)
{return AjaxResult.success(...);
}

示例2: 以下代码表示必须拥有system:user:addsystem:user:update权限才可访问

@RequiresPermissions({"system:user:add", "system:user:update"})
public AjaxResult save(...)
{return AjaxResult.success(...);
}

示例3: 以下代码表示需要拥有system:user:addsystem:user:update角色才可访问

@RequiresPermissions(value = {"system:user:add", "system:user:update"}, logical = Logical.OR)
public AjaxResult save(...)
{return AjaxResult.success(...);
}

提示

Shiro的认证注解处理是有内定的处理顺序的,如果有个多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关) RequiresRoles、RequiresPermissions、RequiresAuthentication、RequiresUser、RequiresGuest。
例如:你同时声明了RequiresRolesRequiresPermissions,那就要求拥有此角色的同时还得拥有相应的权限。

事务管理

新建的Spring Boot项目中,一般都会引用spring-boot-starter或者spring-boot-starter-web,而这两个起步依赖中都已经包含了对于spring-boot-starter-jdbcspring-boot-starter-data-jpa的依赖。 当我们使用了这两个依赖的时候,框架会自动默认分别注入DataSourceTransactionManagerJpaTransactionManager。 所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。

提示

@Transactional注解只能应用到public可见度的方法上,可以被应用于接口定义和接口方法,方法会覆盖类上面声明的事务。

例如用户新增需要插入用户表、用户与岗位关联表、用户与角色关联表,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作, 这样可以防止出现脏数据,就可以使用事务让它实现回退。
做法非常简单,我们只需要在方法或类添加@Transactional注解即可。

@Transactional
public int insertUser(User user)
{// 新增用户信息int rows = userMapper.insertUser(user);// 新增用户岗位关联insertUserPost(user);// 新增用户与角色管理insertUserRole(user);return rows;
}
  • 常见坑点1:遇到检查异常时,事务开启,也无法回滚。 例如下面这段代码,用户依旧增加成功,并没有因为后面遇到检查异常而回滚!!
@Transactional
public int insertUser(User user) throws Exception
{// 新增用户信息int rows = userMapper.insertUser(user);// 新增用户岗位关联insertUserPost(user);// 新增用户与角色管理insertUserRole(user);// 模拟抛出SQLException异常boolean flag = true;if (flag){throw new SQLException("发生异常了..");}return rows;
}

原因分析:因为Spring的默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。如果想针对检查异常进行事务回滚,可以在@Transactional注解里使用 rollbackFor属性明确指定异常。
例如下面这样,就可以正常回滚:

@Transactional(rollbackFor = Exception.class)
public int insertUser(User user) throws Exception
{// 新增用户信息int rows = userMapper.insertUser(user);// 新增用户岗位关联insertUserPost(user);// 新增用户与角色管理insertUserRole(user);// 模拟抛出SQLException异常boolean flag = true;if (flag){throw new SQLException("发生异常了..");}return rows;
}
  • 常见坑点2: 在业务层捕捉异常后,发现事务不生效。 这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。
    例如:下面这段代码直接导致用户新增的事务回滚没有生效。
@Transactional
public int insertUser(User user) throws Exception
{// 新增用户信息int rows = userMapper.insertUser(user);// 新增用户岗位关联insertUserPost(user);// 新增用户与角色管理insertUserRole(user);// 模拟抛出SQLException异常boolean flag = true;if (flag){try{// 谨慎:尽量不要在业务层捕捉异常并处理throw new SQLException("发生异常了..");}catch (Exception e){e.printStackTrace();}}return rows;
}

推荐做法:在业务层统一抛出异常,然后在控制层统一处理。

@Transactional
public int insertUser(User user) throws Exception
{// 新增用户信息int rows = userMapper.insertUser(user);// 新增用户岗位关联insertUserPost(user);// 新增用户与角色管理insertUserRole(user);// 模拟抛出SQLException异常boolean flag = true;if (flag){throw new RuntimeException("发生异常了..");}return rows;
}

Transactional注解的常用属性表:

属性 说明
propagation 事务的传播行为,默认值为 REQUIRED。
isolation 事务的隔离度,默认值采用 DEFAULT
timeout 事务的超时时间,默认值为-1,不超时。如果设置了超时时间(单位秒),那么如果超过该时间限制了但事务还没有完成,则自动回滚事务。
read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。{xxx1.class, xxx2.class,……}
noRollbackFor 抛出 no-rollback-for 指定的异常类型,不回滚事务。{xxx1.class, xxx2.class,……}

提示

事务的传播机制是指如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。 即:在执行一个@Transactinal注解标注的方法时,开启了事务;当该方法还在执行中时,另一个人也触发了该方法;那么此时怎么算事务呢,这时就可以通过事务的传播机制来指定处理方式。

TransactionDefinition传播行为的常量:

常量 含义
TransactionDefinition.PROPAGATION_REQUIRED 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
TransactionDefinition.PROPAGATION_REQUIRES_NEW 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER 以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

异常处理

通常一个web框架中,有大量需要处理的异常。比如业务异常,权限不足等等。前端通过弹出提示信息的方式告诉用户出了什么错误。 通常情况下我们用try.....catch....对异常进行捕捉处理,但是在实际项目中对业务模块进行异常捕捉,会造成代码重复和繁杂, 我们希望代码中只有业务相关的操作,所有的异常我们单独设立一个类来处理它。全局异常就是对框架所有异常进行统一管理。 我们在可能发生异常的方法里throw抛给控制器。然后由全局异常处理器对异常进行统一处理。 如此,我们的Controller中的方法就可以很简洁了。

所谓全局异常处理器就是使用@ControllerAdvice注解。示例如下:

1、统一返回实体定义
package com.ruoyi.common.core.domain;import java.util.HashMap;/*** 操作消息提醒* * @author ruoyi*/
public class AjaxResult extends HashMap<String, Object>
{private static final long serialVersionUID = 1L;/*** 返回错误消息* * @param code 错误码* @param msg 内容* @return 错误消息*/public static AjaxResult error(String msg){AjaxResult json = new AjaxResult();json.put("msg", msg);json.put("code", 500);return json;}/*** 返回成功消息* * @param msg 内容* @return 成功消息*/public static AjaxResult success(String msg){AjaxResult json = new AjaxResult();json.put("msg", msg);json.put("code", 0);return json;}
}
2、定义登录异常定义
package com.ruoyi.common.exception;/*** 登录异常* * @author ruoyi*/
public class LoginException extends RuntimeException
{private static final long serialVersionUID = 1L;protected final String message;public LoginException(String message){this.message = message;}@Overridepublic String getMessage(){return message;}
}
3、基于@ControllerAdvice注解的Controller层的全局异常统一处理
package com.ruoyi.framework.web.exception;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.LoginException;/*** 全局异常处理器* * @author ruoyi*/
@RestControllerAdvice
public class GlobalExceptionHandler
{private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);/*** 登录异常*/@ExceptionHandler(LoginException.class)public AjaxResult loginException(LoginException e){log.error(e.getMessage(), e);return AjaxResult.error(e.getMessage());}
}
4、测试访问请求
@Controller
public class SysIndexController
{/*** 首页方法*/@GetMapping("/index")public String index(ModelMap mmap){/*** 模拟用户未登录,抛出业务逻辑异常*/SysUser user = ShiroUtils.getSysUser();if (StringUtils.isNull(user)){throw new LoginException("用户未登录,无法访问请求。");}mmap.put("user", user);return "index";}
}

根据上面代码含义,当我们未登录访问/index时就会发生LoginException业务逻辑异常,按照我们之前的全局异常配置以及统一返回实体实例化,访问后会出现AjaxResult格式JSON数据, 下面我们运行项目访问查看效果。
界面输出内容如下所示:

{"msg": "用户未登录,无法访问请求。","code": 500
}

对于一些特殊情况,如接口需要返回json,页面请求返回html可以使用如下方法:

@ExceptionHandler(LoginException.class)
public Object loginException(HttpServletRequest request, LoginException e)
{log.error(e.getMessage(), e);if (ServletUtils.isAjaxRequest(request)){return AjaxResult.error(e.getMessage());}else{return new ModelAndView("/error/500");}
}

若依系统的全局异常处理器GlobalExceptionHandler
注意:如果全部异常处理返回json,那么可以使用@RestControllerAdvice代替@ControllerAdvice,这样在方法上就可以不需要添加@ResponseBody

无法捕获异常?

如果您的异常无法捕获,您可以从以下几个方面着手检查

异常是否已被处理,即抛出异常后被catch,打印了日志或抛出了其它异常 异常是否非Controller抛出,即在拦截器或过滤器中出现的异常

参数验证

spring boot中可以用@Validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。

注解参数说明

注解名称 功能
@Xss 检查该字段是否存在跨站脚本工具
@Null 检查该字段为空
@NotNull 不能为null
@NotBlank 不能为空,常用于检查空字符串
@NotEmpty 不能为空,多用于检测list是否size是0
@Max 该字段的值只能小于或等于该值
@Min 该字段的值只能大于或等于该值
@Past 检查该字段的日期是在过去
@Future 检查该字段的日期是否是属于将来的日期
@Email 检查是否是一个有效的email地址
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
@Size(min=, max=) 检查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等
@Length(min=,max=) 检查所属的字段的长度是否在min和max之间,只能用于字符串
@AssertTrue 用于boolean字段,该字段只能为true
@AssertFalse 该字段的值只能为false

数据校验使用

1、基础使用 因为spring boot已经引入了基础包,所以直接使用就可以了。首先在controller上声明@Validated需要对数据进行校验。

public AjaxResult add(@Validated @RequestBody SysUser user)
{.....
}

2、然后在对应字段Get方法加上参数校验注解,如果不符合验证要求,则会以message的信息为准,返回给前端。

@Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
public String getNickName()
{return nickName;
}@NotBlank(message = "用户账号不能为空")
@Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
public String getUserName()
{return userName;
}@Email(message = "邮箱格式不正确")
@Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
public String getEmail()
{return email;
}@Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
public String getPhonenumber()
{return phonenumber;
}

也可以直接放在字段上面声明。

@Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
private String nickName;

定义注解校验

使用原生的@Validated进行参数校验时,都是特定的注解去校验(例如字段长度、大小、不为空等),我们也可以用自定义的注解去进行校验,例如项目中的@Xss注解。

1、新增Xss注解,设置自定义校验器XssValidator.class

/*** 自定义xss校验注解* * @author ruoyi*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
@Constraint(validatedBy = { XssValidator.class })
public @interface Xss
{String message()default "不允许任何脚本运行";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}

2、自定义Xss校验器,实现ConstraintValidator接口。

/*** 自定义xss校验注解实现* * @author ruoyi*/
public class XssValidator implements ConstraintValidator<Xss, String>
{private final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />";@Overridepublic boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext){return !containsHtml(value);}public boolean containsHtml(String value){Pattern pattern = Pattern.compile(HTML_PATTERN);Matcher matcher = pattern.matcher(value);return matcher.matches();}
}

3、实体类使用自定义的@Xss注解

@Xss(message = "登录账号不能包含脚本字符")
@NotBlank(message = "登录账号不能为空")
@Size(min = 0, max = 30, message = "登录账号长度不能超过30个字符")
public String getLoginName()
{return loginName;
}

此时在去保存会进行验证,如果不符合规则的字符(例如<script>alert(1);</script>)会提示登录账号不能包含脚本字符,代表限制成功。

如果是在方法里面校验整个实体,参考示例。

@Autowired
protected Validator validator;public void importUser(SysUser user)
{BeanValidators.validateWithException(validator, user);
}

自定义分组校验

有时候我们为了在使用实体类的情况下更好的区分出新增、修改和其他操作验证的不同,可以通过groups属性设置。使用方式如下

新增类接口,用于标识出不同的操作类型

public interface Add
{}public interface Edit
{}

Controller.java

// 新增
public AjaxResult addSave(@Validated(Add.class) @RequestBody Xxxx xxxx)
{return success(xxxx);
}// 编辑
public AjaxResult editSave(@Validated(Edit.class) @RequestBody Xxxx xxxx)
{return success(xxxx);
}

Model.java

// 仅在新增时验证
@NotNull(message = "不能为空", groups = {Add.class})
private String xxxx;// 在新增和修改时验证
@NotBlank(message = "不能为空", groups = {Add.class, Edit.class})
private String xxxx;

提示

如果你有更多操作类型,也可以自定义类统一管理,使用方式就变成了Type.AddType.EditType.Xxxx等。

package com.eva.core.constants;/*** 操作类型*/
public interface Type
{interface Add {}interface Edit {}interface Xxxx {}
}

系统日志

在实际开发中,对于某些关键业务,我们通常需要记录该操作的内容,一个操作调一次记录方法,每次还得去收集参数等等,会造成大量代码重复。 我们希望代码中只有业务相关的操作,在项目中使用注解来完成此项功能。

在需要被记录日志的controller方法上添加@Log注解,使用方法如下:

@Log(title = "用户管理", businessType = BusinessType.INSERT)
public AjaxResult addSave(...)
{return success(...);
}

注解参数说明

参数 类型 默认值 描述
title String 操作模块
businessType BusinessType OTHER 操作功能(OTHER其他、INSERT新增、UPDATE修改、DELETE删除、GRANT授权、EXPORT导出、IMPORT导入、FORCE强退、GENCODE生成代码、CLEAN清空数据)
operatorType OperatorType MANAGE 操作人类别(OTHER其他、MANAGE后台用户、MOBILE手机端用户)
isSaveRequestData boolean true 是否保存请求的参数
isSaveResponseData boolean true 是否保存响应的参数

自定义操作功能

1、在BusinessType中新增业务操作类型如:

/*** 测试*/
TEST,

2、在sys_dict_data字典数据表中初始化操作业务类型

insert into sys_dict_data values(25, 10, '测试',     '10', 'sys_oper_type',       '',   'primary', 'N', '0', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '测试操作');

3、在Controller中使用注解

@Log(title = "测试标题", businessType = BusinessType.TEST)
public AjaxResult test(...)
{return success(...);
}

操作日志记录逻辑实现代码LogAspect.java(opens new window)
登录系统(系统管理-操作日志)可以查询操作日志列表和详细信息。

数据权限

在实际开发中,需要设置用户只能查看哪些部门的数据,这种情况一般称为数据权限。
例如对于销售,财务的数据,它们是非常敏感的,因此要求对数据权限进行控制, 对于基于集团性的应用系统而言,就更多需要控制好各自公司的数据了。如设置只能看本公司、或者本部门的数据,对于特殊的领导,可能需要跨部门的数据, 因此程序不能硬编码那个领导该访问哪些数据,需要进行后台的权限和数据权限的控制。

提示

默认系统管理员admin拥有所有数据权限(userId=1),默认角色拥有所有数据权限(如不需要数据权限不用设置数据权限操作)

注解参数说明
参数 类型 默认值 描述
deptAlias String 部门表的别名
userAlias String 用户表的别名
数据权限使用

1、在(系统管理-角色管理)设置需要数据权限的角色 目前支持以下几种权限

  • 全部数据权限
  • 自定数据权限
  • 部门数据权限
  • 部门及以下数据权限
  • 仅本人数据权限

2、在需要数据权限控制方法上添加@DataScope注解,其中du用来表示表的别名

部门数据权限注解

@DataScope(deptAlias = "d")
public List<...> select(...)
{return mapper.select(...);
}

部门及用户权限注解

@DataScope(deptAlias = "d", userAlias = "u")
public List<...> select(...)
{return mapper.select(...);
}

3、在mybatis查询底部标签添加数据范围过滤

<select id="select" parameterType="..." resultMap="...Result"><include refid="select...Vo"/><!-- 数据范围过滤 -->${params.dataScope}
</select>

例如:用户管理(未过滤数据权限的情况):

select u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.phonenumber, u.password, u.sex, u.avatar, u.salt, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name
from sys_user uleft join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'

例如:用户管理(已过滤数据权限的情况):

select u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.phonenumber, u.password, u.sex, u.avatar, u.salt, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name
from sys_user uleft join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'and u.dept_id in (select dept_idfrom sys_role_deptwhere role_id = 2)

结果很明显,我们多了如下语句。通过角色部门表(sys_role_dept)完成了数据权限过滤

and u.dept_id in (select dept_idfrom sys_role_deptwhere role_id = 2
)

逻辑实现代码 com.ruoyi.framework.aspectj.DataScopeAspect

提示

仅实体继承BaseEntity才会进行处理,SQL语句会存放到BaseEntity对象中的params属性中,然后在xml中通过${params.dataScope}获取拼接后的语句。

多数据源

在实际开发中,经常可能遇到在一个应用中可能需要访问多个数据库的情况,在项目中使用注解来完成此项功能。

在需要被切换数据源的ServiceMapper方法上添加@DataSource注解,使用方法如下:

@DataSource(value = DataSourceType.MASTER)
public List<...> select(...)
{return mapper.select(...);
}

其中value用来表示数据源名称,除MASTERSLAVE其他均需要进行配置。

注解参数说明
参数 类型 默认值 描述
value DataSourceType DataSourceType.MASTER 主库
多数据源使用

1、在application-druid.yml配置从库数据源

# 从库数据源
slave:# 从数据源开关/默认关闭enabled: trueurl: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: rootpassword: password

2、在DataSourceType类添加数据源枚举

/*** 从库*/
SLAVE

3、在DruidConfig配置读取数据源

@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(DruidProperties druidProperties)
{DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);
}

4、在DruidConfigdataSource方法添加数据源

setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");

5、在需要使用多数据源方法或类上添加@DataSource注解,其中value用来表示数据源

@DataSource(value = DataSourceType.SLAVE)
public List<SysUser> selectUserList(SysUser user)
{return userMapper.selectUserList(user);
}
@Service
@DataSource(value = DataSourceType.SLAVE)
public class SysUserServiceImpl
手动切换数据源

在需要切换数据源的方法中使用DynamicDataSourceContextHolder类实现手动切换,使用方法如下:

public List<SysUser> selectUserList(SysUser user)
{DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name());List<SysUser> userList = userMapper.selectUserList(user);DynamicDataSourceContextHolder.clearDataSourceType();return userList;
}

1
2
3
4
5
6
7

逻辑实现代码 com.ruoyi.framework.aspectj.DataSourceAspect

注意:目前配置了一个从库,默认关闭状态。如果不需要多数据源不用做任何配置。 另外可新增多个从库。支持不同数据源(Mysql、Oracle、SQLServer)

提示

如果有Service方法内多个注解无效的情况使用内部方法调用SpringUtils.getAopProxy(this).xxxxxx(xxxx);

代码生成

大部分项目里其实有很多代码都是重复的,几乎每个基础模块的代码都有增删改查的功能,而这些功能都是大同小异, 如果这些功能都要自己去写,将会大大浪费我们的精力降低效率。所以这种重复性的代码可以使用代码生成。

默认配置

单应用在resources目录下的application.yml,多模块ruoyi-generator中的resources目录下的generator.yml,可以自己根据实际情况调整默认配置。

# 代码生成
gen: # 开发者姓名,生成到类注释上author: ruoyi# 默认生成包路径 system 需改成自己的模块名称 如 system monitor toolpackageName: com.ruoyi.system# 自动去除表前缀,默认是falseautoRemovePre: false# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)tablePrefix: sys_

单表结构

新建数据库表结构(单表)

drop table if exists sys_student;
create table sys_student (student_id           int(11)         auto_increment    comment '编号',student_name         varchar(30)     default ''        comment '学生名称',student_age          int(3)          default null      comment '年龄',student_hobby        varchar(30)     default ''        comment '爱好(0代码 1音乐 2电影)',student_sex          char(1)         default '0'       comment '性别(0男 1女 2未知)',student_status       char(1)         default '0'       comment '状态(0正常 1停用)',student_birthday     datetime                          comment '生日',primary key (student_id)
) engine=innodb auto_increment=1 comment = '学生信息表';

树表结构

新建数据库表结构(树表)

drop table if exists sys_product;
create table sys_product (product_id        bigint(20)      not null auto_increment    comment '产品id',parent_id         bigint(20)      default 0                  comment '父产品id',product_name      varchar(30)     default ''                 comment '产品名称',order_num         int(4)          default 0                  comment '显示顺序',status            char(1)         default '0'                comment '产品状态(0正常 1停用)',primary key (product_id)
) engine=innodb auto_increment=1 comment = '产品表';

主子表结构

新建数据库表结构(主子表)

-- ----------------------------
-- 客户表
-- ----------------------------
drop table if exists sys_customer;
create table sys_customer (customer_id           bigint(20)      not null auto_increment    comment '客户id',customer_name         varchar(30)     default ''                 comment '客户姓名',phonenumber           varchar(11)     default ''                 comment '手机号码',sex                   varchar(20)     default null               comment '客户性别',birthday              datetime                                   comment '客户生日',remark                varchar(500)    default null               comment '客户描述',primary key (customer_id)
) engine=innodb auto_increment=1 comment = '客户表';-- ----------------------------
-- 商品表
-- ----------------------------
drop table if exists sys_goods;
create table sys_goods (goods_id           bigint(20)      not null auto_increment    comment '商品id',customer_id        bigint(20)      not null                   comment '客户id',name               varchar(30)     default ''                 comment '商品名称',weight             int(5)          default null               comment '商品重量',price              decimal(6,2)    default null               comment '商品价格',date               datetime                                   comment '商品时间',type               char(1)         default null               comment '商品种类',primary key (goods_id)
) engine=innodb auto_increment=1 comment = '商品表';

代码生成使用

1、登录系统(系统工具 -> 代码生成 -> 导入对应表)

2、代码生成列表中找到需要表(可预览、编辑、同步、删除生成配置)

3、点击生成代码会得到一个ruoyi.zip执行sql文件,按照包内目录结构复制到自己的项目中即可

代码生成支持编辑、预览、同步

预览:对生成的代码提前预览,防止出现一些不符合预期的情况。

同步:对原表的字段进行同步,包括新增、删除、修改的字段处理。

修改:对生成的代码基本信息、字段信息、生成信息做一系列的调整。

另外多模块所有代码生成的相关业务逻辑代码在ruoyi-generator模块,不需要可以自行删除模块。

系统接口

在现在的开发过程中还有很大一部分公司都是以口口相传的方式来进行前后端的联调,而接口文档很大一部分都只停留在了说说而已的地步,或者写了代码再写文档。 还有一点就是文档的修改,定义好的接口并不是一成不变的,可能在开发过程中文档修改不止一次的变化,这个时候就会很难受了。 只要不是强制性要求,没人会愿意写这东西,而且在写的过程中,一个字母的错误就会导致联调时候的很大麻烦,但是通过Swagger,我们可以省略了这一步,而且文档出错率近乎于零, 只要你在写代码的时候,稍加几个注解,文档自动生成。

1、在控制层Controller中添加注解来描述接口信息如:

@Api("参数配置")
@Controller
@RequestMapping("/system/config")
public class ConfigController

2、在方法中配置接口的标题信息

@ApiOperation("查询参数列表")
@ResponseBody
public TableDataInfo list(Config config)
{startPage();List<Config> list = configService.selectConfigList(config);return getDataTable(list);
}

3、在系统工具-系统接口测试相关接口

注意:SwaggerConfig可以指定根据注解或者包名扫描具体的API

API详细说明

作用范围 API 使用位置
协议集描述 @Api 用于controller类上
对象属性 @ApiModelProperty 用在出入参数对象的字段上
协议描述 @ApiOperation 用在controller的方法上
Response集 @ApiResponses 用在controller的方法上
Response @ApiResponse 用在 @ApiResponses里边
非对象参数集 @ApiImplicitParams 用在controller的方法上
非对象参数描述 @ApiImplicitParam 用在@ApiImplicitParams的方法里边
描述返回对象的意义 @ApiModel 用在返回对象类上

api标记,用在类上,说明该类的作用。可以标记一个Controller类做为Swagger文档资源,使用方式:

@Api(value = "/user", description = "用户管理")

Controller注解并列使用。 属性配置:

属性名称 备注
value url的路径值
tags 如果设置这个值、value的值会被覆盖
description 对api资源的描述
basePath 基本路径可以不配置
position 如果配置多个Api 想改变显示的顺序位置
produces For example, “application/json, application/xml”
consumes For example, “application/json, application/xml”
protocols Possible values: http, https, ws, wss.
authorizations 高级特性认证时配置
hidden 配置为true 将在文档中隐藏

ApiOperation标记,用在方法上,说明方法的作用,每一个url资源的定义,使用方式:

@ApiOperation("获取用户信息")

Controller中的方法并列使用,属性配置:

属性名称 备注
value url的路径值
tags 如果设置这个值、value的值会被覆盖
description 对api资源的描述
basePath 基本路径可以不配置
position 如果配置多个Api 想改变显示的顺序位置
produces For example, “application/json, application/xml”
consumes For example, “application/json, application/xml”
protocols Possible values: http, https, ws, wss.
authorizations 高级特性认证时配置
hidden 配置为true将在文档中隐藏
response 返回的对象
responseContainer 这些对象是有效的 “List”, “Set” or “Map”.,其他无效
httpMethod “GET”, “HEAD”, “POST”, “PUT”, “DELETE”, “OPTIONS” and “PATCH”
code http的状态码 默认 200
extensions 扩展属性

ApiParam标记,请求属性,使用方式:

public TableDataInfo list(@ApiParam(value = "查询用户列表", required = true)User user)

与Controller中的方法并列使用,属性配置:

属性名称 备注
name 属性名称
value 属性值
defaultValue 默认属性值
allowableValues 可以不配置
required 是否属性必填
access 不过多描述
allowMultiple 默认为false
hidden 隐藏该属性
example 举例子

ApiResponse标记,响应配置,使用方式:

@ApiResponse(code = 400, message = "查询用户失败")

Controller中的方法并列使用,属性配置:

属性名称 备注
code http的状态码
message 描述
response 默认响应类 Void
reference 参考ApiOperation中配置
responseHeaders 参考 ResponseHeader 属性配置说明
responseContainer 参考ApiOperation中配置

ApiResponses标记,响应集配置,使用方式:

@ApiResponses({ @ApiResponse(code = 400, message = "无效的用户") })

Controller中的方法并列使用,属性配置:

属性名称 备注
value 多个ApiResponse配置

ResponseHeader标记,响应头设置,使用方法

@ResponseHeader(name="head",description="响应头设计")

Controller中的方法并列使用,属性配置:

属性名称 备注
name 响应头名称
description 描述
response 默认响应类 void
responseContainer 参考ApiOperation中配置

防重复提交

在接口方法上添加@RepeatSubmit注解即可,注解参数说明:

参数 类型 默认值 描述
interval int 5000 间隔时间(ms),小于此时间视为重复提交
message String 不允许重复提交,请稍后再试 提示消息

示例1:采用默认参数

@RepeatSubmit
public AjaxResult addSave(...)
{return success(...);
}

示例2:指定防重复时间和错误消息

@RepeatSubmit(interval = 1000, message = "请求过于频繁")
public AjaxResult addSave(...)
{return success(...);
}

国际化支持

在我们开发WEB项目的时候,项目可能涉及到在国外部署或者应用,也有可能会有国外的用户对项目进行访问,那么在这种项目中, 为客户展现的页面或者操作的信息就需要使用不同的语言,这就是我们所说的项目国际化。 目前项目已经支持多语言国际化,接下来我们介绍如何使用。

后台国际化流程

1、修改I18nConfig设置默认语言,如默认中文

// 默认语言,英文可以设置Locale.US
slr.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);

2、修改配置application.yml中的basename国际化文件,默认是i18n路径下messages文件
(比如现在国际化文件是xx_zh_CN.propertiesxx_en_US.properties,那么basename配置应为是i18n/xx

spring:# 资源信息messages:# 国际化资源文件路径basename: static/i18n/messages

3、i18n目录文件下定义资源文件
美式英语 messages_en_US.properties

user.login.username=User name
user.login.password=Password
user.login.code=Security code
user.login.remember=Remember me
user.login.submit=Sign In

中文简体 messages_zh_CN.properties

user.login.username=用户名
user.login.password=密码
user.login.code=验证码
user.login.remember=记住我
user.login.submit=登录

4、java代码使用MessageUtils获取国际化

MessageUtils.message("user.login.username")
MessageUtils.message("user.login.password")
MessageUtils.message("user.login.code")
MessageUtils.message("user.login.remember")
MessageUtils.message("user.login.submit")

前端国际化流程

1、html使用国际化#{资源文件key}

<form id="signupForm"><h4 class="no-margins">登录:</h4><p class="m-t-md">你若不离不弃,我必生死相依</p><input type="text"     name="username" class="form-control uname"  th:placeholder="#{user.login.username}"   /><input type="password" name="password" class="form-control pword"  th:placeholder="#{user.login.password}"   /><div class="row m-t" th:if="${captchaEnabled==true}"><div class="col-xs-6"><input type="text" name="validateCode" class="form-control code" th:placeholder="#{user.login.code}" maxlength="5" autocomplete="off"></div><div class="col-xs-6"><a href="javascript:void(0);" title="点击更换验证码"><img th:src="@{captcha/captchaImage(type=${captchaType})}" class="imgcode" width="85%"/></a></div></div><div class="checkbox-custom" th:classappend="${captchaEnabled==false} ? 'm-t'"><input type="checkbox" id="rememberme" name="rememberme"> <label for="rememberme" th:text="#{user.login.remember}">记住我</label></div><button class="btn btn-success btn-block" id="btnSubmit" data-loading="正在验证登录,请稍后..." th:text="#{user.login.submit}">登录</button>
</form>

2、js使用国际化 首先在文件引入jquery-i18n-properties依赖,然后在初始化后即可通过JS函数获取对应国际化文件的内容。

<!--jQuery国际化插件-->
<script src="../static/js/jquery.i18n.properties.min.js" th:src="@{/js/jquery.i18n.properties.min.js}"></script><script th:inline="javascript">//获取应用路径var ROOT = [[${#servletContext.contextPath}]];//获取默认语言var LANG_COUNTRY = [[${#locale.language+'_'+#locale.country}]];//初始化i18n插件$.i18n.properties({path: ROOT + '/i18n/',//这里表示访问路径name: 'messages',//文件名开头language: LANG_COUNTRY,//文件名语言 例如en_USmode: 'map'//默认值});//初始化i18n函数function i18n(msgKey) {try {return $.i18n.prop(msgKey);} catch (e) {return msgKey;}}//获取国际化翻译值console.log(i18n('user.login.username'));console.log(i18n('user.login.password'));console.log(i18n('user.login.code'));console.log(i18n('user.login.remember'));console.log(i18n('user.login.submit'));
</script>

3、界面定义切换语言

<a href="?lang=en_US"> 英语 </a>
<a href="?lang=zh_CN"> 中文 </a>

新建子模块

Maven多模块下新建子模块流程案例。

1、新建业务模块目录,例如:ruoyi-test

2、在ruoyi-test业务模块下新建pom.xml文件以及src\main\javasrc\main\resources目录。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>ruoyi</artifactId><groupId>com.ruoyi</groupId><version>x.x.x</version></parent><modelVersion>4.0.0</modelVersion><artifactId>ruoyi-test</artifactId><description>test系统模块</description><dependencies><!-- 通用工具--><dependency><groupId>com.ruoyi</groupId><artifactId>ruoyi-common</artifactId></dependency></dependencies></project>

3、根目录pom.xml依赖声明节点dependencies中添加依赖

<!-- 测试模块-->
<dependency><groupId>com.ruoyi</groupId><artifactId>ruoyi-test</artifactId><version>${ruoyi.version}</version>
</dependency>

4、根目录pom.xml模块节点modules添加业务模块

<module>ruoyi-test</module>

5、ruoyi-admin目录pom.xml添加模块依赖

<!-- 测试模块-->
<dependency><groupId>com.ruoyi</groupId><artifactId>ruoyi-test</artifactId>
</dependency>

6、测试模块

ruoyi-test业务模块添加com.ruoyi.test包,新建TestService.java

public class TestService
{public String helloTest(){return "hello";}
}

ruoyi-admin新建测试类,调用helloTest成功返回hello代表成功。

BMS养殖后台管理系统开发文档相关推荐

  1. Net通用进销存管理系统 + 开发文档+ 使用说明

    通用进销存管理系统 + 开发文档+ 使用说明Net源码下载 包括下面的模块 基础资料模块 采购管理模块 库存管理模块 商务管理模块 营业管理模块 维修管理模块 会员管理模块 财务管理模块 Net通用进 ...

  2. 图书馆管理系统程序+全套开发文档(系统计划书,系统使用说明,测试报告,UML分析与设计,工作记录)

    图书馆管理系统程序+全套开发文档(系统计划书,系统使用说明,测试报告,UML分析与设计,工作记录): https://download.csdn.net/download/qq_39932172/11 ...

  3. Android官方开发文档Training系列课程中文版:目录

    原文地址 : http://android.xsoftlab.net/training/index.html 引言 在翻译了一篇安卓的官方文档之后,我觉得应该做一件事情,就是把安卓的整篇训练课程全部翻 ...

  4. 前端开发规范和开发文档的书写规范

    1. 前端的开发规范 目录构建的规范 命名原则: 简洁 比如: src 源代码 img 图片资源 js JavaScript脚本 dep 第三方依赖包 不使用复数 比如: 不使用 imgs docs ...

  5. M5(项目)-01-尚硅谷谷粒商城项目分布式基础篇开发文档

    M5(项目)-01-尚硅谷谷粒商城项目分布式基础篇开发文档 分布式基础篇 一.环境搭建 各种开发软件的安装 虚拟机: docker,mysql,redis 主机: Maven, idea(后端),Vs ...

  6. 社区疫情管理信息系统开发文档

    此为大三小组课程作业,基于社区疫情的管理信息系统的开发文档,此为记录. 1.系统概述 1.1项目背景 本小组设计的社区疫情管理系统是计算机技术与疫情管理相结合的产物,通过使用社区疫情管理系统,可以实现 ...

  7. 企业门户网站设计开发文档模板

    案卷号 201304 日期 2013/4/1         企业门户网站设计开发文档     作    者:           xx 完成日期:           2013/04/01 签 收 ...

  8. CRMEB开发文档及目录结构

    CRMEB 开发文档及目录结构 官网 CRMEB v2.6开源地址:http://link.crmeb.net/u/lingting 完整帮助文档:http://help.crmeb.net QQ群: ...

  9. 数据库开发文档记录方法

    一.引言 最近因为项目需求变动原因,数据库已经改的面目全非,和自己最初设计的数据库至少查了十多个版本,基本跟推翻了一样.由于缺少原有的记录加上改的比较零碎,所以忘记了原来的表关系,发现将数据库中的表记 ...

最新文章

  1. 原来益生菌是这么搞定致病菌的
  2. c语言编写订货系统,学位论文_基于c语言的仓库订货系统的仿真.doc
  3. ThreadLocal的设计与实现
  4. Word论文写作如何实现公式居中、编号右对齐
  5. delphi查找对话框
  6. 日语学习-多邻国-平假名4
  7. Redis学习总结(17)——Redis 持久化和过期机制复习
  8. 达奇机器人怎么看电量_圣诞怎么过?看看电影吃点儿特别的
  9. 方法论+本土特色,这个BPM平台不简单
  10. 如何将png图像转换成jpg格式呢?
  11. 新浪微博热门话题(字符串处理)
  12. 服装企业在实践中探索
  13. java十进制转二进制(输入一个十进制数将其转换成二进制)
  14. vue.js实现单选框、复选框和下拉框
  15. Windows 10文件浏览器多标签浏览插件
  16. pip国内镜像解决no matching distribution found for XXX
  17. 哨兵2号(sentinel-2)介绍、下载和预处理、批处理
  18. sun.misc.Cleaner实现堆外内存回收
  19. 批处理语言规则--编辑bat文件
  20. 数字中国与未来世界,听IT领袖如何解读

热门文章

  1. 智云通CRM:采购决策有哪些关键节点?
  2. python安装和学习-最后推荐winpython
  3. 如何学习一种开发框架
  4. java中验证码发送_实现短信验证码的发送[JAVA]
  5. render createElement JSX
  6. qt错误:常量中有换行符
  7. 《架构师》被孟岩表扬,心里高兴
  8. 巴菲特的在University of Florida商学院的一次演讲
  9. socket PHP:详细简单的socket TCP通信PHP实现
  10. 访问者模式(Visitor模式)详解