Files
bidding_watcher/widget/looker/app.go

372 lines
8.2 KiB
Go
Raw Normal View History

package main
import (
"context"
"database/sql"
"fmt"
"os"
"path/filepath"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/joho/godotenv"
)
// DatabaseConfig 数据库配置
type DatabaseConfig struct {
Type string
Host string
Port string
Username string
Password string
Name string
}
// BidItem 投标项目结构体
type BidItem struct {
ID string `json:"id"`
Title string `json:"title"`
URL string `json:"url"`
PublishDate string `json:"publishDate"`
Source string `json:"source"`
Pin bool `json:"pin"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
// AiRecommendation AI推荐结构体
type AiRecommendation struct {
ID string `json:"id"`
Title string `json:"title"`
Confidence int `json:"confidence"`
CreatedAt string `json:"createdAt"`
}
// CrawlInfoStat 爬虫统计信息结构体
type CrawlInfoStat struct {
Source string `json:"source"`
Count int `json:"count"`
LatestUpdate string `json:"latestUpdate"`
LatestPublishDate string `json:"latestPublishDate"`
Error string `json:"error"`
}
// App struct
type App struct {
ctx context.Context
dbConfig *DatabaseConfig
projectRootDir string
}
// NewApp creates a new App application struct
func NewApp() *App {
return &App{}
}
// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
a.loadEnv()
}
// loadEnv 加载 .env 文件
func (a *App) loadEnv() {
// 获取项目根目录(从当前工作目录向上查找 .env 文件)
wd, err := os.Getwd()
if err != nil {
fmt.Printf("获取工作目录失败: %v\n", err)
return
}
// 查找 .env 文件
envPath := a.findEnvFile(wd)
if envPath == "" {
fmt.Println("未找到 .env 文件")
return
}
// 加载 .env 文件
if err := godotenv.Load(envPath); err != nil {
fmt.Printf("加载 .env 文件失败: %v\n", err)
return
}
// 保存项目根目录
a.projectRootDir = filepath.Dir(envPath)
// 读取数据库配置
a.dbConfig = &DatabaseConfig{
Type: os.Getenv("DATABASE_TYPE"),
Host: os.Getenv("DATABASE_HOST"),
Port: os.Getenv("DATABASE_PORT"),
Username: os.Getenv("DATABASE_USERNAME"),
Password: os.Getenv("DATABASE_PASSWORD"),
Name: os.Getenv("DATABASE_NAME"),
}
fmt.Printf("数据库配置已加载: %s@%s:%s/%s\n", a.dbConfig.Username, a.dbConfig.Host, a.dbConfig.Port, a.dbConfig.Name)
}
// findEnvFile 查找 .env 文件
func (a *App) findEnvFile(startDir string) string {
dir := startDir
for {
envPath := filepath.Join(dir, ".env")
if _, err := os.Stat(envPath); err == nil {
return envPath
}
// 检查是否到达根目录
parent := filepath.Dir(dir)
if parent == dir {
break
}
dir = parent
}
return ""
}
// GetDatabaseConfig 获取数据库配置
func (a *App) GetDatabaseConfig() *DatabaseConfig {
return a.dbConfig
}
// GetDatabaseDSN 获取数据库连接字符串
func (a *App) GetDatabaseDSN() string {
if a.dbConfig == nil {
return ""
}
// 根据数据库类型生成 DSN
switch strings.ToLower(a.dbConfig.Type) {
case "mariadb", "mysql":
return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
a.dbConfig.Username,
a.dbConfig.Password,
a.dbConfig.Host,
a.dbConfig.Port,
a.dbConfig.Name,
)
case "postgres", "postgresql":
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
a.dbConfig.Host,
a.dbConfig.Port,
a.dbConfig.Username,
a.dbConfig.Password,
a.dbConfig.Name,
)
default:
return ""
}
}
// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s, It's show time!", name)
}
// GetPinnedBidItems 获取 pin=true 的投标项目
func (a *App) GetPinnedBidItems() ([]BidItem, error) {
dsn := a.GetDatabaseDSN()
if dsn == "" {
return nil, fmt.Errorf("数据库配置未加载")
}
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, fmt.Errorf("连接数据库失败: %v", err)
}
defer db.Close()
// 测试连接
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("数据库连接测试失败: %v", err)
}
// 查询 pin=true 的记录
query := `SELECT id, title, url, publishDate, source, pin, createdAt, updatedAt
FROM bid_items
WHERE pin = true
ORDER BY createdAt DESC`
rows, err := db.Query(query)
if err != nil {
return nil, fmt.Errorf("查询失败: %v", err)
}
defer rows.Close()
var items []BidItem
for rows.Next() {
var item BidItem
var publishDate, createdAt, updatedAt time.Time
err := rows.Scan(
&item.ID,
&item.Title,
&item.URL,
&publishDate,
&item.Source,
&item.Pin,
&createdAt,
&updatedAt,
)
if err != nil {
return nil, fmt.Errorf("扫描行失败: %v", err)
}
item.PublishDate = publishDate.Format("2006-01-02")
item.CreatedAt = createdAt.Format("2006-01-02")
item.UpdatedAt = updatedAt.Format("2006-01-02")
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历行失败: %v", err)
}
return items, nil
}
// GetAiRecommendations 获取 AI 推荐数据
func (a *App) GetAiRecommendations() ([]AiRecommendation, error) {
dsn := a.GetDatabaseDSN()
if dsn == "" {
return nil, fmt.Errorf("数据库配置未加载")
}
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, fmt.Errorf("连接数据库失败: %v", err)
}
defer db.Close()
// 测试连接
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("数据库连接测试失败: %v", err)
}
// 查询 ai_recommendations 表
query := `SELECT id, title, confidence, createdAt
FROM ai_recommendations
ORDER BY createdAt DESC`
rows, err := db.Query(query)
if err != nil {
return nil, fmt.Errorf("查询失败: %v", err)
}
defer rows.Close()
var items []AiRecommendation
for rows.Next() {
var item AiRecommendation
var createdAt time.Time
err := rows.Scan(
&item.ID,
&item.Title,
&item.Confidence,
&createdAt,
)
if err != nil {
return nil, fmt.Errorf("扫描行失败: %v", err)
}
item.CreatedAt = createdAt.Format("2006-01-02")
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历行失败: %v", err)
}
return items, nil
}
// GetCrawlInfoStats 获取爬虫统计信息
func (a *App) GetCrawlInfoStats() ([]CrawlInfoStat, error) {
dsn := a.GetDatabaseDSN()
if dsn == "" {
return nil, fmt.Errorf("数据库配置未加载")
}
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, fmt.Errorf("连接数据库失败: %v", err)
}
defer db.Close()
// 测试连接
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("数据库连接测试失败: %v", err)
}
// 查询 crawl_info_add 表,按 source 分组获取最新记录
query := `SELECT
c1.source,
c1.count,
c1.createdAt as latestUpdate,
c1.latestPublishDate,
c1.error
FROM crawl_info_add c1
WHERE c1.createdAt = (
SELECT MAX(c2.createdAt)
FROM crawl_info_add c2
WHERE c2.source = c1.source
)
ORDER BY c1.source`
rows, err := db.Query(query)
if err != nil {
return nil, fmt.Errorf("查询失败: %v", err)
}
defer rows.Close()
var stats []CrawlInfoStat
for rows.Next() {
var stat CrawlInfoStat
var latestUpdate, latestPublishDate sql.NullTime
var errorStr sql.NullString
err := rows.Scan(
&stat.Source,
&stat.Count,
&latestUpdate,
&latestPublishDate,
&errorStr,
)
if err != nil {
return nil, fmt.Errorf("扫描行失败: %v", err)
}
if latestUpdate.Valid {
stat.LatestUpdate = latestUpdate.Time.Format("2006-01-02 15:04:05")
} else {
stat.LatestUpdate = ""
}
if latestPublishDate.Valid {
stat.LatestPublishDate = latestPublishDate.Time.Format("2006-01-02 15:04:05")
} else {
stat.LatestPublishDate = ""
}
if errorStr.Valid {
stat.Error = errorStr.String
} else {
stat.Error = ""
}
stats = append(stats, stat)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历行失败: %v", err)
}
return stats, nil
}