第六:go 操作 redis-go

Redis

在項目開發中redis的使用也比較頻繁,本文介紹了Go語言中go-redis庫的基本使用。

Redis介紹

Redis是一個開源的內存數據庫,Redis提供了多種不同類型的數據結構,很多業務場景下的問題都可以很自然地映射到這些數據結構上。除此之外,通過復制、持久化和客戶端分片等特性,我們可以很方便地將Redis擴展成一個能夠包含數百GB數據、每秒處理上百萬次請求的系統。

Redis支持的數據結構

Redis支持諸如字符串(string)、哈希(hashe)、列表(list)、集合(set)、帶范圍查詢的排序集合(sorted set)、bitmap、hyperloglog、帶半徑查詢的地理空間索引(geospatial index)和流(stream)等數據結構。

Redis應用場景

  • 緩存系統,減輕主數據庫(MySQL)的壓力。
  • 計數場景,比如微博、抖音中的關注數和粉絲數。
  • 熱門排行榜,需要排序的場景特別適合使用ZSET。
  • 利用 LIST 可以實現隊列的功能。
  • 利用 HyperLogLog 統計UV、PV等數據。
  • 使用 geospatial index 進行地理位置相關查詢。

準備Redis環境

讀者可以選擇在本機安裝 redis 或使用云數據庫,這里直接使用Docker啟動一個 redis 環境,方便學習使用。

使用下面的命令啟動一個名為 redis507 的 5.0.7 版本的 redis server環境。

docker run --name redis507 -p 6379:6379 -d redis:5.0.7

注意:?此處的版本、容器名和端口號可以根據自己需要設置。

啟動一個 redis-cli 連接上面的 redis server。

docker run -it --network host --rm redis:5.0.7 redis-cli

go-redis庫

安裝

Go 社區中目前有很多成熟的 redis client 庫,比如GitHub - gomodule/redigo: Go client for Redis?和GitHub - redis/go-redis: Redis Go client,讀者可以自行選擇適合自己的庫。本文使用 go-redis 這個庫來操作 Redis 數據庫。

使用以下命令下安裝 go-redis 庫。

安裝v8版本:

go get github.com/redis/go-redis/v8

安裝v9版本:

go get github.com/redis/go-redis/v9

連接

在項目中導入?go-redis庫(請根據實際情況導入自己需要的版本)。

import "github.com/redis/go-redis/v9"

SetNX方法僅在鍵不存在時設置值:

err := rdb.SetNX(ctx, "key1", "value", 0).Err()
if err != nil {panic(err)
}

2.5.1 添加和刪除
ZAdd用于添加或更新元素,ZRem用于刪除元素:

err := rdb.ZAdd(ctx, "key", &redis.Z{Score: 2.5, Member: "zhangsan"}).Err()
if err != nil {
?? ?panic(err)
}

rdb.ZRem(ctx, "key", "zhangsan")

2.5.2 查詢操作
ZRange和ZRevRange用于按分數排序返回元素,ZScore用于查詢元素的分數:

vals, err := rdb.ZRange(ctx, "key", 0, -1).Result()
if err != nil {
?? ?panic(err)
}
fmt.Println(vals)

score, err := rdb.ZScore(ctx, "key", "zhangsan").Result()
if err != nil {
?? ?panic(err)
}
fmt.Println(score)

? ? ? ? ? ? ? ? ? ? ? ??
原文鏈接:https://blog.csdn.net/weixin_73833086/article/details/146127620

普通連接模式

注意 如果 redis?的值不存的情況下:

if err == redis.Nil?  的情況判斷

go-redis 庫中使用 redis.NewClient 函數連接 Redis 服務器。

rdb := redis.NewClient(&redis.Options{Addr:     "localhost:6379",Password: "", // 密碼DB:       0,  // 數據庫PoolSize: 20, // 連接池大小
})

除此之外,還可以使用 redis.ParseURL 函數從表示數據源的字符串中解析得到 Redis 服務器的配置信息。

opt, err := redis.ParseURL("redis://<user>:<pass>@localhost:6379/<db>")
if err != nil {panic(err)
}rdb := redis.NewClient(opt)
TLS連接模式

如果使用的是 TLS 連接方式,則需要使用 tls.Config 配置。

rdb := redis.NewClient(&redis.Options{TLSConfig: &tls.Config{MinVersion: tls.VersionTLS12,// Certificates: []tls.Certificate{cert},// ServerName: "your.domain.com",},
})
Redis Sentinel模式? 哨兵模式

使用下面的命令連接到由 Redis Sentinel 管理的 Redis 服務器。

rdb := redis.NewFailoverClient(&redis.FailoverOptions{MasterName:    "master-name",SentinelAddrs: []string{":9126", ":9127", ":9128"},
})
Redis Cluster模式 集群模式

使用下面的命令連接到 Redis Cluster,go-redis 支持按延遲或隨機路由命令。

rdb := redis.NewClusterClient(&redis.ClusterOptions{Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},// 若要根據延遲或隨機路由命令,請啟用以下命令之一// RouteByLatency: true,// RouteRandomly: true,
})

基本使用

執行命令

下面的示例代碼演示了 go-redis 庫的基本使用。

// doCommand go-redis基本使用示例
func doCommand() {ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)defer cancel()// 執行命令獲取結果val, err := rdb.Get(ctx, "key").Result()fmt.Println(val, err)// 先獲取到命令對象cmder := rdb.Get(ctx, "key")fmt.Println(cmder.Val()) // 獲取值fmt.Println(cmder.Err()) // 獲取錯誤// 直接執行命令獲取錯誤err = rdb.Set(ctx, "key", 10, time.Hour).Err()// 直接執行命令獲取值value := rdb.Get(ctx, "key").Val()fmt.Println(value)
}

執行任意命令

go-redis 還提供了一個執行任意命令或自定義命令的 Do 方法,特別是一些 go-redis 庫暫時不支持的命令都可以使用該方法執行。具體使用方法如下。

// doDemo rdb.Do 方法使用示例
func doDemo() {ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)defer cancel()// 直接執行命令獲取錯誤err := rdb.Do(ctx, "set", "key", 10, "EX", 3600).Err()fmt.Println(err)// 執行命令獲取結果val, err := rdb.Do(ctx, "get", "key").Result()fmt.Println(val, err)
}

redis.Nil

go-redis 庫提供了一個 redis.Nil 錯誤來表示 Key 不存在的錯誤。因此在使用 go-redis 時需要注意對返回錯誤的判斷。在某些場景下我們應該區別處理 redis.Nil 和其他不為 nil 的錯誤。

// getValueFromRedis redis.Nil判斷
func getValueFromRedis(key, defaultValue string) (string, error) {ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)defer cancel()val, err := rdb.Get(ctx, key).Result()if err != nil {// 如果返回的錯誤是key不存在if errors.Is(err, redis.Nil) {return defaultValue, nil}// 出其他錯了return "", err}return val, nil
}

其他示例

zset示例

下面的示例代碼演示了如何使用 go-redis 庫操作 zset。

// zsetDemo 操作zset示例
func zsetDemo() {// keyzsetKey := "language_rank"// value// 注意:v8版本使用[]*redis.Z;此處為v9版本使用[]redis.Zlanguages := []redis.Z{{Score: 90.0, Member: "Golang"},{Score: 98.0, Member: "Java"},{Score: 95.0, Member: "Python"},{Score: 97.0, Member: "JavaScript"},{Score: 99.0, Member: "C/C++"},}ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)defer cancel()// ZADDerr := rdb.ZAdd(ctx, zsetKey, languages...).Err()if err != nil {fmt.Printf("zadd failed, err:%v\n", err)return}fmt.Println("zadd success")// 把Golang的分數加10newScore, err := rdb.ZIncrBy(ctx, zsetKey, 10.0, "Golang").Result()if err != nil {fmt.Printf("zincrby failed, err:%v\n", err)return}fmt.Printf("Golang's score is %f now.\n", newScore)// 取分數最高的3個ret := rdb.ZRevRangeWithScores(ctx, zsetKey, 0, 2).Val()for _, z := range ret {fmt.Println(z.Member, z.Score)}// 取95~100分的op := &redis.ZRangeBy{Min: "95",Max: "100",}ret, err = rdb.ZRangeByScoreWithScores(ctx, zsetKey, op).Result()if err != nil {fmt.Printf("zrangebyscore failed, err:%v\n", err)return}for _, z := range ret {fmt.Println(z.Member, z.Score)}
}

執行上面的函數將得到如下輸出結果。

zadd success
Golang's score is 100.000000 now.
Golang 100
C/C++ 99
Java 98
Python 95
JavaScript 97
Java 98
C/C++ 99
Golang 100

掃描或遍歷所有key

在Redis中可以使用KEYS prefix*?命令按前綴查詢所有符合條件的 key,go-redis庫中提供了Keys方法實現類似查詢key的功能。

例如使用以下命令查詢以user:為前綴的所有key(user:cart:00user:order:2023等)。

vals, err := rdb.Keys(ctx, "user:*").Result()

但是如果需要掃描數百萬的 key ,那速度就會比較慢。這種場景下你可以使用Scan命令來遍歷所有符合要求的 key。

// scanKeysDemo1 按前綴查找所有key示例
func scanKeysDemo1() {ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)defer cancel()var cursor uint64for {var keys []stringvar err error// 將redis中所有以prefix:為前綴的key都掃描出來keys, cursor, err = rdb.Scan(ctx, cursor, "prefix:*", 0).Result()if err != nil {panic(err)}for _, key := range keys {fmt.Println("key", key)}if cursor == 0 { // no more keysbreak}}
}

針對這種需要遍歷大量key的場景,go-redis中提供了一個簡化方法——Iterator,其使用示例如下。

// scanKeysDemo2 按前綴掃描key示例
func scanKeysDemo2() {ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)defer cancel()// 按前綴掃描keyiter := rdb.Scan(ctx, 0, "prefix:*", 0).Iterator()for iter.Next(ctx) {fmt.Println("keys", iter.Val())}if err := iter.Err(); err != nil {panic(err)}
}

例如,我們可以寫出一個將所有匹配指定模式的 key 刪除的示例。

// delKeysByMatch 按match格式掃描所有key并刪除
func delKeysByMatch(match string, timeout time.Duration) {ctx, cancel := context.WithTimeout(context.Background(), timeout)defer cancel()iter := rdb.Scan(ctx, 0, match, 0).Iterator()for iter.Next(ctx) {err := rdb.Del(ctx, iter.Val()).Err()if err != nil {panic(err)}}if err := iter.Err(); err != nil {panic(err)}
}

此外,對于 Redis 中的 set、hash、zset 數據類型,go-redis?也支持類似的遍歷方法。

iter := rdb.SScan(ctx, "set-key", 0, "prefix:*", 0).Iterator()
iter := rdb.HScan(ctx, "hash-key", 0, "prefix:*", 0).Iterator()
iter := rdb.ZScan(ctx, "sorted-hash-key", 0, "prefix:*", 0).Iterator(

Pipeline

Redis Pipeline 允許通過使用單個 client-server-client 往返執行多個命令來提高性能。區別于一個接一個地執行100個命令,你可以將這些命令放入 pipeline 中,然后使用1次讀寫操作像執行單個命令一樣執行它們。這樣做的好處是節省了執行命令的網絡往返時間(RTT)。

y在下面的示例代碼中演示了使用 pipeline 通過一個 write + read 操作來執行多個命令。

pipe := rdb.Pipeline()incr := pipe.Incr(ctx, "pipeline_counter")
pipe.Expire(ctx, "pipeline_counter", time.Hour)cmds, err := pipe.Exec(ctx)
if err != nil {panic(err)
}// 在執行pipe.Exec之后才能獲取到結果
fmt.Println(incr.Val())

上面的代碼相當于將以下兩個命令一次發給 Redis Server 端執行,與不使用 Pipeline 相比能減少一次RTT。

INCR pipeline_counter
EXPIRE pipeline_counts 3600

或者,你也可以使用Pipelined?方法,它會在函數退出時調用 Exec。

var incr *redis.IntCmdcmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {incr = pipe.Incr(ctx, "pipelined_counter")pipe.Expire(ctx, "pipelined_counter", time.Hour)return nil
})
if err != nil {panic(err)
}// 在pipeline執行后獲取到結果
fmt.Println(incr.Val())

我們可以遍歷 pipeline 命令的返回值依次獲取每個命令的結果。下方的示例代碼中使用pipiline一次執行了100個 Get 命令,在pipeline 執行后遍歷取出100個命令的執行結果。

cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {for i := 0; i < 100; i++ {pipe.Get(ctx, fmt.Sprintf("key%d", i))}return nil
})
if err != nil {panic(err)
}for _, cmd := range cmds {fmt.Println(cmd.(*redis.StringCmd).Val())
}

在那些我們需要一次性執行多個命令的場景下,就可以考慮使用 pipeline 來優化。

事務

Redis 是單線程執行命令的,因此單個命令始終是原子的,但是來自不同客戶端的兩個給定命令可以依次執行,例如在它們之間交替執行。但是,Multi/exec能夠確保在multi/exec兩個語句之間的命令之間沒有其他客戶端正在執行命令。

在這種場景我們需要使用 TxPipeline 或 TxPipelined 方法將 pipeline 命令使用?MULTI?和EXEC包裹起來。

// TxPipeline demo
pipe := rdb.TxPipeline()
incr := pipe.Incr(ctx, "tx_pipeline_counter")
pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
_, err := pipe.Exec(ctx)
fmt.Println(incr.Val(), err)// TxPipelined demo
var incr2 *redis.IntCmd
_, err = rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error {incr2 = pipe.Incr(ctx, "tx_pipeline_counter")pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)return nil
})
fmt.Println(incr2.Val(), err)

上面代碼相當于在一個RTT下執行了下面的redis命令:

MULTI
INCR pipeline_counter
EXPIRE pipeline_counts 3600
EXEC

Watch

我們通常搭配?WATCH命令來執行事務操作。從使用WATCH命令監視某個 key 開始,直到執行EXEC命令的這段時間里,如果有其他用戶搶先對被監視的 key 進行了替換、更新、刪除等操作,那么當用戶嘗試執行EXEC的時候,事務將失敗并返回一個錯誤,用戶可以根據這個錯誤選擇重試事務或者放棄事務。

Watch方法接收一個函數和一個或多個key作為參數。

Watch(fn func(*Tx) error, keys ...string) error

下面的代碼片段演示了 Watch 方法搭配 TxPipelined 的使用示例。

// watchDemo 在key值不變的情況下將其值+1
func watchDemo(ctx context.Context, key string) error {return rdb.Watch(ctx, func(tx *redis.Tx) error {n, err := tx.Get(ctx, key).Int()if err != nil && err != redis.Nil {return err}// 假設操作耗時5秒// 5秒內我們通過其他的客戶端修改key,當前事務就會失敗time.Sleep(5 * time.Second)_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {pipe.Set(ctx, key, n+1, time.Hour)return nil})return err}, key)
}

將上面的函數執行并打印其返回值,如果我們在程序運行后的5秒內修改了被 watch 的 key 的值,那么該事務操作失敗,返回redis: transaction failed錯誤。

最后我們來看一個 go-redis 官方文檔中使用?GET?、SETWATCH命令實現一個 INCR 命令的完整示例。

// 此處rdb為初始化的redis連接客戶端
const routineCount = 100// 設置5秒超時
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()// increment 是一個自定義對key進行遞增(+1)的函數
// 使用 GET + SET + WATCH 實現,類似 INCR
increment := func(key string) error {txf := func(tx *redis.Tx) error {// 獲得當前值或零值n, err := tx.Get(ctx, key).Int()if err != nil && err != redis.Nil {return err}// 實際操作(樂觀鎖定中的本地操作)n++// 僅在監視的Key保持不變的情況下運行_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {// pipe 處理錯誤情況pipe.Set(ctx, key, n, 0)return nil})return err}// 最多重試100次for retries := routineCount; retries > 0; retries-- {err := rdb.Watch(ctx, txf, key)if err != redis.TxFailedErr {return err}// 樂觀鎖丟失}return errors.New("increment reached maximum number of retries")
}// 開啟100個goroutine并發調用increment
// 相當于對key執行100次遞增
var wg sync.WaitGroup
wg.Add(routineCount)
for i := 0; i < routineCount; i++ {go func() {defer wg.Done()if err := increment("counter3"); err != nil {fmt.Println("increment error:", err)}}()
}
wg.Wait()n, err := rdb.Get(ctx, "counter3").Int()
fmt.Println("最終結果:", n, err)

copy

在這個示例中使用了?redis.TxFailedErr?來檢查事務是否失敗。

更多詳情請查閱官方文檔。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/74009.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/74009.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/74009.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【RabbitMQ】RabbitMQ如何保證消息不丟失?

為了保證消息不丟失&#xff0c;需要在生產者、RabbitMQ本身和消費者三個環節采取相應措施。 1.生產者端&#xff1a;確保消息發送成功 1.1開啟消息確認機制(Publisher Confirms) 原理&#xff1a; 生產者發送消息后&#xff0c;RabbitMQ會返回一個確認(ACK),表示消息已成功…

fastapi+angular外賣系統

說明&#xff1a; fastapiangular外賣系統 1.美食分類&#xff08;粥&#xff0c;粉&#xff0c;面&#xff0c;炸雞&#xff0c;炒菜&#xff0c;西餐&#xff0c;奶茶等等&#xff09; 2.商家列表 &#xff08;kfc&#xff0c;蘭州拉面&#xff0c;湘菜館&#xff0c;早餐店…

Kafka-Exporter 9308端口啟用TLS認證的完整指南

#作者&#xff1a;張桐瑞 文章目錄 1 方案描述2 涉及版本3 使用CA自簽證書3.1一鍵生成證書腳本3.1.1證書腳本3.1.2執行結果 3.2分步自建證書過程3.2.1生成CA私鑰3.2.2生成CA自簽名證書3.2.3生成服務器私鑰和證書申請文件CRS 3.3最終的文件列表 4 Exporter啟動命令4.1參數說明 …

NFS共享搭建

準備工作 首先確保已經建了兩臺虛擬機&#xff0c;都是橋接模式&#xff0c;一臺是windows server 2019 一臺是centos7 用戶配額教程,是在windows server 2019中&#xff0c;先新建虛擬池&#xff0c;然后創建虛擬磁盤&#xff0c;記得添加磁盤類型要選擇第三個&#xff0c;要不…

DFT mode下hard phy STA Nopath

hard Phy boundary No Path 1. shift mode; shift cornor出現No Path的; PHY SI SO在shift mode必須有timing的path; 展示為No constrained path; check step: report_timing -though NO constrained path set timing_report_unconstrained true report again you will…

【工作記錄】F12查看接口信息及postman中使用

可參考 詳細教程&#xff1a;如何從前端查看調用接口、傳參及返回結果&#xff08;附帶圖片案例&#xff09;_f12查看接口及參數-CSDN博客 1、接口信息 接口基礎知識2&#xff1a;http通信的組成_接口請求信息包括-CSDN博客 HTTP類型接口之請求&響應詳解 - 三叔測試筆記…

《自然》:陸地蒸散量研究的統計失誤被撤回-空間加權平均的計算方法

文章目錄 前言一、空間加權平均的計算方法二、代碼1.Python 實現2.MATLAB代碼 前言 In this article, we calculated global land evapotranspiration for 2003 to 2019 using a mass-balance approach. To do this, we calculated evapotranspiration as the residual of the…

開源軟件許可證沖突的原因和解決方法

1、什么是開源許可證以及許可證沖突產生的問題 開源軟件許可證是一種法律文件&#xff0c;它規定了軟件用戶、分發者和修改者使用、復制、修改和分發開源軟件的權利和義務。開源許可證是由軟件的版權所有者&#xff08;通常是開發者或開發團隊&#xff09;發布的&#xff0c;它…

【el-upload】el-upload組件 - list-type=“picture“ 時,文件預覽展示優化

目錄 問題圖el-upload預覽組件 PicturePreview效果展示 問題圖 el-upload <el-uploadref"upload"multipledragaction"#":auto-upload"false":file-list"fileList"name"files":accept".png,.jpg,.jpeg,.JGP,.JPEG,.…

微前端 qiankun vite vue3

文章目錄 簡介主應用 qiankun-main vue3 vite子應用 qiankun-app-vue2 webpack5子應用 qiankun-react webpack5子應用 quankun-vue3 vite遇到的問題 簡介 主要介紹以qiankun框架為基礎&#xff0c;vite 搭建vue3 項目為主應用&#xff0c;wepack vue2 和 webpack react 搭建的…

C#從入門到精通(1)

目錄 第一章 C#與VS介紹 第二章 第一個C#程序 &#xff08;1&#xff09;C#程序基本組成 1.命名空間 2.類 3.Main方法 4.注釋 5.語句 6.標識符及關鍵字 &#xff08;2&#xff09;程序編寫規范 1.代碼編寫規則 2.程序命名方法 3.元素命名規范 第三章 變量 &…

東隆科技攜手PRIMES成立中國校準實驗室,開啟激光診斷高精度新時代

3月12日&#xff0c;上海慕尼黑光博會期間&#xff0c;東隆科技正式宣布與德國PRIMES共同成立“中國校準實驗室”。這一重要合作標志著東隆科技在本地化服務領域的優勢與PRIMES在激光光束診斷領域的頂尖技術深度融合&#xff0c;旨在為中國客戶提供更快速、更高精度的服務以及本…

HarmonyOS Next~鴻蒙系統架構設計解析:分層、模塊化與智慧分發的技術革新

HarmonyOS Next&#xff5e;鴻蒙系統架構設計解析&#xff1a;分層、模塊化與智慧分發的技術革新 ? ? 鴻蒙操作系統&#xff08;HarmonyOS&#xff09;作為華為自主研發的分布式操作系統&#xff0c;其架構設計以全場景、多設備協同為核心目標&#xff0c;通過分層架構、模…

Vue Router工作原理探究

摘要&#xff1a; 隨著單頁應用&#xff08;SPA&#xff09;的廣泛流行&#xff0c;路由系統成為前端開發中至關重要的部分。Vue Router作為Vue.js官方的路由管理器&#xff0c;為Vue應用提供了強大的路由功能。本文深入探討Vue Router的工作原理&#xff0c;包括其核心概念、路…

SysOM 可觀測體系建設(一):萬字長文解讀低開銷、高精度性能剖析工具livetrace

可觀測性是一種通過分析系統輸出結果并推斷和衡量系統內部狀態的能力。談及可觀測性一般包含幾大功能&#xff1a;監控指標、鏈路追蹤、告警日志&#xff0c;及 Continues Profiling 持續剖析能力。對于操作系統可觀測&#xff0c;監控指標可以幫助查看各個子系統&#xff08;I…

網絡安全設備配置與管理-實驗4-防火墻AAA服務配置

實驗4-p118防火墻AAA服務配置 從這個實驗開始&#xff0c;每一個實驗都是長篇大論&#x1f613; 不過有好兄弟會替我出手 注意&#xff1a;1. gns3.exe必須以管理員身份打開&#xff0c;否則ping不通虛擬機。 win10虛擬機無法做本次實驗&#xff0c;必須用學校給的虛擬機。首…

路由Vue Router基本用法

路由的作用是根據URL來匹配對應的組件&#xff0c;并且無刷新切換模板的內容。vue.js中&#xff0c;可使用Vue Router來管理路由&#xff0c;讓構建單頁應用更加簡單。 一、效果 二、實現 1.項目中安裝Vue Router插件 pnpm install vue-routerlastest 2.main.js import { …

24. 狀態模式

原文地址: 狀態模式 更多內容請關注&#xff1a;智想天開 1. 狀態模式簡介 狀態模式&#xff08;State Pattern&#xff09;是一種行為型設計模式&#xff0c;它允許一個對象在其內部狀態改變時改變其行為&#xff0c;使得該對象看起來似乎修改了其類。狀態模式通過將狀態的行…

【Qt】Qt + Modbus 服務端學習筆記

《Qt Modbus 服務端學習筆記》 1.因為項目的需要&#xff0c;要寫一個modbus通信&#xff0c;csdn上感覺有些回答&#xff0c;代碼是人工智能生成的&#xff0c;有些細節不對。我這個經過實測&#xff0c;是可以直接用的。 首先要包含Qt 的相關模塊 Qt Modbus 模塊主要包含以…

CherryStudio + 火山引擎DeepSeek R1 告別服務器繁忙

CherryStudio 火山引擎DeepSeek R1 告別服務器繁忙 一、下載CherryStudio并安裝 CherryStudio是功能強大的多模型桌面客戶端&#xff0c;支持Windows、macOS和Linux系統。集成了多種主流的大語言模型&#xff08;如OpenAI、DeepSeek、Gemini等&#xff09;以及本地模型運行功…