From ce677db88b7e63d328430676f039778ec6be0f97 Mon Sep 17 00:00:00 2001 From: liushuming Date: Fri, 27 Feb 2026 08:52:30 +0800 Subject: [PATCH] first commit --- README.md | 38 ++++++++++++ client/Reader/MainForm.Designer.cs | 76 +++++++++++++++++++++++ client/Reader/MainForm.cs | 98 ++++++++++++++++++++++++++++++ client/Reader/Program.cs | 17 ++++++ client/Reader/Reader.csproj | 9 +++ client/Writer/MainForm.Designer.cs | 95 +++++++++++++++++++++++++++++ client/Writer/MainForm.cs | 72 ++++++++++++++++++++++ client/Writer/Program.cs | 17 ++++++ client/Writer/Writer.csproj | 9 +++ document.txt | 11 ++++ server/app.py | 66 ++++++++++++++++++++ server/db_init.py | 23 +++++++ server/requirements.txt | 2 + 13 files changed, 533 insertions(+) create mode 100644 README.md create mode 100644 client/Reader/MainForm.Designer.cs create mode 100644 client/Reader/MainForm.cs create mode 100644 client/Reader/Program.cs create mode 100644 client/Reader/Reader.csproj create mode 100644 client/Writer/MainForm.Designer.cs create mode 100644 client/Writer/MainForm.cs create mode 100644 client/Writer/Program.cs create mode 100644 client/Writer/Writer.csproj create mode 100644 document.txt create mode 100644 server/app.py create mode 100644 server/db_init.py create mode 100644 server/requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb52e00 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Hiddencode Project + +This workspace contains a minimal implementation matching the provided specification: + +- `server`: Flask server + SQLite database that stores SHA and two codes (upload/download). +- `client/Writer`: C# WinForms app to write codes for a target file. +- `client/Reader`: C# WinForms app to scan a folder and read codes for files. + +Quick start (macOS / Ubuntu for server): + +1. Server + +```bash +cd server +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +python db_init.py +python app.py +``` + +Server listens on `http://0.0.0.0:5000` by default. + +2. Clients (Windows recommended, requires .NET 6+) + +Open `client/Writer/Writer.csproj` and `client/Reader/Reader.csproj` in Visual Studio or run: + +```powershell +cd client/Writer +dotnet run + +cd ../Reader +dotnet run +``` + +Notes: +- Server base URL is `http://localhost:5000` in the client code; change if needed. +- The server stores entries in SQLite file `server/hiddencode.db` with fields: id, sha, upload_code, download_code, created_at. diff --git a/client/Reader/MainForm.Designer.cs b/client/Reader/MainForm.Designer.cs new file mode 100644 index 0000000..7905a8d --- /dev/null +++ b/client/Reader/MainForm.Designer.cs @@ -0,0 +1,76 @@ +namespace Reader +{ + partial class MainForm + { + private System.ComponentModel.IContainer components = null; + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.txtFolder = new System.Windows.Forms.TextBox(); + this.btnBrowse = new System.Windows.Forms.Button(); + this.btnRead = new System.Windows.Forms.Button(); + this.dgv = new System.Windows.Forms.DataGridView(); + ((System.ComponentModel.ISupportInitialize)(this.dgv)).BeginInit(); + this.SuspendLayout(); + // txtFolder + this.txtFolder.Location = new System.Drawing.Point(12, 30); + this.txtFolder.Name = "txtFolder"; + this.txtFolder.Size = new System.Drawing.Size(360, 23); + // btnBrowse + this.btnBrowse.Location = new System.Drawing.Point(378, 28); + this.btnBrowse.Name = "btnBrowse"; + this.btnBrowse.Size = new System.Drawing.Size(75, 25); + this.btnBrowse.Text = "浏览文件夹"; + this.btnBrowse.UseVisualStyleBackColor = true; + this.btnBrowse.Click += new System.EventHandler(this.btnBrowse_Click); + // btnRead + this.btnRead.Location = new System.Drawing.Point(378, 170); + this.btnRead.Name = "btnRead"; + this.btnRead.Size = new System.Drawing.Size(75, 27); + this.btnRead.Text = "开始读取"; + this.btnRead.UseVisualStyleBackColor = true; + this.btnRead.Click += new System.EventHandler(this.btnRead_Click); + // dgv + this.dgv.AllowUserToAddRows = false; + this.dgv.AllowUserToDeleteRows = false; + this.dgv.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgv.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + new System.Windows.Forms.DataGridViewTextBoxColumn() { Name = "colFile", HeaderText = "文件名" }, + new System.Windows.Forms.DataGridViewTextBoxColumn() { Name = "colUpload", HeaderText = "上传信息" }, + new System.Windows.Forms.DataGridViewTextBoxColumn() { Name = "colDownload", HeaderText = "下载信息" }, + new System.Windows.Forms.DataGridViewTextBoxColumn() { Name = "colTime", HeaderText = "暗码写入时间" } + }); + this.dgv.Location = new System.Drawing.Point(12, 62); + this.dgv.Name = "dgv"; + this.dgv.ReadOnly = true; + this.dgv.RowTemplate.Height = 25; + this.dgv.Size = new System.Drawing.Size(441, 100); + // MainForm + this.ClientSize = new System.Drawing.Size(465, 209); + this.Controls.Add(this.txtFolder); + this.Controls.Add(this.btnBrowse); + this.Controls.Add(this.dgv); + this.Controls.Add(this.btnRead); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.Name = "MainForm"; + this.Text = "Reader - 读取暗码"; + ((System.ComponentModel.ISupportInitialize)(this.dgv)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + } + + private System.Windows.Forms.TextBox txtFolder; + private System.Windows.Forms.Button btnBrowse; + private System.Windows.Forms.Button btnRead; + private System.Windows.Forms.DataGridView dgv; + } +} diff --git a/client/Reader/MainForm.cs b/client/Reader/MainForm.cs new file mode 100644 index 0000000..66ff714 --- /dev/null +++ b/client/Reader/MainForm.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Reader +{ + public partial class MainForm : Form + { + private static readonly HttpClient http = new HttpClient(); + private const string ServerUrl = "http://localhost:5000"; + + public MainForm() + { + InitializeComponent(); + } + + private void btnBrowse_Click(object sender, EventArgs e) + { + using var fbd = new FolderBrowserDialog(); + if (fbd.ShowDialog() == DialogResult.OK) + { + txtFolder.Text = fbd.SelectedPath; + } + } + + private async void btnRead_Click(object sender, EventArgs e) + { + var folder = txtFolder.Text; + if (string.IsNullOrWhiteSpace(folder) || !Directory.Exists(folder)) + { + MessageBox.Show("请选择有效的文件夹。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + dgv.Rows.Clear(); + var files = Directory.GetFiles(folder, "*", SearchOption.AllDirectories); + var shaList = new System.Collections.Generic.List(); + var fileMap = new System.Collections.Generic.Dictionary(); + foreach (var f in files) + { + try + { + var sha = await Task.Run(() => ComputeSha256(f)); + shaList.Add(sha); + fileMap[sha] = f; + } + catch { } + } + if (shaList.Count == 0) + { + MessageBox.Show("没有找到文件。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + var payload = new { shas = shaList }; + var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); + var resp = await http.PostAsync(ServerUrl + "/read", content); + if (!resp.IsSuccessStatusCode) + { + MessageBox.Show("读取失败: " + await resp.Content.ReadAsStringAsync(), "失败", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + var text = await resp.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(text); + var root = doc.RootElement; + var data = root.GetProperty("data"); + foreach (var sha in shaList) + { + var fileName = Path.GetFileName(fileMap[sha]); + if (data.TryGetProperty(sha, out var item) && item.ValueKind != JsonValueKind.Null) + { + var up = item.GetProperty("upload_code").GetString() ?? ""; + var down = item.GetProperty("download_code").GetString() ?? ""; + var created = item.GetProperty("created_at").GetString() ?? ""; + dgv.Rows.Add(fileName, up, down, created); + } + else + { + dgv.Rows.Add(fileName, "无", "无", "无"); + } + } + } + + private static string ComputeSha256(string filePath) + { + using var sha = SHA256.Create(); + using var stream = File.OpenRead(filePath); + var hash = sha.ComputeHash(stream); + var sb = new StringBuilder(); + foreach (var b in hash) sb.Append(b.ToString("x2")); + return sb.ToString(); + } + } +} diff --git a/client/Reader/Program.cs b/client/Reader/Program.cs new file mode 100644 index 0000000..8d44229 --- /dev/null +++ b/client/Reader/Program.cs @@ -0,0 +1,17 @@ +using System; +using System.Windows.Forms; + +namespace Reader +{ + static class Program + { + [STAThread] + static void Main() + { + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/client/Reader/Reader.csproj b/client/Reader/Reader.csproj new file mode 100644 index 0000000..eba05bd --- /dev/null +++ b/client/Reader/Reader.csproj @@ -0,0 +1,9 @@ + + + WinExe + net6.0-windows + true + enable + enable + + diff --git a/client/Writer/MainForm.Designer.cs b/client/Writer/MainForm.Designer.cs new file mode 100644 index 0000000..873e404 --- /dev/null +++ b/client/Writer/MainForm.Designer.cs @@ -0,0 +1,95 @@ +namespace Writer +{ + partial class MainForm + { + private System.ComponentModel.IContainer components = null; + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.txtFile = new System.Windows.Forms.TextBox(); + this.btnBrowse = new System.Windows.Forms.Button(); + this.txtUpload = new System.Windows.Forms.TextBox(); + this.txtDownload = new System.Windows.Forms.TextBox(); + this.lblFile = new System.Windows.Forms.Label(); + this.lblUpload = new System.Windows.Forms.Label(); + this.lblDownload = new System.Windows.Forms.Label(); + this.btnWrite = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // txtFile + this.txtFile.Location = new System.Drawing.Point(12, 30); + this.txtFile.Name = "txtFile"; + this.txtFile.Size = new System.Drawing.Size(360, 23); + // btnBrowse + this.btnBrowse.Location = new System.Drawing.Point(378, 28); + this.btnBrowse.Name = "btnBrowse"; + this.btnBrowse.Size = new System.Drawing.Size(75, 25); + this.btnBrowse.Text = "浏览"; + this.btnBrowse.UseVisualStyleBackColor = true; + this.btnBrowse.Click += new System.EventHandler(this.btnBrowse_Click); + // txtUpload + this.txtUpload.Location = new System.Drawing.Point(12, 80); + this.txtUpload.Name = "txtUpload"; + this.txtUpload.Size = new System.Drawing.Size(441, 23); + // txtDownload + this.txtDownload.Location = new System.Drawing.Point(12, 130); + this.txtDownload.Name = "txtDownload"; + this.txtDownload.Size = new System.Drawing.Size(441, 23); + // labels + this.lblFile.AutoSize = true; + this.lblFile.Location = new System.Drawing.Point(12, 12); + this.lblFile.Name = "lblFile"; + this.lblFile.Size = new System.Drawing.Size(59, 15); + this.lblFile.Text = "目标文件"; + this.lblUpload.AutoSize = true; + this.lblUpload.Location = new System.Drawing.Point(12, 62); + this.lblUpload.Name = "lblUpload"; + this.lblUpload.Size = new System.Drawing.Size(83, 15); + this.lblUpload.Text = "上传暗码(上传)"; + this.lblDownload.AutoSize = true; + this.lblDownload.Location = new System.Drawing.Point(12, 112); + this.lblDownload.Name = "lblDownload"; + this.lblDownload.Size = new System.Drawing.Size(83, 15); + this.lblDownload.Text = "下载暗码(下载)"; + // btnWrite + this.btnWrite.Location = new System.Drawing.Point(378, 170); + this.btnWrite.Name = "btnWrite"; + this.btnWrite.Size = new System.Drawing.Size(75, 27); + this.btnWrite.Text = "开始写入"; + this.btnWrite.UseVisualStyleBackColor = true; + this.btnWrite.Click += new System.EventHandler(this.btnWrite_Click); + // MainForm + this.ClientSize = new System.Drawing.Size(465, 209); + this.Controls.Add(this.txtFile); + this.Controls.Add(this.btnBrowse); + this.Controls.Add(this.txtUpload); + this.Controls.Add(this.txtDownload); + this.Controls.Add(this.lblFile); + this.Controls.Add(this.lblUpload); + this.Controls.Add(this.lblDownload); + this.Controls.Add(this.btnWrite); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.Name = "MainForm"; + this.Text = "Writer - 写入暗码"; + this.ResumeLayout(false); + this.PerformLayout(); + } + + private System.Windows.Forms.TextBox txtFile; + private System.Windows.Forms.Button btnBrowse; + private System.Windows.Forms.TextBox txtUpload; + private System.Windows.Forms.TextBox txtDownload; + private System.Windows.Forms.Label lblFile; + private System.Windows.Forms.Label lblUpload; + private System.Windows.Forms.Label lblDownload; + private System.Windows.Forms.Button btnWrite; + } +} diff --git a/client/Writer/MainForm.cs b/client/Writer/MainForm.cs new file mode 100644 index 0000000..9653588 --- /dev/null +++ b/client/Writer/MainForm.cs @@ -0,0 +1,72 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Writer +{ + public partial class MainForm : Form + { + private static readonly HttpClient http = new HttpClient(); + private const string ServerUrl = "http://localhost:5000"; + + public MainForm() + { + InitializeComponent(); + } + + private void btnBrowse_Click(object sender, EventArgs e) + { + using var ofd = new OpenFileDialog(); + if (ofd.ShowDialog() == DialogResult.OK) + { + txtFile.Text = ofd.FileName; + } + } + + private async void btnWrite_Click(object sender, EventArgs e) + { + var file = txtFile.Text; + if (string.IsNullOrWhiteSpace(file) || !File.Exists(file)) + { + MessageBox.Show("请选择有效的文件。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + var upload = txtUpload.Text ?? string.Empty; + var download = txtDownload.Text ?? string.Empty; + try + { + var sha = await Task.Run(() => ComputeSha256(file)); + var payload = new { sha = sha, upload_code = upload, download_code = download }; + var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); + var resp = await http.PostAsync(ServerUrl + "/write", content); + if (resp.IsSuccessStatusCode) + { + MessageBox.Show("写入暗码成功", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + else + { + MessageBox.Show("写入失败: " + await resp.Content.ReadAsStringAsync(), "失败", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + catch (Exception ex) + { + MessageBox.Show("发生错误: " + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private static string ComputeSha256(string filePath) + { + using var sha = SHA256.Create(); + using var stream = File.OpenRead(filePath); + var hash = sha.ComputeHash(stream); + var sb = new StringBuilder(); + foreach (var b in hash) sb.Append(b.ToString("x2")); + return sb.ToString(); + } + } +} diff --git a/client/Writer/Program.cs b/client/Writer/Program.cs new file mode 100644 index 0000000..fe7fd95 --- /dev/null +++ b/client/Writer/Program.cs @@ -0,0 +1,17 @@ +using System; +using System.Windows.Forms; + +namespace Writer +{ + static class Program + { + [STAThread] + static void Main() + { + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/client/Writer/Writer.csproj b/client/Writer/Writer.csproj new file mode 100644 index 0000000..eba05bd --- /dev/null +++ b/client/Writer/Writer.csproj @@ -0,0 +1,9 @@ + + + WinExe + net6.0-windows + true + enable + enable + + diff --git a/document.txt b/document.txt new file mode 100644 index 0000000..bca398a --- /dev/null +++ b/document.txt @@ -0,0 +1,11 @@ +Create a #new project: +功能主要是负责给已经知道的文件进行“暗码”(即从文件上看不到的一个码,但可以使用客户端对该文件进行解读它上面的这个码),具体细节如下: +1、系统由客户端和服务器端组成,客户端主要运行在windows平台,可以使用以C#语言实现,服务器端部署在ubuntu server上面,随便使用什么技术架构都行。 +2、客户端由两个独立的软件组成,一个软件负责给文件“写入暗码”,另一个岀负责“读出”文件的暗码,一个文件有2个暗码(上传暗码,下载暗码)。 +3、负责写入暗码的客户端软件提供图形界面包括: +一个文件选择框用来选择需要写入暗码的目标文件;要写入的上传暗码的文本框,要写入的下载暗码的文本框。 +一个开始写入的按钮,要实现的功能为:对目标文件进行sha提取哈希摘要,然后将摘要信息和上传暗码以及下载暗码发送给服务器后端,并获得返回值,如果成功则提示对该文件“写入暗码成功”,否则提示失败。 +4、服务器端接到信息后,将上述信息写入数据库,一共5个字段:自增ID,文件的SHA摘要,上传暗码,下载暗码,写入时间戳。 +5、负责“读取暗码”的客户端软件提供图形界面包括:一个可以选择目标文件夹的文件夹选择框;一个开始读取暗码的按钮;一个表格控件,一共包括4列(文件名,上传信息,下载信息,暗码写入时间)。 +用户点击开始读取按钮后,递归枚举指定文件夹里面的文件,提取它们的SHA摘要,将将摘要发送给服务器端,服务器端根据摘要去数据库中提取信息,如果没有匹配则返回none,如果匹配上,则将相应的暗码数据返回给客户端。 +客户端收到信息后解析数据并在表格中显示结果:如果有匹配,则文件名显示该文件的文件名,上传信息显示上传暗码,下载信息显示下载暗码,暗码写入时间显示当时的写入时间戳(以YMD-H:M:S格式显示);如果不匹配,则除文件名这一列外,其它各列均显示“无” \ No newline at end of file diff --git a/server/app.py b/server/app.py new file mode 100644 index 0000000..877a672 --- /dev/null +++ b/server/app.py @@ -0,0 +1,66 @@ +from flask import Flask, request, jsonify +from flask_cors import CORS +import sqlite3 +import os +import datetime + +BASE_DIR = os.path.dirname(__file__) +DB_PATH = os.path.join(BASE_DIR, 'hiddencode.db') + +def get_conn(): + return sqlite3.connect(DB_PATH) + +app = Flask(__name__) +CORS(app) + +@app.route('/write', methods=['POST']) +def write(): + data = request.get_json() or {} + sha = data.get('sha') + upload = data.get('upload_code') + download = data.get('download_code') + if not sha: + return jsonify({'status':'error','msg':'missing sha'}), 400 + now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + conn = get_conn() + c = conn.cursor() + try: + # upsert by sha + c.execute(''' + INSERT INTO codes (sha, upload_code, download_code, created_at) + VALUES (?,?,?,?) + ON CONFLICT(sha) DO UPDATE SET upload_code=excluded.upload_code, download_code=excluded.download_code, created_at=excluded.created_at + ''', (sha, upload, download, now)) + conn.commit() + finally: + conn.close() + return jsonify({'status':'ok'}) + +@app.route('/read', methods=['POST']) +def read(): + data = request.get_json() or {} + shas = data.get('shas', []) + conn = get_conn() + c = conn.cursor() + result = {} + if shas: + placeholders = ','.join('?' for _ in shas) + c.execute(f"SELECT sha, upload_code, download_code, created_at FROM codes WHERE sha IN ({placeholders})", shas) + rows = c.fetchall() + for sha, up, down, created in rows: + result[sha] = {'upload_code': up, 'download_code': down, 'created_at': created} + for s in shas: + if s not in result: + result[s] = None + conn.close() + return jsonify({'status':'ok','data':result}) + +@app.route('/health', methods=['GET']) +def health(): + return jsonify({'status':'ok'}) + +if __name__ == '__main__': + if not os.path.exists(DB_PATH): + from db_init import init_db + init_db() + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/server/db_init.py b/server/db_init.py new file mode 100644 index 0000000..6ac611a --- /dev/null +++ b/server/db_init.py @@ -0,0 +1,23 @@ +import sqlite3 +import os + +DB_PATH = os.path.join(os.path.dirname(__file__), 'hiddencode.db') + +def init_db(): + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute(''' + CREATE TABLE IF NOT EXISTS codes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sha TEXT UNIQUE, + upload_code TEXT, + download_code TEXT, + created_at TEXT + ) + ''') + conn.commit() + conn.close() + +if __name__ == '__main__': + init_db() + print('Initialized database at', DB_PATH) diff --git a/server/requirements.txt b/server/requirements.txt new file mode 100644 index 0000000..6dba340 --- /dev/null +++ b/server/requirements.txt @@ -0,0 +1,2 @@ +Flask==2.2.5 +flask-cors==3.0.10 \ No newline at end of file