TypeScript实战篇 - TS实战: 服务层开发 - 完整的聊天服务

目录

huatian-svc/src/main.ts

huatian-svc/src/context/ChatContext.ts

huatian-svc/src/ChatSession.ts

huatian-svc/src/service/ChatIDService.ts

huatian-svc/src/dao/DB.ts

huatian-svc/src/dao/Dao.ts

huatian-svc/src/dao/create_db.ts


huatian-svc/src/main.ts

TypeScript 复制代码
import express, {
  NextFunction,
  Request,
  Response
} from "express";
// 安装cookie-parser: npm add cookie-parser
import cookieParser from "cookie-parser";
import { AccountContext } from "./context/AccountContext";
import { Token } from "./dao/Token";
import { ChatContext } from "./context/ChatContext";
import { Message } from "@huatian/model";
// 创建一个服务
const app = express()
// cookie功能,整理好cookie,req.cookies
app.use(cookieParser())
// 登录后的数据类
type LoggedInRequest = Request & { uid: number }
async function sendStdResponse<T>(res: Response, f: T)
async function sendStdResponse(res: Response, f: Promise<any>)
async function sendStdResponse(res: Response, f: () => Promise<any>)
async function sendStdResponse(res: Response, f: any) {
  try {
    let data = typeof f === 'function' ? f() : f
    if (data instanceof Promise) {
      data = await data
    }
    res.send({
      success: true,
      data
    })
  } catch (ex: any) {
    console.error(ex)
    res.status(500).send({
      success: false,
      message: ex.toString()
    })
  }
}
// token
async function token(
  req: Request & { uid: number },// req 是带着uid的
  res: Response,
  next: NextFunction
) {
  // tokenHash~=sessionid
  const tokenHash = req.cookies["x-token"] as string
  const token = Token.getInstance()
  const tokenObject = token.getToken(tokenHash)
  if (tokenObject === null) {
    res.status(401).send({
      success: false
    })
    return
  }
  req.uid = tokenObject.uid
  next()
}
app.get('/foo', token, (req: Request & { uid: number }, res) => {
  res.send(req.uid + '-ok')
})
// 登录接口,json传参【express.json()解析json】
app.post('/token', express.json(), async (req, res) => {
  const { uname, pwd } = req.body
  const account = AccountContext.getInstance()
  const user = await account.veritfy(uname, pwd)
  console.log(uname, pwd, user.getId())
  const token = Token.getInstance()
  // 刷新token
  const tokenObject = token.refreshToken(user.getId())
  res.cookie("x-token", tokenObject.token)
  sendStdResponse(res, "ok")
})
// ==================聊天接口相关==============================
// 名词命名接口
app.post("/message", token, express.json(), async (req: LoggedInRequest, res) => {
  const uid = req.uid
  // 先拿到场景
  const chatContext = ChatContext.getInstance()
  // 返回,最后发送的信息
  sendStdResponse(res, async () => {
    return await chatContext.send(uid, req.body as Message)
  })
})
// 请求聊天内容
app.get('/message', token, async (req: LoggedInRequest, res) => {
  const uid = req.uid
  // 最后一条id,不传就读所有
  const lastId = parseInt(req.query.last_id as string) || 0
  console.log({ uid, lastId })
  const chatContext = ChatContext.getInstance()
  sendStdResponse(res, () => {
    return chatContext.read(uid, lastId)
  })
})
// =================================================
// 监听一个6001端口号的服务
app.listen(6001, () => {
  console.log('listen at 6001')
})

huatian-svc/src/context/ChatContext.ts

TypeScript 复制代码
// 聊天场景
import { Message } from "@huatian/model"
import { UserRepository } from "../repo/UserRepository"
import { ChatIDService } from "../service/ChatIDService"
// 服务当中的聊天场景【处理网络,处理存储,不处理模型】
export class ChatContext {
  // 单例
  private static inst = new ChatContext()
  private repo = UserRepository.getInstance()
  // 获取单例
  public static getInstance() {
    return ChatContext.inst
  }
  // 发送
  public async send(uid: number, msg: Message) {// Message需要一个服务 生成id
    const sentMsg = { ...msg }
    const toReceiveMsg = { ...msg }
    sentMsg.id = await ChatIDService.getInstance().getId()
    toReceiveMsg.id = await ChatIDService.getInstance().getId()
    msg.from = uid // 覆盖一下from
    const from = this.repo.getUser(msg.from)
    const to = this.repo.getUser(msg.to)
    const session = from.chat().createChatSession(to)
    // 用session的聊天方法
    session.chat(sentMsg, toReceiveMsg)
    // 告诉客户端最新发送的消息是哪条
    return sentMsg.id
  }
  public read(uid: number, lastId: number) {
    // 用户仓库中拿出用户
    const user = this.repo.getUser(uid)
    // 用lastId拿所有未读 消息
    return user.chat().unReadMessage(lastId)
  }
}

huatian-svc/src/ChatSession.ts

TypeScript 复制代码
import { Message } from "./Message";
import { User } from "./User";
// 聊天会话模型
export class ChatSession {
  private from: User
  private to: User
  public constructor(from: User, to: User) {
    this.from = from
    this.to = to
  }
  // 此处修改为收发个一条消息
  public chat(sentMsg: Message, roReceiveMsg: Message) { // 会话中可以聊天
    this.from.chat().send(sentMsg) // 用户发送一条消息
    this.to.chat().receive(roReceiveMsg) // 用户接收一条消息
  }
}

huatian-svc/src/service/ChatIDService.ts

TypeScript 复制代码
// 生成消息id的服务
import { ChatIDSetDao } from "../dao/Dao"
import { DB } from "../dao/DB"
// Message生成id的服务
const STEP = 100000
export class ChatIDService {
  // 初始化时间端的单例写法
  private static inst: ChatIDService = new ChatIDService()
  private id_base: number = 0
  private id_start: number = 0
  public static getInstance(): ChatIDService {
    return ChatIDService.inst
  }
  /**
   * 每次拿到的是一个集合的ID
   * 比如0~99999
   */
  private async requestIdSet() {
    if (
      this.id_base >= this.id_start &&
      this.id_base < this.id_start + STEP
    ) {
      return
    }
    const sequelize = DB.getSequelize()
    const transaction = await sequelize.transaction()
    try {
      // 0---->100000
      // 0----->100000// 上锁之后要等前一条完成,才能继续执行这一条
      // 拿表最后一条数据
      const lastRecord = await ChatIDSetDao.findOne({
        order: [["id", "desc"]], // 根据id倒序,就是最后一条记录
        lock: transaction.LOCK.UPDATE // 锁住,不能同时访问
      })
      const startNumber = lastRecord
        ? lastRecord.getDataValue("start") + 100000
        : 0
      await ChatIDSetDao.create({
        app: "test", // 临时
        start: startNumber
      })
      // 恢复一下值
      this.id_start = startNumber
      this.id_base = startNumber
    } catch (ex) {
      console.error(ex)
      transaction.rollback()// 回滚
    }
  }
  public async getId() {
    await this.requestIdSet()
    return this.id_base++
  }
}

huatian-svc/src/dao/DB.ts

TypeScript 复制代码
// 表,负责连接的
// node端比较好用的数据工具
import path from "path";
import { Sequelize } from "sequelize";
export class DB {
  static sequelize: Sequelize
  // 初始化时间较长的单例写法
  static getSequelize() {
    if (!DB.sequelize) {
      DB.sequelize = new Sequelize({
        dialect: "sqlite", // 数据库类型,上线后替换成mysql
        storage: path.resolve(__dirname, "mydb.db")
      })
    }
    return DB.sequelize
  }
}

huatian-svc/src/dao/Dao.ts

TypeScript 复制代码
// 创建一张表
import { Model, Optional, DataTypes } from "sequelize";
import { DB } from "./DB";
// 聊天集合的一个分类
interface ChatIDSetAttributes {
  id: number,
  app: string,// app,集群,应用名称
  start: number
}
export class ChatIDSetDao extends Model<
  ChatIDSetAttributes,
  Optional<ChatIDSetAttributes, "id">
>{ }
ChatIDSetDao.init(
  {
    id: {
      type: DataTypes.BIGINT, // INTEGER;数量很多的话用,BIGINT,几十亿数据 
      autoIncrement: true,// 自增字段
      primaryKey: true// 主键
    },
    app: {
      type: DataTypes.STRING(20),
      allowNull: false // 不允许null
    },
    start: {
      type: DataTypes.BIGINT,
      allowNull: false // 不允许null
    }
  }, {
  sequelize: DB.getSequelize(),
  tableName: "id_set" // 表的名称
}
)

huatian-svc/src/dao/create_db.ts

TypeScript 复制代码
// 初始化表的脚本
import { ChatIDSetDao } from "./Dao";
//sync自动创建表,force: true存在也创建
ChatIDSetDao.sync({ force: true })
相关推荐
秦jh_7 分钟前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑21320 分钟前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程
Dread_lxy21 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
涔溪1 小时前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与2 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun2 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇2 小时前
ES6进阶知识一
前端·ecmascript·es6
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法