1. 特色方功能迭代
This commit is contained in:
390
utils/content-security.js
Normal file
390
utils/content-security.js
Normal file
@@ -0,0 +1,390 @@
|
||||
/**
|
||||
|
||||
* 微信内容安全检查(患者端小程序)
|
||||
|
||||
* 调用 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);
|
||||
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ export function checkDev(key = '') {
|
||||
// 判断某些模块是否开启
|
||||
switch (key) {
|
||||
case 'dev':
|
||||
return false;
|
||||
// return true;
|
||||
// return false;
|
||||
return true;
|
||||
case 'open-im':
|
||||
// return false;
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user