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"` } // 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 15:04:05") item.CreatedAt = createdAt.Format("2006-01-02 15:04:05") item.UpdatedAt = updatedAt.Format("2006-01-02 15:04:05") items = append(items, item) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("遍历行失败: %v", err) } return items, nil }