邀请好友

1.业务逻辑流程图

客户端提供点击"邀请好友"以后的页面frame,html/invite.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" id="app"><div class="box"><p class="title">邀请好友</p><img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""><div class="content"><img class="invite_code" src="../static/images/code.jpg" alt=""></div><p class="invite_tips">长按保存图片到相册</p></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {prev:{name:"",url:"",params:{}},current:{name:"invite",url:"invite.html",params:{}},}},methods:{close_frame(){this.game.outFrame("invite");},}});}</script>
</body>
</html>

main.css,代码:

....
.friends_list{max-height: 37.2rem;overflow: scroll;
}
.friends_list .avatar{width: 6.39rem;height: 6.39rem;position: relative;
}
.friends_list .avatar_bf{position: absolute;z-index: 1;margin: auto;width: 4.56rem;height: 4.56rem;top: 0;bottom: 0;left: 0;right: 0;
}
.friends_list .user_avatar{position: absolute;z-index: 1;width: 4.56rem;height: 4.56rem;margin: auto;top: 0;bottom: 0;left: 0;right: 0;border-radius: 1rem;
}
.friends_list .avatar_border{position: absolute;z-index: 1;margin: auto;top: 0;bottom: 0;left: 0;right: 0;width: 6.1rem;height: 6.1rem;
}
.friends_list{position: absolute;top: 0rem;left: 3.6rem;
}
.friends_list .item{position: relative;background-color: rgba(196,81,9,0.1);border-radius: 4px;height: 7rem;width: 25.8rem;margin-bottom: 1rem;box-shadow: 2px 2px 5px rgba(9,9,9,0.1);
}
.friends_list .item .avatar{position: absolute;left: 1rem;top: 0;bottom: 0;margin: auto;
}
.friends_list .item .info{position: absolute;left: 8rem;top: 2rem;color: #fff;width: 10rem;
}
.friends_list .item .behavior{position: absolute;left: 16rem;font-size: 1.5rem;text-align: center;line-height: 4rem;height: 4rem;width: 4rem;color: #fff;top: 0;bottom: 0;margin: auto;box-shadow: 2px 2px 5px #333333;
}
.friends_list .item .pick{background: #336633;border-radius: 50%;
}
.friends_list .item .protect{background: #990000;border-top-right-radius: 5px;border-top-left-radius: 5px;border-bottom-left-radius: 30px;border-bottom-right-radius: 30px;
}
.friends_list .item .goto{position: absolute;left: 23rem;top: 0;bottom: 0;margin: auto;width: 0.96rem;height: 1.8rem;
}
.frame input::-webkit-input-placeholder,
.frame textarea::-webkit-input-placeholder{color: #fff;
}
.add_friend .box{top: 4rem;height: 55.56rem;background: url("../images/long_bg1.png") no-repeat 0 0;background-size: 100%;
}
.add_friend .nickname{margin: 4rem 4.6rem 2rem;width: 19rem;height: 4rem;line-height: 4rem;background-color: #cc9966;outline: none;border: 1px solid #330000;text-align: center;font-size: 1rem;color: #ffffcc;
}.add_friend .friends_list{position: absolute;top: 15rem;left: 3.6rem;max-height: 37.2rem;overflow: scroll;
}
.add_friend .friends_list .item{position: relative;margin-left: 1rem;background-color: rgba(196,81,9,0.1);border-radius: 4px;height: 4rem;width: 19rem;margin-bottom: 1rem;box-shadow: 2px 2px 5px rgba(9,9,9,0.1);
}
.add_friend .friends_list .avatar{width: 3.84rem;height: 3.84rem;position: absolute;left: 1rem;
}.add_friend .friends_list .avatar_bf{position: absolute;z-index: 1;margin: auto;width: 2.74rem;height: 2.74rem;top: 0;bottom: 0;left: 0;right: 0;
}.add_friend .friends_list .user_avatar{position: absolute;z-index: 1;width: 2.74rem;height: 2.74rem;margin: auto;top: 0;bottom: 0;left: 0;right: 0;border-radius: 1rem;
}
.add_friend .friends_list .avatar_border{position: absolute;z-index: 1;margin: auto;top: 0;bottom: 0;left: 0;right: 0;width: 3.66rem;height: 3.66rem;
}
.add_friend .friends_list .item .info{top: 0.6rem;left: 6rem;
}
.add_friend .friends_list .item .time{font-size: 0.6rem;
}
.friends_list .item .status{position: absolute;left: 12rem;top: 1.2rem;width: 8rem;text-align: center;height: 2rem;color: #fff;
}
.invite_code{width: 14rem;height: 14rem;position: absolute;left: 7rem;top: 11rem;
}
.invite_tips{position: absolute;left: 7rem;top: 26.4rem;text-align: center;color: #fff;font-size: 1.5rem;
}

用户中心首页, 实现点击打开页面,user.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 user" id="app"><div class="bg"><img src="../static/images/bg0.jpg"></div><img class="back" @click="goto_index" src="../static/images/user_back.png" alt=""><img class="setting" @click='goto_setting' src="../static/images/setting.png" alt=""><div class="header"><div class="info"><div class="avatar"><img class="avatar_bf" src="../static/images/avatar_bf.png" alt=""><img class="user_avatar" :src="avatar" alt=""><img class="avatar_border" src="../static/images/avatar_border.png" alt=""></div><p class="user_name">{{nickname}}</p></div><div class="wallet"><div class="balance"><p class="title"><img src="../static/images/money.png" alt="">钱包</p><p class="num">99,999.00</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="invite" @click='open_invite_page'><img class="invite_btn" src="../static/images/invite.png" alt=""></div></div><div class="menu"><div class="item" @click='open_friend_list'><span class="title">好友列表</span><span class="value">查看</span></div><div class="item"><span class="title">我的主页</span><span class="value">查看</span></div><div class="item"><span class="title">任务列表</span><span class="value">75%</span></div><div class="item"><span class="title">收益明细</span><span class="value">查看</span></div><div class="item"><span class="title">实名认证</span><span class="value">未认证</span></div></ul></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {nickname: '',avatar: '',prev:{name:"",url:"",params:{}},current:{name:"user",url:"user.html",params:{}},}},created(){this.get_user_info();this.change_avatar();},methods:{open_invite_page(){// 打开邀请好友页面this.game.goFrame('invite', 'invite.html', this.current, null, {type: 'push',subType: 'from_top',duration: 300});},open_friend_list(){// 打开好友列表数据页面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);   },change_avatar(){api.addEventListener({name: 'change_avatar'}, (ret, err)=>{if( ret ){var token = this.game.get('access_token') || this.game.fget('access_token');this.avatar = `${this.settings.avatar_url}?sign=${ret.value.avatar}&token=${token}`;}});},get_user_info(){var token = this.game.get('access_token') || this.game.fget('access_token');// 获取当前登录用户基本信息this.axios.post('',{'jsonrpc': '2.0','id': this.uuid(),'method': 'User.info','params': {}},{headers: {Authorization: 'jwt ' + token,}}).then(response=>{var res = response.data.result;this.game.print(res);if(parseInt(res.errno) === 1000){this.nickname = res.nickname;this.avatar = `${this.settings.avatar_url}?sign=${res.avatar}&token=${token}`;}})},goto_index(){// 返回首页this.game.outWin("user");},goto_setting(){// 进入设置this.game.goFrame('setting', 'setting.html', this.current);}}});}</script>
</body>
</html>

2.服务端提供邀请好友的二维码生成接口

flask-qrcode,文档: https://marcoagner.github.io/Flask-QRcode/

安装二维码生成模块

pip install flask-qrcode

初始化qrcode,application/__init__.py,代码:


import os,sysfrom flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session
from flask_migrate import Migrate, MigrateCommand
from flask_jsonrpc import JSONRPC
from flask_marshmallow import Marshmallow
from flask_jwt_extended import JWTManager
from flask_admin import Admin
from flask_babelex import Babel
from faker import Faker
from flask_pymongo import PyMongo
from flask_qrcode import QRcodefrom application.utils import init_blueprint
from application.utils.config import load_config
from application.utils.session import init_session
from application.utils.logger import Log
from application.utils.commands import load_command# 创建终端脚本管理对象
manager = Manager()# 创建数据库链接对象
db = SQLAlchemy()# redis链接对象
redis = FlaskRedis()# Session存储对象
session_store = Session()# 数据迁移实例对象
migrate = Migrate()# 日志对象
log = Log()# jsonrpc模块实例对象
jsonrpc = JSONRPC()# 数据转换器的对象创建
ma = Marshmallow()# jwt认证模块实例化
jwt = JWTManager()# flask_admin模块实例化
admin = Admin()# flask_babelex模块实例化
babel = Babel()# mongoDB
mongo = PyMongo()# qrcode
QRCode = QRcode()def init_app(config_path):"""全局初始化"""# 创建app应用对象app = Flask(__name__)# 项目根目录app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))# 加载导包路径sys.path.insert(0, os.path.join(app.BASE_DIR,"application/utils/language"))# 加载配置Config = load_config(config_path)app.config.from_object(Config)# 数据库初始化db.init_app(app)app.db = dbredis.init_app(app)mongo.init_app(app)# 数据转换器的初始化ma.init_app(app)# session存储初始化init_session(app)session_store.init_app(app)# 数据迁移初始化migrate.init_app(app, db)# 添加数据迁移的命令到终端脚本工具中manager.add_command('db', MigrateCommand)# 日志初始化app.log = log.init_app(app)# 蓝图注册init_blueprint(app)# jsonrpc初始化jsonrpc.service_url = "/api"  # api接口的url地址前缀jsonrpc.init_app(app)# jwt初始化jwt.init_app(app)# admin初始化admin.init_app(app)# 国际化本地化模块的初始化babel.init_app(app)# 初始化终端脚本工具manager.app = app# 数据种子生成器[faker]app.faker = Faker(app.config.get('LANGUAGE'))# qrcode初始化配置QRCode.init_app(app)# 注册自定义命令load_command(manager)return manager

users/views.py,视图提供生成二维码接口,代码:

from application import QRCode
from flask import make_response, request
@jwt_required  # 验证jwt
def invite_code():"""邀请好友的二维码"""current_user_id = get_jwt_identity()user = User.query.get(current_user_id)if user is None:return {'errno': status.CODE_NO_USER,'errmsg': message.user_not_exists}status_path = os.path.join(current_app.BASE_DIR, current_app.config['STATIC_DIR'])if not user.avatar:user.avatar = current_app.config['DEFAULT_AVATAR']avatar = status_path + '/' + user.avatardata = current_app.config.get('SERVER_URL', request.host_url[:-1]) + '/users/invite/download?uid=%s' % current_user_idimage = QRCode.qrcode(data, box_size=16, icon_img=avatar)b64_image = image[image.find(',') + 1:]qrcode_image = base64.b64decode(b64_image)response = make_response(qrcode_image)response.headers['Content-Type'] = 'image/png'return response

users/urls.py,代码:

from . import views
from application.utils import path
urlpatterns = [path('/avatar', views.avatar),path('/invite/code', views.invite_code),path('/invite/download', views.invite_download),
]

application/settings/dev.py,配置代码:

    # 用户默认头像DEFAULT_AVATAR = '54270a03-3587-4638-9156-b1f479efc958.jpeg'# 服务端带外提供的url地址# SERVER_URL = "http://127.0.0.1:5000"

方法二,提供二维码生成接口的视图代码,也可以可以基于原生的QRCode进行编写.

def qrcode():import qrcodefrom PIL import Imagefrom io import BytesIOtext = 'https://127.0.0.1:5000/user/invitation?Invitation_user=31'qr = qrcode.QRCode(version=1,error_correction=qrcode.constants.ERROR_CORRECT_H,box_size=5,border=4,)# 添加数据qr.add_data(text)# 填充数据qr.make(fit=True)# 生成图片img = qr.make_image(fill_color="#009696", back_color="white")# 添加logo,打开logo照片static_path = os.path.join(current_app.BASE_DIR, current_app.config["STATIC_DIR"])icon = Image.open(static_path+'/1b93988a-ee96-45dc-8df6-65ab062dcfc6.jpeg')ext = "jpeg"# 获取图片的宽高img_w, img_h = img.size# 参数设置logo的大小factor = 6size_w = int(img_w / factor)size_h = int(img_h / factor)icon_w, icon_h = icon.sizeif icon_w > size_w:icon_w = size_wif icon_h > size_h:icon_h = size_h# 重新设置logo的尺寸print(icon_w)print(icon_h)icon = icon.resize((icon_w+20, icon_h+20), Image.ANTIALIAS)# 得到画图的x,y坐标,居中显示w = int((img_w - icon_w-10) / 2)h = int((img_h - icon_h-10) / 2)# 黏贴logo照img.paste(icon, (w, h), mask=None)byte_io = BytesIO()img.save(byte_io, ext)byte_io.seek(0)return send_file(byte_io, mimetype='image/%s' % ext)

客户端获取二维码,html/invite.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" id="app"><div class="box"><p class="title">邀请好友</p><img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""><div class="content"><img class="invite_code" :src="code_url" alt=""></div><p class="invite_tips">长按保存图片到相册</p></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {code_url: '',  // 二维码url地址prev:{name:"",url:"",params:{}},current:{name:"invite",url:"invite.html",params:{}},}},created(){this.get_qrcode();},methods:{get_qrcode(){// 获取二维码var token = this.game.get('access_token') || this.game.fget('access_token');this.code_url = `${this.settings.code_url}/users/invite/code?token=${token}`;},close_frame(){this.game.outFrame("invite");},}});}</script>
</body>
</html>

客户端用户长按页面, 保存图片到相册中,html/invite.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" id="app"><div class="box"><p class="title">邀请好友</p><img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt=""><div class="content"><img class="invite_code" :src="code_url" alt=""></div><p class="invite_tips">长按保存图片到相册</p></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {code_url: '',  // 二维码url地址prev:{name:"",url:"",params:{}},current:{name:"invite",url:"invite.html",params:{}},}},created(){this.get_qrcode();},methods:{get_qrcode(){// 获取二维码var token = this.game.get('access_token') || this.game.fget('access_token');this.code_url = `${this.settings.code_url}/users/invite/code?token=${token}`;// 监听页面是否被长按api.addEventListener({name: 'longpress'}, (ret, err)=>{api.saveMediaToAlbum({path: this.code_url}, (ret, err)=>{ if( ret && ret.status ){alert('保存成功');}else{alert('保存失败');}});});},close_frame(){this.game.outFrame("invite");},}});}</script>
</body>
</html>

3.客户端通过第三方识别微信二维码,服务端提供对应的接口允许访问

users/views.py

from flask import render_templatedef invite_download():uid = request.args.get('uid')if 'micromessenger' in request.headers.get('User-Agent').lower():position = 'weixin'else:position = 'other'return render_template('users/download.html', position=position, uid=uid)

模板目录下创建对应的html模板文件,templates/users/download.html, 代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta><title>Title</title><style>body{background-color: #000;}img{width: 100%;}a{color: #fff;}</style>
</head>
<body>{% if position == 'weixin' %}<img src="/static/openbrowse.png" alt="">{% else %}<div id="content"></div><script>// 尝试通过打开客户端已经安装的魔方APPvar iframe = document.createElement('iframe');iframe.src = 'mofang://?uid={{ uid }}';  // app的私有协议iframe.hidden = true;document.body.appendChild(iframe);// 如果等待了4秒以后setTimeout(function (){if (!document.hidden){// 在4秒内如果页面出去了。说明这个时候document.hidden是true,这段代码就不执行了。// 就算是再切回来也是不执行的。// 如果你进了这个函数,没离开。。那就会在4秒后跳进这里alert('你还没安装魔方APP,去下载');u = navigator.userAgent;let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;  // android终端let isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);  // ios终端var content = document.querySelector('#content');if(isiOS){// 去下载ioscontent.innerHTML = `<a href="/static/app/mofang.apk">下载魔方APP</a>`;}if(isAndroid){// 去下载安卓content.innerHTML = `<a href="/static/app/mofang.apk">下载魔方APP</a>`;}}}, 5000);</script>{% endif %}</body>
</html>

templates->Mark Directory as->Template Folder

4.App配置私有协议, 允许第三方应用通过私有协议,唤醒APP

config.xml

 <widget id="A6151729457001"  version="0.0.1"><name>MFdemo</name><description>Example For APICloud.</description><author email="developer@apicloud.com" href="http://www.apicloud.com">Developer</author><content src="html/index.html" /><access origin="*" /><preference name="pageBounce" value="false"/><preference name="appBackground" value="rgba(0,0,0,0.0)"/><preference name="windowBackground" value="rgba(0,0,0,0.0)"/><preference name="frameBackgroundColor" value="rgba(0,0,0,0.0)"/><preference name="hScrollBarEnabled" value="false"/><preference name="vScrollBarEnabled" value="false"/><preference name="autoLaunch" value="true"/><preference name="fullScreen" value="false"/><preference name="autoUpdate" value="true" /><preference name="smartUpdate" value="false" /><preference name="debug" value="true"/><preference name="statusBarAppearance" value="true"/><permission name="readPhoneState" /><permission name="camera" /><permission name="record" /><permission name="location" /><permission name="fileSystem" /><permission name="internet" /><permission name="bootCompleted" /><permission name="hardware" /><preference name='urlScheme' value='mofang' /></widget>

接下来的开发,我们不能再依赖官方提供的Apploader进行功能测试了,所以我们使用由APICloud编辑器提供的本地编译, 编译自定义APPLoader来进行测试

由此,带来了另一个问题,就是接下来,APP中打印的信息, 不能继续通过编辑器提供的console终端来查看了,所以我们修改main.js的代码.

   /*print(data){// 打印数据console.log(JSON.stringify(data));}*/print(data, show=false){// 打印数据if(show){alert(JSON.stringify(data));}else{console.log(JSON.stringify(data));}}

index.html中监听是否来自第三方应用的唤醒.并接收参数.
html/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/main.js"></script><script src="../static/js/uuid.js"></script><script src="../static/js/settings.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" src="../static/images/image1.png"></li><li><img class="module2" @click="gohome" src="../static/images/image2.png"></li><li><img class="module3" src="../static/images/image3.png"></li><li><img class="module4" src="../static/images/image4.png"></li></ul></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {music_play:true,  // 默认播放背景音乐prev:{name:"",url:"",params:{}}, // 上一页状态current:{name:"index",url:"index.html","params":{}}, // 下一页状态}},watch:{music_play(){if(this.music_play){this.game.play_music("../static/mp3/bg1.mp3");}else{this.game.stop_music();}}},created(){this.app_listener();this.check_user_login();},methods:{app_listener(){// 使用appintenr监听并使用appParam接收URLScheme的参数// 收集操作保存起来,并跳转到注册页面.// 注册frame中, 用户注册成功以后,记录邀请信息.api.addEventListener({name: 'appintent'  // 当前事件监听必须是唯一的,整个APP中只能编写一次,否则冲突导致监听无效}, (ret, err)=>{var appParam = ret.appParam;this.game.print(typeof appParam);  // {"uid":"15"}// 保存URLScheme参数到本地this.game.fsave(appParam);// 跳转到注册页面this.game.goWin('user', 'register.html', this.current);});},check_user_login(){let token = this.game.get('access_token') || this.game.fget('access_token');this.game.checkout(this, token, (new_access_token)=>{if(new_access_token.errno == 1005){this.game.save({'access_token': ''});this.game.fremove('access_token');}});},gohome(){if(this.game.get('access_token') || this.game.fget('access_token')){this.game.goWin('user','user.html', this.current);}else {this.game.goWin('user','login.html', this.current);}}}})}</script>
</body>
</html>

html/register.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" 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><div class="form"><div class="form-title"><img src="../static/images/register.png"><img class="back" @click="back" src="../static/images/back.png"></div><div class="form-data"><div class="form-data-bg"><img src="../static/images/bg1.png"></div><div class="form-item"><label class="text">手机</label><input type="text" v-model="mobile" @change="check_mobile" placeholder="请输入手机号"></div><div class="form-item"><label class="text">验证码</label><input type="text" class="code" v-model="sms_code" placeholder="请输入验证码"><img class="refresh" @click="send" src="../static/images/refresh.png"></div><div class="form-item"><label class="text">密码</label><input type="password" v-model="password" placeholder="请输入密码"></div><div class="form-item"><label class="text">确认密码</label><input type="password" v-model="password2" placeholder="请再次输入密码"></div><div class="form-item"><input type="checkbox" class="agree" v-model="agree" checked><label><span class="agree_text">同意磨方《用户协议》和《隐私协议》</span></label></div><div class="form-item"><img class="commit" @click="registerHandle" src="../static/images/commit.png"/></div></div></div></div><script>apiready = function(){init();new Vue({el:"#app",data(){return {is_send: false,send_interval: 60, // 短信发送冷却时间mobile:"",password: "",password2: "",sms_code:"",agree:false,music_play:true,prev:{name:"",url:"",params:{}},current:{name:"register",url:"register.html","params":{}},}},watch:{music_play(){if(this.music_play){this.game.play_music("../static/mp3/bg1.mp3");}else{this.game.stop_music();}}},methods:{send(){// 点击发送短信if (!/1[3-9]\d{9}/.test(this.mobile)){api.alert({title: "警告",msg: "手机号码格式不正确!",});return; // 阻止代码继续往下执行}if(this.is_send){api.alert({title: "警告",msg: `短信发送冷却中,请${this.send_interval}秒之后重新点击发送!`,});return; // 阻止代码继续往下执行}this.axios.post("",{"jsonrpc": "2.0","id": this.uuid(),"method": "Home.sms","params": {"mobile": this.mobile,}}).then(response=>{if(response.data.result.errno != 1000){api.alert({title: "错误提示",msg: response.data.result.errmsg,});}else{this.is_send=true; // 进入冷却状态this.send_interval = 60;var timer = setInterval(()=>{this.send_interval--;if(this.send_interval<1){clearInterval(timer);this.is_send=false; // 退出冷却状态}}, 1000);}}).catch(error=>{this.game.print(error.response);});},registerHandle(){// 注册处理this.game.play_music('../static/mp3/btn1.mp3');// 验证数据[双向验证]if (!/1[3-9]\d{9}/.test(this.mobile)){api.alert({title: "警告",msg: "手机号码格式不正确!",});return; // 阻止代码继续往下执行}if(this.password.length<3 || this.password.length > 16){api.alert({title: "警告",msg: "密码长度必须在3-16个字符之间!",});return;}if(this.password != this.password2){api.alert({title: "警告",msg: "密码和确认密码不匹配!",});return; // 阻止代码继续往下执行}if(this.sms_code.length<1){api.alert({title: "警告",msg: "验证码不能为空!",});return; // 阻止代码继续往下执行}if(this.agree === false){api.alert({title: "警告",msg: "对不起, 必须同意磨方的用户协议和隐私协议才能继续注册!",});return; // 阻止代码继续往下执行}var invite_uid = 0;var uid = this.game.fget('uid');  // {"uid":"15"}if(uid>0){invite_uid = uid;}this.axios.post("",{"jsonrpc": "2.0","id": this.uuid(),"method": "User.register","params": {"mobile": this.mobile,"sms_code":this.sms_code,"password":this.password,"password2":this.password2,'invite_uid': invite_uid}}).then(response=>{this.game.print(response.data.result);if(response.data.result.errno != 1000){api.alert({title: "错误提示",msg: response.data.result.errmsg,});}else{// 注册成功!api.confirm({title: '磨方提示',msg: '注册成功',buttons: ['返回首页', '个人中心']}, (ret, err)=>{if(ret.buttonIndex == 1){// 跳转到首页this.game.outWin("user");}else{// 删除邀请人this.game.femove('uid')// 跳转到个人中心this.game.goFrame("user",'user.html', this.current);}});}}).catch(error=>{this.game.print(error.response);});},check_mobile(){// 验证手机号码this.axios.post("",{"jsonrpc": "2.0","id": this.uuid(),"method": "User.mobile","params": {"mobile": this.mobile}}).then(response=>{this.game.print(response.data.result);if(response.data.result.errno != 1000){api.alert({title: "错误提示",msg: response.data.result.errmsg,});}}).catch(error=>{this.game.print(error.response.data.error);});},back(){// this.game.outWin();// this.game.outFrame();this.game.goGroup("user",0);}}})}</script>
</body>
</html>

settings.js

function init(){if (Game) {var game = new Game("../mp3/bg1.mp3");Vue.prototype.game = game;}server_url = 'http://192.168.20.180:5000';if(axios){// 初始化axiosaxios.defaults.baseURL = server_url+"/api" // 服务端api接口网关地址axios.defaults.timeout = 2500; // 请求超时时间axios.defaults.withCredentials = false; // 跨域请求资源的情况下,忽略cookie的发送Vue.prototype.axios = axios;Vue.prototype.uuid  = UUID.generate;}// 接口相关的配置项Vue.prototype.settings = {captcha_app_id: "2041284967",  // 腾讯防水墙验证码应用IDavatar_url: server_url+"/users/avatar",code_url: server_url,}
}

针对用户的注册功能, 增加invite_uid的处理,users/views.py视图代码:

@jsonrpc.method("User.register")
def register(mobile,password,password2, sms_code, invite_uid):"""用户信息注册"""try:ms = MobileSchema()ms.load({"mobile": mobile})us = UserSchema()user = us.load({"mobile":mobile,"password":password,"password2":password2,"sms_code": sms_code,'invite_uid': invite_uid,})data = {"errno": status.CODE_OK,"errmsg":us.dump(user)}except ValidationError as e:data = {"errno": status.CODE_VALIDATE_ERROR,"errmsg":e.messages}return data

users/marshmallow.py,代码

from marshmallow_sqlalchemy import SQLAlchemyAutoSchema,auto_field
from marshmallow import post_load,pre_load,validates_schema
from application import redis
class UserSchema(SQLAlchemyAutoSchema):mobile = auto_field(required=True, load_only=True)password = fields.String(required=True, load_only=True)password2 = fields.String(required=True, load_only=True)sms_code = fields.String(required=True, load_only=True)invite_uid = fields.Integer(required=True, load_only=True)class Meta:model = Userinclude_fk = True # 启用外键关系include_relationships = True # 模型关系外部属性fields = ["id", "name","mobile","password","password2","sms_code", 'invite_uid']  # 如果要全换全部字段,就不要声明fields或exclude字段即可sql_session = db.session@post_load()def save_object(self, data, **kwargs):invite_uid = int(data['invite_uid'])data.pop("password2")data.pop("sms_code")data.pop('invite_uid')data["name"] = data["mobile"]instance = User(**data)db.session.add( instance )db.session.commit()# 记录邀请信息到Mongdb中if invite_uid > 0:"""只有invite_uid大于0,才是经过邀请注册进来的新用户"""# 验证是否属于有效的邀请invite_user = User.query.get(invite_uid)if invite_user is not None:"""只有邀请人存在的情况下才算有效邀请"""query = {'_id': invite_uid}ret = mongo.db.user_invite_list.find_one(query)if ret:mongo.db.user_invite_list.update(query, {'$push': {'invite_list': instance.id}})else:data = {'_id': invite_uid, 'invited_list': [instance.id]}mongo.db.user_invite_list.insert(data)# 添加好友关系return instance@validates_schemadef validate(self,data, **kwargs):# 校验密码和确认密码if data["password"] != data["password2"]:raise ValidationError(message=Message.password_not_match,field_name="password")#todo 校验短信验证码#1. 从redis中提取验证码redis_sms_code = redis.get("sms_%s" % data["mobile"])if redis_sms_code is None:raise ValidationError(message=Message.sms_code_expired,field_name="sms_code")redis_sms_code = redis_sms_code.decode()#2. 从客户端提交的数据data中提取验证码sms_code = data["sms_code"]#3. 字符串比较,如果失败,则抛出异常,否则,直接删除验证码if sms_code != redis_sms_code:raise ValidationError(message=Message.sms_code_error, field_name="sms_code")redis.delete("sms_%s" % data["mobile"])return data

魔坊APP项目-15-邀请好友(业务逻辑流程图、服务端提供邀请好友的二维码生成接口、客户端通过第三方识别微信二维码,服务端提供接口允许访问、App配置私有协议,允许第三方应用通过私有协议,唤醒APP)相关推荐

  1. 很多应用项目都有配置文件,这些配置文件里面定义一些应用需要的参数数据。 如果客户端使用这个类是通过new一个AppConfig的实例来得到一个操作配置 文件内容的对象,则在系统运行中,有 很多地方都需

      很多应用项目都有配置文件,这些配置文件里面定义一些应用需要的参数数据.   如果客户端使用这个类是通过new一个AppConfig的实例来得到一个操作配置文件内容的对象,则在系统运行中,有很多地方 ...

  2. HTML5 APP项目展示响应式网页模板

    简介: 国外的一款APP项目展示HTML单页模板 1.该模板代码干净整洁: 2.效果相当的炫酷,相当简洁大气高端,模板简单,全部已数据调用 3.网站手工DIV+css,代码精简,首页排版整洁大方.布局 ...

  3. 聊聊 Web 项目二维码生成的最佳姿势

    在设计和实现的过程之后,你永远不知道部署上去的程序会已什么样的姿势运行. 本篇借一次生成二维码逻辑的不同实现,阐述 Web 项目中二维码生成的正确姿势. 文中如有批量,欢迎各位看客老爷拍砖.试运行前5 ...

  4. 魔坊APP项目-16-种植园、websocket协议、服务端基于socket提供服务(基于房间管理分发信息)、种植园页面展示

    种植园 我们需要完成的种植园,是一个互动频繁,并且要求有一定即时性的模块,所以如果继续基于http协议开发,那么需要通过ajax发送大量http请求,同时因为http本身属于单向通讯,所以服务端无法主 ...

  5. SpringBoot交友APP项目实战(详细介绍+案例源码) - 7.即时通讯(基于第三方API)

    陌上花开,可缓缓归矣 系列文章目录 1. 项目介绍及环境配置 2. 短信验证码登录 3. 用户信息 4. MongoDB 5. 推荐好友列表/MongoDB集群/动态发布与查看 6. 圈子动态/圈子互 ...

  6. 以太坊Dapp项目-网页钱包开发手册

    以太坊Dapp项目-网页钱包开发手册 修订日期 姓名 邮箱 2018-10-10 brucefeng brucefeng@brucefeng.com 前言 在之前的一篇文章以太坊智能合约项目-Toke ...

  7. 第五章 手工测试之APP项目

    文章目录 第五章 手工测试之APP项目 一.APP概念 APP应用系统架构 二.APP项目环境 1.开发环境 2.测试环境 3.预发布环境 4.生产环境 5.灰度发布 6.前台发布生产环境 7.发布平 ...

  8. 转载-如何做好项目的需求与业务调研▲▲▲

    原文地址:http://blog.sina.com.cn/s/blog_6a656bb40102vhzf.html 作者:郭致星,百科介绍:http://baike.baidu.com/item/%E ...

  9. 转载-如何做好项目的需求与业务调研

    如何做好项目的需求与业务调研 1. 调研工作如何组织 2. 调研准备阶段容易犯哪些错误 2.1 第一个容易犯的错误:不清楚调研的的目的 2.2 第二个容易犯的错误:计划不够细致 3. 调研准备阶段容易 ...

最新文章

  1. Mqtt ----心跳机制
  2. nginx 配置并发数限制
  3. Golang中使用kafka
  4. 多个cuda 被单进程沾满_报名 | 提高GPU利用率,听英伟达专家分享这个CUDA工具
  5. Java 反射取类中类_Java反射机制(二):通过反射取得类的结构
  6. sap事务代码_「SAP技术」SAP MM 事务代码ME17的用法
  7. akamai:与看视频广告等待相比,用户更不能忍受缓冲等待
  8. 做tab切换时,点击浏览器返回拿不到实时的tab参数,请求不到实时的数据
  9. JBPM工作流引擎原理
  10. js的常见的三种密码加密方式-MD5加密、Base64加密和解密和sha1加密详解总结
  11. 为什么保险公司一直不停地招人?
  12. 程序员微信名昵称_好听的微信名800个
  13. python stdin.write_向stdin写入大量数据
  14. 2022.10.14每日刷题打卡
  15. Python3使用Xpath解析网易云音乐歌手页面
  16. 黑群晖从入门到入土,自编译适合自己硬件的黑群晖7.1.x引导(黑群晖DSM7.X引导用arpl编译教程)
  17. mov格式的视频转换mp4,教你三种方法转换
  18. 使用scrapy创建一个项目爬取网易云音乐的所有歌手的相关资料
  19. 【计算机网络——制作双绞线】
  20. 云服务器部署nginx

热门文章

  1. 关于SAP APO RPMCALL 指定生产订单的BOM更新
  2. 入门PCB设计(杜洋工作室)——Altium Designer Winter 09
  3. 缠论工具(笔, 线段)
  4. 加米谷大数据报告:社交网络大数据的应用有多大的价值
  5. shell脚本--重启nohup后台运行的程序
  6. Ansible基础和常用模块(一)
  7. Docker安装MySQL忽略大小写问题的问题
  8. 【IOS】高仿百度传课
  9. 中小企业ERP——“想说爱你不容易”
  10. java/php/net/python校园招聘管理系统设计