欢迎光临
我们一直在努力

AI人脸识别打卡签到系统2.0版本完成

AI人脸识别打卡签到系统已经实现阶段性完成,完整代码已上传至代码仓,后续第二阶段将延用后端并且开发一个AI人脸识别移动端,将使用微信开发者工具进行实现。


AI考勤系统

阶段性开发日志 v0.99 RC

项目代号:ai-attendance-system

文档日期:2026年6月16日

技术栈:React 18 + Express + SQLite + Python FastAPI

一、项目概览

AI考勤系统是一套基于人脸识别的智能考勤管理平台,支持多租户、多终端部署,涵盖考勤打卡、请假加班、薪资计算、智能排班、访客管理、数据报表等完整业务流程。系统采用前后端分离架构,前端基于 React 18 + Vite + TypeScript,后端基于 Express + SQLite/PostgreSQL,人脸识别服务基于 Python FastAPI + InsightFace。

1.1 核心数据

数据库表:27张 | API端点:149个 | 前端页面:30个 | 路由模块:28个

测试用例:208个 | i18n翻译键:958个/语言 | 部署方案:4种

核心依赖:25个 | 开发依赖:30个 | 数据库索引:25+个

1.2 技术栈

  • 前端:React 18.3 + React Router 7 + Zustand 5 + ECharts 6 + Tailwind CSS 3
  • 后端:Express 4 + better-sqlite3 12 + jsonwebtoken 9 + winston 3
  • 人脸服务:Python FastAPI + InsightFace + ONNX Runtime + OpenCV
  • 构建工具:Vite 6 + TypeScript 5.8 + Vitest 4 + Playwright 1.6
  • 部署方案:Docker + PM2 + Vercel + Electron

二、功能模块详解

2.1 认证与安全模块

认证模块是整个系统的安全基石,负责用户登录验证、JWT Token管理、密码策略执行、速率限制防护等核心安全功能。系统采用 JWT + Refresh Token 双令牌机制,Access Token 有效期24小时,Refresh Token 有效期7天,存储在数据库中支持主动撤销。

2.1.1 登录验证逻辑

登录流程包含速率限制检查、用户查询、密码验证、哈希自动升级、Token生成、登录日志记录等步骤。密码验证兼容旧版SHA-256和新版scrypt两种格式,登录成功后自动将旧哈希升级为scrypt格式。

router.post('/login', async (req, res) => {

  const ip = req.ip || 'unknown'

  if (!checkRateLimit(ip)) {

    return res.status(429).json({ error: '登录尝试过多,请15分钟后再试' })

  }

  const user = db.get('SELECT * FROM user WHERE username=? AND status=?', username, 'active')

  if (!user || !verifyPassword(password, user.password_hash)) {

    db.run('INSERT INTO login_log (username, ip, success) VALUES (?, ?, 0)', username, ip)

    return res.status(401).json({ error: '用户名或密码错误' })

  }

  // 自动升级旧密码哈希 SHA-256 -> scrypt

  if (needsUpgrade(user.password_hash)) {

    const newHash = hashPasswordSecure(password)

    db.run('UPDATE user SET password_hash=? WHERE id=?', newHash, user.id)

  }

  const accessToken = generateToken({ id, username, role, emp_id, tenant_id })

  const refreshToken = jwt.sign({ id, type: 'refresh' }, JWT_SECRET, { expiresIn: '7d' })

  db.run('INSERT OR REPLACE INTO refresh_token (user_id, token, expires_at) VALUES (?, ?, ?)',

    user.id, refreshToken, expiresAt)

  res.json({ success: true, data: { token: accessToken, refreshToken, user } })

})

2.1.2 密码哈希与验证

系统使用scrypt算法进行密码哈希,生成64字节密钥+16字节随机盐,使用timingSafeEqual防止时序攻击。旧版SHA-256哈希在登录时自动升级,无需用户手动操作。

function hashPasswordSecure(password: string): string {

  const salt = randomBytes(16).toString('hex')

  const key = scryptSync(password, salt, 64)

  return `$scrypt$${salt}$${key.toString('hex')}`

}

 

function verifyPassword(password: string, hash: string): boolean {

  if (hash.startsWith('$scrypt$')) {

    const [, , salt, key] = hash.split('$')

    const derivedKey = scryptSync(password, salt, 64)

    return timingSafeEqual(Buffer.from(key, 'hex'), derivedKey)

  }

  // 兼容旧SHA-256格式

  const sha256Hash = createHash('sha256').update(password).digest('hex')

  return sha256Hash === hash

}

2.1.3 登录速率限制

基于IP地址的登录速率限制,5次失败后锁定15分钟,防止暴力破解攻击。每10分钟自动清理过期的限制记录,避免内存泄漏。

const loginAttempts = new Map<string, { count: number; lastAttempt: number }>()

const MAX_ATTEMPTS = 5

const LOCKOUT_MS = 15 * 60 * 1000

 

function checkRateLimit(ip: string): boolean {

  const now = Date.now()

  const attempt = loginAttempts.get(ip)

  if (!attempt) { loginAttempts.set(ip, { count: 1, lastAttempt: now }); return true }

  if (now – attempt.lastAttempt > LOCKOUT_MS) {

    loginAttempts.set(ip, { count: 1, lastAttempt: now }); return true

  }

  if (attempt.count >= MAX_ATTEMPTS) return false

  attempt.count++; attempt.lastAttempt = now; return true

}

2.1.4 JWT认证中间件

所有API请求(除公开路径外)必须携带有效的JWT Token。中间件从Authorization头或query参数中提取Token,验证签名和有效期,将解码后的用户信息注入req.user供后续路由使用。

const publicPaths = ['/api/auth/login', '/api/auth/refresh', '/api/health', …]

app.use((req, res, next) => {

  if (publicPaths.some(p => req.path.startsWith(p))) return next()

  const token = req.headers.authorization?.split(' ')[1] || req.query.token

  if (!token) return res.status(401).json({ error: '未授权访问' })

  try {

    req.user = jwt.verify(token, jwtSecret)

    next()

  } catch { res.status(401).json({ error: '登录已过期' }) }

})

2.1.5 Refresh Token机制

Refresh Token存储在数据库中,有效期7天,支持主动撤销。前端在Access Token过期时自动使用Refresh Token获取新的Access Token,实现无感刷新。

router.post('/refresh', (req, res) => {

  const { refreshToken } = req.body

  const decoded = jwt.verify(refreshToken, JWT_SECRET)

  if (decoded.type !== 'refresh') return res.status(401)

  const stored = db.get('SELECT * FROM refresh_token WHERE user_id=? AND token=?', decoded.id, refreshToken)

  if (!stored) return res.status(401)

  const newAccessToken = jwt.sign({ id, username, role, … }, JWT_SECRET, { expiresIn: '24h' })

  res.json({ success: true, data: { token: newAccessToken } })

})

2.1.6 人脸特征加密

人脸特征数据使用AES-256-GCM算法加密存储,16字节随机IV + 16字节认证标签,确保数据机密性和完整性。加密密钥通过文件或环境变量管理,文件权限设为0600。

encrypt(data: string): string {

  const iv = crypto.randomBytes(16)

  const cipher = crypto.createCipheriv('aes-256-gcm', this.key, iv)

  const encrypted = Buffer.concat([cipher.update(data, 'utf-8'), cipher.final()])

  const authTag = cipher.getAuthTag()

  return `${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted.toString('base64')}`

}

 

decrypt(encrypted: string): string {

  const [ivB64, tagB64, dataB64] = encrypted.split(':')

  const decipher = crypto.createDecipheriv('aes-256-gcm', this.key, Buffer.from(ivB64, 'base64'))

  decipher.setAuthTag(Buffer.from(tagB64, 'base64'))

  return Buffer.concat([decipher.update(Buffer.from(dataB64, 'base64')), decipher.final()]).toString('utf-8')

}

2.2 考勤管理模块

考勤管理是系统的核心业务模块,支持人脸识别打卡、二维码打卡、GPS打卡三种方式,提供考勤记录查询、今日统计、PDF/Excel导出等功能。考勤终端支持活体检测,防止照片欺骗。

2.2.1 考勤记录查询

支持分页查询、日期范围过滤、部门过滤、工号搜索。普通员工只能查看自己的记录,管理员可查看全部。查询结果关联员工表获取姓名和部门信息。

router.get('/', (req, res) => {

  let whereClause = 'WHERE a.tenant_id = ?'; const params = [tenantId]

  if (date) { whereClause += ' AND date(a.capture_time) = ?'; params.push(date) }

  if (department) { whereClause += ' AND e.department = ?'; params.push(department) }

  if (req.user.role === 'employee') {

    whereClause += ' AND a.emp_id = ?'; params.push(req.user.emp_id)

  }

  const records = db.all(`SELECT a.*, e.name, e.department

    FROM attendance_record a LEFT JOIN employee e ON a.emp_id = e.emp_id

    ${whereClause} ORDER BY a.capture_time DESC LIMIT ? OFFSET ?`, …params, pageSize, offset)

  res.json({ success: true, data: { items: records, total, page, page_size: pageSize } })

})

2.2.2 今日考勤统计

实时统计当日出勤情况,包括应到人数、实到人数、迟到人数、缺勤人数,以及最近10条打卡记录,供仪表盘实时展示。

router.get('/today-stats', (req, res) => {

  const total = db.get("SELECT COUNT(*) as count FROM employee WHERE status='active'")?.count || 0

  const present = db.get("SELECT COUNT(DISTINCT emp_id) as count FROM attendance_record WHERE type='check_in' AND date(capture_time)=?")?.count || 0

  const late = db.get("SELECT COUNT(DISTINCT emp_id) as count … WHERE status='late' …")?.count || 0

  const absent = total – present

  const recentRecords = db.all("SELECT a.*, e.name … ORDER BY capture_time DESC LIMIT 10")

  res.json({ success: true, data: { total, present, late, absent, recentRecords } })

})

2.2.3 考勤终端打卡

前端考勤终端页面通过摄像头实时采集人脸图像,发送到人脸识别服务进行检测和比对,识别成功后自动创建考勤记录。支持活体检测防止照片欺骗,离线模式下自动存入队列等待网络恢复后上传。

// Scanner.tsx 核心检测循环

const detectAndSearch = async () => {

  const formData = new FormData()

  formData.append('image', captureFrame())

  const result = await apiPostForm('/api/fr/detect-and-search', formData)

  if (result.faces?.length > 0) {

    const face = result.faces[0]

    if (face.liveness?.is_real === false) {

      showToast('疑似照片,请真人打卡'); return

    }

    await apiPost('/api/attendance', {

      emp_id: face.emp_id, capture_time: new Date().toISOString(),

      device_id: deviceId, face_score: face.score

    })

    checkedInSet.add(face.emp_id)  // 60秒内不重复打卡

    playSuccessSound()

  }

}

2.3 员工管理模块

员工管理模块提供员工信息的完整CRUD操作,支持批量导入、模板下载、部门列表查询。人脸特征数据加密存储,临时解密文件使用随机数据覆写后删除,防止磁盘恢复攻击。

2.3.1 员工列表查询

支持分页、搜索(工号/姓名)、部门过滤。查询结果按创建时间倒序排列,返回员工基本信息和人脸注册状态。

router.get('/', (req, res) => {

  let whereClause = 'WHERE tenant_id = ?'; const params = [tenantId]

  if (search) { whereClause += ' AND (emp_id LIKE ? OR name LIKE ?)'; params.push(`%${search}%`, `%${search}%`) }

  if (department) { whereClause += ' AND department = ?'; params.push(department) }

  const employees = db.all(`SELECT * FROM employee ${whereClause}

    ORDER BY created_at DESC LIMIT ? OFFSET ?`, …params, pageSize, offset)

  res.json({ success: true, data: { items: employees, total, page } })

})

2.3.2 人脸特征安全擦除

当需要临时解密人脸特征文件进行比对时,使用完毕后先用随机数据覆写文件内容,再执行删除操作,确保敏感数据无法从磁盘恢复。

function rencryptFeatureFile(decryptedPath: string): void {

  const absolutePath = path.resolve(process.cwd(), decryptedPath)

  if (fs.existsSync(absolutePath)) {

    const stat = fs.statSync(absolutePath)

    const randomData = crypto.randomBytes(stat.size)

    fs.writeFileSync(absolutePath, randomData)  // 随机数据覆写

    fs.unlinkSync(absolutePath)                  // 安全删除

  }

}

2.4 请假管理模块

请假管理模块支持多种请假类型(年假、事假、病假等),提供请假申请、审批(通过/驳回)、撤销、假期余额查询等功能。审批通过后自动更新考勤记录,将请假日期的打卡状态标记为"leave"。

2.4.1 请假申请

员工提交请假申请时,系统自动检查日期冲突,防止重复请假。申请创建后自动向所有管理员发送审批通知。

router.post('/requests', (req, res) => {

  const conflict = db.get(`SELECT id FROM leave_request

    WHERE emp_id=? AND status IN ('pending','approved')

    AND start_date <= ? AND end_date >= ?`, emp_id, end_date, start_date)

  if (conflict) return res.status(400).json({ error: '该日期范围已有请假申请' })

  const result = db.run('INSERT INTO leave_request (…) VALUES (…)', …)

  // 发送审批通知给管理员

  const admins = db.all("SELECT id FROM user WHERE role='admin' AND status='active'")

  for (const admin of admins) {

    notificationDispatcher.dispatchLeaveApproval(admin.id, '', leaveInfo)

  }

  res.json({ success: true, data: { id: result.lastInsertRowid } })

})

2.4.2 审批通过

管理员审批通过后,系统自动将请假日期范围内的考勤记录状态更新为"leave",并发送结果通知给申请人。

router.post('/requests/:id/approve', (req, res) => {

  db.run("UPDATE leave_request SET status='approved', approver_id=?, approve_time=datetime('now','localtime')", …)

  // 更新考勤记录为请假状态

  db.run(`UPDATE attendance_record SET status='leave', reason=?

    WHERE emp_id=? AND date(capture_time) >= ? AND date(capture_time) <= ?

    AND status IN ('normal','late','early','absent')`, …)

  notificationDispatcher.dispatchLeaveResult(applicantUser.id, '', leaveInfo)

  res.json({ success: true })

})

2.5 加班管理模块

加班管理模块与请假模块结构类似,支持加班申请、审批、撤销、统计等功能。加班时长自动计入薪资计算。

router.post('/', (req, res) => {

  const { emp_id, date, start_time, end_time, hours, reason } = req.body

  const result = db.run('INSERT INTO overtime_request (…) VALUES (…)', …)

  const admins = db.all("SELECT id FROM user WHERE role='admin' AND status='active'")

  for (const admin of admins) {

    notificationDispatcher.dispatchOvertimeApproval(admin.id, '', overtimeInfo)

  }

  res.json({ success: true, data: { id: result.lastInsertRowid } })

})

2.6 薪资管理模块

薪资管理模块支持自定义薪资规则(基本工资、迟到扣款、加班费率等),一键生成月度薪资,自动计算迟到扣款、早退扣款、缺勤扣款、加班费等,支持CSV导出。

router.post('/generate', (req, res) => {

  const rules = db.all('SELECT * FROM salary_rule WHERE enabled=1 ORDER BY priority')

  const ruleMap = {}; rules.forEach(r => { ruleMap[r.type] = r })

  const baseSalary = ruleMap.base?.value || 0

  const lateDeductPerTime = ruleMap.late_deduct?.value || 0

  const overtimeRate = ruleMap.overtime_pay?.value || 1.5

  for (const emp of employees) {

    const lateCount = …; const overtimeHours = …

    const lateDeduct = lateCount * lateDeductPerTime

    const dailySalary = baseSalary / 22

    const overtimePay = overtimeHours * (dailySalary / 8) * overtimeRate

    const totalSalary = baseSalary – lateDeduct – earlyDeduct – absenceDeduct + overtimePay

    db.run('INSERT INTO salary_record (…) VALUES (…) ON CONFLICT DO UPDATE SET …', …)

  }

  res.json({ success: true, data: { generated, year, month } })

})

2.7 智能排班模块

排班模块支持排班模板管理(设置上下班时间、迟到/早退容忍时间)和员工排班分配(按星期分配模板)。支持批量排班、部门排班、事务性操作保证数据一致性。

router.post('/employee/:emp_id', (req, res) => {

  const { schedules } = req.body

  db.exec('BEGIN TRANSACTION')

  try {

    db.run('DELETE FROM employee_schedule WHERE emp_id=? AND tenant_id=?', emp_id, tenantId)

    for (const s of schedules) {

      db.run('INSERT INTO employee_schedule (emp_id, schedule_id, weekday, tenant_id) VALUES (?, ?, ?, ?)',

        emp_id, s.schedule_id, s.weekday, tenantId)

    }

    db.exec('COMMIT')

  } catch (err) { db.exec('ROLLBACK'); throw err }

})

2.8 人脸识别服务

人脸识别服务基于Python FastAPI框架,使用InsightFace引擎进行人脸检测、特征提取和比对。支持三种活体检测模式:静默活体(无感知分析)、动作挑战(眨眼/摇头/张嘴)、双重验证。后端通过fr-proxy路由代理请求到Python服务。

router.post('/extract', async (req, res) => {

  const rawBody = await collectRawBody(req)

  const response = await fetch(`${FR_SERVICE_URL}/fr/extract`, {

    method: 'POST', headers: { 'Content-Type': contentType }, body: rawBody,

  })

  const data = await response.json()

  res.status(response.status).json({ success: true, data })

})

2.9 访客管理模块

访客管理模块提供访客登记、人脸注册、签到签退、状态管理等功能。访客信息包含姓名、手机、公司、来访目的、接待人等字段,支持有效期设置。所有操作均按租户隔离。

2.10 通知系统

通知系统支持站内通知和邮件通知两种渠道。站内通知实时推送(WebSocket),邮件通知基于nodemailer。通知偏好可按类型配置,支持测试通知功能。请假审批、加班审批等业务事件自动触发通知。

2.11 智能报表模块

报表模块提供出勤趋势分析、部门统计、迟到热力图、异常列表、个人汇总等多种报表视图,支持Excel导出。数据按租户隔离,普通员工只能查看个人数据。

2.12 多租户与计费模块

系统支持多租户数据隔离,每个租户有独立的员工、考勤、设置等数据。计费模块提供用量统计、账单生成、财务概览等功能。支付模块集成微信支付和支付宝,支持订单管理、退款、模拟确认等。

租户注册功能当前已禁用(返回403),仅允许管理员通过后台创建租户。

2.13 授权管理模块

授权管理模块基于机器码+授权码机制,支持4种授权类型(试用版、标准版、专业版、企业版)。机器码通过系统硬件信息生成,授权码由管理端生成后激活。

2.14 数据迁移模块

数据迁移模块支持CSV格式的员工、考勤、排班数据导入,提供模板下载、数据预览、执行导入、导入历史等功能。同时支持SQLite到PostgreSQL的数据库迁移,通过DbMigrationService管理版本化迁移。

2.15 监控与运维模块

监控模块提供Prometheus格式的指标输出、JSON格式的API指标、仪表盘聚合数据。错误监控系统支持错误趋势分析、错误列表、健康状态、告警规则配置。结构化日志系统支持JSON Lines格式文件输出,按模块分类,包含traceId、tenantId等上下文信息。

class Logger {

  private module: string; private _traceId?: string; private _tenantId?: string

  private log(level, message, meta?, error?) {

    const entry = { timestamp, level, message, module: this.module, traceId, tenantId }

    if (error) entry.error = { name: error.name, message: error.message, stack: error.stack }

    addToBuffer(entry); outputToConsole(entry); outputToFile(entry)

  }

  withTrace(traceId) { return new Logger(this.module, traceId, …) }

  startTimer() { const start = Date.now(); return { end(msg) { this.log(INFO, msg, { duration: Date.now()-start }) } } }

}

2.16 隐私合规模块

隐私合规模块遵循GDPR要求,提供隐私协议管理、用户同意记录、数据导出(JSON格式)、账户删除等功能。用户可以查看自己的隐私同意状态,随时导出或删除个人数据。

三、前端架构

3.1 路由配置

前端采用HashRouter + React.lazy实现代码分割,所有30个页面均按需加载。路由分为公开路由(登录、安装)、管理路由(需认证+Layout包裹)、终端路由(需认证+ScannerLayout包裹)三类。

const Dashboard = lazy(() => import('@/pages/Dashboard'))

const Employees = lazy(() => import('@/pages/Employees'))

const Scanner = lazy(() => import('@/pages/Scanner'))

// … 30+ 页面懒加载

<Routes>

  <Route path="/login" element={<Login />} />

  <Route element={<ProtectedRoute><Layout /></ProtectedRoute>}>

    <Route path="/" element={<Dashboard />} />

    <Route path="/employees" element={<Employees />} />

    {/* … 20+ 管理路由 … */}

  </Route>

  <Route element={<ProtectedRoute><ScannerLayout /></ProtectedRoute>}>

    <Route path="/scanner" element={<Scanner />} />

  </Route>

</Routes>

3.2 状态管理

使用Zustand进行轻量级状态管理,认证状态持久化到localStorage,支持Token自动刷新和登出时服务端撤销。

export const useAuthStore = create<AuthState>((set, get) => ({

  token: localStorage.getItem('auth_token'),

  user: safeParse('auth_user', null),

  isAuthenticated: !!localStorage.getItem('auth_token'),

  setAuth: (token, user, refreshToken?) => {

    localStorage.setItem('auth_token', token)

    setRefreshToken(refreshToken || null)

    set({ token, user, isAuthenticated: true })

  },

  logout: () => {

    const rt = getRefreshToken()

    if (rt) fetch('/api/auth/logout', { method: 'POST', body: JSON.stringify({ refreshToken: rt }) })

    localStorage.removeItem('auth_token'); setRefreshToken(null)

    set({ token: null, user: null, isAuthenticated: false })

  },

}))

3.3 Token自动刷新

前端封装了fetchWithRefresh函数,当API请求返回401时自动使用Refresh Token获取新的Access Token并重试请求,实现无感刷新。

export async function refreshAccessToken(): Promise<string | null> {

  const token = getRefreshToken()

  if (!token) return null

  const res = await fetch('/api/auth/refresh', {

    method: 'POST', headers: { 'Content-Type': 'application/json' },

    body: JSON.stringify({ refreshToken: token })

  })

  const data = await res.json()

  if (data.success && data.data.token) {

    localStorage.setItem('auth_token', data.data.token)

    return data.data.token

  }

  return null

}

3.4 表单验证

使用Zod库定义表单验证Schema,覆盖员工表单、请假表单、加班表单、改密表单等核心场景,统一错误提示格式。

export const schemas = {

  employeeId: z.string().min(1, '工号不能为空').max(20).regex(/^[A-Za-z0-9_-]+$/),

  employeeName: z.string().min(1, '姓名不能为空').max(50),

  leaveReason: z.string().min(1, '请假原因不能为空').max(200),

  password: z.string().min(6, '密码至少6位'),

}

export const employeeFormSchema = z.object({ emp_id: schemas.employeeId, … })

export const leaveFormSchema = z.object({ … })

export function validateForm<T>(schema, data) { … }

3.5 国际化

系统支持中文和英文两种语言,共958个翻译键,覆盖所有页面和组件。翻译系统基于自定义Hook实现,支持运行时切换语言,翻译内容持久化到localStorage。

四、安全审计记录

4.1 审计概要

Phase 3安全审计共发现12个安全问题,其中严重3个、高危5个、中危4个,全部已修复。

4.2 严重问题修复

4.2.1 修改密码端点公开访问

问题:/api/auth/change-password 在 publicPaths 中,无需JWT认证即可访问,攻击者可暴力破解修改任意用户密码。修复:移出公开路径,使用JWT中的user.id代替请求体中的user_id,添加速率限制。

4.2.2 硬编码默认密码

问题:新建租户时管理员密码硬编码为"admin123"。修复:改用crypto.randomBytes(12)生成随机密码,并设置must_change_password=1。

4.2.3 跨租户数据泄露

问题:settings、visitors、holidays、audit等表的查询未按tenant_id过滤,允许跨租户访问数据。修复:所有查询添加WHERE tenant_id=?条件,数据库迁移添加tenant_id列和复合唯一索引。

4.3 高危问题修复

  • visitors删除/更新无tenant_id检查 — 已添加WHERE条件
  • holidays删除无tenant_id检查 — 已添加WHERE条件
  • audit日志无tenant_id过滤 — 已添加WHERE条件
  • 错误响应泄露内部信息 — db-migrate路由错误信息改为通用消息
  • .env.example弱默认JWT_SECRET — 移除默认值,添加生成命令提示

4.4 中危问题修复

  • error-tracker端点缺少管理员权限检查 — 已添加requireAdmin中间件
  • 输入净化未覆盖req.query — 已扩展中间件同时净化query参数
  • 注册/安装接口无速率限制 — 已添加authLimiter(5次/分钟)
  • 临时解密文件未安全擦除 — 已添加randomBytes覆写+unlink

4.5 安全措施总览

  • 密码哈希:scrypt + salt + timingSafeEqual防时序攻击
  • 人脸加密:AES-256-GCM + 随机IV + 认证标签
  • 登录限速:5次/15分钟/IP锁定
  • JWT:生产环境强制设置JWT_SECRET,24小时过期
  • CORS:白名单校验,生产环境不允许任意源
  • CSP:生产环境启用Content-Security-Policy
  • 输入净化:XSS过滤中间件覆盖body和query
  • 审计日志:所有敏感操作记录audit_log
  • 路径遍历防护:备份文件名检查..、/、\\

五、测试体系

5.1 测试覆盖

项目共23个测试文件,208个测试用例,覆盖API路由、前端组件、E2E场景和工具函数。

5.2 API测试

15个API测试文件,167个测试用例,覆盖所有核心路由模块。使用Vitest + supertest框架,每个测试文件独立创建测试数据库,测试完成后自动清理。

5.3 前端组件测试

3个前端测试文件,14个测试用例,覆盖Login、ChangePassword、Attendance核心页面。使用Vitest + Testing Library + jsdom环境,Mock了API调用和i18n。

5.4 E2E测试

4个Playwright E2E测试文件,12个测试场景,覆盖认证流程、页面导航、员工管理、API健康检查。使用Chromium浏览器,兼容中英文界面。

test('should login successfully', async ({ page }) => {

  await page.goto('/')

  await usernameInput.fill('admin')

  await passwordInput.fill('admin123')

  await page.locator('button[type="submit"]').click()

  await expect(page).toHaveURL(/\\/dashboard/, { timeout: 10000 })

})

六、构建与部署

6.1 Vite构建配置

使用Vite 6构建,配置了React插件、路径别名、Bundle可视化分析、代码分割策略。resolve.alias强制React单实例,resolve.dedupe防止重复打包。

export default defineConfig({

  plugins: [react(), tsconfigPaths(), visualizer({ filename: 'dist/stats.html' })],

  build: {

    outDir: 'dist/frontend',

    rollupOptions: { output: { manualChunks(id) {

      if (id.includes('node_modules')) {

        if (id.includes('echarts')) return 'vendor-charts'

        if (id.includes('exceljs')) return 'vendor-utils'

        if (id.includes('lucide')) return 'vendor-ui'

        return 'vendor'

      }

    } } },

  },

  resolve: { dedupe: ['react', 'react-dom'], alias: { react: path.resolve(__dirname, 'node_modules/react') } },

  server: { host: 'localhost', hmr: { protocol: 'ws', host: 'localhost' },

    proxy: { '/api': { target: 'http://localhost:3001', changeOrigin: true, ws: true } } }

})

6.2 TypeScript配置

已启用strict模式,包含noUnusedLocals、noUnusedParameters、noFallthroughCasesInSwitch等严格检查选项。

{

  "compilerOptions": {

    "target": "ES2020", "module": "ESNext", "jsx": "react-jsx",

    "strict": true, "noUnusedLocals": true, "noUnusedParameters": true,

    "noFallthroughCasesInSwitch": true, "forceConsistentCasingInFileNames": true,

    "baseUrl": "./", "paths": { "@/*": ["./src/*"] }

  }

}

6.3 Docker部署

Docker采用多阶段构建:阶段1构建前端,阶段2运行时包含Node.js + Python人脸服务。强制设置JWT_SECRET环境变量,配置健康检查。

FROM node:20-alpine AS frontend-builder

WORKDIR /app; COPY package*.json ./; RUN npm ci; COPY . .; RUN npm run build

 

FROM node:20-alpine

RUN apk add –no-cache python3 py3-pip

COPY –from=frontend-builder /app/dist ./dist

COPY api ./api; COPY face_recognition_service ./face_recognition_service

RUN cd face_recognition_service && pip install -r requirements.txt

EXPOSE 3001 3002; VOLUME ["/app/data"]

6.4 其他部署方案

  • PM2:双进程(API + 人脸服务),内存限制512M/1G,最大重启10次
  • Vercel:API路由重写 + SPA fallback,适合轻量部署
  • Electron:桌面应用打包,NSIS安装程序,包含Python人脸服务

七、数据库设计

7.1 表结构总览

系统共27张数据库表,采用SQLite WAL模式,启用外键约束,设置busy_timeout=5000ms。所有业务表均包含tenant_id字段实现多租户隔离。

7.2 核心表设计

7.2.1 employee(员工表)

字段:id, emp_id, name, department, position, phone, email, photo_path, feature_path, status, hire_date, face_registered, tenant_id, created_at, updated_at

7.2.2 attendance_record(考勤记录表)

字段:id, emp_id, capture_time, verify_method, device_id, photo_path, confidence, latitude, longitude, ip_address, type, face_score, status, reason, tenant_id

7.2.3 user(用户表)

字段:id, username, password_hash, role, emp_id, tenant_id, status, must_change_password, last_login, created_at, updated_at

7.2.4 leave_request(请假申请表)

字段:id, emp_id, leave_type_id, start_date, end_date, start_half, end_half, days, reason, status, approver_id, approve_time, tenant_id, created_at

7.2.5 salary_record(薪资记录表)

字段:id, emp_id, year, month, base_salary, overtime_pay, bonus, late_deduct, early_deduct, absence_deduct, leave_deduct, total_deduct, net_salary, tenant_id, created_at

7.3 索引策略

共25+个索引,覆盖主要查询字段:员工工号、考勤时间、部门、状态、租户ID等。复合唯一索引(key, tenant_id)确保设置项按租户隔离。

7.4 数据库类型定义

为所有数据库查询结果定义了TypeScript接口(api/types/db.ts),共25个接口,替代了大部分as any类型断言,提升类型安全性。

export interface EmployeeRow {

  id: number; emp_id: string; name: string; department: string

  position: string; phone: string | null; email: string | null

  status: string; hire_date: string | null; face_registered: number

  tenant_id: number | null; created_at: string; updated_at: string

}

export interface AttendanceRecordRow {

  id: number; emp_id: string; capture_time: string

  verify_method: string; device_id: string | null

  face_score: number | null; status: string; reason: string | null

  tenant_id: number | null

}

八、版本演进记录

8.1 v0.9.0(2026-06-09)

初始版本,完成28个前端页面、27个API路由模块、人脸识别服务、多租户支持、计费支付系统等核心功能。

8.2 v0.95.0(2026-06-10)

  • Zod表单验证(Employee/Leave/Overtime/ChangePassword)
  • Empty组件重写+9个列表页集成
  • console清理+JSON Lines文件日志
  • 9个新API测试文件
  • Refresh Token机制
  • 密码策略(8位+大小写+数字)

8.3 v0.97.0(2026-06-12)

  • TypeScript strict模式启用
  • any类型从~100处减少至<20处
  • 25个数据库行类型接口
  • 前端组件测试框架(Vitest + Testing Library)
  • Bundle分析与优化(visualizer + manualChunks)

8.4 v0.99 RC(2026-06-16)

  • 安全审计:12个安全问题全部修复
  • E2E测试框架(Playwright + Chromium)
  • 后端console全部替换为structured logger
  • CHANGELOG建立
  • 密码策略调整为6位最低要求
  • 注册功能关闭,仅允许admin账号登录
  • React多实例问题修复(移除react-dev-locator + resolve.alias)

九、后续开发计划

9.1 v1.0 正式版

  • 生产环境压力测试验证
  • 支付流程生产环境验证
  • 技术文档完善
  • 正式发布

9.2 v1.1(1-2个月后)

  • 数据看板增强(趋势分析、异常检测)
  • 自动排班算法优化
  • 钉钉/企微集成

9.3 v1.2(3-4个月后)

  • 移动端App(React Native/Taro)
  • 推送通知(FCM/APNs)
  • 生物识别快捷登录

9.4 v1.3(5-6个月后)

  • 多语言扩展(日语/韩语)
  • Redis分布式缓存
  • Kubernetes部署配置
  • 异地备份(S3/OSS)
赞(0)
未经允许不得转载:171主机测评 » AI人脸识别打卡签到系统2.0版本完成
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址