前言
公司前端项目用的是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)
@Documented
public @interface EnableAuth {
}
public class ApiAuthDataInit implements ApplicationContextAware {
/** 存放需要权限拦截的接口uri */
public static List<String> checkApis = new ArrayList<>();
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 {
private RedisService redisService;
public void init(FilterConfig filterConfig) throws ServletException {
}
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);
}
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>