后台手册

2019-03-30 14:36 更新

分页实现

前端基于Bootstrap的轻量级表格插件 Bootstrap Table
后端分页组件使用Mybatis分页插件 PageHelper

分页实现流程

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

  1. var options = {
  2. url: prefix + "/list",
  3. columns: [{
  4. field: 'id',
  5. title: '主键'
  6. },
  7. {
  8. field: 'name',
  9. title: '名称'
  10. }]
  11. };
  12. $.table.init(options);

2、后台实现查询逻辑,调用startPage()方法即可自动完成服务端分页。

  1. @PostMapping("/list")
  2. @ResponseBody
  3. public TableDataInfo list(User user)
  4. {
  5. startPage(); // 此方法配合前端完成自动分页
  6. List<User> list = userService.selectUserList(user);
  7. return getDataTable(list);
  8. }

注意:启动分页关键代码startPage()(只对该语句以后的第一个查询语句得到的数据进行分页)
如果改为其他数据库需修改配置application.yml helperDialect=你的数据库

导入导出

导入导出使用 Apache POI,目前支持参数如下

参数 类型 默认值 描述
name String 导出到Excel中的名字
dateFormat String 日期格式, 如: yyyy-MM-dd
readConverterExp String 读取内容转表达式 (如: 0=男,1=女,2=未知)
height String 14 导出时在excel中每个列的高度 单位为字符
width String 16 导出时在excel中每个列的宽 单位为字符
suffix String 文字后缀,如% 90 变成90%
defaultValue String 当值为空时,字段的默认值
prompt String 提示信息
combo String Null 设置只能选择不能输入的列内容
isExport String true 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写
targetAttr String 另一个类中的属性名称,支持多级获取,以小数点隔开
type Enum Type.ALL 字段类型(0:导出导入;1:仅导出;2:仅导入)

导出实现流程

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

  1. var options = {
  2. exportUrl: prefix + "/export",
  3. columns: [{
  4. field: 'id',
  5. title: '主键'
  6. },
  7. {
  8. field: 'name',
  9. title: '名称'
  10. }]
  11. };
  12. $.table.init(options);

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

  1. @Excel(name = "用户序号")
  2. private Long id;
  3. @Excel(name = "用户名称")
  4. private String userName;

3、在Controller添加导出方法

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

导入实现流程

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

  1. var options = {
  2. importUrl: prefix + "/importData",
  3. columns: [{
  4. field: 'id',
  5. title: '主键'
  6. },
  7. {
  8. field: 'name',
  9. title: '名称'
  10. }]
  11. };
  12. $.table.init(options);

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

  1. @Excel(name = "用户序号")
  2. private Long id;
  3. @Excel(name = "部门编号", type = Type.IMPORT)
  4. private Long deptId;
  5. @Excel(name = "用户名称")
  6. private String userName;

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

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

上传下载

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

  1. drop table if exists sys_file;
  2. create table sys_file (
  3. fileid int(11) not null auto_increment comment '文件id',
  4. filename varchar(50) default '' comment '文件名称',
  5. filepath varchar(255) default '' comment '文件路径',
  6. primary key (fileid)
  7. ) engine=innodb auto_increment=200 default charset=utf8 comment = '文件表';

上传实现流程

1、参考示例代码。

  1. function submitHandler() {
  2. if ($.validate.form()) {
  3. uploadFile();
  4. }
  5. }
  6. function uploadFile() {
  7. var formData = new FormData();
  8. if($('#file')[0].files[0] == null) {
  9. $.modal.alertWarning("请先选择文件路径");
  10. return false;
  11. }
  12. formData.append('fileName', $("#fileName").val());
  13. formData.append('file', $('#file')[0].files[0]);
  14. $.ajax({
  15. url: prefix + "/add",
  16. type: 'post',
  17. cache: false,
  18. data: formData,
  19. processData: false,
  20. contentType: false,
  21. dataType: "json",
  22. success: function(result) {
  23. $.operate.successCallback(result);
  24. }
  25. });
  26. }

2、在Controller添加对应上传方法

  1. @Autowired
  2. private ServerConfig serverConfig;
  3. @PostMapping("/add")
  4. @ResponseBody
  5. public AjaxResult addSave(MultipartFile file, SysFile sysFile) throws IOException
  6. {
  7. // 上传文件路径
  8. String filePath = Global.getUploadPath();
  9. // 上传并返回新文件名称
  10. String fileName = FileUploadUtils.upload(filePath, file);
  11. sysFile.setFilePath(fileName);
  12. return toAjax(sysFileService.insertSysFile(sysFile));
  13. }

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

  1. {
  2. title: '文件预览',
  3. formatter: function(value, row, index) {
  4. return '<a href="javascript:downloadFile(' + row.fileId + ')"><img style="width:30;height:30px;" src="/profile/upload/' + row.filePath + '"/></a>';
  5. }
  6. },

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

下载实现流程

1、参考示例代码。

  1. function downloadFile(fileId){
  2. window.location.href = ctx + "system/sysFile/downloadFile/" + fileId;
  3. }

2、在Controller添加对应上传方法

  1. @GetMapping("/downloadFile/{fileId}")
  2. public void downloadFile(@PathVariable("fileId") Integer fileId, HttpServletResponse response) throws Exception
  3. {
  4. SysFile sysFile = sysFileService.selectSysFileById(fileId);
  5. String filePath = sysFile.getFilePath();
  6. String realFileName = sysFile.getFileName() + filePath.substring(filePath.indexOf("."));
  7. String path = Global.getUploadPath() + sysFile.getFilePath();
  8. response.setCharacterEncoding("utf-8");
  9. response.setContentType("multipart/form-data");
  10. response.setHeader("Content-Disposition", "attachment;fileName=" + realFileName);
  11. FileUtils.writeBytes(path, response.getOutputStream());
  12. }

事务管理

在Spring Boot中,当我们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。 所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。

例如:新增用户时需要插入用户表、用户与岗位关联表、用户与角色关联表。就可以使用事务让它实现回退。
做法非常简单,我们只需要在方法上添加@Transactional注解即可。事务可以用于ServiceController

  1. @Transactional
  2. public int insertUser(User user)
  3. {
  4. // 新增用户信息
  5. int rows = userMapper.insertUser(user);
  6. // 新增用户岗位关联
  7. insertUserPost(user);
  8. // 新增用户与角色管理
  9. insertUserRole(user);
  10. return rows;
  11. }

常见坑点1:遇到非检测异常时,事务开启,也无法回滚。 例如下面这段代码,账户余额依旧增加成功,并没有因为后面遇到检测异常而回滚!!

  1. @Transactional
  2. public void addMoney() throws Exception {
  3. //先增加余额
  4. accountMapper.addMoney();
  5. //然后遇到故障
  6. throw new SQLException("发生异常了..");
  7. }

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

  1. @Transactional(rollbackFor = Exception.class)
  2. public void addMoney() throws Exception {
  3. //先增加余额
  4. accountMapper.addMoney();
  5. //然后遇到故障
  6. throw new SQLException("发生异常了..");
  7. }

常见坑点2: 在业务层捕捉异常后,发现事务不生效。 这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。 例如:下面这段代码直接导致增加余额的事务回滚没有生效。

  1. @Transactional
  2. public void addMoney() throws Exception {
  3. //先增加余额
  4. accountMapper.addMoney();
  5. //谨慎:尽量不要在业务层捕捉异常并处理
  6. try {
  7. throw new SQLException("发生异常了..");
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }
  11. }

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

  1. @Transactional
  2. public void addMoney() throws Exception {
  3. //先增加余额
  4. accountMapper.addMoney();
  5. //推荐:在业务层将异常抛出
  6. throw new RuntimeException("发生异常了..");
  7. }

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,……}
....

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。

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

异常处理

在日常开发中程序发生了异常,往往需要通过一个统一的异常处理,来保证客户端能够收到友好的提示。
通常情况下我们用try..catch..对异常进行捕捉处理,但是在实际项目中对业务模块进行异常捕捉,会造成代码重复和繁杂, 我们希望代码中只有业务相关的操作,所有的异常我们单独设立一个类来处理它。全局异常就是对框架所有异常进行统一管理 而这就表示在框架需要一个机制,将程序的异常转换为用户可读的异常。而且最重要的,是要将这个机制统一,提供统一的异常处理。 我们在可能发生异常的方法,全部throw抛给前端控制器;然后由前端控制器调用 全局异常处理器 对异常进行统一处理。 如此,我们现在的Controller中的方法就可以很简洁了。

1、统一返回实体定义

  1. package com.ruoyi.common.core.domain;
  2. import java.util.HashMap;
  3. /**
  4. * 操作消息提醒
  5. *
  6. * @author ruoyi
  7. */
  8. public class AjaxResult extends HashMap<String, Object>
  9. {
  10. private static final long serialVersionUID = 1L;
  11. /**
  12. * 返回错误消息
  13. *
  14. * @param code 错误码
  15. * @param msg 内容
  16. * @return 错误消息
  17. */
  18. public static AjaxResult error(String msg)
  19. {
  20. AjaxResult json = new AjaxResult();
  21. json.put("msg", msg);
  22. json.put("code", 500);
  23. return json;
  24. }
  25. /**
  26. * 返回成功消息
  27. *
  28. * @param msg 内容
  29. * @return 成功消息
  30. */
  31. public static AjaxResult success(String msg)
  32. {
  33. AjaxResult json = new AjaxResult();
  34. json.put("msg", msg);
  35. json.put("code", 0);
  36. return json;
  37. }
  38. }

2、定义登录异常定义

  1. package com.ruoyi.common.exception;
  2. /**
  3. * 登录异常
  4. *
  5. * @author ruoyi
  6. */
  7. public class LoginException extends RuntimeException
  8. {
  9. private static final long serialVersionUID = 1L;
  10. protected final String message;
  11. public LoginException(String message)
  12. {
  13. this.message = message;
  14. }
  15. @Override
  16. public String getMessage()
  17. {
  18. return message;
  19. }
  20. }

3、基于@ControllerAdvice注解的Controller层的全局异常统一处理

  1. package com.ruoyi.framework.web.exception;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.web.bind.annotation.ExceptionHandler;
  5. import org.springframework.web.bind.annotation.RestControllerAdvice;
  6. import com.ruoyi.common.core.domain.AjaxResult;
  7. import com.ruoyi.common.exception.LoginException;
  8. /**
  9. * 全局异常处理器
  10. *
  11. * @author ruoyi
  12. */
  13. @RestControllerAdvice
  14. public class GlobalExceptionHandler
  15. {
  16. private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
  17. /**
  18. * 登录异常
  19. */
  20. @ExceptionHandler(LoginException.class)
  21. public AjaxResult loginException(LoginException e)
  22. {
  23. log.error(e.getMessage(), e);
  24. return AjaxResult.error(e.getMessage());
  25. }
  26. }

4、测试访问请求

  1. @Controller
  2. public class SysIndexController
  3. {
  4. /**
  5. * 首页方法
  6. */
  7. @GetMapping("/index")
  8. public String index(ModelMap mmap)
  9. {
  10. /**
  11. * 模拟用户未登录,抛出业务逻辑异常
  12. */
  13. SysUser user = ShiroUtils.getSysUser();
  14. if (StringUtils.isNull(user))
  15. {
  16. throw new LoginException("用户未登录,无法访问请求。");
  17. }
  18. mmap.put("user", user);
  19. return "index";
  20. }
  21. }

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

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

这个代码示例写的非常浅显易懂,但是需要注意的是:基于@ControllerAdvice注解的全局异常统一处理只能针对于Controller层的异常,意思是只能捕获到Controller层的异常, 在service层或者其他层面的异常都不能捕获。

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

系统日志

在实际开发中,对于某些关键业务,我们通常需要记录该操作的内容,一个操作调一次记录方法,每次还得去收集参数等等,会造成大量代码重复。 我们希望代码中只有业务相关的操作,所有的异常我们单独设立一个注解来处理它。

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

  1. @Log(title = "用户管理", businessType = BusinessType.INSERT)

支持参数如下:

参数 类型 默认值 描述
title String 操作模块
businessType BusinessType BusinessType.OTHER 操作功能
operatorType OperatorType OperatorType.MANAGE 操作人类别
isSaveRequestData boolean true 是否保存请求的参数

逻辑实现代码 com.ruoyi.framework.aspectj.LogAspect
查询操作详细记录可以登录系统(系统管理-操作日志)

数据权限

在实际开发中,需要设置用户只能查看哪些部门的数据,一般称为数据权限
默认系统管理员admin拥有所有数据权限(userId=1)
在需要数据权限控制方法上添加@DataScope注解
@DataScope(tableAlias = "u"),其中u用来表示表的别名

  1. /** 表的别名 */
  2. String tableAlias() default "";

在mybatis查询标签中添加数据范围过滤 ${params.dataScope}

会生成如下关键代码:

  1. select u.user_id, u.dept_id, u.login_name, u.user_name, u.email , u.phonenumber,
  2. u.password, u.sex, u.avatar, u.salt, u.status, u.del_flag, u.login_ip,
  3. u.login_date, u.create_by, u.create_time, u.remark, d.dept_name
  4. from sys_user u
  5. left join sys_dept d on u.dept_id = d.dept_id
  6. where u.del_flag = '0'
  7. and u.dept_id in ( select dept_id from sys_role_dept where role_id = 2 )

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

多数据源

在实际开发中,经常可能遇到在一个应用中可能需要访问多个数据库的情况
在需要切换数据源ServiceMapper方法上添加@DataSource注解
@DataSource(value = DataSourceType.MASTER),其中value用来表示数据源名称

  1. /** 切换数据源名称 */
  2. public DataSourceType value() default DataSourceType.MASTER;

注解实现数据源切换

  1. @DataSource(value = DataSourceType.MASTER)
  2. public List<SysUser> selectUserList(SysUser user)
  3. {
  4. return userMapper.selectUserList(user);
  5. }

手动实现数据源切换

  1. public List<SysUser> selectUserList(SysUser user)
  2. {
  3. DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
  4. List<SysUser> userList = userMapper.selectUserList(user);
  5. DynamicDataSourceContextHolder.clearDataSourceType();
  6. return userList;
  7. }

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

注意:目前配置了一个从库,默认关闭状态。可新增多个从库,支持不同数据源(Mysql、Oracle、SQLServer)

代码生成

大部分项目里其实有很多代码都是重复的,几乎每个基础模块的代码都有增删改查的功能,而这些功能都是大同小异,如果这些功能都要自己去写,将会大大浪费我们的精力降低效率。所以这种重复性的代码可以使用代码生成。 1、修改代码生成配置
编辑resources目录下的generator.yml
author: # 开发者姓名,生成到类注释上
packageName: # 默认生成包路径
autoRemovePre: # 是否自动去除表前缀
tablePrefix: # 表前缀

2、新建数据库表结构(需要表注释)

  1. drop table if exists sys_test;
  2. create table sys_test (
  3. test_id int(11) auto_increment comment '测试id',
  4. test_name varchar(30) default '' comment '测试名称',
  5. primary key (test_id)
  6. ) engine=innodb auto_increment=1 default charset=utf8 comment = '测试表';

3、登录系统-系统工具 -> 代码生成
找到sys_test表,点击生成代码会得到一个ruoyi.zip
执行sql文件,覆盖文件到对应目录即可

所有代码生成的相关业务逻辑代码在ruoyi-generator模块,可以自行调整或剔除

定时任务

在实际项目开发中,除了Web应用、还有一类不可缺少的,那就是定时任务。 定时任务的场景可以说非常广泛,比如某些视频网站,购买会员后,每天会给会员送成长值,每月会给会员送一些电影券; 比如在保证最终一致性的场景中,往往利用定时任务调度进行一些比对工作;比如一些定时需要生成的报表、邮件;比如一些需要定时清理数据的任务等。 所有我们提供方便友好的web界面,实现动态管理任务,可以达到动态控制定时任务启动、暂停、重启、删除、添加、修改等操作,极大地方便了开发过程。

1、新建定时任务信息(系统监控 -> 定时任务)
任务名称:对应后台bean注解名称,如@Component("ryTask")
任务组名:对应的定时任务组名 随意填写。
方法名称:对应后台任务方法名称如ryParams
方法参数:对应后台任务方法名称值如ry,没有可不填。
执行表达式:可查询官方cron表达式介绍
执行策略:
立即执行(所有被misfire的执行会被立即执行,然后按照正常调度继续执行trigger。 9点和10点的被忽略掉,好像什么都没发生一样。下次执行将在11点被执行。) 执行一次(立即执行第一次misfire的操作,并且放弃其他misfire的(类似所有misfire的操作被合并执行了)。然后继续按调度执行。无论misfire多少次trigger的执行,都只会立刻执行1次 9点和10点的被合并执行一次(换句话说,10点需要执行的那次,被pass了)。下次执行将在11点被准时执行)
放弃执行(所有被misfire的执行都被忽略掉,调度器会像平时一样等待下次调度 9点和10点的执行(misfire的2个)被立即执行,下次执行将在11点被准时执行。)
并发执行:是否需要多个任务间同时执行
状态:是否启动定时任务
备注:描述信息

2、点击执行一次,测试定时任务是否正常及调度日志是否正确记录

所有定时任务的相关业务逻辑代码在ruoyi-quartz模块,可以自行调整或剔除

注意:不同数据源定时任务都有对应脚本,Oracle、Mysql已经有了,其他的可自行下载执行

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号