找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1615

积分

1

好友

227

主题
发表于 4 天前 | 查看: 17| 回复: 0

传统指纹打卡机故障频发,导致考勤数据混乱、HR统计困难。本文将分享如何利用Python快速开发一个部署简单、功能清晰的轻量级电子考勤系统。该系统使用Flask框架提供Web服务,SQLite存储数据,员工可通过手机网页打卡,HR可便捷查看报表。

需求分析与系统设计

先明确核心需求,避免过度设计:

  1. 打卡记录:员工可记录上下班时间。
  2. 报表查看:HR可按人、按天查询出勤情况。
  3. 考勤规则:支持简单的迟到(如9:00后)、早退(如18:00前)判断。
  4. 部署简易:单Python脚本+轻量Web服务,使用SQLite作为数据库

系统架构分为三层:

  • 存储层:用户表、打卡记录表。
  • 业务层:打卡逻辑、工时计算、状态判断。
  • 展示层:HTTP接口与简易HTML页面。

数据存储设计:使用SQLite

对于内网小工具,SQLite是无需独立服务、零配置的最佳选择。设计两张核心表:

  • users:存储员工基本信息。
  • attendance_records:存储每日打卡记录。设计为“一人一天一条记录”,将上班(check_in_time)和下班时间(check_out_time)放在同一行,便于后续统计。

使用Python标准库sqlite3进行初始化:

# db.py
import sqlite3
from contextlib import contextmanager

DB_PATH = “attendance.db”

@contextmanager
def get_conn():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    try:
        yield conn
        conn.commit()
    finally:
        conn.close()

def init_db():
    with get_conn() as conn:
        c = conn.cursor()
        # 创建员工表
        c.execute(
            “””
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                username TEXT NOT NULL UNIQUE,
                display_name TEXT NOT NULL
            )
            “””
        )
        # 创建打卡记录表
        c.execute(
            “””
            CREATE TABLE IF NOT EXISTS attendance_records (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                date TEXT NOT NULL,          — 日期,如 2025-05-20
                check_in_time TEXT,          — 上班时间,如 09:01:02
                check_out_time TEXT,         — 下班时间,如 18:10:11
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                UNIQUE(user_id, date),       — 确保一人一天一条记录
                FOREIGN KEY(user_id) REFERENCES users(id)
            )
            “””
        )

if __name__ == “__main__”:
    init_db()
    print(“数据库初始化完成”)

核心业务逻辑实现

打卡的核心逻辑是:如果当天没有记录则插入新行,已有记录则更新对应的时间字段。

1. 员工获取与创建
# service.py
from datetime import datetime, date
from db import get_conn

def get_or_create_user(username: str, display_name: str = None) -> int:
    if display_name is None:
        display_name = username
    with get_conn() as conn:
        c = conn.cursor()
        c.execute(“SELECT id FROM users WHERE username = ?”, (username,))
        row = c.fetchone()
        if row:
            return row[“id”]
        c.execute(
            “INSERT INTO users (username, display_name) VALUES (?, ?)”,
            (username, display_name),
        )
        return c.lastrowid
2. 上班打卡与下班打卡
# service.py (续)
WORK_START = “09:00:00”
WORK_END = “18:00:00”

def _today_str() -> str:
    return date.today().strftime(“%Y-%m-%d”)

def _now_time_str() -> str:
    return datetime.now().strftime(“%H:%M:%S”)

def _now_str() -> str:
    return datetime.now().strftime(“%Y-%m-%d %H:%M:%S”)

def check_in(username: str, display_name: str = None):
    user_id = get_or_create_user(username, display_name)
    today = _today_str()
    now_time = _now_time_str()
    now_full = _now_str()
    with get_conn() as conn:
        c = conn.cursor()
        c.execute(
            “SELECT id, check_in_time FROM attendance_records WHERE user_id=? AND date=?”, 
            (user_id, today),
        )
        row = c.fetchone()
        if row:
            # 已有记录,更新上班时间
            c.execute(
                “””
                UPDATE attendance_records
                SET check_in_time=?, updated_at=?
                WHERE id=?
                “””,
                (now_time, now_full, row[“id”]),
            )
        else:
            # 新增记录
            c.execute(
                “””
                INSERT INTO attendance_records (
                    user_id, date, check_in_time, check_out_time, created_at, updated_at
                ) VALUES (?, ?, ?, NULL, ?, ?)
                “””,
                (user_id, today, now_time, now_full, now_full),
            )
    return {“user_id”: user_id, “date”: today, “check_in_time”: now_time}

def check_out(username: str, display_name: str = None):
    user_id = get_or_create_user(username, display_name)
    today = _today_str()
    now_time = _now_time_str()
    now_full = _now_str()
    with get_conn() as conn:
        c = conn.cursor()
        c.execute(
            “SELECT id, check_out_time FROM attendance_records WHERE user_id=? AND date=?”, 
            (user_id, today),
        )
        row = c.fetchone()
        if row:
            c.execute(
                “””
                UPDATE attendance_records
                SET check_out_time=?, updated_at=?
                WHERE id=?
                “””,
                (now_time, now_full, row[“id”]),
            )
        else:
            # 早上未打卡,直接创建下班记录
            c.execute(
                “””
                INSERT INTO attendance_records (
                    user_id, date, check_in_time, check_out_time, created_at, updated_at
                ) VALUES (?, ?, NULL, ?, ?, ?)
                “””,
                (user_id, today, now_time, now_full, now_full),
            )
    return {“user_id”: user_id, “date”: today, “check_out_time”: now_time}

在Python交互环境中测试:

from db import init_db
from service import check_in, check_out

init_db()
print(check_in(“alice”, “小艾”)) # 上班打卡
print(check_out(“alice”)) # 下班打卡

考勤状态分析

基于原始打卡时间,根据预设规则(如9:00上班,18:00下班)计算考勤状态。

# analysis.py
from datetime import datetime
from db import get_conn

WORK_START = “09:00:00”
WORK_END = “18:00:00”

def _time_obj(t: str):
    return datetime.strptime(t, “%H:%M:%S”).time()

def analyze_one_day(username: str, target_date: str):
    with get_conn() as conn:
        c = conn.cursor()
        c.execute(
            “””
            SELECT ar.check_in_time, ar.check_out_time, u.display_name
            FROM attendance_records ar
            JOIN users u ON ar.user_id = u.id
            WHERE u.username=? AND ar.date=?
            “””,
            (username, target_date),
        )
        row = c.fetchone()
    if not row:
        return {
            “date”: target_date,
            “username”: username,
            “status”: “缺勤”,
            “detail”: “当天没有任何打卡记录”,
        }
    check_in_time = row[“check_in_time”]
    check_out_time = row[“check_out_time”]
    display_name = row[“display_name”]
    status_list = []
    if not check_in_time:
        status_list.append(“未打上班卡”)
    else:
        if _time_obj(check_in_time) > _time_obj(WORK_START):
            status_list.append(“迟到”)
        else:
            status_list.append(“正常上班”)
    if not check_out_time:
        status_list.append(“未打下班卡”)
    else:
        if _time_obj(check_out_time) < _time_obj(WORK_END):
            status_list.append(“早退”)
        else:
            status_list.append(“正常下班”)
    status = “,”.join(status_list)
    return {
        “date”: target_date,
        “username”: username,
        “display_name”: display_name,
        “check_in_time”: check_in_time,
        “check_out_time”: check_out_time,
        “status”: status,
    }

使用示例:

from analysis import analyze_one_day
result = analyze_one_day(“alice”, “2025-05-20”)
# 可能返回:{‘date’: ‘2025-05-20’, ‘username’: ‘alice’, … , ‘status’: ‘迟到,正常下班’}

使用Flask构建Web界面

为了让员工通过手机便捷访问,使用轻量级Web框架 Flask快速搭建服务。

# app.py
from flask import Flask, request, render_template_string, jsonify
from db import init_db
from service import check_in, check_out
from analysis import analyze_one_day

app = Flask(__name__)

INDEX_HTML = “””
<!doctype html>
<html>
<head>
    <meta charset=“utf-8”>
    <title>简易考勤系统</title>
</head>
<body>
    <h3>简易考勤系统</h3>
    <form method=“post” action=“/checkin”>
        <label>用户名:
            <input name=“username” required>
        </label>
        <label>昵称(可选):
            <input name=“display_name”>
        </label>
        <button type=“submit”>上班打卡</button>
    </form>
    <br>
    <form method=“post” action=“/checkout”>
        <label>用户名:
            <input name=“username” required>
        </label>
        <button type=“submit”>下班打卡</button>
    </form>
    <br>
    <form method=“get” action=“/report”>
        <label>用户名:
            <input name=“username” required>
        </label>
        <label>日期(YYYY-MM-DD):
            <input name=“date” required>
        </label>
        <button type=“submit”>查看当天记录</button>
    </form>
    {% if message %}
    <p style=“color: green;”>{{ message }}</p>
    {% endif %}
    {% if report %}
    <h4>考勤结果</h4>
    <pre>{{ report | safe }}</pre>
    {% endif %}
</body>
</html>
“””

@app.route(“/”, methods=[“GET”])
def index():
    return render_template_string(INDEX_HTML)

@app.route(“/checkin”, methods=[“POST”])
def web_checkin():
    username = request.form.get(“username”)
    display_name = request.form.get(“display_name”) or None
    result = check_in(username, display_name)
    return render_template_string(
        INDEX_HTML,
        message=f“{username} 上班打卡成功:{result[‘check_in_time’]}”,
        report=None,
    )

@app.route(“/checkout”, methods=[“POST”])
def web_checkout():
    username = request.form.get(“username”)
    result = check_out(username)
    return render_template_string(
        INDEX_HTML,
        message=f“{username} 下班打卡成功:{result[‘check_out_time’]}”,
        report=None,
    )

@app.route(“/report”, methods=[“GET”])
def web_report():
    username = request.args.get(“username”)
    date = request.args.get(“date”)
    result = analyze_one_day(username, date)
    return render_template_string(
        INDEX_HTML,
        message=None,
        report=result,
    )

# 提供纯JSON接口供前端或其他系统调用
@app.route(“/api/report”, methods=[“GET”])
def api_report():
    username = request.args.get(“username”)
    date = request.args.get(“date”)
    result = analyze_one_day(username, date)
    return jsonify(result)

if __name__ == “__main__”:
    init_db()
    app.run(host=“0.0.0.0”, port=5000, debug=True)

运行app.py后,在内网通过浏览器访问 http://服务器IP:5000/ 即可使用。

系统扩展思路

以上是一个可用的最小版本,后续可根据实际需求迭代:

  • 身份认证:集成公司SSO或使用简单的Token鉴权。
  • 灵活班次:支持早班、晚班等不同工时制度。
  • 数据报表:使用pandas库将查询结果导出为Excel文件。
  • 多维统计:按部门、按月统计迟到早退次数、加班时长等。
  • 位置校验:打卡时附加GPS位置信息,确保在指定范围内。

开发此类系统的通用原则是:

  1. 用一张“原始记录表”忠实记录所有打卡事实。
  2. 所有统计和状态判断均在应用层逻辑中完成,便于后期规则调整。
  3. 保持核心数据模型稳定,业务规则变化不影响底层存储。

本系统基于 Python 生态快速构建,代码简洁,部署方便,能有效替代不稳定的硬件打卡机与繁琐的线下统计流程,是一个值得尝试的内部工具解决方案。




上一篇:旅行商问题动态规划解法详解:Python实战与面试题解析
下一篇:Zephyr RTOS在STM32H7上的以太网调试实战:栈溢出排查与问题解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 17:08 , Processed in 0.225970 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表