391 lines
5.2 KiB
JavaScript
391 lines
5.2 KiB
JavaScript
/**
|
||
|
||
* 微信内容安全检查(患者端小程序)
|
||
|
||
* 调用 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.code === 0) {
|
||
|
||
return true;
|
||
|
||
}
|
||
|
||
throw { msg: (res && res.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);
|
||
|
||
}
|
||
|