前段时间,我使用 TypeScript 语言实现了一个简单的聊天应用程序。主要目的是编写一个演示,以解释如何在客户端和服务器上使用这种编程语言。客户端应用程序使用了最新的 Angular 功能。
在本文中,我将向你展示如何从头开始实现该应用程序。
什么是实时应用程序?
根据 维基百科的定义,实时应用程序允许接收到信息一发布就能接收到,而不需要定期检查源以获取更新。因此,这种应用程序应该让用户感觉事件和操作立即发生。
WebSockets
WebSockets 是提供双向通信通道的协议。这意味着浏览器和 Web 服务器可以维护实时通信,在连接打开的同时来回发送消息。
Websockets 通信
应用程序结构
我们将分离与服务器相关的代码和客户端代码。当最重要的文件被解释时,我将进入详细说明。现在,这是我们应用程序的预期结构:
server/
|- src/
|- package.json
|- tsconfig.json
|- gulpfile.js
client/
|- src/
|- package.json
|- tsconfig.json
|- .angular-cli.json
服务器代码
由于 WebSockets 是规范,我们可以找到几个实现。我们可以选择 J̶a̶v̶a̶S̶c̶r̶i̶p̶t̶ TypeScript 或其他编程语言。
在这种情况下,我们将使用 Socket.IO,它是最快和最可靠的实时引擎之一。
为什么在服务器端使用 TypeScript?
TypeScript 具有非常酷的功能,并且经常更新。它可以预防约 15% 的错误。你需要更多的理由吗? 😄
初始化服务器应用程序
创建一个<a class="af of" href="https://docs.npmjs.com/cli/init" rel="noopener ugc nofollow" target="_blank">package.json</a>
文件,然后安装以下依赖项:
npm install --save express socket.io @types/express @types/socket.io
我们需要安装一些 devDependencies
,以允许使用 gulp
和 typescript
轻松定义构建任务:
npm install --save-dev typescript gulp gulp-typescript
TypeScript 编译器配置
创建一个 tsconfig.json
文件,内容如下:
{
"files": [
"src/*.ts",
"src/model/*.ts"
],
"compilerOptions": {
"target": "es5"
}
}
数据模型定义
利用静态类型,让我们定义一个小型数据模型,如下所示:
export class User {
constructor(private name: string) {}
}
export class Message {
constructor(private from: User, private content: string) {}
}
export class ChatMessage extends Message{
constructor(from: User, content: string) {
super(from, content);
}
}
..让我们进一步查看 server/src
目录中的细节:
server/
|- src/
|- model/
|- message.model.ts
|- user.model.ts
|- index.ts
|- server.ts
|- package.json
|- tsconfig.json
|- gulpfile.js
聊天服务器实现
server
目录中的主要文件是 index.ts
和 chat-server.ts
。第一个允许我们创建和导出我们的 ChatServer
应用程序,而最后一个包含 express 和 socket.IO 配置:
import { ChatServer } from './chat-server';
let app = new ChatServer().getApp();
export { app };
import { createServer, Server } from 'http';
import * as express from 'express';
import * as socketIo from 'socket.io';
import { Message } from './model';
export class ChatServer {
public static readonly PORT:number = 8080;
private app: express.Application;
private server: Server;
private io: SocketIO.Server;
private port: string | number;
constructor() {
this.createApp();
this.config();
this.createServer();
this.sockets();
this.listen();
}
private createApp(): void {
this.app = express();
}
private createServer(): void {
this.server = createServer(this.app);
}
private config(): void {
this.port = process.env.PORT || ChatServer.PORT;
}
private sockets(): void {
this.io = socketIo(this.server);
}
private listen(): void {
this.server.listen(this.port, () => {
console.log('Running server on port %s', this.port);
});
this.io.on('connect', (socket: any) => {
console.log('Connected client on port %s.', this.port);
socket.on('message', (m: Message) => {
console.log('[server](message): %s', JSON.stringify(m));
this.io.emit('message', m);
});
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
}
public getApp(): express.Application {
return this.app;
}
}
服务器类
上述代码将得到以下类和关系:
服务器类图
构建和运行服务器
为了拥有 Node.js 的 V8 引擎所需的 JavaScript 文件,我们可以在我们的 gulpfile.js
文件中添加一个 build
任务:
var gulp = require("gulp");
var ts = require("gulp-typescript");
var tsProject = ts.createProject("tsconfig.json");
gulp.task("build", function () {
return tsProject.src()
.pipe(tsProject())
.js.pipe(gulp.dest("./dist"));
});
如你所见,构建过程的输出(JavaScript 文件)将位于 dist
目录中。要执行此操作,你需要运行:
gulp build
现在,我们可以运行 node dist/index.js
命令来运行服务器。
客户端代码
让我们使用最新的 Angular CLI 版本生成我们的 client
目录:
ng new typescript-chat-client --routing --prefix tcc --skip-install
然后运行 npm install
(我更喜欢使用 Yarn)安装依赖项:
cd typescript-chat-client
yarn install
添加 Angular Material
查找并按照最新的指南在你的 Angular CLI
项目中安装 Angular Material。
作为在我们的项目结构中使用最佳实践的一部分,我们可以创建 shared
和 material
模块:
client/
|- src/
|- app/
|- chat/
|- shared/
|- material/
|- material.module.ts
|- shared.module.ts
|-app.module.ts
我们可以从命令行界面执行此操作:
ng generate module shared --module app
ng generate module shared/material --module shared
检查 app.module.ts
和 shared.module.ts
中的更改,以查看这些模块之间创建的关系。
添加 express 和 socket.IO
我们需要将 express
和 socket.io
模块添加到我们的客户端应用程序中:
npm install express socket.io --save
聊天模块和组件
在开始创建聊天应用程序的组件之前,让我们创建一个新模块:
ng generate module chat --module app
现在将组件添加到最新的模块中:
ng generate component chat --module chat
为了使用 websockets 和自定义模型,让我们在 chat
目录中创建另一个 shared
文件夹:
ng generate service chat/shared/services/socket --module chat
ng generate class chat/shared/model/user
ng generate class chat/shared/model/message
我们将最终拥有类似于以下结构:
client/
|- src/
|- app/
|- chat/
|- shared/
|- model/
|- user.ts
|- message.ts
|- services/
|- socket.service.ts
|- shared/
|-app.module.ts
Observables 和 Web Sockets
由于我们的 Angular 应用程序附带了 RxJS
,我们可以使用 Observables
来捕获 Socket.IO 事件:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { Message } from '../model/message';
import { Event } from '../model/event';
import * as socketIo from 'socket.io-client';
const SERVER_URL = 'http://localhost:8080';
@Injectable()
export class SocketService {
private socket;
public initSocket(): void {
this.socket = socketIo(SERVER_URL);
}
public send(message: Message): void {
this.socket.emit('message', message);
}
public onMessage(): Observable<Message> {
return new Observable<Message>(observer => {
this.socket.on('message', (data: Message) => observer.next(data));
});
}
public onEvent(event: Event): Observable<any> {
return new Observable<Event>(observer => {
this.socket.on(event, () => observer.next());
});
}
}
我们需要定义一些枚举来管理应用程序中的Actions
和Events
:
// Actions you can take on the App
export enum Action {
JOINED,
LEFT,
RENAME
}
// Socket.io events
export enum Event {
CONNECT = 'connect',
DISCONNECT = 'disconnect'
}
现在我们已经准备好从服务器中监听消息了:
import { Component, OnInit } from '@angular/core';
import { Action } from './shared/model/action';
import { Event } from './shared/model/event';
import { Message } from './shared/model/message';
import { User } from './shared/model/user';
import { SocketService } from './shared/services/socket.service';
@Component({
selector: 'tcc-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css']
})
export class ChatComponent implements OnInit {
action = Action;
user: User;
messages: Message[] = [];
messageContent: string;
ioConnection: any;
constructor(private socketService: SocketService) { }
ngOnInit(): void {
this.initIoConnection();
}
private initIoConnection(): void {
this.socketService.initSocket();
this.ioConnection = this.socketService.onMessage()
.subscribe((message: Message) => {
this.messages.push(message);
});
this.socketService.onEvent(Event.CONNECT)
.subscribe(() => {
console.log('connected');
});
this.socketService.onEvent(Event.DISCONNECT)
.subscribe(() => {
console.log('disconnected');
});
}
public sendMessage(message: string): void {
if (!message) {
return;
}
this.socketService.send({
from: this.user,
content: message
});
this.messageContent = null;
}
public sendNotification(params: any, action: Action): void {
let message: Message;
if (action === Action.JOINED) {
message = {
from: this.user,
action: action
}
} else if (action === Action.RENAME) {
message = {
action: action,
content: {
username: this.user.name,
previousUsername: params.previousUsername
}
};
}
this.socketService.send(message);
}
}
此文件中省略了 Material 代码和 UI 事件。
一旦 ChatComponent
初始化,该组件将订阅 SocketService
可观察对象,以便开始接收连接事件或传入的消息。
sendMessage
和 sendNotification
函数将通过同一服务发送相应的内容。此时发送的通知是 Rename User 和 User Joined。
聊天应用程序功能
- Angular CLI
- Angular 5
- Angular Material
- 表单验证
源代码
在此 GitHub 存储库中找到完整的项目:
实时演示
转到 typescript-chat.firebaseapp.com,在你最喜欢的浏览器中打开两个或多个选项卡,开始聊天。
评论(0)