feat(config): implement dynamic security blocklist and home marketing popup with admin controls

这个提交包含在:
xcbyy
2026-10-10 13:49:01 +08:00
父节点 df92166162
当前提交 2e726e125d
共有 8 个文件被更改,包括 616 次插入 和 8 次删除

查看文件

@@ -30,6 +30,25 @@ export class ContentSecurityService {
]
};
// 动态自定义敏感词列表(支持管理后台增删改并实时热生效)
private customKeywords: string[] = [];
/**
* 载入或更新动态敏感词黑名单
*/
public setCustomKeywords(keywords: string[]): void {
if (Array.isArray(keywords)) {
this.customKeywords = keywords.map(k => k.trim()).filter(Boolean);
}
}
/**
* 获取当前生效的所有自定义敏感词
*/
public getCustomKeywords(): string[] {
return [...this.customKeywords];
}
/**
* 检查文本内容安全 (security.msgSecCheck)
* @param text 待检测文本(支持动态、评论、活动标题、个人简介等)
@@ -47,6 +66,21 @@ export class ContentSecurityService {
const cleanText = text.replace(/[\s\r\n\t\-_,.,。!?]/g, '').toLowerCase();
// 1. 优先检测管理后台运营配置的自定义违规词库
for (const kw of this.customKeywords) {
const cleanKw = kw.replace(/[\s\r\n\t\-_,.,。!?]/g, '').toLowerCase();
if (cleanText.includes(cleanKw)) {
return {
pass: false,
code: 87014,
label: 'CUSTOM_BLOCKLIST',
suggest: 'block',
message: `内容含有涉嫌违规或敏感词汇[${kw}],已被系统拦截,请文明发言`
};
}
}
// 2. 检测系统内置的核心安全敏感词库
for (const [category, keywords] of Object.entries(ContentSecurityService.SENSITIVE_KEYWORDS)) {
for (const kw of keywords) {
const cleanKw = kw.replace(/[\s\r\n\t\-_,.,。!?]/g, '').toLowerCase();

查看文件

@@ -686,6 +686,21 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
}
]);
const securityWordsVal = JSON.stringify([
'加微信私聊', '私下走微信', '走线下免佣金', '线下给现金', '私下单',
'联系方式v', 'vx号', '微信转账', '私加好友', '线下私约'
]);
const homePopupVal = JSON.stringify({
enabled: true,
title: '新人专享同城陪伴好礼',
subtitle: '注册即送20元无门槛立减券,首单专享特惠',
imageUrl: 'https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?w=800',
link: '/pages/activity/list',
btnText: '立即领券组局',
frequency: 'once_daily' // once_daily | always | once_ever
});
const [cfgItemRows]: any = await conn.query('SELECT COUNT(*) as count FROM platform_configs');
if (cfgItemRows[0].count === 0) {
const customerServiceVal = JSON.stringify({
@@ -720,10 +735,12 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
('platform_protocol', '平台服务协议与合规规范', ?, '小程序端服务协议与隐私政策详情', 1),
('community_topics', '友圈热门推荐话题库', ?, '友圈广场发帖推荐话题与热门标签', 1),
('community_notice', '友圈社区置顶头条公告', ?, '友圈广场顶部置顶横幅公告与社区公约', 1),
('faq_list', '帮助中心与常见问题FAQ', ?, '小程序端帮助中心常见问答与指引', 1);
`, [customerServiceVal, safetyNoticeVal, protocolVal, communityTopicsVal, communityNoticeVal, faqListVal]);
('faq_list', '帮助中心与常见问题FAQ', ?, '小程序端帮助中心常见问答与指引', 1),
('security_words', '私下引流与违规自定义敏感词库', ?, '全平台发帖、评论及订单防私下交易关键词实时过滤', 1),
('home_popup', '小程序首页运营全局弹窗', ?, '节日活动推广、新用户专享首单优惠全局浮层弹窗', 1);
`, [customerServiceVal, safetyNoticeVal, protocolVal, communityTopicsVal, communityNoticeVal, faqListVal, securityWordsVal, homePopupVal]);
} else {
// 增量保证 community_topics, community_notice, faq_list 存在
// 增量保证各核心配置项完整就绪
const [existingRows]: any = await conn.query('SELECT config_key FROM platform_configs');
const existingKeys = new Set(existingRows.map((r: any) => r.config_key));
if (!existingKeys.has('community_topics')) {
@@ -744,5 +761,17 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
VALUES ('faq_list', '帮助中心与常见问题FAQ', ?, '小程序端帮助中心常见问答与指引', 1)
`, [faqListVal]);
}
if (!existingKeys.has('security_words')) {
await conn.query(`
INSERT INTO platform_configs (config_key, name, value_json, description, status)
VALUES ('security_words', '私下引流与违规自定义敏感词库', ?, '全平台发帖、评论及订单防私下交易关键词实时过滤', 1)
`, [securityWordsVal]);
}
if (!existingKeys.has('home_popup')) {
await conn.query(`
INSERT INTO platform_configs (config_key, name, value_json, description, status)
VALUES ('home_popup', '小程序首页运营全局弹窗', ?, '节日活动推广、新用户专享首单优惠全局浮层弹窗', 1)
`, [homePopupVal]);
}
}
}

查看文件

@@ -1279,6 +1279,23 @@ export class MysqlRepository {
return result;
}
async getPlatformConfigByKey(configKey: string): Promise<any> {
const [rows]: any = await pool.query(`
SELECT id, config_key as configKey, name, value_json as valueJson, description, status
FROM platform_configs
WHERE config_key = ?
`, [configKey]);
if (!rows || rows.length === 0) return null;
const r = rows[0];
let val = r.valueJson;
if (typeof val === 'string') {
try {
val = JSON.parse(val);
} catch (_) {}
}
return { ...r, valueJson: val };
}
async getAdminPlatformConfigs(): Promise<any[]> {
const [rows]: any = await pool.query(`
SELECT id, config_key as configKey, name, value_json as valueJson, description, status,

查看文件

@@ -1078,11 +1078,13 @@ app.put('/api/admin/system/configs/:key', async (req: Request, res: Response) =>
const configKey = String(req.params.key);
const { name, valueJson, description, status } = req.body;
// 微信内容安全审查(检查文本内容或字符串化后的配置)
const textToCheck = `${name || ''} ${description || ''} ${JSON.stringify(valueJson || '')}`;
const sec = contentSecurityService.checkText(textToCheck);
if (!sec.pass) {
return res.status(400).json({ code: sec.code, message: sec.message });
// 微信内容安全审查(检查文本内容或字符串化后的配置,security_words 黑名单库配置项除外)
if (configKey !== 'security_words') {
const textToCheck = `${name || ''} ${description || ''} ${JSON.stringify(valueJson || '')}`;
const sec = contentSecurityService.checkText(textToCheck);
if (!sec.pass) {
return res.status(400).json({ code: sec.code, message: sec.message });
}
}
const updated = await repo.updatePlatformConfig(configKey, {
@@ -1096,6 +1098,11 @@ app.put('/api/admin/system/configs/:key', async (req: Request, res: Response) =>
return res.status(404).json({ code: 40400, message: '配置项不存在' });
}
// 若更新的是自定义敏感词库,实时热更新内存过滤引擎
if (configKey === 'security_words' && Array.isArray(valueJson)) {
contentSecurityService.setCustomKeywords(valueJson);
}
res.json({ code: 20000, data: updated, message: '系统配置更新成功' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
@@ -1616,6 +1623,13 @@ async function bootstrap() {
try {
await initDatabase();
console.log('[Peiwan Server] MySQL 数据库核心表及种子数据已校验就绪');
// 载入自定义敏感词过滤库
const secWordsConfig = await repo.getPlatformConfigByKey('security_words');
if (secWordsConfig && Array.isArray(secWordsConfig.valueJson)) {
contentSecurityService.setCustomKeywords(secWordsConfig.valueJson);
console.log(`[Peiwan Server] 已成功装载 ${secWordsConfig.valueJson.length} 条自定义安全拦截词`);
}
} catch (err) {
console.error('[Peiwan Server] MySQL 初始化连接发生警告:', err);
}