ruoyi学习笔记
项目配置端口号配置:80806 out of range据说1024以下端口需要管理员权限?是因为我就是windows管理员吗?运行jre默认用的项目的sdk是怎么回事?父子模块子模块以一个为主(现在学的是web or controller)父子项目都没有以springboot为父项目父项目这个是干什么的?以sb为父项目的还需要不?<!-- SpringBoot的依赖配置--><
ruoyi前后端不分离版本4.4.0
对一个开源web项目要学习什么?
- 需求分析
- 权限管理
- 数据权限
- 操作权限
- 数据库设计
- UI设计
- 日志系统
- 文档书写
properties
ruoyi是后缀为Properties的类,用于读取配置文件中的配置,而不是java中的properties类
这种类加上@Configuration、@ConfigurationProperties注解,用@Value注解配合spel(或在类上指定配置中的前缀)获取配置文件中的属性
- 跨模块的配置文件也可以读取,因为依赖?还是因为读取classpath下的文件?
- maven依赖是单向的(毕竟会报循环依赖)
在配置类用@EnableConfigurationProperties({MySqlBinlogConnectJavaProperties.class})
注解读取获取到的属性
动态数据源配置
- 什么是“动态数据源”?
- 似乎要先系统学学mysql主从,,话说单纯mysql主从不需要改代码吧。
public class DruidConfig
{
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@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);
}
日志学习
sys_oper_log 操作日志记录
其他日志由logback保存在日志文件中,操作日志和登录日志保存在数据库中
handleLog方法中,user,dept,deptName真是每一步都要判空啊
记录日志:用单例模式的异步管理类
异步任务工厂AsyncFactory生产异步任务,由异步任务管理类AsyncManager执行(管理)
在 RuoYi-Vue 脚手架中,使用了 Java JUC 中的 ScheduledExecutorService 去完成这个延时任务,AsyncManager.me().execute 方法实际上就是去执行一个延时任务,这个延时任务就是一个往数据库中写入一条记录。
如果请求类型是 GET 或者 DELETE,则请求参数就直接从请求对象提取了。是如果请求类型是 PUT 或者 POST,就意味着请求参数是在请求体中,请求参数有可能是二进制数据(例如上传的文件),二进制数据就不好保存了,所以对于 POST 和 PUT 还是从接口参数中提取,然后过滤掉二进制数据即可。
操作日志
LogAspect
若 ServletUtils.getRequest().getParameterMap()
不为空
否则 使用joinPoint的参数
根据Druid,每次更新Dept的时候应该时把状态单独执行的
业务类型
0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据
授权角色:先删除user_role再插入user_role
- controller参数: id,ids
删除都是状态改变,可能有定时任务清理?businessType还是删除
实践:改造操作日志->记录数据变化
思路:
- 提前取出老数据再上下文中?
- 比较同一个类两个对象?
- 删除/导出可以多选,怎么办?
- 传递参数时逗号分隔的字符串
- 导出的数据是什么?导出是导出全部,传入一个空数据
binlog学习
client.connect() is blocking (meaning that client will listen for events in the current thread).
因为阻塞所以在前台吗
mysql的binlog有三种模式(级别),Row(输出变化)、Statement(默认,输出Sql语句)、Mixed
show global variables like '%binlog_format%';
BinlogMiner:离线挖掘模式,将日志发到其他机器上分析(脱机?)
系统日志
# 日志配置
logging:
level:
com.ruoyi: debug
org.springframework: warn
这个日志级别配置和ems_vue一样么?
权限学习
shiro使用
知乎上一篇教程
Realm负责Autherication和Authorizztion
shiro和slf4j的关系?
认真和授权流程相似
注:该实例中的类全部来自Shiro
//新建Realm
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
//添加账户,并使他具有admin和user两个角色(可以不设置角色)
//在自定义的Realm中,应该由Subject传过来的信息中提取用户名,再根据用户名从数据库中提取权限、角色、凭证等信息
simpleAccountRealm.addAccount("wmyskxz", "123456", "admin", "user");
// 1.构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm);
// 2.主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager); // 设置SecurityManager环境
Subject subject = SecurityUtils.getSubject(); // 获取当前主体
//将用户输入的账号密码包装成token,并尝试登录看看是否能被认证
UsernamePasswordToken token = new UsernamePasswordToken("wmyskxz", "123456");
subject.login(token); // 登录
// subject.isAuthenticated()方法返回一个boolean值,用于判断用户是否认证成功
System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出true
subject.logout(); // 登出
System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出false
// 判断subject是否具有admin和user两个角色权限,如没有则会报错
subject.checkRoles("admin","user");
}
自定义Realm
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.*;
public class MyRealm extends AuthorizingRealm {
/**
* 模拟从数据库中取得所有需要的用户名、密码
*/
Map<String, String> userMap = new HashMap<>(16);
{
userMap.put("wmyskxz", "123456");
super.setName("myRealm"); // 设置自定义Realm的名称,取什么无所谓..
}
/**
* 授权
* 根据用户名,获取用户的权限和角色。类似上例addAccount中的功能
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String userName = (String) principalCollection.getPrimaryPrincipal();
// 从数据库获取角色和权限数据,
Set<String> roles = dao.getRolesByUserName(userName);
Set<String> permissions = dao.getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permissions);
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
/**
* 认证
* @param authenticationToken 主体传过来的认证信息
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1.从主体传过来的认证信息中,获得用户名
String userName = (String) authenticationToken.getPrincipal();
// 2.通过用户名到数据库中获取凭证(此处为密码,找得到就返回认证信息)
String password = getPasswordByUserName(userName);
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("wmyskxz", password, "myRealm");
return authenticationInfo;
}
}
然后将之前例子中的Realm改成自己的就好,也不用手动添加addAccount了
springboot配置-ruoyi示例
# Shiro
shiro:
user:
# 登录地址
loginUrl: /login
# 权限认证失败地址
unauthorizedUrl: /unauth
# 首页地址
indexUrl: /index
# 验证码开关
captchaEnabled: true
# 验证码类型 math 数组计算 char 字符
captchaType: math
cookie:
# 设置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
操作权限
通过shiro,在controller层添加@RequiresPermissions,在访问前认证;通过shiro标签控制是否显示;
删除角色前需要查看角色操作权限?和数据权限,且如果角色已被分配则不能删除
checkRoleAllowed(new SysRole(roleId));
checkRoleDataScope(roleId);
自定义UserRealm类认证、授权,
认证
SysLoginController
@PostMapping("/login")
@ResponseBody
public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe)
{
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
try
{
subject.login(token);
return success();
}
catch (AuthenticationException e)
{
String msg = "用户或密码错误";
if (StringUtils.isNotEmpty(e.getMessage()))
{
msg = e.getMessage();
}
return error(msg);
}
}
- get的login是干什么的?
- shiro配置login是哪个?还是说都是?
UserRealm
从token中取出password、username,tryloginService.login(username, password)
,逐渐catch认证;没出错则包装返回
授权
ShiroUtils中获得当前User,根据userId,从Sys_menu表中取出perms字段,从sys_role表中取出SysRole对象(当前用户的角色、权限),放在授权信息中。
doGetAuthorizationInfo方法触发机制
在需要进行认证、授权的时候,SecurityManager调用Realm中的方法
数据权限
按照部门分配
角色与部门关联表
通过注解、切面、拼接sql字符串来动态拼接查询语句。
只有角色和数据权限直接相关,用户通过自己的角色来获得权限
@DataScope
common模块中的annotion包中定义DataScope注解,两个属性用于指定别名;指定别名是为了在切面类拼接sql语句时和Mapper.xml中的别名一致
在Service层
使用该注解的方法:
- 查询user(分页查询、未分配、已分配)
- 查询role(只能看到自己数据权限内的角色)
- 查询部门列表
- 查询部门管理树
- 查询部门管理树(排除下级)
后两个方法就是把第三个全查出来再在Service中剔除、排列
校验部门是否有数据权限(checkDeptDataScope)没用上?校验角色的倒是在删除角色用上了
/**
* 修改数据权限信息
*/
@Override
@Transactional
public int authDataScope(SysRole role)
{
// 修改角色信息(role中有datascope字段?ORM一对多要求的么)
roleMapper.updateRole(role);
//先删后增而不是直接修改,因为修改前后条数不一定一样(多对多关联表修改都是这样么?)
// 删除角色与部门关联
roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId());
// 新增角色和部门信息(数据权限)
return insertRoleDept(role);
}
- 设置了数据权限后,角色、用户、部门树等只能看到自己数据权限给的部门内的。
SysUser类的orm对象包含着roles的对象引用列表,而不是像数据库一样只有外键;在从数据库查询用户的时候就已经将roles读取到内存,而不是读取sys_user_role中的roles_id,在内存中不是通过id查找的。
此时类和数据库不是一一对应的,SysUser类中的roles需要在其他表中查找
DataScopeAspect
framework模块中的aspectj包实现切面、注解
@Before匹配的注解是下面自己的参数的
- 没有配置切入点
在执行@DataScope标注的方法之前,用ShiroUtils.getSysUser()
获取当前用户,for循环处理用户的roles,获取每个role唯一的dataScope,然后if-else判断拼接哪句sql;
然后用joinPoint.getArgs()[0]
获取参数(SysUser或SysDept),获取为Object,判断后转换为BaseEntity类,放入params并开始查询
SysUser类继承自BaseEntity类,父类中有Map类型的params属性,在mapper中调用,用于获取dataScope。虽然是私有属性,但是可以通过继承的共有方法调用??还是说,其实是SysUser->Object->BaseEntity?
为什么是subString(4)?
- 全部权限就直接为空,不用筛选
- 自定义数据权限的角色从sys_role_dept表里查dept_id,获得这个role能看的部门id
- 只看自己部门就
d.depi_id = user的deptId
- 能看子部门就从部门表中选择自己部门 or 祖先有自己部门的
- 只看自己就筛选按user_id,没有别名就为空(什么时候有这种情况呢?)
基本是查看d.dept_id是否in一个子查询中
aop中指定切点为某个包?则该包下只能由接口,实现类要转移至业务层
- 切点只能是接口吗?
相关表(sys_前缀)
理解 | ||
---|---|---|
user | 用户 | |
role | 角色 | |
menu | 菜单权限表 | 菜单是前端出现的选项,有些菜单需要权限才渲染 |
role_menu | 角色权限多对多 | |
user_role | 角色用户多对多 |
菜单里有权限字段,user通过连接表查询自己的所有权限,有权限则显示菜单、访问controller接口
不需要用户-权限直接配置?因为用户的权限通过给角色配置过了吧(被范式优化掉了?因为shiro需要“角色”?spring security还需要角色不,数据库还这么搞不?)
- 没分配角色侧边栏什么也看不到;
- 父子联动:
- 关掉父子联动,有父权限才能在任务栏看到父标签,有子权限父权限标签下才有东西
- 选父会全选子,选一个子会自动选父;子没了自动取消
在角色中有这个权限才可以看到这个选项
相关类
- role中这个属性干嘛的?
/** 部门组(数据权限) */
private Long[] deptIds;
- @RequiresRoles写在哪里?
controller
- system/dept/treeData 公司部门树
- /system/user/list
数据库设计
表名加上前缀以区分
用户和岗位关联表,角色和部门关联
ancestors字段保存树形结构的父节点,然后用find_in_set(#{deptId}, ancestors)
查找子节点
当前时间用sysdate()
接口的概念:编程语言中和业务中不一样,做范围区分图
- Fernflower decompiler / x jad
判断方法:
if (table == null? false: table.startsWith(“sys”))
从canal学到的
Java中定义Map常量,List常量
一般的方式的使用静态代码块。比如:
public final static Map map = new HashMap();
static {
map.put("key1", "value1");
map.put("key2", "value2");
}
下面为一种简单定义Map常量的方式
public final static Map<String, Fragment> NAV_ITEM_ADPTER = new HashMap<String, Fragment>() {
{
put("拍录传", new CameraFragment());
put("集群对讲", new GroupTalkFragment());
put("视通", new VideoCallFragment());
put("位置", new PositionFragment());
put("浏览", new BrowseFragment());
put("消息", new MsgFragment());
put("群组", new GroupFragment());
put("设置", null);
put("退出", null);
}
};
mv.addObject("pageInfo",pageInfo);
mv.setViewName("orders-page-list");
modelView是把页面和页面需要的数据当成一个对象来操作,这种思想可以借鉴。如果需要的数据是一个对象,就addObject,把需要的数据包装成对象。似乎前后端分离也可以这么封装。
- 这点儿可以看Spring的解释证明一下
- 字典数据是干嘛的?
- 内连接的on只能写主键与外键?
更多推荐
所有评论(0)