1.实现功能

当用户访问/fortune路径后,返回一个json,格式如下

{
  "coin": "1",
  "cookie": "19ad4983-8480-4d3d-8186-675f516b0ca6",
  "date": "2025-05-19",
  "fortune": "大吉",
  "hitokoto": "因为你在,我不是太阳,而是化作星辰,与你遨游星空",
  "pic": "/static/images/pic12.jpg",
  "luck": false,
  "success": true
}

各键含义如下表所示:

键值

含义

coin

幸运币个数,每次登录获取1枚,每天最多可消耗1枚获得幸运(详见luck)

cookie

访问用户的cookie,用以识别用户身份,如果没有则创建

date

当天的日期

fortune

按照"大吉":"中吉":"小吉":"末吉":"凶"为3:6:12:8:3的比例随机抽取一个

pic

随机图片,在pic1.webp-pic32.webp中随机抽取一个

hitokoto

从/static/hitokoto.txt中随机选择一行

luck

默认为False,用户可以向/luck路径发送请求,请求格式内容固定为"/luck?luckcoin=1",拒绝一切其他请求,当用户发送正确请求后,首先判断用户ip是否创建了当日的json,如果没有,返回"请先抽个签吧";如果存在,则查看json文件中luck的键值,如果为False,则改写为True并保存,返回"好运与你常伴";如果存在,且json文件中的luck键值为True,则直接返回"好运已伴你左右"。

2.实现思路

  1. 首次抽签:客户端请求 /fortune → 生成用户 ID 和运势 → 保存到文件 → 返回结果并设置 Cookie。

  2. 重复抽签:客户端携带 Cookie 请求 /fortune → 读取文件 → 返回已存结果。

  3. 添加好运:客户端请求 /luck → 验证参数和状态 → 更新文件中的好运状态。

3.实现方式

编程语言为python,程序基于 Flask 框架构建API,Flask-CORS 处理跨域请求,提供抽签和添加好运功能,通过文件系统实现了简单的持久化存储,确保用户每日获得唯一运势结果,并提供了基本的状态管理功能。运行前,请先确保安装了python3.x,并pip install flask flask-cors安装flask、flask-cors应用框架。

  1. 随机运势生成:使用加权随机算法生成不同概率的运势类型(大吉、中吉、小吉、末吉、凶)。

  2. 文件存储:每日运势结果以 JSON 文件形式存储在 static/log/日期/ 目录下,文件名基于用户 ID 生成。

  3. 用户识别:通过 Cookie 中的 user_id 识别用户,无 Cookie 时自动生成 UUID。

  4. API 接口

    • /fortune:获取今日运势,首次请求生成新运势,重复请求返回已存结果。

    • /luck:激活好运状态,需携带 luckcoin=1 参数。

  5. 文件结构

your-project/
├── app.py                     # 主应用文件
├── static/                    # 静态资源目录
│   ├── hitokoto.txt           # 存储每日一句的文本文件
│   ├── images/                # 图片资源目录
│   │   ├── pic1.webp
│   │   ├── pic2.webp
│   │   └── ...
│   └── log/
        ├── coincount.json     # 存储cookie信息和幸运币个数
│       └── 2025-05-20/        # 日期子目录(自动生成)
│           ├── user1.json     # 用户运势文件
│           └── user2.json
└── requirements.txt           # 依赖包列表

4.具体代码

前端部分(需修改your-api-domian为自己的api地址)

<!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>幸运占卜</title>
            <script src="https://cdn.tailwindcss.com"></script>
            <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
            <script>
                tailwind.config = {
                    theme: {
                        extend: {
                            colors: {
                                primary: '#1E293B',
                                accent: '#D4AF37',
                                neutral: '#F8FAFC'
                            },
                            fontFamily: {
                                sans: ['Inter', 'system-ui', 'sans-serif'],
                            },
                        }
                    }
                }
            </script>
            <style type="text/tailwindcss">
                body {
                    font-family: 'Inter', system-ui, sans-serif;
                    background-image: url('./tarotBGA.jpg');
                    background-size: cover;
                    background-position: center;
                    background-attachment: fixed;
                }
                #testwwwdiv{
                    position: absolute;
                    top: 15%;
                    left: 50%;
                    transform: translateX(-50%);
                    text-align: center;
                    margin: 0;
                }
                @layer utilities {
                    .content-auto {
                        content-visibility: auto;
                    }
                    .gold-glow {
                        box-shadow: 0 0 15px rgba(212, 175, 55, 0.7);
                    }
                    .text-shadow {
                        text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
                    }
                    .button-pulse {
                        animation: pulse 2s infinite;
                    }
                    @keyframes pulse {
                        0% { transform: scale(1); }
                        50% { transform: scale(1.05); }
                        100% { transform: scale(1); }
                    }
                    .fade-in {
                        animation: fadeIn 0.5s ease-in-out;
                    }
                    @keyframes fadeIn {
                        from { opacity: 0; }
                        to { opacity: 1; }
                    }
                    .shake {
                        animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
                    }
                    @keyframes shake {
                        10%, 90% { transform: translate3d(-1px, 0, 0); }
                        20%, 80% { transform: translate3d(2px, 0, 0); }
                        30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
                        40%, 60% { transform: translate3d(4px, 0, 0); }
                    }
                }
            </style>
        </head>
        <body>
            <div id="testwwwdiv">
                <!-- 头部 -->
                <header class="text-center mb-10">
                    <h1 class="text-[clamp(2rem,5vw,3rem)] font-bold text-transparent bg-clip-text bg-gradient-to-r from-accent to-yellow-300 mb-2">幸运占卜</h1>
                    <p class="text-gray-400 text-lg"></p>
                </header>

                <!-- 图片区域 -->
                <div class="relative mx-auto mb-8 w-[min(300px,100%)] h-[min(300px,100%)] rounded-xl overflow-hidden border-4 border-accent/80 shadow-lg shadow-accent/20">
                    <img 
                        id="fortuneimg" 
                        src="" 
                        alt="今日运势" 
                        class="w-full h-full object-cover transition-all duration-500 hover:brightness-110"
                    >
                    <div class="absolute left-[22%] top-[60%] -translate-y-1/2 w-[24%]">
                        <div class="flex flex-col items-start">
                            <p 
                                id="fortunetxt" 
                                class="text-left text-[clamp(2rem,3vw,10rem)] font-bold text-black text-shadow max-w-[90%] fade-in"
                            >点击下方按钮获取运势</p>
                        </div>
                    </div>
                </div>

                <!-- 按钮区域 -->
                <div class="text-center">
                    <button 
                        id="luckytap" 
                        class="bg-black text-accent font-bold py-3 px-10 rounded-full border-2 border-accent hover:bg-gray-900 transition-all duration-300 button-pulse hover:gold-glow flex items-center justify-center mx-auto"
                    >
                        <i class="fa-solid fa-coins mr-2"></i>
                        Luckycoin
                    </button>
                </div>

                <!-- 底部信息 -->
                <footer class="mt-10 text-center text-gray-500 text-sm">
                    <p id="hitokototxt">每次点击都会带来新的运势 | 仅供娱乐</p>
                    <div class="mt-3 flex justify-center space-x-4">
                        <a href="#" class="text-gray-400 hover:text-accent transition-colors"><i class="fa-brands fa-twitter"></i></a>
                        <a href="#" class="text-gray-400 hover:text-accent transition-colors"><i class="fa-brands fa-instagram"></i></a>
                        <a href="#" class="text-gray-400 hover:text-accent transition-colors"><i class="fa-brands fa-facebook"></i></a>
                    </div>
                </footer>
            </div>

            <script>
                document.addEventListener('DOMContentLoaded', () => {
                    setTimeout(myFunction, 300);
                });
                function myFunction() {
                    const hitokotoTxt = document.getElementById('hitokototxt');
                    const fortuneImg = document.getElementById('fortuneimg');
                    const fortuneTxt = document.getElementById('fortunetxt');
                    const luckyTap = document.getElementById('luckytap');

                    //获取api内容并命名
                    fetch('your-api-domian/fortune')
                    .then(response => response.json())
                    .then(data => {
                    if (data.success) {
                        const fortunexz = data.fortune;
                        const hitokoxz = data.hitokoto;
                        const luckxz = data.luck;
                        const picxz = data.pic;
                        const coinxz = data.coin;
                        fortuneTxt.textContent = fortunexz;
                        luckyTap.innerHTML=luckyTap.innerHTML+coinxz
                        hitokotoTxt.innerHTML=hitokoxz;
                        // 更新图片
                        fortuneImg.style.opacity = '0';
                        setTimeout(() => {
                            fortuneImg.src = `your-api-domian${picxz}`;
                            fortuneImg.style.opacity = '1';
                        }, 300);
                    }
                    });


                    // 按钮点击事件
                    luckyTap.addEventListener('click', async () => {
                        // 添加按钮点击效果
                        luckyTap.classList.add('scale-95');
                        setTimeout(() => luckyTap.classList.remove('scale-95'), 150);

                        // 添加图片抖动效果
                        fortuneImg.classList.add('shake');
                        setTimeout(() => fortuneImg.classList.remove('shake'), 500);

                        try {
                        // 发送 GET 请求
                        const response = await fetch('/luck?luckcoin=1');
                        if (!response.ok) {
                        throw new Error('请求失败');
                        }
                        // 刷新页面
                        location.reload();
                        } catch (error) {
                        console.error('请求出错:', error);
                        }

                    });
                };
            </script>
        </body>
        </html>

后端部分

from flask import Flask, jsonify, request, make_response, abort
from flask_cors import CORS
import os
import uuid
import random
from datetime import datetime
import json

app = Flask(__name__)

# 预加载每日一句
with open('static/hitokoto.txt', 'r', encoding='utf-8') as f:
    hitokoto_list = [line.strip() for line in f if line.strip()]
# 运势概率分布
FORTUNE_WEIGHTS = [3, 6, 12, 8, 3]
FORTUNE_TYPES = ["大吉", "中吉", "小吉", "末吉", "凶"]
# 生成随机运势
def generate_fortune():
    return random.choices(FORTUNE_TYPES, weights=FORTUNE_WEIGHTS, k=1)[0]
# 生成随机图片路径
def get_random_pic():
    return f"/static/images/pic{random.randint(1, 32)}.webp"
# 构建log文件路径
count_dir = os.path.join('static', 'log')
os.makedirs(count_dir, exist_ok=True)
count_path = os.path.join(count_dir,  f'coincount.json')
# 读取log文件
def init_or_load_stats():
    if os.path.exists(count_path):
        try:
            with open(count_path, 'r') as f:
                return json.load(f)
        except json.JSONDecodeError:
            return []
    return []
# 查找log中cookie是否已存在
def exist_stats(cookie_id):
    stats = init_or_load_stats()
    for entry in stats:
        if entry['cookie'] == cookie_id:
            return True
        return False
# 创建log
def create_log(cookie_id):
    stats = init_or_load_stats()
    stats.append({
        'cookie': cookie_id,
        'count': '1'
    })
    with open(count_path, 'w') as f:
        json.dump(stats, f, indent=4)
# log中硬币数量+1
def update_stats(cookie_id):
    stats = init_or_load_stats()
    # 查找cookie是否已存在
    for entry in stats:
        if entry['cookie'] == cookie_id:
            entry['count'] = str(int(entry['count']) + 1)
    with open(count_path, 'w') as f:
        json.dump(stats, f, indent=4)
# log中硬币数量-1
def down_stats(cookie_id):
    stats = init_or_load_stats()
    # 查找log中cookie是否已存在
    for entry in stats:
        if entry['cookie'] == cookie_id:
            entry['count'] = str(int(entry['count']) - 1)
    with open(count_path, 'w') as f:
        json.dump(stats, f, indent=4)
# 获取硬币数量
def get_cookie_count(cookie_id):
    stats = init_or_load_stats()
    for entry in stats:
        if entry['cookie'] == cookie_id:
            return int(entry['count'])
        return 1
    return 999

@app.route('/fortune')
def handle_fortune():
    # 获取基本信息
    current_date = datetime.now().strftime("%Y-%m-%d")
    user_id = request.cookies.get('user_id')  # 修正:使用正确的cookie名称
    if not user_id:
        # 生成唯一的用户ID
        user_id = str(uuid.uuid4())
    # 生成daily文件路径
    daily_name = ''.join(c for c in user_id if c.isalnum() or c in ('_', '-'))
    log_dir = os.path.join(count_dir, current_date)
    os.makedirs(log_dir, exist_ok=True)
    file_path = os.path.join(log_dir, f'{daily_name}.json')
    # 查找log中cookie是否存在
    if exist_stats(user_id):
        # 查找daily是否存在
        if os.path.exists(file_path):
            with open(file_path, 'r+', encoding='utf-8') as f:
                dataccp = json.load(f)
                f.seek(0)
                json.dump(dataccp, f, indent=2, ensure_ascii=False)
                f.truncate()
                # 并返回
                resp = make_response(jsonify(dataccp))
                return resp
        update_stats(user_id)
        new_fortune = {
            "success": True,
            "date": current_date,
            "cookie": daily_name,
            "fortune": generate_fortune(),
            "hitokoto": random.choice(hitokoto_list),
            "pic": get_random_pic(),
            "luck": False,
            "coin": get_cookie_count(user_id)
        }
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(new_fortune, f, indent=2, ensure_ascii=False)
            resp = make_response(jsonify(new_fortune))
            # 刷新cookie持续时间为365天
            resp.set_cookie('user_id', user_id, max_age=365 * 24 * 60 * 60)
            return resp
    if not exist_stats(user_id):
        create_log(user_id)
        new_fortune = {
            "success": True,
            "date": current_date,
            "cookie": daily_name,
            "fortune": generate_fortune(),
            "hitokoto": random.choice(hitokoto_list),
            "pic": get_random_pic(),
            "luck": False,
            "coin": "1"
        }
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(new_fortune, f, indent=2, ensure_ascii=False)
            resp = make_response(jsonify(new_fortune))
            # 创建cookie持续时间为365天
            resp.set_cookie('user_id', user_id, max_age=365 * 24 * 60 * 60)
            return resp

@app.route('/luck')
def handle_luck():
    # 验证请求参数
    if request.args.to_dict() != {'luckcoin': '1'}:
        abort(400, description="无效请求格式")

    # 获取基本信息
    current_date = datetime.now().strftime("%Y-%m-%d")
    user_id = request.cookies.get('user_id')
    # 检查是否存在运势记录
    if not user_id:
        return "请先抽个签吧", 400
    daily_name = ''.join(c for c in user_id if c.isalnum() or c in ('_', '-'))
    file_path = os.path.join('static', 'log', current_date, f'{daily_name}.json')
    # 检查是否存在运势记录
    if not exist_stats(user_id):
        return "请先抽个签吧", 400
    if not os.path.exists(file_path):
        return "请先抽个签吧", 400

    # 修改运势状态
    with open(file_path, 'r+', encoding='utf-8') as f:
        data = json.load(f)
        if data['luck']:
            return "好运已伴你左右"
        down_stats(user_id)
        data['luck'] = True
        data['fortune'] = "皆顺"
        data['coin'] = get_cookie_count(user_id)
        f.seek(0)
        json.dump(data, f, indent=2, ensure_ascii=False)
        f.truncate()
    resp = make_response("好运与你常伴")
    return resp

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=6666)

5.测试网址

运势抽卡现已上线网站主页,可以直接访问主页获取自己的运势啦~!