分页实现
- 前端采用基于bootstrap的轻量级表格插件bootstrap-table(opens new window)
- 后端采用基于mybatis的轻量级分页插件pageHelper(opens new window)
提示
前后端分页实现流程
#前端调用实现
| 1 | var options = { | 
自定义查询条件参数(特殊情况提前设置查询条件下使用)
| 1 | var options = { | 
#后台逻辑实现
| 1 | ("/list") | 
- 常见坑点1:selectPostById莫名其妙的分页。例如下面这段代码
| 1 | startPage(); | 
原因分析:这种情况下由于user存在null的情况,就会导致pageHelper生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。 当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
上面这个代码,应该写成下面这个样子才能保证安全。
| 1 | List<User> list; | 
- 常见坑点2:添加了startPage方法。也没有正常分页。例如下面这段代码
| 1 | startPage(); | 
原因分析:只对该语句以后的第一个查询(Select)语句得到的数据进行分页。
上面这个代码,应该写成下面这个样子才能正常分页。
| 1 | Post post = postService.selectPostById(1L); | 
注意
如果改为其他数据库需修改配置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 | 
| celltype | 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 | 设置只能选择不能输入的列内容 | 
| headerBackgroundColor | Enum | IndexedColors.GREY_50_PERCENT | 导出列头背景色IndexedColors.XXXX | 
| headerColor | Enum | IndexedColors.WHITE | 导出列头字体颜色IndexedColors.XXXX | 
| backgroundColor | Enum | IndexedColors.WHITE | 导出单元格背景色IndexedColors.XXXX | 
| color | Enum | IndexedColors.BLACK | 导出单元格字体颜色IndexedColors.XXXX | 
| targetAttr | String | 空 | 另一个类中的属性名称,支持多级获取,以小数点隔开 | 
| isStatistics | boolean | false | 是否自动统计数据,在最后追加一行统计数据总和 | 
| type | Enum | Type.ALL | 字段类型(0:导出导入;1:仅导出;2:仅导入) | 
| align | Enum | HorizontalAlignment.CENTER | 导出对齐方式HorizontalAlignment.XXXX | 
| handler | Class | ExcelHandlerAdapter.class | 自定义数据处理器 | 
| args | String[] | {} | 自定义数据处理器参数 | 
#导出实现流程
1、前端调用封装好的方法$.table.init,传入后台exportUrl
| 1 | var options = { | 
2、添加导出按钮事件
| 1 | <a class="btn btn-warning" onclick="$.table.exportExcel()"> | 
3、在实体变量上添加@Excel注解
| 1 | (name = "用户序号", prompt = "用户编号") | 
4、在Controller添加导出方法
| 1 | ("/export") | 
提示
导出默认流程是先创建一个临时文件,等待前端请求下载结束后马上删除这个临时文件。如遇到迅雷这种二次请求下载应用可能会导致文件已经被删除,我们也可以改成流的形式返回给前端。 参考实现 - 如何解决导出使用下载插件出现异常
#导入实现流程
1、前端调用封装好的方法$.table.init,传入后台importUrl。
| 1 | var options = { | 
2、添加导入按钮事件
| 1 | <a class="btn btn-info" onclick="$.table.importExcel()"> | 
3、添加导入前端代码,form默认id为importForm,也可指定importExcel(id)
| 1 | <!-- 导入区域 --> | 
4、在实体变量上添加@Excel注解,默认为导出导入,也可以单独设置仅导入Type.IMPORT
| 1 | (name = "用户序号") | 
5、在Controller添加导入方法,updateSupport属性为是否存在则覆盖(可选)
| 1 | ("/importData") | 
提示
也可以直接到main运行此方法测试。
| 1 | InputStream is = new FileInputStream(new File("D:\\test.xlsx")); | 
#自定义标题信息
有时候我们希望导出表格包含标题信息,我们可以这样做。
导出用户管理表格新增标题(用户列表)
| 1 | public AjaxResult export(SysUser user) | 
导入表格包含标题处理方式,其中1表示标题占用行数,根据实际情况填写。
| 1 | public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception | 
#自定义数据处理器
有时候我们希望数据展现为一个特殊的格式,或者需要对数据进行其它处理。Excel注解提供了自定义数据处理器以满足各种业务场景。而实现一个数据处理器也是非常简单的。如下:
1、在实体类用Excel注解handler属性指定自定义的数据处理器
| 1 | public class User extends BaseEntity | 
2、编写数据处理器MyDataHandler继承ExcelHandlerAdapter,返回值为处理后的值。
| 1 | public class MyDataHandler implements ExcelHandlerAdapter | 
#自定义隐藏属性列
有时候我们希望对列信息根据业务去动态显示,那么我们可以进行如下处理。
示例:对用户进行条件判断,符合条件则隐藏属性。导出的文件则不会显示此列信息。
| 1 | ("/export") | 
#导出对象的子列表
有时候对象里面还包含集合列表,例如用户管理包含多个角色需要导出,那么我们可以进行如下处理。
SysUser.java
| 1 | public class SysUser | 
SysRole.java
| 1 | public class SysRole | 
测试验证
| 1 | public class Test | 
导出文件结果 
#上传下载
首先创建一张上传文件的表,例如:
| 1 | drop table if exists sys_file_info; | 
#上传实现流程
1、代码生成sys_file_info表相关代码并复制到对应目录。
2、参考示例修改代码。
| 1 | <input id="filePath" name="filePath" class="form-control" type="file"> | 
| 1 | function submitHandler() { | 
3、在SysFileInfoController添加对应上传方法
| 1 | ("/add") | 
4、上传成功后需要预览可以对该属性格式化处理
| 1 | { | 
如需对文件格式控制,设置application.yml中的multipart属性
| 1 | # 文件上传 | 
注意:如果只是单纯的上传一张图片没有其他参数可以使用通用方法 /common/upload
请求处理方法 com.ruoyi.web.controller.common.CommonController
#下载实现流程
1、参考示例代码。
| 1 | function downloadFile(value){ | 
2、参考Controller下载方法
| 1 | /** | 
权限注解
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角色才可访问
| 1 | ("admin") | 
示例2: 以下代码表示必须拥有admin和common角色才可访问
| 1 | ({"admin", "common"}) | 
示例3: 以下代码表示需要拥有admin或common角色才可访问
| 1 | (value = {"admin", "common"}, logical = Logical.OR) | 
#@RequiresPermissions
@RequiresPermissions注解用于配置接口要求用户拥有某(些)权限才可访问,它拥有两个参数
| 参数 | 类型 | 描述 | 
|---|---|---|
| value | String[] | 权限列表 | 
| logical | Logical | 权限之间的判断关系,默认为Logical.AND | 
示例1: 以下代码表示必须拥有system:user:add权限才可访问
| 1 | ("system:user:add") | 
示例2: 以下代码表示必须拥有system:user:add和system:user:edit权限才可访问
| 1 | ({"system:user:add", "system:user:edit"}) | 
示例3: 以下代码表示需要拥有system:user:add或system:user:edit权限才可访问
| 1 | (value = {"system:user:add", "system:user:edit"}, logical = Logical.OR) | 
提示
Shiro的认证注解处理是有内定的处理顺序的,如果有个多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关) RequiresRoles、RequiresPermissions、RequiresAuthentication、RequiresUser、RequiresGuest。
例如:你同时声明了RequiresRoles和RequiresPermissions,那就要求拥有此角色的同时还得拥有相应的权限。
#编程式判断资源访问权限
示例: 编程式判断资源访问权限
| 1 | Subject subject = ShiroUtils.getSubject(); | 
示例: 编程式判断角色访问权限
| 1 | Subject subject = ShiroUtils.getSubject(); | 
#基于URI判断用户访问权限
示例: 基于URI判断用户访问权限
| 1 | // 验证是否有资源权限 | 
示例: 基于URI判断角色访问权限
| 1 | // 验证是否有角色权限 | 
URI通配符
| 1 | ? :匹配一个字符。 | 
事务管理
新建的Spring Boot项目中,一般都会引用spring-boot-starter或者spring-boot-starter-web,而这两个起步依赖中都已经包含了对于spring-boot-starter-jdbc或spring-boot-starter-data-jpa的依赖。 当我们使用了这两个依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。 所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。
提示
@Transactional注解只能应用到public可见度的方法上,可以被应用于接口定义和接口方法,方法会覆盖类上面声明的事务。
例如用户新增需要插入用户表、用户与岗位关联表、用户与角色关联表,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作, 这样可以防止出现脏数据,就可以使用事务让它实现回退。
做法非常简单,我们只需要在方法或类添加@Transactional注解即可。
| 1 | 
 | 
- 常见坑点1:遇到检查异常时,事务开启,也无法回滚。 例如下面这段代码,用户依旧增加成功,并没有因为后面遇到检查异常而回滚!!
| 1 | 
 | 
原因分析:因为Spring的默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。如果想针对检查异常进行事务回滚,可以在@Transactional注解里使用 rollbackFor属性明确指定异常。
例如下面这样,就可以正常回滚:
| 1 | (rollbackFor = Exception.class) | 
- 常见坑点2: 在业务层捕捉异常后,发现事务不生效。 这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。
 例如:下面这段代码直接导致用户新增的事务回滚没有生效。
| 1 | 
 | 
推荐做法:在业务层统一抛出异常,然后在控制层统一处理。
| 1 | 
 | 
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、统一返回实体定义
| 1 | package com.ruoyi.common.core.domain; | 
2、定义登录异常定义
| 1 | package com.ruoyi.common.exception; | 
3、基于@ControllerAdvice注解的Controller层的全局异常统一处理
| 1 | package com.ruoyi.framework.web.exception; | 
4、测试访问请求
| 1 | 
 | 
根据上面代码含义,当我们未登录访问/index时就会发生LoginException业务逻辑异常,按照我们之前的全局异常配置以及统一返回实体实例化,访问后会出现AjaxResult格式JSON数据, 下面我们运行项目访问查看效果。
界面输出内容如下所示:
| 1 | { | 
对于一些特殊情况,如接口需要返回json,页面请求返回html可以使用如下方法:
| 1 | (LoginException.class) | 
若依系统的全局异常处理器GlobalExceptionHandler
注意:如果全部异常处理返回json,那么可以使用@RestControllerAdvice代替@ControllerAdvice,这样在方法上就可以不需要添加@ResponseBody。
无法捕获异常?
如果您的异常无法捕获,您可以从以下几个方面着手检查
异常是否已被处理,即抛出异常后被catch,打印了日志或抛出了其它异常 异常是否非Controller抛出,即在拦截器或过滤器中出现的异常
#参数验证
spring boot中可以用@Validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。
#注解参数说明
| 注解名称 | 功能 | 
|---|---|
| @Xss | 检查该字段是否存在跨站脚本工具 | 
| @Null | 检查该字段为空 | 
| @NotNull | 不能为null | 
| @NotBlank | 不能为空,常用于检查空字符串 | 
| @NotEmpty | 不能为空,多用于检测list是否size是0 | 
| @Max | 该字段的值只能小于或等于该值 | 
| @Min | 该字段的值只能大于或等于该值 | 
| @Past | 检查该字段的日期是在过去 | 
| @Future | 检查该字段的日期是否是属于将来的日期 | 
| 检查是否是一个有效的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需要对数据进行校验。
| 1 | public AjaxResult add(@Validated @RequestBody SysUser user) | 
2、然后在对应字段Get方法加上参数校验注解,如果不符合验证要求,则会以message的信息为准,返回给前端。
| 1 | (min = 0, max = 30, message = "用户昵称长度不能超过30个字符") | 
也可以直接放在字段上面声明。
| 1 | (min = 0, max = 30, message = "用户昵称长度不能超过30个字符") | 
自定义注解校验
使用原生的@Validated进行参数校验时,都是特定的注解去校验(例如字段长度、大小、不为空等),我们也可以用自定义的注解去进行校验,例如项目中的@Xss注解。
1、新增Xss注解,设置自定义校验器XssValidator.class
| 1 | /** | 
2、自定义Xss校验器,实现ConstraintValidator接口。
| 1 | /** | 
3、实体类使用自定义的@Xss注解
| 1 | (message = "登录账号不能包含脚本字符") | 
此时在去保存会进行验证,如果不符合规则的字符(例如<script>alert(1);</script>)会提示登录账号不能包含脚本字符,代表限制成功。
如果是在方法里面校验整个实体,参考示例。
| 1 | 
 | 
#自定义分组校验
有时候我们为了在使用实体类的情况下更好的区分出新增、修改和其他操作验证的不同,可以通过groups属性设置。使用方式如下
新增类接口,用于标识出不同的操作类型
| 1 | public interface Add | 
Controller.java
| 1 | // 新增 | 
Model.java
| 1 | // 仅在新增时验证 | 
提示
如果你有更多操作类型,也可以自定义类统一管理,使用方式就变成了Type.Add、Type.Edit、Type.Xxxx等。
| 1 | package com.eva.core.constants; | 
#系统日志
在实际开发中,对于某些关键业务,我们通常需要记录该操作的内容,一个操作调一次记录方法,每次还得去收集参数等等,会造成大量代码重复。 我们希望代码中只有业务相关的操作,在项目中使用注解来完成此项功能。
在需要被记录日志的controller方法上添加@Log注解,使用方法如下:
| 1 | (title = "用户管理", businessType = BusinessType.INSERT) | 
#注解参数说明
| 参数 | 类型 | 默认值 | 描述 | 
|---|---|---|---|
| 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 | 是否保存响应的参数 | 
| excludeParamNames | String[] | {} | 排除指定的请求参数 | 
#自定义操作功能
1、在BusinessType中新增业务操作类型如:
| 1 | /** | 
2、在sys_dict_data字典数据表中初始化操作业务类型
| 1 | 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中使用注解
| 1 | (title = "测试标题", businessType = BusinessType.TEST) | 
操作日志记录逻辑实现代码LogAspect.java(opens new window)
登录系统(系统管理-操作日志)可以查询操作日志列表和详细信息。
#数据权限
在实际开发中,需要设置用户只能查看哪些部门的数据,这种情况一般称为数据权限。
例如对于销售,财务的数据,它们是非常敏感的,因此要求对数据权限进行控制, 对于基于集团性的应用系统而言,就更多需要控制好各自公司的数据了。如设置只能看本公司、或者本部门的数据,对于特殊的领导,可能需要跨部门的数据, 因此程序不能硬编码那个领导该访问哪些数据,需要进行后台的权限和数据权限的控制。
提示
默认系统管理员admin拥有所有数据权限(userId=1),默认角色拥有所有数据权限(如不需要数据权限不用设置数据权限操作)
#注解参数说明
| 参数 | 类型 | 默认值 | 描述 | 
|---|---|---|---|
| deptAlias | String | 空 | 部门表的别名 | 
| userAlias | String | 空 | 用户表的别名 | 
#数据权限使用
1、在(系统管理-角色管理)设置需要数据权限的角色 目前支持以下几种权限
- 全部数据权限
- 自定数据权限
- 部门数据权限
- 部门及以下数据权限
- 仅本人数据权限
2、在需要数据权限控制方法上添加@DataScope注解,其中d和u用来表示表的别名
部门数据权限注解
| 1 | (deptAlias = "d") | 
部门及用户权限注解
| 1 | (deptAlias = "d", userAlias = "u") | 
3、在mybatis查询底部标签添加数据范围过滤
| 1 | <select id="select" parameterType="..." resultMap="...Result"> | 
例如:用户管理(未过滤数据权限的情况):
| 1 | select u.user_id, u.dept_id, u.login_name, u.user_name, u.email | 
例如:用户管理(已过滤数据权限的情况):
| 1 | select u.user_id, u.dept_id, u.login_name, u.user_name, u.email | 
结果很明显,我们多了如下语句。通过角色部门表(sys_role_dept)完成了数据权限过滤
| 1 | and u.dept_id in ( | 
逻辑实现代码 com.ruoyi.framework.aspectj.DataScopeAspect
提示
仅实体继承BaseEntity才会进行处理,SQL语句会存放到BaseEntity对象中的params属性中,然后在xml中通过${params.dataScope}获取拼接后的语句。
#多数据源
在实际开发中,经常可能遇到在一个应用中可能需要访问多个数据库的情况,在项目中使用注解来完成此项功能。
在需要被切换数据源的Service或Mapper方法上添加@DataSource注解,使用方法如下:
| 1 | (value = DataSourceType.MASTER) | 
其中value用来表示数据源名称,除MASTER和SLAVE其他均需要进行配置。
#注解参数说明
| 参数 | 类型 | 默认值 | 描述 | 
|---|---|---|---|
| value | DataSourceType | DataSourceType.MASTER | 主库 | 
#多数据源使用
1、在application-druid.yml配置从库数据源
| 1 | # 从库数据源 | 
2、在DataSourceType类添加数据源枚举
| 1 | /** | 
3、在DruidConfig配置读取数据源
| 1 | 
 | 
4、在DruidConfig类dataSource方法添加数据源
| 1 | setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); | 
5、在需要使用多数据源方法或类上添加@DataSource注解,其中value用来表示数据源
| 1 | (value = DataSourceType.SLAVE) | 
| 1 | 
 | 
#手动切换数据源
在需要切换数据源的方法中使用DynamicDataSourceContextHolder类实现手动切换,使用方法如下:
| 1 | public List<SysUser> selectUserList(SysUser user) | 
逻辑实现代码 com.ruoyi.framework.aspectj.DataSourceAspect
| 1 | 注意:目前配置了一个从库,默认关闭状态。如果不需要多数据源不用做任何配置。 另外可新增多个从库。支持不同数据源(Mysql、Oracle、SQLServer) | 
提示
如果有Service方法内多个注解无效的情况使用内部方法调用SpringUtils.getAopProxy(this).xxxxxx(xxxx);
#代码生成
大部分项目里其实有很多代码都是重复的,几乎每个基础模块的代码都有增删改查的功能,而这些功能都是大同小异, 如果这些功能都要自己去写,将会大大浪费我们的精力降低效率。所以这种重复性的代码可以使用代码生成。
#默认配置
单应用在resources目录下的application.yml,多模块ruoyi-generator中的resources目录下的generator.yml,可以自己根据实际情况调整默认配置。
| 1 | # 代码生成 | 
#单表结构
新建数据库表结构(单表)
| 1 | drop table if exists sys_student; | 
#树表结构
新建数据库表结构(树表)
| 1 | drop table if exists sys_product; | 
#主子表结构
新建数据库表结构(主子表)
| 1 | -- ---------------------------- | 
#代码生成使用
1、登录系统(系统工具 -> 代码生成 -> 导入对应表)
2、代码生成列表中找到需要表(可预览、编辑、同步、删除生成配置)
3、点击生成代码会得到一个ruoyi.zip执行sql文件,按照包内目录结构复制到自己的项目中即可
代码生成支持编辑、预览、同步
预览:对生成的代码提前预览,防止出现一些不符合预期的情况。
同步:对原表的字段进行同步,包括新增、删除、修改的字段处理。
修改:对生成的代码基本信息、字段信息、生成信息做一系列的调整。
另外多模块所有代码生成的相关业务逻辑代码在ruoyi-generator模块,不需要可以自行删除模块。
#定时任务
在实际项目开发中Web应用有一类不可缺少的,那就是定时任务。 定时任务的场景可以说非常广泛,比如某些视频网站,购买会员后,每天会给会员送成长值,每月会给会员送一些电影券; 比如在保证最终一致性的场景中,往往利用定时任务调度进行一些比对工作;比如一些定时需要生成的报表、邮件;比如一些需要定时清理数据的任务等。 所以我们提供方便友好的web界面,实现动态管理任务,可以达到动态控制定时任务启动、暂停、重启、删除、添加、修改等操作,极大地方便了开发过程。
提示
关于定时任务使用流程
1、后台添加定时任务处理类(支持Bean调用、Class类调用)Bean调用示例:需要添加对应Bean注解@Component或@Service。调用目标字符串:ryTask.ryParams('ry')Class类调用示例:添加类和方法指定包即可。调用目标字符串:com.ruoyi.quartz.task.RyTask.ryParams('ry')
| 1 | /** | 
2、前端新建定时任务信息(系统监控 -> 定时任务)
任务名称:自定义,如:定时查询任务状态
任务分组:根据字典sys_job_group配置
调用目标字符串:设置后台任务方法名称参数
执行表达式:可查询官方cron表达式介绍
执行策略:定时任务自定义执行策略
并发执行:是否需要多个任务间同时执行
状态:是否启动定时任务
备注:定时任务描述信息
3、点击执行一次,测试定时任务是否正常及调度日志是否正确记录,如正常执行表示任务配置成功。
执行策略详解:立即执行(所有misfire的任务会马上执行)打个比方,如果9点misfire了,在10:15系统恢复之后,9点,10点的misfire会马上执行执行一次(会合并部分的misfire,正常执行下一个周期的任务)假设9,10的任务都misfire了,系统在10:15分起来了。只会执行一次misfire,下次正点执行。放弃执行(所有的misfire不管,执行下一个周期的任务)
方法参数详解:字符串(需要单引号’’标识 如:ryTask.ryParams(’ry’))布尔类型(需要true false标识 如:ryTask.ryParams(true))长整型(需要L标识 如:ryTask.ryParams(2000L))浮点型(需要D标识 如:ryTask.ryParams(316.50D))整型(纯数字即可)
cron表达式语法:
[秒] [分] [小时] [日] [月] [周] [年]
| 说明 | 必填 | 允许填写的值 | 允许的通配符 | 
|---|---|---|---|
| 秒 | 是 | 0-59 | , - * / | 
| 分 | 是 | 0-59 | , - * / | 
| 时 | 是 | 0-23 | , - * / | 
| 日 | 是 | 1-31 | , - * / | 
| 月 | 是 | 1-12 / JAN-DEC | , - * ? / L W | 
| 周 | 是 | 1-7 or SUN-SAT | , - * ? / L # | 
| 年 | 是 | 1970-2099 | , - * / | 
通配符说明:* 表示所有值。 例如:在分的字段上设置 ,表示每一分钟都会触发? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为”?” 具体设置为 0 0 0 10  ?- 表示区间。例如 在小时上设置 “10-12”,表示 10,11,12点都会触发, 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发/ 用于递增触发。如在秒上面设置”5/15” 表示从5秒开始,每增15秒触发(5,20,35,50)。 在月字段上设置’1/3’所示每月1号开始,每隔三天触发一次L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于”7”或”SAT”。如果在”L”前加上数字,则表示该数据的最后一个。例如在周字段上设置”6L”这样的格式,则表示“本月最后一个星期五”W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上置”15W”,表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,”W”前只能设置具体的数字,不允许区间”-“)# 序号(表示每月的第几个周几),例如在周字段上设置”6#3”表示在每月的第三个周六.注意如果指定”#5”,正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了) ;小提示:’L’和 ‘W’可以一组合使用。如果在日字段上设置”LW”,则表示在本月的最后一个工作日触发;周字段的设置,若使用英文字母是不区分大小写的,即MON与mon相同
常用表达式例子:
| 表达式 | 说明 | 
|---|---|
| 0 0 2 1 ? | 表示在每月的1日的凌晨2点调整任务 | 
| 0 15 10 ? * MON-FRI | 表示周一到周五每天上午10:15执行作业 | 
| 0 15 10 ? 6L 2002-2006 | 表示2002-2006年的每个月的最后一个星期五上午10:15执行作 | 
| 0 0 10,14,16 ? | 每天上午10点,下午2点,4点 | 
| 0 0/30 9-17 ? | 朝九晚五工作时间内每半小时 | 
| 0 0 12 ? * WED | 表示每个星期三中午12点 | 
| 0 0 12 ? | 每天中午12点触发 | 
| 0 15 10 ? | 每天上午10:15触发 | 
| 0 15 10 ? | 每天上午10:15触发 | 
| 0 15 10 ? * | 每天上午10:15触发 | 
| 0 15 10 ? 2005 | 2005年的每天上午10:15触发 | 
| 0 14 * ? | 在每天下午2点到下午2:59期间的每1分钟触发 | 
| 0 0/5 14 ? | 在每天下午2点到下午2:55期间的每5分钟触发 | 
| 0 0/5 14,18 ? | 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 | 
| 0 0-5 14 ? | 在每天下午2点到下午2:05期间的每1分钟触发 | 
| 0 10,44 14 ? 3 WED | 每年三月的星期三的下午2:10和2:44触发 | 
| 0 15 10 ? * MON-FRI | 周一至周五的上午10:15触发 | 
| 0 15 10 15 * ? | 每月15日上午10:15触发 | 
| 0 15 10 L * ? | 每月最后一日的上午10:15触发 | 
| 0 15 10 ? * 6L | 每月的最后一个星期五上午10:15触发 | 
| 0 15 10 ? * 6L 2002-2005 | 2002年至2005年的每月的最后一个星期五上午10:15触发 | 
| 0 15 10 ? * 6#3 | 每月的第三个星期五上午10:15触发 | 
多模块所有定时任务的相关业务逻辑代码在ruoyi-quartz模块,可以自行调整或剔除
| 1 | 注意:不同数据源定时任务都有对应脚本,Oracle、Mysql已经有了,其他的可自行下载执行 | 
#系统接口
在现在的开发过程中还有很大一部分公司都是以口口相传的方式来进行前后端的联调,而接口文档很大一部分都只停留在了说说而已的地步,或者写了代码再写文档。 还有一点就是文档的修改,定义好的接口并不是一成不变的,可能在开发过程中文档修改不止一次的变化,这个时候就会很难受了。 只要不是强制性要求,没人会愿意写这东西,而且在写的过程中,一个字母的错误就会导致联调时候的很大麻烦,但是通过Swagger,我们可以省略了这一步,而且文档出错率近乎于零, 只要你在写代码的时候,稍加几个注解,文档自动生成。
1、在控制层Controller中添加注解来描述接口信息如:
| 1 | ("参数配置") | 
2、在方法中配置接口的标题信息
| 1 | @ApiOperation("查询参数列表") | 
3、在系统工具-系统接口测试相关接口
| 1 | 注意:SwaggerConfig可以指定根据注解或者包名扫描具体的API | 
API详细说明
| 作用范围 | API | 使用位置 | 
|---|---|---|
| 协议集描述 | @Api | 用于controller类上 | 
| 对象属性 | @ApiModelProperty | 用在出入参数对象的字段上 | 
| 协议描述 | @ApiOperation | 用在controller的方法上 | 
| Response集 | @ApiResponses | 用在controller的方法上 | 
| Response | @ApiResponse | 用在 @ApiResponses里边 | 
| 非对象参数集 | @ApiImplicitParams | 用在controller的方法上 | 
| 非对象参数描述 | @ApiImplicitParam | 用在@ApiImplicitParams的方法里边 | 
| 描述返回对象的意义 | @ApiModel | 用在返回对象类上 | 
api标记,用在类上,说明该类的作用。可以标记一个Controller类做为Swagger文档资源,使用方式:
| 1 | (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资源的定义,使用方式:
| 1 | ("获取用户信息") | 
与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标记,请求属性,使用方式:
| 1 | public TableDataInfo list(@ApiParam(value = "查询用户列表", required = true)User user) | 
与Controller中的方法并列使用,属性配置:
| 属性名称 | 备注 | 
|---|---|
| name | 属性名称 | 
| value | 属性值 | 
| defaultValue | 默认属性值 | 
| allowableValues | 可以不配置 | 
| required | 是否属性必填 | 
| access | 不过多描述 | 
| allowMultiple | 默认为false | 
| hidden | 隐藏该属性 | 
| example | 举例子 | 
ApiResponse标记,响应配置,使用方式:
| 1 | (code = 400, message = "查询用户失败") | 
与Controller中的方法并列使用,属性配置:
| 属性名称 | 备注 | 
|---|---|
| code | http的状态码 | 
| message | 描述 | 
| response | 默认响应类 Void | 
| reference | 参考ApiOperation中配置 | 
| responseHeaders | 参考 ResponseHeader 属性配置说明 | 
| responseContainer | 参考ApiOperation中配置 | 
ApiResponses标记,响应集配置,使用方式:
| 1 | ({ (code = 400, message = "无效的用户") }) | 
与Controller中的方法并列使用,属性配置:
| 属性名称 | 备注 | 
|---|---|
| value | 多个ApiResponse配置 | 
ResponseHeader标记,响应头设置,使用方法
| 1 | (name="head",description="响应头设计") | 
与Controller中的方法并列使用,属性配置:
| 属性名称 | 备注 | 
|---|---|
| name | 响应头名称 | 
| description | 描述 | 
| response | 默认响应类 void | 
| responseContainer | 参考ApiOperation中配置 | 
#防重复提交
在接口方法上添加@RepeatSubmit注解即可,注解参数说明:
| 参数 | 类型 | 默认值 | 描述 | 
|---|---|---|---|
| interval | int | 5000 | 间隔时间(ms),小于此时间视为重复提交 | 
| message | String | 不允许重复提交,请稍后再试 | 提示消息 | 
示例1:采用默认参数
| 1 | 
 | 
示例2:指定防重复时间和错误消息
| 1 | (interval = 1000, message = "请求过于频繁") | 
#国际化支持
在我们开发WEB项目的时候,项目可能涉及到在国外部署或者应用,也有可能会有国外的用户对项目进行访问,那么在这种项目中, 为客户展现的页面或者操作的信息就需要使用不同的语言,这就是我们所说的项目国际化。 目前项目已经支持多语言国际化,接下来我们介绍如何使用。
#后台国际化流程
1、修改I18nConfig设置默认语言,如默认中文:
| 1 | // 默认语言,英文可以设置Locale.US | 
2、修改配置application.yml中的basename国际化文件,默认是i18n路径下messages文件
(比如现在国际化文件是xx_zh_CN.properties、xx_en_US.properties,那么basename配置应为是i18n/xx
| 1 | spring: | 
3、i18n目录文件下定义资源文件
美式英语 messages_en_US.properties
| 1 | user.login.username=User name | 
中文简体 messages_zh_CN.properties
| 1 | user.login.username=用户名 | 
4、java代码使用MessageUtils获取国际化
| 1 | MessageUtils.message("user.login.username") | 
#前端国际化流程
1、html使用国际化#{资源文件key}
| 1 | <form id="signupForm"> | 
2、js使用国际化 首先在文件引入jquery-i18n-properties依赖,然后在初始化后即可通过JS函数获取对应国际化文件的内容。
| 1 | <!--jQuery国际化插件--> | 
3、界面定义切换语言
| 1 | <a href="?lang=en_US"> 英语 </a> | 
#新建子模块
Maven多模块下新建子模块流程案例。
1、新建业务模块目录,例如:ruoyi-test。
2、在ruoyi-test业务模块下新建pom.xml文件以及src\main\java,src\main\resources目录。
| 1 | 
 | 
3、根目录pom.xml依赖声明节点dependencies中添加依赖
| 1 | <!-- 测试模块--> | 
4、根目录pom.xml模块节点modules添加业务模块
| 1 | <module>ruoyi-test</module> | 
5、ruoyi-admin目录pom.xml添加模块依赖
| 1 | <!-- 测试模块--> | 
6、测试模块
在ruoyi-test业务模块添加com.ruoyi.test包,新建TestService.java
| 1 | public class TestService | 
在ruoyi-admin新建测试类,调用helloTest成功返回hello代表成功。
 
        