当前位置: 首页 > news >正文

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

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() : fif (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~=sessionidconst tokenHash = req.cookies["x-token"] as stringconst token = Token.getInstance()const tokenObject = token.getToken(tokenHash)if (tokenObject === null) {res.status(401).send({success: false})return}req.uid = tokenObject.uidnext()
}
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.bodyconst account = AccountContext.getInstance()const user = await account.veritfy(uname, pwd)console.log(uname, pwd, user.getId())const token = Token.getInstance()// 刷新tokenconst 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) || 0console.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

// 聊天场景
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需要一个服务 生成idconst sentMsg = { ...msg }const toReceiveMsg = { ...msg }sentMsg.id = await ChatIDService.getInstance().getId()toReceiveMsg.id = await ChatIDService.getInstance().getId()msg.from = uid // 覆盖一下fromconst 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

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

huatian-svc/src/service/ChatIDService.ts

// 生成消息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 = 0private id_start: number = 0public 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: 0await ChatIDSetDao.create({app: "test", // 临时start: startNumber})// 恢复一下值this.id_start = startNumberthis.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

// 表,负责连接的
// 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", // 数据库类型,上线后替换成mysqlstorage: path.resolve(__dirname, "mydb.db")})}return DB.sequelize}
}

huatian-svc/src/dao/Dao.ts

// 创建一张表
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

// 初始化表的脚本
import { ChatIDSetDao } from "./Dao";
//sync自动创建表,force: true存在也创建
ChatIDSetDao.sync({ force: true })

相关文章:

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…...

【雕爷学编程】MicroPython动手做(32)——物联网之MQTT

MQTT &#xff08;Message Queuing Telemetry Transport&#xff09;消息队列遥测传输协议&#xff0c;是一种基于发布/订阅&#xff08;publish/subscribe&#xff09;模式的"轻量级"通讯协议&#xff0c;该协议构建于TCP/IP协议上&#xff0c;由IBM在1999年发布。M…...

操作系统专栏4-网络专题from小林coding

网络专题 文件传输mmapwritesend file大文件传输过程 文件传输 传统的文件传输过程 在这个过程中发生了4次用户态与内核态之间的切换,4次数据拷贝分别是 read系统调用陷入内核,read完成返回write调用陷入内核,write返回 4次数据拷贝分别是 磁盘->内核缓冲区->用户缓冲…...

《C和指针》(6)指针

1、内存和地址 计算机的内存是由数以亿万计的位&#xff08;bit&#xff09;组成&#xff0c;每一个位可以容纳值0、1值。由于一个位所能表示的值的范围太有限&#xff0c;所以单独的位用处不大。通常许多为合成一组作为一个单位&#xff0c;这样就可以存储范围较大的值。下图…...

零基础强化学习入门分享

&#xff08;一&#xff09;前言&#xff1a;强化学习入门顺序。 以前主要学习硬件PCB单片机等知识&#xff0c;后来接触的项目也大多与电气相关&#xff0c;从一窍不通到稍微找到点门道&#xff0c;中间走过不少弯路&#xff0c;误打误撞中&#xff0c;也留下了一些经验。 我的…...

QT快捷键

--------------------------------------------------- --------------------------------------------------- QT断点调试 Ctrl B 编译程序 F5 调试运行程序 F10 单步调试 F11 进入函数调试 --------------------------------------------------- -----------------------…...

LabVIEW 开发在不确定路况下自动速度辅助系统

LabVIEW 开发在不确定路况下自动速度辅助系统 智能驾驶辅助系统是汽车行业最先进的升级和尖端技术&#xff0c;智能交通系统依靠智能驾驶辅助系统在公共交通部门工作。该智能驾驶辅助系统技术包括自适应巡航控制&#xff0c;防抱死制动系统&#xff0c;安全气囊展开&#xff0…...

《面试1v1》ElasticSearch 和 Lucene

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…...

P5727 【深基5.例3】冰雹猜想

【深基5.例3】冰雹猜想 题目描述 给出一个正整数 n n n&#xff0c;然后对这个数字一直进行下面的操作&#xff1a;如果这个数字是奇数&#xff0c;那么将其乘 3 3 3 再加 1 1 1&#xff0c;否则除以 2 2 2。经过若干次循环后&#xff0c;最终都会回到 1 1 1。经过验证很…...

ConcurrentHashMap1.7 源码浅析

分析过HashMap的1.7的版本的结构&#xff0c;但是HashMap是线程不安全的&#xff0c;多线程触发扩容还会发生死循环问题&#xff0c;那么ConcurrentHashMap 就是解决这个问题的&#xff0c;这是一个线程安全的Map&#xff0c;那么对应的内部实现是怎么样的&#xff0c;简单分析…...

跨境电商时代的安全护航

随着跨境电商业务的蓬勃发展&#xff0c;网络安全问题日益突出。为了保障个人信息的安全和商业竞争的公平性&#xff0c;防关联浏览器和多开浏览器的需求日益增长。本文将为您介绍隐擎fox指纹浏览器&#xff0c;探讨其在跨境电商时代的重要作用&#xff0c;以及如何通过该浏览器…...

JavaScript Es6 _1 笔记

JavaScript Es6 _1 笔记 学习作用域、变量提升、闭包等语言特征&#xff0c;加深对 JavaScript 的理解&#xff0c;掌握变量赋值、函数声明的简洁语法&#xff0c;降低代码的冗余度。 理解作用域对程序执行的影响能够分析程序执行的作用域范围理解闭包本质&#xff0c;利用闭包…...

结构体和 Json 相互转换(序列化反序列化)

关于 JSON 数据 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也 易于机器解析和生成。RESTfull Api 接口中返回的数据都是 json 数据。 Json 的基本格式如下&#xff1a; { "a": "Hello", "b": "…...

【力扣刷题 | 第二十四天】

目录 前言&#xff1a; 416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 总结 前言&#xff1a; 今晚我们爆刷动态规划类型的题目。 416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这…...

PyTorch使用(一)(常用库)

1.各大模型库 hub&#xff1a;简单来说就是专门为PyTorch集成的算法模型库 网站&#xff1a;GitHub - pytorch/hub: Submission to https://pytorch.org/hub/ Model Zoo&#xff1a;这个平台上提供预训练模型&#xff0c;在每个模型上&#xff0c;会标注出这个模型在GitHub的标…...

React ~ React Router 6

React Router 6 VS React Router 5.x 内置组件的变化; 移除<Switch /> , 新增<Routes />语法的变化; component { About } 变为 element { <About /> }新增多个hook官方明确推荐函数式组件了! 一级路由(变化) 安装路由 npm i react-router-dom (默认是最…...

【LeetCode每日一题】——304.二维区域和检索-矩阵不可变

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 矩阵 二【题目难度】 中等 三【题目编号】 304.二维区域和检索-矩阵不可变 四【题目描述】 …...

硬件串口通信协议学习(UART、IIC、SPI、CAN)

0.前言 学习资料&#xff1a;江协科技的个人空间-江协科技个人主页-哔哩哔哩视频 通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统通信协议&#xff1a;制定通信的规则&#xff0c;通信双方按照协议规则进行数据收发 全双工&#xff1a;通信…...

第一章-JavaScript基础进阶part2:事件

文章目录 概念一、注册事件&#xff08;绑定事件&#xff09;1.1 addEventListener事件监听 二、删除事件&#xff08;解绑&#xff09;三、DOM事件流四、事件对象event4.1 e.target与this与e.currentTarget的区别4.2 事件对象的常见属性 五、阻止事件默认行为及冒泡六、事件委…...

如何优雅的使用后端接口

优雅的后端接口 一个后端接口大致分为四个部分&#xff1a;接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响 应数据(response)。 一、URL & Method Rest 设计风格 》 Restful API 简单理解&#xff1a; URI 是用来唯一标志一个互联网资源&#xff1b;Me…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...