微信小程序 微信授权前后端解决方案 (更新至5.10授权机制)

一、基本说明

微信小程序中对于用户的登录授权机制,是非常重要的。许多的业务都必须拿到具体的微信数据才能进行。我们在之前的微信小程序开发中,因为业务比较简单只有首页,并且在进入小程序的时候就必须进行授权,所以在操作的时候比较简单。随着业务的不断增加,业务场景的增多,之前的授权机制无法覆盖所有的业务场景。本次是在之前的授权机制的场景下,进行优化和改版。前端和后台进行配合,完善微信的授权机制,达到适应现有的业务。本文将讲解从老版本到现版本的微信授权机制的改版优化过程。
注:5月10日的授权方案可在第四部分直接查看

二、代码说明

(1) 1.0版本微信授权

本文对具体的小程序端和后台之间的微信授权不做具体的说明,详情请看文档。对于小程序我们封装了两个主要的工具模块类。用户模块user.js,请求模块request.js。
user.js主要处理用户的基本信息模块,包括授权管理和登录,用户其他数据的绑定等。
var user = {
  config:{},
  userinfo:null,
  // 初始化user模块,校验ukey
  init(data){
    this.config = data;
    var self = this;
    wx.checkSession({
      success:function(res){
        //存在登录状态
        self.checkUkey();
      },
      fail:function(){
        //过期-重新登录
        self.login();
      }
    });
  },
  // 发起微信登录接口 获取登录凭证code
  login(){
    var self = this;
    wx.login({
      success:function(res){
        self.config.code = res.code;
        wx.setStorageSync(self.config.storage.code,res.code);
        self.getWXUserInfo(true);
      },
      fail:function(res){
        console.log("user login fail")
      }
    })
  },
  // 获取用户信息
  getWXUserInfo(isthrough){
    var self = this;
    wx.getUserInfo({
      success:function(res){
        self.userinfo = res.userInfo;
        wx.setStorageSync(self.config.storage.wxUser, res.userInfo);
        if (isthrough) {
          self.getUserUkey(res.rawData);
        }
      },
      fail:function(res){
        wx.openSetting({
          success:function(res){
            if(res.authSetting["scope.userInfo"]){
              wx.getUserInfo({
                success: res => {
                  // 可以将 res 发送给后台解码出 unionId
                  self.userinfo = res.userInfo;
                  wx.setStorageSync(self.config.storage.wxUser, res.userInfo);
                  if (isthrough) {
                    self.getUserUkey(res.rawData);
                  }
                }
              });
            }
          }
        })
      }
    })
  },
  // 获取Ukey
  getUserUkey(rawData){
    var self = this;
    var data = {
      code: wx.getStorageSync(self.config.storage.code),
      rawData: rawData
    };
    wx.request({
      url:self.config.requrl+"/Wdservice/api/wxLogin",
      data: data,
      method:"GET",
      success:function(res){
        res = res.data;
        console.log(res);
        if(res.code==1){
          // 缓存Ukey          
           wx.setStorageSync(self.config.storage.ukey,res.result.ukey);

         // 缓存Ukey存储时间
          wx.setStorageSync(self.config.storage.ukeytime,new Date().getTime());
       }
      }
    })
  },
  // 检验ukey是否过期
  checkUkey(){
    var ukey = wx.getStorageSync(this.config.storage.ukey);
    var ukeyTime = wx.getStorageSync(this.config.storage.ukeytime);
    var tmpTime = new Date().getTime();
    var token = wx.getStorageSync(this.config.storage.ukey_licaiyi);
    if (tmpTime - ukeyTime >= 1000 * 60 * 60 * 12 || !token){
      ukey = "";
      this.login();
    } else {
      this.getWXUserInfo(false);
    }
  }
}

module.exports = user;

request.js主要对请求的再封装,统一加入ukey。可对接口统一管理,增加扩展性。
var app = getApp();

// 打包ukey和参数
function ukeyData (data) {

  var copyData = JSON.parse(JSON.stringify(data));
  var ukey = { "ukey": wx.getStorageSync(app.globalData.config.storage.ukey) };
  var okUkeyData = Object.assign(copyData, ukey);
  return okUkeyData;
}

function post(data){

  req({
    url:data.url,
    method:"POST",
    data:ukeyData(data.data),
    succ:data.succ,
    fail:data.fail,
    complete:data.complete
  })
}

function get(data){
  req({
    url:data.url,
    method:"GET",
    data: ukeyData(data.data),
    succ:data.succ,
    fail:data.fail,
    complete:data.complete
  })
}

function req(data){
  wx.request({
    url: data.url,
    data: ukeyData(data.data),
    method: data.method, // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
    header: {
      'content-type':'application/x-www-form-urlencoded',
    }, // 设置请求的 header
    success: function(res){
      // success
      res = res.data;
      if(typeof data.succ === "function"){
        data.succ(res);
      }
    },
    fail:function(res){
      if(typeof data.fail === "function"){
        data.fail(res);
      }
    },
    complete:function(res){
      if(typeof data.complete === "function"){
        data.complete(res);
      }
    }
  })
}

const request = {
  post:post,
  get:get
}

module.exports = request;

具体使用:在app.js中引用user.js初始化该组件即可。每次程序启动的时候会自动校验ukey的有效性,如果没有登录或者ukey过期会重新登录。
var user = require("/tools/user");
onLaunch: function () {
    // 检测用户
    user.init(config);
}

(2) 2.0版本微信授权

随着业务的增加,小程序添加了扫码支付的功能。该功能在1.0版本的情况下会出现很重大的bug。虽然在app.js中初始化了user.js组件并得到ukey等信息。但是跳转到扫码页面的时候,因为登录请求都是异步执行的,在app.js完成授权之前,界面上的接口已经调用了。由于当时可能还没有获取到ukey导致界面上的接口报错,而获取不到需要的数据,界面一片空白,又没有刷新机制,导致出现严重的授权bug。对于这个业务场景下1.0的微信授权已经无法解决我们的问题,所以我们对user.js进行优化,以适应该业务。
在user.js中添加登录完成的回调succ
var user = {
  config:{},
  userinfo:null,
  init(data){
    this.config = data;
    var self = this;
    wx.checkSession({
      success:function(res){
        //存在登录状态
        self.checkUkey();
      },
      fail:function(){
        //过期-重新登录
        self.login(null);
      }
    });
  },
  login(succ){
    var self = this;
    wx.login({
      success:function(res){
        self.config.code = res.code;
        wx.setStorageSync(self.config.storage.code,res.code);
        self.getWXUserInfo(true, succ);
      },
      fail:function(res){
        console.log("user login fail")
      }
    })
  },
  getWXUserInfo(isthrough, succ){
    var self = this;
    wx.getUserInfo({
      success:function(res){
        self.userinfo = res.userInfo;
        wx.setStorageSync(self.config.storage.wxUser, res.userInfo);
        if (isthrough) {
          self.getUserUkey(res.rawData, succ);
        }
      },
      fail:function(res){
        wx.openSetting({
          success:function(res){
            if(res.authSetting["scope.userInfo"]){
              wx.getUserInfo({
                success: res => {
                  // 可以将 res 发送给后台解码出 unionId
                  self.userinfo = res.userInfo;
                  wx.setStorageSync(self.config.storage.wxUser, res.userInfo);
                  if (isthrough) {
                    self.getUserUkey(res.rawData, succ);
                  }
                }
              });
            }
          }
        })
      }
    })
  },
  getUserUkey(rawData, succ){
    var self = this;
    var data = {
      code: wx.getStorageSync(self.config.storage.code),
      rawData: rawData
    };
    wx.request({
      url:self.config.requrl+"/Invest/api/wxLogin",
      data: data,
      method:"GET",
      success:function(res){
        res = res.data;
        if(res.code==1){
          wx.setStorageSync(self.config.storage.ukey,res.result.ukey);
          wx.setStorageSync(self.config.storage.ukeytime,new Date().getTime());
          self.getAuthenticationUserUkey(rawData, res.result.oid, succ);
        }
      }
    })
  },
  getAuthenticationUserUkey(rawData, openId, succ){
    var self = this;
    var data = {
      code: wx.getStorageSync(self.config.storage.code),
      rawData: JSON.parse(rawData),
      openId: openId,
      source: 2,
    };
    wx.request({
      url: self.config.couresurl + "/WechatApi/authentication",
      data: data,
      method: "POST",
      success: function (res) {
        res = res.data;
        if (res.resCode == 1) {
          wx.setStorageSync(self.config.storage.ukey_licaiyi, res.resObject);
          if (succ) {
            succ();
          }
        }
      }
    })
  },
  checkUkey(){
    var ukey = wx.getStorageSync(this.config.storage.ukey);
    var ukeyTime = wx.getStorageSync(this.config.storage.ukeytime);
    var tmpTime = new Date().getTime();
    var token = wx.getStorageSync(this.config.storage.ukey_licaiyi);
    if (tmpTime - ukeyTime >= 1000 * 60 * 60 * 12 || !token){
      ukey = "";
      this.login(null);
    } else {
      this.getWXUserInfo(false, null);
    }
  }
}

module.exports = user;

在扫码的界面上我们首先校验ukey,如果存在直接调用详细的接口,如果获取不到ukey,说明微信尚未授权,我们主动调用user模块的login方法,在登录的成功回调成功之后再执行接口,便可解决该问题。
if (wx.getStorageSync(app.globalData.config.storage.ukey)) {
    this.starToConfig();
 } else {
    app.globalData.wxUser.login(function(){
       self.starToConfig();
    });
 }
该解决方案具有很大的局限性,如果添加一个新的业务场景我们就需要多次判断,扩展性很差。但是对于只有这样一个特殊场景来说,还是适用的。

(3) 3.0版本微信授权

随着业务的增加,微信小程序进行了更大的改版,首页不再强制需要授权。我的页面则需要授权。在这样的背景下,我们对授权机制和请求机制进行联合,把授权的时机判断放在后台进行联合判断。这样就可以解决2.0遗漏下来的授权比较局限的问题。对于所有需要授权的地方后台如果检测到我们的接口用户没有授权,便返回统一的未授权的错误码,小程序端对该状态进行统一的操作,强制用户登录并在回调后执行后续的接口。
在request.js中, 我们对返回状态进行了细分,分为请求成功,请求错误,微信未授权等。
var app = getApp();

// 添加返回标识
var ResultCode = {
  ResultCodeSucc: 1,
  ResultCodeFail: -1,
  ResultCodeNoAccredit: -2
};

function ukeyData (data) {

  var okUkeyData = null;
  if (data) {
    var copyData = JSON.parse(JSON.stringify(data));
    var ukey = { "ukey": wx.getStorageSync(app.globalData.config.storage.ukey) };
    okUkeyData = Object.assign(copyData, ukey);
  } else {
    var ukey = { "ukey": wx.getStorageSync(app.globalData.config.storage.ukey) };
    okUkeyData = ukey;
  }
  return okUkeyData;
}

function post(data){
  req({
    url:data.url,
    method:"POST",
    data:ukeyData(data.data),
    succ:data.succ,
    error:data.error,
    no_accredit:data.no_accredit,
    fail:data.fail,
    complete:data.complete
  })
}

function get(data){
  req({
    url:data.url,
    method:"GET",
    data: ukeyData(data.data),
    succ:data.succ,
    error: data.error,
    no_accredit: data.no_accredit,
    fail:data.fail,
    complete:data.complete
  })
}

function req(data){
  wx.request({
    url: data.url,
    data: ukeyData(data.data),
    method: data.method, // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
    header: {
      'content-type':'application/x-www-form-urlencoded',
    }, // 设置请求的 header
    success: function(res){
      // success
      res = res.data;
     // 根据返回码 分发行为
     if (res.code == ResultCode.ResultCodeSucc) {
        // 请求成功
        if (typeof data.succ === "function") {
          data.succ(res);
        }
      } else if (res.code == ResultCode.ResultCodeNoAccredit) {
        if (typeof data.no_accredit === "function") {
          // 微信未授权
          app.globalData.wxUser.login(function () {
            data.no_accredit(res);
          });
        }
      } else {
        // 请求错误
        if (typeof data.error === "function") {
          data.error(res);
        }
      }
    },
    fail:function(res){
      // 请求失败
      if(typeof data.fail === "function"){
        data.fail(res);
      }
    },
    complete:function(res){
      if(typeof data.complete === "function"){
        data.complete(res);
      }
    }
  })
}

const request = {
  post:post,
  get:get
}

module.exports = request;

接口改造:
// 预约活动
  person_appointment_tap: function (e) {
    if (!this.data.isAppointment) {
      var self = this;
      request.post({
        url: app.globalData.config.requrl + "/Invest/Wx/subscribeMsg",
        data: {
          formid: e.detail.formId,
          activity_id: self.data.activity_id
        },
        header: {
          'content-type': 'application/x-www-form-urlencoded'
        },
        succ: function (res) {
          // 成功
          wx.showModal({
            title: '预约成功',
            content: "我们会在活动开始前给您发送消息提醒,请您及时关注",
            showCancel: false,
            confirmColor: "#ffcf00",
            success: function (res) {
            }
          })
          self.setData({ isAppointment: true });
        },
        error: function (res) {
          // 失败
          prompt.showModal(res.code == 2 ? "您已经预约过了!" : "预约失败,请稍后再试!", null);
          if (res.code == 2) {
            self.setData({ isAppointment: true });
          }
        },
        no_accredit: function (res) {
          // 在授权成功后再次调用该接口即可
          self.person_appointment_tap(e);
        }
      })
    }
  }
  
我们在app.js中删除用户初始化代码,在需要用户第一次授权的地方就进行初始化即可。在3.0中,我们将初始化授权代码放在我的单页的onLoad方法中,之后只要在后台需要授权权限的地方,检测到未授权,就可提示到前端,让前端进行用户登录授权了。

(4) 4.0版本微信授权(5月10日微信小程序授权调整)

5月10日微信小程序官方突然发布了新的微信小程序授权机制,用户首次授权必须使用按钮的方式进行显示授权。对于这次更新我们也在原有的基础上进行了改版,因为小程序的入口繁多而且微信没有继承的概念和像window一样的顶级浮层,就导致我们必须对授权机制进行统一管理。我们这次删除了user.js,将授权及整个登陆机制放在封装好的授权组件内进行统一管理。并在接口处处理登陆失效的情况,这样就能很好的解决这次微信新授权机制对我们的影响。
微信小程序获取用户信息接口优化调整文章 : 地址
(1)comp-auto组件
// index.js
var app = getApp();

Component({  
  properties: {
  },  
  data: {  
    show:false,
  },  
  methods: { 
    open(){
      this.setData({show:true});
    },
    close(){
      this.setData({show:false});
    },
      // 微信授权btn授权完成后会将用户信息返回,我们在这个方法中可以直接获取到用户数据
    userauth(){
      var self = this;
      wx.getUserInfo({
        success:function(res){
          wx.setStorageSync(app.globalData.config.storage.wxUser, res.userInfo);
          self.getUserUkey(res.rawData,true);
        },
        fail:function(res){
          console.log("userinfo fail",res);
        }
      })
    },
    checkUkey() {
      var ukey = wx.getStorageSync(app.globalData.config.storage.ukey);
      var ukeyTime = wx.getStorageSync(app.globalData.config.storage.ukeytime);
      var tmpTime = new Date().getTime();
      if (tmpTime - ukeyTime >= 1000 * 60 * 60 * 12) {
        wx.setStorageSync(app.globalData.config.storage.ukey, "");
        this.login();
      } else {
        this.getWXUserInfo();
      }
    },
    login(){
      var self = this;
      wx.login({
        success:function(res){
          wx.setStorageSync(app.globalData.config.storage.code,res.code);
          self.getWXUserInfo();
        },
        fail:function(res){
          console.log("user login fail")
        }
      })
    },
    getWXUserInfo() {
      var self = this;
      wx.getUserInfo({
        success: function (res) {
          wx.setStorageSync(app.globalData.config.storage.wxUser, res.userInfo);
          self.getUserUkey(res.rawData, false);
        },
        fail: function (res) {
          // 防止已授权的用户,主动在设置中关闭授权后,可以弹出授权组件
          self.open();
        }
      })
    },
    getUserUkey(rawData, close) {
      var data = {
        code: wx.getStorageSync(app.globalData.config.storage.code),
        rawData: rawData
      }
      var self = this;
      wx.request({
        url: app.globalData.config.requrl + "",
        data: data,
        method: "GET",
        success: function (res) {
          res = res.data;
          if (res.code == 1) {
            wx.setStorageSync(app.globalData.config.storage.ukey, res.result.ukey);
            wx.setStorageSync(app.globalData.config.storage.ukeytime, new Date().getTime());
            if (close) {
              self.close();
            }
          }
        },
        fail: function (res) {
          console.log(res);
        },
        complete: function () {

          // 授权完成并获取到用户数据后,给页面提供的回调
          self.triggerEvent("callback");
        }
      })
    },
    
    // 校验是否授权
    judgeAuth: function () {

      var self = this;
      this.close();
      wx.getSetting({
        success(res) {
          if (res.authSetting['scope.userInfo']) {
            // 已经授权关闭授权窗口
            // 直接调用已授权的方法
            self.close();
            self.triggerEvent("callback");
            return;
          } else {
            // 未授权去授权
            self.checkSession();
          }
        }
      })
    },

    checkSession: function () {

      var self = this;
      wx.checkSession({
        success: function (res) {
          //存在登录状态
          self.checkUkey();
        },
        fail: function () {
          //过期-重新登录
          self.login();
        }
      });
    }
  },
})
// index.wxml 用户可将授权button放在需要的地方
    <button class="auth_btn" type="default" open-type="getUserInfo" bindgetuserinfo="userauth">微信授权</button> 
(2)request.js
修改之前接口未获取到用户信息的处理,删除自动登录代码将回调直接返回.request.js在3中给出了源码,这边就不再展示了。我们修改req方法。
function req(data){
  wx.request({
    url: data.url,
    data: ukeyData(data.data),
    method: data.method, // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
    header: {
      'content-type':'application/x-www-form-urlencoded',
    }, // 设置请求的 header
    success: function(res){
      // success
      res = res.data;
      if (res.code == ResultCode.ResultCodeSucc) {
        if (typeof data.succ === "function") {
          data.succ(res);
        }
      } else if (res.code == ResultCode.ResultCodeNoAccredit) {
        // 用户登录失效,将回调直接放回
        if (typeof data.no_accredit === "function") {
          data.no_accredit(res);
        }
      } else {
        if (typeof data.error === "function") {
          data.error(res);
        }
      }
    },
    fail:function(res){
      if(typeof data.fail === "function"){
        data.fail(res);
      }
    },
    complete:function(res){
      if(typeof data.complete === "function"){
        data.complete(res);
      }
    }
  })
}
(3)在page中使用comp-auto组件
1.在onShow方法中校验授权情况,未授权展示授权组件,让用户授权
  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    this.selectComponent("#compauth").judgeAuth();
  },
2.书写授权组件回调,在此方法中书写只有授权才能调用的接口
  callback: function () {
    this.person_appointment_tap();
  }
3.修改需要授权才能使用的接口
 // 预约活动
  person_appointment_tap: function (e) {
    if (!this.data.isAppointment) {
      var self = this;
      request.post({
        url: app.globalData.config.requrl + "/Invest/Wx/subscribeMsg",
        data: {
          formid: e.detail.formId,
          activity_id: self.data.activity_id
        },
        succ: function (res) {
          // 成功
          wx.showModal({
            title: '预约成功',
            content: "我们会在活动开始前给您发送消息提醒,请您及时关注",
            showCancel: false,
            confirmColor: "#ffcf00",
            success: function (res) {
            }
          })
          self.setData({ isAppointment: true });
        },
        error: function (res) {
          // 失败
          prompt.showModal(res.code == 2 ? "您已经预约过了!" : "预约失败,请稍后再试!", null);
          if (res.code == 2) {
            self.setData({ isAppointment: true });
          }
        },
        no_accredit: function (res) {
          // 吊起授权组件登录方法,授权结束后会调用回调方法callback,再次请求该接口
        self.selectComponent("#compauth").login();
        }
      })
    }
  }
4.在index.json中添加组件
 {
  "usingComponents": {
   "compauth": "../../components/comp-auth/index"
  }
}
5.在index.wxml中添加组件
<compauth id="compauth" bind:callback="callback"></compauth>
这样优化后可以完善授权机制但是也有很大的弊端。因为微信没有顶层视图,所以导致了我们必须在每个需要授权的页面添加组件。如果再修改授权机制,每个页面都要进行维护,会产生很多的冗余代码,让项目变得难以维护。希望微信小程序之后可以考虑添加顶层视图,减少码农的痛苦。

本文将持续对授权机制的处理进行更新,谢谢大家的观看,有建议或者疑问欢迎给我留言。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 转载链接 注:本文转载知乎上的回答 作者:初雪 链接:https://www.zhihu.com/question...
    pengshuangta阅读 28,473评论 9 295
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,394评论 25 707
  • 过年回去几天都是上午帮婆婆折菜,下午找自己妈去了。打了三次牌,一次斗地主,两次打麻将。每年过年都是一家人唠嗑唠嗑,...
    苏小文S阅读 137评论 0 0
  • 记得有一句话这样说“一个人得成功,20%是个人的知识储备,80%来源于人脉”,以前对这句话没有深入的思考,随着...
    分秒早成阅读 164评论 1 1
  • 梦见琼瑶变成小姑娘,在和我讨论她的剧本
    烟涩寒阅读 79评论 0 0