SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019 开发企业人事管理系统 — 语法知识点及使用方法详解(21)

SQL Server 2019 开发企业人事管理系统 --- 语法知识点及使用方法详解


📅 当前环境 :SQL Server 2019 + .NET Framework 4.8 / .NET 6+ + Visual Studio 2022

📌 适用对象 :.NET 开发人员、数据库开发工程师、企业系统架构师

🧩 技术栈:C# + WinForms/WPF + ADO.NET + SQL Server 2019


一、需求分析与系统功能结构(非语法,略)


二、构建开发环境

2.1 安装 SQL Server 2019 开发版

✅ 推荐使用 SQL Server 2019 Developer Edition(免费,功能完整)

2.2 创建数据库项目(Visual Studio)

sql 复制代码
-- 在 SQL Server Management Studio (SSMS) 中创建数据库
CREATE DATABASE HRMS2025;
GO

USE HRMS2025;
GO

2.3 配置连接字符串(App.config / appsettings.json)

WinForms(App.config)
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name="HRMSConnectionString" 
         connectionString="Server=localhost;Database=HRMS2025;Integrated Security=true;Encrypt=false;TrustServerCertificate=true;"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
</configuration>
.NET 6+(appsettings.json)
json 复制代码
{
  "ConnectionStrings": {
    "HRMSConnectionString": "Server=localhost;Database=HRMS2025;Integrated Security=true;Encrypt=false;TrustServerCertificate=true;"
  }
}

三、数据库设计

3.1 数据库实体 E-R 图(略,概念设计)

3.2 数据库表设计(核心表结构 + SQL 脚本)

表1:员工档案表(Employees)
sql 复制代码
-- 员工主表
CREATE TABLE dbo.Employees (
    EmployeeID INT IDENTITY(1,1) PRIMARY KEY, -- 员工ID,自增主键
    EmployeeCode NVARCHAR(20) UNIQUE NOT NULL, -- 工号(唯一)
    FullName NVARCHAR(50) NOT NULL, -- 姓名
    Gender CHAR(1) CHECK (Gender IN ('M', 'F')), -- 性别 M/F
    BirthDate DATE, -- 出生日期
    IDCard NVARCHAR(18) UNIQUE, -- 身份证号(唯一)
    DepartmentID INT NOT NULL, -- 部门ID(外键)
    Position NVARCHAR(50), -- 职位
    HireDate DATE NOT NULL, -- 入职日期
    Salary DECIMAL(10,2), -- 月薪
    Phone NVARCHAR(20), -- 电话
    Email NVARCHAR(100), -- 邮箱
    Address NVARCHAR(200), -- 地址
    Photo VARBINARY(MAX), -- 照片(二进制存储)
    Status NVARCHAR(20) DEFAULT '在职' CHECK (Status IN ('在职', '离职', '休假')), -- 状态
    CreatedAt DATETIME2 DEFAULT GETDATE(), -- 创建时间
    UpdatedAt DATETIME2 DEFAULT GETDATE() -- 更新时间
);
GO
表2:部门表(Departments)
sql 复制代码
CREATE TABLE dbo.Departments (
    DepartmentID INT IDENTITY(1,1) PRIMARY KEY,
    DepartmentName NVARCHAR(50) NOT NULL UNIQUE,
    ManagerID INT NULL, -- 部门经理(可为空,自引用)
    Description NVARCHAR(200),
    CreatedAt DATETIME2 DEFAULT GETDATE()
);
GO

-- 添加外键约束(自引用)
ALTER TABLE dbo.Departments
ADD CONSTRAINT FK_Departments_Manager
FOREIGN KEY (ManagerID) REFERENCES dbo.Employees(EmployeeID);
GO

-- 员工表引用部门表
ALTER TABLE dbo.Employees
ADD CONSTRAINT FK_Employees_Department
FOREIGN KEY (DepartmentID) REFERENCES dbo.Departments(DepartmentID);
GO
表3:用户登录表(Users)
sql 复制代码
CREATE TABLE dbo.Users (
    UserID INT IDENTITY(1,1) PRIMARY KEY,
    Username NVARCHAR(50) UNIQUE NOT NULL, -- 登录名
    PasswordHash NVARCHAR(256) NOT NULL, -- 密码哈希(禁止明文)
    FullName NVARCHAR(50), -- 对应员工姓名
    EmployeeID INT NULL, -- 关联员工ID(可选)
    Role NVARCHAR(20) NOT NULL CHECK (Role IN ('Admin', 'HR', 'User')), -- 角色
    IsActive BIT DEFAULT 1, -- 是否启用
    LastLogin DATETIME2 NULL,
    CreatedAt DATETIME2 DEFAULT GETDATE()
);
GO

-- 关联员工(可选)
ALTER TABLE dbo.Users
ADD CONSTRAINT FK_Users_Employee
FOREIGN KEY (EmployeeID) REFERENCES dbo.Employees(EmployeeID);
GO
表4:权限表(Permissions)--- RBAC 模型
sql 复制代码
-- 权限定义表
CREATE TABLE dbo.Permissions (
    PermissionID INT IDENTITY(1,1) PRIMARY KEY,
    PermissionName NVARCHAR(50) NOT NULL UNIQUE, -- 如:ViewEmployee, EditEmployee, BackupDB
    Description NVARCHAR(200)
);
GO

-- 角色权限关联表
CREATE TABLE dbo.RolePermissions (
    Role NVARCHAR(20) NOT NULL, -- 对应 Users.Role
    PermissionID INT NOT NULL,
    PRIMARY KEY (Role, PermissionID),
    FOREIGN KEY (PermissionID) REFERENCES dbo.Permissions(PermissionID)
);
GO

-- 插入基础权限
INSERT INTO dbo.Permissions (PermissionName, Description) VALUES
('ViewEmployee', '查看员工档案'),
('EditEmployee', '编辑员工档案'),
('DeleteEmployee', '删除员工档案'),
('ViewSalary', '查看薪资'),
('EditSalary', '编辑薪资'),
('BackupDatabase', '备份数据库'),
('RestoreDatabase', '还原数据库'),
('ManageUsers', '管理用户'),
('ManagePermissions', '管理权限');
GO

-- 分配角色权限(示例:HR角色)
INSERT INTO dbo.RolePermissions (Role, PermissionID) VALUES
('HR', 1), -- ViewEmployee
('HR', 2), -- EditEmployee
('HR', 3), -- DeleteEmployee
('HR', 4), -- ViewSalary
('HR', 5); -- EditSalary

-- Admin 拥有所有权限
INSERT INTO dbo.RolePermissions (Role, PermissionID)
SELECT 'Admin', PermissionID FROM dbo.Permissions;
GO
表5:日常记事表(Diaries)
sql 复制代码
CREATE TABLE dbo.Diaries (
    DiaryID INT IDENTITY(1,1) PRIMARY KEY,
    UserID INT NOT NULL, -- 创建者
    Title NVARCHAR(100) NOT NULL,
    Content NVARCHAR(MAX),
    ReminderDate DATETIME2 NULL, -- 提醒时间
    IsReminder BIT DEFAULT 0, -- 是否提醒
    IsCompleted BIT DEFAULT 0, -- 是否完成
    CreatedAt DATETIME2 DEFAULT GETDATE(),
    UpdatedAt DATETIME2 DEFAULT GETDATE(),
    FOREIGN KEY (UserID) REFERENCES dbo.Users(UserID)
);
GO

四、开发前的准备工作 --- C# 数据库连接类

4.1 定义数据库连接方法(DbHelper.cs)

csharp 复制代码
using System;
using System.Configuration; // WinForms
// using Microsoft.Extensions.Configuration; // .NET 6+
using System.Data.SqlClient;

public class DbHelper
{
    // 获取连接字符串(WinForms 示例)
    private static string GetConnectionString()
    {
        return ConfigurationManager.ConnectionStrings["HRMSConnectionString"].ConnectionString;
    }

    // 执行非查询语句(INSERT, UPDATE, DELETE)
    public static int ExecuteNonQuery(string sql, SqlParameter[] parameters = null)
    {
        using (SqlConnection conn = new SqlConnection(GetConnectionString()))
        {
            using (SqlCommand cmd = new SqlCommand(sql, conn))
            {
                if (parameters != null)
                    cmd.Parameters.AddRange(parameters);

                conn.Open();
                return cmd.ExecuteNonQuery(); // 返回影响行数
            }
        }
    }

    // 执行查询并返回 SqlDataReader(需手动关闭)
    public static SqlDataReader ExecuteReader(string sql, SqlParameter[] parameters = null)
    {
        SqlConnection conn = new SqlConnection(GetConnectionString());
        SqlCommand cmd = new SqlCommand(sql, conn);
        if (parameters != null)
            cmd.Parameters.AddRange(parameters);

        conn.Open();
        // CommandBehavior.CloseConnection:当Reader关闭时自动关闭连接
        return cmd.ExecuteReader(CommandBehavior.CloseConnection);
    }

    // 执行标量查询(返回单个值)
    public static object ExecuteScalar(string sql, SqlParameter[] parameters = null)
    {
        using (SqlConnection conn = new SqlConnection(GetConnectionString()))
        {
            using (SqlCommand cmd = new SqlCommand(sql, conn))
            {
                if (parameters != null)
                    cmd.Parameters.AddRange(parameters);

                conn.Open();
                return cmd.ExecuteScalar();
            }
        }
    }

    // 执行查询并返回 DataTable(适合数据绑定)
    public static DataTable ExecuteDataTable(string sql, SqlParameter[] parameters = null)
    {
        using (SqlConnection conn = new SqlConnection(GetConnectionString()))
        {
            using (SqlCommand cmd = new SqlCommand(sql, conn))
            {
                if (parameters != null)
                    cmd.Parameters.AddRange(parameters);

                using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
                {
                    DataTable dt = new DataTable();
                    adapter.Fill(dt);
                    return dt;
                }
            }
        }
    }
}

💡 关键点

  • 使用 using 确保连接自动释放。
  • 使用参数化查询防止 SQL 注入。
  • CommandBehavior.CloseConnection 用于 DataReader 场景。

五、用户登录模块

5.1 防止窗口被关闭(WinForms)

csharp 复制代码
// 在 LoginForm.cs 中重写 OnFormClosing
protected override void OnFormClosing(FormClosingEventArgs e)
{
    if (e.CloseReason == CloseReason.UserClosing)
    {
        // 询问是否退出
        var result = MessageBox.Show("确定要退出系统吗?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
        if (result == DialogResult.No)
        {
            e.Cancel = true; // 取消关闭
        }
    }
    base.OnFormClosing(e);
}

5.2 验证用户名和密码(带权限检查)

csharp 复制代码
public class LoginService
{
    // 验证用户并返回用户信息
    public static (bool success, string message, User currentUser) ValidateLogin(string username, string password)
    {
        try
        {
            string sql = @"
                SELECT UserID, Username, FullName, Role, IsActive, EmployeeID 
                FROM dbo.Users 
                WHERE Username = @Username AND PasswordHash = @PasswordHash";

            // 计算密码哈希(推荐 SHA256 + Salt,此处简化)
            string passwordHash = ComputePasswordHash(password);

            SqlParameter[] parameters = {
                new SqlParameter("@Username", username),
                new SqlParameter("@PasswordHash", passwordHash)
            };

            using (SqlDataReader reader = DbHelper.ExecuteReader(sql, parameters))
            {
                if (reader.Read())
                {
                    bool isActive = (bool)reader["IsActive"];
                    if (!isActive)
                        return (false, "账户已被禁用!", null);

                    User user = new User
                    {
                        UserID = (int)reader["UserID"],
                        Username = reader["Username"].ToString(),
                        FullName = reader["FullName"].ToString(),
                        Role = reader["Role"].ToString(),
                        EmployeeID = reader["EmployeeID"] as int? // 可空
                    };

                    // 更新最后登录时间
                    UpdateLastLogin(user.UserID);

                    return (true, "登录成功!", user);
                }
                else
                {
                    return (false, "用户名或密码错误!", null);
                }
            }
        }
        catch (Exception ex)
        {
            return (false, $"登录异常:{ex.Message}", null);
        }
    }

    private static string ComputePasswordHash(string password)
    {
        // 简化版:实际应加 Salt 并使用 SHA256/BCrypt
        using (var sha256 = System.Security.Cryptography.SHA256.Create())
        {
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(password);
            byte[] hash = sha256.ComputeHash(bytes);
            return Convert.ToBase64String(hash);
        }
    }

    private static void UpdateLastLogin(int userId)
    {
        string sql = "UPDATE dbo.Users SET LastLogin = GETDATE() WHERE UserID = @UserID";
        SqlParameter[] parameters = { new SqlParameter("@UserID", userId) };
        DbHelper.ExecuteNonQuery(sql, parameters);
    }
}

// 用户实体类
public class User
{
    public int UserID { get; set; }
    public string Username { get; set; }
    public string FullName { get; set; }
    public string Role { get; set; }
    public int? EmployeeID { get; set; }
}

🔐 安全建议

  • 密码必须哈希存储(加 Salt)。
  • 使用 HTTPS 传输。
  • 登录失败次数限制。
  • Session 管理。

六、人事档案管理模块

6.1 界面开发(WinForms DataGridView 绑定)

csharp 复制代码
// EmployeeForm.cs
public partial class EmployeeForm : Form
{
    public EmployeeForm()
    {
        InitializeComponent();
        LoadEmployees();
    }

    private void LoadEmployees()
    {
        string sql = @"
            SELECT 
                e.EmployeeID,
                e.EmployeeCode,
                e.FullName,
                CASE e.Gender WHEN 'M' THEN '男' ELSE '女' END AS Gender,
                e.BirthDate,
                d.DepartmentName,
                e.Position,
                e.HireDate,
                e.Salary,
                e.Status
            FROM dbo.Employees e
            INNER JOIN dbo.Departments d ON e.DepartmentID = d.DepartmentID
            ORDER BY e.EmployeeID";

        DataTable dt = DbHelper.ExecuteDataTable(sql);
        dataGridView1.DataSource = dt;
        FormatDataGridView();
    }

    private void FormatDataGridView()
    {
        // 设置列标题
        dataGridView1.Columns["EmployeeID"].HeaderText = "ID";
        dataGridView1.Columns["EmployeeCode"].HeaderText = "工号";
        dataGridView1.Columns["FullName"].HeaderText = "姓名";
        dataGridView1.Columns["Gender"].HeaderText = "性别";
        dataGridView1.Columns["BirthDate"].HeaderText = "出生日期";
        dataGridView1.Columns["DepartmentName"].HeaderText = "部门";
        dataGridView1.Columns["Position"].HeaderText = "职位";
        dataGridView1.Columns["HireDate"].HeaderText = "入职日期";
        dataGridView1.Columns["Salary"].HeaderText = "月薪";
        dataGridView1.Columns["Status"].HeaderText = "状态";

        // 设置列宽和格式
        dataGridView1.Columns["Salary"].DefaultCellStyle.Format = "C2"; // 货币格式
        dataGridView1.Columns["BirthDate"].DefaultCellStyle.Format = "yyyy-MM-dd";
        dataGridView1.Columns["HireDate"].DefaultCellStyle.Format = "yyyy-MM-dd";
    }
}

6.2 添加和编辑员工(含照片)

csharp 复制代码
public class EmployeeService
{
    // 添加员工
    public static bool AddEmployee(Employee emp, byte[] photoData = null)
    {
        string sql = @"
            INSERT INTO dbo.Employees (
                EmployeeCode, FullName, Gender, BirthDate, IDCard, 
                DepartmentID, Position, HireDate, Salary, Phone, Email, 
                Address, Photo, Status, CreatedAt, UpdatedAt
            ) VALUES (
                @EmployeeCode, @FullName, @Gender, @BirthDate, @IDCard,
                @DepartmentID, @Position, @HireDate, @Salary, @Phone, @Email,
                @Address, @Photo, @Status, GETDATE(), GETDATE()
            )";

        SqlParameter[] parameters = {
            new SqlParameter("@EmployeeCode", emp.EmployeeCode),
            new SqlParameter("@FullName", emp.FullName),
            new SqlParameter("@Gender", emp.Gender),
            new SqlParameter("@BirthDate", emp.BirthDate ?? (object)DBNull.Value),
            new SqlParameter("@IDCard", emp.IDCard ?? (object)DBNull.Value),
            new SqlParameter("@DepartmentID", emp.DepartmentID),
            new SqlParameter("@Position", emp.Position ?? (object)DBNull.Value),
            new SqlParameter("@HireDate", emp.HireDate),
            new SqlParameter("@Salary", emp.Salary ?? (object)DBNull.Value),
            new SqlParameter("@Phone", emp.Phone ?? (object)DBNull.Value),
            new SqlParameter("@Email", emp.Email ?? (object)DBNull.Value),
            new SqlParameter("@Address", emp.Address ?? (object)DBNull.Value),
            new SqlParameter("@Photo", photoData ?? (object)DBNull.Value), // 照片
            new SqlParameter("@Status", emp.Status ?? "在职")
        };

        try
        {
            int rows = DbHelper.ExecuteNonQuery(sql, parameters);
            return rows > 0;
        }
        catch (Exception ex)
        {
            MessageBox.Show($"添加失败:{ex.Message}");
            return false;
        }
    }

    // 编辑员工
    public static bool UpdateEmployee(Employee emp, byte[] photoData = null)
    {
        string sql = @"
            UPDATE dbo.Employees SET
                EmployeeCode = @EmployeeCode,
                FullName = @FullName,
                Gender = @Gender,
                BirthDate = @BirthDate,
                IDCard = @IDCard,
                DepartmentID = @DepartmentID,
                Position = @Position,
                HireDate = @HireDate,
                Salary = @Salary,
                Phone = @Phone,
                Email = @Email,
                Address = @Address,
                Photo = @Photo, -- 可选更新
                Status = @Status,
                UpdatedAt = GETDATE()
            WHERE EmployeeID = @EmployeeID";

        SqlParameter[] parameters = {
            new SqlParameter("@EmployeeID", emp.EmployeeID),
            new SqlParameter("@EmployeeCode", emp.EmployeeCode),
            new SqlParameter("@FullName", emp.FullName),
            new SqlParameter("@Gender", emp.Gender),
            new SqlParameter("@BirthDate", emp.BirthDate ?? (object)DBNull.Value),
            new SqlParameter("@IDCard", emp.IDCard ?? (object)DBNull.Value),
            new SqlParameter("@DepartmentID", emp.DepartmentID),
            new SqlParameter("@Position", emp.Position ?? (object)DBNull.Value),
            new SqlParameter("@HireDate", emp.HireDate),
            new SqlParameter("@Salary", emp.Salary ?? (object)DBNull.Value),
            new SqlParameter("@Phone", emp.Phone ?? (object)DBNull.Value),
            new SqlParameter("@Email", emp.Email ?? (object)DBNull.Value),
            new SqlParameter("@Address", emp.Address ?? (object)DBNull.Value),
            new SqlParameter("@Photo", photoData != null ? photoData : (object)DBNull.Value),
            new SqlParameter("@Status", emp.Status ?? "在职")
        };

        try
        {
            int rows = DbHelper.ExecuteNonQuery(sql, parameters);
            return rows > 0;
        }
        catch (Exception ex)
        {
            MessageBox.Show($"更新失败:{ex.Message}");
            return false;
        }
    }

    // 根据ID获取员工(含照片)
    public static Employee GetEmployeeById(int employeeId)
    {
        string sql = @"
            SELECT * FROM dbo.Employees WHERE EmployeeID = @EmployeeID";

        SqlParameter[] parameters = { new SqlParameter("@EmployeeID", employeeId) };

        using (SqlDataReader reader = DbHelper.ExecuteReader(sql, parameters))
        {
            if (reader.Read())
            {
                Employee emp = new Employee
                {
                    EmployeeID = (int)reader["EmployeeID"],
                    EmployeeCode = reader["EmployeeCode"].ToString(),
                    FullName = reader["FullName"].ToString(),
                    Gender = reader["Gender"].ToString(),
                    BirthDate = reader["BirthDate"] as DateTime?,
                    IDCard = reader["IDCard"]?.ToString(),
                    DepartmentID = (int)reader["DepartmentID"],
                    Position = reader["Position"]?.ToString(),
                    HireDate = (DateTime)reader["HireDate"],
                    Salary = reader["Salary"] as decimal?,
                    Phone = reader["Phone"]?.ToString(),
                    Email = reader["Email"]?.ToString(),
                    Address = reader["Address"]?.ToString(),
                    Status = reader["Status"].ToString(),
                    Photo = reader["Photo"] as byte[] // 读取照片
                };
                return emp;
            }
            return null;
        }
    }
}

// 员工实体类
public class Employee
{
    public int EmployeeID { get; set; }
    public string EmployeeCode { get; set; }
    public string FullName { get; set; }
    public string Gender { get; set; }
    public DateTime? BirthDate { get; set; }
    public string IDCard { get; set; }
    public int DepartmentID { get; set; }
    public string Position { get; set; }
    public DateTime HireDate { get; set; }
    public decimal? Salary { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
    public string Address { get; set; }
    public byte[] Photo { get; set; } // 照片二进制数据
    public string Status { get; set; }
}
WinForms 照片上传/显示
csharp 复制代码
// 在 EmployeeEditForm.cs 中
private void btnUploadPhoto_Click(object sender, EventArgs e)
{
    using (OpenFileDialog ofd = new OpenFileDialog())
    {
        ofd.Filter = "Image Files|*.jpg;*.jpeg;*.png;*.bmp";
        if (ofd.ShowDialog() == DialogResult.OK)
        {
            // 读取图片为字节数组
            photoData = File.ReadAllBytes(ofd.FileName);
            // 显示预览
            using (MemoryStream ms = new MemoryStream(photoData))
            {
                pictureBox1.Image = Image.FromStream(ms);
            }
        }
    }
}

// 保存时调用 EmployeeService.AddEmployee 或 UpdateEmployee,并传入 photoData

七、用户设置模块

7.1 添加和修改用户信息

csharp 复制代码
public class UserService
{
    public static bool AddUser(User user, string plainPassword)
    {
        string sql = @"
            INSERT INTO dbo.Users (Username, PasswordHash, FullName, EmployeeID, Role, IsActive, CreatedAt)
            VALUES (@Username, @PasswordHash, @FullName, @EmployeeID, @Role, @IsActive, GETDATE())";

        string passwordHash = ComputePasswordHash(plainPassword);

        SqlParameter[] parameters = {
            new SqlParameter("@Username", user.Username),
            new SqlParameter("@PasswordHash", passwordHash),
            new SqlParameter("@FullName", user.FullName ?? (object)DBNull.Value),
            new SqlParameter("@EmployeeID", user.EmployeeID ?? (object)DBNull.Value),
            new SqlParameter("@Role", user.Role),
            new SqlParameter("@IsActive", user.IsActive ? 1 : 0)
        };

        return DbHelper.ExecuteNonQuery(sql, parameters) > 0;
    }

    public static bool UpdateUser(User user, string plainPassword = null)
    {
        string sql;
        List<SqlParameter> parameters = new List<SqlParameter>
        {
            new SqlParameter("@UserID", user.UserID),
            new SqlParameter("@Username", user.Username),
            new SqlParameter("@FullName", user.FullName ?? (object)DBNull.Value),
            new SqlParameter("@EmployeeID", user.EmployeeID ?? (object)DBNull.Value),
            new SqlParameter("@Role", user.Role),
            new SqlParameter("@IsActive", user.IsActive ? 1 : 0)
        };

        if (!string.IsNullOrEmpty(plainPassword))
        {
            string passwordHash = ComputePasswordHash(plainPassword);
            sql = @"
                UPDATE dbo.Users SET
                    Username = @Username,
                    PasswordHash = @PasswordHash,
                    FullName = @FullName,
                    EmployeeID = @EmployeeID,
                    Role = @Role,
                    IsActive = @IsActive
                WHERE UserID = @UserID";
            parameters.Add(new SqlParameter("@PasswordHash", passwordHash));
        }
        else
        {
            sql = @"
                UPDATE dbo.Users SET
                    Username = @Username,
                    FullName = @FullName,
                    EmployeeID = @EmployeeID,
                    Role = @Role,
                    IsActive = @IsActive
                WHERE UserID = @UserID";
        }

        return DbHelper.ExecuteNonQuery(sql, parameters.ToArray()) > 0;
    }

    // 简化密码哈希(生产环境请加 Salt)
    private static string ComputePasswordHash(string password)
    {
        using (var sha256 = System.Security.Cryptography.SHA256.Create())
        {
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(password);
            byte[] hash = sha256.ComputeHash(bytes);
            return Convert.ToBase64String(hash);
        }
    }
}

7.2 设置用户权限(RBAC)

csharp 复制代码
public class PermissionService
{
    // 获取用户所有权限
    public static List<string> GetUserPermissions(string role)
    {
        string sql = @"
            SELECT p.PermissionName
            FROM dbo.RolePermissions rp
            INNER JOIN dbo.Permissions p ON rp.PermissionID = p.PermissionID
            WHERE rp.Role = @Role";

        SqlParameter[] parameters = { new SqlParameter("@Role", role) };
        DataTable dt = DbHelper.ExecuteDataTable(sql, parameters);

        List<string> permissions = new List<string>();
        foreach (DataRow row in dt.Rows)
        {
            permissions.Add(row["PermissionName"].ToString());
        }
        return permissions;
    }

    // 检查用户是否有某权限
    public static bool HasPermission(string role, string permissionName)
    {
        string sql = @"
            SELECT COUNT(1)
            FROM dbo.RolePermissions rp
            INNER JOIN dbo.Permissions p ON rp.PermissionID = p.PermissionID
            WHERE rp.Role = @Role AND p.PermissionName = @PermissionName";

        SqlParameter[] parameters = {
            new SqlParameter("@Role", role),
            new SqlParameter("@PermissionName", permissionName)
        };

        object result = DbHelper.ExecuteScalar(sql, parameters);
        return result != null && (int)result > 0;
    }

    // 在界面中控制按钮权限
    public static void ApplyUserPermissions(User currentUser, Form mainForm)
    {
        List<string> permissions = GetUserPermissions(currentUser.Role);

        // 示例:控制按钮可见性
        var editEmployeeBtn = mainForm.Controls.Find("btnEditEmployee", true).FirstOrDefault() as Button;
        if (editEmployeeBtn != null)
            editEmployeeBtn.Visible = permissions.Contains("EditEmployee");

        var backupBtn = mainForm.Controls.Find("btnBackup", true).FirstOrDefault() as Button;
        if (backupBtn != null)
            backupBtn.Visible = permissions.Contains("BackupDatabase");
    }
}

八、数据库维护模块

8.1 数据库备份功能

csharp 复制代码
public class BackupService
{
    public static bool BackupDatabase(string backupPath, string databaseName = "HRMS2025")
    {
        try
        {
            // 构造备份SQL
            string sql = $@"
                BACKUP DATABASE [{databaseName}] 
                TO DISK = @BackupPath 
                WITH FORMAT, MEDIANAME = 'HRMSBackup', NAME = 'Full Backup of {databaseName}'";

            SqlParameter[] parameters = { new SqlParameter("@BackupPath", backupPath) };

            int rows = DbHelper.ExecuteNonQuery(sql, parameters);
            return true; // BACKUP 无返回行数,成功则无异常
        }
        catch (Exception ex)
        {
            MessageBox.Show($"备份失败:{ex.Message}");
            return false;
        }
    }
}

// 调用示例(WinForms)
private void btnBackup_Click(object sender, EventArgs e)
{
    using (SaveFileDialog sfd = new SaveFileDialog())
    {
        sfd.Filter = "Backup Files|*.bak";
        sfd.FileName = $"HRMS_{DateTime.Now:yyyyMMdd_HHmmss}.bak";
        if (sfd.ShowDialog() == DialogResult.OK)
        {
            bool success = BackupService.BackupDatabase(sfd.FileName);
            if (success)
                MessageBox.Show("备份成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }
}

8.2 数据库还原功能

csharp 复制代码
public class RestoreService
{
    public static bool RestoreDatabase(string backupPath, string databaseName = "HRMS2025")
    {
        try
        {
            // 首先将数据库设置为单用户模式
            string setSingleUser = $@"
                ALTER DATABASE [{databaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE";

            DbHelper.ExecuteNonQuery(setSingleUser);

            // 执行还原
            string restoreSql = $@"
                RESTORE DATABASE [{databaseName}] 
                FROM DISK = @BackupPath 
                WITH REPLACE, RECOVERY";

            SqlParameter[] parameters = { new SqlParameter("@BackupPath", backupPath) };
            DbHelper.ExecuteNonQuery(restoreSql, parameters);

            // 恢复多用户模式
            string setMultiUser = $@"
                ALTER DATABASE [{databaseName}] SET MULTI_USER";

            DbHelper.ExecuteNonQuery(setMultiUser);

            return true;
        }
        catch (Exception ex)
        {
            MessageBox.Show($"还原失败:{ex.Message}");
            // 尝试恢复多用户模式
            try
            {
                string setMultiUser = $@"
                    ALTER DATABASE [{databaseName}] SET MULTI_USER";
                DbHelper.ExecuteNonQuery(setMultiUser);
            }
            catch { }
            return false;
        }
    }
}

// 调用示例
private void btnRestore_Click(object sender, EventArgs e)
{
    using (OpenFileDialog ofd = new OpenFileDialog())
    {
        ofd.Filter = "Backup Files|*.bak";
        if (ofd.ShowDialog() == DialogResult.OK)
        {
            var confirm = MessageBox.Show("还原将覆盖当前数据,是否继续?", "警告", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
            if (confirm == DialogResult.Yes)
            {
                bool success = RestoreService.RestoreDatabase(ofd.FileName);
                if (success)
                {
                    MessageBox.Show("还原成功!请重新登录。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    Application.Restart(); // 重启应用
                }
            }
        }
    }
}

⚠️ 注意

  • 还原前断开所有连接(设为单用户模式)。
  • 还原后恢复多用户模式。
  • 生产环境应在维护窗口操作。

九、系统运行界面示例(代码片段)

9.1 主界面权限控制(MainForm.cs)

csharp 复制代码
public partial class MainForm : Form
{
    private User currentUser;

    public MainForm(User user)
    {
        InitializeComponent();
        this.currentUser = user;
        lblWelcome.Text = $"欢迎,{user.FullName} ({user.Role})";
        ApplyPermissions();
    }

    private void ApplyPermissions()
    {
        // 应用权限控制
        PermissionService.ApplyUserPermissions(currentUser, this);

        // 或手动控制
        if (!PermissionService.HasPermission(currentUser.Role, "ManageUsers"))
        {
            menuUserManagement.Visible = false; // 隐藏菜单
        }
    }

    private void menuEmployeeManage_Click(object sender, EventArgs e)
    {
        if (PermissionService.HasPermission(currentUser.Role, "ViewEmployee"))
        {
            EmployeeForm empForm = new EmployeeForm();
            empForm.MdiParent = this;
            empForm.Show();
        }
        else
        {
            MessageBox.Show("您没有权限访问此模块!", "权限不足", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }
    }
}

9.2 员工信息提醒界面(生日/合同到期)

csharp 复制代码
public partial class ReminderForm : Form
{
    public ReminderForm()
    {
        InitializeComponent();
        LoadReminders();
    }

    private void LoadReminders()
    {
        // 查询本月生日员工
        string sql = @"
            SELECT 
                FullName, 
                FORMAT(BirthDate, 'MM-dd') AS BirthDateStr,
                '生日提醒' AS ReminderType
            FROM dbo.Employees 
            WHERE MONTH(BirthDate) = MONTH(GETDATE()) 
              AND Status = '在职'
            UNION ALL
            -- 可添加合同到期提醒等
            SELECT 
                FullName,
                FORMAT(DATEADD(YEAR, 1, HireDate), 'yyyy-MM-dd') AS ExpireDate,
                '入职周年' AS ReminderType
            FROM dbo.Employees
            WHERE MONTH(HireDate) = MONTH(GETDATE())
              AND DAY(HireDate) = DAY(GETDATE())
              AND Status = '在职'
            ORDER BY ReminderType, FullName";

        DataTable dt = DbHelper.ExecuteDataTable(sql);
        dataGridView1.DataSource = dt;
    }
}

9.3 员工通讯录界面

csharp 复制代码
private void LoadContacts()
{
    string sql = @"
        SELECT 
            e.FullName AS 姓名,
            d.DepartmentName AS 部门,
            e.Position AS 职位,
            e.Phone AS 电话,
            e.Email AS 邮箱
        FROM dbo.Employees e
        INNER JOIN dbo.Departments d ON e.DepartmentID = d.DepartmentID
        WHERE e.Status = '在职'
        ORDER BY d.DepartmentName, e.FullName";

    dataGridViewContacts.DataSource = DbHelper.ExecuteDataTable(sql);
}

9.4 日常记事界面(增删改查)

csharp 复制代码
public partial class DiaryForm : Form
{
    private int currentUserId;

    public DiaryForm(int userId)
    {
        InitializeComponent();
        this.currentUserId = userId;
        LoadDiaries();
    }

    private void LoadDiaries()
    {
        string sql = @"
            SELECT 
                DiaryID,
                Title,
                LEFT(Content, 50) + '...' AS ContentPreview,
                ReminderDate,
                IsReminder,
                IsCompleted,
                CreatedAt
            FROM dbo.Diaries 
            WHERE UserID = @UserID
            ORDER BY IsCompleted ASC, ReminderDate DESC, CreatedAt DESC";

        SqlParameter[] parameters = { new SqlParameter("@UserID", currentUserId) };
        dataGridView1.DataSource = DbHelper.ExecuteDataTable(sql, parameters);
    }

    private void btnAdd_Click(object sender, EventArgs e)
    {
        // 打开编辑窗口
        DiaryEditForm editForm = new DiaryEditForm(currentUserId);
        if (editForm.ShowDialog() == DialogResult.OK)
        {
            LoadDiaries(); // 刷新
        }
    }
}

// DiaryEditForm.cs 保存逻辑
private void btnSave_Click(object sender, EventArgs e)
{
    string sql;
    SqlParameter[] parameters;

    if (diaryId > 0) // 更新
    {
        sql = @"
            UPDATE dbo.Diaries SET
                Title = @Title,
                Content = @Content,
                ReminderDate = @ReminderDate,
                IsReminder = @IsReminder,
                IsCompleted = @IsCompleted,
                UpdatedAt = GETDATE()
            WHERE DiaryID = @DiaryID AND UserID = @UserID";
        parameters = new SqlParameter[]
        {
            new SqlParameter("@DiaryID", diaryId),
            new SqlParameter("@UserID", userId),
            new SqlParameter("@Title", txtTitle.Text),
            new SqlParameter("@Content", txtContent.Text),
            new SqlParameter("@ReminderDate", dtpReminder.Value),
            new SqlParameter("@IsReminder", chkReminder.Checked ? 1 : 0),
            new SqlParameter("@IsCompleted", chkCompleted.Checked ? 1 : 0)
        };
    }
    else // 插入
    {
        sql = @"
            INSERT INTO dbo.Diaries (UserID, Title, Content, ReminderDate, IsReminder, IsCompleted, CreatedAt, UpdatedAt)
            VALUES (@UserID, @Title, @Content, @ReminderDate, @IsReminder, @IsCompleted, GETDATE(), GETDATE())";
        parameters = new SqlParameter[]
        {
            new SqlParameter("@UserID", userId),
            new SqlParameter("@Title", txtTitle.Text),
            new SqlParameter("@Content", txtContent.Text),
            new SqlParameter("@ReminderDate", dtpReminder.Value),
            new SqlParameter("@IsReminder", chkReminder.Checked ? 1 : 0),
            new SqlParameter("@IsCompleted", chkCompleted.Checked ? 1 : 0)
        };
    }

    if (DbHelper.ExecuteNonQuery(sql, parameters) > 0)
    {
        MessageBox.Show("保存成功!");
        this.DialogResult = DialogResult.OK;
        this.Close();
    }
}

十、综合性案例

综合案例1:员工入职流程自动化

csharp 复制代码
public class OnboardingService
{
    // 事务:添加员工 + 创建用户 + 分配默认权限
    public static bool CompleteOnboarding(Employee emp, string loginUsername, string loginPassword, byte[] photo = null)
    {
        using (SqlConnection conn = new SqlConnection(DbHelper.GetConnectionString()))
        {
            conn.Open();
            using (SqlTransaction tran = conn.BeginTransaction())
            {
                try
                {
                    // 1. 插入员工记录
                    string empSql = @"
                        INSERT INTO dbo.Employees (
                            EmployeeCode, FullName, Gender, BirthDate, IDCard, 
                            DepartmentID, Position, HireDate, Salary, Phone, Email, 
                            Address, Photo, Status, CreatedAt, UpdatedAt
                        ) VALUES (
                            @EmployeeCode, @FullName, @Gender, @BirthDate, @IDCard,
                            @DepartmentID, @Position, @HireDate, @Salary, @Phone, @Email,
                            @Address, @Photo, @Status, GETDATE(), GETDATE()
                        );
                        SELECT SCOPE_IDENTITY();"; // 返回新员工ID

                    SqlCommand empCmd = new SqlCommand(empSql, conn, tran);
                    // ... 设置参数(略)
                    object newEmpIdObj = empCmd.ExecuteScalar();
                    if (newEmpIdObj == null) throw new Exception("员工插入失败");
                    int newEmpId = Convert.ToInt32(newEmpIdObj);

                    // 2. 创建用户
                    string userSql = @"
                        INSERT INTO dbo.Users (Username, PasswordHash, FullName, EmployeeID, Role, IsActive, CreatedAt)
                        VALUES (@Username, @PasswordHash, @FullName, @EmployeeID, @Role, 1, GETDATE())";

                    string passwordHash = ComputePasswordHash(loginPassword);
                    SqlCommand userCmd = new SqlCommand(userSql, conn, tran);
                    userCmd.Parameters.AddRange(new SqlParameter[]
                    {
                        new SqlParameter("@Username", loginUsername),
                        new SqlParameter("@PasswordHash", passwordHash),
                        new SqlParameter("@FullName", emp.FullName),
                        new SqlParameter("@EmployeeID", newEmpId),
                        new SqlParameter("@Role", "User") // 默认角色
                    });
                    int userRows = userCmd.ExecuteNonQuery();
                    if (userRows == 0) throw new Exception("用户创建失败");

                    // 3. 记录入职日志(可选)
                    string logSql = "INSERT INTO dbo.SystemLogs (Action, Description, CreatedBy) VALUES (@Action, @Desc, @User)";
                    SqlCommand logCmd = new SqlCommand(logSql, conn, tran);
                    logCmd.Parameters.AddRange(new SqlParameter[]
                    {
                        new SqlParameter("@Action", "Onboarding"),
                        new SqlParameter("@Desc", $"员工 {emp.FullName} 入职,工号 {emp.EmployeeCode}"),
                        new SqlParameter("@User", "System")
                    });
                    logCmd.ExecuteNonQuery();

                    tran.Commit();
                    return true;
                }
                catch (Exception ex)
                {
                    tran.Rollback();
                    throw new Exception($"入职流程失败:{ex.Message}");
                }
            }
        }
    }

    private static string ComputePasswordHash(string password)
    {
        using (var sha256 = System.Security.Cryptography.SHA256.Create())
        {
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(password);
            byte[] hash = sha256.ComputeHash(bytes);
            return Convert.ToBase64String(hash);
        }
    }
}

事务保证:员工、用户、日志要么全部成功,要么全部回滚。


综合案例2:基于角色的动态菜单生成

csharp 复制代码
public partial class MainForm : Form
{
    private User currentUser;

    public MainForm(User user)
    {
        InitializeComponent();
        this.currentUser = user;
        InitializeDynamicMenu();
    }

    private void InitializeDynamicMenu()
    {
        // 清空现有菜单(除"系统"外)
        for (int i = menuStrip1.Items.Count - 1; i >= 0; i--)
        {
            if (menuStrip1.Items[i].Text != "系统")
                menuStrip1.Items.RemoveAt(i);
        }

        // 获取用户权限
        List<string> permissions = PermissionService.GetUserPermissions(currentUser.Role);

        // 定义菜单结构
        var menuMap = new Dictionary<string, List<(string text, string permission, EventHandler handler)>>
        {
            ["人事管理"] = new List<(string, string, EventHandler)>
            {
                ("员工档案", "ViewEmployee", menuEmployeeManage_Click),
                ("部门管理", "ViewDepartment", menuDeptManage_Click),
                ("薪资管理", "ViewSalary", menuSalaryManage_Click)
            },
            ["系统管理"] = new List<(string, string, EventHandler)>
            {
                ("用户管理", "ManageUsers", menuUserManage_Click),
                ("权限设置", "ManagePermissions", menuPermission_Click),
                ("数据备份", "BackupDatabase", menuBackup_Click),
                ("数据还原", "RestoreDatabase", menuRestore_Click)
            }
        };

        // 动态添加菜单
        foreach (var menuGroup in menuMap)
        {
            ToolStripMenuItem parentMenu = new ToolStripMenuItem(menuGroup.Key);
            foreach (var item in menuGroup.Value)
            {
                if (permissions.Contains(item.permission))
                {
                    ToolStripMenuItem menuItem = new ToolStripMenuItem(item.text);
                    menuItem.Click += item.handler;
                    parentMenu.DropDownItems.Add(menuItem);
                }
            }
            if (parentMenu.DropDownItems.Count > 0)
            {
                menuStrip1.Items.Add(parentMenu);
            }
        }
    }

    // 事件处理方法(略)
    private void menuEmployeeManage_Click(object sender, EventArgs e) { /* 打开员工管理 */ }
    private void menuBackup_Click(object sender, EventArgs e) { /* 打开备份界面 */ }
    // ...
}

🌟 优势

  • 权限变更无需修改代码。
  • 自动隐藏无权限菜单项。
  • 提升用户体验和安全性。

项目总结

技术亮点:

  1. 安全架构

    • 密码哈希存储。
    • RBAC 权限控制。
    • 参数化查询防注入。
  2. 智能数据库利用

    • 使用 SQL Server 2019 的智能优化(自动内存反馈等)。
    • 高效索引设计(部门、工号、状态等字段)。
  3. 模块化设计

    • 分层架构(UI、Service、DAL)。
    • 可复用的 DbHelper 类。
  4. 用户体验

    • 动态权限菜单。
    • 照片上传与显示。
    • 数据备份还原一键操作。
  5. 健壮性

    • 事务处理关键流程。
    • 异常捕获与友好提示。
    • 数据验证。

部署建议

  • 数据库:SQL Server 2019 Standard/Enterprise。
  • 应用服务器:Windows Server + .NET Framework 4.8。
  • 客户端:Win10/Win11 + .NET 运行时。
  • 备份策略:每日全备 + 事务日志备份。

🚀 扩展方向

  • Web 版(ASP.NET Core + Blazor)。
  • 移动端(Xamarin / MAUI)。
  • 集成 AD 域登录。
  • 工作流引擎(请假、审批)。

本系统完整覆盖企业人事管理核心需求,代码结构清晰、安全可靠、易于维护,是学习 SQL Server 2019 与 C# 企业级开发的优秀范例。

相关推荐
YIN_尹2 小时前
【MySQL】SQL里的“套娃”与“拼图”:子查询和合并查询
数据库·sql·mysql
秋氘渔2 小时前
MySQL EXPLAIN命令详解:SQL查询性能分析与优化指南(基础篇)
sql·mysql·adb
cqbzcsq2 小时前
MC Forge1.20.1 mod开发学习笔记(数据生成、食物)
java·笔记·学习·mc
浪子不回头4152 小时前
Triton学习笔记
笔记·学习
我的xiaodoujiao2 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 51--CI/CD 4--推送本地代码到Git远程仓库
python·学习·测试工具·ci/cd·pytest
babe小鑫2 小时前
大专政务大数据应用专业学习数据分析的价值分析
大数据·学习·政务
kyle~2 小时前
MySQL基础知识点与常用SQL语句整理
android·sql·mysql
青衫码上行2 小时前
高频SQL 50题 | 聚合
数据库·sql·mysql·leetcode·面试
有点心急10212 小时前
SQL 执行 MCP 工具开发(二)
数据库·sql