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…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
智能职业发展系统:AI驱动的职业规划平台技术解析
智能职业发展系统:AI驱动的职业规划平台技术解析 引言:数字时代的职业革命 在当今瞬息万变的就业市场中,传统的职业规划方法已无法满足个人和企业的需求。据统计,全球每年有超过2亿人面临职业转型困境,而企业也因此遭…...
