微信企业机器人创建
This commit is contained in:
@@ -17,6 +17,7 @@ import type { AuthUser } from '../../common/guards/jwt-auth.guard';
|
||||
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
|
||||
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
|
||||
import { SystemConfigService } from '../../common/system-config/system-config.service';
|
||||
import { WecomAibotService } from '../../integrations/wecom/wecom-aibot.service';
|
||||
|
||||
@Controller('admin/system-config')
|
||||
@UseGuards(HqAuthGuard, HqPermissionGuard)
|
||||
@@ -24,6 +25,7 @@ export class AdminSystemConfigController {
|
||||
constructor(
|
||||
private readonly systemConfig: SystemConfigService,
|
||||
private readonly permissions: HqPermissionsResolver,
|
||||
private readonly wecomAibot: WecomAibotService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@@ -46,7 +48,12 @@ export class AdminSystemConfigController {
|
||||
async update(@CurrentUser() user: AuthUser, @Body() dto: SystemConfigUpdateRequest) {
|
||||
const keys = await this.permissions.resolveEffectiveKeys(user.actorId);
|
||||
const allowedGroups = allowedConfigGroups(keys);
|
||||
return this.systemConfig.update(dto, allowedGroups);
|
||||
const result = await this.systemConfig.update(dto, allowedGroups);
|
||||
if (result.updatedKeys.includes('WECOM_AIBOT_ENABLED')) {
|
||||
const wecomStatus = await this.wecomAibot.reload('system-config');
|
||||
return { ...result, wecomAibot: wecomStatus };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post('sync-env')
|
||||
@@ -74,7 +81,7 @@ export class AdminSystemConfigController {
|
||||
|
||||
function allowedConfigGroups(permissionKeys: HqPermissionKey[]): string[] | null {
|
||||
if (SYSTEM_SETTINGS_PERMISSION_KEYS.every((k) => permissionKeys.includes(k))) {
|
||||
return null; // 全部
|
||||
return null;
|
||||
}
|
||||
return Object.entries(SYSTEM_CONFIG_GROUP_PERMISSION)
|
||||
.filter(([, perm]) => permissionKeys.includes(perm))
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { CreateWecomBotRequest, UpdateWecomBotRequest } from '@dukang/shared-types';
|
||||
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||
import {
|
||||
HqPermissionGuard,
|
||||
RequireHqPermissions,
|
||||
} from '../../common/guards/hq-permission.guard';
|
||||
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
|
||||
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
|
||||
import { AdminWecomBotsService } from './admin-wecom-bots.service';
|
||||
|
||||
@Controller('admin/wecom-bots')
|
||||
@UseGuards(HqAuthGuard, HqPermissionGuard)
|
||||
@RequireHqPermissions('wecom_bots')
|
||||
export class AdminWecomBotsController {
|
||||
constructor(private readonly service: AdminWecomBotsService) {}
|
||||
|
||||
@Get()
|
||||
list(
|
||||
@Query('name') name?: string,
|
||||
@Query('role') role?: string,
|
||||
@Query('enabled') enabled?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
return this.service.list({
|
||||
name,
|
||||
role,
|
||||
enabled,
|
||||
page: page ? Number(page) : undefined,
|
||||
pageSize: pageSize ? Number(pageSize) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@Post('reload')
|
||||
@HqOperation({
|
||||
action: HqOperationAction.WECOM_BOT_RELOAD,
|
||||
refType: 'WECOM_BOT',
|
||||
batch: true,
|
||||
})
|
||||
reload() {
|
||||
return this.service.reloadRuntime();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
detail(@Param('id') id: string) {
|
||||
return this.service.detail(BigInt(id));
|
||||
}
|
||||
|
||||
@Post()
|
||||
@HqOperation({
|
||||
action: HqOperationAction.WECOM_BOT_CREATE,
|
||||
refType: 'WECOM_BOT',
|
||||
includeBody: true,
|
||||
})
|
||||
create(@Body() body: CreateWecomBotRequest) {
|
||||
return this.service.create(body);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@HqOperation({
|
||||
action: HqOperationAction.WECOM_BOT_UPDATE,
|
||||
refType: 'WECOM_BOT',
|
||||
refIdField: 'id',
|
||||
includeBody: true,
|
||||
})
|
||||
update(@Param('id') id: string, @Body() body: UpdateWecomBotRequest) {
|
||||
return this.service.update(BigInt(id), body);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HqOperation({
|
||||
action: HqOperationAction.WECOM_BOT_DELETE,
|
||||
refType: 'WECOM_BOT',
|
||||
refIdField: 'id',
|
||||
})
|
||||
remove(@Param('id') id: string) {
|
||||
return this.service.remove(BigInt(id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
WECOM_BOT_ROLES,
|
||||
parseWecomBotPermissions,
|
||||
resolveWecomBotPermissions,
|
||||
type CreateWecomBotRequest,
|
||||
type UpdateWecomBotRequest,
|
||||
type WecomBotDto,
|
||||
type WecomBotPermission,
|
||||
type WecomBotRole,
|
||||
} from '@dukang/shared-types';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
||||
import { WecomAibotService } from '../../integrations/wecom/wecom-aibot.service';
|
||||
|
||||
function isWecomRole(v: string): v is WecomBotRole {
|
||||
return (WECOM_BOT_ROLES as readonly string[]).includes(v);
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminWecomBotsService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly wecomAibot: WecomAibotService,
|
||||
) {}
|
||||
|
||||
async list(query: { name?: string; role?: string; enabled?: string; page?: number; pageSize?: number }) {
|
||||
const page = query.page ?? 1;
|
||||
const pageSize = query.pageSize ?? 20;
|
||||
const where: {
|
||||
name?: { contains: string };
|
||||
role?: string;
|
||||
enabled?: boolean;
|
||||
} = {};
|
||||
if (query.name?.trim()) where.name = { contains: query.name.trim() };
|
||||
if (query.role?.trim()) where.role = query.role.trim();
|
||||
if (query.enabled === 'true' || query.enabled === 'false') {
|
||||
where.enabled = query.enabled === 'true';
|
||||
}
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.wecomBot.findMany({
|
||||
where,
|
||||
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
}),
|
||||
this.prisma.wecomBot.count({ where }),
|
||||
]);
|
||||
|
||||
return serializeBigInt({
|
||||
items: items.map((row) => this.toDto(row)),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
runtime: this.wecomAibot.getStatus(),
|
||||
});
|
||||
}
|
||||
|
||||
async detail(id: bigint) {
|
||||
const row = await this.prisma.wecomBot.findUnique({ where: { id } });
|
||||
if (!row) throw new NotFoundException('机器人不存在');
|
||||
return this.toDto(row);
|
||||
}
|
||||
|
||||
async create(dto: CreateWecomBotRequest) {
|
||||
const name = dto.name?.trim();
|
||||
const botId = dto.botId?.trim();
|
||||
const secret = dto.secret?.trim();
|
||||
if (!name) throw new BadRequestException('请填写名称');
|
||||
if (!botId) throw new BadRequestException('请填写 BotID');
|
||||
if (!secret) throw new BadRequestException('请填写 Secret');
|
||||
if (!isWecomRole(dto.role)) throw new BadRequestException('无效角色');
|
||||
|
||||
const exists = await this.prisma.wecomBot.findUnique({ where: { botId } });
|
||||
if (exists) throw new BadRequestException('BotID 已存在');
|
||||
|
||||
const permissions = resolveWecomBotPermissions(dto.role, dto.permissions);
|
||||
const row = await this.prisma.wecomBot.create({
|
||||
data: {
|
||||
name,
|
||||
role: dto.role,
|
||||
botId,
|
||||
secret,
|
||||
avatarUrl: dto.avatarUrl?.trim() || null,
|
||||
welcome: dto.welcome?.trim() || null,
|
||||
permissions: JSON.stringify(permissions),
|
||||
enabled: dto.enabled !== false,
|
||||
sortOrder: dto.sortOrder ?? 0,
|
||||
},
|
||||
});
|
||||
await this.wecomAibot.reload('bot-create');
|
||||
return this.toDto(row);
|
||||
}
|
||||
|
||||
async update(id: bigint, dto: UpdateWecomBotRequest) {
|
||||
const existing = await this.prisma.wecomBot.findUnique({ where: { id } });
|
||||
if (!existing) throw new NotFoundException('机器人不存在');
|
||||
|
||||
const role = dto.role && isWecomRole(dto.role) ? dto.role : (existing.role as WecomBotRole);
|
||||
if (dto.role && !isWecomRole(dto.role)) throw new BadRequestException('无效角色');
|
||||
|
||||
let botId = existing.botId;
|
||||
if (dto.botId !== undefined) {
|
||||
botId = dto.botId.trim();
|
||||
if (!botId) throw new BadRequestException('BotID 不能为空');
|
||||
if (botId !== existing.botId) {
|
||||
const dup = await this.prisma.wecomBot.findUnique({ where: { botId } });
|
||||
if (dup) throw new BadRequestException('BotID 已存在');
|
||||
}
|
||||
}
|
||||
|
||||
let permissionsJson = existing.permissions;
|
||||
if (dto.permissions !== undefined || dto.role !== undefined) {
|
||||
const permissions =
|
||||
dto.permissions !== undefined
|
||||
? parseWecomBotPermissions(dto.permissions)
|
||||
: resolveWecomBotPermissions(role, existing.permissions);
|
||||
const finalPerms =
|
||||
dto.permissions !== undefined
|
||||
? permissions.length
|
||||
? permissions
|
||||
: resolveWecomBotPermissions(role, null)
|
||||
: resolveWecomBotPermissions(role, existing.permissions);
|
||||
permissionsJson = JSON.stringify(finalPerms);
|
||||
}
|
||||
|
||||
const secret =
|
||||
dto.secret !== undefined && dto.secret.trim() ? dto.secret.trim() : existing.secret;
|
||||
|
||||
const row = await this.prisma.wecomBot.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: dto.name !== undefined ? dto.name.trim() : undefined,
|
||||
role: dto.role,
|
||||
botId,
|
||||
secret,
|
||||
avatarUrl:
|
||||
dto.avatarUrl === undefined ? undefined : dto.avatarUrl?.trim() || null,
|
||||
welcome: dto.welcome === undefined ? undefined : dto.welcome?.trim() || null,
|
||||
permissions: permissionsJson,
|
||||
enabled: dto.enabled,
|
||||
sortOrder: dto.sortOrder,
|
||||
},
|
||||
});
|
||||
await this.wecomAibot.reload('bot-update');
|
||||
return this.toDto(row);
|
||||
}
|
||||
|
||||
async remove(id: bigint) {
|
||||
const existing = await this.prisma.wecomBot.findUnique({ where: { id } });
|
||||
if (!existing) throw new NotFoundException('机器人不存在');
|
||||
await this.prisma.wecomBot.delete({ where: { id } });
|
||||
await this.wecomAibot.reload('bot-delete');
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
async reloadRuntime() {
|
||||
return this.wecomAibot.reload('manual');
|
||||
}
|
||||
|
||||
private toDto(row: {
|
||||
id: bigint;
|
||||
name: string;
|
||||
role: string;
|
||||
botId: string;
|
||||
secret: string;
|
||||
avatarUrl: string | null;
|
||||
welcome: string | null;
|
||||
permissions: string;
|
||||
enabled: boolean;
|
||||
sortOrder: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}): WecomBotDto {
|
||||
const role = (isWecomRole(row.role) ? row.role : 'CUSTOM') as WecomBotRole;
|
||||
const permissions = resolveWecomBotPermissions(role, row.permissions) as WecomBotPermission[];
|
||||
return {
|
||||
id: row.id.toString(),
|
||||
name: row.name,
|
||||
role,
|
||||
botId: row.botId,
|
||||
secretConfigured: !!row.secret,
|
||||
avatarUrl: row.avatarUrl,
|
||||
welcome: row.welcome,
|
||||
permissions,
|
||||
enabled: row.enabled,
|
||||
sortOrder: row.sortOrder,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
|
||||
import { BenefitModule } from '../benefit/benefit.module';
|
||||
import { CommonModule } from '../common/common.module';
|
||||
import { IntegrationsModule } from '../../integrations/integrations.module';
|
||||
import { WecomModule } from '../../integrations/wecom/wecom.module';
|
||||
import { AdminXiaofeixiaController } from './admin-xiaofeixia.controller';
|
||||
import { AdminXiaofeixiaService } from './admin-xiaofeixia.service';
|
||||
import { AdminProductDetailTemplatesController } from './admin-product-detail-templates.controller';
|
||||
@@ -59,10 +60,12 @@ import { AdminHqPermissionsService } from './admin-hq-permissions.service';
|
||||
import { AdminDeployController } from './admin-deploy.controller';
|
||||
import { AdminDeployService } from './admin-deploy.service';
|
||||
import { AdminSystemConfigController } from './admin-system-config.controller';
|
||||
import { AdminWecomBotsController } from './admin-wecom-bots.controller';
|
||||
import { AdminWecomBotsService } from './admin-wecom-bots.service';
|
||||
import { AdminFulfillmentProvidersController } from './admin-fulfillment-providers.controller';
|
||||
|
||||
@Module({
|
||||
imports: [CityScopeModule, IamModule, TradeModule, FulfillmentModule, BenefitModule, CommonModule, IntegrationsModule, RedeemModule, StoreModule],
|
||||
imports: [CityScopeModule, IamModule, TradeModule, FulfillmentModule, BenefitModule, CommonModule, IntegrationsModule, WecomModule, RedeemModule, StoreModule],
|
||||
controllers: [
|
||||
AdminDashboardController,
|
||||
AdminDeployController,
|
||||
@@ -98,6 +101,7 @@ import { AdminFulfillmentProvidersController } from './admin-fulfillment-provide
|
||||
AdminWechatBindingsController,
|
||||
AdminHqPermissionsController,
|
||||
AdminSystemConfigController,
|
||||
AdminWecomBotsController,
|
||||
AdminFulfillmentProvidersController,
|
||||
],
|
||||
providers: [
|
||||
@@ -124,6 +128,7 @@ import { AdminFulfillmentProvidersController } from './admin-fulfillment-provide
|
||||
AdminWechatBindingsService,
|
||||
AdminHqPermissionsService,
|
||||
AdminDeployService,
|
||||
AdminWecomBotsService,
|
||||
SuperAdminGuard,
|
||||
],
|
||||
exports: [CityScopeModule],
|
||||
|
||||
Reference in New Issue
Block a user