一、搭建环境
1.1 一把嗦搭建一个Springboot项目(略过)
pom.xml文件 需要集成springboot、mybatis、mysql驱动、web等相关依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入模版引擎相关的包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<!--MyBatis依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
</dependencies>
二、后端实现
1. 编写启动类(入口类)
public class PermissionSystemAppStart {
public static void main(String[] args) {
SpringApplication.run(PermissionSystemAppStart.class, args);
}
}
2. 准备一个Permission实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Permission {
private Long id;
private String name;
private String url;
private String descs;
private String sn;
//父权限对象
private Permission parent;
private List<Permission> children = new ArrayList<>();
public Permission(Long id, String name, String url, String descs, String sn) {
this.id = id;
this.name = name;
this.url = url;
this.descs = descs;
this.sn = sn;
}
public Permission(Long id, String name, String url, String descs, String sn, Permission parent) {
this.id = id;
this.name = name;
this.url = url;
this.descs = descs;
this.sn = sn;
this.parent = parent;
}
public Permission(String name, String url, String descs, String sn) {
this.name = name;
this.url = url;
this.descs = descs;
this.sn = sn;
}
}
我们需要对每一个访问进行权限管理那么首先需要将所有的地址进行获取,也就是获取到controller的请求。
所以需要自定义一个注解类来获取controller的注解信息和方法信息,将其添加进入数据库
数据库字段如下:
3. 注解类:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyPermission {
String name();
String descs() default "";
}
需要注意的是对target注解的使用,TYPE是对类上注解进行获取的,METHOD是对方法上的注解进行获取的。
重点-自动扫描
设置监听器,在入口类启动时对其进行加载操作,将权限插入数据库。
@WebListener
//@Component
public class InitDataListener implements ServletContextListener {
@Autowired
private IPermissionScanService scanService;
@Autowired
private IPermissionService service;
//容器初始化好
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("======================初始化开始======================");
service.deleteAll();
scanService.scan();
System.out.println("======================初始化完成======================");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("销毁完成.........");
}
}
其中需要实现scan方法
需要获取对应的controller目录,获取字节码对象,在此之前需要设置yml文件扫描类的位置
kjj:
permission:
scan-package: com.kjj.controller
工具类util,类名的解析
public static List<String> getAllClassName(String packageName){
String packagePath = packageName.replace(".", "/"); //替换为目录
URL url = ClassLoader.getSystemResource("");
File[] files = new File(url.getPath() + packagePath)
.listFiles(file -> file.getName().endsWith(".class"));
System.out.println(files);
List<String> result = new ArrayList<>();
for(File file : files){
// 输出类名称
String fileName = file.getName();
fileName = fileName.substring(0,fileName.lastIndexOf("."));
//全限定类名
String allName = packageName+"."+fileName;
result.add(allName);
}
return result;
}
ServiceImpl实现类
@Service
public class PermissionScanServiceImpl implements IPermissionScanService {
@Value("${kjj.permission.scan-package}")
private String permissionScanPackage;
@Autowired
private PermissionMapper permissionMapper;
@Override
public void scan() {
//找到特定包下所有类
List<String> allClassName = ClassUtils.getAllClassName(permissionScanPackage);
List<Permission> permissions = new ArrayList<>();
//遍历类,获取所有方法
for (String className:allClassName){
try {
//获取字节码对象
Class<?> clazz = Class.forName(className);
//父权限处理
Permission parent = getParentPermission(clazz);
//父权限
if (parent==null) //如果类上都没有权限,方法上面也不应该要有
continue;
permissions.add(parent);
//获取所有方法
Method[] methods = clazz.getMethods();
//判断是否添加了权限注解
for (Method method:methods){
MyPermission annotation = method
.getAnnotation(MyPermission.class);
if (annotation==null)
continue;
String name = annotation.name();
String descs = annotation.descs();
String sn = clazz.getSimpleName() + ":" + method.getName();
String url = getUrl(clazz, method);
permissions.add(new Permission(null,name, url, descs, sn,parent));
}
} catch (Exception e) {
e.printStackTrace();
}
}
//对数据库进行操作,通过对controller的扫描,自动添加请求方法和资源路径
permissions.forEach(permission->{
Permission permissionByDb = permissionMapper.loadBySn(permission.getSn());
// System.out.println(permission);
if (permissionByDb!=null){
//修改
permission.setId(permissionByDb.getId());
permissionMapper.update(permission);
}
else{
permissionMapper.save(permission);
}
});
}
扫描到后的逻辑是,先删除所有的数据库信息,然后添加扫描到的信息,这样就不会有重复数据,以便于后期在前端进行展示
<insert id="save" parameterType="Permission" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
-- alter table t_permission AUTO_INCREMENT =1;
insert into t_permission(
name,
url,
descs,
sn,
parent_id
)value(
#{name},
#{url},
#{descs},
#{sn},
#{parent.id}
)
</insert>
测试完成后需要进行分页操作把所有的数据都展示到前端进行回显,需要有一个分页工具类去记录,每页的数据,每页的总数,这里用到的分页知识点,真分页(需要多少数据查多少),假分页(将数据库的数据全部查询出来,对前端显示的数据进行限制达到分页的效果)
三、 前端集成
首先抽取公共模板为一个common.html,使用thymeleaf对div进行替换,使用ajax局部刷新技术对页面进行刷新加载数据,这里发送的是两次请求,第一次是请求页面,第二次是请求数据,页面刷新数据使用的框架是jqgrid,初始化表格
mounted(){
$("#permissionGrid").jqGrid({ //初始化表格操作
url: '/permission',
datatype: "json",
ajaxGridOptions: {
contentType: "application/json",
},//post请求需要加
mtype: "POST",//post请求需要加
serializeGridData: function(postData) {
postData.keyword = $("#keywordInput").val(); //添加关键字属性
return JSON.stringify(postData);
},//post请求需要加
colModel: [
{ label: 'ID', name: 'id', width: 50, key: true ,align:"center"},
{ label: '名称', name: 'name', width: 100 ,align:"center"},
{ label: '标识', name: 'sn', width: 100 ,align:"center"},
{ label: '描述', name: 'descs', width: "200" ,align:"center"},
{ label:'操作', name:'opr',index:"id",width:"100%",align:"center",sortable:false},
],
loadonce: false, //false每次往后台发数据
viewrecords: true,
width: 1200,
height: 400,
rowNum: 3, //一页展示多少条
rowList : [2,4,6],
rownumbers: true,
rownumWidth: 25,
multiselect: true,
pager: "#permissionGridPager",
jsonReader : {
root: "rows", //当前页数据
records: "total", //总记录数
total: "totalPage",
page: "page", //当前页
},
prmNames : { //传递到后台参数
page:"currentPage",
rows:"pageSize",
order: "order",
},
gridComplete:function(){
var ids=$("#permissionGrid").jqGrid('getDataIDs');
for(var i=0; i<ids.length; i++){
var id=ids[i];
opr = "<a href='#' style='color:#f60' onclick='vue.edit(" + id + ")'>修改 </a>"+"<a href='#' style='color:#f60' onclick='vue.del(" + id + ")' >删除</a>";
$("#permissionGrid").jqGrid('setRowData', ids[i], { "opr": opr });
}
}
});
}