目录
接口说明
服务设计
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
大型聊天平台
【Socket主打体验,我们可以用它做收发数据,其他的操作不建议用Socket】
// 开发前先添加一些工具
// 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"
}
}
// ts配置文件
{
"compilerOptions": {
"target": "ES6",
"lib": [
"DOM",
"ESNext"
],
"moduleResolution": "node",
"esModuleInterop": true// 引用一些文件时,不用写如: import * as express from "express";
},
"include": [
"src/**/*.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')
})
{
// node 监控器配置
"restartable": "rs", // 重启
"watch": [ // 监听改变
"src",
"../huatian-model//src"
],
"execMap": {
"ts": "ts-node", // 执行文件的配置
"js": "node"
},
"verbose": true // 详细报错信息
}
// 账户场景
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
}
}
// 用户信息仓库
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)
}
}
}
// 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 索引文件
export * from "./User"
export * from "./Message"
export * from "./ChatSession"
export * from "./UserChat"
{
"name": "@huatian/model",
"version": "1.0.0",
"description": "",
"main": "src/index.ts", // 入口索引文件
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}