幼儿园考勤刷卡系统:C#实现与嵌入式部署
本文介绍了一个基于C#开发的幼儿园考勤刷卡系统,该系统专为嵌入式设备设计,集成了刷卡终端、考勤管理、家长通知及信息查询等功能,并提供了详细的实现代码和部署方案。
系统架构
系统采用分层架构,主要包括:
- 刷卡终端: 负责刷卡信息的采集。
- 主控系统: 运行在嵌入式设备上,处理刷卡逻辑、本地数据存储(SQLite)和与服务器的通信。
- 中心服务器: 负责数据同步、用户认证、消息推送(短信网关、家长APP)。
- 家长APP: 提供给家长查询孩子考勤信息和接收通知的接口。
核心组件实现
1. 数据模型 (Models.cs)
定义了系统中所需的数据结构,包括幼儿信息、考勤记录、通知以及用户信息。
using System;
using SQLite;
namespace KindergartenCardSystem.Models
{
// 幼儿档案
public class ChildProfile
{
[PrimaryKey, AutoIncrement]
public int RecordId { get; set; }
[NotNull]
public string FullName { get; set; }
[NotNull, Unique]
public string IdentificationTag { get; set; } // RFID/IC卡号
public string ClassGroupName { get; set; }
public string AssignedTeacher { get; set; }
public string GuardianName { get; set; }
public string GuardianContactPhone { get; set; }
public string PortraitImagePath { get; set; }
public DateTime RegistrationDate { get; set; }
public bool IsActive { get; set; } = true;
}
// 出勤日志
public class AttendanceLog
{
[PrimaryKey, AutoIncrement]
public int LogId { get; set; }
[NotNull]
public int ChildRecordId { get; set; }
[NotNull]
public DateTime EventTimestamp { get; set; }
[NotNull]
public string EventType { get; set; } // "Entry" (入园) 或 "Exit" (离园)
public string Remarks { get; set; }
}
// 消息记录
public class MessageRecord
{
[PrimaryKey, AutoIncrement]
public int MessageId { get; set; }
[NotNull]
public int ChildRecordId { get; set; }
[NotNull]
public DateTime EventTimestamp { get; set; }
[NotNull]
public string EventType { get; set; } // "Entry" 或 "Exit"
public string Content { get; set; }
public bool DeliveryStatus { get; set; }
public string DeliveryMethod { get; set; } // SMS, AppPush, WeChat
}
// 用户账户
public class UserAccount
{
[PrimaryKey, AutoIncrement]
public int UserId { get; set; }
[NotNull, Unique]
public string UserName { get; set; }
[NotNull]
public string HashedPassword { get; set; } // 实际应用中存储加密密码
[NotNull]
public string UserRole { get; set; } // Admin, Teacher, Parent
public int? AssociatedChildId { get; set; } // 家长关联的孩子ID
public bool IsAccountActive { get; set; } = true;
}
}
2. 数据访问服务 (DataAccessService.cs)
封装了对SQLite数据库的读写操作,确保数据持久化和一致性。
using System;
using System.Collections.Generic;
using System.IO;
using SQLite;
using KindergartenCardSystem.Models;
namespace KindergartenCardSystem.Services
{
public class DataAccessService
{
private SQLiteConnection _dbConnection;
private readonly string _databaseFilePath;
public DataAccessService(string dbPath = null)
{
_databaseFilePath = dbPath ?? Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"KindergartenData.db");
InitializeDatabaseSchema();
}
private void InitializeDatabaseSchema()
{
_dbConnection = new SQLiteConnection(_databaseFilePath);
_dbConnection.CreateTable<ChildProfile>();
_dbConnection.CreateTable<AttendanceLog>();
_dbConnection.CreateTable<MessageRecord>();
_dbConnection.CreateTable<UserAccount>();
// 确保存在默认管理员账户
if (_dbConnection.Table<UserAccount>().Count() == 0)
{
_dbConnection.Insert(new UserAccount
{
UserName = "admin",
HashedPassword = "hashed_admin_password", // 实际应用需加密
UserRole = "Admin",
IsAccountActive = true
});
}
}
// 幼儿档案操作
public List<ChildProfile> GetAllChildProfiles() => _dbConnection.Table<ChildProfile>().ToList();
public ChildProfile GetChildProfileById(int id) => _dbConnection.Table<ChildProfile>().FirstOrDefault(c => c.RecordId == id);
public ChildProfile GetChildProfileByTag(string tagId) => _dbConnection.Table<ChildProfile>().FirstOrDefault(c => c.IdentificationTag == tagId);
public int AddChildProfile(ChildProfile child) => _dbConnection.Insert(child);
public int UpdateChildProfile(ChildProfile child) => _dbConnection.Update(child);
public int RemoveChildProfile(int id) => _dbConnection.Delete<ChildProfile>(id);
// 出勤日志操作
public int LogAttendanceEvent(AttendanceLog logEntry) => _dbConnection.Insert(logEntry);
public List<AttendanceLog> QueryAttendanceLogs(int childId, DateTime startPeriod, DateTime endPeriod)
{
return _dbConnection.Table<AttendanceLog>()
.Where(r => r.ChildRecordId == childId && r.EventTimestamp >= startPeriod && r.EventTimestamp <= endPeriod)
.OrderBy(r => r.EventTimestamp)
.ToList();
}
// 消息记录操作
public int AddMessageRecord(MessageRecord message) => _dbConnection.Insert(message);
public List<MessageRecord> GetPendingMessages() =>
_dbConnection.Table<MessageRecord>().Where(n => !n.DeliveryStatus).ToList();
// 用户认证
public UserAccount AuthenticateUser(string username, string password)
{
// 实际应用中需要比较密码哈希值
return _dbConnection.Table<UserAccount>()
.FirstOrDefault(u => u.UserName == username && u.HashedPassword == password && u.IsAccountActive);
}
public List<UserAccount> GetAccountsByRole(string role) =>
_dbConnection.Table<UserAccount>().Where(u => u.UserRole == role).ToList();
}
}
3. 读卡器服务 (CardScannerService.cs)
负责与串口通信,读取刷卡器传来的卡号,并触发回调事件。
using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
namespace KindergartenCardSystem.Services
{
public class CardScannerService : IDisposable
{
private SerialPort _commPort;
private string _portName;
private int _baudRate;
private Action<string> _onTagReadCallback;
private volatile bool _isScanningActive;
private Thread _scanThread;
public CardScannerService(string portName = "COM3", int baudRate = 9600)
{
_portName = portName;
_baudRate = baudRate;
}
public void ActivateScanner(Action<string> tagDetectedHandler)
{
_onTagReadCallback = tagDetectedHandler;
_isScanningActive = true;
_commPort = new SerialPort(_portName, _baudRate)
{
Parity = Parity.None,
DataBits = 8,
StopBits = StopBits.One,
Handshake = Handshake.None,
ReadTimeout = 500, // 设置读取超时
WriteTimeout = 500
};
try
{
_commPort.Open();
_scanThread = new Thread(ContinuousScanLoop) { IsBackground = true };
_scanThread.Start();
Console.WriteLine($"读卡器已启动,监听端口: {_portName}");
}
catch (Exception ex)
{
Console.WriteLine($"串口 {_portName} 打开失败: {ex.Message}");
// 可在此处添加重试或错误处理逻辑
}
}
private void ContinuousScanLoop()
{
while (_isScanningActive)
{
try
{
if (_commPort != null && _commPort.IsOpen)
{
string rawData = _commPort.ReadLine().Trim(); // 读取一行数据
if (!string.IsNullOrEmpty(rawData))
{
_onTagReadCallback?.Invoke(rawData);
}
}
// 短暂休眠以降低CPU占用
Thread.Sleep(50);
}
catch (TimeoutException) { /* 超时是正常情况,继续循环 */ }
catch (Exception ex)
{
Console.WriteLine($"读取数据时发生错误: {ex.Message}");
// 考虑在此处添加断线重连逻辑
Thread.Sleep(2000); // 发生错误时增加延迟
}
}
}
public void DeactivateScanner()
{
_isScanningActive = false;
_scanThread?.Join(1000); // 等待线程结束
if (_commPort != null && _commPort.IsOpen)
{
_commPort.Close();
}
Console.WriteLine("读卡器已停止。");
}
public void Dispose()
{
DeactivateScanner();
_commPort?.Dispose();
}
}
}
4. 通知推送服务 (PushNotificationService.cs)
负责发送短信通知,并处理待发送的消息队列。
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using KindergartenCardSystem.Models;
namespace KindergartenCardSystem.Services
{
public class PushNotificationService
{
private readonly string _smsGatewayApiKey;
private readonly string _smsGatewayEndpoint;
private readonly DataAccessService _dataService;
public PushNotificationService(string apiKey, string apiUrl, DataAccessService dataService)
{
_smsGatewayApiKey = apiKey;
_smsGatewayEndpoint = apiUrl;
_dataService = dataService;
}
public async Task SendSmsAsync(string recipientPhone, string messageContent)
{
try
{
using (var httpClient = new HttpClient())
{
var requestData = new
{
apiKey = _smsGatewayApiKey,
phoneNumber = recipientPhone,
message = messageContent
};
var jsonPayload = JsonConvert.SerializeObject(requestData);
var httpContent = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(_smsGatewayEndpoint, httpContent);
response.EnsureSuccessStatusCode(); // 检查HTTP状态码
Console.WriteLine($"短信发送至 {recipientPhone} 成功。");
}
}
catch (HttpRequestException httpEx)
{
Console.WriteLine($"短信发送失败 (HTTP错误): {httpEx.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"短信发送失败 (未知错误): {ex.Message}");
}
}
public async Task ProcessOutboundMessagesAsync()
{
var pendingMessages = _dataService.GetPendingMessages();
foreach (var message in pendingMessages)
{
var child = _dataService.GetChildProfileById(message.ChildRecordId);
if (child == null) continue;
bool successfullySent = false;
if (message.DeliveryMethod == "SMS" && !string.IsNullOrEmpty(child.GuardianContactPhone))
{
// 格式化消息内容
string formattedMessage = $"【幼儿园提醒】您的孩子 {child.FullName} 已于 {message.EventTimestamp:HH:mm} 完成{(message.EventType == "Entry" ? "入园" : "离园")}。";
await SendSmsAsync(child.GuardianContactPhone, formattedMessage);
successfullySent = true;
}
// TODO: 添加App推送和微信通知的实现逻辑
if (successfullySent)
{
message.DeliveryStatus = true;
_dataService.AddMessageRecord(message); // 更新数据库状态
}
}
}
}
}
5. 主界面逻辑 (MainForm.cs)
处理用户界面交互,包括登录、刷卡事件响应、界面状态更新等。
using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using KindergartenCardSystem.Models;
using KindergartenCardSystem.Services;
using System.Threading.Tasks;
namespace KindergartenCardSystem.UI
{
public partial class MainForm : Form
{
private readonly DataAccessService _repository;
private readonly CardScannerService _scanner;
private readonly PushNotificationService _notifier;
private UserAccount _activeUser;
public MainForm()
{
InitializeComponent();
// 初始化核心服务
_repository = new DataAccessService();
_scanner = new CardScannerService();
// 替换为实际的API密钥和URL
_notifier = new PushNotificationService("YOUR_SMS_API_KEY", "https://api.sms-provider.com/v1/send", _repository);
InitializeSystemInterface();
// 启动刷卡器
_scanner.ActivateScanner(HandleCardScanEvent);
// 启动后台消息处理
Task.Run(MessageProcessingLoop);
}
private void InitializeSystemInterface()
{
// 显示登录界面
var loginDialog = new LoginForm(_repository);
if (loginDialog.ShowDialog() != DialogResult.OK)
{
Application.Exit(); // 用户取消登录则退出程序
return;
}
_activeUser = loginDialog.AuthenticatedUser;
// 根据用户角色配置界面
ConfigureUserInterface(_activeUser);
// 更新时间显示
lblCurrentDateTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
timerDateTime.Start();
}
private void ConfigureUserInterface(UserAccount user)
{
pnlLoginArea.Visible = false; // 隐藏登录提示
if (user.UserRole == "Admin" || user.UserRole == "Teacher")
{
// 显示教师/管理员视图
pnlScanControlPanel.Visible = true;
lblWelcomeMessage.Text = $"欢迎, {user.UserName} ({user.UserRole})";
pnlScanResultDisplay.Visible = true;
rtbScanFeedback.Visible = true;
lstRecentActivity.Visible = true;
lblRecentActivityHeader.Visible = true;
RefreshRecentActivityLog();
}
else if (user.UserRole == "Parent")
{
// 显示家长视图
pnlParentView.Visible = true;
lblWelcomeMessage.Text = $"欢迎, {user.UserName} (家长)";
// 加载孩子信息
if (user.AssociatedChildId.HasValue)
{
var child = _repository.GetChildProfileById(user.AssociatedChildId.Value);
if (child != null)
{
lblChildName.Text = child.FullName;
lblChildClass.Text = child.ClassGroupName;
lblChildTeacher.Text = child.AssignedTeacher;
// 加载头像
if (!string.IsNullOrEmpty(child.PortraitImagePath) && File.Exists(child.PortraitImagePath))
{
picChildPortrait.Image = Image.FromFile(child.PortraitImagePath);
}
// 加载今日出勤
RefreshDailyAttendance(child.RecordId);
}
}
}
}
private void RefreshRecentActivityLog()
{
lstRecentActivity.Items.Clear();
// 获取最近7天的记录
var recentLogs = _repository.QueryAttendanceLogs(0, DateTime.Today.AddDays(-7), DateTime.Now)
.OrderByDescending(r => r.EventTimestamp)
.Take(15); // 显示最近15条
foreach (var log in recentLogs)
{
var child = _repository.GetChildProfileById(log.ChildRecordId);
if (child != null)
{
string actionText = log.EventType == "Entry" ? "入园" : "离园";
lstRecentActivity.Items.Add($"{log.EventTimestamp:HH:mm:ss} - {child.FullName} ({child.ClassGroupName}) - {actionText}");
}
}
}
private void RefreshDailyAttendance(int childId)
{
lstTodayAttendance.Items.Clear();
var startOfDay = DateTime.Today;
var endOfDay = startOfDay.AddDays(1);
var dailyLogs = _repository.QueryAttendanceLogs(childId, startOfDay, endOfDay);
foreach (var log in dailyLogs)
{
lstTodayAttendance.Items.Add($"{log.EventTimestamp:HH:mm:ss} - {(log.EventType == "Entry" ? "入园" : "离园")}");
}
}
private void HandleCardScanEvent(string cardNumber)
{
// 确保在UI线程上更新界面
this.Invoke((MethodInvoker)delegate {
ProcessCardNumber(cardNumber);
});
}
private void ProcessCardNumber(string cardNumber)
{
var child = _repository.GetChildProfileByTag(cardNumber);
if (child == null)
{
lblScanStatus.Text = "无效卡号";
lblScanStatus.ForeColor = Color.Red;
rtbScanFeedback.Text = $"卡号 {cardNumber} 未在系统中注册。";
return;
}
if (!child.IsActive)
{
lblScanStatus.Text = "卡片无效";
lblScanStatus.ForeColor = Color.Orange;
rtbScanFeedback.Text = $"{child.FullName} ({child.ClassGroupName}) 的卡片已停用。";
return;
}
// 智能判断入园/离园
var now = DateTime.Now;
var startOfToday = DateTime.Today;
var todayLogs = _repository.QueryAttendanceLogs(child.RecordId, startOfToday, startOfToday.AddDays(1));
var lastLog = todayLogs.OrderByDescending(r => r.EventTimestamp).FirstOrDefault();
string eventType = "Entry"; // 默认入园
// 如果当天已有入园记录且当前时间较晚,则判定为离园
if (lastLog != null && lastLog.EventType == "Entry" && now.TimeOfDay > new TimeSpan(12, 0, 0))
{
eventType = "Exit";
}
// 记录考勤事件
var newLog = new AttendanceLog
{
ChildRecordId = child.RecordId,
EventTimestamp = now,
EventType = eventType,
Remarks = $"刷卡{(eventType == "Entry" ? "入园" : "离园")}"
};
_repository.LogAttendanceEvent(newLog);
// 创建并入队通知消息
var message = new MessageRecord
{
ChildRecordId = child.RecordId,
EventTimestamp = now,
EventType = eventType,
Content = $"孩子{child.FullName}已于{now:HH:mm}完成{(eventType == "Entry" ? "入园" : "离园")}",
DeliveryMethod = "SMS" // 可配置为AppPush, WeChat等
};
_repository.AddMessageRecord(message);
// 更新界面状态
lblScanStatus.Text = $"{(eventType == "Entry" ? "入园" : "离园")}成功";
lblScanStatus.ForeColor = Color.Green;
rtbScanFeedback.Text = $"{child.FullName} ({child.ClassGroupName}) - {cardNumber}";
// 播放提示音
System.Sounds.SystemSounds.Asterisk.Play();
// 刷新活动日志
RefreshRecentActivityLog();
}
// 后台循环处理消息
private async Task MessageProcessingLoop()
{
while (true)
{
try
{
await _notifier.ProcessOutboundMessagesAsync();
}
catch (Exception ex)
{
Console.WriteLine($"消息处理循环错误: {ex.Message}");
}
// 每3分钟检查一次待处理消息
await Task.Delay(3 * 60 * 1000);
}
}
private void timerDateTime_Tick(object sender, EventArgs e)
{
lblCurrentDateTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
// ... 其他UI事件处理方法 ...
}
}
6. 登录界面 (LoginForm.cs)
提供用户认证功能。
using System;
using System.Windows.Forms;
using KindergartenCardSystem.Models;
using KindergartenCardSystem.Services;
namespace KindergartenCardSystem.UI
{
public partial class LoginForm : Form
{
private readonly DataAccessService _userRepository;
public UserAccount AuthenticatedUser { get; private set; }
public LoginForm(DataAccessService userRepository)
{
InitializeComponent();
_userRepository = userRepository;
}
private void btnLogin_Click(object sender, EventArgs e)
{
string enteredUsername = txtUsername.Text.Trim();
string enteredPassword = txtPassword.Text;
if (string.IsNullOrEmpty(enteredUsername) || string.IsNullOrEmpty(enteredPassword))
{
MessageBox.Show("请输入用户名和密码。", "登录凭据不足", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var user = _userRepository.AuthenticateUser(enteredUsername, enteredPassword);
if (user == null)
{
MessageBox.Show("用户名或密码不匹配。", "认证失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
AuthenticatedUser = user;
DialogResult = DialogResult.OK; // 设置对话框结果为成功
Close(); // 关闭登录窗口
}
}
}
系统管理功能
系统还包含设备管理、报表生成等模块,用于配置硬件、监控系统状态以及生成考勤报表。
7. 设备管理 (DeviceManagementForm.cs)
用于查看和配置连接的刷卡终端。
using System;
using System.Windows.Forms;
using KindergartenCardSystem.Models;
using KindergartenCardSystem.Services;
namespace KindergartenCardSystem.UI
{
public partial class DeviceManagementForm : Form
{
private readonly DataAccessService _dataService;
public DeviceManagementForm(DataAccessService dataService)
{
InitializeComponent();
_dataService = dataService;
PopulateDeviceList();
}
private void PopulateDeviceList()
{
// 模拟从配置或数据库加载设备列表
// 实际应用中会查询设备注册表
lvDevices.Items.Clear();
lvDevices.Items.Add(new ListViewItem(new[] { "Terminal-001", "主入口", "COM3", "在线" }));
lvDevices.Items.Add(new ListViewItem(new[] { "Terminal-002", "教室A", "COM4", "在线" }));
lvDevices.Items.Add(new ListViewItem(new[] { "Terminal-003", "侧门", "COM5", "离线" }));
}
private void btnAddDevice_Click(object sender, EventArgs e)
{
// 弹出添加设备对话框
var addDeviceDialog = new AddDeviceDialog();
if (addDeviceDialog.ShowDialog() == DialogResult.OK)
{
// 将新设备添加到列表
lvDevices.Items.Add(new ListViewItem(new[] {
addDeviceDialog.DeviceId,
addDeviceDialog.LocationName,
addDeviceDialog.ComPort,
"配置中"
}));
}
}
private void btnConfigureDevice_Click(object sender, EventArgs e)
{
if (lvDevices.SelectedItems.Count == 0) return;
var selectedItem = lvDevices.SelectedItems[0];
var configDialog = new ConfigureDeviceDialog(
selectedItem.SubItems[0].Text,
selectedItem.SubItems[1].Text,
selectedItem.SubItems[2].Text);
if (configDialog.ShowDialog() == DialogResult.OK)
{
// 更新界面显示
selectedItem.SubItems[1].Text = configDialog.LocationName;
selectedItem.SubItems[2].Text = configDialog.ComPort;
selectedItem.SubItems[3].Text = "待连接"; // 更新状态
}
}
private void btnTestConnection_Click(object sender, EventArgs e)
{
if (lvDevices.SelectedItems.Count == 0) return;
var selectedItem = lvDevices.SelectedItems[0];
MessageBox.Show($"正在尝试连接到设备 {selectedItem.SubItems[0].Text}...",
"设备连接测试", MessageBoxButtons.OK, MessageBoxIcon.Information);
// 模拟测试结果
selectedItem.SubItems[3].Text = "在线";
}
}
// 模拟对话框类,实际项目中需单独创建
public class AddDeviceDialog : Form { public string DeviceId; public string LocationName; public string ComPort; /* ... */ }
public class ConfigureDeviceDialog : Form { public string LocationName; public string ComPort; /* ... */ }
}
8. 报表生成 (AttendanceReporter.cs)
用于生成和导出考勤报表。
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using KindergartenCardSystem.Models;
using KindergartenCardSystem.Services;
namespace KindergartenCardSystem.Reports
{
public class AttendanceReporter
{
private readonly DataAccessService _dataAccessor;
public AttendanceReporter(DataAccessService dataAccessor)
{
_dataAccessor = dataAccessor;
}
public DataTable GenerateDailySummaryReport(DateTime reportDate)
{
var summaryTable = new DataTable();
summaryTable.Columns.Add("班级名称", typeof(string));
summaryTable.Columns.Add("应到人数", typeof(int));
summaryTable.Columns.Add("实际到岗", typeof(int));
summaryTable.Columns.Add("迟到", typeof(int));
summaryTable.Columns.Add("早退", typeof(int));
summaryTable.Columns.Add("出勤率", typeof(string));
var uniqueClasses = _dataAccessor.GetAllChildProfiles()
.Where(c => c.IsActive)
.Select(c => c.ClassGroupName)
.Distinct()
.OrderBy(c => c)
.ToList();
foreach (var className in uniqueClasses)
{
var enrolledChildren = _dataAccessor.GetAllChildProfiles()
.Where(c => c.ClassGroupName == className && c.IsActive)
.ToList();
int totalExpected = enrolledChildren.Count;
int actualPresent = 0;
int lateComers = 0;
int earlyDepartures = 0;
foreach (var child in enrolledChildren)
{
var logsForChild = _dataAccessor.QueryAttendanceLogs(child.RecordId, reportDate, reportDate.AddDays(1));
bool checkedInToday = false;
bool checkedOutToday = false;
foreach (var log in logsForChild)
{
if (log.EventType == "Entry")
{
checkedInToday = true;
// 假设上午9点后入园算迟到
if (log.EventTimestamp.TimeOfDay > new TimeSpan(9, 0, 0))
lateComers++;
}
else if (log.EventType == "Exit")
{
checkedOutToday = true;
// 假设下午4点前离园算早退
if (log.EventTimestamp.TimeOfDay < new TimeSpan(16, 0, 0))
earlyDepartures++;
}
}
if (checkedInToday) actualPresent++;
}
double attendancePercentage = totalExpected > 0 ? (double)actualPresent / totalExpected * 100 : 0;
summaryTable.Rows.Add(className, totalExpected, actualPresent, lateComers, earlyDepartures, $"{attendancePercentage:F2}%");
}
return summaryTable;
}
public void ExportToCsvFile(DataTable dataTable, string outputPath)
{
using (var csvWriter = new StreamWriter(outputPath))
{
// 写入列头
for (int i = 0; i < dataTable.Columns.Count; i++)
{
csvWriter.Write($"\"{dataTable.Columns[i].ColumnName}\"");
if (i < dataTable.Columns.Count - 1)
csvWriter.Write(",");
}
csvWriter.WriteLine();
// 写入数据行
foreach (DataRow row in dataTable.Rows)
{
for (int i = 0; i < dataTable.Columns.Count; i++)
{
// 处理可能包含逗号的值
string cellValue = row[i].ToString().Replace("\"", "\"\"");
csvWriter.Write($"\"{cellValue}\"");
if (i < dataTable.Columns.Count - 1)
csvWriter.Write(",");
}
csvWriter.WriteLine();
}
}
}
}
}
系统部署与配置
1. 硬件选型建议
- 主控板: 树莓派4B (4GB RAM) 是一个性价比较高的选择,能够运行.NET Core应用。
- 读卡器: MFRC522 RFID模块,支持13.56MHz卡片。
- 显示屏: 7英寸触摸屏,提供直观的用户交互界面。
- 摄像头: USB摄像头,用于可选的访客拍照或人脸识别功能。
- 网络: USB WiFi适配器,用于与服务器通信。
2. 软件部署步骤
- 安装.NET运行时: 在树莓派上安装.NET Core 6.0或更高版本的运行时。
- 部署应用程序: 将编译后的应用程序文件(DLLs, 配置文件等)传输到设备。
- 配置服务: 设置应用程序配置文件(如
appsettings.json),指定数据库路径、串口配置、短信API密钥等。 - 设置自启动: 配置系统服务(如使用
systemd)确保应用程序在设备启动时自动运行。
3. 核心配置示例 (appsettings.json)
{
"AppConfig": {
"DatabasePath": "/home/pi/kindergarten_app/data/kindergarten.db",
"DefaultComPort": "COM3",
"DefaultBaudRate": 9600
},
"SmsService": {
"ApiKey": "YOUR_SECRET_SMS_API_KEY",
"ApiEndpoint": "https://api.sms-provider.com/v1/send"
},
"Security": {
"EncryptionSalt": "a_secure_salt_for_hashing"
}
}
系统特性
- 嵌入式优化: 使用SQLite本地数据库,资源占用少,适合低功耗设备。
- 高可靠性: 支持网络中断时本地数据缓存,联网后自动同步。
- 安全性: 采用数据加密、基于角色的访问控制和操作日志记录。
- 用户友好: 简洁的触摸屏界面,支持语音提示。
- 可扩展性: 易于集成人脸识别、体温检测等新功能。
未来扩展方向
- 生物识别集成: 集成人脸或指纹识别,提升安全性与便捷性。
- 健康监控: 集成非接触式体温检测,自动记录健康数据。
- 访客管理: 增加访客登记和管理功能,用于临时访客。