update: 公众奥自动回复

This commit is contained in:
ctwj
2025-11-02 23:55:28 +08:00
parent dbde0e1675
commit 270022188e
4 changed files with 259 additions and 41 deletions

View File

@@ -152,9 +152,15 @@ func main() {
// 将Repository管理器注入到handlers中
handlers.SetRepositoryManager(repoManager)
// 将Repository管理器注入到services中
services.SetRepositoryManager(repoManager)
// 设置Meilisearch管理器到handlers中
handlers.SetMeilisearchManager(meilisearchManager)
// 设置Meilisearch管理器到services中
services.SetMeilisearchManager(meilisearchManager)
// 设置全局调度器的Meilisearch管理器
scheduler.SetGlobalMeilisearchManager(meilisearchManager)

102
services/base.go Normal file
View File

@@ -0,0 +1,102 @@
package services
import (
"github.com/ctwj/urldb/db/entity"
"github.com/ctwj/urldb/db/repo"
"github.com/ctwj/urldb/utils"
)
var repoManager *repo.RepositoryManager
var meilisearchManager *MeilisearchManager
// SetRepositoryManager 设置Repository管理器
func SetRepositoryManager(manager *repo.RepositoryManager) {
repoManager = manager
}
// SetMeilisearchManager 设置Meilisearch管理器
func SetMeilisearchManager(manager *MeilisearchManager) {
meilisearchManager = manager
}
// UnifiedSearchResources 执行统一搜索优先使用Meilisearch否则使用数据库搜索并处理违禁词
func UnifiedSearchResources(keyword string, limit int, systemConfigRepo repo.SystemConfigRepository, resourceRepo repo.ResourceRepository) ([]entity.Resource, error) {
var resources []entity.Resource
var total int64
var err error
// 如果启用了Meilisearch优先使用Meilisearch搜索
if meilisearchManager != nil && meilisearchManager.IsEnabled() {
// 构建MeiliSearch过滤器
filters := make(map[string]interface{})
// 使用Meilisearch搜索
service := meilisearchManager.GetService()
if service != nil {
docs, docTotal, err := service.Search(keyword, filters, 1, limit)
if err == nil {
// 将Meilisearch文档转换为Resource实体
for _, doc := range docs {
resource := entity.Resource{
ID: doc.ID,
Title: doc.Title,
Description: doc.Description,
URL: doc.URL,
SaveURL: doc.SaveURL,
FileSize: doc.FileSize,
Key: doc.Key,
PanID: doc.PanID,
CreatedAt: doc.CreatedAt,
UpdatedAt: doc.UpdatedAt,
}
resources = append(resources, resource)
}
total = docTotal
// 获取违禁词配置并处理违禁词
cleanWords, err := utils.GetForbiddenWordsFromConfig(func() (string, error) {
return systemConfigRepo.GetConfigValue(entity.ConfigKeyForbiddenWords)
})
if err != nil {
utils.Error("获取违禁词配置失败: %v", err)
cleanWords = []string{} // 如果获取失败,使用空列表
}
// 处理违禁词替换
if len(cleanWords) > 0 {
resources = utils.ProcessResourcesForbiddenWords(resources, cleanWords)
}
return resources, nil
} else {
utils.Error("MeiliSearch搜索失败回退到数据库搜索: %v", err)
}
}
}
// 如果MeiliSearch未启用、搜索失败或没有搜索关键词使用数据库搜索
resources, total, err = resourceRepo.Search(keyword, nil, 1, limit)
if err != nil {
return nil, err
}
if total == 0 {
return []entity.Resource{}, nil
}
// 获取违禁词配置并处理违禁词
cleanWords, err := utils.GetForbiddenWordsFromConfig(func() (string, error) {
return systemConfigRepo.GetConfigValue(entity.ConfigKeyForbiddenWords)
})
if err != nil {
utils.Error("获取违禁词配置失败: %v", err)
cleanWords = []string{} // 如果获取失败,使用空列表
}
// 处理违禁词替换
if len(cleanWords) > 0 {
resources = utils.ProcessResourcesForbiddenWords(resources, cleanWords)
}
return resources, nil
}

View File

@@ -197,6 +197,24 @@ func (s *WechatBotServiceImpl) handleTextMessage(msg *message.MixMessage) (inter
return message.NewText("请输入搜索关键词"), nil
}
// 检查搜索关键词是否包含违禁词
cleanWords, err := utils.GetForbiddenWordsFromConfig(func() (string, error) {
return s.systemConfigRepo.GetConfigValue(entity.ConfigKeyForbiddenWords)
})
if err != nil {
utils.Error("获取违禁词配置失败: %v", err)
cleanWords = []string{} // 如果获取失败,使用空列表
}
// 检查关键词是否包含违禁词
if len(cleanWords) > 0 {
containsForbidden, matchedWords := utils.CheckContainsForbiddenWords(keyword, cleanWords)
if containsForbidden {
utils.Info("[WECHAT:MESSAGE] 搜索关键词包含违禁词: %v", matchedWords)
return message.NewText("您的搜索关键词包含违禁内容,不予处理"), nil
}
}
// 搜索资源
utils.Debug("[WECHAT:MESSAGE] 开始搜索资源,限制数量: %d", s.config.SearchLimit)
resources, err := s.SearchResources(keyword)
@@ -212,7 +230,7 @@ func (s *WechatBotServiceImpl) handleTextMessage(msg *message.MixMessage) (inter
}
// 创建搜索会话并保存第一页结果
s.searchSessionManager.CreateSession(string(msg.FromUserName), keyword, resources, 5)
s.searchSessionManager.CreateSession(string(msg.FromUserName), keyword, resources, 4)
pageResources := s.searchSessionManager.GetCurrentPageResources(string(msg.FromUserName))
// 格式化第一页搜索结果
@@ -270,42 +288,82 @@ func (s *WechatBotServiceImpl) handleGetResource(userID, command string) (interf
return message.NewText("没有找到搜索记录,请先进行搜索"), nil
}
// 检查是否只输入了"获取"或"get",没有指定编号
if command == "获取" || command == "get" {
return message.NewText("📌 请输入要获取的资源编号\n\n💡 提示:回复\"获取 1\"或\"get 1\"获取第一个资源的详细信息"), nil
}
// 解析命令,例如:"获取 1" 或 "get 2"
// 支持"获取4"这种没有空格的格式
var index int
_, err := fmt.Sscanf(command, "获取 %d", &index)
_, err := fmt.Sscanf(command, "获取%d", &index)
if err != nil {
_, err = fmt.Sscanf(command, "获取 %d", &index)
if err != nil {
_, err = fmt.Sscanf(command, "get%d", &index)
if err != nil {
_, err = fmt.Sscanf(command, "get %d", &index)
if err != nil {
return message.NewText("命令格式错误,请使用:获取 1 或 get 1"), nil
return message.NewText("命令格式错误\n\n📌 正确格式:\n • 获取 1\n • get 1\n • 获取1\n • get1"), nil
}
}
}
}
if index < 1 || index > len(session.Resources) {
return message.NewText(fmt.Sprintf("资源编号超出范围请输入 1-%d 之间的数字", len(session.Resources))), nil
return message.NewText(fmt.Sprintf("资源编号超出范围\n\n📌 请输入 1-%d 之间的数字\n💡 提示:回复\"获取 %d\"获取第%d个资源", len(session.Resources), index, index)), nil
}
// 获取指定资源
resource := session.Resources[index-1]
// 格式化资源详细信息
// 格式化资源详细信息(美化输出)
var result strings.Builder
result.WriteString(fmt.Sprintf("📋 资源详情\n\n"))
result.WriteString(fmt.Sprintf("标题: %s\n", resource.Title))
// result.WriteString(fmt.Sprintf("📌 资源详情\n\n"))
// 标题
result.WriteString(fmt.Sprintf("📌 标题: %s\n", resource.Title))
// 描述
if resource.Description != "" {
result.WriteString(fmt.Sprintf("描述: %s\n", resource.Description))
result.WriteString(fmt.Sprintf("\n📝 描述:\n %s\n", resource.Description))
}
// 文件大小
if resource.FileSize != "" {
result.WriteString(fmt.Sprintf("大小: %s\n", resource.FileSize))
result.WriteString(fmt.Sprintf("\n📊 大小: %s\n", resource.FileSize))
}
// 作者
if resource.Author != "" {
result.WriteString(fmt.Sprintf("作者: %s\n", resource.Author))
result.WriteString(fmt.Sprintf("\n👤 作者: %s\n", resource.Author))
}
// 分类
if resource.Category.Name != "" {
result.WriteString(fmt.Sprintf("\n📂 分类: %s\n", resource.Category.Name))
}
// 标签
if len(resource.Tags) > 0 {
result.WriteString("\n🏷 标签: ")
var tags []string
for _, tag := range resource.Tags {
tags = append(tags, tag.Name)
}
result.WriteString(fmt.Sprintf("%s\n", strings.Join(tags, " ")))
}
// 链接(美化)
if resource.SaveURL != "" {
result.WriteString(fmt.Sprintf("\n📥 转存链接: %s", resource.SaveURL))
result.WriteString(fmt.Sprintf("\n📥 转存链接:\n %s", resource.SaveURL))
} else if resource.URL != "" {
result.WriteString(fmt.Sprintf("\n🔗 资源链接: %s", resource.URL))
result.WriteString(fmt.Sprintf("\n🔗 资源链接:\n %s", resource.URL))
}
// 添加操作提示
result.WriteString(fmt.Sprintf("\n\n💡 提示:回复\"获取 %d\"可再次查看此资源", index))
return message.NewText(result.String()), nil
}
@@ -322,18 +380,65 @@ func (s *WechatBotServiceImpl) formatPageResources(keyword string, resources []e
result.WriteString(fmt.Sprintf("🔍 搜索\"%s\"的结果(第%d/%d页\n\n", keyword, currentPage, totalPages))
for i, resource := range resources {
// 构建当前资源的文本表示
var resourceText strings.Builder
// 计算全局索引当前页的第i个资源在整个结果中的位置
globalIndex := (currentPage-1)*5 + i + 1
result.WriteString(fmt.Sprintf("%d. %s\n", globalIndex, resource.Title))
globalIndex := (currentPage-1)*4 + i + 1
resourceText.WriteString(fmt.Sprintf("%d. 📌 %s\n", globalIndex, resource.Title))
if resource.Description != "" {
// 限制描述长度以避免消息过长(正确处理中文字符)
desc := resource.Description
if len(desc) > 50 {
desc = desc[:50] + "..."
// 将字符串转换为 rune 切片以正确处理中文字符
runes := []rune(desc)
if len(runes) > 50 {
desc = string(runes[:50]) + "..."
}
result.WriteString(fmt.Sprintf(" %s\n", desc))
resourceText.WriteString(fmt.Sprintf(" 📝 %s\n", desc))
}
result.WriteString(fmt.Sprintf(" 回复\"获取 %d\"查看详细信息\n", globalIndex))
result.WriteString("\n")
// 添加标签显示(格式:🏷️标签,空格,再接别的标签)
if len(resource.Tags) > 0 {
var tags []string
for _, tag := range resource.Tags {
tags = append(tags, "🏷️"+tag.Name)
}
// 限制标签数量以避免消息过长
if len(tags) > 5 {
tags = tags[:5]
}
resourceText.WriteString(fmt.Sprintf(" %s\n", strings.Join(tags, " ")))
}
resourceText.WriteString(fmt.Sprintf(" 👉 回复\"获取 %d\"查看详细信息\n", globalIndex))
resourceText.WriteString("\n")
// 预计算添加当前资源后的消息长度
tempMessage := result.String() + resourceText.String()
// 添加分页提示和预留空间
if currentPage > 1 || currentPage < totalPages {
tempMessage += "💡 提示:回复\""
if currentPage > 1 && currentPage < totalPages {
tempMessage += "上一页\"或\"下一页"
} else if currentPage > 1 {
tempMessage += "上一页"
} else {
tempMessage += "下一页"
}
tempMessage += "\"翻页\n"
}
// 检查添加当前资源后是否会超过微信限制
tempRunes := []rune(tempMessage)
if len(tempRunes) > 550 {
result.WriteString("💡 内容较多,请翻页查看更多\n")
break
}
// 如果不会超过限制,则添加当前资源到结果中
result.WriteString(resourceText.String())
}
// 添加分页提示
@@ -349,7 +454,16 @@ func (s *WechatBotServiceImpl) formatPageResources(keyword string, resources []e
result.WriteString(fmt.Sprintf("💡 提示:回复\"%s\"翻页\n", strings.Join(pageTips, "\"或\"")))
}
return result.String()
// 确保消息不超过微信限制(正确处理中文字符)
message := result.String()
// 将字符串转换为 rune 切片以正确处理中文字符
runes := []rune(message)
if len(runes) > 600 {
// 如果还是超过限制截断消息微信建议不超过600个字符
message = string(runes[:597]) + "..."
}
return message
}
// handleEventMessage 处理事件消息
@@ -363,17 +477,8 @@ func (s *WechatBotServiceImpl) handleEventMessage(msg *message.MixMessage) (inte
// SearchResources 搜索资源
func (s *WechatBotServiceImpl) SearchResources(keyword string) ([]entity.Resource, error) {
// 使用现有的资源搜索功能
resources, total, err := s.resourceRepo.Search(keyword, nil, 1, s.config.SearchLimit)
if err != nil {
return nil, err
}
if total == 0 {
return []entity.Resource{}, nil
}
return resources, nil
// 使用统一搜索函数包含Meilisearch优先搜索和违禁词处理
return UnifiedSearchResources(keyword, s.config.SearchLimit, s.systemConfigRepo, s.resourceRepo)
}
// formatSearchResults 格式化搜索结果

View File

@@ -130,7 +130,7 @@ func (p *ForbiddenWordsProcessor) ProcessForbiddenWords(text string, forbiddenWo
// ParseForbiddenWordsConfig 解析违禁词配置字符串
// 参数:
// - config: 违禁词配置字符串,多个词用逗号分隔
// - config: 违禁词配置字符串,多个词用逗号或换行符分隔
//
// 返回:
// - []string: 处理后的违禁词列表
@@ -139,16 +139,21 @@ func (p *ForbiddenWordsProcessor) ParseForbiddenWordsConfig(config string) []str
return nil
}
words := strings.Split(config, ",")
var cleanWords []string
for _, word := range words {
word = strings.TrimSpace(word)
var words []string
// 首先尝试用换行符分割
lines := strings.Split(config, "\n")
for _, line := range lines {
// 对每一行再用逗号分割(兼容两种格式)
parts := strings.Split(line, ",")
for _, part := range parts {
word := strings.TrimSpace(part)
if word != "" {
cleanWords = append(cleanWords, word)
words = append(words, word)
}
}
}
return cleanWords
return words
}
// 全局实例,方便直接调用