15. 用户签到抽奖处理
签到抽奖
签到抽奖页面显示
- 主页面
index.html
,点击跳转签到抽奖页面,代码:
<!DOCTYPE html>
<html lang="en">
<head>
<title>首页</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta name="format-detection" content="telephone=no,email=no,date=no,address=no">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="bg">
<img src="../static/images/bg0.jpg">
</div>
<ul>
<li><img class="module1" @click="open_orchard" src="../static/images/image1.png"></li>
<li><img class="module2" @click="open_user" src="../static/images/image2.png"></li>
<li><img class="module3" src="../static/images/image3.png"></li>
<li><img class="module4" @click="open_lucky" src="../static/images/image4.png"></li>
</ul>
</div>
<script>
apiready = function(){
// 把game入口程序对象绑定到vue中作为属性使用
Vue.prototype.game = new Game("../static/mp3/bg1.mp3");
new Vue({
el:"#app",
data(){
return {
music_play:true, // 默认播放背景音乐
}
},
watch:{
music_play(){
if(this.music_play){
this.game.play_music("../static/mp3/bg1.mp3");
}else{
this.game.stop_music();
}
}
},
created(){
this.listener();
},
methods:{
listener(){
this.listen_invite();
this.listen_noticeclicked();
},
listen_noticeclicked(){
// 通知处理
api.addEventListener({
name: 'noticeclicked'
}, (ret, err)=>{
this.game.print( ret.value);
this.open_orchard();
});
},
listen_invite(){
// 监听来自其他应用唤醒当前APP时附带的参数信息
// 使用appintenr监听并使用appParam接收URLScheme的参数
// 收集到参数以后,可以结合本地是否有token认证领来,进行操作,
// 如果没有token,则可以跳转到注册页面.
// 如果存在token,则可以跳转到用户中心,并跳转到添加好友,甚至可以发送请求获取二维码主人的身份信息
api.addEventListener({
name:'appintent' // 当前事件监听必须是唯一的,整个APP中只能编写一次,否则冲突导致监听无效
},(ret,err)=>{
var appParam = ret.appParam;
this.game.print(typeof appParam,1); // object
this.game.print(appParam,1); //{"uid":xxx,"user_type":xxx};
// 保存URLScheme参数到本地
this.game.setfs(appParam);
// 判断当前APP用户是否有账号,如果登陆,则证明已经有账号了。
let token = this.game.getdata("access_token") || this.game.getfs("access_token");
// 跳转到注册页面
if(!token && appParam.user_type == "invite"){
// 其他用户邀请注册!!
this.game.openWin("login","login.html");
}else{
this.game.openWin("user","user.html");
api.sendEvent({
name: 'appintent_friend_list',
extra: {
uid: appParam.uid,
}
});
}
});
},
open_lucky(){
// 打开签到抽奖
this.game.check_user_login(this,()=>{
this.game.openWin("lucky","lucky.html");
});
},
open_login(){
// 打开登录页面
this.game.openWin("login","login.html");
},
open_user(){
// 打开个人中心
this.game.check_user_login(this,()=>{
this.game.openWin("user","user.html");
});
},
open_orchard(){
this.game.check_user_login(this,()=>{
this.game.openWin("orchard","orchard.html");
});
}
}
})
}
</script>
</body>
</html>
抽奖环节使用vue-luck-draw模块,用于生成抽奖过程中的抽奖大转盘。
官网:https://100px.net/usage/vue.html
- 签到抽奖页面
lucky.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>用户中心</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/vue-luck-draw.umd.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app lucky" id="app">
<div class="bg">
<img class="meadow" src="../static/images/bg2.png" alt="">
<img class="board_bg2" src="../static/images/board_bg2.png" alt="">
</div>
<div class="lucky_panel">
<ul class="lucky_info">
<li><p class="title">{{sign_continue}}</p><p class="info">连续签到</p></li>
<li><p class="title">{{sign_month}}</p><p class="info">本月签到</p></li>
<li><p class="title">{{sign_total}}</p><p class="info">总共签到</p></li>
</ul>
<img class="signin" @click="sign_in_handle" :src="sign_in?'../static/images/sign2.gif':'../static/images/sign.gif'" alt="">
</div>
<img class="lucky_bg" src="../static/images/bg.3.png" alt="">
<div class="lucky_wheel">
<lucky-wheel
v-cloak
ref="LuckyWheel"
width="230px"
height="230px"
:prizes="prizes"
:default-style="defaultStyle"
:blocks="blocks"
:buttons="buttons"
@start="startCallBack"
@end="endCallBack"
/>
</div>
<div class="lucky_list">
<p v-for="data in lucky_list">{{data}}</p>
</div>
</div>
<script>
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
apiready = function(){
new Vue({
el:"#app",
data(){
return {
sign_continue: 5,
sign_month: 15,
sign_total: 65,
sign_in:false,
lucky_list:[
"12:03:40 深圳用户 xxx 130xxxx0000 抽到了3个小舔狗",
"12:03:39 深圳用户 xxx 130xxxx0000 抽到了50元红包",
"12:03:38 深圳用户 xxx 130xxxx0000 抽到了1个小舔狗",
"12:03:37 深圳用户 xxx 130xxxx0000 抽到了500元红包",
"12:03:36 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:35 深圳用户 xxx 130xxxx0000 抽到了1个苹果树",
"12:03:34 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:33 深圳用户 xxx 130xxxx0000 抽到了1个贵宾犬",
"12:03:32 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:41 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
],
prizes: [],
defaultStyle: {
fontColor: '#d64737',
fontSize: '14px'
},
blocks: [
{ padding: '13px', background: '#d64737' }
],
buttons: [
{
radius: '35px', background: '#ffdea0',
imgs: [{ src: '../static/images/btn.1.png', width: '100%', top: '-120%' }]
}
],
}
},
mounted(){
// 事件监听
this.listener();
this.getPrizesList();
setInterval(()=>{ // 这段代码仅作测试
let data = this.lucky_list.pop();
this.lucky_list.unshift(data);
},1000);
},
methods:{
sign_in_handle(){
// 签到处理
this.sign_continue+=1;
this.sign_month+=1;
this.sign_total+=1;
this.sign_in=true;
},
listener(){
// 全局事件监听方法
},
getPrizesList () {
const prizes = [];
let data = ['1元红包', '100元红包', '0.5元红包', '2元红包', '10元红包', '50元红包', '5元红包']
data.forEach((item, index) => {
prizes.push({
title: item,
background: index % 2 ? '#f9e3bb' : '#f8d384',
fonts: [{ text: item, top: '10%' }],
imgs:[{ src: `../static/images/${index}.png`, width: '30%', top: '35%' }],
});
})
this.prizes = prizes
},
startCallBack () {
if(!this.sign_in){
this.game.tips('对不起,没有签到,无法抽奖哦!~',5000);
return;
}
this.$refs.LuckyWheel.play();
setTimeout(() => {
this.$refs.LuckyWheel.stop(Math.random() * 8 >> 0);
}, 5000);
},
endCallBack (prize) {
this.game.tips(`恭喜你获得${prize.title}`,5000);
this.lucky_list.unshift("00:00:00 深圳用户 xxx 130xxxx0000 抽到了1个"+prize.title)
},
}
});
}
</script>
</body>
</html>
- 添加css样式,
main.css
,代码:
.lucky .bg{
position: absolute;
overflow: hidden;
top: 0rem;
width: 100%;
max-width: 100%;
height: 100%;
max-width: 100%;
background: #000;
}
.lucky .bg .board_bg2,
.lucky .bg .meadow{
animation: normal;
position: absolute;
top: 0;
}
.lucky .bg .board_bg2{
top: -6rem;
}
.lucky_wheel{
position: absolute;
top: 20.2rem;
left: 0;
right: 0;
width: 230px;
height: 230px;
margin: auto;
}
.lucky_bg{
position: absolute;
top: 18rem;
left: 0;
right: 0;
width: 278px;
height: 278px;
margin: auto;
}
.lucky_panel .signin{
position: absolute;
right: 2rem;
top: 3rem;
width: 84px;
height: 80px;
}
.lucky_info{
margin-left: 1rem;
margin-top: 2rem;
}
.lucky_info li{
float: left;
margin: 0.5rem;
font-size: 1.5rem;
text-align: center;
vertical-align: middle;
}
.lucky_info li .title{
color: #fff;
font-size: 3rem;
background: url("../images/btn5.png") no-repeat 0 0;
background-size: 6rem 6rem;
width: 6rem;
height: 6rem;
line-height: 6rem;
}
.lucky_info li .info{
margin-top: 1rem;
color: #fff;
}
.lucky_list{
position: absolute;
bottom: -4rem;
right: 0;
left: 0;
margin: auto;
width: 28.6rem;
height: 16rem;
background: url("../images/waper.png") no-repeat 0 0;
background-size: 28.6rem 16rem;
padding-top: 2.6rem;
}
.lucky_list p{
padding: 0;
color: #000;
margin-bottom: 1rem;
margin-left: 1.3rem;
line-height: 1rem;
}
分别创建签到和抽奖的蓝图目录,并注册和绑定总路由。
cd application/apps
python ../../manage.py blue -ncheckin
python ../../manage.py blue -nlucky
settings.dev
,配置文件中注册蓝图,代码:
"""蓝图列表"""
INSTALL_BLUEPRINT = [
"application.apps.users", # 用户
"application.apps.home", # 公共蓝图
"application.apps.orchard", # 种植园蓝图
"application.apps.checkin", # 签到
"application.apps.lucky", # 抽奖
]
总路由,绑定蓝图的路由信息,代码:
from application.utils import include
urlpatterns = [
include("","home.urls"),
include("/users","users.urls"),
include("/relation","relation.urls"),
include("/orchard", "orchard.urls"),
include("/checkin", "checkin.urls"),
include("/lucky", "lucky.urls"),
]
签到功能
用户签到功能的实现,其实主要考虑的是怎么记录用户是持续签到还是断续签到而已。
获取签到信息
服务端提供用户签到信息的api接口
- 创建签到信息模型
apps.checkin.documents
,代码:
from datetime import datetime
from application import mongoengine as mgdb
# 会员签到信息
class UserCheckInDocument(mgdb.Document):
"""会员签到信息"""
meta = {
'collection': 'user_checkin',
'ordering': ['-_id'], # 倒序排列
'strict': False, # 是否严格语法
}
user_id = mgdb.IntField(required=True, unique=True, verbose_name='用户ID')
duration = mgdb.IntField(default=0, verbose_name='用户连续签到')
month_times = mgdb.IntField(default=0, verbose_name='用户本月签到')
total_times = mgdb.IntField(default=0, verbose_name='用户总签到')
last_time = mgdb.DateTimeField(null=True, default=datetime.now, verbose_name='上一次签到时间')
# 会员签到日志
class UserCheckInLog(mgdb.Document):
"""会员签到日志"""
meta = {
'collection': 'user_checkin_log',
'ordering': ['-_id'], # 倒序排列
'strict': False, # 是否严格语法
}
user_id = mgdb.IntField(required=True, verbose_name='用户ID')
create_time = mgdb.DateTimeField(default=datetime.now, verbose_name='日志创建时间')
- 视图接口,
apps.checkin.api
,代码:
from flask_jwt_extended import jwt_required
from application.utils import decorator
from application import code,message
from . import services
# 获取当前用户的签到信息记录
@jwt_required()
@decorator.get_user_object
def checkin_info(user):
'''
获取当前用户的签到信息记录
:param user: 装饰器通过token获取的用户模型对象
:return:
'''
# 从MongoDB中获取签到信息
checkin_info = services.get_checkin_info_by_id(user.id)
data = {
"errno":code.CODE_OK,
"errmsg":message.ok,
"checkin_info":checkin_info,
}
return data
- 数据处理层,
checkin.services
,代码:
from .documents import UserCheckInDocument
from .marshmallow import UserCheckInSchema
# 获取当前用户的签到信息记录
def get_checkin_info_by_id(user_id):
'''
获取当前用户的签到信息记录
:param user_id: 用户ID
:return:
'''
try:
checkin_info = UserCheckInDocument.objects.get(user_id=user_id)
# 序列化数据
ucis = UserCheckInSchema()
return ucis.dump(checkin_info)
except UserCheckInDocument.DoesNotExist:
# 如果没有数据,初始化用户签到记录
init_user_checkin_info(user_id)
# 中心调用自己
return get_checkin_info_by_id(user_id)
# 初始化用户签到记录
def init_user_checkin_info(user_id):
'''
初始化用户签到记录
:param user_id: 用户ID
:return:
'''
UserCheckInDocument.objects.create(
user_id=user_id,
duration=0,
month_times=0,
total_times=0,
last_time=None,
)
- 序列化构造器,
checkin.marshmallow
,代码:
from datetime import datetime
from marshmallow import Schema, fields, post_dump
# 用户签到信息的构造器
class UserCheckInSchema(Schema):
"""用户签到信息的构造器"""
user_id = fields.Integer()
duration = fields.Integer()
month_times = fields.Integer()
total_times = fields.Integer()
last_time = fields.DateTime()
# 序列化后处理数据
@post_dump
def post_dump(self, data, **kwargs):
data["is_checkin"] = False
if data.get('last_time') is not None:
# 获取最后一次签到的日期和今天的日期进行字符串比较,相等则已经签到
data["is_checkin"] = data.get('last_time').split("T")[0] == datetime.now().strftime("%Y-%m-%d")
print(data)
return data
- 路由
checkin.urls
,代码:
from application import path, api_rpc
# 引入当前蓝图应用视图, 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = [
# path('/index', views.index, methods=['post']),
]
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('checkin_info', api.checkin_info), # 用户签到信息
]
客户端展示用户的签到信息
lucky.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>用户中心</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/vue-luck-draw.umd.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app lucky" id="app">
<div class="bg">
<img class="meadow" src="../static/images/bg2.png" alt="">
<img class="board_bg2" src="../static/images/board_bg2.png" alt="">
</div>
<div class="lucky_panel">
<ul class="lucky_info">
<li><p class="title">{{checkin_info.duration}}</p><p class="info">连续签到</p></li>
<li><p class="title">{{checkin_info.month_times}}</p><p class="info">本月签到</p></li>
<li><p class="title">{{checkin_info.total_times}}</p><p class="info">总共签到</p></li>
</ul>
<img class="signin" @click="sign_in_handle" :src="sign_in?'../static/images/sign2.gif':'../static/images/sign.gif'" alt="">
</div>
<img class="lucky_bg" src="../static/images/bg.3.png" alt="">
<div class="lucky_wheel">
<lucky-wheel
v-cloak
ref="LuckyWheel"
width="230px"
height="230px"
:prizes="prizes"
:default-style="defaultStyle"
:blocks="blocks"
:buttons="buttons"
@start="startCallBack"
@end="endCallBack"
/>
</div>
<div class="lucky_list">
<p v-for="data in lucky_list">{{data}}</p>
</div>
</div>
<script>
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
apiready = function(){
new Vue({
el:"#app",
data(){
return {
checkin_info:{}, // 用户签到信息
sign_in:false,
lucky_list:[
"12:03:40 深圳用户 xxx 130xxxx0000 抽到了3个小舔狗",
"12:03:39 深圳用户 xxx 130xxxx0000 抽到了50元红包",
"12:03:38 深圳用户 xxx 130xxxx0000 抽到了1个小舔狗",
"12:03:37 深圳用户 xxx 130xxxx0000 抽到了500元红包",
"12:03:36 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:35 深圳用户 xxx 130xxxx0000 抽到了1个苹果树",
"12:03:34 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:33 深圳用户 xxx 130xxxx0000 抽到了1个贵宾犬",
"12:03:32 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:41 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
],
prizes: [],
defaultStyle: {
fontColor: '#d64737',
fontSize: '14px'
},
blocks: [
{ padding: '13px', background: '#d64737' }
],
buttons: [
{
radius: '35px', background: '#ffdea0',
imgs: [{ src: '../static/images/btn.1.png', width: '100%', top: '-120%' }]
}
],
}
},
created(){
// 获取用户签到信息
this.get_user_checkin();
},
mounted(){
// 事件监听
this.listener();
this.getPrizesList();
setInterval(()=>{ // 这段代码仅作测试
let data = this.lucky_list.pop();
this.lucky_list.unshift(data);
},1000);
},
methods:{
// 获取用户签到信息
get_user_checkin(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Checkin.checkin_info',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// self.game.print(data.result.checkin_info,1)
self.checkin_info = data.result.checkin_info
}else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
sign_in_handle(){
// 签到处理
this.sign_continue+=1;
this.sign_month+=1;
this.sign_total+=1;
this.sign_in=true;
},
listener(){
// 全局事件监听方法
},
getPrizesList () {
const prizes = [];
let data = ['1元红包', '100元红包', '0.5元红包', '2元红包', '10元红包', '50元红包', '5元红包']
data.forEach((item, index) => {
prizes.push({
title: item,
background: index % 2 ? '#f9e3bb' : '#f8d384',
fonts: [{ text: item, top: '10%' }],
imgs:[{ src: `../static/images/${index}.png`, width: '30%', top: '35%' }],
});
})
this.prizes = prizes
},
startCallBack () {
if(!this.sign_in){
this.game.tips('对不起,没有签到,无法抽奖哦!~',5000);
return;
}
this.$refs.LuckyWheel.play();
setTimeout(() => {
this.$refs.LuckyWheel.stop(Math.random() * 8 >> 0);
}, 5000);
},
endCallBack (prize) {
this.game.tips(`恭喜你获得${prize.title}`,5000);
this.lucky_list.unshift("00:00:00 深圳用户 xxx 130xxxx0000 抽到了1个"+prize.title)
},
}
});
}
</script>
</body>
</html>
用户点击签到的处理
服务端提供用户签到的处理接口
# 判断是否连续签到的情况
今天的签到时间的00:00:00的时间戳 - 上一次签到的00:00:00的时间戳 > 24小时的时间,则不是连续签到,否则是连续签到
# 判断是否属于本月签到
今天的时间提取月份 == 上一次签到的月份 == 本月签到+1
今天的时间提取月份 != 上一次签到的月份 == 1
- 视图,
apps.checkin.api
,代码:
from datetime import datetime
from flask_jwt_extended import jwt_required
from application.utils import decorator
from application import code,message
from . import services
# 用户签到处理
@jwt_required()
@decorator.get_user_object
def check_in(user):
'''
用户签到处理
:param user: 装饰器通过token获取的用户模型对象
:return:
'''
# 1.获取签到信息,判断用户是否已经签到了
checkin_info = services.get_checkin_info_by_id(user.id)
if checkin_info['is_checkin']:
# 已经签到
return {
"errno": code.CODE_CHECKIN_REPEAT,
"errmsg": message.checkin_repeat,
}
# 2.获取上次签到的时间
last_time = checkin_info['last_time']
# 新用户,从未签到,点击签到都加1
if last_time is None:
checkin_info["duration"] = 1
checkin_info["total_times"] = 1
checkin_info["month_times"] = 1
else:
# 2.1 老用户,则判断是否连续签到
# 上次签到的时间戳[当天的00:00:00]
last_timestamp = datetime.strptime(last_time.split("T")[0], "%Y-%m-%d").timestamp()
# 本次签到的时间戳[今天的00:00:00]
today = datetime.now().strftime("%Y-%m-%d")
today_timestamp = datetime.strptime(today, "%Y-%m-%d").timestamp()
if today_timestamp - last_timestamp == 24 * 3600:
# 连续签到
checkin_info["duration"] += 1
else:
# 不是连续签到,重新计算连续签到
checkin_info["duration"] = 1
# 2.2 判断是否属于当月签到
last_month = "-".join(last_time.split("-")[0:2])
current_month = datetime.now().strftime("%Y-%m")
if last_month == current_month:
# 本次和上次处于同一个月签到
checkin_info["month_times"] += 1
else:
checkin_info["month_times"] = 1
# 总签到次数一直累加
checkin_info["total_times"] += 1
# 更改最后签到时间
checkin_info['last_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 3.更新本次签到信息
try:
services.update_user_checkin_info(checkin_info)
except services.CheckInFail:
return {
"errno": code.CODE_CHECKIN_ERROR,
"errmsg": message.checkin_error,
}
# 4.重新获取签到信息,并返回结果
checkin_info = services.get_checkin_info_by_id(user.id)
return {
"errno": code.CODE_OK,
"errmsg": message.ok,
"checkin_info": checkin_info,
}
- 数据处理层,
checkin.services
,代码:
# 用户签到失败错误类
class CheckInFail(Exception):
pass
# 更新用户签到信息
def update_user_checkin_info(checkin_info):
'''
更新用户签到信息
:param checkin_info: checkin_info
:return:
'''
try:
# 1.更新用户签到信息
UserCheckInDocument.objects.update(
user_id=checkin_info["user_id"],
duration=checkin_info["duration"],
month_times=checkin_info["month_times"],
total_times=checkin_info["total_times"],
last_time=checkin_info["last_time"],
)
# 2.添加用户签到日志
UserCheckInLog.objects.create(
user_id=checkin_info["user_id"],
create_time=checkin_info["last_time"],
)
except Exception:
raise CheckInFail
- 路由
checkin.urls
,代码:
from application import path, api_rpc
# 引入当前蓝图应用视图, 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = [
# path('/index', views.index, methods=['post']),
]
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('checkin_info', api.checkin_info), # 用户签到信息
api_rpc('check_in', api.check_in), # 用户签到处理
]
错误状态码:application/utils/code.py
CODE_CHECKIN_REPEAT = 1201 # 用户已经签到
CODE_CHECKIN_ERROR = 1202 # 签到失败
错误提示:application/utils/message.py
checkin_repeat = '用户已经签到'
checkin_error = '签到失败'
客户端实现用户签到功能
<!DOCTYPE html>
<html>
<head>
<title>用户中心</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/vue-luck-draw.umd.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app lucky" id="app">
<div class="bg">
<img class="meadow" src="../static/images/bg2.png" alt="">
<img class="board_bg2" src="../static/images/board_bg2.png" alt="">
</div>
<div class="lucky_panel">
<ul class="lucky_info">
<li><p class="title">{{checkin_info.duration}}</p><p class="info">连续签到</p></li>
<li><p class="title">{{checkin_info.month_times}}</p><p class="info">本月签到</p></li>
<li><p class="title">{{checkin_info.total_times}}</p><p class="info">总共签到</p></li>
</ul>
<!-- <img class="signin" @click="checkin_handle" :src="checkin_info.is_checkin?'../static/images/sign2.gif':'../static/images/sign.gif'" alt=""> -->
<img class="signin" v-if='!checkin_info.is_checkin' @click="checkin_handle" src="../static/images/sign.gif" alt="">
<img class="signin" v-else @click="game.tips('已经签到了!')" src="../static/images/sign2.gif" alt="">
</div>
<img class="lucky_bg" src="../static/images/bg.3.png" alt="">
<div class="lucky_wheel">
<lucky-wheel
v-cloak
ref="LuckyWheel"
width="230px"
height="230px"
:prizes="prizes"
:default-style="defaultStyle"
:blocks="blocks"
:buttons="buttons"
@start="startCallBack"
@end="endCallBack"
/>
</div>
<div class="lucky_list">
<p v-for="data in lucky_list">{{data}}</p>
</div>
</div>
<script>
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
apiready = function(){
new Vue({
el:"#app",
data(){
return {
checkin_info:{ // 用户签到信息
duration: 0, // 持续签到天数
month_times:0, // 本月签到天数
total_times: 0, // 总签到天数
is_checkin:false // 今天是否签到了
},
lucky_list:[
"12:03:40 深圳用户 xxx 130xxxx0000 抽到了3个小舔狗",
"12:03:39 深圳用户 xxx 130xxxx0000 抽到了50元红包",
"12:03:38 深圳用户 xxx 130xxxx0000 抽到了1个小舔狗",
"12:03:37 深圳用户 xxx 130xxxx0000 抽到了500元红包",
"12:03:36 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:35 深圳用户 xxx 130xxxx0000 抽到了1个苹果树",
"12:03:34 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:33 深圳用户 xxx 130xxxx0000 抽到了1个贵宾犬",
"12:03:32 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:41 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
],
prizes: [],
defaultStyle: {
fontColor: '#d64737',
fontSize: '14px'
},
blocks: [
{ padding: '13px', background: '#d64737' }
],
buttons: [
{
radius: '35px', background: '#ffdea0',
imgs: [{ src: '../static/images/btn.1.png', width: '100%', top: '-120%' }]
}
],
}
},
created(){
// 获取用户签到信息
this.get_user_checkin();
},
mounted(){
// 事件监听
this.listener();
this.getPrizesList();
setInterval(()=>{ // 这段代码仅作测试
let data = this.lucky_list.pop();
this.lucky_list.unshift(data);
},1000);
},
methods:{
// 获取用户签到信息
get_user_checkin(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Checkin.checkin_info',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// self.game.print(data.result.checkin_info,1)
self.checkin_info = data.result.checkin_info
}else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 用户签到处理
checkin_handle(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Checkin.check_in',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// self.game.print(data.result.checkin_info,1)
self.checkin_info = data.result.checkin_info
}else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
listener(){
// 全局事件监听方法
},
getPrizesList () {
const prizes = [];
let data = ['1元红包', '100元红包', '0.5元红包', '2元红包', '10元红包', '50元红包', '5元红包']
data.forEach((item, index) => {
prizes.push({
title: item,
background: index % 2 ? '#f9e3bb' : '#f8d384',
fonts: [{ text: item, top: '10%' }],
imgs:[{ src: `../static/images/${index}.png`, width: '30%', top: '35%' }],
});
})
this.prizes = prizes
},
startCallBack () {
if(!this.sign_in){
this.game.tips('对不起,没有签到,无法抽奖哦!~',5000);
return;
}
this.$refs.LuckyWheel.play();
setTimeout(() => {
this.$refs.LuckyWheel.stop(Math.random() * 8 >> 0);
}, 5000);
},
endCallBack (prize) {
this.game.tips(`恭喜你获得${prize.title}`,5000);
this.lucky_list.unshift("00:00:00 深圳用户 xxx 130xxxx0000 抽到了1个"+prize.title)
},
}
});
}
</script>
</body>
</html>
抽奖功能
客户端,我们使用了一个vue的插件来完成抽奖的大转盘特效。插件官网:https://100px.net/usage/vue.html
奖池配置信息
- 抽奖默认配置信息,
setting.plant
,代码:
'''抽奖配置信息'''
# 抽奖奖项设置
INIT_LUCKY = {
# 抽奖活动开始时间, None表示没有时间限制
"luck_start_time": None,
# 抽奖活动持续时间, 单位:秒,0表示没有时间限制
"luck_time": 0,
# 奖品分配模式
# TIMELINE, 按assign_unit指定时间片段平均产出奖品,适用于周期比较长的抽奖活动
# RANDOM, 随机产出奖品,适用于周期较短的抽奖活动
"assign_type": "TIMELINE",
# 当分配模式设置为timeline时间线时,需要设置分配的时间单元
# 例如:默认24 * 3600,表示产品平均按每天分配
"assign_unit": 24 * 3600,
# 礼品列表
# 奖品中奖概率配置:
# 1. 分2块设置, 未中奖概率默认为50%,也就是说50%几率中奖
# 2. gift_list中除了miss选项以外,所有奖品加起来最多只能设置满100%,不要超过!
# 例如:贵宾犬的中奖概率是10%,这表示在用户已经中奖的情况下,抽到贵宾犬的概率是10%!!!
"gift_list":{
# 道具类型奖品
# 注意:prop中的奖品仅用于本地开发环境,关闭debug模式以后,默认从数据库中提取
# 如果数据库中,没有道具类型的奖品,则默认抽奖活动中,没有道具奖品
"prop":[
{
"text": "贵宾犬", # 提示奖品时使用的奖品标题
"image": "pet2.png",
"table": "mf_item_pet", # 当奖品是道具时,mysql中存储道具信息的数据表名
"pid": 1, # 当奖品是道具时,mysql数据库中奖品对应的主键ID
"total_num": 100, # 奖品池中当前奖品的最大阀值, 0表示不设置上限
"unit_num": 1, # 单次中奖的奖品数量
"init_day_num": 0, # 每天奖品发放上限,0表示不设置上限,直接抽完
"time_num": 0, # 当奖品分配模式设置为TIMELINE模式时,奖品在限定时间片段内允许发放数量,0表示不设置上限,直接抽完
"unit_word": "只", # 提示奖品时使用的计量单位
"chance": 10, # 中奖概率,默认为10%
"weight": 10, # 权重值,值越小表示越廉价
'is_guaranteed_gift':True, # 是否保底抽奖
},
{
"text": "人参果",
"image": "fruit_tree.png",
"table": "mf_item_seed",
"pid": 2,
"total_num": 500,
"unit_num": 5,
"init_day_num": 0,
"time_num": 0,
"unit_word": "颗",
"chance": 10,
"weight": 10,
'is_guaranteed_gift': True,
},
{
"text": "过期牛奶",
"image": "prop4.png",
"table": "mf_item_pet_food",
"pid": 2,
"total_num": 1000,
"unit_num": 5,
"init_day_num": 0,
"time_num": 0,
"unit_word": "袋",
"chance": 10,
"weight": 5,
'is_guaranteed_gift': True,
},
],
# 积分礼品
"credit":[
{
"text": "积分",
"image":"integral2.png",
"total_num": 5000,
"unit_num": 100,
"init_day_num": 0,
"time_num": 0,
"unit_word": "个",
"chance": 20,
"weight": 2,
'is_guaranteed_gift': True,
}
],
# 现金礼品
"money":[
{
"text": "红包",
"image": "1.png",
"total_num": 500,
"unit_num": 100,
"init_day_num": 0,
"time_num": 0,
"unit_word": "元",
"chance": 15,
"weight": 10,
'is_guaranteed_gift': False,
},
{
"text": "红包",
"image": "4.png",
"total_num": 1000,
"unit_num": 10,
"init_day_num": 0,
"time_num": 0,
"unit_word": "元",
"chance": 15,
"weight": 10,
'is_guaranteed_gift': False,
},
],
# 其他奖项礼品
"other": [
{
"text": "马来6天7夜游",
"image": "prop6.png",
"total_num": 100,
"unit_num": 1,
"init_day_num": 0,
"time_num": 0,
"unit_word": "个",
"chance": 20,
"weight": 20,
'is_guaranteed_gift': True,
},
],
},
# 未中奖[chance=0表示本次抽奖必中]
"miss": {
"text": "谢谢参与",
"image": "face2.png",
"chance": 50, # 不中奖概率默认为50%,也就是说中奖概率为剩下的50%
},
# 保底中奖,默认True,表示开启保底中奖模式
# 一但开启,则默认必须配置触发条件
"guaranteed": True,
# 当开启保底中奖功能时的触发条件,
# 默认连抽10次不中奖则触发保底中奖机制
"guaranteed_start": 10,
}
- 建立抽奖功能基本配置信息模型
lucky.documents
,代码:
from application import mongoengine as mgdb
from application.settings.plant import INIT_LUCKY
# 抽奖功能基本配置信息
class LuckyInitConfigDocument(mgdb.Document):
"""抽奖功能基本配置信息"""
meta = {
'collection': 'lucky_init_config',
'ordering': ['_id'], # 倒序排列
'strict': False, # 是否严格语法
'indexes': [ # 索引列表
'name', # 普通索引
]
}
# 奖品分配模式的分配选项
ASSIGN_OPTION = (
("TIMELINE","按指定时间片段"),
("RANDOM","随机产出奖品"),
)
id = mgdb.SequenceField(primary_key=True)
name = mgdb.StringField(unique=True, required=True, verbose_name="抽象活动配置名称")
luck_start_time = mgdb.DateTimeField(null=True, default=INIT_LUCKY["luck_start_time"],verbose_name="抽奖活动开始时间")
luck_time = mgdb.IntField(default=INIT_LUCKY["luck_time"],verbose_name="抽奖活动持续时间【秒】")
assign_type = mgdb.StringField(default=INIT_LUCKY["assign_type"], choices=ASSIGN_OPTION,verbose_name="奖品分配模式")
assign_unit = mgdb.IntField(default=INIT_LUCKY["assign_unit"],verbose_name="奖品分配模式为TIMELINE时的奖品产出时间单位")
miss = mgdb.DictField(default=INIT_LUCKY["miss"],verbose_name="不中奖设置")
guaranteed = mgdb.BooleanField(default=INIT_LUCKY["guaranteed"],verbose_name="是否开启保底中奖机制")
guaranteed_start = mgdb.IntField(default=INIT_LUCKY["guaranteed_start"],verbose_name="触发保底中奖的条件")
@classmethod
def test_data(self,name="测试抽奖活动"):
return self.objects.create(name=name)
# 抽奖奖品池
class LuckyPrizeConfigDocument(mgdb.Document):
"""抽奖奖品池"""
meta = {
'collection': 'lucky_prize_config',
'ordering': ['_id'], # 倒序排列
'strict': False, # 是否严格语法
'indexes': [ # 索引列表
'chance', # 概率
'lucky_id', # 抽奖基本配置的id
]
}
PRIZE_TYPE_OPTION = (
("credit", "积分类型"),
("money", "现金类型"),
("prop","道具类型"),
("other","其他类型"),
)
id = mgdb.SequenceField(primary_key=True)
lucky_id = mgdb.IntField(required=True) # 识别当前奖品属于哪个抽奖活动主键ID
prize_type = mgdb.StringField(default=PRIZE_TYPE_OPTION[0], choices=PRIZE_TYPE_OPTION, verbose_name='奖品类型')
text = mgdb.StringField(required=True, verbose_name='奖品标题')
image = mgdb.StringField(null=True, verbose_name='奖品照片')
table = mgdb.StringField(null=True, verbose_name='mysql中存储道具信息的数据表名')
pid = mgdb.IntField(default=0, verbose_name='mysql数据库中奖品对应的主键ID')
total_num = mgdb.IntField(default=1000, verbose_name='奖品池中当前奖品的最大阀值, 0表示不设置上限')
unit_num = mgdb.IntField(precision=1,default=1, verbose_name='单次中奖的奖品数量')
init_day_num = mgdb.IntField(default=10, verbose_name='每天奖品发放上限,0表示不设置上限,直接抽完')
time_num = mgdb.IntField(default=0, verbose_name='当奖品分配模式设置为TIMELINE模式时,奖品在限定时间片段内允许发放数量,0表示不设置上限,直接抽完')
unit_word = mgdb.StringField(required=True, verbose_name='提示奖品时使用的计量单位')
chance = mgdb.IntField(default=1, verbose_name='中奖概率')
weight = mgdb.IntField(default=1, verbose_name='权重值,值越小表示越廉价')
is_guaranteed_gift = mgdb.BooleanField(default=True, verbose_name='是否参与到保底中奖环节')
@classmethod
def test_data(self,luck_id=1):
"""添加测试奖品信息"""
for prop in INIT_LUCKY["gift_list"]["prop"]:
self.objects.create(
lucky_id=luck_id,
prize_type="prop",
**prop
)
for credit in INIT_LUCKY["gift_list"]["credit"]:
self.objects.create(
lucky_id=luck_id,
prize_type="credit",
**credit
)
for money in INIT_LUCKY["gift_list"]["money"]:
self.objects.create(
lucky_id=luck_id,
prize_type="money",
**money
)
for other in INIT_LUCKY["gift_list"]["other"]:
self.objects.create(
lucky_id=luck_id,
prize_type="other",
**other
)
3.自定义终端命令,添加抽奖活动配置信息的测试数据
application.utils.commands
,代码:
# 批量生成测试数据
class FakerCommand(Command):
"""批量生成测试数据"""
name = 'faker' # 生成命令名称
# 传递的参数
option_list = [
Option('--type', '-t', dest='type', default='user'), # 指定生成数据的类型(用户数据,其他数据)
Option('--num', '-n', dest='num', default=1), # 生成数据的数量
]
# 以后想要生成数据类型列表
type_list = ['user','seed','pet','pet_food','plant','user_pet','lucky']
def __call__(self, app, type, num):
# 判断想要生成数据的类型是否在类型列表中
if type not in self.type_list:
print("数据类型不正确\n当前Faker生成数据类型仅支持: %s" % self.type_list)
return None
num = int(num)
if num < 1:
print("生成数量不正确\n当前Faker生成数据至少1个以上")
return None
if type == 'user':
# 生成用户测试数据
self.create_user(app, num)
elif type == 'seed':
# 生成种子道具测试数据
self.create_seed(app, num)
elif type == 'pet':
# 生成宠物道具测试数据
self.create_pet(app, num)
elif type == 'pet_food':
# 生成宠物食物测试数据
self.create_pet_food(app, num)
elif type == 'plant':
# 生成植物加速成长道具测试数据
self.create_plant(app, num)
elif type == 'user_pet':
# 生成用户宠物测试数据(mongodb)
self.create_user_pet(app, num)
elif type == 'lucky':
# 生成用户抽奖奖品测试数据(mongodb)
self.create_lucky(app, num)
# 生成用户抽奖测试数据(mongodb)
def create_lucky(self, app, num):
from application.apps.lucky.documents import LuckyInitConfigDocument, LuckyPrizeConfigDocument
init = LuckyInitConfigDocument.test_data()
LuckyPrizeConfigDocument.test_data(init.id)
print("生成测试奖品活动完成...")
# 1. 生成指定数量的测试用户信息
def create_user(self,app,num):
"""生成指定数量的测试用户信息"""
from application.apps.users.models import User,UserProfile
faker = app.faker
faker.add_provider(internet)
user_list = [] # 用户模型对象列表
# 从配置文件中读取默认测试用户的登录密码
password = app.config.get("DEFAULT_TEST_USER_PASSWORD", "12345678")
for i in range(num):
sex = bool(random.randint(0,2))
nickname = faker.name()
# 登录账号[随机字母,6-16位]
name = faker.pystr(min_chars=6, max_chars=16)
age = random.randint(13, 50)
birthday = faker.date_time_between(start_date="-%sy" % age, end_date="-12y", tzinfo=None)
hometown_province = faker.province()
hometown_city = faker.city()
hometown_area = faker.district()
living_province = faker.province()
living_city = faker.city()
living_area = faker.district()
user = User(
nickname=nickname,
sex=sex,
name=name,
age=age,
password=password,
pay_password=password,
money=random.randint(100, 99999),
credit=random.randint(100, 99999),
ip_address=faker.ipv4_public(),
email=faker.ascii_free_email(),
mobile=faker.phone_number(),
unique_id=faker.uuid4(),
province=faker.province(),
city=faker.city(),
area=faker.district(),
info=UserProfile(
birthday=birthday,
hometown_province=hometown_province,
hometown_city=hometown_city,
hometown_area=hometown_area,
hometown_address=hometown_province + hometown_city + hometown_area + faker.street_address(),
living_province=living_province,
living_city=living_city,
living_area=living_area,
living_address=living_province + living_city + living_area + faker.street_address()
)
)
user_list.append(user)
# 存储数据
with app.app_context():
User.add_all(user_list)
print("生成%s个用户信息完成..." % num)
# 2. 生成指定数量的种子道具测试数据
def create_seed(self, app, num):
"""生成指定数量的种子道具测试数据"""
from application.apps.orchard.models import SeedItem
seed_list = [] # 模型对象列表
for i in range(num):
seed = SeedItem(
name = f'苹果{i}号',
price = random.randint(1, 20),
credit = random.randint(100, 1000),
image = 'fruit_tree.png',
remark = '果实饱满香甜',
)
seed_list.append(seed)
# 存储数据
with app.app_context():
SeedItem.add_all(seed_list)
print("生成%s个种子道具信息完成..." % num)
# 3. 生成指定数量的宠物道具测试数据
def create_pet(self, app, num):
"""生成指定数量的宠物道具测试数据"""
from application.apps.orchard.models import PetItem
pet_list = [] # 模型对象列表
pet_name_list = ["中华田园犬", "贵宾犬", "哮天犬"]
pet_img_list = ["pet1.png", "pet2.png", "pet3.png"]
for i in range(num):
name = random.choice(pet_name_list)
if name == '中华田园犬':
image = pet_img_list[0]
elif name == '贵宾犬':
image = pet_img_list[1]
elif name == '哮天犬':
image = pet_img_list[2]
pet = PetItem(
name = name,
price = random.randint(1, 20),
credit = random.randint(100, 1000),
remark = '看家护院好帮手',
image = image,
)
pet_list.append(pet)
# 存储数据
with app.app_context():
PetItem.add_all(pet_list)
print("生成%s个宠物道具信息完成..." % num)
# 4. 生成指定数量的宠物食物道具测试数据
def create_pet_food(self, app, num):
"""生成指定数量的宠物食物道具测试数据"""
from application.apps.orchard.models import PetFoodItem
pet_food_list = [] # 模型对象列表
for i in range(num):
pet_food = PetFoodItem(
name = f'牛奶{i}号',
price = random.randint(1, 20),
credit = random.randint(100, 1000),
remark = '补充体力',
image = 'prop4.png',
)
pet_food_list.append(pet_food)
# 存储数据
with app.app_context():
PetFoodItem.add_all(pet_food_list)
print("生成%s个宠物食物道具信息完成..." % num)
# 5. 生成指定数量的植物成长加速道具测试数据
def create_plant(self, app, num):
"""生成指定数量的植物成长加速道具测试数据"""
from application.apps.orchard.models import PlantItem
plant_list = [] # 模型对象列表
for i in range(num):
plant = PlantItem(
name = f'化肥{i}号',
price = random.randint(1, 20),
credit = random.randint(100, 1000),
remark = '补充体力',
image = 'prop1.png',
)
plant_list.append(plant)
# 存储数据
with app.app_context():
PlantItem.add_all(plant_list)
print("生成%s个植物加速成长道具信息完成..." % num)
# 6.生成用户宠物测试数据(mongodb)
def create_user_pet(self,app, num):
# 引入用户宠物文档模型
from application.apps.orchard.documents import UserPetDocument
# 用户宠物信息
pet = {
"user_id": 1,
"pet_position": 2,
"pet_1": {
"start_time": "2021-03-31 14:00:00",
"pet_image": "pet1.png",
"pet_id": 1,
"life_time": -1,
"hunger_num": 20,
"satiety_num": 80,
"hit_rate": 10
},
"pet_2": {
"start_time": "2021-07-02 14:00:30",
"pet_image": "pet2.png",
"pet_id": 3,
"life_time": -1,
"hunger_num": 20,
"satiety_num": 80,
"hit_rate": 10
}
}
# 添加数据
UserPetDocument.objects.create(**pet)
print("生成%s个用户宠物信息完成..." % num)
终端命令生成抽奖测试数据
python manage.py faker -tlucky
1. 服务端提供抽奖池配置和奖品清单的api接口
- 视图代码
apps.lucky.api
,代码:
from flask_jwt_extended import jwt_required
from application import code, message
from application.utils import decorator
from . import services
from .marshmallow import LuckyPrizeConfigSchema
# 获取抽奖活动信息
@jwt_required()
@decorator.get_user_object
def get_lucky_info(user):
'''
获取抽奖活动信息
:param user: 装饰器通过token获取的用户模型对象
:return:
'''
# 获取抽奖的基本配置信息
lucky_init_config = services.get_lucky_init_config("every_day_checkin")
# 获取本次奖品池的奖品清单
try:
gift_list = services.get_lucky_prize_config(lucky_init_config)
except services.PrizeError:
# 没有奖品报错
return {
"errno": code.CODE_NO_PRIZE,
"errmsg": message.no_prize,
}
# 如果不中奖概率大于0, 追加没有中奖配置信息
if lucky_init_config.miss['chance'] > 0:
gift_list.append({
'text': lucky_init_config.miss['text'],
'image': lucky_init_config.miss['image']
})
print("gift_list =", gift_list)
return {
'errno': code.CODE_OK,
'errmsg': message.ok,
'gift_list': gift_list
}
- 数据处理层,
lucky.services
,代码:
from datetime import datetime
from application.settings.plant import INIT_LUCKY, GILT_LENGTH
from .documents import LuckyInitConfigDocument, LuckyPrizeConfigDocument
# 根据抽奖活动的名称获取配置信息
def get_lucky_init_config(config_name):
"""根据抽奖活动的名称获取配置信息"""
try:
lucky_init_config = LuckyInitConfigDocument.objects.get(name=config_name)
except:
# 如果获取不到,新创建数据
lucky_init_config = LuckyInitConfigDocument.objects.create(name=config_name)
return lucky_init_config
# 定义奖品错误类
class PrizeError(Exception):
pass
# 获取抽奖活动配置对应的奖品列表
def get_lucky_prize_config(lucky_init_config):
"""
获取抽奖活动配置对应的奖品列表
:param lucky_init_config:
:return:
"""
gift_list = LuckyPrizeConfigDocument.objects.filter(
LuckyPrizeConfigDocument.lucky_id == lucky_init_config.id
).all()[:GILT_LENGTH]
if len(gift_list) < 1:
raise PrizeError
# 序列化输出
lpcs = LuckyPrizeConfigSchema(many=True)
gift_list = lpcs.dump(gift_list)
return gift_list
- 奖品列表模型构造
lucky.marshmallow
from marshmallow import Schema, fields
# 抽奖奖品池构造器
class LuckyPrizeConfigSchema(Schema):
id = fields.Integer()
lucky_id = fields.Integer()
prize_type = fields.String()
text = fields.String()
image = fields.String()
table = fields.String()
pid = fields.Integer()
total_num = fields.Integer()
unit_num = fields.Integer()
init_day_num = fields.Integer()
time_num = fields.Integer()
unit_word = fields.String()
chance = fields.Integer()
weight = fields.Integer()
is_guaranteed_gift = fields.Boolean()
- 路由
lucky.urls
,代码:
from application import path, api_rpc
# 引入当前蓝图应用视图, 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = [
# path('/index', views.index, methods=['post']),
]
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('lucky_info', api.get_lucky_info), # 获取抽奖奖品信息
]
- 错误提示码和提示信息
错误提示码 application.utils.code
CODE_NO_PRIZE = 1203 # 没有奖品
错误提示信息 application.utils.code
no_prize = '没有奖品'
客户端获取奖品池的奖品清单(在获取签到信息后)
lucky.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>用户中心</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/vue-luck-draw.umd.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app lucky" id="app">
<div class="bg">
<img class="meadow" src="../static/images/bg2.png" alt="">
<img class="board_bg2" src="../static/images/board_bg2.png" alt="">
</div>
<div class="lucky_panel">
<ul class="lucky_info">
<li><p class="title">{{checkin_info.duration}}</p><p class="info">连续签到</p></li>
<li><p class="title">{{checkin_info.month_times}}</p><p class="info">本月签到</p></li>
<li><p class="title">{{checkin_info.total_times}}</p><p class="info">总共签到</p></li>
</ul>
<!-- <img class="signin" @click="checkin_handle" :src="checkin_info.is_checkin?'../static/images/sign2.gif':'../static/images/sign.gif'" alt=""> -->
<img class="signin" v-if='!checkin_info.is_checkin' @click="checkin_handle" src="../static/images/sign.gif" alt="">
<img class="signin" v-else @click="game.tips('已经签到了!')" src="../static/images/sign2.gif" alt="">
</div>
<img class="lucky_bg" src="../static/images/bg.2.png" alt="">
<div class="lucky_wheel">
<lucky-wheel
v-cloak
ref="LuckyWheel"
width="230px"
height="230px"
:prizes="prizes"
:default-style="defaultStyle"
:blocks="blocks"
:buttons="buttons"
@start="startCallBack"
@end="endCallBack"
/>
</div>
<div class="lucky_list">
<p v-for="data in lucky_list">{{data}}</p>
</div>
</div>
<script>
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
apiready = function(){
new Vue({
el:"#app",
data(){
return {
checkin_info:{ // 用户签到信息
duration: 0, // 持续签到天数
month_times:0, // 本月签到天数
total_times: 0, // 总签到天数
is_checkin:false // 今天是否签到了
},
gift_list:[], // 抽奖奖品列表
lucky_list:[
"12:03:40 深圳用户 xxx 130xxxx0000 抽到了3个小舔狗",
"12:03:39 深圳用户 xxx 130xxxx0000 抽到了50元红包",
"12:03:38 深圳用户 xxx 130xxxx0000 抽到了1个小舔狗",
"12:03:37 深圳用户 xxx 130xxxx0000 抽到了500元红包",
"12:03:36 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:35 深圳用户 xxx 130xxxx0000 抽到了1个苹果树",
"12:03:34 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:33 深圳用户 xxx 130xxxx0000 抽到了1个贵宾犬",
"12:03:32 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:41 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
],
prizes: [],
defaultStyle: {
fontColor: '#d64737',
fontSize: '14px'
},
blocks: [
{ padding: '13px', background: '#d64737' }
],
buttons: [
{
radius: '35px', background: '#ffdea0',
imgs: [{ src: '../static/images/btn.1.png', width: '100%', top: '-120%' }]
}
],
}
},
created(){
// 获取用户签到信息,成功后获取抽奖奖品列表信息
this.get_user_checkin();
},
mounted(){
setInterval(()=>{ // 这段代码仅作测试
let data = this.lucky_list.pop();
this.lucky_list.unshift(data);
},1000);
},
methods:{
// 获取用户签到信息
get_user_checkin(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Checkin.checkin_info',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// self.game.print(data.result.checkin_info,1)
self.checkin_info = data.result.checkin_info
// 获取抽奖奖品信息
self.get_lucky_info();
}else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 用户签到处理
checkin_handle(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Checkin.check_in',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// self.game.print(data.result.checkin_info,1)
self.checkin_info = data.result.checkin_info
}else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 获取抽奖奖品信息
get_lucky_info(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Lucky.lucky_info',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// self.game.print(data.result.gift_list,1)
self.gift_list = data.result.gift_list
// 初始化奖池中的奖品列表
self.initgiftlist();
}else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 初始化奖池中的奖品列表
initgiftlist(){
// 获取奖品池中的奖品列表
const prizes = [];
this.gift_list.forEach((item, index)=>{
if(item.unit_num == undefined){
item.unit_num = "";
}
if(item.unit_word == undefined){
item.unit_word = "";
}
prizes.push({
title: item.unit_num+item.unit_word+item.text,
background: index % 2 ? '#f9e3bb' : '#f8d384',
fonts: [{ text: item.unit_num+item.unit_word+item.text, top: '10%' }],
imgs:[{ src: `../static/images/${item.image}`, width: '30%', top: '35%' }],
});
})
this.prizes = prizes
},
startCallBack () {
if(!this.checkin_info.is_checkin){
this.game.tips('对不起,没有签到,无法抽奖哦!~',5000);
return;
}
this.$refs.LuckyWheel.play();
setTimeout(() => {
this.$refs.LuckyWheel.stop(Math.random() * 8 >> 0);
}, 5000);
},
endCallBack (prize) {
this.game.tips(`恭喜你获得${prize.title}`,5000);
this.lucky_list.unshift("00:00:00 深圳用户 xxx 130xxxx0000 抽到了1个"+prize.title)
},
}
});
}
</script>
</body>
</html>
基于洗牌算法每次调整奖品的清单进行乱序处理
# 洗牌算法 random.shuffle()
import random
length = len(gift_list)
for i in range( 0, length - 1):
index = length - i
arr[index],arr[random.randint(0, index)] = arr[random.randint(0, index)],arr[index]
# 随机因子 时序攻击手法
random.SystemRandom(time.time()).randint(1,10000)
apps.lucky.api
,代码:
import random
from flask_jwt_extended import jwt_required
from application import code, message
from application.utils import decorator
from . import services
from .marshmallow import LuckyPrizeConfigSchema
# 获取抽奖活动信息
@jwt_required()
@decorator.get_user_object
def get_lucky_info(user):
'''
获取抽奖活动信息
:param user: 装饰器通过token获取的用户模型对象
:return:
'''
# 获取抽奖的基本配置信息
lucky_init_config = services.get_lucky_init_config("every_day_checkin")
# 获取本次奖品池的奖品清单
try:
lucky_prize_list = services.get_lucky_prize_config(lucky_init_config)
# 序列化输出
lpcs = LuckyPrizeConfigSchema(many=True)
gift_list = lpcs.dump(lucky_prize_list)
except services.PrizeError:
# 没有奖品报错
return {
"errno": code.CODE_NO_PRIZE,
"errmsg": message.no_prize,
}
# 如果不中奖概率大于0, 追加没有中奖配置信息
if lucky_init_config.miss['chance'] > 0:
gift_list.append({
'text': lucky_init_config.miss['text'],
'image': lucky_init_config.miss['image']
})
# 洗牌算法,打乱抽奖顺序
random.shuffle(gift_list)
# print("gift_list =", gift_list)
return {
'errno': code.CODE_OK,
'errmsg': message.ok,
'gift_list': gift_list
}
2. 服务端提供用户抽奖功能的api接口
- 用户抽奖配置模型,
lucky.documents
,代码:
# 用户抽奖配置
class LuckyUserConfigDocument(mgdb.Document):
"""用户抽奖配置"""
meta = {
'collection': 'lucky_user_config',
'ordering': ['_id'], # 正序排列
'strict': False, # 是否严格语法
# 索引列表
'indexes': [{
'fields': ['-user_id','config_name'], # 用户id和抽奖配置名称作为唯一联合索引
'unique': True,
'sparse': True,
}]
}
user_id = mgdb.IntField(required=True)
config_name = mgdb.StringField(required=True, verbose_name="抽奖活动配置名称")
# 如果,用户可有多次抽奖机会,则判断times是否大于0
times = mgdb.IntField(required=True,default=10, verbose_name="用户可抽奖次数")
# 如果,每天一次抽奖机会,判断last_time即可
last_time = mgdb.DateTimeField(null=True, verbose_name="最后一次抽奖时间")
# 连续不中奖的次数
guaranteed = mgdb.IntField(required=True,default=0, verbose_name="连续miss次数")
- 用户抽奖处理
apps.lucky.api
,视图代码:
import random
from flask_jwt_extended import jwt_required
from application import code, message
from application.utils import decorator
from . import services
# 用户抽奖处理
@jwt_required()
@decorator.get_user_object
def user_lucky_start(user):
'''
用户抽奖处理
:param user: 装饰器通过token获取的用户模型对象
:return:
'''
# 1.获取抽奖活动配置信息
lucky_init_config = services.get_lucky_init_config('every_day_checkin')
# 判断是否在抽奖活动时间范围内?
if not services.is_start_lucky_activity(lucky_init_config):
return {
"errno": code.CODE_NOT_SUCH_ACTIVITY,
"errmsg": message.not_such_activity,
}
# 2.判断用户是否有抽奖资格
if not services.has_lucky_times(user.id):
return {
"errno": code.CODE_NOT_LUCKY_CHANCE,
"errmsg": message.not_lucky_chance,
}
# 3.判断用户本次是否属于保底中奖环节
# 获取用户抽奖配置信息
user_lucky_config = services.get_user_lucky_config(user.id, config_name='every_day_checkin')
is_guaranteed = services.is_guaranteed(lucky_init_config, user_lucky_config)
# 4. 获取抽奖池中的奖品清单
prizes_list = services.get_prize_list(lucky_init_config, is_guaranteed)
# 5. 用户抽奖
lucky_result = services.lucky_start(lucky_init_config, prizes_list, is_guaranteed)
# 6. 扣除抽奖次数,在每天签到的时候恢复抽奖次数
# 修改用户抽奖配置信息
services.update_lucky_user_config(user.id, lucky_result)
# 7. todo 判断中奖奖品的类型,根据不同的类型,
if lucky_result['is_skill']:
pass
# 7.1 属于积分,现金,则直接添加到当前用户账户上
# 7.2 属于道具,则添加到用户的背包中
# 7.3 属于其他类型奖品,则提示用户等待工作人员的联系
try:
print('抽奖结果 = ', lucky_result)
except:
pass
# 8. 返回抽奖结果
return {
"errno": code.CODE_OK,
"errmsg": message.ok,
"lucky_result": lucky_result
}
- 数据处理层,
lucky.services
,代码:
# 判断当前时间是否属于抽奖活动开展时间范围内
def is_start_lucky_activity(config):
"""
判断当前时间是否属于抽奖活动开展时间范围内
:param config: 抽奖活动的初始化配置[字典格式]
:return:
"""
# 1.活动没有时间限制
if (config['luck_start_time'] is None) and (config['luck_time']==0):
return True
# 2.活动时间限制[判断当前时间是否在活动时间内]
# 当前时间戳
now_timestamp = datetime.now().timestamp()
if config['luck_start_time']:
# 活动开始时间戳
act_start_timestamp = datetime.strptime(config["luck_start_time"],"%Y-%m-%d %H:%M:%S").timestamp()
if now_timestamp < act_start_timestamp:
return False
if config['luck_time'] > 0:
# 活动结束时间戳
act_end_timestamp = act_start_timestamp + int(config['luck_time'])
if now_timestamp > act_end_timestamp:
return False
return True
# 判断用户是否有抽奖资格
def has_lucky_times(user_id):
'''
判断用户是否有抽奖资格
:param user_id: 用户ID
:return:
'''
# 1. 判断用户是否签到了
# 获取用户签到信息
user_checkin_info = checkin_services.get_checkin_info_by_id(user_id)
if not user_checkin_info['is_checkin']:
return False
# 2.判断用户在每天签到抽奖中是否还有抽奖次数
# 获取用户抽奖配置信息
user_lucky_config = get_user_lucky_config(user_id, config_name='every_day_checkin')
if user_lucky_config['times'] <= 0:
return False
return True
# 获取用户抽奖配置信息
def get_user_lucky_config(user_id,config_name):
try:
# 获取用户抽奖配置信息
user_lucky_config = LuckyUserConfigDocument.objects.get(
user_id = user_id,
config_name = config_name
)
# 序列化输出
lucs = LuckyUserConfigSchema()
return lucs.dump(user_lucky_config)
except LuckyUserConfigDocument.DoesNotExist:
# 没有,重新初始化用户抽奖配置信息
init_user_lucky(
user_id=user_id,
config_name="every_day_checkin",
)
return get_user_lucky_config(user_id,config_name)
# 初始化用户抽奖配置信息
def init_user_lucky(user_id, config_name):
'''
初始化用户抽奖配置信息
:param user_id: 用户ID
:param config_name: 抽奖活动名称
:return:
'''
return LuckyUserConfigDocument.objects.create(
user_id = user_id,
config_name = config_name
)
# 判断用户是否进入保底中奖环节
def is_guaranteed(lucky_init_config, user_lucky_config):
"""
判断用户是否进入保底中奖环节
:param lucky_init_config: 当前抽奖活动的初始化配置
:param user_lucky_config: 当前抽奖活动的用户配置
:return:
"""
return int(user_lucky_config["guaranteed"]) >= int(lucky_init_config["guaranteed_start"])
# 获取用户抽奖的奖品清单
def get_prize_list(lucky_init_config, is_guaranteed):
"""
获取用户抽奖的奖品清单
:param lucky_init_config: 抽奖活动的基本配置
:param is_guaranteed: 是否进入保底中奖环节
:return:
"""
# 获取奖品池清单列表
prizes_list = get_lucky_prize_config(lucky_init_config)
# 判断用户是否进入保底中奖环节
if not is_guaranteed:
# 没进入,返回清单列表
return prizes_list
# 否则
data = []
for item in prizes_list:
if item['is_guaranteed_gift']:
data.append(item)
return data
# 用户抽奖功能
def lucky_start(lucky_init_config, prizes_list, is_guaranteed):
"""
用户抽奖功能
:param lucky_init_config: 当前抽奖活动的基本配置(对象)
:param prizes_list: 奖品清单
:param is_guaranteed: 是否保底中奖
:return:
"""
# 如果当前属于保底中奖,则默认用户已经中奖了
is_skill = is_guaranteed
if not is_guaranteed:
# 1. 判断是否有设置了不中奖的情况
# print('lucky_init_config = ',lucky_init_config)
miss = lucky_init_config.miss
if (miss is None) and (type(miss) is not dict):
# 如果没有设置不中奖的情况,则默认用户直接中奖
is_skill = True
# 获取系统设置的不中奖概率
miss_chance = miss.get("chance")
# 基于随机数,判断用户是否中奖
lucky_first_result = random.SystemRandom(time.time()).randint(1, 100)
if lucky_first_result <= (100-miss_chance):
is_skill = True
else:
return {
"is_skill": False, # 没中奖
"prize": {
"text": lucky_init_config.miss["text"],
"image": lucky_init_config.miss["image"],
"unit_num": "",
"unit_word": "",
}
}
# 2. 判断用户中的是什么奖
# 2.1 获取真正的奖品清单和奖品中奖概率
# 1. 真正的奖品清单: 剩余的奖品数量 / TIMELINE模式下限定时间片段内的奖品
print(prizes_list)
lucky_second_result = random.SystemRandom(time.time()).randint(1, 100)
start = 0
end = 0
for prize in prizes_list:
end += int(prize["chance"])
print(f"中奖区间:{start}~{end},本次抽奖结果: %{lucky_second_result}")
if start < lucky_second_result and lucky_second_result <= end:
return {
"is_skill":True,
"prize":{
"text": prize["text"],
"prize_type": prize["prize_type"],
"image": prize["image"],
"unit_num": prize["unit_num"],
"unit_word": prize["unit_word"],
"weight": prize["weight"],
}
}
else:
start+=int(prize["chance"])
- 用户签到数据处理, 重置用户抽奖配置信息
checkin.services
# 更新用户签到信息,并且重置用户抽奖配置信息
def update_user_checkin_info(checkin_info):
'''
更新用户签到信息,并且重置用户抽奖配置信息
:param checkin_info: checkin_info
:return:
'''
try:
# 1.更新用户签到信息
UserCheckInDocument.objects.update(
user_id=checkin_info["user_id"],
duration=checkin_info["duration"],
month_times=checkin_info["month_times"],
total_times=checkin_info["total_times"],
last_time=checkin_info["last_time"],
)
# 2.添加用户签到日志
UserCheckInLog.objects.create(
user_id=checkin_info["user_id"],
create_time=checkin_info["last_time"],
)
# 3.重置用户抽奖配置信息
lucky_services.init_user_lucky(
user_id=checkin_info["user_id"],
config_name='every_day_checkin'
)
except Exception:
raise CheckInFail
- 用户抽奖配置信息构造器
lucky.marshmallow
from marshmallow import Schema, fields
# 用户抽奖配置信息构造器
class LuckyUserConfigSchema(Schema):
user_id = fields.Integer()
config_name = fields.String()
times = fields.Integer()
last_time = fields.DateTime()
guaranteed = fields.Integer()
- 路由
lucky.urls
,代码:
from application import path, api_rpc
# 引入当前蓝图应用视图, 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = [
# path('/index', views.index, methods=['post']),
]
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('lucky_info', api.get_lucky_info), # 获取抽奖奖品信息
api_rpc('lucky_start', api.user_lucky_start), # 用户抽奖
]
状态码:application.utils.code
CODE_NOT_SUCH_ACTIVITY = 1204 # 没有这样的抽奖活动或者抽奖活动已结束
CODE_NOT_LUCKY_CHANCE = 1205 # 没有抽奖次数或者抽奖次数已用完
错误提示,代码:application.utils.message
not_such_activity = '没有抽奖次数或抽奖次数已用完'
not_lucky_chance = '没有这样的抽奖活动或者抽奖活动已结束'
客户端实现用户点击抽奖
html/lucky.html
<!DOCTYPE html>
<html>
<head>
<title>用户中心</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/vue-luck-draw.umd.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app lucky" id="app">
<div class="bg">
<img class="meadow" src="../static/images/bg2.png" alt="">
<img class="board_bg2" src="../static/images/board_bg2.png" alt="">
</div>
<div class="lucky_panel">
<ul class="lucky_info">
<li><p class="title">{{checkin_info.duration}}</p><p class="info">连续签到</p></li>
<li><p class="title">{{checkin_info.month_times}}</p><p class="info">本月签到</p></li>
<li><p class="title">{{checkin_info.total_times}}</p><p class="info">总共签到</p></li>
</ul>
<!-- <img class="signin" @click="checkin_handle" :src="checkin_info.is_checkin?'../static/images/sign2.gif':'../static/images/sign.gif'" alt=""> -->
<img class="signin" v-if='!checkin_info.is_checkin' @click="checkin_handle" src="../static/images/sign.gif" alt="">
<img class="signin" v-else @click="game.tips('已经签到了!')" src="../static/images/sign2.gif" alt="">
</div>
<img class="lucky_bg" src="../static/images/bg.2.png" alt="">
<div class="lucky_wheel">
<lucky-wheel
v-cloak
ref="LuckyWheel"
width="230px"
height="230px"
:prizes="prizes"
:default-style="defaultStyle"
:blocks="blocks"
:buttons="buttons"
@start="startCallBack"
@end="endCallBack"
/>
</div>
<div class="lucky_list">
<p v-for="data in lucky_list">{{data}}</p>
</div>
</div>
<script>
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
apiready = function(){
new Vue({
el:"#app",
data(){
return {
checkin_info:{ // 用户签到信息
duration: 0, // 持续签到天数
month_times:0, // 本月签到天数
total_times: 0, // 总签到天数
is_checkin:false // 今天是否签到了
},
gift_list:[], // 抽奖奖品列表
lucky_list:[
"12:03:40 深圳用户 xxx 130xxxx0000 抽到了3个小舔狗",
"12:03:39 深圳用户 xxx 130xxxx0000 抽到了50元红包",
"12:03:38 深圳用户 xxx 130xxxx0000 抽到了1个小舔狗",
"12:03:37 深圳用户 xxx 130xxxx0000 抽到了500元红包",
"12:03:36 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:35 深圳用户 xxx 130xxxx0000 抽到了1个苹果树",
"12:03:34 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:33 深圳用户 xxx 130xxxx0000 抽到了1个贵宾犬",
"12:03:32 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
"12:03:41 深圳用户 xxx 130xxxx0000 抽到了1个过期化肥",
],
prizes: [],
defaultStyle: {
fontColor: '#d64737',
fontSize: '14px'
},
blocks: [
{ padding: '13px', background: '#d64737' }
],
buttons: [
{
radius: '35px', background: '#ffdea0',
imgs: [{ src: '../static/images/btn.1.png', width: '100%', top: '-120%' }]
}
],
}
},
created(){
// 获取用户签到信息,成功后获取抽奖奖品列表信息
this.get_user_checkin();
},
mounted(){
setInterval(()=>{ // 这段代码仅作测试
let data = this.lucky_list.pop();
this.lucky_list.unshift(data);
},1000);
},
methods:{
// 获取用户签到信息
get_user_checkin(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Checkin.checkin_info',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// self.game.print(data.result.checkin_info,1)
self.checkin_info = data.result.checkin_info
// 获取抽奖奖品信息
self.get_lucky_info();
}else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 用户签到处理
checkin_handle(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Checkin.check_in',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// self.game.print(data.result.checkin_info,1)
self.checkin_info = data.result.checkin_info
}else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 获取抽奖奖品信息
get_lucky_info(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Lucky.lucky_info',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
// self.game.print(data.result.gift_list,1)
self.gift_list = data.result.gift_list
// 初始化奖池中的奖品列表
self.initgiftlist();
}else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 初始化奖池中的奖品列表
initgiftlist(){
// 获取奖品池中的奖品列表
const prizes = [];
this.gift_list.forEach((item, index)=>{
if(item.unit_num == undefined){
item.unit_num = "";
}
if(item.unit_word == undefined){
item.unit_word = "";
}
prizes.push({
title: item.unit_num+item.unit_word+item.text,
background: index % 2 ? '#f9e3bb' : '#f8d384',
fonts: [{ text: item.unit_num+item.unit_word+item.text, top: '10%' }],
imgs:[{ src: `../static/images/${item.image}`, width: '30%', top: '35%' }],
});
})
this.prizes = prizes
},
// 开始转动大转盘
startCallBack () {
if(!this.checkin_info.is_checkin){
this.game.tips('对不起,没有签到,无法抽奖哦!~');
return;
}
this.$refs.LuckyWheel.play();// 开始转动大转盘
// 获取用户抽奖结果
this.get_lucky_result();
// setTimeout(() => {
// this.$refs.LuckyWheel.stop(Math.random() * 8 >> 0);
// }, 5000);
},
// 停止大转盘
endCallBack (prize) {
this.game.tips(`恭喜你获得${prize.title}`,5000);
this.lucky_list.unshift("00:00:00 深圳用户 xxx 130xxxx0000 抽到了1个"+prize.title)
},
// 获取用户抽奖结果
get_lucky_result(){
let self = this;
self.game.check_user_login(self,()=>{
let token = self.game.getdata("access_token") || self.game.getfs("access_token");
self.game.post(self,{
'method': 'Lucky.lucky_start',
'params': {},
'header': {
'Authorization': 'jwt '+ token
},
success(response){
let data = response.data;
if(data.result && data.result.errno == 1000){
let prize = data.result.lucky_result.prize;
// self.game.print( prize,1);
// 判断结果是否在奖品清单上
self.gift_list.forEach((item, index) => {
if(item.image==prize.image && item.text==prize.text && item.weight==prize.weight){
self.$refs.LuckyWheel.stop(index);
return ;
}
});
}else{
self.game.tips(data.result.errmsg);
}
}
})
})
},
}
});
}
</script>
</body>
</html>
mongodb的事务transaction
在MongoDB4.0以前版本中单文档原生操作就支持原子性,也具备事务的特性,但是一般说起事务,通常是指在多文档中的实现,因此MongoDB 在 4.0 版本以后陆续支持并完善了多文档事务特性。目前最新稳定版本4.4,对于MongoDB来说,事务带来的性能影响有多大,暂时还无法确定,使用需谨慎。
pymongo提供了一个自带的session,使用它可以完成事务的操作,代码:
from application import mongo
session = mongo.start_session()
# 事务开始
session.start_transaction()
try:
collection1.insert({"world": 1})
collection2.insert({"hello": 1})
except:
# 操作异常,中断事务
session.abort_transaction()
else:
session.commit_transaction()
finally:
session.end_session()
mongengine目前更新较慢,现在还在github上讨论这玩意怎么落地,这是我从github上的讨论板块上了解到源码里面已经有一个实现方案写出来的。😂
代码:
from mongoengine import get_connection
k = get_connection(alias="default")
with k.start_session() as mongosession:
with mongosession.start_transaction():
# 内部事务操作
# 内部事务操作
try:
Document1.create(字段1="值1", 字段2="值2")
Document2.create(字段1="值1", 字段2="值2")
except:
mongosession.abort_transaction()