当前位置: 首页 > news >正文

log4j2或者logback配置模版实现灵活输出服务名

介绍

在我们使用log4j2或者logback打印日志时,输出的内容中通常是一定要加上服务名的。以log4j2为例:

<!--输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT"><!-- 输出日志的格式 --><PatternLayout pattern="server-case %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n"/>
</Console>

服务名为server-case,输出的内容为

server-case 2023-09-15 17:44:38  INFO ServerCaseApplication:648 - No active profile set, falling back to default profiles: default
server-case 2023-09-15 17:44:39  INFO TomcatWebServer:108 - Tomcat initialized with port(s): 7081 (http)
server-case 2023-09-15 17:44:39  INFO Http11NioProtocol:173 - Initializing ProtocolHandler ["http-nio-7081"]
server-case 2023-09-15 17:44:39  INFO StandardService:173 - Starting service [Tomcat]
...

但是有种情况,有的项目要部署在甲方内网或者连接甲方的资源。项目是同一套代码,但要服务于不同的甲方,所以一个项目会有不同的服务名的情况。

这样的话,服务名就不能写死,要根据不同的服务名来输出。

问题

有的人可能会想到直接设置一个对象实现EnvironmentAware接口中的setEnvironment(Environment environment),来获取environment来获取spring.application.name

但这样有问题,设置的这个对象是要在spring上下文中进行加载后才能获得environment,所以在这个对象加载之前的日志输出还是拿不到environment

@Component
public class Test implements EnvironmentAware {private Environment environment;@Overridepublic void setEnvironment(final Environment environment) {this.environment = environment;System.setProperty("applicationName", Objects.requireNonNull(environment.getProperty("spring.application.name")));}
}
<!--输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT"><!-- 输出日志的格式 --><PatternLayout pattern="${sys:applicationName} %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n"/>
</Console>
${sys:applicationName} 2023-09-18 10:18:34  INFO ServerCaseApplication:55 - Starting ServerCaseApplication on lukuan with PID 21472 (D:\idea_work_my\gitee\cook-frame\server\server-case\target\classes started by lukuan in D:\idea_work_my\gitee\cook-frame)
${sys:applicationName} 2023-09-18 10:18:34  INFO ServerCaseApplication:648 - No active profile set, falling back to default profiles: default
${sys:applicationName} 2023-09-18 10:18:34  INFO TomcatWebServer:108 - Tomcat initialized with port(s): 7081 (http)
${sys:applicationName} 2023-09-18 10:18:34  INFO Http11NioProtocol:173 - Initializing ProtocolHandler ["http-nio-7081"]
${sys:applicationName} 2023-09-18 10:18:34  INFO StandardService:173 - Starting service [Tomcat]
${sys:applicationName} 2023-09-18 10:18:34  INFO StandardEngine:173 - Starting Servlet engine: [Apache Tomcat/9.0.46]
${sys:applicationName} 2023-09-18 10:18:34  INFO [/]:173 - Initializing Spring embedded WebApplicationContext
${sys:applicationName} 2023-09-18 10:18:34  INFO ServletWebServerApplicationContext:285 - Root WebApplicationContext: initialization completed in 795 ms_ _   |_  _ _|_. ___ _ |    _ 
| | |\/|_)(_| | |_\  |_)||_|_\ /               |         3.5.1 
service-case 2023-09-18 10:18:35  INFO ThreadPoolTaskExecutor:181 - Initializing ExecutorService 'applicationTaskExecutor'
service-case 2023-09-18 10:18:35  INFO PropertySourcedRequestMappingHandlerMapping:69 - Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
service-case 2023-09-18 10:18:35  INFO Http11NioProtocol:173 - Starting ProtocolHandler ["http-nio-7081"]
service-case 2023-09-18 10:18:35  INFO TomcatWebServer:220 - Tomcat started on port(s): 7081 (http) with context path ''

可以看到比较靠前的日志输出中的applicationName变量是没有被替换成真正的服务名的。

那怎么办呢?

分析

所以我们要从SpringBoot的启动过程入手

SpringApplication#run(String… args)
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();prepareContext(context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}listeners.started(context);callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;
}

突破点在environment的创建这步骤,所以分析prepareEnvironment(listeners, applicationArguments)看能不能实现我们想要的需求

SpringApplication#prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {// Create and configure the environmentConfigurableEnvironment environment = getOrCreateEnvironment();configureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);//经过debug发现当这步方法执行完后,environment.getProperty("spring.application.name")是可以获取值listeners.environmentPrepared(environment);bindToSpringApplication(environment);if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);return environment;
}

listeners.environmentPrepared(environment);为关键点,environment.getProperty("spring.application.name")就是在此进行完成的,所以我们要进入分析。

SpringApplicationRunListeners#environmentPrepared
void environmentPrepared(ConfigurableEnvironment environment) {for (SpringApplicationRunListener listener : this.listeners) {listener.environmentPrepared(environment);}
}

listeners有一个实现类EventPublishingRunListener

public void environmentPrepared(ConfigurableEnvironment environment) {this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

这里是发布了一个ApplicationEnvironmentPreparedEvent事件。

既然有发布,就有监听。我们接下来分析下监听事件的逻辑

ConfigFileApplicationListener#onApplicationEvent
public void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationEnvironmentPreparedEvent) {onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);}if (event instanceof ApplicationPreparedEvent) {onApplicationPreparedEvent(event);}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();postProcessors.add(this);AnnotationAwareOrderComparator.sort(postProcessors);for (EnvironmentPostProcessor postProcessor : postProcessors) {postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());}
}

postProcessors有多个,其中ConfigFileApplicationListener就是设置spring.applicaton.name的值。至于怎么设置的,就不进去分析了,有兴趣的同学可以自行去仔细研究。

通常上述分析,我们知道了Environment设置spring.applicaton.name值的步骤,那么我们可不可以紧接着,这个设置步骤之后就进行自定义设值,然后让log4j2来取呢?

答案是当然可以!

让我们回到SpringApplicationRunListeners#environmentPrepared

SpringApplicationRunListeners#environmentPrepared
void environmentPrepared(ConfigurableEnvironment environment) {for (SpringApplicationRunListener listener : this.listeners) {listener.environmentPrepared(environment);}
}

默认listeners为一个,那我们只要自己实现一个,让listeners为2个,第一个默认执行完后,不就能紧接着执行我们的了吗,那我们分析下listeners是怎么来的

SpringApplication#getRunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = getClassLoader();// Use names and ensure unique to protect against duplicatesSet<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;
}

SpringFactoriesLoader.loadFactoryNames(type, classLoader)和自动装配的方法很像啊,那去看一眼

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

果然在文件中指定了org.springframework.boot.context.event.EventPublishingRunListener,也就是默认的SpringApplicationRunListener实现类。

然后在分析createSpringFactoriesInstances看看是如何加载的

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,ClassLoader classLoader, Object[] args, Set<String> names) {List<T> instances = new ArrayList<>(names.size());for (String name : names) {try {Class<?> instanceClass = ClassUtils.forName(name, classLoader);Assert.isAssignable(type, instanceClass);Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);T instance = (T) BeanUtils.instantiateClass(constructor, args);instances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}return instances;
}

这里就是创建对象的过程了,用的是有参构造方法需要两个参数constructor, args,所以我们参考EventPublishingRunListener的结构来实现即可

实现

自定义SpringApplicationRunListener的实现类CustomEventPublishingRunListener

CustomEventPublishingRunListener
public class CustomEventPublishingRunListener implements SpringApplicationRunListener, Ordered {private static final String SPRING_APPLICATION_NAME = "spring.application.name";private final SpringApplication application;private final String[] args;public CustomEventPublishingRunListener(SpringApplication application, String[] args){this.application = application;this.args = args;}@Overridepublic int getOrder() {return 1;}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {String applicationName = environment.getProperty(SPRING_APPLICATION_NAME);if (StringUtil.isNotEmpty(applicationName)) {System.setProperty("applicationName", applicationName);}}
}
  • 有参构造方法中的SpringApplication application, String[] args参数为固定的
  • getOrder返回的值要为1,因为EventPublishingRunListener中的getOrder返回值为0

在自动装配文件spring.factories指定位置

org.springframework.boot.SpringApplicationRunListener=\com.example.runlistener.CustomEventPublishingRunListener

在log4j2的xml文件中进行取值

<!--输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT"><!-- 输出日志的格式 --><PatternLayout pattern="${sys:applicationName} %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n"/>
</Console>

输出结果

service-case 2023-09-18 14:57:25  INFO ServerCaseApplication:55 - Starting ServerCaseApplication on lukuan with PID 20004 (D:\idea_work_my\gitee\cook-frame\server\server-case\target\classes started by lukuan in D:\idea_work_my\gitee\cook-frame)
service-case 2023-09-18 14:57:25  INFO ServerCaseApplication:648 - No active profile set, falling back to default profiles: default
service-case 2023-09-18 14:57:25  INFO TomcatWebServer:108 - Tomcat initialized with port(s): 7081 (http)
service-case 2023-09-18 14:57:25  INFO Http11NioProtocol:173 - Initializing ProtocolHandler ["http-nio-7081"]
service-case 2023-09-18 14:57:25  INFO StandardService:173 - Starting service [Tomcat]
service-case 2023-09-18 14:57:25  INFO StandardEngine:173 - Starting Servlet engine: [Apache Tomcat/9.0.46]
service-case 2023-09-18 14:57:25  INFO [/]:173 - Initializing Spring embedded WebApplicationContext
service-case 2023-09-18 14:57:25  INFO ServletWebServerApplicationContext:285 - Root WebApplicationContext: initialization completed in 716 ms_ _   |_  _ _|_. ___ _ |    _ 
| | |\/|_)(_| | |_\  |_)||_|_\ /               |         3.5.1 
service-case 2023-09-18 14:57:26  INFO ThreadPoolTaskExecutor:181 - Initializing ExecutorService 'applicationTaskExecutor'
service-case 2023-09-18 14:57:26  INFO PropertySourcedRequestMappingHandlerMapping:69 - Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
service-case 2023-09-18 14:57:26  INFO Http11NioProtocol:173 - Starting ProtocolHandler ["http-nio-7081"]
service-case 2023-09-18 14:57:26  INFO TomcatWebServer:220 - Tomcat started on port(s): 7081 (http) with context path ''
....

可以看到实现了我们想要的功能

相关文章:

log4j2或者logback配置模版实现灵活输出服务名

介绍 在我们使用log4j2或者logback打印日志时&#xff0c;输出的内容中通常是一定要加上服务名的。以log4j2为例&#xff1a; <!--输出控制台的配置--> <Console name"Console" target"SYSTEM_OUT"><!-- 输出日志的格式 --><Patter…...

使用HTTP爬虫ip中的常见误区与解决方法

在如今的互联网时代&#xff0c;为了保障个人隐私和实现匿名浏览&#xff0c;许多人选择使用HTTP爬虫ip。然而&#xff0c;由于缺乏了解和使用经验&#xff0c;常常会出现一些误区。本文将为大家介绍使用HTTP爬虫ip过程中常见的误区&#xff0c;并提供相应的解决方法&#xff0…...

MySQL学习笔记3

MySQL的源码编译安装&#xff1a; 1、参考MySQL的源码安装官方文档&#xff1a; 2、源码安装定制选项&#xff1a; 3、源码安装三部曲&#xff1a;配置、编译、安装。 4、软件安装包&#xff1a; mysql-boost-5.7.43.tar.gz 5、安装需求&#xff1a; 安装需求具体配置安装目…...

快速掌握ES6

什么是ES6 ES6&#xff08;ECMAScript 6&#xff09;&#xff0c;也被称为ES2015&#xff0c;是JavaScript的第六个版本&#xff0c;于2015年发布。ES6引入了许多新的语法和功能&#xff0c;旨在提高JavaScript的开发效率和代码质量。 ES6的一些主要特性和改进包括&#xff1…...

电池厂提供excel电池曲线zcv到mtk电池曲线zcv转换

#encoding:utf8 #电池厂提供excel电池曲线zcv到mtk电池曲线zcv转换 import pandas as pd import openpyxl import math # 读取Excel文件 df pd.read_excel("a55-zcv.xlsx") for j in range(0,10): if(j<3): offset0 #T0~T2 if(j3): offset…...

重写和重载、抽象类和接口

文章目录 前言一、重载与重写1.重载&#xff08;Overload&#xff09;&#xff08;1&#xff09;条件&#xff08;2&#xff09;举例 2.重写&#xff08;Override)&#xff08;1&#xff09;规则&#xff08;2&#xff09;举例 3.重载和重写区别 二、抽象类与接口1.抽象类&…...

Untiy UDP局域网 异步发送图片

同步画面有问题&#xff0c;传图片吧 using System.Text; using System.Net.Sockets; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using System.Net; using System; using System.Threading.Tasks; using Sy…...

移动端H5封装一个 ScrollList 横向滚动列表组件,实现向左滑动

效果&#xff1a; 1.封装组件&#xff1a; <template><div class"scroll-list"><divclass"scroll-list-content":style"{ background, color, fontSize: size }"ref"scrollListContent"><div class"scroll…...

Docker一键安装和基本配置

一键安装脚本 注&#xff1a;该脚本需要root权限 curl -sSL https://get.docker.com/ | sh非root组用户赋权 sudo groupadd docker # 若使用一键安装脚本会自动创建这个组&#xff0c;提示已存在 sudo gpasswd -a ${USER} docker # 将当前用户添加到docker组&#xff0c;也…...

MVC设计思想理解和ASP.NET MVC理解

三层模式 三层模式包括:UI层,业务逻辑层,数据访问层,模型层 MVC设计思想和ASP.NET MVC理解 MVC设计思想: MVC的思想就是把我们的程序分为三个核心的模块,这三个模块的详细介绍如下: 模型(Model) :负责封装与引用程序的业务逻辑相关的数据以及对数据的处理方法。模型层有对…...

大模型应用选择对比

大模型应用选择对比 1、知识库对比&#xff1a;dify、fastgpt、langchatchat 2、agent构建器选择&#xff1a;flowise、langflow、bisheng 3、召回率提升方案...

c++STL概述

目录 STL基本概念 STL六大组件 STL的优点 STL三大组件 容器 算法 迭代器 普通的迭代器访问vector容器元素 算法for_each实现循环 迭代器指向的元素类型是自定义数据类型 迭代器指向容器 常用容器 string容器 string的基本概念 string容器的操作 string的构造函…...

利用容器技术优化DevOps流程

利用容器技术优化DevOps流程 随着云计算的快速发展&#xff0c;容器技术也日益流行。容器技术可以打包和分发应用程序&#xff0c;并实现快速部署和扩展。在DevOps流程中&#xff0c;容器技术可以大大优化开发、测试、部署和运维各个环节。本文将介绍如何利用容器技术优化DevO…...

91 # 实现 express 的优化处理

上一节实现 express 的请求处理&#xff0c;这一节来进行实现 express 的优化处理 让 layer 提供 match 方法去匹配 pathname&#xff0c;方便拓展让 layer 提供 handle_request 方法&#xff0c;方便拓展利用第三方库 methods 批量生成方法性能优化问题 进行路由懒加载&#…...

arcgis拓扑检查实现多个矢量数据之间消除重叠区域

目录 环境介绍&#xff1a; 操作任务&#xff1a; 步骤&#xff1a; 1、数据库和文件结构准备 2、建立拓扑规则 3、一直下一页默认参数后&#xff0c;进行拓扑检查 4、打开TP_CK_Topology&#xff0c;会自动带出拓扑要素&#xff0c;红色区域为拓扑错误的地方&#xff1…...

基于Vue+ELement搭建登陆注册页面实现后端交互

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《ELement》。&#x1f3af;&#x1f3af; &#x1…...

JS获取经纬度, 并根据经纬度得到城市信息

在JavaScript中&#xff0c;获取经纬度通常需要使用定位服务&#xff0c;比如HTML5的Geolocation API。然而拿到坐标后&#xff0c;将经纬度转换为城市信息&#xff0c;则需要使用逆地理编码服务接口&#xff0c;比如百度或者高德的 API, 但是他们收费都很高, 我们可以使用一些…...

mac m1 docker安装nacos

文章目录 引言I m1安装docker1.1 Docker 下载1.2 终端Docker相关命令II docker安装nacos2.1 安装nacos2.2 镜像启动see alsoMac 查看进程端口引言 使用docker方式安装是最方便的 I m1安装docker 1.1 Docker 下载 https://docs.docker.com/docker-for-mac/apple-silicon/点击…...

位段 联合体 枚举

Hello好久不见&#xff0c;今天分享的是接上次结构体没有分享完的内容&#xff0c;这次我们讲讲位段 枚举和联合体的概念以及他们的用法。 2.1 什么是位段 位段的声明和结构是类似的&#xff0c;有两个不同&#xff1a; 1.位段的成员必须是 int、unsigned int 或signed int 。 …...

PHP循环获取Excel表头字母A-Z,当超过时输出AA,AB,AC,AD······

PHP循环获取Excel表头字母A-Z&#xff0c;当超过时输出AA,AB,AC,AD PHP循环生成Excel的列字母表 $count_num 26 * 27; $letter A; $arr []; while($count_num--){$arr[] $letter;$letter; }结果如下&#xff1a; 转为JSON更为直观&#xff1a; ["A","B&…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

AI语音助手的Python实现

引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...

MySQL的pymysql操作

本章是MySQL的最后一章&#xff0c;MySQL到此完结&#xff0c;下一站Hadoop&#xff01;&#xff01;&#xff01; 这章很简单&#xff0c;完整代码在最后&#xff0c;详细讲解之前python课程里面也有&#xff0c;感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...