Skip to content

Repository files navigation

shorturl

自建短链接服务。把长 URL 变成 https://short.zzheng.dev/<6位短码>,供自己日常分享使用。

线上入口:https://short.zzheng.dev

组成 位置 说明
后端服务 仓库根目录(Go + Gin) 提供创建短链与跳转的 HTTP 接口
Raycast 客户端 raycast-extension/(TypeScript) 日常使用入口:在 Mac 上一条命令生成短链

怎么用

方式一:Raycast 插件(日常推荐)

在 MacBook 上用 Raycast 命令 Short URL Generator

  1. 打开 Raycast,输入 Short URL Generator
  2. 在搜索框粘贴或输入要缩短的 URL
  3. 回车 → 提示「短链接已生成」,短链已自动复制到剪贴板,直接粘贴即可分享

输入要求与容错:

  • 接受 http / https 地址;不写协议会自动补 https://,所以直接粘贴 example.com/path 也能用
  • 只补协议头,不会改写你给的路径与查询串
  • 非法输入(例如 qwqhttp:///Volumes/xxx 这类主机名不成立的内容)会被拦下并提示原因,不会提交

这是本地开发的扩展,没有发布到 Raycast Store。它由 MacBook 上的 launchd 常驻 ray develop 维持注册; 安装、验证与排障见 Ops 仓库的 hosts/macbook/raycast-shorturl/

方式二:直接调 API

curl -X POST https://short.zzheng.dev/shorten \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com/some/very/long/path?with=query"}'

返回:

{
  "code": 200,
  "msg": "Short URL created successfully",
  "data": {
    "id": 2098322940138360832,
    "short_url": "https://short.zzheng.dev/aB3xY9"
  }
}

拿到的 short_url 直接访问即可。

方式三:访问短链

浏览器或 curl 访问 https://short.zzheng.dev/<短码>,服务返回 302 跳转到原始地址:

curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://short.zzheng.dev/aB3xY9
# 302 https://example.com/some/very/long/path?with=query

接口一览

方法 路径 说明
POST /shorten 创建短链。body {"url":"..."}
GET /<短码> 302 跳转到原始地址
GET /ping 存活探针,返回 pong

错误响应统一是 {"code":<非200>, "msg":"..."}

HTTP code 场景
400 400 body 不是合法 JSON,或缺少 url
404 404 短码不存在,返回 Short URL not found
500 500 写库失败

实现细节(需要时再看):

  • 短码是 6 位 base62 随机串crypto/rand),不是自增
  • ping / shorten / info保留字,不会被分配为短码
  • 同一个 URL 重复提交会生成多条记录(没有按 URL 去重)

它是怎么连起来的

后端跑在家里 NAS 上,NAS 没有公网 IP,所以中间要经过两步"借道"才能被公网访问:

① Raycast 插件(MacBook)          ② curl / 浏览器(任意设备)
        │  HTTPS POST /shorten                │  HTTPS GET /<短码>
        └──────────────┬───────────────────────┘
                       ▼
          ③ 阿里云 Nginx(120.55.127.195:443)
             · TLS 终止,持有 short.zzheng.dev 证书
             · 把请求转给本机 FRPS(只暴露 80/443,NAS 不直接对外)
                       ▼
          ④ FRPS(阿里云)⇄ FRPC(NAS,代理名 logit-shorturl)
             · 反向隧道:把公网流量"穿"进没有公网 IP 的 NAS
                       ▼
          ⑤ NAS Docker Compose 项目 short-url
             ├── short-url        业务容器 :8087 ← 真正处理请求
             └── short-url-redis  缓存容器 :6379(仅容器内部通信)
                       ▼
          ⑥ SQLite 文件 /home/ziheng/short-url-data/shorturls.db ← 短链存这里

各段为什么存在:

组件 作用 少了它会怎样
阿里云 Nginx 固定公网入口 + HTTPS 证书 没有公网域名,只能用 IP+端口;也没有 TLS
FRPS / FRPC 内网穿透 NAS 无公网 IP,外界直接访问不到
Docker Compose 托管业务容器与缓存容器 手工 docker run 容易漂移(曾因此出过问题)
SQLite 持久化短链 重启后数据丢失

两条数据通路

创建短链POST /shorten):

写 SQLite(新短链记录)
  └─ 同时写 Redis 缓存(key = 短码,TTL 24 小时)

访问短链GET /<短码>):

先查 Redis ──命中──▶ 直接 302
  │
  未命中
  ▼
查 SQLite ──找到──▶ 302,并异步把 visit_count / last_visit 落库
  │
  没找到
  ▼
404

Redis 只是读缓存,且是可选的:连不上只会打一行错误日志,不影响创建与跳转(服务本身仍能正常工作,只是每次都查库)。


仓库结构

.
├── README.md                 本文件:用法与连接关系
├── Makefile                  构建 / 运行 / 部署 / 扩展的统一入口(make help 看全部)
├── docs/deployment.md        部署、验证、回滚手册
├── main.go                   后端入口与路由
├── config/                   配置加载(viper,支持环境变量覆盖)
├── database/                 SQLite(GORM AutoMigrate)与 Redis 初始化
├── handlers/                 HTTP 处理器:/shorten、/<短码>
├── models/                   数据模型(短链、会话、访问记录、地理位置)
├── services/                 业务逻辑(创建、缓存读写、访问统计)
├── utils/                    工具(雪花 ID、6 位 base62 短码)
├── tests/                    单元 / 集成 / 演示测试
├── Dockerfile                多阶段构建(CGO + sqlite)
├── docker-compose.yml        NAS 部署定义(路径为 NAS 绝对路径)
└── raycast-extension/        macOS Raycast 客户端

后端保持在仓库根目录(而不是挪进 backend/)是刻意选择:Dockerfiledocker-compose.yml 与部署流程都以仓库根为构建上下文,移动目录会连带改动部署链路和 NAS 上的克隆路径,收益不抵风险。


本地开发

后端

make dev        # Air 热重载(需先 go install github.com/air-verse/air@latest)
make build      # 编译到 tmp/short-url
make test       # go test ./...
make lint       # go fmt + go vet

不依赖 Docker 直接跑(会用到仓库里的 config.yaml):

go run .
curl -s http://127.0.0.1:3001/ping        # config.yaml 默认端口是 3001

扩展

cd raycast-extension
pnpm install     # 包管理器是 pnpm(仓库内为 pnpm-lock.yaml)
pnpm run dev     # ray develop:注册到 Raycast,改动热重载
pnpm run lint

扩展的输入校验逻辑是纯函数 src/validate-url.ts,可以脱离 Raycast GUI 测试:

make ext-test    # 19 条用例(合法 URL、无协议、IP、localhost、各类非法输入)

pnpm run dev 只在进程存活期间注册扩展,关终端就消失。长期可用要交给 launchd 常驻。


部署

部署目标只有一处:NAS。镜像在开发机交叉编译为 linux/amd64,导出 tar 传到 NAS 后 docker load

make deploy-nas

做的事:检查工作区干净 → 构建 short-url-nas:<git-short-sha>docker saversync 到 NAS → 写 NAS 部署目录的 .envIMAGE_TAG)→ docker load + docker compose up -d → 自检 /ping

三个要点:

  • 镜像标签 = 源码 commit SHA,所以"线上跑的是哪个版本"总能对回某个提交。回滚不必重新构建:
    make images-nas                  # 看 NAS 上已有哪些标签
    make rollback-nas TAG=<旧 sha>   # 改 .env 后 up -d
  • 部署不会覆盖线上配置。运行配置在 NAS 上独立维护(/home/ziheng/short-url-config/config.yaml),让"改配置"和"发版本"解耦。改配置走配置,改代码走部署。
  • 工作区脏时默认拒绝部署(标签会与构建内容不符,之后无法凭标签回滚)。确需跳过用 ALLOW_DIRTY=1 make deploy-nas

完整流程、验证清单、回滚与历史残留说明见 docs/deployment.md


配置

优先级:环境变量 > config.yaml > 代码内默认值。

server:
  port: "8087"
  gin_mode: "release"                    # debug | release
  domain: "https://short.zzheng.dev/"    # 用于拼接返回的 short_url
database:
  driver: "sqlite"
  dsn: "/data/shorturls.db"
redis:
  host: "redis"                          # 容器内用 compose 服务名;本地调试可写 localhost
  port: "6379"
  password: ""
  db: 0

两个容易踩的点:

  • server.domain 必须以 / 结尾:返回的 short_urldomain + 短码 直接拼接
  • 容器内 redis.host 要写 redis 而不是 localhost:Redis 是独立容器,写 localhost 会指向业务容器自身(曾经因此让缓存半年没生效)

环境变量可覆盖(viper AutomaticEnv):SERVER_PORTSERVER_GIN_MODEDATABASE_DSNREDIS_HOSTREDIS_PORT


相关文档

主题 位置
部署、验证、回滚、历史残留 docs/deployment.md
Raycast 扩展的安装与 launchd 常驻 Ops 仓库 hosts/macbook/raycast-shorturl/
NAS 侧服务配置副本与漂移记录 Ops 仓库 hosts/nas/short-url/
证书与 ACME 续期(short.zzheng.dev Ops 仓库 docs/services/证书与ACME续期速查.md

已知边界

  • 没有鉴权:能访问 /shorten 就能创建短链。属自用范围的取舍,靠入口未公开 + HTTPS 控制。
  • 没有删除接口:清理脏数据只能直接改 SQLite,并同步删掉 Redis 里同名 key,否则缓存还在、短链仍能跳转。
  • 不按 URL 去重:同一 URL 反复提交会产生多条短链。
  • 访问统计有延迟:重定向只读 original_url,命中缓存时不回写,所以 visit_count 不保证即时准确。
  • 运行镜像不随源码自动更新:改完代码必须显式 make deploy-nas
  • 单点:没有公网冗余,阿里云或 NAS 任一不可用都会导致短链失效。

About

短链 + Raycast 插件

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages