一个现代化的 URL 短链接服务,使用 Ruby Roda 作为后端,SvelteKit 作为前端,PocketBase 作为数据存储。
- URL 短链接生成,支持自定义短码
- 链接密码保护
- 链接过期设置
- 点击统计和分析
- 用户认证和管理
- 管理后台
- API Key 支持
- RESTful API
- 框架: Roda (Ruby)
- 数据库: PocketBase
- 认证方式: JWT + API Keys
- 框架: SvelteKit 2 (Svelte 5 with runes)
- 样式: Tailwind CSS
- 语言: TypeScript
cd frontend bun install # 安装依赖 bun run dev # 开发服务器 (端口 5173) bun run build # 生产构建 bun run preview # 预览生产构建 bun run check # TypeScript 类型检查 bun run lint # ESLint 检查
cd backend bundle install # 安装依赖 rackup -p 3000 # 启动服务器 bundle exec rerun rackup -p 3000 # 开发模式 (自动重载) bundle exec rubocop # 代码检查 bundle exec rubocop -a # 自动修复
- Podman
- Podman Compose
- 进入项目目录:
cd cuty- 启动所有服务:
podman-compose up -d
-
等待服务启动完成:
- PocketBase: http://localhost:8090
- 后端 API: http://localhost:3000
- 前端: http://localhost:5173
-
在浏览器中打开 http://localhost:5173
- 下载 PocketBase:
curl -L -o pocketbase https://github.com/pocketbase/pocketbase/releases/latest/download/pocketbase_*.zip unzip pocketbase_*.zip
- 启动 PocketBase:
./pocketbase serve --dev
- 进入后端目录:
cd backend- 安装 Ruby 依赖:
bundle install
- 复制环境变量文件:
cp .env.example .env
-
编辑
.env文件配置 -
启动后端服务:
bundle exec rackup -o 0.0.0.0 -p 3000- 进入前端目录:
cd frontend- 安装依赖:
bun i
- 创建环境变量文件:
echo "VITE_API_URL=http://localhost:3000/api/v1" > .env
- 启动开发服务器:
bun run dev
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}
POST /api/v1/shorten
Authorization: Bearer <token>
Content-Type: application/json
{
"target_url": "https://example.com/very/long/url",
"custom_url": "my-link",
"description": "My link",
"password": "optional-password",
"expires_at": "2024-12-31T23:59:59Z"
}
GET /api/v1/urls?limit=20&page=1
Authorization: Bearer <token>
GET /api/v1/urls/:id/stats
Authorization: Bearer <token>
GET /api/v1/info/:short_url
GET /u/:short_url
GET /u/:short_url?password=<password>
| 变量名 | 描述 | 默认值 |
|---|---|---|
| PORT | 服务器端口 | 3000 |
| JWT_SECRET | JWT 密钥 | - (必填) |
| POCKETBASE_URL | PocketBase 连接地址 | http://localhost:8090 |
| POCKETBASE_ADMIN_EMAIL | PocketBase 管理员邮箱 | admin@example.com |
| POCKETBASE_ADMIN_PASSWORD | PocketBase 管理员密码 | admin123456 |
| TOKEN_EXPIRY | Token 过期时间(秒) | 86400 |
| DEFAULT_DOMAIN | 默认域名 | localhost:3000 |
| LINK_LENGTH | 默认短链接长度 | 6 |
| DISALLOW_REGISTRATION | 禁止用户注册 | false |
| DISALLOW_ANONYMOUS_LINKS | 禁止匿名链接 | false |
| RATE_LIMIT_ENABLED | 启用限流 | true |
| RATE_LIMIT | 每分钟请求数限制 | 60 |
| 变量名 | 描述 |
|---|---|
| RECAPTCHA_SITE_KEY | reCAPTCHA 网站密钥 |
| RECAPTCHA_SECRET_KEY | reCAPTCHA 密钥 |
| MAIL_SMTP_HOST | SMTP 服务器地址 |
| MAIL_SMTP_PORT | SMTP 端口 |
| MAIL_SMTP_USER | SMTP 用户名 |
| MAIL_SMTP_PASSWORD | SMTP 密码 |
| MAIL_FROM | 发件人邮箱 |
| 变量名 | 描述 |
|---|---|
| VITE_API_URL | 后端 API 地址 |
cuty/
├── backend/ # Ruby Roda API
│ ├── app.rb # 主应用程序
│ ├── config.ru # Rack 配置
│ ├── Gemfile # Ruby 依赖
│ ├── routes/ # API 路由
│ │ ├── api.rb # API 基础路由
│ │ ├── auth.rb # 认证路由
│ │ ├── shortener.rb # 短链接路由
│ │ └── admin.rb # 管理后台路由
│ ├── models/ # 数据模型
│ ├── services/ # 业务逻辑
│ │ ├── database.rb # 数据库连接
│ │ ├── auth_service.rb # 认证服务
│ │ ├── shortener_service.rb # 短链接服务
│ │ └── stats_service.rb # 统计服务
│ └── middleware/ # Rack 中间件
├── frontend/ # SvelteKit 应用
│ ├── src/
│ │ ├── lib/
│ │ │ ├── api/ # API 客户端
│ │ │ ├── components/ # Svelte 组件
│ │ │ │ ├── Header.svelte
│ │ │ │ ├── Footer.svelte
│ │ │ │ ├── Toast.svelte
│ │ │ │ ├── LoadingSpinner.svelte
│ │ │ │ └── DashboardLayout.svelte
│ │ │ ├── stores/ # 状态管理
│ │ │ ├── types/ # TypeScript 类型
│ │ │ └── utils/ # 工具函数
│ │ ├── routes/ # 页面路由
│ │ │ ├── +page.svelte # 首页
│ │ │ ├── login/ # 登录
│ │ │ ├── register/ # 注册
│ │ │ ├── dashboard/ # 用户仪表盘
│ │ │ ├── settings/ # 设置
│ │ │ ├── admin/ # 管理后台
│ │ │ ├── link/[shortUrl]/ # 公开链接页
│ │ │ ├── 404/ # 404 页面
│ │ │ └── +error.svelte # 全局错误页面
│ │ └── app.css # 全局样式
│ ├── package.json
│ ├── svelte.config.js
│ └── tailwind.config.ts
├── docker-compose.yml
├── AGENTS.md # 开发指南
└── README.md
MIT