欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_go_cgo

本文讲解鸿蒙PC基于musl库、应用沙箱与二进制强制签名机制,不原生支持Go语言,通用Linux编译产物无法直接运行。需借助社区Harmonybrew包管理器搭建开发环境:纯Go开发安装go与ohos-sdk,依托SDK实现编译自动签名;CGO跨语言开发需额外安装llvm-gcc-compat补齐cc编译命令,编译时手动开启CGO参数。搭配CodeArts IDE可完成全流程开发,同时需提前处理软件冲突、使用原生终端规避环境报错。

搭建环境的话,可以参考以下文章:OpenHarmony 鸿蒙 PC + CodeArts IDE 实现 Go开发完整开发环境搭建指南


一、snowflake 库介绍

1. 库作用

github.com/bwmarrin/snowflake 是 Go 实现的雪花算法ID生成库,雪花ID是分布式全局唯一ID,多用于订单、用户、消息主键,优势:

  1. 全局不重复,分布式多服务并发无冲突;
  2. ID自带时间戳,可直接解析生成时间;
  3. 64位数字,排序有序,数据库索引性能好;
  4. 可配置机器ID、进程ID区分不同服务器/实例。

2. 安装命令

go get github.com/bwmarrin/snowflake

请添加图片描述

3. 无报错完整可运行代码

package main

import (
	"fmt"
	"sync"
	"time"

	"github.com/bwmarrin/snowflake"
)

// 全局雪花节点(项目开发推荐全局单例,只初始化一次)
var snowNode *snowflake.Node
var once sync.Once

// InitSnowflake 初始化雪花生成器,单例执行只创建一次
// machineId:机器ID,集群每台机器使用 0~1023 唯一数字
func InitSnowflake(machineId int64) error {
	var err error
	once.Do(func() {
		// 创建雪花节点
		snowNode, err = snowflake.NewNode(machineId)
	})
	return err
}

// GetSnowID 获取int64类型雪花ID(数据库主键存储用)
func GetSnowID() int64 {
	return snowNode.Generate().Int64()
}

// GetSnowIDStr 获取字符串雪花ID(接口返回前端,避免大数精度丢失)
func GetSnowIDStr() string {
	return snowNode.Generate().String()
}

// ParseIDInfo 解析雪花ID内部全部信息
func ParseIDInfo(id int64) {
	snowID := snowflake.ParseInt64(id)
	fmt.Println("===== 雪花ID解析详情 =====")
	fmt.Printf("原始ID数值: %d\n", id)
	ms := snowID.Time()
	// 毫秒时间戳转time.Time
	createTime := time.UnixMilli(ms)
	fmt.Printf("生成时间戳(ms): %d\n", ms)
	fmt.Printf("格式化创建时间: %s\n", createTime.Format("2006-01-02 15:04:05"))
	fmt.Printf("机器编号(NodeID): %d\n", snowID.Node())
	fmt.Printf("毫秒内自增序列号(Step): %d\n", snowID.Step())
	fmt.Println("==========================")
}

// 并发测试函数:多协程批量生成ID,验证无重复
func concurrentTest(wg *sync.WaitGroup, ch chan<- int64) {
	defer wg.Done()
	for i := 0; i < 500; i++ {
		id := GetSnowID()
		ch <- id
	}
}

func main() {
	// 1. 初始化雪花生成器,机器ID设为1,分布式环境每台机器修改唯一值
	err := InitSnowflake(1)
	if err != nil {
		fmt.Printf("雪花算法初始化失败,错误:%v\n", err)
		return
	}
	fmt.Println("雪花ID生成器初始化完成!\n")

	// 2. 基础用法:生成两种格式ID
	idNum := GetSnowID()
	idStr := GetSnowIDStr()
	fmt.Printf("数字型雪花ID(存数据库): %d\n", idNum)
	fmt.Printf("字符串雪花ID(接口返回): %s\n\n", idStr)

	// 3. 解析ID内置时间、机器、序列号信息
	ParseIDInfo(idNum)

	// 4. 单循环批量生成ID
	fmt.Println("单次循环批量生成10个ID:")
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", GetSnowID())
	}
	fmt.Println("\n")

	// 5. 高并发多协程测试,校验ID全局唯一
	const goroutineNum = 10
	const totalCount = goroutineNum * 500
	idChan := make(chan int64, totalCount)
	var wg sync.WaitGroup

	fmt.Printf("开启%d个协程,总共生成%d个ID,并发唯一性测试\n", goroutineNum, totalCount)
	for i := 0; i < goroutineNum; i++ {
		wg.Add(1)
		go concurrentTest(&wg, idChan)
	}
	wg.Wait()
	close(idChan)

	// 去重校验
	idMap := make(map[int64]bool)
	duplicateFlag := false
	for val := range idChan {
		if idMap[val] {
			fmt.Printf("发现重复ID:%d\n", val)
			duplicateFlag = true
		}
		idMap[val] = true
	}

	if !duplicateFlag {
		fmt.Printf("并发校验完成:%d 条ID全部唯一,无重复!\n", totalCount)
	}

	// 6. 业务模拟:生成订单ID、消息ID
	orderId := GetSnowIDStr()
	msgId := GetSnowID()
	fmt.Printf("\n模拟业务订单ID:%s\n", orderId)
	fmt.Printf("模拟消息主键ID:%d\n", msgId)
}

请添加图片描述

二、代码逐段说明

  1. snowflake.NewNode(1)
    创建生成器节点,参数为机器ID,集群中每台服务必须使用 0~1023 唯一数字,多机避免ID重复;创建失败会返回err,必须捕获。
  2. node.Generate()
    生成一条全局唯一雪花ID,返回snowflake.ID类型。
  3. 常用方法
  • .Int64():转为int64数字,存入数据库主键;
  • .String():转字符串,接口返回防止前端大数精度丢失;
  • .Time():提取ID内置的创建时间;
  • .Node():获取当前机器编号;
  • .Step():同一毫秒内自增序列号,保证同毫秒多条数据不重复。

一、头部包导入

package main

标记这是可执行程序,不是第三方库,能直接编译运行。

import (
	"fmt"
	"sync"
	"time"
	"github.com/bwmarrin/snowflake"
)
  1. fmt:控制台打印输出;
  2. sync:提供同步锁 sync.Once、等待组 sync.WaitGroup,用于协程并发控制;
  3. time:时间转换,把雪花ID里的毫秒时间戳格式化成年月日时分秒;
  4. snowflake:第三方雪花ID生成库,用来生成分布式全局唯一ID。

二、全局变量(生产项目标准单例设计)

var snowNode *snowflake.Node
var once sync.Once
  1. snowNode:雪花ID生成器全局实例,整个项目只创建一个;
  2. once sync.Once:Go内置一次性执行锁,保证 InitSnowflake 函数全局只会初始化一次,避免多次创建生成器浪费资源。

三、初始化函数 InitSnowflake

func InitSnowflake(machineId int64) error {
	var err error
	once.Do(func() {
		snowNode, err = snowflake.NewNode(machineId)
	})
	return err
}
  1. 参数 machineId:机器编号,取值范围 0~1023;分布式集群每台服务器必须填不同数字,防止ID重复;
  2. once.Do():无论代码调用多少次初始化,内部创建节点逻辑只执行一次;
  3. snowflake.NewNode(machineId):创建雪花ID生成节点,失败会返回错误,外部捕获即可。

四、两个获取ID工具函数(适配两种业务场景)

1. GetSnowID() 返回 int64 数字ID

func GetSnowID() int64 {
	return snowNode.Generate().Int64()
}
  • 作用:返回纯数字int64类型ID;
  • 使用场景:MySQL数据库主键、数字存储字段。

2. GetSnowIDStr() 返回字符串ID

func GetSnowIDStr() string {
	return snowNode.Generate().String()
}
  • 作用:把数字ID转字符串;
  • 使用场景:HTTP接口返回给前端JS,JS数字超过2^53会丢失精度,用字符串可以避免该问题。

五、ParseIDInfo 雪花ID解析工具函数

func ParseIDInfo(id int64) {
	snowID := snowflake.ParseInt64(id)
	fmt.Println("===== 雪花ID解析详情 =====")
	fmt.Printf("原始ID数值: %d\n", id)
	ms := snowID.Time()
	createTime := time.UnixMilli(ms)
	fmt.Printf("生成时间戳(ms): %d\n", ms)
	fmt.Printf("格式化创建时间: %s\n", createTime.Format("2006-01-02 15:04:05"))
	fmt.Printf("机器编号(NodeID): %d\n", snowID.Node())
	fmt.Printf("毫秒内自增序列号(Step): %d\n", snowID.Step())
	fmt.Println("==========================")
}
  1. snowflake.ParseInt64(id):传入雪花数字ID,反向拆解ID内部存储的信息;
  2. snowID.Time():拿到ID内置的毫秒时间戳(int64);
  3. time.UnixMilli(ms):毫秒时间戳转为Go标准time.Time对象,才能调用Format格式化;
  4. 输出4个核心信息:原始ID、生成毫秒时间戳、可读格式化时间、机器编号、同一毫秒内自增序号;
  5. 业务用途:根据订单/消息ID直接查到创建时间,不用额外查数据库时间字段。

六、concurrentTest 并发测试协程函数

func concurrentTest(wg *sync.WaitGroup, ch chan<- int64) {
	defer wg.Done()
	for i := 0; i < 500; i++ {
		id := GetSnowID()
		ch <- id
	}
}
  1. wg *sync.WaitGroup:协程等待组,每个协程执行完调用wg.Done(),主函数等待所有协程全部执行完毕;
  2. ch chan<- int64:通道,用来收集所有协程生成的ID;
  3. 循环500次:单个协程生成500条唯一ID,推入通道。

七、main 主函数(完整功能演示入口)

1. 初始化雪花生成器

err := InitSnowflake(1)
if err != nil {
	fmt.Printf("雪花算法初始化失败,错误:%v\n", err)
	return
}
fmt.Println("雪花ID生成器初始化完成!\n")

传入机器ID=1;分布式部署时每台机器更换唯一ID,初始化失败直接退出程序。

2. 两种格式ID基础使用

idNum := GetSnowID()
idStr := GetSnowIDStr()
fmt.Printf("数字型雪花ID(存数据库): %d\n", idNum)
fmt.Printf("字符串雪花ID(接口返回): %s\n\n", idStr)

分别拿到数据库存储用数字ID、前端返回用字符串ID并打印。

3. 解析ID内部信息

ParseIDInfo(idNum)

调用解析函数,打印该ID对应的创建时间、机器号、序列号。

4. 单循环批量生成ID

fmt.Println("单次循环批量生成10个ID:")
for i := 0; i < 10; i++ {
	fmt.Printf("%d ", GetSnowID())
}
fmt.Println("\n")

串行批量生成10个ID,直观看到ID有序递增。

5. 高并发多协程唯一性测试(核心验证雪花算法特性)

const goroutineNum = 10
const totalCount = goroutineNum * 500
idChan := make(chan int64, totalCount)
var wg sync.WaitGroup

fmt.Printf("开启%d个协程,总共生成%d个ID,并发唯一性测试\n", goroutineNum, totalCount)
for i := 0; i < goroutineNum; i++ {
	wg.Add(1)
	go concurrentTest(&wg, idChan)
}
wg.Wait()
close(idChan)
  1. 定义10个协程,每个协程生成500条ID,合计5000条;
  2. wg.Add(1) 每开启一个协程等待计数+1;
  3. wg.Wait() 阻塞主程序,直到所有协程执行完毕;
  4. 关闭通道,停止读取。

6. map去重校验,验证ID全局不重复

idMap := make(map[int64]bool)
duplicateFlag := false
for val := range idChan {
	if idMap[val] {
		fmt.Printf("发现重复ID:%d\n", val)
		duplicateFlag = true
	}
	idMap[val] = true
}

if !duplicateFlag {
	fmt.Printf("并发校验完成:%d 条ID全部唯一,无重复!\n", totalCount)
}
  1. 使用map存储已经生成过的ID;
  2. 如果map中已存在当前ID,标记出现重复并打印;
  3. 全部遍历完成无重复则输出校验通过,证明雪花算法并发下ID全局唯一。

7. 模拟真实业务场景

orderId := GetSnowIDStr()
msgId := GetSnowID()
fmt.Printf("\n模拟业务订单ID:%s\n", orderId)
fmt.Printf("模拟消息主键ID:%d\n", msgId)

模拟电商订单号(字符串给前端)、聊天消息数据库主键(数字存入MySQL)。

整体代码设计亮点

  1. 单例全局管理:整个项目只初始化一次雪花生成器,符合线上项目规范;
  2. 双格式ID封装:同时兼容数据库存储、前端接口两种场景;
  3. ID反向解析工具:支持通过ID溯源创建时间,方便日志、订单排查;
  4. 并发完整性测试:模拟线上高并发场景,验证雪花ID无重复核心特性;
  5. 完整错误捕获,无未处理异常,编译零报错;
  6. 代码可直接复制到鸿蒙PC,编译+签名后运行。

鸿蒙PC运行命令

go get github.com/bwmarrin/snowflake
go build -o snow main.go
chmod +x snow
ohos-signpost snow
./snow

四、业务使用场景

  1. MySQL主键替代自增ID:分布式多服务部署时自增ID会冲突,雪花ID全局唯一;
  2. 订单号、流水号、消息ID、用户唯一标识;
  3. 可通过ID反查数据创建时间,无需额外存创建时间字段做统计;
  4. 按ID排序等价于按创建时间排序,分页查询更友好。

五、鸿蒙PC运行注意事项

鸿蒙不能直接go run,编译签名后执行:

go build -o snow main.go
chmod +x snow
ohos-signpost snow
./snow
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐