feat: 初始化FTP服务器项目 - 支持Web管理界面
This commit is contained in:
+330
@@ -0,0 +1,330 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// DB 数据库实例
|
||||
type DB struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
// FTPUser FTP用户
|
||||
type FTPUser struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password,omitempty"`
|
||||
HomeDir string `json:"home_dir"`
|
||||
Permissions string `json:"permissions"` // "read", "write", "read,write", "admin"
|
||||
QuotaSize int64 `json:"quota_size"` // 字节, 0 = 无限制
|
||||
QuotaFiles int `json:"quota_files"` // 文件数, 0 = 无限制
|
||||
UploadRate int `json:"upload_rate"` // KB/s, 0 = 无限制
|
||||
DownloadRate int `json:"download_rate"` // KB/s, 0 = 无限制
|
||||
Enabled bool `json:"enabled"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// FTPLog FTP操作日志
|
||||
type FTPLog struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
IP string `json:"ip"`
|
||||
Action string `json:"action"`
|
||||
FilePath string `json:"file_path"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// OnlineUser 在线用户
|
||||
type OnlineUser struct {
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
IP string `json:"ip"`
|
||||
LoginTime time.Time `json:"login_time"`
|
||||
LastActivity time.Time `json:"last_activity"`
|
||||
CurrentDir string `json:"current_dir"`
|
||||
}
|
||||
|
||||
// Open 打开数据库
|
||||
func Open(dbPath string) (*DB, error) {
|
||||
dir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("创建数据库目录失败: %w", err)
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("打开数据库失败: %w", err)
|
||||
}
|
||||
|
||||
if err := db.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("连接数据库失败: %w", err)
|
||||
}
|
||||
|
||||
d := &DB{db}
|
||||
if err := d.initTables(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// initTables 初始化数据表
|
||||
func (db *DB) initTables() error {
|
||||
schema := `
|
||||
CREATE TABLE IF NOT EXISTS ftp_users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
home_dir TEXT NOT NULL,
|
||||
permissions TEXT DEFAULT 'read',
|
||||
quota_size INTEGER DEFAULT 0,
|
||||
quota_files INTEGER DEFAULT 0,
|
||||
upload_rate INTEGER DEFAULT 0,
|
||||
download_rate INTEGER DEFAULT 0,
|
||||
enabled INTEGER DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ftp_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT DEFAULT '',
|
||||
ip TEXT DEFAULT '',
|
||||
action TEXT NOT NULL,
|
||||
file_path TEXT DEFAULT '',
|
||||
file_size INTEGER DEFAULT 0,
|
||||
status TEXT DEFAULT 'success',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_username ON ftp_logs(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_created ON ftp_logs(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_action ON ftp_logs(action);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_stats (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
total_uploads INTEGER DEFAULT 0,
|
||||
total_downloads INTEGER DEFAULT 0,
|
||||
total_upload_bytes INTEGER DEFAULT 0,
|
||||
total_download_bytes INTEGER DEFAULT 0,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`
|
||||
|
||||
_, err := db.Exec(schema)
|
||||
return err
|
||||
}
|
||||
|
||||
// --- FTP用户 CRUD ---
|
||||
|
||||
// CreateUser 创建FTP用户
|
||||
func (db *DB) CreateUser(user *FTPUser) error {
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
result, err := db.Exec(`
|
||||
INSERT INTO ftp_users (username, password, home_dir, permissions, quota_size, quota_files,
|
||||
upload_rate, download_rate, enabled, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
user.Username, user.Password, user.HomeDir, user.Permissions,
|
||||
user.QuotaSize, user.QuotaFiles, user.UploadRate, user.DownloadRate,
|
||||
user.Enabled, now, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建用户失败: %w", err)
|
||||
}
|
||||
user.ID, _ = result.LastInsertId()
|
||||
user.CreatedAt = now
|
||||
user.UpdatedAt = now
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUser 获取用户
|
||||
func (db *DB) GetUser(username string) (*FTPUser, error) {
|
||||
user := &FTPUser{}
|
||||
var enabled int
|
||||
err := db.QueryRow(`
|
||||
SELECT id, username, password, home_dir, permissions, quota_size, quota_files,
|
||||
upload_rate, download_rate, enabled, created_at, updated_at
|
||||
FROM ftp_users WHERE username = ?`, username,
|
||||
).Scan(&user.ID, &user.Username, &user.Password, &user.HomeDir, &user.Permissions,
|
||||
&user.QuotaSize, &user.QuotaFiles, &user.UploadRate, &user.DownloadRate,
|
||||
&enabled, &user.CreatedAt, &user.UpdatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Enabled = enabled == 1
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// ListUsers 列出所有用户
|
||||
func (db *DB) ListUsers() ([]FTPUser, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT id, username, password, home_dir, permissions, quota_size, quota_files,
|
||||
upload_rate, download_rate, enabled, created_at, updated_at
|
||||
FROM ftp_users ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []FTPUser
|
||||
for rows.Next() {
|
||||
var user FTPUser
|
||||
var enabled int
|
||||
err := rows.Scan(&user.ID, &user.Username, &user.Password, &user.HomeDir, &user.Permissions,
|
||||
&user.QuotaSize, &user.QuotaFiles, &user.UploadRate, &user.DownloadRate,
|
||||
&enabled, &user.CreatedAt, &user.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Enabled = enabled == 1
|
||||
users = append(users, user)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户
|
||||
func (db *DB) UpdateUser(user *FTPUser) error {
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err := db.Exec(`
|
||||
UPDATE ftp_users SET home_dir=?, permissions=?, quota_size=?, quota_files=?,
|
||||
upload_rate=?, download_rate=?, enabled=?, updated_at=?
|
||||
WHERE username=?`,
|
||||
user.HomeDir, user.Permissions, user.QuotaSize, user.QuotaFiles,
|
||||
user.UploadRate, user.DownloadRate, user.Enabled, now, user.Username)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateUserPassword 更新用户密码
|
||||
func (db *DB) UpdateUserPassword(username, password string) error {
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err := db.Exec(`UPDATE ftp_users SET password=?, updated_at=? WHERE username=?`,
|
||||
password, now, username)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (db *DB) DeleteUser(username string) error {
|
||||
_, err := db.Exec(`DELETE FROM ftp_users WHERE username=?`, username)
|
||||
return err
|
||||
}
|
||||
|
||||
// --- 日志 ---
|
||||
|
||||
// AddLog 添加操作日志
|
||||
func (db *DB) AddLog(log *FTPLog) error {
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
result, err := db.Exec(`
|
||||
INSERT INTO ftp_logs (username, ip, action, file_path, file_size, status, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
log.Username, log.IP, log.Action, log.FilePath, log.FileSize, log.Status, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.ID, _ = result.LastInsertId()
|
||||
log.CreatedAt = now
|
||||
return nil
|
||||
}
|
||||
|
||||
// QueryLogs 查询日志
|
||||
func (db *DB) QueryLogs(username, action string, page, pageSize int) ([]FTPLog, int, error) {
|
||||
where := "WHERE 1=1"
|
||||
args := []interface{}{}
|
||||
|
||||
if username != "" {
|
||||
where += " AND username LIKE ?"
|
||||
args = append(args, "%"+username+"%")
|
||||
}
|
||||
if action != "" {
|
||||
where += " AND action = ?"
|
||||
args = append(args, action)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
var total int
|
||||
countSQL := fmt.Sprintf("SELECT COUNT(*) FROM ftp_logs %s", where)
|
||||
err := db.QueryRow(countSQL, args...).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (page - 1) * pageSize
|
||||
querySQL := fmt.Sprintf(`
|
||||
SELECT id, username, ip, action, file_path, file_size, status, created_at
|
||||
FROM ftp_logs %s ORDER BY id DESC LIMIT ? OFFSET ?`, where)
|
||||
queryArgs := append(args, pageSize, offset)
|
||||
|
||||
rows, err := db.Query(querySQL, queryArgs...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var logs []FTPLog
|
||||
for rows.Next() {
|
||||
var log FTPLog
|
||||
err := rows.Scan(&log.ID, &log.Username, &log.IP, &log.Action, &log.FilePath,
|
||||
&log.FileSize, &log.Status, &log.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
logs = append(logs, log)
|
||||
}
|
||||
return logs, total, nil
|
||||
}
|
||||
|
||||
// GetLogStats 获取日志统计
|
||||
func (db *DB) GetLogStats() (map[string]interface{}, error) {
|
||||
stats := make(map[string]interface{})
|
||||
|
||||
var totalUsers int
|
||||
db.QueryRow("SELECT COUNT(*) FROM ftp_users").Scan(&totalUsers)
|
||||
stats["total_users"] = totalUsers
|
||||
|
||||
var enabledUsers int
|
||||
db.QueryRow("SELECT COUNT(*) FROM ftp_users WHERE enabled=1").Scan(&enabledUsers)
|
||||
stats["enabled_users"] = enabledUsers
|
||||
|
||||
var todayLogins int
|
||||
db.QueryRow("SELECT COUNT(DISTINCT username) FROM ftp_logs WHERE action='login' AND created_at >= date('now')").Scan(&todayLogins)
|
||||
stats["today_logins"] = todayLogins
|
||||
|
||||
var todayUploads int
|
||||
db.QueryRow("SELECT COUNT(*) FROM ftp_logs WHERE action='upload' AND created_at >= date('now')").Scan(&todayUploads)
|
||||
stats["today_uploads"] = todayUploads
|
||||
|
||||
var todayDownloads int
|
||||
db.QueryRow("SELECT COUNT(*) FROM ftp_logs WHERE action='download' AND created_at >= date('now')").Scan(&todayDownloads)
|
||||
stats["today_downloads"] = todayDownloads
|
||||
|
||||
var totalUploadBytes int64
|
||||
db.QueryRow("SELECT COALESCE(SUM(file_size),0) FROM ftp_logs WHERE action='upload'").Scan(&totalUploadBytes)
|
||||
stats["total_upload_bytes"] = totalUploadBytes
|
||||
|
||||
var totalDownloadBytes int64
|
||||
db.QueryRow("SELECT COALESCE(SUM(file_size),0) FROM ftp_logs WHERE action='download'").Scan(&totalDownloadBytes)
|
||||
stats["total_download_bytes"] = totalDownloadBytes
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// CleanOldLogs 清理旧日志(保留最近N天)
|
||||
func (db *DB) CleanOldLogs(days int) (int64, error) {
|
||||
result, err := db.Exec(`DELETE FROM ftp_logs WHERE created_at < datetime('now', ?||' days')`,
|
||||
fmt.Sprintf("-%d", days))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
Reference in New Issue
Block a user