☰
基于SSM框架与Android的扫码签到系统:从架构设计到数据落地
2026/10/19 9:21:40 网站建设 项目流程

简介:基于安卓的学生签到系统是一份面向高校课堂考勤场景的完整项目资源,适合Android开发学习者、毕业设计或课程设计使用者。系统基于SSM后端框架与Android Studio前端实现,覆盖扫码签到、数字签到、定位签到三种方式,并支持对签到记录进行修改,还附带加课码选课功能,实用性强。资源包共3个文件,包含两个RAR工程压缩包与一个SQL初始数据库文件,整体大小约139.01MB:其中一个RAR对应Android前端工程,另一个为IntelliJ IDEA下的后端工程,SQL文件可帮助快速初始化数据库,目录结构清晰,便于导入与二次开发。已有838人学习下载,尤其适合需要参考完整前后端交互、定位与扫码逻辑,或希望在此基础上扩展功能的开发者;整体结构前后端分离,也可作为课程设计或毕业设计的参考基础。

1. 基于安卓的学生签到系统:从扫码到数据落库的一次完整闭环

大学课堂的签到一直是考勤管理的痛点,传统点名浪费时间,代签现象又很难杜绝。这个基于安卓的学生签到系统,采用 Android Studio 作为客户端开发工具,服务端基于 SSM(Spring + Spring MVC + MyBatis)框架,配合 IntelliJ IDEA 进行后端调试,实现了一套从学生扫码、教师端实时查看到考勤数据落库的完整流程。适合计算机专业做毕业设计、课程设计,也适合想系统学习 SSM 与 Android 联调的开发者参考。整个项目不是单一 App,而是包含了服务端接口、管理后台和安卓客户端三部分,跑起来之后能直接体验到完整的业务链路。

2. 系统架构与工程结构:客户端和服务端如何分工协作

2.1 整体技术选型:为什么是 SSM 而不是 Spring Boot

选择 SSM 而不是 Spring Boot,主要原因是教学场景和毕业设计中对框架原理的考察需求。SSM 的配置是显式可见的,Spring MVC 的处理器映射、MyBatis 的 SQL 绑定都能在 XML 文件中直观看到,这对理解框架运作机制很有帮助。Spring Boot 虽然开发效率高,但大量自动化配置把细节封装成了黑匣子,反而不利于学习。

服务端采用经典的三层架构:Controller 层接收安卓客户端的 HTTP 请求,Service 层处理业务逻辑,Mapper 层通过 MyBatis 操作 MySQL 数据库。安卓客户端则按照简易 MVP 思想组织,Activity 负责界面展示和用户交互,数据请求统一封装在独立的网络模块中。

2.2 Android 客户端工程目录:按功能模块分包

客户端工程是典型的 Android Studio 项目结构,业务代码集中在 app/src/main/java 目录下。包名按功能拆分为活动、适配器、网络、模型和工具五个部分,每个包职责单一,后期维护时定位问题非常快。

app/src/main/java/com/example/signin/ ├── activity/ │ ├── LoginActivity.java // 学生登录 │ ├── TeacherMainActivity.java // 教师主页,展示签到统计 │ └── StudentSignActivity.java // 学生扫码签到页 ├── adapter/ │ └── SignRecordAdapter.java // 签到记录列表适配器 ├── network/ │ ├── ApiClient.java // 网络请求客户端封装 │ ├── ApiService.java // 定义接口方法 │ └── ApiResponse.java // 统一响应实体 ├── model/ │ ├── Student.java // 学生实体 │ ├── Course.java // 课程实体 │ └── SignRecord.java // 签到记录实体 └── util/ ├── SharedPrefsUtil.java // 本地缓存登录状态 └── QRCodeScanner.java // 二维码扫描工具类

每个 Activity 只负责自己的业务场景,比如 LoginActivity 只处理登录请求和 Token 存储,StudentSignActivity 只负责扫码和提交签到。这种按业务分包的方式比按技术类型分包更容易理解,尤其适合多人协作开发。

2.3 服务端工程目录:SSM 标准分层

服务端采用 Maven 管理依赖,标准的 SSM 工程结构。使用 IntelliJ IDEA 打开后,可以直接导入为 Maven 项目,等待依赖下载完成即可启动。

signin-server/ ├── src/main/java/com/example/signin/ │ ├── controller/ │ │ ├── StudentController.java // 学生相关接口 │ │ ├── TeacherController.java // 教师相关接口 │ │ └── SignController.java // 签到核心接口 │ ├── service/ │ │ ├── SignService.java // 签到业务逻辑接口 │ │ └── impl/SignServiceImpl.java │ ├── mapper/ │ │ ├── StudentMapper.java │ │ ├── CourseMapper.java │ │ └── SignRecordMapper.java │ └── entity/ │ ├── Student.java │ ├── Course.java │ └── SignRecord.java ├── src/main/resources/ │ ├── spring-mvc.xml // Spring MVC 配置 │ ├── spring-mybatis.xml // 数据源和 MyBatis 配置 │ ├── mybatis/ │ │ ├── StudentMapper.xml │ │ ├── CourseMapper.xml │ │ └── SignRecordMapper.xml │ └── jdbc.properties // 数据库连接配置 └── pom.xml

2.4 数据库设计:三张核心表的关系

签到系统的核心数据模型围绕学生、课程和签到记录展开。学生表存储学生的基本信息和登录凭证,课程表存储开课信息,签到记录表则是整个系统的核心业务表,关联学生和课程。这里给出核心的建表语句。

CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) UNIQUE NOT NULL, student_name VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(100) NOT NULL, teacher_id INT NOT NULL, generate_code VARCHAR(64) UNIQUE NOT NULL, start_time DATETIME, end_time DATETIME ); CREATE TABLE sign_record ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, sign_time DATETIME DEFAULT CURRENT_TIMESTAMP, sign_status TINYINT DEFAULT 1, UNIQUE KEY uk_student_course (student_id, course_id) );

sign_record 表的联合唯一索引是关键设计,它保证了同一个学生对同一门课程只能签到一次,从数据库层面杜绝了重复提交的问题。generate_code 字段存储课程对应的二维码内容,每次发起签到时重新生成,学生扫码后服务端会根据这个 code 定位到对应的课程。

3. 运行环境准备与项目启动:从零到能跑通全流程

3.1 开发工具版本选择:这步最容易翻车

这个项目的运行依赖三个核心工具:Android Studio、IntelliJ IDEA 和 MySQL。Android Studio 建议使用 4.x 以上版本,配套的 Android SDK 版本建议 28 以上。IntelliJ IDEA 使用 2021 版本以上,需要配置好 Maven 3.6 和 JDK 1.8。MySQL 使用 5.7 或 8.0 均可,5.7 兼容性更好,8.0 需要注意数据库驱动版本。

血泪经验:JDK 版本是最大的坑。SSM 框架对 JDK 版本的兼容性非常敏感,用 JDK 11 编译 JDK 1.8 的项目经常会出现各种奇怪的反射异常。建议直接用 JDK 1.8 跑服务端,Android Studio 内部默认的 JDK 版本不会影响服务端,两侧独立使用,互不干扰。

3.2 MySQL 数据库初始化

数据库初始化是第一步。先创建数据库,然后导入项目提供的 SQL 文件即可。如果没有现成的 SQL 文件,就按第二章给出的三张表结构手动执行建表语句,然后插入一条测试数据。

mysql -u root -p CREATE DATABASE signin_system DEFAULT CHARACTER SET utf8mb4; USE signin_system; source /path/to/signin.sql;

utf8mb4 字符集是必须的,如果用了 utf8 字符集,保存表情符号时会报错。数据库字符集选错导致的乱码问题,在后期联调时排查起来非常耗时。建表完成后,可以手动插入一个学生账号和一个教师账号用于测试登录。

3.3 服务端启动流程:IntelliJ IDEA 配置 Tomcat

在 IntelliJ IDEA 中导入 Maven 项目后,需要配置 Tomcat 才能运行。这里的核心配置点是修改 jdbc.properties 中的数据库连接信息,包括账号、密码和 URL。

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/signin_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456

配置完成后,在 IDEA 的 Run Configuration 中添加 Tomcat Server,选择 Local 模式,Deployment 中点击加号选择 Artifact,类型选 war exploded。这里的玄学在于:很多人直接点右上角的运行按钮发现 Tomcat 没有正确配置 Deployment,导致访问接口一直 404。正确做法是 Deployment 里添加后,Application Context 设置为/signin,这样访问地址就是http://localhost:8080/signin/。

启动成功后,在浏览器访问http://localhost:8080/signin/api/ping,如果返回 JSON 数据说明服务端已经跑通。

3.4 安卓客户端连接服务端:IP 地址配置技巧

安卓客户端需要修改网络模块中的服务器地址。在 ApiClient.java 中,将 BASE_URL 改成电脑的局域网 IP,而不是本地回环地址。

public class ApiClient { // 注意:使用局域网IP,模拟器访问电脑不能用127.0.0.1 private static final String BASE_URL = "http://192.168.1.100:8080/signin/"; public static ApiService getApiService() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); return retrofit.create(ApiService.class); } }

这里的核心问题在于 Android 模拟器中的 127.0.0.1 指向的是模拟器自身,而不是电脑。模拟器访问宿主机的正确方式是使用 10.0.2.2,但如果用真机调试,则需要填电脑的局域网 IP。还有一个常见问题是 Android 9 及以上默认禁止 HTTP 明文请求,需要在 AndroidManifest.xml 中配置android:usesCleartextTraffic="true",否则所有接口请求都会失败。

4. 核心模块代码拆解:签到业务从请求到入库的完整链路

4.1 服务端:签到接口的业务逻辑实现

签到是系统的核心业务,它的完整流程是:学生扫码获取签到码,然后提交到服务端,服务端校验签到码有效性,校验学生是否已签到,最后写入数据库。这里看 SignServiceImpl 中的核心方法。

@Service public class SignServiceImpl implements SignService { @Autowired private SignRecordMapper signRecordMapper; @Autowired private CourseMapper courseMapper; @Override public Result sign(String signCode, Integer studentId) { // 1. 根据签到码查找对应的课程 Course course = courseMapper.selectByGenerateCode(signCode); if (course == null) { return Result.error("签到码无效"); } // 2. 校验签到时间是否在有效期内 Date now = new Date(); if (now.before(course.getStartTime()) || now.after(course.getEndTime())) { return Result.error("不在签到时间范围内"); } // 3. 校验是否重复签到 SignRecord existing = signRecordMapper.selectByStudentAndCourse(studentId, course.getId()); if (existing != null) { return Result.error("请勿重复签到"); } // 4. 插入签到记录 SignRecord record = new SignRecord(); record.setStudentId(studentId); record.setCourseId(course.getId()); record.setSignTime(now); record.setSignStatus(1); signRecordMapper.insert(record); return Result.success("签到成功"); } }

这段代码展示了典型的业务校验链。第一层校验签到码是否存在,这能过滤掉乱扫的二维码;第二层校验时间有效期,防止学生在课程开始前或结束后提交签到;第三层通过数据库的唯一索引做兜底校验。实际开发中,前三步的校验顺序不能颠倒,因为签到码查询是后续所有操作的前提。

4.2 服务端:二维码生成与课程绑定

教师端发起签到时,服务端生成一个随机字符串作为签到码,创建课程记录并返回给客户端。客户端将签到码编码为二维码展示在屏幕上。这里用的是 UUID 的变体,加上了时间戳做混淆。

public String generateSignCode(Integer teacherId) { // 生成规则:teacherId + 时间戳 + 随机数,然后取MD5后截取前16位 String raw = teacherId + "_" + System.currentTimeMillis() + "_" + ThreadLocalRandom.current().nextInt(1000, 9999); String code = MD5Util.md5(raw).substring(0, 16); return code; }

这个生成规则的重点在于不可预测性。如果只用纯数字递增的签到码,学生可以提前构造一个相邻的签到码完成签到,形成一种变相的作弊。加入时间戳和随机数之后,签到码的有效期由课程表里的 startTime 和 endTime 控制,过期自动失效,不需要额外设置状态位。

4.3 客户端:扫码模块的工程实现

安卓端扫码使用了 ZXing 库,核心逻辑封装在 QRCodeScanner 工具类中。扫码界面通过 SurfaceView 预览相机画面,在回调中解析出二维码内容后自动发起签到请求。

public class QRCodeScanner implements ZXingLiteScanner.Callback { private ZXingLiteScanner scanner; private OnScanResultListener listener; public void startScan(Activity activity) { scanner = new ZXingLiteScanner(activity); scanner.setCallback(this); // 设置扫码框大小,单位是像素 scanner.setScanBoxSize(240, 240); } @Override public void onResult(String rawResult) { // rawResult 即签到码,直接回调给业务层 if (listener != null) { listener.onScanSuccess(rawResult); } } }

扫码得到的 rawResult 就是服务端生成的签到码。拿到这个字符串后,客户端调用 ApiService 中的 sign 接口将签到码和当前登录学生的 ID 一起提交。ZXingLiteScanner 封装了对相机权限的处理,但在 Android 6.0 以上,仍然需要在 Activity 中动态申请相机权限,否则扫码界面会黑屏。这个权限属于运行时权限,只在 AndroidManifest 中声明是不够的。

4.4 客户端:签到记录展示的适配器实现

教师端查看签到情况是本系统的另一个核心功能。列表页使用 RecyclerView 展示所有学生的签到状态,核心代码在 SignRecordAdapter 中。

public class SignRecordAdapter extends RecyclerView.Adapter<SignRecordAdapter.ViewHolder> { private List<SignRecord> records; @Override public void onBindViewHolder(ViewHolder holder, int position) { SignRecord record = records.get(position); holder.tvStudentName.setText(record.getStudentName()); holder.tvSignTime.setText(DateFormatUtils.format(record.getSignTime(), "yyyy-MM-dd HH:mm:ss")); // 根据签到状态显示不同颜色 if (record.getSignStatus() == 1) { holder.tvStatus.setText("已签到"); holder.tvStatus.setTextColor(Color.parseColor("#4CAF50")); } else { holder.tvStatus.setText("未签到"); holder.tvStatus.setTextColor(Color.parseColor("#F44336")); } } }

这段代码的业务逻辑很简单,但提示了一个开发习惯:不要在适配器中直接操作数据库或发送网络请求。适配器的职责是绑定数据到视图,数据的获取和计算应该放在更上层的 Activity 或 ViewModel 中。如果直接把 Retrofit 调用写在 onBindViewHolder 里,数据刷新时会频繁触发请求,同时内存泄漏的风险也会增加。

5. 常见问题与避坑指南:配置环境时容易翻车的五个细节

5.1 项目导入后无法识别为 Maven 项目

现象:用 IntelliJ IDEA 打开服务端源码后,pom.xml 没有被识别,右键项目也没有 Maven 相关选项。原因是 IDEA 没有自动识别 Maven 工程类型,或者导入时选择了错误的打开方式。

解决:直接关闭项目窗口,回到 IDEA 欢迎页,选择 Open,定位到服务端根目录时不要直接点 OK,而是选中 pom.xml 文件再点击 Open。在弹出的对话框中选择 Open as Project,IDEA 就会自动识别并下载 Maven 依赖。如果依赖下载缓慢,可以修改 Maven 的 settings.xml,配置国内的镜像源。

5.2 Android 9 以上设备无法访问 HTTP 接口

现象:客户端运行后,所有网络请求都报 IOException,错误信息是 CLEARTEXT communication not permitted。这个问题是因为 Android 9 开始,系统默认禁止应用使用明文 HTTP 协议访问网络。

解决:在 AndroidManifest.xml 的 application 标签中添加android:usesCleartextTraffic="true"。生产环境更规范的做法是配置 network-security-config,只允许特定域名使用明文流量。但在本地调试阶段,直接开启这个开关是最快的方案。

5.3 模拟器无法访问电脑上的服务端接口

现象:使用 Android Studio 自带模拟器运行客户端,登录时一直提示超时。很多人以为是服务端没启动或者 IP 地址配错了,实际上问题出在模拟器的网络机制上。

解决:Android 模拟器中 127.0.0.1 指向模拟器自身,要想访问宿主机,必须使用 10.0.2.2。如果客户端跑在真机上,则需要填写电脑在当前局域网内的 IP 地址。还有一点极易忽略:电脑的防火墙可能拦截了 8080 端口的入站请求,需要在防火墙中放行 Java 进程或直接开放 8080 端口。

5.4 Linux 和 macOS 下 Tomcat 启动端口冲突

现象:启动服务端时,Tomcat 报端口被占用错误。通常是因为电脑上已经运行了其他 Java 进程占用了 8080 端口。解决方法是先查看占用端口的进程,再决定是杀掉进程还是修改 Tomcat 端口。

# Windows下查看端口占用 netstat -ano | findstr 8080 # Linux/macOS 下查看端口占用 lsof -i :8080

如果不想杀掉占用端口的进程,可以直接修改 Tomcat 的 server.xml 中 Connector 的 port 属性,改成 8081 或其他未被占用的端口。改了端口后,记得同步修改安卓客户端里的 BASE_URL 和数据库连接配置。

5.5 MyBatis XML 映射文件扫描不到

现象:服务端启动时没有报错,但调用 Mapper 接口方法时抛出 Invalid bound statement (not found) 异常。原因是 MyBatis 在初始化时没有扫描到对应的 XML 文件。

解决:在 spring-mybatis.xml 中加入 mapperLocations 配置,显式指定 XML 文件位置。这个坑只会在 Maven 结构中遇到,因为 Maven 默认不会把 src/main/java 下的 XML 文件打包到 classes 目录,如果 Mapper 接口和 XML 放在同一个包下,就会导致运行时找不到 XML。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mybatis/*.xml" /> </bean>

6. 二次开发指南:把二维码签到升级为带照片验证的签到系统

这个项目预留了很多可以扩展的切入点。这里分享一个实际改造案例:在原有扫码签到基础上增加拍照验证的功能。改造前思考一下,二维码签到最大的问题是学生可以截屏签到码发给同学代签。增加拍照验证后,学生扫码时需要拍摄一张现场照片,服务端把照片和已存储的该学生入学照片做比对,相似度超过阈值才允许签到。

技术选型上,客户端需要增加一个拍照模块。Android 原生提供 Intent 调起相机,拿到照片后压缩上传到服务端。服务端的人脸比对可以使用开源库,常见的方案是两个:一个是调用第三方云服务的人脸比对接口,另一个是本地做人脸特征提取和余弦相似度计算。

接口设计层面,在 SignController.java 中新增一个接口。

@PostMapping("/signWithPhoto") @ResponseBody public Result signWithPhoto(@RequestParam("signCode") String signCode, @RequestParam("studentId") Integer studentId, @RequestParam("file") MultipartFile file, HttpServletRequest request) { // 1. 保存照片到本地磁盘或OSS String photoUrl = uploadFile(file); // 2. 调用人脸比对服务 boolean verified = faceCompareService.compare(photoUrl, getStudentPhotoUrl(studentId)); if (!verified) { return Result.error("人脸校验未通过"); } // 3. 执行原有签到逻辑 return sign(signCode, studentId); }

这个改造步骤的关键在于保持原有 sign 方法的复用性。签到逻辑没有改变,只是在签到的前置链路中增加了人脸校验这一步。这样原有测试用例仍然可以复用,新增代码的风险范围被控制在新增链路内,相当于给系统加了一个可插拔的校验器。

改造完成后,实际验证时用了一个例子:A同学用手机扫描二维码,拍摄了自己的照片,签到成功;然后让 B 同学拿着手机扫描同一个二维码,拍摄的却是 A 同学的照片,人脸比对失败,签到被拒绝。这个验证方式确认了改造方案的可行性。从那以后,我每次做类似功能改造,都强制走一遍第三方服务依赖注入、原有逻辑保持兼容、失败场景模拟这三步检查流程。希望这个项目的解析和改造思路能帮到你,让你在跑通这个签到系统的路上少踩几个坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询