家电搜索系统后端 API,基于 Hono + PostgreSQL 构建,部署在阿里云函数计算 (FC 3.0)。
| 服务 | 地址 |
|---|---|
| API | https://appliance-api.cheapgo.top |
| 管理后台 | https://appliance-api.cheapgo.top/admin |
| 静态资源 | https://static.cheapgo.top (CDN) |
- 运行时: Node.js 20
- 框架: Hono
- ORM: Drizzle ORM
- 数据库: 阿里云 RDS PostgreSQL 18 (Serverless) + pg_jieba 中文分词
- 对象存储: 阿里云 OSS (
cheapgo-assets) - CDN: 阿里云 CDN (
static.cheapgo.top) - 部署: 阿里云函数计算 (FC 3.0)
- CI/CD: GitHub Actions + Serverless Devs
- SSL: Let's Encrypt 通配符证书 (
*.cheapgo.top)
appliance-api/
├── src/
│ ├── admin/ # 管理后台 (SSR)
│ │ ├── pages/ # 页面模板
│ │ │ ├── dashboard.ts # 仪表盘
│ │ │ ├── products.ts # 产品管理 (含图片上传)
│ │ │ ├── categories.ts # 分类管理
│ │ │ ├── category-params.ts # 品类参数规范
│ │ │ ├── product-images.ts # 图片管理
│ │ │ ├── users.ts # 用户管理
│ │ │ ├── logs.ts # 操作日志
│ │ │ └── login.ts # 登录
│ │ ├── routes.ts # SSR 路由
│ │ ├── layout.ts # 布局模板
│ │ └── middleware.ts # 认证中间件
│ ├── routes/ # API 路由
│ │ ├── search.ts # 搜索接口
│ │ ├── detail.ts # 详情接口
│ │ ├── category.ts # 分类接口
│ │ └── admin/ # 管理后台 API
│ │ ├── products.ts # 产品 CRUD
│ │ ├── categories.ts # 分类 CRUD
│ │ ├── category-params.ts # 参数规范 CRUD
│ │ ├── product-images.ts # 图片 CRUD
│ │ ├── upload.ts # 图片上传 API
│ │ ├── auth.ts # 认证 API
│ │ └── stats.ts # 统计 API
│ ├── db/ # 数据库(schema / seed / 查询)
│ │ ├── schema.ts # Drizzle schema 定义
│ │ ├── drizzle.ts # Drizzle 客户端
│ │ ├── queries.ts # 类型安全查询函数
│ │ ├── index.ts # 连接池 (兼容旧代码)
│ │ ├── seed.ts # 初始化种子数据
│ │ ├── snapshots/ # category_params 快照
│ │ ├── migrate-search-vector.ts # 加权全文 + params_search_text
│ │ └── fill-pinyin.ts # 拼音补全
│ ├── utils/
│ │ └── oss.ts # 阿里云 OSS 上传工具
│ ├── middleware/
│ │ └── auth.ts # JWT 认证中间件
│ ├── fc-handler.ts # FC 3.0 入口
│ └── index.ts # 本地开发入口
├── scripts/ # 打包与数据导入脚本
├── drizzle/ # Drizzle migration 文件
├── s.yaml # Serverless Devs 配置
├── drizzle.config.ts # Drizzle Kit 配置
├── .github/workflows/
│ ├── deploy.yml # 代码部署
│ └── renew-ssl.yml # SSL 证书续签
└── package.json
# 1. 安装依赖
npm install
# 2. 配置环境变量
cp .env.example .env
# 编辑 .env 填入数据库连接信息
# 3. 初始化数据库 (首次)
npm run db:push # Drizzle 推送 schema
npm run db:seed # 灌入种子
npm run migrate:search-vector # 全文搜索(空库或升级加权索引时跑)
# 4. 导入本地整理数据(可选)
npm run import:zol-ac
npm run fill:pinyin
npm run check:etl
# 5. 启动开发服务器
npm run dev
# 访问 http://localhost:3000| 变量名 | 说明 | 必填 |
|---|---|---|
DB_HOST |
数据库地址 | ✅ |
DB_PORT |
数据库端口 (默认 5432) | |
DB_NAME |
数据库名 | ✅ |
DB_USER |
数据库用户 | ✅ |
DB_PASSWORD |
数据库密码 | ✅ |
JWT_SECRET |
JWT 密钥 | ✅ |
ALIYUN_ACCESS_KEY_ID |
阿里云 AK (图片上传) | ✅ |
ALIYUN_ACCESS_KEY_SECRET |
阿里云 SK (图片上传) | ✅ |
CDN_DOMAIN |
CDN 域名 (默认 https://static.cheapgo.top) |
访问 /admin,默认账号 admin / admin123。
| 模块 | 说明 |
|---|---|
| 📊 仪表盘 | 数据概览、分类统计、热门搜索、最近产品 |
| 📦 产品管理 | CRUD + 图片上传 (拖拽暂存, 提交时传 OSS) |
| 📂 分类管理 | 16 个家电分类, 支持层级, 带 icon |
| ⚙️ 参数规范 | 定义每个品类的参数 (enum/number/text, 可筛选/排序) |
| 🖼️ 图片管理 | 查看/编辑/删除产品图片, 支持按类型筛选 |
| 👥 用户管理 | 超级管理员专属 |
| 📋 操作日志 | 记录登录、增删改操作 |
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/admin/login |
登录 (返回 JWT + Cookie) |
| GET | /api/admin/products |
产品列表 |
| POST | /api/admin/products |
新增产品 |
| PUT | /api/admin/products/:id |
编辑产品 |
| DELETE | /api/admin/products/:id |
删除产品 (软删除) |
| GET | /api/admin/categories |
分类列表 (树形) |
| POST/PUT/DELETE | /api/admin/categories/:id |
分类 CRUD |
| GET | /api/admin/category-params?category_id=X |
品类参数规范 |
| POST/PUT/DELETE | /api/admin/category-params/:id |
参数规范 CRUD |
| GET | /api/admin/product-images?product_id=X |
产品图片列表 |
| POST/PUT/DELETE | /api/admin/product-images/:id |
图片 CRUD |
| POST | /api/admin/upload/image |
上传图片到 OSS |
| GET | /api/admin/stats/overview |
统计概览 |
| GET | /api/admin/logs |
操作日志 |
后台 SSR 页面用 Cookie (admin_token, path /),API 接口支持 Bearer Token 和 Cookie 两种方式,认证中间件优先读
Authorization header,没有则读 Cookie。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/search?keyword=关键词 |
搜索产品 (支持多字段、多关键词) |
| GET | /api/detail?id=xxx |
产品详情 |
| GET | /api/suggest?keyword=关键词 |
搜索建议 |
| GET | /api/recommend |
推荐产品 |
| GET | /api/categories |
分类列表 |
| GET | /api/brands |
品牌列表 |
基于 PostgreSQL pg_jieba 中文分词 + 加权 search_vector。
索引内容(触发器维护):
| 权重 | 来源 |
|---|---|
| A | 产品名称 name |
| B | 品牌 + 型号 |
| D | params_search_text(仅 params 的 value 拼接,不含 JSON key) |
写入/更新
name|brand|model|params时自动刷新;导入脚本不必手写覆盖。
查询路径:
- 先全文(
search_vector @@,可用 GIN)按ts_rank_cd排序 - 无命中再 ILIKE 降级(name / brand / model / pinyin /
params_search_text/ 分类名) - 搜索日志记整句;用户用空格分开的词再各记一截(如「美的 一级能效」)。检索用二字切分不写日志 (避免「工业空调」→「工业」)
示例:
| 搜索词 | 结果 | 说明 |
|---|---|---|
格力 |
格力相关产品 | 品牌/名称(高权重) |
变频 |
变频相关产品 | 参数值进索引 |
美的 一级 |
同时命中两词 | AND;日志会记「美的」「一级」等 |
只做主图:products.main_image = /local-images/{id}.jpg
文件在 ~/Desktop/crawler_test/images-data/(不在项目内,可用 IMAGE_STAGING_DIR 覆盖)。
无 OSS Key 时导入/上传自动落本地(也可设 IMAGE_STORAGE=local)。缺主图时重跑 npm run import:zol-ac。
示例:http://localhost:3000/local-images/3819.jpg
有 OSS Key 时,上传/导入走 image-oss-pipeline,main_image 写 CDN URL。
| 字段 | 本地 | 线上 |
|---|---|---|
products.main_image |
/local-images/xxx.jpg(目前只做主图) |
CDN URL |
images / product_images |
已删除 | 不应再使用 |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /local-images/* |
读桌面 crawler_test/images-data |
| POST | /api/admin/upload/image |
后台上传(无 Key 时落本地) |
| GET | /api/image/:id |
本地有文件则 302 到 /local-images/ |
- Base64 暂存 → 提交
- 本地:写入
~/Desktop/crawler_test/images-data,main_image=/local-images/... - 有 OSS Key:上传 CDN
校验:扩展名与 MIME 白名单、≤ 5MB。
脚本:scripts/import-zol-air-condition.ts(值归一化:src/utils/normalize-param-value.ts)
# 从爬虫 JSON 导入(默认 ~/Desktop/crawler_test/data;含白名单 + 值归一化)
npm run import:zol-ac
# 预览导入,不写库
npx tsx scripts/import-zol-air-condition.ts --dry-run
# 指定数据目录
npx tsx scripts/import-zol-air-condition.ts --dir /path/to/data
# 只回刷:从爬虫 JSON 再 Transform(按需入座 + 例外),不重导图片
npm run import:zol-ac:normalizeETL 约定:
| 层 | 字段 / 产物 | 说明 |
|---|---|---|
| Extract | 桌面爬虫 JSON | 原材料,不进库 |
| Transform | products.params |
白名单 + 入座结果 |
| 例外 | import_exceptions |
未知键 / 入不了座 / 规则丢弃 |
| 规范 | src/db/snapshots/category-params-air-condition.json |
seed 对齐 |
| 图片 | products.main_image |
仅主图 URL |
规则简述:白名单过滤 → PARAM_MAP 异名翻译 → 枚举/布尔按需入座;对不上的不硬塞。新批次会把同平台旧
open 标为 superseded。设计说明见知识库「家电 · 项目总览」。
scripts/
| 脚本 | 说明 | 命令 |
|---|---|---|
package.js |
打包部署产物 | npm run package |
import-zol-air-condition.ts |
导入 ZOL 空调 / 回刷 params | 见上方「ZOL 空调导入」 |
check-etl.ts |
ZOL 空调 ETL 质检 | npm run check:etl |
export-category-params-snapshot.ts |
导出 category_params 快照 | npm run export:category-params |
lib/import-exceptions.ts |
例外写入(被导入脚本引用) | — |
src/db/
| 脚本 | 说明 | 命令 |
|---|---|---|
seed.ts |
分类/管理员/参数规范种子数据 | npm run db:seed |
migrate-search-vector.ts |
加权全文 + params_search_text | npm run migrate:search-vector |
fill-pinyin.ts |
补拼音字段 | npm run fill:pinyin |
update-admin-password.ts |
更新管理员密码 | npx tsx src/db/update-admin-password.ts |
| 命令 | 说明 |
|---|---|
npm run db:push |
推送 schema 到数据库(本地整理用) |
npm run db:seed |
灌入种子数据 |
npm run migrate:search-vector |
安装/重建全文搜索(空库或升级加权索引时跑) |
npm run fill:pinyin |
导入后补拼音 |
npm run db:generate / db:migrate |
生产 migration(上线前再写,本地不必依赖) |
代码推送到 main 分支自动触发 GitHub Actions 部署到 FC:
git add .
git commit -m "feat: 新功能"
git push origin main
# 自动构建 + 部署到 FCnpm ci+npm run build(TypeScript 编译到dist/)npm run package(dist/+ 生产依赖 →code.zip,与本地一致)s deploy(Serverless Devs 部署到 FC)
通配符证书 *.cheapgo.top,通过 acme.sh + Let's Encrypt 签发,每 90 天自动续期,续期后自动推送到 CDN。
手动触发续签:GitHub → Actions → Renew SSL Certificate → Run workflow
| 资源 | 说明 |
|---|---|
| RDS 实例 | pgm-wz926p594292r913 (cn-shenzhen, Serverless) |
| OSS Bucket | cheapgo-assets (cn-shenzhen, public-read) |
| CDN 域名 | static.cheapgo.top (源站 OSS) |
| FC 函数 | appliance-api (cn-shenzhen, Node.js 20) |
| 域名 | cheapgo.top (阿里云云解析 DNS) |
| SSL 证书 | *.cheapgo.top (Let's Encrypt 通配符) |
| 仓库 | 说明 |
|---|---|
| home-appliances/appliance-web | 前端 (Taro H5) |