Skip to content

Commit db8916c

Browse files
committed
feat: 移除项目表中的过时字段并添加 trigram 索引以优化搜索性能
1 parent 746e2da commit db8916c

File tree

9 files changed

+949
-101
lines changed

9 files changed

+949
-101
lines changed

docs/search-api.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Search API 文档
2+
3+
## 接口地址
4+
5+
- `GET /searchapi`
6+
7+
## 功能概述
8+
9+
统一搜索接口,支持以下类型:
10+
11+
- 项目(`projects`
12+
- 用户(`users`
13+
- 帖子(`posts`
14+
- 项目文件(`project_files`
15+
- 列表(`lists`
16+
- 标签(`tags`,来自 `ow_projects_tags` 关联表)
17+
18+
关键字匹配规则:
19+
20+
- `projects/users/posts` 使用 `pg_trgm` 相似度匹配(容错拼写)
21+
- `lists/tags` 使用子串匹配
22+
- `project_files` 使用全文检索(仅当前用户可搜索)
23+
24+
## Query 参数
25+
26+
- `keyword`: 通用关键字(推荐,兼容 `q`
27+
- `scope`: 搜索范围(推荐),支持 `projects|users|posts|project_files|lists|tags`(兼容 `search_scope``list|tag`
28+
- `userId`: 用户 ID 过滤(推荐,兼容 `search_userid`
29+
- `tags`: 项目标签过滤(推荐,兼容 `search_tag`
30+
- `type`: 项目类型过滤(兼容 `search_type`
31+
- `orderBy`: 项目排序(推荐),支持 `view_up/down``time_up/down``id_up/down``star_up/down`(兼容 `search_orderby`
32+
- `state`: 项目/列表状态过滤(兼容 `search_state`
33+
- `postType`: 帖子类型过滤(兼容 `search_post_type`
34+
- `userStatus`: 用户状态过滤(兼容 `search_user_status`
35+
- `page`: 页码,默认 `1`(兼容 `curr`
36+
- `perPage`: 每页数量,默认 `10`,最大 `50`(兼容 `limit`
37+
38+
`userId``tags` 传值方式:
39+
40+
- 逗号分隔:`userId=1,2,3&tags=scratch,game`
41+
- 重复参数:`userId=1&userId=2&tags=scratch&tags=game`
42+
43+
## 返回结构
44+
45+
```json
46+
{
47+
"scope": "projects",
48+
"query": "scratch",
49+
"page": 1,
50+
"limit": 10,
51+
"projects": [],
52+
"users": [],
53+
"posts": [],
54+
"projectFiles": [],
55+
"lists": [],
56+
"tags": [
57+
{ "name": "scratch", "count": 12 }
58+
],
59+
"totals": {
60+
"projects": 0,
61+
"users": 0,
62+
"posts": 0,
63+
"projectFiles": 0,
64+
"lists": 0,
65+
"tags": 1
66+
},
67+
"totalCount": 0,
68+
"fileSearchStrategy": "fulltext"
69+
}
70+
```
71+
72+
字段说明:
73+
74+
- `scope`: 实际生效的范围
75+
- `query`: 实际关键字(优先 `q`
76+
- `tags`: 标签聚合结果,`count` 表示匹配到该标签的记录数
77+
- `totals`: 各类型总数
78+
- `totalCount`: 当前 `scope` 总数
79+
- `fileSearchStrategy`: 文件搜索策略(`disabled|owner_filtered|fulltext`
80+
81+
## 兼容行为
82+
83+
`search_scope=projects` 且没有 `q` 时,保持旧结构:
84+
85+
```json
86+
{
87+
"projects": [],
88+
"totalCount": 0
89+
}
90+
```
91+
92+
## 示例
93+
94+
1. 仅标签模糊搜索
95+
96+
`GET /searchapi?keyword=scr&scope=tags&page=1&perPage=20`
97+
98+
2. 搜索公开列表
99+
100+
`GET /searchapi?keyword=教程&scope=lists&state=public`
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
-- Enable pg_trgm for trigram indexes
2+
CREATE EXTENSION IF NOT EXISTS pg_trgm;
3+
4+
-- Remove deprecated columns from projects
5+
ALTER TABLE ow_projects DROP COLUMN IF EXISTS tags;
6+
ALTER TABLE ow_projects DROP COLUMN IF EXISTS devenv;
7+
8+
-- Projects: name/title/description
9+
CREATE INDEX IF NOT EXISTS idx_ow_projects_name_trgm
10+
ON ow_projects USING gin (name gin_trgm_ops);
11+
CREATE INDEX IF NOT EXISTS idx_ow_projects_title_trgm
12+
ON ow_projects USING gin (title gin_trgm_ops);
13+
CREATE INDEX IF NOT EXISTS idx_ow_projects_description_trgm
14+
ON ow_projects USING gin (description gin_trgm_ops);
15+
16+
-- Posts: content
17+
CREATE INDEX IF NOT EXISTS idx_ow_posts_content_trgm
18+
ON ow_posts USING gin (content gin_trgm_ops);
19+
20+
-- Project files: source (trgm), creator, create_time
21+
CREATE INDEX IF NOT EXISTS idx_ow_projects_file_source_tsv_simple
22+
ON ow_projects_file
23+
USING gin (to_tsvector('simple', left(COALESCE(source, ''), 200000)));
24+
CREATE INDEX IF NOT EXISTS idx_ow_projects_file_create_userid
25+
ON ow_projects_file (create_userid);
26+
CREATE INDEX IF NOT EXISTS idx_ow_projects_file_create_time
27+
ON ow_projects_file (create_time);
28+
29+
-- Users: username/display_name/bio/motto/location/region
30+
CREATE INDEX IF NOT EXISTS idx_ow_users_username_trgm
31+
ON ow_users USING gin (username gin_trgm_ops);
32+
CREATE INDEX IF NOT EXISTS idx_ow_users_display_name_trgm
33+
ON ow_users USING gin (display_name gin_trgm_ops);
34+
CREATE INDEX IF NOT EXISTS idx_ow_users_bio_trgm
35+
ON ow_users USING gin (bio gin_trgm_ops);
36+
CREATE INDEX IF NOT EXISTS idx_ow_users_motto_trgm
37+
ON ow_users USING gin (motto gin_trgm_ops);
38+
CREATE INDEX IF NOT EXISTS idx_ow_users_location_trgm
39+
ON ow_users USING gin (location gin_trgm_ops);
40+
CREATE INDEX IF NOT EXISTS idx_ow_users_region_trgm
41+
ON ow_users USING gin (region gin_trgm_ops);

prisma/schema.prisma

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ model ow_projects {
5555
title String @default("ZeroCat新项目") @db.VarChar(1000)
5656
description String @default("ZeroCat上的项目") @db.VarChar(1000)
5757
history Boolean @default(true)
58-
devenv Boolean @default(true)
59-
tags String @default("") @db.VarChar(100)
6058
fork Int?
6159
star_count Int @default(0)
6260
@@ -942,4 +940,3 @@ model ow_posts_media {
942940
@@index([asset_id])
943941
@@map("ow_posts_media")
944942
}
945-

src/controllers/projects.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ function projectSelectionFields() {
149149
time: true,
150150
title: true,
151151
description: true,
152-
tags: true,
153152
star_count: true,
154153
fork: true,
155154
author: {

src/routes/admin/projects.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ router.put('/:id', async (req, res) => {
113113
'name', 'default_branch', 'type', 'license', 'thumbnail',
114114
'authorid', 'state', 'view_count',
115115
'like_count', 'favo_count', 'time', 'title',
116-
'description', 'history', 'devenv', 'tags',
116+
'description', 'history',
117117
'fork', 'star_count'
118118
];
119119

@@ -126,7 +126,6 @@ router.put('/:id', async (req, res) => {
126126
if (updateData.fork) updateData.fork = parseInt(updateData.fork);
127127
if (updateData.time) updateData.time = new Date(updateData.time);
128128
if (updateData.history !== undefined) updateData.history = Boolean(updateData.history);
129-
if (updateData.devenv !== undefined) updateData.devenv = Boolean(updateData.devenv);
130129

131130
// Filter out any fields that aren't in the allowed list
132131
const validUpdateData = Object.keys(updateData)

src/routes/router_api.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ router.get("/projectinfo", async function (req, res, next) {
130130
state: true,
131131
description: true,
132132
license: true,
133-
tags: true,
134133
name: true,
135134
star_count: true,
136135
},

src/routes/router_project.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,7 @@ router.put("/id/:id", needLogin, async (req, res, next) => {
952952
});
953953

954954
// 其他可能变更的字段
955-
["description", "license", "tags", "thumbnail"].forEach((field) => {
955+
["description", "license", "thumbnail"].forEach((field) => {
956956
if (
957957
updatedData[field] !== undefined &&
958958
updatedData[field] !== project[field]

src/routes/router_scratch.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ router.get("/projectinfo", async function (req, res, next) {
4646
license: true,
4747
thumbnail: true,
4848
default_branch: true,
49-
tags: true,
5049
name: true,
5150
star_count: true,
5251
},

0 commit comments

Comments
 (0)