簡単
空き時間にチャットルームを作ってフロントエンドのスキルを固めたいと思い、2020年6月24日にAstroChatの開発の旅を始めました。
このプロジェクトは完全なタイプスクリプトで開発されており、これは将来の機能反復の基礎となるものです。もちろん、私はタイプスクリプトが大好きです。
プロジェクトインターフェース
特徴
- モバイル対応
- ユーザー情報の変更
- グループ/プライベートチャット
- グループの作成 / グループへの参加 / グループからの退出 / ファジー検索グループ
- 友達追加/友達削除/ファジー検索ユーザー
- メッセージページング
- 顔文字
- イメージ送信/プレビュー
- オンライン統計
- カスタム背景
- 再接続アラート
技術概要
- Typescript: JavaScript のスーパーセットで、最大の利点は型システムを提供し、コードの可読性と保守性を向上させることです。
- Vue 2.6.x: フロントエンド・プログレッシブ・フレームワーク。
- Socket/io: リアルタイム通信、websocketサードパーティライブラリ.
- Vuex: Vue.jsアプリケーション専用に開発された状態管理モデル。
- Nestjs: 効率的でスケーラブルなNode.jsサーバーサイドアプリケーションを構築するためのフレームワークで、TypeScriptで書かれ、OOP1、FP2、FRP3の概念を組み合わせています。
- Typeorm:最新のJavaScript機能をサポートし、データベースを使用するあらゆるアプリケーションの開発を支援する追加機能を提供します。
- ES6+:ES6+の構文を使用し、アロー関数、async/await、その他の構文がとても使いやすくなっています。
- SASS(SCSS):CSSの前処理言語としてSCSSを使用することで、少ないコードで最も効率的な方法で複雑なデザインを作成できます。
タイプスクリプトを使う理由
- チャットルームには複雑なデータ構造が含まれるため、タイプスクリプトを使用することで、開発プロセス中にコンパイラに警告を出すことで、多くの低レベルのエラーを回避することができます。
- タイプスクリプトの型定義により、システムのデータ構造が一目でわかり、コードの保守性と可読性が大幅に向上しました。
データベースのテーブル構造設計
データベースは以下の6つのテーブルを使用します。
- user ユーザーテーブル
- グループ グループテーブル
- user_group user_group 中間テーブル
- group_message グループ・メッセージ・テーブル
- user_friend user_friend 中間テーブル
- friend_message プライベートチャットメッセージテーブル
真ん中の表は、グループ/友人とユーザーとのつながりを確立するために使われます。以下は私が描いたマインドマップです。これを読めば、その謎が理解できると思います。
WebSocket作成ロジック
ユーザールームの作成
チャットルームに入室したユーザーは、自動的にpublicという名前のWebSocketルームと、ユーザーのidにちなんだ名前のWebSocketルームに入室することになります。ルームの概念を理解していない場合、ルームにいる人だけがルームからのブロードキャストを受信することができると仮定することができます。
グループチャットルームの作成
groupIdをWebSocketルームの名前とし、新しいユーザがグループに参加するたびに、グループルームに新しいユーザの詳細とともにユーザの参加イベントをブロードキャストし、他のユーザは新しいユーザの情報を保存します。新しいユーザがメッセージを送信すると、他のユーザはそのメッセージのuserIdから対応するユーザの詳細を見つけることができます。これにより、他のユーザーは、メッセージが送信されるとすぐに、そのメッセージの所有者を知ることができます。
プライベートチャットルームの作成
バディ追加リクエストが開始されるたびに、ユーザーの userId とバディの userId を連結した文字列を WebSocket ルーム名として使用して、プライベートチャットルームが作成されます。
バックエンドアーキテクチャ
バックエンドはnestjsを使用しています。nestjsは近年急速に成長しているnode.jsフレームワークです:
- TypeScriptで作られており、通常のES6と互換性があります。
- nestjsの@nestjs/websocketsパッケージは、WebSocketイベントの処理をカプセル化します。
バックエンドのロジックコードです。
- nestjsによるWebSocket接続の確立
// chat.gateway.ts
@WebSocketGateway()
export class ChatGateway {
// socket接続フック
async handleConnection(client: Socket): Promise<string> {
let userRoom = client.handshake.query.userId;
// デフォルトで公開ルームに接続される
client.join('public');
// ユーザーIDに基づくユーザー専用メッセージルーム
if(userRoom) {
client.join(userRoom);
}
return '接続に成功した!'
}
}
- グローバルミドルウェアをカプセル化し、開発中のデバッグを容易にします。
// middleware.js
export function logger(req, res, next) {
const { method, path } = req;
console.log(`${method} ${path}`);
next();
};
// main.js
グローバルミドルウェアを使う
app.use(logger)
- nestjsの静的リソース設定
// main.js
静的リソースの設定
app.useStaticAssets(join(__dirname, '../public/', 'static'), {
prefix: '/static/',
});
- nestjs カスタム例外フィルタ
// http-exception.filter.ts
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter<HttpException> {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
const exceptionRes: any = exception.getResponse();
const {
error,
message,
} = exceptionRes;
// リクエストエラーの場合、以下のフォーマットがフロントエンドに返される。
response.status(status).json({
status,
timestamp: new Date().toISOString(),
path: request.url,
error,
message,
});
}
}
フロントエンドアーキテクチャ
ページの初期化
初期化では、WebSocket接続関数を呼び出し、全ユーザのグループ情報と全フレンド情報を取得し、WebSocket通信のルールを確立して対応するルームに参加し、vuexを使って最新データをディスパッチします。
データ処理
グループのデータタイプ
//
interface Group {
groupId: string;
userId: string; // グループオーナーID
groupName: string;
notice: string;
messages: GroupMessage[];
createTime: number;
}
友人のデータ型
//
interface Friend {
userId: string;
username: string;
avatar: string;
role?: string;
tag?: string;
messages: FriendMessage[];
createTime: number;
}
以前は、すべてのグループ/フレンドデータをオブジェクトの配列[ friend1 , friend2 ... ]で管理していました。 しかし、データが大量になると、グループ/フレンドデータのクエリや更新に非常に負荷がかかります。フレンドの名前やアバターが変わるたびに、情報を更新するために配列を走査しなければなりません。
そこで、チャットルームのコードをオブジェクト構造で最適化しました。groupId/userIdがキーで、値は対応するグループ/フレンドのデータです。
gather = {
'userId': {
userId: 'userId'
username: 'xxx'
messages: [];
...
}
}
各グループとユーザーは一意な ID を持っているので、重複を心配する必要はありません。この構造では、データの更新はとても簡単です。更新が必要なidを取得して、対応する値でgather.idを上書きするだけです。
vuex
チャットルームでは、データの即時更新やvueコンポーネント間のデータの同期が必要で、このようなビジネスシナリオへの対応はvuexの強みです。 vuexのアクションにWebSocket接続を確立する関数を記述し、ユーザがログインに成功した後に接続関数を呼び出すようにしました。
// actions.ts
const actions: ActionTree<ChatState, RootState> = {
// WebSocketを初期化する
async connectSocket({commit, state, dispatch, rootState}, callback) {
// WebSocket接続確立
socket.on('connect', async () => {
// グループメッセージタイムに登録する
socket.on('groupMessage', (res: any) => {
console.log('on groupMessage', res)
if (!res.code) {
// グループメッセージを処理する
commit(ADD_GROUP_MESSAGE, res.data)
}
})
}
}
vuex-classライブラリは、typescriptでvuexを開発するための素晴らしい接着剤です。vuex-classを使えば、vuexコンポーネントでvuexメソッドを呼び出すのは、このように書くだけです:
// GenalChat.vue
import { namespace } from 'vuex-class'
const appModule = namespace('app')
export default class GenalChat extends Vue {
@appModule.Getter('user') user: User;
@appModule.Action('login') login: Function;
}
技術概要
🖖🏻 現在、チャットルームは完全なチャット機能を備えています。同時に、友人が私を励ますために星を与えるような、よりクールな機能を開発し続けるつもりです!
オンラインアドレス
github: