Bootstrap

微信小程序登录实现

一、登录流程

        首先我们看下官方提供的登录流程图,小程序端通过wx.login()获取登录需要得登录凭证code,然后将该code传递给后端接口,配合小程序参数appId、srcret实现登录验证,验证成功后获得相关信息如:openId、session_key。后续使用openId、session_key等实现自定义的登录状态,例如生成token返回给前端。

二、代码实现
1、前端涉及接口

(1)wx.login()  获取登录凭证 code 

(2)wx.getUserInfo() 获取用户信息  encryptedData、iv

2、涉及依赖
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-miniapp</artifactId>
            <version>4.0.7.B</version>
        </dependency>
3、Controller层实现

LoginParam:

/**
 * 登陆LoginDTO
 */
@Getter
@Setter
public class LoginParam {
    /**
     * 登录凭证
     */
    @NotEmpty(message = "参数code不能为空!")
    private String code;

    /**
     * 微信登录接口返回的加密用户信息
     */
    @NotEmpty(message = "参数encryptedData不能为空!")
    private String encryptedData;

    /**
     * 加密偏移数据
     */
    @NotEmpty(message = "参数iv不能为空!")
    private String iv;
}

Controller:

@RestController
@Validated
public class AuthWxController {
    @Autowired
    private AuthWxService authWxService;

    /**
     * 微信小程序登录
     **/
    @ApiOperation("微信小程序登录")
    @PostMapping("/auth/wx/doLogin")
    public CommonResult<String> doLogin(@RequestBody @Valid LoginParam loginParam) {
        return CommonResult.data(authWxService.doLogin(loginParam));
    }
}
4、Service层实现

AuthWxService:

/**
 * 微信
 */
public interface AuthWxService {
    /**
     * 小程序登录
     *
     * @param loginParam 登录参数
     * @return
     */
    String doLogin(LoginParam loginParam);
}

AuthWxServiceImpl:

@Slf4j
@Service
public class AuthWxServiceImpl implements AuthWxService {
    @Autowired
    private TblWxUserService tblWxUserService;
    @Autowired
    private TransService transService;

    /**
     * 小程序登录
     *
     * @param loginParam 登录参数
     * @return
     */
    @Override
    public String doLogin(LoginParam loginParam) {
        log.info("微信小程序登陆入参:{}", JSONObject.toJSONString(loginParam));
        //执行登录
        WxMaUserInfo wxMaUserInfo = getWxMauserInfo(loginParam);
        if (ObjectUtil.isEmpty(wxMaUserInfo)) {
            throw new CommonException("微信登陆失败!");
        }
        log.info("微信登陆微信返回数据:{}", JSONObject.toJSONString(wxMaUserInfo));

        //处理创建用户
        TblWxUser tblWxUser = this.createWxUser(wxMaUserInfo);

        //执行自定义登录逻辑,返回token
        return "token";
    }

    private WxMaUserInfo getWxMauserInfo(LoginParam loginParam) {
        //调用微信登录
        WxMaService wxMaService = WxMaConfig.getWxMaService();
        WxMaJscode2SessionResult session = null;
        try {
            session = wxMaService.getUserService().getSessionInfo(loginParam.getCode());
            log.info("微信登录返回的session:{}", session);
        } catch (WxErrorException ex) {
            log.error("微信登录失败:{}", ExceptionUtils.getMessage(ex));
            throw new CommonException("微信登录失败!");
        }
        WxMaUserInfo wxMpUser = wxMaService.getUserService()
                .getUserInfo(session.getSessionKey(), loginParam.getEncryptedData(), loginParam.getIv());
        wxMpUser.setOpenId(session.getOpenid());
        wxMpUser.setUnionId(session.getUnionid());
        return wxMpUser;
    }

    private TblWxUser createWxUser(WxMaUserInfo wxMpUser) {
        try {
            //判断用户是否存在
            TblWxUser tblWxUser = tblWxUserService.getOne(Wrappers.<TblWxUser>lambdaQuery()
                    .eq(TblWxUser::getAccount, wxMpUser.getOpenId()), false);
            if (ObjectUtil.isEmpty(tblWxUser)) {
                //过滤掉表情
                HttpServletRequest request = CommonServletUtil.getRequest();
                String ip = CommonIpAddressUtil.getIp(request);

                TblWxUser user = new TblWxUser();
                user.setAccount(wxMpUser.getOpenId());
                user.setNickname(wxMpUser.getNickName());
                user.setAvatar(wxMpUser.getAvatarUrl());
                user.setAddIp(ip);
                user.setLastIp(ip);
                user.setUserStatus("ENABLE");

                //构建微信用户
                WechatUserDto wechatUserDTO = WechatUserDto.builder()
                        .nickname(wxMpUser.getNickName())
                        .routineOpenid(wxMpUser.getOpenId())
                        .unionId(wxMpUser.getUnionId())
                        .sex(Integer.valueOf(wxMpUser.getGender()))
                        .language(wxMpUser.getLanguage())
                        .city(wxMpUser.getCity())
                        .province(wxMpUser.getProvince())
                        .country(wxMpUser.getCountry())
                        .headimgurl(wxMpUser.getAvatarUrl())
                        .build();

                user.setWxProfile(wechatUserDTO);
                tblWxUserService.save(user);
                tblWxUser = user;
            } else {
                WechatUserDto wechatUser = tblWxUser.getWxProfile();
                if ((StrUtil.isBlank(wechatUser.getRoutineOpenid()) && StrUtil.isNotBlank(wxMpUser.getOpenId()))
                        || (StrUtil.isBlank(wechatUser.getUnionId()) && StrUtil.isNotBlank(wxMpUser.getUnionId()))) {
                    wechatUser.setRoutineOpenid(wxMpUser.getOpenId());
                    wechatUser.setUnionId(wxMpUser.getUnionId());
                    tblWxUser.setWxProfile(wechatUser);
                }
                tblWxUserService.updateById(tblWxUser);
            }
            return tblWxUser;
        } catch (Exception e) {
            log.error("微信登录失败,创建用户失败:{}", ExceptionUtils.getMessage(e));
            throw new CommonException("微信登录失败!");
        }
    }
}

WxMaConfig:

/**
 * 微信配置类
 */
@Configuration
public class WxMaConfig {
    private static final String WEI_XIN_MA_SERVICE = "wei_xin_ma_service";
    private static Map<String, WxMaService> maServices = Maps.newHashMap();

    /**
     * 登录获取WxMaService
     *
     * @return 返回参数
     */
    public static WxMaService getWxMaService() {
        CommonCacheOperator commonCacheOperator = SpringUtil.getBean(CommonCacheOperator.class);
        //获取WxMaService
        WxMaService wxMaService = maServices.get(WEI_XIN_MA_SERVICE);
        //获取缓存标识
        Object object = commonCacheOperator.get(WEI_XIN_MA_SERVICE);
        if (wxMaService == null || object == null) {
            //获取小程序appId、secret
            String appId = AuthWxUtil.getAppId();
            String secret = AuthWxUtil.getSecret();

            WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
            config.setMsgDataFormat("JSON");
            config.setAppid(appId);
            config.setSecret(secret);
            wxMaService = new WxMaServiceImpl();
            wxMaService.setWxMaConfig(config);
            maServices.put(WEI_XIN_MA_SERVICE, wxMaService);
            //增加标识
            commonCacheOperator.put(WEI_XIN_MA_SERVICE, "weixin");
        }
        return wxMaService;
    }
}
三、涉及用户表
create table tbl_wx_user
(
    id               bigint unsigned auto_increment comment '用户id'
        primary key,
    account          varchar(255) charset utf8mb4             null comment '用户账户(OPENID)',
    password          varchar(255)                             null comment '用户密码(跟pwd)',
    name         varchar(25) charset utf8mb4 default ''   null comment '真实姓名',
    birthday          int                         default 0    null comment '生日',
    id_card_number           varchar(20)                 default ''   null comment '身份证号码',
    mark              varchar(255)                default ''   null comment '用户备注',
    nickname          varchar(1000) charset utf8mb4            null comment '用户昵称',
    avatar            varchar(256)                             null comment '用户头像',
    phone             varchar(15)                              null comment '手机号码',
    add_ip            varchar(16)                 default ''   null comment '添加ip',
    create_time       timestamp                                null comment '添加时间',
    update_time       timestamp                                null comment '最后一次登录时间',
    last_ip           varchar(16)                 default ''   null comment '最后一次登录ip',
    wx_profile        json                                     null comment '微信用户json信息',
    is_del            tinyint(1)                  default 0    not null,
    black_list        tinyint(1) unsigned         default 0    not null comment '黑名单(0-否,1-是)',
    user_status       varchar(20)         default 'ENABLE'    not null comment '用户状态ENABLE DISABLED',
    constraint account
        unique (account)
)
    comment '用户表' charset = utf8
                     row_format = DYNAMIC;
四、总结

以上代码使用了三方依赖,免去了服务端自己请求微信登录验证API的步骤,总体实现还是比较容易的,如有不足之处,烦请指正。

;