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