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
}
各键含义如下表所示:
2.实现思路
首次抽签:客户端请求
/fortune
→ 生成用户 ID 和运势 → 保存到文件 → 返回结果并设置 Cookie。重复抽签:客户端携带 Cookie 请求
/fortune
→ 读取文件 → 返回已存结果。添加好运:客户端请求
/luck
→ 验证参数和状态 → 更新文件中的好运状态。
3.实现方式
编程语言为python,程序基于 Flask 框架构建API,Flask-CORS 处理跨域请求,提供抽签和添加好运功能,通过文件系统实现了简单的持久化存储,确保用户每日获得唯一运势结果,并提供了基本的状态管理功能。运行前,请先确保安装了python3.x,并pip install flask flask-cors
安装flask、flask-cors应用框架。
随机运势生成:使用加权随机算法生成不同概率的运势类型(大吉、中吉、小吉、末吉、凶)。
文件存储:每日运势结果以 JSON 文件形式存储在
static/log/日期/
目录下,文件名基于用户 ID 生成。用户识别:通过 Cookie 中的
user_id
识别用户,无 Cookie 时自动生成 UUID。API 接口:
/fortune
:获取今日运势,首次请求生成新运势,重复请求返回已存结果。/luck
:激活好运状态,需携带luckcoin=1
参数。
文件结构
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.测试网址
运势抽卡现已上线网站主页,可以直接访问主页获取自己的运势啦~!
评论