一.登录
1.1 生成验证码
基本思路
-
后端生成一个表达式,
7+4=?@11
-
7+4=?
转成图片,传到前端进行展示 -
将结果
11
存入redis -
前端代码实现:
请求后端地址:http://localhost/dev-api/captchaImage,通过反向代理解决前后端跨域问题,将请求路径变为:http://localhost:8080/captchaImage
-
后端代码实现:
/** * 生成验证码 */ @GetMapping("/captchaImage") public AjaxResult getCode(HttpServletResponse response) throws IOException { AjaxResult ajax = AjaxResult.success(); boolean captchaEnabled = configService.selectCaptchaEnabled(); ajax.put("captchaEnabled", captchaEnabled); if (!captchaEnabled) { return ajax; } // 保存验证码信息 String uuid = IdUtils.simpleUUID(); String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; String capStr = null, code = null; BufferedImage image = null; // 生成验证码 String captchaType = RuoYiConfig.getCaptchaType(); if ("math".equals(captchaType)) { String capText = captchaProducerMath.createText(); capStr = capText.substring(0, capText.lastIndexOf("@")); code = capText.substring(capText.lastIndexOf("@") + 1); image = captchaProducerMath.createImage(capStr); } else if ("char".equals(captchaType)) { capStr = code = captchaProducer.createText(); image = captchaProducer.createImage(capStr); } redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try { ImageIO.write(image, "jpg", os); } catch (IOException e) { return AjaxResult.error(e.getMessage()); } ajax.put("uuid", uuid); ajax.put("img", Base64.encode(os.toByteArray())); return ajax; }
1.2 登录
-
后端
- 1.校验验证码
- 2.校验用户名和密码
- 3.生成ToKen
/** * 登录方法 * * @param loginBody 登录信息 * @return 结果 */ @PostMapping("/login") public AjaxResult login(@RequestBody LoginBody loginBody) { AjaxResult ajax = AjaxResult.success(); // 生成令牌 String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid()); ajax.put(Constants.TOKEN, token); return ajax; }
使用异步任务管理器,结合线程池,实现了异步的操作日志记录,和业务逻辑实现异步解耦合.
1.3 获取用户信息
/**
* 获取用户信息
*
* @return 用户信息
*/
@GetMapping("getInfo")
public AjaxResult getInfo()
{
SysUser user = SecurityUtils.getLoginUser().getUser();
// 角色集合
Set<String> roles = permissionService.getRolePermission(user); // 超级管理员
// 权限集合
Set<String> permissions = permissionService.getMenuPermission(user); // *:*:*
AjaxResult ajax = AjaxResult.success();
ajax.put("user", user);
ajax.put("roles", roles);
ajax.put("permissions", permissions);
return ajax;
}
获取当前用户的角色和权限信息,存储到Vuex中
1.4 获取路由信息
根据当前用户的权限信息获取动态路由,最终完成动态菜单的展示
/**
* 获取路由信息
*
* @return 路由信息
*/
@GetMapping("getRouters")
public AjaxResult getRouters()
{
Long userId = SecurityUtils.getUserId();
List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
return AjaxResult.success(menuService.buildMenus(menus));
}
二.首页加载
当用户登录成功以后我们可以根据路由看出,默认跳转到路由
/viees/index.vue
页面
三.用户管理
3.1 查询
流程:加载vue页面 -> 请求后台数据
-
获取用户信息后端
/** * 获取用户列表 */ @PreAuthorize("@ss.hasPermi('system:user:list')") @GetMapping("/list") public TableDataInfo list(SysUser user) { startPage(); // 做分页 List<SysUser> list = userService.selectUserList(user); // 查询数据 return getDataTable(list); // 返回结果 }
-
获取树状图后端
/** * 获取部门树列表 */ @PreAuthorize("@ss.hasPermi('system:user:list')") @GetMapping("/deptTree") public AjaxResult deptTree(SysDept dept) { return success(deptService.selectDeptTreeList(dept)); }
3.2 添加用户
- 前端
/** 新增按钮操作 */
handleAdd() {
this.reset();// 清空表单
getUser().then(response => {
this.postOptions = response.posts;// 岗位
this.roleOptions = response.roles;// 角色
this.open = true;
this.title = "添加用户";
this.form.password = this.initPassword;
});
},
/** 提交按钮 */
submitForm: function() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.userId != undefined) {
updateUser(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addUser(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
- 后端
/**
* 根据用户编号获取详细信息
*/
@PreAuthorize("@ss.hasPermi('system:user:query')")
@GetMapping(value = { "/", "/{userId}" })
public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId)
{
userService.checkUserDataScope(userId);
AjaxResult ajax = AjaxResult.success();
List<SysRole> roles = roleService.selectRoleAll();
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("posts", postService.selectPostAll());
if (StringUtils.isNotNull(userId))
{
SysUser sysUser = userService.selectUserById(userId);
ajax.put(AjaxResult.DATA_TAG, sysUser);
ajax.put("postIds", postService.selectPostListByUserId(userId));
ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList()));
}
return ajax;
}
/**
* 新增用户
*/
@PreAuthorize("@ss.hasPermi('system:user:add')")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody SysUser user)
{
if (!userService.checkUserNameUnique(user))
{
return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
}
else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
{
return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
}
else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
{
return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setCreateBy(getUsername());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
return toAjax(userService.insertUser(user));
}
修改和删除查看源码步骤相同,这里省略
四.异步任务管理器
这里选取一段代码作示例:
com.ruoyi.framework.web.service.SysLoginService
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
通过异步任务管理器记录登录日志
AsyncManager.me()
:获取一个AsyncManager对象- 执行
execute
方法,传入的是一个Task对象,实现了Runnable接口
表示是一个任务,由线程Thread去执行
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息
* @param args 列表
* @return 任务task
*/
public static TimerTask recordLogininfor(final String username, final String status, final String message,
final Object... args)
{
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
final String ip = IpUtils.getIpAddr();
return new TimerTask()
{
@Override
public void run()
{
String address = AddressUtils.getRealAddressByIP(ip);
StringBuilder s = new StringBuilder();
s.append(LogUtils.getBlock(ip));
s.append(address);
s.append(LogUtils.getBlock(username));
s.append(LogUtils.getBlock(status));
s.append(LogUtils.getBlock(message));
// 打印信息到日志
sys_user_logger.info(s.toString(), args);
// 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(ip);
logininfor.setLoginLocation(address);
logininfor.setBrowser(browser);
logininfor.setOs(os);
logininfor.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
{
logininfor.setStatus(Constants.SUCCESS);
}
else if (Constants.LOGIN_FAIL.equals(status))
{
logininfor.setStatus(Constants.FAIL);
}
// 插入数据
SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
}
};
}
封装了登录用户的信息,执行添加操作,这里不会执行,而是将任务交给线程对象来执行
异步任务管理器,内部定义了一个线程池,然后根据业务创建添加日志的任务,交给线程池来处理,这样做到日志和业务的抽象,解耦合,日志全部统一处理.
五.代码自动生成
5.1 创建数据表
use ry_vue;
create table test_user
(
id int primary key auto_increment,
name varchar(11),
password varchar(11)
);
5.2 系统工具–>代码生成
5.2.1 导入数据表
5.2.2 编辑数据表
5.2.3 点击生成代码
5.2.4 解压压缩包
5.2.5 导入代码,重启项目
- 后端
- 前端
- 数据库