如何在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. …