day111:MoFang:邀請好友流程&生成邀請好友二維碼&第三方應用識別二維碼&本地編譯測試&記錄邀請人資訊

目錄

1.邀請業務邏輯流程圖

2.邀請好友-前端

3.邀請好友-後端介面(生成二維碼)

4.前端獲取後端生成的二維碼

5.前端長按頁面,保存圖片到相冊

6.客戶端通過第三方識別微信二維碼,服務端提供對應的介面允許訪問

7.download.html

8.App配置私有協議, 允許第三方應用通過私有協議,喚醒APP

9.開始使用本地編譯測試

10.首頁監聽是否有來自第三方應用的喚醒:app_listener

11.註冊頁面接收invite_uid參數

12.對於後端註冊介面(User.register):增加invite_uid的處理

1.邀請業務邏輯流程圖

 

2.邀請好友-前端

1.邀請好友頁面初始化:invite.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>

邀請好友頁面初始化:invite.html

2.邀請好友頁面CSS程式碼

main.css,程式碼:

.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;
}

邀請好友頁面CSS程式碼

3.用戶中心點擊邀請好友進入到邀請好友介面

用戶中心首頁, 實現點擊打開頁面,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             //動畫過渡時間,默認300毫秒
                    });
                    },
                    open_friend_list(){
                        // 打開好友列表主框架頁面
                        // this.game.goWin("friends","friends.html",this.current);

                        // 打開好友列表數據頁面
                        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>

用戶中心點擊邀請好友進入到邀請好友介面

3.邀請好友-後端介面(生成二維碼)

1.安裝生成二維碼模組:flask-qrcode

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

安裝二維碼生成模組

pip install flask-qrcode

初始化qrcode,application/__init__.py,程式碼:

from flask_qrcode import QRcode

# qrcode
QRCode = QRcode()

def init_app(config_path):
    """全局初始化"""
    # qrcode初始化配置
    QRCode.init_app(app)


    return manager

2.生成二維碼的後端介面

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,
        }
    
    static_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 = static_path +"/"+ user.avatar
    data = current_app.config.get("SERVER_URL",request.host_url[:-1])+"/users/invite/download?uid=%s" % current_user_id # //127.0.0.1:5000/users/invite/download?uid=15
    image = QRCode.qrcode(data,box_size=16,icon_img=avatar) # 根據用戶頭像生成二維碼
    b64_image = image[image.find(",")+1:]
    qrcode_iamge = base64.b64decode(b64_image)
    response = make_response(qrcode_iamge)
    response.headers["Content-Type"] = "image/png"
    return response

user/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 = "95822582-39d8-43ce-9498-fdced7f6a144.jpeg"
    # 服務端帶外提供的url地址
    SERVER_URL = "//127.0.0.1:5000"

4.前端獲取後端生成的二維碼

客戶端獲取二維碼,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>

前端獲取後端生成的二維碼

5.前端長按頁面,保存圖片到相冊

客戶端用戶長按頁面, 保存圖片到相冊中,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>

前端長按頁面,保存圖片到相冊

6.客戶端通過第三方識別微信二維碼,服務端提供對應的介面允許訪問

users/views.py

from flask import render_template
def 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)

7.download.html

模板目錄下創建對應的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/openbrowser.png" alt="">
    {% else %}
        <div id="content">

        </div>
        <script>
        // 嘗試通過打開客戶端已經安裝的魔方APP
        var 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) {
                // 去下載ios
                content.innerHTML = `<a href="/static/app/mofang.apk">下載魔方APP</a>`;
            }
            if (isAndroid){
                // 去下載Android
                content.innerHTML = `<a href="/static/app/mofang.apk">下載魔方APP</a>`;
            }
          }
        }, 5000);

        </script>
    {% endif %}
</body>
</html>

8.App配置私有協議, 允許第三方應用通過私有協議,喚醒APP

config.xml程式碼

 

 <widget id="A6151729457001"  version="0.0.1">

    <name>MFdemo</name>

    <description>

        Example For APICloud.

    </description>

    <author email="[email protected]" href="//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" />  <!-- **允許第三方應用通過私有協議,喚醒APP** -->

</widget> 

9.開始使用本地編譯測試

1.如何本地編譯

接下來的開發,我們不能再依賴官方提供的Apploader進行功能測試了,

所以我們使用由APICloud編輯器提供的本地編譯, 編譯自定義APPLoader來進行測試。

 

2.修改main.js的print函數

由此,帶來了另一個問題,就是接下來,APP中列印的資訊, 不能繼續通過編輯器提供的console終端來查看了,所以我們修改main.js的程式碼.

 

    print(data,show=false){
        // 列印數據
        if(show){
            alert(JSON.stringify(data));
        }else{
            console.log(JSON.stringify(data));
        }
    }

10.首頁監聽是否有來自第三方應用的喚醒:app_listener

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>

index.html中監聽是否來自第三方應用的喚醒

11.註冊頁面接收invite_uid參數

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 //**註冊頁面接收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>

註冊頁面接收invite_uid參數

12.對於後端註冊介面(User.register):增加invite_uid的處理

針對用戶的註冊功能, 增加invite_uid的處理,user/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

user/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 = User
        include_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_schema
    def 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