前言
公司产品针对样例中敏感数据的高亮,一直是使用高亮数据字符直接进行匹配的,这样就会有一个问题,比如:某验证码是敏感数据,如果某个数字串之间刚好包含这个验证码的值,那么这区间就会高亮,出现误差。现在需要换成后端返回敏感数据字符的起始下标值,前端计算敏感数据的范围,使高亮数据准确。
计算字符串中敏感数据在编辑器中的范围
刚接到这个需求,看到这个后端返回的偏移量,笔者以为返回的是在每一行的偏移量,只要算出该敏感数据在编辑器中的每一行就行了。到后面发现后端返回的偏移量是从第一行第一个字符开始,因为,样例原始数据在后端就是一个字符串,没法根据换行符计算在编辑器的哪一行。
没有思路就看看monaco
的文档吧,无聊之际,给我发现了一个API
:getPositionAt
,用于获取给定偏移量的位置,那么后端返回的起始下标就有了用武之地,可以计算出敏感数据的range
,直接救了我🐶命!!!兄弟们,还是要好好看文档!
getPositionAt
- 语法:
getPositionAt(offset: number): IPosition
- 参数:
offset
- 返回值:
IPosition
- 描述: 用于获取给定偏移量的位置。
offset
参数是一个number
类型,表示要获取位置的偏移量。
IPosition
ts 代码解读复制代码interface IPosition {
column: number,
lineNumber: number
}
- column
类型:
number
只读
;默认值:-
;可选项:-
;描述:列
- lineNumber
类型:
number
只读
;默认值:-
‘可选项:-
;描述:行号
- 根据
getPositionAt
获取position
:
js 代码解读复制代码const model = editor.getModel();
const startPosition = model.getPositionAt(valueIndex.startIndex);
const endPosition = model.getPositionAt(valueIndex.endIndex);
const currentRange = {
startLineNumber: startPosition.lineNumber, // 起始位置的行号。
startColumn: startPosition.column, // 起始位置的列号。
endLineNumber: endPosition.lineNumber, // 结束位置的行号。
endColumn: endPosition.column + 1 // 结束位置的列号。
};
editor.setSelection(currentRange);
根据以上代码,便可以得出该敏感数据在编辑器中的具体范围,再使用setSelection
进行选中该范围
getPositionAt存在的问题(位置有偏差)
在自测过程中,发现如果后端返回类型为html
、doc
时,在编辑器中会自动换行,使用getPositionAt
进行计算位置时会有偏差,如下图所示:
经过排查发现,这是后端返回的原始内容中只要携带\n
就会出现计算位置不准确,比如上面的html
、text
文本。原本的想法是后端把所有的\n
全部去除,但会修改原始内容,直接pass
,那么只能由前端重新计算敏感数据的位置。
计算带\n
字符串中敏感数据在编辑器中的范围
为了解决这个问题,需要分三步完成操作:
- 解析字符串:将字符串按换行符分割成多行,以便精确计算字符的位置。
- 计算行列范围:将全局字符偏移量(
startIndex
和endIndex
)转换为 Monaco 编辑器中对应的行和列。 - 应用高亮:利用 Monaco 的 API 将计算出的范围应用到编辑器中,以实现精准的高亮效果。
实现步骤详解
1. 将字符串分割为行
我们首先需要将原始字符串按照换行符 \n
分割成多个行。这样可以将字符位置从全局偏移量转化为每行的相对偏移量。以下代码展示了如何实现:
js 代码解读复制代码const originValue = `'HTTP/1.1 200 OKdate: Thu, 1#################51 GMT\r\nserver: Simple###############/2.7.5\r\ncontent-length: 1#\r\nlast-modified: Wed, 0#################19 GMT\r\ncontent-disposition: attach#############=f.txt\r\ncontent-type: applic############stream\r\n\r\nfile content test!!!!\n 147######12\n'`;
// 分割字符串为行
const lines = originValue.split('\n');
将计算方法封装成一个方法函数,具体实现见如下代码,传入索引以及行数组即可得到行号和列号:
js 代码解读复制代码/**
* 获取行号和列号
* @param {number} index 索引
* @param {string[]} lines 行数组
*/
export function getLineAndColumn(index, lines) {
let line = 0; // 当前行索引
let column = index; // 当前列索引(初始为字符偏移量)
/**
* 遍历每一行。lineLength:当前行的长度,加 1 是因为换行符 \n 也占一个字符。
* 判断当前偏移量是否在当前行内:
* 如果偏移量小于当前行的长度,则说明目标字符在这一行,返回行号和列号。
* 如果偏移量超过当前行的长度,则减去当前行的长度,更新剩余偏移量,继续检查下一行。
* 如果遍历完所有行,偏移量仍未完全分配,则认为字符在最后一行的某个位置。
*/
// 遍历每一行,计算行和列
while (line < lines.length) {
const lineLength = lines[line].length + 1; // 每行的字符数(+1 包括换行符)
if (column < lineLength) {
return { lineNumber: line + 1, column: column + 1 }; // Monaco 编辑器的行、列从 1 开始
}
column -= lineLength; // 减去当前行的总长度,更新偏移量
line++; // 检查下一行
}
// 如果超出范围,返回最后一行
return { lineNumber: lines.length - 1, column: column }; // 最后一行
}
具体使用(本项目中的数据结构代码,循环因数据结构而异):
js 代码解读复制代码const lines = editorContent.split('\n');
const initEditorRangeList =
locationObj.data.extractValueDtos?.flatMap(
(extractValue) =>
extractValue.labelValueIndexList?.flatMap((labelValueIndex) =>
labelValueIndex.valueIndexList.map((valueIndex) => {
const startPosition = getLineAndColumn(valueIndex.startIndex,lines);
const endPosition = getLineAndColumn(valueIndex.endIndex,lines);
return {
startLineNumber: startPosition.lineNumber,
startColumn: startPosition.column,
endLineNumber: endPosition.lineNumber,
endColumn: endPosition.column + 1
};
})
) || []
) || [];
// 仅渲染可见区域内的装饰器,视窗外的内容则延迟加载或忽略。
highlightVisibleRanges(editor, initEditorRangeList);
highlightVisibleRanges
方法,请继续阅读下文。使用修改之后的计算位置逻辑,实现精准匹配:
优化高亮逻辑
有些样例数据过大,为了避免每次渲染量过大,可能导致该样例内的敏感数据没有高亮,可以使用getVisibleRanges()
获取编辑器的可见范围,只渲染编辑器可视范围内的敏感数据,当可视范围发生变化时,再次执行匹配高亮逻辑。优化后的代码如下:
js 代码解读复制代码/**
* 高亮指定范围内的敏感数据
* @param {*} editor 编辑器实例
* @param {*} rangeList 需要高亮的范围数组
* @param {*} inlineClassName 自定义高亮类名
*/
export function highlightVisibleRanges(
editor,
rangeList,
inlineClassName = 'mtk5'
) {
function handler() {
// 获取编辑器的可见范围
const visibleRanges = editor.getVisibleRanges();
const visibleDecorations = rangeList.filter((range) => {
return visibleRanges.some(
(visibleRange) =>
range.startLineNumber >= visibleRange.startLineNumber &&
range.endLineNumber <= visibleRange.endLineNumber
);
});
// 创建装饰器集合,使其高亮
editor.createDecorationsCollection(
visibleDecorations.map((range, index) => ({
id: index,
range,
options: { inlineClassName }
}))
);
}
if (rangeList.length > 0) {
// 监听编辑器滚动事件并刷新匹配逻辑
editor.onDidScrollChange(() => {
handler();
});
handler();
}
}
评论记录:
回复评论: