title: 'Golang 教學筆記'
–-
在命令提示字元輸入指令即可
編譯 test.go 程式碼, 產生執行檔
go build test.go
清除編譯結果
go clean
安裝套件1 [go get 套件url]
go get github.com/gorilla/websocket
編譯 + 執行 test.go
go run test.go
單元測試 (函式要宣告成 func TestNew(t *testing.T))
go test -v -run=New
傳統舊方式是設定 gopath, 來讓編輯器知道你程式碼檔案路徑
以方便編譯出執行檔
但這種方式每一台電腦都要設定一次 windows環境變數檔案
又分windows 和 linux 兩種, 會很瑣碎, 可攜性較差
使用GoMod 來管理專案編譯路徑
建立好專案資料夾後
輸入 go mod init 專案名稱
會出現兩個檔案 go.mod 和 go.sum
其中 go.mod 是我們要維護的【使用套件檔案】 (編譯器發現你有使用第三方套件, 會幫你自動添加套件github路徑)
實際運行後的套件檔案內容
module example
require (
CommonLib v0.0.0 // 自製套件
github.com/cweill/gotests v1.5.3 // indirect
github.com/go-sql-driver/mysql v1.4.1
github.com/gorilla/mux v1.7.3
github.com/mitchellh/mapstructure v1.1.2
golang.org/x/tools v0.0.0-20191210221141-98df12377212 // indirect
)
replace CommonLib => ../CommonLib
go 1.13
實際目錄配置
package main
import "fmt"
func main() {
fmt.Println("Hello world!")
var b int = 123 // 正規 宣告+定義
a := 10 // 快速 整數快速初始化
str := "測試字串" // 快速 字串快速初始化
pi := 3.14
fmt.Printf("測試範例 %d, str=%s \r\n", a, str)
fmt.Printf("b=%d, 指標=%p \r\n", b, &b)
fmt.Printf("pi=%f pi=%v\r\n", pi, pi )
var c int64 = 456 // 有指定初始值
var d int64 //
d = 789
fmt.Println("類似console.log c=", c, " d=", d)
}
package main 代表是主程式進入點
會搭配 func main() 來告知編譯器起始執行的函式
import 類似 node.js 的 require, 用來引入第三方套件或是自製套件
寫程式要點
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceRoot}/game_server.go",
"env": {},
"args": []
}
]
}
"program": 是告訴vscode編輯器, 預設執行的程式碼路徑
上面範例是強制使用 game_server.go 當成第一個執行的檔案
預設值為 "program": "${fileDirname}",
代表是成為視窗焦點的程式碼會被執行
基本型態
資料型態 | 說明 |
---|---|
uint | 無號整數 |
uint8 | 無號整數 |
uint16 | 無號整數 |
uint32 | 無號整數 |
uint64 | 無號整數 |
int | 有號整數 |
int8 | 有號整數 |
int16 | 有號整數 |
int32 | 有號整數 |
int64 | 有號整數 |
flaot32 | 浮點數 |
flaot64 | 浮點數 |
string | 字串 |
bool | 布林值 true 或 false |
儲存結構
資料型態 | 說明 |
---|---|
Array和Slice | 陣列和切片 |
Map | 映射或字典, key 和 value |
struct | 結構 |
非同步使用
資料型態 | 說明 |
---|---|
channel | 通道, 工作佇列或訊息佇列 |
sync.Mutex | 執行序鎖 lock unlock |
package main
import (
"fmt"
)
func main() {
var b int = 123
a := 10
// 兩個數值比較
if a >= b {
fmt.Printf("%d >= %d \r\n", a, b)
} else {
fmt.Printf("%d < %d \r\n", a, b)
}
}
package main
import (
"fmt"
)
// 列舉
const (
NET_STATUS_UNKNOW int = iota // 預設值 0
NET_STATUS_IDLE // 閒置狀態 1
NET_STATUS_CONNECT // 連線中 2
NET_STATUS_LOBBY // 大廳中 3
NET_STATUS_GAME // 遊戲中 4
)
// 多參數回傳值
/*
* @param status 傳入網路狀態
* @return result 狀態判斷是否成功旗標
* @return message 回傳處理的狀態字串
*/
func NetStatusCheck(status int) (result bool, message string) {
// 判斷網路狀態
switch status {
case NET_STATUS_IDLE:
result = true
message = fmt.Sprintf("閒置狀態 status=%d", status)
case NET_STATUS_CONNECT:
result = true
message = fmt.Sprintf("連線中 status=%d", status)
case NET_STATUS_LOBBY:
result = true
message = fmt.Sprintf("大廳中 status=%d", status)
case NET_STATUS_GAME:
result = true
message = fmt.Sprintf("遊戲中 status=%d", status)
default:
result = false
message = fmt.Sprintf("未知狀態 status=%d", status)
}
// 因為函式 【明確】 宣告兩個回傳值, 所以return 時, 可以簡寫
return
}
// 單一參數回傳值
/*
* @param a 兩數相加的參數1
* @param b 兩數相加的參數2
* @return 回傳處理的相加結果
*/
func Add( a int , b int) ( int ){
value := a + b
return value
}
func main() {
// 呼叫 NetStatusCheck 函式, 並且
result, msg := NetStatusCheck(NET_STATUS_IDLE)
if result == true {
println("判斷1 msg=", msg)
}
// 上面已經使用 := 創造變數了, 所以可以繼續使用
result, msg = NetStatusCheck(NET_STATUS_LOBBY)
if result == true {
println("判斷2 msg=", msg)
}
// 使用 _ 來忽略不想處理的回傳參數
_, msg = NetStatusCheck(NET_STATUS_UNKNOW)
println("判斷3 msg=", msg)
}
package main
import (
"fmt"
)
// 列印map
func PrintfMap(m map[string]int) {
// 使用range 尋訪map
fmt.Println("使用range 尋訪map 長度=", len(m) )
for key, value := range m {
fmt.Println(fmt.Sprintf("key=%v, value=%v", key, value))
}
}
func main() {
// map 初始化 (要使用make)
m := make(map[string]int)
// var m map[string]int 錯誤的使用方式, 沒有使用make配置記憶體, 會導致程式panic噴掉
// 設定值
m["test"] = 1
m["dog"] = 10
m["cat"] = 111
m["monkey"] = 222
fmt.Printf("m=%v \r\n", m)
// 取得值1
value := m["cat"]
fmt.Printf("取得值1 value=%v\r\n", value)
// 取得值2
value, ret := m["dog"]
if ret == true {
fmt.Printf("取得值2 value=%v, ret=%v \r\n", value, ret)
}
// 列印map
PrintfMap(m)
// 刪除map某一個key => delete
delete(m, "dog")
// 列印map
PrintfMap(m)
}
package main
import (
"fmt"
)
func main() {
var a [5]int //宣告一個大小5的 int array, 陣列初始值為0
var b []int //宣告空的 int array
var c = []int{1, 2, 3, 4, 5} //宣告一個大小5的 int array, 並給初始值
// 尋訪陣列1
for i := 0; i < len(c); i++ {
value := c[i]
fmt.Println("尋訪陣列1 值=", value)
}
// 尋訪陣列2
for i := range c {
value := c[i]
fmt.Println("尋訪陣列2 值=", value)
}
fmt.Println("a=", a)
fmt.Println("b=", b)
fmt.Println("c=", c)
}
slice切出來的陣列其實都指向同一個陣列
所以操作時候要特別小心避免改到原始陣列
如果說想要複製一個新的陣列, 就使用 copy 來拷貝
完整範例
package main
import "fmt"
func main() {
var TraditionArray [10]int // 傳統陣列只有 length 沒有 cap
var list []int = make([]int, 5) // slice 有 length 有 cap, 當length超過cap, 則會自動擴充cap*2的長度 => 資料型態為int, 配置空間為5的陣列
var list2 []int = make([]int, 5)
var list4 []int // 未初始化
//var list3 []int = make([]int, 20)
for i := 0; i < 10; i++ {
list = append(list, i+1)
list2 = append(list2, i+10)
}
// 陣列[6] 到 陣列[9-1]
slice := list[6:9]
// 陣列[4] 到底
slice2 := list[4:]
// 陣列[0] 到 陣列[6-1]
slice5 := list[0:6]
// 冒號左邊沒寫數字代表預設0 => 陣列[0] 到 陣列[6-1]
slice6 := list[:6]
// COPY slice ( 若目的陣列 空間過小, 則copy時, 以目的陣列大小為主, 所以通常copy前, 目的陣列會配置跟來源陣列一樣大 )
var list3 []int = make([]int, len(list2)-5)
copy(list3, list2)
// 一次append 整個 list2, 但從 list[10] 到最後
list3 = append(list3, list2[10:]...)
fmt.Printf("TraditionArray(傳統陣列, 基本預設值就都是0了)=%v \r\n", TraditionArray)
fmt.Printf("list(有make初始化, 所以內容都塞0)=%v \r\n", list)
fmt.Printf("slice=%v \r\n", slice)
fmt.Printf("slice2=%v \r\n", slice2)
fmt.Printf("list3=%v \r\n", list3)
fmt.Printf("list4(沒有make初始化, 所以是nil)=%v \r\n", list4)
fmt.Printf("slice5=%v \r\n", slice5)
fmt.Printf("slice6=%v \r\n", slice6)
// 使用 range 搜尋陣列 (注意 他雖然可以每一個陣列都搜尋, 但不保證順序 )
// 要固定順序請使用 傳統搜尋陣列法
// 使用 range 搜尋陣列, data是拷貝出來的變數 並不等於 list3[n] 內的值
//
for i, data := range list3 {
fmt.Printf("使用range搜尋法 i=%d, data(指標)=%p, data(內容)=%v \r\n", i, &data, data)
}
// 傳統陣列搜尋法
for i := 0; i < len(list3); i++ {
pData := &list3[i]
fmt.Printf("傳統陣列搜尋法 i=%d, data(指標)=%p, data(內容)=%v \r\n", i, pData, pData)
}
}
package main
import "fmt"
// 列舉
const (
GameType_Poker int = iota // 撲克牌
GameType_Slot // 老虎機
GameType_Fish // 魚機
)
// 父類別
type GameBase struct {
GameId int
GameName string
}
// 衍生類別
type GameInfo struct {
GameBase // (大寫) 組合 ( c++ 的 has-a ) 公有物件
Money float32 // (大寫) 衍生類別的 public 公有變數
gameType int // (小寫) 衍生類別的 private 私有變數
}
// 建立一個遊戲物件
func NewGameInfo(gameId int, gameName string, gameType int) (pGame *GameInfo) {
pGame = &GameInfo{
// 父類別初始化
GameBase: GameBase{
GameId: gameId,
GameName: gameName,
},
// 衍生類別初始化
Money: 0.0,
gameType: gameType,
}
return
}
// GameInfo的函式 設定 gameType
func (pGame *GameInfo) SetGameType(gameType int) {
pGame.gameType = gameType
}
// GameInfo的函式 取得 gameType
func (game GameInfo) GetGameType() (gameType int) {
gameType = game.gameType
return
}
// GameInfo的函式 設定 money
func (pGame *GameInfo) SetMoney(money float32) {
pGame.Money = money
}
// GameInfo的函式 取得 Money
func (pGame *GameInfo) GetMoney() float32 {
return pGame.Money
}
func main() {
// 建立一個物件
gameFish := NewGameInfo(1001, "蟲蟲危機", GameType_Fish)
fmt.Printf("GameId=%v, GameName=%v, GameType=%v \r\n", gameFish.GameId, gameFish.GameName, gameFish.GetGameType())
gameFish.SetMoney(123.456)
fmt.Printf("money=%f \r\n", gameFish.GetMoney())
}
完整範例
package main
import "fmt"
type GameId int
// 遊戲列舉
const (
GameId_Unknow GameId = iota
GameId_Dodizu // 鬥地主
GameId_Big2 // 大老二
GameId_Slot // 老虎機
GameId_Fish // 魚機
)
// 定義介面 interface, 所有的遊戲都要實作相關函式
type IGame interface {
Init() // 遊戲初始化
Play() // 遊玩
// Process() // 遊戲邏輯
End() // 離開
}
// 鬥地主遊戲
type Dodizu struct {
Name string
GameId GameId
}
// 產生鬥地主控制器
func NewDodizu(name string, gameId GameId) *Dodizu {
pDodizu := &Dodizu{
Name: name,
GameId: gameId,
}
return pDodizu
}
// 每一個物件實作 interface 內的函示
func (pDodizu *Dodizu) Init() {
fmt.Println("Dodizu Init")
}
func (pDodizu *Dodizu) Play() {
fmt.Println("Dodizu Play")
}
func (pDodizu *Dodizu) End() {
fmt.Println("Dodizu End")
}
type Big2 struct {
Name string
GameId GameId
}
// 產生一個大老二物件
func NewBig2(name string, gameId GameId) *Big2 {
pBig2 := &Big2{
Name: name,
GameId: gameId,
}
return pBig2
}
// 每一個物件實作 interface 內的函示
func (pBig2 *Big2) Init() {
fmt.Println("Big2 Init")
}
func (pBig2 *Big2) Play() {
fmt.Println("Big2 Play")
}
func (pBig2 *Big2) End() {
fmt.Println("Big2 End")
}
// 工廠模式, 根據不同的GameId, 產生不同的 遊戲邏輯層物件
// 回傳時, 轉型成 IGame 介面, 如果遊戲物件沒有實作介面, 編譯會錯誤
func FactoryNewGame(gameName string, gameId GameId) IGame {
switch gameId {
case GameId_Dodizu:
return NewDodizu(gameName, gameId)
case GameId_Big2:
return NewBig2(gameName, gameId)
default:
panic(fmt.Sprintf("Unknown gameName=%s, GameId=%d", gameName, gameId))
}
}
// 將 interface 當參數傳進去, 會根據不同遊戲物件, 呼叫到相對應的函式
func Common_Init(iGame IGame) {
iGame.Init() // 介面直接呼叫 Init
}
func Common_Play(iGame IGame) {
iGame.Play() // 介面直接呼叫 Play
}
func Common_End(iGame IGame) {
iGame.End() // 介面直接呼叫 End
}
func main() {
fmt.Println("start Demo2 (簡易工廠模式 + 介面實作)")
// 介面
var GameList []IGame
var GameListTmp IGame
fmt.Printf("%v \r\n", GameListTmp)
// 產生鬥地主和大老二遊戲
gameDodizu := FactoryNewGame("鬥地主", GameId_Dodizu)
gameBig2 := FactoryNewGame("大老二", GameId_Big2)
// 使用方式1 將物件塞入 Interface列表內, 統一執行 ==========================
// 加入 GameList列表 [] 或是在 Table 物件內, 宣告一個 GameLogic IGame 變數, 在 GameLogic = gameDodizu 之後, 就可以直接使用 GameLogic.Init() ]
GameList = append(GameList, gameDodizu)
GameList = append(GameList, gameBig2)
// 因為放在動態陣列內, 所以依序掃秒陣列值
for _, game := range GameList {
// 呼叫遊戲邏輯層的init
game.Init()
// 呼叫遊戲邏輯層的Play
game.Play()
// 呼叫遊戲邏輯層的End
game.End()
}
// 其實直接 new 出物件後, 他會會傳介面, 就可以直接對介面做操作了
newGame := FactoryNewGame("新大老二", GameId_Big2)
newGame.Init()
newGame.Play()
newGame.End()
newGame2 := FactoryNewGame("新鬥地主", GameId_Dodizu)
newGame2.Init()
newGame2.Play()
newGame2.End()
// 使用方式2 將物件塞入 Common_XXX函式內, 根據不同物件執行相對應函式 ==========================
// 各遊戲初始化
Common_Init(newGame)
Common_Init(newGame2)
// 各遊戲執行
Common_Play(newGame)
Common_Play(newGame2)
// 各遊戲釋放
Common_End(newGame)
Common_End(newGame2)
fmt.Println("end Demo2")
}
// You can edit this code!
// Click here and start typing.
package main
import (
"fmt"
"sync"
"time"
)
var DataMutex sync.Mutex
var Value int // 全域同步資料
// 加法
func Add() {
DataMutex.Lock() // 上鎖
defer DataMutex.Unlock() // 解鎖, 用defer包起來, 是為了要確保一定會執行到 unlock
// 同步資料運算
Value++
}
// 減法
func Sub() {
DataMutex.Lock() // 上鎖
defer DataMutex.Unlock() // 解鎖, 用defer包起來, 是為了要確保一定會執行到 unlock
// 同步資料運算
Value--
}
// 處理執行序1
func Process1(jobName string) {
fmt.Println("Process1 開始 jobName=", jobName, " Value=", Value)
ProcessCount := 0
for {
// 離開 goroutine 的方法
ProcessCount++
if ProcessCount > 30 {
fmt.Println("Process1 離開迴圈 jobName=", jobName)
break
}
// 工作1 執行累加
Add()
time.Sleep(time.Millisecond)
}
fmt.Println("Process1 離開 jobName=", jobName)
}
// 處理執行序2
func Process2(jobName string) {
fmt.Println("Process2 開始 jobName=", jobName, " Value=", Value)
ProcessCount2 := 0
for {
// 離開 goroutine 的方法
ProcessCount2++
if ProcessCount2 > 40 {
fmt.Println("Process2 離開迴圈 jobName=", jobName)
break
}
// 工作2, 執行遞減
Sub()
time.Sleep(time.Millisecond)
}
fmt.Println("Process2 離開 jobName=", jobName)
}
func main() {
fmt.Println("DemoGoroutine 開始")
go Process1("啟動goroutine1") // 執行30次 add
go Process2("啟動goroutine2") // 執行40次 sub
// 等待10秒
time.Sleep(time.Second * 10)
fmt.Println("DemoGoroutine 結果 Value=", Value)
}
用來當作工作佇列(JobQueue)或是訊息佇列用(MessageQueue)
基本單一通道原理: 接收者跟傳送者都要準備好, 才能開始收發資料, 不然會阻塞卡在此處
建立通道
// 建立一個字元通道
var c chan string = make(chan string)
// 建立一個整數值通道
var i chan int = make(chan int)
fmt.Println("i=", i)
// 建立一個整數值有緩存100的通道 ( 代表資料塞到101筆時 會阻塞住 )
var iQueue chan int = make(chan int, 100)
fmt.Printf("iQueue=%v, len=%d, cap(長度)=%d \r\n", iQueue, len(iQueue), cap(iQueue))
接收通道內的資料, 將資料往通道內送
// 建立一個通道 make(chan string)
// 通道名稱為 c
// 通道內的資料為 string
var c chan string = make(chan string)
// 開啟一個goroutine, 來接收資料
go func() {
// 看箭頭方向, 箭頭從通道方向出來, 代表從通道內讀取資料
message := <-c
fmt.Printf("接收到通道內的資料 message=%s \r\n", message)
}()
fmt.Println("通道範例 執行開始")
// 看箭頭方向, 箭頭往通道, 代表塞資料進通道內
c <- "卡住"
fmt.Println("通道範例 睡眠中...")
time.Sleep(time.Minute * 1)
fmt.Println("通道範例 執行完畢")
完整範例
package main
import (
"fmt"
"time"
)
// 傳送方
func pinger(c chan string) {
fmt.Println("Run pinger....")
// 無窮迴圈
for i := 0; ; i++ {
msgData := fmt.Sprintf("ping i=%d", i)
// 將字串資料往通道內塞
// 由於是單一通道, 若另一端沒準備好, 則會阻塞在此
c <- msgData
}
}
// 接收方
func printer(c chan string) {
fmt.Println("Run printer....")
for {
// 從通道內接收資料 (資料型態是string)
// 由於是單一通道, 若另一端沒準備好, 則會阻塞在此
msgData := <-c
fmt.Println(msgData)
time.Sleep(time.Second * 1)
}
}
func main() {
// 建立一個通道 make(chan string)
// 通道名稱為 c
// 通道內的資料為 string
var c chan string = make(chan string)
// 呼叫 goroutine 將通道c, 當參數傳遞進去
go pinger(c) // 傳送方: 不斷的ping, 並且將資料送到 通道內
go printer(c) // 接收方: 不斷的收資料
// 睡10秒, 等待 goroutine 處理完畢
time.Sleep(time.Second * 10)
//var input string
//fmt.Scanln(&input)
}
這是一個小型websocket範例
所以有很多GameServer機制請再慢慢補上
安裝套件
go get github.com/gorilla/websocket
http://www.websocket.org/echo.html
ws://127.0.0.1:8899/echo
package main
import (
"log"
"net/http"
"time"
//"time"
"github.com/gorilla/websocket"
)
// 收到封包的callbackfunc
// 當有Client Connect時, 就會觸發此函式 ( 所以是多執行序並行處理 )
func RecvFunc(w http.ResponseWriter, r *http.Request) {
// websocket 環境設定 設定緩衝區, 封包是否壓縮
upgrader := &websocket.Upgrader{
//如果有 cross domain 的需求,可加入這個,不檢查 cross domain
CheckOrigin: func(r *http.Request) bool { return true },
ReadBufferSize: 10000,
EnableCompression: true,
}
// 取得 websocketObj => ws
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("upgrade:", err)
return
}
defer func() {
log.Println("disconnect !!")
// websocket 連線資源釋放
ws.Close()
}()
// TODO 將 websocketObj 儲存在動態陣列內(slice)
// 無窮迴圈, 這個client 的 connection, 不間斷的收發資料
for {
// 線上有發現即使close websocket, ReadMessage低機率不會return error
// 每次將收封包前都設定閒置 2分鐘 timeout , 太久沒反應直接把client斷線
deadline := time.Now().Add(time.Duration(2) * time.Minute)
ws.SetReadDeadline(deadline)
// 阻塞在此, 直到 ReadMessage 有讀取到資料
mtype, msg, err := ws.ReadMessage()
if err != nil {
log.Println("接收資料錯誤, 有client斷線了 mtype:", mtype, " err=", err)
// DOTO 如果有將 websocketObj儲存在slice內的話, 這邊要釋放client資源
break
}
// 列印收到的封包資料 通常是 json 字串
log.Printf("receive: %s\n", msg)
// DOTO: 通常封包資料是Json字串, 所以需要 json to obj
// DOTO: 根據obj 的封包資料, 來決定要呼叫哪個函式來處理
// DOTO: 打包封包回應資料 obj to json
// 簡單的將收到的封包送出 (WriteMessage 本身不支援多執行序, 需要用lock, unlock包起來, 或使用channel jobqueue包起來)
err = ws.WriteMessage(mtype, msg)
if err != nil {
log.Println("準備傳送資料 write:", err)
break
}
}
}
func main() {
// 設定收到封包的 callbackfunc 完整的gameServer Url= ws://127.0.0.1:8899/echo
http.HandleFunc("/echo", RecvFunc)
log.Println("server start at :8899")
// 啟動 websocket 模組, 會阻塞在此, 監聽Port:8899
log.Fatal(http.ListenAndServe(":8899", nil))
}
安裝套件
go get -u github.com/gorilla/mux
完整範例
package main
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
type Post struct {
ID string `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
}
var posts []Post
func getPosts(w http.ResponseWriter, r *http.Request) {
fmt.Println("收到封包 getPosts")
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(posts)
}
func createPost(w http.ResponseWriter, r *http.Request) {
fmt.Println("收到封包 createPost")
w.Header().Set("Content-Type", "application/json")
var post Post
_ = json.NewDecoder(r.Body).Decode(&post)
post.ID = strconv.Itoa(rand.Intn(1000000))
posts = append(posts, post)
json.NewEncoder(w).Encode(&post)
}
func getPost(w http.ResponseWriter, r *http.Request) {
fmt.Println("收到封包 getPost")
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
for _, item := range posts {
if item.ID == params["id"] {
json.NewEncoder(w).Encode(item)
return
}
}
json.NewEncoder(w).Encode(&Post{})
}
func updatePost(w http.ResponseWriter, r *http.Request) {
fmt.Println("收到封包 updatePost")
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
for index, item := range posts {
if item.ID == params["id"] {
posts = append(posts[:index], posts[index+1:]...)
var post Post
_ = json.NewDecoder(r.Body).Decode(&post)
post.ID = params["id"]
posts = append(posts, post)
json.NewEncoder(w).Encode(&post)
return
}
}
json.NewEncoder(w).Encode(posts)
}
func deletePost(w http.ResponseWriter, r *http.Request) {
fmt.Println("收到封包 deletePost")
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
for index, item := range posts {
if item.ID == params["id"] {
posts = append(posts[:index], posts[index+1:]...)
break
}
}
json.NewEncoder(w).Encode(posts)
}
func main() {
// 設定 監聽的 router
router := mux.NewRouter()
posts = append(posts, Post{ID: "1", Title: "My first post", Body: "This is the content of my first post"})
router.HandleFunc("/posts", getPosts).Methods("GET")
router.HandleFunc("/posts", createPost).Methods("POST")
router.HandleFunc("/posts/{id}", getPost).Methods("GET")
router.HandleFunc("/posts/{id}", updatePost).Methods("PUT")
router.HandleFunc("/posts/{id}", deletePost).Methods("DELETE")
// 啟動webapi, 阻塞監聽 port 8000
http.ListenAndServe(":8000", router)
}
驗證工具使用 post man
Golang 內建 記憶體回收
錯誤訊息 import cycle not allowed
發生原因
模組A import 模組B
模組B import 模組A
解決方法1
想辦法調整架構 模組B import 模組A
解決方法2
使用interface
websocket套件-gorilla
gameServer框架-mqant
後台GUI套件-goadmin
訊息佇列-rabbitmq
grpc