官方网站

未来计划

密码管理器说到底是一个功能有限的小软件,最初也只是作为我学习Winform的一个小作品。一来一旦功能基本完善,就没有很多地方值得更新;二来由于我的技术水平目前还很有限,前期写的代码很多结构不是非常规范,对于大版本的更新也不不是很方便的事情,甚至不如推倒重来。所以接下来没有大的功能性问题不再更新,更新频率不定(可以通过检查更新按钮来查看)。其实我想更新的功能还有很多,比如说:
1.更兼容的数据导入功能
2.开机引导功能
3.免责声明和作者寄语提示
4.更好看的UI甚至可以换肤
5.支持选择同步坚果云WebDav
6.高级用户允许选择使用自己购买的数据库
7.非对称加密
8.标准的应用打包方式
9.增加反馈区
10.自动下载覆盖更新的功能 11.支持通过邮箱找回密码的功能等等。
但是我毕竟不是我的主要学习方向,所以这些都以后再说吧,说不定哪天一时兴起就更新了,所以咱们有缘再见。

主要特性及功能(截至2020年09月27日)

  • 1.注册用户功能及登陆功能
  • 2.平台账号密码等信息添加到云端数据库功能
  • 3.修改或者删除或者搜索已添加数据的功能
  • 4.生成随机密码、快捷复制、打开网址功能、记住密码、自动登录等懒人功能
  • 5.禁止后台多开的特性
  • 6.数据加密存储的特性
  • 7.支持检查更新的特性
  • 8.软件临时锁定的功能
  • 9.数据支持导出到本地以及从本地导入到云端
  • 10.拥有软件官网(包含一些常见问题说明,功能指南等)

快速使用视频预览

视频地址如下:
https://www.bilibili.com/video/BV1oT4y1c7qT/

代码记录

防止软件多开

在Program.cs文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void Main()
{
bool isAppRunning = false;
Mutex mutex = new Mutex(true, System.Diagnostics.Process.GetCurrentProcess().ProcessName, out isAppRunning);
if (!isAppRunning)
{
MessageBox.Show("程序已运行,不能再次打开!");
Environment.Exit(0);
}
GC.KeepAlive(mutex);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainWindow());
}
在打开主窗体前打开注册登录窗体

在Program.cs文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
LoginWindow loginWindow = new LoginWindow();
DialogResult loginResult = loginWindow.ShowDialog();
if(loginResult==DialogResult.OK)
{
Application.Run(new MainWindow());
}
else
{
Application.Exit();
}
}
生成随机密码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static string GenerateNoncePwd(int length)
{
char[] chars = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z',
'`', '~', '!', '@', '#', '$', '%', '^', '&', '*',
'(', ')', '_', '+', '=', '<', '>', '?', ':', '"',
'{', '}', '[', ']', ',', '.', '\\', '/', ';', '\'',
};
string result = "";
Random rnd = new Random(Guid.NewGuid().GetHashCode());
for (int i = 0; i < length; i++)
{
result += chars[rnd.Next(chars.Length)];
}
return result;
}
Base64加密解密

可以用于简单加密字符串(比如说邮箱地址、数据库账号等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
static string encryptKey = "abcd";//字符串加密密钥(注意:密钥只能是4位)
public static string Encrypt(string str)
{//加密字符串

try
{
byte[] key = Encoding.Unicode.GetBytes(encryptKey);//密钥
byte[] data = Encoding.Unicode.GetBytes(str);//待加密字符串

DESCryptoServiceProvider descsp = new DESCryptoServiceProvider();//加密、解密对象
MemoryStream MStream = new MemoryStream();//内存流对象

//用内存流实例化加密流对象
CryptoStream CStream = new CryptoStream(MStream, descsp.CreateEncryptor(key, key), CryptoStreamMode.Write);
CStream.Write(data, 0, data.Length);//向加密流中写入数据
CStream.FlushFinalBlock();//将数据压入基础流
byte[] temp = MStream.ToArray();//从内存流中获取字节序列
CStream.Close();//关闭加密流
MStream.Close();//关闭内存流

return Convert.ToBase64String(temp);//返回加密后的字符串
}
catch
{
return str;
}
}


public static string Decrypt(string str)
{//解密字符串

try
{
byte[] key = Encoding.Unicode.GetBytes(encryptKey);//密钥
byte[] data = Convert.FromBase64String(str);//待解密字符串

DESCryptoServiceProvider descsp = new DESCryptoServiceProvider();//加密、解密对象
MemoryStream MStream = new MemoryStream();//内存流对象

//用内存流实例化解密流对象
CryptoStream CStream = new CryptoStream(MStream, descsp.CreateDecryptor(key, key), CryptoStreamMode.Write);
CStream.Write(data, 0, data.Length);//向加密流中写入数据
CStream.FlushFinalBlock();//将数据压入基础流
byte[] temp = MStream.ToArray();//从内存流中获取字节序列
CStream.Close();//关闭加密流
MStream.Close();//关闭内存流
return Encoding.Unicode.GetString(temp);//返回解密后的字符串
}
catch
{
return str;
}
}
利用正则表达式设置规则

判断是否为密码格式

1
2
3
4
5
6
7
8
9
10
11
12
string api_password = textBox1.Text;
//输入的密码格式要求
Regex regex_password = new Regex(@"^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]{8,19}$");
//判断密码格式是否符合要求
if (regex_password.IsMatch(api_password))
{
MessageBox.Show("满足格式要求");
}
else
{
MessageBox.Show("不满足格式要求");
}

判断是否为链接格式

1
2
3
4
public static bool IsUrl(string url)
{
return Regex.IsMatch(url, @"^(((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&%_\./-~-]*)?$",RegexOptions.IgnoreCase);
}
注册功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
string conStr="server=xxx;user id=xxx;password=xxx;port=xxx;database=xxx";
MySqlConnection con = new MySqlConnection(conStr);//连接数据库
con.Open();//打开数据库
MySqlCommand checkCmd = con.CreateCommand();//创建SQL命令执行对象
string sql_command = "Select Api_User from Software where Api_User='" + api_user + "'";
checkCmd.CommandText = sql_command;
MySqlDataAdapter check = new MySqlDataAdapter();//实例化数据适配器
check.SelectCommand = checkCmd;//让适配器执行SELECT命令
DataSet checkData = new DataSet();//实例化结果数据集
int n = check.Fill(checkData, "Api_User");
if (n != 0)
{
MessageBox.Show("用户名已存在!");
}
else
{
//SQL数据插入命令
string sql_cmd = "insert into Software(Api_User,Api_Password) values ('" + api_user + "','" + api_password + "')";
//初始化命令
MySqlCommand mycon = new MySqlCommand(sql_cmd, con);
mycon.ExecuteNonQuery();//执行语句
con.Close();//断开数据库连接
DialogResult dlgResult = MessageBox.Show("恭喜您注册成功");
if(dlgResult == DialogResult.OK)
{
this.Close();
}
}

补充:如果要多次使用数据库连接字符串,可以存储在配置文件中。

在App.config文件中

1
2
3
<connectionStrings>
<add name="conStr" connectionString="server=xxx;user id=xxx;password=xxx;port=xxx;database=xxx" />
</connectionStrings>

在其它文件中获取配置文件中的连接字符串

1
string conStr=  ConfigurationManager.ConnectionStrings["连接字符串名称"].ConnectionString;
记住密码功能

在App.config文件中

1
2
3
4
<appSettings>
<add key="remember_password" value="false" />
<add key="password" value="" />
</appSettings>

在勾选记住密码checkbox函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Configuration cfa = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked)
{
cfa.AppSettings.Settings["remember_password"].Value = "true";
cfa.AppSettings.Settings["password"].Value = textBox2.Text;
cfa.Save();
}
else
{
cfa.AppSettings.Settings["remember_password"].Value = "false";
cfa.AppSettings.Settings["password"].Value = "";
cfa.Save();
}
}

在窗体载入事件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void LoginWindow_Load(object sender, EventArgs e)
{
if (ConfigurationManager.AppSettings["remember_password"].Equals("true"))
{
//这边的textBox2用来填充密码的
textBox2.Text = ConfigurationManager.AppSettings["password"];
//这边的checkBox1记住密码的勾选框
checkBox1.Checked = true;
}
else
{
textBox2.Text = "";
checkBox1.Checked = false;
}
}
检查更新功能

在云端update.xml文件中

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8" ?>
<Update>
<Soft Name="密码管理器">
<Verson>3.0.0.0</Verson>
<DownLoad>https://www.aye.ink/PasswordManager/publish.zip</DownLoad>
</Soft>
</Update>

在检查更新按钮点击事件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const string updateUrl = "https://www.aye.ink/PasswordManager/update.xml";
string newVerson = "1.0.0.0", download = "https://www.aye.ink/PasswordManager/publish.zip";
try
{
WebClient wc = new WebClient();
Stream stream = wc.OpenRead(updateUrl);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(stream);
XmlNode list = xmlDoc.SelectSingleNode("Update");
foreach (XmlNode node in list)
{
if (node.Name == "Soft" && node.Attributes["Name"].Value.ToString() == "密码管理器")
{
foreach (XmlNode xml in node)
{
if (xml.Name == "Verson")
newVerson = xml.InnerText;//拿到最新版本号
else if (xml.Name == "Download")
download = xml.InnerText;//拿到下载地址
}
}
}

Version ver = new Version(newVerson);
Version verson = new Version(Assembly.GetExecutingAssembly().GetName().Version.ToString());
int tm = verson.CompareTo(ver); //版本号比较
if (tm >= 0)
MessageBox.Show("当前已经为最新版本");
else
{
DialogResult result = MessageBox.Show("有新版本呢可以更新,是否更新?", "提示",
MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
if (result == DialogResult.OK)
{
Process.Start(download);
}
else
{
return;
}
}
}
catch (Exception)
{
throw new Exception("更新出现错误,请确认网络连接无误后重试!");
}
数据导出功能

将数据从datagridview中导出到电脑本地保存为csv格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public bool dataGridViewToCSV(DataGridView dataGridView)
{
if (dataGridView.Rows.Count == 0)
{
MessageBox.Show("没有数据可导出!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return false;
}
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "CSV files (*.csv)|*.csv";
saveFileDialog.FilterIndex = 0;
saveFileDialog.RestoreDirectory = true;
saveFileDialog.CreatePrompt = true;
saveFileDialog.FileName = "PasswordData";
saveFileDialog.Title = "保存";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
Stream stream = saveFileDialog.OpenFile();
StreamWriter sw = new StreamWriter(stream, System.Text.Encoding.GetEncoding(-0));
string strLine = "";
try
{
//表头
for (int i = 0; i < dataGridView.ColumnCount; i++)
{
if (i > 0)
strLine += ",";
strLine += dataGridView.Columns[i].HeaderText;
}
strLine.Remove(strLine.Length - 1);
sw.WriteLine(strLine);
strLine = "";
//表的内容
for (int j = 0; j < dataGridView.Rows.Count; j++)
{
strLine = "";
int colCount = dataGridView.Columns.Count;
for (int k = 0; k < colCount; k++)
{
if (k > 0 && k < colCount)
strLine += ",";
if (dataGridView.Rows[j].Cells[k].Value == null)
strLine += "";
else
{
string cell = dataGridView.Rows[j].Cells[k].Value.ToString().Trim();
//防止里面含有特殊符号
cell = cell.Replace("\"", "\"\"");
cell = "\"" + cell + "\"";
strLine += cell;
}
}
sw.WriteLine(strLine);
}
sw.Close();
stream.Close();
MessageBox.Show("数据被导出到:" + saveFileDialog.FileName.ToString(), "导出完毕", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "导出错误", MessageBoxButtons.OK, MessageBoxIcon.Information);
return false;
}
}
return true;
}
窗体间相互传值

C#中WinForm窗体间相互传值的几种方法

C#Winform+MySQL无服务器聊天项目

值得学习的地方主要有:1.smtp邮件发送(这样就可以完成找回密码的功能) 2.后台数据刷新及传递(winform中数据库的一些操作)3.里面两个封装好的类(一个是数据库相关的类;一个是邮件相关的类)非常好用

C#和MySQL实现实时通讯