- core/module: 命令模板、门控(feature flag/confirm/dry-run)、ecode 错误标签、ResultFormatter - runbook executor: http.get/http.post/assert.json(expect_success 严格断言)、目标主机管理、任务落库、最终 stdout 直接回传 - cf 模块: /cf dns list 支持域名自动解析 zone_id;dns list/zones/workers list 格式化回传;API success:false 判真失败 - 全部 CF/CPA runbook 接入严格断言,杜绝假阳性 success - 新增 cpa/cfdav/mail/sub 模块与 runbooks;QQ 订阅到期提醒调度 - Web 后台: 模块设置(CF/CPA/cfdav/AI)、OPS 任务、渠道管理、审计页
155 lines
3.9 KiB
Go
155 lines
3.9 KiB
Go
package service
|
|
|
|
import (
|
|
"math"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
|
|
"ops-assistant/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
|
|
}
|
|
|
|
// CategoryStat 分类统计结果
|
|
type CategoryStat struct {
|
|
Category string
|
|
Total int64
|
|
Count int
|
|
}
|
|
|
|
// GetCategoryStats 获取用户指定日期范围的分类统计
|
|
func (s *FinanceService) GetCategoryStats(userID int64, dateFrom, dateTo string) ([]CategoryStat, error) {
|
|
var stats []CategoryStat
|
|
err := s.db.Model(&models.Transaction{}).
|
|
Select("category, SUM(amount) as total, COUNT(*) as count").
|
|
Where("user_id = ? AND date >= ? AND date <= ? AND is_deleted = ?", userID, dateFrom, dateTo, false).
|
|
Group("category").
|
|
Order("total desc").
|
|
Find(&stats).Error
|
|
return stats, err
|
|
}
|
|
|
|
// DailyStat 每日统计结果
|
|
type DailyStat struct {
|
|
Date string
|
|
Total int64
|
|
Count int
|
|
}
|
|
|
|
// GetDailyStats 获取用户指定日期范围的每日统计
|
|
func (s *FinanceService) GetDailyStats(userID int64, dateFrom, dateTo string) ([]DailyStat, error) {
|
|
var stats []DailyStat
|
|
err := s.db.Model(&models.Transaction{}).
|
|
Select("date, SUM(amount) as total, COUNT(*) as count").
|
|
Where("user_id = ? AND date >= ? AND date <= ? AND is_deleted = ?", userID, dateFrom, dateTo, false).
|
|
Group("date").
|
|
Order("date asc").
|
|
Find(&stats).Error
|
|
return stats, err
|
|
}
|