owned this note
owned this note
Published
Linked with GitHub
# Final project USER STORY
:::info
藍色:前端
:::
:::success
綠色:後端
:::
:::danger
紅色:全端
:::
== 使用者 PL ==
:::info
- [ ] P0 身為一個使用者,我希望我可以以 e-mail 註冊會員、並登入、登出、修改帳號及密碼
* 個人頁面、登入使用 google、FB 等 API 串
- [ ] P1 身為一個使用者,我希望我可以在不同的裝置上正常使用網站功能。
- [ ] P1 身為一個使用者,我希望我可以在網站首頁看到使用指引。
:::
:::success
- [ ] P1 身為一個使用者,我希望我可以在將使用期間遇到的問題向管理員回報。
- [ ] P3 身為一個使用者,我希望我可以匯出我在房間的活動紀錄。
* 聊天紀錄、完整 LOG
:::
:::danger
- [ ] P0 身為一個使用者,我希望我可以在登入後瀏覽、創立、編輯和刪除房間。
- [ ] P0 身為一個使用者,我希望我可以以不同的身分(參與者、觀眾、房主)加入房間。
- [ ] P1 身為一個使用者,我希望我可以在房間列表搜尋符合條件(系統、團名)的房間。
- [ ] P2 身為一個使用者,我希望我可以觀看其他使用者的個人資訊、並得知他的上線狀態。
:::
== 觀眾 viewer == (觀眾身分組本身可能就是 P1 或 P2?)
:::info
- [ ] P1 身為一個觀眾,我希望我可以觀看房間中的所有訊息。
- [ ] P1 身為一個觀眾,我希望我可以使用觀眾與房主專用的聊天頻道。
:::
== 參與者 PC ==
:::info
- [ ] P1 身為一個參與者,我希望我可以在房間中使用擲骰功能。
- [ ] P1 身為一個參與者,我希望我可以在進入房間時接收房主傳送的資訊。
- [ ] P2 身為一個參與者,我希望我可以拖曳筆記的位置或將其至頂。
:::
:::danger
- [ ] P0 身為一個參與者,我希望我可以設定自己的角色訊息(頭像、基礎數值)。
- [ ] P0 身為一個參與者,我希望我可以發送訊息,並選擇發送的頻道。
- [ ] P0 身為一個參與者,我希望我可以即時收到他人的訊息。
- [ ] P0 身為一個參與者,我希望我可以編輯、刪除自己的訊息。
- [ ] P1 身為一個參與者,我希望我可以在進入房間時自動載入先前的訊息。
- [ ] P1 身為一個參與者,我希望我可以創立並管理自己的筆記。
:::
== 房主 GM ==
:::info
- [ ] P0 身為一個房主,我希望我可以查看並管理參與者的角色資訊。
- [ ] P0 身為一個房主,我希望我可以管理參與者的角色資訊。
- [ ] P0 身為一個房主,我希望我可以使用指令擲骰。
- [ ] P2 身為一個房主,我希望我可以在房間中播放 BGM。
- [ ] P2 身為一個房主,我希望我可以將特定內容投影到參與者的介面上。
:::
:::success
- [ ] P0 身為一個房主,我希望我可以發送訊息,並選擇發送的頻道。
- [ ] P0 身為一個房主,我希望我可以即時收到他人的訊息。
- [ ] P0 身為一個房主,我希望我可以編輯、刪除房間的所有訊息、並管理發言權限。
- [ ] P1 身為一個房主,我希望我可以在設立房間時使用密碼為房間上鎖、並管理玩家跟觀眾的人數。
- [ ] P2 身為一個房主,我希望我可以透過帳號邀請使用者加入房間。
:::
:::danger
- [ ] P0 身為一個房主,我希望我可以設立房間,並設置房間的基本狀態。
- [ ] P1 身為一個房主,我希望我可以傳送私人訊息給參與者、觀眾、並設定觀眾是否能看到。
- [ ] P1 身為一個房主,我希望我可以創立並管理自己的筆記。
- [ ] P1 身為一個房主,我希望我可以在房間中設置公告和行事曆。
:::
== 管理者 ADMIN ==
:::info
- [ ] P0 身為一個管理者,我希望我可以看到所有的使用者資料。
:::
:::danger
- [ ] P0 身為一個管理者,我希望我可以在後台管理所有的房間。
- [ ] P1 身為一個管理者,我希望我可以在首頁發布文章。
- [ ] P1 身為一個管理者,我希望我可以接收使用者的問題回報,並給予回應。
:::
# 網站頁面架構
1. 首頁(房間推薦、公告、問題回報)
2. 個人頁面(個人資料確認、修改)
3. 房間列表(搜尋房間、加入)
4. 房間頁面(聊天室、各種擴充功能)
5. 後台(管理房間、使用者)
# 開發工具
1. 後端:Express + MySQL
2. 前端:React + Tailwind or Materialize + Sass
3. 部屬:Heroku(後端) + githubPage(前端)
# 後端架構
:::success
## 資料庫結構
### 使用者
- id
- username
- nickname
- password (hashed)
- email
- relatedAccount (用FB/Google等帳號登入者)
- onlineState (登入/離線/忙碌等)
- info (讓使用者自訂的個人資訊,應該會用JSON存物件字串,避免把細節一項一項寫成 model)
- picture (允許使用者用圖片網址/外部圖床API上傳來放大頭貼)
### 房間
- id
- title
- ~~message (聊天紀錄)
↑這裡應該會設計成每一條 message 本身有 content、from、to、isPublic 四個屬性
用 to 屬性決定是否是私訊、用 isPublic 決定是否讓觀眾看到~~
改為 chat 為單獨的 table,用資料庫關聯處理
- notes (使用者加入的筆記)
- ~~owner~~ 用資料庫關聯處理:userId
- ~~participants~~ ← 不需要存進資料庫的內容,從 ws 判斷連線狀況更好
- ~~audiences~~ ← 同上
- system (使用何種遊戲規則)
### 對話紀錄
- id
- content
- userId ← 與使用者關聯,取用使用者的 id
- roomId ← 與房間關聯,取用房間的 id
:::
:::info
## 對前端 API 接口
### 使用者帳號相關
#### 註冊
【POST】
/user/register
body:{ username, password, email }
回傳值:
- 成功 → {ok:1, token:token user:user}
token 由 jwt 輸入 { username, email } 生成
user 是登入的使用者的詳細資訊
- 失敗 → {ok:0, error:error}
錯誤訊息 error 由後端寫定
***
#### 登入
【POST】
/user/login
body:{ username, password }
回傳值:
- 成功 → {ok:1, token:token, user:user}
token 由 jwt 輸入 { username, email } 生成
user 是登入的使用者的詳細資訊
- 失敗 → {ok:0, error:error}
錯誤訊息 error 由後端寫定
========================================
回傳的 token 的格式是 bearer token,會長得像這樣:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySW5mbyI6eyJ1c2VybmFtZSI6Imhhc2gtdGVzdGVyIiwiZW1haWwiOiIxMjNAZ21haWwuY29tIn0sImlhdCI6MTYzMzUxMTI0NSwiZXhwIjoxNjMzNTExMjc1fQ.91XN4QgXa9AU6hLJJx4ykAzLga5q_6YI5FDfTqnDzcA
直接將這個字串在發 request 的時候放在 header 的 Authorization 就可以了。
token 的有效時間是四個小時,資訊紀錄在 token 本身,時間超過之後就會視同沒有 token。
***
#### 檢查登入狀態
【GET】
Header: {Authorization: (bearer token)}
/user/checkToken
- 成功 → {ok:1, token:token, user:user}
token 是更新過的
user 是登入的使用者的詳細資訊
- 失敗 → {ok:0,error:"token invalid or expired"}
***
#### 修改使用者資訊
【POST】
/user/update?\_method=PUT
Header: {Authorization: (bearer token)}
body: { [email, info, nickname, picture] }
回傳值:
- 成功 → 修改後的使用者之物件,物件包含該使用者創建的所有房間之物件的矩陣
========================================
所有的 body 內容都是 optional,放空白的也無所謂
不需要在網址指定 id,直接從使用者的 token 中取得 id,也就是只能夠修改目前登入的使用者的資訊
這個其實是【PUT】,但是 HTML 預設沒有 PUT 這個請求方法,所以是用 POST 發一個加上了特定 query 的請求,後端會把它解析成用 PUT 處理
***
#### 查詢所有使用者資訊
【GET】
/user
Query: {username: string, nickname: string, offset:number, limit:number}
回傳值:
- 成功 → 包含所有使用者之物件的矩陣
username 與 nickname 可以用 substring 的方式篩選使用者帳號/暱稱
offset 是略過指定數量,limit 是限制回傳數量
***
#### 查詢使用者數量
【GET】
/user/count
回傳值:
- {userCount: n}
回傳目前資料庫中的使用者資訊總數
***
#### 查詢特定使用者資訊
【GET】
/user/:id
:id 為任意正整數
回傳值:
- 成功 → id 等於網址 :id 的使用者之物件,物件包含該使用者創建的所有房間之物件的矩陣
- 失敗 → {ok:0, error:'no such user'}
***
### 房間相關
#### 使用者檢視房間列表
【GET】
/room
Query: {title: string, system: string, offset:number, limit:number}
回傳值:
- 包含所有房間之物件的矩陣,如果 querystring 中有 title 或者 system 則篩選出符合 substring 的相應房間
- 失敗 → {ok:0, error:'no such room'}
title 與 system 可以用 substring 的方式篩選房間名稱/系統相符的房間
offset 是略過指定數量,limit 是限制回傳數量
另外後端預設按 id 降冪排序,越晚創建的房間在回傳矩陣中越前面
***
#### 查詢房間數量
【GET】
/room/count
回傳值:
- {roomCount: n}
回傳目前資料庫中的房間資訊總數
***
#### 使用者創建新房間
【POST】
/room/new
Header: {Authorization: (bearer token)}
body: { title, system }
回傳值:
- 所建立房間之物件
***
#### 使用者變更房間設定
#### 使用者進入房間
【GET】
/room/:id
:id 為任意正整數
回傳值:
- 成功 → id 等於網址 :id 的房間之物件
- 失敗 → {ok:0, error:'no such room'}
***
#### 使用者刪除房間
【POST】
/room/:id?\_method=DELETE
Header: {Authorization: (bearer token)}
:id 為任意正整數
刪除 id 等於 :id 的房間
回傳值:
- 成功 → {ok:1}
- 失敗 → {ok:0, error: error}
#### 在有使用者的期間房間設定被變更
#### 發送訊息
見下方 socket.io 相關
#### 接收訊息
見下方 socket.io 相關
#### 接收私人訊息
:::
:::danger
## Socket.io
客戶端的使用方法:
安裝插件 npm i socket.io-client
import { io } from 'socket.io-client'
const socket = io('https://fay-trpg-api.herokuapp.com')
### 監聽事件
在 component 內:
```
useEffect(()=>{
socket.on("event1", function(message){
// do something here when receive event1
})
socket.on("event2", function(message){
// do something here when receive event2
})
return () => {
socket.offAny() // clear all listener
}
})
```
用 useEffect 建立事件監聽器,在 re-render 時刪除前面建立的監聽器然後設置新的
監聽器的第一個參數是 String,監聽的目標是「從伺服器發過來的訊息」
當後端發送了一個事件名稱為 "hello" 的訊息時,如果前端有設置 socket.on("hello", function(message){}) 的監聽器,
那後面的 function 就會被執行、message 是後端發送的事件中帶著的資訊(一個物件)
傳送的資訊會是 JSON,但是 socket.io 自己寫好了 parser,所以不用特別去用 JSON.stringfy 等函式處理
### 發送事件
`socket.emit(eventName, message)`
跟前端接收事件的方式一樣,後端也是設置對特定 eventName 的監聽器來做出回應
輸入的 message 是一個物件,裡面帶著要傳送的資訊
## websocket API
### 已登入的使用者發送聊天室訊息
`socket.on("sendChat", {userId, roomId, content, to})`
userId: 發送者的使用者 id
roomId: 當前所在的房間 id
content: 聊天訊息的內容
to: 發送的目標對象的 id,為 null 時為公開訊息
回傳值:
- 成功 → `("chatSent", chat)`
chat 為被存入資料庫的 chat 物件,包含發送該 chat 的使用者之帶有 id、username、nickname、onlineState 資訊的物件
- 失敗 → `("errorSentFromServer", err)`
err 為相應的錯誤資訊
備註:
為了減少資料庫的讀寫,客戶端的聊天紀錄讀寫我預想中是這樣:
進入房間時讀取房間資料、裡面就會帶有該房間的聊天紀錄,這會是這次連線中唯一一次從資料庫讀取聊天訊息。
之後每當有人發送訊息,後端就會廣播 chatSent 事件,使用者後續畫面更新所需要的新訊息是從這裡接收。
### 進入房間
`socket.on("joinRoom", {roomId})`
roomId: 要進入的房間 id
回傳值:無
沒有回傳值的原因是客戶端的 socket instance 並沒有 room 屬性,
只有在伺服器端才能從連接中的 socket instance 取得這個客戶目前在哪些房間中。
伺服器端在廣播訊息時可以針對特定房間進行廣播。
:::