/** * 微信内容安全检查(患者端小程序) * 调用 xk-api /api/mobile/content-security/* 接口 */ import { post } from '@/request/api/http.js'; import { checkDev } from '@/utils/utils'; /** 资料类场景(个人信息编辑) */ export const SCENE_PROFILE = 1; /** 社交日志场景(聊天) */ export const SCENE_CHAT = 4; /** 微信 msg_sec_check 单条内容上限 */ const MAX_TEXT_LEN = 2500; /** * 获取当前环境 xkApi 基址 */ function getXkApiBase() { const env = checkDev(); const map = { prod: 'https://api.xiaokang88.com/api/mobile', test: 'https://test.xk.api.nailaoyun.cn/api/mobile', dev: 'http://127.0.0.1:18001/api/mobile', }; return map[env] || map.prod; } function getAuthHeader() { let token = uni.getStorageSync('token') || ''; token = String(token).replace(/^Bearer\s+/i, '').trim(); return token ? { authorization: `Bearer ${token}` } : {}; } /** * 统一展示内容安全错误 */ export function handleSecurityError(err) { const msg = (err && (err.msg || err.message)) || '内容含有违规信息,请修改后重试'; uni.showToast({ title: msg, icon: 'none' }); } function assertCheckOk(res) { if (res && res.data.code === 0) { return true; } throw { msg: (res && res.data.message) || '内容含有违规信息,请修改后重试' }; } /** * 将字段值规范为可比较的字符串(数组用逗号拼接) */ function normalizeFieldValue(val) { if (val == null) { return ''; } if (Array.isArray(val)) { return val.filter(Boolean).map((item) => String(item).trim()).filter(Boolean).join(','); } return String(val).trim(); } /** * 与 baseline 对比,返回有变动且非空的文本列表 */ export function pickChangedTexts(fields, baseline = {}) { const texts = []; const base = baseline || {}; Object.keys(fields || {}).forEach((key) => { const current = normalizeFieldValue(fields[key]); const original = normalizeFieldValue(base[key]); if (current && current !== original) { texts.push(current); } }); return texts; } /** * 将多条文本用换行拼接;超过 2500 字则按字段拆成多批请求 */ export function joinTextsForCheck(texts) { const list = (Array.isArray(texts) ? texts : []) .map((item) => String(item || '').trim()) .filter(Boolean); if (!list.length) { return []; } const batches = []; let current = ''; list.forEach((text) => { const merged = current ? `${current}\n${text}` : text; if (merged.length <= MAX_TEXT_LEN) { current = merged; return; } if (current) { batches.push(current); current = ''; } if (text.length <= MAX_TEXT_LEN) { current = text; } else { for (let i = 0; i < text.length; i += MAX_TEXT_LEN) { batches.push(text.slice(i, i + MAX_TEXT_LEN)); } } }); if (current) { batches.push(current); } return batches; } /** * 表单提交前:仅检测变动字段,合并为一次或少量 msg_sec_check 调用 */ export async function checkProfileTexts(fields, baseline, scene = SCENE_PROFILE) { const changed = pickChangedTexts(fields, baseline); if (!changed.length) { return true; } const batches = joinTextsForCheck(changed); for (let i = 0; i < batches.length; i++) { await checkText(batches[i], scene); } return true; } /** * 文本内容安全检查 */ export async function checkText(content, scene = SCENE_PROFILE) { const text = String(content || '').trim(); if (!text) { return true; } const res = await post('/content-security/check-text', { content: text, scene }, 3); return assertCheckOk(res); } /** * 批量文本检查(按顺序,每条独立请求) */ export async function checkTexts(texts, scene = SCENE_PROFILE) { const list = Array.isArray(texts) ? texts : [texts]; for (let i = 0; i < list.length; i++) { const item = list[i]; if (item) { await checkText(item, scene); } } return true; } /** * 本地图片安全检查(选图后、上传前) */ export function checkImage(filePath, scene = SCENE_PROFILE) { if (!filePath) { return Promise.resolve(true); } const baseUrl = getXkApiBase(); return new Promise((resolve, reject) => { uni.uploadFile({ url: `${baseUrl}/content-security/check-image`, filePath, name: 'file', formData: { scene: String(scene) }, header: getAuthHeader(), success: (uploadRes) => { try { const result = JSON.parse(uploadRes.data); assertCheckOk(result); resolve(true); } catch (e) { reject(e.msg ? e : { msg: '图片安全检查失败' }); } }, fail: () => reject({ msg: '图片安全检查失败' }), }); }); } /** * 公网图片 URL 检查(OSS 直传登记前可选) */ export async function checkImageUrl(url, scene = SCENE_PROFILE) { const imageUrl = String(url || '').trim(); if (!imageUrl) { return true; } const res = await post('/content-security/check-image-url', { url: imageUrl, scene }, 3); return assertCheckOk(res); }