first commit

This commit is contained in:
liushuming
2026-02-27 08:52:30 +08:00
commit ce677db88b
13 changed files with 533 additions and 0 deletions

38
README.md Normal file
View File

@@ -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.

76
client/Reader/MainForm.Designer.cs generated Normal file
View File

@@ -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;
}
}

98
client/Reader/MainForm.cs Normal file
View File

@@ -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<string>();
var fileMap = new System.Collections.Generic.Dictionary<string, string>();
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();
}
}
}

17
client/Reader/Program.cs Normal file
View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

95
client/Writer/MainForm.Designer.cs generated Normal file
View File

@@ -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;
}
}

72
client/Writer/MainForm.cs Normal file
View File

@@ -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();
}
}
}

17
client/Writer/Program.cs Normal file
View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

11
document.txt Normal file
View File

@@ -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格式显示如果不匹配则除文件名这一列外其它各列均显示“无”

66
server/app.py Normal file
View File

@@ -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)

23
server/db_init.py Normal file
View File

@@ -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)

2
server/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
Flask==2.2.5
flask-cors==3.0.10