OpenCode 实践课:4 分钟用 Python 写出一个命令行 URL 短链接服务

项目介绍

你是否遇到过需要在手机上输入一长串网址的尴尬?今天我们用 Python 标准库写一个命令行 URL 短链接工具,零外部依赖、不到 100 行代码。它支持三个子命令:shorten 生成短码、serve 启动 HTTP 重定向服务、listdelete 管理映射。整个开发过程只用自然语言指挥 OpenCode,无需手写一行代码。

  • 技术栈:Python 3(标准库,无第三方依赖)
  • 最终代码:约 85 行

准备工作

  • Python 3.6+(系统自带即可,无需额外安装)
  • 在终端运行 pip install opencode 安装 OpenCode(详见 opencode.ai
  • 进入你的工作目录,打开 OpenCode 对话界面

实践过程

第 1 步:搭建 CLI 框架

打开 OpenCode,用一句话描述需求:

> 用户:帮我用 Python 写一个命令行 URL 短链接工具,文件名 shorturl.py,支持 shorten <url> 命令,生成 6 位短码并用字典存到内存,然后打印 http://localhost:8080/<code>。用 argparse 实现。

OpenCode 随即分析需求,生成包含 argparse 子命令解析、MD5 哈希截取前 6 位作为短码、以及主函数调度逻辑的代码。全程不到 10 秒。

关键要点:用自然语言把"输入→处理→输出"三条线说清楚即可,OpenCode 会自己处理边界。不一定要精确描述算法,说"生成 6 位短码"就够了,OpenCode 会选用恰当的哈希方案。

第 2 步:持久化到 JSON 文件

当前映射存在内存里,进程退出就没了。只需要追加一句话:

> 用户:把映射数据持久化到 ~/.url_shortener.json,启动时加载,每次 shorten 后保存,用 json 库就行。

OpenCode 理解了"启动时加载"和"操作后保存"的语义,自动添加了 load_db() / save_db() 两个函数,并在 shorten 和后续命令中正确插入了读写调用。它还在 JSON 文件不存在时返回空字典,避免了 FileNotFoundError

关键要点:不需要指定函数签名或异常处理细节,"用 json 库就行"就足够。OpenCode 会推断出合适的设计——这里它自然地将读写逻辑拆成了两个辅助函数。

第 3 步:添加 HTTP 重定向服务器

接下来让短链接真正"能用"——启动一个 HTTP 服务,访问短码时 302 跳转到原始 URL:

> 用户:加一个 serve 子命令,用 http.server 启动一个 HTTP 服务,收到 GET 请求时从 JSON 加载映射,匹配 path 中的短码,302 重定向到原 URL。找不到则返回 404。

OpenCode 创建了一个 RedirectHandler 类继承 BaseHTTPRequestHandler,在 do_GET 中解析路径、查映射、返回 302 或 404。它还贴心地重写了 log_message 来抑制默认的请求日志,保持终端清爽。

关键要点:描述行为而非实现——说"302 重定向"比说"设置 Location 头"更直观。OpenCode 知道 http.server / HTTPServer 的标准用法,不需要背 API。

第 4 步:增加 list 和 delete 管理命令

最后补齐管理功能:

> 用户:加上 list 命令列出所有短码和对应 URL,delete <code> 命令删除指定短码。list 展示为空时提示 "No URLs found."。

OpenCode 在已有的 CLI 分支中追加了两个 elif 分支,分别调用新增的 list_urls()delete() 函数,并在 delete 找不到短码时给出了友好的错误提示。

到此为止,整个项目完成。我们一共对 OpenCode 说了 4 句话,每次都是一句自然语言需求描述,它就生成并迭代了完整可运行代码。

完整代码

import argparse
import hashlib
import json
import os
from http.server import HTTPServer, BaseHTTPRequestHandler

DB_FILE = os.path.expanduser("~/.url_shortener.json")


def load_db():
    if not os.path.exists(DB_FILE):
        return {}
    with open(DB_FILE, "r") as f:
        return json.load(f)


def save_db(db):
    with open(DB_FILE, "w") as f:
        json.dump(db, f, indent=2)


def shorten(url):
    db = load_db()
    code = hashlib.md5(url.encode()).hexdigest()[:6]
    db[code] = url
    save_db(db)
    print(f"Short URL: http://localhost:8080/{code}")


def list_urls():
    db = load_db()
    if not db:
        print("No URLs found.")
        return
    for code, url in db.items():
        print(f"  /{code}  ->  {url}")


def delete(code):
    db = load_db()
    if code in db:
        del db[code]
        save_db(db)
        print(f"Deleted: /{code}")
    else:
        print(f"Not found: /{code}")


class RedirectHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        db = load_db()
        code = self.path.lstrip("/")
        if code in db:
            self.send_response(302)
            self.send_header("Location", db[code])
            self.end_headers()
        else:
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b"404 Not Found")

    def log_message(self, format, *args):
        pass


def serve(port):
    server = HTTPServer(("0.0.0.0", port), RedirectHandler)
    print(f"Serving on http://localhost:{port}")
    server.serve_forever()


def main():
    parser = argparse.ArgumentParser(description="URL Shortener CLI")
    sub = parser.add_subparsers(dest="command", required=True)

    p_shorten = sub.add_parser("shorten")
    p_shorten.add_argument("url", help="The long URL to shorten")

    sub.add_parser("list")

    p_delete = sub.add_parser("delete")
    p_delete.add_argument("code", help="Short code to delete")

    p_serve = sub.add_parser("serve")
    p_serve.add_argument("--port", type=int, default=8080, help="Listen port")

    args = parser.parse_args()

    if args.command == "shorten":
        shorten(args.url)
    elif args.command == "list":
        list_urls()
    elif args.command == "delete":
        delete(args.code)
    elif args.command == "serve":
        serve(args.port)


if __name__ == "__main__":
    main()

使用方式:

# 生成短链接
python shorturl.py shorten https://www.example.com/very/long/url

# 查看所有映射
python shorturl.py list

# 启动重定向服务
python shorturl.py serve --port 8080

# 删除指定短码
python shorturl.py delete a1b2c3

小结

这个项目从零到能用,全程只对 OpenCode 说了 4 句话,耗时约 4 分钟。对比传统开发流程——查文档、写 argparse、处理文件 I/O、实现 HTTP Server——至少需要 20-30 分钟。

OpenCode 真正改变的不是"能不能写代码",而是交互方式:你不再需要记住 API 签名或翻 STL 文档,只需要清晰描述你想要什么,AI 负责翻译成代码。这个从"脑子里的需求"到"屏幕上的代码"的链路,现在被压缩到了秒级。

如果你想试试,打开终端,输入上面任意一段提示词,看看 OpenCode 能不能帮你跑通整个过程。