iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 1
1

定義服務

這篇是我建一個部落格系統的心得,目標是能讓所有使用者能夠像階層式的管理自己的部落格文章,因為是學習用途,我會加上一些我想學的應用在系統裡面,主要語言會用到go與mysql,請多多指教。

先把想要完成的服務規劃好,方便動工,以下是一開始的規劃。

  • 使用者擁有自己的網址,發表的blog也有固定的網址且隸屬於使用者下
  • blog可以是文章,或其他音樂,影片等媒體
  • 多個blog可以組合在一起,定義為project,project也要有自己的網址
  • project可以包含其他project或blog

由此定義以下網址,之後搭配CRUD操作寫REST API

  • /使用者名
  • /使用者名/blog名
  • /使用者名/project名
  • /使用者名/project名*n/blog名

工具

這系列的文章會用到docker,主要的程式碼語言是go,雖然有docker就夠了,不過還是建議連go都安裝好

環境

先建立以下工作環境

.
├── app
│   ├── main.go
│   └── config
└── database
  • app 程式碼
  • config 設定檔案
  • database 資料庫資料

進入app後輸入

go mod init app

使用go mod的好處是會自動幫你拉import的第三方package,很方便。

題外話

如果不想把私密的檔案一起commmit,可以建一個.gitignore檔案來新增忽略名單

touch .gitignore

在裡面寫入

# 忽略設定檔
/config

# 忽略自己
.gitignore

這樣之後正常的git操作就會忽略這些檔案了

設定配置

應該沒有人會想把所有程序配置與公共參數全部都寫死在source code裡面吧(例如database的密碼)?

常見的作法是把參數寫在設定擋上,再寫一個配置pkg,統一讀取與管理。

設定檔

設定檔格式有很多選擇,簡單介紹一下

  • ini 對應key與value,value只支持單一常數,由section來分區,直觀好讀寫
  • json 對應key與value,但型態更豐富(對應JS object),缺點是沒有parser看起來會很痛苦,而且要寫一堆鬥好與引號
  • yaml 對應key與value,可以自訂型態,使用tab就能分出階層,直觀好讀寫
  • xml 跟html一樣我絕對不會想用來直接寫的東西...

這邊使用yaml來編寫。
在config下建立app資料夾,接著建立app.yaml,寫入

---
servers:
  main:
    host: local.com     # 自訂網址
    port: 80            # app綁定的port
    RunMode: debug      # gin run mode
    ReadTimeout: 60s    # http.server.ReadTimeout
    WriteTimeout: 60s   # http.server.WriteTimeout
...

先寫這些,之後可以視情況新增其他資料,有需要甚至可以將整個server struct都在yaml設定好。

code

我們只需要讀取,並不需要複雜的操作,所以yaml parser使用gopkg.in/yaml.v3,如果想要更複雜點可以試試github.com/goccy/go-yaml

在app中建立setting package在setting下創建setting.go,寫入

package setting

import (
	"gopkg.in/yaml.v3"
	"io/ioutil"
)

type ServerStruct struct {
	Host         string            `yaml:"host"`
	Port         int               `yaml:"port"`
	RunMode      string            `yaml:"RunMode,omitempty"`
	ReadTimeout  time.Duration     `yaml:"ReadTimeout,omitempty"`
	WriteTimeout time.Duration     `yaml:"WriteTimeout,omitempty"`
}

type ConfigStruct struct {
	Servers map[string]*ServerStruct
}

解釋

  • ServerStruct是對應yaml檔案的結構,之後app.yaml有改動就要跟著改
  • ConfigStruct是主要對外的型態,裡面的Servers是一個map,這樣參照設定檔就可以去調用對應的Server參數
  • yaml:"WriteTimeout,omitempty"對應yaml的WriteTimeout key,omitempty代表yaml檔中這field可以省略不寫

上面只是型態而已,接著

var (
	WorkPath = "./config/app/"
	Config *ConfigStruct
	Servers map[string]*ServerStruct
)

func init() {
	// read file
	config, err := ioutil.ReadFile(WorkPath + "app.yaml")
	if err != nil {
		panic(err)
	}

	// binding
	var c ConfigStruct
	err = yaml.Unmarshal(config, &c)
	if err != nil {
		panic(err)
	}

	Config = &c
	Servers = Config.Servers
}

解釋:

  • init是初始化函數,import這個package時,go會執行這個函數,之後來自其他的import不會重複執行init,有時你會看到import _ SomePackage,這就是代表只呼叫init,不import整個package的東西
  • go允許一個pkg中有多個init,如果所有init都在同個檔案那就是由上而下執行,但如果init分散在不同檔案呢?!這篇有提到是按照檔名順序,不過為了避免混亂,最好只寫一個init
  • 注意: var c ConfigStruct很重要,如果沒有給值,go初始化的物件都會有zero value,指標Config *ConfigStructzero valuenil,直接用nil去bind是會跳錯誤的,而var c ConfigStruct不是指標,而是一個型態zero value為**{map[]}**,可以自己用Println看看
  • Servers是我偷懶想要少打Config拉出來的

總結

先帶過設定檔,之後依照需求慢慢加入更多參數

現在的目錄長這樣

.
├── app
│   ├── config
│   │   └── app
│   │       └── app.yaml
│   ├── go.mod
│   ├── main.go
│   └── setting
│       └── setting.go
└── database

下一篇
Day2 路由配置
系列文
從coding到上線-打造自己的blog系統30

尚未有邦友留言

立即登入留言