1. なぜ WeChat 認証ログインを使用するのですか?
1. メリット
- 便利で速い: WeChat 認証ログインにより、ユーザーは自分の WeChat アカウントを使用してミニ プログラムに簡単にログインできるため、面倒な登録プロセスが不要になり、ユーザーのログイン エクスペリエンスが向上します。
- ユーザーの信頼: WeChat は広く使用されているソーシャル プラットフォームの 1 つであり、ユーザーの心にはある程度の信頼があり、WeChat を使用してログインを認証することで、ユーザーはミニ プログラムを受け入れ、信頼しやすくなります。
- ユーザー情報の取得:WeChat認証でログインすると、ミニプログラムはニックネーム、アバター、性別などのユーザーの基本情報を取得し、パーソナライズされたユーザーエクスペリエンスを促進し、より正確なサービスを提供できます。
2. デメリット
- ユーザー選択制限: WeChat ユーザーのみがログインして WeChat によって許可されたミニ プログラムを使用できるため、他の非 WeChat ユーザーによる使用の可能性が制限されます。
- プライバシーの問題: WeChat を使用してログインを承認するには、ユーザーの基本情報を取得する必要があるため、ユーザーにプライバシー上の懸念が生じる可能性があります。ミニ プログラムに適切なプライバシー ポリシーとデータ保護メカニズムが備わっていない場合、ユーザーはログインを承認したがらない可能性があります。
- プラットフォームの依存関係: ミニ プログラムは WeChat プラットフォームに依存しているため、WeChat プラットフォームに障害が発生したり制限されたりすると、ミニ プログラムの通常の動作に影響が出る可能性があります。
2. ミニプログラムへのログイン
1. ログイン処理のタイミング
理解するには、公式 Web サイトのオープン機能/ユーザー情報/ミニ プログラム ログイン (qq.com)を参照してください。
1.1. 説明
- wx.login()を呼び出して一時的なログイン認証コードを取得し 、それを開発者サーバーに送り返します。
- ユーザーの一意の識別子 OpenID 、 WeChat オープン プラットフォーム アカウントにあるユーザーの一意の識別子 UnionID (現在のミニ プログラムが WeChat オープン プラットフォーム アカウントにバインドされている場合)、およびセッション キー session_keyと引き換えにauth.code2Sessionインターフェイスを 呼び出します。
開発者サーバーは、ユーザー ID に基づいてカスタム ログイン状態を生成できます。これは、後続のビジネス ロジックでフロントエンドおよびバックエンドと対話するときにユーザーの ID を識別するために使用できます。
2、wx.ログイン
wx.login は、ユーザーのログイン資格情報コードを取得するために使用されるインターフェイスです。wx.login を呼び出すことによって、ユーザーの一時的なログイン資格情報コードを取得できます。このコードは、その後のユーザーの一意の識別子 openid とセッション キー session_key の交換で使用され、ユーザーログイン機能。
wxml
<!--pages/index/index.wxml-->
<view>
<button type="primary" class="wx-login-btn" bindgetuserinfo="wxLogin">wx.login>微信直接登录</button>
<image mode="scaleToFill" src="{
{userInfo.avatarUrl}}" />
<text>昵称:{
{userInfo.nickName}}</text>
</view>
js
// pages/index/index.js
Page({
data: {
userInfo: {}
},
onLoad() {
}
wxLogin: function(e) {
debugger
console.log('wxLogin')
console.log(e.detail.userInfo);
this.setData({
userInfo: e.detail.userInfo
})
if (e.detail.userInfo == undefined) {
app.globalData.hasLogin = false;
util.showErrorToast('微信登录失败');
return;
}
}
})
3、wx.getUserProfile
wx.getUserProfileはユーザーの個人情報を取得するためのインターフェースであり、wx.getUserProfileを呼び出すことでユーザーのニックネーム、アバター、性別などの個人情報を取得することができます。
wxml
<!--pages/index/index.wxml-->
<view>
<button type="primary" class="wx-login-btn" bindtap="getUserProfile">wx.getUserProfile>微信直接登录</button>
<image mode="scaleToFill" src="{
{userInfo.avatarUrl}}" />
<text>昵称:{
{userInfo.nickName}}</text>
</view>
js
// pages/index/index.js
Page({
data: {
userInfo: {}
},
wxLogin: function(e) {
debugger
console.log('wxLogin')
console.log(e.detail.userInfo);
this.setData({
userInfo: e.detail.userInfo
})
if (e.detail.userInfo == undefined) {
app.globalData.hasLogin = false;
util.showErrorToast('微信登录失败');
return;
}
}
})
4. 違い
wx.login と wx.getUserProfile は、ユーザーのログインとユーザー情報の取得のための WeChat アプレットの 2 つのインターフェイスです。wx.login はユーザーのログイン機能の実装に使用され、wx.getUserProfile はユーザーの個人情報の取得に使用されます。この 2 つは、wx.login を呼び出してユーザーのログイン認証コードを取得し、次に wx.getUserProfile を呼び出してユーザーの個人情報を取得することにより、併用されることが多く、これにより、より完全なユーザー認証と情報収集が実現されます。
それらには次のような違いがあります。
さまざまな機能:
- wx.login: wx.login は、ユーザーのログイン資格情報コードを取得するために使用されるインターフェイスです。wx.login を呼び出すことで、ユーザーの一時的なログイン資格情報コードを取得できます。このコードは、その後のユーザーの一意の識別子 openid とセッション キーの交換で使用されます。 session_key はユーザーのログイン機能を実現します。
- wx.getUserProfile: wx.getUserProfileはユーザーの個人情報を取得するためのインターフェースであり、wx.getUserProfileを呼び出すことでユーザーのニックネーム、アバター、性別などの個人情報を取得することができます。
呼び出し方法は異なります。
- wx.login: wx.login は、基本ライブラリによって提供されるネイティブ インターフェイスであり、直接呼び出すことができます。
- wx.getUserProfile: wx.getUserProfile は、基本ライブラリのバージョン 2.10.4 で導入されました。対応する権限は、ミニ プログラムのバックグラウンド設定で有効にする必要があり、ユーザーの個人情報は、ユーザーがそれを許可した後にのみ取得できます。初めて。
戻り値の型は異なります。
- wx.login: wx.login インターフェースは、ユーザーのログイン認証コードを含むオブジェクトを返します。
- wx.getUserProfile: wx.getUserProfile インターフェースは、ニックネーム、アバター、性別などを含むユーザーの個人情報を含むオブジェクトを返します。
3. ケース
1. バックエンド
ユーザーがログインしたかどうかを判断するために使用されます
package com.zking.ssm.wxcontroller; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import java.util.Random; import com.zking.ssm.annotation.LoginUser; import com.zking.ssm.model.WxUser; import com.zking.ssm.service.UserTokenManager; import com.zking.ssm.service.WxUserService; import com.zking.ssm.util.ResponseUtil; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import com.alibaba.fastjson.JSONObject; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; /** * 用户服务 */ @Slf4j @RestController @RequestMapping("/wx/user") @Validated public class WxUserController { @Autowired private WxUserService userService; /** * 用户个人页面数据 * <p> * @param userId * 用户ID * @return 用户个人页面数据 */ @GetMapping("index") public Object list(@LoginUser Integer userId, @RequestHeader("X-OA-token") String token) { log.info("【请求开始】用户个人页面数据,请求参数,userId:{}", userId); log.info("【请求开始】用户个人页面数据,请求参数,token:{}", token); if (userId == null) { log.error("用户个人页面数据查询失败:用户未登录!!!"); return ResponseUtil.unlogin(); } WxUser wxUser = userService.selectByPrimaryKey(userId); Map<Object, Object> data = new HashMap<Object, Object>(); data.put("metting_pubs", wxUser.getUserLevel()); data.put("metting_joins",wxUser.getUserLevel()); return ResponseUtil.ok(data); } }
関数の実装に使用されるメソッド
package com.zking.ssm.wxcontroller; /** * @Autho donkee * @Since 2022/6/27 */ import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; import com.alibaba.fastjson.JSONObject; import com.zking.ssm.annotation.LoginUser; import com.zking.ssm.model.UserInfo; import com.zking.ssm.model.WxLoginInfo; import com.zking.ssm.model.WxUser; import com.zking.ssm.service.UserToken; import com.zking.ssm.service.UserTokenManager; import com.zking.ssm.service.WxUserService; import com.zking.ssm.util.JacksonUtil; import com.zking.ssm.util.ResponseUtil; import com.zking.ssm.util.UserTypeEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; import javax.servlet.http.HttpServletRequest; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 鉴权服务 */ @Slf4j @RestController @RequestMapping("/wx/auth") public class WxAuthController { @Autowired private WxMaService wxService; @Autowired private WxUserService userService; /** * 微信登录 * * @param wxLoginInfo * 请求内容,{ code: xxx, userInfo: xxx } * @param request * 请求对象 * @return 登录结果 */ @PostMapping("login_by_weixin") public Object loginByWeixin(@RequestBody WxLoginInfo wxLoginInfo, HttpServletRequest request) { //客户端需携带code与userInfo信息 String code = wxLoginInfo.getCode(); UserInfo userInfo = wxLoginInfo.getUserInfo(); if (code == null || userInfo == null) { return ResponseUtil.badArgument(); } //调用微信sdk获取openId及sessionKey String sessionKey = null; String openId = null; try { long beginTime = System.currentTimeMillis(); // WxMaJscode2SessionResult result = this.wxService.getUserService().getSessionInfo(code); // Thread.sleep(6000); long endTime = System.currentTimeMillis(); log.info("响应时间:{}",(endTime-beginTime)); sessionKey = result.getSessionKey();//session id openId = result.getOpenid();//用户唯一标识 OpenID } catch (Exception e) { e.printStackTrace(); } if (sessionKey == null || openId == null) { log.error("微信登录,调用官方接口失败:{}", code); return ResponseUtil.fail(); }else{ log.info("openId={},sessionKey={}",openId,sessionKey); } //根据openId查询wx_user表 //如果不存在,初始化wx_user,并保存到数据库中 //如果存在,更新最后登录时间 WxUser user = userService.queryByOid(openId); if (user == null) { user = new WxUser(); user.setUsername(openId); user.setPassword(openId); user.setWeixinOpenid(openId); user.setAvatar(userInfo.getAvatarUrl()); user.setNickname(userInfo.getNickName()); user.setGender(userInfo.getGender()); user.setUserLevel((byte) 0); user.setStatus((byte) 0); user.setLastLoginTime(new Date()); user.setLastLoginIp(IpUtil.client(request)); user.setShareUserId(1); userService.add(user); } else { user.setLastLoginTime(new Date()); user.setLastLoginIp(IpUtil.client(request)); if (userService.updateById(user) == 0) { log.error("修改失败:{}", user); return ResponseUtil.updatedDataFailed(); } } // token UserToken userToken = null; try { userToken = UserTokenManager.generateToken(user.getId()); } catch (Exception e) { log.error("微信登录失败,生成token失败:{}", user.getId()); e.printStackTrace(); return ResponseUtil.fail(); } userToken.setSessionKey(sessionKey); log.info("SessionKey={}",UserTokenManager.getSessionKey(user.getId())); Map<Object, Object> result = new HashMap<Object, Object>(); result.put("token", userToken.getToken()); result.put("tokenExpire", userToken.getExpireTime().toString()); userInfo.setUserId(user.getId()); if (!StringUtils.isEmpty(user.getMobile())) {// 手机号存在则设置 userInfo.setPhone(user.getMobile()); } try { DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); String registerDate = df.format(user.getAddTime() != null ? user.getAddTime() : new Date()); userInfo.setRegisterDate(registerDate); userInfo.setStatus(user.getStatus()); userInfo.setUserLevel(user.getUserLevel());// 用户层级 userInfo.setUserLevelDesc(UserTypeEnum.getInstance(user.getUserLevel()).getDesc());// 用户层级描述 } catch (Exception e) { log.error("微信登录:设置用户指定信息出错:"+e.getMessage()); e.printStackTrace(); } result.put("userInfo", userInfo); log.info("【请求结束】微信登录,响应结果:{}", JSONObject.toJSONString(result)); return ResponseUtil.ok(result); } /** * 绑定手机号码 * * @param userId * @param body * @return */ @PostMapping("bindPhone") public Object bindPhone(@LoginUser Integer userId, @RequestBody String body) { log.info("【请求开始】绑定手机号码,请求参数,body:{}", body); String sessionKey = UserTokenManager.getSessionKey(userId); String encryptedData = JacksonUtil.parseString(body, "encryptedData"); String iv = JacksonUtil.parseString(body, "iv"); WxMaPhoneNumberInfo phoneNumberInfo = null; try { phoneNumberInfo = this.wxService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv); } catch (Exception e) { log.error("绑定手机号码失败,获取微信绑定的手机号码出错:{}", body); e.printStackTrace(); return ResponseUtil.fail(); } String phone = phoneNumberInfo.getPhoneNumber(); WxUser user = userService.selectByPrimaryKey(userId); user.setMobile(phone); if (userService.updateById(user) == 0) { log.error("绑定手机号码,更新用户信息出错,id:{}", user.getId()); return ResponseUtil.updatedDataFailed(); } Map<Object, Object> data = new HashMap<Object, Object>(); data.put("phone", phone); log.info("【请求结束】绑定手机号码,响应结果:{}", JSONObject.toJSONString(data)); return ResponseUtil.ok(data); } /** * 注销登录 */ @PostMapping("logout") public Object logout(@LoginUser Integer userId) { log.info("【请求开始】注销登录,请求参数,userId:{}", userId); if (userId == null) { return ResponseUtil.unlogin(); } try { UserTokenManager.removeToken(userId); } catch (Exception e) { log.error("注销登录出错:userId:{}", userId); e.printStackTrace(); return ResponseUtil.fail(); } log.info("【请求结束】注销登录成功!"); return ResponseUtil.ok(); } }
2. フロントエンド
正確な内容については、 WeChat オープンドキュメントの公式ドキュメントを参照してください。
wx.getUserProfileログイン メソッドがバックエンドに送信され、秘密キーが操作と保存に使用されます。
var util = require('../../../utils/util.js'); var user = require('../../../utils/user.js'); const app = getApp(); getUserProfile(e) { // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认 // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗 wx.getUserProfile({ desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写 success: (res) => { //console.log(res); // debugger user.checkLogin().catch(() => { user.loginByWeixin(res.userInfo).then(res => { app.globalData.hasLogin = true; // debugger wx.navigateBack({ delta: 1 }) }).catch((err) => { app.globalData.hasLogin = false; if(err.errMsg=="request:fail timeout"){ util.showErrorToast('微信登录超时'); }else{ util.showErrorToast('微信登录失败'); } this.setData({ lock:false }) }); }); }, fail: (res) => { app.globalData.hasLogin = false; console.log(res); util.showErrorToast('微信登录失败'); } }); }, onLoad: function(options) { // 页面初始化 options为页面跳转所带来的参数 // 页面渲染完成 if (wx.getUserProfile) { this.setData({ canIUseGetUserProfile: true }) } //console.log('login.onLoad.canIUseGetUserProfile='+this.data.canIUseGetUserProfile) }
完全なコード
// pages/auth/login/login.js var util = require('../../../utils/util.js'); var user = require('../../../utils/user.js'); const app = getApp(); Page({ /** * 页面的初始数据 */ data: { canIUseGetUserProfile: false, // 用于向前兼容 lock:false }, onLoad: function(options) { // 页面初始化 options为页面跳转所带来的参数 // 页面渲染完成 if (wx.getUserProfile) { this.setData({ canIUseGetUserProfile: true }) } //console.log('login.onLoad.canIUseGetUserProfile='+this.data.canIUseGetUserProfile) }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady() { }, /** * 生命周期函数--监听页面显示 */ onShow() { }, getUserProfile(e) { // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认 // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗 wx.getUserProfile({ desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写 success: (res) => { //console.log(res); // debugger user.checkLogin().catch(() => { user.loginByWeixin(res.userInfo).then(res => { app.globalData.hasLogin = true; // debugger wx.navigateBack({ delta: 1 }) }).catch((err) => { app.globalData.hasLogin = false; if(err.errMsg=="request:fail timeout"){ util.showErrorToast('微信登录超时'); }else{ util.showErrorToast('微信登录失败'); } this.setData({ lock:false }) }); }); }, fail: (res) => { app.globalData.hasLogin = false; console.log(res); util.showErrorToast('微信登录失败'); } }); }, wxLogin: function(e) { if (e.detail.userInfo == undefined) { app.globalData.hasLogin = false; util.showErrorToast('微信登录失败'); return; } user.checkLogin().catch(() => { user.loginByWeixin(e.detail.userInfo).then(res => { app.globalData.hasLogin = true; wx.navigateBack({ delta: 1 }) }).catch((err) => { app.globalData.hasLogin = false; if(err.errMsg=="request:fail timeout"){ util.showErrorToast('微信登录超时'); }else{ util.showErrorToast('微信登录失败'); } }); }); }, accountLogin() { console.log('开发中....') } })
ユーザーは情報を取得します
ページ
<!--pages/ucenter/user/user.wxml--> <form bindsubmit="formSubmit"> <view class='personal-data'> <view class='list'> <view class='item acea-row row-between-wrapper'> <view>头像</view> <view class='pictrue'> <image src='{ {userInfo.avatarUrl}}'></image> </view> </view> <view class='item acea-row row-between-wrapper'> <view>名字</view> <view class='input'><input type='text' disabled='true' name='nickname' value='{ {userInfo.nickName}}'></input></view> </view> <view class='item acea-row row-between-wrapper'> <view>手机号码</view> <button name='phone' class='phone' value='{ {userInfo.phone}}' wx:if="{ {!userInfo.phone}}" bindgetphonenumber="getPhoneNumber" hover-class='none' open-type='getPhoneNumber'> 点击获取 </button> <view class='input acea-row row-between-wrapper' wx:else> <input type='text' disabled='true' name='phone' value='{ {userInfo.phone}}' class='id'></input> <text class='iconfont icon-suozi'></text> </view> </view> <view class='item acea-row row-between-wrapper'> <view>ID号</view> <view class='input acea-row row-between-wrapper'> <input type='text' value='1000{ {userInfo.userId}}' disabled='true' class='id'></input> <text class='iconfont icon-suozi'></text> </view> </view> </view> <button class='modifyBnt' bindtap="exitLogin">退 出</button> </view> </form>
ユーザー情報を取得する
onShow: function () { let that = this; //获取用户的登录信息 let userInfo = wx.getStorageSync('userInfo'); this.setData({ userInfo: userInfo, hasLogin: true }); }
完全なコード
var util = require('../../../utils/util.js'); var api = require('../../../config/api.js'); var user = require('../../../utils/user.js'); var app = getApp(); Page({ /** * 页面的初始数据 */ data: { userInfo: {}, hasLogin: false, userSharedUrl: '' }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { }, onShow: function () { let that = this; //获取用户的登录信息 let userInfo = wx.getStorageSync('userInfo'); this.setData({ userInfo: userInfo, hasLogin: true }); }, getPhoneNumber: function (e) { let that = this; if (e.detail.errMsg !== "getPhoneNumber:ok") { // 拒绝授权 return; } if (!this.data.hasLogin) { wx.showToast({ title: '绑定失败:请先登录', icon: 'none', duration: 2000 }); return; } util.request(api.AuthBindPhone, { iv: e.detail.iv, encryptedData: e.detail.encryptedData }, 'POST').then(function (res) { if (res.errno === 0) { let userInfo = wx.getStorageSync('userInfo'); userInfo.phone = res.data.phone;//设置手机号码 wx.setStorageSync('userInfo', userInfo); that.setData({ userInfo: userInfo, hasLogin: true }); wx.showToast({ title: '绑定手机号码成功', icon: 'success', duration: 2000 }); } }); }, exitLogin: function () { wx.showModal({ title: '', confirmColor: '#b4282d', content: '退出登录?', success: function (res) { if (!res.confirm) { return; } util.request(api.AuthLogout, {}, 'POST'); app.globalData.hasLogin = false; wx.removeStorageSync('token'); wx.removeStorageSync('userInfo'); wx.reLaunch({ url: '/pages/index/index' }); } }) } })
4. ストレージの問題
1、絵文字
MySQL の UTF8 でエンコードされた文字は最大 3 バイトですが、絵文字表現は 4 バイトであるため、UTF8 は絵文字表現の保存をサポートしていません。ただし、utf8 のスーパーセットである utf8mb4 は 1文字あたり最大 4 バイトを持つことができるため、絵文字表現の保存をサポートできます。
Linux システムにおける MySQL の設定ファイルは my.cnf です。
Winnows の構成ファイルはmy.iniです。
[mysql] # 设置mysql客户端默认字符集 default-character-set=utf8mb4 [mysqld] #设置3306端口 port = 3306 # 设置mysql的安装目录 basedir=D:\\tools\\mysql-5.7.23-winx64 # 设置mysql数据库的数据的存放目录 datadir=D:\\tools\\mysql-5.7.23-winx64\\data # 允许最大连接数 max_connections=200 # 服务端使用的字符集默认为8比特编码的latin1字符集 character-set-server=utf8mb4 # 创建新表时将使用的默认存储引擎 default-storage-engine=INNODB