package cn.quantgroup.xyqb.controller.external;

import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.accessable.IpValidator;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaFiniteValidator;
import cn.quantgroup.xyqb.aspect.captcha.LoginInterceptor;
import cn.quantgroup.xyqb.aspect.limit.PasswordFreeAccessValidator;
import cn.quantgroup.xyqb.aspect.lock.PasswordErrorFiniteValidator;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.entity.Merchant;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.entity.UserAttached;
import cn.quantgroup.xyqb.entity.UserDetail;
import cn.quantgroup.xyqb.exception.UserNotExistException;
import cn.quantgroup.xyqb.exception.VerificationCodeErrorException;
import cn.quantgroup.xyqb.model.*;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.service.merchant.IMerchantService;
import cn.quantgroup.xyqb.service.register.IUserRegisterService;
import cn.quantgroup.xyqb.service.session.ISessionService;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.service.user.ILockIpv4Service;
import cn.quantgroup.xyqb.service.user.IUserDetailService;
import cn.quantgroup.xyqb.service.user.IUserService;
import cn.quantgroup.xyqb.service.user.UserCenterService;
import cn.quantgroup.xyqb.service.wechat.IWechatService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.IpUtil;
import cn.quantgroup.xyqb.util.PasswordUtil;
import cn.quantgroup.xyqb.util.ValidationUtil;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.*;

import static cn.quantgroup.xyqb.Constants.VERIFICATION_CODE_FINITE_COUNT_NEW;

/**
 * Http服务接口：用户注册、登录、重置密码
 * Created by FrankChow on 15/7/5.
 */
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController implements IBaseController {

    @Autowired
    private IUserService userService;
    @Autowired
    private UserCenterService userCenterService;
    @Autowired
    private ILockIpv4Service lockIpv4Service;
    @Autowired
    @Qualifier("stringRedisTemplate")
    private RedisTemplate<String, String> stringRedisTemplate;
    @Autowired
    private ISmsService smsService;

    @Autowired
    private ISessionService sessionService;

    @Autowired
    private IUserDetailService userDetailService;
    @Autowired
    private IMerchantService merchantService;
    @Autowired
    private IWechatService wechatService;
    @Autowired
    private IUserRegisterService userRegisterService;


    /**
     * 登录（账号 + 密码）
     * 密码错误达到限定次数时执行图形验证码校验
     * 图形验证码累计错误达到限定次数时须重新获取
     *
     * @param channelId
     * @param appChannel
     * @param createdFrom
     * @param userId
     * @param key
     * @param request
     * @param dimension
     * @return
     * @yapi unknown
     */
    @LoginInterceptor
    @CaptchaFiniteValidator
    @RequestMapping("/loginV1")
    public JsonResult loginV1(
            @RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
            @RequestParam(required = false, defaultValue = "1") Long createdFrom,
            @RequestParam(required = false, defaultValue = "") String userId,
            @RequestParam(required = false, defaultValue = "xyqb") String key,
            @RequestParam(required = false) String dimension,
            HttpServletRequest request) {
        log.info("loginV1 -> channelId:{},appChennel:{},createdFrom:{},userId:{},key:{},dimension:{}", channelId, appChannel, createdFrom, userId, key, dimension);
        return login(channelId, appChannel, createdFrom, userId, key, dimension, request);
    }


    /**
     * 慢速登陆
     *
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/7310
     */
    @PasswordErrorFiniteValidator
    @RequestMapping("/login")
    public JsonResult login(
            @RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
            @RequestParam(required = false, defaultValue = "1") Long createdFrom,
            @RequestParam(required = false, defaultValue = "") String userId,
            @RequestParam(required = false, defaultValue = "xyqb") String key,
            @RequestParam(required = false) String dimension,
            HttpServletRequest request) {
        log.info("login -> channelId:{},appChannel:{},createdFrom:{},userId:{},key:{},dimension:{}", channelId, appChannel, createdFrom, userId, key, dimension);
        Merchant merchant = merchantService.findMerchantByName(key);
        if (merchant == null) {
            return JsonResult.buildErrorStateResult("未知的连接", null);
        }
        if (StringUtils.length(userId) > Constants.UUID_MIN_LENGTH) {
            return loginWithUserId(channelId, appChannel, createdFrom, userId, merchant, dimension, request);
        } else {
            return loginWithHttpBasic(channelId, appChannel, createdFrom, merchant, dimension, request);
        }
    }

    /**
     * 快速登录（手机号 + 短信验证码），H5专用入口
     * 短信验证码错误达到限定次数时执行图形验证码校验
     * 图形验证码累计错误达到限定次数时须重新获取
     *
     * @param channelId
     * @param appChannel
     * @param createdFrom
     * @param key
     * @param btRegisterChannelId
     * @param dimension
     * @param request
     * @return
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/1786
     */
    @RequestMapping("/login/fastV1")
    public JsonResult loginFastV1(
            @RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
            @RequestParam(required = false, defaultValue = "1") Long createdFrom,
            @RequestParam(required = false, defaultValue = "xyqb") String key,
            @RequestParam(required = false) Long btRegisterChannelId,
            @RequestParam(required = false) String dimension,
            @RequestParam(name = "click_id", required = false) String clickId,
            HttpServletRequest request) {
        log.info("login/fastV1 -> channelId:{},ZappChennel:{},createdFrom:{},key:{},btRegisterChannelId:{},dimension:{},clickId:{}", channelId, appChannel, createdFrom, key, btRegisterChannelId, dimension, clickId);
        return loginFast(channelId, appChannel, createdFrom, key, btRegisterChannelId, dimension, clickId, request);
    }

    /**
     * 快速登陆 - 同h5login代码实现一致
     *
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/2066
     */
    @LoginInterceptor
    @RequestMapping("/login/fast")
    public JsonResult loginFast(
            @RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
            @RequestParam(required = false, defaultValue = "1") Long createdFrom,
            @RequestParam(required = false, defaultValue = "xyqb") String key,
            @RequestParam(required = false) Long btRegisterChannelId,
            @RequestParam(required = false) String dimension,
            @RequestParam(name = "click_id", required = false) String clickId,
            HttpServletRequest request) {
        Map<String, JsonResult> validMap = getHeaderParam(request);
        log.info("login/fast -> channelId:{},appChannel:{},createdFrom:{},btRegisterChannelId:{},key:{},dimension:{},clickId:{}", channelId, appChannel, createdFrom, btRegisterChannelId, key, dimension, clickId);
        JsonResult failResult = validMap.get(Constants.CHECK_FAIL);
        if (null != failResult) {
            return failResult;
        }
        Merchant merchant = merchantService.findMerchantByName(key);
        if (merchant == null) {
            return JsonResult.buildErrorStateResult("未知的连接", null);
        }
        JsonResult successResult = validMap.get("success");
        String phoneNo = successResult.getData().toString();
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            log.info("用户快速登录失败，手机号错误, createdFrom:{},phoneNo:{}", createdFrom, phoneNo);
            throw new UserNotExistException("手机号错误");
        }
        String verificationCode = successResult.getMsg();
        // 执行短信验证码检查
        verifyPhoneAndCodeForOnce(phoneNo, verificationCode);
        return userService.loginFast(channelId, appChannel, createdFrom, btRegisterChannelId, dimension, clickId, request, merchant, phoneNo);
    }

    /**
     * 快速登录验证
     *
     * @param request
     * @return
     */
    private Map<String, JsonResult> getHeaderParam(HttpServletRequest request) {
        Map<String, JsonResult> result = new HashMap<>();
        String verificationHeader = "Verification ";
        String credential = request.getHeader("authorization");
        if (StringUtils.isBlank(credential)) {
            result.put(Constants.CHECK_FAIL, JsonResult.buildErrorStateResult("登录失败", null));
            return result;
        }
        if (!credential.startsWith(verificationHeader)) {
            result.put(Constants.CHECK_FAIL, JsonResult.buildErrorStateResult("登录失败", null));
            return result;
        }
        credential = credential.substring(verificationHeader.length(), credential.length());
        byte[] buf = Base64.decodeBase64(credential);
        credential = new String(buf, Charset.forName("UTF-8"));
        String[] credentialArr = credential.split(Constants.SPLIT_CHAR);
        if (credentialArr.length != Constants.VERIFICATION_LEN) {
            result.put(Constants.CHECK_FAIL, JsonResult.buildErrorStateResult("登录失败", null));
            return result;
        }
        String phoneNo = credentialArr[0];
        String verificationCode = credentialArr[1];
        log.info("用户快速登录,phoneNo:{} , verificationCode:{}", phoneNo, verificationCode);
        if (!ValidationUtil.validatePhoneNo(phoneNo) || StringUtils.isBlank(verificationCode)) {
            result.put(Constants.CHECK_FAIL, JsonResult.buildErrorStateResult("登录失败", null));
            return result;
        }
        result.put("success", JsonResult.buildSuccessResult(verificationCode, phoneNo));
        return result;
    }

    /**
     * 用户注册
     *
     * @param phoneNo
     * @param password
     * @param verificationCode
     * @param channelId
     * @return
     * @yapi unknown
     */
    @RequestMapping("/register")
    public JsonResult register(@RequestParam String phoneNo, @RequestParam String password,
                               @RequestParam String verificationCode, @RequestParam(required = false) Long channelId,
                               @RequestParam(required = false) Long registerFrom,
                               @RequestParam(required = false) Long btRegisterChannelId, @RequestParam(required = false) String dimension) {
        log.info("用户注册：register -> phoneNo:{}, verificationCode:{}, channelId:{}, registerFrom:{},btRegisterChannelId:{},dimension:{}", phoneNo, verificationCode, channelId, registerFrom, btRegisterChannelId, dimension);
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            log.info("用户注册失败，手机号错误：register -> registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
            return JsonResult.buildErrorStateResult("手机号错误", null);
        }
        if (StringUtils.isEmpty(password)) {
            log.info("用户注册失败，密码不能为空：register -> registerFrom:{}, phoneNo:{}, password:{}", registerFrom, phoneNo, password);
            return JsonResult.buildErrorStateResult("密码不能为空", null);
        }
        if (!ValidationUtil.validatePassword(password)) {
            log.info("用户注册失败，{}：register -> registerFrom:{}, phoneNo:{}, password:{}", PasswordUtil.TOAST_MSG, registerFrom, phoneNo, password);
            return JsonResult.buildErrorStateResult(PasswordUtil.TOAST_MSG, null);
        }
        if (null == registerFrom) {
            registerFrom = 1L;
        }
        verifyPhoneAndCode(phoneNo, verificationCode);
        User user = userService.findByPhoneInDb(phoneNo);
        if (user != null) {
            user.setPassword(PasswordUtil.MD5WithSalt(password));
            userService.saveUser(user);
            log.info("用户注册失败，该手机号已经被注册：register -> registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
            //已存在的用户, 经过短信认证, 也认为是注册成功的
            return JsonResult.buildSuccessResult(null, null);
        }
        if (!userRegisterService.register(phoneNo, password, registerFrom, getIp(), channelId, btRegisterChannelId, dimension)) {
            log.info("用户快速注册失败，请稍后重试, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
            return JsonResult.buildErrorStateResult("注册失败，请稍后重试", null);
        }
        log.info("用户注册成功：register -> registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
        return JsonResult.buildSuccessResult(null, null);
    }

    /**
     * 检查用户是否存在
     *
     * @param phoneNo 手机号
     * @return
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/2388
     * @Deprecated 20210318
     */
    @IpValidator
    @RequestMapping("/exist")
    @Deprecated
    public JsonResult exist(@RequestParam String phoneNo) {
        log.info("检查用户是否存在, phoneNo:{}", phoneNo);
        if (userService.exist(phoneNo)) {
            log.info("该手机号已经注册, phoneNo:{}，remoteIp:{}", phoneNo, getIp());
            return JsonResult.buildErrorStateResult("该手机号已经注册", null);
        }
        return JsonResult.buildSuccessResult(null, null);
    }

    /**
     * 检查用户是否存在
     *
     * @param phoneNo 手机号
     * @return
     * @yapi unknown
     * @Deprecated 20210318
     */
    @IpValidator
    @RequestMapping("/exist_check")
    @Deprecated
    public JsonResult existForResetPwd(@RequestParam String phoneNo) {
        log.info("检查用户是否存在, phoneNo:{}，remoteIp:{}", phoneNo, getIp());
        return JsonResult.buildSuccessResult(null, userService.exist(phoneNo));
    }

    /**
     * 重置密码
     *
     * @param phoneNo
     * @param password
     * @param verificationCode
     * @return
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/3263
     */
    @RequestMapping("/reset_password")
    public JsonResult resetPassword(@RequestParam String phoneNo,
                                    @RequestParam String password,
                                    @RequestParam(required = false) String registerFrom,
                                    @RequestParam String verificationCode) {
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            return JsonResult.buildErrorStateResult("手机号错误", null);
        }
        if (StringUtils.isBlank(password)) {
            return JsonResult.buildErrorStateResult("密码不能为空", null);
        }
        if (!ValidationUtil.validatePassword(password)) {
            return JsonResult.buildErrorStateResult(PasswordUtil.TOAST_MSG, null);
        }
        verifyPhoneAndCode(phoneNo, verificationCode);
        if (!userService.exist(phoneNo)) {
            log.info("修改密码失败，该手机号尚未注册, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
            return JsonResult.buildErrorStateResult("修改密码失败", null);
        }
        if (!userService.resetPassword(phoneNo, password)) {
            return JsonResult.buildErrorStateResult("修改密码失败", null);
        }

        // TODO  加渠道号
        log.info("修改密码成功, phoneNo:{}, registerFrom:{}", phoneNo, registerFrom);
        //修改密码成功也要清除一下
        lockIpv4Service.unLockPhone(phoneNo);
        return JsonResult.buildSuccessResult(null, null);
    }

    /**
     * 重置密码
     *
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/3263
     * @Deprecated 20210318, 仅有一次调用2021-03-15 00:38:57.752
     */
    @Deprecated
    @PasswordFreeAccessValidator
    @RequestMapping(path = "/resetPassword", method = RequestMethod.POST)
    public JsonResult resetPassword(@RequestParam String phoneNo, @RequestParam String password, @RequestParam(required = false) String passwordNew) {
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            return JsonResult.buildErrorStateResult("手机号错误", null);
        }
        if (StringUtils.isBlank(passwordNew)) {
            return JsonResult.buildErrorStateResult("密码不能为空", null);
        }
        if (!ValidationUtil.validatePassword(password)) {
            return JsonResult.buildErrorStateResult(PasswordUtil.TOAST_MSG, null);
        }
        User user = userService.findByPhoneWithCache(phoneNo);
        if (Objects.isNull(user)) {
            log.info("修改密码失败，该手机号尚未注册, phoneNo:{}", phoneNo);
            return JsonResult.buildErrorStateResult("修改密码失败", null);
        }
        if (!user.getEnable()) {
            log.info("修改密码失败，该用户已禁用, phoneNo:{}", phoneNo);
            return JsonResult.buildErrorStateResult("修改密码失败", null);
        }
        // 验证密码：原密码不存在时，必须为空
        if (StringUtils.isBlank(user.getPassword()) ^ StringUtils.isBlank(password)) {
            return JsonResult.buildErrorStateResult("修改密码失败", null);
        }
        if (StringUtils.isNotBlank(user.getPassword()) && !PasswordUtil.validatePassword(password, user.getPassword())) {
            return JsonResult.buildErrorStateResult("修改密码失败", null);
        }
        if (!userService.resetPassword(phoneNo, passwordNew)) {
            return JsonResult.buildErrorStateResult("修改密码失败", null);
        }
        return JsonResult.buildSuccessResult("修改密码成功");
    }


    /**
     * 检查token是否已经过期不存在了
     *
     * @param token - sid,session的id
     * @return
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/4012
     */
    @IpValidator
    @RequestMapping("/exists_token")
    public JsonResult checkToken(@RequestParam String token) {
        if (StringUtils.isEmpty(token)) {
            return JsonResult.buildSuccessResult(null, false);
        }
        if (token.contains(Constants.TOKEN_MASTER)) {
            return JsonResult.buildSuccessResult(null, false);
        }
        String tokenKey = Constants.SESSION_PREFIX + token;
        String tokenKey2 = Constants.Session.USER_SESSION_CACHE + token;
        // 判断token是否存在
        boolean exist = stringRedisTemplate.hasKey(tokenKey) || stringRedisTemplate.hasKey(tokenKey2);
        return JsonResult.buildSuccessResult("token valid", exist);
    }

    /**
     * 获取 token 信息
     *
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/9191
     */
    @RequestMapping("/token")
    public JsonResult token(@RequestParam String token) {
        Map<String, Object> result = new HashMap<>();
        result.put("exist", false);
        if (StringUtils.isEmpty(token)) {
            return JsonResult.buildSuccessResult(null, result);
        }
        if (token.contains(Constants.TOKEN_MASTER)) {
            return JsonResult.buildSuccessResult(null, result);
        }
        SessionStruct sessionStruct = XyqbSessionContextHolder.getXSessionFromRedis(token);
        if (sessionStruct == null || sessionStruct.getValues() == null) {
            return JsonResult.buildSuccessResult(null, result);
        }
        User user = sessionStruct.getValues().getUser();
        String phoneNo = user.getPhoneNo();
        result.put("phoneNo", phoneNo);
        result.put("userId", user.getId());
        result.put("exist", true);
        return JsonResult.buildSuccessResult(null, result);
    }

    /**
     * 用户中心首页，显示用户头像、昵称、姓名
     *
     * @return
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/267
     */
    @PasswordFreeAccessValidator
    @RequestMapping("/center/index")
    @ApiOperation(value = "用户中心首页", notes = "用户中心首页显示头像, 昵称, 姓名", httpMethod = "POST")
    public JsonResult userCenterIndex() {

        UserBrief brief = new UserBrief();
        //TODO:he 返回""，待解决
        Long userId = getCurrentUserFromRedis().getId();
        UserAttached userAttached = userCenterService.searchUserAttachedByUserId(userId);
        if (userAttached != null) {
            brief.setAvatar(userAttached.getAvatar());
            brief.setNick(userAttached.getNick());
        }
        UserDetail userDetail = userDetailService.findSlaveByUserId(userId);
        if (userDetail != null) {
            brief.setName(userDetail.getName());
            brief.setSex(Optional.ofNullable(userDetail.getGender()).orElse(cn.quantgroup.xyqb.model.Gender.UNKNOWN).ordinal() + "");
            brief.setPhoneNo(userDetail.getPhoneNo().substring(0, 3) + "****" + userDetail.getPhoneNo().substring(7, 11));
        }
        return JsonResult.buildSuccessResult(null, brief);
    }

    /**
     * @yapi unknown
     * @Deprecated 20210318, 20210201-20210203有集中调用274次，其他时间没有
     */
    @Deprecated
    @RequestMapping("/syncUserInfo")
    public JsonResult syncUserInfo(HttpServletRequest request) {
        log.error("[监控][UserController][syncUserInfo] request-Header:{}", JSON.toJSONString(getRequestHeaderMap(request)));
        User user = getCurrentUserFromRedis();
        if (null == user) {
            return JsonResult.buildErrorStateResult(null, null);
        }
        UserDetail detail = userDetailService.findByUserId(user.getId());
        UserModel userModel = new UserModel(user, detail);
        return JsonResult.buildSuccessResult("token校验成功", userModel);
    }

    /**
     * 登出接口
     *
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/23661
     */
    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public JsonResult logout(HttpServletRequest request) {

        String token = request.getHeader("x-auth-token");

        if (null == token || "".equals(token)) {
            return JsonResult.buildErrorStateResult("服务器异常,请稍后再试", null);
        }

        try {
            userService.logout(token);
        } catch (Exception e) {
            log.error("登出接口系统异常token:{}", token);
            return JsonResult.buildErrorStateResult("服务器异常,请稍后再试", null);
        }

        return JsonResult.buildSuccessResult("登出成功");
    }

    private JsonResult loginWithHttpBasic(Long channelId, String appChannel, Long createdFrom, Merchant merchant, String dimension, HttpServletRequest request) {
        User user = verificateUserNameAndPassword(request);
        if (user == null) {
            return JsonResult.buildErrorStateResult("用户名或密码不正确", null);
        } else if (!wechatRelateUserIfNecessary(user, request)) {
            return JsonResult.buildErrorStateResult("登录时微信关联失败", null);
        }
        LoginProperties loginProperties = new LoginProperties("", 1, channelId, createdFrom, appChannel, merchant.getId(), merchant.getName());
        AuthBean authBean=sessionService.createSession(user, loginProperties);
        if(authBean!=null){
            authBean.setRegister(false);
        }
        return new JsonResult(authBean);
    }

    private User verificateUserNameAndPassword(HttpServletRequest request) {
        String credential = request.getHeader("authorization");
        if (StringUtils.isBlank(credential) || !credential.startsWith(Constants.PASSWORD_HEADER)) {
            return null;
        }
        credential = credential.substring(Constants.PASSWORD_HEADER.length());
        byte[] buf = Base64.decodeBase64(credential);
        String bufStr = "";
        try {
            bufStr = new String(buf, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("不支持的编码: ", e);
        }
        String clientIp = getIp();
        String[] credentialArr = bufStr.split(":");
        if (credentialArr.length != Constants.AUTHORIZE_HEADER_SIZE) {
            log.info("用户登录失败：{}", bufStr);
            // 向该ipv4添加错误计数器
            lockIpv4Service.countErrorByIpv4(clientIp);
            return null;
        }
        String phoneNo = credentialArr[0];
        String pass = credentialArr[1];
        User user = userService.findByPhoneWithCache(phoneNo);
        if (user == null || !user.getEnable()) {
            // 向该ipv4添加错误计数器
            lockIpv4Service.countErrorByIpv4(clientIp);
            // 向该phoneNo添加错误计数器
            lockIpv4Service.countErrorByPhoneNo(phoneNo);
            return null;
        }
        //验证密码
        if (!PasswordUtil.validatePassword(pass, user.getPassword())) {
            // 向该ipv4添加错误计数器
            lockIpv4Service.countErrorByIpv4(clientIp);
            // 向该phoneNo添加错误计数器
            lockIpv4Service.countErrorByPhoneNo(phoneNo);
            return null;
        }

        // 向该ipv4添加成功计数器
        lockIpv4Service.countSuccessByIpv4(clientIp);
        //尝试解锁
        lockIpv4Service.unLockPhone(phoneNo);

        return user;
    }

    private JsonResult loginWithUserId(Long channelId, String appChannel, Long createdFrom, String userId, Merchant merchant, String dimension, HttpServletRequest request) {
        //查询用户
        User user = userService.findByUuidInDb(userId);
        if (Objects.isNull(user) || !user.getEnable()) {
            log.error("用户不存在，或者已经注销，userId:{}", userId);
            return JsonResult.buildErrorStateResult("登录失败", null);
        } else if (!wechatRelateUserIfNecessary(user, request)) {
            return JsonResult.buildErrorStateResult("登录时微信关联失败", null);
        }
        LoginProperties loginProperties = new LoginProperties("", 4, channelId, createdFrom, appChannel, merchant.getId(), merchant.getName());
        //尝试解锁
        lockIpv4Service.unLockPhone(user.getPhoneNo());
        //更新session
        return new JsonResult(sessionService.createSession(user, loginProperties));
    }

    /**
     * 如果必要的话，关联用户和微信
     *
     * @param user    - 用户标识
     * @param request - 当前请求
     * @return true - 继续登录，false - 微信关联失败，重新登录
     */
    private boolean wechatRelateUserIfNecessary(User user, HttpServletRequest request) {
        Objects.requireNonNull(request, "无效请求");
        String clientIp = IpUtil.getRemoteIP(request);
        Set<String> paramKeys = request.getParameterMap().keySet();
        boolean ready = paramKeys.contains(Constants.WECHAT_OPEN_ID);
        if (!ready) {
            return true;
        } else if (Objects.isNull(user) || Objects.isNull(user.getId()) || StringUtils.isBlank(request.getParameter(Constants.WECHAT_OPEN_ID))) {
            log.warn("微信关联失败，user:{}, request-Header:{}", user, JSON.toJSONString(getRequestHeaderMap(request)));
            return false;
        }
        Long userId = user.getId();
        String phoneNo = user.getPhoneNo();
        try {
            int rows = wechatService.relateUser(userId, phoneNo, request.getParameter(Constants.WECHAT_OPEN_ID));
            return rows > 0;
        } catch (Exception e) {
            log.error("微信关联失败，user:{}, request-Header:{}", user, JSON.toJSONString(getRequestHeaderMap(request)), e);
        }
        return false;
    }

    /**
     * 校验短信验证码
     *
     * @param phoneNo
     * @param verificationCode
     */
    private void verifyPhoneAndCode(String phoneNo, String verificationCode) {
        if (!smsService.verifyPhoneAndCode(phoneNo, verificationCode)) {
            // 是否需要重新发送短信验证码
            if (smsService.needResendCode(phoneNo)) {
                throw new VerificationCodeErrorException("验证码失效，请重新获取");
            }
            log.info("验证码校验失败,phoneNo:{} , verificationCode:{}", phoneNo, verificationCode);
            throw new VerificationCodeErrorException("短信验证码错误");
        }
    }

    /**
     * 校验验证码 不论成功与否都删除
     *
     * @param phoneNo
     * @param verificationCode
     */
    private void verifyPhoneAndCodeForOnce(String phoneNo, String verificationCode) {
        if (!smsService.verifyPhoneAndCode(phoneNo, verificationCode)) {
            // 是否需要重新发送短信验证码
            if (smsService.needResendCode(phoneNo, VERIFICATION_CODE_FINITE_COUNT_NEW, false)) {
                throw new VerificationCodeErrorException("验证码失效，请重新获取");
            }

            smsService.deleteOnlyCodeFromCache(phoneNo);

            log.info("验证码校验失败,phoneNo:{} , verificationCode:{}", phoneNo, verificationCode);
            throw new VerificationCodeErrorException("短信验证码错误");
        } else {
            smsService.deleteCodeFromCache(phoneNo);
        }
    }
}
