程序完整代码请看github,该程序为个人完成。
Go Online平台的分享链接
Cobra包
- 输入以下指令安装
sys
和text
项目git clone https://github.com/golang/text git clone https://github.com/golang/sys
- 输入
go install github.com/spf13/cobra/cobra
安装cobra
包,在此之前请确认已经安装spf13
包。此时,若安装成功,在bin
文件夹下有conbra.exe
可执行文件。 - 在
$GOPATH/src
路径下输入..\bin\cobra.exe init agenda
指令会调用cobra
包生成子样例文件夹agenda
,在该目录下执行go run main.go
指令,输出以下内容。 - 此时输入指令
..\..\bin cobra.exe add test
指令,创建main.go
的子程序test.go
,更改test.go
中init
添加新参数user
即输入代码testCmd.Flags().StringP("user", "u", "", "test")
后改动变量var testCmd
中的匿名函数Run
,添加代码parameter, _ := cmd.Flags().GetString("user")
获取标签信息并输出,输出结果如下,其中可以通过-u Simon
指定参数user
值。
Agenda
Agenda
的目录路径如下agenda │ main.go │ agenda.log | LICENSE │ └───service │ │ Data_process.go │ │ Date.go │ │ Log.go │ │ Meeting.go │ │ Service.go │ │ User.go │ │ │ └───data │ │ User.txt │ │ Metting.txt │ └───cmd │ agenda.go │ root.go
前期准备
- 数据存储
- 根据
Agenda
指令,需要记录用户User
和会议Meeting
信息,其中分别保存在data/User.txt
和/data/Meeting.txt
中
- 根据
- Data_process.go
- 由于需要在用户注册时判断用户名是否存在以及用户登陆时用户信息是否正确,程序执行前,需要先从上述文件中读取相应信息。以
Meeting
信息为例。 - 信息的储存与读取都是按照
json
格式操作,因此需要对Meeting
成员进行encode、decode
处理。其中分别调用json.Marshal
和json.Unmarshal
函数
func meeting_decode(json_info []byte) Meeting{ var res Meeting err := json.Unmarshal(json_info, &res) if err != nil { defer log.Println("[Error] Get meeting error!") defer fmt.Println("[Error] Get meeting error!") } return res } func meeting_encode(meeting Meeting)[]byte{ json_info, err := json.Marshal(meeting) if err != nil{ defer log.Println("[Error] Write meeting error!") defer fmt.Println("[Error] Write meeting error!") } return json_info }
- 使用
bufio.NewReader
函数打开相应文件并调用ReadString('\n')
函数每次读取一行数据或调用WriteString
函数每次写入一行数据并写入'\n'
数据,并进行相应的编码、解码操作。以read_meetings
为例,返回读取的所有Meeting
信息。
func read_meetings() []Meeting{ file, err := os.Open("./service/data/Meeting.txt") if err != nil { panic(err) } defer file.Close() var res []Meeting meetings := bufio.NewReader(file) for { meeting_json, err := meetings.ReadString('\n') if err != nil || io.EOF == err { break } res = append(res, meeting_decode([]byte(meeting_json))) } return res }
- 由于需要在用户注册时判断用户名是否存在以及用户登陆时用户信息是否正确,程序执行前,需要先从上述文件中读取相应信息。以
- User.go
- 根据需求,用户注册时需要记录用户名、密码、邮箱、电话以及参与的会议记录即使用以下结构体保存用户名信息。
type User struct{ User_name string User_password string User_email string User_phone int User_meeting []Meeting }
- 按照要求,使用
create_user
函数创建用户,其中需要确认传入的参数是否符合需求,包括user_phone
是否满足11位要求,user_email
是否满足对应的规则。调用regexp
库使用正则表达式处理
func isPhone(phone int) bool{ res, _ := regexp.MatchString("^1[0-9]{10}$", strconv.Itoa(phone)) if !res{ defer log.Println("[Error] Phone's format is not correct!") defer fmt.Println("[Error] Phone's format is not correct!") } return res } func isEmail(email string) bool{ res, _ := regexp.MatchString("^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$", email) if !res{ defer log.Println("[Error] Email's format is not correct!") defer fmt.Println("[Error] Email's format is not correct!") } return res }
- 还需要确认用户名是否重复,通过遍历从
User.txt
中读取的users
信息查重即可。
for _,user := range users{ if user.User_name == name { defer log.Println("[Error] User's name has exist!") defer fmt.Println("[Error] User's name has exist!") return false } }
- 当且仅当上述满足要求时,创建对应
user
对象并添加到users
中
user := User{name, password, email, phone, empty} users = append(users, user)
- 根据需求,用户注册时需要记录用户名、密码、邮箱、电话以及参与的会议记录即使用以下结构体保存用户名信息。
- Date.go
- 会议创建时需要判断会议的时间是否有效,其中根据月份等要求判断是否满足一定逻辑。
func isValid(date Date) bool{ if date.Year < 1000 || date.Year > 9999 || date.Month > 12 || date.Month < 1 || date.Hour > 24 || date.Hour < 0 || date.Minute > 60 || date.Minute < 0 || date.Day < 1{ return false } switch date.Day { case 1, 3, 5, 7, 8, 10, 12: if date.Day > 31 { return false } case 2: if date.Year % 4 == 0{ if date.Day > 29{ return false } }else{ if date.Day > 28{ return false } } default: if date.Day > 30{ return false } } return true }
- 会议创建时要判断成员之间是否存在时间冲突,根据要求,创建
compare_date
函数判断日期大小,具体代码此处不贴出。 - 从终端读取
string
需要进一步转化为data
对象,其中包含DateToString
和StringToDate
,此处设定的规则为yyyy-mm-dd-hh:mm
对应date
对象中的year month day hour minute
func DateToString(date Date) string{ res := strconv.Itoa(date.Year) + "-" + strconv.Itoa(date.Month) + "-" + strconv.Itoa(date.Day) + "-" + strconv.Itoa(date.Hour) + "-" + strconv.Itoa(date.Minute) return res }
- Meeting.go
- 创建会议时需要记录会议发起者、参加者以及会议时间,对应的
Meeting
结构体如下
type Meeting struct{ Participators []string //just record user's name Sponsor,Title string Start_time, End_time Date }
- 调用
crete_meeting
函数创建会议时,需要调用check_meeting
判断对应参数是否出错,其中包含调用isValid、compare_date
确认日期是否满足需求、判断Title
是否唯一、判断用户名是否有重复以及用户参加的会议是否有冲突或用户是否存在或重复,其中部分条件检测代码如下:
func check_meeting(res Meeting, participators []string, sponsor, title string, start_time, end_time Date) bool{ if !isValid(start_time) { defer log.Println("[Error] Error start time!") defer fmt.Println("[Error] Error start time!") return false } if !isValid(end_time) { defer log.Println("[Error] Error end time!") defer fmt.Println("[Error] Error end time!") return false } if compare_date(start_time, end_time) { // check date defer log.Println("[Error] start time should earily than end time!") defer fmt.Println("[Error] start time should earily than end time!") return false } for _, meeting := range meetings{ // check title if meeting.Title == title { defer log.Println("[Error] Meeting's title has exist!") defer fmt.Println("[Error] Meeting's title has exist!") return false } } for _, par := range participators{ user_exist := false for _, user := range users{ if user.User_name == par{ //check user existence user_exist = true for _, user_meeting := range user.User_meeting{ // check whether user meetings overlap if isOverlap(res, user_meeting){ defer log.Println("[Error] Some users'meetings may overlap!") defer fmt.Println("[Error] Some users'meetings may overlap!") return false } } } } if !user_exist{ defer log.Println("[Error] Some users may not exist") defer fmt.Println("[Error] Some users may not exist") return false } } for i := 0; i < len(participators); i++{ // check whether users duplicate for j := i + 1; j < len(participators); j++{ if participators[i] == participators[j]{ defer log.Println("[Error] Some users may not duplicate") defer fmt.Println("[Error] Some users may not duplicate") return false } } } return true } func isOverlap(meeting1, meeting2 Meeting) bool{ // false mean not overlap, input is one person's two meetings if compare_date(meeting1.Start_time, meeting2.End_time) || compare_date(meeting2.Start_time, meeting1.End_time){ return false }else{ return true } }
- 删除会议有两种情况,直接删除会议或删除某个成员,但无论那种情况下删除会议前都需要通过会议信息删除对应成员的会议记录,其中调用
delete_user_meeting
删除掉该成员的该条会议信息。 - 首先需要找到用户名对应的
users
下标并调用append
函数删除用户名参加的会议Title
对应的会议。
func delete_user_meeting(meeting Meeting, user_name string) bool{//delete meeting from users user_idx := -1 for index, user_tmp := range users{ if user_tmp.User_name == user_name{ user_idx = index break } } if user_idx == -1{ defer log.Println("[Error] User does not partipant current meeting!") defer fmt.Println("[Error] User does not partipant current meeting!") return false } for index, meeting_tmp := range users[user_idx].User_meeting{ if meeting_tmp.Title == meeting.Title { users[user_idx].User_meeting = append(users[user_idx].User_meeting[:index], users[user_idx].User_meeting[index + 1 :]...) break } } return true }
- 以删除会议为例,删除整个会议前需要对会议的参加者和发起者都调用
delete_user_meeting
函数后再调用append
函数从meetings
中删除对应会议对象。而删除会议特定成员只需要找到对应的成员是否存在会议中并调用相关函数即可
func delete_meeting(meeting_idx int) bool{ meetings[meeting_idx].Participators = append(meetings[meeting_idx].Participators, meetings[meeting_idx].Sponsor) for _, user := range meetings[meeting_idx].Participators { //update all influenced user info delete_user_meeting(meetings[meeting_idx], user) } meetings = append(meetings[:meeting_idx], meetings[meeting_idx + 1 : ]...) defer log.Println("[Success] Delete meeting successful!") defer fmt.Println("[Success] Delete meeting successful!") return true }
- 创建会议时需要记录会议发起者、参加者以及会议时间,对应的
- Log.go
- 根据题目要求,将程序的输出保存到日志中,对应的```Log.go``中代码如下:
type LogFile struct{ file string } func (Log *LogFile) Write(p []byte) (int, error) { f, err := os.OpenFile(Log.file, os.O_CREATE|os.O_APPEND, 0666) defer f.Close() if err != nil { return -1, err } return f.Write(p) }
- 由于程序运行过程中也需要提示用户操作步骤是否出错或成功,因此输出包含
fmt
和log
同时执行,如
log.Println("[Error] Some info do not fit the standard!") fmt.Println("[Error] Some info do not fit the standard!")
- Service.go
- 上述代码准备完成后,配置
UI
代码Service.go
,此处包含对Agenda
的初始化以及提供agenda.go
使用的交互函数如Login_in()、Create_user()
,以大写字母开头表示public
允许函数在包外调用,此处需要注意的是,由于agenda.go
与Data_process.go
不在同级目录下,因此如果要在包外执行程序,对应的txt
文件路径要有相应的变化。 - 用户选择注册操作时,会调用相应的
Create_user
函数,其中直接调用create_user
即可,而当注册成功后会默认进入Login_in()
函数,需要验证用户名与密码信息是否存在与匹配。
for index, user := range users{ if user.User_name == name && user.User_password == password{ user_index = index break } }
- 如果匹配,则进入
process
函数即用户交互界面,其中提供了5个功能,分别为退出、创建会议、删除会议、删除会议成员、查询当前用户会议记录
。每个功能对应不同函数,并分别调用上述所讲的对应函数进行操作。当运行结束后,则写入信息。
func process(){ var choice string for { fmt.Println("--q input q to quit agenda") fmt.Println("--cm create a new meeting") fmt.Println("--mr remove a meeting") fmt.Println("--pr remove a particopator from a meeting") fmt.Println("--qm query current user's meeting") fmt.Scanln(&choice) if(choice == "q"){ break }else if(choice == "cm"){ cm_cmd() }else if(choice == "mr"){ mr_cmd() }else if(choice == "pr"){ pr_cmd() }else if(choice == "qm"){ qm_cmd() } } write_users() write_meetings() }
- 以
cm_cmd
函数为例,其中需要创建会议,因此需要用户输入会议信息后调用create_meeting
函数即可。
func cm_cmd(){ var participators []string //sponsor is default to be current user var par, par_number, title, start_time, end_time string fmt.Println("Input your meeting's participators number: ") fmt.Scanln(&par_number) par_num, _ := strconv.Atoi(par_number) for i := 0; i < par_num ; i++{ fmt.Println("Input number ", i, "participator's name") fmt.Scanln(&par) participators = append(participators, par) } fmt.Println("Input your meeting's title: ") fmt.Scanln(&title) fmt.Println("Input your meeting's start time: ") fmt.Scanln(&start_time) fmt.Println("Input your meeting's end time: ") fmt.Scanln(&end_time) create_meeting(participators, users[user_index].User_name, title, StringToDate(start_time), StringToDate(end_time)) }
- 上述代码准备完成后,配置
- Agenda.go
- 此时调用
Cobra
包生成相应的文件便于执行程序,按照上述做法创建Agenda
后修改相应的匿名函数
和Init
,具体为通过命令行获取相应的初始化信息如用户登陆/注册前需要用到的信息而非直接进入process
函数进行用户交互。 - 其中
Init
函数如下:
agendaCmd.Flags().StringP("user_name", "u", "", "user's name") agendaCmd.Flags().StringP("user_password", "p", "", "user's password") agendaCmd.Flags().StringP("user_email", "e", "", "user's email") agendaCmd.Flags().IntP("user_telephone", "t", 0, "user's telephone number") agendaCmd.Flags().BoolP("register", "r", false, "register a new user and login in") agendaCmd.Flags().BoolP("login_in", "l", false, "login in with current user")
Run
对应添加的代码如下:
Run: func(cmd *cobra.Command, args []string) { fmt.Println("agenda called") user_name, _ := cmd.Flags().GetString("user_name") user_password, _ := cmd.Flags().GetString("user_password") user_email, _ := cmd.Flags().GetString("user_email") user_phone, _ := cmd.Flags().GetInt("user_telephone") register, _ := cmd.Flags().GetBool("register") login, _ := cmd.Flags().GetBool("login_in") if(register && login){ log.Println("[Error] Do not set -l and -r true together!") fmt.Println("[Error] Do not set -l and -r true together!") return } if(register){ service.Init() if service.Create_user(user_name, user_password, user_email, user_phone){ service.Login_in(user_name, user_password) }else{ log.Println("[Error] Some info do not fit the standard!") fmt.Println("[Error] Some info do not fit the standard!") } } if(login){ service.Init() service.Login_in(user_name, user_password) } },
- 此时调用
测试
- 在
agenda
目录下运行go run main.go agenda -h
指令获取对应命令行提示信息,如下: - 此时输入添加相应的指令如
-r true -u d -p 123456 -t 11111111111 -e 111111@qq,com
输入必要信息以此注册用户,其中需要注意-l -r
指令默认为false
需要指定为true
且不能两个同时为true
否则会报错。若用户名不存在则创建成功,输出以下内容 - 若输入信息不全或格式不正确,则输出以下内容:
- 若用户名存在,则输出以下内容:
- 此时尝试登陆指令,由于之前注册登陆过使用
-l true -u a -p 123456
进入相应账号。
- 输入指令
cm
可以创建会议,根据相应指示输入参数,如下
- 当输入的用户不存在或参数格式不正确或参数重复或时间冲突等情况下,会报相应错误,此处不细讲,具体的可以自己运行程序尝试。
- 此时调用
qm
可以查询当前用户的会议信息,如下 - 调用
pr
移除特定Title
的会议成员,如下:
- 调用
mr
则移除整个对应Title
会议,此时再次输入qm
指令输出为空,操作如下:
- 观察
User.txt
文件,发现其中users
信息以json
格式保存: - 同理,
Meeting.txt
如下 - 观察输出日志
agenda.log
,其中记录了操作过程中的相关信息,如下
- 然而程序运行需要在
go online
上运行,而相关配置与visual studio code
不同,相关包的安装使用有问题,因此只能将文件压缩到main.go
中并修改了相应的UI界面,直接输入指令go run main.go
进入程序并按照相似指令操作即可,即
- 其中保存数据的
txt
文件正常运行无误,Agenda
执行完毕。