开发过程中经常需要查 DNS 记录排查域名解析问题,每次打开命令行敲 dig 或 nslookup 虽然方便,但输出信息杂乱。本文我们用 OpenCode 写一个整洁的命令行 DNS 查询工具 dnslookup,支持 A、AAAA、MX、TXT、NS 等常见记录类型,还能指定自定义 DNS 服务器。
dns 模块)效果预览:
$ node dnslookup.js github.com -t A github.com ────────────────────────────────────────── A 140.82.121.3 A 140.82.121.4 $ node dnslookup.js github.com -t MX github.com ────────────────────────────────────────── MX aspmx.l.google.com. (priority: 1) MX alt1.aspmx.l.google.com. (priority: 5)
dns.promises 需要)npm install -g @anthropic/opencode 或参考 opencode.ai在终端进入项目目录,opencode 启动交互会话即可开始。
打开 OpenCode,用一句话描述项目需求:
> 我对 OpenCode 说:
> "用 Node.js 写一个命令行 DNS 查询工具,叫 dnslookup.js。用内置的 dns.promises,支持参数 -t 指定记录类型(默认 A),-s 指定 DNS 服务器,-h 查看帮助。入口函数叫 main,要求能处理错误。"
OpenCode 立刻生成了带 shebang 的完整脚本骨架,包括参数解析、帮助信息和基本的 resolve 调用逻辑。它自动加了 'use strict',还贴心地对不支持的记录类型做了提示。
关键要点: 第一次提示词要覆盖核心需求——入口、参数、帮助、错误处理。OpenCode 会补全你没想到的细节,比如 --help 的长格式。
骨架能跑了,但输出是裸 JSON,不够直观:
> 我对 OpenCode 说:
> "输出太丑了。帮我美化:顶部打印域名和下划线分隔,每条记录加记录类型的标签(如 A、MX),MX 记录显示优先级。用 ANSI 颜色区分标签(A 绿色、MX 黄色、错误红色)。"
OpenCode 分析了当前 console.log 的调用位置,重构成一个 printResults 函数,用模板字符串拼接颜色代码,还自动处理了 MX 记录的解构取 priority 和 exchange 字段。
// OpenCode 生成的颜色常量
const COLORS = {
reset: '\x1b[0m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
cyan: '\x1b[36m',
};
关键要点: 告诉 OpenCode"哪个字段怎么显示",它会自己找到数据结构中的对应属性。不用写具体代码,描述视觉效果就行。
测试时发现 TXT 返回的是数组数组,SOA 返回的是对象,显示成一团乱码:
> 我对 OpenCode 说:
> "TXT 记录返回的是二维数组,需要把每行的 strings 拼成一段文本。SOA 记录是一个对象,把 nsname、hostmaster、serial、refresh、retry、expire、minttl 字段逐行格式化展示。"
OpenCode 在 printResults 里加了两段专门的格式化逻辑,TXT 用 join(' ') 拼接,SOA 格式化成多行缩进。整个过程不到 30 秒。
关键要点: 遇到输出格式问题,直接把"原始数据长什么样、希望变成什么样"告诉 OpenCode,它比你手动写 forEach + join 快得多。
以下是最终的 dnslookup.js,约 100 行,零依赖,保存后直接 node dnslookup.js baidu.com 即可使用。
#!/usr/bin/env node
'use strict';
const dns = require('dns').promises;
const VALID_TYPES = ['A', 'AAAA', 'MX', 'NS', 'CNAME', 'TXT', 'SOA', 'PTR', 'SRV'];
const COLORS = {
reset: '\x1b[0m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
cyan: '\x1b[36m',
bold: '\x1b[1m',
};
function help() {
console.log(`dnslookup - DNS 记录查询工具
用法:
node dnslookup.js <domain> [选项]
选项:
-t, --type <type> 记录类型: A, AAAA, MX, NS, CNAME, TXT, SOA (默认: A)
-s, --server <ip> 指定 DNS 服务器
-h, --help 显示帮助信息
示例:
node dnslookup.js github.com
node dnslookup.js github.com -t MX
node dnslookup.js baidu.com -t SOA -s 8.8.8.8`);
}
function color(type, text) {
return `${COLORS[type] || ''}${text}${COLORS.reset}`;
}
function printResults(domain, type, records) {
console.log(`\n ${color('bold', domain)}`);
console.log(` ${'─'.repeat(40)}`);
if (!records || records.length === 0) {
console.log(` ${color('red', '未查询到记录')}\n`);
return;
}
if (type === 'SOA') {
const soa = records[0];
console.log(` ${color('green', 'SOA')} ${soa.nsname}`);
console.log(` hostmaster: ${soa.hostmaster}`);
console.log(` serial: ${soa.serial}`);
console.log(` refresh: ${soa.refresh}`);
console.log(` retry: ${soa.retry}`);
console.log(` expire: ${soa.expire}`);
console.log(` minttl: ${soa.minttl}`);
} else if (type === 'TXT') {
records.forEach(r => {
const text = Array.isArray(r) ? r.join(' ') : r;
console.log(` ${color('green', 'TXT')} ${text}`);
});
} else if (type === 'MX') {
records.forEach(r => {
console.log(` ${color('yellow', 'MX')} ${r.exchange} (priority: ${r.priority})`);
});
} else {
records.forEach(r => {
const value = typeof r === 'string' ? r : r.address || JSON.stringify(r);
console.log(` ${color('green', type)} ${value}`);
});
}
console.log('');
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
help();
process.exit(0);
}
const domain = args[0];
let type = 'A';
let server = null;
for (let i = 1; i < args.length; i++) {
if (args[i] === '-t' || args[i] === '--type') {
type = args[++i]?.toUpperCase() || 'A';
} else if (args[i] === '-s' || args[i] === '--server') {
server = args[++i];
}
}
if (!VALID_TYPES.includes(type)) {
console.error(color('red', `不支持的类型: ${type},支持: ${VALID_TYPES.join(', ')}`));
process.exit(1);
}
try {
dns.setServers(server ? [server] : []);
const resolver = server ? new dns.Resolver() : dns;
if (server) resolver.setServers([server]);
let records;
switch (type) {
case 'A': records = await dns.resolve4(domain); break;
case 'AAAA': records = await dns.resolve6(domain); break;
case 'MX': records = await dns.resolveMx(domain); break;
case 'NS': records = await dns.resolveNs(domain); break;
case 'CNAME': records = await dns.resolveCname(domain); break;
case 'TXT': records = await dns.resolveTxt(domain); break;
case 'SOA': records = await dns.resolveSoa(domain); break;
case 'PTR': records = await dns.resolvePtr(domain); break;
case 'SRV': records = await dns.resolveSrv(domain); break;
default: records = [];
}
printResults(domain, type, [].concat(records));
} catch (err) {
console.error(`\n ${color('red', '查询失败')}: ${err.message}\n`);
process.exit(1);
}
}
main();
全程 3 轮对话,约 4 分钟,产出一个 100 行、零依赖、开箱即用的 DNS 查询工具。如果手写的话,查文档、调格式、处理各种记录类型的差异,至少要 20-30 分钟。
OpenCode 最大的价值不在于"帮你写代码",而是省掉了查文档和调试格式化逻辑的时间。你只需要描述"想要什么效果",它负责翻译成代码。对于这种小而明确的工具类项目,效率提升尤为明显。