专注小程序 / APP 开发,用代码改变生活

获取方案
全国分站 主站首页 北京分站 石家庄分站 唐山分站 保定分站 廊坊分站 沧州分站 郑州分站

ThinkPHP开发小程序签到功能实战

开发笔记 2026-10-02 1 阅读 0 点赞 原创

签到功能几乎是小程序的标配模块,看起来简单,真正落地时却涉及连续签到判定、积分奖励、防重复签到、时区处理等一堆细节。本文基于 ThinkPHP 6 分享一套可以直接复用的签到功能实现方案,包含数据表设计、后端接口与核心业务逻辑。

一、数据表设计

签到功能至少需要两张表:一张记录用户的签到流水,一张记录用户的积分与连续签到状态。分开设计的好处是流水表可以按时间归档,而状态表只保留一行,查询效率高。

-- 签到流水表
CREATE TABLE `sign_log` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int unsigned NOT NULL,
  `sign_date` date NOT NULL COMMENT '签到日期',
  `days` int unsigned NOT NULL DEFAULT 1 COMMENT '连续天数',
  `points` int unsigned NOT NULL DEFAULT 0 COMMENT '本次获得积分',
  `create_time` int unsigned NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_date` (`user_id`,`sign_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 用户签到状态表
CREATE TABLE `user_sign` (
  `user_id` int unsigned NOT NULL,
  `points` int unsigned NOT NULL DEFAULT 0 COMMENT '累计积分',
  `continuous_days` int unsigned NOT NULL DEFAULT 0 COMMENT '当前连续天数',
  `last_sign_date` date DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意 uk_user_date 这个唯一索引,它是防止并发重复签到的最后一道防线,比在代码里查一次再插入可靠得多。

二、连续签到与积分规则

常见的奖励规则是:基础 5 积分,连续第 7 天额外奖励 20 积分,断签后连续天数归零。规则建议抽成独立方法,方便后期改成配置化。

protected function calcPoints(int $days): int
{
    $points = 5;
    if ($days % 7 === 0) {
        $points += 20;
    }
    return $points;
}

三、核心签到逻辑

签到接口的关键在于:判断今天是否已签到、判断昨天是否签到以决定连续天数、写流水与更新状态必须放在同一个事务里。

public function sign()
{
    $userId = $this->request->uid;
    $today  = date('Y-m-d');
    $yesterday = date('Y-m-d', strtotime('-1 day'));

    $exist = Db::name('sign_log')
        ->where('user_id', $userId)
        ->where('sign_date', $today)
        ->find();
    if ($exist) {
        return json(['code' => 1001, 'msg' => '今天已经签过啦']);
    }

    $state = Db::name('user_sign')->where('user_id', $userId)->find();
    if (!$state) {
        Db::name('user_sign')->insert([
            'user_id' => $userId,
            'points'  => 0,
            'continuous_days' => 0,
        ]);
        $state = ['continuous_days' => 0, 'last_sign_date' => null];
    }

    $days = ($state['last_sign_date'] === $yesterday)
        ? $state['continuous_days'] + 1
        : 1;
    $points = $this->calcPoints($days);

    Db::startTrans();
    try {
        Db::name('sign_log')->insert([
            'user_id'    => $userId,
            'sign_date'  => $today,
            'days'       => $days,
            'points'     => $points,
            'create_time'=> time(),
        ]);
        Db::name('user_sign')->where('user_id', $userId)->update([
            'points'          => Db::raw('points + ' . $points),
            'continuous_days' => $days,
            'last_sign_date'  => $today,
        ]);
        Db::commit();
    } catch (\Throwable $e) {
        Db::rollback();
        return json(['code' => 1002, 'msg' => '签到失败,请重试']);
    }

    return json([
        'code' => 0,
        'msg'  => '签到成功',
        'data' => ['days' => $days, 'points' => $points],
    ]);
}

这里用 Db::raw('points + n') 做原子自增,避免读改写导致的积分丢失。即使并发请求同时进来,唯一索引也会让第二条插入失败并触发回滚。

四、日历与状态查询接口

小程序端通常需要一个日历视图,展示本月哪些天已签到。可以一次返回本月签到日期数组和当前状态。

public function info()
{
    $userId = $this->request->uid;
    $month  = $this->request->param('month', date('Y-m'));
    $start  = $month . '-01';
    $end    = date('Y-m-t', strtotime($start));

    $days = Db::name('sign_log')
        ->where('user_id', $userId)
        ->whereBetween('sign_date', [$start, $end])
        ->column('sign_date');

    $state = Db::name('user_sign')->where('user_id', $userId)->find();

    return json([
        'code' => 0,
        'data' => [
            'signed_days' => array_map(fn($d) => (int)date('j', strtotime($d)), $days),
            'points'      => $state['points'] ?? 0,
            'continuous'  => $state['continuous_days'] ?? 0,
            'today_signed'=> in_array(date('Y-m-d'), $days),
        ],
    ]);
}

五、几个容易踩的坑

  • 时区问题:ThinkPHP 默认时区可能不是 Asia/Shanghai,务必在 config/app.php 中设置 default_timezone,否则凌晨签到会算错日期。
  • 补签需求:如果产品要求补签,不要把补签写进 sign_log 的普通流程,建议单独加 is_makeup 字段并限制次数。
  • 防刷:签到接口建议加上用户级限流,ThinkPHP 的 think-throttle 中间件即可满足。
  • 积分流水:真实项目里积分变动最好单独记一张流水表,方便对账和排查问题。

整体思路就是:唯一索引兜底、事务保证一致性、原子自增防并发。这套代码在日活几千的小程序上跑过一年,没有出现过重复签到或积分错乱的问题,可以直接拿去改造使用。

ThinkPHP实战:小程序登录与手机号获取2026年小程序开发的五个关键趋势

评论 (0)

还没有评论,快来抢沙发吧~

好想法,值得被认真交付

从小程序、APP 到全栈网站,一站式把想法变成可落地的产品

微信咨询

微信扫码,直接沟通需求