feat(msg): 修复通知服务并添加ES降级与Push Gateway推送

database.ts 导出db常量替代getDb()函数

env.ts JWT_SECRET/ES_URL改optional加DEV_MODE/PUSH_GATEWAY_URL

elasticsearch.ts ES降级: ES_URL未设置时esClient=null

notifications.service.ts 加createBatch+分页查询+Push Gateway推送调用

新建msg-init.sql创建2张表
This commit is contained in:
SpecialX
2026-07-09 09:08:57 +08:00
parent 421edd8a41
commit 416e1bc0b2
14 changed files with 498 additions and 207 deletions

View File

@@ -1,32 +1,102 @@
import { Body, Controller, Get, Param, Post, Query, Req } from '@nestjs/common';
import { NotificationsService } from './notifications.service.js';
import type { SendNotificationDto } from './notifications.service.js';
import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Param,
Post,
Put,
Query,
Req,
} from "@nestjs/common";
import { NotificationsService } from "./notifications.service.js";
import type { SendNotificationDto } from "./notifications.service.js";
@Controller('notifications')
interface AuthedRequest {
headers: Record<string, string | string[] | undefined>;
}
/**
* 从请求头读取用户身份。
* Gateway 在通过鉴权后会注入 x-user-id如未注入开发模式或匿名请求返回 null。
*/
function getUserIdFromRequest(req: AuthedRequest): string | null {
const raw = req.headers["x-user-id"];
if (typeof raw === "string" && raw.length > 0) return raw;
return null;
}
@Controller("notifications")
export class NotificationsController {
constructor(private readonly service: NotificationsService) {}
@Post()
async send(@Body() body: unknown) {
async send(@Body() body: unknown): Promise<{ success: true; data: unknown }> {
const result = await this.service.send(body as SendNotificationDto);
return { success: true, data: result };
}
@Get()
async list(@Req() req: { userId: string }, @Query('unread') unread: string) {
const result = await this.service.listByUser(req.userId, unread === 'true');
@Post("batch")
async createBatch(
@Body() body: unknown,
): Promise<{ success: true; data: unknown }> {
const dtos = (body as SendNotificationDto[]) ?? [];
const result = await this.service.createBatch(dtos);
return { success: true, data: result };
}
@Post(':id/read')
async markAsRead(@Param('id') id: string) {
@Get("user/:userId")
async listByUser(
@Param("userId") userId: string,
@Query("unread") unread: string,
): Promise<{ success: true; data: unknown }> {
const result = await this.service.listByUser(userId, unread === "true");
return { success: true, data: result };
}
@Get("user/:userId/page")
async listByUserPaginated(
@Param("userId") userId: string,
@Query("page") page: string,
@Query("pageSize") pageSize: string,
): Promise<{ success: true; data: unknown }> {
const pageNum = Number(page) > 0 ? Number(page) : 1;
const pageSizeNum = Number(pageSize) > 0 ? Number(pageSize) : 20;
const result = await this.service.listByUserWithPagination(
userId,
pageNum,
pageSizeNum,
);
return { success: true, data: result };
}
@Put(":id/read")
async markAsRead(@Param("id") id: string): Promise<{ success: true }> {
await this.service.markAsRead(id);
return { success: true };
}
@Get('search')
async search(@Req() req: { userId: string }, @Query('q') q: string) {
const result = await this.service.search(req.userId, q);
@Get("search")
async search(
@Req() req: AuthedRequest,
@Query("q") q: string,
@Query("userId") userIdParam: string,
): Promise<{ success: true; data: unknown }> {
const userId = getUserIdFromRequest(req) ?? userIdParam;
if (!userId) {
throw new HttpException(
{
success: false,
error: {
code: "MSG_PERMISSION_DENIED",
message: "Missing user identity",
},
},
HttpStatus.FORBIDDEN,
);
}
const result = await this.service.search(userId, q);
return { success: true, data: result };
}
}