从零到一:用 Python Flask 构建签到领取权益金服务并发布到华为云开发者展览馆

举报
yd_291797886 发表于 2026/11/01 19:26:28 2026/11/01
【摘要】  本文记录了如何用 Python + Flask 开发一个完整的签到领取权益金 API 服务,通过 DevBridge 隧道分享给外部用户,并最终发布到华为云开发者作品展览馆的全过程。## 一、项目背景在很多运营活动中,「签到领奖励」是最常见的用户留存手段之一。用户每天登录签到,连续签到天数越多,奖励越丰厚。这次我决定用 Python Flask 实现一个完整的签到领取权益金后端服务,包含以...

 本文记录了如何用 Python + Flask 开发一个完整的签到领取权益金 API 服务,通过 DevBridge 隧道分享给外部用户,并最终发布到华为云开发者作品展览馆的全过程。

## 一、项目背景

在很多运营活动中,「签到领奖励」是最常见的用户留存手段之一。用户每天登录签到,连续签到天数越多,奖励越丰厚。这次我决定用 Python Flask 实现一个完整的签到领取权益金后端服务,包含以下功能:

- 用户注册与登录(JWT 认证)
- 每日签到(防重复,连续签到加成)
- 权益金领取
- 余额与历史查询

## 二、技术选型

| 组件 | 选择 | 理由 |
|------|------|------|
| Web 框架 | Flask 3.x | 轻量、生态成熟、适合中小型 API 服务 |
| 数据库 | SQLite | 零配置,Python 内置库直接支持,适合演示和小规模应用 |
| 认证 | PyJWT | 轻量 JWT 库,无状态认证,适合 RESTful API |
| 密码存储 | pbkdf2_hmac + salt | Python 标准库 hashlib 内置,无需额外依赖 |

选 Flask 而非 FastAPI/Django,是因为这个项目逻辑简单、路由不多,Flask 的「微框架」特性正好——不引入不必要的复杂度。SQLite 不需要单独装数据库服务进程,一个文件搞定,部署成本极低。

## 三、系统设计

### 3.1 数据库表结构

```
users       用户表       (id, username, password_hash, created_at)
checkins    签到记录     (user_id, checkin_date, consecutive_days, reward_amount, claimed)
claims      领取记录     (user_id, checkin_id, amount, created_at)
balances    余额表       (user_id, balance, updated_at)
```

四张表的关系很直接:用户注册写入 `users`;每次签到在 `checkins` 写一条记录,同时计算连续天数和奖励金额;用户领取权益金时,在 `claims` 写记录并更新 `balances` 余额。

### 3.2 权益金规则

| 连续签到天数 | 基础奖励 | 额外奖励 | 合计 |
|-------------|---------|---------|------|
| 1-2 天 | 10 | 0 | 10 |
| 3-6 天 | 10 | +5 | 15 |
| 7-29 天 | 10 | +20 | 30 |
| 30 天+ | 10 | +100 | 110 |

规则取最高档——连续签到 7 天时,额外奖励是 +20(不是 +5+20=25),不是累加。这样设计是为了让奖励曲线有明显的阶梯感,激励用户向下一个里程碑冲刺。

### 3.3 API 设计

```
POST /api/register     注册(用户名 + 密码)
POST /api/login        登录(返回 JWT token)
POST /api/checkin      每日签到(需认证)
POST /api/claim        领取权益金(需认证)
GET  /api/balance      查看余额 + 待领取金额(需认证)
GET  /api/history      签到记录 + 领取记录(需认证)
GET  /api/health       健康检查
```

RESTful 风格:资源用名词,动作用 HTTP 方法。认证通过 `Authorization: Bearer <token>` 头传递 JWT。

## 四、核心代码实现

### 4.1 密码安全

密码不能明文存储。用 PBKDF2 + 随机 salt 做哈希:

```python
def hash_password(password):
    salt = secrets.token_hex(16)
    dk = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt.encode('utf-8'), 100000)
    return salt + ':' + dk.hex()

def verify_password(password, stored):
    salt, hash_hex = stored.split(':')
    dk = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt.encode('utf-8'), 100000)
    return hmac.compare_digest(dk.hex(), hash_hex)
```

`hmac.compare_digest` 而非 `==` 比较哈希值,是为了防止时序攻击——普通字符串比较会在第一个不匹配字符处短路返回,攻击者可以通过测量响应时间逐字符猜解哈希。`compare_digest` 恒定时间比较,不泄露任何信息。

### 4.2 连续签到计算

```python
def calculate_reward(consecutive_days):
    bonus = 0
    for days, amount in BONUS_RULES:
        if consecutive_days >= days:
            bonus = amount
            break
    return BASE_REWARD + bonus
```

`BONUS_RULES` 是一个 `[(天数, 额外奖励), ...]` 列表,按天数从高到低排列。遍历时第一个满足条件的就是最高档——简洁有效。

连续天数的计算逻辑:查上一天的签到记录,如果存在则 `consecutive_days + 1`,否则重置为 1。用 `UNIQUE(user_id, checkin_date)` 约束保证每天只能签到一次。

### 4.3 JWT 认证中间件

```python
def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization', '').replace('Bearer ', '')
        if not token:
            return jsonify({'code': 401, 'message': '缺少认证 token'}), 401
        try:
            payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
            current_user = get_user_by_id(payload['user_id'])
        except jwt.ExpiredSignatureError:
            return jsonify({'code': 401, 'message': 'token 已过期'}), 401
        except jwt.InvalidTokenError:
            return jsonify({'code': 401, 'message': 'token 无效'}), 401
        return f(current_user, *args, **kwargs)
    return decorated
```

Flask 装饰器做认证拦截,干净利落。token 过期和无效分别返回不同错误码,方便客户端区分处理。

## 五、服务部署与分享

### 5.1 本地启动

```bash
pip install -r requirements.txt
python app.py
# * Running on http://0.0.0.0:5000
```

数据库在首次启动时自动建表,无需手动初始化。

### 5.2 DevBridge 隧道分享

本地服务跑起来了,但外部用户无法访问。通过华为云 DevBridge 开发隧道,可以将本地端口安全暴露到公网:

```
本地 5000 端口 → DevBridge 中继 → 公网 URL
```

创建隧道后获得公网地址:`https://yxw5m5p2-5000.devbridge-s2.hwtunnel.com`

外部用户直接通过这个地址调用 API:

```bash
# 注册
curl -X POST https://yxw5m5p2-5000.devbridge-s2.hwtunnel.com/api/register \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"secret123"}'

# 登录(拿到 token)
curl -X POST https://yxw5m5p2-5000.devbridge-s2.hwtunnel.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"secret123"}'

# 签到
curl -X POST https://yxw5m5p2-5000.devbridge-s2.hwtunnel.com/api/checkin \
  -H "Authorization: Bearer <YOUR_TOKEN>"

# 查看余额
curl https://yxw5m5p2-5000.devbridge-s2.hwtunnel.com/api/balance \
  -H "Authorization: Bearer <YOUR_TOKEN>"
```

DevBridge 的好处是**不需要公网 IP、不需要改防火墙、不需要端口转发**——本地服务通过出站连接到中继服务器,外部请求经中继转发回来。隧道有效期 72 小时,适合开发调试和临时演示。

## 六、发布到开发者作品展览馆

代码推送到 GitCode 仓库后,通过华为云开发者作品展览馆的发布流程,将作品提交到「华为云AI产品实践」活动:

1. **扫描项目** — 自动识别项目根目录和类型
2. **IAM 认证** — 生成 STS 临时凭证,不泄露永久 AK/SK
3. **GitCode 授权** — OAuth 扫码登录,获取仓库访问权限
4. **生成封面** — 自动截图 + 合成封面图(含项目标题和技术标签)
5. **生成详情包** — 打包 README 和资源文件
6. **选择活动** — 从当前可投稿的活动中选择目标
7. **发布** — 提交到展览馆平台

发布成功后获得:
- 作品页面:`https://gallery.developer.huaweicloud.com/gallery/00770bbd76c00000`
- 积分奖励:200 积分

## 七、总结与思考

### 做了什么

用一个 Flask 文件(323 行)实现了一个功能完整的签到领取权益金后端服务,涵盖用户认证、签到逻辑、权益金计算、余额管理。通过 DevBridge 隧道分享到公网,最终发布到华为云开发者展览馆。

### 几点设计决策的思考

**为什么用 SQLite 而不是 MySQL/PostgreSQL?** 这个服务的数据量不大(用户量、签到记录都在可控范围),SQLite 的单文件数据库足够用,且零运维成本。如果后续用户量增长,迁移到 PostgreSQL 也只是改连接字符串的事——SQL 语法基本兼容。

**为什么 JWT 而不是 Session?** RESTful API 设计原则是无状态——每个请求自包含所有信息。JWT 把用户身份编码在 token 里,服务端不需要存 session,水平扩展时不需要共享 session 存储。代价是 token 无法主动失效(除非维护黑名单),但对签到这种场景足够。

**为什么奖励取最高档而不是累加?** 累加会导致奖励增长过快(30 天 = 10+5+20+100=135),运营成本不可控。取最高档让奖励曲线呈阶梯状,每个里程碑有明确的跃升感,既控制成本又有激励效果。

### 技术栈一览

```
Python 3.12 + Flask 3.x + SQLite + PyJWT
```

没有额外的数据库、缓存、消息队列——有时候最简单的技术栈就是最好的选择。先跑起来,再优化。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。