欢迎来到十九分快乐的博客

生死看淡,不服就干。

15. 用户签到抽奖处理

签到抽奖

签到抽奖页面显示

  1. 主页面 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

  1. 签到抽奖页面 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>
  1. 添加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接口
  1. 创建签到信息模型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='日志创建时间')


  1. 视图接口,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
  1. 数据处理层,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,
    )
  1. 序列化构造器,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
  1. 路由 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
  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,
    }
  1. 数据处理层,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
  1. 路由 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

奖池配置信息

  1. 抽奖默认配置信息,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,
}
  1. 建立抽奖功能基本配置信息模型 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接口

  1. 视图代码 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
    }

  1. 数据处理层,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
  1. 奖品列表模型构造 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()

  1. 路由 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), # 获取抽奖奖品信息
]
  1. 错误提示码和提示信息

错误提示码 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接口

  1. 用户抽奖配置模型, 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次数")

  1. 用户抽奖处理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
    }

  1. 数据处理层,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"])

  1. 用户签到数据处理, 重置用户抽奖配置信息 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
  1. 用户抽奖配置信息构造器 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()

  1. 路由 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()
posted @ 2021-07-15 18:52  十九分快乐  阅读(131)  评论(0编辑  收藏  举报