aop应用-2-统一日志处理

        关联文章:《AOP编程:理解Spring框架中的切面、通知与切入点》和《aop应用-1 使用注解,返回统一数据格式

        接着,讲统一处理日志。 在平时中,对于一些接口,要记录参数和返回值,这时候,就适合用添加注解,统一去处理。

数据库表:

        要记录参数和返回值,这个记录到数据库里面。 

参数日志表:

CREATE TABLE `common_api_log` (
  `log_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `staff_id` BIGINT(20) DEFAULT NULL COMMENT '操作人ID',
  `create_time` DATETIME DEFAULT NULL COMMENT '创建时间', 
  `log_flag` VARCHAR(12) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '执行状态',
  `log_key` VARCHAR(600) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '日志key',
  `log_type` VARCHAR(150) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '日志类型',
  `can_repeat` VARCHAR(12) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '是否可重复',
  `request_addr` VARCHAR(768) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求地址',
  `handle_method` VARCHAR(1500) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '处理方法',
  `log_class` VARCHAR(150) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '日志类',
  `parent_log_id` BIGINT(15) DEFAULT NULL COMMENT '父级logId',
  `request_type` VARCHAR(600) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求类型',
  `request_count` INT(11) NOT NULL COMMENT '请求次数',
  `log_desc` TEXT COLLATE utf8mb4_bin  COMMENT '请求的参数',
  `request_id` VARCHAR(108) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求id', 
  `request_ip` VARCHAR(192) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求ip',
  `request_method` VARCHAR(1500) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求的方法',
  PRIMARY KEY (`log_id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'aip日志表';

请求结果日志表:

CREATE TABLE `common_api_log_result` (
  `detail_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '详情标识',
  `log_id` BIGINT(20) DEFAULT NULL COMMENT '日志标识',
  `log_result` LONGTEXT COLLATE utf8_bin COMMENT '响应结果',
  `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
  `request_time` DATETIME DEFAULT NULL COMMENT '请求时间',
  `log_flag` VARCHAR(50) COLLATE utf8_bin DEFAULT NULL COMMENT '执行状态',
  `response_time` DATETIME DEFAULT NULL COMMENT '响应时间',
  PRIMARY KEY (`detail_id`) USING BTREE,
  KEY `idx_form_log_id` (`log_id`) USING BTREE
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC COMMENT='api日志操作结果';

代码:

entity

CommonApiLog
@Data
public class CommonApiLog {
    private Long logId;
    private Long staffId;
    private String staffName;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    private String logFlag;
    private String logKey;
    private String logType;
    private String canRepeat;
    private String requestAddr;
    private String requestMethod;
    private String logClass;
    private Long parentLogId;
    private String requestType;
    private Integer requestCount;
    private String logDesc;
    private List<CommonApiLogResult> apiLogDetails; 
}
 CommonApiLogResult
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

@Data
public class CommonApiLogResult {
    private Long detailId;
    private Long logId;
    private String logResult;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date requestTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date responseTime;
    private String logFlag;
}

utils:

SpringUtils
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class SpringUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public SpringUtils() {
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}

StringUtil
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StringUtil {

    public static String emptyStringSort(String... args) {
        for (String arg : args) {
            if (StringUtils.isNotEmpty(arg)) {
                return arg;
            }
        }
        return null;
    }

    public static String nullStringSort(String... args) {
        for (String arg : args) {
            if (arg != null) {
                return arg;
            }
        }
        return null;
    }

    public static boolean isArray(Object object) {
        Class cls = object.getClass();
        if (cls.isArray()) {
            return true;
        }
        return false;
    }

    public static boolean isObject(Object object) {
        if (isArray(object)) {
            return true;
        }
        if (isList(object)) {
            return true;
        }
        if (isMap(object)) {
            return true;
        }
        return false;
    }

    public static boolean isList(Object object) {
        if (object instanceof List) {
            return true;
        }
        return false;
    }

    public static boolean isNull(String str) {
        return str == null || str.equals("") || str.toLowerCase().equals("null");
    }
    public static boolean isMap(Object object) {
        if (object instanceof Map) {
            return true;
        }
        return false;
    }

    public static Object arrayToString(Object object) {
        if (object == null) {
            return "";
        }
        if (isArray(object)) {
            return StringUtils.join(Arrays.asList(object), ",");
        }
        if (isList(object)) {
            return String.join(",", (List) object);
        }
        return object;
    }

    public static Object stringToObject(Object object) {
        if (object == null) {
            return null;
        }
        if (isObject(object)) {
            return object;
        }
        return JSONObject.parse(String.valueOf(object));
    }

    public static Object objectToString(Object object) {
        if (object == null) {
            return null;
        }
        if (object instanceof String) {
            return object;
        }
        return JSONObject.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect);
    }

    public static Object stringToArray(Object object) {
        if (object == null) {
            return null;
        }
        if (isObject(object)) {
            return object;
        }
        return Arrays.asList(String.valueOf(object).split(","));
    }

    public static String parseValue(Object value) {
        if (value == null) {
            return "";
        }
        return String.valueOf(value);
    }

    public static boolean contains(String source, String dest) {
        if (source == null || dest == null)
            return false;
        if (Arrays.asList(source.split(",")).contains(dest)) {
            return true;
        }
        return false;
    }

    public static String valueOf(Object value) {
        if (value == null) {
            return "";
        }
        return String.valueOf(value);
    }

    public static boolean isEmpty(Object value) {
        if (value == null) {
            return true;
        }
        return "".equals(value);
    }

    public static boolean isContains(String sValue, String cValue) {
        if (StringUtils.isEmpty(sValue) || StringUtils.isEmpty(cValue)) {
            return false;
        }
        List<String> cValues = Arrays.asList(cValue.split(","));
        return cValues.contains(sValue);
    }

    public static String remove(String src, String dest) {
        if (StringUtils.isEmpty(src))
            return "";
        List<String> srcList = Arrays.asList(src.split(","));
        srcList.remove(dest);
        return srcList.stream().collect(Collectors.joining(","));
    }

}

aop:

注解:CommonLog
import java.lang.annotation.*;

/**
 * 日志注解 只能作用于受spring管理的实现类或方法上
 * 可以使用spel表达式取参数的值 例如: #param.id
 *
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CommonLog {

    String logKey() default "";

    String logType() default "0";

    String logMethod() default "";

    String logClass() default "";

    String requestType() default "s";

    String canRepeat() default "T";

    /**
     * 加在方法的参数上 用于忽略该参数
     */
    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface Ignore {

    }
}
LogAnnotationAdvisor

1,参数的获取:

        在around 方法里面,pjp.proceed() 执行前,把参数,做一个记录。

2,请求结果的获取: pjp.proceed() 记录执行的结果

     对于参数的解析,使用SpingEL


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.yan.web.spring.aop.log.entity.CommonApiLog;
import com.yan.web.spring.aop.log.entity.CommonApiLogResult;
import com.yan.web.spring.aop.log.util.SpringUtils;
import com.yan.web.spring.aop.log.util.StringUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.apache.commons.lang.WordUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class LogAnnotationAdvisor  implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(LogAnnotationAdvisor.class);
    private static final ThreadLocal<LinkedList<Boolean>> FLAG = new ThreadLocal<>();

    private static ThreadPoolExecutor pool;

    private final ExpressionParser parser = new SpelExpressionParser();


    @Pointcut("@annotation(com.yan.web.spring.aop.log.CommonLog)||" +
            "@within(com.yan.web.spring.aop.log.CommonLog)")
    public void log() {
    }

    public static String toJsonString(Object o) {
        if (StringUtil.isEmpty(o)) {
            return null;
        }
        if (o instanceof String) {
            return (String) o;
        }
        try {
            return JSON.toJSONString(o, SerializerFeature.DisableCircularReferenceDetect,
                    SerializerFeature.IgnoreNonFieldGetter, SerializerFeature.WriteClassName);
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
            return o.toString();
        }
    }

    public static void setFlag(Boolean flag) {
        if (FLAG.get() == null) {
            FLAG.set(new LinkedList<>());
        }
        FLAG.get().addLast(flag);
    }

    public static void removeFlag() {
        LinkedList<Boolean> flags = FLAG.get();
        if (flags != null) {
            Boolean flag = flags.pollLast();
            if (flag == null || CollectionUtils.isEmpty(flags)) {
                FLAG.remove();
            }
        }
    }

    private Boolean getFlag() {
        LinkedList<Boolean> flags = FLAG.get();
        return flags == null ? null : flags.peekLast();
    }

    @Around(value = "log()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        logger.debug("-----do around log-----");
        if (!Optional.ofNullable(getFlag()).orElse(Boolean.TRUE)) {
            return pjp.proceed();
        }
        CommonApiLog apiLog = new CommonApiLog();
        boolean addLog = true;
        Object logResult = null;
        try {
            try {
                buildRequestInfo(pjp, apiLog);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                addLog = false;
            }
            Object response = pjp.proceed();
            apiLog.setLogFlag("1");
            logResult = response;
            return response;
        } catch (Throwable t) {
            apiLog.setLogFlag("0");
            logResult = t;
            throw t;
        } finally {
            if (addLog) {
                Date now = new Date();
                sendApiLog(apiLog, logResult, now);
            }
        }
    }

    private boolean canNotInvokeClass(Class<?> clazz) {
        List<Class<?>> classes = Arrays.asList(ServletResponse.class, ServletRequest.class, HttpSession.class,
                MultipartFile.class, InputStream.class, OutputStream.class);
        for (Class<?> c : classes) {
            if (c.isAssignableFrom(clazz)) {
                return true;
            }
        }
        return false;
    }

    // 获取和拼接请求参数
    private void buildRequestInfo(ProceedingJoinPoint pjp, CommonApiLog apiLog) {
        MethodSignature sign = (MethodSignature) pjp.getSignature();
        Method method = sign.getMethod();
        CommonLog commonLog = method.getAnnotation(CommonLog.class);
        if (commonLog == null) {
            commonLog = method.getDeclaringClass().getAnnotation(CommonLog.class);
        }
        logger.info("commonLog:{}", commonLog);
        String[] parameterNames = sign.getParameterNames();
        Class<?>[] parameterTypes = sign.getParameterTypes();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Object[] parameters = pjp.getArgs();
        Map<String, Map<String, Object>> logRequest = new LinkedHashMap<>(parameters.length);
        EvaluationContext context = new StandardEvaluationContext();
        String canRepeat = null;
        if (parameterNames != null && parameterNames.length > 0) {
            for (int i = 0, n = parameterNames.length; i < n; i++) {
                String parameterName = parameterNames[i];
                Object parameter = parameters[i];
                Class<?> parameterType = parameterTypes[i];
                context.setVariable(parameterName, parameter);
                if (canRepeat == null && canNotInvokeClass(parameterType)) {
                    canRepeat = "F";
                }
                String beanName = parameter == null ? null : getSpringBeanName(parameter.getClass());
                if (parameter instanceof HttpSession || parameter instanceof HttpServletRequest
                        || parameter instanceof HttpServletResponse || hasIgnoreAnnotation(parameterAnnotations[i])) {
                    logRequest.put(parameterName, new HashMap<>(Collections.singletonMap(parameterType.getName(),
                            StringUtils.isBlank(beanName) ? null : "Bean@" + beanName)));
                } else if (parameter instanceof MultipartFile) {
                    logRequest.put(parameterName, new HashMap<>(Collections.singletonMap(parameterType.getName(),
                            ((MultipartFile) parameter).getOriginalFilename())));
                } else if (StringUtils.isNotBlank(beanName)) {
                    logRequest.put(parameterName,
                            new HashMap<>(Collections.singletonMap(parameterType.getName(), "Bean@" + beanName)));
                } else {
                    logRequest.put(parameterName,
                            new HashMap<>(Collections.singletonMap(parameterType.getName(), parameter)));
                }
            }
        }
        apiLog.setCreateTime(new Date());
        apiLog.setStaffId(1L); // 获取登录人的staffId
        apiLog.setLogDesc(toJsonString(logRequest));
        apiLog.setLogKey(getSpelValue(context, commonLog.logKey()));
        apiLog.setLogType(getSpelValue(context, commonLog.logType()));
        apiLog.setLogClass(getSpelValue(context, commonLog.logClass()));
        String logMethod = getSpelValue(context, commonLog.logMethod());
        // 获取注解的logMethod
        if (StringUtils.isBlank(logMethod)) {
            apiLog.setRequestMethod(pjp.getTarget().getClass().getName() + "#" + method.getName());
        } else {
            apiLog.setRequestMethod(pjp.getTarget().getClass().getName() + "#" + logMethod);
        }
        apiLog.setRequestCount(1);
        apiLog.setRequestType(getSpelValue(context, commonLog.requestType()));
        if (canRepeat != null) {
            apiLog.setCanRepeat(canRepeat);
        } else {
            apiLog.setCanRepeat(getSpelValue(context, commonLog.canRepeat()));
        }
    }

    private String getSpringBeanName(Class<?> clazz) {
        String beanName = null;
        if (clazz.isAnnotationPresent(Service.class)) {
            beanName = clazz.getAnnotation(Service.class).value();
        } else if (clazz.isAnnotationPresent(Component.class)) {
            beanName = clazz.getAnnotation(Component.class).value();
        }
        if (StringUtils.isBlank(beanName)) {
            try {
                String simpleName = clazz.getSimpleName();
                // WordUtils.uncapitalize 首字母转小写,比如 Abcd 变成 abcd
                SpringUtils.getBean(WordUtils.uncapitalize(simpleName));
                beanName = simpleName;
            } catch (Throwable t) {
                try {
                    SpringUtils.getBean(clazz);
                    beanName = clazz.getName();
                } catch (Throwable tt) {
                    //ignore
                }
            }
        }
        return beanName;
    }

    private void sendApiLog(CommonApiLog apiLog, Object logResult, Date responseTime) {
        pool.execute(() -> {
            CommonApiLogResult apiLogDetail = new CommonApiLogResult();
            apiLogDetail.setCreateTime(apiLog.getCreateTime());
            apiLogDetail.setRequestTime(apiLog.getCreateTime());
            apiLogDetail.setResponseTime(responseTime);
            apiLogDetail.setLogFlag(apiLog.getLogFlag());
            if (logResult != null) {
                String logDesc;
                if ("1".equals(apiLog.getLogFlag())) {
                    logDesc = toJsonString(logResult);
                } else {
                    logDesc = getTraceInfo((Throwable) logResult);
                }
                if (!StringUtil.isNull(logDesc)) {
                    apiLogDetail.setLogResult(logDesc);
                }
            }
            apiLog.setApiLogDetails(Collections.singletonList(apiLogDetail));
            try {
                // 保存日志到数据库
                //  addCommonApiLog(apiLog);
            } catch (Exception e) {
                logger.error("apiLog:{}", toJsonString(apiLog), e);
            }
        });
    }

    private boolean hasIgnoreAnnotation(Annotation[] annotations) {
        boolean ignore = false;
        if (annotations != null && annotations.length > 0) {
            for (Annotation annotation : annotations) {
                if (annotation instanceof CommonLog.Ignore) {
                    ignore = true;
                    break;
                }
            }
        }
        return ignore;
    }

    // 获取表达式的值
    private String getSpelValue(EvaluationContext context, String template) {
        if (StringUtils.isBlank(template)) {
            return null;
        }
        if (!template.startsWith("#")) {
            return template;
        }
        try {
            Expression expression = parser.parseExpression(template);
            return expression.getValue(context, String.class);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return template;
        }
    }

    public static String getTraceInfo(Throwable t) {
        try (Writer writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer)) {
            t.printStackTrace(printWriter);
            return writer.toString();
        } catch (Exception e) {
            return t.getMessage();
        }
    }

    @After(value = "log()")
    public void doAfter() {
        logger.debug("-----do after log-----");
    }

    @Override
    public void afterPropertiesSet() {
        // Runtime.getRuntime().availableProcessors() 获取当前系统可用的逻辑处理器的数量
        int size = Runtime.getRuntime().availableProcessors() + 1;
        pool = new ThreadPoolExecutor(size, size * 2, 120L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
    }
}

使用:

      

@WorkflowLog(logKey = "#request.contentId")
public void delEvent(EventWorkSheetRequest request) {}


@WorkflowLog(logKey = "#processData.process.id")
public ValidationErr saveProgress(ProcessData processData, 
@WorkflowLog.Ignore IProcess iProcess) throws FormEngineException {
}

总结:

        使用aop注解,统一记录请求参数和日志,便于查询日志。这个时间长了,数据库的日志表的数据会很多,要定期进行清理。

内容概要:本文深入探讨了AMESim仿真平台在电动汽车(EV)热泵空调系统设计与优化中的应用。首先介绍了AMESim的基础建模方法,如构建制冷循环模型中的压缩机、蒸发器和冷凝器等组件,并详细解释了各部件的工作原理及其参数设定。接着重点阐述了EV热泵空调系统的特殊之处,即不仅能够制冷还可以在冬季提供高效的制热功能,这对于提高电动汽车在寒冷条件下的续航里程和乘坐舒适性非常重要。文中给出了几个具体的案例,包括通过改变压缩机运行频率来进行性能优化,以及针对低温环境下热泵系统的控制策略,如四通阀切换逻辑、电子膨胀阀开度调节等。此外,还讨论了热泵系统与其他子系统(如电池温控)之间的协同工作方式,强调了系统集成的重要性。最后分享了一些实用的经验技巧,例如如何避免仿真过程中可能出现的问题,怎样评估系统的整体性能等。 适合人群:从事汽车工程、暖通空调(HVAC)领域的研究人员和技术人员,特别是关注新能源汽车热管理系统的专业人士。 使用场景及目标:适用于希望深入了解电动汽车热泵空调系统特性的工程师们,旨在帮助他们掌握基于AMESim进行系统建模、仿真分析的方法论,以便更好地指导实际产品研发。 阅读建议:由于涉及到较多的专业术语和技术细节,建议读者具备一定的机械工程背景知识,同时配合官方文档或其他参考资料一起研读,以加深理解。
期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目),个人经导师指导并认可通过的高分设计项目,评审分99分,代码完整确保可以运行,小白也可以亲自搞定,主要针对计算机相关专业的正在做大作业的学生和需要项目实战练习的学习者,可作为毕业设计、课程设计、期末大作业,代码资料完整,下载可用。 期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作业Python实现基于图神经网络的信任评估项目源代码+使用说明(高分项目)期末作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天狼1222

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值