/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.service.sysmgr.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import com.crunii.micro.common.constants.CommonConstants;
import com.crunii.micro.common.constants.LockConstants;
import com.crunii.micro.common.constants.RedisKeys;
import com.crunii.micro.common.dto.Result;
import com.crunii.micro.common.exception.BusinessException;
import com.crunii.micro.common.utils.AESUtil;
import com.crunii.micro.common.utils.JsonUtil;
import com.crunii.micro.common.utils.NanoIdUtil;
import com.crunii.micro.common.utils.RSAUtil;
import com.crunii.micro.domain.dto.CallHolder;
import com.crunii.micro.domain.dto.LoginInfoDto;
import com.crunii.micro.domain.dto.Worker;
import com.crunii.micro.domain.entity.main.*;
import com.crunii.micro.domain.repo.main.*;
import com.crunii.micro.feign.client.InterService;
import com.crunii.micro.feign.client.ObsService;
import com.crunii.micro.feign.client.SmsService;
import com.crunii.micro.feign.client.dto.*;
import com.crunii.micro.platform.components.Locker;
import com.crunii.micro.service.sysmgr.constants.SysmgrConstants;
import com.crunii.micro.service.sysmgr.convert.SysmgrRoleConvert;
import com.crunii.micro.service.sysmgr.convert.SysmgrUrlConvert;
import com.crunii.micro.service.sysmgr.dto.sysmgr.*;
import com.crunii.micro.service.sysmgr.enums.WorkerTypeEnum;
import com.crunii.micro.service.sysmgr.jdbc.WorkerJdbcDao;
import com.crunii.micro.service.sysmgr.service.AuthService;
import com.crunii.micro.service.sysmgr.service.LoginService;
import com.crunii.micro.service.sysmgr.service.ThemeService;
import com.crunii.micro.service.sysmgr.service.WorkerService;
import com.crunii.micro.service.sysmgr.utils.HttpClientUtil;
import com.crunii.micro.service.sysmgr.utils.IPUtils;
import com.google.common.base.Stopwatch;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.crunii.micro.service.sysmgr.constants.ErrorMsgConstants.*;
import static com.crunii.micro.service.sysmgr.constants.SysmgrConstants.*;

/**
 * @author 田平 create 2018年10月31日上午10:20:55
 */
@Slf4j
@Service
@RefreshScope
public class LoginServiceImpl implements LoginService {

	@Autowired
	private RedissonClient redisson;

	@Autowired
	private TsmWorkerRepo tsmWorkerRepo;

	@Resource
	private TsmWorkerLogoutRepo tsmWorkerLogoutRepo;

	@Autowired
	private AuthService authService;

	@Autowired
	private WorkerService workerService;

	@Autowired
	private YcqLeagueRepo ycqLeagueRepo;

	@Autowired
	private Locker locker;

	@Resource
	private TsmPropertyValueRepo tsmPropertyValueRepo;

	@Resource
	private SmsService smsService;

	@Resource
	private LoginCacheService loginCacheService;

	@Resource
	private TsmLoginLogRepo loginLogRepo;

	@Autowired
	private TsmAppButtonRepo buttonRepo;

	@Autowired
	private TsmOrgRepo tsmOrgRepo;

	@Autowired
	private ObsService obsService;

	@Autowired
	private ThemeService themeService;

	@Autowired
	private WorkerJdbcDao workerJdbcDao;

	@Autowired
	private InterService interService;

	@Value("${upload.url}")
	private String urlPrefix;

	@Value("${spring.application.name}")
	private String appName;

	private final static String IP_INFO_API = "https://api.vore.top/api/IPdata?ip=";

	private CopyOnWriteArrayList<TsmLoginLog> logCache = new CopyOnWriteArrayList<>();
	/**
	 * // 单位秒 验证码有效期
	 */
	@Value("${captcha.inactive.timeout:120}")
	public int captchaInactiveTimeout = 120;


	/**
	 * // 单位分钟 web端token过期时间
	 */
//	@Value("${token.web.inactive.timeout:10080}")
	public int tokenWebInactiveTimeout = 0;

	public int tokenExpireTimeout = 24*7;

	/**
	 * // 单位小时 手机token过期时间
	 */
//	@Value("${token.phone.inactive.timeout:168}")
	public int tokenPhoneInactiveTimeout = 0;

	/**
	 * // 最大密码登录错误次数，超过后单位时间内不能登录
	 */
	@Value("${login.pwd.max.error.times:5}")
	public int loginPwdMaxErrorTimes = 5;

	/**
	 * // 单位小时 登录密码错误后多少时间不能从新登录
	 */
	@Value("${login.pwd.max.error.period:5}")
	public int loginPwdMaxErrorPeriod = 1;

	@Resource
	private SysmgrRoleConvert sysmgrRoleConvert;
	
	@Resource
	private SysmgrUrlConvert sysmgrUrlConvert;

	@Value("Ug8AD9CysRJ3CmWu1eL+chKIZLxVEbHVBLPqA6rh/jc=")
	public String ykzAppKey;

	@Value("HyvnnAu2rZbmpHhcs53c5avlwUvR6knhL31RP58n7FLIk2yKQ3S2wi/UXEEN7jbDr8xNTJd7Md+ZEkWVzmB7rw==")
	public String ykzAppSecret;

	@Value("${ykb.token:1}")
	public String ykzTokenUrl;

	@Value("${ykb.userDetail:test}")
	public String ykzUrl;


	@Override
	public void storeCaptchaKeyId(String key, String code) {
		RMapCache<String, String> captchaMap = redisson.getMapCache(RedisKeys.CAPTCHA_MAP_KEY);
		captchaMap.put(key, code, captchaInactiveTimeout, TimeUnit.SECONDS);
	}

	@Override
	public String removeCaptchaKeyId(String key) {
		RMapCache<String, String> captchaMap = redisson.getMapCache(RedisKeys.CAPTCHA_MAP_KEY);
		String code = captchaMap.remove(key);
		return code;
	}

	@Override
	public Worker doLogin(HttpServletRequest request,String username, String password, String scope, String callUrl) {
		TsmLoginLog tsmLoginLog = new TsmLoginLog();
		tsmLoginLog.setMobile(username);
		tsmLoginLog.setClientType(scope);
		tsmLoginLog.setLoginType(CommonConstants.LOGIN_TYPE_PASSWORD);

		Stopwatch started = Stopwatch.createStarted();
		RMapCache<Integer, Integer> loginErrorMap = redisson.getMapCache(RedisKeys.LOGIN_PWD_ERROR_KEY);
		String realPwd = "";
		try {
			realPwd = RSAUtil.decryptHex(password);
		}catch (Exception e){
			tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_FAIL_ERR_DECRYPTION);
			saveLoginLog(request,tsmLoginLog);
			throw e;
		}

		log.info("log 1 -->{}ms",started.stop().elapsed(TimeUnit.MILLISECONDS));

		started.reset().start();
		Worker worker = new Worker();
		Map<String, Object> props = new HashMap<>(2);
		props.put("mobile", username);
		TsmWorker curr=null;
		if (CommonConstants.SCOPE_WEB.equals(scope)){
			List<TsmWorker> workers = tsmWorkerRepo.findByMobileOrCode(username);
			if (workers.size()>1){
				throw new BusinessException(WORKER_DATE_ERROR);
			}
			if (workers.size()==1){
				curr = workers.get(0);
			}
		}else {
			curr = tsmWorkerRepo.getUniqueByProps(props);
		}
		//该用户是否拥有当前客户端的登录权限
		if (ObjectUtil.isNotEmpty(curr) && CommonConstants.SCOPE_WEB.equals(scope) && WorkerTypeEnum.APP.getType().equals(curr.getWorkerType())){
			throw new BusinessException(WORKER_LOGIN_OUT_SCOPE);
		}
		log.info("log 2 -->{}ms",started.stop().elapsed(TimeUnit.MILLISECONDS));
		if (curr == null || curr.getState() == 0 || curr.getState() == 2) {
			tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_FAIL_INVALID_USER);
			saveLoginLog(request,tsmLoginLog);
			throwNewException(curr);
		} else if (loginErrorMap.getOrDefault(curr.getLoginId(), 0) >= loginPwdMaxErrorTimes) {
			// throw new BusinessException("密码输入错误超过{}次，请{}小时后再登录！", loginPwdMaxErrorTimes, loginPwdMaxErrorPeriod);
			curr.setState(2);
			tsmWorkerRepo.saveAndFlush(curr);
			throw new BusinessException("账号或密码不正确，账号已锁定，请联系管理员找回密码");
		} else {
			started.reset().start();
			if (curr.getLoginPwd().equals(AESUtil.encrypt(realPwd))) {
				String token = NanoIdUtil.randomLowerNanoId();
				BeanUtils.copyProperties(curr, worker);
				worker.setScope(scope);
				// 设置组织名称
				if(StringUtils.hasText(curr.getOrgId())){
					TsmOrg one = tsmOrgRepo.getOne(Long.valueOf(curr.getOrgId()));
					if (ObjectUtil.isNotEmpty(one)){
						worker.setOrgName(one.getOrgName());
					}
				}
				//设置部门
				worker.setDepartment(curr.getDepartment());
				//设置当前用户
				CallHolder callInfo = CallHolder.initialCall();
				callInfo.setCallUrl(callUrl);
				callInfo.setToken(worker.getToken());
				callInfo.setWorker(worker);

				//登录成功后清空登录错误记录 && 设置当前登录时间
				loginErrorMap.remove(curr.getLoginId());
				curr.setLoginNum(curr.getLoginNum() + 1);
				curr.setLastLoginTime(new Date());
//				tsmWorkerRepo.save(curr);
//				tsmWorkerRepo.flush();
				log.info("log 3 -->{}ms",started.stop().elapsed(TimeUnit.MILLISECONDS));
				//设置超级管理员，超级管理员不会返回urls
				if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())) {
					worker.setSuperWorker(true);
				}
				// token对应的worker的json字符串，么有使用java对象的原因在用，几乎所有的项目都会用到当前登录用户，
				// 为了保证，当worker修改了添加了属性，只需要发布相关使用到这些属性的项目即可，不会因为序列化的问题，发布所有依赖了worker的项目
				RMapCache<String, String> tokenMap = null;
				started.reset().start();
				if (CommonConstants.SCOPE_WEB.equals(scope)) {
					token = CommonConstants.SCOPE_WEB + "_" + token;
					worker.setToken(token);
					// 设置角色和菜单权限
					worker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(worker.getLoginId())));
					log.info("log 4 -->{}ms",started.stop().elapsed(TimeUnit.MILLISECONDS));
					started.reset().start();
					worker.setTreeNodes(authService.getAuthTreeNode(worker.getLoginId()));
					log.info("log 5 -->{}ms",started.stop().elapsed(TimeUnit.MILLISECONDS));
					started.reset().start();
					worker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(worker.getLoginId())));
					log.info("log 6 -->{}ms",started.stop().elapsed(TimeUnit.MILLISECONDS));
					started.reset().start();
					//2024年1月3日10:10:21 前端指定需要的参数
					findAuthButton(worker);
					log.info("log 8 -->{}ms",started.stop().elapsed(TimeUnit.MILLISECONDS));
					started.reset().start();
					tokenMap = redisson.getMapCache(RedisKeys.TOKEN_WEB_KEY);
					tokenMap.put(token, JsonUtil.getJsonString(worker), tokenExpireTimeout, TimeUnit.HOURS, tokenWebInactiveTimeout, TimeUnit.MINUTES);
					log.info("log 7 -->{}ms",started.stop().elapsed(TimeUnit.MILLISECONDS));
				} else if (CommonConstants.SCOPE_PHONE.equals(scope)) {
					token = CommonConstants.SCOPE_PHONE + "_" + token;
					worker.setToken(token);
					// 设置角色和菜单权限
					worker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(worker.getLoginId())));
					worker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(worker.getLoginId())));
					if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())){
						List<TsmAppButton> all = buttonRepo.findAll();
						List<String> collect = all.stream().filter(btn -> StringUtils.hasText(btn.getCode())).map(item -> item.getCode()).distinct()
								.collect(Collectors.toList());
						worker.setAuthButtons(collect);
					}else{
						worker.setAuthButtons(authService.getBtnIds(worker));
					}
					tokenMap = redisson.getMapCache(RedisKeys.TOKEN_PHONE_KEY);
					locker.lock(LockConstants.LOCK_TOKEN, worker.workerId());
					try {
						RMapCache<Integer, List<String>> tokenUserMap = redisson.getMapCache(RedisKeys.PHONE_TOKEN_USER_MAP_KEY);
						List<String> oldTokens = tokenUserMap.get(worker.getLoginId());
						if (oldTokens != null) {
							if (oldTokens.size() + 1 > CommonConstants.MAX_PHONE_CLIENT) {
								String oldToken = oldTokens.remove(0);
								tokenMap.remove(oldToken);
							}
						} else {
							oldTokens = new ArrayList<String>(CommonConstants.MAX_PHONE_CLIENT);
						}
						oldTokens.add(token);
						tokenUserMap.put(worker.getLoginId(), oldTokens);
					} finally {
						locker.unlock();
					}
					tokenMap.put(token, JsonUtil.getJsonString(worker), 0, TimeUnit.HOURS, tokenPhoneInactiveTimeout, TimeUnit.HOURS);
				}else if(CommonConstants.SCOPE_SSO.equals(scope)){
					// 如果是SSO过来的参数，都不用做，参数已经够用了
					//todo 2024年1月3日10:08:13 加上前端需要的参数
					findAuthButton(worker);
				} else {
					tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_FAIL_ERR_SCOPE);
					saveLoginLog(request,tsmLoginLog);
					throw new RuntimeException("not support this scope:" + scope);
				}
			} else {
				// 记录登录密码错误次数
				int loginErrorTimes = loginErrorMap.getOrDefault(curr.getLoginId(), 0);
				loginErrorMap.put(curr.getLoginId(), loginErrorTimes + 1, loginPwdMaxErrorPeriod, TimeUnit.HOURS);
				tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_FAIL_ERR_PWD);
				saveLoginLog(request,tsmLoginLog);
				throw new BusinessException("手机号或者密码错误！");
			}
		}
		if(StringUtils.hasText(curr.getHeadPortraitPicKey())){
			worker.setFileUrl(urlPrefix+curr.getHeadPortraitPicKey());
		}
		//前端不需要所有的url权限,不用返回前端，减少返回数据
//		worker.setUrls(null);
        // 2023-06-16 baoxy 业务扩充:根据用户的密码最后修改时间与当前时间进行比较,若超过了90天,需要提示用户进行密码的修改
//		checkPasswordExpire(worker);
		// 设置主题
		worker.setSysTheme(themeService.findThem());
		tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_SUCCESS);
		saveLoginLog(request,tsmLoginLog);
		//是否需要签名
		TsmWorker reviewerWorker = tsmWorkerRepo.findByReviewerId(worker.getLoginId());
		worker.setSign(ObjectUtil.isEmpty(reviewerWorker));
		return worker;
	}

	public void saveLoginLog(HttpServletRequest request,TsmLoginLog loginLog){
		String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
		loginLog.setUserAgent(userAgent);
		loginLog.setLoginTime(new Date());
		String clientIP = ServletUtil.getClientIP(request);
		if(StringUtils.hasText(clientIP)){
			loginLog.setIp(clientIP);
			//getIpInfo(clientIP,loginLog);
		}
		UserAgent parse = UserAgentUtil.parse(userAgent);
		String os = parse.getOs().toString();
		loginLog.setOs(os);
		logCache.add(loginLog);
		loginLogRepo.saveAndFlush(loginLog);
	}

	/**
	 *
	 * @param ipAddr 客户端ip地址
	 * @param loginLog 登录日志对象
	 */
	public void getIpInfo(String ipAddr,TsmLoginLog loginLog){
		String pash = IPUtils.getIpRegion(ipAddr);
		if(pash.contains("中国")){
			pash = pash.substring(2);
			loginLog.setIpLocation(pash);
			loginLog.setCnIp(CommonConstants.STATE_YES);
		} else{
			loginLog.setCnIp(CommonConstants.STATE_NO);
		}
	}


	/**
	 * 抛出异常curr状态
	 * @param curr curr
	 */
	private void throwNewException(TsmWorker curr) {
		if(Objects.isNull(curr)){
			throw new BusinessException("手机号失效");
		}
		if(curr.getState() == 0){
			throw new BusinessException("手机号无效");
		}
		if(curr.getState() == 2){
			throw new BusinessException("手机号已被锁定");
		}
	}

	@Override
	public Worker doRefreshToken(Worker currWorker) {
		Worker newWorker = new Worker();
		TsmWorker curr = tsmWorkerRepo.getOne(currWorker.getLoginId());
		String token = currWorker.getScope() + "_" + NanoIdUtil.randomLowerNanoId();

		BeanUtils.copyProperties(curr, newWorker);
		newWorker.setToken(token);
		newWorker.setScope(currWorker.getScope());
		newWorker.setDepartment(curr.getDepartment());

		//设置超级管理员，超级管理员不会返回urls
		if (CommonConstants.SUPER_LOGIN_IDS.contains(newWorker.getLoginId())) {
			newWorker.setSuperWorker(true);
		}
		//设置组织
		if(StringUtils.hasText(newWorker.getOrgId())){
			TsmOrg one = tsmOrgRepo.getOne(Long.valueOf(newWorker.getOrgId()));
			if (ObjectUtil.isNotEmpty(one)){
				newWorker.setOrgName(one.getOrgName());
			}
		}
		// token对应的worker的json字符串，么有使用java对象的原因在用，几乎所有的项目都会用到当前登录用户，
		// 为了保证，当worker修改了添加了属性，只需要发布相关使用到这些属性的项目即可，不会因为序列化的问题，发布所有依赖了worker的项目
		RMapCache<String, String> tokenMap;
		if (CommonConstants.SCOPE_WEB.equals(newWorker.getScope())) {
			// 设置角色和菜单权限
			newWorker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(newWorker.getLoginId())));
			newWorker.setTreeNodes(authService.getAuthTreeNode(newWorker.getLoginId()));
			newWorker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(newWorker.getLoginId())));
			findAuthButton(newWorker);
			tokenMap = redisson.getMapCache(RedisKeys.TOKEN_WEB_KEY);
			tokenMap.remove(currWorker.getToken());
			tokenMap.put(token, JsonUtil.getJsonString(newWorker), tokenExpireTimeout, TimeUnit.HOURS, tokenWebInactiveTimeout, TimeUnit.MINUTES);

		} else if (CommonConstants.SCOPE_PHONE.equals(newWorker.getScope())) {
			tokenMap = redisson.getMapCache(RedisKeys.TOKEN_PHONE_KEY);
			// 设置角色和菜单权限
			newWorker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(curr.getLoginId())));
			newWorker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(curr.getLoginId())));
			if (CommonConstants.SUPER_LOGIN_IDS.contains(newWorker.getLoginId())){
				List<TsmAppButton> all = buttonRepo.findAll();
				List<String> collect = all.stream().filter(btn -> StringUtils.hasText(btn.getCode())).map(item -> item.getCode()).distinct()
						.collect(Collectors.toList());
				newWorker.setAuthButtons(collect);
			}else{
				newWorker.setAuthButtons(authService.getBtnIds(newWorker));
			}
//			newWorker.setAuthButtons(collect);
			locker.lock(LockConstants.LOCK_TOKEN, newWorker.workerId());
			try {
				RMapCache<Integer, List<String>> tokenUserMap = redisson.getMapCache(RedisKeys.PHONE_TOKEN_USER_MAP_KEY);
				List<String> oldTokens = tokenUserMap.get(newWorker.getLoginId());
				if (oldTokens == null) {
					oldTokens = new ArrayList<>(CommonConstants.MAX_PHONE_CLIENT);
				} else {
					oldTokens.remove(currWorker.getToken());
				}
				oldTokens.add(token);
				tokenUserMap.put(newWorker.getLoginId(), oldTokens);
			} finally {
				locker.unlock();
			}
			tokenMap.put(token, JsonUtil.getJsonString(newWorker), 0, TimeUnit.DAYS, tokenPhoneInactiveTimeout, TimeUnit.HOURS);
			tokenMap.remove(currWorker.getToken());
		} else {
			throw new RuntimeException("not support this scope:" + newWorker.getScope());
		}
		if(StringUtils.hasText(curr.getHeadPortraitPicKey())){
			newWorker.setFileUrl(urlPrefix+curr.getHeadPortraitPicKey());
		}
		//前端不需要所有的url权限,不用返回前端，减少返回数据
//		newWorker.setUrls(null);
		// 设置组织名称
//		YcqLeague byLeagueCode = ycqLeagueRepo.findByLeagueCode(newWorker.getOrgId());
//		if(byLeagueCode!=null) {
//			newWorker.setOrgName(byLeagueCode.getLeagueFullName());
//		}
		// 设置主题
		newWorker.setSysTheme(themeService.findThem());
		//是否需要签名
		TsmWorker reviewerWorker = tsmWorkerRepo.findByReviewerId(curr.getLoginId());
		newWorker.setSign(ObjectUtil.isEmpty(reviewerWorker));
		return newWorker;
	}

	@Override
	public Worker getUserInfo(Worker currWorker){
//		Worker newWorker = new Worker();
//		TsmWorker curr = tsmWorkerRepo.getOne(currWorker.getLoginId());
//		BeanUtils.copyProperties(curr, newWorker);
//		newWorker.setToken(currWorker.getToken());
//		newWorker.setScope(currWorker.getScope());
//
//		//设置超级管理员，超级管理员不会返回urls
//		if (CommonConstants.SUPER_LOGIN_IDS.contains(newWorker.getLoginId())) {
//			newWorker.setSuperWorker(true);
//		}
//		// token对应的worker的json字符串，么有使用java对象的原因在用，几乎所有的项目都会用到当前登录用户，
//		// 为了保证，当worker修改了添加了属性，只需要发布相关使用到这些属性的项目即可，不会因为序列化的问题，发布所有依赖了worker的项目
//		RMapCache<String, String> tokenMap;
//		if (CommonConstants.SCOPE_WEB.equals(newWorker.getScope())) {
//			// 设置角色和菜单权限
//			newWorker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(newWorker.getLoginId())));
//			newWorker.setTreeNodes(authService.getAuthTreeNode(newWorker.getLoginId()));
//			newWorker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(newWorker.getLoginId())));
//
//		} else if (CommonConstants.SCOPE_PHONE.equals(newWorker.getScope())) {
//			// 设置角色和菜单权限
//			newWorker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(curr.getLoginId())));
//			newWorker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(curr.getLoginId())));
//			if (CommonConstants.SUPER_LOGIN_IDS.contains(newWorker.getLoginId())){
//				List<TsmAppButton> all = buttonRepo.findAll();
//				List<String> collect = all.stream().filter(btn -> StringUtils.hasText(btn.getCode())).map(item -> item.getCode()).distinct()
//						.collect(Collectors.toList());
//				newWorker.setAuthButtons(collect);
//			}else{
//				newWorker.setAuthButtons(authService.getBtnIds(newWorker));
//			}
////			newWorker.setAuthButtons(collect);
//		} else {
//			throw new RuntimeException("not support this scope:" + newWorker.getScope());
//		}
//		if(StringUtils.hasText(curr.getHeadPortraitPicKey())){
//			newWorker.setFileUrl(urlPrefix+curr.getHeadPortraitPicKey());
//		}
//		//前端不需要所有的url权限,不用返回前端，减少返回数据
//		newWorker.setUrls(null);
//		// 设置组织名称
////		YcqLeague byLeagueCode = ycqLeagueRepo.findByLeagueCode(newWorker.getOrgId());
////		if(byLeagueCode!=null) {
////			newWorker.setOrgName(byLeagueCode.getLeagueFullName());
////		}
//		if(StringUtils.hasText(newWorker.getOrgId())){
//			newWorker.setOrgName(tsmOrgRepo.getOne(Long.valueOf(newWorker.getOrgId())).getOrgName());
//		}
//		// 设置主题
//		newWorker.setSysTheme(themeService.findThem());
		return currWorker;
	}

	@Override
	public void doLogout(String token) {
		if (StringUtils.hasText(token)) {
			RMapCache<String, String> tokenMap = null;
			if (token.startsWith(CommonConstants.SCOPE_WEB)) {
				tokenMap = redisson.getMapCache(RedisKeys.TOKEN_WEB_KEY);
				tokenMap.remove(token);
			} else if (token.startsWith(CommonConstants.SCOPE_PHONE)) {
				tokenMap = redisson.getMapCache(RedisKeys.TOKEN_PHONE_KEY);
				String workerJson = tokenMap.remove(token);
				RMapCache<Integer, List<String>> tokenUserMap = redisson.getMapCache(RedisKeys.PHONE_TOKEN_USER_MAP_KEY);
				if (StringUtils.hasText(workerJson)) {
					Worker worker = JsonUtil.jsonToObject(workerJson, Worker.class);
					List<String> tokens = tokenUserMap.get(worker.getLoginId());
					tokens.remove(token);
				}
			} else {
				return;
			}
		} else {
			return;
		}
	}

	@Override
	public void clearPwdError(int loginId) {
		redisson.getMapCache(RedisKeys.LOGIN_PWD_ERROR_KEY).remove(loginId);
	}

	@Override
	public String retrievePwd(String phoneNum, String shortMsgCode) {
		Map<String,Object> prop = new HashMap<>(4);
		prop.put("mobile",phoneNum);
		prop.put("state",1);
		TsmWorker uniqueByProps = tsmWorkerRepo.getUniqueByProps(prop);
		if(Objects.isNull(uniqueByProps)){
			throw new BusinessException("该账号不存在系统！");
		}
		String phoneCode = loginCacheService.removeSmsCode(phoneNum);
		if(!StringUtils.hasText(phoneCode)){
			throw new BusinessException("验证码已过期！");
		}
		if(!phoneCode.equalsIgnoreCase(shortMsgCode)){
			throw new BusinessException("手机验证码错误！");
		}
		// 返回加密id
		String encrypt = AESUtil.encrypt(uniqueByProps.getLoginCode() + System.currentTimeMillis());
		RMapCache<String, Integer> mapCache = redisson.getMapCache(RedisKeys.RETRIEVE_PWD_KEY);
		mapCache.put(encrypt,uniqueByProps.getLoginId(), SysmgrConstants.RETRIEVE_PWD_MINUTES,TimeUnit.MINUTES);
		return encrypt;
	}

	@Override
	public String updatePwd(String secretKey, String newPwd) {
		RMapCache<String, Integer> mapCache = redisson.getMapCache(RedisKeys.RETRIEVE_PWD_KEY);
		Integer loginId = mapCache.remove(secretKey);
		if(Objects.isNull(loginId)){
			throw new BusinessException("手机号校验失败！");
		}
		String s = RSAUtil.decryptHex(newPwd);
		String encrypt = AESUtil.encrypt(s);
		TsmWorker one = tsmWorkerRepo.getOne(loginId);
		one.setLoginPwd(encrypt);
        one.setLastChgPwdTime(new Date());
		tsmWorkerRepo.saveAndFlush(one);
		return "success";
	}

	@Override
	public String getPhoneCode(SmsCodeSend smsCodeSend) {
		String storeCaptcha = this.removeCaptchaKeyId(smsCodeSend.getCaptchaKey());
		if(!StringUtils.hasText(storeCaptcha)){
			throw new BusinessException("图形验证码已失效！");
		}
		if (!storeCaptcha.equalsIgnoreCase(smsCodeSend.getCaptcha())) {
			throw new BusinessException("图形验证码错误！");
		}
		String mobile = smsCodeSend.getMobile();
		Result<String> stringResult = smsService.smsCode(mobile);
		if(stringResult.isSuccess()){
			loginCacheService.putSmsCode(mobile,stringResult.getData());
			return "短信验证码下发成功!";
		}else {

			return stringResult.getMsg();
		}

	}

	@Override
	public String getPhoneCodeNoText(SmsCodeNoTextDTO smsCodeNoTextDTO) {
		Result<String> stringResult = smsService.smsCode(smsCodeNoTextDTO.getMobile());
		if(stringResult.isSuccess()){
			loginCacheService.putSmsCode(smsCodeNoTextDTO.getMobile(),stringResult.getData());
			return "短信验证码下发成功!";
		}else {

			return stringResult.getMsg();
		}
	}


	@Override
	public Worker loginMobile(HttpServletRequest request,LoginPhoneDto loginPhoneDto,String callUrl,boolean isRegister) {
		TsmLoginLog tsmLoginLog = new TsmLoginLog();
		tsmLoginLog.setMobile(loginPhoneDto.getMobile());
		tsmLoginLog.setClientType(loginPhoneDto.getScope());
		tsmLoginLog.setLoginType(CommonConstants.LOGIN_TYPE_SMS);
		validatedCode(loginPhoneDto,request,tsmLoginLog,isRegister);

		Worker worker = new Worker();
		Map<String, Object> props = new HashMap<>(2);
		props.put("mobile", loginPhoneDto.getMobile());
		TsmWorker curr = tsmWorkerRepo.getUniqueByProps(props);
		//该用户是否拥有当前客户端的登录权限
		if (ObjectUtil.isNotEmpty(curr) && CommonConstants.SCOPE_WEB.equals(loginPhoneDto.getScope()) && WorkerTypeEnum.APP.getType().equals(curr.getWorkerType())){
			throw new BusinessException(WORKER_LOGIN_OUT_SCOPE);
		}
		if(Objects.isNull(curr)){
			String s = MD5.create().digestHex(loginPhoneDto.getMobile(),"utf-8");
			loginCacheService.putPhoneRegister(s,loginPhoneDto.getMobile());
			worker.setFirstLogin(true);
			worker.setSecretKey(s);
			String id = request.getSession(true).getId();
			loginCacheService.putPhoneRegisterSession(id,id);
			return worker;
		}
		worker.setDepartment(curr.getDepartment());
		if (curr.getState() == 0 || curr.getState() == 2) {
			throwNewException(curr);
		}else {
			String token = NanoIdUtil.randomLowerNanoId();
			BeanUtils.copyProperties(curr, worker);
			worker.setScope(loginPhoneDto.getScope());
			//设置当前用户
			CallHolder callInfo = CallHolder.initialCall();
			callInfo.setCallUrl(callUrl);
			callInfo.setToken(worker.getToken());
			callInfo.setWorker(worker);

			curr.setLoginNum(curr.getLoginNum() + 1);
			curr.setLastLoginTime(new Date());

			tsmWorkerRepo.save(curr);
			tsmWorkerRepo.flush();
			//设置超级管理员，超级管理员不会返回urls
			if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())) {
				worker.setSuperWorker(true);
			}
			// token对应的worker的json字符串，么有使用java对象的原因在用，几乎所有的项目都会用到当前登录用户，
			// 为了保证，当worker修改了添加了属性，只需要发布相关使用到这些属性的项目即可，不会因为序列化的问题，发布所有依赖了worker的项目
			RMapCache<String, String> tokenMap = null;
			if (CommonConstants.SCOPE_WEB.equals(loginPhoneDto.getScope())) {
				token = CommonConstants.SCOPE_WEB + "_" + token;
				worker.setToken(token);
				// 设置角色和菜单权限
				worker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(worker.getLoginId())));
				worker.setTreeNodes(authService.getAuthTreeNode(worker.getLoginId()));
				worker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(worker.getLoginId())));
				//2024年1月3日10:10:21 前端指定需要的参数
				findAuthButton(worker);
				tokenMap = redisson.getMapCache(RedisKeys.TOKEN_WEB_KEY);
				tokenMap.put(token, JsonUtil.getJsonString(worker), tokenExpireTimeout, TimeUnit.HOURS, tokenWebInactiveTimeout, TimeUnit.MINUTES);
			} else if (CommonConstants.SCOPE_PHONE.equals(loginPhoneDto.getScope())) {
				token = CommonConstants.SCOPE_PHONE + "_" + token;
				worker.setToken(token);
				// 设置角色和菜单权限
				worker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(worker.getLoginId())));
				worker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(worker.getLoginId())));
				if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())){
					List<TsmAppButton> all = buttonRepo.findAll();
					List<String> collect = all.stream().filter(btn -> StringUtils.hasText(btn.getCode())).map(item -> item.getCode()).distinct()
							.collect(Collectors.toList());
					worker.setAuthButtons(collect);
				}else{
					worker.setAuthButtons(authService.getBtnIds(worker));
				}
				tokenMap = redisson.getMapCache(RedisKeys.TOKEN_PHONE_KEY);

				locker.lock(LockConstants.LOCK_TOKEN, worker.workerId());
				try {
					RMapCache<Integer, List<String>> tokenUserMap = redisson.getMapCache(RedisKeys.PHONE_TOKEN_USER_MAP_KEY);
					List<String> oldTokens = tokenUserMap.get(worker.getLoginId());
					log.info("oldTokenCount----------》{}",oldTokens);
					if (oldTokens != null) {
						if (oldTokens.size() + 1 > CommonConstants.MAX_PHONE_CLIENT) {
							String oldToken = oldTokens.remove(0);
							tokenMap.remove(oldToken);
						}
					} else {
						oldTokens = new ArrayList<String>(CommonConstants.MAX_PHONE_CLIENT);
					}
					oldTokens.add(token);
					tokenUserMap.put(worker.getLoginId(), oldTokens);
				} finally {
					locker.unlock();
				}
				tokenMap.put(token, JsonUtil.getJsonString(worker), 0, TimeUnit.DAYS, tokenPhoneInactiveTimeout, TimeUnit.HOURS);
			}else if(CommonConstants.SCOPE_SSO.equals(loginPhoneDto.getScope())){
				//todo 2024年1月3日10:08:13 加上前端需要的参数
				findAuthButton(worker);
			} else {
				tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_FAIL_ERR_SCOPE);
				saveLoginLog(request,tsmLoginLog);
				throw new RuntimeException("not support this scope:" + loginPhoneDto.getScope());
			}
		}
		if(StringUtils.hasText(curr.getHeadPortraitPicKey())){
			worker.setFileUrl(urlPrefix+curr.getHeadPortraitPicKey());
		}
		//前端不需要所有的url权限,不用返回前端，减少返回数据
//		worker.setUrls(null);
		// 2023-06-16 baoxy 业务扩充:根据用户的密码最后修改时间与当前时间进行比较,若超过了90天,需要提示用户进行密码的修改
//		checkPasswordExpire(worker);
		// 设置组织名称
//		YcqLeague byLeagueCode = ycqLeagueRepo.findByLeagueCode(worker.getOrgId());
//		if(byLeagueCode!=null) {
//			worker.setOrgName(byLeagueCode.getLeagueFullName());
//		}
		TsmOrg org = tsmOrgRepo.getOne(Long.valueOf(worker.getOrgId()));
		if(StringUtils.hasText(worker.getOrgId())&& ObjectUtil.isNotEmpty(org)){
			worker.setOrgName(org.getOrgName());
		}
		// 设置主题
		worker.setSysTheme(themeService.findThem());
		tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_SUCCESS);
		saveLoginLog(request,tsmLoginLog);
		return worker;
	}

	@Override
	public Worker loginFace(HttpServletRequest request, LoginCheckoutFaceDTO loginCheckoutFaceDTO) {
		TsmLoginLog tsmLoginLog = new TsmLoginLog();
		tsmLoginLog.setMobile(loginCheckoutFaceDTO.getMobile());
		tsmLoginLog.setClientType(CommonConstants.SCOPE_PHONE);
		tsmLoginLog.setLoginType(CommonConstants.LOGIN_TYPE_SMS);
		//面容校验
		Map<String, Object> p = new HashMap<>(2);
		p.put("mobile", loginCheckoutFaceDTO.getMobile());
		TsmWorker user = tsmWorkerRepo.getUniqueByProps(p);
		if (ObjectUtil.isEmpty(user)){
			throw new BusinessException("未找到手机号");
		}
		if(StringUtil.isBlank(user.getFaceKey())){
			throw new BusinessException("未开启指纹/面容ID登录");
		}else {
			if (!verifySignature(loginCheckoutFaceDTO.getSignature(),loginCheckoutFaceDTO.getPayload(),user.getFaceKey())) {
				throw new BusinessException("匹配失败");
			}
		}


		// 当前调用的url
		String callUrl = "/" + appName + request.getRequestURI();
		Worker worker = new Worker();
		Map<String, Object> props = new HashMap<>(2);
		props.put("mobile", loginCheckoutFaceDTO.getMobile());
		TsmWorker curr = tsmWorkerRepo.getUniqueByProps(props);
		if(Objects.isNull(curr)){
			String s = MD5.create().digestHex(loginCheckoutFaceDTO.getMobile(),"utf-8");
			loginCacheService.putPhoneRegister(s,loginCheckoutFaceDTO.getMobile());
			worker.setFirstLogin(true);
			worker.setSecretKey(s);
			String id = request.getSession(true).getId();
			loginCacheService.putPhoneRegisterSession(id,id);
			return worker;
		}
		worker.setDepartment(curr.getDepartment());
		if (curr.getState() == 0 || curr.getState() == 2) {
			throwNewException(curr);
		}else {
			String token = NanoIdUtil.randomLowerNanoId();
			BeanUtils.copyProperties(curr, worker);
			worker.setScope(CommonConstants.SCOPE_PHONE);
			//设置当前用户
			CallHolder callInfo = CallHolder.initialCall();
			callInfo.setCallUrl(callUrl);
			callInfo.setToken(worker.getToken());
			callInfo.setWorker(worker);

			curr.setLoginNum(curr.getLoginNum() + 1);
			curr.setLastLoginTime(new Date());
			tsmWorkerRepo.save(curr);
			tsmWorkerRepo.flush();
			//设置超级管理员，超级管理员不会返回urls
			if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())) {
				worker.setSuperWorker(true);
			}
			// token对应的worker的json字符串，么有使用java对象的原因在用，几乎所有的项目都会用到当前登录用户，
			// 为了保证，当worker修改了添加了属性，只需要发布相关使用到这些属性的项目即可，不会因为序列化的问题，发布所有依赖了worker的项目
			RMapCache<String, String> tokenMap = null;

				token = CommonConstants.SCOPE_PHONE + "_" + token;
				worker.setToken(token);
				// 设置角色和菜单权限
				worker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(worker.getLoginId())));
				worker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(worker.getLoginId())));
				if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())){
					List<TsmAppButton> all = buttonRepo.findAll();
					List<String> collect = all.stream().filter(btn -> StringUtils.hasText(btn.getCode())).map(TsmAppButton::getCode).distinct()
							.collect(Collectors.toList());
					worker.setAuthButtons(collect);
				}else{
					worker.setAuthButtons(authService.getBtnIds(worker));
				}
				tokenMap = redisson.getMapCache(RedisKeys.TOKEN_PHONE_KEY);

				locker.lock(LockConstants.LOCK_TOKEN, worker.workerId());
				try {
					RMapCache<Integer, List<String>> tokenUserMap = redisson.getMapCache(RedisKeys.PHONE_TOKEN_USER_MAP_KEY);
					List<String> oldTokens = tokenUserMap.get(worker.getLoginId());
					if (oldTokens != null) {
						if (oldTokens.size() + 1 > CommonConstants.MAX_PHONE_CLIENT) {
							String oldToken = oldTokens.remove(0);
							tokenMap.remove(oldToken);
						}
					} else {
						oldTokens = new ArrayList<String>(CommonConstants.MAX_PHONE_CLIENT);
					}
					oldTokens.add(token);
					tokenUserMap.put(worker.getLoginId(), oldTokens);
				} finally {
					locker.unlock();
				}
				tokenMap.put(token, JsonUtil.getJsonString(worker), 0, TimeUnit.DAYS, tokenPhoneInactiveTimeout, TimeUnit.HOURS);

		}
		if(StringUtils.hasText(curr.getHeadPortraitPicKey())){
			worker.setFileUrl(urlPrefix+curr.getHeadPortraitPicKey());
		}
		worker.setUrls(null);
		if(StringUtils.hasText(worker.getOrgId())){
			TsmOrg one = tsmOrgRepo.getOne(Long.valueOf(worker.getOrgId()));
			if (Objects.nonNull(one)){
				worker.setOrgName(one.getOrgName());
			}
		}
		// 设置主题
		worker.setSysTheme(themeService.findThem());
		tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_SUCCESS);
		saveLoginLog(request,tsmLoginLog);
		return worker;
	}

	public boolean verifySignature(String signature, String payload, String pubKey) {
		boolean result = false;

		try {
			final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(pubKey));
			PublicKey key = keyFactory.generatePublic(publicKeySpec);

			Signature sha_rsa = Signature.getInstance("SHA256withRSA");
			sha_rsa.initVerify(key);
			sha_rsa.update(payload.getBytes());

			result = sha_rsa.verify(Base64.getDecoder().decode(signature));
		} catch (Exception e) {
			// Error management
		}

		return result;
	}

	@Override
	public Result<String> verificationMobile(SetUserPhoneDTO dto) {
		String verifyCode = loginCacheService.getSmsCode(dto.getMobile());
		if(!StringUtils.hasText(verifyCode)){
			throw new BusinessException("验证码已过期！");
		}
		if(!dto.getServiceCode().equalsIgnoreCase(verifyCode)){
			throw new BusinessException("手机验证码错误！");
		}
		Map<String,Object> prop = new HashMap<>(4);
		prop.put("mobile",dto.getMobile());
		prop.put("state",CommonConstants.STATE_VALID);
		TsmWorker curr = tsmWorkerRepo.getUniqueByProps(prop);
		if(Objects.isNull(curr)){
			throw new BusinessException("该账号不存在系统！");
		}
		// 返回加密id
		String encrypt = AESUtil.encrypt(curr.getMobile() + System.currentTimeMillis());
		RMapCache<String, Integer> mapCache = redisson.getMapCache(RedisKeys.RETRIEVE_PWD_KEY);
		mapCache.put(encrypt,curr.getLoginId(), SysmgrConstants.RETRIEVE_PWD_MINUTES,TimeUnit.MINUTES);
		return Result.ofSuccess(encrypt);
	}

	@Override
	public Result<String> setUserPhone(SetUserPhoneDTO dto) {
		//判断手机验证码
		String verifyCode = loginCacheService.getSmsCode(dto.getMobile());
		if(!StringUtils.hasText(verifyCode)){
			throw new BusinessException("验证码已过期！");
		}
		if(!dto.getServiceCode().equalsIgnoreCase(verifyCode)){
			throw new BusinessException("手机验证码错误！");
		}

		//判断key
		RMapCache<String, Integer> mapCache = redisson.getMapCache(RedisKeys.RETRIEVE_PWD_KEY);
		Integer loginId = mapCache.remove(dto.getUpdateKey());
		if(Objects.isNull(loginId)){
			throw new BusinessException("手机号校验失败！");
		}

		//判断手机号是否存在
		Integer whetherMobile = workerJdbcDao.queryWorkerCount(dto.getMobile());
		if (!whetherMobile.equals(CommonConstants.STATE_INVALID)){
			throw new BusinessException("手机号已存在");
		}

		TsmWorker curr = tsmWorkerRepo.getOne(loginId);
		curr.setMobile(dto.getMobile());
		tsmWorkerRepo.saveAndFlush(curr);

		return Result.ofSuccess("成功");
	}

	@Override
	public Result<String> deleteUserLogout(Worker worker) {
		// 备份worker信息后，删除worker
		TsmWorker curr = tsmWorkerRepo.getOne(worker.getLoginId());
		TsmWorkerLogout tsmWorkerLogout = new TsmWorkerLogout();
		BeanUtils.copyProperties(curr,tsmWorkerLogout);
		tsmWorkerLogoutRepo.save(tsmWorkerLogout);

		TsmWorker tsmWorker  = new TsmWorker();
		tsmWorker.setLoginId(worker.getLoginId());
		tsmWorkerRepo.delete(tsmWorker);
		return Result.ofSuccess("成功");
	}

	@Override public void saveLoginLog() {
		loginLogRepo.saveAllAndFlush(logCache);
		logCache.clear();
	}

	@Override
	public Worker createWorkerAndLogin(HttpServletRequest request, LoginWorkerC loginWorkerC, String callUrl) {
		TsmWorker byMobile = tsmWorkerRepo.findByMobile(loginWorkerC.getMobile());
		if(Objects.nonNull(byMobile)){
			throw new BusinessException("电话号码已存在！");
		}
		validatedSession(request);
		String phone = loginCacheService.removePhoneRegister(loginWorkerC.getSecretKey());
		if(!StringUtils.hasText(phone)){
			// 删除session
			request.getSession().invalidate();
			loginCacheService.removePhoneRegisterSession(request.getSession().getId());
			throw new BusinessException("手机验证码已失效");
		}
		if(!loginWorkerC.getMobile().equals(phone)){
			throw new BusinessException("当前手机号与登录手机号不一致");
		}
		WorkerC workerC = new WorkerC();
		BeanUtils.copyProperties(loginWorkerC,workerC);
		workerC.setApp(true);
		//app注册添加默认组织与角色
		workerC.setOrgId(DEFAULT_ORG);
		workerService.appCreateWorker(workerC);
		LoginPhoneDto loginPhoneDto = new LoginPhoneDto();
		loginPhoneDto.setMobile(loginWorkerC.getMobile());
		loginPhoneDto.setSmsCode(null);
		loginPhoneDto.setScope(loginWorkerC.getScope());
		// 删除session
		request.getSession().invalidate();
		loginCacheService.removePhoneRegisterSession(request.getSession().getId());
		return this.loginMobile(request, loginPhoneDto, callUrl, true);
	}

	@Override
	public FileVo uploadHeadPic(MultipartFile file,HttpServletRequest request) {
		validatedSession(request);
		Result<FileVo> fileVoResult = obsService.feignFileUpload(file);
		if(!fileVoResult.isSuccess()){
			throw new BusinessException("调用上传失败！");
		}
		return fileVoResult.getData();
	}

	@Override
	public Worker wechatDoLogin(HttpServletRequest request, WechatLoginDto dto, TsmWorker curr, boolean manualLogin) {
		String mobile = dto.getMobile();
		String smsCode = dto.getSmsCode();
		TsmLoginLog tsmLoginLog = new TsmLoginLog();
		tsmLoginLog.setMobile(mobile);
		tsmLoginLog.setClientType(dto.getScope());
		tsmLoginLog.setLoginType(CommonConstants.LOGIN_TYPE_SMS);
		LoginPhoneDto loginPhoneDto = new LoginPhoneDto();
		loginPhoneDto.setMobile(mobile);
		loginPhoneDto.setSmsCode(smsCode);
		loginPhoneDto.setScope(dto.getScope());
		if (null == curr){
			curr = new TsmWorker();
			curr.setState(1);
			curr.setMobile(dto.getMobile());
			curr.setLoginPwd("pwd_" + System.currentTimeMillis());
			curr.setWorkerName(mobile);
		}
		curr.setOpenid(dto.getOpenid());
		// 验证码验证
		if (manualLogin){
			validatedCode(loginPhoneDto,request,tsmLoginLog, false);
		}
		// 登录
		Worker worker = new Worker();
		if (curr.getState() == 0 || curr.getState() == 2) {
			throwNewException(curr);
		}
		String callUrl = "/" + appName + request.getRequestURI();

		String token = NanoIdUtil.randomLowerNanoId();
		BeanUtils.copyProperties(curr, worker);
		worker.setScope(loginPhoneDto.getScope());
		//设置当前用户
		CallHolder callInfo = CallHolder.initialCall();
		callInfo.setCallUrl(callUrl);
		callInfo.setToken(worker.getToken());
		callInfo.setWorker(worker);
		curr.setLoginNum(curr.getLoginNum() + 1);
		curr.setLastLoginTime(new Date());
		tsmWorkerRepo.save(curr);
		tsmWorkerRepo.flush();
		worker.setLoginId(curr.getLoginId());
		//设置超级管理员，超级管理员不会返回urls
		if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())) {
			worker.setSuperWorker(true);
		}
		// token对应的worker的json字符串，么有使用java对象的原因在用，几乎所有的项目都会用到当前登录用户，
		// 为了保证，当worker修改了添加了属性，只需要发布相关使用到这些属性的项目即可，不会因为序列化的问题，发布所有依赖了worker的项目
		RMapCache<String, String> tokenMap = null;
		if (CommonConstants.SCOPE_WEB.equals(loginPhoneDto.getScope())) {
			token = CommonConstants.SCOPE_WEB + "_" + token;
			worker.setToken(token);
			// 设置角色和菜单权限
			worker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(worker.getLoginId())));
			worker.setTreeNodes(authService.getAuthTreeNode(worker.getLoginId()));
			worker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(worker.getLoginId())));
			tokenMap = redisson.getMapCache(RedisKeys.TOKEN_WEB_KEY);
			tokenMap.put(token, JsonUtil.getJsonString(worker), tokenExpireTimeout, TimeUnit.HOURS, tokenWebInactiveTimeout, TimeUnit.MINUTES);
		} else if (CommonConstants.SCOPE_PHONE.equals(loginPhoneDto.getScope())) {
			token = CommonConstants.SCOPE_PHONE + "_" + token;
			worker.setToken(token);
			// 设置角色和菜单权限
			worker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(worker.getLoginId())));
			worker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(worker.getLoginId())));
			if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())){
				List<TsmAppButton> all = buttonRepo.findAll();
				List<String> collect = all.stream().filter(btn -> StringUtils.hasText(btn.getCode())).map(item -> item.getCode()).distinct()
						.collect(Collectors.toList());
				worker.setAuthButtons(collect);
			}else{
				worker.setAuthButtons(authService.getBtnIds(worker));
			}
			tokenMap = redisson.getMapCache(RedisKeys.TOKEN_PHONE_KEY);

			locker.lock(LockConstants.LOCK_TOKEN, worker.workerId());
			try {
				RMapCache<Integer, List<String>> tokenUserMap = redisson.getMapCache(RedisKeys.PHONE_TOKEN_USER_MAP_KEY);
				List<String> oldTokens = tokenUserMap.get(worker.getLoginId());
				if (oldTokens != null) {
					if (oldTokens.size() + 1 > CommonConstants.MAX_PHONE_CLIENT) {
						String oldToken = oldTokens.remove(0);
						tokenMap.remove(oldToken);
					}
				} else {
					oldTokens = new ArrayList<String>(CommonConstants.MAX_PHONE_CLIENT);
				}
				oldTokens.add(token);
				tokenUserMap.put(worker.getLoginId(), oldTokens);
			} finally {
				locker.unlock();
			}
			tokenMap.put(token, JsonUtil.getJsonString(worker), 0, TimeUnit.DAYS, tokenPhoneInactiveTimeout, TimeUnit.HOURS);
		}else if(CommonConstants.SCOPE_SSO.equals(loginPhoneDto.getScope())){

		} else {
			tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_FAIL_ERR_SCOPE);
			saveLoginLog(request,tsmLoginLog);
			throw new RuntimeException("not support this scope:" + loginPhoneDto.getScope());
		}
		if(StringUtils.hasText(curr.getHeadPortraitPicKey())){
			worker.setFileUrl(urlPrefix+curr.getHeadPortraitPicKey());
		}
		//前端不需要所有的url权限,不用返回前端，减少返回数据
		worker.setUrls(null);
		// 2023-06-16 baoxy 业务扩充:根据用户的密码最后修改时间与当前时间进行比较,若超过了90天,需要提示用户进行密码的修改
//		checkPasswordExpire(worker);
		// 设置组织名称
		YcqLeague byLeagueCode = ycqLeagueRepo.findByLeagueCode(worker.getOrgId());
		if(byLeagueCode!=null) {
			worker.setOrgName(byLeagueCode.getLeagueFullName());
		}
		// 设置主题
		worker.setSysTheme(themeService.findThem());
		tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_SUCCESS);
		saveLoginLog(request,tsmLoginLog);
		return worker;
	}

	/**
	 * 验证session
	 */
	private void validatedSession(HttpServletRequest request) {
		String id = request.getSession().getId();
		String phoneRegisterSession = loginCacheService.getPhoneRegisterSession(id);
		if(!StringUtils.hasText(phoneRegisterSession)){
			throw new BusinessException("请先登录再完成后续步骤！");
		}
	}

	/**
	 * 验证 验证码
	 */
	private void validatedCode(LoginPhoneDto loginPhoneDto,HttpServletRequest request,TsmLoginLog tsmLoginLog,boolean isRegister)throws BusinessException {
		if(isRegister){
			return;
		}
		//是否是通用验证码
		List<String> codes = tsmPropertyValueRepo.findAllByPropCodeAndState(code, 1).stream().map(TsmPropertyValue::getValueCode).collect(Collectors.toList());
		if (StringUtils.hasText(loginPhoneDto.getSmsCode()) && codes.contains(loginPhoneDto.getSmsCode())){
			return;
		}
		// 如果注册进来不验证
		String smsCode = loginCacheService.getSmsCode(loginPhoneDto.getMobile());
		if(!StringUtils.hasText(smsCode)){
			tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_FAIL_ERR_SMS_CODE);
			saveLoginLog(request,tsmLoginLog);
			throw new BusinessException("短信验证码已失效！");
		}
		if (!smsCode.equalsIgnoreCase(loginPhoneDto.getSmsCode())) {
			tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_FAIL_ERR_SMS_CODE);
			saveLoginLog(request,tsmLoginLog);
			throw new BusinessException("短信验证码错误！");
		}
	}

	public void checkPasswordExpire(Worker worker) {
		LocalDateTime lastChgPwdTime = LocalDateTime.ofInstant(worker.getLastChgPwdTime().toInstant(), ZoneId.systemDefault());
		Duration duration = Duration.between(lastChgPwdTime, LocalDateTime.now());
		// 获取当前时间与
		long day = duration.toDays();
		// 获取静态值中的账号密码过期时间属性值
		TsmPropertyValue value = tsmPropertyValueRepo.findTsmPropertyValueByPropCode(SysmgrConstants.PASSWORD_EXPIRE_TIME);
		if (day > Long.parseLong(value.getValueCode())) {
			// 说明最后一次修改密码时间与当前时间已超过90天,
			worker.setUpdatePassword(true);
		}
	}

	@Override
	public IRSWorkerVo getUserInfoIRS(String loginId) {
//		Integer id = Integer.parseInt(loginId);
//		TsmWorker curr = tsmWorkerRepo.getOne(id);
		IRSWorkerVo workerVo = new IRSWorkerVo();
//		workerVo.setRealName(curr.getWorkerName());
		workerVo.setIdCardNo("500103200001010999");
		workerVo.setRealName("张伟");
		return workerVo;
	}

	@Override
	public Worker loginExternalMobile(HttpServletRequest request, LoginInfoDto loginInfoDto, String callUrl, boolean isRegister) throws IOException {
		log.info("loginInfoDto参数值---->{}",loginInfoDto.toString());
		String phone = "";
		if(loginInfoDto.getYkz().equals("1")){
			phone = loginInfoDto.getMobile();
		} else {
			if (org.apache.commons.lang3.StringUtils.isEmpty(loginInfoDto.getUserId())){
				throw new BusinessException("未获取到渝快办个人信息！");
			}
			Map requestMap = new HashMap();
			requestMap.put("appKey", ykzAppKey);
			requestMap.put("appSecret", ykzAppSecret);
			String returnStr = HttpClientUtil.doPost(ykzTokenUrl, requestMap);
			log.info("获取accessToken接口返回值----->{}",returnStr);
			JSONObject json = new JSONObject(returnStr);
			String data = json.get("data").toString();
			JSONObject jsonObject1 = new JSONObject(data);
			String records = jsonObject1.get("accessToken").toString();
			// 使用 HttpClient 类库的示例
			CloseableHttpClient httpClient = HttpClients.createDefault();
			HttpGet httpGet = new HttpGet(ykzUrl + loginInfoDto.getUserId() + "");
			httpGet.addHeader("Authorization", "Bearer" + " " + records);
			CloseableHttpResponse response = httpClient.execute(httpGet);
			try {
				// 响应体
				String bodyAsString = EntityUtils.toString(response.getEntity());
				log.info("获取手机号接口返回值----->{}",bodyAsString);
				JSONObject jsonPhone = new JSONObject(bodyAsString);
				String dataPhone = jsonPhone.get("data").toString();
				JSONObject jsonObject2 = new JSONObject(dataPhone);
				phone = jsonObject2.get("phone").toString();
				int statusCode = response.getStatusLine().getStatusCode();
				// 处理响应
			} finally {
				response.close();
			}
			if (!StringUtils.hasText(phone)) {
				throw new BusinessException("获取手机号失败!");
			}
		}
		TsmLoginLog tsmLoginLog = new TsmLoginLog();
		tsmLoginLog.setMobile(phone);
		tsmLoginLog.setClientType(loginInfoDto.getScope());
		tsmLoginLog.setLoginType(CommonConstants.LOGIN_TYPE_SMS);
		Worker worker = new Worker();
		Map<String, Object> props = new HashMap<>(2);
		props.put("mobile", phone);
		TsmWorker curr = tsmWorkerRepo.getUniqueByProps(props);
		if(Objects.isNull(curr)){
			throw new BusinessException("该用户未注册青春重庆!");
		}
		if (curr.getState() == 0 || curr.getState() == 2) {
			throwNewException(curr);
		}else {
			String token = NanoIdUtil.randomLowerNanoId();
			BeanUtils.copyProperties(curr, worker);
			worker.setScope(loginInfoDto.getScope());
			//设置当前用户
			CallHolder callInfo = CallHolder.initialCall();
			callInfo.setCallUrl(callUrl);
			callInfo.setToken(worker.getToken());
			callInfo.setWorker(worker);

			curr.setLoginNum(curr.getLoginNum() + 1);
			curr.setLastLoginTime(new Date());
			tsmWorkerRepo.save(curr);
			tsmWorkerRepo.flush();
			//设置超级管理员，超级管理员不会返回urls
			if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())) {
				worker.setSuperWorker(true);
			}
			// token对应的worker的json字符串，么有使用java对象的原因在用，几乎所有的项目都会用到当前登录用户，
			// 为了保证，当worker修改了添加了属性，只需要发布相关使用到这些属性的项目即可，不会因为序列化的问题，发布所有依赖了worker的项目
			RMapCache<String, String> tokenMap = null;
			if (CommonConstants.SCOPE_WEB.equals(loginInfoDto.getScope())) {
				token = CommonConstants.SCOPE_WEB + "_" + token;
				worker.setToken(token);
				// 设置角色和菜单权限
				worker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(worker.getLoginId())));
				worker.setTreeNodes(authService.getAuthTreeNode(worker.getLoginId()));
				worker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(worker.getLoginId())));
				tokenMap = redisson.getMapCache(RedisKeys.TOKEN_WEB_KEY);
				tokenMap.put(token, JsonUtil.getJsonString(worker), tokenExpireTimeout, TimeUnit.HOURS, tokenWebInactiveTimeout, TimeUnit.MINUTES);
			} else if (CommonConstants.SCOPE_PHONE.equals(loginInfoDto.getScope())) {
				token = CommonConstants.SCOPE_PHONE + "_" + token;
				worker.setToken(token);
				// 设置角色和菜单权限
				worker.setRoles(sysmgrRoleConvert.toTsmRoleDtos(authService.getAuthRoles(worker.getLoginId())));
				worker.setUrls(sysmgrUrlConvert.toTsmUrlDtos(authService.getAuthUrls(worker.getLoginId())));
				if (CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())){
					List<TsmAppButton> all = buttonRepo.findAll();
					List<String> collect = all.stream().filter(btn -> StringUtils.hasText(btn.getCode())).map(item -> item.getCode()).distinct()
							.collect(Collectors.toList());
					worker.setAuthButtons(collect);
				}else{
					worker.setAuthButtons(authService.getBtnIds(worker));
				}
				tokenMap = redisson.getMapCache(RedisKeys.TOKEN_PHONE_KEY);

				locker.lock(LockConstants.LOCK_TOKEN, worker.workerId());
				try {
					RMapCache<Integer, List<String>> tokenUserMap = redisson.getMapCache(RedisKeys.PHONE_TOKEN_USER_MAP_KEY);
					List<String> oldTokens = tokenUserMap.get(worker.getLoginId());
					if (oldTokens != null) {
						if (oldTokens.size() + 1 > CommonConstants.MAX_PHONE_CLIENT) {
							String oldToken = oldTokens.remove(0);
							tokenMap.remove(oldToken);
						}
					} else {
						oldTokens = new ArrayList<String>(CommonConstants.MAX_PHONE_CLIENT);
					}
					oldTokens.add(token);
					tokenUserMap.put(worker.getLoginId(), oldTokens);
				} finally {
					locker.unlock();
				}
				tokenMap.put(token, JsonUtil.getJsonString(worker), 0, TimeUnit.DAYS, tokenPhoneInactiveTimeout, TimeUnit.HOURS);
			}else if(CommonConstants.SCOPE_SSO.equals(loginInfoDto.getScope())){

			} else {
				tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_FAIL_ERR_SCOPE);
				saveLoginLog(request,tsmLoginLog);
				throw new RuntimeException("not support this scope:" + loginInfoDto.getScope());
			}
		}
		if(StringUtils.hasText(curr.getHeadPortraitPicKey())){
			worker.setFileUrl(urlPrefix+curr.getHeadPortraitPicKey());
		}
		//前端不需要所有的url权限,不用返回前端，减少返回数据
		worker.setUrls(null);
		if(StringUtils.hasText(worker.getOrgId())){
			TsmOrg one = tsmOrgRepo.getOne(Long.valueOf(worker.getOrgId()));
			//测试环境又脏数据，预防一下，加个判断
			if (Objects.nonNull(one)){
				worker.setOrgName(one.getOrgName());
			}
		}
		// 设置主题
		findAuthButton(worker);
		worker.setSysTheme(themeService.findThem());
		tsmLoginLog.setLoginStatus(CommonConstants.LOGIN_SUCCESS);
		saveLoginLog(request,tsmLoginLog);
		return worker;
	}

	@Override
	public Result doRefreshAccountId(LoginPhoneDto loginPhoneDto) {
		Result result = new Result();
		//是否是通用验证码
		List<String> codes = tsmPropertyValueRepo.findAllByPropCodeAndState(code, 1).stream().map(TsmPropertyValue::getValueCode).collect(Collectors.toList());
		if (!StringUtils.hasText(loginPhoneDto.getSmsCode()) || !codes.contains(loginPhoneDto.getSmsCode())){
			String smsCode = loginCacheService.getSmsCode(loginPhoneDto.getMobile());
			if(!StringUtils.hasText(smsCode)){
				throw new BusinessException("短信验证码已失效！");
			}
			if (!smsCode.equalsIgnoreCase(loginPhoneDto.getSmsCode())) {
				throw new BusinessException("短信验证码错误！");
			}
		}
		//通过feign调用刷新渝快政的刷唯一值的接口
		return interService.saveYkzUserInfo(loginPhoneDto);
	}

	private void findAuthButton(Worker worker){
		//设置前端需要的authButton和authUrl
		List<TsmMenu> menus = authService.getAuthMenus(worker.getLoginId());
		if (CollectionUtils.isNotEmpty(menus)){
			List<String> authButton = menus.stream().filter(e -> e.getMenuType() == 2 && org.apache.commons.lang3.StringUtils.isNotEmpty(e.getMenuUrl()))
					.map(TsmMenu::getMenuUrl).distinct()
					.collect(Collectors.toList());
			List<String> authUrl = menus.stream().filter(e -> e.getMenuType() != 2 && org.apache.commons.lang3.StringUtils.isNotEmpty(e.getMenuUrl()))
					.map(TsmMenu::getMenuUrl).distinct()
					.collect(Collectors.toList());
			worker.setAuthButton(authButton);
			worker.setAuthUrl(authUrl);
		}
	}
}
