feat: 添加投标项目查看器组件及后端支持
This commit is contained in:
214
widget/looker/app.go
Normal file
214
widget/looker/app.go
Normal file
@@ -0,0 +1,214 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user