Spaces:
Running
Running
File size: 4,927 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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | // 内容安全链路(微信平台规范)
// 文本:security.msgSecCheck v2(二维码内容 / 扫码导入内容 / 水印)
// 图片:先按官方建议压缩到 750×1334 内、1MB 内的 JPG,再上传云存储由云函数调用 security.imgSecCheck
// 约定返回:{ status: 'pass' | 'risky' | 'error' }
// - risky:命中违规,必须拦截
// - error:检测服务不可用,同样拦截(禁止静默放行)
// 通过结果按内容缓存,导出时无需重复请求,显著提升导出速度
const FUNCTION_NAME = 'quickstartFunctions'
const MAX_W = 750
const MAX_H = 1334
const textCache = new Map() // value -> 'pass' | 'risky'
const pending = new Map() // value -> Promise
function callFunction(data) {
return new Promise((resolve) => {
if (!wx.cloud) { resolve(null); return }
wx.cloud.callFunction({
name: FUNCTION_NAME,
data,
success: (res) => resolve((res && res.result) || null),
fail: () => resolve(null),
})
})
}
function normalize(result) {
if (!result) return 'error'
if (result.risk) return 'risky'
if (result.skipped) return 'error'
return 'pass'
}
function checkText(content, scene = 2) {
const value = String(content || '').trim().slice(0, 2500)
if (!value) return Promise.resolve({ status: 'pass' })
const cached = textCache.get(value)
if (cached) return Promise.resolve({ status: cached, cached: true })
if (pending.has(value)) return pending.get(value)
const task = callFunction({ type: 'checkMessage', content: value, scene }).then((result) => {
const status = normalize(result)
if (status !== 'error') textCache.set(value, status)
pending.delete(value)
return { status }
})
pending.set(value, task)
return task
}
// 导出前一次性校验多段文本(已缓存的直接复用,未缓存的合并为一次云函数调用)
function checkTexts(items) {
const list = items.map((it) => ({ value: String(it.value || '').trim().slice(0, 2500), scene: it.scene || 2 })).filter((it) => it.value)
const results = list.map((it) => textCache.get(it.value))
if (results.includes('risky')) return Promise.resolve({ status: 'risky' })
const missing = list.filter((it, i) => !results[i])
if (!missing.length) return Promise.resolve({ status: 'pass' })
return callFunction({ type: 'checkMessages', items: missing }).then((res) => {
if (!res || !Array.isArray(res.results)) return { status: 'error' }
let status = 'pass'
res.results.forEach((r, i) => {
const s = normalize(r)
if (s !== 'error') textCache.set(missing[i].value, s)
if (s === 'risky') status = 'risky'
else if (s === 'error' && status === 'pass') status = 'error'
})
return { status }
})
}
function isTextPassed(value) {
const v = String(value || '').trim()
return !v || textCache.get(v) === 'pass'
}
// 按官方尺寸建议压缩(同时加快上传)
function compressForCheck(canvas, image, path) {
return new Promise((resolve) => {
if (!canvas || !image) { resolve(path); return }
try {
const w0 = image.width || image._w
const h0 = image.height || image._h
const scale = Math.min(1, MAX_W / w0, MAX_H / h0)
canvas.width = Math.max(1, Math.round(w0 * scale))
canvas.height = Math.max(1, Math.round(h0 * scale))
const ctx = canvas.getContext('2d')
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
wx.canvasToTempFilePath({
canvas,
fileType: 'jpg',
quality: 0.82,
destWidth: canvas.width,
destHeight: canvas.height,
success: (res) => resolve(res.tempFilePath),
fail: () => resolve(path),
})
} catch (e) {
resolve(path)
}
})
}
function upload(filePath) {
return new Promise((resolve) => {
if (!wx.cloud) { resolve(''); return }
wx.cloud.uploadFile({
cloudPath: `sec-check/${Date.now()}-${Math.random().toString(36).slice(2, 10)}.jpg`,
filePath,
success: (res) => resolve((res && res.fileID) || ''),
fail: () => resolve(''),
})
})
}
// image:已加载的 CanvasImage(用于压缩);path:本地临时路径
function checkImage(path, image, canvas) {
if (!path) return Promise.resolve({ status: 'error' })
return compressForCheck(canvas, image, path)
.then(upload)
.then((fileID) => {
if (!fileID) return { status: 'error' }
// 云函数检测完毕后会立即删除该临时文件
return callFunction({ type: 'checkImage', fileID }).then((result) => ({ status: normalize(result) }))
})
.catch(() => ({ status: 'error' }))
}
const MESSAGES = {
risky: '内容含违规信息,请修改后再试',
error: '安全检测暂不可用,请检查网络后重试',
}
module.exports = { checkText, checkTexts, isTextPassed, checkImage, MESSAGES }
|