Commit 18f93b0a authored by 技术部-任文超's avatar 技术部-任文超

重构微信关联:

1、关闭独立的关联请求接口;
2、创建微信关联、解绑、冲突用户下线事务
3、捆绑该关联动作到用户账密登录接口和快速登录接口
parent 75ca7b4c
......@@ -28,6 +28,8 @@ public interface Constants {
String X_AUTH_TOKEN = "x-auth-token";
/** 登录账号/手机号参数名 */
String PHONE_NO = "phoneNo";
/** 微信标识参数名 */
String OPEN_ID = "openId";
// -- Start -- IPV4安全策略常量组
/** 账密不匹配错误 - 按账号计数 */
......
......@@ -3,6 +3,7 @@ package cn.quantgroup.xyqb.controller;
import cn.quantgroup.xyqb.exception.PasswordErrorLimitException;
import cn.quantgroup.xyqb.exception.UserNotExistException;
import cn.quantgroup.xyqb.exception.VerificationCodeErrorException;
import cn.quantgroup.xyqb.exception.WechatRelateUserException;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.util.IPUtil;
import org.slf4j.Logger;
......@@ -63,6 +64,18 @@ public class ExceptionHandlingController implements IBaseController {
return new JsonResult(unee.getMessage(), 401L, null);
}
/**
* 微信关联异常
* @param wrue
* @return
*/
@ExceptionHandler(WechatRelateUserException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public JsonResult wechatRelateUserException(WechatRelateUserException wrue) {
LOGGER.info("throw WechatRelateUserException,msg={},businessCode={},code={}", wrue.getMessage(), 1L, 401L);
return new JsonResult(wrue.getMessage(), 401L, null);
}
/**
* 其他全局异常
* @param e
......
......@@ -27,6 +27,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
......@@ -206,6 +208,9 @@ public class WeChatController implements IBaseController {
* @param response
*/
private void receiveCodeWithDefault(String code, String systemKey, String schema, Long registerFrom, String redirect, HttpServletResponse response){
// 微信跳转请求入参监控
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
LOGGER.info("[WeChatController][receiveCodeWithDefault]微信授权及跳转:code:{},systemKey:{},schema:{},registerFrom:{},redirect:{},request:{},response:{}", code, systemKey, schema, registerFrom, redirect, request, response);
/*
* 预处理(容错)
*/
......@@ -220,6 +225,7 @@ public class WeChatController implements IBaseController {
// 从code获取token
Merchant merchant = merchantService.findMerchantByName(systemKey);
AccessTokenResponse token = wechatService.getToken(code);
LOGGER.info("[WeChatController][receiveCodeWithDefault]微信授权及跳转:merchant:{},token:{}", merchant, token);
if (token == null) {
// 让用户登录,不关联微信, 构造不关联微信的url
redirectNormalUrl(response, merchant, registerFrom,domain);
......@@ -228,12 +234,14 @@ public class WeChatController implements IBaseController {
WechatUserInfo userInfo =
wechatService.getWechatUserInfoFromWechatServer(token.getAccessToken(),
token.getOpenId());
LOGGER.info("[WeChatController][receiveCodeWithDefault]微信授权及跳转:WechatUserInfo - from wechat api:{}", userInfo);
if (userInfo == null || StringUtils.isEmpty(userInfo.getOpenId())) {
// 让用户登录,不关联微信, 构造不关联微信的url
redirectNormalUrl(response, merchant, registerFrom, domain);
return;
}
WechatUserInfo userInfoInDb = wechatService.findWechatUserInfoFromDb(userInfo.getOpenId());
LOGGER.info("[WeChatController][receiveCodeWithDefault]微信授权及跳转:WechatUserInfo - from DB:{}", userInfo);
// welcome 首次登录
if (userInfoInDb == null) {
// 微信用户首次登录界面, 首先保存userInfo, 跳入到微信注册登录界面
......@@ -287,6 +295,7 @@ public class WeChatController implements IBaseController {
}
private String createUserSession(User user, Merchant merchant, String redirect, String domain, Long registerFrom) {
LOGGER.info("[WeChatController][createUserSession]微信授权及跳转:user:{},merchant:{},redirect:{},domain:{},registerFrom:{}",user, merchant, redirect, domain, registerFrom);
if (StringUtils.isEmpty(redirect) || "redirect".equals(redirect)) {
LOGGER.info("微信登录:redirect为null,走正常流程.");
if ("baitiao".equals(merchant.getName())) {
......@@ -311,19 +320,20 @@ public class WeChatController implements IBaseController {
private String loginInWechatWithSessionCreated(User user, Merchant merchant, String target, Long channelId, String domain, Long registerFrom) {
AuthBean authBean = sessionService.createSession(channelId, registerFrom, "", user, merchant);
LOGGER.info("[WeChatController][loginInWechatWithSessionCreated]微信授权及跳转:user:{},merchant:{},target:{},channelId:{},domain:{},registerFrom:{}",user, merchant, target, channelId, domain, registerFrom);
return domain + "/landing?token=" + authBean.getToken() + "&registerFrom=" + registerFrom + "&channelId=" + channelId + "&key=" + merchant.getName() + "&target=" + target;
}
private void redirectWechatLoginUrlWithoutLogin(HttpServletResponse response, Merchant merchant, WechatUserInfo userInfo, Long registerFrom,String domain) {
String redirectUrl = assembleWechatRedirectUrl(merchant, userInfo, registerFrom,domain);
LOGGER.info("redirectWechatLoginUrlWithoutLogin redirectUrl:[{}]",redirectUrl);
LOGGER.info("[WeChatController][redirectWechatLoginUrlWithoutLogin]微信授权及跳转:redirectUrl:[{}]",redirectUrl);
response.setHeader("Location", redirectUrl);
response.setStatus(301);
}
private void redirectNormalUrl(HttpServletResponse response, Merchant merchant, Long registerFrom,String domain) {
String redirectUrl = assembleNormalRedirectUrl(merchant, registerFrom,domain);
LOGGER.info("redirectNormalUrl redirectUrl:[{}]",redirectUrl);
LOGGER.info("[WeChatController][redirectNormalUrl]微信授权及跳转: redirectUrl:[{}]",redirectUrl);
response.setHeader("Location", redirectUrl);
response.setStatus(301);
}
......
package cn.quantgroup.xyqb.exception;
/**
* 微信关联用户异常
* @author 任文超
* @time 2018-04-03
*/
public class WechatRelateUserException extends RuntimeException {
private static final long serialVersionUID = -1L;
public WechatRelateUserException(String msg, Throwable t) {
super(msg, t);
}
public WechatRelateUserException(String msg) {
super(msg);
}
}
......@@ -7,6 +7,8 @@ import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.transaction.annotation.Propagation.MANDATORY;
/**
* Created by 11 on 2017/1/18.
*/
......@@ -23,8 +25,27 @@ public interface IWeChatUserRepository extends JpaRepository<WechatUserInfo, Lon
@Modifying
int removeByUserId(Long userId);
/**
* 关联用户
* @param userId - 用户标识
* @param phoneNo
* @param openId - 微信标识
* @return 记录更新行数
*/
@Transactional
@Modifying
@Query(value = "update wechat_userinfo set user_id=?1,phone_no=?2 where open_id=?3 and user_id is null", nativeQuery = true)
int bindingUser(Long userId, String phoneNo, String openId);
int relateUser(Long userId, String phoneNo, String openId);
/**
* 解除关联关系 -- 包括:1、当前微信旧的关联用户;2、当前用户旧的关联微信
* 强制事务
* @param openId - 微信标识
* @param userId - 用户标识
* @return 记录更新行数
*/
@Transactional(propagation = MANDATORY)
@Modifying
@Query(value = "update wechat_userinfo set user_id=null,phone_no='*' where open_id=?1 or user_id=?2", nativeQuery = true)
int dissociateUser(String openId, Long userId);
}
......@@ -12,7 +12,18 @@ import cn.quantgroup.xyqb.model.session.SessionValue;
*/
public interface ISessionService {
/**
* 更新session
* 用户信息存在,更新session中的最后访问时间,重新写入缓存.
* 存在则保存用户session信息,userId为uuid
*
* @param channelId
* @param createdFrom
* @param appChannel
* @param user
* @param merchant
* @return
*/
AuthBean createSession(Long channelId, Long createdFrom, String appChannel, User user, Merchant merchant);
SessionStruct createSessionAndPersist(User user, LoginProperties loginProperties);
......
......@@ -44,6 +44,18 @@ public class SessionServiceImpl implements ISessionService {
@Autowired
private IUserService userService;
/**
* 更新session
* 用户信息存在,更新session中的最后访问时间,重新写入缓存.
* 存在则保存用户session信息,userId为uuid
*
* @param channelId
* @param createdFrom
* @param appChannel
* @param user
* @param merchant
* @return
*/
@Override
public AuthBean createSession(Long channelId, Long createdFrom, String appChannel, User user, Merchant merchant) {
AuthBean authBean = new AuthBean();
......
......@@ -17,8 +17,21 @@ public interface IWechatService {
WechatUserInfo saveWechatUserInfo(WechatUserInfo userInfo);
int bindingUser(Long userId, String phoneNo, String openId);
/**
* 关联用户和微信
* @param userId - 用户标识
* @param phoneNo
* @param openId - 微信标识
* @return
*/
int relateUser(Long userId, String phoneNo, String openId);
/**
* 按userId查微信信息
* 此接口实现处不应加缓存,否则解绑时(在关联用户的事务中)不好清除
* @param userId - 用户标识
* @return
*/
WechatUserInfo queryByUserId(Long userId);
int forbiddenUserWeChat(Long userId);
......
package cn.quantgroup.xyqb.service.wechat.impl;
import cn.quantgroup.xyqb.entity.WechatUserInfo;
import cn.quantgroup.xyqb.exception.WechatRelateUserException;
import cn.quantgroup.xyqb.model.webchat.AccessTokenResponse;
import cn.quantgroup.xyqb.repository.IWeChatUserRepository;
import cn.quantgroup.xyqb.service.http.IHttpService;
import cn.quantgroup.xyqb.service.session.ISessionService;
import cn.quantgroup.xyqb.service.wechat.IWechatService;
import cn.quantgroup.xyqb.util.ValidationUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
......@@ -14,12 +17,14 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.Example;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
......@@ -32,19 +37,21 @@ import java.util.concurrent.TimeUnit;
public class WechatServiceImpl implements IWechatService {
private static final String WECHAT_TOKEN_KEY_PREFIX = "wechat:token:";
private static final String WECHAT_USERINFO_KEY_PREFIX = "wechat:userinfo:";
@Autowired
private IHttpService httpService;
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
@Value("${wechat.appid}")
private String appId;
@Value("${wechat.secret}")
private String secret;
@Autowired
private IWeChatUserRepository weChatUserRepository;
private String accessTokenUrl;
private String refreshTokenUrl = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s";
@Resource
private IHttpService httpService;
@Resource
private ISessionService sessionService;
@Resource
private IWeChatUserRepository weChatUserRepository;
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
@PostConstruct
private void init() {
......@@ -136,15 +143,36 @@ public class WechatServiceImpl implements IWechatService {
@Override
@CacheEvict(value = "WechatUserInfo", key = "'openId:' + #openId", cacheManager = "cacheManager")
@Transactional(rollbackFor = Exception.class)
public int bindingUser(Long userId, String phoneNo, String openId) {
if(Objects.isNull(userId) || Objects.isNull(openId)){
public int relateUser(Long userId, String phoneNo, String openId) {
if(Objects.isNull(userId) || Objects.isNull(openId) || StringUtils.isBlank(openId)){
log.error("微信关联失败:非法入参:[service]:userId:{},phoneNo:{},openId:{}", userId, phoneNo, openId);
return 0;
}
return weChatUserRepository.bindingUser(userId, Optional.ofNullable(phoneNo).orElse(""), openId);
/* 如果当前openId已关联其他用户,则解绑成功后要注销其登录session */
WechatUserInfo wechatUserInfo = weChatUserRepository.findByUserId(userId);
// 强制解除关联
int dissociate = weChatUserRepository.dissociateUser(openId, userId);
if(dissociate < 1){
log.error("微信关联失败:解绑条数<1:[service]:userId:{},phoneNo:{},openId:{}", userId, phoneNo, openId);
throw new WechatRelateUserException("微信关联失败");
}
int relate = weChatUserRepository.relateUser(userId, Optional.ofNullable(phoneNo).orElse(""), openId);
if(relate < 1){
log.error("微信关联失败:绑定条数<1:[service]:userId:{},phoneNo:{},openId:{}", userId, phoneNo, openId);
throw new WechatRelateUserException("微信关联失败");
}else if(Objects.nonNull(wechatUserInfo) && Objects.nonNull(wechatUserInfo.getUserId())){
// 如果当前openId已关联其他用户,则解绑成功后要注销其登录session
sessionService.deleteByUserId(wechatUserInfo.getUserId());
}
log.info("微信关联成功:[service]:userId:{},phoneNo:{},openId:{},dissociate:{},relate:{}", userId, phoneNo, openId,dissociate,relate);
return relate;
}
@Override
public WechatUserInfo findWechatUserInfoByPhoneNo(String phoneNo) {
if(!ValidationUtil.validatePhoneNo(phoneNo)){
return null;
}
return weChatUserRepository.findByPhoneNo(phoneNo);
}
......@@ -163,15 +191,19 @@ public class WechatServiceImpl implements IWechatService {
return httpService.get(String.format(refreshTokenUrl, refreshToken));
}
/**
* 按userId查微信信息
* 此处不应加缓存,否则解绑时不好清除
* @param userId - 用户标识
* @return
*/
@Override
@Cacheable(value = "WechatUserInfo", key = "'userId:' + #userId", unless = "#result == null", cacheManager = "cacheManager")
public WechatUserInfo queryByUserId(Long userId) {
return weChatUserRepository.findByUserId(userId);
}
@Override
@CacheEvict(value = "WechatUserInfo", key = "'userId:' + #userId", cacheManager = "cacheManager")
public int forbiddenUserWeChat(Long userId) {
return weChatUserRepository.removeByUserId(userId);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment