using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using TeamAAS.Database.Interfaces;
using TeamAAS.Database.Models;
using TeamAAS.Database.Providers;
namespace TeamAAS.Database.Services
{
///
/// 系统数据库(用户权限 + 操作审计)。
/// 固定 SQLite 文件,首次运行自动建表并创建默认管理员。
///
public class SystemDatabase : IDisposable
{
private readonly IDatabase _db;
private readonly string _dbPath;
///
/// 数据库实例
///
public IDatabase Database => _db;
///
/// 是否连接成功
///
public bool IsConnected => _db?.IsConnected == true;
///
/// 构造系统数据库
///
/// 配置目录(如 Config\),数据库文件放这里
public SystemDatabase(string configDir)
{
if (string.IsNullOrWhiteSpace(configDir))
throw new ArgumentNullException(nameof(configDir));
_dbPath = Path.Combine(configDir, "system.db");
var config = new DatabaseConfig
{
Id = Guid.Parse("11111111-1111-1111-1111-111111111111"),
Name = "SystemDB",
ProviderType = "sqlite",
Server = _dbPath
};
_db = new SqliteDatabase();
_db.Configure(config);
}
///
/// 初始化:打开连接 + 自动建表 + 确保默认管理员存在
///
public (bool Success, string Message) Initialize()
{
try
{
var dir = Path.GetDirectoryName(_dbPath);
if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
if (!_db.Open())
return (false, "系统数据库打开失败");
CreateTables();
EnsureDefaultAdmin();
return (true, "系统数据库初始化成功");
}
catch (Exception ex)
{
return (false, $"系统数据库初始化失败:{ex.Message}");
}
}
#region 建表
private void CreateTables()
{
var sqls = new[]
{
// 角色表
@"CREATE TABLE IF NOT EXISTS sys_roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
role_name TEXT NOT NULL UNIQUE,
display_name TEXT,
description TEXT,
is_enabled INTEGER DEFAULT 1,
created_at TEXT DEFAULT (datetime('now','localtime')),
updated_at TEXT DEFAULT (datetime('now','localtime'))
)",
// 权限表
@"CREATE TABLE IF NOT EXISTS sys_permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
permission_key TEXT NOT NULL UNIQUE,
display_name TEXT,
category TEXT,
description TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
)",
// 用户表
@"CREATE TABLE IF NOT EXISTS sys_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
salt TEXT NOT NULL,
display_name TEXT,
email TEXT,
is_enabled INTEGER DEFAULT 1,
is_locked INTEGER DEFAULT 0,
login_failed_count INTEGER DEFAULT 0,
last_login_at TEXT,
created_at TEXT DEFAULT (datetime('now','localtime')),
updated_at TEXT DEFAULT (datetime('now','localtime'))
)",
// 用户-角色关联
@"CREATE TABLE IF NOT EXISTS sys_user_roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now','localtime')),
UNIQUE(user_id, role_id)
)",
// 角色-权限关联
@"CREATE TABLE IF NOT EXISTS sys_role_permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
role_id INTEGER NOT NULL,
permission_id INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now','localtime')),
UNIQUE(role_id, permission_id)
)",
// 审计日志
@"CREATE TABLE IF NOT EXISTS sys_audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
username TEXT,
action_type TEXT NOT NULL,
action_detail TEXT,
module TEXT,
ip_address TEXT,
result INTEGER DEFAULT 1,
error_message TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
)",
// 索引
"CREATE INDEX IF NOT EXISTS idx_audit_user ON sys_audit_logs(username)",
"CREATE INDEX IF NOT EXISTS idx_audit_action ON sys_audit_logs(action_type)",
"CREATE INDEX IF NOT EXISTS idx_audit_time ON sys_audit_logs(created_at)",
"CREATE INDEX IF NOT EXISTS idx_user_roles_uid ON sys_user_roles(user_id)",
"CREATE INDEX IF NOT EXISTS idx_role_perm_rid ON sys_role_permissions(role_id)"
};
_db.ExecuteTransaction(sqls);
}
#endregion
#region 默认数据
///
/// 确保默认管理员账号存在(admin / admin123)
///
private void EnsureDefaultAdmin()
{
var count = Convert.ToInt32(_db.ExecuteScalar(
"SELECT COUNT(*) FROM sys_users WHERE username = 'admin'"));
if (count > 0) return;
// 默认角色
var roleId = Convert.ToInt32(_db.ExecuteScalar(
"SELECT id FROM sys_roles WHERE role_name = 'admin'"));
if (roleId == 0)
{
_db.ExecuteNonQuery(
"INSERT INTO sys_roles (role_name, display_name, description) VALUES ('admin', '系统管理员', '拥有全部权限')");
roleId = Convert.ToInt32(_db.ExecuteScalar("SELECT last_insert_rowid()"));
}
// 默认管理员
var salt = GenerateSalt();
var hash = HashPassword("admin123", salt);
var userParams = new Dictionary
{
{"@username", "admin"},
{"@password_hash", hash},
{"@salt", salt},
{"@display_name", "系统管理员"}
};
_db.ExecuteNonQuery(
"INSERT INTO sys_users (username, password_hash, salt, display_name) VALUES (@username, @password_hash, @salt, @display_name)",
userParams);
var userId = Convert.ToInt32(_db.ExecuteScalar("SELECT last_insert_rowid()"));
// 关联角色
var relateParams = new Dictionary
{
{"@user_id", userId},
{"@role_id", roleId}
};
_db.ExecuteNonQuery(
"INSERT OR IGNORE INTO sys_user_roles (user_id, role_id) VALUES (@user_id, @role_id)",
relateParams);
// 默认权限(种子数据)
SeedPermissions();
}
private void SeedPermissions()
{
var perms = new (string Key, string Name, string Category)[]
{
("system.user.view", "查看用户", "用户管理"),
("system.user.manage", "管理用户", "用户管理"),
("system.role.view", "查看角色", "角色权限"),
("system.role.manage", "管理角色", "角色权限"),
("system.audit.view", "查看审计日志", "系统审计"),
("product.view", "查看产品", "产品管理"),
("product.manage", "管理产品", "产品管理"),
("recipe.view", "查看配方", "配方管理"),
("recipe.manage", "管理配方", "配方管理"),
("device.control", "设备控制", "设备操作"),
("flow.edit", "编辑流程", "流程编辑"),
("flow.run", "运行流程", "流程运行"),
("system.config", "系统配置", "系统设置")
};
// 管理员角色ID
var roleId = Convert.ToInt32(_db.ExecuteScalar(
"SELECT id FROM sys_roles WHERE role_name = 'admin'"));
foreach (var p in perms)
{
var pms = new Dictionary
{
{"@k", p.Key}, {"@n", p.Name}, {"@c", p.Category}
};
_db.ExecuteNonQuery(
"INSERT OR IGNORE INTO sys_permissions (permission_key, display_name, category) VALUES (@k, @n, @c)",
pms);
var permId = Convert.ToInt32(_db.ExecuteScalar(
"SELECT id FROM sys_permissions WHERE permission_key = @k",
new Dictionary {{"@k", p.Key}}));
var rpm = new Dictionary
{
{"@rid", roleId}, {"@pid", permId}
};
_db.ExecuteNonQuery(
"INSERT OR IGNORE INTO sys_role_permissions (role_id, permission_id) VALUES (@rid, @pid)",
rpm);
}
}
#endregion
#region 用户认证
///
/// 验证用户名密码
///
public (bool Success, int UserId, string Message) ValidateUser(string username, string password)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
return (false, 0, "用户名或密码不能为空");
var dt = _db.ExecuteQuery(
"SELECT id, password_hash, salt, is_enabled, is_locked FROM sys_users WHERE username = @u",
new Dictionary {{"@u", username}});
if (dt.Rows.Count == 0)
return (false, 0, "用户不存在");
var row = dt.Rows[0];
var userId = Convert.ToInt32(row["id"]);
var hash = row["password_hash"].ToString();
var salt = row["salt"].ToString();
var enabled = Convert.ToInt32(row["is_enabled"]) == 1;
var locked = Convert.ToInt32(row["is_locked"]) == 1;
if (!enabled) return (false, userId, "账号已禁用");
if (locked) return (false, userId, "账号已锁定");
var inputHash = HashPassword(password, salt);
if (inputHash != hash)
{
// 失败次数+1
_db.ExecuteNonQuery(
"UPDATE sys_users SET login_failed_count = login_failed_count + 1 WHERE id = @id",
new Dictionary {{"@id", userId}});
return (false, userId, "密码错误");
}
// 登录成功,清失败计数,写登录时间
var up = new Dictionary {{"@id", userId}};
_db.ExecuteNonQuery(
"UPDATE sys_users SET login_failed_count = 0, last_login_at = datetime('now','localtime') WHERE id = @id",
up);
return (true, userId, "登录成功");
}
///
/// 获取用户的所有权限 key
///
public List GetUserPermissions(int userId)
{
var result = new List();
var sql = @"
SELECT DISTINCT p.permission_key
FROM sys_permissions p
INNER JOIN sys_role_permissions rp ON p.id = rp.permission_id
INNER JOIN sys_user_roles ur ON rp.role_id = ur.role_id
WHERE ur.user_id = @uid";
var dt = _db.ExecuteQuery(sql, new Dictionary {{"@uid", userId}});
foreach (DataRow row in dt.Rows)
result.Add(row["permission_key"].ToString());
return result;
}
///
/// 检查用户是否有指定权限
///
public bool HasPermission(int userId, string permissionKey)
{
var sql = @"
SELECT COUNT(*)
FROM sys_permissions p
INNER JOIN sys_role_permissions rp ON p.id = rp.permission_id
INNER JOIN sys_user_roles ur ON rp.role_id = ur.role_id
WHERE ur.user_id = @uid AND p.permission_key = @pk";
var count = Convert.ToInt32(_db.ExecuteQuery(sql,
new Dictionary {{"@uid", userId}, {"@pk", permissionKey}}).Rows[0][0]);
return count > 0;
}
#endregion
#region 审计日志
///
/// 写入审计日志
///
public void WriteAuditLog(string username, string actionType, string actionDetail,
string module = null, string ipAddress = null, bool success = true, string errorMessage = null)
{
try
{
var sql = @"INSERT INTO sys_audit_logs
(username, action_type, action_detail, module, ip_address, result, error_message)
VALUES (@u, @a, @d, @m, @ip, @r, @e)";
var p = new Dictionary
{
{"@u", (object)username ?? DBNull.Value},
{"@a", actionType},
{"@d", (object)actionDetail ?? DBNull.Value},
{"@m", (object)module ?? DBNull.Value},
{"@ip", (object)ipAddress ?? DBNull.Value},
{"@r", success ? 1 : 0},
{"@e", (object)errorMessage ?? DBNull.Value}
};
_db.ExecuteNonQuery(sql, p);
}
catch
{
// 审计日志写入失败不影响主流程
}
}
///
/// 查询审计日志(分页)
///
public (DataTable Rows, int TotalCount) QueryAuditLogs(
string username = null, string actionType = null, string module = null,
DateTime? startTime = null, DateTime? endTime = null,
int pageIndex = 1, int pageSize = 50)
{
var conditions = new List();
var parameters = new Dictionary();
if (!string.IsNullOrWhiteSpace(username))
{
conditions.Add("username LIKE @u");
parameters["@u"] = $"%{username}%";
}
if (!string.IsNullOrWhiteSpace(actionType))
{
conditions.Add("action_type = @a");
parameters["@a"] = actionType;
}
if (!string.IsNullOrWhiteSpace(module))
{
conditions.Add("module = @m");
parameters["@m"] = module;
}
if (startTime.HasValue)
{
conditions.Add("created_at >= @st");
parameters["@st"] = startTime.Value.ToString("yyyy-MM-dd HH:mm:ss");
}
if (endTime.HasValue)
{
conditions.Add("created_at <= @et");
parameters["@et"] = endTime.Value.ToString("yyyy-MM-dd HH:mm:ss");
}
var where = conditions.Count > 0 ? "WHERE " + string.Join(" AND ", conditions) : "";
var totalCount = Convert.ToInt32(
_db.ExecuteScalar($"SELECT COUNT(*) FROM sys_audit_logs {where}", parameters));
var offset = (pageIndex - 1) * pageSize;
var dataSql = $@"
SELECT * FROM sys_audit_logs {where}
ORDER BY id DESC
LIMIT @limit OFFSET @offset";
parameters["@limit"] = pageSize;
parameters["@offset"] = offset;
var dt = _db.ExecuteQuery(dataSql, parameters);
return (dt, totalCount);
}
#endregion
#region 密码工具
private static string GenerateSalt()
{
using (var rng = new RNGCryptoServiceProvider())
{
var bytes = new byte[16];
rng.GetBytes(bytes);
return Convert.ToBase64String(bytes);
}
}
private static string HashPassword(string password, string salt)
{
using (var sha256 = SHA256.Create())
{
var combined = Encoding.UTF8.GetBytes(password + salt);
var hash = sha256.ComputeHash(combined);
var sb = new StringBuilder();
foreach (var b in hash)
sb.Append(b.ToString("x2"));
return sb.ToString();
}
}
#endregion
#region IDisposable
private bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
try { _db?.Dispose(); } catch { }
}
_disposed = true;
}
~SystemDatabase()
{
Dispose(false);
}
#endregion
}
}