在软件开发过程中,登录功能、注册功能以及对应的不同账号的操作权限十分常见。本文讨论.NET平台C#语言如何开发窗体界面的登录注册功能。
具体思路为,在本地创建数据库文件保存用户名与密码信息,软件中的LoginForml类查询数据库登录,Regist类写入数据库注册。登录后跨窗体改变控件的使能(Enabled)等属性,实现权限控制。
涉及到的重点知识包括:
1. SQLite数据库文件读写
2. 跨窗体控件操作(子窗体控制夫窗体+父窗体控制子窗体)
3. 编码加密的思维
目录
零、预备知识: SQLite与数据库文件.db
1. 环境搭建:
要在.NET平台使用SQLite数据库,需要右键解决方案,选择NuGet包管理,搜索System.Data.SQLite,安装。在代码前加using System.Data.SQLite; 即可调用。
2. 文件准备:
本文不涉及创建数据库的操作,只涉及数据库的读写,想知道更完整的数据库操作命令,请期待本人其他文章(写完了会放链接)。因为登录与注册操作文件格式固定的特点,读者可以在本地创建一个文件,后缀修改为.db,例如:login,db。本文后面的数据库操作就用login.db指代数据库文件名。
想直观地查看和修改数据库文件,可以安装轻量级数据库小程序SQLIteExpertPro,也可以在Visual Studio Code装SQL插件。
SQLiteExpertPro的下载链接如下
链接:https://pan.baidu.com/s/18stalFuKbXISRfvpaVLKSw?pwd=HFSS
提取码:HFSS
编辑login.db,添加若干个table(表),账户分几级就创建多少表。这里创建三个表单,分别命名为admin(管理员)、monitor(巡检员)、worker(工人)。给每个表单添加两个TEXT列,一个命名为用户名,第二列命名为密码。
至此,文件准备好了。
3. SQLite读写命令
3.1 打开.db文件
//等号后为数据库文件的地址
string conStr = "data source= /bin/Debug/login.db;version=3;";
using (SQLiteConnection connect = new SQLiteConnection(conStr))
{
//打开文件
connect.Open();
//读写操作
//...
//读写操作结束
//关闭文件
connect.Close();
}
3.2 读.db文件(SELECT)
public void Read()
{
//等号后为数据库文件的地址
string conStr = "data source= "+FilePath+“;version=3;";
using (SQLiteConnection connect = new SQLiteConnection(conStr))
{
//打开文件
connect.Open();
//读操作
using (SQLiteCommand fmd = connect.CreateCommand())
{
//table与name自行替换成具体的表名与元素名
//从table中选中用户名=name的行,只返回选中行的用户名项与密码项
//name两端一定要加引号,否则会报错SQLite Logic Error
fmd.CommandText = "SELECT 用户名,密码 FROM "+table+" WHERE 用户名=" +'"'+ name+'"';
fmd.ExecuteScalar();
using (SQLiteDataReader reader = fmd.ExecuteReader())
{
//一行行读取
while (reader.Read())
{
//读取当前行
//获取密码
//第0列是用户名,第一列是密码,都是字符串TEXT格式
string s = (string)reader["密码”];
//也可以写成string s = (string)reader[1];
}
}
//读写操作结束
//关闭文件
}
}
}
3.3 写.db文件
//打开.db文件
//FilePath是文件地址
string conStr = "data source=" + FilePath + ";version=3;";
using (SQLiteConnection connect = new SQLiteConnection(conStr))
{
//打开文件
connect.Open();
using (SQLiteCommand fmd = connect.CreateCommand())
{
SQLiteCommand cmd = new SQLiteCommand();
//table替换为表名,name替换为元素名
cmd.CommandText = "INSERT INTO "+table+" VALUES(" + "\"" + name + "\"" + "," + "\"" + pw + "\"" + ")";
cmd.ExecuteNonQuery();
connect.Close();
}
}
一、SQLite 注册操作
在注册窗体上创建文本框控件textBox_name与textBox_password, 用于输入用户名与密码字符串。新建一个Hash类,添加public static string HashEncoding(string s)方法,加密原始密码后再存入数据库,提升安全性能。在后续的登录环节还会用到这个HashEncoding方法,把输入的密码用同样的逻辑加密,比照数据库对应内容,判断密码是否正确。
private void Regist()
{
//表名
string table = "admin";
//若输入框为空,报错
if (textBox_name.Text == "" || textBox_password.Text == "")
{
MessageBox.Show("用户名或密码不能为空");
return;
}
//打开数据库的命令
string conStr = "data source=" + GetFilePath() + ";version=3;";
using (SQLiteConnection connect = new SQLiteConnection(conStr))
{
connect.Open();
int count=0;
using (SQLiteCommand fmd = connect.CreateCommand())
{
fmd.CommandText =SearchName(textBox_name.Text.Trim(),table);
fmd.ExecuteScalar();
using (SQLiteDataReader reader = fmd.ExecuteReader())
{
while (reader.Read())
{
count++;
}
}
connect.Close();
}
if(count > 0)
{
label_info.Text= "用户名已存在,请直接登录";
return;
}
}
//Hash类有public static方法HashEncoding(string s)对密码进行加密
string codedCode = Hash.HashEncoding(textBox_password.Text.Trim());
Console.WriteLine(codedCode);
using (SQLiteConnection connect = new SQLiteConnection(conStr))
{
SQLiteCommand cmd = new SQLiteCommand();
cmd.Connection = connect;
connect.Open();
cmd.CommandText = Insert(textBox_name.Text.Trim(), codedCode,table);
try
{
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
connect.Close();
}
label_info.Text = "注册成功";
}
private string Insert(string name,string pw,string table)
{
return string.Format("INSERT INTO "+table+" VALUES(" + "\"" + name + "\"" + "," + "\"" + pw + "\"" + ")");
}
private static string GetFilePath()
{
string path = System.IO.Directory.GetCurrentDirectory();
return System.IO.Path.Combine(path, "login.db");
}
private string SearchName(string name,string table)
{
return "SELECT 用户名,密码 FROM "+table+" WHERE 用户名=" + '"'+name+'"';
}
二、SQLite登录操作
//若成功登录,返回true,否则返回false
private bool Succeeded(string name, string password, string table)
{
string truecode="";
string conStr = "data source=" + GetFilePath() + ";version=3;";
using (SQLiteConnection connect = new SQLiteConnection(conStr))
{
connect.Open();
int count = 0;
using (SQLiteCommand fmd = connect.CreateCommand())
{
fmd.CommandText = SearchName(name,table);
fmd.ExecuteScalar();
using (SQLiteDataReader reader = fmd.ExecuteReader())
{
while (reader.Read())
{
truecode = (string)reader["密码"];
count++;
}
}
connect.Close();
}
if (count==0)
{
MessageBox.Show("用户名不存在");
return false;
}
else
{
if(Hash.HashEncoding(password)==truecode)
{
if (table == "admin")
{
//管理员权限操作
}
else if(table == "monitor")
{
//巡检员权限操作
}
else if(table == "worker")
{
//操作工权限操作
}
MessageBox.Show("登录成功");
}
else
{
MessageBox.Show("密码错误");
}
}
}
return false;
}
private static string GetFilePath()
{
string path = System.IO.Directory.GetCurrentDirectory();
return System.IO.Path.Combine(path, "login.db");
}
private string SearchName(string name, string table)
{
return "SELECT 用户名,密码 FROM "+table+" WHERE 用户名=" +'"'+ name+'"';
}
三、跨窗体控件控制
1. 子窗体控制父窗体
这里用一个定义在Owner窗体类的public static f1变量实现子窗体对Owner窗体的控制。父窗体类命名为Form1,子窗体类为LoginForm。
父窗体内代码
public partial class Form1 : Form
{
public static Form1 f1;
private LoginForm lf = new LoginForm();
pubulic Form1()
{
f1 = this;
lf.Owner = this;
}
}
子窗体内代码
public partial class LoginForm : Form
{
Form1 f1;
public bool IsAdmin;
public LoginForm()
{
InitializeComponent();
f1= (Form1)this.Owner;
}
private void Action()
{
//选择任意Form1中的修饰符为public的控件或变量进行修改
Form1.f1.button1.Enabled = true;
}
}
值得指出的是,拖拽生成的控件默认访问修饰符为private,如果需要跨窗体访问,需要将修饰符改为public。修改方式是,选中控件,右键属性,找到Modifier栏。 如果控件由代码生成,在代码中修改访问修饰符即可。
2. 父窗体修改子窗体的方法1
方法1类似于子窗体修改父窗体的方法,就是用static变量。static修饰符已经修改,该类的所有实例的对应值都会修改。这样可以避免多次创建实例后变量值不一致。
3. 父窗体修改子窗体的方法2(初始化参数)
方法2的大致思路是,给子类的初始化添加形参,实例化子类的变量由父窗体提供。
子窗体内代码
public Regist(bool IsAdmin)
{
InitializeComponent();
if (IsAdmin)
{
//相应操作
}
else
{
//相应操作
}
}
父窗体内代码
Regist rg = new Regist(IsAdmin);
rg.ShowDialog();