SpringBoot多租户系统的5种架构设计方案

news2025/6/4 0:09:44

多租户(Multi-tenancy)是一种软件架构模式,允许单个应用实例服务于多个客户(租户),同时保持租户数据的隔离性和安全性。

通过合理的多租户设计,企业可以显著降低运维成本、提升资源利用率,并实现更高效的服务交付。

本文将分享SpringBoot环境下实现多租户系统的5种架构设计方案

方案一:独立数据库模式

原理与特点

独立数据库模式为每个租户提供完全独立的数据库实例,是隔离级别最高的多租户方案。在这种模式下,租户数据完全分离,甚至可以部署在不同的服务器上。

实现步骤

  1. 创建多数据源配置:为每个租户配置独立的数据源
@Configuration
public class MultiTenantDatabaseConfig {
    
    @Autowired
    private TenantDataSourceProperties properties;
    
    @Bean
    public DataSource dataSource() {
        AbstractRoutingDataSource multiTenantDataSource = new TenantAwareRoutingDataSource();
        
        Map<Object, Object> targetDataSources = new HashMap<>();
        
        // 为每个租户创建数据源
        for (TenantDataSourceProperties.TenantProperties tenant : properties.getTenants()) {
            DataSource tenantDataSource = createDataSource(tenant);
            targetDataSources.put(tenant.getTenantId(), tenantDataSource);
        }
        
        multiTenantDataSource.setTargetDataSources(targetDataSources);
        return multiTenantDataSource;
    }
    
    private DataSource createDataSource(TenantDataSourceProperties.TenantProperties tenant) {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(tenant.getUrl());
        dataSource.setUsername(tenant.getUsername());
        dataSource.setPassword(tenant.getPassword());
        dataSource.setDriverClassName(tenant.getDriverClassName());
        return dataSource;
    }
}
  1. 实现租户感知的数据源路由
public class TenantAwareRoutingDataSource extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContextHolder.getTenantId();
    }
}
  1. 租户上下文管理
public class TenantContextHolder {
    
    private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
    
    public static void setTenantId(String tenantId) {
        CONTEXT.set(tenantId);
    }
    
    public static String getTenantId() {
        return CONTEXT.get();
    }
    
    public static void clear() {
        CONTEXT.remove();
    }
}
  1. 添加租户识别拦截器
@Component
public class TenantIdentificationInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String tenantId = extractTenantId(request);
        if (tenantId != null) {
            TenantContextHolder.setTenantId(tenantId);
            return true;
        }
        
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        return false;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) {
        TenantContextHolder.clear();
    }
    
    private String extractTenantId(HttpServletRequest request) {
        // 从请求头中获取租户ID
        String tenantId = request.getHeader("X-TenantID");
        
        // 或者从子域名提取
        if (tenantId == null) {
            String host = request.getServerName();
            if (host.contains(".")) {
                tenantId = host.split("\.")[0];
            }
        }
        
        return tenantId;
    }
}
  1. 配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private TenantIdentificationInterceptor tenantInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tenantInterceptor)
                .addPathPatterns("/api/**");
    }
}
  1. 实现动态租户管理
@Entity
@Table(name = "tenant")
public class Tenant {
    
    @Id
    private String id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private String databaseUrl;
    
    @Column(nullable = false)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    @Column(nullable = false)
    private String driverClassName;
    
    @Column
    private boolean active = true;
    
    // getters and setters
}

@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
}

@Service
public class TenantManagementService {
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private ApplicationContext applicationContext;
    
    // 用ConcurrentHashMap存储租户数据源
    private final Map<String, DataSource> tenantDataSources = new ConcurrentHashMap<>();
    
    @PostConstruct
    public void initializeTenants() {
        List<Tenant> activeTenants = tenantRepository.findByActive(true);
        for (Tenant tenant : activeTenants) {
            addTenant(tenant);
        }
    }
    
    public void addTenant(Tenant tenant) {
        // 创建新的数据源
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(tenant.getDatabaseUrl());
        dataSource.setUsername(tenant.getUsername());
        dataSource.setPassword(tenant.getPassword());
        dataSource.setDriverClassName(tenant.getDriverClassName());
        
        // 存储数据源
        tenantDataSources.put(tenant.getId(), dataSource);
        
        // 更新路由数据源
        updateRoutingDataSource();
        
        // 保存租户信息到数据库
        tenantRepository.save(tenant);
    }
    
    public void removeTenant(String tenantId) {
        DataSource dataSource = tenantDataSources.remove(tenantId);
        if (dataSource != null && dataSource instanceof HikariDataSource) {
            ((HikariDataSource) dataSource).close();
        }
        
        // 更新路由数据源
        updateRoutingDataSource();
        
        // 从数据库移除租户
        tenantRepository.deleteById(tenantId);
    }
    
    private void updateRoutingDataSource() {
        try {
            TenantAwareRoutingDataSource routingDataSource = (TenantAwareRoutingDataSource) dataSource;
            
            // 使用反射访问AbstractRoutingDataSource的targetDataSources字段
            Field targetDataSourcesField = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
            targetDataSourcesField.setAccessible(true);
            
            Map<Object, Object> targetDataSources = new HashMap<>(tenantDataSources);
            targetDataSourcesField.set(routingDataSource, targetDataSources);
            
            // 调用afterPropertiesSet初始化数据源
            routingDataSource.afterPropertiesSet();
        } catch (Exception e) {
            throw new RuntimeException("Failed to update routing data source", e);
        }
    }
}
  1. 提供租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class TenantAdminController {
    
    @Autowired
    private TenantManagementService tenantService;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        return tenantService.getAllTenants();
    }
    
    @PostMapping
    public ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.addTenant(tenant);
        return ResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.removeTenant(tenantId);
        return ResponseEntity.noContent().build();
    }
}

优缺点分析

优点:

  • 数据隔离级别最高,安全性最佳
  • 租户可以使用不同的数据库版本或类型
  • 易于实现租户特定的数据库优化
  • 故障隔离,一个租户的数据库问题不影响其他租户
  • 便于独立备份、恢复和迁移

缺点:

  • 资源利用率较低,成本较高
  • 运维复杂度高,需要管理多个数据库实例
  • 跨租户查询困难
  • 每增加一个租户需要创建新的数据库实例
  • 数据库连接池管理复杂

适用场景

  • 高要求的企业级SaaS应用
  • 租户数量相对较少但数据量大的场景
  • 租户愿意支付更高费用获得更好隔离性的场景

方案二:共享数据库,独立Schema模式

原理与特点

在这种模式下,所有租户共享同一个数据库实例,但每个租户拥有自己独立的Schema(在PostgreSQL中)或数据库(在MySQL中)。这种方式在资源共享和数据隔离之间取得了平衡。

实现步骤

  1. 创建租户Schema配置
@Configuration
public class MultiTenantSchemaConfig {
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @PostConstruct
    public void initializeSchemas() {
        for (Tenant tenant : tenantRepository.findByActive(true)) {
            createSchemaIfNotExists(tenant.getSchemaName());
        }
    }
    
    private void createSchemaIfNotExists(String schema) {
        try (Connection connection = dataSource.getConnection()) {
            // PostgreSQL语法,MySQL使用CREATE DATABASE IF NOT EXISTS
            String sql = "CREATE SCHEMA IF NOT EXISTS " + schema;
            try (Statement stmt = connection.createStatement()) {
                stmt.execute(sql);
            }
        } catch (SQLException e) {
            throw new RuntimeException("Failed to create schema: " + schema, e);
        }
    }
}
  1. 租户实体和存储
@Entity
@Table(name = "tenant")
public class Tenant {
    
    @Id
    private String id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false, unique = true)
    private String schemaName;
    
    @Column
    private boolean active = true;
    
    // getters and setters
}

@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
    Optional<Tenant> findBySchemaName(String schemaName);
}
  1. 配置Hibernate多租户支持
@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
@EntityScan(basePackages = "com.example.entity")
public class JpaConfig {
    
    @Autowired
    private DataSource dataSource;
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        
        Map<String, Object> properties = new HashMap<>();
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT, 
                MultiTenancyStrategy.SCHEMA);
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, 
                multiTenantConnectionProvider());
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, 
                currentTenantIdentifierResolver());
        
        // 其他Hibernate配置...
        
        return builder
                .dataSource(dataSource)
                .packages("com.example.entity")
                .properties(properties)
                .build();
    }
    
    @Bean
    public MultiTenantConnectionProvider multiTenantConnectionProvider() {
        return new SchemaBasedMultiTenantConnectionProvider();
    }
    
    @Bean
    public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
        return new TenantSchemaIdentifierResolver();
    }
}
  1. 实现多租户连接提供者
public class SchemaBasedMultiTenantConnectionProvider 
        implements MultiTenantConnectionProvider {
    
    private static final long serialVersionUID = 1L;
    
    @Autowired
    private DataSource dataSource;
    
    @Override
    public Connection getAnyConnection() throws SQLException {
        return dataSource.getConnection();
    }
    
    @Override
    public void releaseAnyConnection(Connection connection) throws SQLException {
        connection.close();
    }
    
    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        final Connection connection = getAnyConnection();
        try {
            // PostgreSQL语法,MySQL使用USE database_name
            connection.createStatement()
                    .execute(String.format("SET SCHEMA '%s'", tenantIdentifier));
        } catch (SQLException e) {
            throw new HibernateException("Could not alter JDBC connection to schema [" 
                    + tenantIdentifier + "]", e);
        }
        return connection;
    }
    
    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) 
            throws SQLException {
        try {
            // 恢复到默认Schema
            connection.createStatement().execute("SET SCHEMA 'public'");
        } catch (SQLException e) {
            // 忽略错误,确保连接关闭
        }
        connection.close();
    }
    
    @Override
    public boolean supportsAggressiveRelease() {
        return false;
    }
    
    @Override
    public boolean isUnwrappableAs(Class unwrapType) {
        return false;
    }
    
    @Override
    public <T> T unwrap(Class<T> unwrapType) {
        return null;
    }
}
  1. 实现租户标识解析器
public class TenantSchemaIdentifierResolver implements CurrentTenantIdentifierResolver {
    
    private static final String DEFAULT_TENANT = "public";
    
    @Override
    public String resolveCurrentTenantIdentifier() {
        String tenantId = TenantContextHolder.getTenantId();
        return tenantId != null ? tenantId : DEFAULT_TENANT;
    }
    
    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}
  1. 动态租户管理服务
@Service
public class TenantSchemaManagementService {
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private EntityManagerFactory entityManagerFactory;
    
    public void createTenant(Tenant tenant) {
        // 1. 创建Schema
        createSchemaIfNotExists(tenant.getSchemaName());
        
        // 2. 保存租户信息
        tenantRepository.save(tenant);
        
        // 3. 初始化Schema的表结构
        initializeSchema(tenant.getSchemaName());
    }
    
    public void deleteTenant(String tenantId) {
        Tenant tenant = tenantRepository.findById(tenantId)
                .orElseThrow(() -> new RuntimeException("Tenant not found: " + tenantId));
        
        // 1. 删除Schema
        dropSchema(tenant.getSchemaName());
        
        // 2. 删除租户信息
        tenantRepository.delete(tenant);
    }
    
    private void createSchemaIfNotExists(String schema) {
        try (Connection connection = dataSource.getConnection()) {
            String sql = "CREATE SCHEMA IF NOT EXISTS " + schema;
            try (Statement stmt = connection.createStatement()) {
                stmt.execute(sql);
            }
        } catch (SQLException e) {
            throw new RuntimeException("Failed to create schema: " + schema, e);
        }
    }
    
    private void dropSchema(String schema) {
        try (Connection connection = dataSource.getConnection()) {
            String sql = "DROP SCHEMA IF EXISTS " + schema + " CASCADE";
            try (Statement stmt = connection.createStatement()) {
                stmt.execute(sql);
            }
        } catch (SQLException e) {
            throw new RuntimeException("Failed to drop schema: " + schema, e);
        }
    }
    
    private void initializeSchema(String schemaName) {
        // 设置当前租户上下文
        String previousTenant = TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(schemaName);
            
            // 使用JPA/Hibernate工具初始化Schema
            // 可以使用SchemaExport或更推荐使用Flyway/Liquibase
            Session session = entityManagerFactory.createEntityManager().unwrap(Session.class);
            session.doWork(connection -> {
                // 执行DDL语句
            });
            
        } finally {
            // 恢复之前的租户上下文
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
}
  1. 租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class TenantSchemaController {
    
    @Autowired
    private TenantSchemaManagementService tenantService;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        return tenantRepository.findAll();
    }
    
    @PostMapping
    public ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.createTenant(tenant);
        return ResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.deleteTenant(tenantId);
        return ResponseEntity.noContent().build();
    }
}

优缺点分析

优点:

  • 资源利用率高于独立数据库模式
  • 较好的数据隔离性
  • 运维复杂度低于独立数据库模式
  • 容易实现租户特定的表结构
  • 数据库级别的权限控制

缺点:

  • 数据库管理复杂度增加
  • 可能存在Schema数量限制
  • 跨租户查询仍然困难
  • 无法为不同租户使用不同的数据库类型
  • 所有租户共享数据库资源,可能出现资源争用

适用场景

  • 中型SaaS应用
  • 租户数量中等但增长较快的场景
  • 需要较好数据隔离但成本敏感的应用
  • PostgreSQL或MySQL等支持Schema/数据库隔离的数据库环境

方案三:共享数据库,共享Schema,独立表模式

原理与特点

在这种模式下,所有租户共享同一个数据库和Schema,但每个租户有自己的表集合,通常通过表名前缀或后缀区分不同租户的表。

实现步骤

  1. 实现多租户命名策略
@Component
public class TenantTableNameStrategy extends PhysicalNamingStrategyStandardImpl {
    
    private static final long serialVersionUID = 1L;
    
    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId != null && !tenantId.isEmpty()) {
            String tablePrefix = tenantId + "_";
            return new Identifier(tablePrefix + name.getText(), name.isQuoted());
        }
        return super.toPhysicalTableName(name, context);
    }
}
  1. 配置Hibernate命名策略
@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
@EntityScan(basePackages = "com.example.entity")
public class JpaConfig {
    
    @Autowired
    private TenantTableNameStrategy tableNameStrategy;
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder,
            DataSource dataSource) {
        
        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.physical_naming_strategy", 
                tableNameStrategy);
        
        // 其他Hibernate配置...
        
        return builder
                .dataSource(dataSource)
                .packages("com.example.entity")
                .properties(properties)
                .build();
    }
}
  1. 租户实体和仓库
@Entity
@Table(name = "tenant_info") // 避免与租户表前缀冲突
public class Tenant {
    
    @Id
    private String id;
    
    @Column(nullable = false)
    private String name;
    
    @Column
    private boolean active = true;
    
    // getters and setters
}

@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
}
  1. 表初始化管理器
@Component
public class TenantTableManager {
    
    @Autowired
    private EntityManagerFactory entityManagerFactory;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @PersistenceContext
    private EntityManager entityManager;
    
    public void initializeTenantTables(String tenantId) {
        String previousTenant = TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(tenantId);
            
            // 使用JPA/Hibernate初始化表结构
            // 在生产环境中,推荐使用Flyway或Liquibase进行更精细的控制
            Session session = entityManager.unwrap(Session.class);
            session.doWork(connection -> {
                // 执行建表语句
                // 这里可以使用Hibernate的SchemaExport,但为简化,直接使用SQL
                
                // 示例:创建用户表
                String createUserTable = "CREATE TABLE IF NOT EXISTS " + tenantId + "_users (" +
                        "id BIGINT NOT NULL AUTO_INCREMENT, " +
                        "username VARCHAR(255) NOT NULL, " +
                        "email VARCHAR(255) NOT NULL, " +
                        "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
                        "PRIMARY KEY (id)" +
                        ")";
                
                try (Statement stmt = connection.createStatement()) {
                    stmt.execute(createUserTable);
                    // 创建其他表...
                }
            });
            
        } finally {
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
    
    public void dropTenantTables(String tenantId) {
        // 获取数据库中所有表
        try (Connection connection = entityManager.unwrap(SessionImplementor.class).connection()) {
            DatabaseMetaData metaData = connection.getMetaData();
            String tablePrefix = tenantId + "_";
            
            try (ResultSet tables = metaData.getTables(
                    connection.getCatalog(), connection.getSchema(), tablePrefix + "%", new String[]{"TABLE"})) {
                
                List<String> tablesToDrop = new ArrayList<>();
                while (tables.next()) {
                    tablesToDrop.add(tables.getString("TABLE_NAME"));
                }
                
                // 删除所有表
                for (String tableName : tablesToDrop) {
                    try (Statement stmt = connection.createStatement()) {
                        stmt.execute("DROP TABLE " + tableName);
                    }
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Failed to drop tenant tables", e);
        }
    }
}
  1. 租户管理服务
@Service
public class TenantTableManagementService {
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @Autowired
    private TenantTableManager tableManager;
    
    @PostConstruct
    public void initializeAllTenants() {
        for (Tenant tenant : tenantRepository.findByActive(true)) {
            tableManager.initializeTenantTables(tenant.getId());
        }
    }
    
    @Transactional
    public void createTenant(Tenant tenant) {
        // 1. 保存租户信息
        tenantRepository.save(tenant);
        
        // 2. 初始化租户表
        tableManager.initializeTenantTables(tenant.getId());
    }
    
    @Transactional
    public void deleteTenant(String tenantId) {
        // 1. 删除租户表
        tableManager.dropTenantTables(tenantId);
        
        // 2. 删除租户信息
        tenantRepository.deleteById(tenantId);
    }
}
  1. 提供租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class TenantTableController {
    
    @Autowired
    private TenantTableManagementService tenantService;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        return tenantRepository.findAll();
    }
    
    @PostMapping
    public ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.createTenant(tenant);
        return ResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.deleteTenant(tenantId);
        return ResponseEntity.noContent().build();
    }
}

优缺点分析

优点:

  • 简单易实现,特别是对现有应用的改造
  • 资源利用率高
  • 跨租户查询相对容易实现
  • 维护成本低
  • 租户间表结构可以不同

缺点:

  • 数据隔离级别较低
  • 随着租户数量增加,表数量会急剧增长
  • 数据库对象(如表、索引)数量可能达到数据库限制
  • 备份和恢复单个租户数据较为复杂
  • 可能需要处理表名长度限制问题

适用场景

  • 租户数量适中且表结构相对简单的SaaS应用
  • 需要为不同租户提供不同表结构的场景
  • 快速原型开发或MVP(最小可行产品)
  • 从单租户向多租户过渡的系统

方案四:共享数据库,共享Schema,共享表模式

原理与特点

这是隔离级别最低但资源效率最高的方案。所有租户共享相同的数据库、Schema和表,通过在每个表中添加"租户ID"列来区分不同租户的数据。

实现步骤

  1. 创建租户感知的实体基类
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public abstract class TenantAwareEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "tenant_id", nullable = false)
    private String tenantId;
    
    @CreatedDate
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @PrePersist
    public void onPrePersist() {
        tenantId = TenantContextHolder.getTenantId();
    }
}
  1. 租户实体和仓库
@Entity
@Table(name = "tenants")
public class Tenant {
    
    @Id
    private String id;
    
    @Column(nullable = false)
    private String name;
    
    @Column
    private boolean active = true;
    
    // getters and setters
}

@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
}
  1. 实现租户数据过滤器
@Component
public class TenantFilterInterceptor implements HandlerInterceptor {
    
    @Autowired
    private EntityManager entityManager;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId != null) {
            // 设置Hibernate过滤器
            Session session = entityManager.unwrap(Session.class);
            Filter filter = session.enableFilter("tenantFilter");
            filter.setParameter("tenantId", tenantId);
            return true;
        }
        
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        return false;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                               Object handler, Exception ex) {
        Session session = entityManager.unwrap(Session.class);
        session.disableFilter("tenantFilter");
    }
}
  1. 为实体添加过滤器注解
@Entity
@Table(name = "users")
@FilterDef(name = "tenantFilter", parameters = {
    @ParamDef(name = "tenantId", type = "string")
})
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
public class User extends TenantAwareEntity {
    
    @Column(name = "username", nullable = false)
    private String username;
    
    @Column(name = "email", nullable = false)
    private String email;
    
    // 其他字段和方法...
}
  1. 租户管理服务
@Service
public class SharedTableTenantService {
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @Autowired
    private EntityManager entityManager;
    
    @Transactional
    public void createTenant(Tenant tenant) {
        // 直接保存租户信息
        tenantRepository.save(tenant);
        
        // 初始化租户默认数据
        initializeTenantData(tenant.getId());
    }
    
    @Transactional
    public void deleteTenant(String tenantId) {
        // 删除该租户的所有数据
        deleteAllTenantData(tenantId);
        
        // 删除租户记录
        tenantRepository.deleteById(tenantId);
    }
    
    private void initializeTenantData(String tenantId) {
        String previousTenant = TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(tenantId);
            
            // 创建默认用户、角色等
            // ...
            
        } finally {
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
    
    private void deleteAllTenantData(String tenantId) {
        // 获取所有带有tenant_id列的表
        List<String> tables = getTablesWithTenantIdColumn();
        
        // 从每个表中删除该租户的数据
        for (String table : tables) {
            entityManager.createNativeQuery("DELETE FROM " + table + " WHERE tenant_id = :tenantId")
                    .setParameter("tenantId", tenantId)
                    .executeUpdate();
        }
    }
    
    private List<String> getTablesWithTenantIdColumn() {
        List<String> tables = new ArrayList<>();
        
        try (Connection connection = entityManager.unwrap(SessionImplementor.class).connection()) {
            DatabaseMetaData metaData = connection.getMetaData();
            
            try (ResultSet rs = metaData.getTables(
                    connection.getCatalog(), connection.getSchema(), "%", new String[]{"TABLE"})) {
                
                while (rs.next()) {
                    String tableName = rs.getString("TABLE_NAME");
                    
                    // 检查表是否有tenant_id列
                    try (ResultSet columns = metaData.getColumns(
                            connection.getCatalog(), connection.getSchema(), tableName, "tenant_id")) {
                        
                        if (columns.next()) {
                            tables.add(tableName);
                        }
                    }
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Failed to get tables with tenant_id column", e);
        }
        
        return tables;
    }
}
  1. 租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class SharedTableTenantController {
    
    @Autowired
    private SharedTableTenantService tenantService;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        return tenantRepository.findAll();
    }
    
    @PostMapping
    public ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.createTenant(tenant);
        return ResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.deleteTenant(tenantId);
        return ResponseEntity.noContent().build();
    }
}

优缺点分析

优点:

  • 资源利用率最高
  • 维护成本最低
  • 实现简单,对现有单租户系统改造容易
  • 跨租户查询简单
  • 节省存储空间,特别是当数据量小时

缺点:

  • 数据隔离级别最低
  • 安全风险较高,一个错误可能导致跨租户数据泄露
  • 所有租户共享相同的表结构
  • 需要在所有数据访问层强制租户过滤

适用场景

  • 租户数量多但每个租户数据量小的场景
  • 成本敏感的应用
  • 原型验证或MVP阶段

方案五:混合租户模式

原理与特点

混合租户模式结合了多种隔离策略,根据租户等级、重要性或特定需求为不同租户提供不同级别的隔离。例如,免费用户可能使用共享表模式,而付费企业用户可能使用独立数据库模式。

实现步骤

  1. 租户类型和存储
@Entity
@Table(name = "tenants")
public class Tenant {
    
    @Id
    private String id;
    
    @Column(nullable = false)
    private String name;
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private TenantType type;
    
    @Column
    private String databaseUrl;
    
    @Column
    private String username;
    
    @Column
    private String password;
    
    @Column
    private String driverClassName;
    
    @Column
    private String schemaName;
    
    @Column
    private boolean active = true;
    
    public enum TenantType {
        DEDICATED_DATABASE,
        DEDICATED_SCHEMA,
        DEDICATED_TABLE,
        SHARED_TABLE
    }
    
    // getters and setters
}

@Repository
public interface TenantRepository extends JpaRepository<Tenant, String> {
    List<Tenant> findByActive(boolean active);
    List<Tenant> findByType(Tenant.TenantType type);
}
  1. 创建租户分类策略
@Component
public class TenantIsolationStrategy {
    
    @Autowired
    private TenantRepository tenantRepository;
    
    private final Map<String, Tenant> tenantCache = new ConcurrentHashMap<>();
    
    @PostConstruct
    public void loadTenants() {
        tenantRepository.findByActive(true).forEach(tenant -> 
            tenantCache.put(tenant.getId(), tenant));
    }
    
    public Tenant.TenantType getIsolationTypeForTenant(String tenantId) {
        Tenant tenant = tenantCache.get(tenantId);
        if (tenant == null) {
            tenant = tenantRepository.findById(tenantId)
                    .orElseThrow(() -> new RuntimeException("Tenant not found: " + tenantId));
            tenantCache.put(tenantId, tenant);
        }
        return tenant.getType();
    }
    
    public Tenant getTenant(String tenantId) {
        Tenant tenant = tenantCache.get(tenantId);
        if (tenant == null) {
            tenant = tenantRepository.findById(tenantId)
                    .orElseThrow(() -> new RuntimeException("Tenant not found: " + tenantId));
            tenantCache.put(tenantId, tenant);
        }
        return tenant;
    }
    
    public void evictFromCache(String tenantId) {
        tenantCache.remove(tenantId);
    }
}
  1. 实现混合数据源路由
@Component
public class HybridTenantRouter {
    
    @Autowired
    private TenantIsolationStrategy isolationStrategy;
    
    private final Map<String, DataSource> dedicatedDataSources = new ConcurrentHashMap<>();
    
    @Autowired
    private DataSource sharedDataSource;
    
    public DataSource getDataSourceForTenant(String tenantId) {
        Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);
        
        if (isolationType == Tenant.TenantType.DEDICATED_DATABASE) {
            // 对于独立数据库的租户,查找或创建专用数据源
            return dedicatedDataSources.computeIfAbsent(tenantId, this::createDedicatedDataSource);
        }
        
        return sharedDataSource;
    }
    
    private DataSource createDedicatedDataSource(String tenantId) {
        Tenant tenant = isolationStrategy.getTenant(tenantId);
        
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(tenant.getDatabaseUrl());
        dataSource.setUsername(tenant.getUsername());
        dataSource.setPassword(tenant.getPassword());
        dataSource.setDriverClassName(tenant.getDriverClassName());
        
        return dataSource;
    }
    
    public void removeDedicatedDataSource(String tenantId) {
        DataSource dataSource = dedicatedDataSources.remove(tenantId);
        if (dataSource instanceof HikariDataSource) {
            ((HikariDataSource) dataSource).close();
        }
    }
}
  1. 混合租户路由数据源
public class HybridRoutingDataSource extends AbstractRoutingDataSource {
    
    @Autowired
    private HybridTenantRouter tenantRouter;
    
    @Autowired
    private TenantIsolationStrategy isolationStrategy;
    
    @Override
    protected Object determineCurrentLookupKey() {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId == null) {
            return "default";
        }
        
        Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);
        
        if (isolationType == Tenant.TenantType.DEDICATED_DATABASE) {
            return tenantId;
        }
        
        return "shared";
    }
    
    @Override
    protected DataSource determineTargetDataSource() {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId == null) {
            return super.determineTargetDataSource();
        }
        
        return tenantRouter.getDataSourceForTenant(tenantId);
    }
}
  1. 混合租户拦截器
@Component
public class HybridTenantInterceptor implements HandlerInterceptor {
    
    @Autowired
    private TenantIsolationStrategy isolationStrategy;
    
    @Autowired
    private EntityManager entityManager;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String tenantId = extractTenantId(request);
        if (tenantId != null) {
            TenantContextHolder.setTenantId(tenantId);
            
            Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);
            
            // 根据隔离类型应用不同策略
            switch (isolationType) {
                case DEDICATED_DATABASE:
                    // 已由数据源路由处理
                    break;
                case DEDICATED_SCHEMA:
                    setSchema(isolationStrategy.getTenant(tenantId).getSchemaName());
                    break;
                case DEDICATED_TABLE:
                    // 由命名策略处理
                    break;
                case SHARED_TABLE:
                    enableTenantFilter(tenantId);
                    break;
            }
            
            return true;
        }
        
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        return false;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                               Object handler, Exception ex) {
        String tenantId = TenantContextHolder.getTenantId();
        if (tenantId != null) {
            Tenant.TenantType isolationType = isolationStrategy.getIsolationTypeForTenant(tenantId);
            
            if (isolationType == Tenant.TenantType.SHARED_TABLE) {
                disableTenantFilter();
            }
        }
        
        TenantContextHolder.clear();
    }
    
    private void setSchema(String schema) {
        try {
            entityManager.createNativeQuery("SET SCHEMA '" + schema + "'").executeUpdate();
        } catch (Exception e) {
            // 处理异常
        }
    }
    
    private void enableTenantFilter(String tenantId) {
        Session session = entityManager.unwrap(Session.class);
        Filter filter = session.enableFilter("tenantFilter");
        filter.setParameter("tenantId", tenantId);
    }
    
    private void disableTenantFilter() {
        Session session = entityManager.unwrap(Session.class);
        session.disableFilter("tenantFilter");
    }
    
    private String extractTenantId(HttpServletRequest request) {
        // 从请求中提取租户ID的逻辑
        return request.getHeader("X-TenantID");
    }
}
  1. 综合租户管理服务
@Service
public class HybridTenantManagementService {
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @Autowired
    private TenantIsolationStrategy isolationStrategy;
    
    @Autowired
    private HybridTenantRouter tenantRouter;
    
    @Autowired
    private EntityManager entityManager;
    
    @Autowired
    private DataSource dataSource;
    
    // 不同隔离类型的初始化策略
    private final Map<Tenant.TenantType, TenantInitializer> initializers = new HashMap<>();
    
    @PostConstruct
    public void init() {
        initializers.put(Tenant.TenantType.DEDICATED_DATABASE, this::initializeDedicatedDatabase);
        initializers.put(Tenant.TenantType.DEDICATED_SCHEMA, this::initializeDedicatedSchema);
        initializers.put(Tenant.TenantType.DEDICATED_TABLE, this::initializeDedicatedTables);
        initializers.put(Tenant.TenantType.SHARED_TABLE, this::initializeSharedTables);
    }
    
    @Transactional
    public void createTenant(Tenant tenant) {
        // 1. 保存租户基本信息
        tenantRepository.save(tenant);
        
        // 2. 根据隔离类型初始化
        TenantInitializer initializer = initializers.get(tenant.getType());
        if (initializer != null) {
            initializer.initialize(tenant);
        }
        
        // 3. 更新缓存
        isolationStrategy.evictFromCache(tenant.getId());
    }
    
    @Transactional
    public void deleteTenant(String tenantId) {
        Tenant tenant = tenantRepository.findById(tenantId)
                .orElseThrow(() -> new RuntimeException("Tenant not found: " + tenantId));
        
        // 1. 根据隔离类型清理资源
        switch (tenant.getType()) {
            case DEDICATED_DATABASE:
                cleanupDedicatedDatabase(tenant);
                break;
            case DEDICATED_SCHEMA:
                cleanupDedicatedSchema(tenant);
                break;
            case DEDICATED_TABLE:
                cleanupDedicatedTables(tenant);
                break;
            case SHARED_TABLE:
                cleanupSharedTables(tenant);
                break;
        }
        
        // 2. 删除租户信息
        tenantRepository.delete(tenant);
        
        // 3. 更新缓存
        isolationStrategy.evictFromCache(tenantId);
    }
    
    // 独立数据库初始化
    private void initializeDedicatedDatabase(Tenant tenant) {
        // 创建数据源
        DataSource dedicatedDs = tenantRouter.getDataSourceForTenant(tenant.getId());
        
        // 初始化数据库结构
        try (Connection conn = dedicatedDs.getConnection()) {
            // 执行DDL脚本
            // ...
        } catch (SQLException e) {
            throw new RuntimeException("Failed to initialize database for tenant: " + tenant.getId(), e);
        }
    }
    
    // Schema初始化
    private void initializeDedicatedSchema(Tenant tenant) {
        try (Connection conn = dataSource.getConnection()) {
            // 创建Schema
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CREATE SCHEMA IF NOT EXISTS " + tenant.getSchemaName());
            }
            
            // 切换到该Schema
            conn.setSchema(tenant.getSchemaName());
            
            // 创建表结构
            // ...
            
        } catch (SQLException e) {
            throw new RuntimeException("Failed to initialize schema for tenant: " + tenant.getId(), e);
        }
    }
    
    // 独立表初始化
    private void initializeDedicatedTables(Tenant tenant) {
        // 设置线程上下文中的租户ID以使用正确的表名前缀
        String previousTenant = TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(tenant.getId());
            
            // 创建表
            // ...
            
        } finally {
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
    
    // 共享表初始化
    private void initializeSharedTables(Tenant tenant) {
        // 共享表模式下,只需插入租户特定的初始数据
        String previousTenant = TenantContextHolder.getTenantId();
        try {
            TenantContextHolder.setTenantId(tenant.getId());
            
            // 插入初始数据
            // ...
            
        } finally {
            if (previousTenant != null) {
                TenantContextHolder.setTenantId(previousTenant);
            } else {
                TenantContextHolder.clear();
            }
        }
    }
    
    // 清理方法
    private void cleanupDedicatedDatabase(Tenant tenant) {
        // 关闭并移除数据源
        tenantRouter.removeDedicatedDataSource(tenant.getId());
        
        // 注意:通常不会自动删除实际的数据库,这需要DBA手动操作
    }
    
    private void cleanupDedicatedSchema(Tenant tenant) {
        try (Connection conn = dataSource.getConnection()) {
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("DROP SCHEMA IF EXISTS " + tenant.getSchemaName() + " CASCADE");
            }
        } catch (SQLException e) {
            throw new RuntimeException("Failed to drop schema for tenant: " + tenant.getId(), e);
        }
    }
    
    private void cleanupDedicatedTables(Tenant tenant) {
        // 查找并删除该租户的所有表
        try (Connection conn = dataSource.getConnection()) {
            DatabaseMetaData metaData = conn.getMetaData();
            String tablePrefix = tenant.getId() + "_";
            
            try (ResultSet tables = metaData.getTables(
                    conn.getCatalog(), conn.getSchema(), tablePrefix + "%", new String[]{"TABLE"})) {
                
                while (tables.next()) {
                    String tableName = tables.getString("TABLE_NAME");
                    try (Statement stmt = conn.createStatement()) {
                        stmt.execute("DROP TABLE " + tableName);
                    }
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Failed to drop tables for tenant: " + tenant.getId(), e);
        }
    }
    
    private void cleanupSharedTables(Tenant tenant) {
        // 从所有带有tenant_id列的表中删除该租户的数据
        entityManager.createNativeQuery(
                "SELECT table_name FROM information_schema.columns " +
                "WHERE column_name = 'tenant_id'")
                .getResultList()
                .forEach(tableName -> 
                    entityManager.createNativeQuery(
                            "DELETE FROM " + tableName + " WHERE tenant_id = :tenantId")
                            .setParameter("tenantId", tenant.getId())
                            .executeUpdate()
                );
    }
    
    // 租户初始化策略接口
    @FunctionalInterface
    private interface TenantInitializer {
        void initialize(Tenant tenant);
    }
}
  1. 提供租户管理API
@RestController
@RequestMapping("/admin/tenants")
public class HybridTenantController {
    
    @Autowired
    private HybridTenantManagementService tenantService;
    
    @Autowired
    private TenantRepository tenantRepository;
    
    @GetMapping
    public List<Tenant> getAllTenants() {
        return tenantRepository.findAll();
    }
    
    @PostMapping
    public ResponseEntity<Tenant> createTenant(@RequestBody Tenant tenant) {
        tenantService.createTenant(tenant);
        return ResponseEntity.status(HttpStatus.CREATED).body(tenant);
    }
    
    @PutMapping("/{tenantId}")
    public ResponseEntity<Tenant> updateTenant(
            @PathVariable String tenantId, 
            @RequestBody Tenant tenant) {
        
        tenant.setId(tenantId);
        tenantService.updateTenant(tenant);
        return ResponseEntity.ok(tenant);
    }
    
    @DeleteMapping("/{tenantId}")
    public ResponseEntity<Void> deleteTenant(@PathVariable String tenantId) {
        tenantService.deleteTenant(tenantId);
        return ResponseEntity.noContent().build();
    }
    
    @GetMapping("/types")
    public ResponseEntity<List<Tenant.TenantType>> getTenantTypes() {
        return ResponseEntity.ok(Arrays.asList(Tenant.TenantType.values()));
    }
}

优缺点分析

优点:

  • 最大的灵活性,可根据租户需求提供不同隔离级别
  • 可以实现资源和成本的平衡
  • 可以根据业务价值分配资源
  • 适应不同客户的安全和性能需求

缺点:

  • 实现复杂度最高
  • 维护和测试成本高
  • 需要处理多种数据访问模式
  • 可能引入不一致的用户体验
  • 错误处理更加复杂

适用场景

  • 需要提供灵活定价模型的应用
  • 资源需求差异大的租户集合

方案对比

隔离模式数据隔离级别资源利用率成本复杂度适用场景
独立数据库最高企业级应用、金融/医疗行业
独立Schema中型SaaS、安全要求较高的场景
独立表中高中低中小型应用、原型验证
共享表最高大量小租户、成本敏感场景
混合模式可变可变中高多层级服务、复杂业务需求

总结

多租户架构是构建现代SaaS应用的关键技术,选择多租户模式需要平衡数据隔离、资源利用、成本和复杂度等多种因素。

通过深入理解这些架构模式及其权衡,可以根据实际情况选择适合的多租户架构,构建可扩展、安全且经济高效的企业级应用。

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

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

相关文章

数据分析实战1(Excel制作报表)

Excel数据链接&#xff1a;【课程4.0】第2章_Excel.zip - 飞书云文档 1、拿到数据第一步 备份数据 ctrlshiftL&#xff1a;筛选 相关快捷键&#xff1a;&#xff08;alt&#xff1a;自动求和、ctrlshift5&#xff1a;转换为%&#xff09; 2、环比、同比 环比&#xff08;本…

本地部署大模型llm+RAG向量检索问答系统 deepseek chatgpt

项目视频讲解: 本地部署大模型llm+RAG向量检索问答系统 deepseek chatgpt_哔哩哔哩_bilibili 运行结果:

LabVIEW 中内存释放相关问题

在LabVIEW 编程领域&#xff0c;内存管理是一个关键且复杂的议题。我们常常关注 LabVIEW 如何将内存释放回操作系统&#xff08;OS&#xff09;&#xff0c;以及是否有方法确保在特定数据结构&#xff08;如队列、变体属性、动态数据引用 DVR 等&#xff09;销毁、删除或清空后…

基于内存高效算法的 LLM Token 优化:一个有效降低 API 成本的技术方案

在使用 OpenAI、Claude、Gemini 等大语言模型 API 构建对话系统时&#xff0c;开发者普遍面临成本不断上升的挑战。无论是基于检索增强生成&#xff08;RAG&#xff09;的应用还是独立的对话系统&#xff0c;这些系统都需要维护对话历史以确保上下文的连贯性&#xff0c;类似于…

Python打卡训练营Day42

DAY 42 Grad-CAM与Hook函数 知识点回顾 回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 import torch import torch.nn as nn import torch.nn.functional as F import torchvision import torchvision.transforms as tr…

基于微信小程序的scratch学习系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

【C++ 多态】—— 礼器九鼎,釉下乾坤,多态中的 “风水寻龙诀“

欢迎来到一整颗红豆的博客✨&#xff0c;一个关于探索技术的角落&#xff0c;记录学习的点滴&#x1f4d6;&#xff0c;分享实用的技巧&#x1f6e0;️&#xff0c;偶尔还有一些奇思妙想&#x1f4a1; 本文由一整颗红豆原创✍️&#xff0c;感谢支持❤️&#xff01;请尊重原创…

SCSAI平台面向对象建模技术的设计与实现

一、核心设计思想 SCSAI平台的核心目标是通过元建模&#xff08;Meta-Modeling&#xff09;技术实现面向对象建模的零编码化。其核心思想为&#xff1a; 自反性设计&#xff1a;定义ObjectClassInfo (OCI)为元类&#xff08;Meta-Class&#xff09;&#xff0c;所有对象类均为…

pikachu通关教程-CSRF

CSRF(get) 用bp进行抓包 选择action value值的修改 点击test in browser copy然后放在bp代理的浏览器上&#xff0c;会出现一个提交按钮&#xff0c;这时候点击之后信息就被修改了。 CSRF(post) 请求的方式不同&#xff0c;其他都是一样 CSRF Token 存在cookie 首先要先下载一…

智能体觉醒:AI开始自己“动手”了-自主进化开启任务革命时代

1. 智能体&#xff1a;AI从“工具”到“伙伴”的关键跃迁 1.1 什么是智能体&#xff1f; 智能体&#xff08;Agent&#xff09;是AI的“进化版”——它不再局限于生成文字或图像&#xff0c;而是能像人类一样“规划任务”“调用工具”甚至“协同合作”。例如&#xff0c;一个…

【C++指南】C++ list容器完全解读(二):list模拟实现,底层架构揭秘

. &#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C指南》 期待您的关注 文章目录 引言一、链表节点设计&#xff1a;双向链表的基石1.1 节点类的实现 二、list框架与核心成员函…

[神经网络]使用olivettiface数据集进行训练并优化,观察对比loss结果

结合归一化和正则化来优化网络模型结构&#xff0c;观察对比loss结果 搭建的神经网络&#xff0c;使用olivettiface数据集进行训练&#xff0c;结合归一化和正则化来优化网络模型结构&#xff0c;观察对比loss结果 from sklearn.datasets import fetch_olivetti_faces #倒入数…

华院计算出席信创论坛,分享AI教育创新实践并与燧原科技共同推出教育一体机

5月21日&#xff0c;信创论坛于上海漕河泾会议中心举办。本次论坛以“聚力融合&#xff0c;繁荣生态”为主题&#xff0c;话题聚焦工业制造、交通运输、金融、教育、医疗等领域。华院计算技术&#xff08;上海&#xff09;股份有限公司&#xff08;以下简称“华院计算”&#x…

华为OD机试真题——会议接待 /代表团坐车(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 200分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》 华为OD机试真题《会议…

LabVIEW Val (Sgnl) 属性

在 LabVIEW 事件驱动架构中&#xff0c;Val (Sgnl) 属性&#xff08;Value (Signaling)&#xff09;是实现编程触发与用户交互行为一致性的关键技术。与普通 Value 属性不同&#xff0c;Val (Sgnl) 在修改控件值的同时强制生成值改变事件&#xff0c;确保程序逻辑与 UI 交互保持…

STM32G4 电机外设篇(三) TIM1 发波 和 ADC COMP DAC级联

目录 一、STM32G4 电机外设篇&#xff08;三&#xff09; TIM1 发波 和 ADC COMP DAC级联1 TIM1 高级定时器发波1.1 stm32cubemx配置 2 TIM1 ADC COMP DAC级联2.1 stm32cubemx配置 附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^) 一、STM32G4 电机外设篇&#xff08;三&…

DAY 35 超大力王爱学Python

知识点回顾&#xff1a; 三种不同的模型可视化方法&#xff1a;推荐torchinfo打印summary权重分布可视化进度条功能&#xff1a;手动和自动写法&#xff0c;让打印结果更加美观推理的写法&#xff1a;评估模式 作业&#xff1a;调整模型定义时的超参数&#xff0c;对比下效果。…

【数据结构】图的存储(十字链表)

弧节点 tailvex数据域&#xff1a;存储弧尾一端顶点在顺序表中的位置下标&#xff1b;headvex 数据域&#xff1a;存储弧头一端顶点在顺序表中的位置下标&#xff1b;hlink 指针域&#xff1a;指向下一个以当前顶点作为弧头的弧&#xff1b;tlink 指针域&#xff1a;指向下一个…

Redis最佳实践——秒杀系统设计详解

基于Redis的高并发秒杀系统设计&#xff08;十万级QPS&#xff09; 一、秒杀系统核心挑战 瞬时流量洪峰&#xff1a;100万 QPS请求冲击库存超卖风险&#xff1a;精准扣减防止超卖系统高可用性&#xff1a;99.99%服务可用性要求数据强一致性&#xff1a;库存/订单/支付状态同步…

STM32软件spi和硬件spi

核心观点 本文主要介绍了SPI通信的两种实现方式&#xff1a;软件SPI和硬件SPI。详细阐述了SPI通信协议的基本概念、硬件电路连接方式、移位示意图、时序基本单元以及四种工作模式。同时&#xff0c;对W25Q64模块进行了详细介绍&#xff0c;包括其硬件电路、框图以及操作注意事…