自定义注解实现 后台系统-记录日志功能

文章目录

  • 1 记录日志
    • 1.1 记录日志的意义
    • 1.2 日志数据表结构
    • 1.3 记录日志思想
    • 1.4 切面类环境搭建
      • 1.4.1 日志模块创建
      • 1.4.2 自定义Log注解
      • 1.4.3 OperatorType
      • 1.4.4 LogAspect
      • 1.4.5 EnableLogAspect
      • 1.4.6 测试日志切面类
    • 1.5 保存日志数据
      • 1.5.1 SysOperLog
      • 1.5.2 LogAspect
      • 1.5.3 AsyncOperLogService
      • 1.5.4 SysOperLogMapper
      • 1.5.5 SysOperLogMapper.xml
      • 1.5.6 测试
    • 1.6 事务失效
      • 1.6.1 事务失效演示
      • 1.6.2 问题分析
      • 1.6.3 问题解决

本文是尚硅谷商品zx的笔记,对于aop日志记录这一块感觉很有用,特此记录一下

后端代码地址:https://gitee.com/zcystart/shopping-mall
前端代码地址:https://gitee.com/zcystart/shopping-mall-vue

1 记录日志

记录日志:记录业务人员的操作日志【删除数据、修改数据、新增操作…】

1.1 记录日志的意义

后台管理系统记录操作日志的意义非常重要,主要体现在以下几个方面:

1、安全性:操作日志可以记录管理员操作行为,以此来监控和防止管理员滥用权限或进行其他不当操作。如果后台管理系统没有记录操作日志,那么一旦出现不当操作,就无法对其进行追踪和定位,造成不可估量的安全风险。

2、追溯性:操作日志可以帮助管理员及时发现问题,并可以通过日志进行快速定位和处理。例如某个用户投诉自己的订单异常,管理员可以直接通过查询该订单的操作日志,找到问题所在并进行修改或解决。

因此,后台管理系统记录操作日志,对于维护系统的安全稳定性、保障客户数据的完整性和隐私性、提高系统及时响应和处理能力等方面具有重要意义,是保障企业正常运营和客户满意度的重要手段。

1.2 日志数据表结构

记录操作日志的表结构如下所示:

CREATE TABLE `sys_oper_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
  `title` varchar(50) DEFAULT '' COMMENT '模块标题',
  `business_type` varchar(20) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
  `method` varchar(100) DEFAULT '' COMMENT '方法名称',
  `request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
  `operator_type` varchar(20) DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
  `oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
  `dept_name` varchar(50) DEFAULT '' COMMENT '部门名称',
  `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
  `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
  `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
  `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
  `status` int DEFAULT '0' COMMENT '操作状态(0正常 1异常)',
  `error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
  `oper_time` datetime DEFAULT NULL COMMENT '操作时间',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb3 COMMENT='操作日志记录';

1.3 记录日志思想

原始做法的伪代码实现

// 保存品牌的方法
@PostMapping("save")
public Result save( @RequestBody Brand brand) {
    
    // 创建SysOperLog对象封装操作日志的相关参数
    SysOperLog sysOperLog = new SysOperLog() ;
    sysOperLog.setTitle("品牌管理")
    sysOperLog.setBusinessType("新增品牌")
    sysOperLog.setMethod("com.atguigu.spzx.product.controller.BrandController.save()")
    ...
	
    //执行业务操作
    brandService.save(brand);
    Result result = Result.build(null , ResultCodeEnum.SUCCESS) ; 
    
    // 将响应结果设置到SysOperLog对象中
    sysOperLog.setJsonResult(JSON.toJsonString(result)) ;
    
    // 保存日志数据
    sysOperLogService.save(sysOperLog) ;
    
    return result ;
}


// 修改品牌的方法
@PostMapping("updateById")
public Result updateById( @RequestBody Brand brand) {
    
    // 创建SysOperLog对象封装操作日志的相关参数
    SysOperLog sysOperLog = new SysOperLog() ;
    sysOperLog.setTitle("品牌管理")
    sysOperLog.setBusinessType("修改品牌")
    sysOperLog.setMethod("com.atguigu.spzx.product.controller.BrandController.updateById()")
    ...
	
    //执行业务操作
    brandService.updateById(brand);
    Result result = Result.build(null , ResultCodeEnum.SUCCESS) ; 
    
    // 将响应结果设置到SysOperLog对象中
    sysOperLog.setJsonResult(JSON.toJsonString(result)) ;
    
    // 保存日志数据
    sysOperLogService.save(sysOperLog) ;
    
    return result ;
}

上述方式存在的弊端:

1、需要更改每一个业务接口,不符合开闭原则(对修改关闭对扩展开放)

2、在每一个业务接口中都需要添加记录日志的代码,影响开发效率

3、业务接口中添加记录日志的代码非常类似,每一个业务接口中都编写一次代码复用性较差

AOP记录日志

AOP记录日志的主要优点包括:

1、低侵入性:AOP记录日志不需要修改原有的业务逻辑代码,只需要新增一个切面即可。

2、统一管理:通过AOP记录日志可以将各个模块中需要记录日志的部分进行统一管理,降低了代码重复度,提高了代码可维护性和可扩展性。

3、提升效率:通过引入AOP记录日志,可以避免手动编写日志记录代码,减少了开发人员的工作量,提升了开发效率。

4、安全性:通过AOP记录日志,可以收集系统的操作日志,帮助管理员及时发现问题并进行调整,从而提高系统的安全性。

AOP记录日志的整体思想

1、基于自定义注解来确定切入点【优势:可以通过自定义注解携带一些变化的参数,比如模块名称】

2、基于环绕通知来完成日志记录

1.4 切面类环境搭建

1.4.1 日志模块创建

具体步骤:

1、在common模块下创建一个独立的记录日志的模块【common-log】

在这里插入图片描述

2、在该模块下加入如下的依赖

<dependencies>
    <dependency>
        <groupId>com.atguigu</groupId>
        <artifactId>spzx-model</artifactId>
        <version>1.0-SNAPSHOT</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <scope>provided</scope>
    </dependency>
    
    <dependency>
        <groupId>com.atguigu</groupId>
        <artifactId>common-util</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

1.4.2 自定义Log注解

自定义Log注解,如下所示:

//  com.atguigu.spzx.common.log.annotation;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {		// 自定义操作日志记录注解

    public String title() ;								// 模块名称
    public OperatorType operatorType() default OperatorType.MANAGE;	// 操作人类别
    public int businessType() ;     // 业务类型(0其它 1新增 2修改 3删除)
    public boolean isSaveRequestData() default true;   // 是否保存请求的参数
    public boolean isSaveResponseData() default true;  // 是否保存响应的参数
    
}

1.4.3 OperatorType

操作人枚举类定义:

// com.atguigu.spzx.common.log.enums;
public enum OperatorType {		// 操作人类别
    OTHER,		// 其他
    MANAGE,		// 后台用户
    MOBILE		// 手机端用户
}

1.4.4 LogAspect

定义一个切面类,并且在该切面类中提供一个环绕通知方法,代码如下所示:

// com.atguigu.spzx.common.log.aspect;
@Aspect
@Component
@Slf4j
public class LogAspect {            // 环绕通知切面类定义

    @Around(value = "@annotation(sysLog)")
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint , Log sysLog) {
        String title = sysLog.title();
        log.info("LogAspect...doAroundAdvice方法执行了"+title);
        System.out.println("LogAspect...doAroundAdvice方法执行了"+title);
        Object proceed = null;
        try {
            proceed = joinPoint.proceed();              // 执行业务方法
        } catch (Throwable e) {                         // 代码执行进入到catch中,业务方法执行产生异常
            throw new RuntimeException(e);
        }
        return proceed ;                                // 返回执行结果
    }
}

1.4.5 EnableLogAspect

想让LogAspect这个切面类在其他的业务服务中进行使用,那么就需要该切面类纳入到Spring容器中。Spring Boot默认会扫描和启动类所在包相同包中的bean以及子包中的bean。而LogAspect切面类不满足扫描条件,因此无法直接在业务服务中进行使用。那么此时可以通过自定义注解进行实现,

代码如下所示:

// com.atguigu.spzx.common.log.annotation;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(value = LogAspect.class)            // 通过Import注解导入日志切面类到Spring容器中
public @interface EnableLogAspect {
    
}

1.4.6 测试日志切面类

  • 在spzx-manager引入依赖
 <dependency>
     <groupId>com.atguigu</groupId>
     <artifactId>common-log</artifactId>
     <version>1.0-SNAPSHOT</version>
 </dependency>
  • 在ManagerApplication服务的启动类上添加**@EnableLogAspect**注解
@EnableLogAspect
@EnableScheduling
@SpringBootApplication
@EnableConfigurationProperties(value = {UserAuthProperties.class, MinioProperties.class})
public class ManagerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ManagerApplication.class , args) ;
    }

}
  • 在要添加日志功能的业务接口方法上添加Log注解,启动服务进行测试。
@Log(title = "角色添加",businessType = 0) //添加Log注解,设置属性
@PostMapping(value = "/saveSysRole")
public Result saveSysRole(@RequestBody SysRole SysRole) {
    sysRoleService.saveSysRole(SysRole) ;
    return Result.build(null , ResultCodeEnum.SUCCESS) ;
}
  • 结果:
2023-07-19 14:09:32 [INFO ] com.atguigu.spzx.common.aspect.LogAspect LogAspect...doAroundAdvice方法执行了角色添加

1.5 保存日志数据

更改LogAspect切面类代码完成日志数据数据的保存。

1.5.1 SysOperLog

定义一个与日志数据库表相对应的实体类:

// com.atguigu.spzx.model.entity.system;
@Data
public class SysOperLog extends BaseEntity {

	private String title;					// 模块标题
	private String method;					// 方法名称
	private String requestMethod;			// 请求方式
	private String operatorType;			// 操作类别(0其它 1后台用户 2手机端用户)
    private Integer businessType ;			// 业务类型(0其它 1新增 2修改 3删除)
	private String operName;				// 操作人员
	private String operUrl;					// 请求URL
	private String operIp;					// 主机地址
	private String operParam;				// 请求参数
	private String jsonResult;				// 返回参数
	private Integer status;					// 操作状态(0正常 1异常)
	private String errorMsg;				// 错误消息

}

1.5.2 LogAspect

  • common-log添加工具类
package com.atguigu.spzx.common.log.utils;

public class LogUtil {

    //操作执行之后调用
    public static void afterHandlLog(Log sysLog, Object proceed,
                                     SysOperLog sysOperLog, int status ,
                                     String errorMsg) {
        if(sysLog.isSaveResponseData()) {
            sysOperLog.setJsonResult(JSON.toJSONString(proceed));
        }
        sysOperLog.setStatus(status);
        sysOperLog.setErrorMsg(errorMsg);
    }

    //操作执行之前调用
    public static void beforeHandleLog(Log sysLog,
                                       ProceedingJoinPoint joinPoint,
                                       SysOperLog sysOperLog) {

        // 设置操作模块名称
        sysOperLog.setTitle(sysLog.title());
        sysOperLog.setOperatorType(sysLog.operatorType().name());

        // 获取目标方法信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature() ;
        Method method = methodSignature.getMethod();
        sysOperLog.setMethod(method.getDeclaringClass().getName());

        // 获取请求相关参数
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        sysOperLog.setRequestMethod(request.getMethod());
        sysOperLog.setOperUrl(request.getRequestURI());
        sysOperLog.setOperIp(request.getRemoteAddr());

        // 设置请求参数
        if(sysLog.isSaveRequestData()) {
            String requestMethod = sysOperLog.getRequestMethod();
            if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
                String params = Arrays.toString(joinPoint.getArgs());
                sysOperLog.setOperParam(params);
            }
        }
        sysOperLog.setOperName(AuthContextUtil.get().getUserName());
    }
}
  • 日志切面类代码修改,如下所示:
// com.atguigu.spzx.common.log.aspect;
package com.atguigu.spzx.common.log.aspect;
@Aspect
@Component
@Slf4j
public class LogAspect {            // 环绕通知切面类定义

    @Autowired
    private AsyncOperLogService asyncOperLogService ;

    @Around(value = "@annotation(sysLog)")
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint , Log sysLog) {

        // 构建前置参数
        SysOperLog sysOperLog = new SysOperLog() ;

        LogUtil.beforeHandleLog(sysLog , joinPoint , sysOperLog) ;

        Object proceed = null;
        try {
            proceed = joinPoint.proceed();
            // 执行业务方法
            LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 0 , null) ;
            // 构建响应结果参数
        } catch (Throwable e) {                                 // 代码执行进入到catch中,
            // 业务方法执行产生异常
            e.printStackTrace();                                // 打印异常信息
            LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 1 , e.getMessage()) ;
            throw new RuntimeException();
        }

        // 保存日志数据
        asyncOperLogService.saveSysOperLog(sysOperLog);

        // 返回执行结果
        return proceed ;
    }
}

1.5.3 AsyncOperLogService

在common-log模块中定义保存日志数据的service接口,然后在具体的业务服务中给出实现。

//  com.atguigu.spzx.common.log.service;
public interface AsyncOperLogService {			// 保存日志数据
    public abstract void saveSysOperLog(SysOperLog sysOperLog) ;
}

// com.atguigu.spzx.manager.service;
@Service
public class AsyncOperLogServiceImpl implements AsyncOperLogService {

    @Autowired
    private SysOperLogMapper sysOperLogMapper;

    @Async      // 异步执行保存日志操作
    @Override
    public void saveSysOperLog(SysOperLog sysOperLog) {
        sysOperLogMapper.insert(sysOperLog);
    }
    
}

注意:要想通过异步线程执行saveSysOperLog方法,那么此时就需要在启动类上添加**@EnableAsync**注解。

1.5.4 SysOperLogMapper

SysOperLogMapper持久层接口:

// com.atguigu.spzx.manager.mapper;
@Mapper
public interface SysOperLogMapper {
    public abstract void insert(SysOperLog sysOperLog);
}

1.5.5 SysOperLogMapper.xml

  • 在SysOperLogMapper.xml映射文件中添加如下的SQL语句:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atguigu.spzx.mapper.SysOperLogMapper">

    <insert id="insert" >
        insert into sys_oper_log (
            id,
            title,
            method,
            request_method,
            operator_type,
            oper_name,
            oper_url,
            oper_ip,
            oper_param,
            json_result,
            status,
            error_msg
        ) values (
                     #{id},
                     #{title},
                     #{method},
                     #{requestMethod},
                     #{operatorType},
                     #{operName},
                     #{operUrl},
                     #{operIp},
                     #{operParam},
                     #{jsonResult},
                     #{status},
                     #{errorMsg}
                 )
    </insert>

</mapper>

1.5.6 测试

  • 在需要添加操作日志的接口方法上添加**@Log**注解进行测试。
@Log(title = "品牌列表",businessType = 0,operatorType = OperatorType.MANAGE)
//品牌列表(分页)
@GetMapping("/{page}/{limit}")
public Result list(@PathVariable Integer page,
                   @PathVariable Integer limit) {
    PageInfo<Brand> pageInfo = brandService.list(page,limit);
    return Result.build(pageInfo,ResultCodeEnum.SUCCESS);
}

1.6 事务失效

当我们自定义了切面类以后,如果不注意异常的处理,那么此时就会出现事务失效的情况。

1.6.1 事务失效演示

以给角色分配菜单的代码为例,演示事务失效的问题,代码如下所示:

// com.atguigu.spzx.manager.service.impl.SysRoleMenuServiceImpl
@Log(title = "角色菜单模块" , businessType = 2 )		
@Transactional
@Override
public void doAssign(AssginMenuDto assginMenuDto) {

    // 根据角色的id删除其所对应的菜单数据
    sysRoleMenuMapper.deleteByRoleId(assginMenuDto.getRoleId());

    int a = 1 / 0 ;		// 手动抛出异常

    // 获取菜单的id
    List<Map<String, Number>> menuInfo = assginMenuDto.getMenuIdList();
    if(menuInfo != null && menuInfo.size() > 0) {
        sysRoleMenuMapper.doAssign(assginMenuDto) ;
    }

}

注意:不加@Log注解事务可以进行回滚,但是加上该注解以后事务就会失效。

1.6.2 问题分析

Spring的事务控制是通过aop进行实现的,在框架底层会存在一个事务切面类,当业务方法产生异常以后,事务切面类感知到异常以后事务进行回滚。

当系统中存在多个切面类的时候,Spring框架会按照**@Order注解的值对切面进行排序,@Order的值越小优先级越高,@Order的值越大优先级越低。优先级越高的切面类越优先执行,当我们没有给切面类指定排序值的时候,我们自定义的切面类的优先级和aop切面类的优先级相同,那么此时事务切面类的优先级要高于自定义切面类**,那么切面类的执行顺序如下所示:

在这里插入图片描述

当在自定义切面类中对异常进行了捕获,没有将异常进行抛出,那么此时事务切面类是感知不到异常的存在,因此事务失效。

1.6.3 问题解决

解决方案一:使用@Order注解提高自定义切面类的优先级

解决方案二:在自定义切面类的catch中进行异常的抛出

在这里插入图片描述


**注意**:不加@Log注解事务可以进行回滚,但是加上该注解以后事务就会失效。



### 1.6.2 问题分析

Spring的事务控制是通过aop进行实现的,在框架底层会存在一个事务切面类,当业务方法产生异常以后,事务切面类感知到异常以后事务进行回滚。

当系统中存在多个切面类的时候,Spring框架会按照**@Order**注解的值对切面进行排序,@Order的值越小优先级越高,@Order的值越大优先级越低。优先级越高的切面类越优先执行,当我们没有给切面类指定排序值的时候,我们自定义的切面类的优先级和aop切面类的优先级相同,那么此时**事务切面类的优先级要高于自定义切面类**,那么切面类的执行顺序如下所示:

[外链图片转存中...(img-s1aCC4rj-1703140264446)] 

当在自定义切面类中对异常进行了捕获,没有将异常进行抛出,那么此时事务切面类是感知不到异常的存在,因此事务失效。



### 1.6.3 问题解决

解决方案一:使用@Order注解提高自定义切面类的优先级

解决方案二:在自定义切面类的catch中进行异常的抛出

[外链图片转存中...(img-X5F8nHzi-1703140264447)]

























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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【教程】cocos2dx资源加密混淆方案详解

1,加密,采用blowfish或其他 2,自定是32个字符的混淆code 3,对文件做blowfish加密,入口文件加密前将混淆code按约定格式(自定义的文件头或文件尾部)写入到文件 4,遍历资源目录,对每个文件做md5混淆,混淆原始串“相对路径”“文件名”混淆code, 文件改名并且移动到资源目录根…

C# try-catch异常处理的用法

try-catch 是一种在编程语言中用于捕获和处理异常的结构。它的作用是在可能引发异常的代码块中进行异常处理&#xff0c;以避免程序崩溃或产生不可预料的结果。 当在 try 块中的代码执行时&#xff0c;如果发生了异常&#xff0c;程序会立即跳转到对应的 catch 块。catch…

http客户端Feign

http客户端Feign 文章目录 http客户端Feign定义和使用Feign客户端自定义Feign的配置Feign的性能优化feign的最佳实践 定义和使用Feign客户端 <!-- feign客户端依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>s…

为什么C语言没有被C++所取代呢?

今日话题&#xff0c;为什么C语言没有被C所取代呢&#xff1f;虽然C是一个功能更强大的语言&#xff0c;但C语言在嵌入式领域仍然广泛使用&#xff0c;因为它更轻量级、更具可移植性&#xff0c;并且更适合在资源受限的环境中工作。这就是为什么C语言没有被C所取代的原因。如果…

玩转 Scrapy 框架 (一):Scrapy 框架介绍及使用入门

目录 一、Scrapy 框架介绍二、Scrapy 入门 一、Scrapy 框架介绍 简介&#xff1a; Scrapy 是一个基于 Python 开发的爬虫框架&#xff0c;可以说它是当前 Python 爬虫生态中最流行的爬虫框架&#xff0c;该框架提供了非常多爬虫的相关组件&#xff0c;架构清晰&#xff0c;可扩…

高速视频采集卡设计方案:620-基于PCIe的高速视频采集卡

一、产品概述 基于PCIe的高速视频采集卡&#xff0c;通过PCIe3.0X8传输到存储计算服务器&#xff0c;实现信号的分析、存储。 北京太速科技 产品固化FPGA逻辑&#xff0c;适配视频连续采集&#xff0c;缓存容量2GB&#xff0c;开源的PCIe QT客户端软件&#xff0c…

常用网络接口自动化测试框架

(一&#xff09;GUI界面测试工具&#xff1a;jmeter 1、添加线程组 2、添加http请求 3、为线程组添加察看结果树 4、写入接口参数并运行 5、在查看结果树窗口查看结果 6、多组数据可增加CSVDataSetConfig(添加.csv格式的文件&#xff0c;并在参数值里以${x}格式写入) 此时变量…

第五讲观测值中与卫星、接收机有关的误差 第六讲观测值中与信号传播路径有关的误差以及电离层、对流层相关模型 | GNSS(RTK)课程学习笔记day3

说明&#xff1a;以下笔记来自计算机视觉life吴桐老师课程&#xff1a;从零掌握GNSS、RTK定位[链接]&#xff0c;从零掌握RTKLIB[链接]。非原创&#xff01;且笔记仅供自身与大家学习使用&#xff0c;无利益目的。 第五讲 观测值中与卫星、接收机有关的误差 卫星轨道误差 由卫…

如何有效压缩图片大小?保持质量的同时减小文件大小

图片体积过大&#xff0c;不仅仅会导致我们电脑或者手机内存不足&#xff0c;在一些网络平台上传的时候&#xff0c;也会因为限制上传失败&#xff0c;为了有效减少文件所占用的存储空间和上传&#xff0c;我们可以先利用图片压缩功能去把图片缩小kb&#xff0c;那么具体是怎么…

Python Pandas 多重索引DataFrame数据(第19讲)

Python Pandas 多重索引DataFrame数据(第19讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

【powershell】Windows环境powershell 运维之历史文件压缩清理

&#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&am…

Find My资讯|苹果Find My大升级:可添加物品数量翻倍,从16个飙升至32个!

苹果Find My是Find My iPhone&#xff08;查找我的iPhone&#xff09;和Find My Friends&#xff08;查找朋友&#xff09;的结合体&#xff0c;新系统中苹果为了追求简洁&#xff0c;于是把这两个应用整合到一块了。Find My作用于iOS13、iPadOS&#xff08;苹果为iPad定制的新…

笨爸爸工房携手洛阳科学技术馆,开启全新手工课程!

笨爸爸工房与洛阳科学技术馆达成合作&#xff0c;每周六上午9点50分在洛阳科学技术馆开设全新的手工课程。通过丰富多彩的手工课程&#xff0c;让孩子在动手实践中感受传统木艺的乐趣&#xff0c;了解中国传统文化的内涵。笨爸爸工房以木工为媒介&#xff0c;希望通过提升父亲参…

LeetCode 739每日温度 496 下一个更大元素 | 代码随想录25期训练营day58

单调栈1 LeetCode 739 每日温度 2023.12.21 题目链接代码随想录讲解[链接] vector<int> dailyTemperatures(vector<int>& temperatures) {//暴力求解&#xff0c;但会超时/*vector<int> answer(temperatures.size(), 0);for (int i 0; i < tempe…

BWS2000倾角传感器c++测试代码【2】

问题一&#xff1a;串口频率的初始化 由于本次项目之中使用的线长为40米的倾角传感器&#xff0c;需要对于其频率输出存在要求&#xff0c;如何测试其频率如下所示&#xff1a; 如上所示相应的软件&#xff0c;软件中存在一句如果设置后不保存&#xff0c;则存在传感器断电后设…

开源堡垒机JumpServer结合内网穿透实现远程访问

开源堡垒机JumpServer结合内网穿透实现远程访问 前言1. 安装Jump server2. 本地访问jump server3. 安装 cpolar内网穿透软件4. 配置Jump server公网访问地址5. 公网远程访问Jump server6. 固定Jump server公网地址 前言 JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 …

python使用Tesseract

python使用Tesseract 一、介绍二、安装Tesseract三、使用Tesseract3.1 简单例子 一、介绍 Tesseract是一个开源的ocr工具&#xff0c;它是由C编写的&#xff0c;可以直接在Windows上运行&#xff0c;也可以用各种编程语言调用。 二、安装Tesseract Tesseract支持多个平台&am…

C#中var、object和dynamic的区别

在C#编程语言中&#xff0c;我们经常会遇到var、object和dynamic这三个关键字。它们都用于声明变量&#xff0c;但在使用方法和特性上存在一些重要的区别。本文将详细介绍这三者的差异。 目录 var关键字object关键字dynamic关键字总结 var关键字 var是C#语言中的隐式类型推断…

CentOS 7 Tomcat服务的安装

前提 安装ava https://blog.csdn.net/qq_36940806/article/details/134945175?spm1001.2014.3001.5501 1. 下载 wget https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-9/v9.0.84/bin/apache-tomcat-9.0.84.tar.gzps: 可选择自己需要的版本下载安装https://mirr…

使用Python爬取GooglePlay并从复杂的自定义数据结构中实现解析

文章目录 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向感兴趣的朋友可以关注《爬虫JS逆向实战》&#xff0c;对分布…