Files
xk-client-wx/utils/content-security.js

389 lines
5.2 KiB
JavaScript
Raw Normal View History

2026-07-13 16:08:29 +08:00
/**
* 微信内容安全检查患者端小程序
* 调用 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) {
2026-07-13 16:08:29 +08:00
return true;
}
throw { msg: (res && res.data.message) || '内容含有违规信息,请修改后重试' };
2026-07-13 16:08:29 +08:00
}
/**
* 将字段值规范为可比较的字符串数组用逗号拼接
*/
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);
}