- Telegram Bot + QQ Bot (WebSocket) 双平台支持 - 150+ 预设分类关键词,jieba 智能分词 - Web 管理后台(记录查看/删除/CSV导出) - 金额精确存储(分/int64) - 版本信息嵌入(编译时注入) - Docker 支持 - 优雅关闭(context + signal)
117 lines
2.7 KiB
Go
117 lines
2.7 KiB
Go
package service
|
||
|
||
import (
|
||
"math"
|
||
"regexp"
|
||
"strconv"
|
||
"time"
|
||
|
||
"xiaji-go/models"
|
||
|
||
"github.com/yanyiwu/gojieba"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type FinanceService struct {
|
||
db *gorm.DB
|
||
jieba *gojieba.Jieba
|
||
}
|
||
|
||
func NewFinanceService(db *gorm.DB) *FinanceService {
|
||
return &FinanceService{
|
||
db: db,
|
||
jieba: gojieba.NewJieba(),
|
||
}
|
||
}
|
||
|
||
func (s *FinanceService) Close() {
|
||
s.jieba.Free()
|
||
}
|
||
|
||
// ParseText 从自然语言文本中提取金额(分)和分类
|
||
func (s *FinanceService) ParseText(text string) (int64, string) {
|
||
// 1. 提取金额 — 优先匹配带单位的,如 "15.5元"、"¥30"、"20块"
|
||
amountPatterns := []*regexp.Regexp{
|
||
regexp.MustCompile(`[¥¥]\s*(\d+\.?\d*)`),
|
||
regexp.MustCompile(`(\d+\.?\d*)\s*[元块]`),
|
||
}
|
||
|
||
var amountStr string
|
||
for _, re := range amountPatterns {
|
||
m := re.FindStringSubmatch(text)
|
||
if len(m) > 1 {
|
||
amountStr = m[1]
|
||
break
|
||
}
|
||
}
|
||
|
||
// 兜底:取最后一个独立数字
|
||
if amountStr == "" {
|
||
re := regexp.MustCompile(`(\d+\.?\d*)`)
|
||
matches := re.FindAllStringSubmatch(text, -1)
|
||
if len(matches) > 0 {
|
||
amountStr = matches[len(matches)-1][1]
|
||
}
|
||
}
|
||
|
||
if amountStr == "" {
|
||
return 0, ""
|
||
}
|
||
|
||
amountFloat, err := strconv.ParseFloat(amountStr, 64)
|
||
if err != nil || amountFloat <= 0 {
|
||
return 0, ""
|
||
}
|
||
|
||
// 转为分
|
||
amountCents := int64(math.Round(amountFloat * 100))
|
||
|
||
// 2. 提取分类(Jieba 分词 + 数据库匹配)
|
||
words := s.jieba.Cut(text, true)
|
||
category := "其他"
|
||
|
||
for _, word := range words {
|
||
var ck models.CategoryKeyword
|
||
if err := s.db.Where("keyword = ?", word).First(&ck).Error; err == nil {
|
||
category = ck.Category
|
||
break
|
||
}
|
||
}
|
||
|
||
return amountCents, category
|
||
}
|
||
|
||
// AddTransaction 解析文本并创建一条交易记录
|
||
func (s *FinanceService) AddTransaction(userID int64, text string) (int64, string, error) {
|
||
amount, category := s.ParseText(text)
|
||
if amount == 0 {
|
||
return 0, "", nil
|
||
}
|
||
|
||
tx := models.Transaction{
|
||
UserID: userID,
|
||
Amount: amount,
|
||
Category: category,
|
||
Note: text,
|
||
Date: time.Now().Format("2006-01-02"),
|
||
}
|
||
|
||
return amount, category, s.db.Create(&tx).Error
|
||
}
|
||
|
||
// GetTransactions 获取用户的交易记录
|
||
func (s *FinanceService) GetTransactions(userID int64, limit int) ([]models.Transaction, error) {
|
||
var items []models.Transaction
|
||
err := s.db.Where("user_id = ? AND is_deleted = ?", userID, false).
|
||
Order("id desc").Limit(limit).Find(&items).Error
|
||
return items, err
|
||
}
|
||
|
||
// GetTransactionsByDate 获取用户指定日期的交易记录
|
||
func (s *FinanceService) GetTransactionsByDate(userID int64, date string) ([]models.Transaction, error) {
|
||
var items []models.Transaction
|
||
err := s.db.Where("user_id = ? AND date = ? AND is_deleted = ?", userID, date, false).
|
||
Order("id desc").Find(&items).Error
|
||
return items, err
|
||
}
|