news 2026/10/22 8:22:35

基于Django与人脸识别的考勤系统设计与实现全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Django与人脸识别的考勤系统设计与实现全解析

做考勤系统时,我最常被问到一个问题:为什么不用刷卡机?其实不是不用,而是刷卡机的漏洞太多,代刷、忘刷、丢卡都是管理上的老大难。后来接触了基于Django和人脸识别的考勤系统,发现这条路才真正解决“人、时间、记录”三者对不上的问题——它既不依赖物理介质,又能把考勤数据直接落到数据库里,统计和回溯都很方便。这篇文章就把我个人动手实现这套系统的主要思路、踩过的坑和完整流程拆开来讲,给正在做课程设计、毕业设计或者公司内部小工具的同学一个可以照抄的参考。

1. 项目背景与方案选型

1.1 传统考勤方式的痛点

我最早给一家小公司做内部考勤时,用的是某厂商的指纹机。看起来挺可靠,但实际用起来问题很多:员工指纹磨损导致识别失败,排队打卡浪费时间,考勤数据还得一个个导出来再合并到Excel,月底对账能熬掉半条命。换刷卡或二维码,又会面临代刷、截屏转发的问题,很多人已经形成习惯“帮同事打一下卡”。

人脸识别考勤的出现,核心解决两件事:第一,考勤动作必须本人“到场”;第二,记录直接电子化,不用人工二次整理。人脸作为生物特征,不需要额外携带卡片,也不易被他人轻易替代,所以很适合作为考勤系统的身份凭证。

当然,人脸识别不是万能的,它也有光线、角度、遮挡等方面的限制,这是后话。但在技术选型时,我们首先要清楚:这个项目要覆盖多少员工?每天的数据量有多大?是否需要实时提醒?这些决定了我们是用纯Django服务端渲染,还是加上前端摄像头实时识别。

1.2 为什么用Django而不是其他框架

如果只是写一个人脸识别脚本,用Flask甚至直接写个Python脚本就够了,但考勤系统是一个完整的信息系统,它需要用户管理、权限控制、数据库建模、日志记录、报表输出这些能力。Django在这方面优势非常明显:

  • 自带ORM,数据库模型改起来方便,不需要写一堆原生SQL;
  • Admin后台开箱即用,管理人员可以快速维护员工信息、部门信息;
  • Session和认证体系完整,普通用户和管理员能有效区分权限;
  • 模板引擎配合Bootstrap,几分钟就能搭出一个可用的管理界面。

很多人觉得Django“重”,但考勤系统本身就需要这些重量级能力。如果选择一个轻量框架,后面要补认证、权限、分页、迁移这些模块,工作量反而更大。尤其是Django的migration机制,当你把员工表加一个字段时,一条命令就能同步到数据库,这在长期迭代中特别省心。

1.3 人脸识别方案选型

人脸识别是整个系统的核心,方案选型决定识别精度和开发成本。目前常见的有几种:

方案优点缺点适用场景
OpenCV Haar Cascade文件小、速度快、部署简单检测率低,容易误检,特征可靠性差人脸检测实验、入门Demo
Dlib + HOG检测稳定,CPU可运行,社区成熟特征提取较慢,模型表达能力有限中低并发的人脸检测与识别
Face Recognition库封装了Dlib和深度学习特征,接口简洁模型文件约100MB+,内存占用偏高中小型考勤系统、快速开发
MTCNN + FaceNet / ArcFace精度高,支持大姿态、复杂背景依赖深度学习框架,推理耗时,需要GPU加速大规模考勤、安防等高要求场景

我最终选择的是Face Recognition库。理由很直接:它把“人脸检测—人脸对齐—特征提取—相似度比对”整个过程封装成了几个函数,部署起来比MTCNN+FaceNet简单得多,识别精度在光线正常的室内环境下足够用。对于几百人规模的公司,纯Python后端的推理速度也能接受,如果以后想提高并发,可以把特征比对部分抽出来单独部署。

提示:Face Recognition库底层依赖Dlib,在Windows上安装时容易遇到编译问题。建议用Python 3.8配合预编译的轮子,或直接使用Linux环境,能少走很多弯路。

2. 系统整体架构与数据库设计

2.1 前后端交互方式选择

我在这个项目里用了“Django模板+少量Ajax”的方式。这意味着页面渲染由Django完成,摄像头截帧和人脸比对则通过浏览器里的JavaScript把图片传到后端接口,再由视图函数返回识别结果。

为什么不用前后端分离?因为考勤系统是一个内部工具,并发量不大,Django模板直接生成页面足以满足需求。前后端分离会让项目结构更复杂,但收益并不明显。如果你打算把它做成对外SaaS产品,那就应该用Django REST Framework提供API,前端用Vue或React单独部署。我后续重构时也是往这个方向走的。

不过这里必须注意一个关键点:摄像头在浏览器里面获取视频流,通常在本地localhost或者HTTPS环境下才能运行。如果部署到内网IP,浏览器会限制getUserMedia接口。解决办法是在Django项目里配置SECURE_SSL_REDIRECT,或者用反向代理加SSL证书。开发阶段可以直接在settings.py里设置CSRF_TRUSTED_ORIGINS允许内网IP访问。

2.2 模块划分

我把系统拆成了三个功能模块:

  • 员工管理模块:负责部门、员工、人脸照片的录入与维护;
  • 考勤记录模块:负责上下班打卡、异常状态判断、补卡及查询;
  • 人脸识别模块:负责将摄像头照片转为128维人脸特征向量,并做一对一或一对多比对。

模块之间通过Django的APP机制隔离。每个APP只做自己负责的事情,例如人脸识别模块只提供注册和识别接口,不直接操作打卡记录。这样后期替换算法或调整考勤规则时,改动范围能控制在一个模块内。

2.3 数据库表设计

数据库设计直接关系到后续统计是否顺畅。我最初只设了一张员工表和一张打卡记录表,结果发现“一个员工对应多张人脸特征”时根本没有地方存,导致重新训练时反复改表。后来我把表分成了四张核心表:

  • Department:部门表,保存部门名称和负责人;
  • Employee:员工表,关联部门,保存工号、姓名、职位、入职状态;
  • FaceImage:人脸特征表,关联员工,保存照片路径和128维特征编码;
  • AttendanceRecord:考勤记录表,关联员工,保存打卡时间、识别分数、打卡类型和异常状态。

人脸特征存储是我踩坑最多的地方。Dlib生成的128维向量是一串浮点数,不能直接存到普通的CharField里。我一开始用json.dumps存成字符串,后面发现比对时要反复解析,效率很低。后来改成BinaryField,直接存向量序列化后的字节,再用numpy.frombuffer还原,这样明显快了一些。

下面是简化版models.py:

import pickle from django.db import models class Department(models.Model): name = models.CharField(max_length=64, unique=True, verbose_name='部门名称') class Employee(models.Model): emp_no = models.CharField(max_length=20, unique=True, verbose_name='工号') name = models.CharField(max_length=64, verbose_name='姓名') department = models.ForeignKey( Department, on_delete=models.SET_NULL, null=True, verbose_name='部门' ) position = models.CharField(max_length=64, blank=True, verbose_name='职位') is_active = models.BooleanField(default=True, verbose_name='是否在职') class FaceImage(models.Model): employee = models.ForeignKey( Employee, on_delete=models.CASCADE, related_name='faces', verbose_name='员工' ) image = models.ImageField(upload_to='faces/', verbose_name='人脸照片') encoding = models.BinaryField(verbose_name='人脸特征编码') created_at = models.DateTimeField(auto_now_add=True, verbose_name='注册时间') class AttendanceRecord(models.Model): STATUS = [ ('normal', '正常'), ('late', '迟到'), ('early', '早退'), ('leave', '请假'), ('overtime', '加班'), ] employee = models.ForeignKey( Employee, on_delete=models.CASCADE, related_name='attendance', verbose_name='员工' ) punch_time = models.DateTimeField(auto_now_add=True, verbose_name='打卡时间') status = models.CharField(max_length=20, choices=STATUS, default='normal', verbose_name='考勤状态') recognition_score = models.FloatField(default=0, verbose_name='识别相似度') photo_path = models.CharField(max_length=255, blank=True, verbose_name='打卡照片路径') record_date = models.DateField(verbose_name='考勤日期')

这里特别说明:AttendanceRecord新增了record_date字段,不要只依赖punch_time做日期查询。因为一天内可能有多次打卡(上午上班、下午上班、下班),单独用时间戳查询容易出错,加上日期字段后,统计日考勤和月考勤都会清晰很多。

2.4 关键流程设计

人脸注册流程:管理员上传员工照片,系统用人脸检测库从照片中提取人脸,如果检测不到或检测到多张人脸则提示“照片不合格”,检测到单个人脸则生成128维特征编码,存入FaceImage表。员工可以注册多张人脸照片,例如正面、侧面各一张,以解决角度和表情变化带来的识别率下降。

打卡流程:员工点击打卡按钮,浏览器打开摄像头预览画面,系统自动从视频流中截取一帧,将其上传到后端的识别接口。后端先做人脸检测,如果画面中没有检测到人脸,直接返回错误;如果检测到,则提取特征,再和库里的所有员工特征做距离比对。当最小距离小于阈值时,判定为对应员工,然后查询当天是否已经打过卡,如果打过则更新记录,否则生成一条新记录,最后返回员工信息、打卡时间和状态。

这样设计的好处是,后端不需要持久化保存每次打卡的原图,只保留特征编码和用于问题追溯的截图路径,兼顾了存储成本和审计需求。

3. 核心功能实现与踩坑记录

3.1 人脸注册与特征提取

注册功能的核心是把“照片文件”变成“特征向量”。我封装了一个公共方法:

import face_recognition import numpy as np import pickle def extract_face_encoding(image_path): image = face_recognition.load_image_file(image_path) face_locations = face_recognition.face_locations(image) if len(face_locations) != 1: return None, '照片中必须且只能包含一张人脸' face_encoding = face_recognition.face_encodings(image, face_locations)[0] return face_encoding, None

注意,如果照片里有多个人,比如身份证照片和集体照乱传,系统必须拒绝注册,否则会污染特征库。我的做法是先判断len(face_locations) != 1,直接返回错误,而不是取最大的人脸。这一点在多人照片比较多的企业内部环境里非常重要。

保存特征向量时,用pickle.dumps(face_encoding)序列化到BinaryField:

face_image = FaceImage( employee=employee, image=uploaded_file, encoding=pickle.dumps(face_encoding), ) face_image.save()

有些同学会把encoding存成JSON字符串,虽然开发时读起来方便,但数据库查询和比对时反而要多一步解析,性能不升反降。推荐直接用二进制字段,代码也更干净。

3.2 实时识别与考勤判定

摄像头截图上传的后端视图函数大概是这样的:

import base64 import pickle from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt import face_recognition import numpy as np @csrf_exempt def punch(request): if request.method != 'POST': return JsonResponse({'error': '仅支持POST请求'}, status=405) # 前端传的是 base64 图片数据 image_data = request.POST.get('image') if not image_data: return JsonResponse({'error': '缺少图像数据'}, status=400) try: image_bytes = base64.b64decode(image_data.split(',')[1]) with open('temp.jpg', 'wb') as f: f.write(image_bytes) except Exception: return JsonResponse({'error': '图像数据格式错误'}, status=400) image = face_recognition.load_image_file('temp.jpg') face_locations = face_recognition.face_locations(image) if len(face_locations) == 0: return JsonResponse({'error': '未检测到人脸,请调整角度和光线'}) face_encoding = face_recognition.face_encodings(image, face_locations)[0] # 从数据库中读取所有已注册员工的特征 known_faces = [] known_employees = [] for face_image in FaceImage.objects.select_related('employee').all(): encoding = pickle.loads(bytes(face_image.encoding)) known_faces.append(encoding) known_employees.append(face_image.employee) if len(known_faces) == 0: return JsonResponse({'error': '人脸库为空,请先注册'}) # 计算最相似的人脸 distances = face_recognition.face_distance(known_faces, face_encoding) min_distance_index = np.argmin(distances) min_distance = distances[min_distance_index] # 阈值设为 0.45,可依据实际环境调整 if min_distance > 0.45: return JsonResponse({'error': '未识别人脸,请靠近摄像头再试'}) employee = known_employees[min_distance_index] # 当天判重,避免短时间重复打卡 from datetime import date, datetime today = date.today() existing = AttendanceRecord.objects.filter( employee=employee, record_date=today, punch_time__gt=datetime.combine(today, time(12, 0)), ).first() status = 'normal' now = datetime.now() if now.time() > time(9, 0): status = 'late' AttendanceRecord.objects.create( employee=employee, punch_time=now, status=status, recognition_score=round(float(min_distance), 4), record_date=today, ) return JsonResponse({ 'employee': employee.name, 'status': status, 'time': now.strftime('%H:%M:%S'), 'score': round(float(min_distance), 4), })

这个阈值0.45怎么定的?我实际测试中,同一个人在不同角度的face_distance一般在0.30到0.42之间,两个人的距离通常大于0.50。所以0.45是一个比较稳妥的临界点。如果现场光线暗、手机拍到的人脸模糊,可以适当放宽到0.50,但要注意识别混淆的风险。最稳妥的方式是每次注册时让员工正对摄像头拍摄,而不是只用一张早期照片。

3.3 考勤统计与报表设计

考勤系统除了打卡,更需要月底的统计报表。Django的ORM天然支持聚合查询,这里用annotate和Count就很方便。

比如统计一个人当月迟到次数:

from django.db.models import Count from datetime import date current_month_start = date.today().replace(day=1) late_count = AttendanceRecord.objects.filter( employee=employee, status='late', record_date__gte=current_month_start, ).count()

如果想做月度汇总,可以用Values按员工分组:

from django.db.models import Sum summary = AttendanceRecord.objects.filter( record_date__year=year, record_date__month=month, ).values('employee__name', 'employee__emp_no').annotate( normal_count=Count('id', filter=models.Q(status='normal')), late_count=Count('id', filter=models.Q(status='late')), early_count=Count('id', filter=models.Q(status='early')), )

这里要注意一点:考勤状态不应该只靠打卡时间硬算。我在实际项目里最开始就是“超过9:00就算迟到”,结果发现有些人是出差、外勤、漏打卡,全被判成迟到,月底解释成本很高。后来我在设计上增加了AttendanceRecord.status的可修改性,管理员可以在后台手动把状态改成“请假”或“正常”,同时记录修改人。真正合理的考勤规则应该是“系统初判 + 人工复核”,不要期望一个规则覆盖所有现实情况。

关于报表展示,我用的是Django模板加json把统计数据传到前端,再用ECharts画柱状图和饼图。如果你不想引入额外前端库,直接用HTML表格导出CSV也完全够用。

3.4 定时任务与异常处理

很多考勤系统还需要在每天下班后自动统计当日异常打卡人员,然后发通知给管理员。这里我用Django的APScheduler来跑定时任务。

安装scheduler配置:

from apscheduler.schedulers.background import BackgroundScheduler from django_apscheduler.jobstores import DjangoJobStore scheduler = BackgroundScheduler() scheduler.add_jobstore(DjangoJobStore(), 'default') def daily_report(): from datetime import date, timedelta today = date.today() records = AttendanceRecord.objects.filter(record_date=today) # 统计异常打卡人员并发送通知 # 这里省略具体实现 scheduler.add_job( daily_report, trigger='cron', hour=21, minute=0, id='daily_report', replace_existing=True, )

定时任务的坑主要在内存泄漏和重复执行。开发环境用runserver跑没问题,但生产环境如果用多进程方式启动,可能会启动多个调度器。我的解决方案是单独用一条指令启动一个后台服务,或者直接在系统的cron里调用Django的管理命令,避免调度器互相抢任务。

4. 常见问题与排查技巧实录

4.1 人脸识别准确率低怎么办

准确率问题几乎是必然遇到的。最常见的几个原因和解决办法:

  • 光线不足:建议在打卡机位置增加补光灯,或要求员工正对光源;
  • 逆光导致人脸过暗:可在图像送入识别前做直方图均衡化;
  • 角度倾斜:注册时多存两张不同角度的照片;
  • 照片分辨率太低:摄像头截图时设置更高分辨率,或在上传前做放大处理;
  • 人脸库过大导致比对时间变长:可以按部门分组,优先比对该员工所在部门的特征,减少距离计算次数。

如果现场人脸重复签到问题严重,除了提高阈值,更有效的办法是采用“活体检测”。Face Recognition库本身不带活体检测,需要额外集成眨眼、张嘴等动作指令识别,或者使用深度相机红外图像。对于一般考勤系统,加入随机动作指令就能挡住大部分屏幕翻拍攻击。

注意:不要为了追求识别率把阈值无限放宽。阈值过宽的后果是,两个长相相近的员工会被系统互相识别,这种错误在人事考勤里影响很大,宁可识别失败让员工重新打卡,也不要误判成另一个人。

4.2 重复打卡和并发问题

用户在摄像头前多逗留一秒,可能就会触发多次上传,导致同一个人连续产生好几条打卡记录。我在项目里做了两个层面的限制:

第一层是数据库约束。在AttendanceRecord中添加一个逻辑上的“唯一打卡”字段,比如employee + record_date + punch_period,其中punch_period分为上午、下午。也就是说,一个员工一天最多有两条记录,一条上午上班,一条下午下班(或其他班次)。用ORM创建记录时捕获IntegrityError,再返回“已经打过卡”的提示。

第二层是接口幂等。在视图函数里加一个时间窗口,比如5秒内同一个员工不重复生成记录。用Redis存last_punch:{employee_id},过期时间是5秒,这样能很好地避免用户重复操作。如果你不想引入Redis,也可以用Django内置的cache,但多进程环境下缓存一致性不如Redis。

下面这个片段是我在项目里实际用的Redis锁简化版:

import redis from django.conf import settings rds = redis.Redis(host='localhost', port=6379, db=0) def punch_once(employee_id): key = f'punch_lock:{employee_id}' if rds.set(key, '1', ex=5, nx=True): return True return False

4.3 摄像头截帧上传的浏览器兼容问题

在Chrome上跑通的摄像头打卡页面,换到Edge或旧版Safari就出问题。这种情况通常是getUserMedia的适配问题。我推荐直接用navigator.mediaDevices.getUserMedia,但要注意浏览器安全上下文限制,非HTTPS或localhost环境下它是不工作的。

在我的实践里,内网开发环境最省事的方案是直接用Django自带runserver配合localhost访问,调试时先把功能跑通。上线到内网服务器时,再配置Nginx反向代理加自签名SSL证书。如果你所在网络环境不允许外网请求,那可以在前端把视频流分辨率固定为640x480,这样也能降低识别耗时。

4.4 模型推理速度慢与内存占用高

Dlib的人脸识别模型在CPU上跑一次特征提取大约需要0.4到0.8秒,对于打卡这种交互场景勉强够用。我刚开始把face_recognition.face_encodings写在每次请求里,导致项目并发上来之后CPU被打满,接口响应时间变成2秒以上。后来优化的方法是:模型和特征库加载后常驻内存,不每次请求重新初始化模型。

在Django里可以用AppConfig.ready()成员变量保存模型实例:

from django.apps import AppConfig import face_recognition class FaceConfig(AppConfig): name = 'face' model = None def ready(self): # 在Django启动时预先加载人脸模型 FaceConfig.model = face_recognition.face_encodings

虽然这只是很小的改动,但效果非常明显。另外,如果员工数量很大,建议把特征向量加载到内存缓存里,避免每次请求都查一遍数据库。几百人的系统,哪怕从SQLite读一遍也要好几十毫秒,累积下来很影响体验。

5. 优化方向与项目感悟

5.1 从软件到硬件的部署思路

软件跑通后,如果想要部署成独立打卡机,可以考虑用树莓派或Jetson Nano这类小型硬件作为终端,摄像头直接连在设备上。Django服务部署在中心服务器上,树莓派只负责采集图片并发送HTTP请求。这样做的优点是企业不需要购买商业考勤机,成本低廉且功能可控。但如果员工数量很多,网络带宽和中心服务器压力会成为一个新问题,需要考虑边缘计算——在终端上直接完成识别,再只把“员工工号+打卡时间”上报给Django。

从个人实践来看,如果只是为了验证算法,直接在Django上跑没问题;如果真想放到公司门口,最好把识别任务独立成服务,不要让Django进程同时承担Web服务和模型推理。可以把识别接口单独部署到另一台机器上,通过内部API调用。

5.2 安全与合规的几个细节

考勤数据属于员工敏感个人信息,尤其是人脸特征,一旦泄露会产生法律风险。我在系统里至少做了以下几件事:

  • 人脸特征编码不能直接还原成原始人脸照片,存储时再做一层加密;
  • 打卡照片保存路径使用随机文件名,不包含员工姓名和工号;
  • 管理员操作要有日志记录,后台登录使用强密码并启用两步验证;
  • 数据库定期备份,备份文件单独加密保存。

这些点听起来很简单,但我见过不少课程设计和内部项目完全没做安全处理,这样的人脸考勤系统反而比传统考勤更危险。安全合规不是加分项,而是底线要求。尤其是“人脸特征”这类生物识别信息,相关法规对收集和存储都有严格约束,做项目之前一定要想清楚自己的数据从哪来、存多久、谁能访问。

5.3 我做完这个项目后的几点体会

这类系统在技术上不算难,难点在于把它做成一个真正可用、能落地的软件。我在实际开发过程中最深的体会是:不要一开始就追求复杂功能,先把“注册—打卡—统计”这条主链路跑通,再逐步加排班、请假、补卡、报表导出。看似简单的考勤,背后牵扯到考勤规则、加班计算、倒班逻辑,这些业务需求比人脸识别算法本身复杂得多。

另外,做课程设计或毕设的同学,建议把文档也写得完整一些,包括系统结构图、数据库设计说明、测试用例和识别准确率报告,这些材料在答辩时比代码更有说服力。如果是有打算开源这个项目,可以在Django框架基础上把前端界面做得更干净一点,并把配置抽成环境变量,这样其他同学拿到代码后,不需要改代码就能直接跑起来。

人脸识别考勤这件事,我认为未来会有两个趋势:一是从“识别你是谁”走向“确认你就是你”,活体检测和多模态验证会越来越普及;二是系统会从单一考勤延伸到“员工全生命周期管理”,跟请假、绩效串联起来。不过无论怎么发展,“数据准确、流程规范、可回溯”这三个基础,始终是考勤系统最重要的事。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 8:22:19

多智能体系统企业落地实践:从架构设计到成本优化

最近很多做企业AI落地的朋友都在聊同一个词:多智能体系统。单个大模型当助手已经不够用了,大家开始琢磨怎么让多个AI像真实团队一样分工协作——有人负责拆解任务,有人负责查资料,有人负责写方案,还有人负责把关校验。…

作者头像 李华
网站建设 2026/10/22 8:20:43

亲眼看AI干活:从聊天到真正执行任务的智能体实践

“亲眼看AI,真能干活了!”——说实话,过去一年我听到太多“AI只是个高级聊天机器人”的说法,自己也一度这么认为。直到最近我专门搭了个环境,把AI丢进一个真实的编程任务里,不光是让它“说”,而…

作者头像 李华
网站建设 2026/10/22 8:20:24

Kubernetes Deployment故障排查:从三层结构到根因定位

1. 故障排查的底层逻辑设计——先给大脑装一张“地图” 先说个我一直坚持的观点:排查 Kubernetes Deployment 故障,真正难的不是某个具体命令不会敲,而是面对一坨报错时不知道从哪里下手。Kubernetes 的报错信息天生就“分层”,有…

作者头像 李华
网站建设 2026/10/22 8:20:24

AI辅助学术专著写作:从框架推演到成稿的实战指南

前阵子帮一位做社会学研究的老师整理手头的专著资料,他跟我抱怨:书稿拖了快三年,目录改了四版,每章开头写起来都特别费劲,资料堆了一桌子但不知道从哪下笔。后来我建议他试试用AI专著撰写工具做"预写"和&quo…

作者头像 李华
网站建设 2026/10/22 8:19:40

家庭财务管理系统源码实战:从解压到本地部署的完整指南

简介:这是一套完整可用的家庭财务管理系统源码,面向软件开发初学者、课程设计学生及个人开发者,解决家庭收支记录、预算控制与财务分析等实际需求。资源共200个文件,包含66个C#后端逻辑文件(.cs)、27个ASPX…

作者头像 李华
网站建设 2026/10/22 8:18:27

Spring Boot + Vue构建医疗用品商城:全栈开发实战指南

做医疗用品销售商城这个项目,最初并不是因为有多宏大的规划,纯粹是朋友那边需要一个能跑起来的电商系统,要求不复杂:能上架商品、用户能注册登录、能加购物车、能下单,后台能管订单和库存。技术选型他们没硬性规定&…

作者头像 李华