# 常见问题

# 如何新增系统图标

如果你没有在本项目 Icon (opens new window) 中找到需要的图标,可以到 iconfont.cn (opens new window) 上选择并生成自己的业务图标库,再进行使用。或者其它 svg 图标网站,下载 svg 并放到文件夹之中就可以了。

下载完成之后将下载好的 .svg 文件放入 @/icons/svg 文件夹下之后就会自动导入。

使用方式

<svg-icon icon-class="password" /> // icon-class 为 icon 的名字
1

提示

菜单图标会自动引入@/icons/svg,放入此文件夹中图标就可以选择了

# 如何不登录直接访问

在 SecurityConfig 中设置httpSecurity 配置匿名访问

// 使用 permitAll() 方法所有人都能访问,包括带上 token 访问
.antMatchers("/admins/**").permitAll()

// 使用 anonymous() 所有人都能访问,但是带上 token 访问后会报错
.antMatchers("/admins/**").anonymous()
1
2
3
4
5

提示

匿名访问方法@PreAuthorize权限注解也需要去掉。

前端不登录如何直接访问

如果是前端页面可以在src/permission.js配置whiteList属性白名单即可。

# 如何更换项目包路径

参考如何更换项目包路径

# 业务模块访问出现404

参考业务模块访问出现404

# 系统接口访问出现401

在测试系统接口中可能存在一些接口用到用户信息或权限验证,此时需要添加全局的token参数。如图

swagger

token是在登录成功后返回的,可以在浏览器通过F12查看Network中的请求地址,对应参数Authorization。复制截图内容到swagger全局Authorization属性value参数中,点击Authorize,以后每次访问接口会携带此token信息。

swagger

# 如何更换后端请求地址

vue.config.js中,修改target值为对应的的后端接口地址。

devServer: {
  ...,
  proxy: {
    [process.env.VUE_APP_BASE_API]: {
      target: `http://localhost:8080`,
      ...
    }
  },
  ...
},
1
2
3
4
5
6
7
8
9
10

# 如何获取用户登录信息

  1. 第一种方法
// 获取当前的用户名称
String userName = SecurityUtils.getUsername();
1
2

2、缓存获取当前用户信息

@Autowired
private TokenService tokenService;
	
LoginUser loginUser = tokenService.getLoginUser();
// 获取当前的用户名称
String userName = loginUser.getUsername();
1
2
3
4
5
6

3、vue中获取当前用户信息

var userName = this.$store.state.user.name;
1

# 提示您没有数据的权限

这种情况都属于权限标识配置不对在菜单管理配置好权限标识(菜单&按钮)

  1. 确认此用户是否已经配置角色
  2. 确认此角色是否已经配置菜单权限
  3. 确认此菜单权限标识是否和后台代码一致

如参数管理
后台配置@PreAuthorize("@ss.hasPermi('system:config:query')")对应参数管理权限标识为system:config:query

注:如需要角色权限,配置角色权限字符 使用@PreAuthorize("@ss.hasRole('admin')")

# 如何创建新的菜单页签

Vue设置路由跳转的两种方法

一、路由跳转router.push

// 字符串
router.push('apple')
// 对象
router.push({path:'apple'})
// 命名路由
router.push({name: 'applename'})
//直接路由带查询参数query,地址栏变成 /apple?color=red
router.push({path: 'apple', query: {color: 'red' }})
// 命名路由带查询参数query,地址栏变成/apple?color=red
router.push({name: 'applename', query: {color: 'red' }})
//直接路由带路由参数params,params 不生效,如果提供了 path,params 会被忽略
router.push({path:'applename', params:{ color: 'red' }})
// 命名路由带路由参数params,地址栏是/apple/red
router.push({name:'applename', params:{ color: 'red' }})
// 其他方式
this.$router.push({ path: "/system/user" });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

二、动态赋值<router-link :to="...">to里的值可以是一个字符串路径,或者一个描述地址的对象。例如:

// 字符串
<router-link to="apple"> to apple</router-link>
// 对象
<router-link :to="{path:'apple'}"> to apple</router-link>
// 命名路由
<router-link :to="{name: 'applename'}"> to apple</router-link>
//直接路由带查询参数query,地址栏变成 /apple?color=red
<router-link :to="{path: 'apple', query: {color: 'red' }}"> to apple</router-link>
// 命名路由带查询参数query,地址栏变成/apple?color=red
<router-link :to="{name: 'applename', query: {color: 'red' }}"> to apple</router-link>
//直接路由带路由参数params,params 不生效,如果提供了 path,params 会被忽略
<router-link :to="{path: 'apple', params: { color: 'red' }}"> to apple</router-link>
// 命名路由带路由参数params,地址栏是/apple/red
<router-link :to="{name: 'applename', params: { color: 'red' }}"> to apple</router-link>
// 其他方式
<router-link :to="'/system/user/' + scope.row.userId" class="link-type">
  <span>{{ scope.row.userId }}</span>
</router-link>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 如何防止请求重复提交

后端可以通过@RepeatSubmit注解控制

/**
 * 在对应方法添加注解 @RepeatSubmit
 */
@RepeatSubmit
public AjaxResult edit()
1
2
3
4
5

# 异步处理获取用户信息

项目中可以通过SecurityContextHolder.getContext().getAuthentication()获取用户信息,例如

LoginUser loginUser = SecurityUtils.getLoginUser()
1

绝大多数情况下都是通过同步的方式来获取用户信息,如果通过异步获取还需要添加AsyncConfigurerSupport处理。

// 启动类上面添加,开启异步调用
@EnableAsync
// 方法上面添加,异步执行
@Async
1
2
3
4
package com.ruoyi.framework.config;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;

@Configuration
public class AsyncConfig extends AsyncConfigurerSupport
{
    /**
     * 异步执行需要使用权限框架自带的包装线程池 保证权限信息的传递
     */
    @Override
    public Executor getAsyncExecutor()
    {
        return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 前端如何配置后端接口

对于特殊情况,需要直接调用后台接口或者指定域名可以修改.env.production文件VUE_APP_BASE_API属性

# 后端接口地址
VUE_APP_BASE_API = '//localhost:8080'
1
2

# 图片上传成功不能显示

文件上传成功后,请求访问后台地址会根据profile进行匹配,需要自己配置nginx代理,参考如下。

location /profile/ {
    # 方式一:指向地址
    proxy_pass http://127.0.0.1:9999/profile/; 
}
1
2
3
4
location /profile/
{
    # 方式二:指向目录,对应后台`application.yml`中的`profile`配置
    alias /home/ruoyi/uploadPath/;
}
1
2
3
4
5

# 进入首页如何默认记忆控制台

例如用户退出后,下次登陆系统,能默认打开之前工作路径。

可以在request.js,修改LogOut

store.dispatch('LogOut').then(() => {
  location.href = '/index';
})
1
2
3

换成

store.dispatch('LogOut').then(() => {
  location.reload();
})
1
2
3

# 解决node-sass安装失败

node-sass 安装失败的原因 npm 安装 node-sass 依赖时,会从 github.com 上下载 .node 文件。由于国内网络环境的问题,这个下载时间可能会很长,甚至导致超时失败。 这是使用 sass 的同学可能都会遇到的郁闷的问题。

解决方案就是使用其他源,或者使用工具下载,然后将安装源指定到本地。

解决方法一:使用淘宝镜像源(推荐)
设置变量 sass_binary_site,指向淘宝镜像地址。示例

npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

// 也可以设置系统环境变量的方式。示例
// linux、mac 下
SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ npm install node-sass

// window 下
set SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ && npm install node-sass
1
2
3
4
5
6
7
8

或者设置全局镜像源:

npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
1

之后再涉及到 node-sass 的安装时就会从淘宝镜像下载。

解决方法二:使用 cnpm
使用 cnpm 安装 node-sass 会默认从淘宝镜像源下载,也是一个办法:

cnpm install node-sass
1

解决方法三:创建.npmrc文件
在项目根目录创建.npmrc文件,复制下面代码到该文件。

phantomjs_cdnurl=http://cnpmjs.org/downloads
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
registry=https://registry.npm.taobao.org
1
2
3

保存后 删除之前安装失败的包(第一次安装请跳过此步)

npm uninstall node-sass
1

重新安装

npm install node-sass
1

# 浏览器兼容性问题需求

本项目暂时没有兼容性需求,如有兼容性需求可自行使用 babel-polyfill。

// 下载依赖
npm install --save babel-polyfill
1
2

在入口文件中引入

import 'babel-polyfill'
// 或者
require('babel-polyfill') //es6
1
2
3

在 webpack.config.js 中加入 babel-polyfill 到你的入口数组:

module.exports = {
  entry: ['babel-polyfill', './app/js']
}
1
2
3

具体可参考 link (opens new window)

或者更简单暴力 polyfill.io (opens new window) 使用它给的一个 cdn 地址,引入这段 js 之后它会自动判断游览器,加载缺少的那部分 polyfill,但国内速度肯能不行,大家可以自己搭 cdn。

# 如何分析构建文件体积

如果你的构建文件很大,你可以通过 webpack-bundle-analyzer 命令构建并分析依赖模块的体积分布,从而优化你的代码。

npm run preview -- --report
1

运行之后你就可以在 http://localhost:9526/report.html (opens new window) 页面看到具体的体积分布

具体的优化可以参考 Webpack 大法之 Code Splitting (opens new window)

TIP

强烈建议开启 gzip ,使用之后普遍体积只有原先 1/3 左右。打出来的 app.js 过大,查看一下是不是 Uglify 配置不正确或者 sourceMap 没弄对。 优化相关请看该 Webpack Freestyle 之 Long Term Cache (opens new window)

# 模态框点击空白不消失

设置属性:close-on-click-modal="false"

<el-dialog  :close-on-click-modal="false"></el-dialog>
1

# 如何支持多类型数据库

参考如何支持多类型数据库

# 如何降低mysql驱动版本

参考如何降低mysql驱动版本

# 如何配置tomcat访问日志

参考如何配置tomcat访问日志

# 如何汉化系统接口Swagger

参考如何汉化系统接口Swagger

# 如何Excel导出子对象多个字段

参考如何Excel导出子对象多个字段

# Tomcat部署多个War包项目异常

参考Tomcat部署多个War包项目异常

# Tomcat临时目录tmp抛错误异常

参考Tomcat临时目录tmp抛错误异常

# 如何部署配置支持https访问

参考如何部署配置支持https访问

# 特殊字符串被过滤的解决办法

参考特殊字符串被过滤的解决办法

# Linux系统验证码乱码解决方法

参考Linux系统验证码乱码解决方法

# 如何处理Long类型精度丢失问题

如何处理Long类型精度丢失问题

# 如何修改Swagger默认访问地址

由于采用的前后端分离模式,且前端Swagger使用的iframe打开页面。所以默认请求的是前端地址,然后前端通过代理转发到后端接口。对于特殊情况需要直接请求后端提供如下方案:

方案1:使用新窗口打开,不要用iframe打开。因为swagger默认是获取当前服务的地址。

方案2:在SwaggerConfig配置中createRestApi方法设置后端的地址。

return new Docket(DocumentationType.SWAGGER_2)
    ....
	// 后端地址
    .host("localhost:8080")
1
2
3
4

# 如何修改超级管理员登录密码

1、如果是自己知道超级管理员的密码且需要修改的情况。
默认口令 admin/admin123,可以登录后在首页个人中心修改密码。

2、如果自己忘记了超级管理员的密码可以重新生成秘钥替换数据库密码。

public static void main(String[] args)
{
	System.out.println(SecurityUtils.encryptPassword("admin123"));
}
1
2
3
4

# 如何格式化前端日期时间戳内容

对应一些时间格式需要在前端进行格式化操作情况,解决方案如下

1、后端使用JsonFormat注解格式化日期,时间戳yyyy-MM-dd HH:mm:ss

/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date time;
1
2
3

2、前端使用parseTime方法格式化日期,时间戳{y}-{m}-{d} {h}:{i}:{s}

<el-table-column label="创建时间" align="center" prop="createTime" width="160">
	<template slot-scope="scope">
	  <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
	</template>
</el-table-column>
1
2
3
4
5

# 如何限制账户不允许多终端登录

这本来是一个可有可无的问题,不过经常有小伙伴有这样的需求。废话不多说,先来看同一用户不同终端限制登录的解决方法。方法很简单,大致思路就是做出userid与token(一个用户对应一个token,userid唯一)的键值对,存于缓存中。用于登录时判断用户是否在别的终端在线。详细实现代码如下:

1、application.yml新增一个配置soloLogin用于限制多终端同时登录。

# token配置
token:
    # 是否允许账户多终端同时登录(true允许 false不允许)
    soloLogin: false
1
2
3
4

2、Constants.java新增一个常量LOGIN_USERID_KEY公用

/**
 * 登录用户编号 redis key
 */
public static final String LOGIN_USERID_KEY = "login_userid:";
1
2
3
4

3、调整TokenService.java,存储&刷新缓存用户编号信息

// 是否允许账户多终端同时登录(true允许 false不允许)
@Value("${token.soloLogin}")
private boolean soloLogin;

/**
 * 删除用户身份信息
 */
public void delLoginUser(String token, Long userId)
{
	if (StringUtils.isNotEmpty(token))
	{
		String userKey = getTokenKey(token);
		redisCache.deleteObject(userKey);
	}
	if (!soloLogin && StringUtils.isNotNull(userId))
	{
		String userIdKey = getUserIdKey(userId);
		redisCache.deleteObject(userIdKey);
	}
}

/**
 * 刷新令牌有效期
 * 
 * @param loginUser 登录信息
 */
public void refreshToken(LoginUser loginUser)
{
	loginUser.setLoginTime(System.currentTimeMillis());
	loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
	// 根据uuid将loginUser缓存
	String userKey = getTokenKey(loginUser.getToken());
	redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
	if (!soloLogin)
	{
		// 缓存用户唯一标识,防止同一帐号,同时登录
		String userIdKey = getUserIdKey(loginUser.getUser().getUserId());
		redisCache.setCacheObject(userIdKey, userKey, expireTime, TimeUnit.MINUTES);
	}
}

private String getUserIdKey(Long userId)
{
	return Constants.LOGIN_USERID_KEY + userId;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

4、自定义退出处理类LogoutSuccessHandlerImpl.java清除缓存方法添加用户编号

// 删除用户缓存记录
tokenService.delLoginUser(loginUser.getToken(), loginUser.getUser().getUserId());
1
2

5、登录方法SysLoginService.java,验证如果用户不允许多终端同时登录,清除缓存信息

// 是否允许账户多终端同时登录(true允许 false不允许)
@Value("${token.soloLogin}")
private boolean soloLogin;
	
if (!soloLogin)
{
	// 如果用户不允许多终端同时登录,清除缓存信息
	String userIdKey = Constants.LOGIN_USERID_KEY + loginUser.getUser().getUserId();
	String userKey = redisCache.getCacheObject(userIdKey);
	if (StringUtils.isNotEmpty(userKey))
	{
		redisCache.deleteObject(userIdKey);
		redisCache.deleteObject(userKey);
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 前端静态资源如何整合到后端访问

分离版本都是前端和后端单独部署的,但是有些特殊情况想把前端静态资源整合到后端。提供如下方案:

1、修改ruoyi-ui中的.env.production(二选一)

// 本机地址访问
VUE_APP_BASE_API = '/'
1
2
// 任意地址访问
VUE_APP_BASE_API = '//localhost:8080'
1
2

2、修改ruoyi-ui中的router/index.js,设置mode属性为hash

export default new Router({
  mode: 'hash',
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})
1
2
3
4
5

3、执行bin\build.bat打包前端静态资源文件。

4、修改后端resources中的application.yml,添加thymeleaf模板引擎配置

spring:
  # 模板引擎
  thymeleaf:
    mode: HTML
    encoding: utf-8
    cache: false
1
2
3
4
5
6

5、修改后端pom.xml,增加thymeleaf模板引擎依赖

<!-- spring-boot-thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
1
2
3
4
5

6、修改后端ResourcesConfig.java中的addResourceHandlers,添加静态资源映射地址

/** 前端静态资源配置 */
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
1
2

7、修改后端SecurityConfig.java中的configure,添加允许访问的地址。

.antMatchers(
		HttpMethod.GET,
		"/*.html",
		"/**/*.html",
		"/**/*.css",
		"/**/*.js",
		"/static/**",
		"/",
		"/index"
).permitAll()
1
2
3
4
5
6
7
8
9
10

8、后端新建访问控制处理IndexController.java设置对应访问页面。

package com.ruoyi.project.system.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController
{
    // 系统首页
    @GetMapping(value = { "/", "/index", "/login" })
    public String index()
    {
        return "index";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

9、整合前端dist静态资源文件到后端

  • 后端resources下新建templates目录,复制静态页面index.html过来。

  • 复制静态文件staticresources目录下。

10、启动测试访问地址

打开浏览器,输入:http://localhost:8080 能正常访问和登录表示成功。

# 使用Velocity模板引擎兼容$符号

使用Velocity模板引擎兼容$符号

# 登录密码如何使用加密传输方式

集成jsencrypt实现密码加密传输方式

# 如何解决多数据源事务的一致性

参考如何解决多数据源事务的一致性

# 登录出现DatatypeConverter异常

错误提示:Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter

由于>= jdk9中不再包含这个jar包,所以需要在ruoyi-framework\pom.xml手动添加依赖。

<dependency>
	<groupId>javax.xml.bind</groupId>
	<artifactId>jaxb-api</artifactId>
	<version>2.3.0</version>
</dependency>
1
2
3
4
5

# 如何优雅的关闭后台系统服务

优雅停机主要应用在版本更新的时候,为了等待正在工作的线程全部执行完毕,然后再停止。我们可以使用SpringBoot提供的Actuator

1、pom.xml中引入actuator依赖

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

2、配置文件中endpoint开启shutdown

management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "shutdown"
      base-path: /monitor
1
2
3
4
5
6
7
8
9

3、在SecurityConfig中设置httpSecurity配置匿名访问

.antMatchers("/monitor/shutdown").anonymous()
1

4、Post请求测试验证优雅停机 curl -X POST http://localhost:8080/monitor/shutdown