refactor: 重构禅音工具路径与配置,新增私人剪贴板技能
这是一次大型重构和功能新增: 1. 将原 zen_box 工具重命名为 web_zen_box,调整所有相关路径与配置 2. 新增 web_order_box 原生Web应用的完整资源与部署脚本 3. 新增私人剪贴板后端技能与配套测试脚本 4. 修复订单盒子APP的API地址配置,避免路径重复拼接 5. 新增本地开发跨域白名单支持localhost 6. 清理旧版zen_box的冗余文件
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
// 重置 rateLimitMap(通过重启服务或等待)
|
||||
// 这里直接测试单个请求
|
||||
const http = require('http')
|
||||
const BASE = 'http://localhost:8976'
|
||||
const API = `${BASE}/api/v1/ingest/private_clipboard`
|
||||
|
||||
function post(path, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = JSON.stringify(body)
|
||||
const req = http.request(`${API}${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-API-Id': 'private_clipboard',
|
||||
'X-API-Key': 'pcb_nuS0qf-W63qcvAPsrG5ePA',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Content-Length': Buffer.byteLength(data)
|
||||
}
|
||||
}, (res) => {
|
||||
let b = ''
|
||||
res.on('data', d => b += d)
|
||||
res.on('end', () => resolve({ status: res.statusCode, body: b }))
|
||||
})
|
||||
req.on('error', reject)
|
||||
req.write(data)
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
async function sleep(ms) { return new Promise(r => setTimeout(r, ms)) }
|
||||
|
||||
async function run() {
|
||||
console.log('等待 32 秒让速率限制重置...')
|
||||
await sleep(32000)
|
||||
|
||||
// 1. 英文
|
||||
console.log('\n--- Test 1: 英文 ---')
|
||||
const r1 = await post('/text', { text: 'hello world' })
|
||||
console.log(`Status: ${r1.status}, Body: ${r1.body}`)
|
||||
|
||||
// 2. 中文
|
||||
console.log('\n--- Test 2: 中文 ---')
|
||||
const r2 = await post('/text', { text: '你好世界' })
|
||||
console.log(`Status: ${r2.status}, Body: ${r2.body}`)
|
||||
|
||||
// 3. XSS
|
||||
console.log('\n--- Test 3: XSS ---')
|
||||
const r3 = await post('/text', { text: '<script>alert(1)</script>' })
|
||||
console.log(`Status: ${r3.status}, Body: ${r3.body}`)
|
||||
|
||||
// 4. 特殊字符
|
||||
console.log('\n--- Test 4: 特殊字符 & < > ---')
|
||||
const r4 = await post('/text', { text: 'Tom & Jerry <best> "movie"' })
|
||||
console.log(`Status: ${r4.status}, Body: ${r4.body}`)
|
||||
|
||||
// 5. 查询验证
|
||||
console.log('\n--- Test 5: 查询最近 4 条 ---')
|
||||
await new Promise((resolve, reject) => {
|
||||
http.get(`${API}/latest?limit=4`, {
|
||||
headers: {
|
||||
'X-API-Id': 'private_clipboard',
|
||||
'X-API-Key': 'pcb_nuS0qf-W63qcvAPsrG5ePA'
|
||||
}
|
||||
}, (res) => {
|
||||
let b = ''
|
||||
res.on('data', d => b += d)
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(b)
|
||||
data.items.forEach((item, i) => {
|
||||
console.log(` [${i}] text_content: "${item.text_content}"`)
|
||||
})
|
||||
} catch { console.log(`Body: ${b}`) }
|
||||
resolve()
|
||||
})
|
||||
}).on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
run().catch(console.error)
|
||||
@@ -0,0 +1,78 @@
|
||||
// 快速测试:写入 + 查询
|
||||
const http = require('http')
|
||||
const API = 'http://localhost:8976/api/v1/ingest/private_clipboard'
|
||||
|
||||
function post(path, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = JSON.stringify(body)
|
||||
const req = http.request(`${API}${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-API-Id': 'private_clipboard',
|
||||
'X-API-Key': 'pcb_nuS0qf-W63qcvAPsrG5ePA',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Content-Length': Buffer.byteLength(data)
|
||||
}
|
||||
}, (res) => {
|
||||
let b = ''
|
||||
res.on('data', d => b += d)
|
||||
res.on('end', () => resolve({ status: res.statusCode, body: b }))
|
||||
})
|
||||
req.on('error', reject)
|
||||
req.write(data)
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
async function run() {
|
||||
let pass = 0, fail = 0
|
||||
|
||||
// 1. 中文
|
||||
const r1 = await post('/text', { text: '你好世界' })
|
||||
if (r1.status === 200) { console.log('✅ 中文写入成功'); pass++ }
|
||||
else { console.log(`❌ 中文写入失败: ${r1.body}`); fail++ }
|
||||
|
||||
// 2. XSS
|
||||
const r2 = await post('/text', { text: '<script>alert(1)</script>' })
|
||||
if (r2.status === 200) { console.log('✅ XSS 写入成功'); pass++ }
|
||||
else { console.log(`❌ XSS 写入失败: ${r2.body}`); fail++ }
|
||||
|
||||
// 3. 特殊字符
|
||||
const r3 = await post('/text', { text: 'Tom & Jerry <best> "movie"' })
|
||||
if (r3.status === 200) { console.log('✅ 特殊字符写入成功'); pass++ }
|
||||
else { console.log(`❌ 特殊字符写入失败: ${r3.body}`); fail++ }
|
||||
|
||||
// 4. 查询验证
|
||||
await new Promise((resolve) => {
|
||||
http.get(`${API}/latest?limit=3`, {
|
||||
headers: { 'X-API-Id': 'private_clipboard', 'X-API-Key': 'pcb_nuS0qf-W63qcvAPsrG5ePA' }
|
||||
}, (res) => {
|
||||
let b = ''
|
||||
res.on('data', d => b += d)
|
||||
res.on('end', () => {
|
||||
const data = JSON.parse(b)
|
||||
console.log('\n--- 查询结果 ---')
|
||||
data.items.forEach((item, i) => {
|
||||
console.log(`[${i}] "${item.text_content}"`)
|
||||
})
|
||||
// 验证转义
|
||||
const xss = data.items.find(i => i.text_content.includes('<'))
|
||||
if (xss) { console.log('\n✅ XSS 已转义'); pass++ }
|
||||
else { console.log('\n❌ XSS 未转义'); fail++ }
|
||||
|
||||
const cn = data.items.find(i => i.text_content === '你好世界')
|
||||
if (cn) { console.log('✅ 中文存储正确'); pass++ }
|
||||
else { console.log('❌ 中文存储错误'); fail++ }
|
||||
|
||||
const sp = data.items.find(i => i.text_content.includes('&') && i.text_content.includes('<'))
|
||||
if (sp) { console.log('✅ 特殊字符转义正确'); pass++ }
|
||||
else { console.log('❌ 特殊字符转义错误'); fail++ }
|
||||
|
||||
console.log(`\n=== 结果: ${pass} 通过, ${fail} 失败 ===`)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,43 @@
|
||||
// 测试速率限制
|
||||
const http = require('http')
|
||||
const API = 'http://localhost:8976/api/v1/ingest/private_clipboard'
|
||||
|
||||
function post(path, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = JSON.stringify(body)
|
||||
const req = http.request(`${API}${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-API-Id': 'private_clipboard',
|
||||
'X-API-Key': 'pcb_nuS0qf-W63qcvAPsrG5ePA',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Content-Length': Buffer.byteLength(data)
|
||||
}
|
||||
}, (res) => {
|
||||
let b = ''
|
||||
res.on('data', d => b += d)
|
||||
res.on('end', () => resolve({ status: res.statusCode, body: b }))
|
||||
})
|
||||
req.on('error', reject)
|
||||
req.write(data)
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
async function run() {
|
||||
console.log('开始发送 65 次请求...')
|
||||
let limited = false
|
||||
for (let i = 1; i <= 65; i++) {
|
||||
const r = await post('/text', { text: `ratelimit-${i}` })
|
||||
if (r.status === 429) {
|
||||
console.log(`第 ${i} 次请求被限制 (429)`)
|
||||
console.log(`响应: ${r.body}`)
|
||||
limited = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!limited) console.log('❌ 65 次请求全部通过,速率限制未生效')
|
||||
else console.log('✅ 速率限制生效')
|
||||
}
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* 邮件 SMTP 连接测试脚本
|
||||
* 测试 Gmail SMTP 465 端口连接和认证
|
||||
*/
|
||||
const nodemailer = require('nodemailer')
|
||||
const path = require('path')
|
||||
|
||||
// 加载环境变量
|
||||
const fs = require('fs')
|
||||
const localCredsPath = path.join(process.env.USERPROFILE || 'C:\\Users\\Administrator', 'Toolbox_local_creds.env.local')
|
||||
if (fs.existsSync(localCredsPath)) {
|
||||
fs.readFileSync(localCredsPath, 'utf-8')
|
||||
.split('\n')
|
||||
.filter(l => l.trim() && !l.startsWith('#'))
|
||||
.forEach(line => {
|
||||
const [key, ...valParts] = line.split('=')
|
||||
const val = valParts.join('=').trim().replace(/^["']|["']$/g, '')
|
||||
if (key && val) process.env[key.trim()] = val
|
||||
})
|
||||
}
|
||||
|
||||
// 也加载项目根目录的 .env(如果存在)
|
||||
const projectEnvPath = path.join(__dirname, '..', '..', '..', '..', '.env')
|
||||
if (fs.existsSync(projectEnvPath)) {
|
||||
fs.readFileSync(projectEnvPath, 'utf-8')
|
||||
.split('\n')
|
||||
.filter(l => l.trim() && !l.startsWith('#'))
|
||||
.forEach(line => {
|
||||
const [key, ...valParts] = line.split('=')
|
||||
const val = valParts.join('=').trim().replace(/^["']|["']$/g, '')
|
||||
if (key && val) process.env[key.trim()] = val
|
||||
})
|
||||
}
|
||||
|
||||
const smtpHost = process.env.EMAIL_SENDER_SMTP_HOST || 'smtp.gmail.com'
|
||||
const smtpUser = process.env.EMAIL_SENDER_SMTP_USER || ''
|
||||
const smtpPass = process.env.EMAIL_SENDER_SMTP_PASS || ''
|
||||
|
||||
console.log('=== Email Sender SMTP 连接测试 ===\n')
|
||||
console.log(`SMTP Host: ${smtpHost}`)
|
||||
console.log(`SMTP User: ${smtpUser || '(未配置)'}`)
|
||||
console.log(`SMTP Pass: ${smtpPass ? smtpPass.substring(0, 4) + '****' + smtpPass.substring(smtpPass.length - 2) : '(未配置)'}`)
|
||||
console.log(`\n`)
|
||||
|
||||
if (!smtpUser || !smtpPass) {
|
||||
console.error('❌ 错误: EMAIL_SENDER_SMTP_USER 或 EMAIL_SENDER_SMTP_PASS 未配置')
|
||||
console.error('请在 Toolbox_local_creds.env.local 中配置这两个环境变量')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function testPort(port, secure) {
|
||||
const mode = secure ? 'SSL (465)' : 'STARTTLS (587)'
|
||||
console.log(`\n--- 测试端口 ${port} (${mode}) ---\n`)
|
||||
|
||||
// DNS
|
||||
console.log('[1/3] 测试 DNS 解析...')
|
||||
try {
|
||||
const dns = require('dns')
|
||||
const addresses = await dns.promises.resolve(smtpHost)
|
||||
console.log(`✅ ${smtpHost} -> ${addresses.join(', ')}\n`)
|
||||
} catch (e) {
|
||||
console.error(`❌ DNS 解析失败: ${e.message}\n`)
|
||||
return false
|
||||
}
|
||||
|
||||
// TCP
|
||||
console.log(`[2/3] 测试 TCP 连接到 ${smtpHost}:${port}...`)
|
||||
const net = require('net')
|
||||
const tcpTimeout = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error('TCP 连接超时 (10s)')), 10000)
|
||||
})
|
||||
const tcpConnect = new Promise((resolve, reject) => {
|
||||
const socket = net.createConnection({ host: smtpHost, port })
|
||||
socket.on('connect', () => {
|
||||
socket.end()
|
||||
resolve()
|
||||
})
|
||||
socket.on('error', reject)
|
||||
})
|
||||
|
||||
try {
|
||||
await Promise.race([tcpConnect, tcpTimeout])
|
||||
console.log(`✅ TCP 连接成功\n`)
|
||||
} catch (e) {
|
||||
console.error(`❌ TCP 连接失败: ${e.message}\n`)
|
||||
return false
|
||||
}
|
||||
|
||||
// SMTP
|
||||
console.log('[3/3] 测试 SMTP 认证和发信...')
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: smtpHost,
|
||||
port,
|
||||
secure,
|
||||
auth: { user: smtpUser, pass: smtpPass },
|
||||
connectionTimeout: 15000,
|
||||
socketTimeout: 30000,
|
||||
greetingTimeout: 15000
|
||||
})
|
||||
|
||||
try {
|
||||
console.log(' → 验证 SMTP 连接...')
|
||||
await transporter.verify()
|
||||
console.log(' ✅ SMTP 验证成功\n')
|
||||
|
||||
console.log(' → 尝试发送测试邮件...')
|
||||
const info = await transporter.sendMail({
|
||||
from: smtpUser,
|
||||
to: smtpUser,
|
||||
subject: 'TRAE-Toolbox SMTP 测试',
|
||||
text: `这是一封自动测试邮件\n发送时间: ${new Date().toISOString()}\n如果收到此邮件,说明 SMTP 配置正常。`
|
||||
})
|
||||
console.log(` ✅ 邮件发送成功!`)
|
||||
console.log(` Message ID: ${info.messageId}`)
|
||||
console.log(` Response: ${info.response}`)
|
||||
} catch (e) {
|
||||
console.error(`❌ SMTP 操作失败: ${e.message}`)
|
||||
return false
|
||||
} finally {
|
||||
transporter.close()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function test() {
|
||||
// 先试 465 SSL
|
||||
const ok465 = await testPort(465, true)
|
||||
if (ok465) {
|
||||
console.log('\n=== 465 SSL 测试通过 ✅ ===')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('\n465 SSL 失败,尝试 587 STARTTLS...')
|
||||
|
||||
// 再试 587 STARTTLS
|
||||
const ok587 = await testPort(587, false)
|
||||
if (ok587) {
|
||||
console.log('\n=== 587 STARTTLS 测试通过 ✅ ===')
|
||||
console.log('\n建议: 将 EMAIL_SENDER_SMTP_PORT 改为 587,EMAIL_SENDER_SMTP_SECURE 设为 false')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('\n=== 两个端口都失败了 ❌ ===')
|
||||
console.log('\n可能原因:')
|
||||
console.log('1. Gmail 限制了当前 IP 的 SMTP 访问')
|
||||
console.log('2. 企业防火墙/安全组阻断了 Gmail SMTP')
|
||||
console.log('3. 需要检查 Gmail 账号安全设置')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
test().catch(e => {
|
||||
console.error('未预期错误:', e)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,108 @@
|
||||
// 私人剪贴板 API 验证测试
|
||||
const http = require('http')
|
||||
|
||||
const BASE = 'http://localhost:8976'
|
||||
const API = `${BASE}/api/v1/ingest/private_clipboard`
|
||||
const HEADERS = {
|
||||
'X-API-Id': 'private_clipboard',
|
||||
'X-API-Key': 'pcb_nuS0qf-W63qcvAPsrG5ePA',
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
|
||||
function post(path, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = JSON.stringify(body)
|
||||
const req = http.request(`${API}${path}`, {
|
||||
method: 'POST',
|
||||
headers: { ...HEADERS, 'Content-Length': Buffer.byteLength(data) }
|
||||
}, (res) => {
|
||||
let body = ''
|
||||
res.on('data', d => body += d)
|
||||
res.on('end', () => {
|
||||
try { resolve({ status: res.statusCode, body: JSON.parse(body) }) }
|
||||
catch { resolve({ status: res.statusCode, body }) }
|
||||
})
|
||||
})
|
||||
req.on('error', reject)
|
||||
req.write(data)
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
function get(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get(`${API}${path}`, { headers: HEADERS }, (res) => {
|
||||
let body = ''
|
||||
res.on('data', d => body += d)
|
||||
res.on('end', () => {
|
||||
try { resolve({ status: res.statusCode, body: JSON.parse(body) }) }
|
||||
catch { resolve({ status: res.statusCode, body }) }
|
||||
})
|
||||
}).on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
async function sleep(ms) { return new Promise(r => setTimeout(r, ms)) }
|
||||
|
||||
async function runTests() {
|
||||
let pass = 0, fail = 0
|
||||
|
||||
console.log('=== 1. 中文编码测试 ===')
|
||||
const r1 = await post('/text', { text: '你好世界' })
|
||||
if (r1.status === 200 && r1.body.ok) {
|
||||
// 查询最新一条
|
||||
const r1q = await get('/latest?limit=1')
|
||||
const text = r1q.body.items[0].text_content
|
||||
if (text === '你好世界') {
|
||||
console.log(' ✅ 中文存储和查询正常')
|
||||
pass++
|
||||
} else {
|
||||
console.log(` ❌ 查询返回: "${text}" (期望: "你好世界")`)
|
||||
fail++
|
||||
}
|
||||
} else {
|
||||
console.log(` ❌ 写入失败: ${JSON.stringify(r1)}`)
|
||||
fail++
|
||||
}
|
||||
|
||||
console.log('\n=== 2. XSS 转义测试 ===')
|
||||
const xssPayload = '<script>alert(1)</script>'
|
||||
const r2 = await post('/text', { text: xssPayload })
|
||||
if (r2.status === 200 && r2.body.ok) {
|
||||
const r2q = await get('/latest?limit=1')
|
||||
const text = r2q.body.items[0].text_content
|
||||
if (text.includes('<') && text.includes('>') && !text.includes('<script>')) {
|
||||
console.log(` ✅ XSS 已转义: "${text}"`)
|
||||
pass++
|
||||
} else {
|
||||
console.log(` ❌ XSS 未转义: "${text}"`)
|
||||
fail++
|
||||
}
|
||||
} else {
|
||||
console.log(` ❌ 写入失败: ${JSON.stringify(r2)}`)
|
||||
fail++
|
||||
}
|
||||
|
||||
console.log('\n=== 3. 速率限制测试 ===')
|
||||
let rateLimited = false
|
||||
for (let i = 0; i < 62; i++) {
|
||||
const r = await post('/text', { text: `ratelimit-test-${i}` })
|
||||
if (r.status === 429) {
|
||||
console.log(` ✅ 第 ${i + 1} 次请求被限制 (429)`)
|
||||
rateLimited = true
|
||||
break
|
||||
}
|
||||
// 快速请求
|
||||
}
|
||||
if (!rateLimited) {
|
||||
console.log(' ❌ 62 次请求全部通过,未触发速率限制')
|
||||
fail++
|
||||
} else {
|
||||
pass++
|
||||
}
|
||||
|
||||
console.log(`\n=== 结果: ${pass} 通过, ${fail} 失败 ===`)
|
||||
process.exit(fail > 0 ? 1 : 0)
|
||||
}
|
||||
|
||||
runTests().catch(e => { console.error('Error:', e); process.exit(1) })
|
||||
Reference in New Issue
Block a user