修复监控视频展示并更新产品素材
@@ -1,4 +1,4 @@
|
||||
import { Inject, forwardRef } from '@nestjs/common';
|
||||
import { Inject, Logger, forwardRef } from '@nestjs/common';
|
||||
import {
|
||||
WebSocketServer,
|
||||
SubscribeMessage,
|
||||
@@ -38,8 +38,13 @@ interface CameraConfig {
|
||||
serveClient: false,
|
||||
})
|
||||
export class AiEndGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||
private readonly logger = new Logger(AiEndGateway.name);
|
||||
|
||||
@WebSocketServer()
|
||||
wsServer: Server;
|
||||
|
||||
connecetedClients: Map<string, Socket> = new Map();
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CameraService))
|
||||
private cameraService: CameraService,
|
||||
@@ -51,14 +56,12 @@ export class AiEndGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||
private utilsService: UtilsService,
|
||||
) {}
|
||||
|
||||
connecetedClients: Map<string, Socket> = new Map();
|
||||
|
||||
@SubscribeMessage('message')
|
||||
handleMessage(
|
||||
@MessageBody() body: string,
|
||||
@ConnectedSocket() client: Socket,
|
||||
) {
|
||||
console.log('message received: ', body);
|
||||
this.logger.log(`message received: ${body}`);
|
||||
client.emit('message', 'hello from server');
|
||||
}
|
||||
|
||||
@@ -67,11 +70,9 @@ export class AiEndGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||
@MessageBody() body: AlarmData,
|
||||
@ConnectedSocket() client: Socket,
|
||||
) {
|
||||
console.log('alarm received: ');
|
||||
|
||||
const { cameraID } = client.data as ClientInfo;
|
||||
const sourceCamera = new Camera();
|
||||
sourceCamera.id = parseInt(cameraID);
|
||||
sourceCamera.id = parseInt(cameraID, 10);
|
||||
const alarmRule = new AlarmRule();
|
||||
alarmRule.id = body.alarmRuleID;
|
||||
|
||||
@@ -93,13 +94,12 @@ export class AiEndGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||
const config = await this.cameraService.getById(cameraID, true);
|
||||
if (!config) return;
|
||||
cameraConfig = {
|
||||
alarmRules: config?.alarmRules ?? [],
|
||||
alarmRules: config.alarmRules ?? [],
|
||||
rtspUrl: config.rtspUrl,
|
||||
};
|
||||
}
|
||||
client?.emit('cameraConfigChange', cameraConfig);
|
||||
|
||||
console.log(`notify ${cameraID} CameraConfigChange`);
|
||||
this.logger.log(`notify ${cameraID} CameraConfigChange`);
|
||||
}
|
||||
|
||||
async disconnectClient(cameraID: number) {
|
||||
@@ -107,74 +107,83 @@ export class AiEndGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||
}
|
||||
|
||||
async handleConnection(@ConnectedSocket() client: Socket) {
|
||||
let data: ClientInfo | undefined;
|
||||
try {
|
||||
if (typeof client.client.request.headers.data !== 'string') {
|
||||
this.logger.warn('拒绝连接:缺少 data 请求头');
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
data = JSON.parse(client.client.request.headers.data) as ClientInfo;
|
||||
if (!data.cameraID) {
|
||||
this.logger.warn('拒绝连接:缺少 cameraID');
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
const data: ClientInfo = JSON.parse(client.client.request.headers.data);
|
||||
|
||||
const user = await this.userService.authLogin(
|
||||
data.username,
|
||||
data.password,
|
||||
);
|
||||
|
||||
if (user?.role !== 'admin') {
|
||||
this.logger.warn(
|
||||
`拒绝连接:用户 ${data.username} 无管理员权限或鉴权失败`,
|
||||
);
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.connecetedClients.has(data.cameraID)) {
|
||||
this.connecetedClients.get(data.cameraID)?.disconnect();
|
||||
const cameraID = parseInt(data.cameraID, 10);
|
||||
if (!Number.isInteger(cameraID) || cameraID <= 0) {
|
||||
this.logger.warn(`拒绝连接:非法 cameraID=${data.cameraID}`);
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
const camera = await this.cameraService.getById(cameraID, false, false);
|
||||
if (!camera) {
|
||||
this.logger.warn(`拒绝连接:摄像头 ${data.cameraID} 不存在`);
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
const oldClient = this.connecetedClients.get(data.cameraID);
|
||||
if (oldClient && oldClient !== client) {
|
||||
oldClient.disconnect();
|
||||
this.connecetedClients.delete(data.cameraID);
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
client.data = data;
|
||||
|
||||
const camera = await this.cameraService.getById(
|
||||
parseInt(data.cameraID),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
if (!camera) {
|
||||
client.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.cameraService.updateCamera({
|
||||
id: parseInt(data.cameraID),
|
||||
online: true,
|
||||
});
|
||||
|
||||
await this.cameraService.updateCamera({ id: cameraID, online: true });
|
||||
this.connecetedClients.set(data.cameraID, client);
|
||||
|
||||
console.log(`client ${data.cameraID} connected`);
|
||||
|
||||
await this.notifyCameraConfigChange(parseInt(data.cameraID));
|
||||
} catch {
|
||||
this.logger.log(`client ${data.cameraID} connected`);
|
||||
await this.notifyCameraConfigChange(cameraID);
|
||||
} catch (error) {
|
||||
const cameraID = data?.cameraID ?? 'unknown';
|
||||
this.logger.error(
|
||||
`摄像头 ${cameraID} 连接处理失败: ${String(error)}`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
);
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
async handleDisconnect(@ConnectedSocket() client: Socket) {
|
||||
this.connecetedClients.delete(client.data.cameraID);
|
||||
const data = client.data as ClientInfo | undefined;
|
||||
client.removeAllListeners();
|
||||
|
||||
const data = client.data as ClientInfo;
|
||||
await this.cameraService.updateCamera({
|
||||
id: parseInt(data.cameraID),
|
||||
online: false,
|
||||
});
|
||||
if (!data?.cameraID) return;
|
||||
|
||||
console.log(`client ${data.cameraID} disconnected`);
|
||||
}
|
||||
const currentClient = this.connecetedClients.get(data.cameraID);
|
||||
if (currentClient === client) {
|
||||
this.connecetedClients.delete(data.cameraID);
|
||||
}
|
||||
|
||||
const sleep = async (ms: number) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(null);
|
||||
}, ms);
|
||||
});
|
||||
};
|
||||
const cameraID = parseInt(data.cameraID, 10);
|
||||
if (Number.isInteger(cameraID)) {
|
||||
await this.cameraService.updateCamera({ id: cameraID, online: false });
|
||||
}
|
||||
this.logger.log(`client ${data.cameraID} disconnected`);
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 633 KiB After Width: | Height: | Size: 685 KiB |
|
Before Width: | Height: | Size: 519 KiB After Width: | Height: | Size: 515 KiB |
|
Before Width: | Height: | Size: 504 KiB After Width: | Height: | Size: 515 KiB |
|
Before Width: | Height: | Size: 425 KiB After Width: | Height: | Size: 547 KiB |
|
Before Width: | Height: | Size: 610 KiB After Width: | Height: | Size: 706 KiB |
|
Before Width: | Height: | Size: 380 KiB After Width: | Height: | Size: 450 KiB |
|
Before Width: | Height: | Size: 933 KiB After Width: | Height: | Size: 1012 KiB |
|
Before Width: | Height: | Size: 596 KiB After Width: | Height: | Size: 756 KiB |
|
Before Width: | Height: | Size: 447 KiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 874 KiB After Width: | Height: | Size: 866 KiB |
|
Before Width: | Height: | Size: 657 KiB After Width: | Height: | Size: 683 KiB |
|
Before Width: | Height: | Size: 533 KiB After Width: | Height: | Size: 563 KiB |
@@ -1,6 +1,7 @@
|
||||
FROM node:20-bookworm-slim
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENV TZ=Asia/Shanghai \
|
||||
NPM_CONFIG_REGISTRY=https://registry.npmmirror.com
|
||||
RUN echo $TZ > /etc/timezone && \
|
||||
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
|
||||
dpkg-reconfigure -f noninteractive tzdata
|
||||
@@ -11,7 +12,7 @@ COPY ./backend /usr/share/smart-parking-platform/backend
|
||||
|
||||
WORKDIR /usr/share/smart-parking-platform/backend
|
||||
|
||||
RUN npm i -g pnpm@8.15.0 && \
|
||||
RUN npm i -g pnpm@8.15.0 --registry=https://registry.npmmirror.com && \
|
||||
pnpm install --frozen-lockfile && \
|
||||
pnpm run build && \
|
||||
mkdir -p /var/www/html/public
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
.player {
|
||||
// hide the timeline
|
||||
display: block;
|
||||
background: #020617;
|
||||
|
||||
&::-webkit-media-controls-timeline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
.content {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.video {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 320px;
|
||||
object-fit: cover;
|
||||
background: #020617;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
FROM node:20-bookworm-slim
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENV TZ=Asia/Shanghai \
|
||||
NPM_CONFIG_REGISTRY=https://registry.npmmirror.com
|
||||
RUN echo $TZ > /etc/timezone && \
|
||||
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
|
||||
dpkg-reconfigure -f noninteractive tzdata
|
||||
|
||||
# ffmpeg 用于向 nginx-rtMP 循环推流
|
||||
# ffmpeg 用于向 Nginx-RTMP 循环推流
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /simulator
|
||||
|
||||
COPY simulator/package.json ./
|
||||
RUN npm install --omit=dev
|
||||
RUN npm config set registry https://registry.npmmirror.com && \
|
||||
npm install --omit=dev
|
||||
|
||||
COPY simulator/simulator.js ./
|
||||
COPY simulator/assets /assets
|
||||
|
||||