当前位置: 首页 > news >正文

新乡河南网站建设如何提高网站在搜索引擎中的排名

新乡河南网站建设,如何提高网站在搜索引擎中的排名,学校校园网站使用,杭州竞彩网站开发这里写目录标题目标:类图(聚合关系)使用到的工具类可获取容器的实例的工具类参数处理工具类获取两个对象不同的属性实现代码:注解顶层抽象具体实现(其一)切面总结2.0版切面around目标: 最近公司有需求需要记录公司活动的修改日志,我就在想能…

这里写目录标题

  • 目标:
  • 类图(聚合关系)
  • 使用到的工具类
      • 可获取容器的实例的工具类
      • 参数处理工具类
      • 获取两个对象不同的属性
  • 实现代码:
      • 注解
      • 顶层抽象
      • 具体实现(其一)
      • 切面
  • 总结
  • 2.0版
    • 切面around

目标:

最近公司有需求需要记录公司活动的修改日志,我就在想能不能做一个通用的日志记录

本篇代码较多, 主要是觉得光说不练假把式, 且为了同学可以拿走自己跑起来

  • 目标

    • 需要有日志记录操作的点支持自定义
    • 需要记录的内容(字段)支持自定义
  • 计划

    • 使用切面完成修改记录
    • 切点由自定义的注解定位,切点的属性
      • 参数 id params
      • 需要记录的字段 needSaveField
      • 日志类型
      • 是否全部字段需要记录
      • 不需要记录的字段 unSaveField1
      • 获取修改数据的服务 serviceName2

类图(聚合关系)

在这里插入图片描述

从图中可以看出,aspect 调用了两个工具类和baseQueryData的实现类 以及一个枚举(枚举的作用是用来表示日志的类型) 其中baseQueryData定义了两个查询和一个保存日志的方法(规则),并提供了公共的方法,这样不同的日志就可以通过实现query接口提供统一标准的服务

在这里插入图片描述

在这里插入图片描述

使用到的工具类

可获取容器的实例的工具类


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;/*** @author yang* @program * @description 容器工具* @create 2021/06/08 14:01*/
@Component
public class SpringUtil  implements ApplicationContextAware {private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {if(SpringUtil.applicationContext == null) {SpringUtil.applicationContext = applicationContext;}}//获取applicationContextpublic static ApplicationContext getApplicationContext() {return applicationContext;}//通过name获取 Bean.public static Object getBean(String name){return getApplicationContext().getBean(name);}//通过class获取Bean.public static <T> T getBean(Class<T> clazz){return getApplicationContext().getBean(clazz);}//通过name,以及Clazz返回指定的Beanpublic static <T> T getBean(String name,Class<T> clazz){return getApplicationContext().getBean(name, clazz);}}

参数处理工具类

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.CodeSignature;import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** @author yang* @program * @description 通用工具方法* @create 2021/06/09 07:28*/
@Slf4j
public class PointUtil {/*** 通过参数和标签获取storeIds* @param point* @param param* @return*/public static List<Long> getIds(ProceedingJoinPoint point, String param){Map<String, Object> map = getNameAndValue(point);String[] parmas = param.split("\\.");//参数本身为idif(parmas.length==1){Object o = map.get(parmas[0]);if(o instanceof Long){return Arrays.asList((Long)o);}else if(o instanceof List){return (List<Long>) o;}else{return new ArrayList<>();}//参数某个字段为id}else{Object o = map.get(parmas[0]);String parmaName=parmas[1];if(o instanceof List){List<Object> list = (List) o;return list.stream().map(l -> {Long id = getIdByObject(l, parmaName);return id;}).collect(Collectors.toList());}else{return Arrays.asList(getIdByObject(o,parmaName));}}}/*** 通过反射获取id* @param obj* @param paramsName;* @return*/public static  Long getIdByObject(Object obj,String paramsName) {try {Class<?> aClass = obj.getClass();String methodName = "get"+paramsName.substring(0, 1).toUpperCase() + paramsName.substring(1);Method method = aClass.getMethod(methodName);return (Long) method.invoke(obj);}catch (Exception e){log.error("无法通过反射获得参数",e);// 这个异常就是自己定义的运行时异常, 可以自定义throw new RrException("无法通过反射获得参数:"+paramsName);}}/*** 获取参数Map集合* @param joinPoint* @return*/public static Map<String, Object> getNameAndValue(ProceedingJoinPoint joinPoint) {Map<String, Object> param = new HashMap<>();Object[] paramValues = joinPoint.getArgs();String[] paramNames = ((CodeSignature)joinPoint.getSignature()).getParameterNames();for (int i = 0; i < paramNames.length; i++) {param.put(paramNames[i], paramValues[i]);}return param;}}

获取两个对象不同的属性


import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.*;/*** @author dongwang* @version V1.0* @since 2019-08-24 10:11*/
public class ClassCompareUtils {/*** 比较两个实体属性值,返回一个boolean,true则表时两个对象中的属性值无差异* @param oldObject 进行属性比较的对象1* @param newObject 进行属性比较的对象2* @return 属性差异比较结果boolean*/public static boolean compareObject(Object oldObject, Object newObject) {Map<String, Map<String,Object>> resultMap=compareFields(oldObject,newObject);if(resultMap.size()>0) {return true;}else {return false;}}/*** 比较两个实体属性值,返回一个map以有差异的属性名为key,value为一个Map分别存oldObject,newObject此属性名的值* @param oldObject 进行属性比较的对象1* @param newObject 进行属性比较的对象2* @return 属性差异比较结果map*/@SuppressWarnings("rawtypes")public static Map<String, Map<String,Object>> compareFields(Object oldObject, Object newObject) {Map<String, Map<String, Object>> map = null;try{/*** 只有两个对象都是同一类型的才有可比性*/if (oldObject.getClass() == newObject.getClass()) {map = new HashMap<String, Map<String,Object>>();Class clazz = oldObject.getClass();//获取object的所有属性PropertyDescriptor[] pds = Introspector.getBeanInfo(clazz,Object.class).getPropertyDescriptors();for (PropertyDescriptor pd : pds) {//遍历获取属性名String name = pd.getName();//获取属性的get方法Method readMethod = pd.getReadMethod();// 在oldObject上调用get方法等同于获得oldObject的属性值Object oldValue = readMethod.invoke(oldObject);// 在newObject上调用get方法等同于获得newObject的属性值Object newValue = readMethod.invoke(newObject);if(oldValue instanceof List && newValue instanceof List){Object[] oldArray = null;Object[] newArrays = null;if (oldValue != null && ((List) oldValue).size() > 0) {if (((List) oldValue).get(0) instanceof Long) {oldArray = ((List) oldValue).toArray(new Long[((List) oldValue).size()]);}if (((List) oldValue).get(0) instanceof String) {oldArray = ((List) oldValue).toArray(new String[((List) oldValue).size()]);}}if (newValue != null && ((List) newValue).size() > 0) {if (((List) newValue).get(0) instanceof Long) {newArrays = ((List) newValue).toArray(new Long[((List) newValue).size()]);}if (((List) newValue).get(0) instanceof String) {newArrays = ((List) newValue).toArray(new String[((List) newValue).size()]);}}if (!(oldArray == null && newArrays == null) && !Arrays.deepEquals(oldArray, newArrays)) {Map<String,Object> valueMap = new HashMap<String,Object>();valueMap.put("oldValue",oldValue);valueMap.put("newValue",newValue);map.put(name, valueMap);}}if(oldValue instanceof Timestamp){oldValue = new Date(((Timestamp) oldValue).getTime());}if(newValue instanceof Timestamp){newValue = new Date(((Timestamp) newValue).getTime());}if(oldValue == null && newValue == null){continue;}else if(oldValue == null && newValue != null){Map<String,Object> valueMap = new HashMap<String,Object>();valueMap.put("oldValue",oldValue);valueMap.put("newValue",newValue);map.put(name, valueMap);continue;}//比较这两个值是否相等,不等就可以放入map了if (!oldValue.equals(newValue)) {Map<String,Object> valueMap = new HashMap<String,Object>();valueMap.put("oldValue",oldValue);valueMap.put("newValue",newValue);map.put(name, valueMap);}}}}catch(Exception e){e.printStackTrace();}return map;}}

实现代码:

注解


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UpdateActivityTag {/*** 主键id所对应的参数名称* 如果参数本身为 id 或者 id List 则直接传参数名* 如果参数某个字段为 id 则传 参数名.字段名* @return*/String idsParmas() default "";/*** 日志类型,可根据业务需求自定义* @return*/int logType() default 0;/*** 服务名** @return*/String serviceName() default "";/*** 需要校验保存的服务名 如果和unSaveField 同时存在 以需要的为准** @return*/String[] needSaveField() default {};/*** 不需要校验保存的服务名** @return*/String[] unSaveField() default {};}
注解中的属性都是要在切面中使用到的

顶层抽象


import com.shuguolili.web.common.aspect.UpdateActivityTag;
import com.shuguolili.web.utils.ClassCompareUtils;
import org.springframework.scheduling.annotation.Async;import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;/*** @author yang* @program * @description 查询数据的顶层接口* @create 2021/06/08 14:20*/public abstract class BaseQueryData {public abstract Object QueryData(Long id);public abstract Object QueryData(List<Long> ids);@Asyncpublic abstract void saveCompareLog(Object oldData, Object newData, Long recordId, String operName, Long operId, UpdateActivityTag tag);public Map<String,Map<String, Object>> getUpdateLogDTO(Object oldData, Object newData, UpdateActivityTag tag) {Map<String,Map<String, Object>> result = new HashMap<>();Map<String, Map<String, Object>> maps = ClassCompareUtils.compareFields(oldData, newData);if (maps.size() > 0) {Iterator<String> keys = maps.keySet().iterator();Map<String, Object> beforeMap = new HashMap<>();Map<String, Object> afterMap = new HashMap<>();while (keys.hasNext()) {String key = keys.next();if (checkNeedSave(key,tag)) {Map<String, Object> values = maps.get(key);String oldValue = getValue(values.get("oldValue"));String newValue = getValue(values.get("newValue"));beforeMap.put(key,oldValue);afterMap.put(key,newValue);}}if(afterMap.size() > 0) {result.put("after",afterMap);}if(beforeMap.size() > 0 ) {result.put("before",beforeMap);}}return result;}private boolean checkNeedSave(String key,UpdateActivityTag tag) {//如果是所有的字段都有保存,直接返回trueif(tag.allSave()){return true;}if(tag.needSaveField() != null && tag.needSaveField().length > 0){List<String> fields = Arrays.asList(tag.needSaveField());return  fields.contains(key);}if(tag.unSaveField() != null && tag.unSaveField().length > 0){List<String> fields = Arrays.asList(tag.unSaveField());return  !fields.contains(key);}return false;}private String getValue(Object object) {String value = "";if (object != null) {if (object instanceof Boolean) {Boolean flag = (Boolean) object;value = flag ? "是" : "否";} else {value = object.toString();}}return value;}}

具体实现(其一)

只用一个来举例,如果有多个日志需要保存, 新增实现即可

import com.shuguolili.api.model.dto.UpdateLogDTO;
import com.shuguolili.api.service.read.IActivityInfoReadService;
import com.shuguolili.api.service.write.IUpdateLogWriteService;
import com.shuguolili.utils.JsonUtils;
import com.shuguolili.web.common.aspect.UpdateActivityTag;
import com.shuguolili.web.service.update.BaseQueryData;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Map;/*** @author yang* @program * @description 活动的查询接口* @create 2021/06/08 14:21*/
@Service("activityInfoDataImpl")
public class ActivityInfoDataImpl extends BaseQueryData {//实现类就是业务(保存日志)代码@Reference(version = "1.0.0", group = "${dubbo.service.group}", check = false)private IActivityInfoReadService activityInfoReadService;@Reference(version = "1.0.0", group = "${dubbo.service.group}", check = false)private IUpdateLogWriteService updateLogWriteService;@Overridepublic Object QueryData(Long id) {return activityInfoReadService.findDetailById(id);}@Overridepublic Object QueryData(List<Long> ids) {return activityInfoReadService.listByIds(ids.toArray(new Long[0]));}@Overridepublic void saveCompareLog(Object oldData, Object newData, Long recordId, String operName, Long operId, UpdateActivityTag tag) {if(oldData == null || newData == null){return;}//根据数据中的自动的注解判断注解是否需要记录修改日志Class<?> aClass = oldData.getClass();if(!aClass.equals(newData.getClass())){return;}//old 和 new 同种对象UpdateLogDTO dto = new UpdateLogDTO();//被修改的iddto.setRecordId(recordId);//日志类型,通过注解的tag.logType 传递dto.setDataType(tag.logType());//操作人iddto.setOperatorId(operId);//操作人姓名dto.setOperatorName(operName);//通过getUpdateLogDTO 获得被修改字段的修后值Map<String, Map<String, Object>> updateLogDTO = getUpdateLogDTO(oldData, newData, tag);Map<String, Object> before = updateLogDTO.get("before");Map<String, Object> after = updateLogDTO.get("after");// 将数据转换为json 这个工具比较常见就没有贴代码if(before!= null && before.size()>0){dto.setBeforData(JsonUtils.toJsonString(before));}if(after!= null && after.size()>0){dto.setAfterData(JsonUtils.toJsonString(after));}//保存if(dto.getAfterData() != null || dto.getBeforData() != null){updateLogWriteService.save(dto);}}
}

切面


//定义日志类型的枚举
import com.shuguolili.api.model.enums.UpdateLogTypeEnum;
//这里有引用两个工具类
import com.shuguolili.web.common.util.PointUtil;
import com.shuguolili.web.common.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;/*** @author yang* @program * @description 修改日志* @create 2021/06/08 11:11*/
@Aspect
@Component
@Slf4j
public class ActivityInfoUpdateLongAspect {@Pointcut("@annotation(com.shuguolili.web.common.aspect.UpdateActivityTag)")public void logPointCut() {}@Around("logPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();Parameter[] parameters = method.getParameters();UpdateActivityTag tag = method.getAnnotation(UpdateActivityTag.class);//通过注解 定位 参数  和 具体处理的实现类别名//根据别名找到spring注册过得的服务bean实例,bean实例通过参数查询数据List<Long> ids = PointUtil.getIds(point,tag.idsParmas());BaseQueryData query = (BaseQueryData)SpringUtil.getBean(tag.serviceName());//变更前Object oldData = query.QueryData(ids.get(0));//执行方法Object result = point.proceed();//变更后Object newData = query.QueryData(ids.get(0));//异步保存,通过自己的实现的服务是将修改前和修改后的数据保存到数据库就OK了query.saveCompareLog(oldData,newData,ids.get(0), CurrentAdminUtil.getCurrentName(),CurrentAdminUtil.getCurrentId(),tag);return result;}}
  • 根据注解中提供的要素:
    • 参数id/ids
    • 需要/不需要校验保存的字段,
    • spring注册的实例名,在切面中通过工具类获取服务,根据id分别查询修改后和修改前的数据, 调服务进行校验并保存日志记录
    • 日志类型
    • 是否所有字段都需要校验保存

总结

  1. 通过切面编程将需要有日志记录的地方统一加上日志记录(侵入性低)
  2. 通过注解达到不同的数据校验保存不同的字段变化(灵活性高)
  3. 通过抽象规范顶层规则,便于扩展(扩展性高)

如果对你有帮助希望点个赞, 鼓励下
另:如果考虑用到自己项目,有需要可以评论, 思想1+1 > 2 .


2.0版

  1. 在注解中添加新的属性serverClass,支持通过类名来指定服务的类名,方便功能扩展,降低了同事的学习成本
  2. 将通过@Async实现的异步替换为CompletableFuture.runAsync的方式实现,为抽取为starter 做铺垫
  3. 提供默认的class实现,并对异常信息处理,达到无侵入性,用户没有指定,不会影响原功能
	/*** 服务类** @return*/Class serviceClass() default DefaultUpdateImpl.class;

切面around

    @Around("logPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();UpdateLogTag tag = method.getAnnotation(UpdateLogTag.class);List<Long> ids = PointUtil.getIds(point,tag.idsParmas());BaseQueryData query = null;try {if (StringUtils.isNotBlank(tag.serviceName())) {query = (BaseQueryData) SpringUtil.getBean(tag.serviceName());} else {query = (BaseQueryData) SpringUtil.getBean(tag.serviceClass());}} catch (Exception e) {log.error("获取修改日志处理类失败,{}", ExceptionUtils.getStackTrace(e));}//变更前Object oldData =null;if(query != null) {oldData = query.QueryData(ids.get(0));}//执行方法Object result = point.proceed();//变更后if(query != null) {Object newData = query.QueryData(ids.get(0));//异步保存Object finalOldData = oldData;String currentName = CurrentAdminUtil.getCurrentName();Long currentId = CurrentAdminUtil.getCurrentId();BaseQueryData finalQuery = query;CompletableFuture.runAsync(() -> finalQuery.saveCompareLog(finalOldData,newData,ids.get(0), currentName,currentId,tag));}return result;}

后续有时间和机会,会将改功能抽取为stater ,提供通用的日志插件


  1. 需要记录的字段和不需要记录的字段互斥,当两个都存在时以需要记录的字段为准 ↩︎

  2. serviceName 是通过id params查询到别修改的数据的服务,且服务都是继承了顶层接口的实现类, 这样统一服务规则 ↩︎

http://www.lbrq.cn/news/2570293.html

相关文章:

  • 株洲市政府网官网网站优化主要优化哪些地方
  • 网店运营招聘要求百度seo工具
  • 上海哪家做网站关键词排名北京企业网站seo平台
  • 浙江网站建设品牌升级汕头网站制作设计
  • 做公益做的好的的网站济南seo网络优化公司
  • 男的做直播哪个网站友情链接检测平台
  • 青岛网站建设ihuibest深圳正规seo
  • 微网站难做么合肥优化营商环境
  • wordpress导航栏的文件在哪太原seo自媒体
  • 自己电脑怎样做网站关键词快速排名seo怎么优化
  • 备案怎么关闭网站整合营销传播的方法包括
  • 成都市武侯区建设局门户网站链接购买
  • 平面网页设计培训宁波网站关键词优化代码
  • 传媒的域名做个什么网站上海seo优化服务公司
  • 湖南省住房和建设厅网站山西seo
  • 北京网站制作报价域名解析查询站长工具
  • 做网站要花钱吗seo引擎优化是做什么的
  • 网站个人建设亚马逊排名seo
  • 什么2007做视频网站服装品牌营销策划方案
  • 哪里有做网站app的泰州seo平台
  • 自己做衣服网站网络营销策划的目的
  • 做网站的系统功能需求产品推广文案范文
  • 网站建设设计设计成都纯手工seo
  • 淘宝网首页官网登录焦作seo推广
  • 那些网站可以接私活做seo网络推广企业
  • 免费个人简历制作网站怎么网络推广自己业务
  • 网页设计要多少钱电脑系统优化工具
  • 网站建设一条龙ue365企业文化标语
  • 肥西网站建设教育培训机构有哪些
  • 网站pv统计方法站长之家音效
  • 零基础 “入坑” Java--- 十六、字符串String 异常
  • 搜索与图论(最小生成树 二分图)
  • 【DeepSeek-R1 】分词系统架构解析
  • 下载一个JeecgBoot-master项目 导入idea需要什么操作启动项目
  • Vue 详情模块 1
  • 第七章:进入Redis的SET核心