微信企业机器人创建

This commit is contained in:
2026-07-26 08:16:56 +08:00
parent f053908acb
commit 34532dc9bf
26 changed files with 1963 additions and 4 deletions
@@ -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(),
};
}
}