项目简介
程序功能
-
用户注册
注册新用户时,用户需设置一个唯一的用户名和一个密码。另外,还需登记邮箱及电话信息。
如果注册时提供的用户名已由其他用户使用,应反馈一个适当的出错信息;成功注册后,亦应反馈一个成功注册的信息。 -
用户登录
用户使用用户名和密码登录 Agenda 系统。
用户名和密码同时正确则登录成功并反馈一个成功登录的信息。否则,登录失败并反馈一个失败登录的信息。
代码传送门
程序结构
- 该程序使用cobra实现linux格式的命令行输入,比如:register -n xxx -p xxxx 将会尝试使用参数注册一个账号。
- 程序使用json格式文件存储系统及用户数据,数据存储的结构为:
- configure.json(存储所有文件的路径信息)
- user.json(存储所有已注册用户的基本信息,包括姓名和密码)
- xxx.json(xxx代表某个用户名,存储该用户的参与会议的信息,一个账号有且只有一个该文件)
- 使用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)
}
}
可见这里除了错误处理外只有三步:
- 函数 Marshal 将存储数据的结构转换成json格式的字符串并存储在[]byte类型的变量中。
- 函数 Indent 格式化json数据的格式(包括换行,缩进等等,否则写入的数据将只会占据一行)。
- 函数 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
}
逻辑依次下来:
- 检查用户输入的名称是否为空
- 检查用户输入的名称是否已被注册
- 用户是否未指定密码
询问用户是否使用默认密码- 是,则将该账号写入文件
- 否,则返回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
}
}
代码逻辑:
- 检查用户名是否为空
- 检查用户名是否存在
- 检查密码是否为空
- 空,提示用户输入密码
- 检查账号密码是否正确