如何在SpringBoot項目中優雅的連接多臺Redis

如何在SpringBoot項目中優雅的連接多臺Redis

在Spring Boot項目中,連接單個Redis實例是常見需求,但有時需要同時連接多個Redis實例(例如,主Redis用于業務數據存儲,另一個Redis用于爬蟲數據緩存)。本文將基于一個實際案例,詳細介紹如何在Spring Boot中優雅地配置和使用多個Redis實例,解決常見的注入歧義問題,并提供一個通用的Redis工具類來簡化操作。


背景

在一個Spring Boot項目中,我們需要連接兩臺Redis實例:

  • 主Redis:用于常規業務數據,配置在spring.redis(數據庫索引4)。
  • 爬蟲Redis:用于爬蟲相關數據,配置在spring.redis-crawler(數據庫索引7)。

項目中遇到以下問題:

  1. 配置兩個RedisTemplate時,Spring容器找不到redisCrawlerConnectionFactory
  2. 配置多個RedisProperties導致注入歧義,拋出NoUniqueBeanDefinitionException
  3. 需要一個通用的RedisCache工具類,支持動態選擇Redis實例,同時兼容現有代碼。

下面,我們將逐步解決這些問題,并展示如何優雅地實現多Redis連接。


項目配置

1. 配置文件(application.yml)

首先,在application.yml中定義兩套Redis配置:

spring:redis:host: [REDACTED_HOST]port: 6379database: 4# password: [REDACTED_PASSWORD]timeout: 10slettuce:pool:min-idle: 0max-idle: 8max-active: 8max-wait: -1msredis-crawler:host: [REDACTED_HOST]port: 6379database: 7# password: [REDACTED_PASSWORD]timeout: 10slettuce:pool:min-idle: 0max-idle: 8max-active: 8max-wait: -1ms
  • spring.redis:主Redis,數據庫索引4。
  • spring.redis-crawler:爬蟲Redis,數據庫索引7。
  • 如果Redis需要密碼,取消password字段的注釋并設置正確值(此處已脫敏)。

2. 依賴配置(pom.xml)

確保項目包含以下依賴:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.51</version>
</dependency>
  • spring-boot-starter-data-redis:提供Redis支持。
  • lettuce-core:使用Lettuce作為Redis客戶端。
  • fastjson:用于自定義序列化(項目中使用了FastJson2JsonRedisSerializer)。

配置多個Redis實例

問題1:找不到redisCrawlerConnectionFactory

最初,我們嘗試在RedisConfig.java中定義兩個RedisTemplate

@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {// 主Redis模板
}@Bean(name = "redisTemplateCrawl")
public RedisTemplate<Object, Object> redisTemplateCrawl(@Qualifier("redisCrawlerConnectionFactory") RedisConnectionFactory redisCrawlerConnectionFactory) {// 爬蟲Redis模板
}

啟動時拋出異常:

No qualifying bean of type 'org.springframework.data.redis.connection.RedisConnectionFactory' available

原因:Spring Boot自動為spring.redis創建了一個RedisConnectionFactory,但不會為spring.redis-crawler創建。redisTemplateCrawl依賴的redisCrawlerConnectionFactory未定義。

解決方法:顯式定義redisCrawlerConnectionFactory和對應的RedisProperties

@Bean(name = "redisCrawlerProperties")
@ConfigurationProperties(prefix = "spring.redis-crawler")
public RedisProperties redisCrawlerProperties() {return new RedisProperties();
}@Bean(name = "redisCrawlerConnectionFactory")
public RedisConnectionFactory redisCrawlerConnectionFactory(@Qualifier("redisCrawlerProperties") RedisProperties redisCrawlerProperties) {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setHostName(redisCrawlerProperties.getHost());config.setPort(redisCrawlerProperties.getPort());config.setDatabase(redisCrawlerProperties.getDatabase());if (redisCrawlerProperties.getPassword() != null) {config.setPassword(redisCrawlerProperties.getPassword());}return new LettuceConnectionFactory(config);
}
  • redisCrawlerProperties:綁定spring.redis-crawler配置。
  • redisCrawlerConnectionFactory:根據redisCrawlerProperties創建連接工廠。

問題2:RedisProperties注入歧義

配置了redisCrawlerProperties后,啟動時又遇到新問題:

Parameter 0 of constructor in org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration required a single bean, but 2 were found:- redisCrawlerProperties- spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties

原因:Spring Boot自動為spring.redis創建了一個RedisProperties,而我們又定義了redisCrawlerProperties,導致LettuceConnectionConfiguration無法確定使用哪個RedisProperties

解決方法:顯式定義主Redis的RedisProperties,并用@Primary標記為默認:

@Bean(name = "redisProperties")
@Primary
@ConfigurationProperties(prefix = "spring.redis")
public RedisProperties redisProperties() {return new RedisProperties();
}@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory redisConnectionFactory(@Qualifier("redisProperties") RedisProperties redisProperties) {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setHostName(redisProperties.getHost());config.setPort(redisProperties.getPort());config.setDatabase(redisProperties.getDatabase());if (redisProperties.getPassword() != null) {config.setPassword(redisProperties.getPassword());}return new LettuceConnectionFactory(config);
}
  • redisProperties:綁定spring.redis,用@Primary標記為默認。
  • redisConnectionFactory:為主Redis創建連接工廠,確保redisTemplate使用正確配置。

最終的RedisConfig.java

以下是完整的RedisConfig.java(敏感信息已脫敏):

package com.caven.framework.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {@Bean(name = "redisProperties")@Primary@ConfigurationProperties(prefix = "spring.redis")public RedisProperties redisProperties() {return new RedisProperties();}@Bean(name = "redisConnectionFactory")public RedisConnectionFactory redisConnectionFactory(@Qualifier("redisProperties") RedisProperties redisProperties) {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setHostName(redisProperties.getHost());config.setPort(redisProperties.getPort());config.setDatabase(redisProperties.getDatabase());if (redisProperties.getPassword() != null) {config.setPassword(redisProperties.getPassword());}return new LettuceConnectionFactory(config);}@Bean(name = "redisTemplate")@SuppressWarnings(value = {"unchecked", "rawtypes"})public RedisTemplate<Object, Object> redisTemplate(@Qualifier("redisConnectionFactory") RedisConnectionFactory connectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);serializer.setObjectMapper(mapper);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}@Bean(name = "redisCrawlerProperties")@ConfigurationProperties(prefix = "spring.redis-crawler")public RedisProperties redisCrawlerProperties() {return new RedisProperties();}@Bean(name = "redisCrawlerConnectionFactory")public RedisConnectionFactory redisCrawlerConnectionFactory(@Qualifier("redisCrawlerProperties") RedisProperties redisCrawlerProperties) {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setHostName(redisCrawlerProperties.getHost());config.setPort(redisCrawlerProperties.getPort());config.setDatabase(redisCrawlerProperties.getDatabase());if (redisCrawlerProperties.getPassword() != null) {config.setPassword(redisCrawlerProperties.getPassword());}return new LettuceConnectionFactory(config);}@Bean(name = "redisTemplateCrawl")@SuppressWarnings(value = {"unchecked", "rawtypes"})public RedisTemplate<Object, Object> redisTemplateCrawl(@Qualifier("redisCrawlerConnectionFactory") RedisConnectionFactory redisCrawlerConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisCrawlerConnectionFactory);FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);serializer.setObjectMapper(mapper);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}@Beanpublic DefaultRedisScript<Long> limitScript() {DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(limitScriptText());redisScript.setResultType(Long.class);return redisScript;}private String limitScriptText() {return "local key = KEYS[1]\n" +"local count = tonumber(ARGV[1])\n" +"local time = tonumber(ARGV[2])\n" +"local current = redis.call('get', key);\n" +"if current and tonumber(current) > count then\n" +"    return tonumber(current);\n" +"end\n" +"current = redis.call('incr', key)\n" +"if tonumber(current) == 1 then\n" +"    redis.call('expire', key, time)\n" +"end\n" +"return tonumber(current);";}
}

實現通用的Redis工具類

為了簡化Redis操作,我們創建了一個RedisCache工具類,支持動態選擇RedisTemplate,同時兼容現有代碼。

問題3:動態選擇Redis實例

最初的RedisCache.java只注入了一個RedisTemplate

@Autowired
public RedisTemplate redisTemplate;

這導致無法操作爬蟲Redis。我們希望:

  • 現有代碼繼續使用主Redis(redisTemplate),無需修改。
  • 新代碼可以通過參數選擇主Redis或爬蟲Redis(redisTemplateCrawl)。

解決方法

  • 注入兩個RedisTemplateredisTemplateredisTemplateCrawl)。
  • 保留原有方法,默認使用redisTemplate
  • 為每個方法添加帶templateName參數的重load版本,支持選擇Redis實例。

最終的RedisCache.java

package com.caven.framework.redis;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;import java.util.*;
import java.util.concurrent.TimeUnit;@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {@Autowired@Qualifier("redisTemplate")private RedisTemplate redisTemplate;@Autowired@Qualifier("redisTemplateCrawl")private RedisTemplate redisTemplateCrawl;private RedisTemplate getRedisTemplate(String templateName) {if ("crawl".equalsIgnoreCase(templateName)) {return redisTemplateCrawl;}return redisTemplate;}public <T> void setCacheObject(final String key, final T value) {redisTemplate.opsForValue().set(key, value);}public <T> void setCacheObject(final String templateName, final String key, final T value) {getRedisTemplate(templateName).opsForValue().set(key, value);}public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}public <T> void setCacheObject(final String templateName, final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {getRedisTemplate(templateName).opsForValue().set(key, value, timeout, timeUnit);}// 其他方法類似,省略完整代碼
}

關鍵點

  • 使用@Qualifier注入redisTemplateredisTemplateCrawl
  • 保留原有方法(如setCacheObject(String key, T value)),默認使用redisTemplate
  • 新增帶templateName的重載方法(如setCacheObject(String templateName, String key, T value)),支持選擇Redis實例。
  • getRedisTemplate方法根據templateName返回對應的RedisTemplate"crawl"返回redisTemplateCrawl,否則返回redisTemplate)。

使用示例

Service層中:

@Service
public class MyService {@Autowiredprivate RedisCache redisCache;public void example() {// 現有代碼:默認使用主Redis (database: 4)redisCache.setCacheObject("key1", "value1");String value1 = redisCache.getCacheObject("key1");// 新代碼:使用爬蟲Redis (database: 7)redisCache.setCacheObject("crawl", "key2", "value2");String value2 = redisCache.getCacheObject("crawl", "key2");}
}
  • 現有代碼無需修改,繼續使用主Redis。
  • 新代碼通過templateName="crawl"操作爬蟲Redis。

優化建議

  1. 使用枚舉替代字符串
    為避免templateName的硬編碼,可使用枚舉:

    public enum RedisInstance {DEFAULT,CRAWL
    }private RedisTemplate getRedisTemplate(RedisInstance instance) {return instance == RedisInstance.CRAWL ? redisTemplateCrawl : redisTemplate;
    }
    

    使用示例:

    redisCache.setCacheObject(RedisInstance.CRAWL, "key2", "value2");
    
  2. 錯誤處理
    getRedisTemplate中添加空檢查:

    private RedisTemplate getRedisTemplate(String templateName) {if (redisTemplate == null || redisTemplateCrawl == null) {throw new IllegalStateException("RedisTemplate not initialized");}return "crawl".equalsIgnoreCase(templateName) ? redisTemplateCrawl : redisTemplate;
    }
    
  3. 連接測試
    確保Redis服務器可訪問(此處IP已脫敏):

    redis-cli -h [REDACTED_HOST] -p 6379 -n 4  # 主Redis
    redis-cli -h [REDACTED_HOST] -p 6379 -n 7  # 爬蟲Redis
    
  4. 序列化器
    確保FastJson2JsonRedisSerializer實現正確,支持序列化和反序列化。


總結

通過以下步驟,我們在Spring Boot項目中實現了優雅的多Redis連接:

  1. application.yml中配置兩套Redis(spring.redisspring.redis-crawler)。
  2. RedisConfig.java中定義兩個RedisPropertiesRedisConnectionFactoryRedisTemplate,使用@Primary@Qualifier解決注入歧義。
  3. 實現RedisCache工具類,支持動態選擇Redis實例,同時兼容現有代碼。

這種方案既靈活又易于擴展,適合需要操作多個Redis實例的場景。如果你有更多Redis實例,只需重復上述步驟,定義新的RedisPropertiesRedisTemplate,并在RedisCache中擴展支持。


參考

  • Spring Boot官方文檔:https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.redis
  • Lettuce官方文檔:https://lettuce.io/

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

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

相關文章

追覓科技舉辦2025「敢夢敢為」發布會,發布超30款全場景重磅新品

上海&#xff0c;2025年9月4日——在以「敢夢敢為」為主題的2025新品發布會上&#xff0c;追覓科技一次性發布超30款新品&#xff0c;全面涵蓋智能清潔、智能家電、家庭健康與個護等核心領域。在清潔家電與大家電“高端智能生態矩陣”已然成型的當下&#xff0c;追覓科技正在邁…

去服務器化的流媒體分發:輕量級RTSP服務的技術邏輯與優勢

一、設計背景&#xff1a;RTSP/RTP協議的技術根基 在流媒體傳輸體系中&#xff0c;RTSP&#xff08;Real-Time Streaming Protocol&#xff09; RTP/RTCP 組合被廣泛認為是最經典、最標準化的解決方案。 RTSP 作為應用層協議&#xff0c;本質上是一個 遠程會話控制協議。它通過…

mysql分頁SQL

在 MySQL 中&#xff0c;實現分頁查詢通常使用 LIMIT 子句。LIMIT 可以指定返回結果的起始位置和數量&#xff0c;非常適合實現分頁功能。 基本語法如下&#xff1a; SELECT 列名 FROM 表名 WHERE 條件 ORDER BY 排序字段 [ASC|DESC] LIMIT 起始位置, 每頁顯示數量;說明&#x…

刷新記錄:TapData Oracle 日志同步性能達 80K TPS,重塑實時同步新標準

在當前數據驅動的企業環境中&#xff0c;高效、穩定的數據同步能力已成為支撐關鍵業務系統的核心需求。尤其在高頻變更、大量增量數據的業務場景中&#xff0c;傳統的 Oracle 日志解析方案往往在吞吐能力和延遲控制方面力不從心。 隨著企業全面邁入“實時化”時代&#xff0c;金…

Java全棧開發面試實戰:從基礎到高并發的深度解析

Java全棧開發面試實戰&#xff1a;從基礎到高并發的深度解析 在一次真實的面試中&#xff0c;一位擁有5年全棧開發經驗的程序員&#xff0c;面對來自某互聯網大廠的技術面試官&#xff0c;展現出了扎實的基礎與豐富的項目經驗。以下是這次面試的完整記錄。 面試官開場 面試官&a…

【mac】如何在 macOS 終端中高效查找文件:五種實用方法

【mac】如何在 macOS 終端中高效查找文件&#xff1a;五種實用方法 在 macOS 上&#xff0c;終端是一個強大的工具&#xff0c;不僅可以執行命令&#xff0c;還能幫助你快速找到需要的文件。無論是按文件名、類型、大小&#xff0c;還是文件內容搜索&#xff0c;都有多種命令可…

React筆記_組件之間進行數據傳遞

目錄父子組件傳值- props父傳子子傳父嵌套組件傳值-Context API概念React.createContext APIProvider組件正確示例錯誤示例消費 ContextReact.Consumer組件useContext Hook區別使用場景舉例說明-用戶信息狀態管理-Redux父子組件傳值- props 在React中父子組件傳值是單向數據流…

Elixir通過Onvif協議控制IP攝像機,擴展ExOnvif的攝像頭停止移動 Stop 功能

ExOnvif官方文檔 在使用 Elixir 進行 IPdome 控制時&#xff0c;可以使用 ExOnvif 庫。 ExOnvif官方文檔中未給停止移動調用命令&#xff0c;自己按照onvif協議 Onvif協議 擴展的此項功能&#xff1b; 停止移動 Stop 在Onvif協議中&#xff0c;用于停止云臺移動的操作為Stop…

spring boot autoconfigure 自動配置的類,和手工 @configuration + @bean 本質區別

它們在本質功能上都是為了向 Spring 容器注冊 Bean&#xff0c;但在觸發方式、加載時機、可控性和適用場景上有明顯區別。可以這樣理解&#xff1a;1?? 核心區別對比維度Configuration Bean&#xff08;手工配置&#xff09;Spring Boot EnableAutoConfiguration / 自動配置…

論文解讀 | Franka 機器人沉浸式遠程操作:高斯濺射 VR 賦能的遙操框架研發與應用

研究背景 在工業制造、危險環境作業等領域&#xff0c;機器人遠程操作技術是突破人類作業邊界的關鍵手段。傳統遠程操作依賴2D 相機反饋與操縱桿控制&#xff0c;存在空間感知差、操作精度低、沉浸感弱等問題&#xff0c;難以滿足復雜移動操作任務需求。 例如在核設施退役、災后…

【Unity Shader學習筆記】(四)Shader編程

一、OpenGL與DirectX 這是計算機圖形學中兩個最核心的應用程序接口(API),它們充當了應用程序與顯卡硬件之間的橋梁,讓開發者能夠調用GPU進行圖形渲染和通用計算。 特性維度 OpenGL DirectX 主導公司 Khronos Group (原SGI) Microsoft

程序員之電工基礎-初嘗線掃相機

一、背景 興趣愛好來了&#xff0c;決定研發一個產品。涉及到電工和機械等知識&#xff0c;所以記錄一下相關的基礎知識。本期主題是初嘗線掃相機&#xff0c;雖然又回到了編程&#xff0c;但是對于我來說&#xff0c;硬件集成的經驗不足&#xff0c;缺乏相機、鏡頭的專業知識。…

qt QWebSocket詳解

1、概述 QWebSocket是Qt網絡模塊中的一個類&#xff0c;用于實現WebSocket協議的通信。WebSocket是一種全雙工的通信協議&#xff0c;允許在客戶端和服務器之間建立實時的雙向通信。QWebSocket提供了對WebSocket協議的支持&#xff0c;使得開發者能夠在Qt應用中方便地實現實時…

Java基礎IO流全解析:常用知識點與面試高頻考點匯總

Java基礎IO流全解析&#xff1a;常用知識點與面試高頻考點匯總 前言 IO&#xff08;Input/Output&#xff09;流是Java中處理數據傳輸的核心機制&#xff0c;無論是文件操作、網絡通信還是數據持久化&#xff0c;都離不開IO流的身影。對于Java初學者而言&#xff0c;IO流的分類…

PDF.AI-與你的PDF文檔對話

本文轉載自&#xff1a;PDF.AI-與你的PDF文檔對話 - Hello123工具導航 ** 一、&#x1f916; PDF.AI&#xff1a;秒懂 PDF 的智能對話助手 PDF.AI 是一款超實用的AI 文檔分析工具&#xff0c;專門幫你快速搞定各種 PDF 文件。不管多長的合同、報告或論文&#xff0c;你只需上…

微軟出品!這個免費開源工具集獲得了GitHub 123k程序員點贊

大家晚上好&#xff0c;我是顧北&#xff0c;是一名AI應用探索者&#xff0c;當然也是GitHub開源項目收集愛好者。最近我在整理Windows效率工具時&#xff0c;發現了一個讓我一晚上沒睡著覺的開源項目——微軟官方出品的 PowerToys&#xff0c;可謂是徹夜難眠啊。經過我兩個月多…

【開題答辯全過程】以 小眾商戶小程序為例,包含答辯的問題和答案

個人簡介一名14年經驗的資深畢設內行人&#xff0c;語言擅長Java、php、微信小程序、Python、Golang、安卓Android等開發項目包括大數據、深度學習、網站、小程序、安卓、算法。平常會做一些項目定制化開發、代碼講解、答辯教學、文檔編寫、也懂一些降重方面的技巧。感謝大家的…

Vue 3.5 重磅新特性:useTemplateRef 讓模板引用更優雅、更高效!

Vue 3.5 重磅新特性:useTemplateRef 讓模板引用更優雅、更高效! 目錄 前言 什么是 useTemplateRef 傳統 ref 的問題 useTemplateRef 的優勢 基礎用法 進階用法 最佳實踐 遷移指南 性能對比 注意事項 總結 前言 Vue 3.5 帶來了一個激動人心的新特性 useTemplateRef,它徹底革…

uni app 的app端 寫入運行日志到指定文件夾。

uni app 的app 端 寫入指定目錄文件夾。并自動生成當前日期的日志文件。刪除十日前的日志文件其中 writefile.js 代碼如下const {default: logger } require("./logger")var name var url var params var method var resfunction setlog(name, url, params, method)…

桌面應用開發語言與框架選擇指南

桌面應用開發的語言和框架選擇非常豐富&#xff0c;從原生性能到跨平臺解決方案應有盡有。下面我將它們分為幾大類進行詳細介紹&#xff0c;并附上各自的優缺點和適用場景。 一、 原生開發 (Native Development) 原生開發能提供最佳的性能和與操作系統最完美的集成體驗。 1. …