82 lines
2.1 KiB
Go
82 lines
2.1 KiB
Go
|
|
package handler
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"net/http"
|
|||
|
|
"strings"
|
|||
|
|
"unicode/utf8"
|
|||
|
|
|
|||
|
|
"soul-api/internal/database"
|
|||
|
|
"soul-api/internal/model"
|
|||
|
|
|
|||
|
|
"github.com/gin-gonic/gin"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// escapeLike 转义 LIKE 中的 % _ \,防止注入与通配符滥用
|
|||
|
|
func escapeLike(s string) string {
|
|||
|
|
s = strings.ReplaceAll(s, "\\", "\\\\")
|
|||
|
|
s = strings.ReplaceAll(s, "%", "\\%")
|
|||
|
|
s = strings.ReplaceAll(s, "_", "\\_")
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SearchGet GET /api/search?q= 从 chapters 表搜索(GORM,参数化)
|
|||
|
|
func SearchGet(c *gin.Context) {
|
|||
|
|
q := strings.TrimSpace(c.Query("q"))
|
|||
|
|
if q == "" {
|
|||
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "请输入搜索关键词"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
pattern := "%" + escapeLike(q) + "%"
|
|||
|
|
var list []model.Chapter
|
|||
|
|
err := database.DB().Model(&model.Chapter{}).
|
|||
|
|
Where("section_title LIKE ? OR content LIKE ?", pattern, pattern).
|
|||
|
|
Order("sort_order ASC, id ASC").
|
|||
|
|
Limit(50).
|
|||
|
|
Find(&list).Error
|
|||
|
|
if err != nil {
|
|||
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"keyword": q, "total": 0, "results": []interface{}{}}})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
lowerQ := strings.ToLower(q)
|
|||
|
|
results := make([]gin.H, 0, len(list))
|
|||
|
|
for _, ch := range list {
|
|||
|
|
matchType := "content"
|
|||
|
|
score := 5
|
|||
|
|
if strings.Contains(strings.ToLower(ch.SectionTitle), lowerQ) {
|
|||
|
|
matchType = "title"
|
|||
|
|
score = 10
|
|||
|
|
}
|
|||
|
|
snippet := ""
|
|||
|
|
pos := strings.Index(strings.ToLower(ch.Content), lowerQ)
|
|||
|
|
if pos >= 0 && len(ch.Content) > 0 {
|
|||
|
|
start := pos - 50
|
|||
|
|
if start < 0 {
|
|||
|
|
start = 0
|
|||
|
|
}
|
|||
|
|
end := pos + utf8.RuneCountInString(q) + 50
|
|||
|
|
if end > len(ch.Content) {
|
|||
|
|
end = len(ch.Content)
|
|||
|
|
}
|
|||
|
|
snippet = ch.Content[start:end]
|
|||
|
|
if start > 0 {
|
|||
|
|
snippet = "..." + snippet
|
|||
|
|
}
|
|||
|
|
if end < len(ch.Content) {
|
|||
|
|
snippet = snippet + "..."
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
price := 1.0
|
|||
|
|
if ch.Price != nil {
|
|||
|
|
price = *ch.Price
|
|||
|
|
}
|
|||
|
|
results = append(results, gin.H{
|
|||
|
|
"id": ch.ID, "title": ch.SectionTitle, "partTitle": ch.PartTitle, "chapterTitle": ch.ChapterTitle,
|
|||
|
|
"price": price, "isFree": ch.IsFree, "matchType": matchType, "score": score, "snippet": snippet,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
c.JSON(http.StatusOK, gin.H{
|
|||
|
|
"success": true,
|
|||
|
|
"data": gin.H{"keyword": q, "total": len(results), "results": results},
|
|||
|
|
})
|
|||
|
|
}
|