CLI 命令行實用程序開發實戰 - Agenda
導航
前言
這是中山大學數據科學與計算機學院2019年服務計算的作業項目。所有代碼與博客將被上傳至Go-Online與github當中。(Go-Online與github上代碼在路徑略有差異,主要是Go-Online上不能按相對路徑來導入本地packege)
Go-Online項目地址: http://www.go-online.org.cn:8080/share/bmrc08e76kvn1rs2tr20?secret=false
Github項目地址: https://github.com/StarashZero/ServerComputing/tree/master/hw4
個人主頁: https://starashzero.github.io
實驗要求: https://pmlpml.github.io/ServiceComputingOnCloud/ex-cli-agenda
概述
命令行實用程序並不是都象 cat、more、grep 是簡單命令。go 項目管理程序,類似 java 項目管理 maven、Nodejs 項目管理程序 npm、git 命令行客戶端、 docker 與 kubernetes 容器管理工具等等都是採用了較複雜的命令行。即一個實用程序同時支持多個子命令,每個子命令有各自獨立的參數,命令之間可能存在共享的代碼或邏輯,同時隨着產品的發展,這些命令可能發生功能變化、添加新命令等。因此,符合 OCP 原則 的設計是至關重要的編程需求。
項目要求
用戶註冊
- 註冊新用戶時,用戶需設置一個唯一的用戶名和一個密碼。另外,還需登記郵箱及電話信息。
- 如果註冊時提供的用戶名已由其他用戶使用,應反饋一個適當的出錯信息;成功註冊後,亦應反饋一個成功註冊的信息。
用戶登錄
- 用戶使用用戶名和密碼登錄 Agenda 系統。
- 用戶名和密碼同時正確則登錄成功並反饋一個成功登錄的信息。否則,登錄失敗並反饋一個失敗登錄的信息。
用戶登出
- 已登錄的用戶登出系統後,只能使用用戶註冊和用戶登錄功能。
用戶查詢
- 已登錄的用戶可以查看已註冊的所有用戶的用戶名、郵箱及電話信息。
用戶刪除
- 已登錄的用戶可以刪除本用戶賬戶(即銷號)。
- 操作成功,需反饋一個成功註銷的信息;否則,反饋一個失敗註銷的信息。
- 刪除成功則退出系統登錄狀態。刪除後,該用戶賬戶不再存在。
- 用戶賬戶刪除以後:
- 以該用戶爲 發起者 的會議將被刪除
- 以該用戶爲 參與者 的會議將從 參與者 列表中移除該用戶。若因此造成會議 參與者 人數爲0,則會議也將被刪除。
創建會議
- 已登錄的用戶可以添加一個新會議到其議程安排中。會議可以在多個已註冊
用戶間舉行,不允許包含未註冊用戶。添加會議時提供的信息應包括:- 會議主題(title)(在會議列表中具有唯一性)
- 會議參與者(participator)
- 會議起始時間(start time)
- 會議結束時間(end time)
- 注意,任何用戶都無法分身參加多個會議。如果用戶已有的會議安排(作爲發起者或參與者)與將要創建的會議在時間上重疊 (允許僅有端點重疊的情況),則無法創建該會議。
- 用戶應獲得適當的反饋信息,以便得知是成功地創建了新會議,還是在創建過程中出現了某些錯誤。
增刪會議參與者
- 已登錄的用戶可以向 自己發起的某一會議增加/刪除 參與者 。
- 增加參與者時需要做 時間重疊 判斷(允許僅有端點重疊的情況)。
- 刪除會議參與者後,若因此造成會議 參與者 人數爲0,則會議也將被刪除。
查詢會議
- 已登錄的用戶可以查詢自己的議程在某一時間段(time interval)內的所有會議安排。
- 用戶給出所關注時間段的起始時間和終止時間,返回該用戶議程中在指定時間範圍內找到的所有會議安排的列表。
- 在列表中給出每一會議的起始時間、終止時間、主題、以及發起者和參與者。
- 注意,查詢會議的結果應包括用戶作爲 發起者或參與者 的會議。
取消會議
- 已登錄的用戶可以取消 自己發起 的某一會議安排。
- 取消會議時,需提供唯一標識:會議主題(title)。
退出會議
- 已登錄的用戶可以退出 自己參與 的某一會議安排。
- 退出會議時,需提供一個唯一標識:會議主題(title)。若因此造成會議 參與者 人數爲0,則會議也將被刪除。
清空會議
- 已登錄的用戶可以清空 自己發起 的所有會議安排。
項目結構與思路
- 項目結構:
GoOnline截不下來全部結構,所以用的IDEA的截圖。
myAgenda中存放User
和Meeting
對象讀寫與處理邏輯
cmd中存放命令實現代碼 - 程序思路:
由於以前使用c++實現過了Agenda,因此這次更多的還是代碼翻譯,結合go語言的特性進行優化。
代碼解釋與測試
由於工作量較多,時間比較緊促,所以不一定能測試到所有方面,程序可能存在未發現的Bug。
myAgenda
myAgenda中存放User
和Meeting
對象讀寫與處理邏輯,包括Date、Meeting、Storage、User四個文件,所有的成員都是公開的,省去get、set的編寫以減少工作量。
- Date
Date包裝程序需要使用的時期信息,並提供一些功能函數
包含的數據有年、月、日、時、分
需要對用戶輸入的時期進行檢驗,判斷其是否合法,因此Date文件包含一個判斷時期合法的函數IsValidtype Date struct { M_year int M_month int M_day int M_hour int M_minute int }
在判斷查詢Meeting與創建Meeting時需要對時期進行比較,因此Date提供一個比較函數CompareDate//判斷一個Date結構體數據是否合法 var date = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; func isleap(year int) bool { if year%4 == 0 { if year%400 != 0 && year%100 == 0 { return false } return true } return false } func IsValid(t_date Date) bool { judge := true if isleap(t_date.M_year) { date[1] = 29 } else { date[1] = 28 } if t_date.M_year > 9999 || t_date.M_year < 1000 { judge = false } else if t_date.M_month > 12 || t_date.M_month < 1 { judge = false } else if t_date.M_day > date[t_date.M_month-1] || t_date.M_day < 1 { judge = false } else if t_date.M_hour > 23 || t_date.M_hour < 0 { judge = false } else if t_date.M_minute > 59 || t_date.M_minute < 0 { judge = false } return judge }
從用戶接收的時期參數是以字符串形式存在,因此Date提供一個將string轉爲Date結構體的函數StringToDate(格式爲yyyy-mm-dd/hh:mm)func CompareDate(date1, date2 Date) int { if date1.M_year < date2.M_year { return -1 } else if date1.M_year > date2.M_year { return 1 } if date1.M_month < date2.M_month { return -1 } else if date1.M_month > date2.M_month { return 1 } if date1.M_day < date2.M_day { return -1 } else if date1.M_day > date2.M_day { return 1 } if date1.M_hour < date2.M_hour { return -1 } else if date1.M_hour > date2.M_hour { return 1 } if date1.M_minute < date2.M_minute { return -1 } else if date1.M_minute > date2.M_minute { return 1 } return 0 }
同理,查詢Meeting時需要將時期轉變爲字符串,因此Date提供一個將Date結構體轉變爲string的函數DateToString(格式爲yyyy-mm-dd/hh:mm)//從string轉爲Date func StringToDate(str string) Date { var d Date fmt.Sscanf(str, "%d-%d-%d/%d:%d", &d.M_year, &d.M_month, &d.M_day, &d.M_hour, &d.M_minute) return d }
//從Date轉爲string func (d *Date) DateToString() string { var str string str = fmt.Sprintf("%04d-%02d-%02d/%02d:%02d", d.M_year, d.M_month, d.M_day, d.M_hour, d.M_minute) return str }
- User
User包裝用戶的信息,包含的數據有用戶名、密碼、郵箱、手機號//User包裝用戶信息 type User struct { M_name string M_password string M_email string M_phone string }
- Meeting
Meeting包裝會議的信息,並提供一些功能函數
包含的數據有標題、主持人、參與者、開始時間、結束時間
需要對Meeting的參與者進行增加和刪除,因此Meeting提供了AddParticipator與RemoveParticipator兩個函數//Meeting包裝會議數據 type Meeting struct { M_sponsor string M_participators []string M_startDate Date M_endDate Date M_title string }
需要判斷一個用戶是否爲參與者,因此Meeting提供了IsParticipator函數//刪除一個參與者 func (m *Meeting)RemoveParticipator(t_participator string) { for element := 0; element< len(m.M_participators);element++{ if m.M_participators[element] == t_participator{ m.M_participators = append(m.M_participators[0:element], m.M_participators[element+1:len(m.M_participators)]...) break } } } //添加一個參與者 func (m *Meeting)AddParticipator(t_participator string) { m.M_participators = append(m.M_participators, t_participator) }
//判斷參與者是否在會議中 func (m Meeting)IsParticipator(t_participator string) bool { for element := 0; element< len(m.M_participators);element++{ if m.M_participators[element] == t_participator{ return true } } return false }
- Storage
Storage存儲程序所需的一切信息,並提供增刪、查詢、修改、文件讀寫等操作
包含的數據有用戶列表、會議列表、登錄用戶列表。
需要從文件中讀取三個列表的數據,因此Storage提供ReadFromFile函數,對用戶列表與會議列表以json形式讀取,對登錄用戶列表以text形式讀取//Storage存儲用戶與會議數據 type Storage struct { m_userList list.List m_meetingList list.List m_curUserList list.List }
需要將三個列表寫回文件中,因此Storage提供WriteToFile函數,對用戶列表與會議列表以json形式讀取,對登錄用戶列表以text形式讀取//從文件中讀取數據 func (s *Storage) ReadFormFile() bool { userFin, uErr := os.OpenFile("user.json", os.O_CREATE, 0666) if uErr != nil { fmt.Fprintf(os.Stderr, "could not open user file\n") return false } defer userFin.Close() userReader := bufio.NewReader(userFin) for { line, crc := userReader.ReadString('\n') if crc != nil && len(line) == 0 { break } var user User var jsonData []byte fmt.Sscanf(line, "%s\n", &jsonData) json.Unmarshal(jsonData, &user) s.m_userList.PushBack(user) } meetingFin, mErr := os.OpenFile("meeting.json", os.O_CREATE, 0666) if mErr != nil { fmt.Fprintf(os.Stderr, "could not open meeting file\n") return false } defer meetingFin.Close() meetingReader := bufio.NewReader(meetingFin) for { line, crc := meetingReader.ReadString('\n') if crc != nil && len(line) == 0 { break } var meeting Meeting var jsonData []byte fmt.Sscanf(line, "%s\n", &jsonData) json.Unmarshal(jsonData, &meeting) s.m_meetingList.PushBack(meeting) } curFin, cErr := os.OpenFile("curUser.txt", os.O_CREATE, 0666) if cErr != nil { fmt.Fprintf(os.Stderr, "could not open curUser file\n") return false } defer curFin.Close() curReader := bufio.NewReader(curFin) for { line, crc := curReader.ReadString('\n') if crc != nil && len(line) == 0 { break } var username string fmt.Sscanf(line, "%s\n", &username) s.m_curUserList.PushBack(username) } return true }
需要對用戶進行增刪、查詢操作,因此Storage提供CreateUser、DeleteUser、QueryUser三個函數,其中QueryUser需要提供一個選擇函數filter作爲參數//將數據寫入文件 func (s *Storage) WriteToFile() bool { userFout, uErr := os.Create("user.json") if uErr != nil { fmt.Fprintf(os.Stderr, "could not open user file\n") return false } defer userFout.Close() for { if s.m_userList.Len() == 0 { break } element := s.m_userList.Front() user := element.Value.(User) jsonData, jErr := json.Marshal(user) if jErr != nil { panic(jErr) } fmt.Fprintf(userFout, "%s\n", jsonData) s.m_userList.Remove(element) } meetingFout, mErr := os.Create("meeting.json") if mErr != nil { fmt.Fprintf(os.Stderr, "could not open meeting file\n") return false } defer meetingFout.Close() for { if s.m_meetingList.Len() == 0 { break } element := s.m_meetingList.Front() meeting := element.Value.(Meeting) jsonData, jErr := json.Marshal(meeting) if jErr != nil { panic(jErr) } fmt.Fprintf(meetingFout, "%s\n", jsonData) s.m_meetingList.Remove(element) } curFout, cErr := os.Create("curUser.txt") if cErr != nil { fmt.Fprintf(os.Stderr, "could not open curUser file\n") return false } defer curFout.Close() for { if s.m_curUserList.Len() == 0 { break } element := s.m_curUserList.Front() user := element.Value.(string) fmt.Fprintf(curFout, "%s\n", user) s.m_curUserList.Remove(element) } return true }
需要對登錄用戶進行增刪、查詢操作,因此Storage提供CreateCurUser、DeleteCurUser、QueryCurUser三個函數,其中QueryCurUser需要提供一個選擇函數filter作爲參數//創建用戶 func (s *Storage) CreateUser(u User) { s.m_userList.PushBack(u) } //刪除用戶 func (s *Storage) DeleteUser(u User) { for i := s.m_userList.Front(); i != nil; i = i.Next() { user := i.Value.(User) if user.M_name == u.M_name { s.m_userList.Remove(i) break } } } //查詢用戶 func (s *Storage) QueryUser(filter func(User) bool) list.List { var res list.List for i := s.m_userList.Front(); i != nil; i = i.Next() { user := i.Value.(User) if filter(user) { res.PushBack(user) } } return res }
需要對會議進行增刪、查詢、修改操作,因此Storage提供CreateMeeting、DeleteMeeting、QueryMeeting、UpdateMeeting四個個函數,其中QueryCurUser需要提供一個選擇函數filter作爲參數,UpdateMeeting需要提供一個一個選擇函數filter和一個修改函數switcher作爲參數//創建登錄用戶 func (s *Storage) CreateCurUser(username string) { s.m_curUserList.PushBack(username) } //刪除登錄用戶 func (s *Storage) DeleteCurUser(username string) { for i := s.m_curUserList.Front(); i != nil; i = i.Next() { user := i.Value.(string) if username == user { s.m_curUserList.Remove(i) break } } } //查詢登錄用戶 func (s *Storage) QueryCurUser(filter func(string) bool) list.List { var res list.List for i := s.m_curUserList.Front(); i != nil; i = i.Next() { user := i.Value.(string) if filter(user) { res.PushBack(i.Value.(string)) } } return res }
//創建會議 func (s *Storage) CreateMeeting(m Meeting) { s.m_meetingList.PushBack(m) } //查詢會議 func (s *Storage) QueryMeeting(filter func(Meeting) bool) list.List { var res list.List for i := s.m_meetingList.Front(); i != nil; i = i.Next() { meeting := i.Value.(Meeting) if filter(meeting) { res.PushBack(meeting) } } return res } //更新會議 func (s *Storage) UpdateMeeting(filter func(Meeting) bool, switcher func(*Meeting)) int { count := 0 for i := s.m_meetingList.Front(); i != nil; i = i.Next() { meeting := i.Value.(Meeting) if filter(meeting) { switcher(&meeting) i.Value = meeting count++ } } return count } //刪除會議 func (s *Storage) DeleteMeeting(filter func(Meeting) bool) int { count := 0 for i := s.m_meetingList.Front(); i != nil; { meeting := i.Value.(Meeting) if filter(meeting) { j := i i = i.Next() s.m_meetingList.Remove(j) count++ } else { i = i.Next() } } return count }
cmd
cmd中存放命令實現代碼,作爲用戶使用程序的接口,包括了要求中的所有需求。
- 用戶註冊: register
register進行用戶註冊- 用戶需要提供四個參數: 用戶名、密碼、郵箱、手機號
registerCmd.Flags().StringP("username", "u", "username", "Username") registerCmd.Flags().StringP("password", "p", "password", "Password") registerCmd.Flags().StringP("email", "e", "[email protected]", "Email") registerCmd.Flags().StringP("phone", "t", "123456789", "Phone")
- 獲得參數後首先需要判斷需要註冊的用戶是否已存在(用戶名唯一),使用Storage提供的QueryUser進行查詢,若用戶已存在,則返回報錯信息
valid := s.QueryUser(func(user myAgenda.User) bool { return user.M_name == username }) if valid.Len()!=0{ fmt.Fprintf(os.Stderr, "Register fail!: User exited!\n") return }
- 若用戶名不存在,則進行註冊,使用Storage提供的CreateUser與WriteToFile保存信息,並輸出正確信息
s.CreateUser(myAgenda.User{ username, password, email, phone, }) s.WriteToFile() fmt.Println("Register successed")
- 測試:
- 用戶需要提供四個參數: 用戶名、密碼、郵箱、手機號
- 用戶登錄: logIn
logIn進行用戶登錄- 用戶需要提供用戶名與密碼
logInCmd.Flags().StringP("username", "u", "username", "Username") logInCmd.Flags().StringP("password", "p", "password", "Password")
- 拿到參數後首先比對用戶與密碼是否正確,若不正確則返回報錯信息
valid := s.QueryUser(func(user myAgenda.User) bool { return user.M_name == username && user.M_password == password }) if valid.Len()==0{ fmt.Fprintf(os.Stderr, "Log in failed!: username or password error\n") return }
- 然後查詢用戶是否已經登錄,若已登錄則返回報錯信息
curValid := s.QueryCurUser(func(s string) bool { return s == username }) if curValid.Len()!=0{ fmt.Fprintf(os.Stderr, "Log in failed!: User has logged in!\n") return }
- 若沒有問題,則進行登錄,輸出成功信息
s.CreateCurUser(username) s.WriteToFile() fmt.Println("Log in successed")
- 測試:
- 用戶需要提供用戶名與密碼
- 用戶登出: logOut
logOut進行用戶登出- 用戶需要提供用戶名和密碼
logOutCmd.Flags().StringP("username", "u", "username", "Username") logOutCmd.Flags().StringP("password", "p", "password", "Password")
- 比對用戶名與密碼是否正確,不正確則返回報錯信息
valid := s.QueryUser(func(user myAgenda.User) bool { return user.M_name == username && user.M_password == password }) if valid.Len()==0{ fmt.Fprintf(os.Stderr, "Log out failed!\n") return }
- 判斷用戶是否已登錄,未登錄則返回報誤信息
curValid := s.QueryCurUser(func(s string) bool { return s == username }) if curValid.Len()==0{ fmt.Fprintf(os.Stderr, "LogOut fail!: User not logged in!\n") return }
- 無誤則進行用戶登出,並輸出成功信息
s.DeleteCurUser(username) s.WriteToFile() fmt.Println("LogOut successed")
- 測試:
- 用戶需要提供用戶名和密碼
- 用戶刪除: cancel
cancel進行用戶註銷- 用戶需要提供用戶名和密碼
cancelCmd.Flags().StringP("username", "u", "username", "username") cancelCmd.Flags().StringP("password", "p", "password", "password")
- 比對用戶名與密碼是否正確,不正確則返回報錯信息
valid := s.QueryUser(func(user myAgenda.User) bool { return user.M_name == username && user.M_password == password }) if valid.Len()==0{ fmt.Fprintf(os.Stderr, "Cancel failed!: Incorrect username or password!\n") return }
- 註銷時需要刪除用戶主持的所有會議
s.DeleteMeeting(func(meeting myAgenda.Meeting) bool { return meeting.M_sponsor == username })
- 還需要將用戶所有參與的會議中刪除用戶
s.UpdateMeeting(func(meeting myAgenda.Meeting) bool { return meeting.IsParticipator(username) }, func(meeting *myAgenda.Meeting) { meeting.RemoveParticipator(username) })
- 可能存在會議參與者爲0的情況,需要將這些會議刪除
s.DeleteMeeting(func(meeting myAgenda.Meeting) bool { return len(meeting.M_participators) == 0 })
- 如果用戶已登錄,則從登陸列表中刪除
curValid := s.QueryCurUser(func(s string) bool { return s == username }) if curValid.Len()!=0{ s.DeleteCurUser(username) }
- 最後進行用戶刪除,並輸出成功信息
s.DeleteUser(valid.Front().Value.(myAgenda.User)) s.WriteToFile() fmt.Println("Cancel successed")
- 測試(僅展示部分情況):
- 用戶需要提供用戶名和密碼
- 用戶查詢: listUsers
listUsers列出當前存在的所有用戶- 用戶需要提供一個已登錄的用戶名(因爲允許多賬號登陸)
listUsersCmd.Flags().StringP("username", "u", "username", "username")
- 判斷用戶提供的用戶名是否已登錄,未登錄則返回報錯信息
curValid := s.QueryCurUser(func(s string) bool { return s == username }) if curValid.Len()==0{ fmt.Fprintf(os.Stderr, "ListUsers failed!: User not log in!\n") return }
- 若已登錄則查詢所有的用戶並輸出
valid := s.QueryUser(func(user myAgenda.User) bool { return true }) for i:=valid.Front();i!=nil;i=i.Next(){ fmt.Printf("Username: %s\tEmail: %s\tPhone: %s\n", i.Value.(myAgenda.User).M_name, i.Value.(myAgenda.User).M_email, i.Value.(myAgenda.User).M_phone) }
- 測試:
- 用戶需要提供一個已登錄的用戶名(因爲允許多賬號登陸)
- 創建會議: createMeeting
createMeeting用於創建會議- 用戶需要提供會議的主持人、標題、開始時間、結束時間、參與者人數(稍後會要求輸入參與者)
createMeetingCmd.Flags().StringP("sponsor", "u", "sponsor", "Sponsor of meeting") createMeetingCmd.Flags().StringP("title", "t", "title", "Title of meeting") createMeetingCmd.Flags().StringP("startTime", "s", "start time", "Start time of meeting(yyyy-mm-dd/hh:mm)") createMeetingCmd.Flags().StringP("endTime", "e", "end time", "End time of meeting(yyyy-mm-dd/hh:mm)") createMeetingCmd.Flags().IntP("participatorNumber", "p", 0, "Number of participator")
- 主持人必須爲已經登錄的用戶,若未登錄,則輸出報錯信息
valid := s.QueryCurUser(func(username string) bool { return username == sponsor }) if valid.Len() == 0 { fmt.Fprintf(os.Stderr, "Create Meeting failed!: Sponsor not log in!\n") return }
- 輸入的日期必須有效,並且開始日期不能大於結束日期
d1, d2 := myAgenda.StringToDate(startTime), myAgenda.StringToDate(endTime) if !(myAgenda.IsValid(d1) && myAgenda.IsValid(d2)) { fmt.Fprintf(os.Stderr, "Create Meeting failed!: StartDate or endDate isn't valid!\n") return } if myAgenda.CompareDate(d1, d2) != -1 { fmt.Fprintf(os.Stderr, "Create Meeting failed!: StartDate more than or equal to endDate!\n") return }
- 參與者人數必須大於0,否則報錯
if participatorNumberTime <= 0 { fmt.Fprintf(os.Stderr, "Create Meeting failed!: The num of participator less than or equal zero!\n") return }
- 會議標題不能重複,否則報錯
valid = s.QueryMeeting(func(meeting myAgenda.Meeting) bool { return meeting.M_title == title }) if valid.Len() != 0 { fmt.Fprintf(os.Stderr, "Create Meeting failed!: Title has existed!\n") return }
- 無誤則要求用戶輸入參與者
var participators []string for i := 0; i < participatorNumberTime; i++ { fmt.Printf("Please enter NO.%d participator: ", i+1) var participator string fmt.Scanf("%s\n", &participator) participators = append(participators, participator) }
- 檢查主持人是否存在時間衝突(其參與的會議時間不能重疊)
valid = s.QueryMeeting(func(meeting myAgenda.Meeting) bool { return (!((myAgenda.CompareDate(meeting.M_startDate, d2) >= 0) || (myAgenda.CompareDate(meeting.M_endDate, d1) <= 0))) && (meeting.IsParticipator(sponsor) || (meeting.M_sponsor == sponsor)) }) if valid.Len() != 0 fmt.Fprintf(os.Stderr, "Create Meeting failed!: Conflicting meeting of sponsor!(date)\n") return }
- 對於每個參與者進行檢測:參與者必須已註冊、主持人不能是參與者、不能有相同的參與者、不能存在時間衝突
for i := 0; i < len(participators); i++ { if participators[i] == sponsor { fmt.Fprintf(os.Stderr, "Create Meeting failed!: The sponsor can't be the participator!\n") return } valid := s.QueryUser(func(user myAgenda.User) bool { return user.M_name == participators[i] }) if valid.Len() == 0 { fmt.Fprintf(os.Stderr, "Create Meeting failed!: Participator isn't in user!\n") return } valid = s.QueryMeeting(func(meeting myAgenda.Meeting) bool { return (!((myAgenda.CompareDate(meeting.M_startDate, d2) >= 0) || (myAgenda.CompareDate(meeting.M_endDate, d1) <= 0))) && (meeting.IsParticipator(participators[i]) || (meeting.M_sponsor == participators[i])) }) if valid.Len() != 0 { fmt.Fprintf(os.Stderr, "Create Meeting failed!: Conflicting meeting of participator!(date)\n") return } for j := i+1; j < len(participators); j++ { if participators[i] == participators[j] { fmt.Fprintf(os.Stderr, "Create Meeting failed!: Multiple participator!\n") return } } }
- 無誤則創建會議並輸出成功信息
meeting := myAgenda.Meeting{ sponsor, participators, d1, d2, title, } s.CreateMeeting(meeting) s.WriteToFile() fmt.Println("CreateMeeting successed")
- 測試:
- 用戶需要提供會議的主持人、標題、開始時間、結束時間、參與者人數(稍後會要求輸入參與者)
- 增加會議參與者: addMeetingParticipator
addMeetingParticipator用於添加會議的參與者- 用戶需要輸入主持人、標題、待添加的參與者
addMeetingParticipatorCmd.Flags().StringP("sponsor", "u", "sponsor", "Sponsor of meeting") addMeetingParticipatorCmd.Flags().StringP("title", "t", "title", "Title of meeting") addMeetingParticipatorCmd.Flags().StringP("participator", "p", "participator", "participator need to be added")
- 判斷主持人與參與者是否合法: 主持人需登錄、參與者需註冊且不與主持人相同
if sponsor == participator { fmt.Fprintf(os.Stderr, "AddMeetingParticipator failed!: The sponsor can't be the participator!\n") return } valid := s.QueryCurUser(func(username string) bool { return username == sponsor }) if valid.Len() == 0 { fmt.Fprintf(os.Stderr, "AddMeetingParticipator failed!: Sponsor not log in!\n") return } valid = s.QueryUser(func(user myAgenda.User) bool { return user.M_name == participator }) if valid.Len() == 0 { fmt.Fprintf(os.Stderr, "AddMeetingParticipator failed!: Participator isn't in user!\n") return }
- 參與者不能存在時間衝突
cnt := s.UpdateMeeting(func(meeting myAgenda.Meeting) bool { if meeting.M_title == title && meeting.M_sponsor == sponsor && !meeting.IsParticipator(participator) { valid = s.QueryMeeting(func(meeting2 myAgenda.Meeting) bool { return (!(myAgenda.CompareDate(meeting2.M_startDate, meeting.M_endDate) >= 0 || myAgenda.CompareDate(meeting2.M_endDate, meeting.M_startDate) <= 0)) && (meeting2.IsParticipator(participator) || meeting2.M_sponsor == participator) }) return valid.Len() == 0 } return false }, func(meeting *myAgenda.Meeting) { meeting.AddParticipator(participator) }) if cnt == 0 { fmt.Fprintf(os.Stderr, "AddMeetingParticipator failed!: Conflicting meeting of participator!(date or title)\n") return }
- 無誤則將參與者添加到會議當中
s.WriteToFile() fmt.Println("AddMeetingParticipator successed")
- 測試:
- 用戶需要輸入主持人、標題、待添加的參與者
- 查詢會議: meetingQuery
meetingQuery用於查詢會議- 用戶需要提供用戶名、開始時間、結束時間
meetingQueryCmd.Flags().StringP("username", "u", "username", "Username") meetingQueryCmd.Flags().StringP("startTime", "s", "start time", "Start time(yyyy-mm-dd/hh:mm)") meetingQueryCmd.Flags().StringP("endTime", "e", "end time", "End time(yyyy-mm-dd/hh:mm)")
- 用戶需要登錄,未登錄則報錯
valid := s.QueryCurUser(func(user string) bool { return user == username }) if valid.Len() == 0 { fmt.Fprintf(os.Stderr, "MeetingQuery failed!: User not log in!\n") return }
- 通過查詢找出在用戶主持或參與的時間段內的所有會議
d1, d2 := myAgenda.StringToDate(startTime), myAgenda.StringToDate(endTime) if myAgenda.IsValid(d1)&&myAgenda.IsValid(d2){ valid := s.QueryMeeting(func(meeting myAgenda.Meeting) bool { return (meeting.M_sponsor==username||meeting.IsParticipator(username))&&!(myAgenda.CompareDate(d2,meeting.M_startDate)<0||myAgenda.CompareDate(d1,meeting.M_endDate)>0) }) for i:=valid.Front();i!=nil;i=i.Next(){ met := i.Value.(myAgenda.Meeting) fmt.Printf("Title: %s\tSponser: %s\tStart Time: %s\tEnd Time: %s\tParticipators: %s\n", met.M_title,met.M_sponsor, met.M_startDate.DateToString(), met.M_endDate.DateToString(), met.M_participators) } } fmt.Println("MeetingQuery successed")
- 測試:
- 用戶需要提供用戶名、開始時間、結束時間
- 刪除會議參與者: removeMeetingParticipator
removeMeetingParticipator用於移除會議中的參與者- 用戶需要提供主持人、標題、待刪除的參與者
removeMeetingParticipatorCmd.Flags().StringP("sponsor", "u", "sponsor", "Sponsor of meeting") removeMeetingParticipatorCmd.Flags().StringP("title", "t", "title", "Title of meeting") removeMeetingParticipatorCmd.Flags().StringP("participator", "p", "participator", "Participator need to be removed")
- 主持人必須已登錄
valid := s.QueryCurUser(func(username string) bool { return username == sponsor }) if valid.Len() == 0 { fmt.Fprintf(os.Stderr, "RemoveMeetingParticipator failed!: Sponsor not log in!\n") return }
- 查找匹配的會議,並將參與者從中刪除
cnt:=s.UpdateMeeting(func(meeting myAgenda.Meeting) bool { return (meeting.M_title==title)&&(meeting.M_sponsor==sponsor)&&(meeting.IsParticipator(participator)) }, func(meeting *myAgenda.Meeting) { meeting.RemoveParticipator(participator) }) if cnt==0{ fmt.Fprintf(os.Stderr, "RemoveMeetingParticipator failed!: No matching meeting or participator!\n") return }
- 可能存在參與者爲0的會議,將其刪除
s.DeleteMeeting(func(meeting myAgenda.Meeting) bool { return len(meeting.M_participators) == 0 }) s.WriteToFile() fmt.Println("RemoveMeetingParticipator successed")
- 測試:
- 用戶需要提供主持人、標題、待刪除的參與者
- 退出會議: quitMeeting
quitMeeting用於退出一個會議- 用戶需要提供用戶名、標題
quitMeetingCmd.Flags().StringP("username", "u", "username", "Username") quitMeetingCmd.Flags().StringP("title", "t", "title", "Title of meeting")
- 用戶必須已登錄,否則報錯
valid := s.QueryCurUser(func(user string) bool { return user == username }) if valid.Len() == 0 { fmt.Fprintf(os.Stderr, "QuitMeeting failed!: User not log in!\n") return }
- 查詢符合條件的會議,並將用戶從參與者中刪除
cnt:=s.UpdateMeeting(func(meeting myAgenda.Meeting) bool { return meeting.M_title==title&&meeting.IsParticipator(username)&&meeting.M_sponsor!=username }, func(meeting *myAgenda.Meeting) { meeting.RemoveParticipator(username) }) if cnt==0{ fmt.Fprintf(os.Stderr, "QuitMeeting failed!: No matching meeting for username or username is the sponsor!\n") return }
- 可能存在參與者爲0的會議,將其刪除
s.DeleteMeeting(func(meeting myAgenda.Meeting) bool { return len(meeting.M_participators) == 0 })
- 測試:
- 用戶需要提供用戶名、標題
- 取消會議: deleteMeeting
deleteMeeting用於刪除一個會議- 用戶需要輸入主持人、標題
deleteMeetingCmd.Flags().StringP("sponsor", "u", "sponsor", "Sponsor of meeting") deleteMeetingCmd.Flags().StringP("title", "t", "title", "Title of meeting")
- 主持人必須已登錄,否則報錯
valid := s.QueryCurUser(func(username string) bool { return username == sponsor }) if valid.Len() == 0 { fmt.Fprintf(os.Stderr, "DeleteMeeting failed!: Sponsor not log in!\n") return }
- 查詢符合條件的會議並將其刪除
cnt:=s.DeleteMeeting(func(meeting myAgenda.Meeting) bool { return meeting.M_sponsor==sponsor&&meeting.M_title==title }) if cnt==0{ fmt.Fprintf(os.Stderr, "DeleteMeeting failed!: No matching meeting for username!\n") return } s.WriteToFile() fmt.Println("DeleteMeeting successed")
- 測試:
- 用戶需要輸入主持人、標題
- 清空會議: deleteAllMeeting
deleteAllMeeting用於清空會議- 用戶需要輸入主持人
deleteAllMeetingCmd.Flags().StringP("sponsor", "u", "sponsor", "Sponsor")
- 主持人必須已登錄,否則報錯
valid := s.QueryCurUser(func(username string) bool { return username == sponsor }) if valid.Len() == 0 { fmt.Fprintf(os.Stderr, "DeleteAllMeeting failed!: Sponsor not log in!\n") return }
- 將主持人創建的所有會議刪除
cnt:=s.DeleteMeeting(func(meeting myAgenda.Meeting) bool { return meeting.M_sponsor == sponsor }) if cnt==0{ fmt.Fprintf(os.Stderr, "DeleteAllMeeting failed!: No matching meeting for username!\n") return } s.WriteToFile() fmt.Println("DeleteAllMeeting successed")
- 測試:
- 用戶需要輸入主持人