🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@network-tool/shared-crypto",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"typescript": "^5.4.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import crypto from 'crypto';
|
||||
|
||||
// ============================================================
|
||||
// 密码哈希
|
||||
// ============================================================
|
||||
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
export function hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
export function verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// JWT
|
||||
// ============================================================
|
||||
|
||||
export interface JwtPayload {
|
||||
user_id: number;
|
||||
email: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export function generateToken(payload: JwtPayload, secret: string, expiresIn: string | number = '24h'): string {
|
||||
return jwt.sign(payload, secret, { expiresIn: expiresIn as any });
|
||||
}
|
||||
|
||||
export function verifyToken(token: string, secret: string): JwtPayload | null {
|
||||
try {
|
||||
return jwt.verify(token, secret) as JwtPayload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 连接 Token(用于客户端-节点验证)
|
||||
// ============================================================
|
||||
|
||||
export interface ConnectionTokenPayload {
|
||||
user_id: number;
|
||||
device_id: number;
|
||||
node_id: number;
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
export function generateConnectionToken(
|
||||
payload: ConnectionTokenPayload,
|
||||
secret: string,
|
||||
expiresIn: string | number = '5m'
|
||||
): string {
|
||||
return jwt.sign(payload, secret, { expiresIn: expiresIn as any });
|
||||
}
|
||||
|
||||
export function verifyConnectionToken(
|
||||
token: string,
|
||||
secret: string
|
||||
): ConnectionTokenPayload | null {
|
||||
try {
|
||||
return jwt.verify(token, secret) as ConnectionTokenPayload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 哈希 & ID 生成
|
||||
// ============================================================
|
||||
|
||||
export function sha256(data: string): string {
|
||||
return crypto.createHash('sha256').update(data).digest('hex');
|
||||
}
|
||||
|
||||
export function generateDeviceFingerprint(seed?: string): string {
|
||||
return sha256(seed || uuidv4());
|
||||
}
|
||||
|
||||
export function generateMessageId(): string {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
export function generateNonce(): string {
|
||||
return crypto.randomBytes(16).toString('hex');
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "@network-tool/shared-protocol",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@network-tool/shared-types": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { NetworkMessage } from '@network-tool/shared-types';
|
||||
|
||||
/**
|
||||
* 消息帧格式:
|
||||
* ┌──────────────┬──────────────────┐
|
||||
* │ Header (4B) │ Payload (JSON) │
|
||||
* │ msg length │ UTF-8 bytes │
|
||||
* └──────────────┴──────────────────┘
|
||||
*/
|
||||
|
||||
export const PROTOCOL_VERSION = 1;
|
||||
export const FRAME_HEADER_SIZE = 4; // 4 bytes for length prefix
|
||||
|
||||
/**
|
||||
* 编码消息为网络传输格式
|
||||
*/
|
||||
export function encodeMessage(msg: NetworkMessage): Buffer {
|
||||
const json = JSON.stringify(msg);
|
||||
const payload = Buffer.from(json, 'utf-8');
|
||||
const header = Buffer.alloc(FRAME_HEADER_SIZE);
|
||||
header.writeUInt32BE(payload.length, 0);
|
||||
return Buffer.concat([header, payload]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码网络消息
|
||||
*/
|
||||
export function decodeMessage(data: Buffer): NetworkMessage {
|
||||
const json = data.toString('utf-8');
|
||||
return JSON.parse(json) as NetworkMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流式数据中提取帧(处理粘包/拆包)
|
||||
*/
|
||||
export class FrameParser {
|
||||
private buffer: Buffer = Buffer.alloc(0);
|
||||
|
||||
push(chunk: Buffer): NetworkMessage[] {
|
||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||
const messages: NetworkMessage[] = [];
|
||||
|
||||
while (this.buffer.length >= FRAME_HEADER_SIZE) {
|
||||
const payloadLength = this.buffer.readUInt32BE(0);
|
||||
const totalLength = FRAME_HEADER_SIZE + payloadLength;
|
||||
|
||||
if (this.buffer.length < totalLength) {
|
||||
break; // 数据不完整,等待更多数据
|
||||
}
|
||||
|
||||
const payload = this.buffer.subarray(FRAME_HEADER_SIZE, totalLength);
|
||||
try {
|
||||
messages.push(decodeMessage(payload));
|
||||
} catch {
|
||||
// 跳过损坏的消息
|
||||
}
|
||||
this.buffer = this.buffer.subarray(totalLength);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.buffer = Buffer.alloc(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@network-tool/shared-types",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// ============================================================
|
||||
// 用户相关类型
|
||||
// ============================================================
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
password_hash: string;
|
||||
role: 'user' | 'admin';
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface UserPublic {
|
||||
id: number;
|
||||
email: string;
|
||||
role: 'user' | 'admin';
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface RegisterRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
token: string;
|
||||
user: UserPublic;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 设备相关类型
|
||||
// ============================================================
|
||||
|
||||
export interface Device {
|
||||
id: number;
|
||||
user_id: number;
|
||||
device_name: string;
|
||||
device_fingerprint: string;
|
||||
public_key: string;
|
||||
status: 'online' | 'offline';
|
||||
last_seen_at: string;
|
||||
}
|
||||
|
||||
export interface DeviceRegisterRequest {
|
||||
device_name: string;
|
||||
device_fingerprint: string;
|
||||
public_key: string;
|
||||
}
|
||||
|
||||
export interface DeviceStatusUpdate {
|
||||
status: 'online' | 'offline';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 节点相关类型
|
||||
// ============================================================
|
||||
|
||||
export interface RelayNode {
|
||||
id: number;
|
||||
name: string;
|
||||
host: string;
|
||||
port: number;
|
||||
status: 'online' | 'offline' | 'busy';
|
||||
load: number;
|
||||
last_heartbeat_at: string;
|
||||
}
|
||||
|
||||
export interface NodeRegisterRequest {
|
||||
name: string;
|
||||
host: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface NodeHeartbeatRequest {
|
||||
load: number;
|
||||
status: 'online' | 'offline' | 'busy';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 连接相关类型
|
||||
// ============================================================
|
||||
|
||||
export interface ConnectionRequest {
|
||||
device_id: number;
|
||||
target_device_id?: number;
|
||||
node_id: number;
|
||||
}
|
||||
|
||||
export interface ConnectionTokenResponse {
|
||||
token: string;
|
||||
node: RelayNode;
|
||||
expires_at: string;
|
||||
}
|
||||
|
||||
export interface TokenVerifyRequest {
|
||||
token: string;
|
||||
device_id: number;
|
||||
}
|
||||
|
||||
export interface TokenVerifyResponse {
|
||||
valid: boolean;
|
||||
user_id: number;
|
||||
device_id: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface DisconnectRequest {
|
||||
connection_id?: string;
|
||||
device_id: number;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 消息协议类型
|
||||
// ============================================================
|
||||
|
||||
export type MessageType = 'data' | 'control' | 'heartbeat' | 'error' | 'auth';
|
||||
|
||||
export interface NetworkMessage {
|
||||
type: MessageType;
|
||||
from_device_id: number;
|
||||
to_device_id: number;
|
||||
payload: string;
|
||||
timestamp: number;
|
||||
message_id: string;
|
||||
}
|
||||
|
||||
export interface ControlMessage {
|
||||
action: 'connect' | 'disconnect' | 'heartbeat';
|
||||
device_id: number;
|
||||
token?: string;
|
||||
data?: string;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// API 响应统一格式
|
||||
// ============================================================
|
||||
|
||||
export interface ApiResponse<T = unknown> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 连接日志
|
||||
// ============================================================
|
||||
|
||||
export interface ConnectionLog {
|
||||
id: number;
|
||||
user_id: number;
|
||||
device_id: number;
|
||||
node_id: number;
|
||||
action: string;
|
||||
status: 'success' | 'failed';
|
||||
message: string;
|
||||
created_at: string;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user