SpringBoot自动扫描API接口入库

avatar 2022年05月23日18:04:32 7 743 views
博主分享免费Java教学视频,B站账号:Java刘哥

博主在给大家做项目的时候,经常需要权限菜单等配置

为了简化这些操作,我这里想实现通过注解配置来自动往数据库里插入菜单(注意我这里菜单和权限公用 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、查看页面左侧菜单

 

 

 

 

 

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
avatar

发表评论

avatar 登录者:匿名
可以匿名评论或者登录后台评论,评论回复后会有邮件通知

  

已通过评论:1   待审核评论数:0
  1. avatar 数字货币杂志

    学到了不少东西!我的博客,欢迎回访