update: uni-cms-articles.schema.ext.js

This commit is contained in:
chenruilong 2024-06-12 13:05:21 +08:00
parent 14ba7b910a
commit 39d8db19df
2 changed files with 142 additions and 29 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@opendb/uni-cms-articles", "name": "@opendb/uni-cms-articles",
"version": "0.0.1", "version": "0.0.2",
"description": "uni-cms-articles", "description": "uni-cms-articles",
"keywords": ["文章&评论", "文章表", "uni-CMS", "CMS"], "keywords": ["文章&评论", "文章表", "uni-CMS", "CMS"],
"opendb": { "opendb": {

View File

@ -1,5 +1,6 @@
// 获取配置 // 获取配置
const createConfig = safeRequire('uni-config-center') const createConfig = safeRequire('uni-config-center')
const {QuillDeltaToHtmlConverter, QuillDeltaToJSONConverter} = safeRequire('quill-delta-converter')
const config = createConfig({ const config = createConfig({
pluginId: 'uni-cms' pluginId: 'uni-cms'
}).config() }).config()
@ -48,9 +49,23 @@ async function checkImageSec(image, requestId, errorMsg) {
requestId requestId
}) })
const images = typeof image === "string" ? [image]: image
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({ const res = await uniSecCheck.imgSecCheck({
image, // 待检测的图片URL image: item, // 待检测的图片URL
scene: 1, // 表示资料类场景 scene: 1, // 表示资料类场景
version: 1 // 调用检测API的版本号 version: 1 // 调用检测API的版本号
}) })
@ -63,6 +78,7 @@ async function checkImageSec(image, requestId, errorMsg) {
throw new Error('内容安全检测异常:' + res.errCode) throw new Error('内容安全检测异常:' + res.errCode)
} }
} }
}
// 检测内容安全开关 // 检测内容安全开关
function checkContentSecurityEnable(field) { function checkContentSecurityEnable(field) {
@ -88,6 +104,10 @@ module.exports = {
// addDataList 是一个数组,因为可以一次性创建多条数据 // addDataList 是一个数组,因为可以一次性创建多条数据
if (addDataList.length <= 0) return if (addDataList.length <= 0) return
// 检测内容安全开关
const allowCheckContent = checkContentSecurityEnable('content')
const allowCheckImage = checkContentSecurityEnable('image')
// 遍历数组,对每一条数据进行安全检测 // 遍历数组,对每一条数据进行安全检测
for (const addData of addDataList) { for (const addData of addDataList) {
// 如果是草稿,不检测 // 如果是草稿,不检测
@ -96,19 +116,19 @@ module.exports = {
// 并行检测 // 并行检测
const parallel = [] const parallel = []
// 检测标题 // 检测标题
if (addData.title && checkContentSecurityEnable('content')) { if (allowCheckContent && addData.title) {
parallel.push(checkContentSec(addData.title, clientInfo.requestId, '标题存在敏感字,请修改后提交')) parallel.push(checkContentSec(addData.title, clientInfo.requestId, '标题存在敏感字,请修改后提交'))
} }
// 检测摘要 // 检测摘要
if (addData.excerpt && checkContentSecurityEnable('content')) { if (allowCheckContent && addData.excerpt) {
parallel.push(checkContentSec(addData.excerpt, clientInfo.requestId, '摘要存在敏感字,请修改后提交')) 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, '内容存在敏感字,请修改后提交')) 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, '封面图存在违规,请修改后提交')) parallel.push(checkImageSec(addData.thumbnail, clientInfo.requestId, '封面图存在违规,请修改后提交'))
} }
// 等待所有并行检测完成 // 等待所有并行检测完成
@ -124,23 +144,27 @@ module.exports = {
// 如果是草稿,不检测 // 如果是草稿,不检测
if (updateData.article_status !== 1) return if (updateData.article_status !== 1) return
// 检测内容安全开关
const allowCheckContent = checkContentSecurityEnable('content')
const allowCheckImage = checkContentSecurityEnable('image')
// 并行检测 // 并行检测
const parallel = [] const parallel = []
// 检测标题 // 检测标题
if (updateData.title && checkContentSecurityEnable('content')) { if (allowCheckContent && updateData.title) {
parallel.push(checkContentSec(updateData.title, clientInfo.requestId, '标题存在敏感字,请修改后提交')) parallel.push(checkContentSec(updateData.title, clientInfo.requestId, '标题存在敏感字,请修改后提交'))
} }
// 检测摘要 // 检测摘要
if (updateData.excerpt && checkContentSecurityEnable('content')) { if (allowCheckContent && updateData.excerpt) {
parallel.push(checkContentSec(updateData.excerpt, clientInfo.requestId, '摘要存在敏感字,请修改后提交')) 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, '内容存在敏感字,请修改后提交')) 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, '封面图存在违规,请修改后提交')) parallel.push(checkImageSec(updateData.thumbnail, clientInfo.requestId, '封面图存在违规,请修改后提交'))
} }
@ -148,7 +172,7 @@ module.exports = {
await Promise.all(parallel) await Promise.all(parallel)
}, },
// 读取文章触发 // 读取文章触发
afterRead: async function ({userInfo, clientInfo, result, where, field}) { afterRead: async function ({userInfo, clientInfo, result, where, field}) {
// 检查是否配置了clientAppIds字段如果没有则抛出错误 // 检查是否配置了clientAppIds字段如果没有则抛出错误
if (!config.clientAppIds && clientInfo.uniPlatform !== 'web') { if (!config.clientAppIds && clientInfo.uniPlatform !== 'web') {
@ -184,9 +208,27 @@ module.exports = {
let needUnlock = false let needUnlock = false
let unlockContent = [] 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) { for (const op of article.content.ops) {
unlockContent.push(op) unlockContent.push(op)
// 遍历文章内容,找到解锁内容
if (op.insert.unlockContent) { if (op.insert.unlockContent) {
needUnlock = true needUnlock = true
break 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 const uniqueId = adConfig.watchAdUniqueType === 'user' ? userInfo.uid : clientInfo.deviceId
// 如果未登录或者文章未解锁,则返回解锁内容 // 如果未登录或者文章未解锁,则返回解锁内容
if (!uniqueId || !article._id) { if (!uniqueId || !article._id) {
article.content = { article.content = getRenderableArticleContent({
ops: unlockContent ops: unlockContent
} }, clientInfo)
return return
} }
@ -215,22 +260,90 @@ module.exports = {
// 如果未解锁,则返回解锁内容 // 如果未解锁,则返回解锁内容
if (unlockRecord.data && unlockRecord.data.length <= 0) { if (unlockRecord.data && unlockRecord.data.length <= 0) {
article.content = { article.content = getRenderableArticleContent({
ops: unlockContent ops: unlockContent
} }, clientInfo)
return return
} }
// 将文章解锁替换为行结束符 \n // 将文章解锁替换为行结束符 \n
article.content = { article.content = getRenderableArticleContent({
ops: article.content.ops.map(op => { ops: article.content.ops.map(op => {
if (op.insert.unlockContent) { if (op.insert.unlockContent) {
op.insert = "\n" op.insert = "\n"
} }
return op 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)
}, [])
}