CLI 命令行實用程序開發實戰 - Agenda

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 原則 的設計是至關重要的編程需求。

項目要求

(老師課程要求至少實現兩個命令,由於時間比較充足,所以我就全實現了)

用戶註冊

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

用戶登錄

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

用戶登出

  1. 已登錄的用戶登出系統後,只能使用用戶註冊和用戶登錄功能。

用戶查詢

  1. 已登錄的用戶可以查看已註冊的所有用戶的用戶名、郵箱及電話信息。

用戶刪除

  1. 已登錄的用戶可以刪除本用戶賬戶(即銷號)。
  2. 操作成功,需反饋一個成功註銷的信息;否則,反饋一個失敗註銷的信息。
  3. 刪除成功則退出系統登錄狀態。刪除後,該用戶賬戶不再存在。
  4. 用戶賬戶刪除以後:
    • 以該用戶爲 發起者 的會議將被刪除
    • 以該用戶爲 參與者 的會議將從 參與者 列表中移除該用戶。若因此造成會議 參與者 人數爲0,則會議也將被刪除。

創建會議

  1. 已登錄的用戶可以添加一個新會議到其議程安排中。會議可以在多個已註冊
    用戶間舉行,不允許包含未註冊用戶。添加會議時提供的信息應包括:
    • 會議主題(title)(在會議列表中具有唯一性)
    • 會議參與者(participator)
    • 會議起始時間(start time)
    • 會議結束時間(end time)
  2. 注意,任何用戶都無法分身參加多個會議。如果用戶已有的會議安排(作爲發起者或參與者)與將要創建的會議在時間上重疊 (允許僅有端點重疊的情況),則無法創建該會議。
  3. 用戶應獲得適當的反饋信息,以便得知是成功地創建了新會議,還是在創建過程中出現了某些錯誤。

增刪會議參與者

  1. 已登錄的用戶可以向 自己發起的某一會議增加/刪除 參與者 。
  2. 增加參與者時需要做 時間重疊 判斷(允許僅有端點重疊的情況)。
  3. 刪除會議參與者後,若因此造成會議 參與者 人數爲0,則會議也將被刪除。

查詢會議

  1. 已登錄的用戶可以查詢自己的議程在某一時間段(time interval)內的所有會議安排。
  2. 用戶給出所關注時間段的起始時間和終止時間,返回該用戶議程中在指定時間範圍內找到的所有會議安排的列表。
  3. 在列表中給出每一會議的起始時間、終止時間、主題、以及發起者和參與者。
  4. 注意,查詢會議的結果應包括用戶作爲 發起者或參與者 的會議。

取消會議

  1. 已登錄的用戶可以取消 自己發起 的某一會議安排。
  2. 取消會議時,需提供唯一標識:會議主題(title)。

退出會議

  1. 已登錄的用戶可以退出 自己參與 的某一會議安排。
  2. 退出會議時,需提供一個唯一標識:會議主題(title)。若因此造成會議 參與者 人數爲0,則會議也將被刪除。

清空會議

  1. 已登錄的用戶可以清空 自己發起 的所有會議安排。

項目結構與思路

  • 項目結構:
    在這裏插入圖片描述
    GoOnline截不下來全部結構,所以用的IDEA的截圖。
    myAgenda中存放UserMeeting對象讀寫與處理邏輯
    cmd中存放命令實現代碼
  • 程序思路:
    由於以前使用c++實現過了Agenda,因此這次更多的還是代碼翻譯,結合go語言的特性進行優化。

代碼解釋與測試

由於工作量較多,時間比較緊促,所以不一定能測試到所有方面,程序可能存在未發現的Bug。

myAgenda

myAgenda中存放UserMeeting對象讀寫與處理邏輯,包括Date、Meeting、Storage、User四個文件,所有的成員都是公開的,省去get、set的編寫以減少工作量。

  • Date

    Date包裝程序需要使用的時期信息,並提供一些功能函數
    包含的數據有年、月、日、時、分
    type Date struct {
        M_year   int
        M_month  int
        M_day    int
        M_hour   int
        M_minute int
    }
    
    需要對用戶輸入的時期進行檢驗,判斷其是否合法,因此Date文件包含一個判斷時期合法的函數IsValid
    //判斷一個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
    }
    
    在判斷查詢Meeting與創建Meeting時需要對時期進行比較,因此Date提供一個比較函數CompareDate
    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
    }
    
    從用戶接收的時期參數是以字符串形式存在,因此Date提供一個將string轉爲Date結構體的函數StringToDate(格式爲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
    }
    
    同理,查詢Meeting時需要將時期轉變爲字符串,因此Date提供一個將Date結構體轉變爲string的函數DateToString(格式爲yyyy-mm-dd/hh:mm)
    //從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包裝會議數據
    type Meeting struct {
        M_sponsor string
        M_participators []string
        M_startDate Date
        M_endDate Date
        M_title string
    }
    
    需要對Meeting的參與者進行增加和刪除,因此Meeting提供了AddParticipator與RemoveParticipator兩個函數
    //刪除一個參與者
    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)
    }
    
    需要判斷一個用戶是否爲參與者,因此Meeting提供了IsParticipator函數
    //判斷參與者是否在會議中
    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存儲用戶與會議數據
    type Storage struct {
        m_userList    list.List
        m_meetingList list.List
        m_curUserList list.List
    }
    
    需要從文件中讀取三個列表的數據,因此Storage提供ReadFromFile函數,對用戶列表與會議列表以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提供WriteToFile函數,對用戶列表與會議列表以json形式讀取,對登錄用戶列表以text形式讀取
    //將數據寫入文件
    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提供CreateUser、DeleteUser、QueryUser三個函數,其中QueryUser需要提供一個選擇函數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提供CreateCurUser、DeleteCurUser、QueryCurUser三個函數,其中QueryCurUser需要提供一個選擇函數filter作爲參數
    //創建登錄用戶
    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
    }
    
    需要對會議進行增刪、查詢、修改操作,因此Storage提供CreateMeeting、DeleteMeeting、QueryMeeting、UpdateMeeting四個個函數,其中QueryCurUser需要提供一個選擇函數filter作爲參數,UpdateMeeting需要提供一個一個選擇函數filter和一個修改函數switcher作爲參數
    //創建會議
    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")
      
    • 測試:
      在這裏插入圖片描述
發佈了34 篇原創文章 · 獲贊 8 · 訪問量 4515
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章