Spring Boot動態數據源切換:優雅實現多數據源管理

在復雜的企業應用中,多數據源管理是常見需求。本文將介紹如何基于Spring Boot實現優雅的動態數據源切換方案,通過自定義注解和AOP實現透明化切換。

核心設計思路

通過三層結構實現數據源動態路由:
1. 注解層:聲明式標記數據源
2. 路由層:基于ThreadLocal的上下文管理
3. 切面層:在方法執行前后自動切換數據源

核心實現代碼

1. 數據源注解定義

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String name() default ""; // 數據源名稱
}

2. 動態數據源上下文

public class DynamicDataSourceContext extends AbstractRoutingDataSource {private static String DEFAULT_DATASOURCE_NAME;private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();public DynamicDataSourceContext(String defaultDataSourceName,?Map<Object, Object> targetDataSources) {super.setDefaultTargetDataSource(targetDataSources.get(defaultDataSourceName));super.setTargetDataSources(targetDataSources);DEFAULT_DATASOURCE_NAME = defaultDataSourceName;super.afterPropertiesSet(); // 關鍵初始化}@Overrideprotected Object determineCurrentLookupKey() {return getDataSourceKey(); // 獲取當前數據源標識}// 數據源操作工具方法public static void setDataSourceKey(String key) {CONTEXT_HOLDER.set(key);}public static String getDataSourceKey() {return CONTEXT_HOLDER.get();?}public static void clearDataSourceKey() {CONTEXT_HOLDER.remove();?}public static String getDefaultDataSourceName() {return DEFAULT_DATASOURCE_NAME;}
}

3. AOP切面實現

@Aspect
@Component
@Order(-1) // 確保在事務切面前執行
public class DataSourceAspect {@Around("@within(DataSource) || @annotation(DataSource)")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();// 優先級:方法注解 > 類注解 > 默認數據源DataSource methodAnno = method.getAnnotation(DataSource.class);DataSource classAnno = method.getDeclaringClass().getAnnotation(DataSource.class);String dataSource = DynamicDataSourceContext.getDefaultDataSourceName();if (methodAnno != null && StringUtils.hasText(methodAnno.name())) {dataSource = methodAnno.name();} else if (classAnno != null && StringUtils.hasText(classAnno.name())) {dataSource = classAnno.name();}try {DynamicDataSourceContext.setDataSourceKey(dataSource);return point.proceed(); // 執行目標方法} finally {DynamicDataSourceContext.clearDataSourceKey(); // 清理數據源標識}}
}

自動配置

在`src/main/resources/META-INF/spring`目錄下創建文件:
org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.test.datasourcestater.aspect.DataSourceAspect

使用示例

1、新增動態切換數據源定義

@Configuration
@Component
public class DynamicDataSourceConfig {//默認數據源定義@Resource(name = "defaultDataSource")private DataSource defaultDataSource;//其他數據源定義@Resource(name = "testDataSource")private DataSource testDataSource;@Bean("dynamicDataSource")@Primarypublic DynamicDataSourceContext dynamicDataSource() {Map<Object, Object> targetDataSources = new HashMap<>();//添加默認數據源和其他數據源targetDataSources.put("default",defaultDataSource);targetDataSources.put("testDataSource",testDataSource);return new DynamicDataSourceContext("default", targetDataSources);}@Bean("dataSourceTransactionManager")public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dynamicDataSource") DataSource dataSource){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}@Bean("jdbcTemplate")public JdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return  jdbcTemplate;}
}

其他數據源定義:

@Configuration
@ConfigurationProperties(prefix = "xxx")
@Data
public class TestDataSourceConfig {private String driverClassName;private String url;private String username;private String password;private String validationQuery;private int initialSize;private int maxActive;private int maxIdle;private int minIdle;@Bean("testDataSource")public BasicDataSourceDecrypt basicDataSourceDecrypt(){BasicDataSourceDecrypt basicDataSourceDecrypt = new BasicDataSourceDecrypt();basicDataSourceDecrypt.setUsername(username);basicDataSourceDecrypt.setPassword(password);basicDataSourceDecrypt.setDriverClassName(driverClassName);basicDataSourceDecrypt.setPoolName("xxx");// 數據庫連接地址basicDataSourceDecrypt.setJdbcUrl(url);//  最小空閑連接,默認值10,小于0或大于maximum-pool-size,都會重置為maximum-pool-sizebasicDataSourceDecrypt.setMinimumIdle(minIdle);// 最大連接數,小于等于0會被重置為默認值10;大于零小于1會被重置為minimum-idle的值basicDataSourceDecrypt.setMaximumPoolSize(maxActive);// 空閑連接超時時間,默認值600000(10分鐘),大于等于max-lifetime且max-lifetime>0,會被重置為0;不等于0且小于10秒,會被重置為10秒。basicDataSourceDecrypt.setIdleTimeout(30000);// 連接最大存活時間,不等于0且小于30秒,會被重置為默認值30分鐘.設置應該比mysql設置的超時時間短basicDataSourceDecrypt.setMaxLifetime(360000L);// 連接超時時間:毫秒,小于250毫秒,否則被重置為默認值30秒basicDataSourceDecrypt.setConnectionTimeout(500);// 用于測試連接是否可用的查詢語句basicDataSourceDecrypt.setConnectionTestQuery(validationQuery);return basicDataSourceDecrypt;}
}

2、修改原始的DataSourceConfig為默認數據源

  • 將datasource bean 定義名稱改成@Bean("defaultDataSource")
  • 將@Qualifier("dataSource")改成@Qualifier("dynamicDataSource") 參考代碼,如下:
@Configuration
@ConfigurationProperties(prefix = "xxx")
@Data
public class DataSourceConfig {private String driverClassName;private String url;private String username;private String password;private String validationQuery;private int initialSize;private int maxActive;private int maxIdle;private int minIdle;@Bean("defaultDataSource")public BasicDataSourceDecrypt basicDataSourceDecrypt(){BasicDataSourceDecrypt basicDataSourceDecrypt = new BasicDataSourceDecrypt();basicDataSourceDecrypt.setDriverClassName(driverClassName);basicDataSourceDecrypt.setUsername(username);basicDataSourceDecrypt.setPassword(password);basicDataSourceDecrypt.setPoolName("xx");// 數據庫連接地址basicDataSourceDecrypt.setJdbcUrl(url);//  最小空閑連接,默認值10,小于0或大于maximum-pool-size,都會重置為maximum-pool-sizebasicDataSourceDecrypt.setMinimumIdle(minIdle);// 最大連接數,小于等于0會被重置為默認值10;大于零小于1會被重置為minimum-idle的值basicDataSourceDecrypt.setMaximumPoolSize(maxActive);// 空閑連接超時時間,默認值600000(10分鐘),大于等于max-lifetime且max-lifetime>0,會被重置為0;不等于0且小于10秒,會被重置為10秒。basicDataSourceDecrypt.setIdleTimeout(60000);// 連接最大存活時間,不等于0且小于30秒,會被重置為默認值30分鐘.設置應該比mysql設置的超時時間短basicDataSourceDecrypt.setMaxLifetime(600000);// 連接超時時間:毫秒,小于250毫秒,否則被重置為默認值30秒basicDataSourceDecrypt.setConnectionTimeout(30000);// 用于測試連接是否可用的查詢語句basicDataSourceDecrypt.setConnectionTestQuery(validationQuery);return basicDataSourceDecrypt;}@Bean("messageResource")public ResourceBundleMessageSource resourceBundleMessageSource(){ResourceBundleMessageSource messageResource = new ResourceBundleMessageSource();messageResource.setDefaultEncoding("UTF-8");messageResource.setCacheSeconds(0);return  messageResource;}@Bean("dataSourceTransactionManager")public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dynamicDataSource") BasicDataSourceDecrypt dataSource){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}@Bean("jdbcTemplate")public JdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") BasicDataSourceDecrypt dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return  jdbcTemplate;}}

3、修改mybatisPlusConfig配置

dataSource注入,改成注入@Resource(name="dynamicDataSource")

@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {@Resource(name="dynamicDataSource")private DataSource dataSource;@Autowiredprivate MybatisPlusProperties properties;@Autowiredprivate ResourceLoader resourceLoader = new DefaultResourceLoader();@Autowired(required = false)private DatabaseIdProvider databaseIdProvider;@Beanpublic DatabaseIdProvider getDatabaseIdProvider() {DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();Properties properties = new Properties();databaseIdProvider.setProperties(properties);return databaseIdProvider;}/***    mybatis-plus分頁插件*/@Bean("paginationInterceptor")public PaginationInnerInterceptor paginationInterceptor(@Value("${database.type:mysql}") String databaseType) {PaginationInnerInterceptor page = new PaginationInnerInterceptor();page.setDbType(DbType.getDbType(databaseType));return page;}@Bean("optimisticLockerInterceptor")public OptimisticLockerInnerInterceptor optimisticLockerInterceptor() {return new OptimisticLockerInnerInterceptor();}@Bean("mybatisPlusInterceptor")public MybatisPlusInterceptor mybatisPlusInterceptor(@Qualifier("paginationInterceptor") PaginationInnerInterceptor paginationInterceptor,@Qualifier("optimisticLockerInterceptor") OptimisticLockerInnerInterceptor optimisticLockerInterceptor){MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(paginationInterceptor);mybatisPlusInterceptor.addInnerInterceptor(optimisticLockerInterceptor);return mybatisPlusInterceptor;}/*** 這里全部使用mybatis-autoconfigure 已經自動加載的資源。不手動指定* 配置文件和mybatis-boot的配置文件同步* @return*/@Bean("sqlSessionFactory")public MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean(@Qualifier("globalConfiguration") GlobalConfig globalConfig,@Qualifier("mybatisPlusInterceptor") MybatisPlusInterceptor mybatisPlusInterceptor) {MybatisSqlSessionFactoryBean mybatisPlus = new MybatisSqlSessionFactoryBean();mybatisPlus.setDataSource(dataSource);mybatisPlus.setVfs(SpringBootVFS.class);if (StringUtils.hasText(this.properties.getConfigLocation())) {mybatisPlus.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));}mybatisPlus.setConfiguration(properties.getConfiguration());mybatisPlus.setPlugins(mybatisPlusInterceptor);mybatisPlus.setGlobalConfig(globalConfig);MybatisConfiguration mc = new MybatisConfiguration();mc.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);mybatisPlus.setConfiguration(mc);if (this.databaseIdProvider != null) {mybatisPlus.setDatabaseIdProvider(this.databaseIdProvider);}if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {mybatisPlus.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());}if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {mybatisPlus.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());}if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {mybatisPlus.setMapperLocations(this.properties.resolveMapperLocations());}return mybatisPlus;}@Bean("globalConfiguration")public GlobalConfig globalConfig(@Qualifier("myMetaObjectHandler") ModelMetaObjectHandler myMetaObjectHandler,@Qualifier("customIdGenerator") CustomerIdGenerator customIdGenerator){GlobalConfig globalConfig = new GlobalConfig();globalConfig.setMetaObjectHandler(myMetaObjectHandler);globalConfig.setIdentifierGenerator(customIdGenerator);return globalConfig;}@Bean("myMetaObjectHandler")public ModelMetaObjectHandler modelMetaObjectHandler(){return new ModelMetaObjectHandler();}@Bean("customIdGenerator")public CustomerIdGenerator customerIdGenerator(){return new CustomerIdGenerator();}
}

4、使用說明

在Controller層、Service層和Dao層的方法或者類加上@DataSource(name="數據源名字")注解,完成數據源的自動切換,其中數據源名字來自DynamicDataSourceConfig中dynamicDataSource方法中定義的數據源

4.1、類級別注解

在類上添加多數據源注解,類中的所有方法都是使用注解中設置的數據源

4.1.1、Controller層用法
@RestController
@DataSource(name = "default")
@RequestMapping("/Order")
public class OrderController{// 所有方法默認使用default數據源@GetMapping("/list")public List<Order> queryAll() {// ...}// 默認使用default數據源@GetMapping("/{id}")public Order selectById(@PathVariable Long id) {// ...}
}
4.1.2、Service層用法

Service層使用多數據源注解時,需使用在@Service修飾的類上多數據源注解才能生效

@Service
@DataSource(name = "default")
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {// 所有方法默認使用default數據源public List<Order> findAll() {// ...}// 默認使用default數據源public Order findById() {// ...}
}
4.1.3、Dao層用法

Dao層使用多數據源注解時,需使用在@Component、@Repository或者@Mapper修飾的Dao層接口上多數據源注解才能生效

@Component
@DataSource(name = "default")
public interface OrderMapper extends BaseMapper<Order> {// 使用default數據源Order getById(@Param("id")Long id);// 使用default數據源List<Order> getList();}

4.2、方法級別注解

在方法上添加多數據源注解,具體方法使用注解中設置的數據源

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{// 使用testDataSource數據源@DataSource(name = "testDataSource")public User getUserById(Long id) {// ...}// 使用default數據源@DataSource(name = "default") public User deleteUserById(Long id) {// ...}// 使用默認數據源(default數據源)public void updateUser(User user) {// ...}
}

Controller層與Dao層用法與類級別注解部分的使用介紹類似,此處不再贅述。

4.3、混合使用

多數據源注解的優先級別:方法級別注解>類級別注解>無注解(默認數據源)

當類和方法中都使用多數據源注解,會按照優先級別選擇具體數據源

@Service
@DataSource(name = "testDataSource")
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService{// 繼承類注解,使用testDataSource數據源public Product getProduct(Long id) {// ...}// 方法級別注解的優先級最高,優先使用方法注解,使用default數據源@DataSource(name = "default")public void updateProduct(Product product) {// ...}
}

Controller層與Dao層用法與類級別注解部分的使用介紹類似,此處不再贅述。

5、注意事項

???重要限制:由于數據源切換基于AOP,與@Transactional注解聯用時需注意:

  1. 事務注解應加在數據源注解外層
// ? 正確:事務在外層
@Transactional
@DataSource(name = "slave1")
public void transactionalMethod() { /* ... */ }// ? 危險:數據源切換可能不生效
@DataSource(name = "slave1")
@Transactional
public void riskyMethod() { /* ... */ }

總結

本文實現的多數據源方案具有以下優勢:
1. 非侵入式:通過注解透明切換,不影響業務邏輯
2. 靈活配置:支持方法級和類級數據源指定
3. 線程安全:基于ThreadLocal的上下文管理
4. 易于擴展:可快速添加新數據源

通過這種設計,開發者可以輕松管理多個數據源,特別適用于多租戶系統、讀寫分離、分庫分表等復雜場景。完整代碼已托管至Gitee,gitee地址:https://gitee.com/mutigmss/multiple-data-source-stater

歡迎在評論區交流使用體驗和優化建議!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/86224.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/86224.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/86224.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何挑選一款1588PTP時鐘同步服務器?

在當今數字化程度極高的時代&#xff0c;高精度時間同步對于眾多關鍵領域的高效、穩定運行起著決定性作用。PTP&#xff08;精確時間協議&#xff09;時鐘作為實現高精度時間同步的核心設備&#xff0c;其性能優劣直接關乎系統整體表現。挑選一款合適的 ptp網絡同步時鐘&#x…

Harmony狀態管理 @Local和@Param

深入理解ArkUI中的Param與Local裝飾器 引言 在ArkUI的狀態管理系統中&#xff0c;Param和Local是兩個核心裝飾器&#xff0c;它們分別用于處理組件間的數據傳遞和組件內部狀態管理。本文將詳細介紹這兩個裝飾器的使用場景、特性差異以及最佳實踐。 Param裝飾器&#xff1a;組…

物聯網攝像頭模塊的應用場景

一、智慧城市治理 ?智能交通優化? ?動態信號控制?&#xff1a;杭州部署20萬物聯網攝像頭&#xff0c;實時分析車流密度并聯動1200個紅綠燈&#xff0c;早高峰通行效率提升40%。 ?違規行為識別?&#xff1a;搭載GB/T28181協議的攝像頭AI抓拍交通違章&#xff0c;車牌識…

k8s Ingress、Service配置各樣例大全

目錄 壹、k8s Ingress 樣例大全&#x1f527; 一、基礎路由與 TLS 終止&#x1f504; 二、高級路由控制1. **URL 重寫**&#xff08;適用后端服務路徑與入口路徑不一致&#xff09;2. **多路徑路由到不同服務** &#x1f6a6; 三、流量治理策略1. **金絲雀發布&#xff08;灰度…

領域驅動設計(DDD)【10】之DDD戰術模式:工廠模式與表意接口模式

文章目錄 引言&#xff1a;DDD戰術模式的重要性一、DDD中的工廠模式1.1 工廠模式的核心概念1.2 工廠模式的三種實現方式1.2.1 簡單工廠方法1.2.2 工廠類1.2.3 抽象工廠模式 1.3 工廠模式的適用場景1.4 實際案例&#xff1a;電商訂單系統 二、表意接口模式2.1 表意接口2.2 表意接…

鴻蒙ArkTS---登錄邏輯,數據持久化,ArkUI,網絡請求等基礎內容記錄

該內容是在【博學谷】學習過程中的代碼記錄&#xff0c;如有任何問題請與作者聯系。 也歡迎同在學習鴻蒙開發的小伙伴的留言&#xff0c;一同學習&#xff0c;一同進步。 功能實現&#xff08;只記錄代碼&#xff0c;沒有相關配置&#xff0c;跑不起來&#xff09;&#xff…

沒有公網ip可以實現跨網p2p互通嗎?內網讓公網直連訪問常用工具

沒有公網IP的情況下仍然可以實現P2P通信&#xff0c;但需要借助NAT穿透技術或類似nat123同端口映射等第三方工具實現內網穿透?。???? 一、什么是P2P通信&#xff1f; P2P網絡&#xff08;Peer-to-Peer Network&#xff09;是一種去中心化的網絡架構&#xff0c;其中每個…

云服務器安裝寶塔面板(BT Panel)

安裝寶塔面板&#xff08;BT Panel&#xff09;是很多服務器管理員常用的操作&#xff0c;尤其適合用于管理網站、數據庫、FTP等。以下是基于 Linux 系統&#xff08;推薦 CentOS 或 Ubuntu&#xff09;的寶塔面板安裝步驟。 安裝前準備 云服務器一臺 可以訂購服務器 海外云主…

mongoose解析http字段值

最近在使用mongoose開發嵌入式web后端時&#xff0c;會遇到要解析js前端發送過來的http消息&#xff0c;比如傳遞用戶名&#xff0c;密碼過來&#xff0c;后端要解析出來并判斷是否登錄成功。 前端http有兩種組裝字段的方式。 第一種是 $.ajax({url: /upgradePackage,method: P…

高德地圖地址解析獲取經緯度失敗原因JSAPI

高德地圖地址解析獲取經緯度失敗原因JSAPI 地圖加載的時候老是報異常碼&#xff0c;地圖是可以加載出來的&#xff0c;但是在地圖上的操作老是有異常碼&#xff0c;找了好久不知道什么問題&#xff0c;異常碼會報兩種&#xff0c;一種是說什么key的問題&#xff0c;但是我當時…

極速JavaScript:全面性能優化實戰指南

在現代Web開發中&#xff0c;JavaScript性能直接影響用戶體驗。一個優化良好的應用能帶來更流暢的交互、更快的加載速度和更低的資源消耗。本文將深入探討實用的JavaScript性能優化技術&#xff0c;幫助您打造高性能Web應用。 一、性能瓶頸分析與診斷工具 性能問題的常見來源&…

【開源模型】高考數學139分!小米MiMo開源模型:7B參數突出重圍

小米 MiMo&#xff1a;7 B 參數撬動推理巔峰&#xff0c;開源模型的技術突圍 70 億參數超越 320 億對手&#xff0c;高考數學 139 分的背后是訓練策略的全面革新。 2025 年 4 月 30 日&#xff0c;小米開源的首個推理大模型 Xiaomi MiMo-7 B 橫空出世&#xff0c;以??僅 7 B …

用vscode破解最新typora1.10.8

1.下載格式化插件防止打開文件一團亂 1&#xff09;下載vscode&#xff1a; Download Visual Studio Code - Mac, Linux, Windows 2&#xff09;vscode下載中文插件重啟 如果沒變中文&#xff0c;在vscode界面按下&#xff1a; ctrl shift p 調出命令行 再輸入&#xff…

在 CI/CD 流程中使用 Jenkins 與 Docker 集成

在 CI/CD 流程中&#xff0c;Jenkins 與 Docker 的集成可以實現自動構建、測試、打包、發布容器鏡像&#xff0c;并部署到測試/生產環境。下面是從概念到落地操作的完整集成方案。 一、常見的集成方式有哪些&#xff1f; 方式描述1?? Jenkins 主機安裝 DockerJenkins 可以直…

閑庭信步使用SV搭建圖像測試平臺:第十課——繼續說說類

&#xff08;本系列只需要modelsim即可完成數字圖像的處理&#xff0c;每個工程都搭建了全自動化的仿真環境&#xff0c;只需要雙擊top_tb.bat文件就可以完成整個的仿真&#xff0c;大大降低了初學者的門檻&#xff01;&#xff01;&#xff01;&#xff01;如需要該系列的工程…

如何改進復雜推理 - 從提示詞設計入手

引言&#xff08;動機&#xff09; 在使用大語言模型&#xff08;如 GPT-4、Claude、DeepSeek 等&#xff09;構建智能問答、輔助決策或復雜任務代理系統時&#xff0c;可能遇到這些問題&#xff1a; 模型回答跳步驟、思路混亂同樣問題&#xff0c;模型表現高度不穩定新任務一…

如何解決和各個經銷商不同軟件對接的問題?湯臣案例分享

一、項目背景 湯臣倍健作為健康產品行業的領軍企業&#xff0c;其營銷云系統與全國經銷商 ERP 系統的數據無縫對接&#xff0c;對于提升業務運營效率和營銷精準度至關重要。傳統數據集成方法在面對經銷商 ERP 系統的多樣性和復雜性時&#xff0c;暴露出諸多問題&#xff0c;如…

Wordvice AI:Wordvice 推出的免費,基于先進的 AI 技術幫助用戶提升英文寫作質量

Wordvice AI&#xff1a;智能寫作助手&#xff0c;助力高效英文寫作 在當今全球化時代&#xff0c;英文寫作已成為眾多學生、研究人員、職場人士必備技能。然而&#xff0c;語法錯誤、表達不流暢、詞匯匱乏等問題常困擾著大家。別擔心&#xff0c;今天就來給大家介紹一款強大的…

【UE5】如何開發安卓項目的udp客戶端

1關于如何打包安卓項目這里就不贅述了 2代碼舉例。最重要的就是這兩句 #if PLATFORM_ANDROID #endif#if PLATFORM_WINDOWS #endif全部代碼如下&#xff1a; Button_Sheng.h: // Fill out your copyright notice in the Description page of Project Settings.#pragma once#in…

2025年6月21和22日復習和預習(python)

一、作業內容 &#xff08;一&#xff09;知識點回顧 用戶輸入處理 使用input()函數獲取用戶輸入的字符串&#xff0c;并存儲到變量中。 條件判斷語句 if-elif-else結構&#xff1a;根據不同條件執行相應代碼塊&#xff0c;適用于多分支判斷。 語音合成技術 導入pyttsx3庫實現…