Add plugin ztool_developer_box v1.0.0#277
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new ZTools developer toolbox plugin built with Vue 3, Vite, and TypeScript, featuring tools for random number and string generation, XML formatting/compression, and file operations. The code review highlights several key issues: an exclusive upper bound bug in the random integer generator, a performance bottleneck in random string generation due to excessive crypto.getRandomValues calls, a bug in the XML formatter that prevents using a zero-space indent, and a silent failure risk in the image-writing preload service when handling invalid Base64 formats.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const output: string[] = [] | ||
| for (let i = 0; i < countVal; i++) { | ||
| const raw = minVal + Math.random() * (maxVal - minVal) | ||
| if (isInteger.value) { | ||
| output.push(String(Math.floor(raw))) | ||
| } else { | ||
| const places = Math.max(0, Math.min(10, Number(decimalPlaces.value) || 0)) | ||
| output.push(raw.toFixed(places)) | ||
| } | ||
| } |
There was a problem hiding this comment.
在生成随机整数时,使用 Math.floor(minVal + Math.random() * (maxVal - minVal)) 会导致最大值 maxVal 永远无法被取到(上界是开区间)。例如,当最小值为 1,最大值为 2 时,生成的随机数永远只能是 1。为了使随机整数的生成范围包含最大值(闭区间),建议修正生成公式。
const output: string[] = []
for (let i = 0; i < countVal; i++) {
if (isInteger.value) {
const val = Math.floor(Math.random() * (maxVal - minVal + 1)) + minVal
output.push(String(val))
} else {
const raw = minVal + Math.random() * (maxVal - minVal)
const places = Math.max(0, Math.min(10, Number(decimalPlaces.value) || 0))
output.push(raw.toFixed(places))
}
}
| const pickRandomChar = (pool: string) => { | ||
| const randomValues = new Uint32Array(1) | ||
| crypto.getRandomValues(randomValues) | ||
| return pool[randomValues[0] % pool.length] | ||
| } | ||
|
|
||
| const generateOne = (pool: string, len: number) => { | ||
| let result = '' | ||
| for (let i = 0; i < len; i++) { | ||
| result += pickRandomChar(pool) | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| const generate = () => { | ||
| error.value = '' | ||
| const len = Number(length.value) | ||
| const countVal = Number(count.value) | ||
| const pool = charsetPool.value | ||
|
|
||
| if (!useDigit.value && !useUpper.value && !useLower.value && !useSpecial.value) { | ||
| error.value = '请至少选择一种字符类型' | ||
| results.value = [] | ||
| return | ||
| } | ||
| if (Number.isNaN(len) || Number.isNaN(countVal)) { | ||
| error.value = '请输入有效的数字' | ||
| results.value = [] | ||
| return | ||
| } | ||
| if (len < 1 || len > 1024 || !Number.isInteger(len)) { | ||
| error.value = '字符串长度须为 1 ~ 1024 的整数' | ||
| results.value = [] | ||
| return | ||
| } | ||
| if (countVal < 1 || countVal > 1000 || !Number.isInteger(countVal)) { | ||
| error.value = '生成数量须为 1 ~ 1000 的整数' | ||
| results.value = [] | ||
| return | ||
| } | ||
|
|
||
| const output: string[] = [] | ||
| for (let i = 0; i < countVal; i++) { | ||
| output.push(generateOne(pool, len)) | ||
| } | ||
| results.value = output | ||
| } |
There was a problem hiding this comment.
在循环中高频调用 crypto.getRandomValues 会带来极大的性能开销。如果用户尝试生成 1000 个长度为 1024 的字符串,将会调用该 API 超过 100 万次,导致界面严重卡顿。建议在生成前一次性分配所需大小的 Uint32Array,通过单次 API 调用获取所有随机值,从而大幅提升性能。
const generate = () => {
error.value = ''
const len = Number(length.value)
const countVal = Number(count.value)
const pool = charsetPool.value
if (!useDigit.value && !useUpper.value && !useLower.value && !useSpecial.value) {
error.value = '请至少选择一种字符类型'
results.value = []
return
}
if (Number.isNaN(len) || Number.isNaN(countVal)) {
error.value = '请输入有效的数字'
results.value = []
return
}
if (len < 1 || len > 1024 || !Number.isInteger(len)) {
error.value = '字符串长度须为 1 ~ 1024 的整数'
results.value = []
return
}
if (countVal < 1 || countVal > 1000 || !Number.isInteger(countVal)) {
error.value = '生成数量须为 1 ~ 1000 的整数'
results.value = []
return
}
const totalChars = len * countVal
const randomValues = new Uint32Array(totalChars)
crypto.getRandomValues(randomValues)
let valIndex = 0
const output: string[] = []
for (let i = 0; i < countVal; i++) {
let result = ''
for (let j = 0; j < len; j++) {
result += pool[randomValues[valIndex++] % pool.length]
}
output.push(result)
}
results.value = output
}
| const runFormat = () => { | ||
| error.value = '' | ||
| try { | ||
| output.value = formatXml(input.value, ' '.repeat(Math.max(0, Number(indentSize.value) || 2))) | ||
| } catch (err) { | ||
| error.value = err instanceof Error ? err.message : 'XML 格式化失败' | ||
| output.value = '' | ||
| } | ||
| } |
There was a problem hiding this comment.
在 runFormat 中,使用 Number(indentSize.value) || 2 来处理缩进大小。当用户主动将缩进设置为 0 时,Number(0) 的结果为 0(假值),这会导致表达式回退到默认值 2。因此,用户将无法生成无缩进的格式化 XML。建议使用更精确的空值合并或类型判断来保留 0 缩进。
const runFormat = () => {
error.value = ''
try {
const indent = typeof indentSize.value === 'number' && !Number.isNaN(indentSize.value) ? Math.max(0, indentSize.value) : 2
output.value = formatXml(input.value, ' '.repeat(indent))
} catch (err) {
error.value = err instanceof Error ? err.message : 'XML 格式化失败'
output.value = ''
}
}
| // 图片写入到下载目录 | ||
| writeImageFile(base64Url) { | ||
| const matchs = /^data:image\/([a-z]{1,20});base64,/i.exec(base64Url) | ||
| if (!matchs) return | ||
| const filePath = path.join( | ||
| window.ztools.getPath('downloads'), | ||
| Date.now().toString() + '.' + matchs[1] | ||
| ) | ||
| fs.writeFileSync(filePath, base64Url.substring(matchs[0].length), { encoding: 'base64' }) | ||
| return filePath | ||
| } |
There was a problem hiding this comment.
在 writeImageFile 中,如果传入的 base64Url 格式不匹配正则表达式(例如不包含 data:image/... 前缀),函数会直接返回 undefined。这会导致调用方 Write/index.vue 无法检测到错误(因为没有抛出异常),从而静默退出插件,给用户造成保存成功的假象。建议在格式不匹配时抛出异常,以便触发调用方的错误捕获和通知机制。
// 图片写入到下载目录
writeImageFile(base64Url) {
const matchs = /^data:image\/([a-z]{1,20});base64,/i.exec(base64Url)
if (!matchs) {
throw new Error('无效的图片 Base64 格式')
}
const filePath = path.join(
window.ztools.getPath('downloads'),
Date.now().toString() + '.' + matchs[1]
)
fs.writeFileSync(filePath, base64Url.substring(matchs[0].length), { encoding: 'base64' })
return filePath
}
插件信息
本次变更
截图 / 演示
自检清单
plugins/ztooldeveloperbox/目录此 PR 由 ztools-plugin-cli 自动管理:每次
ztools publish在分支上追加一个 commit,PR 链接保持不变。