基于关系型数据库的传统签到系统!

164 阅读4分钟

点击上方“程序员蜗牛g”,选择“设为星标”跟蜗牛哥一起,每天进步一点点程序员蜗牛g大厂程序员一枚 跟蜗牛一起 每天进步一点点31篇原创内容**公众号

最直接的签到系统实现方式是利用关系型数据库(如MySQL、PostgreSQL)记录每次签到行为。

这种方案设计简单,易于理解和实现,适合大多数中小型应用场景。

-- 用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 签到记录表
CREATE TABLE check_ins (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    check_in_time TIMESTAMP NOT NULL,
    check_in_date DATE NOT NULL,
    check_in_type VARCHAR(20) NOT NULL, -- 'DAILY', 'COURSE', 'MEETING' 等
    location VARCHAR(255),
    device_info VARCHAR(255),
    remark VARCHAR(255),
    FOREIGN KEY (user_id) REFERENCES users(id),
    UNIQUE KEY unique_user_date (user_id, check_in_date, check_in_type)
);

-- 签到统计表
CREATE TABLE check_in_stats (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    total_days INT DEFAULT 0,
    continuous_days INT DEFAULT 0,
    last_check_in_date DATE,
    FOREIGN KEY (user_id) REFERENCES users(id),
    UNIQUE KEY unique_user (user_id)
);

 核心代码实现

实体类设计

@Data
@TableName("check_ins")
public class CheckIn {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    @TableField("user_id")
    private Long userId;
    
    @TableField("check_in_time")
    private LocalDateTime checkInTime;
    
    @TableField("check_in_date")
    private LocalDate checkInDate;
    
    @TableField("check_in_type")
    private String checkInType;
    
    private String location;
    
    @TableField("device_info")
    private String deviceInfo;
    
    private String remark;
}

@Data
@TableName("check_in_stats")
public class CheckInStats {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    @TableField("user_id")
    private Long userId;
    
    @TableField("total_days")
    private Integer totalDays = 0;
    
    @TableField("continuous_days")
    private Integer continuousDays = 0;
    
    @TableField("last_check_in_date")
    private LocalDate lastCheckInDate;
}

@Data
@TableName("users")
public class User {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    private String username;
    
    private String password;
    
    private String email;
    
    @TableField("created_at")
    private LocalDateTime createdAt;
}

Mapper层

@Mapper
public interface CheckInMapper extends BaseMapper<CheckIn> {
    
    @Select("SELECT COUNT(*) FROM check_ins WHERE user_id = #{userId} AND check_in_type = #{type}")
    int countByUserIdAndType(@Param("userId") Long userId, @Param("type") String type);
    
    @Select("SELECT * FROM check_ins WHERE user_id = #{userId} AND check_in_date BETWEEN #{startDate} AND #{endDate} ORDER BY check_in_date ASC")
    List<CheckIn> findByUserIdAndDateBetween(
            @Param("userId") Long userId, 
            @Param("startDate") LocalDate startDate, 
            @Param("endDate") LocalDate endDate);
    
    @Select("SELECT COUNT(*) FROM check_ins WHERE user_id = #{userId} AND check_in_date = #{date} AND check_in_type = #{type}")
    int existsByUserIdAndDateAndType(
            @Param("userId") Long userId, 
            @Param("date") LocalDate date, 
            @Param("type") String type);
}

@Mapper
public interface CheckInStatsMapper extends BaseMapper<CheckInStats> {
    
    @Select("SELECT * FROM check_in_stats WHERE user_id = #{userId}")
    CheckInStats findByUserId(@Param("userId") Long userId);
}

@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    @Select("SELECT * FROM users WHERE username = #{username}")
    User findByUsername(@Param("username") String username);
}

Service层

@Service
@Transactional
public class CheckInService {
    
    @Autowired
    private CheckInMapper checkInMapper;
    
    @Autowired
    private CheckInStatsMapper checkInStatsMapper;
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 用户签到
     */
    public CheckIn checkIn(Long userId, String type, String location, String deviceInfo, String remark) {
        // 检查用户是否存在
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new RuntimeException("User not found");
        }
        
        LocalDate today = LocalDate.now();
        
        // 检查今天是否已经签到
        if (checkInMapper.existsByUserIdAndDateAndType(userId, today, type) > 0) {
            throw new RuntimeException("Already checked in today");
        }
        
        // 创建签到记录
        CheckIn checkIn = new CheckIn();
        checkIn.setUserId(userId);
        checkIn.setCheckInTime(LocalDateTime.now());
        checkIn.setCheckInDate(today);
        checkIn.setCheckInType(type);
        checkIn.setLocation(location);
        checkIn.setDeviceInfo(deviceInfo);
        checkIn.setRemark(remark);
        
        checkInMapper.insert(checkIn);
        
        // 更新签到统计
        updateCheckInStats(userId, today);
        
        return checkIn;
    }
    
    /**
     * 更新签到统计信息
     */
    private void updateCheckInStats(Long userId, LocalDate today) {
        CheckInStats stats = checkInStatsMapper.findByUserId(userId);
        
        if (stats == null) {
            stats = new CheckInStats();
            stats.setUserId(userId);
            stats.setTotalDays(1);
            stats.setContinuousDays(1);
            stats.setLastCheckInDate(today);
            
            checkInStatsMapper.insert(stats);
        } else {
            // 更新总签到天数
            stats.setTotalDays(stats.getTotalDays() + 1);
            
            // 更新连续签到天数
            if (stats.getLastCheckInDate() != null) {
                if (today.minusDays(1).equals(stats.getLastCheckInDate())) {
                    // 连续签到
                    stats.setContinuousDays(stats.getContinuousDays() + 1);
                } else if (today.equals(stats.getLastCheckInDate())) {
                    // 当天重复签到,不计算连续天数
                } else {
                    // 中断连续签到
                    stats.setContinuousDays(1);
                }
            }
            
            stats.setLastCheckInDate(today);
            
            checkInStatsMapper.updateById(stats);
        }
    }
    
    /**
     * 获取用户签到统计
     */
    public CheckInStats getCheckInStats(Long userId) {
        CheckInStats stats = checkInStatsMapper.findByUserId(userId);
        if (stats == null) {
            throw new RuntimeException("Check-in stats not found");
        }
        return stats;
    }
    
    /**
     * 获取用户指定日期范围内的签到记录
     */
    public List<CheckIn> getCheckInHistory(Long userId, LocalDate startDate, LocalDate endDate) {
        return checkInMapper.findByUserIdAndDateBetween(userId, startDate, endDate);
    }
}

Controller层

@RestController
@RequestMapping("/api/check-ins")
public class CheckInController {
    
    @Autowired
    private CheckInService checkInService;
    
    @PostMapping
    public ResponseEntity<CheckIn> checkIn(@RequestBody CheckInRequest request) {
        CheckIn checkIn = checkInService.checkIn(
                request.getUserId(),
                request.getType(),
                request.getLocation(),
                request.getDeviceInfo(),
                request.getRemark()
        );
        return ResponseEntity.ok(checkIn);
    }
    
    @GetMapping("/stats/{userId}")
    public ResponseEntity<CheckInStats> getStats(@PathVariable Long userId) {
        CheckInStats stats = checkInService.getCheckInStats(userId);
        return ResponseEntity.ok(stats);
    }
    
    @GetMapping("/history/{userId}")
    public ResponseEntity<List<CheckIn>> getHistory(
            @PathVariable Long userId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
        List<CheckIn> history = checkInService.getCheckInHistory(userId, startDate, endDate);
        return ResponseEntity.ok(history);
    }
}

@Data
public class CheckInRequest {
    private Long userId;
    private String type;
    private String location;
    private String deviceInfo;
    private String remark;
}

1.4 优缺点分析

优点:

  • • 设计简单直观,易于理解和实现
  • • 支持丰富的数据查询和统计功能
  • • 事务支持,确保数据一致性
  • • 易于与现有系统集成

缺点:

  • • 数据量大时查询性能可能下降
  • • 连续签到统计等复杂查询逻辑实现相对繁琐
  • • 不适合高并发场景
  • • 数据库负载较高

1.5 适用场景

  • • 中小型企业的员工考勤系统
  • • 课程签到系统
  • • 会议签到管理
  • • 用户量不大的社区签到功能

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!