Spring Cloud+Nacos+KMS 动态配置最佳实践
liuian 2024-12-11 15:45 103 浏览
前言
Cloud Native
Spring Cloud 框架在微服务领域被广大开发者所使用,@Value 是每位开发者都会接触到的注解,在 SpringBean 中可以通过 Value 注解引用 application.properties 属性,实现配置代码分离,提升应用代码部署的灵活性,但无法在运行期动态更新配置。Nacos 是一款集服务发现和配置管理功能的中间件产品,其中配置中心可以实现运行期配置实时生效,将工程本地的属性文件配置在 Nacos 中,在应用中做一些配置上的改动就可以轻易集成 Nacos 实现配置的动态刷新,工程依赖的属性多种多样,其中把有一些敏感数据配置在中心化的 Nacos 中可能会存在一些安全性层面的顾虑,Nacos 也有方法来应对这个问题,本次我们就对以上问题进行介绍。
本文将以如下步骤展开:
- 集成 Nacos 实现动态配置更新
- 集成 KMS 零代码改造实现敏感配置加密
- Spring Cloud+Nacos 工作原理介绍
SpringCloud 应用配置常规用法
Cloud Native
在一个 Spring Cloud 应用中,可以在 Bean 中通过 @Value 注解来引用 Spring 上下文中的属性值,可以引用环境变量,JVM 参数以及我们常见的 application.properties 配置文件中的属性。
以下是该种用法简易实例:
application.properties:
app.switch=true
app.threadhold=0.8一个简单的 Spring Bean:
@Component
public class AppConfig{
@Value("${app.switch:false}")
boolean switch;
@Value("${app.threadhold}")
double threadhold;
}AppConfig 可以被其他的 SpringBean 引用,可以正常获取到配置在 application.properties 中的 app.switch 和 app.threadhold 属性。
当我们需要修改 app.switch 和 app.threadhold 的值时,我们需要修改配置文件中的内容并对应用进行重启,当我们需要频繁修改某些业务参数时,重启应用的效率较低。
集成 Nacos 实现配置动态刷新
Cloud Native
以下我们将介绍如何在 Spring Cloud 应用中结合 Nacos 实现配置动态更新。
Spring 在 2.4.x 版本开始,引入了 spring.config.import 参数,可以自定义外部的属性源,通过 Spring Cloud Alibaba 组件可以实现将 Nacos 中的配置添加为 Spring 的属性源之一,因此在一个 Spring Bean 中也可以通过 Value 注解读取到 Nacos 中的配置。
以下我们将 Spring Cloud Alibaba 简称为 SCA。
1. pom 中添加 SCA nacos config 依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${spring.cloud.alibaba.version}</version>
</dependency>组件的版本名称和 spring boot 版本相关,可以根据 sca 官网的版本说明选择对应的版本:https://sca.aliyun.com/docs/2021/overview/version-explain/
2. 初始化 Nacos 配置
可以选择开源 nacos 或者购买商业化 MSE Nacos 版本,以下的图示为商业化 Nacos。
假设我们的应用为支付业务的应用,应用名为 pay。
在 Nacos 实例中可以创建 dataId=pay-application.properties,group=core 的配置。
3. 修改应用工程中的 application.properties
spring.config.import=nacos:pay-application.properties?group=core&refreshEnabled=true
spring.cloud.nacos.config.server-addr={server addr}添加 sping.config.import 参数将 Nacos 中 dataId=pay-application.properties,group=core 的配置添加为属性源,refreshEnabled=true 表示当 Nacos 中的配置变更时,需要同步刷新 Spring 中的属性源。
添加 spring.cloud.nacos.config.server-addr 参数指定连接的 Nacos 地址。
删除工程本地 application.properties 的 app.switch,app.threadhold 参数。
4. Spring Bean 中添加 RefreshScope 注解
@Component
@RefreshScope
public class AppConfig{
@Value("${app.switch:false}")
boolean switch;
@Value("${app.threadhold}")
double threadhold;
}在业务代码中仍然使用 Value 注解来引用 Spring 上下文中的配置,但需要在 Bean 上添加 RefreshScope 注解,只有添加该注解,Spring 才会在内部属性源更新时将属性刷新到当前的 Bean 中。
重启应用后,此时我们在 Nacos 中对配置 pay-application.properties 中属性进行修改,应用程序中 AppConfig 的参数值就会动态更新。
集成 KMS 实现配置无感加密
Cloud Native
上一节中我们通过集成 Nacos 实现了 SpringCloud 应用的配置动态更新,应用中的配置类型多种多样,其中某些配置具有较高的敏感性,比如数据库的连接地址,用户名密码,一些第三方组件的秘钥,Token 以及其他业务功能中敏感配置等等,这些配置的安全性非常重要,如果泄漏可能会对业务造成不可估量的影响。这些数据在 Nacos 中是存放在 pay-application.properties 中,以下是示例:
以下示例中的敏感参数均为模拟数据:
dataId=pay-application.properties,group=core:
# 数据库配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myapp
spring.datasource.username=user001
spring.datasource.password=pass!@#$%
# 秘钥Token等 secret and token
app.secret=GFYIdryujixxx
key.token=eedsjpp56hko8h
# 业务参数
app.switch=false
app.threadhold=0.8
SpringBean:
@Component
public class SecretConfig{
@Value("${app.secret}")
String secret;
@Value("${app.token}")
String token;
@PostConstruct
public void init(){
//init client use token and secret
}
}@Component
public class AppConfig{
@Value("${app.switch:false}")
boolean switch;
@Value("${app.threadhold}")
double threadhold;
}对于其中数据库密码,Token 等数据,通常会有更多安全性层面的考虑,比如这些敏感配置存储在 Nacos 中安全性是否可以保证,应用访问 Nacos 传输过程中数据是否存在泄漏可能性,敏感配置和普通的业务配置能否设置不同的读写权限,要实现以上安全性的要求,业务的代码是否可以尽量低成本改造等等。
要实现以上的安全性诉求,我们要做到以下几点:
1. 敏感配置在 Nacos 需要加密存储,不能直接明文存储
2. 敏感配置在传输过程中需要加密传输,防止中间网络设备通过抓包方式窃取数据。
3. 应用中的业务代码不能感知配置是否加密,仍需要按照之前的方式读取属性值,降低改造成本。
以下我们将介绍如何通过集成 KMS 实现零代码改造实现上述诉求。
加密配置拆分
如果我们期望将普通业务配置和敏感配置分离,我们可以 dataId=pay-application.properties,group=core 的配置进行拆分,将敏感数据单独拆分出一个独立的加密 Nacos 配置,比如 dataId=cipher-kms-aes-256-pay-application.properties,group=secret,用于存放数据源 token 等相关的敏感配置。为了防止解密配置和普通配置属性文件中的属性值重复,我们可以在加密配置中的属性值统一加上 encrypted 前缀。
Nacos 中的配置
1. dataId=cipher-kms-aes-256-pay-application.properties,group=secret
# 数据库配置
encrypted.spring.datasource.driver-class-name=com.mysql.jdbc.Driver
encrypted.spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
encrypted.spring.datasource.username=user001
encrypted.spring.datasource.password=pass!@#$%
# 秘钥Token等 secret and token
encrypted.app.secret=test_GFYIdryujixxx
encrypted.key.token=test_eedsjpp56hko8h2. dataId=pay-application.properties,group=core
原先的 pay-application.properties 中的属性暂时保持不动,等应用程序侧的所有节点引入新配置 cipher-kms-aes-256-pay-application.properties 之后,再对其做变更。
工程内配置改造
1. 引入 MSE 插件扩展包
加密的配置在存储和网络传输过程中都是密文,因此需要在应用侧支持解密的功能,在 pom 中引入解密插件。
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client-mse-extension</artifactId>
<version>1.0.4</version>
</dependency>2. 调整项目工程下的 application.properties
在 spring.config.import 添加新加配置 cipher-kms-aes-256-pay-application.properties,以及设置 KMS 初始化相关参数。
spring.config.import=nacos:cipher-kms-aes-256-pay-application.properties?group=secret&refreshEnabled=true,nacos:pay-application.properties?group=core&refreshEnabled=true
spring.cloud.nacos.config.server-addr={server addr}
# 设置客户端NacosClient访问KMS所需参数
spring.cloud.nacos.config.kms_region_id=cn-hangzhou
spring.cloud.nacos.config.kmsEndpoint=kst-xxx.cryptoservice.kms.aliyuncs.com
spring.cloud.nacos.config.kmsVersion=v3.0
spring.cloud.nacos.config.kmsClientKeyFilePath=clientKey_hangzhou.json
spring.cloud.nacos.config.kmsPasswordKey=10xxxd1d
spring_cloud_nacos_config_kmsPasswordKey=10xxxd1d
spring.cloud.nacos.config.kmsCaFilePath=clientKey_hangzhou.json客户端 NacosClient 访问 KMS 所需参数和 KMS 版本相关,具体步骤及后续更新见 MSE 官方文档:https://help.aliyun.com/zh/mse/user-guide/create-and-use-encrypted-configurations
修改配置重启业务应用完成后,此时应用程序读取的还是 Nacos 中 pay-application.properties 的属性值,但是此时 encrypted. 前缀的相关属性已经存在于 Spring 的上下文中。
加密配置迁移
当前应用程序重启完成之后,我们对 Nacos 的配置做如下修改:
dataId=pay-applicaition.properties,core=core
# 数据库配置
spring.datasource.driver-class-name=${encrypted.spring.datasource.driver-class-name}
spring.datasource.url={encrypted.spring.datasource.url}
spring.datasource.username=${encrypted.spring.datasource.username}
spring.datasource.password=${encrypted.spring.datasource.password}
# 秘钥Token等 secret and token
app.secret=${encrypted.app.secret}
key.token=${encrypted.key.token}
# 业务参数
app.switch=false
app.threadhold=0.8将原先在 pay-applicaition.properties 中的敏感属性以 ${} 方式引用 cipher-kms-aes-256-application.properties 中的属性 key。其中 cipher-kms-aes-256-pay-application.properties 中的属性并不会被应用程序代码中直接引用,而是在 pay-application.properties 通过配置嵌套的模式间接引用,程序代码中本质上还是读取的 pay-application.properties 中的属性。
过程中,我们只对工程中的配置文件做了改造,而业务代码层面没有做任何改动。改造完成后,cipher-kms-aes-256-pay-application.properties 配置中的内容在 Nacos 服务端,传输过程中以及应用本地的缓存中都是密文形式,在业务应用进程中,NacosClient 内部会和 KMS 交互完成密文解密成明文。
配置动态刷新工作原理介绍
Cloud Native
通过以上改造,我们就完成了 Spring Cloud+Nacos 实现配置动态刷新的功能,下面我们将介绍 Spring Cloud + Nacos 实现动态配置刷新的工作原理。
启动加载机制
Spring Bean 的初始化需要读取 Nacos 中的配置,因此 Nacos 初始化的过程是在所有 Spring Bean 初始化之前进行。Spring Clound Aliababa 组件会根据当前的 application.properties 参数对 Nacos 进行初始化,从 Nacos Server 加载配置,并构建为 NacosPropertySource。在此阶段中,Spring 也可以从 JVM 或者环境变量中读取参数,因此 Nacos 初始化所需的参数也可以通过 JVM 参数和环境变量进行设置,比如 Nacos server 的地址,命名空间 namespace,鉴权相关的 accessKey 及 secretKey 等。
在构建好完整属性源之后,Spring 会进入 Bean 的初始化流程中,只有在该阶段正常完成了 Nacos 的初始化以及 Nacos 配置的加载,Bean 才可以正常读取到 Nacos 中的配置。
动态更新机制
在上一章节中,我们在设置 spring.config.import 参数时,指定了 refreshEnabled=true 参数,该参数表示是否需要动态监听远端 NacosServer 中该配置的变化,如果不指定该参数,SCA 只会在启动时加载一次配置,并不会在运行期监听配置变化以及更新 NacosPropertySource 中的内容,SpringBean 中的属性值也就无法运行期更新。
可以按照上图图示中的数字顺序了解 Nacos 配置动态更新的机制,当 spring.config.import 配置中添加了 refreshEnabled=true 参数,SCA 就会在 Spring 容器初始化完成后对 Nacos 配置进行监听,时间点上和配置启动加载的时间点并不一致,配置初始化的时间点是在所有 Bean 初始化之前,而监听配置变更的时间点是在所有 Bean 完成初始化之后。
成功监听后,当我们在 Nacos 控制台对配置进行更新时,应用程序中的 NacosClient 会通知 SCA 配置发生变化,SCA 在接受到底层 Nacos 回调后会向 Spring 发布 RefreshEvent 事件,Spring 中的 ContextRefresher 会接受该事件,将最新的配置更新到 NacosPropertySource 中,更新 Enviroment 对象,并且发布 RefreshScopeRefreshedEvent 事件,对所有添加了 RefreshScope 注解以及 ConfigurationProperties 注解的 SpringBean 进行重新初始化,从未获取到最新的属性值。
以上流程中 spring.config.import 配置中的 refreshEnabled=true 参数决定了 SCA 是否会监听配置并在 Nacos 中配置的变化时更新 Enviroment,而在 Bean 中添加 RefreshScope 注解以及 ConfigurationProperties 直接决定了当 Enviroment 对象中的属性发生变化时刷新 Bean 中的属性值。
属性源的优先级
上面我们了解到 Value 注解可以读取环境变量,JVM,application.properties 中的配置,不同的属性源中的属性 key 可能重复,这种情况下,Spring 读取属性有一个优先级,如下图所示,优先级为 JVM 参数>环境变量>Nacos 配置(spring.config.import 参数引入属性源)>工程本地 application.properties。
Nacos 中设置的属性值会覆盖工程本地的属性文件,但是其优先级低于 JVM 和环境变量,如果在环境变量和 JVM 参数配置了相同的参数,Nacos 中的配置将不会生效。SCA 在实现配置动态加载遵循了 Spring Boot 官方推荐的属性源优先级顺序,参考:https://docs.spring.io/spring-boot/reference/features/external-config.html
此外,spring.config.import 参数可以指定多个属性源,不同的属性源之间通过逗号 "," 分隔,多个不同属性源之间,引入顺序靠前,优先级更低。
在 spring boot 2.4 之前的版本中,Spring 不支持通过 spring.config.import 指定外部属性源,SCA 内部提供了 spring.cloud.nacos.config.shared-configs 和spring.cloud.nacos.config.extension-configs 参数来指定多个 Nacos 配置属性源,在最新的 SCA 版本 2023.0.1.3 中已经废弃这两个参数,统一到标准的 spring.config.import 参数。此外,在低版本的 Spring 中,支持在 bootstrap.yml 文件中配置参数,该种用法也在新版本 Spring 中废弃,统一将参数配置 application.properties,我们建议对依赖低版本的应用代码进行升级,统一改造为标准的方法进行配置。
Nacos日志
Nacos 扮演了配置动态推送的核心功能,通过查看 Nacos 的启动及运行时日志,可以帮助大家更好地理解两者整合的内部原理,并且有助于大家自主排查配置中心的常见问题,Nacos 客户端的日志目录默认在 {user.home}/logs/nacos/目录下,其中 {user.home} 是应用进程运行所属用户的主目录,在 Linux 系统中,如果进程以 root 启动,日志默认在/root/logs/nacos/下,如果以 admin 用户启动,日志默认在/home/admin/logs/nacos/下。在 nacos 目录下,我们可以看到 remote.log,config.log,naming.log 三个日志文件,其中 remote.log 记录 Nacos 客户端和服务端的长连接相关的日志,naming.log 是服务管理相关日志,config.log 是我们需要核心关注的配置相关日志,其中记录着 Nacos 客户端和 Nacos 服务端交互的详细日志。以下是几个关键的日志:
- add-listener:表示应用程序监听了配置,包括 namespace,dataId,group 三元组,只有正常监听了配置,才能在配置变更时收到推送
- server-push:表示应用程序收到了服务端的配置变更推送事件。
- data-received:表示应用程序收到推送事件后向服务端查询到了配置内容,包括 namespace,dataId,group 三元组以及接受到的配置 md5,可以和 Nacos 控制台比对 md5 值来判断是否接受到正确的版本
- notify-listener:表示应用程序收到了更新后的配置内容,并且尝试将最新的配置内容回调给对应的监听器,比如通知 SCA 重新加载 Nacos 的配置并且更新 Spring 上下文。
- notify-ok:表示 Nacos 已经成功回调了监听器,监听器中的回调已经正常执行。
- notify-error:表示 Nacos 回调了监听器,但是监听器执行是抛出了异常,从业务视角,该种情况会表现配置更新没有效果,需要根据具体异常进行处理。
- notify-block-monitor:表示 Nacos 回调了监听器,但监听器执行超时,默认监听器执行超过 60s 时会打印该日志。
通过阅读 Nacos 的日志,可以排查在使用 Nacos 配置中心过程遇到的问题,比如通过日志判断应用程序是否连接到了正确的 Nacos 服务端地址,是否监听了正确的 namespace,dataId 以及 group,是否正常收到了变更推送以及监听器回调时是否存在异常报错以及阻塞超时的情况。
在启动和运行时我们也可以在 Nacos 的 config.log 日志中观察到 Nacos 和 KMS 交互的日志,以便于更好地排查遇到的问题,关于集成 KMS 实现配置加解密的原理,可以参考《Nacos 安全零信任实践》一文中 Nacos 存储安全一节。
结语
Cloud Native
以上我们在 Spring Cloud 应用中结合 Nacos 实现了运行期配置动态更新的功能,以及在此基础上结合 KMS 在不改动代码的情况下对应用使用的敏感配置进行保护,解决将配置迁移到 Nacos 中可能存在的数据安全顾虑,并对其底层工作原理做了简单介绍。Nacos 作为广泛使用的配置中心,除了基础的配置实时动态更新的核心功能外,还支持配置监听查询(配置订阅节点查询),推送轨迹,标签灰度等进阶的提升易用性功能。
安全性方面,我们对普通业务配置和敏感配置进行了拆分,从应用程序侧解决了敏感数据泄漏的问题,除此之外,我们在 MSE 控制台中也会有针对不同账号设置细粒度的访问控制的功能,比如控制业务普通配置对应用开发开放访问,数据源秘钥等敏感数据只对运维人员开放,MSE Nacos 也可以支持该功能,对于访问控制部分,我们后续也会有文章进行单独介绍,可关注后续文章。
相关链接:
[1] Nacos 官网
https://nacos.io
[2] Nacos Github 主仓库
https://github.com/alibaba/nacos
[3] 生态组仓库
https://github.com/nacos-group
[4] Spring Cloud Alibaba
https://sca.aliyun.com/docs/2023/user-guide/nacos/quick-start/
Nacos 多语言生态仓库:
[1] Nacos-GO-SDK
https://github.com/nacos-group/nacos-sdk-go
[2] Nacos-Python-SDK
https://github.com/nacos-group/nacos-sdk-python
[3] Nacos-Rust-SDK
https://github.com/nacos-group/nacos-sdk-rust
[4] Nacos C# SDK
https://github.com/nacos-group/nacos-sdk-csharp
[5] Nacos C++ SDK
https://github.com/nacos-group/nacos-sdk-cpp
[6] Nacos PHP-SDK
https://github.com/nacos-group/nacos-sdk-php
[7] Rust Nacos Server
https://github.com/nacos-group/r-nacos
推荐阅读:
《MSE Nacos:解决敏感配置的安全隐患》
《Nacos 配置中心变更利器:自定义标签灰度》
相关推荐
-
- 驱动网卡(怎么从新驱动网卡)
-
网卡一般是指为电脑主机提供有线无线网络功能的适配器。而网卡驱动指的就是电脑连接识别这些网卡型号的桥梁。网卡只有打上了网卡驱动才能正常使用。并不是说所有的网卡一插到电脑上面就能进行数据传输了,他都需要里面芯片组的驱动文件才能支持他进行数据传输...
-
2026-01-30 00:37 liuian
- win10更新助手装系统(微软win10更新助手)
-
1、点击首页“系统升级”的按钮,给出弹框,告诉用户需要上传IMEI码才能使用升级服务。同时给出同意和取消按钮。华为手机助手2、点击同意,则进入到“系统升级”功能华为手机助手华为手机助手3、在检测界面,...
- windows11专业版密钥最新(windows11专业版激活码永久)
-
Windows11专业版的正版密钥,我们是对windows的激活所必备的工具。该密钥我们可以通过微软商城或者通过计算机的硬件供应商去购买获得。获得了windows11专业版的正版密钥后,我...
-
- 手机删过的软件恢复(手机删除过的软件怎么恢复)
-
操作步骤:1、首先,我们需要先打开手机。然后在许多图标中找到带有[文件管理]文本的图标,然后单击“文件管理”进入页面。2、进入页面后,我们将在顶部看到一行文本:手机,最新信息,文档,视频,图片,音乐,收藏,最后是我们正在寻找的[更多],单击...
-
2026-01-29 23:55 liuian
- 一键ghost手动备份系统步骤(一键ghost 备份)
-
步骤1、首先把装有一键GHOST装系统的U盘插在电脑上,然后打开电脑马上按F2或DEL键入BIOS界面,然后就选择BOOT打USDHDD模式选择好,然后按F10键保存,电脑就会马上重启。 步骤...
- 怎么创建局域网(怎么创建局域网打游戏)
-
1、购买路由器一台。进入路由器把dhcp功能打开 2、购买一台交换机。从路由器lan端口拉出一条网线查到交换机的任意一个端口上。 3、两台以上电脑。从交换机任意端口拉出网线插到电脑上(电脑设置...
- 精灵驱动器官方下载(精灵驱动手机版下载)
-
是的。驱动精灵是一款集驱动管理和硬件检测于一体的、专业级的驱动管理和维护工具。驱动精灵为用户提供驱动备份、恢复、安装、删除、在线更新等实用功能。1、全新驱动精灵2012引擎,大幅提升硬件和驱动辨识能力...
- 一键还原系统步骤(一键还原系统有哪些)
-
1、首先需要下载安装一下Windows一键还原程序,在安装程序窗口中,点击“下一步”,弹出“用户许可协议”窗口,选择“我同意该许可协议的条款”,并点击“下一步”。 2、在弹出的“准备安装”窗口中,可...
- 电脑加速器哪个好(电脑加速器哪款好)
-
我认为pp加速器最好用,飞速土豆太懒,急速酷六根本不工作。pp加速器什么网页都加速,太任劳任怨了!以上是个人观点,具体性能请自己试。ps:我家电脑性能很好。迅游加速盒子是可以加速电脑的。因为有过之...
- 任何u盘都可以做启动盘吗(u盘必须做成启动盘才能装系统吗)
-
是的,需要注意,U盘的大小要在4G以上,最好是8G以上,因为启动盘里面需要装系统,内存小的话,不能用来安装系统。内存卡或者U盘或者移动硬盘都可以用来做启动盘安装系统。普通的U盘就可以,不过最好U盘...
- u盘怎么恢复文件(u盘文件恢复的方法)
-
开360安全卫士,点击上面的“功能大全”。点击文件恢复然后点击“数据”下的“文件恢复”功能。选择驱动接着选择需要恢复的驱动,选择接入的U盘。点击开始扫描选好就点击中间的“开始扫描”,开始扫描U盘数据。...
- 系统虚拟内存太低怎么办(系统虚拟内存占用过高什么原因)
-
1.检查系统虚拟内存使用情况,如果发现有大量的空闲内存,可以尝试释放一些不必要的进程,以释放内存空间。2.如果系统虚拟内存使用率较高,可以尝试增加系统虚拟内存的大小,以便更多的应用程序可以使用更多...
-
- 剪贴板权限设置方法(剪贴板访问权限)
-
1、首先打开iphone手机,触碰并按住单词或图像直到显示选择选项。2、其次,然后选取“拷贝”或“剪贴板”。3、勾选需要的“权限”,最后选择开启,即可完成苹果剪贴板权限设置。仅参考1.打开苹果手机设置按钮,点击【通用】。2.点击【键盘】,再...
-
2026-01-29 21:37 liuian
- 平板系统重装大师(平板重装win系统)
-
如果你的平板开不了机,但可以连接上电脑,那就能好办,楼主下载安装个平板刷机王到你的个人电脑上,然后连接你的平板,平板刷机王会自动识别你的平板,平板刷机王上有你平板的我刷机包,楼主点击下载一个,下载完成...
- 联想官网售后服务网点(联想官网售后服务热线)
-
联想3c服务中心是联想旗下的官方售后,是基于互联网O2O模式开发的全新服务平台。可以为终端用户提供多品牌手机、电脑以及其他3C类产品的维修、保养和保险服务。根据客户需求层次,联想服务针对个人及家庭客户...
- 一周热门
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)
