Featured image of post 让你的小智AI机器人起飞

让你的小智AI机器人起飞

前阵子B站刷到小智AI机器人,入手了一个玩玩,官方的服务器显然不足以满足我的幻想,于是搭建了第三方服务器,顺便给它功能扩展了一下,到底做了点啥呢,似乎有一点意思了。

上手初体验

这个机器人似乎前阵子比较火,有不少硬件形态,有比较小巧可爱的,也有硬核直接的,但内核是不变的,如果你从来没玩过,好像长什么样,可以B站看看或淘宝啥的搜索一下。我家的比较抽象,长得像这样: 小智AI机器人

跟着电路图或者引脚说明接线,这步不是有手就行,还得有力气:)因为ESP32的针脚和面包板孔不完全匹配,需要用力强行按进去。看过B站有个哥们用锤子敲,也真是乐坏我了。供电的话我们可以随便拿个充电宝,倒也是很容易搞定、也有一定移动性。

最开始体验时,参考官方的DIY教程将设备刷好官方固件后,整体响应较快,对话流畅,不过模型数量有限,声音复刻收费,特别是如果这个不能联网做点啥,光和模型过时的数据聊天略有点无聊啊。结合当前较火的MCP功能,于是有了两个扩展目标:

  • 复刻个人的声音让AI用我的语气说话
  • 扩展MCP功能,让AI具备连接更多外界能力

消耗了一点闲暇时间,目前已经跑起来咯,下面简要分享一下过程。如果你也想折腾或者有好点子,欢迎一起交流。

部署专属服务器

小智官方并没有把服务器开源,但提供了交互协议等,有第三方开源服务器实现 xiaozhi-esp32-server,可供使用。要完成可以使用第三方服务器,我们做两件事:

  1. 将服务部署到自己的机器上。
  2. 修改小智的固件的两个地址,重新编译并烧录到设备上。

其实都很简单,我们需要动脑的机会不多,像我这样一步步来即可。

部署服务器

参考xiaozhi-esp32-server的Docker运行全模块教程。我推荐使用docker部署,主要是为了保持本地的干净。

准备配置文件.config.yaml,在这个服务器中,使用了多层config来互相覆盖,我们定义的.config.yaml会覆盖默认的config.yaml。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 在开发中,请在项目根目录创建data目录,然后在data目录创建名称为【.config.yaml】的空文件
# 然后你想修改覆盖修改什么配置,就修改【.config.yaml】文件,而不是修改【config.yaml】文件
# 系统会优先读取【data/.config.yaml】文件的配置,如果【.config.yaml】文件里的配置不存在,系统会自动去读取【config.yaml】文件的配置。
# 这样做,可以最简化配置,保护您的密钥安全。
read_config_from_api: True

manager-api:
  # 你的manager-api的地址,最好使用局域网ip
  url: http://xiaozhi-esp32-server-web:8002/xiaozhi
  # 你的manager-api的token,就是刚才复制出来的server.secret
  secret: 8e0ee7db-3928-400f-9e93-261275f91cf0

# #####################################################################################
# #############################以下是服务器基本运行配置####################################
server:
  # 服务器监听地址和端口(Server listening address and port)
  ip: 0.0.0.0
  port: 8000

这里的配置不用太多,因为它的配置还有一层可以动态通过manager-api来下发更新,所以这里我们只需要配置一个manager-api的地址和secret即可。

然后我们使用docker-compose部署,在此之前你需要自行下载语音识别模型文件到models/SenseVoiceSmall/model.pt,这些细节上面链接都有提,不啰嗦了,最终yaml如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# Docker安装全模块
services:
  # Server模块
  xiaozhi-esp32-server:
    image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
    container_name: xiaozhi-esp32-server
    depends_on:
      - xiaozhi-esp32-server-db
      - xiaozhi-esp32-server-redis
    restart: always
    networks:
      - default
    ports:
      # ws服务端
      - "8000:8000"
    security_opt:
      - seccomp:unconfined
    environment:
      - TZ=Asia/Shanghai
    volumes:
      # 配置文件目录
      - ./data:/opt/xiaozhi-esp32-server/data
      # 模型文件挂接,很重要
      - ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt

  # manager-api和manager-web模块
  xiaozhi-esp32-server-web:
    image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
    container_name: xiaozhi-esp32-server-web
    restart: always
    networks:
      - default
    depends_on:
      xiaozhi-esp32-server-db:
        condition: service_healthy
      xiaozhi-esp32-server-redis:
        condition: service_healthy
    ports:
      # 智控台
      - "8002:8002"
    environment:
      - TZ=Asia/Shanghai
      - SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://xiaozhi-esp32-server-db:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&connectTimeout=30000&socketTimeout=30000&autoReconnect=true&failOverReadOnly=false&maxReconnects=10
      - SPRING_DATASOURCE_DRUID_USERNAME=root
      - SPRING_DATASOURCE_DRUID_PASSWORD=123456
      - SPRING_DATA_REDIS_HOST=xiaozhi-esp32-server-redis
      - SPRING_DATA_REDIS_PORT=6379
  xiaozhi-esp32-server-db:
    # image: mysql/mysql-server:latest
    image: mysql:latest
    container_name: xiaozhi-esp32-server-db
    healthcheck:
      test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
      timeout: 45s
      interval: 10s
      retries: 10
    restart: always
    networks:
      - default
    ports:
      - "3306:3306"
    volumes:
      - ./mysql/data:/var/lib/mysql
    environment:
      - TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=123456
      - MYSQL_DATABASE=xiaozhi_esp32_server
      - MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci"
  xiaozhi-esp32-server-redis:
    image: redis
    expose:
      - 6379
    container_name: xiaozhi-esp32-server-redis
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - default
networks:
  default:

使用命令启动:

1
docker-compose up -d

等服务起来了之后,我们可以使用仓库自带的test页面测试,浏览器打开main/xiaozhi-server/test/test_page.html,可以看到如下页面: 测试页面 你最好是在这个网页测试通过,聊天、语音等都符合你的预期,再进行下一步。话说这个页面也是调试利器了,效率高不少。

这里还有个小技巧,因为我们接下来需要将ota/websocket地址烧录在固件上,如果你不确定服务未来会部署在哪台机器上,可以通过一个反向代理来转发。给个示意了解一下即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: to-xiaozhi-web
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`web-xiaozhi.mrlin.cc`)
      kind: Rule
      services:
        - name: web-xiaozhi
          port: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web-xiaozhi
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8002
---
apiVersion: v1
kind: Endpoints
metadata:
  name: web-xiaozhi
subsets:
  - addresses:
      - ip: 192.168.50.190
    ports:
      - port: 8002
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: to-xiaozhi-api
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`api-xiaozhi.mrlin.cc`)
      kind: Rule
      services:
        - name: api-xiaozhi
          port: 80
---
apiVersion: v1
kind: Service
metadata:
  name: api-xiaozhi
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8000
---
apiVersion: v1
kind: Endpoints
metadata:
  name: api-xiaozhi
subsets:
  - addresses:
      - ip: 192.168.50.190
    ports:
      - port: 8000

上面我将api-xiaozhiweb-xiaozhi都指向了我的本地IP,这样我就可以通过api-xiaozhi.mrlin.ccweb-xiaozhi.mrlin.cc来访问了,未来迁移到其他机器上,只需要修改这个yaml文件即可。你不搞这一步一点问题也没有,继续用IP,注意那样就使用http/ws协议。

修改固件&编译&烧录

这里我们要进入到官方固件仓库来继续我们的操作。并且编译烧录等需要你在Windows下进行了。 我们可以参考这篇文章Windows搭建 ESP IDF 5.3.3开发环境以及编译小智大概了解整个过程;接着看这里简要而关键的固件构建过程。

简单来说,当前最新版本我们仅需要修改OTA_URL地址即可,以往要修改websocket地址,现在它已经通过OTA下发了。

当天我正在照着教程编译,发现不找到websocket的定义了,定睛一看刚好有提交重构了这一块,于是顺便向server那边也提了个PR,居然很快就被合并了。不过当时字段名取得不太好,我延用了固件那边的websocket_url,后面又被修改为server.websocket,这确实更符合项目的命名规范一些。

之后构建就几个命令:

1
2
3
4
5
6
# 终端命令行进入xiaozhi-esp32的根目录
cd xiaozhi-esp32
# 例如我使用的板子是esp32s3,所以设置编译目标为esp32s3,如果你的板子是其他型号,请替换成对应的型号
idf.py set-target esp32s3
# 进入菜单配置
idf.py menuconfig

之后是烧录,将你的小智AI设备通过串口连接到电脑,然后使用idf.py build构建,接着使用idf.py -p PORT flash烧录。 这里的PORT根据你连接的设备不同,可能有所不同,在电脑中我的设备查看串口,我的是COM4。如果嫌烧录慢,可以添加参数-b 2000000来加速。

一切成功后,同时也在上一步骤的服务器的server.websocket中填入wss://api-xiaozhi.mrlin.cc/xiaozhi/v1/,然后就可以打开设备连接自己的服务器开玩了。 server websocket修改 现在你可能发现有了更多的LLM选择,有了更多的TTS(声音)等,似乎是更开放了,或许可以玩得更嗨了。

个人声音复刻

之前在玩火山引擎时,送了几次声音复刻给我,体验了一下,当我想用在小智上时,发现已经过期了,居然只有十天有效期,这也太抠了。我去腾讯云找了一下,也有免费的额度,申请下来有3个月,那么就玩玩吧。不像火山引擎的声音复刻在网页上提交声音即可,腾讯云的复刻要原始得多,需要自己使用API,所幸相关功能也在它的tccli中,那么也就是敲命令的事,也不可能难倒我们。

命令行体验

我们可以参考文档声音复刻相关接口,使用命令行来把过程体验一下。至于如何安装tccli,可以参考腾讯云命令行工具

1. 获取训练文本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ tccli vrs GetTrainingText --TaskType 5 --Domain 1
{
    "Data": {
        "TrainingTextList": [
            {
                "TextId": "5f3ef960-4f11-4f4b-84e6-514692ebe0c5",
                "Text": "亲 默认是发韵达快递哦 您这边可以收到韵达的货吗 "
            }
        ]
    },
    "RequestId": "8c758bac-19fc-474d-9b12-2e813e834102"
}

这一步要我们根据提供的文本来提供一段音频,我盲猜是不是要规避用别人的声音来复刻呢?

2. 环境检测和音频质量检测

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ tccli vrs DetectEnvAndSoundQuality --cli-unfold-argument --TextId "5f3ef960-4f11-4f4b-84e6-514692ebe0c5" --AudioData "$(base64 -i vrs.m4a | tr -d '\n')" --TypeId 2 --Codec m4a --TaskType 5


{
    "Data": {
        "AudioId": "audio0de472d6d96d488ba2159d83c10b440f",
        "DetectionCode": 0,
        "DetectionMsg": "Success",
        "DetectionTip": []
    },
    "RequestId": "6218aa6d-9d45-492c-9c18-dfa342d3bd5e"
}

这一步看名称感觉多余,但是下一步创建任务需要这个AudioId,所以还是得来一下。

3. 创建声音复刻任务

1
2
3
4
5
6
7
❯ tccli vrs CreateVRSTask --SessionId 1234 --VoiceName kevin --VoiceGender 1 --VoiceLanguage 1 --AudioIdList "[\"audio0de472d6d96d488ba2159d83c10b440f\"]"  --TaskType 5
{
    "Data": {
        "TaskId": "fast7feffdeb04ca401583a317002dd14e7a"
    },
    "RequestId": "127ad8c6-a27d-470f-8dab-b9940c9594cb"
}

我们得到了一个任务ID。原因或许是这一步是异步的,服务器训练这个声音也需要时间。

4. 查询声音复刻任务结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
❯ tccli vrs DescribeVRSTaskStatus --TaskId fast7feffdeb04ca401583a317002dd14e7a
{
    "Data": {
        "TaskId": "fast7feffdeb04ca401583a317002dd14e7a",
        "Status": 2,
        "StatusStr": "Success",
        "VoiceType": 200000000,
        "ErrorMsg": "",
        "ExpireTime": "2025-08-02T00:00:00+08:00",
        "FastVoiceType": "WCHN-869b22949cbe4db6a344f1da8cf8a68c"
    },
    "RequestId": "3eb52b2d-3fa1-42f5-84f0-abf71bef32e2"
}

查到的结果里有几个比较关键的,VoiceTypeFastVoiceType,前者是音色ID,后者是快速复刻时使用的音色ID,请用小本本记录下来备用。

5. 查询复刻音色信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ tccli vrs GetVRSVoiceTypeInfo --VoiceType 200000000 --TaskType 5 --FastVoiceType WCHN-869b22949cbe4db6a344f1da8cf8a68c
{
    "Data": {
        "VoiceType": 200000000,
        "VoiceName": "kevin",
        "VoiceGender": 1,
        "TaskType": 5,
        "TaskID": "fast7feffdeb04ca401583a317002dd14e7a",
        "DateCreated": "2025-05-03T15:17:35+08:00",
        "IsDeployed": true, 
        "ExpireTime": "2025-08-02T00:00:00+08:00",
        "FastVoiceType": "WCHN-869b22949cbe4db6a344f1da8cf8a68c"
    },
    "RequestId": "db5709d2-4e2c-432d-8bd3-ed2eddcb6c21"
}

这一步可能真没啥用了,我们要的信息前面也有了,说有用的话,你大概知道这是一个男人名叫kevin的声音。

6. 使用复刻声音来合成语音

1
2
3
4
5
❯ tccli tts TextToVoice \
--Text "我今天带娃在meland玩,这家伙玩疯了呢" \
--SessionId 123 \
--VoiceType 200000000 \
--FastVoiceType WCHN-869b22949cbe4db6a344f1da8cf8a68c > result.json

返回的内容包括base64后的音频数据,我们使用jq来提取并保存到文件:

1
jq -r '.Audio' result.json | base64 -d > tts.wav

播放来听听吧,感受一下自己声音的魔力或者惊吓。

小智server支持使用复刻声音

当我使用复刻声音时,发现小智server并不支持,于是我给它添加了支持,并提交了PRfeat: 支持腾讯TTS声音一句话复刻后的合成,这次没有很快被合并,因为官方说某个字段要废弃了,又没说最新用什么方式,明儿就就腾讯云提工单。

如PR的修改所见,仅是增加了上述fast_voice_type参数的传递即可。不管上游合不合修改,反正我本地已经用上了。有个小发现,这里server中处理不同语音合成模型的参数,UI是基于fields的字段及其定义动态生成展示的,这还不错。顺手update一个DB,对应的UI就有新字段出来了。

1
2
3
4
UPDATE `ai_model_provider`
SET `fields` = '[{"key":"appid","label":"应用ID","type":"string"},{"key":"secret_id","label":"Secret ID","type":"string"},{"key":"secret_key","label":"Secret Key","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"region","label":"区域","type":"string"},{"key":"voice","label":"音色ID","type":"string"},{"key":"fast_voice_type","label":"FastVoiceType","type":"string"}]',
    `update_date` = NOW()
WHERE id = 'SYSTEM_TTS_TencentTTS';

不知道当你发现你自己在和自己说话时是什么感受,反正我挺惊奇的,感觉自己像个神经病。明儿我还是复刻一个我孩子的声音吧,我就可以随时亲子教育了:D

MCP扩展

在这个第三方服务器的实现中,它支持了一部分MCP的能力,也即stdio模式的mcp server。这会因为使用容器的模式,限制了它的使用。容器内可没有npx/uvx啥的,更没有chrome了。我就一个小小愿望,问它谁是最美丽的人,不对,问它昨天曼联输了个几比几,它不能实时联网找到正确答案我就不满意了。

同样的,让我们扩展它,只需要几行代码的修改就可以啦,详见PRfeat: MCP server支持使用sse模式。一般来说宿主机上使用npx/uvx/chrome等,容器内我们通过http协议使用mcp-server还是更简单的。可是有一些mcp server人家就没实现sse咋办?一点都不困难,比如我们借助supergateway就可以让几个模式互转。

使用supergateway

Supergateway runs MCP stdio-based servers over SSE (Server-Sent Events) or WebSockets (WS) with one command. This is useful for remote access, debugging, or connecting to clients when your MCP server only supports stdio.

比如我有这样一个perplexity-ask的mcp server,它只支持stdio模式,那么我就可以使用supergateway来让它支持sse模式:

1
2
3
4
5
6
7
    "perplexity-ask": {
      "command": "/opt/homebrew/bin/node",
      "args": ["/Users/kevinlin/Source/modelcontextprotocol/perplexity-ask/dist/index.js"],
      "env": {
        "PERPLEXITY_API_KEY": "pplx-..."
      }
    }

使用supergateway来启动:

1
2
3
4
export PERPLEXITY_API_KEY=pplx-... && 
npx -y supergateway \
  --stdio "/opt/homebrew/bin/node .../perplexity-ask/dist/index.js" \
  --port 8123 

于是我们给小智server的mcp config就可以这么简单了:

1
2
3
4
5
{
  "perplexity-ask": {
    "url": "http://your-ip:8123"
  }
}

现在你的小智就可以更好的使用MCP server了,我试着问了一下,它说曼联最近欧联13场不败!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
        "role": "assistant",
        "tool_calls": [
            {
                "id": "call_202505041955402e0c441c996740fb_0",
                "function": {
                    "arguments": "{\"messages\": [{\"role\": \"user\", \"content\": \"曼联过去几场欧联杯比赛的对手和结果?\"}]}",
                    "name": "perplexity_ask"
                },
                "type": "function",
                "index": 0
            }
        ]
    },
    {
        "role": "tool",
        "tool_call_id": "call_202505041955402e0c441c996740fb_0",
        "content": "曼联在2024-2025赛季欧联杯中表现出色,以下是他们最近几场欧联杯比赛的对手和结果:\n\n最近的欧联杯比赛,曼联在半决赛首回合客场3-0完胜西甲第四名的毕尔巴鄂竞技[1][2]。这场比赛中,卡塞米罗为曼联首开纪录,随后布鲁诺·费尔南德斯梅开二度,全部进球都是在上半场完成的[2]。比赛中,毕尔巴鄂竞技的后卫丹尼尔·维维安被罚下场[2]。\n\n在四分之一决赛中,曼联对阵法国球队里昂。首回合比赛两队2-2战平[5]。次回合在老特拉福德球场进行的比赛异常激烈,经过常规时间和加时赛的鏖战,曼联最终以5-4战胜里昂,总比分7-6晋级半决赛[5]。在这场比赛中,达洛特建功,布鲁诺·费尔南德斯点射破门,最后时刻马奎尔上演读秒绝杀[5]。\n\n值得一提的是,曼联本赛季在欧联杯中保持着13场不败的强势表现[1],与他们在英超联赛中的低迷形成了鲜明对比。新帅阿莫林有望复制穆里尼奥在2016-17赛季率领曼联首个赛季就夺得欧联杯冠军的成就[2]。\n\n曼联已经一只脚踏入欧联杯决赛,决赛将在本月下旬在毕尔巴鄂的圣马梅斯球场举行[2]。\n\nCitations:\n[1] https://www.163.com/dy/article/JUKFHV4C0530UTM3.html\n[2] https://www.163.com/dy/article/JUHUQC8L05567SBP.html\n[3] https://zh.wikipedia.org/zh-cn/%E6%9B%BC%E8%81%AF2024%E5%B9%B4%E8%87%B32025%E5%B9%B4%E7%90%83%E5%AD%A3\n[4] https://m.dongqiudi.com/article/5071872.html\n[5] https://news.sina.com.cn/zx/2025-04-18/doc-inetppfi2898601.shtml\n"
    },
    {
        "role": "assistant",
        "content": "曼联在2024-2025赛季欧联杯中的表现非常出色。以下是他们最近几场欧联杯比赛的对手和结果:\n\n- 在半决赛首回合,曼联客场以3-0完胜西甲第四名的毕尔巴鄂竞技。卡塞米罗为曼联首开纪录,随后布鲁诺·费尔南德斯梅开二度,全部进球都是在上半场完成的。\n- 在四分之一决赛中,曼联对阵法国球队里昂。首回合比赛两队2-2战平。次回合在老特拉福德球场进行的比赛异常激烈,经过常规时间和加时赛的鏖战,曼联最终以5-4战胜里昂,总比分7-6晋级半决赛。\n\n曼联在本赛季欧联杯中保持着13场不败的强势表现,与他们在英超联赛中的低迷形成了鲜明对比。他们已经一只脚踏入欧联杯决赛,决赛将在本月下旬在毕尔巴鄂的圣马梅斯球场举行。"
    }

使用云函数包装mcp server

像上面这样,它需要依赖机器安装相关的服务,换一台机器就歇菜了,并且为了转换为sse还是比较麻烦。前阵子在试用阿里云百炼平台时,发现有个有意思的扩展,对于它不支持的mcp server,可以通过云函数来自定义扩展它。我想何不借助于它来包装一下mcp server,这样就可以在任何地方使用它了。

我们可以打开https://cap.console.aliyun.com/explore?lang=MCP+Server,这里以高德地图为例,我们只要在云端部署它之后。 使用云函数部署MCP server 本地只需要一个url(填上面公网访问地址)即可使用这个mcp server了,它自动提供了sse接口。至此,你居然就将mcp server “上云”了:)审计和日志也一应俱全呢。现在你可以试一下再和小智聊天,对于地图信息,它比我们还清楚了。小声的说,地图API都提供了天气查询能力,咱也不用再找一个天气服务了呢。

尾声

这个机器人应该还有不少待开发的东西,无论软件还是硬件上。比如如果能再添加几个外设,预期会更有意思了。它本身也支持如Home Assistant等智能家居平台,可以实现如开关灯、开关窗帘等操作。 技术上也有不少地方可以进一步聊聊,比如MCP的使用姿势问题等,以及如何进一步提升对话的响应速度等。 记录一张图留念,本想放视频的,第一不好录制,第二声音这东西,你懂的。

记录一张图留念

考虑到篇幅问题,今天就到这里吧。如果你对某些地方感兴趣,欢迎留言讨论。如果你没什么想说的,点赞、分享、关注,都是对我最好的鼓励。我努力创作有价值的内容,期待未来与你再会。

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:

扫码关注公众号