全球主机交流论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

CeraNetworks网络延迟测速工具IP归属甄别会员请立即修改密码
查看: 1560|回复: 13

分享一下150元找大神定制的Alist支持grep暴力搜索的方案

[复制链接]
发表于 2023-11-19 20:50:41 | 显示全部楼层 |阅读模式
本帖最后由 sharp097 于 2023-11-19 20:52 编辑

如果希望看到更漂亮的排版,可以移步我下面的博客:

https://www.sharpgan.com/alist-modification-to-support-grep-brute-force-search/

# 前言

就我个人而言,不管多么吊的全文搜索,总是很不靠谱,关键时候总是掉链子,即使是买了昂贵的所谓的企业级的Elasticsearch,其提供的全文搜索功能也是很不靠谱,毕竟在维护基于es的日志搜索平台时,每天都有太多的人找我投诉搜不到想要的日志,而一去机器上用grep搜索的时候就有,具体细节我就不赘述了,所以我现在我只相信grep暴力搜索,好了不废话了,下面进入正题。

声明:

下面的代码全部基于Alist的一个老版本做的改动,这个版本是Version: v3.11.0-0-gfe416ba-dirty,如果你需要基于最新版本的Alist修改的话,应该可以举一反三。

修改完了之后参考下面的官方文档进行编译:

[https://alist.nn.ci/zh/guide/install/source.html](https://alist.nn.ci/zh/guide/install/source.html)

# 预览

文件变动一览:

#### 后端

- 删除文件夹:internal/search/db_non_full_text 因为internal/bootstrap/data/setting.go中的key为conf.SearchIndex的options值中的database_non_full_text修改为了grep,所以这个文件夹就用不到了

- 新增文件夹:internal/search/grep 这是新的grep搜索类型,要新增一个文件夹来实现

新增文件:
- internal/search/grep/search.go grep搜索的核心代码
- internal/search/grep/init.go grep搜索的init代码

修改文件:
- internal/bootstrap/data/setting.go 用来把db_non_full_text逻辑替换成grep搜索逻辑
- internal/search/import.go 用来导入新的grep搜索逻辑

#### 前端
新增:无

修改文件:
- src/pages/home/folder/Search.tsx 用来支持在新的标签页打开搜索结果中的html
- src/pages/home/previews/index.ts 用来让html文件默认用html预览工具来打开

# 开干

玩的就是真实。

### 后端

后端新增文件internal/search/grep/search.go文件内容如下:

```go
package db

import (
        "bufio"
        "context"
        "github.com/alist-org/alist/v3/drivers/local"
        "github.com/alist-org/alist/v3/internal/db"
        "github.com/alist-org/alist/v3/internal/model"
        "github.com/alist-org/alist/v3/internal/search/searcher"
        "github.com/alist-org/alist/v3/pkg/utils"
        "os/exec"
        "path/filepath"
        "strings"
)

type Grep struct{}

func (D Grep) Config() searcher.Config {
        return config
}

func (D Grep) ListLocalStorage() []model.Storage {
        storages, _, err := db.GetStorages(0, 500)
        var localStorages []model.Storage
        if err != nil {
                return localStorages
        }

        for i := range storages {
                storage := storages
                if storage.Driver == "Local" {
                        localStorages = append(localStorages, storage)
                }
        }
        return localStorages
}

func (D Grep) FindRealRoot(parentFolder string) (string, string) {
        if len(parentFolder) <= 0 {
                return "", ""
        }
        localStorages := D.ListLocalStorage()
        if len(localStorages) <= 0 {
                return "", ""
        }
        for i := range localStorages {
                localStorage := localStorages
                // Unmarshal Addition
                addition := &local.Addition{}
                err := utils.Json.UnmarshalFromString(localStorage.Addition, addition)
                if err != nil {
                        continue
                }
                if strings.Contains(parentFolder, localStorage.MountPath) {
                        return localStorage.MountPath, addition.RootFolderPath
                }
        }
        return "", ""
}

func (D Grep) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
        mountPath, rootFolderPath := D.FindRealRoot(req.Parent)
        if len(mountPath) == 0 || len(rootFolderPath) == 0 {
                return []model.SearchNode{}, 0, nil
        }

        realRootFolder := strings.Replace(
                req.Parent,
                mountPath,
                rootFolderPath,
                1,
        )
    kw:=req.Keywords
    isSpace:=strings.Contains(kw, " ")
    if isSpace == true {
        strSlice:=strings.Split(kw," ")
        formerStr:=strSlice[0]
        latterStr:=strSlice[1]
        kw=formerStr+".*"+latterStr+"|"+latterStr+".*"+formerStr
}
        cmd := exec.Command("grep", "-R", "-l", "-i", "-E", kw, realRootFolder)

        stderr, _ := cmd.StdoutPipe()
        cmd.Start()

        scanner := bufio.NewScanner(stderr)
        scanner.Split(bufio.ScanLines)
        var fileList []model.SearchNode
        var limit int = 0
        for scanner.Scan() {
                m := scanner.Text()
                fileName := strings.Split(m, ":")[0]
                cdir, cfile := filepath.Split(fileName)
                cfile = strings.TrimSuffix(cfile, "/")
                cdir = strings.Replace(cdir, rootFolderPath, mountPath, 1)

                if itemExists(fileList, cdir, cfile) {
                        continue
                }

                fileList = append(fileList, model.SearchNode{
                        Parent: cdir,
                        Name:   cfile,
                        IsDir:  false,
                        Size:   0,
                })
                limit++
                if limit >= 100 {
                        break
                }
        }
        cmd.Wait()
        return fileList, 0, nil
}

func itemExists(fileList []model.SearchNode, cdir string, cfile string) bool {
        for i := range fileList {
                file := fileList
                if file.Parent == cdir && file.Name == cfile {
                        return true
                }
        }
        return false
}

func (D Grep) Index(ctx context.Context, node model.SearchNode) error {
        return nil
}

func (D Grep) BatchIndex(ctx context.Context, nodes []model.SearchNode) error {
        return nil
}

func (D Grep) Get(ctx context.Context, parent string) ([]model.SearchNode, error) {
        return []model.SearchNode{}, nil
}

func (D Grep) Del(ctx context.Context, prefix string) error {
        return nil
}

func (D Grep) Release(ctx context.Context) error {
        return nil
}

func (D Grep) Clear(ctx context.Context) error {
        return nil
}

var _ searcher.Searcher = (*Grep)(nil)
```

后端新增文件internal/search/grep/init.go文件内容如下:

```go
package db

import (
        "github.com/alist-org/alist/v3/internal/search/searcher"
)

var config = searcher.Config{
        Name:       "grep",
        AutoUpdate: true,
}

func init() {
        searcher.RegisterSearcher(config, func() (searcher.Searcher, error) {
                return &Grep{}, nil
        })
}
```

后端修改文件internal/bootstrap/data/setting.go具体为:

把key为conf.SearchIndex的options值中的database_non_full_text修改为grep

后端修改文件internal/search/import.go具体为:

把db_non_full_text改为grep

### 前端

前端修改文件src/pages/home/folder/Search.tsx具体为:

在SearchResult这个函数下面的return属性中新增一个 `target="_blank"`属性,用来支持在新的标签页打开搜索结果中的html,示例代码如下:

```typescript
const SearchResult = (props: SearchNode) => {
  return (
    <HStack
      w="$full"
      borderBottom={`1px solid ${hoverColor()}`}
      _hover={{
        bgColor: hoverColor(),
      }}
      rounded="$md"
      cursor="pointer"
      px="$2"
      as={LinkWithBase}
      href={props.path}
      target="_blank"
      encode
    >
```

前端修改文件src/pages/home/previews/index.ts具体为:

在函数getPreviews中将常量res改为变量:

从原来的 `const res: PreviewComponent[] = []`变为 `var res: PreviewComponent[] = []`

然后在// iframe previews注释上方新增如下代码,用来让html文件默认用html预览工具来打开:

```typescript
var fileExt = ext(file.name).toLowerCase()
  if (fileExt == "html") {
    res = res.filter(item => item.name === "HTML render")
  }
```

完整示例代码如下:

```typescript
export const getPreviews = (
  file: Obj & { provider: string }
): PreviewComponent[] => {
  var res: PreviewComponent[] = []
  // internal previews
  previews.forEach((preview) => {
    if (preview.provider && !preview.provider.test(file.provider)) {
      return
    }
    if (
      preview.type === file.type ||
      preview.exts === "*" ||
      preview.exts?.includes(ext(file.name).toLowerCase())
    ) {
      res.push({ name: preview.name, component: preview.component })
    }
  })
  var fileExt = ext(file.name).toLowerCase()
  if (fileExt == "html") {
    res = res.filter(item => item.name === "HTML render")
  }
  // iframe previews
  const iframePreviews = getIframePreviews(file.name)
  iframePreviews.forEach((preview) => {
    res.push({
      name: preview.key,
      component: generateIframePreview(preview.value),
    })
  })
  // download page
  res.push({
    name: "Download",
    component: lazy(() => import("./download")),
  })
  return res
}
```

下面是如何实现支持中文:

去[**https://crowdin.com/backend/download/project/alist/zh-CN.zip**](https://crowdin.com/backend/download/project/alist/zh-CN.zip)这里下载中文语言包,解压后放到前端文件夹src/lang下面,然后返回前端项目根目录执行 `node ./scripts/i18n.mjs,`

此时src/lang/zh-CN下面会生成一个叫entry.ts的文件然后重新编译前端项目即可。
发表于 2023-11-19 20:52:08 | 显示全部楼层
这个搜索的优势是啥
发表于 2023-11-19 20:53:16 | 显示全部楼层
muyijiang 发表于 2023-11-19 20:52
这个搜索的优势是啥

慢但是全
发表于 2023-11-19 20:53:30 | 显示全部楼层
这种帖子下水b都不敢碰瓷
 楼主| 发表于 2023-11-19 20:54:24 | 显示全部楼层
muyijiang 发表于 2023-11-19 20:52
这个搜索的优势是啥

就是在搜索文本的时候几乎不会出现一个就在你眼皮子底下的文件中的内容搜索不到的情况~
 楼主| 发表于 2023-11-19 20:55:42 | 显示全部楼层

前几次是有点慢,后面会越来越快
发表于 2023-11-19 20:57:29 | 显示全部楼层
能分享一下效果图吗?
 楼主| 发表于 2023-11-19 21:36:06 来自手机 | 显示全部楼层
YoT 发表于 2023-11-19 20:57
能分享一下效果图吗?

这个好像也没啥效果图,就是跟原生的搜索界面一样,只是换了底层的搜索引擎~
发表于 2023-11-19 22:33:12 | 显示全部楼层
alist官方,感觉有点奇怪,为何没有提供完善的搜索能力和更易用的上下传功能(尤其是拖拽),让其达到一个网盘级别,现在来看,虽说是网盘,但是更像是jdbc桥那样的网盘驱动
发表于 2023-11-19 22:46:22 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|全球主机交流论坛

GMT+8, 2024-5-5 03:29 , Processed in 0.064359 second(s), 9 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表