springboot 统一异常处理 + 日志记录

news2024/11/16 5:43:05

      在项目的开发中,在某些情况下,比如非业务的操作,日志记录,权限认证和异常处理等。我们需要对客户端发出的请求进行拦截,常用的API拦截方式有Fliter,Interceptor,ControllerAdvice以及Aspect。先简单介绍一下不同的拦截方式。

一.拦截方式

过滤器:Filter

可以获得Http原始的请求和响应信息,但是拿不到响应方法的信息。

拦截器:Interceptor

可以获得Http原始的请求和响应信息,也拿得到响应方法的信息,但是拿不到方法响应中参数的值。

ControllerAdvice(Controller增强,自spring3.2的时候推出)

主要是用于全局的异常拦截和处理,这里的异常可以使自定义异常也可以是JDK里面的异常,用于处理当数据库事务业务和预期不同的时候抛出封装后的异常,进行数据库事务回滚,并将异常的显示给用户。

切片:Aspect

主要是进行公共方法的,可以拿得到方法响应中参数的值,但是拿不到原始的Http请求和相对应响应的方法。

二.正文

        在开发过程中,有时候一个业务调用链场景,很长,调了各种各样的方法,看日志的时候,各个接口的日志穿插,确实让人头大 。我们需要把同一次的业务调用链上的日志串起来。于是就引出了日志码,在日志打印时输出一个唯一标识(比如UUID)。如下图所示:

我们可以使用 MDC(Mapped Diagnostic Context)诊断上下文映射来报错日志码,MDC是@Slf4j提供的一个支持动态打印日志信息的工具。话不多说,直接上代码。

pom.xml 依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <!--lombok配置-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>
    </dependencies>

logback-spring.xml 

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--日志存储路径-->
    <property name="log" value="logs" />
    <property name="logName" value="business" />
    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--输出格式化-->
            <pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按天生成日志文件 -->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件名-->
            <FileNamePattern>${log}/${logName}%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
</configuration>

application.yml 

server:
  port: 8080
logging:
  config: classpath:logback-spring.xml

日志切面WebLogAspect

package com.business.aop;

import java.util.Arrays;
import java.util.Enumeration;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import com.business.util.ThreadMdcUtil;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
@Slf4j
public class WebLogAspect {
    @Pointcut("execution(public * com.business.controller..*.*(..))")
    public void webLog() {

    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String tid = UUID.randomUUID().toString().replace("-", "");
        //可以考虑让客户端传入链路ID,但需保证一定的复杂度唯一性;如果没使用默认UUID自动生成
        if (!StringUtils.isEmpty(request.getHeader(ThreadMdcUtil.getTraceId()))){
            tid=request.getHeader("TRACE_ID");
        }
        MDC.put(ThreadMdcUtil.getTraceId(), tid);

        // 记录下请求内容
        log.info("---------------request----------------");
        log.info("请求路径 : " + request.getRequestURL().toString()); //URL : request.getRequestURL().toString()
        log.info("请求方式 : " + request.getMethod()); //HTTP_METHOD : request.getMethod()
        log.info("访问者IP : " + request.getRemoteAddr()); //IP : request.getRemoteAddr()
        log.info("CLASS_METHOD:" + joinPoint.getSignature().getDeclaringTypeName() + "-" + joinPoint.getSignature().getName());
        log.info("ARGS:" + Arrays.toString(joinPoint.getArgs()));

        Enumeration enu = request.getParameterNames();
        while (enu.hasMoreElements()) {
            String name = (String) enu.nextElement();
            log.info("请求参数:" + name + " - 请求值:" + request.getParameter(name));
        }
    }

    @AfterReturning("webLog()")
    public void doAfterReturning() {
        MDC.remove(ThreadMdcUtil.getTraceId());
    }

}

子线程丢失trackId处理

        子线程会丢失trackId,需要进行处理。思路: 将父线程的trackId传递下去给子线程即可。

ThreadPoolConfig线程池

定义线程池,交给spring管理。

package com.business.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.Executor;

/**
 * Author: lgq
 * Date: 2023-4-12 11:07
 * Description: 定义线程池,交给spring管理
 */
@Configuration
@EnableAsync
public class ThreadPoolConfig {
    /**
     * 声明一个线程池
     *
     * @return 执行器
     */
    @Bean("MyExecutor")
    public Executor asyncExecutor() {
        MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();
        //核心线程数5:线程池创建时候初始化的线程数
        executor.setCorePoolSize(5);
        //最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(5);
        //缓冲队列500:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        //允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("asyncSimple");
        executor.initialize();
        return executor;
    }
}

重写MyThreadPoolTaskExecutor

package com.business.config;

import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;

import com.business.util.ThreadMdcUtil;

/**
 * Author: lgq
 * Date: 2023-4-12 11:07
 * Description: 重写ThreadPoolTaskExecutor
 */
public final class MyThreadPoolTaskExecutor  extends ThreadPoolTaskExecutor  {
    public MyThreadPoolTaskExecutor() {
        super();
    }

    @Override
    public void execute(Runnable task) {
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }


    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
}

ThreadMdcUtil工具类

package com.business.util;

import org.slf4j.MDC;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;

/**
 * Author: lgq
 * Date: 2023-4-12 11:07
 * Description: ThreadMDC工具类
 */
public final class ThreadMdcUtil {
    private static final String TRACE_ID = "TRACE_ID";

    public static String getTraceId() {
        return TRACE_ID;
    }

    // 获取唯一性标识
    public static String generateTraceId() {
        return UUID.randomUUID().toString();
    }

    public static void setTraceIdIfAbsent() {
        if (MDC.get(TRACE_ID) == null) {
            MDC.put(TRACE_ID, generateTraceId());
        }
    }

    /**
     * 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
     *
     * @param callable
     * @param context
     * @param <T>
     * @return
     */
    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                return callable.call();
            } finally {
                MDC.clear();
            }
        };
    }

    /**
     * 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
     *
     * @param runnable
     * @param context
     * @return
     */
    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

统一异常处理类

需要注意是,AfterThrowing 优先于 ExceptionHandler,因此在ExceptionHandler中移除traceId。

package com.business.common;


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

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import com.business.exception.BusinessException;
import com.business.exception.ParamException;
import com.business.pojo.response.ResponseResult;
import com.business.util.ThreadMdcUtil;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ResponseBody;


@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 系统未知错误
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = Error.class)
    @ResponseBody
    public ResponseResult errorHandler(Error ex) {
        String logCode = MDC.get(ThreadMdcUtil.getTraceId());
        if (ObjectUtils.isEmpty(logCode)) {
            ThreadMdcUtil.setTraceIdIfAbsent();
            logCode = MDC.get(ThreadMdcUtil.getTraceId());
        }
        log.error("错误码:{}, 未知错误", logCode, ex);
        MDC.remove(ThreadMdcUtil.getTraceId());
        return ResponseResult.fail("系统异常,请联系管理员", logCode);
    }

    /**
     * 系统未知异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResponseResult exceptionHandler(Exception ex) {
        String logCode = MDC.get(ThreadMdcUtil.getTraceId());
        if (ObjectUtils.isEmpty(logCode)) {
            ThreadMdcUtil.setTraceIdIfAbsent();
            logCode = MDC.get(ThreadMdcUtil.getTraceId());
        }
        log.error("错误码:{}, 未知异常", logCode, ex);
        MDC.remove(ThreadMdcUtil.getTraceId());
        return ResponseResult.fail("系统异常,请联系管理员", logCode);
    }


    /**
     * 业务异常
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public ResponseResult handleBusinessException(BusinessException e) {
        String logCode = ThreadMdcUtil.generateTraceId();
        log.error("错误码:{}, 业务处理异常:{}", logCode, e.getMessage(), e);
        MDC.remove(ThreadMdcUtil.getTraceId());
        return ResponseResult.fail(logCode, CommonStatusEnum.BUSINESS_ERROR.getCode(),
                CommonStatusEnum.BUSINESS_ERROR.getValue());
    }

    /**
     * 业务参数异常
     */
    @ExceptionHandler(value = ParamException.class)
    @ResponseBody
    public ResponseResult handleParamException(ParamException e) {
        String logCode = ThreadMdcUtil.generateTraceId();
        log.error("错误码:{}, 业务参数处理异常:{}", logCode, e.getMessage(), e);
        MDC.remove(ThreadMdcUtil.getTraceId());
        return ResponseResult.fail(logCode, CommonStatusEnum.PARAM_ERROR.getCode(),
                CommonStatusEnum.BUSINESS_ERROR.getValue());
    }

    /**
     * 参数校验(Valid)异常
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    @ResponseBody
    public ResponseResult handleValidException(MethodArgumentNotValidException e) {
        String logCode = ThreadMdcUtil.generateTraceId();
        if (ObjectUtils.isEmpty(logCode)) {
            logCode = ThreadMdcUtil.generateTraceId();
        }
        log.error("错误码:{},数据校验异常:{},异常类型:{}", logCode, e.getMessage(), e.getClass(), e);
        MDC.remove(ThreadMdcUtil.getTraceId());
        BindingResult bindingResult = e.getBindingResult();
        Map<String, String> errorMap = getErrorMap(bindingResult);
        return ResponseResult.fail(logCode, CommonStatusEnum.PARAM_ERROR.getCode(), CommonStatusEnum.PARAM_ERROR.getValue(), errorMap);
    }

    /**
     * 参数绑定异常
     */
    @ExceptionHandler(value = {BindException.class})
    @ResponseBody
    public ResponseResult handleValidException(BindException e) {
        String logCode = ThreadMdcUtil.generateTraceId();
        log.error("错误码:{}, 数据校验异常:{},异常类型:{}", logCode, e.getMessage(), e.getClass(), e);
        MDC.remove(ThreadMdcUtil.getTraceId());
        BindingResult bindingResult = e.getBindingResult();
        Map<String, String> errorMap = getErrorMap(bindingResult);
        return ResponseResult.fail(logCode, CommonStatusEnum.PARAM_ERROR.getCode(), CommonStatusEnum.PARAM_ERROR.getValue(), errorMap);
    }

    /**
     * 约束校验异常
     */
    @ExceptionHandler(value = {ConstraintViolationException.class})
    public ResponseResult handleValidException(ConstraintViolationException e) {
        String logCode = ThreadMdcUtil.generateTraceId();
        log.error("错误码:{}, 数据校验异常,{},异常类型:{}", logCode, e.getMessage(), e.getClass(), e);
        MDC.remove(ThreadMdcUtil.getTraceId());
        List<String> violations = e.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage).collect(Collectors.toList());
        String error = violations.get(0);
        return ResponseResult.fail(logCode, CommonStatusEnum.CONSTRAINT_ERROR.getCode(), CommonStatusEnum.CONSTRAINT_ERROR.getValue(), error);
    }

    /**
     * 获取校验失败的结果
     */
    private Map<String, String> getErrorMap(BindingResult result) {
        return result.getFieldErrors().stream().collect(
                Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage, (k1, k2) -> k1)
        );
    }

    /**
     * DataBinder 数据绑定访问器,集合参数校验时需要这个数据绑定
     */
    @InitBinder
    private void activateDirectFieldAccess(DataBinder dataBinder) {
        dataBinder.initDirectFieldAccess();
    }

}

其他异常处理类

package com.business.exception;

public class BusinessException extends RuntimeException {

    public BusinessException() {
        super();
    }

    public BusinessException(String message) {
        super(message);
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }

    public BusinessException(Throwable cause) {
        super(cause);
    }

    protected BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

public class ParamException extends RuntimeException {
    public ParamException() {
        super();
    }

    public ParamException(String message) {
        super(message);
    }

    public ParamException(String message, Throwable cause) {
        super(message, cause);
    }

    public ParamException(Throwable cause) {
        super(cause);
    }

    protected ParamException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

其他类

package com.business.common;

import lombok.Getter;

public enum CommonStatusEnum {

    /**
     * 未知异常
     */
    SERVER_UNKNOW_ERROR(1001,"服务器未知异常,请联系管理员!"),

    /**
     * 业务异常
     */
    BUSINESS_ERROR(1002,"业务逻辑异常!"),

    /**
     * 网络请求异常
     */
    NETWORK_ERROR(1003,"网络请求异常"),

    /**
     * 参数校验(Valid)异常
     */
    PARAM_ERROR(1004,"参数异常"),

    /**
     * 约束校验异常
     */
    CONSTRAINT_ERROR(1005,"约束异常"),





    /**
     * 成功
     */
    SUCCESS(200,"success"),
    /**
     * 失败
     */
    FAIL(500,"fail")

    ;
    @Getter
    private int code;
    @Getter
    private String value;

    CommonStatusEnum(int code, String value) {
        this.code = code;
        this.value = value;
    }
}


package com.bussiness.pojo.response;

import com.bussiness.common.CommonStatusEnum;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class ResponseResult<T> {
    private int code;
    private String message;
    private T data;
    private String logCode;

    /**
     * 成功响应的方法
     *
     * @param <T>
     * @return
     */
    public static <T> ResponseResult<T> success() {
        return new ResponseResult().setCode(CommonStatusEnum.SUCCESS.getCode()).setMessage(CommonStatusEnum.SUCCESS.getValue());
    }

    /**
     * 成功响应的方法
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ResponseResult<T> success(T data) {
        return new ResponseResult().setCode(CommonStatusEnum.SUCCESS.getCode()).setMessage(CommonStatusEnum.SUCCESS.getValue()).setData(data);
    }

    /**
     * 失败:统一的失败
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ResponseResult<T> fail(T data) {
        return new ResponseResult().setCode(CommonStatusEnum.FAIL.getCode()).setData(data);
    }

    /**
     * 失败:统一的失败
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ResponseResult<T> fail(T data, String logCode) {
        return new ResponseResult().setCode(CommonStatusEnum.FAIL.getCode())
                .setLogCode(logCode).setData(data);
    }

    /**
     * 失败:统一的失败
     *
     * @param message
     * @return
     */
    public static ResponseResult fail(String message) {
        return new ResponseResult().setCode(CommonStatusEnum.FAIL.getCode()).setMessage(message);
    }

    /**
     * 失败:统一的失败
     *
     * @param message
     * @return
     */
    public static ResponseResult fail(String message, String logCode) {
        return new ResponseResult().setCode(CommonStatusEnum.FAIL.getCode())
                .setLogCode(logCode).setMessage(message);
    }

    /**
     * 失败:自定义失败 错误码和提示信息
     *
     * @param code
     * @param message
     * @return
     */
    public static ResponseResult fail(int code, String message) {
        return new ResponseResult().setCode(code).setMessage(message);
    }

    /**
     * 失败:自定义失败 错误码和提示信息
     *
     * @param code
     * @param message
     * @return
     */
    public static ResponseResult fail(String logCode, int code, String message) {
        return new ResponseResult().setCode(code).setLogCode(logCode).setMessage(message);
    }

    /**
     * 失败:自定义失败 错误码、提示信息、具体错误
     *
     * @param code
     * @param message
     * @param data
     * @return
     */
    public static <T> ResponseResult<T> fail(int code, String message, T data) {
        return new ResponseResult().setCode(code).setMessage(message).setData(data);
    }

    /**
     * 失败:自定义失败 错误码、提示信息、具体错误
     *
     * @param code
     * @param message
     * @param data
     * @return
     */
    public static <T> ResponseResult<T> fail(String logCode, int code, String message, T data) {
        return new ResponseResult().setCode(code).setMessage(message).setData(data).setLogCode(logCode);
    }


}

package com.business.pojo.request;

import javax.validation.constraints.Min;

import lombok.Getter;
import lombok.Setter;

public class PageQuery {

    @Getter
    @Setter
    @Min(value = 1, message = "当前页码不合法")
    private int pageNo = 1;

    @Getter
    @Setter
    @Min(value = 1, message = "每页展示数量不合法")
    private int pageSize = 10;

    @Setter
    private int offset;

    public int getOffset() {
        return (pageNo - 1) * pageSize;
    }
}

package com.business.pojo.request;

import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
import java.util.List;

@Data
public class DemoReq {
    /**
     * 领域编码
     */
    @NotBlank(message = "领域编码不能为空")
    private String domainKey;

    /**
     * 模型编码
     */
    @NotBlank(message = "模型编码不能为空")
    private String modelKey;

    /**
     * 时间
     */
    private List<String> startTime;

    /**
     * 开始时间
     */
    private String beginTime;

    /**
     * 结束时间
     */
    private String endTime;
}


参考文章:

拦截机制中Aspect、ControllerAdvice、Interceptor、Fliter之间的区别详解 - 简书

Springboot 同一次调用日志怎么用ID串起来,方便最终查找_日志id开线程后还能用吗_小目标青年的博客-CSDN博客
hibernate-validator校验参数(统一异常处理)_无法访问org.hibernate.validator.constraints.range_鱼找水需要时间的博客-CSDN博客

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

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

相关文章

JavaScript【四】JavaScript中的函数

文章目录&#x1f31f;前言&#x1f31f;什么是函数?&#x1f31f;函数声明方式&#x1f31f; function关键字&#x1f31f; 字面量定义(匿名函数)&#x1f31f; 实例化构造函数&#x1f31f;函数调用方式&#x1f31f;通过括号调用&#x1f31f;自调用(IIFE)&#x1f31f;通过…

企业电子招投标采购系统——功能模块功能描述+数字化采购管理 采购招投标

​ 功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外…

windows安装Metasploit

近期在大佬群里各种炫技&#xff0c;各种工具使用&#xff0c;漏洞利用与复现&#xff0c;感觉自己常规安全测试就是个小学生&#xff0c;于是好心的大佬发了个Rapid7Setup-Windows64.exe的渗透工具&#xff0c;但是自己的电脑安装了&#xff0c;破解不了&#xff0c;导致失败&…

计算机组成原理——第四章指令系统(上)

提示&#xff1a;待到秋来九月八&#xff0c;我花开后百花杀 文章目录前言4.1.1 指令格式4.1.2 扩展操作码指令格式4.2.1 指令寻址4.2.2 数据寻址4.2.3 偏移寻址4.2.4 堆栈寻址汇总前言 通过第二章我们学习了运算器是如何进行加减乘除&#xff0c;移位运算操作的&#xff0c;通…

【密码算法 之七】GCM 浅析

文章目录1. 概述1.1 GHASH1.3 GCTR2. GCM 加密3. GCM 解密4. 总结在我的另一篇博客【密码算法 之三】分组密码工作模式 &#xff08;ECB \ CBC \ CFB \ OFB \ CTR \ XTS&#xff09;浅析 中已经详细的介绍了对称算法&#xff08;也称为“分组密码算法”&#xff09;的各种工作模…

排序算法(一)

一、排序算法 排序算法就是将数组按照数值或者字母序排列&#xff0c;常用的排序算法有很多&#xff0c;如下&#xff1a; 详细细节可以直接看维基百科&#xff1a;https://zh.wikipedia.org/zh-cn/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95 C的库中实现了常用的排序算法&#x…

Linux_红帽8学习笔记分享_4

Linux_红帽8学习笔记分享_4 文章目录Linux_红帽8学习笔记分享_41.用户管理useradd命令的使用1.1使用useradd添加用户1.2 Linux系统底层的四个连锁反应2.账户信息文件/etc/passwd中七个字段的含义3.组信息文件/etc/group中四个字段的含义4.影子文件/etc/shadow两个字段的含义5.相…

C++STL——map与set介绍及使用

map与set介绍及使用关联式容器健值对setmultisetmapmultimap关联式容器 之前我们学的list&#xff0c;vector等等是序列式容器&#xff0c;这里的set和map和之后的哈希表都是关联式容器&#xff0c;比如说搜索二叉树我们想插入一个值&#xff0c;不能随意的插入&#xff0c;因…

【JVM】JVM之执行引擎

文章目录前言名词解释机器码指令指令集汇编语言高级语言字节码虚拟机&物理机前端编译器&后端编译器JVM之执行引擎执行引擎是如何工作的&#xff1f;解释器即时编译器&#xff08;JIT&#xff09;分层编译策略虚拟机执行模式热点代码&探测方式1&#xff09;方法调用…

Android 屏蔽锁屏界面上的通知显示

一. 前言 [定制需求描述]:在插入SD后, 锁屏状态下&#xff0c; 去掉提示“SD卡可用于传输照片和媒体文件” 需求拆解: 要求正常显示在SystemUI下拉状态栏, 只需要屏蔽在锁屏状态下的通知. 二. 发送通知 首先来找找这个字符串"可用于传输照片和媒体文件" 是在/f…

buuctf_随便注

根据题目猜测这是一道SQL注入的题目输入一个单引号触发报错&#xff0c;根据报错信息得知闭合条件就是一对单引号继续构造表达式&#xff0c;得出一共包含两个回显位构造表达式求出当前数据库的名称&#xff0c;但是根据回显数据和实践来看&#xff0c;此题是对select进行了过滤…

【软件设计师12】数据流图DFD

数据流图DFD 必考下午第一道大题&#xff01;&#xff01;&#xff01; 基本概念、数据字典、数据平衡原则 1. 基本概念 顶层图是系统&#xff0c;再看中间跟外部数据的交换流不变&#xff0c;内部细化&#xff0c;最底层图进一步细化 数据存储在题干描述时&#xff0c;要么…

一文弄清-BP的过拟合与validationCheck

本站原创文章&#xff0c;转载请说明来自《老饼讲解-BP神经网络》bp.bbbdata.com BP神经网络的训练经常会遇到过拟合的情况&#xff0c;导致模型在训练效果上很好但预测效果差 正因如此&#xff0c;matlab工具箱引入validationCheck来防止BP神经网络走向过拟合 本文介绍过拟合是…

SOTIF 预期功能安全ISO21448介绍、功能安全标准ISO26262 与若干安全标准的适用范围和开发流程映射

SOTIF 预期功能安全ISO21448介绍、功能安全标准ISO26262 与若干安全标准的适用范围和开发流程映射 ISO 21448 中规定了预期功能安全的设计开发流程图&#xff0c; 如下图所示。首先从 第 5 条出发&#xff0c; 进行规范和设计。该部分是将进行整车级、 系统级、 组件级等功能规…

快速了解Depop注册新方法,轻松一分钟入驻

东哥我是在几个月前第一次认识depop这个平台&#xff0c;原因是当时主要是让手底下的员工去操作&#xff0c;团队毕竟涉及到的平台都比较多&#xff0c;我就没那么多精力放在depop上&#xff0c;但却意外发现这个平台给我们带来很不错的业绩&#xff01;所以东哥今天打算给大家…

阿里p8大牛三年整理出全网最全的5万字的《Java核心知识手册》

利用空余时间整理了一份《Java核心知识手册》&#xff0c;初衷也很简单&#xff0c;就是希望在面试的时候能够帮助到大家&#xff0c;减轻大家的负担和节省时间。 前段时间&#xff0c;朋友圈分享了这份这份面试手册的初稿&#xff0c;在几位同学的提议下&#xff0c;对手册进…

线程夯死的排查及解决

最近做的业务一直是和第三方交互的业务&#xff0c;为了加快速度&#xff0c;基本上都是采用多线程&#xff0c;然而时不时总是发生一些推送任务莫名的卡死&#xff0c;知道前几天的一次发现&#xff0c;让我开始了线程的排查之路&#xff0c;希望对大家的有一定的启发和借鉴一…

使用Mybatis-plus在xml文件中实现自己定义的sql逻辑

1、创建数据库表(前提安装配置好Mysql&#xff0c;并且会使用) DROP TABLE IF EXISTS USER; CREATE TABLE USER (id BIGINT(20) NOT NULL COMMENT 主键ID,NAME VARCHAR(30) NULL DEFAULT NULL COMMENT 姓名,age INT(11) NULL DEFAULT NULL COMMENT 年龄,email VARCHAR(50) NUL…

【C51单片机】8-温湿度监测显示系统(LCD1602、温湿度传感器、IIC、OLED)

1.LCD1602概述 LCD1602&#xff08;Liquid Crystal Display&#xff09;是一种工业字符型液晶&#xff0c;能够同时显示 1602 即 32 字符(16列两行) //硬件接线 //电源 VSS -- GND VDD -- 5V //对比度 VO -- GND //控制线 RS -- P1.0 RW -- P1.1 E -- P1.4 //背光灯 A -- 5…

华为VRRP配置

拓扑图 PC1电脑配置 指定ip192.168.10.1 网关192.18.10.254 LSW1交换机配置 ge0/0/1 access &#xff0c;vlan10 ge0/0/2 trunk ge0/0/3 trunk <Huawei>system-view Enter system view, return user view with CtrlZ. [Huawei]undo info-center enable Info: Informa…