SpringBoot中的两种字段自动填充方式

creatby,updateby等字段自动填充

每个字段在插入数据库,或者更新时都要在serviceimpl层对creatby,updateby等字段进行填充,这个太繁琐了,以下两种方法可以实现字段的自动填充。本项目使用第一种

方法一:

首先创建一个AutoFillInterceptor类。下面会对代码逐行分析。
以下代码也可以直接复制粘贴,但前提是你的实体类中的自动填充的字段和下面四个静态常量名字一样。

@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class AutoFillInterceptor implements Interceptor {private static final String CREATE_BY = "createdBy";private static final String UPDATE_BY = "updatedBy";private static final String CREATE_TIME = "createdAt";private static final String UPDATE_TIME = "updatedAt";@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];SqlCommandType sqlCommandType = ms.getSqlCommandType();Object parameter = args[1];if(parameter != null && sqlCommandType!=null){if(SqlCommandType.INSERT.equals(sqlCommandType)){if(parameter instanceof MapperMethod.ParamMap){MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter;ArrayList list= (ArrayList) paramMap.get("list");list.forEach(v->{setFieldValByName(CREATE_TIME, LocalDateTime.now(), v);setFieldValByName(UPDATE_TIME, LocalDateTime.now(), v);});paramMap.put("list", list);}else{// 单条插入的情况// 设置创建人和创建时间字段值setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter);setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);}}else if(SqlCommandType.UPDATE.equals(sqlCommandType)){// 更新操作// 设置更新人和更新时间字段值setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);}}// 继续执行原始方法return invocation.proceed();}private void setFieldValByName(String fieldName, Object fieldVal, Object parameter) {MetaObject metaObject = SystemMetaObject.forObject(parameter);if (metaObject.hasSetter(fieldName)) {metaObject.setValue(fieldName, fieldVal);}}@Overridepublic void setProperties(Properties properties) {Interceptor.super.setProperties(properties);}@Overridepublic Object plugin(Object target) {return Interceptor.super.plugin(target);}
}

代码结构与作用

这是一个实现了MyBatis拦截器(Interceptor接口)的类AutoFillInterceptor,用于在执行SQL操作(INSERT或UPDATE)时,自动填充一些通用字段,比如创建时间(createdAt)、更新时间(updatedAt)等。

在企业级项目中,通常需要记录数据的创建时间和修改时间,这个拦截器就是为了解决这种需求,在新增和修改数据时自动填充这些字段。下面我们来逐行分析代码。

代码逐行解析

@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
  • @Component:Spring的注解,将这个类注册为一个Spring Bean,便于管理。
  • @Intercepts:MyBatis的注解,声明这是一个拦截器,并指定要拦截的目标。
    • @Signature:定义拦截器的具体拦截方法。
      • type = Executor.class:表示拦截MyBatis的Executor类。
      • method = "update":表示拦截update方法,这个方法用于执行更新操作(包括INSERT、UPDATE、DELETE)。
      • args = {MappedStatement.class, Object.class}:指定update方法的参数类型,即SQL映射信息MappedStatement和参数对象Object
public class AutoFillInterceptor implements Interceptor {
  • AutoFillInterceptor类实现了MyBatis的Interceptor接口,用于实现自定义的拦截逻辑。
    private static final String CREATE_BY = "createdBy";private static final String UPDATE_BY = "updatedBy";private static final String CREATE_TIME = "createdAt";private static final String UPDATE_TIME = "updatedAt";
  • 这几行定义了一些常量,分别表示字段名称,如创建者、修改者、创建时间和修改时间。这些常量将在拦截逻辑中用来自动填充字段。
    @Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];SqlCommandType sqlCommandType = ms.getSqlCommandType();Object parameter = args[1];
  • intercept方法是拦截器的核心逻辑。
    • Object[] args = invocation.getArgs():获取拦截方法的参数。
    • MappedStatement ms = (MappedStatement) args[0]:获取MappedStatement,包含了有关SQL语句的信息。
    • SqlCommandType sqlCommandType = ms.getSqlCommandType():获取SQL的操作类型(INSERT、UPDATE、DELETE)。
    • Object parameter = args[1]:获取参数对象,通常是用户要插入或更新的数据。
        if(parameter != null && sqlCommandType != null){
  • 检查参数是否为空,并确认操作类型是否非空,确保有必要继续执行后续操作。
            if(SqlCommandType.INSERT.equals(sqlCommandType)){if(parameter instanceof MapperMethod.ParamMap){MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter;ArrayList list= (ArrayList) paramMap.get("list");list.forEach(v -> {setFieldValByName(CREATE_TIME, LocalDateTime.now(), v);setFieldValByName(UPDATE_TIME, LocalDateTime.now(), v);});paramMap.put("list", list);} else {// 单条插入的情况// 设置创建人和创建时间字段值setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter);setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);}}
  • if (SqlCommandType.INSERT.equals(sqlCommandType)):如果当前SQL是INSERT操作:
    • if (parameter instanceof MapperMethod.ParamMap):判断参数是否是MapperMethod.ParamMap类型,这通常用于批量插入。
      • ArrayList list = (ArrayList) paramMap.get("list"):从参数Map中获取名为list的参数,这是批量插入的数据集合。
      • list.forEach(v -> {...}):对每个元素进行操作,调用setFieldValByName方法设置createdAtupdatedAt为当前时间。
    • else部分:处理单条插入的情况,直接给parameter对象设置创建时间和更新时间。
            else if(SqlCommandType.UPDATE.equals(sqlCommandType)){// 更新操作// 设置更新人和更新时间字段值setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);}
  • else if (SqlCommandType.UPDATE.equals(sqlCommandType)):如果当前SQL是UPDATE操作:
    • 使用setFieldValByName方法将updatedAt字段设置为当前时间。
        }// 继续执行原始方法return invocation.proceed();}
  • 最终通过invocation.proceed()调用被拦截的方法,继续执行原始的数据库操作。
    private void setFieldValByName(String fieldName, Object fieldVal, Object parameter) {MetaObject metaObject = SystemMetaObject.forObject(parameter);if (metaObject.hasSetter(fieldName)) {metaObject.setValue(fieldName, fieldVal);}}
  • setFieldValByName方法用于设置对象中指定字段的值:
    • MetaObject metaObject = SystemMetaObject.forObject(parameter):创建MetaObject,用于操作传入对象的元数据。
    • if (metaObject.hasSetter(fieldName)):检查对象是否有对应字段的setter方法。
    • metaObject.setValue(fieldName, fieldVal):如果有setter方法,则设置字段的值。
    @Overridepublic void setProperties(Properties properties) {Interceptor.super.setProperties(properties);}@Overridepublic Object plugin(Object target) {return Interceptor.super.plugin(target);}
}
  • setPropertiesplugin方法是Interceptor接口的默认实现,plugin方法用于生成代理对象。

总结

  • 这个拦截器的作用是自动填充createdAtupdatedAt字段,以便在执行INSERT和UPDATE操作时自动记录创建和更新时间。
  • 主要拦截Executorupdate方法,通过判断SQL类型来确定是INSERT还是UPDATE操作,从而设置相应字段。
  • 使用了MyBatis的MetaObject工具类来动态操作参数对象的字段值。

通过这个拦截器,开发者不需要在业务代码中手动设置createdAtupdatedAt,大大减少了重复代码,也保证了这些公共字段的一致性和正确性。

方法二:

这个方法是使用自定义注解来写的,所以要在需要填充的sql上加上这个注解。这个可能更加灵活更加简单把。

公共字段自动填充

技术点:枚举、注解、AOP、反射
创建时间、修改时间、创建人、修改人这4个公共字段。
为mapper方法加注解AutoFill,标识需要进行公共字段自动填充
自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值。
在Mapper的方法上接入AutoFill注解。

public enum OperationType {更新操作UPDATE,插入操作INSERT
}@Target(ElementType.METHOD)当前注解加在什么位置
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {//数据库操作类型:UPDATE INSERTOperationType value();
}

补充注解基本知识

public @interface MyAnnotation {// 定义注解的成员String value(); // 这是一个名为"value"的成员int count() default 1; // 这是一个名为"count"的成员,带有默认值
}@MyAnnotation(value = "Hello", count = 3)
public class MyClass {// 类的代码
}

对于AutoFillAspect类
切点、execution表达式

/*** 自定义切面,实现公共字段自动填充处理逻辑*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {/*** 切入点*/所有的类,所有的方法,所有的参数类型@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void autoFillPointCut(){}/*** 前置通知,在通知中进行公共字段的赋值*/@Before("autoFillPointCut()")指定切入点public void autoFill(JoinPoint joinPoint){连接点log.info("开始进行公共字段自动填充...");//获取到当前被拦截的方法上的数据库操作类型MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象OperationType operationType = autoFill.value();//获得数据库操作类型//获取到当前被拦截的方法的参数--实体对象	做一个约定,实体对象放第一个Object[] args = joinPoint.getArgs();if(args == null || args.length == 0){return;}Object entity = args[0];实体//准备赋值的数据LocalDateTime now = LocalDateTime.now();Long currentId = BaseContext.getCurrentId();//根据当前不同的操作类型,为对应的属性通过反射来赋值if(operationType == OperationType.INSERT){//为4个公共字段赋值try {Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//通过反射为对象属性赋值setCreateTime.invoke(entity,now);setCreateUser.invoke(entity,currentId);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {e.printStackTrace();}}else if(operationType == OperationType.UPDATE){//为2个公共字段赋值try {Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//通过反射为对象属性赋值setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {e.printStackTrace();}}}}
使用
@AutoFill(value = OperationType.UPDATE)
void update(Employee employee);

自定义切面:实现公共字段的自动填充

这段代码使用了 Spring AOP(面向切面编程)来实现对数据库操作时,自动填充一些公共字段,例如创建时间、更新时间、创建人、更新人等。接下来,我们逐行解析这段代码,以帮助你理解各个部分的功能和实现逻辑。

代码结构概览

@Aspect
@Component
@Slf4j
public class AutoFillAspect {// 切入点@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void autoFillPointCut(){}// 前置通知@Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint){log.info("开始进行公共字段自动填充...");...}
}

这段代码定义了一个切面 AutoFillAspect,它会在符合条件的数据库操作方法执行之前,通过前置通知 (@Before) 自动对某些公共字段进行填充。

注解解释

  1. @Aspect:表示当前类是一个切面类,用于定义通知和切入点。
  2. @Component:把这个切面类注册为 Spring 容器中的一个组件。
  3. @Slf4j:用来启用日志功能,以方便调试和记录信息。
切入点定义
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
解释:
  • @Pointcut:用于定义一个切入点,描述哪些方法需要被切面逻辑拦截。
  • execution(* com.sky.mapper.*.*(..)):匹配 com.sky.mapper 包下的所有类和所有方法,(..) 表示任意参数类型和数量。
  • && @annotation(com.sky.annotation.AutoFill):表示只拦截被 @AutoFill 注解标记的方法。

通过这种定义,只有符合指定包下的类且有 @AutoFill 注解的方法,才会被切面逻辑拦截。

前置通知(Before Advice)

@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {log.info("开始进行公共字段自动填充...");...
}
  • @Before("autoFillPointCut()"):这是前置通知,表示在切入点所匹配的方法执行之前,执行 autoFill() 方法。
  • JoinPoint joinPointJoinPoint 是一个连接点,表示被拦截的方法,允许获取到目标方法的一些信息,比如方法名和参数等。

获取注解和方法信息

MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
OperationType operationType = autoFill.value();
  1. MethodSignature signature = (MethodSignature) joinPoint.getSignature();:获取当前拦截的方法的签名信息,转换为 MethodSignature 类型。
  2. AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);:获取方法上的 @AutoFill 注解对象。
  3. OperationType operationType = autoFill.value();:获取注解中指定的数据库操作类型(例如 INSERT 或 UPDATE)。

获取方法参数

Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {return;
}
Object entity = args[0];
  • Object[] args = joinPoint.getArgs();:获取当前被拦截的方法的参数。
  • if (args == null || args.length == 0):如果没有参数,直接返回。
  • Object entity = args[0];:假设第一个参数是实体对象,用于操作数据库。这里有一个约定,即实体对象总是第一个参数。

准备赋值的数据

LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
  • LocalDateTime now = LocalDateTime.now();:获取当前时间,用于填充创建时间和更新时间。
  • Long currentId = BaseContext.getCurrentId();:获取当前操作用户的 ID,用于填充创建人和更新人信息。

根据操作类型进行赋值

插入操作(INSERT)
if (operationType == OperationType.INSERT) {try {Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);setCreateTime.invoke(entity, now);setCreateUser.invoke(entity, currentId);setUpdateTime.invoke(entity, now);setUpdateUser.invoke(entity, currentId);} catch (Exception e) {e.printStackTrace();}
}
  • if (operationType == OperationType.INSERT):如果数据库操作类型是插入(INSERT)。
  • 通过反射的方式获取实体类中的 setCreateTimesetCreateUsersetUpdateTimesetUpdateUser 方法。
  • invoke() 方法用于调用这些 setter 方法并传入相应的值,完成公共字段的赋值。
更新操作(UPDATE)
else if (operationType == OperationType.UPDATE) {try {Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);setUpdateTime.invoke(entity, now);setUpdateUser.invoke(entity, currentId);} catch (Exception e) {e.printStackTrace();}
}
  • else if (operationType == OperationType.UPDATE):如果操作类型是更新(UPDATE)。
  • 这里只需填充更新相关的字段,即更新时间和更新人。

小结

这段代码实现了对数据库操作的公共字段自动填充,具体如下:

  • 定义一个切面 AutoFillAspect,用于拦截特定包中的方法,并且方法需要用 @AutoFill 注解进行标记。
  • 使用 AOP 的前置通知在方法执行前进行字段自动填充。
  • 通过反射机制获取实体对象的方法并进行赋值,根据操作类型填充不同的字段。

这使得代码变得更加简洁和可维护,减少了重复的公共字段赋值逻辑,也方便对创建时间、更新时间等公共属性的一致性管理。

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

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

相关文章

宏景eHR uploadLogo.do 任意文件上传致RCE漏洞复现

0x01 产品简介 宏景eHR人力资源管理软件是一款专为复杂单组织或多组织客户设计的人力资源管理软件,融合了最新的互联网技术和先进的人力资源管理理念和实践。宏景eHR软件支持B/S架构,特别适合集团化管理和跨地域使用。它提供了全面的人力资源管理功能,包括人员、组织机构、…

ssm基于JAVA的网上订餐管理系统+vue

系统包含:源码论文 所用技术:SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习,获取源码看文章最下面 需要定制看文章最下面 目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容…

NVR设备ONVIF接入平台EasyCVR私有化部署视频平台如何安装欧拉OpenEuler 20.3 MySQL

在当今数字化时代,安防视频监控系统已成为保障公共安全和个人财产安全的重要工具。NVR设备ONVIF接入平台EasyCVR作为一款功能强大的智能视频监控管理平台,它不仅提供了视频远程监控、录像、存储与回放等基础功能,还涵盖了视频转码、视频快照、…

测试网空投进行中 — 全面了解 DePIN 赛道潜力项目 ICN Protocol 及其不可错过的早期红利

随着云计算技术的飞速发展,越来越多的企业和个人对云服务的需求变得多样化且复杂化。然而,传统的中心化云服务平台(如AWS、微软Azure等)往往存在着高成本、数据隐私保护不足以及灵活性差等问题。 为了解决这些挑战,Imp…

CulturalBench :一个旨在评估大型语言模型在全球不同文化背景下知识掌握情况的基准测试数据集

2024-10-04,为了提升大型语言模型在不同文化背景下的实用性,华盛顿大学、艾伦人工智能研究所等机构联合创建了CulturalBench。这个数据集包含1,227个由人类编写和验证的问题,覆盖了包括被边缘化地区在内的45个全球区域。CulturalBench的推出&…

CAD VBA 图元颜色跟随图层

效果如下: 一、所有图元颜色为bylayer Sub 图元颜色跟随图层() Dim item As AcadEntityFor Each item In ThisDrawing.ModelSpace item.color acByLayer Next ThisDrawing.Regen acActiveViewport End Sub二、与图层颜色相同(不是bylayer):…

介绍一下数组(c基础)(smart 版)

c初期,记住规则,用规则。 我只是介绍规则。(有详细版,这适合smart人看) 数组(同类型) int arr[n] {} ; int 是 元素类型。 int arr[n] {} ; arr为标识符。 {} 集合,元素有次…

【数据结构】插入排序——直接插入排序 和 希尔排序

直接插入排序 和 希尔排序 一、直接插入排序二、直接插入排序的弊端三、希尔排序(1)对插入排序的联想(2)希尔排序的思路 四、直接插入排序和希尔排序效率对比1>随机生成10000个数2>我们随机生成100000个数3>我们随机生成…

python使用turtle画图快速入门,轻松完成作业练习

turtle介绍 turtle是一个绘图库,可以通过编程进行绘图。其模拟了一个乌龟在屏幕上的运动过程。该库通常用于给青少年学习编程,当然,也可以使用其进行作图。 在一些学校中,可能在python学习的课程中,要求完成turtle绘…

K8S群集调度二

一、污点(Taint) 和 容忍(Tolerations) 1.1、污点(Taint) 设置在node上是对pod的一种作用 节点的亲和性,是Pod的一种属性(偏好或硬性要求),它使Pod被吸引到一类特定的节点 而Taint 则相反,它使节点能够排斥一类特…

分布式唯一ID生成(二): leaf

文章目录 本系列前言号段模式双buffer优化biz优化动态step源码走读 雪花算法怎么设置workerId解决时钟回拨源码走读 总结 本系列 漫谈分布式唯一ID分布式唯一ID生成(二):leaf(本文)分布式唯一ID生成(三&am…

MVDR:最小方差无失真响应技术解析

目录 什么是MVDR?MVDR的工作原理主要步骤MVDR的应用场景MVDR的优势与挑战结论 什么是MVDR? MVDR(Minimum Variance Distortionless Response,最小方差无失真响应)是一种用于信号处理中的自适应滤波技术,广…

Flink安装和Flink CDC实现数据同步

一,Flink 和Flink CDC 1, Flink Apache Flink是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。 中文文档 Apache Flink Documentation | Apache Flink 官方文档 :https://flink.apache.org Flink 中文社区…

【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案

作者:CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 使用环境:WebStorm 目录 问题概述 原因 解决方案 解决方法 潜在问题修改 最终效果呈现 额外内容 管理员界面路由配置 WebStorm背景更换 法一: 法二: 问题概…

MCU面试题

面试题 1、Crotex-M 处理器才用的架构是"v7" Cortex-M3处理器是基于ARMv7-M架构的处理器,支持更丰富的指令集,包括许多32位指令,这些指令可以高效的使用高位寄存器。另外,M3还支持: 查表跳转指令和条件执行&…

Mysql COUNT() 函数详解

在使用Mysql的时候,作为开发者,聚合函数是肯定会用到的,下面就来说说我们常用到的统计行数的聚合函数 COUNT()。 COUNT() 的几种用法 说到COUNT() 函数,最常用的几种方法就是 COUNT(*) 、COUNT(1)、 COUNT(column),那…

基于SSM的图书馆座位预约系统+lw示例参考

#1.项目介绍 系统角色:管理员、普通用户功能模块:管理员(用户管理、座位管理、座位分类管理、图书馆管理、预约信息管理、退座管理、系统管理等)、普通用户(信息查看、图书馆管理、个人中心、座位预约等)技…

【数字图像处理+MATLAB】计算并显示灰度图像的直方图(Histogram):使用 imhist 函数

引言 imhist 是 MATLAB 中的一个函数,用于计算并显示图像的直方图。 直方图是一种统计工具,用于显示图像中各个亮度级别的像素数量。直方图的垂直轴表示像素数量,水平轴表示亮度级别。 函数详解 基本语法: imhist(I) imhist(I…

了解云计算工作负载保护的重要性及必要性

云计算de小白 云计算技术的快速发展使数据和应用程序安全成为一种关键需求,而不仅仅是一种偏好。随着越来越多的客户公司将业务迁移到云端,保护他们的云工作负载(指所有部署的应用程序和服务)变得越来越重要。云工作负载保护&…

windows server2019下载docker拉取redis等镜像并运行项目

一、基本概念 1、windows server 指由微软公司开发的“Windows”系列中的“服务器”版本。这意味着它是基于Windows操作系统的,但专门设计用于服务器环境,而不是普通的桌面或个人用户使用。主要用途包括服务器功能、用户和资源管理、虚拟化等 2、dock…