cobra實現agenda

項目簡介

程序功能

  • 用戶註冊
    註冊新用戶時,用戶需設置一個唯一的用戶名和一個密碼。另外,還需登記郵箱及電話信息。
    如果註冊時提供的用戶名已由其他用戶使用,應反饋一個適當的出錯信息;成功註冊後,亦應反饋一個成功註冊的信息。

  • 用戶登錄
    用戶使用用戶名和密碼登錄 Agenda 系統。
    用戶名和密碼同時正確則登錄成功並反饋一個成功登錄的信息。否則,登錄失敗並反饋一個失敗登錄的信息。


代碼傳送門

cobra–Agenda

程序結構

  1. 該程序使用cobra實現linux格式的命令行輸入,比如:register -n xxx -p xxxx 將會嘗試使用參數註冊一個賬號。
  2. 程序使用json格式文件存儲系統及用戶數據,數據存儲的結構爲:
    • configure.json(存儲所有文件的路徑信息)
    • user.json(存儲所有已註冊用戶的基本信息,包括姓名和密碼)
    • xxx.json(xxx代表某個用戶名,存儲該用戶的參與會議的信息,一個賬號有且只有一個該文件)
  3. 使用log包來進行日誌記錄和消息打印。日誌文件結構爲:
    • sys.log(記錄程序運行中與用戶使用無關的程序內部的錯誤或者提示)
    • register.log(記錄用戶在註冊賬號時產生的消息提示,包括註冊成功或者失敗,失敗的原因等等)
    • login.log(記錄用戶在登錄賬號時產生的消息提示)
    • xxx_l.log(記錄名稱爲xxx的賬號在使用過程中產生的消息提示)

代碼邏輯介紹

  • 程序首先讀取configure.json文件中的文件路徑信息,然後根據這些路徑依次打開對應文件並保存指向這些文件的指針。如果發現某個文件並不存在,則會嘗試首先創建該文件。
  • 期間所有出現的消息提示都會通過一個統一的日誌處理函數來寫入對應log後綴文件,並根據參數決定是否輸出在屏幕。
  • 在系統日誌文件sys.log還未被創建或者打開,以及提供的日誌文件指針不存在時,所有產生的錯誤都將直接輸出到屏幕。
  • 程序退出前,將會把存儲路徑信息的變量重新寫入到configure.json文件中進行同步,並通過保存的文件指針關閉所有文件訪問接口。

json讀取與寫入

涉及json文件的所有操作都被集中在jsonTool.go文件中。

func ReadJson(a interface{}, path string) {
	Bytes, err := ioutil.ReadFile(path)
	if err != nil {
		MakeLog("json read error", err.Error(), Files["sys"], false)
		os.Exit(1)
	}
	err = json.Unmarshal(Bytes, a)
	if err != nil {
		MakeLog("Unmarshal error", err.Error(), Files["sys"], false)
		os.Exit(1)
	}
}

函數需要的參數有一個用於存儲json格式信息的指針,以及讀取文件的路徑。
這裏利用 ioutil 包中的 ReadFile 函數來進行文件的讀取。
之後利用 encoding/json 包中的json解析函數 Unmarshal 函數來將格式爲[]byte的信息解析到對應的go格式的結構中去(這裏就是傳入的參數 a)。
這裏舉一個register的例子:

{
	"users": [
		{
			"name": "manager",
			"password": "root"
		},
		{
			"name": "userT1",
			"password": "7uy7"
		}
	]
}

賬戶信息對應的go格式的結構爲:

type Users struct{
	Name string `json:"name"`
	Password string `json:"password"`
}

type Registers struct {
	Users []Users `json:"users"`
}

var Names Registers

結構的成員中導出的字段首字母需要大寫,註釋中的名稱需要和json文件中的完全一致

函數中出現的 MakeLog 函數就是統一處理日誌的入口,將在後面介紹。

func WriteJson(a interface{}, path string) {
	Bytes, err := json.Marshal(a)
	if err != nil {
		MakeLog("json error", err.Error(), Files["sys"], false)
		os.Exit(1)
	}
	var out bytes.Buffer
	err = json.Indent(&out, Bytes, "", "\t")
	if err != nil {
		MakeLog("json convert error", err.Error(), Files["sys"], false)
		os.Exit(1)
	}
	err = ioutil.WriteFile(path, out.Bytes(), 666)
	if err != nil {
		MakeLog("json write error", err.Error(), Files["sys"], false)
		os.Exit(1)
	}
}

可見這裏除了錯誤處理外只有三步:

  1. 函數 Marshal 將存儲數據的結構轉換成json格式的字符串並存儲在[]byte類型的變量中。
  2. 函數 Indent 格式化json數據的格式(包括換行,縮進等等,否則寫入的數據將只會佔據一行)。
  3. 函數 ioutil.WriteFile 用於將存儲了信息的字符串寫入到對應文件中去。

這裏需要注意的是,使用ioutil包只是因爲更加方便。程序運行過程中保存了所有打開文件的指針,因此更好的做法是直接利用文件指針操作文件。


文件打開與讀取

這部分被放置在Tool.go文件的初始化函數中( init ),在程序的開頭被調用。

var Paths = make(map[string]string)
var Files = make(map[string]*os.File)

func init() {
	readConfigure()
	for key, value := range Paths {
		file, err := os.OpenFile(value, os.O_RDWR | os.O_APPEND, 666)
		if err != nil {
			if os.IsNotExist(err) {
				file, err = os.Create(value)
			}else {
				if key == "sys" {
					panic(err)
				}else {
					MakeLog("file error", err.Error(), Files["sys"], false)
					os.Exit(1)
				}
			}
		}
		Files[key] = file
		message := GetMessage("file opened: ", Paths[key])
		MakeLog("file normal", message, Files["sys"], false)
	}
}

func readConfigure() {
	ReadJson(&paths, configurePath)
	for _, value := range paths.Files {
		Paths[value.Short] = value.Path
	}
}

func GetMessage(messages ...string) string {
	message := ""
	for _, str := range messages {
		message += str
	}
	return message
}

首先通過函數 readConfigure 來將路徑信息讀取到Paths變量中,程序保存了configure.json的路徑。
然後通過遍歷paths來創建訪問對應文件的指針。所有指針也會被存儲起來。
Paths以及Files變量均通過一個簡短的索引來訪問(map類型變量)。

這裏還存在一個用於生成日誌信息的函數 GetMessage ,用於拼接所需的信息。


日誌處理

func MakeLog(prefix, message string, writer io.Writer, output bool) {
	if writer == nil {
		panic(errors.New("file to write can't find"))
	}
	prefix = fmt.Sprintf("[%s] ", prefix)
	log.SetPrefix(prefix)
	log.SetFlags(log.Ltime | log.Lshortfile)
	log.SetOutput(writer)
	log.Println(message)
	if output {
		fmt.Println(prefix + message)
	}
}

log 包中提供了設置日誌信息的前綴,格式以及輸出目標的函數。這裏就是使用這些函數來統一處理每一條日誌信息。
output 爲一個bool類型參數,一旦該參數爲真,則這條信息同時會被打印到標準輸出。

文件創建

func CreateFile(short, path string) {
	_, exists := Paths[short]
	if !exists {
		file, err := os.Create(path)
		if err != nil {
			MakeLog("create file error", err.Error(), Files["sys"], false)
			os.Exit(1)
		}
		Files[short] = file
		Paths[short] = path
		paths.Files = append(paths.Files, File{short, path})
		message := "file created: "
		message += path
		MakeLog("file create", message, Files["sys"], false)
	}
}

統一的文件創建函數,首先查看Paths中是否已經存在該文件,不存在的話則創建該文件,並將該文件及其指針添加到Paths和Files中去。程序末尾將會同步Paths和configure.json中的內容。


至此所有的工具類函數都介紹完畢了。接下來就是交互邏輯了。

用戶註冊

這部分內容被放置在 Register.go 文件中。
使用命令:
register -n name -p password

func Check(name, password string) bool {
	if name == "" {
		tool.MakeLog("format error", "empty name", tool.Files["reg"], true)
		return false
	}
	for _, user := range tool.Names.Users {
		if name == user.Name {
			message := tool.GetMessage("duplicate name: ", name)
			tool.MakeLog("repetition error", message, tool.Files["reg"], true)
			return false
		}
	}
	if password == tool.DefaultPassword {
		var check string
		fmt.Println("Sure to use the default password: " + tool.DefaultPassword + " ?(y/n)")
		_, err := fmt.Scanf("%s", &check)
		if err == nil {
			if check == "y" || check == "Y" {
				return true
			}else {
				tool.MakeLog("failed try", "fail to register", tool.Files["reg"], true)
				return false
			}
		}else {
			tool.MakeLog("input error", err.Error(), tool.Files["sys"], false)
			os.Exit(1)
		}
	}
	return true
}

邏輯依次下來:

  1. 檢查用戶輸入的名稱是否爲空
  2. 檢查用戶輸入的名稱是否已被註冊
  3. 用戶是否未指定密碼
    詢問用戶是否使用默認密碼
    • 是,則將該賬號寫入文件
    • 否,則返回false
func AddUser(name, password string) {
	user := tool.Users{Name: name, Password: password}
	tool.Names.Users = append(tool.Names.Users, user)
	tool.WriteJson(tool.Names, tool.Paths["user"])
	tool.CreateFile(name, tool.GetJsonPath(name))
	short := tool.GetMessage(name, "_l")
	tool.CreateFile(short, tool.GetLogPath(short))
	message := tool.GetMessage("add user: ", name, " succeed")
	tool.MakeLog("add user", message, tool.Files["reg"], true)
}

如果 Check 函數返回true,則調用 AddUser 函數,將該用戶的名稱和密碼封裝到對應結構中去,然後調用 WriteJson 函數寫入該賬號信息。
同時爲該賬號生成屬於它的json以及log文件。


用戶登入

命令:
login -n name -p password
login -n name

func CheckLogin(name, password string) bool {
	if name == "" {
		tool.MakeLog("format error", "login with empty name", tool.Files["login"], true)
		return false
	}
	var num = -1
	for n, user := range tool.Names.Users {
		if name == user.Name {
			num = n
			break
		}
	}
	if num == -1 {
		tool.MakeLog("login error", "login with wrong name", tool.Files["login"], true)
		return false
	}
	if password == "_default_" {
		var pass string
		fmt.Println("Enter your password: ")
		_, err := fmt.Scanf("%s", &pass)
		if err == nil {
			password = pass
		}else {
			tool.MakeLog("input error", err.Error(), tool.Files["sys"], false)
			os.Exit(1)
		}
	}
	if tool.Names.Users[num].Password == password {
		message := tool.GetMessage("login with name: ", name, " succeed")
		tool.MakeLog("login normal", message, tool.Files["login"], false)
		LName = name
		//LPassword = password
		return true
	}else {
		tool.MakeLog("login fail", "login with wrong password", tool.Files["login"], true)
		return false
	}
}

代碼邏輯:

  1. 檢查用戶名是否爲空
  2. 檢查用戶名是否存在
  3. 檢查密碼是否爲空
    • 空,提示用戶輸入密碼
  4. 檢查賬號密碼是否正確
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章