魔坊APP專案-20-種植園,揹包顯示道具、使用者購買道具的時候,判斷揹包儲存是否達到上限、揹包解鎖
種植園
一、揹包顯示道具
在揹包中顯示道具,會涉及到使用者的揹包格子的顯示以及解鎖問題,所以我們需要在服務端準備一個引數資訊, 用於儲存種植園中使用者的業務引數,例如:
格子的初始化數量, 每次解鎖揹包格子的價格等等.
引數資訊的儲存與之前專案配置的資訊有所不同, 不同的地方在於, 引數資訊僅僅是種植園額業務引數,會在專案運營的時候允許有所改動,而專案配置的變數引數則在專案上線以後基本不做改動.
所以我們可以使用資料庫表的方式來儲存種植園的引數資訊.orchard/models.py
程式碼:
...
class Setting(BaseModel):
"""引數資訊"""
__tablename__ = 'mf_orchard_setting'
title = db.Column(db.String(255), comment="提示文字")
value = db.Column(db.String(255), comment="數值")
資料遷移, 終端下執行:
python manage.py db migrate -m "add orchard setting table"
python manage.py db upgrade
新增測試資料,sql語句:
INSERT INTO mofang.mf_orchard_setting
(id, name, is_deleted, orders, status, created_time, updated_time, title, value) VALUES
(1, 'package_number_base', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '揹包格子基礎數量', '4'),
(2, 'package_number_max', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '揹包格子上限數量', '32') ,
(3, 'td_prop_max', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '單個格子儲存道具數量上限', '10'),
(4, 'package_unlock_price_1', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖揹包格子價格5-8', '10'),
(5, 'package_unlock_price_2', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖揹包格子價格9-12', '50'),
(6, 'package_unlock_price_3', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖揹包格子價格13-16', '100'),
(7, 'package_unlock_price_4', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖揹包格子價格17-20', '200'),
(8, 'package_unlock_price_5', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖揹包格子價格21-24', '500'),
(9, 'package_unlock_price_6', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖揹包格子價格25-28', '1000'),
(10, 'package_unlock_price_7', 0, 1, 1, '2020-12-30 17:40:46', '2020-12-30 17:40:44', '解鎖揹包格子價格29-32', '5000');
服務端在使用者登陸種植園得到時候需要返回種植園的公共引數以及使用者的私有引數,orchard/socket.py
, 程式碼:
from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods, Setting
from status import APIStatus as status
from message import ErrorMessage as errmsg
# 建立socket通訊
# @socketio.on('connect', namespace='/mofang')
# def user_connect():
# """使用者連線"""
# print('使用者%s連線過來了!' % request.sid)
# # 主動響應資料給客戶端
# socketio.emit('server_response', 'hello', namespace='/mofang')
# 斷開socket通訊
@socketio.on('disconnect', namespace='/mofang')
def user_disconnect():
print('使用者%s退出了種植園' % request.sid)
@socketio.on('login', namespace='/mofang')
def user_login(data):
# 分配房間
room = data['uid']
join_room(room)
# 儲存當前使用者和sid的繫結關係
# 判斷當前使用者是否在mongo中有記錄
query = {
'_id': data['uid']
}
ret = mongo.db.user_info_list.find_one(query)
if ret:
mongo.db.user_info_list.update_one(query, {'$set': {'sid': request.sid}})
else:
mongo.db.user_info_list.insert_one({
'_id': data['uid'],
'sid': request.sid,
})
# 返回種植園的相關配置引數
orchard_settings = {}
setting_list = Setting.query.filter(Setting.is_deleted==False, Setting.status==True).all()
"""
現在的格式:
[<Setting package_number_base>, <Setting package_number_max>, <Setting package_unlock_price_1>...]
需要返回的格式:
{
package_number_base:4,
package_number_max: 32,
...
}
"""
for item in setting_list:
orchard_settings[item.name] = item.value
# 返回當前使用者相關的配置引數
user_settings = {}
# 從mongo中查詢使用者資訊,判斷使用者是否激活了揹包格子
dict = mongo.db.user_info_list.find_one({'sid': request.sid})
# 揹包格子
if dict.get('package_number') is None:
user_settings['package_number'] = orchard_settings.get('package_number_base', 4)
mongo.db.user_info_list.update_one({'sid': request.sid}, {'$set': {'package_number': user_settings['package_number']}})
else:
user_settings['package_number'] = dict.get('package_number')
socketio.emit('login_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'orchard_settings': orchard_settings,
'user_settings': user_settings,
}, namespace='/mofang', room=room)
@socketio.on('user_buy_prop', namespace='/mofang')
def user_buy_prop(data):
"""使用者購買道具"""
room = request.sid
# 從mongo中獲取當前使用者資訊
user_info = mongo.db.user_info_list.find_one({'sid': request.sid})
user = User.query.get(user_info.get('_id'))
if user is None:
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_USER, 'errmsg': errmsg.user_not_exists}, namespace='/mofang', room=room)
return
# 從mysql中獲取商品價格
prop = Goods.query.get(data['pid'])
if float(user.money) < float(prop.price) * int(data['num']):
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_MONEY, 'errmsg': errmsg.money_no_enough}, namespace='/mofang', room=room)
return
# 從mongo中獲取使用者列表資訊,提取購買的商品數量進行累加和餘額
query = {'sid': request.sid}
if user_info.get('prop_list') is None:
"""此前沒有購買任何道具"""
message = {'$set': {'prop_list': {'prop_%s' % prop.id: int(data['num'])}}}
mongo.db.user_info_list.update_one(query, message)
else:
"""此前有購買了道具"""
prop_list = user_info.get('prop_list') # 道具列表
if('prop_%s' % prop.id) in prop_list:
"""如果再次同一款道具"""
prop_list[('prop_%s' % prop.id)] = prop_list[('prop_%s' % prop.id)] + int(data['num'])
else:
"""此前沒有購買過這種道具"""
prop_list[('prop_%s' % prop.id)] = int(data['num'])
mongo.db.user_info_list.update_one(query, {'$set': {'prop_list': prop_list}})
socketio.emit('user_buy_prop_response', {'errno': status.CODE_OK, 'errmsg': errmsg.ok}, namespace='/mofang', room=room)
客戶端顯示格子,orchard.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/main.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/settings.js"></script>
<script src="../static/js/socket.io.js"></script>
</head>
<body>
<div class="app orchard" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="orchard-bg">
<img src="../static/images/bg2.png">
<img class="board_bg2" src="../static/images/board_bg2.png">
</div>
<img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
<div class="header">
<div class="info" @click='go_home'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<p class="user_name">好聽的暱稱</p>
</div>
<div class="wallet">
<div class="balance" @click='user_recharge'>
<p class="title"><img src="../static/images/money.png" alt="">錢包</p>
<p class="num">{{money}}</p>
</div>
<div class="balance">
<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
<p class="num">99,999.00</p>
</div>
</div>
<div class="menu-list">
<div class="menu">
<img src="../static/images/menu1.png" alt="">
排行榜
</div>
<div class="menu">
<img src="../static/images/menu2.png" alt="">
簽到有禮
</div>
<div class="menu" @click='go_orchard_shop'>
<img src="../static/images/menu3.png" alt="">
道具商城
</div>
<div class="menu">
<img src="../static/images/menu4.png" alt="">
郵件中心
</div>
</div>
</div>
<div class="footer">
<ul class="menu-list">
<li class="menu">新手</li>
<li class="menu" @click='go_my_package'>揹包</li>
<li class="menu-center" @click='go_orchard_shop'>商店</li>
<li class="menu">訊息</li>
<li class="menu" @click='go_friends'>好友</li>
</ul>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
music_play:true,
namespace: '/mofang',
token:"",
money:"",
settings_info:{
orchard: {}, // 種植園公共引數
user: {}, // 使用者私有相關引數
},
recharge_list: ['10','20','50','100','200','500','1000'],
timeout: 0,
prev:{name:"",url:"",params:{}},
current:{name:"orchard",url:"orchard.html",params:{}},
}
},
created(){
this.game.goFrame('orchard', 'my_orchard.html', this.current, {
x: 0,
y: 180,
w: 'auto',
h: 410,
}, null);
this.checkout();
this.money = this.game.fget("money");
this.buy_prop();
},
methods:{
user_recharge(){
// 發起充值請求
api.actionSheet({
title: '餘額充值',
cancelTitle: '取消',
buttons: this.recharge_list
}, (ret, err)=>{
if( ret ){
if(ret.buttonIndex <= this.recharge_list.length){
// 充值金額
money = this.recharge_list[ret.buttonIndex-1];
// 呼叫支付寶充值
this.create_recharge(money);
}
}else{
}
});
},
create_recharge(money){
// 獲取歷史資訊記錄
var token = this.game.get('access_token') || this.game.fget('access_token');
this.game.checkout(this, token, (new_access_token)=>{
this.axios.post('', {
'jsonrpc': '2.0',
'id': this.uuid(),
'method': 'Recharge.create',
'params': {
'money': money,
}
},{
headers:{
Authorization: "jwt " + token,
}
}).then(response=>{
// this.game.print(response.data);
if(parseInt(response.data.result.errno)==1000){
// 前往支付寶
var aliPayPlus = api.require("aliPayPlus");
aliPayPlus.payOrder({
orderInfo: response.data.result.order_string,
sandbox: response.data.result.sandbox, // 將來APP上線需要修改成false
}, (ret, err)=>{
pay_result = {
9000:'支付成功',
8000:"正在處理中",
4000:"訂單支付失敗",
5000:"重複請求",
6001:"取消支付",
6002:"網路連接出錯",
6004:"支付結果未知",
}
api.alert({
title: '支付結果',
msg: pay_result[ret.code],
buttons: ['確定']
});
// 通知服務端, 修改充值結果
this.return_recharge(response.data.result.order_number, token);
});
}else {
this.game.print(response.data);
}
}).catch(error=>{
// 網路等異常
this.game.print(error);
});
})
},
return_recharge(out_trade_number, token){
this.axios.post("", {
'jsonrpc':"2.0",
'id':this.uuid(),
'method':'Recharge.return',
'params': {
'out_trade_number':out_trade_number,
}
},{
headers:{
Authorization: "jwt " + token,
}
}).then(response=>{
if(parseInt(response.data.result.errno)==1000){
this.money = response.data.result.money.toFixed(2);
}
})
},
checkout(){
var token = this.game.get("access_token") || this.game.fget("access_token");
this.game.checkout(this,token,(new_access_token)=>{
this.connect();
});
},
connect(){
// socket連線
this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
this.socket.on('connect', ()=>{
this.game.print("開始連線服務端");
var id = this.game.fget('id');
this.socket.emit('login', {'uid': id});
});
},
login(){
this.socket.on('login_response', (message)=>{
this.settings_info.orchard = message.orchard_settings;
this.settings_info.user = message.user_settings;
this.game.fsave({
'orchard_settings': message.orchard_settings,
'user_settings': message.user_settings
});
});
},
go_index(){
this.game.goWin("root");
},
go_friends(){
this.game.goFrame('friends', 'friends.html', this.current);
this.game.goFrame('friend_list', 'friend_list.html', this.current, {
x: 0,
y: 190,
w: 'auto',
h: 'auto',
}, null, true);
},
go_home(){
this.game.goWin('user', 'user.html', this.current);
},
go_orchard_shop(){
// 種植園商店
this.game.goFrame('orchard_shop', 'shop.html', this.current, null, {
type: 'push',
subType: 'from_top',
duration: 300
});
},
go_my_package(){
// 我的揹包
this.game.goFrame('package', 'package.html', this.current, null, {
type: 'push',
subType: 'from_top',
duration: 300
});
},
buy_prop(){
api.addEventListener({
name: 'buy_prop'
}, (ret, err)=>{
if( ret ){
// 使用者購買道具
this.socket.emit('user_buy_prop', ret.value);
}
});
},
}
});
}
</script>
</body>
</html>
package.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/main.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/settings.js"></script>
</head>
<body>
<div class="app frame avatar add_friend package" id="app">
<div class="box">
<p class="title">我的揹包</p>
<img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
<div class="prop_list">
<div class="item" v-for='number in unlock_td_number'></div>
<div class="item lock" v-for='number in lock_td_number'></div>
</div>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
td: 36, // 揹包格子總數量
user_id: "", // 當前登陸使用者Id
orchard_settings: {}, // 種植園相關公共引數
user_settings: {}, // 使用者相關私有引數
prev:{name:"",url:"",params:{}},
current:{name:"package",url:"package.html",params:{}},
}
},
computed:{ // 計算屬性
lock_td_number(){
// 未解鎖的格子
return parseInt(this.orchard_settings.package_number_max-this.user_settings.package_number);
},
unlock_td_number(){
// 解鎖的格子
return parseInt(this.user_settings.package_number);
}
},
created(){
this.user_id = this.game.get("id") || this.game.fget("id");
this.orchard_settings = JSON.parse(this.game.fget('orchard_settings'));
this.user_settings = JSON.parse(this.game.fget('user_settings'));
},
methods:{
close_frame(){
this.game.outFrame("package");
},
}
});
}
</script>
</body>
</html>
接著往下就應用顯示揹包中的道具物品了
在顯示道具的時候, 因為每個格子有儲存上線所以我們可以在引數配置中新增一項引數限制儲存道具的數量, (在上面新增測試資料時已經設定了.)
orchard/socket.py
,程式碼:
...
@socketio.on('user_prop', namespace='/mofang')
def user_prop():
"""使用者道具"""
userinfo = mongo.db.user_info_list.find_one({'sid': request.sid})
prop_list = userinfo.get('prop_list')
prop_id_list = []
for prop_str, num in prop_list.items():
pid = int(prop_str[5:])
prop_id_list.append(pid)
data = []
prop_list_data = Goods.query.filter(Goods.id.in_(prop_id_list)).all()
setting = Setting.query.filter(Setting.name == 'td_prop_max').first()
if setting is None:
td_prop_max = 10
else:
td_prop_max = int(setting.value)
for prop_data in prop_list_data:
num = int(prop_list[('prop_%s' % prop_data.id)])
if td_prop_max > num:
data.append({
'num': num,
'image': prop_data.image,
'pid': prop_data.id
})
else:
padding_time = num // td_prop_max
padding_last = num % td_prop_max
arr = [{
'num': td_prop_max,
'image': prop_data.image,
'pid': prop_data.id
}] * padding_time
if padding_last != 0:
arr.append({
'num': padding_last,
'image': prop_data.image,
'pid': prop_data.id
})
data = data + arr
room = request.sid
socketio.emit('user_prop_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'data': data,
}, namespace='/mofang', room=room)
在客戶端中獲取道具, orchard.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/main.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/settings.js"></script>
<script src="../static/js/socket.io.js"></script>
</head>
<body>
<div class="app orchard" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="orchard-bg">
<img src="../static/images/bg2.png">
<img class="board_bg2" src="../static/images/board_bg2.png">
</div>
<img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
<div class="header">
<div class="info" @click='go_home'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<p class="user_name">好聽的暱稱</p>
</div>
<div class="wallet">
<div class="balance" @click='user_recharge'>
<p class="title"><img src="../static/images/money.png" alt="">錢包</p>
<p class="num">{{money}}</p>
</div>
<div class="balance">
<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
<p class="num">99,999.00</p>
</div>
</div>
<div class="menu-list">
<div class="menu">
<img src="../static/images/menu1.png" alt="">
排行榜
</div>
<div class="menu">
<img src="../static/images/menu2.png" alt="">
簽到有禮
</div>
<div class="menu" @click='go_orchard_shop'>
<img src="../static/images/menu3.png" alt="">
道具商城
</div>
<div class="menu">
<img src="../static/images/menu4.png" alt="">
郵件中心
</div>
</div>
</div>
<div class="footer">
<ul class="menu-list">
<li class="menu">新手</li>
<li class="menu" @click='go_my_package'>揹包</li>
<li class="menu-center" @click='go_orchard_shop'>商店</li>
<li class="menu">訊息</li>
<li class="menu" @click='go_friends'>好友</li>
</ul>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
music_play:true,
namespace: '/mofang',
token:"",
money:"",
settings_info:{
orchard: {}, // 種植園公共引數
user: {}, // 使用者私有相關引數
},
socket: null,
recharge_list: ['10','20','50','100','200','500','1000'],
timeout: 0,
prev:{name:"",url:"",params:{}},
current:{name:"orchard",url:"orchard.html",params:{}},
}
},
created(){
this.game.goFrame('orchard', 'my_orchard.html', this.current, {
x: 0,
y: 180,
w: 'auto',
h: 410,
}, null);
this.checkout();
this.money = this.game.fget("money");
this.buy_prop();
},
methods:{
user_recharge(){
// 發起充值請求
api.actionSheet({
title: '餘額充值',
cancelTitle: '取消',
buttons: this.recharge_list
}, (ret, err)=>{
if( ret ){
if(ret.buttonIndex <= this.recharge_list.length){
// 充值金額
money = this.recharge_list[ret.buttonIndex-1];
// 呼叫支付寶充值
this.create_recharge(money);
}
}else{
}
});
},
create_recharge(money){
// 獲取歷史資訊記錄
var token = this.game.get('access_token') || this.game.fget('access_token');
this.game.checkout(this, token, (new_access_token)=>{
this.axios.post('', {
'jsonrpc': '2.0',
'id': this.uuid(),
'method': 'Recharge.create',
'params': {
'money': money,
}
},{
headers:{
Authorization: "jwt " + token,
}
}).then(response=>{
// this.game.print(response.data);
if(parseInt(response.data.result.errno)==1000){
// 前往支付寶
var aliPayPlus = api.require("aliPayPlus");
aliPayPlus.payOrder({
orderInfo: response.data.result.order_string,
sandbox: response.data.result.sandbox, // 將來APP上線需要修改成false
}, (ret, err)=>{
pay_result = {
9000:'支付成功',
8000:"正在處理中",
4000:"訂單支付失敗",
5000:"重複請求",
6001:"取消支付",
6002:"網路連接出錯",
6004:"支付結果未知",
}
api.alert({
title: '支付結果',
msg: pay_result[ret.code],
buttons: ['確定']
});
// 通知服務端, 修改充值結果
this.return_recharge(response.data.result.order_number, token);
});
}else {
this.game.print(response.data);
}
}).catch(error=>{
// 網路等異常
this.game.print(error);
});
})
},
return_recharge(out_trade_number, token){
this.axios.post("", {
'jsonrpc':"2.0",
'id':this.uuid(),
'method':'Recharge.return',
'params': {
'out_trade_number':out_trade_number,
}
},{
headers:{
Authorization: "jwt " + token,
}
}).then(response=>{
if(parseInt(response.data.result.errno)==1000){
this.money = response.data.result.money.toFixed(2);
}
})
},
checkout(){
var token = this.game.get("access_token") || this.game.fget("access_token");
this.game.checkout(this,token,(new_access_token)=>{
this.connect();
this.login();
this.user_package();
});
},
connect(){
// socket連線
this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
this.socket.on('connect', ()=>{
this.game.print("開始連線服務端");
var id = this.game.fget('id');
this.socket.emit('login', {'uid': id});
this.socket.emit('user_prop');
});
},
login(){
this.socket.on('login_response', (message)=>{
this.settings_info.orchard = message.orchard_settings;
this.settings_info.user = message.user_settings;
this.game.fsave({
'orchard_settings': message.orchard_settings,
'user_settings': message.user_settings
});
});
},
user_package(){
// 使用者揹包道具列表
this.socket.on('user_prop_response', (message)=>{
this.game.fsave({
'user_package': message.data,
})
})
},
go_index(){
this.game.goWin("root");
},
go_friends(){
this.game.goFrame('friends', 'friends.html', this.current);
this.game.goFrame('friend_list', 'friend_list.html', this.current, {
x: 0,
y: 190,
w: 'auto',
h: 'auto',
}, null, true);
},
go_home(){
this.game.goWin('user', 'user.html', this.current);
},
go_orchard_shop(){
// 種植園商店
this.game.goFrame('orchard_shop', 'shop.html', this.current, null, {
type: 'push',
subType: 'from_top',
duration: 300
});
},
go_my_package(){
// 我的揹包
this.game.goFrame('package', 'package.html', this.current, null, {
type: 'push',
subType: 'from_top',
duration: 300
});
},
buy_prop(){
api.addEventListener({
name: 'buy_prop'
}, (ret, err)=>{
if( ret ){
// 使用者購買道具
this.socket.emit('user_buy_prop', ret.value);
}
});
},
}
});
}
</script>
</body>
</html>
在package.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/main.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/settings.js"></script>
</head>
<body>
<div class="app frame avatar add_friend package" id="app">
<div class="box">
<p class="title">我的揹包</p>
<img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
<div class="prop_list">
<div class="item" v-for='prop in user_package' @click='user_prop(prop.pid)'>
<img :src="settings.static_url+prop.image" alt="">
<span>{{prop.num}}</span>
</div>
<div class="item" v-for='number in unlock_td_number'></div>
<div class="item lock" v-for='number in lock_td_number'></div>
</div>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
td: 36, // 揹包格子總數量
user_id: "", // 當前登陸使用者Id
orchard_settings: {}, // 種植園相關公共引數
user_settings: {}, // 使用者相關私有引數
user_package: [], // 使用者揹包資訊
prev:{name:"",url:"",params:{}},
current:{name:"package",url:"package.html",params:{}},
}
},
computed:{ // 計算屬性
lock_td_number(){
// 未解鎖的格子
return parseInt(this.orchard_settings.package_number_max-this.user_settings.package_number);
},
unlock_td_number(){
// 解鎖的格子
return parseInt(this.user_settings.package_number - this.user_package.length);
}
},
created(){
this.user_id = this.game.get("id") || this.game.fget("id");
this.orchard_settings = JSON.parse(this.game.fget('orchard_settings'));
this.user_settings = JSON.parse(this.game.fget('user_settings'));
this.user_package = JSON.parse(this.game.fget('user_package'));
},
methods:{
user_prop(pid){
// 發起使用道具的通知
},
close_frame(){
this.game.outFrame("package");
},
}
});
}
</script>
</body>
</html>
在道具如果被使用或者新增購買, 則服務端響應新的資料到本地socket.py
程式碼:
from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods, Setting
from status import APIStatus as status
from message import ErrorMessage as errmsg
# 建立socket通訊
# @socketio.on('connect', namespace='/mofang')
# def user_connect():
# """使用者連線"""
# print('使用者%s連線過來了!' % request.sid)
# # 主動響應資料給客戶端
# socketio.emit('server_response', 'hello', namespace='/mofang')
# 斷開socket通訊
@socketio.on('disconnect', namespace='/mofang')
def user_disconnect():
print('使用者%s退出了種植園' % request.sid)
@socketio.on('login', namespace='/mofang')
def user_login(data):
# 分配房間
room = data['uid']
join_room(room)
# 儲存當前使用者和sid的繫結關係
# 判斷當前使用者是否在mongo中有記錄
query = {
'_id': data['uid']
}
ret = mongo.db.user_info_list.find_one(query)
if ret:
mongo.db.user_info_list.update_one(query, {'$set': {'sid': request.sid}})
else:
mongo.db.user_info_list.insert_one({
'_id': data['uid'],
'sid': request.sid,
})
# 返回種植園的相關配置引數
orchard_settings = {}
setting_list = Setting.query.filter(Setting.is_deleted==False, Setting.status==True).all()
"""
現在的格式:
[<Setting package_number_base>, <Setting package_number_max>, <Setting package_unlock_price_1>...]
需要返回的格式:
{
package_number_base:4,
package_number_max: 32,
...
}
"""
for item in setting_list:
orchard_settings[item.name] = item.value
# 返回當前使用者相關的配置引數
user_settings = {}
# 從mongo中查詢使用者資訊,判斷使用者是否激活了揹包格子
dict = mongo.db.user_info_list.find_one({'sid': request.sid})
# 揹包格子
if dict.get('package_number') is None:
user_settings['package_number'] = orchard_settings.get('package_number_base', 4)
mongo.db.user_info_list.update_one({'sid': request.sid}, {'$set': {'package_number': user_settings['package_number']}})
else:
user_settings['package_number'] = dict.get('package_number')
socketio.emit('login_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'orchard_settings': orchard_settings,
'user_settings': user_settings,
}, namespace='/mofang', room=room)
@socketio.on('user_buy_prop', namespace='/mofang')
def user_buy_prop(data):
"""使用者購買道具"""
room = request.sid
# 從mongo中獲取當前使用者資訊
user_info = mongo.db.user_info_list.find_one({'sid': request.sid})
user = User.query.get(user_info.get('_id'))
if user is None:
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_USER, 'errmsg': errmsg.user_not_exists}, namespace='/mofang', room=room)
return
# 從mysql中獲取商品價格
prop = Goods.query.get(data['pid'])
if float(user.money) < float(prop.price) * int(data['num']):
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_MONEY, 'errmsg': errmsg.money_no_enough}, namespace='/mofang', room=room)
return
# 從mongo中獲取使用者列表資訊,提取購買的商品數量進行累加和餘額
query = {'sid': request.sid}
if user_info.get('prop_list') is None:
"""此前沒有購買任何道具"""
message = {'$set': {'prop_list': {'prop_%s' % prop.id: int(data['num'])}}}
mongo.db.user_info_list.update_one(query, message)
else:
"""此前有購買了道具"""
prop_list = user_info.get('prop_list') # 道具列表
if('prop_%s' % prop.id) in prop_list:
"""如果再次同一款道具"""
prop_list[('prop_%s' % prop.id)] = prop_list[('prop_%s' % prop.id)] + int(data['num'])
else:
"""此前沒有購買過這種道具"""
prop_list[('prop_%s' % prop.id)] = int(data['num'])
mongo.db.user_info_list.update_one(query, {'$set': {'prop_list': prop_list}})
get_user_prop()
socketio.emit('user_buy_prop_response', {'errno': status.CODE_OK, 'errmsg': errmsg.ok}, namespace='/mofang', room=room)
@socketio.on('user_prop', namespace='/mofang')
def user_prop():
get_user_prop()
def get_user_prop():
"""使用者道具"""
userinfo = mongo.db.user_info_list.find_one({'sid': request.sid})
prop_list = userinfo.get('prop_list')
prop_id_list = []
for prop_str, num in prop_list.items():
pid = int(prop_str[5:])
prop_id_list.append(pid)
data = []
prop_list_data = Goods.query.filter(Goods.id.in_(prop_id_list)).all()
setting = Setting.query.filter(Setting.name == 'td_prop_max').first()
if setting is None:
td_prop_max = 10
else:
td_prop_max = int(setting.value)
for prop_data in prop_list_data:
num = int(prop_list[('prop_%s' % prop_data.id)])
if td_prop_max > num:
data.append({
'num': num,
'image': prop_data.image,
'pid': prop_data.id
})
else:
padding_time = num // td_prop_max
padding_last = num % td_prop_max
arr = [{
'num': td_prop_max,
'image': prop_data.image,
'pid': prop_data.id
}] * padding_time
if padding_last != 0:
arr.append({
'num': padding_last,
'image': prop_data.image,
'pid': prop_data.id
})
data = data + arr
room = request.sid
socketio.emit('user_prop_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'data': data,
}, namespace='/mofang', room=room)
使用者購買道具的時候,判斷揹包儲存是否達到上限
socket.py
程式碼:
from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods, Setting
from status import APIStatus as status
from message import ErrorMessage as errmsg
# 建立socket通訊
# @socketio.on('connect', namespace='/mofang')
# def user_connect():
# """使用者連線"""
# print('使用者%s連線過來了!' % request.sid)
# # 主動響應資料給客戶端
# socketio.emit('server_response', 'hello', namespace='/mofang')
# 斷開socket通訊
@socketio.on('disconnect', namespace='/mofang')
def user_disconnect():
print('使用者%s退出了種植園' % request.sid)
@socketio.on('login', namespace='/mofang')
def user_login(data):
# 分配房間
room = data['uid']
join_room(room)
# 儲存當前使用者和sid的繫結關係
# 判斷當前使用者是否在mongo中有記錄
query = {
'_id': data['uid']
}
ret = mongo.db.user_info_list.find_one(query)
if ret:
mongo.db.user_info_list.update_one(query, {'$set': {'sid': request.sid}})
else:
mongo.db.user_info_list.insert_one({
'_id': data['uid'],
'sid': request.sid,
})
# 返回種植園的相關配置引數
orchard_settings = {}
setting_list = Setting.query.filter(Setting.is_deleted==False, Setting.status==True).all()
"""
現在的格式:
[<Setting package_number_base>, <Setting package_number_max>, <Setting package_unlock_price_1>...]
需要返回的格式:
{
package_number_base:4,
package_number_max: 32,
...
}
"""
for item in setting_list:
orchard_settings[item.name] = item.value
# 返回當前使用者相關的配置引數
user_settings = {}
# 從mongo中查詢使用者資訊,判斷使用者是否激活了揹包格子
dict = mongo.db.user_info_list.find_one({'sid': request.sid})
# 揹包格子
if dict.get('package_number') is None:
user_settings['package_number'] = orchard_settings.get('package_number_base', 4)
mongo.db.user_info_list.update_one({'sid': request.sid}, {'$set': {'package_number': user_settings['package_number']}})
else:
user_settings['package_number'] = dict.get('package_number')
socketio.emit('login_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'orchard_settings': orchard_settings,
'user_settings': user_settings,
}, namespace='/mofang', room=room)
@socketio.on('user_buy_prop', namespace='/mofang')
def user_buy_prop(data):
"""使用者購買道具"""
room = request.sid
# 從mongo中獲取當前使用者資訊
user_info = mongo.db.user_info_list.find_one({'sid': request.sid})
user = User.query.get(user_info.get('_id'))
if user is None:
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_USER, 'errmsg': errmsg.user_not_exists}, namespace='/mofang', room=room)
return
# 判斷揹包物品儲存是否達到上限
use_package_number = int(user_info.get('use_package_number', 0)) # 當前已經使用的格子數量
package_number = int(user_info.get('package_number', 0))
# 本次購買道具需要使用的格子數量
setting = Setting.query.filter(Setting.name == 'td_prop_max').first()
if setting is None:
td_prop_max = 10
else:
td_prop_max = int(setting.value)
# 計算購買道具以後需要額外佔用的格子數量
if('prop_%s' % data['pid']) in user_info.get('prop_list'):
"""曾經購買過當前道具"""
prop_num = int(user_info.get('prop_list')['prop_%s' % data['pdi']]) # 購買前的道具數量
new_prop_num = prop_num + int(data['num']) # 如果成功購買道具以後的數量
old_td_num = prop_num // td_prop_max
if prop_num % td_prop_max > 0:
old_td_num += 1
new_td_num = new_prop_num // td_prop_max
if new_prop_num % td_prop_max > 0:
new_td_num += 1
td_num = new_td_num - old_td_num
else:
"""新增購買的道具"""
# 計算本次購買道具需要佔用的格子數量
if int(data['num']) > td_prop_max:
"""需要多個格子"""
td_num = int(data['num']) // td_prop_max
if int(data['num']) % td_prop_max > 0:
td_num += 1
else:
"""需要一個格子"""
td_num = 1
if use_package_number + td_num > package_number:
"""超出儲存上限"""
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_PACKAGE, 'errmsg': errmsg.no_package}, namespace='/mofang', room=room)
return
# 從mysql中獲取商品價格
prop = Goods.query.get(data['pid'])
if float(user.money) < float(prop.price) * int(data['num']):
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_MONEY, 'errmsg': errmsg.money_no_enough}, namespace='/mofang', room=room)
return
# 從mongo中獲取使用者列表資訊,提取購買的商品數量進行累加和餘額
query = {'sid': request.sid}
if user_info.get('prop_list') is None:
"""此前沒有購買任何道具"""
message = {'$set': {'prop_list': {'prop_%s' % prop.id: int(data['num'])}}}
mongo.db.user_info_list.update_one(query, message)
else:
"""此前有購買了道具"""
prop_list = user_info.get('prop_list') # 道具列表
if('prop_%s' % prop.id) in prop_list:
"""如果再次同一款道具"""
prop_list[('prop_%s' % prop.id)] = prop_list[('prop_%s' % prop.id)] + int(data['num'])
else:
"""此前沒有購買過這種道具"""
prop_list[('prop_%s' % prop.id)] = int(data['num'])
mongo.db.user_info_list.update_one(query, {'$set': {'prop_list': prop_list}})
# 返回購買成功的資訊
socketio.emit('user_buy_prop_response', {'errno': status.CODE_OK, 'errmsg': errmsg.ok}, namespace='/mofang', room=room)
# 返回最新的使用者道具列表
user_prop()
@socketio.on('user_prop', namespace='/mofang')
def user_prop():
"""使用者道具"""
userinfo = mongo.db.user_info_list.find_one({'sid': request.sid})
prop_list = userinfo.get('prop_list')
prop_id_list = []
for prop_str, num in prop_list.items():
pid = int(prop_str[5:])
prop_id_list.append(pid)
data = []
prop_list_data = Goods.query.filter(Goods.id.in_(prop_id_list)).all()
setting = Setting.query.filter(Setting.name == 'td_prop_max').first()
if setting is None:
td_prop_max = 10
else:
td_prop_max = int(setting.value)
for prop_data in prop_list_data:
num = int(prop_list[('prop_%s' % prop_data.id)])
if td_prop_max > num:
data.append({
'num': num,
'image': prop_data.image,
'pid': prop_data.id
})
else:
padding_time = num // td_prop_max
padding_last = num % td_prop_max
arr = [{
'num': td_prop_max,
'image': prop_data.image,
'pid': prop_data.id
}] * padding_time
if padding_last != 0:
arr.append({
'num': padding_last,
'image': prop_data.image,
'pid': prop_data.id
})
data = data + arr
# 儲存當前使用者已經使用的格子數量
mongo.db.user_info_list.update_one({'sid':request.sid}, {'$set': {'use_package_number': len(data)}})
room = request.sid
socketio.emit('user_prop_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'data': data,
}, namespace='/mofang', room=room)
status.py
程式碼:
...
CODE_NO_PACKAGE = 1015 # 揹包儲存達到上限
CODE_NO_CREDIT = 1016 # 果子不足
message.py
程式碼:
...
no_package = "揹包儲存達到上限!"
credit_no_enough = "果子不足!"
在購買道具的流程中, 增加積分(果子)商品.
改變資料庫:
from message import ErrorMessage as Message
from .models import Goods, db
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field
from marshmallow import post_dump
class GoodsInfoSchema(SQLAlchemyAutoSchema):
id = auto_field()
name = auto_field()
price = auto_field()
image = auto_field()
remark = auto_field()
credit = auto_field()
class Meta:
model = Goods
fields = ['id', 'name', 'price', 'image', 'remark', 'credit']
sql_session = db.session
@post_dump()
def mobile_format(self, data, **kwargs):
data['price'] = "%.2f" % data['price']
if data['image'] == None:
data['image'] = ""
return data
在客戶端中, 根據金額或者積分來顯示不同商品.shop.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/main.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/settings.js"></script>
</head>
<body>
<div class="app frame avatar update_nickname add_friend shop" id="app">
<div class="box">
<p class="title">商店</p>
<img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
<div class="friends_list shop_list">
<div class="item" @click='buy_prop(goods.id)' v-for='goods in goods_list'>
<div class="avatar shop_item">
<img :src="settings.static_url + goods.image" alt="">
</div>
<div class="info">
<p class="username">{{goods.name}}</p>
<p class="time">{{goods.remark}}</p>
</div>
<div class="status">
<span v-if='goods.price>0'>{{goods.price}}</span>
<span v-else>{{goods.credit}}個果子</span>
</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
user_id: "", // 當前登陸使用者Id
goods_list: [], // 商品列表
page: 1,
limit: 10,
is_send_ajax: false,
prev:{name:"",url:"",params:{}},
current:{name:"orchard",url:"shop.html",params:{}},
}
},
created(){
this.user_id = this.game.get("id") || this.game.fget("id");
this.get_goods_list();
},
methods:{
close_frame(){
this.game.outFrame("orchard_shop");
},
get_goods_list(){
if(this.is_send_ajax){
return;
}
// 通過請求獲取當前使用者的好友列表
var token = this.game.get('access_token') || this.game.fget('access_token');
this.game.checkout(this, token, (new_access_token)=>{
this.is_send_ajax = true;
this.axios.post("", {
'jsonrpc': '2.0',
"id": this.uuid(),
'method': 'Orchard.goods.list',
'params': {
'page': this.page,
'limit': this.limit,
}
},{
headers:{
Authorization: "jwt " + token,
}
}).then(response=>{
if(parseInt(response.data.result.errno) == 1000){
if(this.page+1 == response.data.result.pages){
this.is_send_ajax = true;
}else{
this.is_send_ajax = false;
this.page+=1;
}
if(this.page>1){
api.refreshHeaderLoadDone();
}
this.goods_list = response.data.result.goods_list.concat(this.goods_list);
}else if (parseInt(response.data.result.errno) == 1008) {
this.friends = [];
}else {
this.game.print(response.data);
}
}).catch(error=>{
// 網路等異常
this.game.print(error);
});
})
},
buy_prop(prop_id){
// 購買商品道具
// 讓使用者選擇購買的數量
api.prompt({
text: 1,
title: '請輸入購買數量',
type: 'number',
buttons: ['確定', '取消']
}, (ret, err)=>{
if(ret.buttonIndex == 1){
// 通過通知告知socket進行商品購買
api.sendEvent({
name: 'buy_prop',
extra: {
'pid': prop_id,
'num': ret.text
}
});
}
});
}
}
});
}
</script>
</body>
</html>
在購買商品成功以後, 計算金額和積分的扣除, socket.py
程式碼:
from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods, Setting, db
from status import APIStatus as status
from message import ErrorMessage as errmsg
# 建立socket通訊
# @socketio.on('connect', namespace='/mofang')
# def user_connect():
# """使用者連線"""
# print('使用者%s連線過來了!' % request.sid)
# # 主動響應資料給客戶端
# socketio.emit('server_response', 'hello', namespace='/mofang')
# 斷開socket通訊
@socketio.on('disconnect', namespace='/mofang')
def user_disconnect():
print('使用者%s退出了種植園' % request.sid)
@socketio.on('login', namespace='/mofang')
def user_login(data):
# 分配房間
room = data['uid']
join_room(room)
# 儲存當前使用者和sid的繫結關係
# 判斷當前使用者是否在mongo中有記錄
query = {
'_id': data['uid']
}
ret = mongo.db.user_info_list.find_one(query)
if ret:
mongo.db.user_info_list.update_one(query, {'$set': {'sid': request.sid}})
else:
mongo.db.user_info_list.insert_one({
'_id': data['uid'],
'sid': request.sid,
})
# 返回種植園的相關配置引數
orchard_settings = {}
setting_list = Setting.query.filter(Setting.is_deleted==False, Setting.status==True).all()
"""
現在的格式:
[<Setting package_number_base>, <Setting package_number_max>, <Setting package_unlock_price_1>...]
需要返回的格式:
{
package_number_base:4,
package_number_max: 32,
...
}
"""
for item in setting_list:
orchard_settings[item.name] = item.value
# 返回當前使用者相關的配置引數
user_settings = {}
# 從mongo中查詢使用者資訊,判斷使用者是否激活了揹包格子
dict = mongo.db.user_info_list.find_one({'sid': request.sid})
# 揹包格子
if dict.get('package_number') is None:
user_settings['package_number'] = orchard_settings.get('package_number_base', 4)
mongo.db.user_info_list.update_one({'sid': request.sid}, {'$set': {'package_number': user_settings['package_number']}})
else:
user_settings['package_number'] = dict.get('package_number')
socketio.emit('login_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'orchard_settings': orchard_settings,
'user_settings': user_settings,
}, namespace='/mofang', room=room)
@socketio.on('user_buy_prop', namespace='/mofang')
def user_buy_prop(data):
"""使用者購買道具"""
room = request.sid
# 從mongo中獲取當前使用者資訊
user_info = mongo.db.user_info_list.find_one({'sid': request.sid})
user = User.query.get(user_info.get('_id'))
if user is None:
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_USER, 'errmsg': errmsg.user_not_exists}, namespace='/mofang', room=room)
return
# 判斷揹包物品儲存是否達到上限
use_package_number = int(user_info.get('use_package_number', 0)) # 當前已經使用的格子數量
package_number = int(user_info.get('package_number', 0)) # # 當前使用者已經解鎖的格子數量
# 本次購買道具需要使用的格子數量
setting = Setting.query.filter(Setting.name == 'td_prop_max').first()
if setting is None:
td_prop_max = 10
else:
td_prop_max = int(setting.value)
# 計算購買道具以後需要額外佔用的格子數量
if('prop_%s' % data['pid']) in user_info.get('prop_list', {}):
"""曾經購買過當前道具"""
prop_num = int(user_info.get('prop_list')['prop_%s' % data['pid']]) # 購買前的道具數量
new_prop_num = prop_num + int(data['num']) # 如果成功購買道具以後的數量
old_td_num = prop_num // td_prop_max
if prop_num % td_prop_max > 0:
old_td_num += 1
new_td_num = new_prop_num // td_prop_max
if new_prop_num % td_prop_max > 0:
new_td_num += 1
td_num = new_td_num - old_td_num
else:
"""新增購買的道具"""
# 計算本次購買道具需要佔用的格子數量
if int(data['num']) > td_prop_max:
"""需要多個格子"""
td_num = int(data['num']) // td_prop_max
if int(data['num']) % td_prop_max > 0:
td_num += 1
else:
"""需要一個格子"""
td_num = 1
if use_package_number + td_num > package_number:
"""超出儲存上限"""
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_PACKAGE, 'errmsg': errmsg.no_package}, namespace='/mofang', room=room)
return
# 從mysql中獲取商品價格
prop = Goods.query.get(data['pid'])
if user.money > 0: # 當前商品需要通過RMB購買
if float(user.money) < float(prop.price) * int(data['num']):
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_MONEY, 'errmsg': errmsg.money_no_enough}, namespace='/mofang', room=room)
return
else:
"""當前通過果子進行購買"""
if int(user.credit) < int(prop.credit) * int(data['num']):
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_CREDIT, 'errmsg': errmsg.credit_no_enough}, namespace='/mofang', room=room)
return
# 從mongo中獲取使用者列表資訊,提取購買的商品數量進行累加和餘額
query = {'sid': request.sid}
if user_info.get('prop_list') is None:
"""此前沒有購買任何道具"""
message = {'$set': {'prop_list': {'prop_%s' % prop.id: int(data['num'])}}}
mongo.db.user_info_list.update_one(query, message)
else:
"""此前有購買了道具"""
prop_list = user_info.get('prop_list') # 道具列表
if('prop_%s' % prop.id) in prop_list:
"""如果再次同一款道具"""
prop_list[('prop_%s' % prop.id)] = prop_list[('prop_%s' % prop.id)] + int(data['num'])
else:
"""此前沒有購買過這種道具"""
prop_list[('prop_%s' % prop.id)] = int(data['num'])
mongo.db.user_info_list.update_one(query, {'$set': {'prop_list': prop_list}})
# 扣除餘額或果子
if prop.price > 0:
user.money = float(user.money) - float(prop.price) * int(data['num'])
else:
user.credit = int(user.credit) - int(prop.credit) * int(data['num'])
db.session.commit()
# 返回購買成功的資訊
socketio.emit('user_buy_prop_response', {'errno': status.CODE_OK, 'errmsg': errmsg.ok}, namespace='/mofang', room=room)
# 返回最新的使用者道具列表
user_prop()
@socketio.on('user_prop', namespace='/mofang')
def user_prop():
"""使用者道具"""
userinfo = mongo.db.user_info_list.find_one({'sid': request.sid})
prop_list = userinfo.get('prop_list', {})
prop_id_list = []
for prop_str, num in prop_list.items():
pid = int(prop_str[5:])
prop_id_list.append(pid)
data = []
prop_list_data = Goods.query.filter(Goods.id.in_(prop_id_list)).all()
setting = Setting.query.filter(Setting.name == 'td_prop_max').first()
if setting is None:
td_prop_max = 10
else:
td_prop_max = int(setting.value)
for prop_data in prop_list_data:
num = int(prop_list[('prop_%s' % prop_data.id)])
if td_prop_max > num:
data.append({
'num': num,
'image': prop_data.image,
'pid': prop_data.id
})
else:
padding_time = num // td_prop_max
padding_last = num % td_prop_max
arr = [{
'num': td_prop_max,
'image': prop_data.image,
'pid': prop_data.id
}] * padding_time
if padding_last != 0:
arr.append({
'num': padding_last,
'image': prop_data.image,
'pid': prop_data.id
})
data = data + arr
# 儲存當前使用者已經使用的格子數量
mongo.db.user_info_list.update_one({'sid':request.sid}, {'$set': {'use_package_number': len(data)}})
room = request.sid
socketio.emit('user_prop_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'data': data,
}, namespace='/mofang', room=room)
二、揹包解鎖
orchard/socket.py
, 程式碼:
from application import socketio
from flask import request
from application.apps.users.models import User
from flask_socketio import join_room, leave_room
from application import mongo
from .models import Goods, Setting, db
from status import APIStatus as status
from message import ErrorMessage as errmsg
# 建立socket通訊
# @socketio.on('connect', namespace='/mofang')
# def user_connect():
# """使用者連線"""
# print('使用者%s連線過來了!' % request.sid)
# # 主動響應資料給客戶端
# socketio.emit('server_response', 'hello', namespace='/mofang')
# 斷開socket通訊
@socketio.on('disconnect', namespace='/mofang')
def user_disconnect():
print('使用者%s退出了種植園' % request.sid)
@socketio.on('login', namespace='/mofang')
def user_login(data):
# 分配房間
room = data['uid']
join_room(room)
# 儲存當前使用者和sid的繫結關係
# 判斷當前使用者是否在mongo中有記錄
query = {
'_id': data['uid']
}
ret = mongo.db.user_info_list.find_one(query)
if ret:
mongo.db.user_info_list.update_one(query, {'$set': {'sid': request.sid}})
else:
mongo.db.user_info_list.insert_one({
'_id': data['uid'],
'sid': request.sid,
})
# 返回種植園的相關配置引數
orchard_settings = {}
setting_list = Setting.query.filter(Setting.is_deleted==False, Setting.status==True).all()
"""
現在的格式:
[<Setting package_number_base>, <Setting package_number_max>, <Setting package_unlock_price_1>...]
需要返回的格式:
{
package_number_base:4,
package_number_max: 32,
...
}
"""
for item in setting_list:
orchard_settings[item.name] = item.value
# 返回當前使用者相關的配置引數
user_settings = {}
# 從mongo中查詢使用者資訊,判斷使用者是否激活了揹包格子
dict = mongo.db.user_info_list.find_one({'sid': request.sid})
# 揹包格子
if dict.get('package_number') is None:
user_settings['package_number'] = orchard_settings.get('package_number_base', 4)
mongo.db.user_info_list.update_one({'sid': request.sid}, {'$set': {'package_number': user_settings['package_number']}})
else:
user_settings['package_number'] = dict.get('package_number')
socketio.emit('login_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'orchard_settings': orchard_settings,
'user_settings': user_settings,
}, namespace='/mofang', room=room)
@socketio.on('user_buy_prop', namespace='/mofang')
def user_buy_prop(data):
"""使用者購買道具"""
room = request.sid
# 從mongo中獲取當前使用者資訊
user_info = mongo.db.user_info_list.find_one({'sid': request.sid})
user = User.query.get(user_info.get('_id'))
if user is None:
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_USER, 'errmsg': errmsg.user_not_exists}, namespace='/mofang', room=room)
return
# 判斷揹包物品儲存是否達到上限
use_package_number = int(user_info.get('use_package_number', 0)) # 當前已經使用的格子數量
package_number = int(user_info.get('package_number', 0)) # # 當前使用者已經解鎖的格子數量
# 本次購買道具需要使用的格子數量
setting = Setting.query.filter(Setting.name == 'td_prop_max').first()
if setting is None:
td_prop_max = 10
else:
td_prop_max = int(setting.value)
# 計算購買道具以後需要額外佔用的格子數量
if('prop_%s' % data['pid']) in user_info.get('prop_list', {}):
"""曾經購買過當前道具"""
prop_num = int(user_info.get('prop_list')['prop_%s' % data['pid']]) # 購買前的道具數量
new_prop_num = prop_num + int(data['num']) # 如果成功購買道具以後的數量
old_td_num = prop_num // td_prop_max
if prop_num % td_prop_max > 0:
old_td_num += 1
new_td_num = new_prop_num // td_prop_max
if new_prop_num % td_prop_max > 0:
new_td_num += 1
td_num = new_td_num - old_td_num
else:
"""新增購買的道具"""
# 計算本次購買道具需要佔用的格子數量
if int(data['num']) > td_prop_max:
"""需要多個格子"""
td_num = int(data['num']) // td_prop_max
if int(data['num']) % td_prop_max > 0:
td_num += 1
else:
"""需要一個格子"""
td_num = 1
if use_package_number + td_num > package_number:
"""超出儲存上限"""
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_PACKAGE, 'errmsg': errmsg.no_package}, namespace='/mofang', room=room)
return
# 從mysql中獲取商品價格
prop = Goods.query.get(data['pid'])
if user.money > 0: # 當前商品需要通過RMB購買
if float(user.money) < float(prop.price) * int(data['num']):
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_MONEY, 'errmsg': errmsg.money_no_enough}, namespace='/mofang', room=room)
return
else:
"""當前通過果子進行購買"""
if int(user.credit) < int(prop.credit) * int(data['num']):
socketio.emit('user_buy_prop_response', {'errno': status.CODE_NO_CREDIT, 'errmsg': errmsg.credit_no_enough}, namespace='/mofang', room=room)
return
# 從mongo中獲取使用者列表資訊,提取購買的商品數量進行累加和餘額
query = {'sid': request.sid}
if user_info.get('prop_list') is None:
"""此前沒有購買任何道具"""
message = {'$set': {'prop_list': {'prop_%s' % prop.id: int(data['num'])}}}
mongo.db.user_info_list.update_one(query, message)
else:
"""此前有購買了道具"""
prop_list = user_info.get('prop_list') # 道具列表
if('prop_%s' % prop.id) in prop_list:
"""如果再次同一款道具"""
prop_list[('prop_%s' % prop.id)] = prop_list[('prop_%s' % prop.id)] + int(data['num'])
else:
"""此前沒有購買過這種道具"""
prop_list[('prop_%s' % prop.id)] = int(data['num'])
mongo.db.user_info_list.update_one(query, {'$set': {'prop_list': prop_list}})
# 扣除餘額或果子
if prop.price > 0:
user.money = float(user.money) - float(prop.price) * int(data['num'])
else:
user.credit = int(user.credit) - int(prop.credit) * int(data['num'])
db.session.commit()
# 返回購買成功的資訊
socketio.emit('user_buy_prop_response', {'errno': status.CODE_OK, 'errmsg': errmsg.ok}, namespace='/mofang', room=room)
# 返回最新的使用者道具列表
user_prop()
@socketio.on('user_prop', namespace='/mofang')
def user_prop():
"""使用者道具"""
userinfo = mongo.db.user_info_list.find_one({'sid': request.sid})
prop_list = userinfo.get('prop_list', {})
prop_id_list = []
for prop_str, num in prop_list.items():
pid = int(prop_str[5:])
prop_id_list.append(pid)
data = []
prop_list_data = Goods.query.filter(Goods.id.in_(prop_id_list)).all()
setting = Setting.query.filter(Setting.name == 'td_prop_max').first()
if setting is None:
td_prop_max = 10
else:
td_prop_max = int(setting.value)
for prop_data in prop_list_data:
num = int(prop_list[('prop_%s' % prop_data.id)])
if td_prop_max > num:
data.append({
'num': num,
'image': prop_data.image,
'pid': prop_data.id
})
else:
padding_time = num // td_prop_max
padding_last = num % td_prop_max
arr = [{
'num': td_prop_max,
'image': prop_data.image,
'pid': prop_data.id
}] * padding_time
if padding_last != 0:
arr.append({
'num': padding_last,
'image': prop_data.image,
'pid': prop_data.id
})
data = data + arr
# 儲存當前使用者已經使用的格子數量
mongo.db.user_info_list.update_one({'sid':request.sid}, {'$set': {'use_package_number': len(data)}})
room = request.sid
socketio.emit('user_prop_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok,
'data': data,
}, namespace='/mofang', room=room)
@socketio.on('unlock_package', namespace='/mofang')
def unlock_package():
"""解鎖揹包"""
room = request.sid
# 從mongo獲取當前使用者解鎖的格子數量
user_info = mongo.db.user_info_list.find_one({'sid': request.sid})
user = User.query.get(user_info.get('_id'))
if user is None:
socketio.emit('unlock_package_response', {'errno': status.CODE_NO_USER, 'errmsg': errmsg.user_not_exists}, namespace='/mofang', room=room)
return
package_number = int(user_info.get('package_number'))
num = 7 - (32 - package_number) // 4 # 沒有解鎖的格子
# 從資料庫中獲取解鎖揹包的價格
setting = Setting.query.filter(Setting.name == 'package_unlock_price_%s' % num).first()
if setting is None:
unlock_price = 0
else:
unlock_price = int(setting.value)
# 判斷是否有足夠的積分或者價格
room = request.sid
if user.money < unlock_price:
socketio.emit('unlock_package_response', {'errno': status.CODE_NO_MONEY, 'errmsg': errmsg.money_no_enough}, namespace='/mofang', room=room)
return
# 解鎖成功
user.money = float(user.money) - float(unlock_price)
db.session.commit()
# mongo中調整數量
mongo.db.user_info_list.update_one({'sid': request.sid}, {'$set': {'package_number': package_number + 1}})
# 返回解鎖的結果
socketio.emit('unlock_package_response', {
'errno': status.CODE_OK,
'errmsg': errmsg.ok}, namespace='/mofang', room=room)
orchard.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/main.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/settings.js"></script>
<script src="../static/js/socket.io.js"></script>
</head>
<body>
<div class="app orchard" id="app">
<img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
<div class="orchard-bg">
<img src="../static/images/bg2.png">
<img class="board_bg2" src="../static/images/board_bg2.png">
</div>
<img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
<div class="header">
<div class="info" @click='go_home'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<p class="user_name">好聽的暱稱</p>
</div>
<div class="wallet">
<div class="balance" @click='user_recharge'>
<p class="title"><img src="../static/images/money.png" alt="">錢包</p>
<p class="num">{{money}}</p>
</div>
<div class="balance">
<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
<p class="num">99,999.00</p>
</div>
</div>
<div class="menu-list">
<div class="menu">
<img src="../static/images/menu1.png" alt="">
排行榜
</div>
<div class="menu">
<img src="../static/images/menu2.png" alt="">
簽到有禮
</div>
<div class="menu" @click='go_orchard_shop'>
<img src="../static/images/menu3.png" alt="">
道具商城
</div>
<div class="menu">
<img src="../static/images/menu4.png" alt="">
郵件中心
</div>
</div>
</div>
<div class="footer">
<ul class="menu-list">
<li class="menu">新手</li>
<li class="menu" @click='go_my_package'>揹包</li>
<li class="menu-center" @click='go_orchard_shop'>商店</li>
<li class="menu">訊息</li>
<li class="menu" @click='go_friends'>好友</li>
</ul>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
music_play:true,
namespace: '/mofang',
token:"",
money:"",
settings_info:{
orchard: {}, // 種植園公共引數
user: {}, // 使用者私有相關引數
},
socket: null,
recharge_list: ['10','20','50','100','200','500','1000'],
timeout: 0,
prev:{name:"",url:"",params:{}},
current:{name:"orchard",url:"orchard.html",params:{}},
}
},
created(){
this.game.goFrame('orchard', 'my_orchard.html', this.current, {
x: 0,
y: 180,
w: 'auto',
h: 410,
}, null);
this.checkout();
this.money = this.game.fget("money");
},
methods:{
user_recharge(){
// 發起充值請求
api.actionSheet({
title: '餘額充值',
cancelTitle: '取消',
buttons: this.recharge_list
}, (ret, err)=>{
if( ret ){
if(ret.buttonIndex <= this.recharge_list.length){
// 充值金額
money = this.recharge_list[ret.buttonIndex-1];
// 呼叫支付寶充值
this.create_recharge(money);
}
}else{
}
});
},
create_recharge(money){
// 獲取歷史資訊記錄
var token = this.game.get('access_token') || this.game.fget('access_token');
this.game.checkout(this, token, (new_access_token)=>{
this.axios.post('', {
'jsonrpc': '2.0',
'id': this.uuid(),
'method': 'Recharge.create',
'params': {
'money': money,
}
},{
headers:{
Authorization: "jwt " + token,
}
}).then(response=>{
// this.game.print(response.data);
if(parseInt(response.data.result.errno)==1000){
// 前往支付寶
var aliPayPlus = api.require("aliPayPlus");
aliPayPlus.payOrder({
orderInfo: response.data.result.order_string,
sandbox: response.data.result.sandbox, // 將來APP上線需要修改成false
}, (ret, err)=>{
pay_result = {
9000:'支付成功',
8000:"正在處理中",
4000:"訂單支付失敗",
5000:"重複請求",
6001:"取消支付",
6002:"網路連接出錯",
6004:"支付結果未知",
}
api.alert({
title: '支付結果',
msg: pay_result[ret.code],
buttons: ['確定']
});
// 通知服務端, 修改充值結果
this.return_recharge(response.data.result.order_number, token);
});
}else {
this.game.print(response.data);
}
}).catch(error=>{
// 網路等異常
this.game.print(error);
});
})
},
return_recharge(out_trade_number, token){
this.axios.post("", {
'jsonrpc':"2.0",
'id':this.uuid(),
'method':'Recharge.return',
'params': {
'out_trade_number':out_trade_number,
}
},{
headers:{
Authorization: "jwt " + token,
}
}).then(response=>{
if(parseInt(response.data.result.errno)==1000){
this.money = response.data.result.money.toFixed(2);
}
})
},
checkout(){
var token = this.game.get("access_token") || this.game.fget("access_token");
this.game.checkout(this,token,(new_access_token)=>{
this.connect();
this.login();
this.user_package();
this.buy_prop();
this.unlock_package_number();
});
},
connect(){
// socket連線
this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
this.socket.on('connect', ()=>{
this.game.print("開始連線服務端");
var id = this.game.fget('id');
this.socket.emit('login', {'uid': id});
this.socket.emit('user_prop');
});
},
login(){
this.socket.on('login_response', (message)=>{
this.settings_info.orchard = message.orchard_settings;
this.settings_info.user = message.user_settings;
this.game.fsave({
'orchard_settings': message.orchard_settings,
'user_settings': message.user_settings
});
});
},
user_package(){
// 使用者揹包道具列表
this.socket.on('user_prop_response', (message)=>{
this.game.fsave({
'user_package': message.data,
})
})
},
go_index(){
this.game.goWin("root");
},
go_friends(){
this.game.goFrame('friends', 'friends.html', this.current);
this.game.goFrame('friend_list', 'friend_list.html', this.current, {
x: 0,
y: 190,
w: 'auto',
h: 'auto',
}, null, true);
},
go_home(){
this.game.goWin('user', 'user.html', this.current);
},
go_orchard_shop(){
// 種植園商店
this.game.goFrame('orchard_shop', 'shop.html', this.current, null, {
type: 'push',
subType: 'from_top',
duration: 300
});
},
go_my_package(){
// 我的揹包
this.game.goFrame('package', 'package.html', this.current, null, {
type: 'push',
subType: 'from_top',
duration: 300
});
},
buy_prop(){
api.addEventListener({
name: 'buy_prop'
}, (ret, err)=>{
if( ret ){
// 使用者購買道具
this.socket.emit('user_buy_prop', ret.value);
}
});
this.socket.on('user_buy_prop_response', (message)=>{
alert(message.errmsg);
})
},
unlock_package_number(){
api.addEventListener({
name: 'unlock_package_number'
}, (ret, err)=>{
if( ret ){
// 使用者購買道具
this.socket.emit('unlock_package');
}
});
this.socket.on('unlock_package_response', (message)=>{
if(parseInt(message.errno) == 1000){
api.sendEvent({
name: 'unlock_package_success',
extra: {
}
});
}else {
api.alert({
title: '提示',
msg: message.errmsg,
});
}
})
}
}
});
}
</script>
</body>
</html>
package.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/main.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/settings.js"></script>
</head>
<body>
<div class="app frame avatar add_friend package" id="app">
<div class="box">
<p class="title">我的揹包</p>
<img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
<div class="prop_list">
<div class="item" v-for='prop in user_package' @click='use_prop(prop.pid)'>
<img :src="settings.static_url+prop.image" alt="">
<span>{{prop.num}}</span>
</div>
<div class="item" v-for='number in unlock_td_number'></div>
<div class="item lock" @click='unlock_package()' v-for='number in lock_td_number'></div>
</div>
</div>
</div>
<script>
apiready = function(){
init();
new Vue({
el:"#app",
data(){
return {
td: 36, // 揹包格子總數量
user_id: "", // 當前登陸使用者Id
orchard_settings: {}, // 種植園相關公共引數
user_settings: {}, // 使用者相關私有引數
user_package: [], // 使用者揹包資訊
prev:{name:"",url:"",params:{}},
current:{name:"package",url:"package.html",params:{}},
}
},
computed:{ // 計算屬性
lock_td_number(){
// 未解鎖的格子
return parseInt(this.orchard_settings.package_number_max-this.user_settings.package_number);
},
unlock_td_number(){
// 解鎖的格子
return parseInt(this.user_settings.package_number - this.user_package.length);
}
},
created(){
this.user_id = this.game.get("id") || this.game.fget("id");
this.orchard_settings = JSON.parse(this.game.fget('orchard_settings'));
this.user_settings = JSON.parse(this.game.fget('user_settings'));
this.user_package = JSON.parse(this.game.fget('user_package'));
},
methods:{
use_prop(pid){
// 發起使用道具的通知
},
unlock_package(){
// 解鎖格子上限
api.confirm({
title: '提示',
msg: '解鎖揹包上限',
buttons: ['確定', '取消']
}, (ret, err)=>{
if(ret.buttonIndex == 1){
api.sendEvent({
name: 'unlock_package_number',
extra: {}
});
api.addEventListener({
name: 'unlock_package_success'
}, (ret, err)=>{
this.user_settings.package_number += 1;
});
}
});
},
close_frame(){
this.game.outFrame("package");
},
}
});
}
</script>
</body>
</html>
關閉資料庫的提示資訊dev.py
:
...
SQLALCHEMY_ECHO = False