zoukankan      html  css  js  c++  java
  • day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作

    目录

    1.种植园商城页面初始化

    2.规划商品种类并且构建关于商品的模型类

    3.解决APP打包编译之后的跨域限制

    4.商品列表后端接口实现

    5.前端获取商品列表并显示

    6.种植园点击充值允许用户选择充值金额

    7.将AlipayPlus模块加载到APP上

    8.集成Alipayplus模块完成支付

    9.增加用户充值订单模型类

    1.种植园商城页面初始化

    1.种植园点击商店会去到种植园商城页面

    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">
                        <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="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">背包</li>
            <li class="menu-center" @click="go_orchard_shop">商店</li>
            <li class="menu">消息</li>
            <li class="menu">好友</li>
          </ul>
        </div>
        </div>
        <script>
        apiready = function(){
            init();
            new Vue({
                el:"#app",
                data(){
                    return {
              music_play:true,
              namespace: '/mofang_orchard',
              token:"",
              socket: null,
              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: 'auto',
            },null);
            this.checkout();
          },
                methods:{
            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("开始连接服务端");
              });
            },
            go_index(){
              this.game.outWin("orchard");
            },
            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
              });
                    }
                }
            });
        }
        </script>
    </body>
    </html>
    种植园点击商城跳转到商城界面

    2.种植园商城页面初始化

    新建种植园商城页面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">
              <div class="avatar shop_item">
                <img src="../static/images/fruit_tree.png" alt="">
              </div>
              <div class="info">
                <p class="username">果树</p>
                <p class="time">200</p>
              </div>
              <div class="status">200</div>
            </div>
          </div>
        </div>
        </div>
        <script>
        apiready = function(){
            init();
            new Vue({
                el:"#app",
                data(){
                    return {
                        user_id: "", // 当前登陆用户Id
                        prev:{name:"",url:"",params:{}},
                        current:{name:"orchard",url:"shop.html",params:{}},
                    }
                },
    
                created(){
                    this.user_id = this.game.get("id") || this.game.fget("id");
                },
                methods:{
            close_frame(){
              this.game.outFrame("orchard_shop");
            },
                }
            });
        }
        </script>
    </body>
    </html>
    种植园商城页面初始化:shop.html

    种植园商城页面CSS样式main.css,代码:

    .shop .shop_list{
      margin-left: 1rem;
      margin-top: -5rem;
    }

    2.规划商品种类并且构建关于商品的模型类

    1.先规划好商品的种类以及相关参数

    商店的商品:
        1. 种子
           果树
              标题
              价格
              描述
              图片
              使用流程相关
                  状态: 种子期, 成长期, 成熟期, 树桩
                  种子期: 3  x 60 x 60
                  成长期: 6  x 60 x 60
                  成熟期: 12 x 60 x 60
                  树桩 :  -1
        2. 宠物
           小狗1,小狗2,小狗3,小狗4
              标题
              价格
              描述
              图片
              使用流程相关:
                  饱食度: <20%(饥饿) <50%(正常) <100%(饱腹)
                  生命期: -1(永久)
                  保护命中率  : 10%   0-1   <10%
        3. 狗粮
           狗粮1,狗粮2,狗粮3
              标题
              价格
              描述
              图片
              使用流程相关:
                  饱食度: 20%
                  有效期: 
        4. 道具
           化肥,....
              标题
              价格
              描述
              图片
              使用流程相关:
              缩短时间: 1小时
        

    2.商品基本信息的模型类

    apps/orchard/models.py,模型,代码:

    from application.utils.models import BaseModel,db
    class Goods(BaseModel):
        """商品基本信息"""
        __tablename__ = "mf_goods"
        remark = db.Column(db.String(255), comment="商品描述")
        price = db.Column(db.Numeric(7,2), comment="商品价格")
        image = db.Column(db.String(255),  comment="商品图片")

    3.为用户添加果子积分字段

    apps/users/models.py,模型代码:

    from werkzeug.security import generate_password_hash, check_password_hash
    from application.utils.models import BaseModel,db
    class User(BaseModel):
        """用户基本信息"""
        __tablename__ = "mf_user"
         
        # 为用户表添加果子积分字段
        credit = db.Column(db.Numeric(7,2),default=0, comment="果子积分")
        

    4.将商品基本信息模型注册到admin中

    在admin站点中进行模型管理注册,apps/orchard/admin.py,代码:

    # 根据模型自动生成页面
    from .models import Goods
    from flask_admin.contrib.sqla import ModelView
    from application import admin,db
    class GoodsAdminModel(ModelView):
        # 列表页显示字段列表
        column_list = ["id","name","price"]
        # 列表页可以直接编辑的字段列表
        column_editable_list = ["price"]
        # 是否允许查看详情
        can_view_details = True
        # 列表页显示直接可以搜索数据的字典
        column_searchable_list = ['name', 'price']
        # 过滤器
        column_filters = ['name']
        # 单页显示数据量
        page_size = 10
    
    admin.add_view(GoodsAdminModel(Goods,db.session,name="商品", category="种植园"))

    5.在商城里添加一些道具(植物 狗粮 化肥等)

    INSERT INTO mofang.mf_goods 
    (id, name, is_deleted, orders, status, created_time, updated_time, remark, price, image) 
    VALUES 
    (1, '果树', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '果树', 10.00, null),
    (2, '小狗1号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗1号', 100.00, null),
    (3, '小狗2号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗2号', 200.00, null),
    (4, '小狗3号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗3号', 300.00, null),
    (5, '小狗4号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗4号', 100.00, null),
    (6, '狗粮1号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '狗粮1号', 10.00, null),
    (7, '狗粮2号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '狗粮2号', 5.00, null),
    (8, '化肥1号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '化肥1号', 3.00, null),
    (9, '化肥2号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '化肥2号', 6.00, null),
    (10, '化肥3号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '化肥3号', 9.00, null);

    3.解决APP打包编译之后的跨域限制

    解决app打包生成以后,页面无法请求服务端数据的跨域问题。

    无法获取数据的原因是,当前APP中获取数据是通过ajax来发送请求的,因为我们当前的APP是混合APP,所以实际来说,这种混合APP就是一个浏览器内核构建的。因此也会存在同源策略的访问限制,因此我们需要在服务端实现跨域资源共享。

    服务端终端运行:

    pip install -U flask-cors

    CORS初始化:

    application/__init__.py,代码:

    from flask_cors import CORS
    
    # flask_cors
    cors = CORS()
    
    def init_app(config_path):
        """全局初始化"""
      
        # cors
        cors.init_app(app,resources={r"/api/*": {"origins": "*"}})

    4.商品列表后端接口实现

    1.视图部分

    提供商品列表给客户端,apps/orchard/views.py, 代码:

    from application import jsonrpc
    from status import APIStatus as status
    from message import ErrorMessage as message
    from application import redis
    from .models import Goods
    from application.apps.users.models import User
    from flask_jwt_extended import jwt_required,get_jwt_identity
    from .marshmallow import GoodsInfoSchema
    @jsonrpc.method(name="Orchard.goods.list")
    @jwt_required  # 验证jwt
    def goods_list(page=1,limit=10):
        """商品列表"""
        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,
            }
    
        pagination = Goods.query.filter(
            Goods.is_deleted==False,
            Goods.status==True
        ).paginate(page, per_page=limit)
        
        # 转换数据格式
        gis = GoodsInfoSchema()
        goods_list = gis.dump(pagination.items,many=True)
    
        return {
            "errno": status.CODE_OK,
            "errmsg": message.ok,
            "goods_list": goods_list,
            "pages": pagination.pages
        }

    2.序列化器部分

    marshmallow.py,代码:

    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()
    
        class Meta:
            model = Goods
            fields = ["id","name","price","image","remark"]
            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

    5.前端获取商品列表并显示

    客户端就可以在打开商品的时候, 获取商品列表,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" 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">{{goods.price}}</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);
                            });
                        })
                    },
                }
            });
        }
        </script>
    </body>
    </html>
    前端获取商品列表并显示

    6.种植园点击充值允许用户选择充值金额

    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">
                      <!-- ***为充值绑定一个user_recharge事件*** -->
                    <div class="balance" @click="user_recharge">
                        <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="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">背包</li>
            <li class="menu-center" @click="go_orchard_shop">商店</li>
            <li class="menu">消息</li>
            <li class="menu">好友</li>
          </ul>
        </div>
        </div>
        <script>
        apiready = function(){
            init();
            new Vue({
                el:"#app",
                data(){
                    return {
              music_play:true,
              namespace: '/mofang_orchard',
              token:"",
              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: 'auto',
            },null);
            this.checkout();
          },
                methods:{
                     // ***充值***
                    user_recharge(){
                        // 充值
                        api.actionSheet({
                            title: '余额充值',
                            cancelTitle: '取消',
                            buttons: this.recharge_list
                        }, function(ret, err){
                            if( ret ){
                                 // 充值金额
                                         money = this.recharge_list[ret.buttonIndex-1];
                                         // 调用支付宝重置
                                         
                            }else{
    
                            }
                        });
    
                    },
            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("开始连接服务端");
              });
            },
            go_index(){
              this.game.outWin("orchard");
            },
            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
              });
                    }
                }
            });
        }
        </script>
    </body>
    </html>
    点击充值允许用户选择充值金额

    7.将AlipayPlus模块加载到APP上

    1.首先来到APIcloud开发者web后台,把Alipayplus模块加载到APP。

    2.点击Alipayplus进入模块详情。

    3.把模块使用到指定APP中。[下图只做参看, 项目已定义叫Mofang]

    8.集成Alipayplus模块完成支付

    1.安装alipay的sdk

    接下来,服务端中需要完成的就是生成订单参数和接收支付结果,所以我们先 安装alipay的sdk,集成到flask项目中。

    终端运行:

    pip install python-alipay-sdk --upgrade

    2.配置支付宝的公钥和私钥

    配置支付宝的公钥和私钥,保存到application/apps/users/keys目录下。

    cd application/apps/users/
    mkdir keys
    cd keys
    openssl
    OpenSSL> genrsa -out app_private_key.pem   2048  # 私钥
    OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥
    OpenSSL> exit

    保存商户公钥到支付宝开放平台, 并从开放平台中把支付宝公钥保存到项目中

    9.增加用户充值订单模型类

    users/models.py,代码:

    class Recharge(BaseModel):
        __tablename__ = "mf_user_recharge"
        status  = db.Column(db.Boolean, default=True, comment="状态(是否支付)")
        out_trade_number  = db.Column(db.String(64), unique=True, comment="订单号")
        user_id = db.Column(db.Integer, comment="用户")
        money = db.Column(db.Numeric(7,2), comment="账户余额")
        trade_number = db.Column(db.String(64), unique=True, comment="流水号")
  • 相关阅读:
    35 个 Java 代码性能优化总结
    P4172 [WC2006]水管局长(LCT)
    P4219 [BJOI2014]大融合(LCT)
    P1501 [国家集训队]Tree II(LCT)
    P4381 [IOI2008]Island(基环树+单调队列优化dp)
    P3332 [ZJOI2013]K大数查询(线段树套线段树+标记永久化)
    P3809 【模板】后缀排序
    P3813 [FJOI2017]矩阵填数(组合数学)
    P2147 [SDOI2008]洞穴勘测(LCT)
    P3924 康娜的线段树(期望)
  • 原文地址:https://www.cnblogs.com/libolun/p/14203599.html
Copyright © 2011-2022 走看看