package cn.quantgroup.xyqb.controller.internal.user;

import cn.quantgroup.tech.db.DSType;
import cn.quantgroup.tech.db.TargetDataSource;
import cn.quantgroup.user.enums.*;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.accessable.IpValidator;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.controller.internal.user.resp.UserFullResp;
import cn.quantgroup.xyqb.entity.*;
import cn.quantgroup.xyqb.event.DisableActiveEvent;
import cn.quantgroup.xyqb.event.UserExtInfoSaveEvent;
import cn.quantgroup.xyqb.exception.UserNotExistException;
import cn.quantgroup.xyqb.model.Gender;
import cn.quantgroup.xyqb.model.IdType;
import cn.quantgroup.xyqb.model.*;
import cn.quantgroup.xyqb.service.api.IUserApiService;
import cn.quantgroup.xyqb.service.auth.IIdCardService;
import cn.quantgroup.xyqb.service.merchant.IMerchantService;
import cn.quantgroup.xyqb.service.register.IUserDeregisterService;
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.*;
import cn.quantgroup.xyqb.service.user.vo.UserDetailVO;
import cn.quantgroup.xyqb.service.wechat.IWechatService;
import cn.quantgroup.xyqb.util.*;
import cn.quantgroup.xyqb.util.encrypt.Md5Util;
import cn.quantgroup.xyqb.validator.ChineseName;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Maps;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;


/**
 * Created by Miraculous on 2016/12/19.
 */
@Slf4j
@RestController
@RequestMapping("/innerapi")
@Validated
public class InnerController implements IBaseController {

    @Autowired
    private IMerchantService merchantService;
    @Autowired
    private IUserService userService;
    @Autowired
    private IUserDetailService userDetailService;
    @Autowired
    private IUserBtRegisterService userBtRegisterService;
    @Autowired
    private IIdCardService idCardService;
    @Autowired
    private IUserExtInfoService userExtInfoService;
    @Autowired
    private IContactService contactService;
    @Autowired
    private IAddressService addressService;
    @Autowired
    private IWechatService wechatService;
    @Autowired
    private IUserSpouseService userSpouseService;
    @Autowired
    private ISessionService sessionService;
    @Autowired
    private IUserApiService userApiService;
    @Autowired
    private ISmsService smsService;
    @Autowired
    private IUserRegisterService userRegisterService;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Resource
    private CleanDataService cleanDataService;



    @Autowired
    private ITenantService tenantService;

    @Autowired
    private IOauthLoginInfoService iOauthLoginInfoService;

    @Autowired
    private IUserDeregisterService userDeregisterService;

    private static final String RESET_PWD_TOKEN = "ecf75c1f-2ccb-4661-8e4b-2874c0f45a2b";
    private static final String MODIFY_CONTACT_TOKEN = "@qwsdedad131323213w!";


    /**
     * 根据手机号或身份证号查询用户UUID
     *
     * @param phoneNo - 手机号
     * @param idNo    - 身份证号
     * @return user表的uuid
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/187
	 * @Deprecated 20210318
     */
    @Deprecated
    @IpValidator
    @RequestMapping("/fetchUuid")
    @ApiOperation(httpMethod = "POST", value = "根据手机号或身份证号查询用户UUID")
    public JsonResult fetchUuid(String phoneNo, String idNo) {
        String uuid = userService.findUuid(phoneNo, idNo);
        log.info("根据手机号或身份证号查询用户UUID,phoneNo:{},idNo:{},uuid:{}", phoneNo, idNo, uuid);
        if (org.apache.commons.lang.StringUtils.isBlank(uuid)) {
            return JsonResult.buildErrorStateResult("未找到该用户", null);
        }
        return JsonResult.buildSuccessResult(null, uuid);
    }

	/**
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/215
	 */
    @RequestMapping("/user/search/phoneNo")
    @ApiOperation(httpMethod = "GET", value = "根据手机号查询用户信息")
    public JsonResult findByPhoneNo(String phoneNo, Integer tenantId) {
        User user = userService.findByPhoneInDb(phoneNo);
        if (user == null) {
            return JsonResult.buildErrorStateResult("查无此人", null);
        }
        if (tenantId != null && !TenantUtil.TENANT_DEFAULT.equals(tenantId)) {
            // userId 查询对应productLogin 获取手机号和customerId
            user = tenantService.getTenantUser(user, tenantId);
            if (user == null ) {
                return JsonResult.buildErrorStateResult("查无此人", null);
            }
        }
        UserRet userRet = new UserRet(user);
        return JsonResult.buildSuccessResult("", userRet);
    }

	/**
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/219
	 */
    @RequestMapping("/user/search/uuid")
    @ApiOperation(httpMethod = "POST", value = "根据UUID查询用户信息")
    public JsonResult findByUuidInfo(String uuid, Integer tenantId) {
        User user = userService.findByUuidWithCache(uuid);
        if (user == null) {
            return JsonResult.buildErrorStateResult("查无此人", null);
        }

        if (tenantId != null && !TenantUtil.TENANT_DEFAULT.equals(tenantId)) {
            // userId 查询对应productLogin 获取手机号和customerId
            user = tenantService.getTenantUser(user, tenantId);
            if (user == null ) {
                return JsonResult.buildErrorStateResult("查无此人", null);
            }
        }
        UserRet userRet = new UserRet(user);
        return JsonResult.buildSuccessResult("", userRet);
    }

	/**
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/287
	 * @Deprecated 20210318
	 */
    @Deprecated
    @GetMapping("/user/search/hash")
    @ApiOperation(httpMethod = "GET", value = "根据md5(手机号)或md5(身份证号)查询用户信息")
    public JsonResult findByUuid(@RequestParam String md5Value,
                                 @RequestParam(defaultValue = "1") Integer type) {
        if (md5Value == null) {
            return JsonResult.buildErrorStateResult("参数错误", null);
        }
        if (md5Value.length() != Constants.MD5_LENGTH) {
            return JsonResult.buildErrorStateResult("参数长度有误", null);
        }

        User user = userService.findByMd5(type, md5Value);
        if (user == null) {
            return JsonResult.buildSuccessResult("", null);
        }
        UserRet userRet = new UserRet(user);
        return JsonResult.buildSuccessResult("", userRet);
    }

    /**
	 * @yapi unknown
	 */
    //与旧的区别就是新的如果返回多个的时候只取最新的
    @GetMapping("/user/search/hash2")
    @ApiOperation(httpMethod = "GET", value = "根据md5(手机号)或md5(身份证号)查询用户信息")
    public JsonResult findByHash(@RequestParam String md5Value,
                                 @RequestParam(defaultValue = "1") Integer type,
                                 @RequestParam(required = false) Integer tenantId) {
        if (md5Value == null) {
            return JsonResult.buildErrorStateResult("参数错误", null);
        }
        if (md5Value.length() != Constants.MD5_LENGTH) {
            return JsonResult.buildErrorStateResult("参数长度有误", null);
        }

        User user = userService.findByMd5New(type, md5Value);
        if (user == null) {
            return JsonResult.buildSuccessResult("", null);
        }
        if (tenantId != null && !TenantUtil.TENANT_DEFAULT.equals(tenantId)) {
            // userId 查询对应productLogin 获取手机号和customerId
            user = tenantService.getTenantUser(user, tenantId);
            if (user == null ) {
                return JsonResult.buildErrorStateResult("", null);
            }
        }
        UserRet userRet = new UserRet(user);
        return JsonResult.buildSuccessResult("", userRet);
    }

	/**
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/229
	 */
    @RequestMapping("/userInfo/search/uuid")
    @ApiOperation(httpMethod = "POST", value = "根据UUID查询用户详细信息")
    public JsonResult findUserInfoByUuid(@RequestParam(value = "uuid") String uuid) {
        log.info("需要查询的用户uuidid, uuid:" + uuid);

        if (StringUtils.isBlank(uuid)) {
            return JsonResult.buildSuccessResult(null, null);
        }
        User user = userService.findByUuidWithCache(uuid);
        if (null != user) {
            if (!user.getEnable()) {
                return JsonResult.buildSuccessResult("", null);
            }
            UserDetail userDetail = userDetailService.findByUserId(user.getId());
            UserInfo info = new UserInfo(user, userDetail);
            return JsonResult.buildSuccessResult("", info);
        }
        return JsonResult.buildSuccessResult("", null);
    }

	/**
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/227
	 */
    @RequestMapping("/userInfo/search/phone")
    @ApiOperation(httpMethod = "POST", value = "根据手机号查询用户详细信息")
    public JsonResult findUserInfoByPhone(@RequestParam(value = "phone") String phone) {
        log.info("需要查询的用户phone, phone:" + phone);

        if (StringUtils.isBlank(phone)) {
            return JsonResult.buildSuccessResult(null, null);
        }
        User user = userService.findByPhoneInDb(phone);
        if (null != user) {
            if (!user.getEnable()) {
                return JsonResult.buildSuccessResult("", null);
            }
            UserDetail userDetail = userDetailService.findByUserId(user.getId());
            UserInfo info = new UserInfo(user, userDetail);
            return JsonResult.buildSuccessResult("", info);
        }
        return JsonResult.buildSuccessResult("", null);
    }

	/**
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/205
	 */
    @RequestMapping("/user/getPhoneByUserIds")
    @ApiOperation(httpMethod = "POST", value = "根据用户 ID 批量查询用户信息")
    public JsonResult findByIds(@RequestParam(value = "userIds") String userIdsString, @RequestParam(required = false) Integer tenantId) {
        log.info("批量查询用户的手机号列表, userIdsString:" + userIdsString + ", tenantId:" + tenantId);
        //默认羊小咩租户
        if (TenantUtil.validationTenantIdIsNullOrZero(tenantId)) {
            tenantId = TenantUtil.TENANT_DEFAULT;
        }
        if (StringUtils.isEmpty(userIdsString)) {
            return JsonResult.buildErrorStateResult(null, null);
        }
        List<Long> userIds = JSONObject.parseObject(userIdsString, new TypeReference<List<Long>>() {
        });
        if (!CollectionUtils.isEmpty(userIds) && userIds.size() <= Constants.USER_ID_BATCH_SIZE) {
            Map<Long, String> userIdAndPhoneMap = userService.findPhoneByIdsInDb(userIds, tenantId);
            return JsonResult.buildSuccessResult("", userIdAndPhoneMap);
        } else {
            return JsonResult.buildErrorStateResult("批量查询每次最多进行500条用户信息的查询", null);
        }
    }

	/**
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/211
	 */
    @RequestMapping("/user/save")
    @ApiOperation(httpMethod = "POST", value = "注册用户")
    public JsonResult saveUser(String phoneNo, Long registeredFrom, Integer tenantId) {
        log.info("保存用户,phoneNo:{},registeredFrom:{}", phoneNo, registeredFrom);
        //参数验证
        if (StringUtils.isBlank(phoneNo)) {
            return JsonResult.buildErrorStateResult("用户手机号不能为空.", null);
        }
        if (registeredFrom == null) {
            registeredFrom = 0L;
        }
        if (tenantId == null) {
            tenantId = TenantUtil.TENANT_DEFAULT;
        }
        User user = userService.findByPhoneWithCache(phoneNo);

        if (user != null) {
            //存在已注销
            if (!user.getEnable()) {
                log.info("用户已经注销，phoneNo：{}", phoneNo);
                return JsonResult.buildErrorStateResult("用户已经注销", null);
            }

            // 插入关联数据
            iOauthLoginInfoService.addLoginInfo(user, tenantId);

            //已存在用户
            return JsonResult.buildSuccessResult(null, new UserRet(user));
        }
        //注册新用户
        user = userRegisterService.register(registeredFrom, phoneNo, null, null, 0L, 0L, tenantId);
        //校验租户ID tenantId
        iOauthLoginInfoService.addLoginInfo(user, tenantId);
        return JsonResult.buildSuccessResult(null, new UserRet(user));
    }

    /**
     * 保存用户详细信息
     * 适用于：创建 或 修改
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/285
     */
    @RequestMapping("/user_detail/save")
    @ApiOperation(httpMethod = "POST", value = "填写用户详情")
    public JsonResult saveUserDetail(@Min(value = 1, message = "用户id为空") @RequestParam Long userId,
                                     String phoneNo,
                                     @ChineseName @RequestParam String name,
                                     String idNo,
                                     String email, String qq) {
        log.info("保存用户详细信息,[saveUserDetail] userId:{},phoneNo:{},name:{},idNo:{},email:{},qq:{}", userId, phoneNo, name, idNo, email, qq);
        //参数验证
        if (Objects.isNull(userId)) {
            return JsonResult.buildErrorStateResult("userId为空.", null);
        }
        if (StringUtils.isBlank(phoneNo)) {
            return JsonResult.buildErrorStateResult("用户手机号为空.", null);
        }
        if (StringUtils.isBlank(name)) {
            return JsonResult.buildErrorStateResult("用户姓名为空.", null);
        }
        if (StringUtils.isBlank(idNo)) {
            return JsonResult.buildErrorStateResult("用户身份证为空.", null);
        }
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            return JsonResult.buildErrorStateResult("用户手机号错误.", null);
        }
        if (!ValidationUtil.validateChinese(name)) {
            return JsonResult.buildErrorStateResult("用户姓名错误.", null);
        }
        IdCardInfo info = idCardService.getIdCardInfo(idNo);
        if (Objects.isNull(info) || !info.isValid()) {
            log.error("用户身份证号错误,userId:{},idNo: {}", userId, idNo);
            return JsonResult.buildErrorStateResult("身份证号码错误", null);
        }
        User user = userService.findById(userId);
        if (Objects.isNull(user)) {
            return JsonResult.buildErrorStateResult("用户不存在", null);
        } else if (!Objects.equals(user.getPhoneNo(), phoneNo)) {
            return JsonResult.buildErrorStateResult("用户手机号不匹配", null);
        }
        /*
         * 如果已存在记录，则更新
         */
        UserDetail userDetail = userDetailService.findByUserId(userId);
        if (Objects.isNull(userDetail)) {
            userDetail = userDetailService.findByPhoneNo(phoneNo);
            // 按手机号查出记录，如果userId非空，说明是存疑数据或是其他用户的信息，停止修改操作，返回失败
            if (Objects.nonNull(userDetail) && Objects.nonNull(userDetail.getUserId())) {
                return JsonResult.buildErrorStateResult("手机号已使用.", null);
            }
        }
        if (Objects.isNull(userDetail)) {
            userDetail = new UserDetail();
        }
        userDetail.setUserId(userId);
        userDetail.setName(name);
        userDetail.setPhoneNo(phoneNo);
        userDetail.setIdNo(idNo);
        userDetail.setIdType(IdType.ID_CARD);
        userDetail.setGender(info.getGender());
        userDetail.setEmail(email);
        userDetail.setQq(qq);
        userDetail = userDetailService.saveUserDetail(userDetail);
        log.info("InnerController saveUserDetail, userId:{}, phoneNo:{}, name:{}", user.getId(), phoneNo, name);
        if (userDetail != null) {
            return JsonResult.buildSuccessResult(null, UserDetailRet.getUserDetail(userDetail));
        }
        return JsonResult.buildErrorStateResult("", null);
    }

    /**
     * 根据用户id查询用户的详细信息
     * @yapi http://yapi.quantgroups.com/project/17/interface/api/237
     */
    @RequestMapping("/user_detail/search/userId")
    @ApiOperation(httpMethod = "POST", value = "查询用户详情")
    public JsonResult findUserDetailByUserId(Long userId, Integer tenantId) {
        UserDetail userDetail = null;
        // 增加容错性，防备DB中存在的脏数据触发异常
        if (userId != null && userId > 0) {
            userDetail = userDetailService.findByUserId(userId);
        }
        if (Objects.isNull(userDetail)) {
            return JsonResult.buildErrorStateResult("该用户无实名信息", null);
        }

        if (tenantId != null && !TenantUtil.TENANT_DEFAULT.equals(tenantId)) {
            userDetail = tenantService.getTenantUserDetail(userDetail,  tenantId);
            if (userDetail == null) {
                return JsonResult.buildErrorStateResult("该用户详情信息不存在", null);
            }
        }
        return JsonResult.buildSuccessResult("用户实名信息", UserDetailRet.getUserDetail(userDetail));
    }

	/**
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/217
	 */
    @RequestMapping("/user/search/userId")
    @ApiOperation(httpMethod = "POST", value = "查询用户基本信息")
    public JsonResult findUserByUserId(Long userId, Integer tenantId) {
        User user = userService.findById(userId);
        if (user != null) {
            if (tenantId != null && !TenantUtil.TENANT_DEFAULT.equals(tenantId)) {
                // userId 查询对应productLogin 获取手机号和customerId
                user = tenantService.getTenantUser(user, tenantId);
                if (user == null ) {
                    return JsonResult.buildErrorStateResult("查无此人", null);
                }
            }
            return JsonResult.buildSuccessResult(null, new UserRet(user));
        }

        return JsonResult.buildErrorStateResult("查无此人", null);
    }

	/**
	 * @yapi http://yapi.quantgroups.com/project/17/interface/api/235
	 */
    @RequestMapping("/user_detail/search/phone")
    @ApiOperation(httpMethod = "POST", value = "查询用户详情")
    public JsonResult findUserDetailByPhone(String phoneNo, Integer tenantId) {
        UserDetail userDetail = null;
        userDetail = userDetailService.findSlaveByPhoneNo(phoneNo);
        if (userDetail != null) {
            if (tenantId != null && !TenantUtil.TENANT_DEFAULT.equals(tenantId)) {
                userDetail = tenantService.getTenantUserDetail(userDetail,  tenantId);
                if (userDetail == null) {
                    return JsonResult.buildErrorStateResult("该用户详情信息不存在", null);
                }
            }
            return JsonResult.buildSuccessResult(null, UserDetailRet.getUserDetail(userDetail));
        }

        return JsonResult.buildErrorStateResult("该用户详情信息不存在", null);
    }

    /**
     * 根据phone查找用户完整信息
     *
     * @param phoneNo
     * @return
     */
    @RequestMapping("/user_full_info/search/phone")
    @ApiOperation(httpMethod = "POST", value = "查询用户全部信息(user+detail)")
    public JsonResult findUserFullInfoByPhone(String phoneNo) {
        if (StringUtils.isBlank(phoneNo)) {
            log.warn("[findUserFullInfoByPhone]phoneNo为空");
            return JsonResult.buildErrorStateResult(null, null);
        }
        Optional<UserFullInfo> userFullInfoOptional = userApiService.getUserFullInfoByPhone(phoneNo);
        if (!userFullInfoOptional.isPresent()) {
            log.warn("[findUserFullInfoByPhone]没有用户信息，phoneNo={}", Md5Util.build(phoneNo));
            return JsonResult.buildErrorStateResult(null, null);
        }
        return JsonResult.buildSuccessResult(null, userFullInfoOptional.get());
    }

    /**
     * 根据uuid查找用户完整信息
     *
     * @param uuid
     * @return
     */
    @RequestMapping("/user_full_info/search/uuid")
    @ApiOperation(httpMethod = "POST", value = "查询用户全部信息(user+detail)")
    public JsonResult findUserFullInfoByUUuid(String uuid) {
        if (StringUtils.isBlank(uuid)) {
            log.warn("[findUserFullInfoByUUuid]uuid为空");
            return JsonResult.buildErrorStateResult(null, null);
        }
        Optional<UserFullInfo> userFullInfoOptional = userApiService.getUserFullInfoByUuid(uuid);
        if (!userFullInfoOptional.isPresent()) {
            log.warn("[findUserFullInfoByUUuid]没有用户信息，uuid={}", uuid);
            return JsonResult.buildErrorStateResult(null, null);
        }
        return JsonResult.buildSuccessResult(null, userFullInfoOptional.get());
    }

    /**
     * 更新用户email和qq
     *
     * @param qq
     * @param email
     * @param userId - 用户主键
     * @return
     */
    @RequestMapping("/user_detail/update")
    @ApiOperation(httpMethod = "POST", value = "更新用户详情")
    public JsonResult updateUserDetail(String qq, String email, Long userId) {
        if (Objects.isNull(userId) || userId == 0L) {
            return JsonResult.buildErrorStateResult("userId为空", null);
        }
        if (StringUtils.isNotBlank(qq)) {
            userDetailService.updateUserQq(userId, qq);
        }
        if (StringUtils.isNotBlank(email)) {
            userDetailService.updateUserEmail(userId, email);
        }
        return JsonResult.buildSuccessResult(null, null);
    }

    @RequestMapping("/user_detail/update/qq")
    @ApiOperation(httpMethod = "POST", value = "更新用户QQ")
    public JsonResult updateUserQQ(String qq, Long userId) {
        if (StringUtils.isEmpty(qq) || userId == null || userId == 0L) {
            return JsonResult.buildErrorStateResult("参数校验失败,qq或用户id为空", null);
        }
        userDetailService.updateUserQq(userId, qq);
        return JsonResult.buildSuccessResult(null, null);
    }

    @RequestMapping("/contact/search/user_id")
    @ApiOperation(httpMethod = "POST", value = "查询用户联系人")
    public JsonResult findContactsByUserId(Long userId) {
        if (null == userId) {
            return JsonResult.buildErrorStateResult(null, null);
        }
        List<Contact> contacts = contactService.findByUserIdAndBizType(userId, BizType.CASH, true);
        if (null == contacts || contacts.size() == 0) {
            return JsonResult.buildErrorStateResult(null, Collections.emptyList());
        }
        return JsonResult.buildSuccessResult(null, ContactRet.contacts2ContactRets(contacts));
    }

    @RequestMapping("/contact/save/contacts")
    @ApiOperation(httpMethod = "POST", value = "保存联系人")
    public JsonResult save2Contact(Long userId, @RequestParam(value = "contacts") String contactsStr) {
        if (Objects.isNull(userId) || StringUtils.isBlank(contactsStr)) {
            log.warn("保存用户联系人:参数不完整：userId:{}, contacts:{}", userId, contactsStr);
            return JsonResult.buildErrorStateResult("参数不完整", String.valueOf(userId).concat(":").concat(contactsStr));
        }
        log.info("保存用户联系人:userId:{}, contacts:{}", userId, contactsStr);
        List<Contact> contacts = JSONObject.parseObject(contactsStr, new TypeReference<List<Contact>>() {
        });
        if (CollectionUtils.isEmpty(contacts)) {
            log.info("联系人不能为空");
            return JsonResult.buildErrorStateResult("联系人不能为空", contacts);
        }
        for (Contact contact : contacts) {
            if (!contact.valid()) {
                log.info("用户手机号或姓名错误, phoneNo:{},name:{}", contact.getPhoneNo(), contact.getName());
                return JsonResult.buildErrorStateResult("用户手机号或姓名错误", contact);
            }
        }
        List<Contact> result = contactService.save(userId, contacts);
        return JsonResult.buildSuccessResult("保存成功", ContactRet.contacts2ContactRets(result));
    }

    @RequestMapping("/contact/update/contact")
    @ApiOperation(httpMethod = "POST", value = "更新用户联系人")
    public JsonResult updateContact(@RequestParam Long contactId, @RequestParam(required = false) String name,
                                    @RequestParam(required = false) String phoneNo,
                                    @RequestParam(required = false) Relation relation, String key,
                                    @RequestParam String reason, HttpServletRequest request) {
        if (!MODIFY_CONTACT_TOKEN.equals(key) || contactId == null) {
            return JsonResult.buildErrorStateResult("参数不合法", null);
        }
        if (StringUtils.isEmpty(name) && StringUtils.isEmpty(phoneNo)) {
            return JsonResult.buildErrorStateResult("修改联系人修改条件不能都为空", null);
        }
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            log.info("用户手机号错误, phoneNo:{}", phoneNo);
            return JsonResult.buildErrorStateResult("用户手机号错误", null);
        }
        if (!ValidationUtil.validateChinese(name)) {
            log.info("用户姓名错误, name:{}", name);
            return JsonResult.buildErrorStateResult("用户姓名错误", null);
        }
        if (StringUtils.isEmpty(reason)) {
            return JsonResult.buildErrorStateResult("修改原因不能为空", null);
        }
        Contact contact = contactService.findById(contactId);
        if (null == contact) {
            return JsonResult.buildErrorStateResult("修改联系人不存在", null);
        }
        contact = contactService.saveContact(name, phoneNo, relation, contact);
        log.info("修改后联系人信息:{},修改原因：{},操作ip:{}", contact, reason, IpUtil.getRemoteIP(request));

        return JsonResult.buildSuccessResult("修改联系人成功", contact);
    }

    @RequestMapping("/address/search/user_id")
    @ApiOperation(httpMethod = "POST", value = "查询用户地址")
    public JsonResult findAddressByUserId(Long userId) {
        if (userId == null) {
            return JsonResult.buildErrorStateResult(null, null);
        }
        Address address = addressService.findByUserId(userId);
        if (address == null) {
            return JsonResult.buildErrorStateResult(null, null);
        }
        return JsonResult.buildSuccessResult(null, AddressRet.address2AddressRet(address));
    }

    @RequestMapping("/address/save")
    @ApiOperation(httpMethod = "POST", value = "保存用户地址")
    public JsonResult saveAddress(
            Long userId, Long provinceCode, Long cityCode, String city,
            Long districtCode, String district, String address, String province) {
        log.info("保存地址详情:city:{},province:{},district:{}, address:{},userId:{}", city, province, district, address, userId);
        if (userId == null || provinceCode == null || cityCode == null || StringUtils.isBlank(address)) {
            log.info("错误保存地址详情，其中参数不能为空:city:{},province:{},district:{}, address:{},userId:{}", city, province, district, address, userId);
            return JsonResult.buildErrorStateResult("参数不能为空", null);
        }
        Address addressObj = addressService.findByUserId(userId);
        if (addressObj == null) {
            addressObj = new Address();
        }
        addressObj.setUserId(userId);
        addressObj.setProvince(province);
        addressObj.setProvinceCode(provinceCode);
        addressObj.setCity(city);
        addressObj.setCityCode(cityCode);
        addressObj.setDistrict(district);
        addressObj.setDistrictCode(districtCode);
        addressObj.setAddress(address);
        addressObj = addressService.save(addressObj);
        log.info("保存后地址详情:{}", addressObj);
        return JsonResult.buildSuccessResult(null, AddressRet.address2AddressRet(addressObj));
    }

    @RequestMapping("/user_ext_info/update")
    @ApiOperation(httpMethod = "POST", value = "更新用户扩展信息")
    public JsonResult updateMarryStatus(
            Long userId, IncomeEnum incomeEnum, IncomeRangeEnum incomeRangeEnum,
            OccupationEnum occupationEnum, EducationEnum educationEnum, Boolean hasCar,
            Boolean hasSocialSecurity, Boolean hasHouse, Boolean hasCreditCard, MaritalStatus maritalStatus) {
        if (null == userId) {
            return JsonResult.buildErrorStateResult("用户ID不能为空", null);
        }
        UserExtInfo info = userExtInfoService.findByUserId(userId);
        if (info == null) {
            info = new UserExtInfo();
            info.setUserId(userId);
        }
        if (incomeEnum != null) {
            info.setIncomeEnum(cn.quantgroup.user.enums.IncomeEnum.valueOf(incomeEnum.name()));
        }
        if (incomeRangeEnum != null) {
            info.setIncomeRangeEnum(cn.quantgroup.user.enums.IncomeRangeEnum.valueOf(incomeRangeEnum.name()));
        }
        if (occupationEnum != null) {
            if (occupationEnum == OccupationEnum.STUDENT) {//如果是学生那么转成其他
                occupationEnum = OccupationEnum.OTHER;
            }
            //不明白为啥这么写 为啥不直接用还要valueof
            info.setOccupationEnum(cn.quantgroup.user.enums.OccupationEnum.valueOf(occupationEnum.name()));
        }
        if (educationEnum != null) {
            info.setEducationEnum(cn.quantgroup.user.enums.EducationEnum.valueOf(educationEnum.name()));
        }
        if (hasCar != null) {
            info.setHasCar(hasCar);
        }
        if (hasCreditCard != null) {
            info.setHasCreditCard(hasCreditCard);
        }

        if (hasSocialSecurity != null) {
            info.setHasSocialSecurity(hasSocialSecurity);
        }


        if (hasHouse != null) {
            info.setHasHouse(hasHouse);
        }
        if (maritalStatus != null) {
            info.setMarryStatus(cn.quantgroup.user.enums.MaritalStatus.valueOf(maritalStatus.name()));
        }
        info = userExtInfoService.save(info);

        applicationEventPublisher.publishEvent(new UserExtInfoSaveEvent(this, info));
        return JsonResult.buildSuccessResult(null, UserExtInfoRet.getUserExtInfoRet(info));
    }

    /**
     * 按照姓名、份证号或手机号查询用户实名信息 - 精确查询，供客服用，不限制入参正确性
     *
     * @param name    - 姓名
     * @param phoneNo - 手机号
     * @param idNo    - 身份证号
     * @return JsonResult<List < UserDetail>>
     */
    @RequestMapping("/user_detail/search_list")
    @ApiOperation(httpMethod = "POST", value = "按照姓名、份证号或手机号查询用户实名信息 - 精确查询，供客服用，不限制入参正确性")
    @TargetDataSource(type = DSType.SLAVE)
    public JsonResult<List<UserDetailVO>> searchUserDetailList(String name, String phoneNo, String idNo) {
        log.info("searchUserDetailList ,param.name:{},phone:{},idNo:{},ip:{}", name, phoneNo, idNo, getIp());
        if (StringUtils.isBlank(name) && StringUtils.isBlank(phoneNo) && StringUtils.isBlank(idNo)) {
            return JsonResult.buildErrorStateResult("至少必须满足一个条件不为空", null);
        }
        List<UserDetailVO> userDetails = userDetailService.searchUserDetailList(name, phoneNo, idNo);
        return JsonResult.buildSuccessResult("success", userDetails);
    }

    /**
     * 按照身份证号和手机号查询用户实名信息查询 - 模糊查询
     *
     * @param phoneNo  - 手机号
     * @param idNo     - 身份证号
     * @param userName - 用户姓名
     * @return JsonResult<List < UserDetail>>
     */
    @ApiOperation(httpMethod = "POST", value = "按照身份证号和手机号查询用户实名信息查询 - 模糊查询")
    @RequestMapping("/user_detail/fuzzyQuery")
    @TargetDataSource(type = DSType.SLAVE)
    public JsonResult<List<UserDetail>> fuzzyQueryUserDetailList(@ApiParam(value = "手机号") String phoneNo,
                                                                 @ApiParam(value = "身份证号") String idNo,
                                                                 @ApiParam(value = "用户姓名") String userName) {
        log.info("fuzzyQueryUserDetailList, phone:{},idNo:{},userName:{},ip:{}", phoneNo, idNo, userName, getIp());
        if (StringUtils.isBlank(phoneNo) && StringUtils.isBlank(idNo)) {
            return JsonResult.buildErrorStateResult("查询条件不能为空", null);
        }
        //最多掩码四位
        int phoneNoMaskSize = 7;
        //最多掩码身份证后五位
        int idNoMaskSize = 13;
        int idNoFullSize = 18;
        boolean phoneNoValid = StringUtils.isBlank(phoneNo) ? false : (phoneNo.length() >= phoneNoMaskSize || ValidationUtil.validatePhoneNo(phoneNo));
        boolean idNoValid = StringUtils.isBlank(idNo) ? false : (idNo.length() >= idNoMaskSize || Objects.equals(idNo.length(), idNoFullSize));
        if (!phoneNoValid && !idNoValid) {
            return JsonResult.buildErrorStateResult("查询条件不规范！", null);
        }
        List<UserDetail> userDetailList = null;
        if (phoneNoValid && idNoValid) {
            userDetailList = userDetailService.fuzzyQueryByPhoneNoAndIdNo(phoneNo, idNo);
        } else if (idNoValid) {
            userDetailList = userDetailService.findByIdNoStartingWith(idNo);
        } else {
            userDetailList = userDetailService.findByPhoneNoStartingWith(phoneNo);
        }
        // 如果传入姓名，则只过滤匹配项
        if (!CollectionUtils.isEmpty(userDetailList) && StringUtils.isNotBlank(userName)) {
            userDetailList = userDetailList.stream().filter(userDetail -> Objects.equals(userName, userDetail.getName())).collect(Collectors.toList());
        }
        return JsonResult.buildSuccessResult("success", userDetailList);
    }

    @RequestMapping("/user_ext_info/search/user_id")
    @ApiOperation(httpMethod = "POST", value = "查询用户扩展信息")
    public JsonResult searchUserExtInfoByUserId(Long userId) {
        if (userId == null) {
            return JsonResult.buildErrorStateResult("userId不能为空", null);
        }
        UserExtInfo userExtInfo = userExtInfoService.findByUserId(userId);
        if (userExtInfo == null) {
            return JsonResult.buildErrorStateResult(null, null);
        }
        return JsonResult.buildSuccessResult(null, UserExtInfoRet.getUserExtInfoRet(userExtInfo));
    }

    @RequestMapping("/user/query/openId")
    @ApiOperation(httpMethod = "POST", value = "查询微信openId")
    public JsonResult queryOpenIdByUserId(Long userId) {
        if (userId == null) {
            return JsonResult.buildErrorStateResult("userId不能为空", null);
        }
        WechatUserInfo wechatUserInfo = wechatService.queryByUserId(userId);
        log.info("根据用户userId查询：微信关联：userId：{},WechatUserInfo:{}", userId, wechatUserInfo);
        if (wechatUserInfo == null) {
            return JsonResult.buildErrorStateResult(null, null);
        }
        return JsonResult.buildSuccessResult("success", wechatUserInfo.getOpenId());
    }

    /**
     * 按照用户主键、账号或uuid查询用户详细信息
     *
     * @param id      - 用户表主键
     * @param phoneNo - 用户账号
     * @param uuid    - 用户全球唯一键（uuid）
     * @return 详细信息包含：
     * {
     * 账号信息
     * 个人信息
     * }
     */
    @RequestMapping("/user-association/search")
    @ApiOperation(httpMethod = "POST", value = "查询用户user+userDetail")
    public JsonResult findUserAssociationModel(Long id, String phoneNo, String uuid) {
        User user = null;
        if (!Objects.isNull(id) && id > 0) {
            user = userService.findById(id);
        } else if (ValidationUtil.validatePhoneNo(phoneNo)) {
            user = userService.findByPhoneWithCache(phoneNo);
        } else if (StringUtils.isNotBlank(uuid)) {
            user = userService.findByUuidWithCache(uuid);
        }
        UserDetail userDetail = null;
        if (Objects.nonNull(user) && Objects.nonNull(user.getId()) && user.getId() > 0) {
            userDetail = userDetailService.findByUserId(user.getId());
        }
        if (Objects.isNull(user)) {
            return JsonResult.buildErrorStateResult("用户不存在", "");
        } else {
            return JsonResult.buildSuccessResult("", new UserInfo(user, userDetail));
        }
    }

    /**
     * 查询用户综合信息模型
     *
     * @param user - 用户User
     * @return 综合信息模型包含：
     * {
     * 账号信息
     * 个人信息
     * 扩展信息
     * 地址信息
     * 联系人信息
     * }
     */
    private UserAssociationModel findUserAssociationModelByUser(User user) {
        if (Objects.isNull(user) || Objects.isNull(user.getId()) || user.getId() < 1) {
            return null;
        }
        UserAssociationModel bean = new UserAssociationModel();
        bean.setId(user.getId());
        bean.setUuid(user.getUuid());
        bean.setRegisterFrom(user.getRegisteredFrom());
        UserDetail userDetail = userDetailService.findByUserId(user.getId());
        if (!Objects.isNull(userDetail)) {
            bean.setPhoneNo(userDetail.getPhoneNo());
            bean.setName(userDetail.getName());
            bean.setIdNo(userDetail.getIdNo());
            bean.setQq(userDetail.getQq());
            bean.setEmail(userDetail.getEmail());
            bean.setGender(Optional.ofNullable(userDetail.getGender()).orElse(Gender.UNKNOWN).getName());
        }
        UserBtRegister userBtRegister = userBtRegisterService.findByUserId(user.getId());
        if (!Objects.isNull(userBtRegister)) {
            bean.setMerchantId(userBtRegister.getRegisterBtMerchantId());
        }
        UserExtInfo extInfo = userExtInfoService.findByUserId(user.getId());
        if (Objects.nonNull(extInfo)) {
            // 婚姻状态
            bean.setMarryStatus(Optional.ofNullable(extInfo.getMarryStatus()).orElse(cn.quantgroup.user.enums.MaritalStatus.UNKNOWN).getDesc());
            // 受教育程度
            bean.setEducationEnum(Optional.ofNullable(extInfo.getEducationEnum()).orElse(cn.quantgroup.user.enums.EducationEnum.UNKNOWN).getName());
            // 职业
            bean.setOccupationEnum(Optional.ofNullable(extInfo.getOccupationEnum()).orElse(cn.quantgroup.user.enums.OccupationEnum.UNKNOWN).getName());
            // 收入水平范围
            bean.setIncomeRangeEnum(Optional.ofNullable(extInfo.getIncomeRangeEnum()).orElse(cn.quantgroup.user.enums.IncomeRangeEnum.UNKNOWN).getDesc());
            // 收入方式
            bean.setIncomeEnum(Optional.ofNullable(extInfo.getIncomeEnum()).orElse(cn.quantgroup.user.enums.IncomeEnum.UNKNOWN).getDesc());
            // 是否有车
            bean.setHasCar(Optional.ofNullable(extInfo.getHasCar()).orElse(false));
            // 是否有社保
            bean.setHasSocialSecurity(Optional.ofNullable(extInfo.getHasSocialSecurity()).orElse(false));
            // 是否有房
            bean.setHasHouse(Optional.ofNullable(extInfo.getHasHouse()).orElse(false));
            // 是否有信用卡
            bean.setHasCreditCard(Optional.ofNullable(extInfo.getHasCreditCard()).orElse(false));
        }
        Address address = addressService.findByUserId(user.getId());
        if (!Objects.isNull(address)) {
        	log.info("[findUserAssociationModelByUser]获取用户地址address:{}", JSON.toJSONString(address));
            bean.putAddressList(Collections.singletonList(address));
        }
        List<Contact> contacts = contactService.findByUserIdAndBizType(user.getId(), BizType.CASH, true);
        if (!Objects.isNull(contacts)) {
            bean.putContactList(contacts);
        }
        return bean;
    }

    @ApiResponses({
            @ApiResponse(code = HttpStatus.SC_OK, message = "Nice!", responseContainer = "UserAssociationModel = {Long id, String uuid, String phoneNo, String idNo, String name, String gender, String marryStatus, String educationEnum, String occupationEnum, String qq, Long registerFrom, Long merchantId, List<AddressModel> addressList, List<ContactModel> contactList}"),
            @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = "Invalid params supplied", response = cn.quantgroup.xyqb.model.ApiResponse.class),
            @ApiResponse(code = HttpStatus.SC_NOT_FOUND, message = "User not found", response = cn.quantgroup.xyqb.model.ApiResponse.class)
    })
    @ApiOperation(notes = "用户全量信息查询接口", value = "用户全量信息查询接口", nickname = "findUserAssociationModel")
    @RequestMapping(path = "/user-association/search/userId", method = {RequestMethod.GET, RequestMethod.POST})
    public JsonResult findUserAssociationModelByUserId(@ApiParam(value = "用户ID", required = false) @RequestParam(name = "userId", required = false) Long userId,
                                                       @ApiParam(value = "用户注册手机号", required = false) @RequestParam(name = "phoneNo", required = false) String phoneNo) {
        boolean userIdOk = Objects.nonNull(userId) && userId > 0;
        boolean phoneNoOk = ValidationUtil.validatePhoneNo(phoneNo);
        if (!userIdOk && !phoneNoOk) {
            return JsonResult.buildErrorStateResult("params invalid", String.valueOf(userId).concat(":").concat(phoneNo));
        }
        UserAssociationModel bean = null;
        User user = userIdOk ? userService.findById(userId) : userService.findByPhoneWithCache(phoneNo);
        if (!Objects.isNull(user)) {
            bean = findUserAssociationModelByUser(user);
        }
        return JsonResult.buildSuccessResult("", bean);
    }

    /**
     * 查询用户全量信息 for koala
     *
     * @param userId 用户id
     * @return
     */
    @GetMapping("/user-full/search/userId")
    public JsonResult<UserFullResp> findUserFullSearchByUserId(@RequestParam Long userId) {
        return JsonResult.buildSuccessResultGeneric(userService.findUserFullSearchByUserId(userId));
    }

    @RequestMapping("/user-association/search/phone")
    public JsonResult findUserAssociationByPhone(String phoneNo) {
        UserDetail userDetail = userDetailService.findByPhoneNo(phoneNo);
        UserAssociation bean = getUserAssociation(userDetail);
        return JsonResult.buildSuccessResult("", bean);
    }

    @RequestMapping("/user-association/search/uid")
    public JsonResult findUserAssociationByUid(Long uid) {
        UserDetail userDetail = userDetailService.findByUserId(uid);
        UserAssociation bean = getUserAssociation(userDetail);
        return JsonResult.buildSuccessResult("", bean);
    }

    /**
     * 获取用户综合信息
     *
     * @param userDetail
     * @return
     */
    private UserAssociation getUserAssociation(UserDetail userDetail) {
        UserAssociation bean = new UserAssociation();
        if (null != userDetail) {
            Address address = addressService.findByUserId(userDetail.getUserId());
            List<Contact> contacts = contactService.findByUserIdAndBizType(userDetail.getUserId(), BizType.CASH, true);
            UserExtInfo extInfo = userExtInfoService.findByUserId(userDetail.getUserId());
            bean.setUserId(userDetail.getUserId());
            bean.setPhoneNo(userDetail.getPhoneNo());
            bean.setName(userDetail.getName());
            bean.setIdNo(userDetail.getIdNo());
            bean.setQq(userDetail.getQq());
            bean.setGender(Optional.ofNullable(userDetail.getGender()).orElse(Gender.UNKNOWN).getName());
            if (null != address) {
                bean.setProvince(address.getProvince());
                bean.setCity(address.getCity());
                bean.setDistrict(address.getDistrict());
                bean.setAddress(address.getAddress());
            }
            if (null != extInfo) {
                if (null == extInfo.getEducationEnum()) {
                    bean.setEducationEnum(cn.quantgroup.user.enums.EducationEnum.UNKNOWN.getName());
                } else {
                    bean.setEducationEnum(extInfo.getEducationEnum().getName());
                }
                if (null == extInfo.getOccupationEnum()) {
                    bean.setOccupationEnum(cn.quantgroup.user.enums.OccupationEnum.UNKNOWN.getName());
                } else {
                    bean.setOccupationEnum(extInfo.getOccupationEnum().getName());
                }
                if (null == extInfo.getMarryStatus()) {
                    bean.setMarryStatus(cn.quantgroup.user.enums.MaritalStatus.UNKNOWN.getDesc());
                } else {
                    bean.setMarryStatus(extInfo.getMarryStatus().getDesc());
                }
            }
            if (org.apache.commons.collections.CollectionUtils.isNotEmpty(contacts)) {
                List<ContactInfo> userContacts = contacts2ContactRets(contacts);
                bean.setContractResults(userContacts);
            }
        }
        return bean;
    }

    public static ContactInfo contact2ContactRet(Contact c) {
        if (c == null) {
            return null;
        }
        ContactInfo ret = new ContactInfo();
        ret.setId(c.getId());
        ret.setUserId(c.getUserId());
        ret.setName(c.getName());
        ret.setPhoneNo(c.getPhoneNo());
        ret.setRelation(c.getRelation().getDesc());
        return ret;
    }

    public static List<ContactInfo> contacts2ContactRets(List<Contact> cs) {
        if (org.apache.commons.collections.CollectionUtils.isEmpty(cs)) {
            return Collections.emptyList();
        }
        List<ContactInfo> contactRets = new ArrayList<>();
        for (Contact c : cs) {
            contactRets.add(contact2ContactRet(c));
        }
        return contactRets;
    }

    @RequestMapping("/user/wechat/phone_no")
    public JsonResult queryOpenIdByPhoneNo(String phoneNo) {
        if (StringUtils.isBlank(phoneNo)) {
            return JsonResult.buildErrorStateResult("手机号错误", null);
        }
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            return JsonResult.buildErrorStateResult("手机号格式错误", null);
        }
        WechatUserInfo wechatUserInfo = wechatService.findWechatUserInfoByPhoneNo(phoneNo);
        return JsonResult.buildSuccessResult(null, null == wechatUserInfo ? null : wechatUserInfo.getOpenId());
    }

    /**
     * 刷新用户激活状态
     *
     * @param userId - 用户主键
     * @param enable - 用户激活状态
     * @return
     */
    private boolean flushUserStatus(Long userId, boolean enable) {
        if (null == userId || 0L == userId) {
            log.info("刷新用户激活状态失败：userId:{},enable:{}", userId, enable);
            return false;
        }
        User user = userService.findById(userId);
        if (null == user) {
            log.info("刷新用户激活状态失败：userId:{},enable:{},user:{}", userId, enable, user);
            return false;
        }
        user.setEnable(enable);
        log.info("刷新用户激活状态失败：userId:{},enable:{},user:{}", userId, enable, user);
        user = userService.saveUser(user);
        if (!user.getEnable()) {
            sessionService.deleteByUserId(userId);
            sessionService.deleteUserCatch(user);
            // 禁用微信
            wechatService.forbiddenUserWeChat(userId);
        }
        applicationEventPublisher.publishEvent(new DisableActiveEvent(this,user));
        return Objects.equals(enable, user.getEnable());
    }

    @RequestMapping("/user/spouse/save")
    @ApiOperation(value = "保存配偶信息", httpMethod = "POST")
    public JsonResult saveSpouse(Long userId, MaritalStatus status, String spousePhone, String spouseName) {
        if (userId == null || userId == 0) {
            return JsonResult.buildErrorStateResult("用户不能为空", null);
        }
        if (status == MaritalStatus.MARRIED) {
            if (!ValidationUtil.validatePhoneNo(spousePhone)) {
                return JsonResult.buildErrorStateResult("手机号格式错误", null);
            }
            if (!ValidationUtil.validateChinese(spouseName)) {
                return JsonResult.buildErrorStateResult("配偶姓名错误", null);
            }
        }
        UserSpouse userSpouse = userSpouseService.findByUserId(userId);
        if (userSpouse == null) {
            userSpouse = new UserSpouse(userId);
        }
        userSpouse.setSpouseName(status == MaritalStatus.MARRIED ? spouseName : "");
        userSpouse.setSpousePhone(status == MaritalStatus.MARRIED ? spousePhone : "");
        userSpouse.setStatus(cn.quantgroup.user.enums.MaritalStatus.valueOf(status.name()));
        userSpouse = userSpouseService.save(userSpouse);
        return JsonResult.buildSuccessResult(null, UserSpouseRet.getUserSpouseRet(userSpouse));
    }

    @RequestMapping("/user/spouse/findByUserId")
    @ApiOperation(value = "查询配偶信息", httpMethod = "POST")
    public JsonResult querySpouse(Long userId) {
        if (userId == null || userId == 0) {
            return JsonResult.buildErrorStateResult("用户不能为空", null);
        }
        UserSpouse userSpouse = userSpouseService.findByUserId(userId);
        if (userSpouse == null || !userSpouse.valid()) {
            userSpouse = new UserSpouse(userId);
            userSpouse.setStatus(cn.quantgroup.user.enums.MaritalStatus.UNKNOWN);
        }
        return JsonResult.buildSuccessResult(null, UserSpouseRet.getUserSpouseRet(userSpouse));
    }

    @RequestMapping("/user/findByPhones")
    @ApiOperation(value = "根据手机号查询用户信息", httpMethod = "POST")
    public JsonResult getUserIdByPhones(@RequestParam("userPhones") String userPhones) {
        if (StringUtils.isBlank(userPhones)) {
            return JsonResult.buildErrorStateResult("传入用户手机号不可为空", null);
        }
        List<String> phones = JSONObject.parseObject(userPhones, new TypeReference<List<String>>() {
        });
        if (!CollectionUtils.isEmpty(phones)) {
            if (phones.size() <= MAX_SIZE) {
                List<User> users = userService.findByPhones(phones);
                if (org.apache.commons.collections.CollectionUtils.isNotEmpty(users)) {
                    return JsonResult.buildSuccessResult(null, users.stream().collect(Collectors.toMap(User::getPhoneNo, User::getId)));
                } else {
                    return JsonResult.buildSuccessResult(null, null);
                }
            }
            return JsonResult.buildErrorStateResult("单次批量查询不可超过500个手机号", null);
        }
        return JsonResult.buildErrorStateResult("传入用户手机号不可为空", null);
    }

    private static long MAX_SIZE = 500L;

    /**
     * 手机号批量查询uuid
     * todo 代码增加读写分离. 这里需要查从库
     *
     * @param userPhones
     * @return
     */
    @RequestMapping("/uuid/findByPhones")
    @ApiOperation(value = "根据手机号批量查询UUID", httpMethod = "POST")
    @TargetDataSource(type = DSType.SLAVE)
    public JsonResult getUuidsByPhones(@RequestParam("userPhones") String userPhones) {
        if (StringUtils.isBlank(userPhones)) {
            return JsonResult.buildErrorStateResult("传入用户手机号不可为空", null);
        }
        List<String> phones = JSONObject.parseObject(userPhones, new TypeReference<List<String>>() {
        });
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(phones)) {
            if (phones.size() <= MAX_SIZE) {
                List<UserInfo> userInfos = userService.findUserInfosByPhones(phones);
                if (org.apache.commons.collections.CollectionUtils.isNotEmpty(userInfos)) {

                    Map<String, UserInfo> userInfoMap = Maps.newHashMapWithExpectedSize(userInfos.size());
                    userInfos.forEach(userInfo -> {
                        userInfoMap.put(userInfo.getPhoneNo(), userInfo);
                    });
                    return JsonResult.buildSuccessResult(null, userInfoMap);
                } else {
                    return JsonResult.buildSuccessResult(null, null);
                }
            }
            return JsonResult.buildErrorStateResult("uuid单次批量查询不可超过500个手机号", null);
        }
        return JsonResult.buildErrorStateResult("uuid批量传入用户手机号不可为空", null);
    }

    /**
     * 保存用户信息，地址信息，联系人信息
     *
     * @author v0.2 jinsong.zhu 2018年05月16日14:22:13
     * 保存用户信息，其中地址信息和联系人信息为非必要条件；注意：如果同时提供了province和address字段则需要保证Address的完整
     */
    @RequestMapping("/user/save_multi")
    @ApiOperation(value = "保存多种用户信息", httpMethod = "POST")
    public JsonResult saveMulti(String registeredFrom, String channelId, String phoneNo, String name, String idNo, String provinceCode, String province, String cityCode, String city, String districtCode, String district, String address, String contacts, @RequestParam(defaultValue = "0", required = false) Long btRegisterChannelId) {
        if (!NumberUtils.isDigits(registeredFrom)) {
            return JsonResult.buildErrorStateResult("注册渠道异常.", null);
        }
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            return JsonResult.buildErrorStateResult("手机号异常.", null);
        }
        if (StringUtils.isBlank(name)) {
            return JsonResult.buildErrorStateResult("用户名异常.", null);
        }
        boolean isIdCard;
        try {
            isIdCard = idCardService.isIdCardValid(idNo);
        } catch (ParseException e) {
            isIdCard = false;
        }
        if (!isIdCard) {
            return JsonResult.buildErrorStateResult("用户身份证号异常.", null);
        }
        Address addressObj = null;
        //地址信息,同时存在provinceCode和address时才校验地址信息
        if (StringUtils.isNotEmpty(provinceCode) && StringUtils.isNotEmpty(address)) {
            if (!NumberUtils.isDigits(provinceCode)) {
                return JsonResult.buildErrorStateResult("省份编号异常.", null);
            }
            if (StringUtils.isBlank(province)) {
                return JsonResult.buildErrorStateResult("省份异常.", null);
            }
            if (!NumberUtils.isDigits(cityCode)) {
                return JsonResult.buildErrorStateResult("城市编号异常.", null);
            }
            if (StringUtils.isBlank(city)) {
                return JsonResult.buildErrorStateResult("城市异常.", null);
            }
            if (!NumberUtils.isDigits(districtCode)) {
                return JsonResult.buildErrorStateResult("区县编号异常.", null);
            }
            if (StringUtils.isBlank(district)) {
                return JsonResult.buildErrorStateResult("区县异常.", null);
            }
            if (StringUtils.isBlank(address)) {
                return JsonResult.buildErrorStateResult("详细地址异常.", null);
            }
            addressObj = new Address();
            addressObj.setProvinceCode(Long.valueOf(provinceCode));
            addressObj.setProvince(province);
            addressObj.setCityCode(Long.valueOf(cityCode));
            addressObj.setCity(city);
            addressObj.setDistrictCode(Long.valueOf(districtCode));
            addressObj.setDistrict(district);
            addressObj.setAddress(address);
        }
        channelId = MoreObjects.firstNonNull(channelId, "-1");
        // 验证用户是否已存在
        User user = userService.findByPhoneInDb(phoneNo);
        if (null != user) {
            return JsonResult.buildErrorStateResult("用户已存在，手机号被占用", null);
        }
        List<Contact> contactList = null;
        // 目前对空白字符串和null值容错，因是api调用，不考虑对非法格式容错（会阻断注册）
        if (StringUtils.isNotBlank(contacts)) {
            contactList = JSONObject.parseObject(contacts, new TypeReference<List<Contact>>() {
            });
            for (Contact contact : contactList) {
                contact.setRelation(contact.getRelation() == null ? Relation.OTHER : contact.getRelation());
                Tuple<String, Boolean> stringBooleanTuple = contact.validAndResult();
                if (!stringBooleanTuple.getValue()) {
                    return JsonResult.buildErrorStateResult(stringBooleanTuple.getKey(), null);
                }
            }
        }
        user = userRegisterService.register(Long.valueOf(registeredFrom), Long.valueOf(channelId), phoneNo, name, idNo, addressObj, contacts, contactList, btRegisterChannelId);
        UserRet userRet = new UserRet(user);
        return JsonResult.buildSuccessResult(null, userRet);
    }

    //根据日期时间段查询新注册用户信息并返回
    @RequestMapping("/contract/queryRegisterUsers")
    @ApiOperation(value = "根据日期时间段查询新注册用户信息并返回", httpMethod = "POST")
    public JsonResult findRegisterUserByTime(String beginTime, String endTime) {
        if (null == beginTime || endTime == null) {
            return JsonResult.buildErrorStateResult(null, null);
        }
        List<User> userList = userService.findRegisterUserByTime(beginTime, endTime);
        return JsonResult.buildSuccessResult(null, userList);
    }

    /**
     * 技术网关 - 验证手机号和验证码是否匹配
     * 仅供可信任的内部服务调用，不执行限次记数、销毁等安全策略
     * 注意：只使用于快速登录类似场景调用
     *
     * @param phoneNo          手机号
     * @param verificationCode 验证码（短信/语音）
     * @return
     */
    @RequestMapping("/verifyPhoneAndCode")
    @ApiOperation(value = "验证手机号和验证码是否匹配", httpMethod = "POST")
    public JsonResult verifyPhoneAndCode(
            @RequestParam String phoneNo, @RequestParam String verificationCode,
            @RequestParam String appChannel,
            @RequestParam(required = false, defaultValue = "1") Long channelId,
            @RequestParam(required = false, defaultValue = "1") Long createdFrom,
            @RequestParam(required = false, defaultValue = "xyqb") String key,
            @RequestParam(required = false) Long btRegisterChannelId,
            @RequestParam(required = false) String dimension) {
        log.info("/innerapi/verifyPhoneAndCode -> phoneNo:{},verificationCode:{},channelId:{},appChannel:{},createdFrom:{},btRegisterChannelId:{},key:{},dimension:{}", phoneNo, verificationCode, channelId, appChannel, createdFrom, btRegisterChannelId, key, dimension);
        // 验证接入方
        Merchant merchant = merchantService.findMerchantByName(key);
        if (merchant == null) {
            return JsonResult.buildErrorStateResult("未知的连接", null);
        }
        // 验证手机号
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            log.info("验证手机号和验证码是否匹配，手机号错误, phoneNo:{}", phoneNo);
            return JsonResult.buildErrorStateResult("手机号错误", "");
        }
        // 验证短信验证码
        if (!smsService.verifyPhoneAndCode(phoneNo, verificationCode)) {
            // 是否需要重新获取
            if (smsService.needResendCode(phoneNo)) {
                return JsonResult.buildErrorStateResult("验证码失效，请重新获取", "");
            }
            log.info("验证码校验失败,phoneNo:{} , verificationCode:{}", phoneNo, verificationCode);
            return JsonResult.buildErrorStateResult("短信验证码错误", "");
        }
        User user = userService.findByPhoneWithCache(phoneNo);
        // 检查用户有效性
        if (user != null && !user.getEnable()) {
            log.error("用户不存在，或者已经注销，phoneNo:{}", phoneNo);
            return JsonResult.buildErrorStateResult("登录失败", null);
        }
        // 用户不存在时自动注册
        if (Objects.isNull(user)) {
            user = userRegisterService.register(phoneNo, channelId, createdFrom, appChannel, btRegisterChannelId, dimension);
            // 注册失败
            if (Objects.isNull(user)) {
                return JsonResult.buildErrorStateResult("用户不存在", "");
            }
        }
        // 推送老的登陆统计信息
        UserStatistics statistics = new UserStatistics(user, dimension, 3, channelId);
        MqUtils.sendLoanVest(statistics);
        return JsonResult.buildSuccessResult("校验成功", new UserRet(user));
    }

    @RequestMapping("/login")
    @ApiOperation(value = "登陆....怎么又一个...", httpMethod = "POST")
    public JsonResult login(@RequestParam String phoneNo, @RequestParam String password) {
        User user = checkPhoneNoAndPassword(phoneNo, password);
        if (user == null) {
            return JsonResult.buildErrorStateResult("用户名或密码不正确", null);
        }
        UserRet model = new UserRet(user);
        return JsonResult.buildSuccessResult("校验成功", model);
    }

    private User checkPhoneNoAndPassword(String phoneNo, String password) {
        byte[] byPhoneNo = Base64.decodeBase64(phoneNo);
        String bufPhoneNo = "";
        try {
            bufPhoneNo = new String(byPhoneNo, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("不支持的编码: ", e);
        }
        if (!ValidationUtil.validatePhoneNo(bufPhoneNo)) {
            log.info("手机号错误, phoneNo:{}", bufPhoneNo);
            throw new UserNotExistException("手机号错误");
        }
        byte[] byPassword = Base64.decodeBase64(password);
        String bufPassword = "";
        try {
            bufPassword = new String(byPassword, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("不支持的编码: ", e);
        }
        User user = userService.findByPhoneWithCache(bufPhoneNo);
        if (Objects.isNull(user) || Objects.equals(Boolean.FALSE, user.getEnable())) {
            return null;
        }
        //验证密码
        if (!PasswordUtil.validatePassword(bufPassword, user.getPassword())) {
            return null;
        }
        return user;
    }

    /**
     * 查询用户是否存在
     *
     * @param phoneNo
     * @return
     */
    @RequestMapping("/user/enable")
    @ApiOperation(value = "查看用户是否存在,存在则返回id,uuid", httpMethod = "POST")
    public JsonResult isEnable(String phoneNo) {
        boolean flag = false;
        Map validMap = Maps.newHashMap();
        validMap.put("valid", flag);
        User user = userService.findByPhoneInDb(phoneNo);
        if (user != null && user.getEnable()) {
            validMap.put("valid", true);
            validMap.put("id", user.getId());
            validMap.put("uuid", user.getUuid());
            return JsonResult.buildSuccessResult("查询成功", validMap);
        }
        return JsonResult.buildErrorStateResult("用户不存在", validMap);
    }

    /**
     * 激活/启用 用户
     * -- 供内部系统免密调用
     *
     * @param userId
     * @return
     */
    @ApiOperation(notes = "激活/启用 用户 - 供内部系统免密调用", value = "激活/启用 用户", nickname = "activeUser")
    @RequestMapping(path = "/user/active", method = RequestMethod.POST)
    public JsonResult activeUser(Long userId) {
        boolean flushed = flushUserStatus(userId, true);
        log.info("激活/启用 用户,userId:[{}]，result:[{}]", userId, flushed);
        return JsonResult.buildSuccessResult("用户已激活.", flushed);
    }

    /**
     * 注销/禁用 用户
     * -- 供内部系统免密调用
     *
     * @param userId
     * @return
     */
    @ApiOperation(notes = "注销/禁用 用户 - 供内部系统免密调用", value = "注销/禁用 用户", nickname = "forbiddenUser")
    @RequestMapping(path = "/user/disable", method = RequestMethod.POST)
    public JsonResult forbiddenUser(Long userId) {
        boolean flushed = flushUserStatus(userId, false);
        log.info("注销/禁用 用户,userId:[{}]，result:[{}]", userId, flushed);
        return JsonResult.buildSuccessResult("用户已禁用.", flushed);
    }

    /**
     * 重置用户实名信息接口
     * -- 供内部系统免密调用
     *
     * @param userId - 用户主键
     * @param idNo   - 身份证号
     * @param name   - 姓名
     */
    @ApiOperation(notes = "重置用户实名信息接口 - 供内部系统免密调用", value = "重置用户实名信息", nickname = "resetName")
    @RequestMapping(path = "/userDetail/reset/{userId}", method = RequestMethod.POST)
    public JsonResult resetName(@PathVariable("userId") Long userId, @RequestParam("name") String name, @RequestParam("idNo") String idNo) {
        log.info("重置用户实名信息 userId:{},name:{},idNo:{}", userId, name, idNo);
        if (Objects.nonNull(userId) && ValidationUtil.validateChinese(name)) {
            try {
                User user = userService.findById(userId);
                int rows = userDetailService.updateNameAndIdCard(name, idNo, user.getPhoneNo());
                log.info("重置用户实名信息,userId:{},name:{},idNo:{},result:[{}]", userId, name, idNo, rows);
                if (rows > 0) {
                    return JsonResult.buildSuccessResult("用户实名信息已重置.", name.concat(",").concat(idNo).concat(",").concat(userId.toString()));
                }
            } catch (Exception e) {
                log.error("用户实名信息重置失败,userId[{}],name[{}],err:[{}]", userId, name, e);
            }
            return JsonResult.buildErrorStateResult("用户实名信息重置失败.", "Error data");
        } else {
            return JsonResult.buildErrorStateResult("用户实名信息重置失败.", "Error data");
        }
    }

    /**
     * 重置密码接口
     * -- 供内部系统免密调用
     */
    @ApiOperation(notes = "重置密码接口 - 供内部系统免密调用", value = "重置密码", nickname = "resetPassword")
    @RequestMapping(path = "/user/password/reset", method = RequestMethod.POST)
    public JsonResult resetPassword(@RequestParam("phone") String phone, @RequestParam(required = false) String password) {
        if (ValidationUtil.validatePhoneNo(phone)) {
            // 默认重置的密码是123456
            if (StringUtils.isBlank(password)) {
                password = PasswordUtil.PASSWORD_DEFAULT;
            } else if (!ValidationUtil.validatePassword(password)) {
                return JsonResult.buildErrorStateResult(PasswordUtil.TOAST_MSG, null);
            }
            try {
                boolean result = userService.resetPassword(phone, password);
                log.info("重置用户密码,phoneNo:[{}]，password:[{}],result:[{}]", phone, password, result);
                if (result) {
                    return JsonResult.buildSuccessResult("用户密码已重置.", password);
                }
            } catch (Exception e) {
                log.error("密码重置失败,phone[{}]", phone, e);
            }
        }
        return JsonResult.buildErrorStateResult("用户密码重置失败.", phone);
    }

    /**
     * 修改实名信息接口
     * -- 供内部人员使用（例如绝影）
     */
    @RequestMapping("/updateIdCard")
    public JsonResult updateIdCard(@RequestParam(required = false) String name, @RequestParam(required = false) String idNo,
                                   @RequestParam String phoneNo, @RequestParam String reason, @RequestParam String content) {
        //密文
        if (null != content) {
            content = content.replaceAll(" ", "+");
        }
        String str = AesUtil.decryptAfterBase64Decode(content, Constants.AES_KEY);
        if (null == str || !str.equals(phoneNo)) {
            return JsonResult.buildErrorStateResult("解密有误", null);
        }
        if (StringUtils.isBlank(name) && StringUtils.isBlank(idNo)) {
            return JsonResult.buildErrorStateResult("不能全为空", null);
        }
        if (StringUtils.isBlank(phoneNo) || StringUtils.isBlank(reason)) {
            return JsonResult.buildErrorStateResult("参数有误", null);
        }
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            log.info("手机号有误, phoneNo:{}", phoneNo);
            return JsonResult.buildErrorStateResult("用户手机号错误", null);
        }
        log.info("重置用户实名信息 phoneNo:{},name:{},idNo:{}", phoneNo, name, idNo);
        try {
            int rows = userDetailService.updateNameAndIdCard(name, idNo, phoneNo);
            log.info("重置用户实名信息,phoneNo:{},name:{},idNo:{},result:[{}],操作的理由reason:{}", phoneNo, name, idNo, rows, reason);
            if (rows > 0) {
                return JsonResult.buildSuccessResult("用户实名信息已重置.", phoneNo.concat(",").concat(name).concat(",").concat(idNo));
            }
        } catch (Exception e) {
            log.error("用户实名信息重置失败,phoneNo[{}],name[{}],err:[{}]", phoneNo, name, e);
        }
        return JsonResult.buildErrorStateResult("用户实名信息重置失败.", "Error data");
    }

    /**
     * 重置密码接口
     * -- 供内部人员使用（例如绝影）
     */
    @RequestMapping("/user/password/reset/{key}/{phone}")
    public JsonResult resetPasswordByKey(@PathVariable("key") String key, @PathVariable("phone") String phone) {
        log.info("密码重置请求,phone:[{}]", phone);
        if (!RESET_PWD_TOKEN.equalsIgnoreCase(key)) {
            log.error("密码重置失败,key错误!@!,phone:[{}]", phone);
            return JsonResult.buildErrorStateResult("用户密码重置失败.", HttpStatus.SC_UNAUTHORIZED);
        }
        return resetPassword(phone, null);
    }

    /**
     * 注销/禁用 用户
     * -- 供内部人员使用（例如绝影）
     *
     * @param phoneNo
     * @param enable
     * @param reason
     * @param content
     * @return
     */
    @RequestMapping("/forbiddenUserOrNot")
    public JsonResult forbiddenUserOrNot(@RequestParam String phoneNo, @RequestParam Boolean enable,
                                         @RequestParam String reason, @RequestParam String content) {
        if (StringUtils.isBlank(phoneNo) || StringUtils.isBlank(reason)) {
            return JsonResult.buildErrorStateResult("参数有误", null);
        }
        //密文
        if (null != content) {
            content = content.replaceAll(" ", "+");
        }
        String str = AesUtil.decryptAfterBase64Decode(content, Constants.AES_KEY);
        if (null == str || !str.equals(phoneNo)) {
            log.info("[forbiddenUserOrNot][禁用或激活用户]：解密有误, phoneNo:{},aes:{}", phoneNo, AesUtil.encryptAndBase64Encode(phoneNo, Constants.AES_KEY));
            return JsonResult.buildErrorStateResult("解密有误", null);
        }
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            log.info("[forbiddenUserOrNot][禁用或激活用户]：用户手机号错误, phoneNo:{}", phoneNo);
            return JsonResult.buildErrorStateResult("用户手机号错误", null);
        }
        int affectedRows = userService.forbiddenUser(enable, phoneNo);
        // 清除缓存，解除微信关联
        User user = userService.findByPhoneInDb(phoneNo);
        if (null != user && !user.getEnable()) {
            sessionService.deleteByUserId(user.getId());
            sessionService.deleteUserCatch(user);
            // 禁用微信
            wechatService.forbiddenUserWeChat(user.getId());
        }
        log.info("[forbiddenUserOrNot][禁用或激活用户]：理由：reason:{},手机号phoneNo:{},受影响的行数affectedRows:{}", reason, phoneNo, affectedRows);
        return JsonResult.buildSuccessResult("用户禁用或重启成功成功", affectedRows);
    }

    /**
     * 清除微信关联
     * -- 供内部人员使用（例如绝影）
     *
     * @param userId
     * @param reason
     * @param content
     * @return
     */
    @RequestMapping("/forbiddenUserWeChat")
    public JsonResult forbiddenUserWeChat(@RequestParam Long userId, @RequestParam String reason, @RequestParam String content) {
        if (StringUtils.isBlank(reason) || null == userId || userId <= 0) {
            return JsonResult.buildErrorStateResult("参数有误", null);
        }
        //密文
        if (null != content) {
            content = content.replaceAll(" ", "+");
        }
        String str = AesUtil.decryptAfterBase64Decode(content, Constants.AES_KEY);
        if (null == str || !str.equals(userId.toString())) {
            return JsonResult.buildErrorStateResult("解密有误", null);
        }
        int affectedRows = wechatService.forbiddenUserWeChat(userId);
        log.info("用户微信禁用,userId:{},禁用的原因reason:{},受影响的行数affectedRows:{}", userId, reason, affectedRows);
        return JsonResult.buildSuccessResult("用户禁用微信成功", affectedRows);
    }

//    @RequestMapping("/clean")
    public JsonResult clean(){
        cleanDataService.cleanData();
       return  JsonResult.buildSuccessResult("已经开始清洗");
    }

    /**
     * 注销用户
     *
     * @param userId 用户id
     * @return 销户结果
     */
    @ApiOperation(value = "注销用户", httpMethod = "GET", notes="注销用户")
    @GetMapping("/user/delete/{userId}")
    public JsonResult<?> deregister(@NotNull @PathVariable("userId") Long userId) {

        log.info("用户id[{}], 销户开始", userId);

        /* 执行销户 */
        userService.deregister(userId);

        log.info("用户id[{}], 销户成功", userId);
        return JsonResult.buildSuccessResult("用户销户成功", true);
    }


    /**
     * 测试定时删除注销记录
     * @return
     */
    @PostMapping("/user/deleteDeregister")
    public JsonResult<?> deleteDeregister() {
        userDeregisterService.executeTask();
        return JsonResult.buildSuccessResult("用户销户成功1231" , true);
    }
}