go 集成nacos注册中心、配置中心
使用限制
Go>=v1.15
Nacos>2.x
安装
使用go get安装SDK:
go get -u github.com/nacos-group/nacos-sdk-go/v2
快速使用
- 初始化客户端配置ClientConfig
constant.ClientConfig{TimeoutMs uint64 // 请求Nacos服务端的超时时间,默认是10000msNamespaceId string // Nacos的命名空间IdEndpoint string // 当使用地址服务器时,需要该配置. https://help.aliyun.com/document_detail/130146.htmlRegionId string // Nacos&KMS的regionId,用于配置中心的鉴权AccessKey string // Nacos&KMS的AccessKey,用于配置中心的鉴权SecretKey string // Nacos&KMS的SecretKey,用于配置中心的鉴权OpenKMS bool // 是否开启kms,默认不开启,kms可以参考文档 https://help.aliyun.com/product/28933.html// 同时DataId必须以"cipher-"作为前缀才会启动加解密逻辑CacheDir string // 缓存service信息的目录,默认是当前运行目录UpdateThreadNum int // 监听service变化的并发数,默认20NotLoadCacheAtStart bool // 在启动的时候不读取缓存在CacheDir的service信息UpdateCacheWhenEmpty bool // 当service返回的实例列表为空时,不更新缓存,用于推空保护Username string // Nacos服务端的API鉴权UsernamePassword string // Nacos服务端的API鉴权PasswordLogDir string // 日志存储路径RotateTime string // 日志轮转周期,比如:30m, 1h, 24h, 默认是24hMaxAge int64 // 日志最大文件数,默认3LogLevel string // 日志默认级别,值必须是:debug,info,warn,error,默认值是info
}
- ServerConfig
constant.ServerConfig{ContextPath string // Nacos的ContextPath,默认/nacos,在2.0中不需要设置IpAddr string // Nacos的服务地址Port uint64 // Nacos的服务端口Scheme string // Nacos的服务地址前缀,默认http,在2.0中不需要设置GrpcPort uint64 // Nacos的 grpc 服务端口, 默认为 服务端口+1000, 不是必填
}
Note:我们可以配置多个ServerConfig,客户端会对这些服务端做轮询请求
Create client
// 创建clientConfig
clientConfig := constant.ClientConfig{NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468", // 如果需要支持多namespace,我们可以创建多个client,它们有不同的NamespaceId。当namespace是public时,此处填空字符串。TimeoutMs: 5000,NotLoadCacheAtStart: true,LogDir: "/tmp/nacos/log",CacheDir: "/tmp/nacos/cache",LogLevel: "debug",
}// 创建clientConfig的另一种方式
clientConfig := *constant.NewClientConfig(constant.WithNamespaceId("e525eafa-f7d7-4029-83d9-008937f9d468"), //当namespace是public时,此处填空字符串。constant.WithTimeoutMs(5000),constant.WithNotLoadCacheAtStart(true),constant.WithLogDir("/tmp/nacos/log"),constant.WithCacheDir("/tmp/nacos/cache"),constant.WithLogLevel("debug"),
)// 至少一个ServerConfig
serverConfigs := []constant.ServerConfig{{IpAddr: "console1.nacos.io",ContextPath: "/nacos",Port: 80,Scheme: "http",},{IpAddr: "console2.nacos.io",ContextPath: "/nacos",Port: 80,Scheme: "http",},
}// 创建serverConfig的另一种方式
serverConfigs := []constant.ServerConfig{*constant.NewServerConfig("console1.nacos.io",80,constant.WithScheme("http"),constant.WithContextPath("/nacos"),),*constant.NewServerConfig("console2.nacos.io",80,constant.WithScheme("http"),constant.WithContextPath("/nacos"),),
}// 创建服务发现客户端
_, _ := clients.CreateNamingClient(map[string]interface{}{"serverConfigs": serverConfigs,"clientConfig": clientConfig,
})// 创建动态配置客户端
_, _ := clients.CreateConfigClient(map[string]interface{}{"serverConfigs": serverConfigs,"clientConfig": clientConfig,
})// 创建服务发现客户端的另一种方式 (推荐)
namingClient, err := clients.NewNamingClient(vo.NacosClientParam{ClientConfig: &clientConfig,ServerConfigs: serverConfigs,},
)// 创建动态配置客户端的另一种方式 (推荐)
configClient, err := clients.NewConfigClient(vo.NacosClientParam{ClientConfig: &clientConfig,ServerConfigs: serverConfigs,},
)
服务发现
- 注册实例:RegisterInstance
success, err := namingClient.RegisterInstance(vo.RegisterInstanceParam{Ip: "10.0.0.11",Port: 8848,ServiceName: "demo.go",Weight: 10,Enable: true,Healthy: true,Ephemeral: true,Metadata: map[string]string{"idc":"shanghai"},ClusterName: "cluster-a", // 默认值DEFAULTGroupName: "group-a", // 默认值DEFAULT_GROUP
})
- 注销实例:DeregisterInstance
success, err := namingClient.DeregisterInstance(vo.DeregisterInstanceParam{Ip: "10.0.0.11",Port: 8848,ServiceName: "demo.go",Ephemeral: true,Cluster: "cluster-a", // 默认值DEFAULTGroupName: "group-a", // 默认值DEFAULT_GROUP
})
- 获取服务信息:GetService
services, err := namingClient.GetService(vo.GetServiceParam{ServiceName: "demo.go",Clusters: []string{"cluster-a"}, // 默认值DEFAULTGroupName: "group-a", // 默认值DEFAULT_GROUP
})
- 获取所有的实例列表:SelectAllInstances
// SelectAllInstance可以返回全部实例列表,包括healthy=false,enable=false,weight<=0
instances, err := namingClient.SelectAllInstances(vo.SelectAllInstancesParam{ServiceName: "demo.go",GroupName: "group-a", // 默认值DEFAULT_GROUPClusters: []string{"cluster-a"}, // 默认值DEFAULT
})
- 获取实例列表 :SelectInstances
// SelectInstances 只返回满足这些条件的实例列表:healthy=${HealthyOnly},enable=true 和weight>0
instances, err := namingClient.SelectInstances(vo.SelectInstancesParam{ServiceName: "demo.go",GroupName: "group-a", // 默认值DEFAULT_GROUPClusters: []string{"cluster-a"}, // 默认值DEFAULTHealthyOnly: true,
})
- 获取一个健康的实例(加权随机轮询):SelectOneHealthyInstance
// SelectOneHealthyInstance将会按加权随机轮询的负载均衡策略返回一个健康的实例
// 实例必须满足的条件:health=true,enable=true and weight>0
instance, err := namingClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanceParam{ServiceName: "demo.go",GroupName: "group-a", // 默认值DEFAULT_GROUPClusters: []string{"cluster-a"}, // 默认值DEFAULT
})
- 监听服务变化:Subscribe
// Subscribe key=serviceName+groupName+cluster
// 注意:我们可以在相同的key添加多个SubscribeCallback.
err := namingClient.Subscribe(vo.SubscribeParam{ServiceName: "demo.go",GroupName: "group-a", // 默认值DEFAULT_GROUPClusters: []string{"cluster-a"}, // 默认值DEFAULTSubscribeCallback: func(services []model.Instance, err error) {log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))},
})
- 取消服务监听:Unsubscribe
err := namingClient.Unsubscribe(vo.SubscribeParam{ServiceName: "demo.go",GroupName: "group-a", // 默认值DEFAULT_GROUPClusters: []string{"cluster-a"}, // 默认值DEFAULTSubscribeCallback: func(services []model.Instance, err error) {log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))},
})
- 获取服务名列表
serviceInfos, err := namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{NameSpace: "0e83cc81-9d8c-4bb8-a28a-ff703187543f",PageNo: 1,PageSize: 10,}),
动态配置
- 发布配置:PublishConfig
success, err := configClient.PublishConfig(vo.ConfigParam{DataId: "dataId",Group: "group",Content: "hello world!222222"})
- 删除配置:DeleteConfig
success, err = configClient.DeleteConfig(vo.ConfigParam{DataId: "dataId",Group: "group"})
- 获取配置:GetConfig
content, err := configClient.GetConfig(vo.ConfigParam{DataId: "dataId",Group: "group"})
- 监听配置变化:ListenConfig
err := configClient.ListenConfig(vo.ConfigParam{DataId: "dataId",Group: "group",OnChange: func(namespace, group, dataId, data string) {fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)},
})
- 取消配置监听:CancelListenConfig
err := configClient.CancelListenConfig(vo.ConfigParam{DataId: "dataId",Group: "group",
})
- 搜索配置: SearchConfig
configPage,err := configClient.SearchConfig(vo.SearchConfigParam{Search: "blur",DataId: "",Group: "",PageNo: 1,PageSize: 10,
})
工具
client
/** Copyright 1999-2020 Alibaba Group Holding Ltd.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package nacosimport ("fmt""github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client""github.com/nacos-group/nacos-sdk-go/v2/vo"
)func RegisterServiceInstance(client naming_client.INamingClient, param vo.RegisterInstanceParam) {success, err := client.RegisterInstance(param)if !success || err != nil {panic("RegisterServiceInstance failed!" + err.Error())}fmt.Printf("RegisterServiceInstance,param:%+v,result:%+v \n\n", param, success)
}func BatchRegisterServiceInstance(client naming_client.INamingClient, param vo.BatchRegisterInstanceParam) {success, err := client.BatchRegisterInstance(param)if !success || err != nil {panic("BatchRegisterServiceInstance failed!" + err.Error())}fmt.Printf("BatchRegisterServiceInstance,param:%+v,result:%+v \n\n", param, success)
}func DeRegisterServiceInstance(client naming_client.INamingClient, param vo.DeregisterInstanceParam) {success, err := client.DeregisterInstance(param)if !success || err != nil {panic("DeRegisterServiceInstance failed!" + err.Error())}fmt.Printf("DeRegisterServiceInstance,param:%+v,result:%+v \n\n", param, success)
}func UpdateServiceInstance(client naming_client.INamingClient, param vo.UpdateInstanceParam) {success, err := client.UpdateInstance(param)if !success || err != nil {panic("UpdateInstance failed!" + err.Error())}fmt.Printf("UpdateServiceInstance,param:%+v,result:%+v \n\n", param, success)
}func GetService(client naming_client.INamingClient, param vo.GetServiceParam) {service, err := client.GetService(param)if err != nil {panic("GetService failed!" + err.Error())}fmt.Printf("GetService,param:%+v, result:%+v \n\n", param, service)
}func SelectAllInstances(client naming_client.INamingClient, param vo.SelectAllInstancesParam) {instances, err := client.SelectAllInstances(param)if err != nil {panic("SelectAllInstances failed!" + err.Error())}fmt.Printf("SelectAllInstance,param:%+v, result:%+v \n\n", param, instances)
}func SelectInstances(client naming_client.INamingClient, param vo.SelectInstancesParam) {instances, err := client.SelectInstances(param)if err != nil {panic("SelectInstances failed!" + err.Error())}fmt.Printf("SelectInstances,param:%+v, result:%+v \n\n", param, instances)
}func SelectOneHealthyInstance(client naming_client.INamingClient, param vo.SelectOneHealthInstanceParam) {instances, err := client.SelectOneHealthyInstance(param)if err != nil {panic("SelectOneHealthyInstance failed!")}fmt.Printf("SelectOneHealthyInstance,param:%+v, result:%+v \n\n", param, instances)
}func Subscribe(client naming_client.INamingClient, param *vo.SubscribeParam) {_ = client.Subscribe(param)
}func UnSubscribe(client naming_client.INamingClient, param *vo.SubscribeParam) {_ = client.Unsubscribe(param)
}func GetAllService(client naming_client.INamingClient, param vo.GetAllServiceInfoParam) {service, err := client.GetAllServicesInfo(param)if err != nil {panic("GetAllService failed!")}fmt.Printf("GetAllService,param:%+v, result:%+v \n\n", param, service)
}
config
/** Copyright 1999-2020 Alibaba Group Holding Ltd.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package nacosimport ("fmt""github.com/nacos-group/nacos-sdk-go/v2/clients/config_client""github.com/nacos-group/nacos-sdk-go/v2/model""github.com/nacos-group/nacos-sdk-go/v2/vo"
)func PublishConfig(client config_client.IConfigClient, param vo.ConfigParam) {//publish config//config key=dataId+group+namespaceId_, err := client.PublishConfig(param)if err != nil {fmt.Printf("PublishConfig err:%+v \n", err)}
}func DeleteConfig(client config_client.IConfigClient, param vo.ConfigParam) {_, err := client.DeleteConfig(param)if err != nil {fmt.Printf("DeleteConfig err:%+v \n", err)}
}func GetConfig(client config_client.IConfigClient, param vo.ConfigParam) string {//get configcontent, err := client.GetConfig(param)if err != nil {fmt.Printf("GetConfig err:%+v \n", err)} else {fmt.Println("GetConfig,config :" + content)}return content
}func ListenConfig(client config_client.IConfigClient, param vo.ConfigParam) {//Listen config change,key=dataId+group+namespaceId.err := client.ListenConfig(vo.ConfigParam{DataId: "test-data",Group: "test-group",OnChange: func(namespace, group, dataId, data string) {fmt.Println("config changed group:" + group + ", dataId:" + dataId + ", content:" + data)},})if err != nil {fmt.Printf("PublishConfig err:%+v \n", err)}
}func CancelListenConfig(client config_client.IConfigClient, param vo.ConfigParam) {//cancel config changeerr := client.CancelListenConfig(param)if err != nil {fmt.Printf("CancelListenConfig err:%+v \n", err)}
}func SearchConfig(client config_client.IConfigClient, param vo.SearchConfigParam) *model.ConfigPage {searchPage, err := client.SearchConfig(param)if err != nil {fmt.Printf("SearchConfig err:%+v \n", err)} else {fmt.Printf("SearchConfig:%+v \n", searchPage)}return searchPage
}
相关文章:
go 集成nacos注册中心、配置中心
使用限制 Go>v1.15 Nacos>2.x 安装 使用go get安装SDK: go get -u github.com/nacos-group/nacos-sdk-go/v2 快速使用 初始化客户端配置ClientConfig constant.ClientConfig{TimeoutMs uint64 // 请求Nacos服务端的超时时间,默…...
ssd202d-badblock-坏块检测
这边文章讲述的是坏快检测功能 思路: 1.第一次烧录固件会实现跳坏块,但是后续使用会导致坏块的产生; 于是我在uboot环境变量添加了两个变量来控制坏快 lb_badnum //坏块个数 lb_badoff //坏块所在位置 2.第一次开机会根据lb_badnum是否…...
MySQL-练习-数据介绍
文章目录 一. 数据介绍1. 数据结构2. 创建数据库,数据表3. 员工表(employees)练习1 4. 顾客表(customers)练习2 5. 商品(products)和商品类别(categories)表练习3 6. 供应商表(suppliers)练习4 7. 订单和订单明细表练习5 二. 数据汇总三. 使用CASE WHEN …...
React框架:解锁现代化Web开发的新维度
在当今前端开发领域,React 无疑是一颗璀璨的明星。React 是由 Facebook 开发的用于构建用户界面的 JavaScript 库,它在前端开发中占据着重要的地位,为开发者提供了一种高效、灵活且可维护的方式来构建复杂的用户界面。 一、React 的背景与开…...
电阻功率,限流,等效电阻
1 电阻额定功率 2 电阻限流作用 3 电阻并联等效电阻...
Qt | 开发工具(top1)
Qt Creator 跨平台、完整的集成开发环境(IDE),供应用程序开发者创建用于多个桌面、嵌入式和移动设备平台的应用程序。 Qt Linguist 一套将Qt C和Qt Quick应用程序翻译成本地语言的工具。 qmake Qt自动化构建工具,简化了不同平台的构建过程。…...
Node.js express
1. express 介绍 express 是一个基于 Node.js 平台的极简、灵活的 WEB 应用开发框架,官方网址:https://www.expressjs.com.cn/简单来说,express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用ÿ…...
ios h5中在fixed元素中的input被focus时,键盘遮挡input (van-popup、van-feild)
问题描述: 前提:我使用的是vant组件库,其中一个页面中有一个van-popup组件,van-popup组件中又嵌套了一个van-field组件预期结果:当点击van-feild输入框时,键盘弹起,输入框显示在键盘上方实际结…...
springboot整合lua脚本在Redis实现商品库存扣减
1、目的 使用lua脚本,可以保证多条命令的操作原子性;同时可以减少操作IO(比如说判断redis对应数据是否小于0,小于0就重置为100,这个场景一般是取出来再判断,再存放进行,就至少存在2次IO,用lua脚…...
MySQL ON DUPLICATE KEY UPDATE影响行数
目录 分析为什么Updates返回7 总结 数据库更新日志如下 insertOrUpdateList|> Preparing: INSERT INTO clue_user_tag (vuid, tag_id, tag_type, content) VALUES (?, ?, ?, ?) , (?, ?, ?, ?) , (?, ?, ?, ?) , (?, ?, ?, ?) ON DUPLICATE KEY UPDATE …...
uniapp小程序 slot中无法传递外部参数的解决方案
最近在封装一个List组件,外部传给我数据,我循环后将每个Item部分slot到外部,由调用者自己去写item布局,类似ElementUI、iView的Tabe列表。 List: <view v-if"list.length > 0" class"list-scroll__item&quo…...
umi实现动态获取菜单权限
文章目录 前景登录组件编写登录逻辑菜单的时机动态路由页面刷新手动修改地址 前景 不同用户拥有不同的菜单权限,现在我们实现登录动态获取权限菜单。 登录组件编写 //当我们需要使用dva的dispatch函数时,除了通过connect函数包裹组件还可以使用这种方…...
Pytest-Bdd-Playwright 系列教程(14):Docstring 参数
Pytest-Bdd-Playwright 系列教程(14):Docstring 参数 前言一、什么是docstring?二、基本语法三、主要特点四、实际例子五、注意事项六、使用建议总结 前言 在自动化测试的过程中,我们经常需要处理复杂的测试数据或需要输入多行文…...
交互开发---测量工具(适用VTK或OpenGL开发的应用程序)
简介: 经常使用RadiAnt DICOM Viewer来查看DICOM数据,该软件中的测量工具比较好用,就想着仿照其交互方式自己实现下。后采用VTK开发应用程序时,经常需要开发各种各样的测量工具,如果沿用VTK的widgets的思路,…...
Qt 一个简单的QChart 绘图
Qt 一个简单的QChart 绘图 先上程序运行结果图: “sample9_1QChart.h” 文件代码如下: #pragma once#include <QtWidgets/QMainWindow> #include "ui_sample9_1QChart.h"#include <QtCharts> //必须这么设置 QT_CHARTS_USE_NAME…...
【Java笔记】LinkedList 底层结构
一、LinkedList 的全面说明 LinkedList底层实现了双向链表和双端队列特点可以添加任意元素(元素可以重复),包括null线程不安全,没有实现同步 二、LinkedList 的底层操作机制 三、LinkedList的增删改查案例 public class LinkedListCRUD { public stati…...
el-table组件树形数据修改展开箭头
<style lang"scss" scoped> ::v-deep .el-table__expand-icon .el-icon-arrow-right:before {content: ">"; // 箭头样式font-size: 16px; }::v-deep .el-table__expand-icon{ // 没有展开的状态background-color: rgba(241, 242, 245, 1);color:…...
太速科技-FMC154-基于FMC 八路SFP+万兆光纤子卡
FMC154-基于FMC 八路SFP万兆光纤子卡 一、板卡概述 本卡是一个FPGA夹层卡(FMC)模块,可提供高达8个SFP / SFP 模块接口,直接插入千兆位级收发器(MGT)的赛灵思FPGA。支持业界标准的小型可插拔࿰…...
记:排查设备web时慢时快问题,速度提升100%
问题描述 问题1: 发现web登录界面刷新和登录功能都比较卡,开浏览器控制台看了下,让我很惊讶,居然能这么慢: 公司2个局域网内的表现不同,局域网A中的都比较卡,局域网B中的又不存在该现象。 问…...
音视频入门基础:MPEG2-TS专题(13)——FFmpeg源码中,解析Section Header的实现
一、引言 在《音视频入门基础:MPEG2-TS专题(11)—— TS中的Section》中讲述了Section Header的基本概念,本文讲述FFmpeg源码中是怎样解析Section Header的。 二、parse_section_header函数的定义 FFmpeg源码中通过parse_section…...
基于CircuitPython与Adafruit IO的物联网倒计时时钟:精准时间同步与远程触发
1. 项目概述:一个精准、可远程触发的物联网倒计时时钟在嵌入式开发里,时间管理是个既基础又容易踩坑的环节。你可能遇到过这种情况:一个基于ESP32的智能浇花器,设定好每天上午10点浇水,结果因为设备内置时钟不准&#…...
OpenRGB终极指南:一站式免费控制所有RGB设备的完整解决方案
OpenRGB终极指南:一站式免费控制所有RGB设备的完整解决方案 【免费下载链接】OpenRGB Open source RGB lighting control that doesnt depend on manufacturer software. Supports Windows, Linux, MacOS. Mirror of https://gitlab.com/CalcProgrammer1/OpenRGB. R…...
AI记忆库CoPaw-Memory:向量检索与结构化存储融合实践
1. 项目概述:当AI学会“记笔记”,一个开源记忆库的诞生最近在折腾AI应用开发的朋友,可能都遇到过同一个痛点:如何让AI记住我们说过的话?无论是构建一个长期陪伴的聊天机器人,还是开发一个能理解复杂上下文的…...
5分钟搞定安卓APK签名:SignatureTools图形化签名工具终极指南
5分钟搞定安卓APK签名:SignatureTools图形化签名工具终极指南 【免费下载链接】SignatureTools 🎡使用JavaFx编写的安卓Apk签名&渠道写入工具,方便快速进行v1&v2签名。 项目地址: https://gitcode.com/gh_mirrors/si/SignatureTool…...
如何快速批量添加专业水印:3分钟掌握摄影作品保护终极指南
如何快速批量添加专业水印:3分钟掌握摄影作品保护终极指南 【免费下载链接】semi-utils 一个批量添加相机机型和拍摄参数的工具,后续「可能」添加其他功能。 项目地址: https://gitcode.com/gh_mirrors/se/semi-utils semi-utils是一款专为摄影师…...
STM32CubeMX配置SPI驱动W25Q64 Flash:从硬件连接到驱动封装,一个完整项目实战
STM32CubeMX实战:构建高可靠SPI Flash存储系统从硬件到软件全解析 在嵌入式开发中,外部Flash存储解决方案往往成为扩展设备数据容量的关键选择。W25Q64作为一款经典的64M-bit SPI Flash芯片,凭借其稳定的性能和广泛的应用场景,成为…...
基于Apify与AI模型的产品安全风险智能识别系统构建指南
1. 项目概述:一个面向产品安全与消费者风险管理的智能工具最近在梳理一些供应链和电商合规的项目时,我反复被一个核心痛点困扰:如何系统性地、自动化地识别和评估海量商品信息中潜藏的消费者风险?无论是作为平台方的风控团队&…...
怎样免费去掉图片水印?2026年免费去水印工具推荐|在线vs软件对比
在日常工作和生活中,我们经常会遇到带有水印的图片。无论是来自社交媒体平台、在线图库还是其他来源,这些水印往往影响图片的使用效果。2026年,市面上出现了多种免费去水印工具,它们采用不同的技术方案,适用于不同的使…...
手把手教你用rtsp-simple-server和FFmpeg在Windows上搭建个人视频流媒体服务器(保姆级教程)
手把手教你用rtsp-simple-server和FFmpeg在Windows上搭建个人视频流媒体服务器 在数字化生活日益普及的今天,个人视频流媒体服务器的需求正在快速增长。无论是想搭建家庭监控系统原型,还是为开发项目创建测试环境,亦或是单纯出于技术爱好探索…...
MVT矢量瓦片实战避坑指南:从配置到渲染的进阶解析
1. MVT矢量瓦片基础概念与核心优势 第一次接触MVT(Mapbox Vector Tile)矢量瓦片时,我和大多数开发者一样困惑:为什么不用传统的栅格瓦片?直到在某次地图项目中遇到动态样式调整需求时才恍然大悟。MVT本质上是将地理数据…...
