记录用户操作字段变更实现方法(写于除夕,西西)
文章目录前言show y the code like the s***自定义注解切面逻辑遍历属性dtocontroller改进地方github最后前言之前技术群里有一个大佬提出一个需求:记录用户的操作。一个看似简单又复杂的需求,我开始头脑风暴:前端对比记录后端进行对比 其中少不了对比的过程。这时群里那个大佬提出个更有建设性的想法,通配以后的这种需求,而不是每次都去复制一遍之前的逻辑......
大家好,我叫大鸡腿,大家可以关注下我,会持续更新技术文章还有人生感悟,感谢~
前言
之前技术群里有一个大佬提出一个需求:记录用户的操作。一个看似简单又复杂的需求,我开始头脑风暴:
- 前端对比记录
- 后端进行对比
其中少不了对比的过程。这时群里那个大佬提出个更有建设性的想法,通配以后的这种需求,而不是每次都去复制一遍之前的逻辑,也就是重复工作。他认为跟业务没有关联的,都可以进行抽象,供以后使用。
这个是很有意义的idea,值得我们平时工作中去学习。从而提高我们日常开发。就像之前看到一位大佬总结:28原则,把20%的精力处理crud,80%时间处理以后对团队,对整个技术有影响的东西,这个才是更有价值的。
下面这个是参考肥朝哥的一个想法,使用自定义注解,在需要对比的字段上加上注解,查询的时候写到redis。编辑的时候,将缓存key传过来,跟之前的缓存对比,对比后的差异存数据库,删缓存~
show y the code like the s***
我写的代码都不咋滴,其实,西西。看看就好~
自定义注解
注解在方法上,因为我要使用aop。
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
@Documented
public @interface MyMethod {
Class toClass() ;
}
注解到字段上
@Target(ElementType.FIELD) // 注解用于字段上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
public @interface MyField {
}
切面逻辑
import com.alibaba.fastjson.JSON;
import com.example.demo.annotation.MyField;
import com.example.demo.annotation.MyMethod;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author M
*/
@Aspect
@Component
public class TestAspect {
@Autowired
private StringRedisTemplate template;
private static ThreadLocal<String> cacheKey = new ThreadLocal<>();
@Pointcut("@annotation(com.example.demo.annotation.MyMethod)")
public void annotationPoinCut() {
}
@Before(value = "annotationPoinCut()")
public void beforeTest(JoinPoint point) throws IllegalAccessException {
//获取方法签名
MethodSignature signature = (MethodSignature) point.getSignature();
//获取切入方法的对象
Method method = signature.getMethod();
//获取方法上的Aop注解
MyMethod myMethod = method.getAnnotation(MyMethod.class);
String args = JSON.toJSONString(point.getArgs());
if (args.length() > 1) {
Object dto = JSON.parseObject(args.substring(1, args.length() - 1), myMethod.toClass());
xx(dto, myMethod.toClass());
}
}
private void xx(Object dto, Class class1) throws IllegalAccessException {
// 获取所有字段
Object lastDto = null;
for (Field f : class1.getDeclaredFields()) {
f.setAccessible(true);
//System.out.println("f:" + f.getName());
// 判断这个字段是否有MyField注解
if ("key".equals(f.getName()) && !StringUtils.isEmpty(f.get(dto))) {
lastDto = JSON.parseObject(template.opsForValue().get(f.get(dto)), class1);
cacheKey.set(String.valueOf(f.get(dto)));
}
System.out.println(lastDto);
if (f.isAnnotationPresent(MyField.class) && lastDto != null) {
Object value = f.get(dto);
Object value1 = f.get(lastDto);
if (value != value1) {
System.out.println("存在不同,之前的值为:" + value1 + ",后面的值:" + value);
}
}
}
}
@After(value = "annotationPoinCut()")
public void afterTest(JoinPoint point) {
System.out.println(cacheKey.get());
template.delete(cacheKey.get());
cacheKey.remove();
}
}
讲解
先切到方法上的MyMethod,里面toClass是为后面实例化对象用的,然后获取请求参数,回传给对象里面的值。根据类遍历属性Field,再拿到属性上的有相关的注解@MyField,获取属性的值,进行对比。期间使用ThreadLocal保持redis 的缓存之前的数据,处理之后再删除缓存。
遍历属性
public static void main(String[] args) {
// 获取类模板
Class c = DajituiDTO.class;
// 获取所有字段
for (Field f : c.getDeclaredFields()) {
// 判断这个字段是否有MyField注解
if (f.isAnnotationPresent(MyField.class)) {
MyField annotation = f.getAnnotation(MyField.class);
System.out.println("字段:[" + f.getName() + "]");
}
}
}
dto
@AllArgsConstructor
@Data
@NoArgsConstructor
public class DajituiDTO {
private String key;
@MyField
private String name;
private Integer age;
public static void main(String[] args) {
// 获取类模板
Class c = DajituiDTO.class;
// 获取所有字段
for (Field f : c.getDeclaredFields()) {
// 判断这个字段是否有MyField注解
if (f.isAnnotationPresent(MyField.class)) {
MyField annotation = f.getAnnotation(MyField.class);
System.out.println("字段:[" + f.getName() + "]");
}
}
}
}
controller
import com.alibaba.fastjson.JSON;
import com.example.demo.annotation.MyMethod;
import com.example.demo.dto.DajituiDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
/**
* @author M
*/
@RestController
public class TestController {
@Autowired
private StringRedisTemplate template;
@GetMapping("search")
public String getSomeThing() {
DajituiDTO dto = new DajituiDTO("大鸡腿", null, 18);
String key = getTheKey("search");
dto.setKey(key);
template.opsForValue().set(key, JSON.toJSONString(dto), 10, TimeUnit.MINUTES);
return dto.toString();
}
@GetMapping("edit")
//@MyMethod(toClass = DajituiDTO.class)
public String editSomeThing(DajituiDTO dto) {
//修改并显示有注解字段的改变情况
return dto.toString();
}
@PostMapping("edit1")
@MyMethod(toClass = DajituiDTO.class)
public String editSomeThingPost(@RequestBody DajituiDTO dto) {
//修改并显示有注解字段的改变情况
return dto.toString();
}
private String getTheKey(String requestUrl) {
return System.currentTimeMillis() + requestUrl + ThreadLocalRandom.current().nextInt();
}
}
请求链接
http://localhost:8080/search 保存信息
http://localhost:8080/edit?key=1579857090663search397251701&name=123
这个key的话需要第一个链接回传的key作为参数
或者post请求http://localhost:8080/edit1
{
"key": "1579858080821search1554559961",
"name": "123"
}
输出
DajituiDTO(key=1579858080821search1554559961, name=null, age=18)
DajituiDTO(key=1579858080821search1554559961, name=null, age=18)
存在不同,之前的值为:null,后面的值:123
DajituiDTO(key=1579858080821search1554559961, name=null, age=18)
1579858080821search1554559961
可以看到之前name没有值,后面修改成123.
改进地方
缓存key的生成
private String getTheKey(String requestUrl) {
return System.currentTimeMillis() + requestUrl + ThreadLocalRandom.current().nextInt();
}
为啥这里使用时间戳?因为当时大家伙在讨论并发操作的时候如何对比,这里有点像一个东西的版本。比如一个iphone 1代,我们要对比就得对比它的1代,如果我们对比其他iphone代,那么久没有对比的意义了。
其次考虑到可能真的什么时间什么的都刚刚好,可以再加个用户名称什么的顶上去。
切面逻辑
在哪一块逻辑上有些需要判空,以及类型转换这样,还需要改进~
github
最后
这是本鸡腿在除夕写的一篇博客,有时不足,各位大佬多多指点。其次无论何时何地,只要有空就要不断努力学习,欧力给~
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)