TypeScript实战篇 - TS实战: 服务层开发-令牌

目录

接口说明

服务设计

WHY NOT Socket?

@huatian/svc【node.js 接口服务端】

huatian-svc/package.json

huatian-svc/tsconfig.json

huatian-svc/src/main.ts

huatian-svc/nodemon.json

huatian-svc/src/context/AccountContext.ts

huatian-svc/src/repo/UserRepository.ts

huatian-svc/src/dao/Token.ts

@huatian/model 模板子项目

huatian-model/src/index.ts 索引文件

huatian-model/package.json


接口说明

  • POST /token 登录接口{uname,pwd}
  • POST /send 发消息接口
  • GET /read 读取未读消息的接口

服务设计

大型聊天平台

TypeScript实战篇 - TS实战: 服务层开发-令牌_第1张图片

WHY NOT Socket?

【Socket主打体验,我们可以用它做收发数据,其他的操作不建议用Socket】

  • Socket有状态【内存消耗更多】
    • 需要保持连接
    • 服务宕机不好恢复
  • 目前的设计可以支持更高的并发

@huatian/svc【node.js 接口服务端】

huatian-svc/package.json

// 开发前先添加一些工具
// cd huatian-svc
// npm add @types/node // node方法
// npm add express // 外部服务器
// npm add sequelize // 存数据用的
// npm add sqlite3 // 开发环境,用一个文件就可以创造数据库了
// npm add typescript // ts
// npm add @types/express // express 的类型
// npm add nodemon // 做服务的启动和刷新功能
{
  "name": "@huatian/svc",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon src/main.ts" // npm run dev
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@types/express": "^4.17.13",
    "@types/node": "^16.11.7",
    "cookie-parser": "^1.4.5",
    "express": "^4.17.1",
    "nodemon": "^2.0.15",
    "sequelize": "^6.9.0",
    "sqlite3": "^5.0.2",
    "typescript": "^4.4.4"
  }
}

huatian-svc/tsconfig.json

// ts配置文件
{
  "compilerOptions": {
    "target": "ES6",
    "lib": [
      "DOM",
      "ESNext"
    ],
    "moduleResolution": "node",
    "esModuleInterop": true// 引用一些文件时,不用写如: import * as express from "express";
  },
  "include": [
    "src/**/*.ts"
  ]
}

huatian-svc/src/main.ts

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";
// 创建一个服务
const app = express()
// cookie功能,整理好cookie,req.cookies
app.use(cookieParser())
async function sendStdResponse(res: Response, f: T)
async function sendStdResponse(res: Response, f: Promise)
async function sendStdResponse(res: Response, f: () => Promise)
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()
}

// 请求已登录用户uid
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")
})

// 监听一个6001端口号的服务
app.listen(6001, () => {
  console.log('listen at 6001')
})

huatian-svc/nodemon.json

{
  // node 监控器配置
  "restartable": "rs", // 重启
  "watch": [ // 监听改变
    "src",
    "../huatian-model//src"
  ],
  "execMap": {
    "ts": "ts-node", // 执行文件的配置
    "js": "node"
  },
  "verbose": true // 详细报错信息
}

huatian-svc/src/context/AccountContext.ts

// 账户场景
import { UserRepository } from "../repo/UserRepository"

export class AccountContext {
  // static 静态成员
  private static inst: AccountContext
  private repo: UserRepository = UserRepository.getInstance()
  public static getInstance() {
    // 单例场景
    if (!AccountContext.inst) {
      AccountContext.inst = new AccountContext()
    }
    return AccountContext.inst
  }
  // 异步方法,用户名,密码查是否有这个用户
  public async veritfy(uname: string, passwd: string) {
    const user = this.repo.getUser(uname, passwd)
    return user
  }
}

huatian-svc/src/repo/UserRepository.ts

// 用户信息仓库
import { User } from "@huatian/model";
export class UserRepository {
  // 本地信息存储,用户信息不会高频变化,尤其是id这样的信息
  private users: Record = {}
  // 做个单例,js单线程
  public static inst = new UserRepository()
  public static getInstance() {
    return UserRepository.inst
  }
  // 需要去huatian-model: npm link
  // 回到huatian-svc: npm link @huatian/model
  // 函数重载
  public getUser(uid: number): User
  public getUser(user: string, passwd: string): User
  public getUser(identity: number | string, passwd?: string): User {
    // 做个窄化
    if (typeof identity === 'number') {
      const uid = identity
      // 拿一下缓存
      if (this.users[uid]) {
        return this.users[uid]
      }
      // 没有就生成一个,做一下缓存
      const newUser = new User(uid)
      this.users[uid] = newUser
      return newUser
    } else {
      // 写死的用户名,密码
      const user = identity
      const idmap = {
        "zhangsan": 1,
        "lisi": 2,
        "wangwu": 3
      }
      // 递归调用自己,把他变成整数型的调用
      return this.getUser(idmap[user] || 1)
    }
  }
}

huatian-svc/src/dao/Token.ts

// dao【data Access object】
// hash 摘要算法
import crypto from "crypto";
type TokenObject = {
  uid: number,
  token: string,
  expires: number
}
export class Token {
  static inst: Token = new Token()
  static getInstance() {
    return Token.inst
  }
  // 缓存
  private cache: Record = {}
  private create(uid: number) {
    const token = Math.random() + "-" + new Date().getTime()
    // token一天后过期
    const expires = new Date().getTime() + 3600 * 24
    const sha = crypto.createHash("sha1")
    sha.update(token)
    const hash = sha.digest('hex')// 16进制串
    const tokenObject = {
      uid,
      token: hash,
      expires
    }
    // 缓存一下新创建的token
    this.catchSet(tokenObject.token, tokenObject)
    return tokenObject
  }
  // 缓存token
  private catchSet(hash: string, token: TokenObject) {
    this.cache[hash] = token
  }
  private catchGet(hash: string) {
    return this.cache[hash] || null
  }
  public getToken(hash: string) {
    const token = this.catchGet(hash)
    // token 不存在
    if (!token) {
      return null
    }
    // token有效期内
    if (token.expires > new Date().getTime()) {
      return token
    }
    // 过期了
    return null
  }
  public refreshToken(uid: number) {
    return this.create(uid)
  }
}

@huatian/model 模板子项目

huatian-model/src/index.ts 索引文件

// @huatian/model 索引文件
export * from "./User"
export * from "./Message"
export * from "./ChatSession"
export * from "./UserChat"

huatian-model/package.json

{
  "name": "@huatian/model",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.ts", // 入口索引文件
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

你可能感兴趣的:(TypeScript学习,#,TypeScript实战篇,网络)