前段时间想给 NAS 找点事情做,发现了 Bitmagnet 这个项目,用了几天发现爬虫的部分还不错,但是自带的 webui 有点简陋,有权限敞开、种子数量上来后搜索慢等问题,用起来总是不那么爽
于是花了几天时间用 Nextjs + NextUI 写了个前端界面,顺带也是为了学一下 Next 的开发,界面功能参考了常见的一些磁力搜索引擎,顺带做了夜间模式、多语言、内容预览和搜索分词等功能
详细的部署过程可以看仓库,提供有 Docker Compose 配置可以快速部署,也可以点 Demo 链接体验,Demo 为了避免版权等问题,内容是固定的
仓库地址: https://github.com/journey-ad/Bitmagnet-Next-Web
Demo: https://bitmagnet-next-web.vercel.app/
下面是一些开发过程的记录:
原版的 Bitmagnet 自带一个 GraphQL 的 api 可以进行搜索,但用下来和 webui 遇到的问题是一样的,数量上去之后普遍搜索时间在几十秒,最重要的是返回的结果数量不准,没办法做分页
关于搜索和索引问题我问过 bitmagnet 的开发者,他回复是已经结合种子标题和文件内容,在 torrent_contents.tsv 里创建了向量索引。研究后发现是先转罗马音然后存的向量,这样好处是可以用 pg 原生支持的 tsquery 来查询,而且转成罗马音后对应中文的是拼音,可以做到错字也能搜到,坏处是只要同音字就能搜到,就算同音字很离谱也是一样
另外就是 bitmagnet 用了 go-unidecode 这个库做罗马音化,但这个库在 node 上没有一比一对应的,转罗马音的过程和 bitmagnet 做不到完全一致,影响搜索效果
综合以上问题,决定自己写后端代码直连 DB 来查询,但之前没怎么搞过后端,更没搞过搜索这种东西,总之边写边测边改,搞出来了 gin 索引+传统 like 模糊匹配+分词,并根据每个关键词的词性确定为必须或非必须,生成对应的 SQL 查询这种野路子方案。至于为什么不上 ES ,还有个考虑是想侵入性的修改尽量少点,后面 bitmagnet 库表结构有升级时好适配,所以没选 ES 这种重的方案
结果比较多的情况下基本在几百毫秒到几秒内就能返回,部分收录少的词可能要跑全表,要等十几秒,这个性能还算能接受,暂时是我能想到的最好的方案了,等有空了研究一下 discuz 之类的论坛怎么做的搜索
1
chunkingName 53 天前
是的,我目前 nas 上爬了三百多万种子,自带的搜索很慢,前几天连续搜索出错了,现在一直报错不进行搜索了,说数据库处于查询状态。试试你这个
|
2
pxiphx891 53 天前
点赞,这个好
|
3
chunkingName 53 天前
搜索报这个错,用他原来的 web 搜索正常
发生意外错误 Message: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error. Digest: 1841269720 |
![]() |
4
journey0ad OP @chunkingName #3
看下容器内的日志? |
5
chunkingName 53 天前
@journey0ad date stream content
2024/07/16 14:44:54 stderr } 2024/07/16 14:44:54 stderr digest: '1841269720' 2024/07/16 14:44:54 stderr at async y (/app/.next/server/app/search/page.js:1:11391) { 2024/07/16 14:44:54 stderr at async b (/app/.next/server/app/search/page.js:1:10813) 2024/07/16 14:44:54 stderr at process.processTicksAndRejections (node:internal/process/task_queues:95:5) 2024/07/16 14:44:54 stderr at a (/app/.next/server/chunks/200.js:1:25217) 2024/07/16 14:44:54 stderr Error: Network response was not ok: Internal Server Error 2024/07/16 14:44:54 stderr at async y (/app/.next/server/app/search/page.js:1:11391) 2024/07/16 14:44:54 stderr at async b (/app/.next/server/app/search/page.js:1:10813) 2024/07/16 14:44:54 stderr at process.processTicksAndRejections (node:internal/process/task_queues:95:5) 2024/07/16 14:44:54 stderr at a (/app/.next/server/chunks/200.js:1:25217) 2024/07/16 14:44:54 stderr Error: Network response was not ok: Internal Server Error 2024/07/16 14:44:54 stderr Failed to fetch: Network response was not ok: Internal Server Error 2024/07/16 14:44:54 stdout keywords: [ { _: '$1', keyword: '雷米', required: true } ] 2024/07/16 14:44:54 stderr } 2024/07/16 14:44:54 stderr extraInfo: undefined 2024/07/16 14:44:54 stderr networkError: null, 2024/07/16 14:44:54 stderr clientErrors: [], 2024/07/16 14:44:54 stderr protocolErrors: [], 2024/07/16 14:44:54 stderr ], 2024/07/16 14:44:54 stderr } 2024/07/16 14:44:54 stderr extensions: [Object] 2024/07/16 14:44:54 stderr path: [Array], 2024/07/16 14:44:54 stderr locations: [Array], 2024/07/16 14:44:54 stderr message: 'Failed to execute search query', 2024/07/16 14:44:54 stderr { 2024/07/16 14:44:54 stderr graphQLErrors: [ 2024/07/16 14:44:54 stderr at t.next (/app/.next/server/chunks/38.js:1:134768) { 2024/07/16 14:44:54 stderr at b (/app/.next/server/chunks/38.js:1:134267) 2024/07/16 14:44:54 stderr at g (/app/.next/server/chunks/38.js:1:133726) 2024/07/16 14:44:54 stderr at Object.next (/app/.next/server/chunks/38.js:1:39726) 2024/07/16 14:44:54 stderr at Object.then (/app/.next/server/chunks/38.js:1:39598) 2024/07/16 14:44:54 stderr at new Promise (<anonymous>) 2024/07/16 14:44:54 stderr at /app/.next/server/chunks/38.js:1:39631 2024/07/16 14:44:54 stderr at o (/app/.next/server/chunks/38.js:1:39716) 2024/07/16 14:44:54 stderr at /app/.next/server/chunks/38.js:1:76524 2024/07/16 14:44:54 stderr at new t (/app/.next/server/chunks/38.js:1:88954) 2024/07/16 14:44:54 stderr t [ApolloError]: Failed to execute search query 2024/07/16 14:44:54 stderr } 2024/07/16 14:44:54 stderr port: 5433 2024/07/16 14:44:54 stderr address: '192.168.11.2', 2024/07/16 14:44:54 stderr syscall: 'connect', 2024/07/16 14:44:54 stderr code: 'ECONNREFUSED', 2024/07/16 14:44:54 stderr errno: -111, 2024/07/16 14:44:54 stderr at async Object.g [as search] (/app/.next/server/app/api/graphql/route.js:131:107) { 2024/07/16 14:44:54 stderr at async Promise.all (index 1) 2024/07/16 14:44:54 stderr at process.processTicksAndRejections (node:internal/process/task_queues:95:5) 2024/07/16 14:44:54 stderr at /app/node_modules/pg-pool/index.js:45:11 2024/07/16 14:44:54 stderr Error in search resolver: Error: connect ECONNREFUSED 192.168.11.2:5433 2024/07/16 14:44:54 stdout [ '%雷米%', 10, 0 ] 2024/07/16 14:44:54 stdout filtered; -- 从过滤后的数据中查询 2024/07/16 14:44:54 stdout FROM 2024/07/16 14:44:54 stdout END AS files -- 结果别名设为 'files' 2024/07/16 14:44:54 stdout ELSE NULL -- 如果 files_count 为空, 则设置为 NULL 2024/07/16 14:44:54 stdout ) 2024/07/16 14:44:54 stdout WHERE torrent_files.info_hash = filtered.info_hash -- 根据 info_hash 匹配文件 2024/07/16 14:44:54 stdout FROM torrent_files 2024/07/16 14:44:54 stdout )) 2024/07/16 14:44:54 stdout 'extension', torrent_files.extension -- 文件扩展名 2024/07/16 14:44:54 stdout 'size', torrent_files.size, -- 文件大小 2024/07/16 14:44:54 stdout 'path', torrent_files.path, -- 文件在种子中的路径 2024/07/16 14:44:54 stdout 'index', torrent_files.index, -- 文件在种子中的索引 2024/07/16 14:44:54 stdout SELECT json_agg(json_build_object( 2024/07/16 14:44:54 stdout -- 如果有数量, 根据 info_hash 查询文件信息到 'files' 列, 聚合成 JSON 2024/07/16 14:44:54 stdout WHEN filtered.files_count IS NOT NULL THEN ( 2024/07/16 14:44:54 stdout CASE 2024/07/16 14:44:54 stdout -- 检查 files_count, 是否有文件数量 2024/07/16 14:44:54 stdout filtered.files_count, -- 种子文件数 2024/07/16 14:44:54 stdout filtered.updated_at, -- 更新时间戳 2024/07/16 14:44:54 stdout filtered.created_at, -- 创建时间戳 2024/07/16 14:44:54 stdout filtered.size, -- 种子大小 2024/07/16 14:44:54 stdout filtered.name, -- 种子名称 2024/07/16 14:44:54 stdout filtered.info_hash, -- 种子哈希 2024/07/16 14:44:54 stdout SELECT 2024/07/16 14:44:54 stdout -- 从过滤后的数据中查询文件信息 2024/07/16 14:44:54 stdout ) |
6
chunkingName 53 天前
@journey0ad 我将默认数据库 5432 改为了 5433 其余的没动 原版网页搜索正常,你的这个报日志这些错误
|
![]() |
7
journey0ad OP @chunkingName #6
看上去是 webui 到数据库容器的连接不通 是用 docker compose 部署的吗? docker 内有网络隔离,db 连接串的 host 部分需要和 postgres 所在的容器名一致才能连接上,参考 https://github.com/journey-ad/Bitmagnet-Next-Web/blob/1981a093cea8291e476fadab82bdf4b07bc207a4/docker-compose.yml 43 行的容器名和暴露的端口号,和 11 行 db 连接串的 postgres:5432 是对应的,29 行的 POSTGRES_HOST=postgres 也是一样的道理 或者不管用什么方法,保证 webui 的容器到 postgres 容器是可联通的就行 |
8
lisawang 50 天前
我在宝塔里用直接复制的 docker-compose.yml 用 Docker Compose 部署,ip:3000 搜索就是错误,但是:3333 是没问题,进去 ping 了一下 PostgreSQL ,ping 的通,奇怪,怎么解决呢
发生意外错误 Message: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error. Digest: 2644143229 |
9
lisawang 50 天前
TypeError: fetch failed
at node:internal/deps/undici/undici:12618:11 at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async a (/app/.next/server/chunks/200.js:1:25185) at async b (/app/.next/server/app/search/page.js:1:10813) at async y (/app/.next/server/app/search/page.js:1:11391) { cause: Error: connect ECONNREFUSED ::1:3000 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16) at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:128:17) { errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '::1', port: 3000 }, digest: '2644143229' } 错误日志 |
![]() |
10
journey0ad OP |