瀏覽代碼

物资-移动端嵌入钉钉

huangguoce 1 周之前
父節點
當前提交
61e7db9971

+ 5 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/factory/UserApiFallbackFactory.java

@@ -120,6 +120,11 @@ public class UserApiFallbackFactory implements FallbackFactory <IUserApi> {
             }
 
             @Override
+            public UserDTO getByDdIdAndTenantId(String ddId, String tenantId) {
+                return null;
+            }
+
+            @Override
             public String getOpenIdsByIds(String userIds) {
                 return null;
             }

+ 3 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/feign/IUserApi.java

@@ -183,6 +183,9 @@ public interface IUserApi {
     @GetMapping(value = BASE_URL + "/getByddId")
     UserDTO getByDdId(@RequestParam("ddId") String ddId);
 
+    @GetMapping(value = BASE_URL + "/getByDdIdAndTenantId")
+    UserDTO getByDdIdAndTenantId(@RequestParam("ddId") String ddId, @RequestParam("tenantId") String tenantId);
+
     /**
      * 根据id获取用户
      *

+ 65 - 0
jeeplus-auth/src/main/java/com/jeeplus/auth/config/DingTalkProperties.java

@@ -0,0 +1,65 @@
+package com.jeeplus.auth.config;
+
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "dingtalk")
+public class DingTalkProperties {
+
+    private String defaultTenantId = "10009";
+
+    private String corpId;
+
+    private String agentId;
+
+    private String appKey;
+
+    private String apiKey;
+
+    private String appSecret;
+
+    private Map<String, TenantConfig> tenants = new HashMap<>();
+
+    public TenantConfig getRequiredConfig(String tenantId) {
+        String resolvedTenantId = StringUtils.defaultIfBlank(tenantId, defaultTenantId);
+        TenantConfig tenantConfig = tenants.get(resolvedTenantId);
+        if (tenantConfig == null && tenants.size() == 1) {
+            tenantConfig = tenants.values().iterator().next();
+        }
+        if (tenantConfig == null && StringUtils.isNotBlank(getResolvedAppKey()) && StringUtils.isNotBlank(appSecret)) {
+            tenantConfig = new TenantConfig();
+            tenantConfig.setCorpId(corpId);
+            tenantConfig.setAgentId(agentId);
+            tenantConfig.setAppKey(getResolvedAppKey());
+            tenantConfig.setAppSecret(appSecret);
+        }
+        if (tenantConfig == null || StringUtils.isBlank(tenantConfig.getResolvedAppKey()) || StringUtils.isBlank(tenantConfig.getAppSecret())) {
+            throw new IllegalStateException("DingTalk config is missing for tenant " + resolvedTenantId);
+        }
+        return tenantConfig;
+    }
+
+    private String getResolvedAppKey() {
+        return StringUtils.defaultIfBlank(appKey, apiKey);
+    }
+
+    @Data
+    public static class TenantConfig {
+        private String corpId;
+        private String agentId;
+        private String appKey;
+        private String apiKey;
+        private String appSecret;
+
+        public String getResolvedAppKey() {
+            return StringUtils.defaultIfBlank(appKey, apiKey);
+        }
+    }
+}

+ 144 - 0
jeeplus-auth/src/main/java/com/jeeplus/auth/controller/LoginController.java

@@ -8,6 +8,7 @@ import cn.hutool.captcha.LineCaptcha;
 import cn.hutool.extra.servlet.ServletUtil;
 import cn.hutool.extra.spring.SpringUtil;
 import com.jeeplus.auth.model.LoginForm;
+import com.jeeplus.auth.service.DingTalkAuthService;
 import com.jeeplus.common.SecurityUtils;
 import com.jeeplus.common.TokenProvider;
 import com.jeeplus.common.constant.CacheNames;
@@ -75,6 +76,9 @@ public class LoginController {
     @Autowired
     private RedisUtils redisUtils;
 
+    @Autowired
+    private DingTalkAuthService dingTalkAuthService;
+
 
     @PostMapping("/login")
     @ApiLog(value = "用户登录", type = LogTypeEnum.LOGIN)
@@ -495,5 +499,145 @@ public class LoginController {
 
     }
 
+    /**
+     * DingTalk H5 client config.
+     */
+    @GetMapping("/sys/dingtalk/clientConfig")
+    @ApiOperation("DingTalk H5 client config")
+    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 = "DingTalk authCode login", type = LogTypeEnum.LOGIN)
+    @ApiOperation("DingTalk authCode login")
+    public ResponseEntity dingTalkAuthCodeLogin(@RequestBody LoginForm loginForm) {
+        String tenantId = StringUtils.defaultIfBlank(loginForm.getTenantId(), "10009");
+        DingTalkAuthService.DingTalkUser dingTalkUser = dingTalkAuthService.getUserByAuthCode(tenantId, loginForm.getAuthCode());
+        UserDTO user = userApi.getByDdIdAndTenantId(dingTalkUser.getUserId(), tenantId);
+        if (user == null) {
+            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");
+        }
+
+        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 = "DingTalk bind login", type = LogTypeEnum.LOGIN)
+    @ApiOperation("DingTalk bind login")
+    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();
+    }
+
 
 }

+ 18 - 0
jeeplus-auth/src/main/java/com/jeeplus/auth/model/LoginForm.java

@@ -43,4 +43,22 @@ public class LoginForm {
      */
     @ApiModelProperty("用户的钉钉Id")
     private String ddId;
+
+    /**
+     * 钉钉code
+     */
+    @ApiModelProperty("钉钉code")
+    private String authCode;
+
+    /**
+     * tenantId
+     */
+    @ApiModelProperty("tenantId")
+    private String tenantId;
+
+    /**
+     * 钉钉key
+     */
+    @ApiModelProperty("钉钉key")
+    private String dingTalkBindKey;
 }

+ 117 - 0
jeeplus-auth/src/main/java/com/jeeplus/auth/service/DingTalkAuthService.java

@@ -0,0 +1,117 @@
+package com.jeeplus.auth.service;
+
+import cn.hutool.core.util.URLUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.jeeplus.auth.config.DingTalkProperties;
+import com.jeeplus.common.redis.RedisUtils;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.UUID;
+
+@Service
+public class DingTalkAuthService {
+
+    private static final String CACHE_NAME = "dingtalk:auth";
+    private static final String ACCESS_TOKEN_KEY = "accessToken:";
+    private static final String BIND_KEY = "bind:";
+    private static final long ACCESS_TOKEN_EXPIRE_SECONDS = 7000L;
+    private static final long BIND_KEY_EXPIRE_SECONDS = 600L;
+
+    @Autowired
+    private DingTalkProperties dingTalkProperties;
+
+    @Autowired
+    private RedisUtils redisUtils;
+
+    public DingTalkProperties.TenantConfig getTenantConfig(String tenantId) {
+        return dingTalkProperties.getRequiredConfig(tenantId);
+    }
+
+    public DingTalkUser getUserByAuthCode(String tenantId, String authCode) {
+        if (StringUtils.isBlank(authCode)) {
+            throw new IllegalArgumentException("DingTalk authCode is required");
+        }
+        String accessToken = getAccessToken(tenantId);
+        JSONObject requestBody = JSONUtil.createObj().set("code", authCode);
+        String response = HttpRequest
+                .post("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo?access_token=" + URLUtil.encodeQuery(accessToken))
+                .body(requestBody.toString())
+                .timeout(10000)
+                .execute()
+                .body();
+        JSONObject responseJson = JSONUtil.parseObj(response);
+        Integer errCode = responseJson.getInt("errcode");
+        if (errCode == null || errCode != 0) {
+            throw new IllegalStateException("DingTalk get user info failed: " + responseJson.getStr("errmsg"));
+        }
+        JSONObject result = responseJson.getJSONObject("result");
+        if (result == null || StringUtils.isBlank(result.getStr("userid"))) {
+            throw new IllegalStateException("DingTalk user id is empty");
+        }
+        return new DingTalkUser(result.getStr("userid"), result.getStr("unionid"));
+    }
+
+    public String createBindKey(String tenantId, String ddId) {
+        String bindKey = UUID.randomUUID().toString().replace("-", "");
+        redisUtils.set(CACHE_NAME, BIND_KEY + bindKey, tenantId + "|" + ddId);
+        redisUtils.expire(CACHE_NAME, BIND_KEY + bindKey, BIND_KEY_EXPIRE_SECONDS);
+        return bindKey;
+    }
+
+    public BindInfo getBindInfo(String bindKey) {
+        Object value = redisUtils.get(CACHE_NAME, BIND_KEY + bindKey);
+        if (value == null) {
+            throw new IllegalArgumentException("DingTalk bind key is expired");
+        }
+        String[] parts = value.toString().split("\\|", 2);
+        if (parts.length != 2 || StringUtils.isBlank(parts[0]) || StringUtils.isBlank(parts[1])) {
+            throw new IllegalArgumentException("DingTalk bind key is invalid");
+        }
+        return new BindInfo(parts[0], parts[1]);
+    }
+
+    public void removeBindKey(String bindKey) {
+        redisUtils.delete(CACHE_NAME, BIND_KEY + bindKey);
+    }
+
+    private String getAccessToken(String tenantId) {
+        String cacheKey = ACCESS_TOKEN_KEY + StringUtils.defaultIfBlank(tenantId, dingTalkProperties.getDefaultTenantId());
+        Object cachedToken = redisUtils.get(CACHE_NAME, cacheKey);
+        if (cachedToken != null && StringUtils.isNotBlank(cachedToken.toString())) {
+            return cachedToken.toString();
+        }
+        DingTalkProperties.TenantConfig tenantConfig = dingTalkProperties.getRequiredConfig(tenantId);
+        String url = "https://oapi.dingtalk.com/gettoken?appkey=" + URLUtil.encodeQuery(tenantConfig.getResolvedAppKey())
+                + "&appsecret=" + URLUtil.encodeQuery(tenantConfig.getAppSecret());
+        String response = HttpRequest.get(url).timeout(10000).execute().body();
+        JSONObject responseJson = JSONUtil.parseObj(response);
+        Integer errCode = responseJson.getInt("errcode");
+        if (errCode == null || errCode != 0 || StringUtils.isBlank(responseJson.getStr("access_token"))) {
+            throw new IllegalStateException("DingTalk access token failed: " + responseJson.getStr("errmsg"));
+        }
+        String accessToken = responseJson.getStr("access_token");
+        redisUtils.set(CACHE_NAME, cacheKey, accessToken);
+        redisUtils.expire(CACHE_NAME, cacheKey, ACCESS_TOKEN_EXPIRE_SECONDS);
+        return accessToken;
+    }
+
+    @Data
+    @AllArgsConstructor
+    public static class DingTalkUser {
+        private String userId;
+        private String unionId;
+    }
+
+    @Data
+    @AllArgsConstructor
+    public static class BindInfo {
+        private String tenantId;
+        private String ddId;
+    }
+}

+ 5 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/feign/UserApiImpl.java

@@ -144,6 +144,11 @@ public class UserApiImpl implements IUserApi {
     }
 
     @Override
+    public UserDTO getByDdIdAndTenantId(String ddId, String tenantId) {
+        return userService.getByDdIdAndTenantId(ddId, tenantId);
+    }
+
+    @Override
     public String getOpenIdsByIds(String userIds) {
         // 使用逗号分隔字符串并转换为数组
         String[] usersArray = userIds.split(",");

+ 3 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/UserMapper.java

@@ -44,6 +44,9 @@ public interface UserMapper extends BaseMapper <User> {
 
     @InterceptorIgnore(tenantLine = "true")
     UserDTO getByDdId(@Param("ddId") String ddId);
+
+    @InterceptorIgnore(tenantLine = "true")
+    UserDTO getByDdIdAndTenantId(@Param("ddId") String ddId, @Param("tenantId") String tenantId);
     /**
      * 获取岗位列表
      *

+ 5 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/xml/UserMapper.xml

@@ -739,6 +739,11 @@ select a.id, a.company_id as "companyDTO.id", a.office_id as "officeDTO.id", a.l
             login_name, password
         FROM sys_user a where dd_id = #{ddId}
     </select>
+    <select id="getByDdIdAndTenantId" resultType="com.jeeplus.sys.service.dto.UserDTO">
+        SELECT
+            login_name, password
+        FROM sys_user a where dd_id = #{ddId} and tenant_id = #{tenantId} and del_flag = '0'
+    </select>
     <select id="getAllOpenIds" resultType="java.lang.String">
         SELECT open_id FROM sys_user WHERE del_flag = 0 and open_id is not null
     </select>

+ 4 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/UserService.java

@@ -90,6 +90,10 @@ public class UserService extends ServiceImpl<UserMapper, User> {
         return baseMapper.getByDdId(ddId);
     }
 
+    public UserDTO getByDdIdAndTenantId(String ddId, String tenantId) {
+        return baseMapper.getByDdIdAndTenantId(ddId, tenantId);
+    }
+
     public List<String> getAllOpenIds() {
         return baseMapper.getAllOpenIds();
     }