小程序——登录与注册设计

本篇文档是笔者对之前开发过的小程序的总结,这个小程序是笔者的毕业之作,自然是尽心尽力了,笔者开发的小程序名叫——爱易园(在微信上可搜索到),是一款校园二手交易APP,前端代码已开源,有兴趣的欢迎Star~
项目地址:GitHub

登录流程

下面这张时序图来源于小程序官网,笔者小程序登录流程大致按照这个流程来设计。

但是作为一款产品,用户登录当然要后端留痕,所以在第一次取得用户信息之后,就保存在数据库,该用户以后每次登录时,都会根据拿到的openId判断用户是否已经注册,如果未登录,就直接注册,否则直接用数据库取出用户信息返回,后端在拿到code数据之后,处理流程图如下图所示:

代码实现

小程序端可以通过微信提供的wx.login接口,拿到临时登录凭证code,再调用 wx.getUserInfo接口,拿到用户的基本信息,然后传向开发服务器的login接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
wx.login({
success: function(res) {
var app = getApp();
if (res.code) {
var jsCode = res.code;
wx.getUserInfo({
success: res => {
// this.globalData.userInfo = res.userInfo;
console.log("res.code " + jsCode)
wx.request({
// 向开发服务器发请求
url: app.globalData.domain.dev + 'people/login/',
method: 'GET',
// dataType: 'json',
data: {
code: jsCode,
userInfo: res.userInfo
},
success: function(res) {
var app = getApp();
var _data = res.data.data;
// _data即为用户信息
}
});
}
});
}
}
})

开发服务器在拿到jscode后,会调用微信的code2Session接口,拿到openId和session_key,session_key。session_key是微信派发给我们的,当前用户进行会话操作的有效性,简单来说就是维护小程序用户的登录态。
如下面代码所示,login接口在用户登录的时候,自动为用户注册了,这样有效提高用户体验,在我浏览过的大多小程序中,大多采用这样的设计。需要注意的是,这样设计的前提,用户需要允许小程序获取他的基本信息,如果用户拒绝授权,小程序可能会无法使用,当然可以重新拉起授权窗口,详情见关于获取用户信息方案介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@RequestMapping(value = "login/", method = RequestMethod.GET)
public NapiRespWrapper<UserVO> login(
@RequestParam(value = "code") String code,
@RequestParam String userInfo) {
if (StringUtils.isEmpty(code) || StringUtils.isEmpty(userInfo)) {
return new NapiRespWrapper<>(NapiRespStatus.INVALID_PARAM, "非法参数");
}
// 调用code2Session 接口,拿到用户唯一标识 OpenID 和 会话密钥 session_key。
WxSessionDTO wxSessionDTO = personService.doRequest(code);
if (wxSessionDTO == null) {
return new NapiRespWrapper<>(NapiRespStatus.INVALID_PARAM, "未取到Open ID");
}

UserInfoDTO userInfoDTO = personService.mapperUserInfo(userInfo);
// 生成session 保存到 Redis
String sessionId = generateSessionId();
personService.saveSession(sessionId, wxSessionDTO);

if (! personService.isRegsiter(wxSessionDTO.getOpenid())) {
personService.insert(userInfoDTO, wxSessionDTO.getOpenid());
}
// 通过openId拿到userId
Integer userId = personService.findUserIdByOpenId(wxSessionDTO.getOpenid());
// 根据userId拿到用户信息
UserVO user = personService.findUserDetailByUid(userId);
return new NapiRespWrapper<>(user);
}

public WxSessionDTO doRequest(String jsCode) {
Map<String, String> params = new HashMap<>();
params.put("appid", APP_ID);
params.put("secret", APP_SECRET);
params.put("js_code", jsCode);
params.put("grant_type", AUTHORIZATION_CODE);
// API_URL:https://api.weixin.qq.com/sns/jscode2session
String response = HttpUtil.doGet(API_URL, params);
if (response == null) {
return null;
}
try {
return objectMapper.readValue(response, WxSessionDTO.class);
} catch (Exception e) {
return null;
}
}

存储设计

笔者在实现存储时考虑到对APP端兼容,所以设计了两张用户表分别是auth_userauth_user_proflie:

  • auth_user:用户基本信息表,包含uid、email、phonenumber等基本信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    `auth_user` (
    `id` int(11) NOT NULL AUTO_INCREMENT, // user id
    `open_id` varchar(50) DEFAULT NULL,
    `union_id` varchar(50) DEFAULT NULL,
    `telephone` varchar(16) DEFAULT NULL,
    `email` varchar(50) DEFAULT NULL,
    `username` varchar(50) DEFAULT NULL, // 如果从微信获取,也是就是微信昵称
    `password` varchar(50) DEFAULT NULL, // 加过加密后的密码
    `status` int(11) DEFAULT '0', // 用户状态,0正常、6屏蔽、11删除
    `is_auth` tinyint(11) NOT NULL DEFAULT '0', // 是否是认证用户
    `register_source` tinyint(11) NOT NULL DEFAULT '0', // 第三方注册平台,0平台、1微信小程序
    `creat_datetime` datetime NOT NULL, // 记录创建
    `gmt_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
  • auth_user_proflie:用户信息表(包含从第三方平台获取的信息)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    `auth_user_profile` (
    `id` int(11) NOT NULL,
    `nickname` varchar(50) DEFAULT NULL,[](http://)
    `avatar_url` varchar(255) DEFAULT NULL,
    `gender` char(2) DEFAULT NULL,
    `integral` int(11) DEFAULT '0',
    `city` varchar(10) DEFAULT NULL,
    `province` varchar(10) DEFAULT NULL,
    `country` varchar(12) DEFAULT NULL,
    `language` varchar(10) DEFAULT NULL,
    `school` varchar(12) DEFAULT NULL,
    `ship_address` char(50) DEFAULT NULL,
    PRIMARY KEY (`id`)
    )

密码如何加密

用户密码不会明文存于数据库中,一般会将它哈希加密的后的字符串存于数据库中,关于数据库加密可以参考加盐密码哈希:如何正确使用

参考

用户登录注册相关设计