Spaces:
Running
Running
File size: 3,771 Bytes
9de7b70 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | // 豆维 · 内容安全云函数
// type: checkMessage | checkMessages | checkImage
// 返回约定:{ risk: boolean, skipped?: boolean };skipped 表示检测未能完成,前端必须拦截
const cloud = require("wx-server-sdk");
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
const MAX_TEXT = 2500;
const MAX_IMAGE_BYTES = 1024 * 1024;
const MAX_BATCH = 5;
const extractErrCode = (error) => {
if (!error) return undefined;
if (typeof error.errCode === "number") return error.errCode;
if (typeof error.code === "number") return error.code;
const match = typeof error.errMsg === "string" ? error.errMsg.match(/-?\d{3,}/) : null;
return match ? Number(match[0]) : undefined;
};
// msgSecCheck v2:scene 1 资料 / 2 评论 / 3 论坛 / 4 社交日志
async function checkOneMessage(content, scene, openid) {
const value = String(content || "").slice(0, MAX_TEXT);
if (!value.trim()) return { risk: false };
const safeScene = [1, 2, 3, 4].includes(Number(scene)) ? Number(scene) : 2;
try {
const response = await cloud.openapi.security.msgSecCheck({
version: 2,
scene: safeScene,
openid,
content: value,
});
const suggest = response && response.result && response.result.suggest;
if (!suggest) return { risk: false, skipped: true };
// review(疑似)按违规处理,宁严勿松
return { risk: suggest === "risky" || suggest === "review", verdict: suggest };
} catch (e) {
if (extractErrCode(e) === 87014) return { risk: true, verdict: "risky" };
return { risk: false, skipped: true, errCode: extractErrCode(e) };
}
}
// 通过文件头判断真实格式,不信任客户端传入的类型
function sniffType(buffer) {
if (buffer.length > 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) return "image/jpeg";
if (buffer.length > 8 && buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) return "image/png";
return "";
}
async function checkImage(fileID) {
// 只允许检测本小程序上传到 sec-check/ 目录的临时文件,防止借接口删除/读取其他文件
if (typeof fileID !== "string" || !/^cloud:\/\/[^/]+\/sec-check\/[\w.-]+\.jpg$/.test(fileID)) {
return { risk: false, skipped: true, reason: "INVALID_FILE" };
}
try {
const download = await cloud.downloadFile({ fileID });
const buffer = download && download.fileContent;
const contentType = buffer ? sniffType(buffer) : "";
if (!buffer || !buffer.length || buffer.length > MAX_IMAGE_BYTES || !contentType) {
return { risk: false, skipped: true, reason: "BAD_IMAGE" };
}
await cloud.openapi.security.imgSecCheck({
media: { contentType, value: buffer },
});
return { risk: false };
} catch (e) {
if (extractErrCode(e) === 87014) return { risk: true, verdict: "risky" };
return { risk: false, skipped: true, errCode: extractErrCode(e) };
} finally {
try {
await cloud.deleteFile({ fileList: [fileID] });
} catch (e) {
// 清理失败不影响检测结果
}
}
}
exports.main = async (event) => {
const { OPENID } = cloud.getWXContext();
if (!OPENID) return { risk: false, skipped: true, reason: "NO_OPENID" };
const type = event && event.type;
if (type === "checkMessage") {
return checkOneMessage(event.content, event.scene, OPENID);
}
if (type === "checkMessages") {
const items = Array.isArray(event.items) ? event.items.slice(0, MAX_BATCH) : [];
const results = await Promise.all(items.map((it) => checkOneMessage(it && it.value, it && it.scene, OPENID)));
return { results };
}
if (type === "checkImage") {
return checkImage(event.fileID);
}
return { risk: false, skipped: true, reason: "UNKNOWN_TYPE" };
};
|