RediSearch

RediSearch

选方案 A 很对:RediSearch(RedisStack)= 产品化搜索体验,10 万本书不需要下拉框,直接“输入即搜索 + 结果列表”,而且还能做排序、分页、权重、模糊匹配。

下面我给你一套可直接落地的方案:Redis 部署/建索引 → 后端 API → 前端交互。你照着做就能跑。


0)你需要的前提:你的 Redis 必须支持 RediSearch

两条路:

方案 A1:自建 RedisStack(最简单)

用 Docker(推荐):

1
2
3
docker run -d --name redisstack \
-p 6379:6379 -p 8001:8001 \
redis/redis-stack:latest
  • 6379:应用连接
  • 8001:RedisInsight(可视化管理)

方案 A2:用支持 Search 的托管 Redis

比如 Redis Cloud 的某些套餐支持 RediSearch(看产品功能是否含 Search/Query)。
如果你的托管 Redis 不支持模块,那就没法用 FT.* 命令。

你可以在服务器上验证:

1
redis-cli MODULE LIST

看到 search/ft 相关模块才算支持。


1)你的数据存储要符合索引规则

你已经在用:

  • vault:book:meta:<bookAddr>(HASH)

只要你确保这个 HASH 里存在这些字段(并保持一致):

  • name(书名)
  • author(作者)
  • symbol(书籍代码)
  • serial(序列号)
  • publisher(出版社地址,小写)
  • createdAt(时间戳,可排序)
  • txHash(可选)

并且 key 前缀统一是:

  • vault:book:meta:

那 RediSearch 就能自动索引。


2)创建 RediSearch 索引(一次性执行)

redis-cli 跑:

1
2
3
4
5
6
7
8
FT.CREATE idx:books ON HASH PREFIX 1 vault:book:meta: \
SCHEMA \
publisher TAG SEPARATOR , \
symbol TAG SEPARATOR , \
serial TAG SEPARATOR , \
name TEXT WEIGHT 5.0 \
author TEXT WEIGHT 2.0 \
createdAt NUMERIC SORTABLE

解释一下(很关键):

  • publisher TAG:你可以按 publisher 精确过滤(必须)
  • symbol/serial TAG:代码/序列号精确匹配非常快
  • name/author TEXT:模糊/分词搜索体验好(name 权重更高)
  • createdAt SORTABLE:可以按最新排序

你可以验证索引是否创建成功:

1
FT.INFO idx:books

你要的交互是:输入书名/作者/代码 → 搜索 → 返回候选 → 选中 → 生成二维码。

推荐接口

GET /api/v1/publisher/books/search?publisher=0x...&q=xxx&limit=20&offset=0

RediSearch 查询语句(核心)

  • 必须过滤 publisher:

    • @publisher:{0xabc...} (TAG 过滤)
  • 同时对 name/author/symbol/serial 搜索:

    • (@name:(xxx*)|@author:(xxx*)|@symbol:{xxx}|@serial:{xxx})

拼成完整 query:

1
2
3
4
5
6
@publisher:{<publisherLower>} (
@name:(<q>*)
| @author:(<q>*)
| @symbol:{<qUpperOrLower>}
| @serial:{<q>}
)

Go(go-redis)实现要点

go-redis 没有强类型 FT 接口,但可以用 Do()

1
2
3
4
5
6
7
res, err := h.RDB.Do(ctx,
"FT.SEARCH", "idx:books",
query,
"SORTBY", "createdAt", "DESC",
"LIMIT", offset, limit,
"RETURN", "7", "name", "author", "symbol", "serial", "publisher", "createdAt", "txHash",
).Result()

返回解析会稍麻烦(RediSearch 返回的是数组结构),但这是标准做法。


4)部署成功时:写入 meta(让搜索立刻可用)

你现在部署成功后一定要写:

Key:vault:book:meta:<bookAddrLower>
Fields:至少包含上面索引字段

这样 RediSearch 才能搜到。

这一步是产品化关键:不扫链、不读事件,部署即入库可检索。


5)前端怎么改:下拉框 → 搜索框 + 候选列表

UI 逻辑:

  • 一个输入框(至少 2 个字符才触发)
  • debounce 300ms
  • 请求 /api/v1/publisher/books/search
  • 返回 items(最多 20 条)
  • 渲染列表(书名/作者/symbol/serial)
  • 点击某条 → selectedBookAddr = item.bookAddr
  • 生成二维码时用 contract = selectedBookAddr

交互体验建议

  • 输入框 placeholder:输入书名 / 作者 / 代码 / serial

  • 列表每条显示:

    • 《name》 - author (symbol / serial)
  • 选中后显示一个 “已选择:xxx” 的 badge

  • 再点“批量生成二维码”


6)10 万本是否扛得住?

扛得住,而且是 RediSearch 的典型场景:

  • 过滤 publisher 后再搜:规模立刻缩小
  • 返回 limit=20:传输很小
  • 支持排序(createdAt DESC)体验很好

你未来要做“全站搜索/多出版社搜索”,也可以加权限/租户隔离,方案不变。


7)你现在需要我给你“可运行的后端文件 + 前端文件”吗?

我可以直接给你两份可下载、可运行的补丁文件:

  1. publisher.go:新增

    • GetPublisherBooksSearch(/api/v1/publisher/books/search)
    • 部署成功写入 vault:book:meta:<bookAddr>
  2. Publisher.tsx:把下拉框换成搜索选择

但有个现实限制:你之前有些上传文件在会话里会过期(你也见到过这种提示)。为了我能把代码“精准地插进你现有项目结构”并保证一次编译通过,你把下面两个文件再上传一下(就这两个):

  • 你当前后端在用的 internal/handlers/publisher.go(最新版)
  • 你当前前端在用的 src/pages/Publisher.tsx(最新版)

我拿到后会直接输出:

  • [Download publisher.go]
  • [Download Publisher.tsx]
    并告诉你 main.go 要加哪两行路由。

你现在就可以先做的事情(不等我改文件也能推进)

  1. 在 Redis 上跑 FT.CREATE idx:books ...
  2. redis-cli 试一下搜索是否返回结果:

举例:

1
FT.SEARCH idx:books "@publisher:{0x5e8de2503881a49ed4db721e4fbafc106c3782e6} @name:(whale*)" LIMIT 0 5

如果能返回,说明 Redis 侧 OK,接下来就是接 API + 前端。

你把两份文件上传后,我就把“完整可运行版本”直接给你。

如果我自建这个服务,那本地的redis数据库是不是用不了了?

docker run -d –name redisstack
-p 6379:6379 -p 8001:8001
redis/redis-stack:latest 如果我自建这个服务,那本地的redis数据库是不是用不了了?


不会“用不了”,但会冲突:你现在机器上如果已经有一个 Redis 在占用 6379,你再 docker run -p 6379:6379 会出现 端口占用,新容器起不来(或你得先停掉旧 Redis)。

你有三种常用做法,按产品化推荐顺序给你:


方案 1(推荐):RedisStack 用不同端口,不影响你现有 Redis

把容器的 6379 映射到主机的 6380(或任意空闲端口):

1
2
3
docker run -d --name redisstack \
-p 6380:6379 -p 8001:8001 \
redis/redis-stack:latest

这时:

  • 你原来的 Redis:还是 localhost:6379(完全不动)
  • RedisStack(带 Search):localhost:6380
  • RedisInsight:http://<服务器IP>:8001

你的后端要用 Search 就把 Redis 地址改成 localhost:6380(或只把需要 Search 的服务指向它)。


方案 2:直接替换掉本机 Redis(如果你不需要保留原 Redis)

如果你愿意让 RedisStack 成为“唯一 Redis”,可以:

  1. 停掉旧 Redis(systemd 示例):
1
2
sudo systemctl stop redis
sudo systemctl disable redis
  1. 再用 RedisStack 占用 6379:
1
2
3
docker run -d --name redisstack \
-p 6379:6379 -p 8001:8001 \
redis/redis-stack:latest

⚠️ 注意:旧 Redis 的数据如果你没迁移/没备份,会“看不到”(因为你换了一个实例)。


方案 3:RedisStack 和旧 Redis 各用一个实例,做“数据迁移/双写”

适合你创业产品演进:

  • 先让 RedisStack 跑在 6380
  • 你的业务逐步迁移到 6380
  • 迁完再切换到 6379

迁移方式:

  • redis-cli --rdb 导出/导入
  • 或者 redis-shake/redis-migrate-tool(更专业)

你真正关心的点:数据会不会丢?

端口不冲突时(方案 1),不会影响你现有 Redis 数据
RedisStack 是另一个实例,数据是独立的。


你现在该怎么选?

如果你现在已经在用 localhost:6379 跑业务(读者码/绑定/领奖等都在里面),我强烈建议你:

先用方案 1(RedisStack 用 6380)
等你验证 Search OK,再决定是否替换到 6379。


两个快速自检命令

1) 看 6379 是否已被占用

1
ss -lntp | grep 6379

2) 测试 RedisStack 是否带 RediSearch

(假设你按方案 1 用 6380)

1
redis-cli -p 6380 MODULE LIST

看到 search / ft 相关模块,就说明可以用 FT.CREATE / FT.SEARCH


如果你把你现在后端的 REDIS_ADDR 环境变量值贴一下(例如 localhost:6379 还是别的),我可以告诉你:你应该用方案 1 还是方案 2,以及改哪些配置最省事。

docker环境搭好了

环境搭好啦 ✅(redis-stack 跑在 6380,RedisInsight 在 8001)。
接下来确实需要改 publisher.go,但更准确地说是:你需要两步

  1. 让后端连接到 redis-stack(6380)(至少 publisher 相关服务要连这个)
  2. 在 RedisStack 里创建 RediSearch 索引 + 在部署成功时写 meta 数据,然后新增搜索 API 给前端用

我按你现在的架构给你一个最稳的落地方案(不会影响你原本 6379 的 Redis)。


第一步:后端连到 RedisStack(6380)

你现在后端默认读的是 REDIS_ADDR(你 main.go 里就是这么写的)。
所以你只要把运行服务的环境变量改成:

1
export REDIS_ADDR=127.0.0.1:6380

如果你用 systemd/supervisor/docker-compose,就在对应配置里改。

✅ 验证方式:后端启动日志应该还能显示 “Redis 连接成功”,并且你用:

1
redis-cli -p 6380 ping

应该返回 PONG

注意:如果你希望“旧业务继续用 6379、只有 publisher 搜索用 6380”,那就要做“双 Redis 客户端”(稍复杂)。创业产品化我建议你逐步迁移到 RedisStack 作为主 Redis。


第二步:创建 RediSearch 索引(只做一次)

在服务器上执行(连接 6380):

1
2
3
4
5
6
7
8
redis-cli -p 6380 FT.CREATE idx:books ON HASH PREFIX 1 vault:book:meta: \
SCHEMA \
publisher TAG SEPARATOR , \
symbol TAG SEPARATOR , \
serial TAG SEPARATOR , \
name TEXT WEIGHT 5.0 \
author TEXT WEIGHT 2.0 \
createdAt NUMERIC SORTABLE

如果提示 “Index already exists”,说明已经建过,忽略即可。

✅ 检查索引:

1
redis-cli -p 6380 FT.INFO idx:books

第三步:修改 publisher.go(你问的重点)

需要改的点有 3 个:

A) 部署成功后,必须写入 meta(让搜索有数据)

部署成功拿到 bookAddr 后写:

  • HSET vault:book:meta:<bookAddrLower> name author symbol serial publisher createdAt txHash
  • (可选)你原来的 ZSET 索引也可保留:ZADD vault:publisher:books:z:<publisher> createdAt bookAddr

这一步做好后,FT.SEARCH 才能搜到书。

后端用:

FT.SEARCH idx:books "<query>" LIMIT offset limit SORTBY createdAt DESC RETURN ...

并且必须带 publisher 过滤:

@publisher:{publisherLower} ...

C) main.go 注册路由

加一行:

1
r.HandleFunc("/api/v1/publisher/books/search", publisherH.SearchPublisherBooks).Methods("GET", "OPTIONS")

我现在可以直接给你“可运行 publisher.go”

但要说明一下:你之前一些文件在会话里会过期(工具提示我有过期情况),我这边不能可靠地读取你本机的现有代码状态

你现在只要把你项目里正在使用的这个文件内容再发我一次(任选一种方式):

  • backend/internal/handlers/publisher.go(你现在正在用的最新版,直接粘贴或上传)

我就能给你:

  • 完整可运行的 publisher.go(包含 SearchPublisherBooks + 部署写 meta)
  • ✅ 告诉你 main.go 要加的路由行
  • ✅ 给你一个“前端搜索选择书籍 → 再生成 zip”的 Publisher.tsx 改法(可下载)

你可以先自己做一个“是否需要改代码”的快速判断

在 RedisStack 上跑:

1
redis-cli -p 6380 FT.SEARCH idx:books "*"
  • 如果返回 0 条:说明你还没有vault:book:meta:* 写到 6380(或字段不对)
  • 如果能返回书:说明 meta 数据已经写对了,你只需要加搜索 API 即可

最关键的一句

是的,你现在需要修改 publisher.go ——因为:

  • 你要产品级搜索(FT.SEARCH)
  • 你要从“下拉框加载 10 万本”变成“输入联想搜索”

publisher.go 上传给我(最新版),我马上给你一个最终可下载、可运行的版本(包含 search API 和 deploy 写索引)。

写入redis数据库的脚本

你这个报错是我上次插入 argparse 代码时缩进没整体缩进进 main(),导致第 64 行的 print(...) 变成“突然缩进”,Python 就直接 IndentationError 了。

我已经把缩进修好了,并重新生成了一个能直接运行的脚本:

✅ 下载修复版:
下载 generate_vault_data.redisstack.fixed.py


你怎么用(写入 redis-stack 6380)

在你的服务器上执行:

1
python3 generate_vault_data.redisstack.fixed.py

默认会连:

  • host: 127.0.0.1
  • port: 6380
  • db: 0

如果你想显式指定(可选):

1
python3 generate_vault_data.redisstack.fixed.py --host 127.0.0.1 --port 6380 --db 0

验证是否写进 redis-stack

1
2
redis-cli -p 6380 DBSIZE
redis-cli -p 6380 SCARD vault:roles:publishers_codes

只要数量 > 0 就说明写入成功了。

查询hash

可以的,这个很简单,本质就是:tell_me_valid_hash.py 里连 Redis 的地方改成连 6380(redis-stack)

我已经帮你改好了一个版本,默认直接连:

  • host: 127.0.0.1
  • port: 6380
  • db: 0

并且支持命令行参数/环境变量(和刚才那个脚本一致风格)。

📦 下载修复版:
下载 tell_me_valid_hash.redisstack.py


这个脚本现在做了什么改动

核心就三点:

1. Redis 连接改成可配置(默认 6380)

1
2
3
4
5
6
7
8
9
10
11
12
13
def get_redis_client():
host = os.getenv("REDIS_HOST", "127.0.0.1")
port = int(os.getenv("REDIS_PORT", "6380"))
password = os.getenv("REDIS_PASSWORD", "")
db = int(os.getenv("REDIS_DB", "0"))

return redis.Redis(
host=host,
port=port,
password=password or None,
db=db,
decode_responses=True
)

2. main 里用这个 client

1
r = get_redis_client()

使用方式

最简单(直接用 redis-stack):

1
python3 tell_me_valid_hash.redisstack.py

如果你想显式指定:

1
REDIS_PORT=6380 python3 tell_me_valid_hash.redisstack.py

或者:

1
python3 tell_me_valid_hash.redisstack.py --host 127.0.0.1 --port 6380

如何确认它真的在查 redis-stack?

你可以故意停掉旧 redis(6379),只留 redis-stack:

1
2
redis-cli -p 6380 ping   # PONG
redis-cli -p 6379 ping # 应该连不上或报错

然后跑脚本,如果还能正常输出结果,说明:

这个脚本 100% 在查 6380 的 redis-stack,而不是旧 redis


工程上非常重要的一点(你现在已经走对路了)

你现在的状态是:

  • 旧 redis(6379):历史遗留
  • 新 redis-stack(6380):产品级主库 + 搜索引擎

正确做法就是你现在这样:

所有 新脚本、新索引、新业务查询 → 全部指向 6380
旧 6379 慢慢淘汰

这是非常标准的 “生产系统迁移姿势”,不是 hack,是正统工程做法。

python tell_me_valid_hash.redisstack.py