springboot 统一日志 链路跟踪 dubbo3链路 springboot log-starter 设计和实现- 统一日志和链路跟踪 管理、设计和实现

news2025/1/26 15:45:53

文章目录

  • 1、目标
  • 2、源代码
  • 3、实现逻辑
    • 操作参数定义
    • 日志拦截器
    • 本地服务日志拦截
    • 调用微服务模块的日志保存接口继承
    • 日志工具类
    • 链路跟踪自实现
  • 4、 logback.xml配置
  • 5、测试类
    • 测试http请求文件
    • 测试接口层
  • 外传

springboot 统一日志 链路跟踪 dubbo3链路 springboot log-starter 设计和实现- 统一日志和链路跟踪 管理、设计和实现

1、目标

1、实现微服务间直接使用log starter
2、springboot ds starter多数据源切换,使用dubbo微服务调用应用
3、统一日志处理
4、链路跟踪实现
5、dubbo3.1链路跟踪处理

2、源代码

springboot版本:2.3.1.RELEASE
dynamic-datasource版本:3.5.1
dubbo版本:3.1.0
nacos版本:2.1
实现源代码地址
分支:microservice-boot-1.0.3-logAndPlat
代码演示和测试:
microservice-boot-common模块
microservice-boot-plat模块

3、实现逻辑

最终log-stater实现截图:
在这里插入图片描述

操作参数定义

此模块使用注解的方式配置,也可以使用web interceptor拦截方式,但是post的数据处理会有问题,自行思考

package org.lwd.microservice.boot.middle.log.annotation;

import org.lwd.microservice.boot.middle.log.type.LogTypeEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 操作参数定义
 * @author weidong
 * @version V1.0.0
 * @since 2023/6/21
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {

    /**
     * 项目模块
     */
    String busModule() default "";

    /**
     * 操作项
     */
    String title() default "";

    /**
     * 操作内容
     */
    String context() default "";

    /**
     * 日志类型
     */
    LogTypeEnum logType() default LogTypeEnum.DEFAULT;

    /**
     * 是否保存到db
     */
    boolean saveDB() default true;
    /**
     * 是否异步记录日志
     */
    boolean async() default true;

    /**
     * 是否记录方法入参
     */
    boolean logParams() default true;

    /**
     * 是否记录方法出参
     */
    boolean logResult() default true;
}



日志拦截器

package org.lwd.microservice.boot.middle.log.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.lwd.microservice.boot.middle.log.annotation.OperationLog;
import org.lwd.microservice.boot.middle.log.entity.LogConsoleTypeEnum;
import org.lwd.microservice.boot.middle.log.utils.LogUtils;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 切面,日志拦截的逻辑
 *
 * @author weidong
 * @version V1.0.0
 * @since 2023/6/21
 */
@Slf4j
@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class OperationLogAspect {

    @Around("@annotation(operateLog)")
    public Object around(ProceedingJoinPoint joinPoint, OperationLog operateLog) throws Throwable {
        if (operateLog == null) {
            operateLog = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(OperationLog.class);
        }
        Date startTime = new Date();
        Object result = null;
        try {
            // 执行目标方法
            result = joinPoint.proceed();
            LogUtils.doLog(LogConsoleTypeEnum.API, joinPoint, operateLog, startTime, result, null);
            return result;
        } catch (Throwable exception) {
            LogUtils.doLog(LogConsoleTypeEnum.API, joinPoint, operateLog, startTime, null, exception);
            throw exception;
        }
    }
/*
    @Pointcut("@annotation(org.lwd.microservice.boot.middle.log.annotation.OperationLogInterceptor)")
    public void logInterceptorPointcut() {
    }

    @Before("logInterceptorPointcut()")
    public void beforeLog(JoinPoint joinPoint) {
        // 在方法执行前执行的逻辑
        log.info("Before logging...");
    }

    @After("logInterceptorPointcut()")
    public void afterLog(JoinPoint joinPoint) {
        // 在方法执行后执行的逻辑
        log.info("After logging...");
    }

    @AfterReturning(pointcut = "logInterceptorPointcut()", returning = "result")
    public void afterReturningLog(JoinPoint joinPoint, Object result) {
        // 在方法返回结果后执行的逻辑
        log.info("After returning logging...");
    }

    @AfterThrowing(pointcut = "logInterceptorPointcut()", throwing = "exception")
    public void afterThrowingLog(JoinPoint joinPoint, Throwable exception) {
        // 在方法抛出异常后执行的逻辑
        log.info("After throwing logging...");
    }*/
}

本地服务日志拦截

package org.lwd.microservice.boot.middle.log.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.lwd.microservice.boot.middle.log.entity.LogConsoleTypeEnum;
import org.lwd.microservice.boot.middle.log.utils.LogUtils;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 系统服务日志
 *
 * @author lwd
 * @since 2023/06/20
 */
@Slf4j
@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ServiceLogAspect {


    /**
     * 服务请求的拦截和处理
     *
     * @param joinPoint 目标地址
     * @return Object
     * @throws Throwable
     */
    @Around("@within(org.springframework.stereotype.Service)")
    public Object doServiceAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Date startTime = new Date();
        Object result = null;
        try {
            // 执行目标方法
            result = joinPoint.proceed();
            LogUtils.doLog(LogConsoleTypeEnum.SERVICE, joinPoint, null, startTime, result, null);
            return result;
        } catch (Throwable exception) {
            LogUtils.doLog(LogConsoleTypeEnum.SERVICE, joinPoint, null, startTime, null, exception);
            throw exception;
        }
    }

}

调用微服务模块的日志保存接口继承

package org.lwd.microservice.boot.middle.log.service;


import org.lwd.microservice.boot.middle.log.entity.OperationLogDTO;

import java.util.concurrent.Future;

/**
 * 日志保存拓展接口
 *
 * @author lwd
 * @since 2023/06/20
 */
public interface ModuleLogService {


    /**
     * 记录操作日志(异步)
     *
     * @param operateLogDTO 操作日志请求
     * @return true: 记录成功,false: 记录失败
     */
    Future<Boolean> savePlatLogAsync(OperationLogDTO operateLogDTO);

    /**
     * 记录平台日志(同步)
     *
     * @param operateLogDTO 操作日志请求
     * @return true: 记录成功,false: 记录失败
     */
    Boolean savePlatLog(OperationLogDTO operateLogDTO);


}

日志工具类

LogUtils ,挺多,弄出来一部分,核心思想,就是获取参数,打印日志

public static void doLog(LogConsoleTypeEnum logConsoleTypeEnum, ProceedingJoinPoint joinPoint, OperationLog operateLog, Date startTime, Object result, Throwable exception) {
        Object target = joinPoint.getTarget();
        if (target instanceof ModuleLogService) {
            return;
        }
        OperationLogDTO operationLogDTO = new OperationLogDTO();
        // 填充TraceId
        operationLogDTO.setTraceId(LogContextUtils.getTraceId());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strDate = sdf.format(DateUtil.date());
        operationLogDTO.setStartTime(strDate);
        // 填充用户信息
        //setUserInfo(operationLogDTO);
        // 填充模块信息
        setModuleInfo(operationLogDTO, joinPoint, operateLog);
        // 填充请求信息
        setRequestInfo(operationLogDTO);
        // 填充方法信息
        setMethodInfo(operationLogDTO, joinPoint, operateLog, startTime, result, exception);
        if (exception != null) {
            //从控制台清理返回结果
            operationLogDTO.setResultData(null);
            printlnLog("Exception", operationLogDTO);
        }
        if (operateLog == null) {
            //从控制台清理返回结果
            operationLogDTO.setResultData(null);
            printlnLog(logConsoleTypeEnum.getName(), operationLogDTO);
        } else {
            //可按不同级别保存日志内容
            if (operateLog.saveDB() && logConsoleTypeEnum.getName().equals("API")) {
                ModuleLogService moduleLogService = SpringContextUtil.getBeanSkipCheck(ModuleLogService.class);
                if (moduleLogService != null) {
                    // 保存日志到db
                    if (operateLog.async()) {
                        moduleLogService.savePlatLogAsync(operationLogDTO);
                    } else {
                        moduleLogService.savePlatLog(operationLogDTO);
                    }
                    return;
                }
            }
            //从控制台清理返回结果
            operationLogDTO.setResultData(null);
            printlnLog(logConsoleTypeEnum.getName(), operationLogDTO);
        }
    }

链路跟踪自实现

自己生成tradeId和自行管理及处理

package org.lwd.microservice.boot.middle.log.filter;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.lwd.microservice.boot.core.constant.CoreConstant;
import org.lwd.microservice.boot.core.constant.FilterOrderConstant;
import org.lwd.microservice.boot.middle.log.utils.LogContextUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * traceId过滤器
 *
 * @author: lwd
 * @since 2023/06/25
 */
@Slf4j
@Component
@Order(FilterOrderConstant.LOG_FILTER)
public class WebTraceIdFilter extends OncePerRequestFilter {


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//        //这里set后必须在后续操作中删除,不然会导致内存泄漏
        try {
            String traceId = request.getHeader(CoreConstant.TRACE_ID);
            if (StrUtil.isBlank(traceId)) {
                traceId = LogContextUtils.getTraceId();
            }
            LogContextUtils.setTraceId(traceId);
            String logContext = request.getHeader(CoreConstant.CONTEXT_ID);
            if (StrUtil.isNotBlank(logContext)) {
                LogContextUtils.setLogContent(logContext);
            }
            //继续执行
            filterChain.doFilter(request, response);
        } finally {
            //清除MDC
            LogContextUtils.removeMDC();
        }


    }
}

4、 logback.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<!-- configuration file for LogBack (slf4J implementation)
See here for more details: http://gordondickens.com/wordpress/2013/03/27/sawing-through-the-java-loggers/ -->
<configuration scan="true" scanPeriod="30 seconds">

    <contextName>microservice-boot-plat-log</contextName>

    <property name="log.path" value="/Users/weidong/data/logs/microservice-boot-plat"/>
    <property name="server.name" value="microservice-boot-plat"/>
    <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
    <property name="log.pattern"
              value="%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} - %logger{0} traceId:%X{traceId} - %msg%n"/>
    <property name="log.file" value="${log.path}/%d{yyyy-MM-dd}.log.gz"/>
    <property name="log.error.file" value="${log.path}/error.log"/>

    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
        <resetJUL>true</resetJUL>
    </contextListener>

    <!-- To enable JMX Management -->
    <jmxConfigurator/>
    <appender name="log" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.file}</fileNamePattern>
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!--控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- kafka输出 -->
    <!--<appender name="KAFKA" class="io.confluent.log4j5.KafkaAppender">
        <destinationTopic>kafka_topic_name</destinationTopic>
        <keySerializer>org.apache.kafka.common.serialization.StringSerializer</keySerializer>
        <valueSerializer>org.apache.kafka.common.serialization.StringSerializer</valueSerializer>
        <producerConfigurations>
            <property>
                <name>bootstrap.servers</name>
                <value>kafka_broker_server:9092</value>
            </property>
        </producerConfigurations>
    </appender>

    <logger name="com.example" level="INFO" additivity="false">
        <appender-ref ref="KAFKA"/>
    </logger>-->

    <logger name="com.lwd.microservice.boot.*" level="INFO"/>
    <logger name="org.springframework" level="INFO"/>
    <logger name="druid.sql.Statement" level="info"/>
    <root level="info">
        <!--<appender-ref ref="log"/>-->
        <!--<appender-ref ref="KAFKA" />-->
        <appender-ref ref="stdout"/>
    </root>
</configuration>

5、测试类

测试http请求文件

GET http://localhost:8022/dubbo/get
Accept: application/json

###
GET http://localhost:8022/dubbo/testTimeout
Accept: application/json

###
GET http://localhost:8022/test/tget?name=llwd

###

POST http://localhost:8022/test/tpost
Content-Type: application/json

{
  "id": "1",
  "name": "lwd",
  "sex": 1
}

###
GET http://localhost:8022/test/detail?pk=llwd

###

测试接口层

package org.lwd.microservice.boot.plat.controller;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.lwd.microservice.boot.common.api.dto.VisitDubboDTO;
import org.lwd.microservice.boot.common.api.dubbo.VisitDubboService;
import org.lwd.microservice.boot.core.entity.BaseResult;
import org.lwd.microservice.boot.core.entity.WebResult;
import org.lwd.microservice.boot.middle.log.annotation.OperationLog;
import org.lwd.microservice.boot.middle.log.type.LogTypeEnum;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotEmpty;
import java.util.Map;

/**
 * @author weidong
 * @version V1.0.0
 * @description
 * @since 2023/4/7
 */
@Slf4j
@RestController
@RequestMapping("/test/")
@CrossOrigin
public class UserController {

    @DubboReference(check = false, timeout = 6000)
    VisitDubboService visitDubboService;

    @GetMapping(value = "/tget")
    public String testInterGet(String name) {
        log.info("----testInterGet---:{}", name);
        return JSON.toJSONString(name);
    }

    // @OperationLog(busModule = "plat",title = "测试日志",context = "",logType = LogTypeEnum.SELECT,async = false)
    @PostMapping(value = "/tpost")
    public String testInterPost(@RequestBody Map<String, Object> param) {
        log.info("----testInterPost---:{}", JSON.toJSONString(param));
        return JSON.toJSONString(param);
    }

    /**
     * 根据主键查询系统访问记录详情
     *
     * @param pk 主键
     * @return VO
     */
    @GetMapping("detail")
    @OperationLog(busModule = "plat", title = "测试dubbo日志", context = "测试dubbo tradeId", logType = LogTypeEnum.SELECT,async = false)
    public WebResult<Boolean> detailVisitByPk(@Validated @NotEmpty String pk) {

        VisitDubboDTO visitDubboDTO = new VisitDubboDTO();
        visitDubboDTO.setServerIpAddress("2.2.2.2");
        visitDubboService.saveVisitDubboService(visitDubboDTO);

        WebResult<Boolean> webResult = WebResult.success();
        webResult.setData(true);
        return webResult;
    }

}

下篇文章再说dubbo3链路和 springboot log-starter 设计和实现

外传

😜 原创不易,如若本文能够帮助到您的同学
🎉 支持我:关注我+点赞👍+收藏⭐️
📝 留言:探讨问题,看到立马回复
💬 格言:己所不欲勿施于人 扬帆起航、游历人生、永不言弃!🔥

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

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

相关文章

Springboot 定时任务,分布式下幂等性如何解决

一、概述&#xff1a; 在分布式环境下&#xff0c;定时任务的幂等性问题需要考虑多个节点之间的数据一致性和事务处理。 一种解决方法是使用分布式锁来保证同一时间只有一个节点能够执行该任务。具体实现可以使用Redis或Zookeeper等分布式协调工具提供的分布式锁功能。 另一…

CC2530 定时器配置步骤

第一章 CC2530时钟源 (1)时钟源 1、内部RC震荡器(32KHz、16MHz) 2、外部石英晶振(32.768KHz、32MHz) 注意:外部石英晶振比较稳定,在无线收发中采用外部石英晶振 (2)时钟源的切换 用于判断时钟源是否切换成功 第二章 定时/技术器的基本原理 定时/计数器,是一种…

(小程序)uniapp调接口完整流程

(小程序)uniapp调接口完整流程 代码&#xff1a; <script lang"ts" setup>import { ref } from "vue"; const form ref({searchVal: "", });//搜索const searchClick () > {console.log(form.value.searchVal)let data {text: form…

【C#】并行编程实战:实现数据并行(2)

本章继续学习实现数据并行&#xff0c;本文主要介绍任务并行度和自定义分区策略相关内容。 本教程对应学习工程&#xff1a;魔术师Dix / HandsOnParallelProgramming GitCode 2、任务并行度 数据并行设计在系统的多个内核上以并行方式运行循环所带来的的优势&#xff…

嵌入式系统:连接物理世界与数字世界的桥梁

目录 导语&#xff1a; 一. 什么是嵌入式系统&#xff1f; 二. 嵌入式系统的应用领域 三. 嵌入式系统的未来发展趋势 导语&#xff1a; 在当今数字时代&#xff0c;我们离不开各类智能设备的便利&#xff0c;它们在我们的生活中起着至关重要的作用。而这些设备中的关键技术…

基于Java+Swing+Mysql项目信息管理系统

基于JavaSwingMysql项目信息管理系统 一、系统介绍二、功能展示1.主页2.新增项目信息3.删除项目信息 三、数据库四、其他系统实现五、获取源码 一、系统介绍 该系统实现了查看项目列表、新增项目信息、删除项目信息 运行环境&#xff1a;eclipse、idea、jdk1.8 二、功能展示…

2023-最新-发布java工具包到 maven 中央仓库,不踩坑

说明 要想将自己的java工具提交到maven中央仓库并公开&#xff0c;但是maven中央仓库是不允许我们直接上传jar包到它上面的&#xff0c;因此我们只能将jar包发布到它指定的第三方maven仓库&#xff0c;然后这个仓库再将jar包同步到中央仓库。而sonatype的OSSRH仓库就是被认可的…

C++ day42

1、思维导图 2、 #include <iostream> using namespace std;class Person { private:int age;int *p; public://无参构造Person():p(new int(89)){age 18;}//有参构造Person(int age,int num){this->age age;this->pnew int(num);}//拷贝构造函数Person(Person …

MKS SERVO4257D 闭环步进电机_系列11 STM32_脉冲和串口例程

第1部分 产品介绍 MKS SERVO 28D/35D/42D/57D 系列闭环步进电机是创客基地为满足市场需求而自主研发的一款产品。具备脉冲接口和RS485/CAN串行接口&#xff0c;支持MODBUS-RTU通讯协议&#xff0c;内置高效FOC矢量算法&#xff0c;采用高精度编码器&#xff0c;通过位置反馈&a…

【java】线程池简介

线程池简介 一、什么是线程池 线程池是一种利用池化技术思想来实现的线程管理技术&#xff0c;主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。 二、线…

通过zOffice SDK实现灵活的数据统计

我们通过zOffice收集的业务数据&#xff0c;如何进行分析统计呢&#xff1f;或许我们会想到数据透视表或者筛选的方式&#xff0c;这当然可以&#xff0c;但是在某些场景下&#xff0c;通过透视表统计有一定的难度&#xff0c;不够灵活&#xff0c;而zOffice SDK给我们提供了丰…

使用autossh实现内网穿刺

前言 需要在客户公司部署一套系统&#xff0c;要求是能公网访问&#xff0c;但是客户那边没有公网IP。所以打算打算使用autossh实现内网穿刺。 准备 硬件&#xff1a; 我需要一台有独立ip的公网服务器82.82.82.82&#xff08;充当穿刺服务器&#xff09;&#xff0c;一台能…

EasyExcel时间处理时的bug,希望阿里不要不识好歹,尽快修复这个bug。

一、情况描述 我使用阿里的EasyExcel进行数据导入&#xff0c;但是我发现它录入的时间和表格上的数据不一样。Excel中录入的时间是7:30:00和21:20:00&#xff0c;但是实际读取的数据是7:30:00 上午&#xff0c;9&#xff1a;20&#xff1a;00 下午&#xff0c;导致我数据录入失…

【脚本工具】Python在MySQL批量造测试数据

测试过程中经常需要批量造一批数据&#xff0c;主要通过fake库来造测试数据&#xff0c;支持多国语言&#xff0c;注意使用的时候&#xff0c;各国具体情况 一、安装Faker 1、官方下载安装&#xff1a; URL&#xff1a;https://pypi.org/project/Faker/ 2、通过pip命令安装 …

5、Mysql事务原理

一、概述 事务&#xff1a;一组操作要么全部成功&#xff0c;要么全部失败&#xff0c;目的是为了保证数据最终的一致性。 数据库一般都会并发执行多个事务&#xff0c;多个事务可能会并发的对相同的一批数据进行增删改查操作&#xff0c;可能就会导致我们说的脏写、脏读、不可…

群辉助手Synology Assistant使用教程

下载 百度网盘链接直接下载即可 链接&#xff1a;https://pan.baidu.com/s/1XlQEcTCqBTsOgp-761jdOg?pwd5vyf 提取码&#xff1a;5vyf --来自百度网盘超级会员V5的分享 安装 直接双击安装&#xff0c;等待安装完成 能搜索到上图应用说明安装成功 使用 点击搜索按钮&…

浅谈餐饮业油烟在线监控系统的设计与研究

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;现阶段餐饮行业发展十分迅速&#xff0c;为了更好地提升餐饮油烟的监控力度&#xff0c;强化餐饮业油烟在线监控系统的设计成为重中之重。油烟在线监控系统的强化可以降低油烟的排放量&#xff0c;同时在排烟的…

【赠书】算力经济概念提出者的倾力之作,深刻诠释超级计算的未来!

目录 1.内容简介2.读者受众3.图书目录 如果说蒸汽机是工业革命的引擎&#xff0c;发电机是电气时代的引擎&#xff0c;那么计算机就是数字信息时代的引擎&#xff0c;而超级计算机是引领科学计算创新、攀登新高峰的引擎。 现在&#xff0c;公有云的发展如火如荼&#xff0c;云…

八、云尚办公系统-管理端-审批设置

云尚办公系统&#xff1a;管理端-审批设置 B站直达【为尚硅谷点赞】: https://www.bilibili.com/video/BV1Ya411S7aT 本博文以课程相关为主发布&#xff0c;并且融入了自己的一些看法以及对学习过程中遇见的问题给出相关的解决方法。一起学习一起进步&#xff01;&#xff01;…

Android处理内存泄漏

么是内存泄漏&#xff1a; 在Android开发过程中&#xff0c;当一个对象已经不需要再使用了&#xff0c;本该被回收时&#xff0c;而另个正在使用的对象持有它引用从而导致它不能被回收&#xff0c;这就导致本该被回收的对象不能被回收而停留在堆内存中&#xff0c;内存泄漏就产…