Java SpringBoot 动态数据源

1. 原理

动态数据源,本质上是把多个数据源存储在一个 Map 中,当需要使用某一个数据源时,使用 key 获取指定数据源进行处理。而在 Spring 中已提供了抽象类 AbstractRoutingDataSource 来实现此功能,继承 AbstractRoutingDataSource 类并覆写其 determineCurrentLookupKey() 方法监听获取 key 即可,该方法只需要返回数据源 key 即可,也就是存放数据源的 Mapkey

因此,我们在实现动态数据源的,只需要继承它,实现自己的获取数据源逻辑即可。AbstractRoutingDataSource 顶级继承了 DataSource,所以它也是可以做为数据源对象,因此项目中使用它作为主数据源。

1.1. AbstractRoutingDataSource 源码解析

![[Pasted image 20240321103621.png]]

        public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
        // 目标数据源 map 集合,存储将要切换的多数据源 bean 信息,可以通过 setTargetDataSource(Map<Object, Object> mp) 设置
        @Nullable
        private Map<Object, Object> targetDataSources;
        // 未指定数据源时的默认数据源对象,可以通过 setDefaultTargetDataSouce(Object obj) 设置
        @Nullable
        private Object defaultTargetDataSource;
		...
        // 数据源查找接口,通过该接口的 getDataSource(String dataSourceName) 获取数据源信息
        private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        //解析 targetDataSources 之后的 DataSource 的 map 集合
        @Nullable
        private Map<Object, DataSource> resolvedDataSources;
        @Nullable
        private DataSource resolvedDefaultDataSource;
    
        //将 targetDataSources 的内容转化一下放到 resolvedDataSources 中,将 defaultTargetDataSource 转为 DataSource 赋值给 resolvedDefaultDataSource
        public void afterPropertiesSet() {
            //如果目标数据源为空,会抛出异常,在系统配置时应至少传入一个数据源
            if (this.targetDataSources == null) {
                throw new IllegalArgumentException("Property 'targetDataSources' is required");
            } else {
                //初始化 resolvedDataSources 的大小
                this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
                //遍历目标数据源信息 map 集合,对其中的 key,value 进行解析
                this.targetDataSources.forEach((key, value) -> {
                    // resolveSpecifiedLookupKey 方法没有做任何处理,只是将 key 继续返回
                    Object lookupKey = this.resolveSpecifiedLookupKey(key);
                    // 将目标数据源 map 集合中的 value 值(Druid 数据源信息)转为 DataSource 类型
                    DataSource dataSource = this.resolveSpecifiedDataSource(value);
                    // 将解析之后的 key,value 放入 resolvedDataSources 集合中
                    this.resolvedDataSources.put(lookupKey, dataSource);
                });
                if (this.defaultTargetDataSource != null) {
                    // 将默认目标数据源信息解析并赋值给 resolvedDefaultDataSource
                    this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
                }
    
            }
        }
    
        protected Object resolveSpecifiedLookupKey(Object lookupKey) {
            return lookupKey;
        }
    
        protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
            if (dataSource instanceof DataSource) {
                return (DataSource)dataSource;
            } else if (dataSource instanceof String) {
                return this.dataSourceLookup.getDataSource((String)dataSource);
            } else {
                throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
            }
        }
        
        // 因为 AbstractRoutingDataSource 继承 AbstractDataSource,而 AbstractDataSource 实现了 DataSource 接口,所有存在获取数据源连接的方法
        public Connection getConnection() throws SQLException {
            return this.determineTargetDataSource().getConnection();
        }
    
        public Connection getConnection(String username, String password) throws SQLException {
            return this.determineTargetDataSource().getConnection(username, password);
        }

		// 最重要的一个方法,也是 DynamicDataSource 需要实现的方法
        protected DataSource determineTargetDataSource() {
            Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
            // 调用实现类中重写的 determineCurrentLookupKey 方法拿到当前线程要使用的数据源的名称
            Object lookupKey = this.determineCurrentLookupKey();
            // 去解析之后的数据源信息集合中查询该数据源是否存在,如果没有拿到则使用默认数据源 resolvedDefaultDataSource
            DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
            if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
                dataSource = this.resolvedDefaultDataSource;
            }
    
            if (dataSource == null) {
                throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
            } else {
                return dataSource;
            }
        }
    
        @Nullable
        protected abstract Object determineCurrentLookupKey();
    }

1.2. 关键类说明

忽略掉 controller/service/entity/mapper/xml介绍。

  • application.yml:数据源配置文件。但是如果数据源比较多的话,根据实际使用,最佳的配置方式还是独立配置比较好。
  • DynamicDataSourceRegister:动态数据源注册配置文件
  • DynamicDataSource:动态数据源配置类,继承自 AbstractRoutingDataSource
  • TargetDataSource:动态数据源注解,切换当前线程的数据源
  • DynamicDataSourceAspect:动态数据源设置切面,环绕通知,切换当前线程数据源,方法注解优先
  • DynamicDataSourceContextHolder:动态数据源上下文管理器,保存当前数据源的 key,默认数据源名,所有数据源 key

1.3. 开发流程

  1. 添加配置文件,设置默认数据源配置,和其他数据源配置
  2. 编写 DynamicDataSource 类,继承 AbstractRoutingDataSource 类,并实现 determineCurrentLookupKey() 方法
  3. 编写 DynamicDataSourceHolder 上下文管理类,管理当前线程的使用的数据源,及所有数据源的 key
  4. 编写 DynamicDataSourceRegister 类通过读取配置文件动态注册多数据源,并在启动类上导入(@Import)该类
  5. 自定义数据源切换注解 TargetDataSource,并实现相应的切面,环绕通知切换当前线程数据源,注解优先级(DynamicDataSourceHolder.setDynamicDataSourceKey() > Method > Class

2. 实现

2.1. 引入 Maven 依赖

<!-- web 模块依赖 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring 核心 aop 模块依赖 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Druid 数据源连接池依赖 -->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
	<version>1.2.8</version>
</dependency>
<!-- mybatis 依赖 -->
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.2.2</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>8.0.24</version>
</dependency>
<!-- lombok 模块依赖 -->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-text</artifactId>
	<version>1.10.0</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>

2.2. application.yml 配置文件

spring:
  datasource:
	type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding-utf8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
custom:
  datasource:
    names: ds1,ds2
    ds1:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/content_center?useUnicode
      username: root
      password: root
    ds2:
	  type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/trade?useUnicode
      username: root
      password: root

2.3. 创建 DynamicDataSource 继承 AbstractRoutingDataSource 类

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

/**
 * @Description: 继承Spring AbstractRoutingDataSource 实现路由切换
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {

	/**
	 * 决定当前线程使用哪种数据源
	 * @return 数据源 key
	 */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

2.4. 编写 DynamicDataSourceHolder 类,管理 DynamicDataSource 上下文

import java.util.ArrayList;
import java.util.List;
 
/**
 * @Description: 动态数据源上下文管理
 */
public class DynamicDataSourceHolder {
    // 存放当前线程使用的数据源类型信息
    private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<String>();
    // 存放数据源 key
    private static final List<String> DATASOURCE_KEYS = new ArrayList<String>();
    // 默认数据源 key
    public static final String DEFAULT_DATESOURCE_KEY = "master";
 
    //设置数据源
    public static void setDynamicDataSourceType(String key) {
        DYNAMIC_DATASOURCE_KEY.set(key);
    }
 
    //获取数据源
    public static String getDynamicDataSourceType() {
        return DYNAMIC_DATASOURCE_KEY.get();
    }
 
    //清除数据源
    public static void removeDynamicDataSourceType() {
        DYNAMIC_DATASOURCE_KEY.remove();
    }

	public static void addDataSourceKey(String key) {
		DATASOURCE_KEYS.add(key)
	}
 
    /**
     * 判断指定 key 当前是否存在
     *
     * @param key
     * @return boolean
     */
    public static boolean containsDataSource(String key){
        return DATASOURCE_KEYS.contains(key);
    }
}

2.5. 编写 DynamicDataSourceRegister 读取配置文件注册多数据源

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
 
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import java.util.Objects;
 
/**
 * @Description: 注册动态数据源
 * 初始化数据源和提供了执行动态切换数据源的工具类
 * EnvironmentAware(获取配置文件配置的属性值)
 */
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
	private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
    // 指定默认数据源类型 (springboot2.0 默认数据源是 hikari 如何想使用其他数据源可以自己配置)
    // private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
    private static final String DEFAULT_DATASOURCE_TYPE = "com.alibaba.druid.pool.DruidDataSource";
    // 默认数据源
    private DataSource defaultDataSource;
    // 用户自定义数据源
    private Map<String, DataSource> customDataSources  = new HashMap<>();
 
    /**
     * 加载多数据源配置
     * @param env 当前环境
     */
    @Override
    public void setEnvironment(Environment env) {
        initDefaultDataSource(env);
        initCustomDataSources(env);
    }
 
 
 
    /**
     * 初始化主数据源
     * @param env
     */
    private void initDefaultDataSource(Environment env) {
        // 读取主数据源
        Map<String, Object> dsMap = new HashMap<>();
        dsMap.put("type", env.getProperty("spring.datasource.type", DEFAULT_DATASOURCE_TYPE));
        dsMap.put("driver", env.getProperty("spring.datasource.driver-class-name"));
        dsMap.put("url", env.getProperty("spring.datasource.url"));
        dsMap.put("username", env.getProperty("spring.datasource.username"));
        dsMap.put("password", env.getProperty("spring.datasource.password"));
        defaultDataSource = buildDataSource(dsMap);
    }
 
 
    /**
     * 初始化更多数据源
     * @param env
     */
    private void initCustomDataSources(Environment env) {
        // 读取配置文件获取更多数据源
        String dsPrefixs = env.getProperty("custom.datasource.names");
        if (!StringUtils.isBlank(dsPrefixs)) {
	        for (String dsPrefix : dsPrefixs.split(",")) {
		        dsPrefix = fsPrefix.trim()
		        if (!StringUtils.isBlank(dsPrefix)) {
			        Map<String, Object> dsMap = new HashMap<>();
			        dsMap.put("type", env.getProperty("custom.datasource." + dsPrefix + ".type", DEFAULT_DATASOURCE_TYPE));
		            dsMap.put("driver", env.getProperty("custom.datasource." + dsPrefix + ".driver-class-name"));
		            dsMap.put("url", env.getProperty("custom.datasource." + dsPrefix + ".url"));
		            dsMap.put("username", env.getProperty("custom.datasource." + dsPrefix + ".username"));
		            dsMap.put("password", env.getProperty("custom.datasource." + dsPrefix + ".password"));
		            DataSource ds = buildDataSource(dsMap);
		            customDataSources.put(dsPrefix, ds);
		        }
	        }
        }
    }
 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        // 将主数据源添加到更多数据源中
        targetDataSources.put(DynamicDataSourceHolder.DEFAULT_DATASOURCE_KEY, defaultDataSource);
        DynamicDataSourceHolder.addDataSourceKey(DynamicDataSourceHolder.DEFAULT_DATASOURCE_KEY);
        // 添加更多数据源
        targetDataSources.putAll(customDataSources);
        for (String key : customDataSources.keySet()) {
            DynamicDataSourceContextHolder.addDataSourceKey(key);
        }
 
        // 创建 DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition); // 注册到 Spring 容器中
        LOGGER.info("Dynamic DataSource Registry");
    }
 
    /**
     * 创建 DataSource
     * @param dsMap 数据库配置参数
     * @return DataSource
     */
    public DataSource buildDataSource(Map<String, Object> dsMap) {
        try {
            Object type = dsMap.get("type");
            if (type == null)
                type = DEFAULT_DATASOURCE_TYPE;// 默认DataSource
 
            Class<? extends DataSource> dataSourceType = (Class<? extends DataSource>)Class.forName((String)type);
            String driverClassName = String.valueOf(dsMap.get("driver"));
            String url = String.valueOf(dsMap.get("url"));
            String username = String.valueOf(dsMap.get("username"));
            String password = String.valueOf(dsMap.get("password"));
            
            // 自定义 DataSource 配置
            DataSourceBuilder<? extends DataSource> factory = DataSourceBuilder.create()
                    .driverClassName(driverClassName)
                    .url(url)
                    .username(username)
                    .password(password)
                    .type(dataSourceType);
            return factory.build();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

2.6. 在启动器类上添加 @Import,导入 register 类

// 注册动态多数据源
@Import({ DynamicDataSourceRegister.class })
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2.7. 自定义注解 @TargetDataSource

/**
 * 自定义多数据源切换注解
 * 优先级:DynamicDataSourceHolder.setDynamicDataSourceKey() > Method > Class
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
    /**
     * 切换数据源名称
     */
    public String value() default DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY;
}

2.8. 定义切面拦截 @TargetDataSource

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;

@Aspect
// 保证在 @Transactional 等注解前面执行
@Order(-1)
@Component
public class DataSourceAspect {
 
    // 设置 DataSource 注解的切点表达式
    @Pointcut("@annotation(com.ayi.config.datasource.DynamicDataSource)")
    public void dynamicDataSourcePointCut(){
 
    }
 
    //环绕通知
    @Around("dynamicDataSourcePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        String key = getDefineAnnotation(joinPoint).value();
        if (!DynamicDataSourceHolder.containsDataSource(key)) {
	        LOGGER.error("数据源[{}]不存在,使用默认数据源[{}]", key, DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY)
	        key = DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY;
        }
        DynamicDataSourceHolder.setDynamicDataSourceKey(key);
        try {
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceHolder.removeDynamicDataSourceKey();
        }
    }
 
    /**
     * 先判断方法的注解,后判断类的注解,以方法的注解为准
     * @param joinPoint 切点
     * @return TargetDataSource
     */
    private TargetDataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        TargetDataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(TargetDataSource.class);
        if (Objects.nonNull(methodSignature)) {
            return dataSourceAnnotation;
        } else {
            Class<?> dsClass = joinPoint.getTarget().getClass();
            return dsClass.getAnnotation(TargetDataSource.class);
        }
    }
 
}

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

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

相关文章

文字图形化:UI设计师的必备能力,带你看看为什么要这么做。

在UI设计中&#xff0c;文字尽可能要进行图形化设计的原因有以下几点&#xff1a; 提高识别性&#xff1a; 图形化设计可以通过视觉效果和形状来吸引用户的注意力&#xff0c;从而提高文字的可识别性。这有助于用户更快地理解并记住信息&#xff0c;同时也可以增强品牌的认知…

Python实现一个简单的计算器

简单版本 使用 Python 的 Tkinter 模块来实现一个简单的图形化计算器。以下是一个基本的示例代码 示例效果 代码源码 import tkinter as tkdef button_click(number):current entry.get()entry.delete(0, tk.END)entry.insert(0, current str(number))def button_clear():e…

翼支付——风控场景中图模型的范式变迁

目录 风控图深度学习模型 风控图大模型

ASP.NET校园新闻发布系统的设计与实现

摘 要 校园新闻发布系统是在学校区域内为学校教育提供资源共享、信息交流和协同工作的计算机网络信息系统。随着网络技术的发展和Internet应用的普及&#xff0c;互联网已成为人们获取信息的重要来源。由于现在各大学校的教师和学生对信息的需求越来越高&#xff0c;校园信息…

Redis未授权访问

一、漏洞描述 Redis未授权访问 因配置不当可以未经授权访问&#xff0c;攻击者无需认证就可以访问到内部数据。 1. 导致敏感信息泄露 2. 执行 flushall 可清空所有数据 3. 通过数据备份功能往磁盘写入后门文件&#xff08;webshell、定时任务&#xff09; 4. 如果Redis以…

Swift 集合类型

集合类型 一、集合的可变性二、数组&#xff08;Arrays&#xff09;1、数组的简单语法2、创建一个空数组3、创建一个带有默认值的数组4、通过两个数组相加创建一个数组5、用数组字面量构造数组6、访问和修改数组7、数组的遍历 三、集合&#xff08;Sets&#xff09;1、集合类型…

共识算法基础

目录 PaxosRaft节点间是如何通讯的什么是任期与任期编号选举有哪些规则随机超时时间Raft日志成员变更Nacos中Raft的运用&#xff08;cp模式&#xff09; DistroZAB协议博客 Paxos paxos算法是由兰伯特与1990年提出的一个分布式系统的共识算法。分布式系统的共识算法通俗易懂的…

【多客系统】社交圈子论坛系统,小程序/app/H5多端圈子社区论坛系统交友,社区圈子论坛小程序前后端搭建,社交圈平台系统

简述 社交圈子论坛系统是一种面向特定人群或特定话题的社交网络&#xff0c;它提供了用户之间交流、分享、讨论的平台。在这个系统中&#xff0c;用户可以创建、加入不同的圈子&#xff0c;圈子可以是基于兴趣、地域、职业等不同主题的。用户可以在圈子中发帖、评论、点赞等互…

聊聊 ASP.NET Core 中间件(二):中间件和筛选器的区别

前言 有些小伙伴看到上一篇文章后&#xff0c;可能会发现中间件和我们之前讲的筛选器非常类似&#xff0c;比如它们都是通过 next 串起来的一系列的组件&#xff0c;并且都可以在请求处理前后执行代码&#xff0c;都可以通过不执行 next 来进行请求的终止。那么筛选器和中间件…

风筝挂在高压线上怎么办?输电线路AI视频监测装置快速识别保平安

放风筝是一项既有趣又能够让人放松心情的活动&#xff0c;如今风筝的造型和设计也是越来越多样&#xff0c;各种形状奇特的风筝随风起舞&#xff0c;飘荡在空中。不过需要注意的是&#xff0c;由于风速变化无常&#xff0c;放风筝时稍不留神就会将风筝挂在高压线等公共基础设施…

从Apache HttpClient类库,说一说springboot应用程序中的AutoConfiguration的封装

一、背景 在使用httpclient框架请求http接口的时候&#xff0c;我们往往会需要自定义配置httpclient&#xff0c;而非直接使用。 <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>…

SPSS之主成分分析

SPSS中主成分分析功能在【分析】--【降维】--【因子分析】中完成&#xff08;在SPSS软件中&#xff0c;主成分分析与因子分析均在【因子分析】模块中完成&#xff09;。 求解主成分通常从分析原始变量的协方差矩阵或相关矩阵着手。 &#xff08;1&#xff09;当变量取值的度量…

20232820 2023-2024-2 《网络攻防实践》实践九报告

20232820 2023-2024-2 《网络攻防实践》实践九报告 1.实践内容 本次实践的对象是一个名为pwn1的linux可执行文件。 该程序正常执行流程是&#xff1a;main调用foo函数,foo函数会简单回显任何用户输入的字符串。 该程序同时包含另一个代码片段&#xff0c;getShell&#xff…

从开发角度理解漏洞成因(02)

文章目录 文件上传类需求文件上传漏洞 文件下载类需求文件下载漏洞 扩展 留言板类&#xff08;XSS漏洞&#xff09;需求XSS漏洞 登录类需求cookie伪造漏洞万能密码登录 持续更新中… 文章中代码资源已上传资源&#xff0c;如需要打包好的请点击PHP开发漏洞环境&#xff08;SQL注…

当导师和学生陷入「隐形冲突」

::: block-1 “时问桫椤”是一个致力于为本科生到研究生教育阶段提供帮助的不太正式的公众号。我们旨在在大家感到困惑、痛苦或面临困难时伸出援手。通过总结广大研究生的经验&#xff0c;帮助大家尽早适应研究生生活&#xff0c;尽快了解科研的本质。祝一切顺利&#xff01;—…

AI写的论文AI疑似度太高怎么办?教你一招解决

随着 AI 技术迅猛发展&#xff0c;各种AI辅助论文写作的工具层出不穷&#xff01; 为了防止有人利用AI工具进行论文代写&#xff0c;在最新的学位法中已经明确规定“已经获得学位者&#xff0c;在获得该学位过程中如有人工智能代写等学术不端行为&#xff0c;经学位评定委员会…

易基因:Nature子刊:ChIP-seq等揭示c-di-AMP与DasR互作以调控细菌生长、发育和抗生素合成|项目文章

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 c-di-AMP是一种在细菌信号中普遍存在且至关重要的核苷酸第二信使&#xff0c;对于大多数c-di-AMP合成生物体来说&#xff0c;c-di-AMP稳态及其信号转导的分子机制非常值得关注。 2024年…

智慧仓储可视化大屏,以最直观的形式展示海量数据。

智慧仓储可视化大屏是一种通过数据可视化技术&#xff0c;将仓储管理系统中的海量数据以图表、地图、仪表盘等形式直观展示在大屏上的解决方案。它可以帮助仓储管理人员更清晰地了解仓库的运营情况&#xff0c;从而做出更明智的决策。 智慧仓储可视化大屏通常包括以下功能和特点…

护眼灯有没有护眼的效果?六大技巧教你选到护眼效果好的护眼台灯

随着孩子学习压力增大&#xff0c;护眼灯的重要性日益凸显。那么&#xff0c;护眼灯有没有护眼的效果&#xff1f;答案是肯定的&#xff0c;但关键在于如何挑选。本文将分享六大选购技巧&#xff0c;帮助大家挑选到护眼效果卓越的台灯&#xff0c;确保孩子在明亮而舒适的光线下…

论文AI疑似度太高怎么办?教你一招解决AIGC降重问题

随着 AI 技术迅猛发展&#xff0c;各种AI辅助论文写作的工具层出不穷&#xff01; 为了防止有人利用AI工具进行论文代写&#xff0c;在最新的学位法中已经明确规定“已经获得学位者&#xff0c;在获得该学位过程中如有人工智能代写等学术不端行为&#xff0c;经学位评定委员会…