Flutter+springboot实现考勤码+gps考勤签到功能

  • 实现步骤和思路:
  • 设计
  • 发布考勤任务
  • 签到考勤
  • 环境准备
  • 后端步骤:
  • 1.教师发布考勤任务接口
  • 2.rabbitmq收到教师发布考勤执行的步骤代码接口
  • 3.考勤过期后,rabbitmq执行的代码
  • 4.websocket推送,以及离线消息缓存,上线取出发送,删除缓存
  • 5.学生考勤接口
  • 前端flutter app
  • 需要用到的插件
  • 2. 发布成功,通过provide更新UI,把考勤记录记入本地换成,并且利用计时器倒计时
  • websocket接收考勤消息推送和处理
  • 1.websocket接收数据
  • 2.websocket处理解析数据
  • 3. 利用NotificationsUtil通知用户,并使用EventBus消息总线将处理好的数据推送到相关的UI界面
  • 4. 要显示跟新的UI界面监听消息总线通道,取出收到的数据写入本地并通过provide刷新UI
  • 效果预览图
  • 总结


本人正在做一个智慧课堂辅助app的毕设作品,刚好有一个功能是考勤签到,正好实现了,分享一下经验。
主要实现有 1.app教师用户发布数字或gps考勤任务,gps考勤可以设置考勤地点和距离范围内有效,并且能设置考勤失效时间。后端将考勤任务通过websocket推送到该课程的所有学生,学生收到后在指定时间地点内输入考勤码完成签到,过期不签到的同学将被系统设置为旷课。利用redis做离线消息缓存七天,离线用户的将消息存储到redis中,用户上线后取出redis离线消息推送。

实现步骤和思路:

后端springboot:
1.教师通过restful风格接口创建考勤任务,生成数据库记录
2. 在该创建考勤接口中使用rabbitmq来执行三个步骤:

1.生成需要签到的学生名单记录到数据库,赋默认状态值-1,表示需要签到;2.将需要考勤的消息通过websocket推送给前端app,3.使用redis把教师创建的考勤任务记入,并设置过期时间为教师设置的有效时间。

3.在创建考勤接口最后一步使用rabbitmq的消息延迟队列去跟新数据库状态为-1(需要考勤)的记录修改为缺勤的状态,并把缺勤的记录通过websocket推送到学生前端。 消息延迟队列时间为考勤有效时间
4. 利用redis做离线消息缓存

前端flutter:
1.教师使用高德地图插件定位获取考勤位置,设置考勤距离和有效时间,通过http请求发送到后端;
3. 成功通过Timer计时器来显示剩余时间,并且使用provide状态管理来保持时间显示;
4. 考勤期间收到完成考勤的学生数据,使用provide插入本地并更新名单记录;
5. 考勤结束收到后端websocket推送的学生缺勤名单,provide更新数据;

学生:1.收到教师发布的考勤任务,前往考勤页面点击考勤,完成考勤更新UI,如果失败,也跟新UI界面

使用到的工具和框架有:

springboot
spring-data-jpa
mysql
redis
rabbitmq
rabbitmq消息延迟队列插件

设计

发布考勤任务

课堂创建者可以发布考勤任务让学生用户在指定的时间内完成考勤。发布的考勤主要分两种方式,第一种是数字考勤,学生只需要在指定的时间内通过考勤码完成考勤任务即可;第二种为GPS考勤,是在第一种考勤的方式上,设置指定考勤的地点范围,只有在指定的地点范围内完成考勤,才算成功,否则系统视为缺勤。该功能主要涉及的工具和技术有Reids非关系型数据库、RabbitMQ消息中间件、消息延时队列、Websocket。服务端具体的设计过程如下:

(1) 客户端通过请求Restful风格接口创建考勤任务,生成数据库记录;

(2) 在该创建考勤任务接口中利用RabbitMQ消息中间件来异步完成三个步骤:

①生成需要签到的学生名单(userId)批量写入到数据库,考勤状态(status)赋默认状态值-1,表示需要完成签到任务;

②将考勤消息通过websocket推送给需要完成考勤任务的所有用户App客户 端;

③把教师用户创建的考勤任务记录写入Redis,并设置过期时间为教师用户指 定的时间(expireTime)。

(3) 利用RabbitMQ的延时队列去修改学生考勤记录状态(status)为-1的记录统一修改为状态为0,表示学生缺勤,未能在指定时间内完成考勤任务,并用Websocket推送缺勤名单给考勤任务创建者;

(4) 需要完成考勤的学生用户在考勤有效时间内完成考勤,数据库学生考勤状态(status)为-1的修改为1,表示出勤, 并用Websocket推送缺勤名单给考勤任务创建者。

Flutter
App客户端Gps考勤使用到高德地图的定位接口,主要分为以下几个步骤:

(1)在Flutter的pubspec.yaml文件中引入高德地图插件:

amap_location_fluttify: 0.8.11+481e45c #高德地图

(2) 到高德地图开放平台(https://lbs.amap.com/)注册开发者,并创建应用;

(3) 在Android的AndroidManifest.xml文件下添加高德地图创建应用生成的Key

(4) 在调用高德地图接口插件前,调用以下代码,初始化接口:

WidgetsFlutterBinding.ensureInitialized();

Await AmapCore.init(‘21f35eb097c0cd048f7668194525ba7a’);

发布考勤任务功能的流程如图。

springboot线下会议签到 springboot学生签到功能_springboot线下会议签到

签到考勤

springboot线下会议签到 springboot学生签到功能_springboot线下会议签到_02

学生用户在收到教师用户发布的考勤任务后,将收到系统提示的考勤消息。在相应的课堂考勤列表中出现需要完成的考勤记录,如果发布的是数字考勤,通过教师告诉学生的考勤码完成签到,如果是GPS考勤,则需要手机定位后在输入考勤码签到。如果考勤任务时间过期,或者考勤用户的定位地点不在指定地点范围内将直接视为缺勤,无法再签到。具体实现流程如图所示。

环境准备

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>25.1-jre</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

后端步骤:

1.教师发布考勤任务接口

springboot线下会议签到 springboot学生签到功能_springboot线下会议签到_03

2.rabbitmq收到教师发布考勤执行的步骤代码接口

springboot线下会议签到 springboot学生签到功能_java_04

3.考勤过期后,rabbitmq执行的代码

springboot线下会议签到 springboot学生签到功能_java_05

4.websocket推送,以及离线消息缓存,上线取出发送,删除缓存

springboot线下会议签到 springboot学生签到功能_springboot线下会议签到_06


用户上线后,websocket取出redis离线消息推送,并且清除离线缓存的数据

@OnPen接口

springboot线下会议签到 springboot学生签到功能_springboot线下会议签到_07

5.学生考勤接口

///学生考勤
@RequestMapping(value = "AttendanceStudentCheck", method = {RequestMethod.GET, RequestMethod.POST})
public BaseResult AttendanceStudentCheck(@Valid @RequestBody AttendanceStudentDto attendanceStudentDto,
                                         HttpServletRequest request) {
    String key = RedisKeyUtil.Attendance.getAttendanceExpireKey(attendanceStudentDto.getAttendanceId());
    Attendance attendance = (Attendance) redisService.get(key);
    String studentId = (String) request.getAttribute("X-AUTH-ID");
    if (attendance == null) {//考勤失败,缺勤
        attendanceStudentService.updateStudentStatus(studentId, attendanceStudentDto, 3, 0);
        return BaseResult.ok(3);
    }
    boolean isSuccess = false;
    double d = 0;
    if (attendance.getAttendCode().equals(attendanceStudentDto.getAttendCode())) {
        if (attendanceStudentDto.getType() == 0) {//数字考勤
            //考勤成功
            isSuccess = true;
        } else {//gps
            BigDecimal lat = attendance.getLatitude();
            BigDecimal lon = attendance.getLongitude();
            double distance = attendance.getDistance();
            attendanceStudentDto.getLatitude();
            attendanceStudentDto.getLongitude();
            d = DistanceUtil.earthDis(lat.doubleValue(), lon.doubleValue(),
                    attendanceStudentDto.getLatitude().doubleValue(),
                    attendanceStudentDto.getLongitude().doubleValue());
            System.out.println("distance: " + d);
            if (d <= distance) {
                isSuccess = true;
            }
        }
    }
    if (isSuccess) {
        AttendanceStudent attendanceStudent =
                attendanceStudentService.updateStudentStatus(studentId, attendanceStudentDto, 0, d);
        Map<String, Object> map = new HashMap<>();
        map.put("method", "teacher/createAttendance");
        map.put("publisherId", attendance.getPublisherId());
        try {
            redisRabbitProvider.websocketSend(attendanceStudent, map);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return BaseResult.ok(0);
    } else {
        return BaseResult.ok(-1);
    }

}

前端flutter app

需要用到的插件

dio: ^3.0.8
 provide: ^1.0.2     #状态管理
 shared_preferences: ^0.5.1
 flutter_local_notifications: ^1.1.5+1 #
 permission_handler: ^4.2.0+hotfix.3 #权限库
 amap_location_fluttify: 0.8.6+356f11c #高德地图
 flutter_event_bus: ^0.0.2

主要思路

教师通过dio 发送http请求创建考勤任务,

经过封装的发布考勤接口,

springboot线下会议签到 springboot学生签到功能_java_08

2. 发布成功,通过provide更新UI,把考勤记录记入本地换成,并且利用计时器倒计时

自定义的provide+计时器工具类

class TimeExpire{
   int expire=0;
   double minutes = 0;
   int second = 0;

}

///倒计时器
class ExpireTimerProvide with ChangeNotifier{

  Map<int, Timer> timerMap = Map();
  Map<int, TimeExpire>  timeExpire=Map();
  ///
  void init(){
    timerMap.clear();
    timeExpire.clear();

  }
  //开始计时方法
  startCountdownTimer(Duration d, int id) {
    int counterTime = d.inSeconds;
    print('进来 ${counterTime}');
    TimeExpire expireT=TimeExpire();
    expireT.expire=counterTime;
    expireT.minutes= counterTime / 60;
    expireT.second = counterTime % 60;
    timeExpire.putIfAbsent(id, ()=>expireT);
    var oneMinuute = Duration(seconds: 1);
    var callback = (Timer timer) async {
     // print(timer.tick);
      if (timeExpire[id].expire < 1) {
         timerMap[id].cancel();
        if (timerMap.containsKey(id)) {
          timerMap.remove(id);
        }
        print('remove :${timerMap}');
      } else {
        int temp=timeExpire[id].expire;
        timeExpire[id].expire =temp- 1;
        timeExpire[id].minutes = temp / 60;
        timeExpire[id].second = temp % 60;
      }
      notifyListeners();
    };
    timerMap.putIfAbsent(id, ()=>Timer.periodic(oneMinuute, callback));
  }

}

显示计时UI代码

springboot线下会议签到 springboot学生签到功能_springboot线下会议签到_09

websocket接收考勤消息推送和处理

1.websocket接收数据

springboot线下会议签到 springboot学生签到功能_推送_10

2.websocket处理解析数据

springboot线下会议签到 springboot学生签到功能_推送_11

3. 利用NotificationsUtil通知用户,并使用EventBus消息总线将处理好的数据推送到相关的UI界面

springboot线下会议签到 springboot学生签到功能_springboot线下会议签到_12

推送总线消息

Application.eventBus.publish(要推送的数据);

4. 要显示跟新的UI界面监听消息总线通道,取出收到的数据写入本地并通过provide刷新UI

springboot线下会议签到 springboot学生签到功能_java_13

完整代码已经上传到github项目,做为项目的某部分,项目将继续完善
github:coureapp源码

效果预览图

springboot线下会议签到 springboot学生签到功能_flutter_14


springboot线下会议签到 springboot学生签到功能_springboot线下会议签到_15


springboot线下会议签到 springboot学生签到功能_springboot线下会议签到_16


springboot线下会议签到 springboot学生签到功能_推送_17


springboot线下会议签到 springboot学生签到功能_推送_18

总结

最终圆满完成考勤功能,推送消息没有问题。本人做后端的,不擅长前端,UI做的太丑了,求手下留情!