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 (Message Queuing Telemetry Transport)消息队列遥测传输协议,是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。M…...
操作系统专栏4-网络专题from小林coding
网络专题 文件传输mmapwritesend file大文件传输过程 文件传输 传统的文件传输过程 在这个过程中发生了4次用户态与内核态之间的切换,4次数据拷贝分别是 read系统调用陷入内核,read完成返回write调用陷入内核,write返回 4次数据拷贝分别是 磁盘->内核缓冲区->用户缓冲…...
《C和指针》(6)指针
1、内存和地址 计算机的内存是由数以亿万计的位(bit)组成,每一个位可以容纳值0、1值。由于一个位所能表示的值的范围太有限,所以单独的位用处不大。通常许多为合成一组作为一个单位,这样就可以存储范围较大的值。下图…...
零基础强化学习入门分享
(一)前言:强化学习入门顺序。 以前主要学习硬件PCB单片机等知识,后来接触的项目也大多与电气相关,从一窍不通到稍微找到点门道,中间走过不少弯路,误打误撞中,也留下了一些经验。 我的…...
QT快捷键
--------------------------------------------------- --------------------------------------------------- QT断点调试 Ctrl B 编译程序 F5 调试运行程序 F10 单步调试 F11 进入函数调试 --------------------------------------------------- -----------------------…...
LabVIEW 开发在不确定路况下自动速度辅助系统
LabVIEW 开发在不确定路况下自动速度辅助系统 智能驾驶辅助系统是汽车行业最先进的升级和尖端技术,智能交通系统依靠智能驾驶辅助系统在公共交通部门工作。该智能驾驶辅助系统技术包括自适应巡航控制,防抱死制动系统,安全气囊展开࿰…...
《面试1v1》ElasticSearch 和 Lucene
🍅 作者简介:王哥,CSDN2022博客总榜Top100🏆、博客专家💪 🍅 技术交流:定期更新Java硬核干货,不定期送书活动 🍅 王哥多年工作总结:Java学习路线总结…...
P5727 【深基5.例3】冰雹猜想
【深基5.例3】冰雹猜想 题目描述 给出一个正整数 n n n,然后对这个数字一直进行下面的操作:如果这个数字是奇数,那么将其乘 3 3 3 再加 1 1 1,否则除以 2 2 2。经过若干次循环后,最终都会回到 1 1 1。经过验证很…...
ConcurrentHashMap1.7 源码浅析
分析过HashMap的1.7的版本的结构,但是HashMap是线程不安全的,多线程触发扩容还会发生死循环问题,那么ConcurrentHashMap 就是解决这个问题的,这是一个线程安全的Map,那么对应的内部实现是怎么样的,简单分析…...
跨境电商时代的安全护航
随着跨境电商业务的蓬勃发展,网络安全问题日益突出。为了保障个人信息的安全和商业竞争的公平性,防关联浏览器和多开浏览器的需求日益增长。本文将为您介绍隐擎fox指纹浏览器,探讨其在跨境电商时代的重要作用,以及如何通过该浏览器…...
JavaScript Es6 _1 笔记
JavaScript Es6 _1 笔记 学习作用域、变量提升、闭包等语言特征,加深对 JavaScript 的理解,掌握变量赋值、函数声明的简洁语法,降低代码的冗余度。 理解作用域对程序执行的影响能够分析程序执行的作用域范围理解闭包本质,利用闭包…...
结构体和 Json 相互转换(序列化反序列化)
关于 JSON 数据 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也 易于机器解析和生成。RESTfull Api 接口中返回的数据都是 json 数据。 Json 的基本格式如下: { "a": "Hello", "b": "…...
【力扣刷题 | 第二十四天】
目录 前言: 416. 分割等和子集 - 力扣(LeetCode) 总结 前言: 今晚我们爆刷动态规划类型的题目。 416. 分割等和子集 - 力扣(LeetCode) 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这…...
PyTorch使用(一)(常用库)
1.各大模型库 hub:简单来说就是专门为PyTorch集成的算法模型库 网站:GitHub - pytorch/hub: Submission to https://pytorch.org/hub/ Model Zoo:这个平台上提供预训练模型,在每个模型上,会标注出这个模型在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.前言 学习资料:江协科技的个人空间-江协科技个人主页-哔哩哔哩视频 通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统通信协议:制定通信的规则,通信双方按照协议规则进行数据收发 全双工:通信…...
第一章-JavaScript基础进阶part2:事件
文章目录 概念一、注册事件(绑定事件)1.1 addEventListener事件监听 二、删除事件(解绑)三、DOM事件流四、事件对象event4.1 e.target与this与e.currentTarget的区别4.2 事件对象的常见属性 五、阻止事件默认行为及冒泡六、事件委…...
如何优雅的使用后端接口
优雅的后端接口 一个后端接口大致分为四个部分:接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响 应数据(response)。 一、URL & Method Rest 设计风格 》 Restful API 简单理解: URI 是用来唯一标志一个互联网资源;Me…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
