diff --git a/collection/uni-cms-articles/package.json b/collection/uni-cms-articles/package.json index 7943e23..1381167 100644 --- a/collection/uni-cms-articles/package.json +++ b/collection/uni-cms-articles/package.json @@ -1,6 +1,6 @@ { "name": "@opendb/uni-cms-articles", - "version": "0.0.1", + "version": "0.0.2", "description": "uni-cms-articles", "keywords": ["文章&评论", "文章表", "uni-CMS", "CMS"], "opendb": { diff --git a/collection/uni-cms-articles/schema.ext.js b/collection/uni-cms-articles/schema.ext.js index a55ebb8..11ef69f 100644 --- a/collection/uni-cms-articles/schema.ext.js +++ b/collection/uni-cms-articles/schema.ext.js @@ -1,5 +1,6 @@ // 获取配置 const createConfig = safeRequire('uni-config-center') +const {QuillDeltaToHtmlConverter, QuillDeltaToJSONConverter} = safeRequire('quill-delta-converter') const config = createConfig({ pluginId: 'uni-cms' }).config() @@ -48,19 +49,34 @@ async function checkImageSec(image, requestId, errorMsg) { requestId }) - // 调用图片安全检测接口 - const res = await uniSecCheck.imgSecCheck({ - image, // 待检测的图片URL - scene: 1, // 表示资料类场景 - version: 1 // 调用检测API的版本号 - }) + const images = typeof image === "string" ? [image]: image - // 如果存在违规内容,抛出异常 - if (res.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) { - throw new Error(errorMsg || '图片违规,请修改后提交') - } else if (res.errCode !== 0) { - console.error(res) - throw new Error('内容安全检测异常:' + res.errCode) + for (let item of images) { + // 处理cloud://开头的链接 + if (item.startsWith('cloud://')) { + const res = await uniCloud.getTempFileURL({ + fileList: [item] + }) + + if (res.fileList && res.fileList.length > 0) { + item = res.fileList[0].tempFileURL + } + } + + // 调用图片安全检测接口 + const res = await uniSecCheck.imgSecCheck({ + image: item, // 待检测的图片URL + scene: 1, // 表示资料类场景 + version: 1 // 调用检测API的版本号 + }) + + // 如果存在违规内容,抛出异常 + if (res.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) { + throw new Error(errorMsg || '图片违规,请修改后提交') + } else if (res.errCode !== 0) { + console.error(res) + throw new Error('内容安全检测异常:' + res.errCode) + } } } @@ -88,6 +104,10 @@ module.exports = { // addDataList 是一个数组,因为可以一次性创建多条数据 if (addDataList.length <= 0) return + // 检测内容安全开关 + const allowCheckContent = checkContentSecurityEnable('content') + const allowCheckImage = checkContentSecurityEnable('image') + // 遍历数组,对每一条数据进行安全检测 for (const addData of addDataList) { // 如果是草稿,不检测 @@ -96,19 +116,19 @@ module.exports = { // 并行检测 const parallel = [] // 检测标题 - if (addData.title && checkContentSecurityEnable('content')) { + if (allowCheckContent && addData.title) { parallel.push(checkContentSec(addData.title, clientInfo.requestId, '标题存在敏感字,请修改后提交')) } // 检测摘要 - if (addData.excerpt && checkContentSecurityEnable('content')) { + if (allowCheckContent && addData.excerpt) { parallel.push(checkContentSec(addData.excerpt, clientInfo.requestId, '摘要存在敏感字,请修改后提交')) } // 检测内容 - if (addData.content && checkContentSecurityEnable('content')) { + if (allowCheckContent && addData.content) { parallel.push(checkContentSec(JSON.stringify(addData.content), clientInfo.requestId, '内容存在敏感字,请修改后提交')) } // 检测封面图 - if (addData.thumbnail && checkContentSecurityEnable('image')) { + if (allowCheckImage && addData.thumbnail) { parallel.push(checkImageSec(addData.thumbnail, clientInfo.requestId, '封面图存在违规,请修改后提交')) } // 等待所有并行检测完成 @@ -124,23 +144,27 @@ module.exports = { // 如果是草稿,不检测 if (updateData.article_status !== 1) return + // 检测内容安全开关 + const allowCheckContent = checkContentSecurityEnable('content') + const allowCheckImage = checkContentSecurityEnable('image') + // 并行检测 const parallel = [] // 检测标题 - if (updateData.title && checkContentSecurityEnable('content')) { + if (allowCheckContent && updateData.title) { parallel.push(checkContentSec(updateData.title, clientInfo.requestId, '标题存在敏感字,请修改后提交')) } // 检测摘要 - if (updateData.excerpt && checkContentSecurityEnable('content')) { + if (allowCheckContent && updateData.excerpt) { parallel.push(checkContentSec(updateData.excerpt, clientInfo.requestId, '摘要存在敏感字,请修改后提交')) } // 检测内容 - if (updateData.content && checkContentSecurityEnable('content')) { + if (allowCheckContent && updateData.content) { parallel.push(checkContentSec(JSON.stringify(updateData.content), clientInfo.requestId, '内容存在敏感字,请修改后提交')) } // 检测封面图 - if (updateData.thumbnail && checkContentSecurityEnable('image')) { + if (allowCheckImage && updateData.thumbnail) { parallel.push(checkImageSec(updateData.thumbnail, clientInfo.requestId, '封面图存在违规,请修改后提交')) } @@ -148,7 +172,7 @@ module.exports = { await Promise.all(parallel) }, - // 读取文章前触发 + // 读取文章后触发 afterRead: async function ({userInfo, clientInfo, result, where, field}) { // 检查是否配置了clientAppIds字段,如果没有则抛出错误 if (!config.clientAppIds && clientInfo.uniPlatform !== 'web') { @@ -184,9 +208,27 @@ module.exports = { let needUnlock = false let unlockContent = [] - // 遍历文章内容,找到解锁内容 + // 获取文章内容中的图片 + article.content_images = article.content.ops.reduce((imageBlocks, block) => { + if (!block.insert.image) return imageBlocks + + const {attributes} = block + const {'data-custom': custom = ""} = attributes || {} + const parseCustom = custom.split('&').reduce((obj, item) => { + const [key, value] = item.split('=') + obj[key] = value + return obj + }) + + return imageBlocks.concat( + parseCustom.source || + block.insert.image + ) + }, []) + for (const op of article.content.ops) { unlockContent.push(op) + // 遍历文章内容,找到解锁内容 if (op.insert.unlockContent) { needUnlock = true break @@ -194,16 +236,19 @@ module.exports = { } // 如果文章不需要解锁,则返回 - if (!needUnlock) return + if (!needUnlock) { + article.content = getRenderableArticleContent(article.content, clientInfo) + return + } // 获取唯一标识符 const uniqueId = adConfig.watchAdUniqueType === 'user' ? userInfo.uid : clientInfo.deviceId // 如果未登录或者文章未解锁,则返回解锁内容 if (!uniqueId || !article._id) { - article.content = { + article.content = getRenderableArticleContent({ ops: unlockContent - } + }, clientInfo) return } @@ -215,22 +260,90 @@ module.exports = { // 如果未解锁,则返回解锁内容 if (unlockRecord.data && unlockRecord.data.length <= 0) { - article.content = { + article.content = getRenderableArticleContent({ ops: unlockContent - } + }, clientInfo) return } // 将文章解锁替换为行结束符 \n - article.content = { + article.content = getRenderableArticleContent({ ops: article.content.ops.map(op => { if (op.insert.unlockContent) { op.insert = "\n" } return op }) + }, clientInfo) + } + } +} + +function getRenderableArticleContent (rawArticleContent, clientInfo) { + const isUniAppX = /uni-app-x/i.test(clientInfo.userAgent) + + if (!isUniAppX) { + const quillDeltaConverter = new QuillDeltaToJSONConverter(rawArticleContent.ops) + return quillDeltaConverter.convert() + } + + const deltaOps = [] + + for (let i = 0; i < rawArticleContent.ops.length; i++) { + const op = rawArticleContent.ops[i] + + if (typeof op.insert === 'object') { + const insertType = Object.keys(op.insert) + const blockRenderList = ['image', 'divider', 'unlockContent', 'mediaVideo'] + if (insertType && insertType.length > 0 && blockRenderList.includes(insertType[0])) { + deltaOps.push({ + type: insertType[0], + ops: [op] + }) + + // 一般块级节点后面都跟一个换行,需要把这个换行给去掉 + const nextOps = rawArticleContent.ops[i + 1] + if (nextOps && nextOps.insert === '\n') { + i ++ + } + + continue } } + const currentIndex = deltaOps.length > 0 ? deltaOps.length - 1: 0 + if ( + typeof deltaOps[currentIndex] !== "object" || + (deltaOps[currentIndex] && deltaOps[currentIndex].type !== 'rich-text') + ) { + deltaOps.push({ + type: 'rich-text', + ops: [] + }) + } + + deltaOps[deltaOps.length - 1].ops.push(op) } + + return deltaOps.reduce((content, item) => { + const isRichText = item.type === 'rich-text' + let block = { + type: item.type, + data: isRichText ? item.ops: item.ops[0] + } + + if (item.type === 'rich-text') { + const lastOp = item.ops.length > 0 ? item.ops[item.ops.length - 1]: null + + if (lastOp !== null && lastOp.insert === "\n") { + item.ops.pop() + } + + const quillDeltaConverter = new QuillDeltaToHtmlConverter(item.ops) + + block.data = quillDeltaConverter.convert() + } + + return content.concat(block) + }, []) }