目录
huatian-svc/src/context/ChatContext.ts
huatian-svc/src/ChatSession.ts
huatian-svc/src/service/ChatIDService.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 })