452 lines
13 KiB
Go
452 lines
13 KiB
Go
package database
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"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"`
|
|
}
|
|
|
|
// IPAccessRule IP访问规则
|
|
type IPAccessRule struct {
|
|
ID int64 `json:"id"`
|
|
Username string `json:"username"` // 为空表示全局规则,有值表示用户专属规则
|
|
IP string `json:"ip"` // 支持单IP、CIDR(192.168.1.0/24)、IP范围(192.168.1.1-192.168.1.100)
|
|
Type string `json:"type"` // "whitelist" 或 "blacklist"
|
|
Note string `json:"note"` // 备注说明
|
|
Enabled bool `json:"enabled"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
// 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
|
|
);
|
|
CREATE TABLE IF NOT EXISTS ip_access_rules (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT DEFAULT '',
|
|
ip TEXT NOT NULL,
|
|
type TEXT NOT NULL DEFAULT 'blacklist',
|
|
note TEXT DEFAULT '',
|
|
enabled INTEGER DEFAULT 1,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_ip_rules_type ON ip_access_rules(type);
|
|
CREATE INDEX IF NOT EXISTS idx_ip_rules_enabled ON ip_access_rules(enabled);
|
|
CREATE INDEX IF NOT EXISTS idx_ip_rules_username ON ip_access_rules(username);
|
|
`
|
|
|
|
_, 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()
|
|
}
|
|
|
|
// --- IP访问规则 CRUD ---
|
|
|
|
// CreateIPRule 创建IP规则
|
|
func (db *DB) CreateIPRule(rule *IPAccessRule) error {
|
|
now := time.Now().Format("2006-01-02 15:04:05")
|
|
result, err := db.Exec(`
|
|
INSERT INTO ip_access_rules (username, ip, type, note, enabled, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
rule.Username, rule.IP, rule.Type, rule.Note, rule.Enabled, now)
|
|
if err != nil {
|
|
return fmt.Errorf("创建IP规则失败: %w", err)
|
|
}
|
|
rule.ID, _ = result.LastInsertId()
|
|
rule.CreatedAt = now
|
|
return nil
|
|
}
|
|
|
|
// ListIPRules 列出IP规则,ruleType和username为空时列出全部
|
|
func (db *DB) ListIPRules(ruleType, username string) ([]IPAccessRule, error) {
|
|
query := `SELECT id, username, ip, type, note, enabled, created_at FROM ip_access_rules`
|
|
var conditions []string
|
|
var args []interface{}
|
|
|
|
if ruleType != "" {
|
|
conditions = append(conditions, "type=?")
|
|
args = append(args, ruleType)
|
|
}
|
|
if username != "" {
|
|
if username == "__empty__" {
|
|
conditions = append(conditions, "username='')")
|
|
} else if username == "__has__" {
|
|
conditions = append(conditions, "username!='')")
|
|
} else {
|
|
conditions = append(conditions, "username=?")
|
|
args = append(args, username)
|
|
}
|
|
}
|
|
if len(conditions) > 0 {
|
|
query += " WHERE " + strings.Join(conditions, " AND ")
|
|
}
|
|
query += " ORDER BY id"
|
|
|
|
rows, err := db.Query(query, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var rules []IPAccessRule
|
|
for rows.Next() {
|
|
var rule IPAccessRule
|
|
var enabled int
|
|
if err := rows.Scan(&rule.ID, &rule.Username, &rule.IP, &rule.Type, &rule.Note, &enabled, &rule.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
rule.Enabled = enabled == 1
|
|
rules = append(rules, rule)
|
|
}
|
|
return rules, nil
|
|
}
|
|
|
|
// DeleteIPRule 删除IP规则
|
|
func (db *DB) DeleteIPRule(id int64) error {
|
|
_, err := db.Exec(`DELETE FROM ip_access_rules WHERE id=?`, id)
|
|
return err
|
|
}
|
|
|
|
// UpdateIPRule 更新IP规则
|
|
func (db *DB) UpdateIPRule(rule *IPAccessRule) error {
|
|
_, err := db.Exec(`UPDATE ip_access_rules SET username=?, ip=?, type=?, note=?, enabled=? WHERE id=?`,
|
|
rule.Username, rule.IP, rule.Type, rule.Note, rule.Enabled, rule.ID)
|
|
return err
|
|
}
|
|
|
|
// GetEnabledIPRules 获取所有启用的IP规则(全局+指定用户)
|
|
func (db *DB) GetEnabledIPRules(username string) ([]IPAccessRule, error) {
|
|
rows, err := db.Query(`SELECT id, username, ip, type, note, enabled, created_at
|
|
FROM ip_access_rules WHERE enabled=1 AND (username='' OR username=?) ORDER BY id`, username)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var rules []IPAccessRule
|
|
for rows.Next() {
|
|
var rule IPAccessRule
|
|
var enabled int
|
|
if err := rows.Scan(&rule.ID, &rule.Username, &rule.IP, &rule.Type, &rule.Note, &enabled, &rule.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
rule.Enabled = enabled == 1
|
|
rules = append(rules, rule)
|
|
}
|
|
return rules, nil
|
|
}
|