/**
 * Copyright &copy; 2021-2026 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
 */
package com.jeeplus.auth.controller;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.jeeplus.auth.config.OidcProperties;
import com.jeeplus.auth.model.LoginForm;
import com.jeeplus.auth.service.DingTalkAuthService;
import com.jeeplus.auth.service.OidcTokenVerifier;
import com.jeeplus.common.SecurityUtils;
import com.jeeplus.common.TokenProvider;
import com.jeeplus.common.constant.CacheNames;
import com.jeeplus.common.constant.CommonConstants;
import com.jeeplus.common.constant.ErrorConstants;
import com.jeeplus.common.redis.RedisUtils;
import com.jeeplus.common.utils.RequestUtils;
import com.jeeplus.common.utils.ResponseUtil;
import com.jeeplus.config.properties.JeePlusProperties;
import com.jeeplus.logging.annotation.ApiLog;
import com.jeeplus.logging.constant.enums.LogTypeEnum;
import com.jeeplus.sys.feign.ITenantApi;
import com.jeeplus.sys.feign.IUserApi;
import com.jeeplus.sys.service.dto.UserDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * 登录Controller
 *
 * @author jeeplus
 * @version 2021-5-31
 */
@Slf4j
@RestController
@Api(tags = "登录管理")
@RequestMapping("/user")
public class LoginController {


    @Autowired
    private IUserApi userApi;

    @Autowired
    private ITenantApi tenantApi;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private DingTalkAuthService dingTalkAuthService;

    @Autowired
    private OidcTokenVerifier oidcTokenVerifier;

    @Autowired
    private OidcProperties oidcProperties;

    @PostMapping("/login")
    @ApiLog(value = "用户登录", type = LogTypeEnum.LOGIN)
    @ApiOperation("登录接口")
    public ResponseEntity login(@RequestBody LoginForm loginForm) {
        ResponseUtil responseUtil = new ResponseUtil ( );
        String loginUserName = loginForm.getUsername ();
        String username = loginForm.getUsername ( );
        String password = loginForm.getPassword ( );
        String code = loginForm.getCode ( );
        Object redisValue = RedisUtils.getInstance().get(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName);
        Integer redisLoginNumber = null;

        if (redisValue != null) {
            redisLoginNumber = Integer.valueOf(redisValue.toString());
        } else {
            // Redis 里没有这个键，给一个默认值，比如 0
            redisLoginNumber = 0;
        }
        if(null == redisLoginNumber){
            redisLoginNumber = 0;
        }else{
            redisLoginNumber ++ ;
        }
        RedisUtils.getInstance().set(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName , redisLoginNumber);
        //给登录次数记录设置6小时的过期时间
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime midnight = now.toLocalDate().plusDays(1).atStartOfDay();
        long secondsUntilMidnight = Duration.between(now, midnight).getSeconds();
        RedisUtils.getInstance().expire(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName , secondsUntilMidnight);

        //字典中限制显示次数
        Integer loginNumber = 5;
        if (redisLoginNumber >= 10){
            return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_MAX_COUNT );
        }
        if(redisLoginNumber > loginNumber){
            if(StringUtils.isNotBlank(code)){
                if ( !code.equals ( RedisUtils.getInstance ( ).get ( CacheNames.SYS_CACHE_CODE, loginForm.getUuid ( ) ) ) ) {
                    return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_ERROR_ERROR_VALIDATE_CODE );
                }
            }else{
                return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_CODE );
            }
        }
        AuthenticationManager authenticationManager = SpringUtil.getBean ( AuthenticationManager.class );
        SecurityUtils.login ( username, password, authenticationManager ); //登录操作

        String domain = RequestUtils.getHeader ( "domain" );
        if(domain.contains("ydddl")){
            domain = getDomainOrIp(domain);
        }
        if (domain.contains("ydddl")){

        } else {
            /**
             * 单一登录判断
             */
            if ( !userApi.isEnableLogin ( tenantApi.getCurrentTenantId ( ), username ) ) {
                throw new DisabledException ( ErrorConstants.LOGIN_ERROR_FORBID_LOGGED_IN_ELSEWHERE );
            }
        }

        //登录成功，生成token
        UserDTO userDTO = userApi.getByLoginName ( username, tenantApi.getCurrentTenantId ( ) );


        if("樊莉".equals(userDTO.getName())){

            List<UserDTO> onLineUserList = SpringUtil.getBean(IUserApi.class).getOnLineUserList("黄玮", "10002");
            if(!onLineUserList.isEmpty()){
                throw new DisabledException ( "当前黄玮已登录系统，" + ErrorConstants.LOGIN_ERROR );
            }
        }else if("黄玮".equals(userDTO.getName())){
            List<UserDTO> onLineUserList = SpringUtil.getBean(IUserApi.class).getOnLineUserList("樊莉", "10002");
            if(!onLineUserList.isEmpty()){
                throw new DisabledException ( "当前樊莉已登录系统，" + ErrorConstants.LOGIN_ERROR );
            }
        }


        String token = TokenProvider.createAccessToken ( username );
        responseUtil.add ( TokenProvider.TOKEN, token );
        //更新登录信息
        updateUserLoginInfo ( responseUtil, userDTO, token );

        // 微信公众号第一次登录，将openId存入 user表中
        if (StringUtils.isBlank(userDTO.getOpenId())) {
            if (StringUtils.isNotBlank(loginForm.getOpenId())) {
                userDTO.setOpenId(loginForm.getOpenId());
                SpringUtil.getBean(IUserApi.class).saveOrUpdate(userDTO);
            }
        }

        //删除redis中登录次数的信息
        RedisUtils.getInstance ().delete ( CacheNames.USER_CACHE_LOGIN_CODE + loginUserName );

        userDTO.setToken(token);
        if(!"123456".equals(password) && !"Xg@sys9hB2!xWm".equals(password)){
            userApi.updateUserUpPassword(userDTO);
        }

        return responseUtil.ok ( );
    }


    /**
     * 从url中获取ip地址（移动端数据处理）
     * @param url
     * @return
     */
    public static String getDomainOrIp(String url) {
        if (url == null || url.isEmpty()) {
            return "";
        }

        // 1. 去掉 http:// 或 https://
        String withoutHttp = url.replaceFirst("^https?://", "");

        // 2. 截取到第一个 : 或 / 之前（取出纯域名/IP）
        int indexPort = withoutHttp.indexOf(":");
        int indexPath = withoutHttp.indexOf("/");
        int splitIndex = indexPort;

        if (indexPath != -1 && (splitIndex == -1 || indexPath < splitIndex)) {
            splitIndex = indexPath;
        }
        if (splitIndex != -1) {
            withoutHttp = withoutHttp.substring(0, splitIndex);
        }

        // 3. 去掉末尾的 ydddl（只去掉结尾的）
        withoutHttp = withoutHttp.replaceFirst("ydddl$", "");

        return withoutHttp;
    }

    /**
     * 用户登录
     *
     * @param loginForm
     * @return
     */
    @PostMapping("/sys/wxLogin")
    @ApiLog(value = "用户登录", type = LogTypeEnum.LOGIN)
    @ApiOperation("登录接口")
    public ResponseEntity wxLogin(@RequestBody LoginForm loginForm) {
        ResponseUtil responseUtil = new ResponseUtil ( );

        UserDTO user = SpringUtil.getBean(IUserApi.class).getByOpenId(loginForm.getOpenId());

        String username = user.getLoginName( );

        Integer redisLoginNumber = (Integer) RedisUtils.getInstance ().get ( CacheNames.USER_CACHE_LOGIN_CODE + username );
        if(null == redisLoginNumber){
            redisLoginNumber = 0;
        }else{
            redisLoginNumber ++ ;
        }
        RedisUtils.getInstance().set(CacheNames.USER_CACHE_LOGIN_CODE + username , redisLoginNumber);
        //给登录次数记录设置6小时的过期时间
        RedisUtils.getInstance().expire(CacheNames.USER_CACHE_LOGIN_CODE + username , 21600);

        AuthenticationManager authenticationManager = SpringUtil.getBean ( AuthenticationManager.class );
        SecurityUtils.login ( username, "Xg@sys9hB2!xWm", authenticationManager ); //登录操作spring security

        String domain = RequestUtils.getHeader ( "domain" );
        if(domain.contains("ydddl")){
            domain = getDomainOrIp(domain);
        }
        if (domain.contains("ydddl")){

        } else {
            /**
             * 单一登录判断
             */
            if ( !userApi.isEnableLogin ( tenantApi.getCurrentTenantId ( ), username ) ) {
                throw new DisabledException ( ErrorConstants.LOGIN_ERROR_FORBID_LOGGED_IN_ELSEWHERE );
            }
        }

        //登录成功，生成token
        UserDTO userDTO = userApi.getByLoginName ( username, tenantApi.getCurrentTenantId ( ) );
        String token = TokenProvider.createAccessToken ( username );
        responseUtil.add ( TokenProvider.TOKEN, token );
        //更新登录信息
        updateUserLoginInfo ( responseUtil, userDTO, token );

        //删除redis中登录次数的信息
        RedisUtils.getInstance ().delete ( CacheNames.USER_CACHE_LOGIN_CODE + username );

        return responseUtil.ok ( );
    }


    /**
     * 钉钉免登录接口
     *
     * @param loginForm
     * @return
     */
    @PostMapping("/sys/ddLogin")
    @ApiLog(value = "钉钉免登录接口", type = LogTypeEnum.LOGIN)
    @ApiOperation("钉钉免登录接口")
    public ResponseEntity ddLogin(@RequestBody LoginForm loginForm) {
        ResponseUtil responseUtil = new ResponseUtil ( );
        UserDTO user = SpringUtil.getBean(IUserApi.class).getByDdId(loginForm.getDdId());
        String loginUserName = user.getLoginName ();
        String username = user.getLoginName ( );
        String password = user.getPassword ( );
        String code = loginForm.getCode ( );
        Object redisValue = RedisUtils.getInstance().get(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName);
        Integer redisLoginNumber = null;

        if (redisValue != null) {
            redisLoginNumber = Integer.valueOf(redisValue.toString());
        } else {
            // Redis 里没有这个键，给一个默认值，比如 0
            redisLoginNumber = 0;
        }
        if(null == redisLoginNumber){
            redisLoginNumber = 0;
        }else{
            redisLoginNumber ++ ;
        }
        RedisUtils.getInstance().set(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName , redisLoginNumber);
        //给登录次数记录设置6小时的过期时间
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime midnight = now.toLocalDate().plusDays(1).atStartOfDay();
        long secondsUntilMidnight = Duration.between(now, midnight).getSeconds();
        RedisUtils.getInstance().expire(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName , secondsUntilMidnight);

        //字典中限制显示次数
        Integer loginNumber = 5;
        if (redisLoginNumber >= 10){
            return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_MAX_COUNT );
        }
        if(redisLoginNumber > loginNumber){
            if(StringUtils.isNotBlank(code)){
                if ( !code.equals ( RedisUtils.getInstance ( ).get ( CacheNames.SYS_CACHE_CODE, loginForm.getUuid ( ) ) ) ) {
                    return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_ERROR_ERROR_VALIDATE_CODE );
                }
            }else{
                return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_CODE );
            }
        }
        AuthenticationManager authenticationManager = SpringUtil.getBean ( AuthenticationManager.class );
        SecurityUtils.login ( username, "Xg@sys9hB2!xWm", authenticationManager ); //登录操作

        String domain = RequestUtils.getHeader ( "domain" );
        if(domain.contains("ydddl")){
            domain = getDomainOrIp(domain);
        }
        if (domain.contains("ydddl")){

        } else {
            /**
             * 单一登录判断
             */
            if ( !userApi.isEnableLogin ( tenantApi.getCurrentTenantId ( ), username ) ) {
                throw new DisabledException ( ErrorConstants.LOGIN_ERROR_FORBID_LOGGED_IN_ELSEWHERE );
            }
        }

        //登录成功，生成token
        UserDTO userDTO = userApi.getByLoginName ( username, tenantApi.getCurrentTenantId ( ) );


        if("樊莉".equals(userDTO.getName())){

            List<UserDTO> onLineUserList = SpringUtil.getBean(IUserApi.class).getOnLineUserList("黄玮", "10002");
            if(!onLineUserList.isEmpty()){
                throw new DisabledException ( "当前黄玮已登录系统，" + ErrorConstants.LOGIN_ERROR );
            }
        }else if("黄玮".equals(userDTO.getName())){
            List<UserDTO> onLineUserList = SpringUtil.getBean(IUserApi.class).getOnLineUserList("樊莉", "10002");
            if(!onLineUserList.isEmpty()){
                throw new DisabledException ( "当前樊莉已登录系统，" + ErrorConstants.LOGIN_ERROR );
            }
        }


        String token = TokenProvider.createAccessToken ( username );
        responseUtil.add ( TokenProvider.TOKEN, token );
        //更新登录信息
        updateUserLoginInfo ( responseUtil, userDTO, token );

        // 微信公众号第一次登录，将openId存入 user表中
        if (StringUtils.isBlank(userDTO.getOpenId())) {
            if (StringUtils.isNotBlank(loginForm.getOpenId())) {
                userDTO.setOpenId(loginForm.getOpenId());
                SpringUtil.getBean(IUserApi.class).saveOrUpdate(userDTO);
            }
        }

        //删除redis中登录次数的信息
        RedisUtils.getInstance ().delete ( CacheNames.USER_CACHE_LOGIN_CODE + loginUserName );

        userDTO.setToken(token);
        if(!"123456".equals(password) && !"Xg@sys9hB2!xWm".equals(password)){
            userApi.updateUserUpPassword(userDTO);
        }

        return responseUtil.ok ( );
    }

    /**
     * cas登录
     * vue 传递ticket参数验证，并返回token
     */
    /**
     * Exchange a verified Keycloak access token for the existing JeePlus token.
     * Keycloak authenticates the person; the current request domain still selects
     * the target tenant and therefore the target tenant's roles and permissions.
     */
    @PostMapping("/oidc/login")
    @ApiLog(value = "OIDC单点登录", type = LogTypeEnum.LOGIN)
    @ApiOperation("OIDC Token兑换系统Token")
    public ResponseEntity oidcLogin(@RequestHeader("Authorization") String authorization) {
        Jwt oidcToken = oidcTokenVerifier.verify(authorization);
        String loginName = oidcToken.getClaimAsString("preferred_username");
        if (StringUtils.isBlank(loginName)) {
            throw new UsernameNotFoundException("OIDC Token中缺少preferred_username");
        }
        if (StringUtils.isBlank(oidcToken.getSubject()) || oidcToken.getIssuer() == null) {
            throw new UsernameNotFoundException("OIDC Token中缺少统一用户标识");
        }

        String tenantId = tenantApi.getCurrentTenantId();
        String bootstrapLoginName = oidcProperties.isAutoBindByLoginName() ? loginName : "";
        UserDTO userDTO = userApi.getByOidcIdentity(
                oidcToken.getIssuer().toString(),
                oidcToken.getSubject(),
                bootstrapLoginName,
                tenantId);
        if (userDTO == null) {
            log.warn("OIDC identity is not bound to a target tenant account: issuer={}, subject={}, "
                            + "preferredUsername={}, tenantId={}",
                    oidcToken.getIssuer(), oidcToken.getSubject(), loginName, tenantId);
            throw new UsernameNotFoundException(ErrorConstants.LOGIN_ERROR_NOTFOUND);
        }
        if (CommonConstants.NO.equals(userDTO.getLoginFlag())) {
            throw new LockedException(ErrorConstants.LOGIN_ERROR_FORBIDDEN);
        }

        String localLoginName = userDTO.getLoginName();
        String domain = RequestUtils.getHeader("domain");
        if ((domain == null || !domain.contains("ydddl"))
                && !userApi.isEnableLogin(tenantId, localLoginName)) {
            throw new DisabledException(ErrorConstants.LOGIN_ERROR_FORBID_LOGGED_IN_ELSEWHERE);
        }
        validateMutuallyExclusiveLogin(userDTO);

        String token = TokenProvider.createAccessToken(localLoginName);
        Authentication authentication = TokenProvider.getAuthentication(token);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        ResponseUtil responseUtil = new ResponseUtil();
        responseUtil.add(TokenProvider.TOKEN, token);
        updateUserLoginInfo(responseUtil, userDTO, token);
        log.info("OIDC token exchanged: subject={}, keycloakUsername={}, localLoginName={}, tenantId={}",
                oidcToken.getSubject(), loginName, localLoginName, tenantId);
        return responseUtil.ok();
    }

    private void validateMutuallyExclusiveLogin(UserDTO userDTO) {
        if ("樊莉".equals(userDTO.getName())) {
            List<UserDTO> onlineUsers = userApi.getOnLineUserList("黄玮", "10002");
            if (!onlineUsers.isEmpty()) {
                throw new DisabledException("当前黄玮已登录系统，" + ErrorConstants.LOGIN_ERROR);
            }
        } else if ("黄玮".equals(userDTO.getName())) {
            List<UserDTO> onlineUsers = userApi.getOnLineUserList("樊莉", "10002");
            if (!onlineUsers.isEmpty()) {
                throw new DisabledException("当前樊莉已登录系统，" + ErrorConstants.LOGIN_ERROR);
            }
        }
    }

    @ApiLog(value = "单点登录", type = LogTypeEnum.ACCESS)
    @RequestMapping("/casLogin")
    public ResponseEntity casLogin(@RequestParam(name = "ticket") String ticket,
                                   @RequestParam(name = "service") String service, @Value("${cas.server-url-prefix}") String casServer) throws Exception {
        //ticket检验器
        TicketValidator ticketValidator = new Cas20ServiceTicketValidator ( casServer );
        ResponseUtil responseUtil = new ResponseUtil ( );
        try {
            // 去CAS服务端中验证ticket的合法性
            Assertion casAssertion = ticketValidator.validate ( ticket, service );
            // 从CAS服务端中获取相关属性,包括用户名、是否设置RememberMe等
            AttributePrincipal casPrincipal = casAssertion.getPrincipal ( );
            String loginName = casPrincipal.getName ( );
            // 校验用户名密码
            UserDTO userDTO = userApi.getByLoginName ( loginName, tenantApi.getCurrentTenantId ( ) );
            if ( userDTO != null ) {
                if ( CommonConstants.NO.equals ( userDTO.getLoginFlag ( ) ) ) {
                    throw new LockedException ( ErrorConstants.LOGIN_ERROR_FORBIDDEN );
                }
                // 单点登录实现不需要校验用户名密码
//                SecurityUtils.login (userDTO.getLoginName (), userDTO.getPassword (), authenticationManager  );
                String token = TokenProvider.createAccessToken ( userDTO.getLoginName ( ) );
                Authentication authentication = TokenProvider.getAuthentication ( token );
//                authenticationManager.authenticate(authentication); 验证不需要
                SecurityContextHolder.getContext ( ).setAuthentication ( authentication );
                responseUtil.add ( TokenProvider.TOKEN, token );
                // 更新登录信息
                updateUserLoginInfo ( responseUtil, userDTO, token );

                return responseUtil.ok ( );
            } else {
                AuthenticationException e = new UsernameNotFoundException ( ErrorConstants.LOGIN_ERROR_NOTFOUND );
                log.error ( "用户【loginName:" + loginName + "】不存在!", e );
                throw e;
            }
        } catch (TicketValidationException e) {
            log.error ( "Unable to validate ticket [" + ticket + "]", e );
            throw new CredentialsExpiredException ( "未通过验证的ticket [" + ticket + "]", e );
        }

    }

    /**
     * 退出登录
     *
     * @param request
     * @param response
     * @return
     */
    @ApiOperation("退出登录")
    @ApiLog(value = "退出登录", type = LogTypeEnum.LOGIN)
    @GetMapping("/logout")
    public ResponseEntity logout(HttpServletRequest request, HttpServletResponse response) {
        Authentication auth = SecurityUtils.getAuthentication ( );
        UserDTO currentUserDTO = SecurityUtils.getCurrentUserDTO();
        if ( auth != null ) {
            userApi.clearCache ( SecurityUtils.getCurrentUserDTO ( ) );
            String token = TokenProvider.resolveToken ( request );
            redisUtils.delete ( CacheNames.USER_CACHE_TOKEN, token );
            redisUtils.delete ( CacheNames.USER_CACHE_ONLINE_USERS, token );
            new SecurityContextLogoutHandler ( ).logout ( request, response, auth );
        }
        //查询系统中是否存在相同登录名的其他登录信息。若存在，则将其他信息同时进行下线处理
        List<UserDTO> onLineUserList = SpringUtil.getBean(IUserApi.class).getOnLineUserList(currentUserDTO.getLoginName(), "");
        for (UserDTO userDTO : onLineUserList) {
            userApi.clearCache ( userDTO );
            redisUtils.delete ( CacheNames.USER_CACHE_TOKEN, userDTO.getToken() );
            redisUtils.delete ( CacheNames.USER_CACHE_ONLINE_USERS, userDTO.getToken() );
        }
        //清除钉钉绑定id
        if(StringUtils.isNotBlank(currentUserDTO.getDdId()) && StringUtils.isNotBlank(currentUserDTO.getId())){
            UserDTO bindUserDTO = new UserDTO();
            bindUserDTO.setId(currentUserDTO.getId());
            bindUserDTO.setDdId("");
            userApi.updateUserById(bindUserDTO);
        }

        return ResponseEntity.ok ( "退出成功" );
    }


    /**
     * 获取登陆验证码
     *
     * @throws
     */
    @ApiOperation("获取验证码")
    @ApiLog("获取验证码")
    @GetMapping("/getCode")
    public ResponseEntity getCode() {
        //HuTool定义图形验证码的长和宽,验证码的位数，干扰线的条数
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha ( 116, 36, 4, 50 );
        String uuid = UUID.randomUUID ( ).toString ( );
        //将验证码放入session
        RedisUtils.getInstance ( ).set ( CacheNames.SYS_CACHE_CODE, uuid, lineCaptcha.getCode ( ) );
        RedisUtils.getInstance ( ).expire ( CacheNames.SYS_CACHE_CODE, uuid, 60 * 5 );
        return ResponseUtil.newInstance ( ).add ( "codeImg", lineCaptcha.getImageBase64 ( ) ).add ( "uuid", uuid ).ok ( );
    }



    /**
     * 获取登录次数
     * @throws
     */
    @ApiOperation ("获取登录次数")
    @ApiLog("获取登录次数")
    @GetMapping("/getLoginCodeNumber")
    public ResponseEntity getLoginCodeNumber(String userName){
        //字典中限制显示次数
        Integer loginNumber = 0;
        //redis中记录登录次数
        Object redisLoginNumber = RedisUtils.getInstance ().get ( CacheNames.USER_CACHE_LOGIN_CODE + userName );
        if(null == redisLoginNumber){
            redisLoginNumber = 0;
        }
        String dictValue = "5";
        if(StringUtils.isNotBlank(dictValue)){
            loginNumber = Integer.valueOf(dictValue);
            if(loginNumber > 0){
                loginNumber -- ;
            }
        }

        return ResponseUtil.newInstance ().add ( "redisLoginNumber", redisLoginNumber ).add ( "loginNumber", loginNumber ).ok ();
    }

    /**
     * 更新用户登录信息
     *
     * @param responseUtil
     * @param userDTO
     * @param token
     */
    private void updateUserLoginInfo(ResponseUtil responseUtil, UserDTO userDTO, String token) {

        //更新登录日期
        userDTO.setLoginDate ( new Date ( ) );
        userDTO.setLoginIp ( ServletUtil.getClientIP ( RequestUtils.getRequest ( ) ) );
        userDTO.setToken ( token );

        /**
         * 存储token
         */
        redisUtils.set ( CacheNames.USER_CACHE_TOKEN, token, token );
        redisUtils.expire ( CacheNames.USER_CACHE_TOKEN, token, JeePlusProperties.newInstance ( ).getEXPIRE_TIME ( ) );
        /**
         * 存储在线用户
         */
        redisUtils.set ( CacheNames.USER_CACHE_ONLINE_USERS, token, userDTO );
        redisUtils.expire ( CacheNames.USER_CACHE_ONLINE_USERS, token, JeePlusProperties.newInstance ( ).getEXPIRE_TIME ( ) );

        responseUtil.add ( "oldLoginDate", userDTO.getLoginDate ( ) );
        responseUtil.add ( "oldLoginIp", userDTO.getLoginIp ( ) );

        userApi.updateUser ( userDTO );

    }

    /**
     * 获取钉钉配置
     */
    @GetMapping("/sys/dingtalk/clientConfig")
    @ApiOperation("获取钉钉配置")
    public ResponseEntity dingTalkClientConfig(@RequestParam(required = false) String tenantId) {
        String resolvedTenantId = StringUtils.defaultIfBlank(tenantId, "10009");
        com.jeeplus.auth.config.DingTalkProperties.TenantConfig tenantConfig = dingTalkAuthService.getTenantConfig(resolvedTenantId);
        return ResponseUtil.newInstance()
                .add("tenantId", resolvedTenantId)
                .add("corpId", tenantConfig.getCorpId())
                .add("agentId", tenantConfig.getAgentId())
                .ok();
    }

    /**
     * 钉钉authCode登录
     */
    @PostMapping("/sys/dingtalk/login")
    @ApiLog(value = "钉钉授权登录", type = LogTypeEnum.LOGIN)
    @ApiOperation("钉钉授权登录")
    public ResponseEntity dingTalkAuthCodeLogin(@RequestBody LoginForm loginForm) {
        log.info("===== 钉钉免登开始 =====");
        log.info("请求参数: tenantId={}, authCode={}", loginForm.getTenantId(), 
                loginForm.getAuthCode() != null ? loginForm.getAuthCode().substring(0, Math.min(10, loginForm.getAuthCode().length())) + "..." : "null");
        
        String tenantId = StringUtils.defaultIfBlank(loginForm.getTenantId(), "10009");
        log.info("步骤1: 通过authCode获取钉钉用户信息...");
        
        DingTalkAuthService.DingTalkUser dingTalkUser = dingTalkAuthService.getUserByAuthCode(tenantId, loginForm.getAuthCode());
        log.info("步骤1成功: 获取到钉钉用户 userId={}, unionId={}", dingTalkUser.getUserId(), dingTalkUser.getUnionId());
        
        log.info("步骤2: 查询系统用户绑定关系...");
        UserDTO user = userApi.getByDdIdAndTenantId(dingTalkUser.getUserId(), tenantId);
        
        if (user == null) {
            log.warn("用户未绑定,需要绑定账号: ddId={}, tenantId={}", dingTalkUser.getUserId(), tenantId);
            String bindKey = dingTalkAuthService.createBindKey(tenantId, dingTalkUser.getUserId());
            return ResponseUtil.newInstance()
                    .add("bindRequired", true)
                    .add("bindKey", bindKey)
                    .add("tenantId", tenantId)
                    .ok("DingTalk account is not bound");
        }
        
        log.info("步骤2成功: 找到绑定用户 loginName={}, name={}", user.getLoginName(), user.getName());

        ResponseUtil responseUtil = new ResponseUtil();
        String loginUserName = user.getLoginName();
        String username = user.getLoginName();
        Object redisValue = RedisUtils.getInstance().get(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName);
        Integer redisLoginNumber = null;

        if (redisValue != null) {
            redisLoginNumber = Integer.valueOf(redisValue.toString());
        } else {
            redisLoginNumber = 0;
        }
        if (null == redisLoginNumber) {
            redisLoginNumber = 0;
        } else {
            redisLoginNumber++;
        }
        RedisUtils.getInstance().set(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName, redisLoginNumber);
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime midnight = now.toLocalDate().plusDays(1).atStartOfDay();
        long secondsUntilMidnight = Duration.between(now, midnight).getSeconds();
        RedisUtils.getInstance().expire(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName, secondsUntilMidnight);

        Integer loginNumber = 5;
        if (redisLoginNumber >= 10) {
            return ResponseEntity.badRequest().body(ErrorConstants.LOGIN_MAX_COUNT);
        }
        if (redisLoginNumber > loginNumber) {
            return ResponseEntity.badRequest().body(ErrorConstants.LOGIN_CODE);
        }

        AuthenticationManager authenticationManager = SpringUtil.getBean(AuthenticationManager.class);
        SecurityUtils.login(username, "Xg@sys9hB2!xWm", authenticationManager);

        String domain = RequestUtils.getHeader("domain");
        if (domain != null && domain.contains("ydddl")) {

        } else {
            if (!userApi.isEnableLogin(tenantId, username)) {
                throw new DisabledException(ErrorConstants.LOGIN_ERROR_FORBID_LOGGED_IN_ELSEWHERE);
            }
        }

        UserDTO userDTO = userApi.getByLoginName(username, tenantId);

        if("樊莉".equals(userDTO.getName())){

            List<UserDTO> onLineUserList = SpringUtil.getBean(IUserApi.class).getOnLineUserList("黄玮", "10002");
            if(!onLineUserList.isEmpty()){
                throw new DisabledException ( "当前黄玮已登录系统，" + ErrorConstants.LOGIN_ERROR );
            }
        }else if("黄玮".equals(userDTO.getName())){
            List<UserDTO> onLineUserList = SpringUtil.getBean(IUserApi.class).getOnLineUserList("樊莉", "10002");
            if(!onLineUserList.isEmpty()){
                throw new DisabledException ( "当前樊莉已登录系统，" + ErrorConstants.LOGIN_ERROR );
            }
        }

        String token = TokenProvider.createAccessToken(username);
        responseUtil.add(TokenProvider.TOKEN, token);
        updateUserLoginInfo(responseUtil, userDTO, token);

        RedisUtils.getInstance().delete(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName);

        userDTO.setToken(token);
        return responseUtil.ok();
    }

    /**
     *  将当前的钉钉用户绑定到系统账户，然后登录。
     */
    @PostMapping("/sys/dingtalk/bindLogin")
    @ApiLog(value = "钉钉授权并绑定用户进行登录", type = LogTypeEnum.LOGIN)
    @ApiOperation("钉钉授权并绑定用户进行登录")
    public ResponseEntity dingTalkBindLogin(@RequestBody LoginForm loginForm) {
        DingTalkAuthService.BindInfo bindInfo = dingTalkAuthService.getBindInfo(loginForm.getDingTalkBindKey());
        String username = loginForm.getUsername();
        String password = loginForm.getPassword();

        AuthenticationManager authenticationManager = SpringUtil.getBean(AuthenticationManager.class);
        SecurityUtils.login(username, password, authenticationManager);

        UserDTO userDTO = userApi.getByLoginName(username, bindInfo.getTenantId());
        if (userDTO == null) {
            throw new UsernameNotFoundException(ErrorConstants.LOGIN_ERROR_NOTFOUND);
        }
        if (CommonConstants.NO.equals(userDTO.getLoginFlag())) {
            throw new LockedException(ErrorConstants.LOGIN_ERROR_FORBIDDEN);
        }
        //此处用作钉钉绑定唯一系统账号的拦截
        //if (StringUtils.isNotBlank(userDTO.getDdId()) && !bindInfo.getDdId().equals(userDTO.getDdId())) {
        //    return ResponseEntity.badRequest().body("Current system account is already bound to another DingTalk account");
        //}
        UserDTO bindUserDTO = new UserDTO();
        bindUserDTO.setId(userDTO.getId());
        bindUserDTO.setDdId(bindInfo.getDdId());
        userApi.updateUserById(bindUserDTO);
        userDTO.setDdId(bindInfo.getDdId());
        userApi.clearCache(userDTO);
        dingTalkAuthService.removeBindKey(loginForm.getDingTalkBindKey());

        ResponseUtil responseUtil = new ResponseUtil();
        String token = TokenProvider.createAccessToken(username);
        responseUtil.add(TokenProvider.TOKEN, token);
        updateUserLoginInfo(responseUtil, userDTO, token);
        return responseUtil.ok();
    }


}
