博主在给大家做项目的时候,经常需要权限菜单等配置
为了简化这些操作,我这里想实现通过注解配置来自动往数据库里插入菜单(注意我这里菜单和权限公用 api 表)。
just do it.
一、添加注解
package com.liuyanzhao.common.config.api;
import java.lang.annotation.*;
/**
* -接口资源注解
*
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiResource {
/**
* 资源名称或菜单名称,如 用户管理,添加用户按钮
*
* @return
*/
String apiName();
/**
* 是否为菜单
*
* @return
*/
boolean resMenuFlag() default false;
/**
* 排序值
*
* @return
*/
int resOrder() default 1;
/**
* 资源图标
*
* @return
*/
String resIcon() default "fa fa-circle-o";
}
二、添加 Bean 后置处理器,扫描注解
package com.liuyanzhao.common.config.api;
import com.liuyanzhao.common.util.StringUtils;
import com.liuyanzhao.entity.Api;
import com.liuyanzhao.service.ApiService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.*;
import java.lang.reflect.Method;
import java.util.*;
@Component
@Slf4j
public class ApiProcessor implements BeanPostProcessor {
@Autowired
private ApiService apiService;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 如果Bean上没有@Controller、@RequestMapping、@ApiResource注解则跳过
if (null == AnnotationUtils.findAnnotation(bean.getClass(), Controller.class)) {
return bean;
}
RequestMapping requestMapping = AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
if (null == requestMapping) {
return bean;
}
ApiResource apiResource = AnnotationUtils.findAnnotation(bean.getClass(), ApiResource.class);
if (null == apiResource) {
return bean;
}
String apiName = apiResource.apiName();
if (StringUtils.isEmpty(apiName)) {
throw new RuntimeException("请正确配置" + processPath(requestMapping.path()) + "的ApiResource注解的apiName属性!");
}
String pref = processPath(requestMapping.path());
log.info("========开始初始化【" + apiName + "】模块接口数据");
// 该Controller的所有方法
Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
if (methods == null || methods.length == 0) {
return bean;
}
Api api = new Api();
api.setApiName(apiResource.apiName());
api.setRequestUrl(pref);
api.setResIcon(apiResource.resIcon());
api.setResMenuFlag(apiResource.resMenuFlag());
api.setResOrder(apiResource.resOrder());
Set<String> requestUrlSet = new HashSet<>();
List<Api> childApiList = new ArrayList<Api>();
if (null != methods) {
Api childApi;
for (Method method : methods) {
//如果是CGLIB方法跳过
if (method.getName().toString().startsWith("CGLIB$")) {
continue;
}
apiResource = AnnotationUtils.findAnnotation(method, ApiResource.class);
requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);
GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);
PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);
if (null == apiResource) {
continue;
}
// apiName是必须字段;resTitle可以不写,默认与apiName相同
if (StringUtils.isEmpty(apiResource.apiName())) {
throw new RuntimeException("请正确配置" + Arrays.asList(requestMapping.path()) + "的ApiResource注解的apiName属性!");
}
String requestUrl = pref;
// 获取URL
if (requestMapping.path().length > 0) {
requestUrl = pref + processPath(requestMapping.path());
} else if (postMapping != null && postMapping.path().length > 0) {
requestUrl = pref + processPath(postMapping.path());
} else if (getMapping != null && getMapping.path().length > 0) {
requestUrl = pref + processPath(getMapping.path());
} else if (putMapping != null && putMapping.path().length > 0) {
requestUrl = pref + processPath(putMapping.path());
} else if (deleteMapping != null && deleteMapping.path().length > 0) {
requestUrl = pref + processPath(deleteMapping.path());
}
requestUrl = requestUrl.replaceAll("/+", "/");
if(!requestUrlSet.contains(requestUrl)) {
childApi = new Api();
childApi.setApiName(apiResource.apiName());
childApi.setRequestUrl(requestUrl);
childApi.setResIcon(apiResource.resIcon());
childApi.setResMenuFlag(apiResource.resMenuFlag());
childApi.setResOrder(apiResource.resOrder());
childApiList.add(childApi);
requestUrlSet.add(requestUrl);
}
}
}
api.setChildApiList(childApiList);
apiService.autoSaveApiList(api);
log.info("========完成初始化【" + apiName + "】模块接口数据,共:" + childApiList.size() + "个接口");
return bean;
}
/**
* 获取到配置的path
*/
private String processPath(String[] paths) {
if (null == paths || StringUtils.isEmpty(paths[0])) {
return "";
} else {
String path = paths[0];
// 保证前面有“/”后面没有
if (!paths[0].startsWith("/")) {
path = "/" + path;
}
if (paths[0].endsWith("}")) {
int index = path.indexOf("{");
path = path.substring(0, index - 1);
}
return path;
}
}
}
三、开始使用
使用一个测试的控制器,在每个方法上添加 @ApiResource 注解
package com.liuyanzhao.controller.admin;;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.liuyanzhao.common.config.api.ApiResource;
import com.liuyanzhao.common.util.CommonUtil;
import com.liuyanzhao.common.util.PageUtil;
import com.liuyanzhao.common.util.StringUtils;
import com.liuyanzhao.common.vo.JsonResult;
import com.liuyanzhao.common.vo.PageVo;
import com.liuyanzhao.controller.common.BaseController;
import com.liuyanzhao.entity.Test;
import com.liuyanzhao.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
/**
* 后台测试管理控制器
*/
@Slf4j
@Controller
@RequestMapping(value = "/admin/test")
@ApiResource(apiName = "测试管理", resMenuFlag = true, resIcon = "fa fa-list", resOrder = 10)
public class TestController extends BaseController {
@Autowired
private TestService testService;
/************以下是页面 控制器方法,返回值即html文件路径(templates下面),浏览器直接访问 ************/
/**
* 测试列表页面
*
* @return 模板路径
*/
@GetMapping
@ApiResource(apiName = "测试列表", resMenuFlag = true, resOrder = 1)
public String listPage(
@RequestParam(value = "keywords", defaultValue = "") String keywords,
@ModelAttribute PageVo pageVo,
HttpServletRequest request,
Model model) {
// 分页入参处理
Page page = PageUtil.initMpPage(pageVo);
// 查询条件构造
LambdaQueryWrapper<Test> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotEmpty(keywords)) {
queryWrapper.and(p -> p.like(Test::getTitle, keywords).or().like(Test::getContent, keywords).or().like(Test::getHobby, keywords).or().like(Test::getMenuType, keywords).or().like(Test::getStatus, keywords));
}
// 调用分页查询
IPage<Test> pageInfo = testService.page(page, queryWrapper);
// 把结果存储在model中,前端可以通过占位符EL表达式获取数据
model.addAttribute("pageInfo", pageInfo);
model.addAttribute("keywords", keywords);
return "admin/test/admin_test";
}
/**
* 创建测试页面
*
* @return 模板路径
*/
@GetMapping("/new")
@ApiResource(apiName = "添加测试", resMenuFlag = true, resOrder = 2)
public String newPage(Model model) {
model.addAttribute("item", new Test());
return "admin/test/admin_test_edit";
}
/**
* 编辑测试页面
*
* @return 模板路径
*/
@GetMapping("/edit")
@ApiResource(apiName = "编辑测试页面")
public String editPage(@RequestParam("id") Long id, Model model, HttpServletRequest request) {
Test test = testService.getById(id);
if (test == null) {
return this.renderNotFound();
}
model.addAttribute("item", test);
return "admin/test/admin_test_edit";
}
/************以上是页面 控制器方法,返回值即html文件路径(templates下面),浏览器直接访问 ************/
/************以下是接口操作 控制器方法,返回JSON数据,为前端ajax服务 ************/
/**
* 删除测试提交操作
*
* @param id 测试Id
* @return JsonResult
*/
@ApiResource(apiName = "删除测试操作")
@PostMapping(value = "/delete")
@ResponseBody
public JsonResult deleteSubmit(@RequestParam("id") Long id, HttpServletRequest request) {
testService.removeById(id);
return JsonResult.success("删除成功");
}
/**
* 批量删除测试提交操作
*
* @param ids 测试ID列表
* @return 响应JSON
*/
@PostMapping(value = "/batchDelete")
@ResponseBody
@ApiResource(apiName = "批量删除测试操作")
public JsonResult batchDeleteSubmit(@RequestParam("ids") List<Long> ids, HttpServletRequest request) {
if (CommonUtil.isNotEmpty(ids)) {
testService.removeByIds(ids);
}
return JsonResult.success("删除成功");
}
/**
* 保存测试操作
*
* @param test test
* @return 响应JSON
*/
@PostMapping(value = "/save")
@ResponseBody
@ApiResource(apiName = "保存测试操作")
public JsonResult saveSubmit(@ModelAttribute Test test, HttpServletRequest request) {
test.setCreateTime(new Date());
testService.saveOrUpdate(test);
return JsonResult.success("保存成功");
}
/************以上是接口操作 控制器方法,返回JSON数据,为前端ajax服务 ************/
}
注册这个Test实体类只是我测试类
四、效果图
1、启动项目打印日志如下
2、查看数据库api表数据
3、查看页面左侧菜单
2022年05月27日 14:21:35
学到了不少东西!我的博客,欢迎回访