修复监控视频展示并更新产品素材

This commit is contained in:
admin
2026-10-10 18:47:30 +08:00
parent 9c9079c998
commit f7e9b146b3
18 changed files with 77 additions and 59 deletions
@@ -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 cameraID = parseInt(data.cameraID, 10);
if (Number.isInteger(cameraID)) {
await this.cameraService.updateCamera({ id: cameraID, online: false });
}
this.logger.log(`client ${data.cameraID} disconnected`);
}
}
const sleep = async (ms: number) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(null);
}, ms);
});
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 KiB

After

Width:  |  Height:  |  Size: 685 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 KiB

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 KiB

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 425 KiB

After

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 KiB

After

Width:  |  Height:  |  Size: 706 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 KiB

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 933 KiB

After

Width:  |  Height:  |  Size: 1012 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 KiB

After

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 447 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 KiB

After

Width:  |  Height:  |  Size: 866 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 657 KiB

After

Width:  |  Height:  |  Size: 683 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 533 KiB

After

Width:  |  Height:  |  Size: 563 KiB

+4 -3
View File
@@ -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
@@ -21,4 +22,4 @@ COPY ./backend/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80 1515
CMD service nginx start && pnpm run start:prod
CMD service nginx start && pnpm run start:prod
@@ -1,5 +1,7 @@
.player {
// hide the timeline
display: block;
background: #020617;
&::-webkit-media-controls-timeline {
display: none;
}
+1 -1
View File
@@ -482,4 +482,4 @@ const AiChat: FC = () => {
);
};
export default AiChat;
export default AiChat;
@@ -1,7 +1,11 @@
.content {
padding: 8px;
}
.video {
display: block;
width: 100%;
height: 320px;
object-fit: cover;
background: #020617;
}
+5 -3
View File
@@ -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