feat: improve application access and money precision
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { randomInt, randomUUID } from 'node:crypto';
|
||||
import { isIpAllowed } from '../common/ip-allowlist';
|
||||
import { assertMoneyUnits } from '../common/money';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
export interface CreateSmsApplicationDto {
|
||||
@@ -358,6 +360,7 @@ export class SmsConfigService {
|
||||
}
|
||||
|
||||
async createApplication(data: CreateSmsApplicationDto) {
|
||||
assertMoneyUnits(data.customerUnitPrice ?? 0, '客户单价');
|
||||
const secret = normalizeApplicationPassword(data.passwordCipher);
|
||||
const queuePriority = normalizeApplicationQueuePriority(data.queuePriority);
|
||||
const interfaceType = normalizeApplicationInterfaceType(data.interfaceType);
|
||||
@@ -402,6 +405,9 @@ export class SmsConfigService {
|
||||
if (!application) {
|
||||
throw new NotFoundException('Application not found');
|
||||
}
|
||||
if (data.customerUnitPrice !== undefined) {
|
||||
assertMoneyUnits(data.customerUnitPrice, '客户单价');
|
||||
}
|
||||
const queuePriority = data.queuePriority === undefined
|
||||
? undefined
|
||||
: normalizeApplicationQueuePriority(data.queuePriority);
|
||||
@@ -587,18 +593,17 @@ export class SmsConfigService {
|
||||
if (!application || (tenantId && application.tenantId !== tenantId)) {
|
||||
throw new NotFoundException('Application not found');
|
||||
}
|
||||
const channel = await this.prisma.smsChannel.findFirst({
|
||||
where: { status: { not: 'deleted' } },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
if (tenantId && !application.interfaceEnabled) {
|
||||
throw new ForbiddenException('该企业应用未开通 CMPP 接口');
|
||||
}
|
||||
return {
|
||||
applicationId: application.id,
|
||||
applicationName: application.name,
|
||||
tenantId: application.tenantId,
|
||||
tenantName: application.tenant.name,
|
||||
appCode: application.id,
|
||||
gatewayHost: channel?.gatewayHost ?? '',
|
||||
gatewayPort: channel?.gatewayPort ?? 0,
|
||||
gatewayHost: process.env.CMPP_PUBLIC_HOST?.trim() || '127.0.0.1',
|
||||
gatewayPort: getPositiveIntegerEnv('CMPP_PUBLIC_PORT', 17890),
|
||||
enterpriseCode: application.cmppEnterpriseCode,
|
||||
account: application.cmppAccount,
|
||||
passwordCipher: application.secretHash,
|
||||
@@ -648,7 +653,7 @@ export class SmsConfigService {
|
||||
async recordDownstreamConnectionEvent(data: GatewayDownstreamConnectionEventDto) {
|
||||
const application = await this.prisma.smsApplication.findUnique({
|
||||
where: { cmppAccount: data.account },
|
||||
select: { id: true, tenantId: true, cmppEnterpriseCode: true },
|
||||
include: { ipAllowlist: true },
|
||||
});
|
||||
if (!application) {
|
||||
throw new BadRequestException('CMPP account does not reference an application');
|
||||
@@ -670,6 +675,22 @@ export class SmsConfigService {
|
||||
});
|
||||
return { connectionId: data.connectionId, status: 'disconnected', deleted: Boolean(existing) };
|
||||
}
|
||||
if (!application.interfaceEnabled || application.status !== 'active') {
|
||||
throw new ForbiddenException('CMPP interface is disabled for this application');
|
||||
}
|
||||
if (data.remoteIp && !isIpAllowed(data.remoteIp, application.ipAllowlist.map((item) => item.ipCidr))) {
|
||||
throw new ForbiddenException('CMPP source IP is not in application allowlist');
|
||||
}
|
||||
const activeConnections = await this.prisma.cmppDownstreamConnection.findMany({
|
||||
where: { applicationId: application.id, status: 'connected' },
|
||||
select: { connectionId: true },
|
||||
orderBy: [{ connectedAt: 'asc' }, { connectionId: 'asc' }],
|
||||
});
|
||||
const allowedConnectionIds = activeConnections.slice(0, application.cmppMaxConnections).map((item) => item.connectionId);
|
||||
if ((!existing && activeConnections.length >= application.cmppMaxConnections)
|
||||
|| (existing && activeConnections.length > application.cmppMaxConnections && !allowedConnectionIds.includes(data.connectionId))) {
|
||||
throw new ForbiddenException(`CMPP connection limit exceeded (${application.cmppMaxConnections})`);
|
||||
}
|
||||
const payload = {
|
||||
tenantId: application.tenantId,
|
||||
applicationId: application.id,
|
||||
|
||||
Reference in New Issue
Block a user