spring boot 使用AOP+自定义注解+反射实现操作日志记录修改前数据和修改后对比数据,并保存至日志表

news2025/2/27 20:46:22

一、添加aop starter依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

二:自定义字段翻译注解。(修改功能时,需要显示如某字段修改前为张三,修改后为李四,name字段对应的中文注释)

package com.test.common.annotation;

import java.lang.annotation.*;

/**
 * 写入日志表时,字段对应的中文注释
 */
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target({ElementType.FIELD,ElementType.METHOD})//定义注解的作用目标**作用范围字段、枚举的常量/方法
@Documented                 //说明该注解将被包含在javadoc中
public @interface FieldMeta {
    /**
     * 汉字全称
     * @return
     */
    String value();
}

使用FieldMeta自定义注解,看个人业务自行觉得是否需要重新定义实体

package com.test.customer.domain;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;

import com.test.common.annotation.FieldMeta;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.test.common.core.domain.BaseEntity;


public class TestDangAnDetail extends BaseEntity {
    private static final long serialVersionUID = 1L;

    /**
     * 次数
     */
   
    @FieldMeta("总次数")
    private Long CountNum;
    
}

parseClass调用查询方法的类(查询数据工具类)

//抽象类
public interface ContentParser {
    
        /**
         * 获取信息返回查询出的对象
         *
         * @param joinPoint       查询条件的参数
         * @param dbAccessLogger 注解
         * @return 获得的结果
         */
        Object getOldResult(JoinPoint joinPoint, DBAccessLogger dbAccessLogger,String sqlSessionFactoryName);
    
        /**
         * 获取信息返回查询出的对象
         *
         * @param joinPoint       查询条件的参数
         * @param dbAccessLogger 注解
         * @return 获得的结果
         */
        Object getNewResult(JoinPoint joinPoint, DBAccessLogger dbAccessLogger,String sqlSessionFactoryName);
    
    }

实现类 :通过该实现类获取更新前后的数据。
该实现类的实现原理为:获取入参出入的id值,获取sqlSessionFactory,通过sqlSessionFactory获取selectByPrimaryKey()该方法,执行该方法可获取id对应数据更新操作前后的数据。

    @Component
    public class DefaultContentParse implements ContentParser {
        /**
         * 获取更新方法的第一个参数解析其id
         * @param joinPoint       查询条件的参数
         * @param enableModifyLog 注解类容
         * @return
         */
        @Override
        public Object getOldResult(JoinPoint joinPoint, DBAccessLogger enableModifyLog,String sqlSessionFactoryName) {
            Object info = joinPoint.getArgs()[0];
            Object id = ReflectionUtils.getFieldValue(info, "id");
            Assert.notNull(id,"未解析到id值,请检查入参是否正确");
            Class<?> aClass = enableModifyLog.serviceClass();
            Object result = null;
            try {
                SqlSessionFactory sqlSessionFactory = SpringUtil.getBean(sqlSessionFactoryName);
                Object instance = Proxy.newProxyInstance(
                        aClass.getClassLoader(),
                        new Class[]{aClass},
                        new MyInvocationHandler(sqlSessionFactory.openSession().getMapper(aClass))
                );
                Method selectByPrimaryKey = aClass.getDeclaredMethod("selectByPrimaryKey", Long.class);
                //调用查询方法
                result = selectByPrimaryKey.invoke(instance, id);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return  result;
        }
    
        @Override
        public Object getNewResult(JoinPoint joinPoint, DBAccessLogger enableModifyLog,String sqlSessionFactoryName) {
            return getOldResult(joinPoint,enableModifyLog,sqlSessionFactoryName);
        }
    
    }

三:定义AOP切面

注意:如不返回操作是否成功状态可能会导致前端出现警告,JSON为空不能被解析

import com.test.common.annotation.FieldMeta;
import com.test.common.core.domain.entity.SysUser;
import com.test.common.enums.BusinessStatus;
import com.test.common.enums.OperatorType;
import com.test.common.utils.*;
import com.test.customer.domain.UserDataOperationLogs;
import com.test.customer.service.IUserDataOperationLogsService;
import com.test.framework.service.OperateLog;
import com.test.common.enums.BusinessType;
import com.test.framework.service.ContentParser;
import com.test.system.domain.SysOperLog;
import com.test.system.service.ISysOperLogService;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 拦截@EnableGameleyLog注解的方法
 * 将具体修改存储到数据库中
 */
@Aspect
@Component
@Slf4j
public class OperateAspect {

    @Autowired
    private IUserDataOperationLogsService iUserDataOperationLogsService;

    @Autowired
    private DefaultContentParse defaultContentParse;

    @Autowired
    private ApplicationContext applicationContext;


    // 环绕通知
    @Around("@annotation(operateLog)")
    public Object around(ProceedingJoinPoint joinPoint, OperateLog operateLog) throws Throwable{
        Map<String, Object> oldMap=new HashMap<>();
        UserDataOperationLogs lg = new UserDataOperationLogs();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 当不传默认operateType时 根据Method类型自动匹配
        setAnnotationType(request, operateLog);

        // fixme 1.0.9开始不再提供自动存入username功能,请在存储实现类中自行存储
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        Object[] args=joinPoint.getArgs(); // 请求参数

        // 获取当前的用户
        SysUser currentUser = ShiroUtils.getSysUser();

        lg.setCreateBy(currentUser.getLoginName());
        lg.setStatus(0l);
        lg.setUpdateTime(new Date());
        if (OperateType.UPDATE.equals(operateLog.operateType())) {
            try {
               ContentParser contentParser=(ContentParser) applicationContext.getBean(operateLog.parseclass());
                Object oldObject = contentParser.getResult(joinPoint, operateLog);
                if (operateLog.needDefaultCompare()) {
                    oldMap = (Map<String, Object>) objectToMap(oldObject); // 存储修改前的对象
                    lg.setStoreId((Long) oldMap.get("storeId"));
                    lg.setItemId((Long) oldMap.get("itemId"));
                }
            } catch (Exception e) {
               e.printStackTrace();
                log.error("service加载失败:", e);
            }
        }
        // joinPoint.proceed()执行前是前置通知,执行后是后置通知
        Object object=joinPoint.proceed();
        if (OperateType.UPDATE.equals(operateLog.operateType())) {
            ContentParser contentParser;
            try {
               String responseParams=JSON.toJSONString(object);
               contentParser=(ContentParser) applicationContext.getBean(operateLog.parseclass());
                object = contentParser.getResult(joinPoint, operateLog);
            } catch (Exception e) {
               log.error("service加载失败:", e);
            }
            // 默认不进行比较,可以自己在logService中自定义实现,降低对性能的影响
            if (operateLog.needDefaultCompare()) {
               lg.setContent(defaultDealUpdate(object, oldMap));
            }
            // 如果使用默认缓存 则需要更新到最新的数据
            if(operateLog.defaultCache()
                  && operateLog.parseclass().equals(DefaultContentParse.class)){
                defaultContentParse.updateCache(joinPoint, operateLog,object);
            }
        } else{
           String responseParams=JSON.toJSONString(object);
        }
        //保存当前日志到数据库中
        int logs = iUserDataOperationLogsService.insertUserDataOperationLogs(lg);
        log.info("新增用户操作数据日志成功");
        //返回用户的操作是否成功
        AjaxResult ajaxResult = logs > 0 ? success() : error();
        return ajaxResult;
    }

    private String defaultDealUpdate(Object newObject, Map<String, Object> oldMap){
        try {
            Map<String, Object> newMap = (Map<String, Object>) objectToMap(newObject);
            StringBuilder str = new StringBuilder();
            Object finalNewObject = newObject;
            oldMap.forEach((k, v) -> {
                Object newResult = newMap.get(k);
                if (null!=v && !v.equals(newResult)) {
                    Field field = ReflectionUtils.getAccessibleField(finalNewObject, k);
                    FieldMeta dataName = field.getAnnotation(FieldMeta.class);
                    SysUser currentUser = ShiroUtils.getSysUser();
                    if (null!=dataName) {
                        str.append("【"+currentUser.getLoginName()+"】").append("修改了【").append(dataName.value() +"】").append("将【").append(v).append("】").append(" 改为:【").append(newResult).append("】。\n");
                    }
                }
            });
            return str.toString();
        } catch (Exception e) {
            log.error("比较异常", e);
            throw new RuntimeException("比较异常",e);
        }
    }

    private Map<?, ?> objectToMap(Object obj) {
        if (obj == null) {
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 如果使用JPA请自己打开这条配置
        // mapper.addMixIn(Object.class, IgnoreHibernatePropertiesInJackson.class);
        Map<?, ?> mappedObject = mapper.convertValue(obj, Map.class);
        return mappedObject;
    }

    private void setAnnotationType(HttpServletRequest request,OperateLog modifyLog){
        if(!modifyLog.operateType().equals(OperateType.NONE)){
            return;
        }
        String method=request.getMethod();
        if(RequestMethod.GET.name().equalsIgnoreCase(method)){
            ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.SELECT);
        } else if(RequestMethod.POST.name().equalsIgnoreCase(method)){
            ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.ADD);
        } else if(RequestMethod.PUT.name().equalsIgnoreCase(method)){
            ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.UPDATE);
        } else if(RequestMethod.DELETE.name().equalsIgnoreCase(method)){
            ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.DELETE);
        }
    }
}

或者写成


	//环绕通知切面,切点:DBAccessLogger 更新拦截数据
	@Aspect
    @Component
    @Order(1)
    public class DBAccessLoggerAspect {
    
        // 注入Service用于把日志保存数据库
        @Autowired
        private LogService logService;
        
        @Around("@annotation(com.****.log.DBAccessLogger)") // 环绕通知
        public Object execute(ProceedingJoinPoint pjp) throws Exception {
            // 获得当前访问的class
            Class<?> className = pjp.getTarget().getClass();
            // 获得访问的方法名
            String methodName = pjp.getSignature().getName();
            @SuppressWarnings("rawtypes")
            Class[] argClass = ((MethodSignature) pjp.getSignature()).getParameterTypes();
            // 操作结果,默认为成功
            Long operResult = DictLogConstant.LOGS_OPER_SUCCESS;
            //返回值
            Object rvt = null;
            Method method = className.getMethod(methodName, argClass);
            DBAccessLogger dbAcessLoggerannotation = method.getAnnotation(DBAccessLogger.class);
            String accessTable = dbAcessLoggerannotation.accessTable();
            DBOperationType accessType = dbAcessLoggerannotation.accessType();
            DatabaseEnum databaseEnum = dbAcessLoggerannotation.accessDatasource();
            String accessDatasource = databaseEnum.constName;
            //crd操作直接执行方法
            if (accessType == DBOperationType.DELETE || accessType == DBOperationType.SELECT || accessType == DBOperationType.CREATE) {
                try {
                    rvt = pjp.proceed();
                } catch (Throwable e) {
                    e.printStackTrace();
                    throw new RuntimeException(e.getMessage());
                }
                // 如果没有返回结果,则不将该日志操作写入数据库。
                if (rvt == null) return rvt;
            }
            if ((accessType == DBOperationType.DELETE)
                    && ((CUDResult) rvt).getReturnVal() == 0) {
                operResult = DictLogConstant.LOGS_OPER_FAILURE;
            }
            if (accessTable != null) {
                if (accessType == DBOperationType.SELECT) {
                    Log sysLog = new Log();
					/**
					设置要存放的日志信息
					**/
                    logService.createLog(sysLog);
    
                }
                else if (accessType == DBOperationType.DELETE || accessType == DBOperationType.CREATE) {
                    for (Long recordId : ((CUDResult) rvt).getRecordIds()) {
                        Log sysLog = new Log();
                      	/**
						设置要存放的日志信息
						**/
                        logService.createLog(sysLog);
                    }
                }
                else {
                    //更新操作
                    Log sysLog = new Log();
                    /**
                   设置日志信息
                   **/
                    //获取更行前的数据
                    Map<String, Object> oldMap = null;
                    Object oldObject;
                    try {
                        ContentParser contentParser = SpringUtil.getBean(dbAcessLoggerannotation.parseClass());
                        //获取更行前的数据
                        oldObject = contentParser.getOldResult(pjp, dbAcessLoggerannotation, databaseEnum.sqlsessionName);
                        oldMap = (Map<String, Object>) objectToMap(oldObject);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //执行service
                    Object serviceReturn;
                    try {
                        serviceReturn = pjp.proceed();
                    } catch (Throwable throwable) {
                        throwable.printStackTrace();
                        throw new RuntimeException(throwable.getMessage());
                    }
                    CUDResult crudResult = (CUDResult) serviceReturn;
                    Object afterResult = null;
                    try {
                        ContentParser contentParser = SpringUtil.getBean(dbAcessLoggerannotation.parseClass());
                        //更新后的数据
                        afterResult = contentParser.getNewResult(pjp, dbAcessLoggerannotation, databaseEnum.sqlsessionName);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //修改前后的变化
                    sysLog.setOperation(accessType.getValue());
                    try {
                        String updatedefrent = defaultDealUpdate(afterResult, oldMap);
                        sysLog.setParams(updatedefrent);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                   /**
                   设置日志信息
                   **/
                    logService.createLog(sysLog);
                    return serviceReturn;
                }
            }
    
            if (operResult.longValue() == DictLogConstant.LOGS_OPER_FAILURE) {
                // 当数据库的UPDATE 和 DELETE操作没有对应的数据记录存在时,抛出异常
                throw new DBAccessException(accessType.getValue() + "的数据记录不存在!");
            }
            return rvt;
        }
        private Map<?, ?> objectToMap(Object obj) {
            if (obj == null) {
                return null;
            }
            ObjectMapper mapper = new ObjectMapper();
            mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            //如果使用JPA请自己打开这条配置
            //mapper.addMixIn(Object.class, IgnoreHibernatePropertiesInJackson.class);
            Map<?, ?> mappedObject = mapper.convertValue(obj, Map.class);
            return mappedObject;
        }
    
        /**
         *
         * @param newObject 更新过后的结果
         * @param oldMap 更新前的结果
         * @return
         */
        private String defaultDealUpdate(Object newObject, Map<String, Object> oldMap) {
            try {
                Map<String, Object> newMap = (Map<String, Object>) objectToMap(newObject);
                StringBuilder str = new StringBuilder();
                Object finalNewObject = newObject;
                oldMap.forEach((k, v) -> {
                    Object newResult = newMap.get(k);
                    if (v != null && !v.equals(newResult)) {
                        Field field = ReflectionUtils.getAccessibleField(finalNewObject, k);
                        //获取类上的注解
                        DataName dataName = field.getAnnotation(DataName.class);
                        if (field.getType().getName().equals("java.util.Date")) {
                            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                            if (dataName != null) {
                                str.append("【").append(dataName.name()).append("】从【")
                                        .append(format.format(v)).append("】改为了【").append(format.format(newResult)).append("】;\n");
                            } else {
                                str.append("【").append(field.getName()).append("】从【")
                                        .append(format.format(v)).append("】改为了【").append(format.format(newResult)).append("】;\n");
                            }
                        } else {
                            if (dataName != null) {
                                str.append("【").append(dataName.name()).append("】从【")
                                        .append(v).append("】改为了【").append(newResult).append("】;\n");
                            } else {
                                str.append("【").append(field.getName()).append("】从【")
                                        .append(v).append("】改为了【").append(newResult).append("】;\n");
                            }
                        }
                    }
                });
                return str.toString();
            } catch (Exception e) {
                throw new RuntimeException("比较异常", e);
            }
        }
    }

四:自定义日志注解

package com.test.framework.service;

import com.test.common.utils.IService;
import com.test.common.utils.OperateType;
import com.test.framework.service.impl.DefaultContentParse;

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;

/**
 * 记录编辑详细信息的标注
 * @author 
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OperateLog {

   // 模块名称
   String moduleCode() default "";

   // 操作的类型 可以直接调用OperateType 不传时根据METHOD自动确定
   OperateType operateType() default OperateType.NONE;

   // 获取编辑信息的解析类,目前为使用id获取,复杂的解析需要自己实现,默认不填写则使用默认解析类
    Class parseclass() default DefaultContentParse.class;

    // 查询数据库所调用的class文件
    Class serviceclass() default IService.class;

    // 具体业务操作名称
    String handleName() default "";

    // 是否需要默认的改动比较
    boolean needDefaultCompare() default false;

    // id的类型
    Class idType() default Long.class;

    // 是否使用默认本地缓存
    boolean defaultCache() default false;


}

五、@OperateLog注解使用

/**
 * 修改记录详情
 * @OperateLog:AOP自定义日志注解
 */
@PostMapping("/editSave")
@OperateLog(moduleCode="editSave", operateType= OperateType.UPDATE, handleName="修改档案信息", needDefaultCompare=true)
@ResponseBody
public AjaxResult editSave(TCustomerItemRecDetail tCustomerItemRecDetail){
    return toAjax(testDangAnDetailService.updateTestDangAnDetail(tCustomerItemRecDetail));
}

修改功能时,需要实现我们自定义的IService接口,并重写 selectById 方法,在修改前我们需要根据主键id去数据库查询对应的信息,然后在和修改后的值进行比较。

在这里插入图片描述

六:IService接口

/**
 * 修改时需要记录修改前后的值时,需要根据主键id去查询修改前的值时需要
 * @author zhang
 */
public interface IService<T, S> {
    T selectById(S id);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/990328.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高压放大器在制备功能材料中的应用

高压放大器在制备功能材料中具有广泛的应用。功能材料是一类具有特殊性能和功能的材料&#xff0c;可以在各个领域中发挥重要作用&#xff0c;如能源存储、电子、光电、催化等。高压放大器在制备功能材料的研究和应用中起到关键的作用&#xff0c;本文将介绍高压放大器在制备功…

快速查询快递单号物流,跟踪物流详情

如果你是一位经常网购的人&#xff0c;那么你一定对快递单号物流查询的过程不陌生。每次收到快递&#xff0c;我们都会在快递官网或者第三方平台上输入快递单号进行查询&#xff0c;整个过程比较繁琐。但是&#xff0c;现在有一款名为“固乔快递查询助手”的软件&#xff0c;可…

WRFDA资料同化实践技术应用

数值预报已经成为提升预报质量的重要手段&#xff0c;而模式初值质量是决定数值预报质量的重要环节。资料同化作为提高模式初值质量的有效方法&#xff0c;成为当前气象、海洋和大气环境和水文等诸多领域科研、业务预报中的关键科学方法。资料同化新方法的快速发展&#xff0c;…

向量数据库Milvus Cloud 核心组件Knowhere升级,支持 GPU 索引和 Cosine 相似性类型

熟悉我们的朋友都知道,在 Milvus Cloud和 Zilliz Cloud 中,有一个至关重要的组件——Knowhere。 Knowhere 是什么?如果把向量数据库整体看作漫威银河护卫队宇宙,那么 Knowhere 就是名副其实的总部,它的主要功能是对向量精确搜索其最近邻或通过构建索引进行低延迟、近似…

解决Microsoft Edge无法正常运行的有效方案分享!

Microsoft Edge打不开或不能加载网页是用户在Windows 10、Android、Mac和iOS设备上的网络浏览器上遇到的许多错误之一。其他Microsoft Edge问题可能包括浏览器窗口和选项卡冻结、网站崩溃、互联网连接错误消息以及丢失Microsoft Edge书签、收藏夹、密码和收藏。 Microsoft Edg…

树的基本概念和存储结构

一、树的基本概念 1、树的定义 树是n&#xff08;n>0&#xff09;个结点的有限集。当n 0时&#xff0c;称为空树。在任意一棵非空树中应满足&#xff1a; 1、有且仅有一个特定的称为根的结点。 2、当n>1时&#xff0c;其余节点可分为m&#xff08;m>0&#xff09…

AFL模糊测试

一、AFL简介 AFL&#xff08;American Fuzzy Lop&#xff09;号称是当前最高级的Fuzzing 测试工具之一 &#xff0c;是由安全研究员Michał Zalewski&#xff08;lcamtuf&#xff09;开发的一款 基于覆盖引导&#xff08;Coverage-guided&#xff09;的 模糊测试工具&#xff0…

CentOS系统环境搭建(十九)——CentOS7安装chat GPT

centos系统环境搭建专栏&#x1f517;点击跳转 CentOS7安装chat GPT Welcome to the AI era! 基于上一篇文章CentOS系统环境搭建&#xff08;十八&#xff09;——CentOS7安装Docker20.10.12和docker compose v2&#xff0c;你已经安装了docker20以上的版本。那么&#xff0…

代码随想录第30天 | ● 860.柠檬水找零 ● 406.根据身高重建队列 ● 452. 用最少数量的箭引爆气球

860.柠檬水找零 /*** param {number[]} bills* return {boolean}*/ var lemonadeChange function(bills) {let d50let d100let d200for(let i0;i<bills.length;i){if(bills[i]5){d51}else if(bills[i]10){if(d5>1){d5--d10}elsereturn false}else{if(d5>1&&…

圣杯交易系统选股公式,ADX指标和EMA均线配合使用

圣杯交易系统是由琳达布拉德福德拉希克(Linda Bradford Raschke)开发的&#xff0c;她是杰克施瓦格《新金融怪杰》书中的访谈对象。圣杯策略的目标是发现强劲趋势确立后的首次回撤&#xff0c;依据平均趋向指标ADX和指数移动平均线EMA的协同作用来确定趋势强度和合适的交易区域…

Vue中extend基本用法

1.Vue.extend(options) 参数: {Object} options用法&#xff1a; 使用基础Vue构造器&#xff0c;创建一个"子类"。参数是一个包含组件选项的对象。 data选项是特例&#xff0c;需要注意&#xff0c;在Vue.extend()中它必须是函数。 <html><head><tit…

武汉凯迪正大—直读激光盐密灰密测试仪

一、凯迪正大—绝缘子灰密盐密测试仪产品概述 凯迪正大绝缘子灰密盐密测试仪采用的检测技术将灰密测试与盐密测试合二为一&#xff0c;可同时检测出被测绝缘子的灰密度和盐密度&#xff0c;简化了绝缘子污秽检测的流程&#xff0c;适合在巡检现场和实验室使用。 二、凯迪正大…

JDK17:未来已来,你准备好了吗?

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

一元多项式的乘法与加法运算

设计函数分别求两个一元多项式的乘积与和。 输入格式: 输入分2行&#xff0c;每行分别先给出多项式非零项的个数&#xff0c;再以指数递降方式输入一个多项式非零项系数和指数&#xff08;绝对值均为不超过1000的整数&#xff09;。数字间以空格分隔。 输出格式: 输出分2行…

正中优配:散户怎么实现T+0?散户在股市上怎么变相T+0?

T0是指当天买入的标的物&#xff0c;在当天就能卖出的买卖方式&#xff0c;其中&#xff0c;在a股市场上&#xff0c;散户能够通过一些办法直接地完成T0买卖方式&#xff0c;接下来&#xff0c;正中优配为大家预备了相关内容&#xff0c;以供参阅。 散户在股票市场上&#xff0…

Linear Decryption: Rate-1 FHE TLP

参考文献&#xff1a; [ILL89] Russell Impagliazzo, Leonid A. Levin, and Michael Luby. Pseudo-random generation from oneway functions (extended abstracts). In 21st Annual ACM Symposium on Theory of Computing, pages 12–24, Seattle, WA, USA, May 15–17, 1989.…

苹果macOS13Ventura更新体验:新功能带来的全新体验

macOS 13 Ventura 是一款功能强大、界面美观的操作系统。它为用户提供了更好的使用体验&#xff0c;加强了与其他设备的互联互通&#xff0c;提高了隐私和安全性。无论是日常办公还是娱乐&#xff0c;macOS 13 Ventura 都能满足用户的需求&#xff0c;并带来更多的便利和乐趣。…

Linux之history、tab、alias、命令执行顺序、管道符以及exit

目录 Linux之history、tab、alias、命令执行顺序、管道符以及exit history历史命令 格式 参数 修改默认记录历史命令条数 案例 案例1 --- 显示history历史记录中出现次数最高的top10 案例2 --- 增加history显示的时间信息 命令与文件名补全 --- tab 命令别名 格式 案…

线性代数的学习和整理22:矩阵的点乘(草稿)

4 矩阵乘法 A,B两个同阶同秩N阵&#xff0c;看上去结构一样&#xff0c;但两厢相乘&#xff0c;在做在右&#xff0c;地位差别巨大。 在左&#xff0c;你就是基&#xff0c;是空间的根本&#xff0c;是坐标系&#xff0c;是往哪去、能到哪的定海神针&#xff0c;是如来佛手&a…

编程语言排行榜

以下是2023年的编程语言排行榜&#xff08;按照流行度排序&#xff09;&#xff1a; Python&#xff1a;Python一直以来都是非常受欢迎的编程语言&#xff0c;它简洁、易读且功能强大。在数据科学、机器学习、人工智能等领域有广泛应用。 JavaScript&#xff1a;作为前端开发…