自定义注解:springboot+vue-限制接口调用
- 2019 年 10 月 4 日
- 筆記
前言
公司前端项目用的是vue,后端用的是Springboot。因为最近公司业务的原因,需要根据条件限制接口的调用。限制的条件是根据指定的key获取Redis中value的值,然后判断value中的日期往后推一年(例如value中的日期是:2018-09-12,往后推一年就是2019-09-12)是否大于当前日期。如果大于则可访问(这里的可访问指的是可访问所有接口)。反之,则所有接口不可访问。
在使用自定义注解之前,我们先来了解Java为我们提供的元注解和相关定义注解的语法。
一、了解Java注解语法
1.元注解(meta-annotation)
元注解的作用就是负责注解其他注解,在java.lang.annotation包中可以找到。
- @Target
- @Retention
- @Documented
- @Inherited
二、每个元注解的作用
1.@Target:
用于描述注解的使用范围。
参数说明:
@Target(ElementType.TYPE) //接口、类、枚举、注解 @Target(ElementType.FIELD) //字段、枚举的常量 @Target(ElementType.METHOD) //方法 @Target(ElementType.PARAMETER) //方法参数 @Target(ElementType.CONSTRUCTOR) //构造函数 @Target(ElementType.LOCAL_VARIABLE) //局部变量 @Target(ElementType.ANNOTATION_TYPE) //注解 @Target(ElementType.PACKAGE) //包
2.@Retention
表示需要在什么级别保存该注释信息,用于描述注解的生命周期。
RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃。 RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。 RetentionPolicy.RUNTIME:注解在运行时有效(运行时保留)。
3.@Documented
Documented是一个标记注解,没有成员。
4.@Inherited
@Inherited 表示该注解会被子类继承。仅针对类,成员属性、方法并不受此注释的影响。对于类来说,子类要继承父类的注解需要该注解被 @Inherited 标识。对于成员属性和方法来说,非重写的都会保持和父类一样的注解,而被实现的抽象方法,被重写的方法都不会有父类的注解。
5.自定义注解
使用@interface自定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型。
三、自定义注解
import java.lang.annotation.*;/** *定制一个接口 */@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface EnableAuth {}
public class ApiAuthDataInit implements ApplicationContextAware { /** 存放需要权限拦截的接口uri */ public static List<String> checkApis = new ArrayList<>(); @Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { Map<String, Object> beanMap = ctx.getBeansWithAnnotation(RestController.class); if (beanMap != null) { for (Object bean : beanMap.values()) { Class<?> clz = bean.getClass(); Method[] methods = clz.getMethods(); for (Method method : methods) { if (method.isAnnotationPresent(EnableAuth.class)) { String uri = getApiUri(clz, method); checkApis.add(uri); } } } } } private String getApiUri(Class<?> clz, Method method) { StringBuilder uri = new StringBuilder(); uri.append(clz.getAnnotation(RequestMapping.class).value()[0]); if (method.isAnnotationPresent(GetMapping.class)) { uri.append(method.getAnnotation(GetMapping.class).value()[0]); } else if (method.isAnnotationPresent(PostMapping.class)) { uri.append(method.getAnnotation(PostMapping.class).value()[0]); }// else if (method.isAnnotationPresent(PutMapping.class)) {// uri.append(method.getAnnotation(PutMapping.class).value()[0]);// }else if (method.isAnnotationPresent(DeleteMapping.class)) {// uri.append(method.getAnnotation(DeleteMapping.class).value()[0]);// } else if (method.isAnnotationPresent(RequestMapping.class)) { uri.append(method.getAnnotation(RequestMapping.class).value()[0]); } return uri.toString(); } }
当一个类实现了ApplicationContextAware接口之后,Spring容器会自动检测所有的Bean,如果发现某个Bean实现了ApplicationContextAware接口,Spring容器就会在创建了该Bean之后,自动调用该Bean的setApplicationContextAware()方法。
在setApplicationContextAware()方法中获取所有带有@RestController注解的类,并获取该类下所有带有@EnableAuth注解的方法,获取该方法@RequestMapping的uri路径,并将uri存入checkApis中。
public class ApiFilter implements Filter { @Autowired private RedisService redisService; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; resp.setCharacterEncoding("utf-8"); resp.setContentType("application/json;charset=utf-8"); // 判断checkApis中是否包含当前请求的uri if (ApiAuthDataInit.checkApis.contains(req.getRequestURI())) { String key = "Devices:xxxxxxx"; CarsDto carsDto = redisService.getCar(key); Calendar c = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String time = carsDto.getDevFixTime().substring(0,10); Date date = null; try { date = sdf.parse(time); } catch (ParseException e) { e.printStackTrace(); } //设置日历时间 c.setTime(date); c.add(Calendar.MONTH,18); String strDate = sdf.format(c.getTime()); String nowDate = sdf.format(new Date()); long nowTime = Long.valueOf(nowDate.replaceAll("[-\s:]","")); long carTime = Long.valueOf(strDate.replaceAll("[-\s:]","")); if (nowTime > carTime) { return; } } chain.doFilter(request, response); } @Override public void destroy() { } }
定义了注解,并在需要的时候给相关类,类属性加上注解信息,如果没有响应的注解信息处理流程,注解可以说是没有实用价值。
四、使用自定义注解
@EnableAuth @PostMapping(value = "/login") public ResultData login(String username, String password) { logger.info("------用户登录 login:{} start", "username:" + username + "password:" + password); User user = userDao.findByUserName(username); if (user == null) { logger.info("------user == null"); return ResultData.bizError(ResultMessage.USER_NOT_FIND); } if (!user.getStatus().equals(UserStatus.ACTIVE.getCode())) { logger.info("------user已删除"); return ResultData.bizError(ResultMessage.USER_NOT_FIND);//被禁止的异常 } if (!loginService.checkUser(user, password)) { logger.info("------用户名或密码不正确"); return ResultData.bizError(ResultMessage.USER_PASSWORD_EQUAL); } // 设置登陆时间 logger.info("------更新登录时间"); loginService.setLoginTime(user); logger.info("------用户登录 login:{} 登录成功", "user:" + new Gson().toJson(user)); LoginUser loginUser = new LoginUser(); //略 logger.info("------用户登录 login:{} end", "loginUser:" + new Gson().toJson(loginUser)); return ResultData.ok().putDataValue("User", loginUser); }
五、vue部分代码
<template> <div id="login"> <div class="loginBox"> <div class="loginForm"> <div class="formHeader"> <span class="title">欢迎登录</span> </div> <el-form :model="loginForm" ref="loginForm" :rules="loginFormRule"> <el-form-item label="" prop="username"> <i class="iconfont icon-touxiang"></i> <el-input v-model="loginForm.username" placeholder="用户名" auto-complete="off"></el-input> </el-form-item> <el-form-item label="" prop="password"> <i class="iconfont icon-mima"></i> <el-input v-model="loginForm.password" placeholder="密码" type="password" auto-complete="off"></el-input> </el-form-item> <el-form-item> <el-button class="loginBtn" type="primary" @click="login">登录</el-button> </el-form-item> </el-form> </div> </div> </div></template> <script> export default { data () { //略 }, //略 methods: { //略 login(){ this.$refs['loginForm'].validate((valid) => { if(valid){ this.api.login(this.loginForm).then((res)=> { if (res.data.code=='OK') { this.$message({ type:'success', message:'登录成功!' }); this.$store.commit('signIn', res.data.data.User); } else { this.$message.error(res.data.message); } }).catch((res)=>{ this.$message.error(res); }); } }); } } }</script>