当前位置:首页 > 技术 > 正文内容

幼儿园考勤刷卡系统:C#实现与嵌入式部署

访客 技术 2026年8月22日 1

本文介绍了一个基于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. 软件部署步骤

  1. 安装.NET运行时: 在树莓派上安装.NET Core 6.0或更高版本的运行时。
  2. 部署应用程序: 将编译后的应用程序文件(DLLs, 配置文件等)传输到设备。
  3. 配置服务: 设置应用程序配置文件(如appsettings.json),指定数据库路径、串口配置、短信API密钥等。
  4. 设置自启动: 配置系统服务(如使用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本地数据库,资源占用少,适合低功耗设备。
  • 高可靠性: 支持网络中断时本地数据缓存,联网后自动同步。
  • 安全性: 采用数据加密、基于角色的访问控制和操作日志记录。
  • 用户友好: 简洁的触摸屏界面,支持语音提示。
  • 可扩展性: 易于集成人脸识别、体温检测等新功能。

未来扩展方向

  • 生物识别集成: 集成人脸或指纹识别,提升安全性与便捷性。
  • 健康监控: 集成非接触式体温检测,自动记录健康数据。
  • 访客管理: 增加访客登记和管理功能,用于临时访客。

相关文章

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

linux screen 用法详情 (nohup 的替代方案)

一、screen 是什么?能干嘛?screen 是一个终端复用器,可以:在一个 SSH 会话中开多个“虚拟终端”SSH 断线后,程序仍然在后台运行随时重新连接到原来的会话特别适合:nohup 的替代方案跑脚本 / 爬虫 / 训练模型运维、远程开发二、安装 screen# CentOS / Rocky / Almayum install -y screen# Debian / Ubuntuapt i...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。