zjc 1 год назад
Родитель
Сommit
3fc2d05db2
21 измененных файлов с 1573 добавлено и 576 удалено
  1. 4 4
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/dingding/SyncHttpConstant.java
  2. 151 27
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dingding/DingThirdAuthController.java
  3. 4 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dingding/vo/AuthLoginRequest.java
  4. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantDingSaveReqVO.java
  5. 6 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantDO.java
  6. 1 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java
  7. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/AdminUserMapper.java
  8. 9 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java
  9. 33 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
  10. 8 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java
  11. 45 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
  12. 4 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dingding/DingAuthServiceInfo.java
  13. 89 12
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dingding/DingAuthServiceInfoImpl.java
  14. 77 14
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dingding/DingThirdTokenService.java
  15. 28 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java
  16. 128 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
  17. 16 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java
  18. 31 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
  19. 6 4
      yudao-server/src/main/resources/application.yaml
  20. 2 2
      yudao-ui/yudao-ui-admin-vue2/package.json
  21. 925 512
      yudao-ui/yudao-ui-admin-vue2/yarn.lock

+ 4 - 4
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/dingding/SyncHttpConstant.java

@@ -7,21 +7,21 @@ public class SyncHttpConstant {
     /**
      * 应用的SuiteKey,登录开发者后台,点击应用管理,进入应用详情可见
      */
-    public static final String SUITE_KEY="suiteyjd6ikxpg8629ydr";
+    public static final String SUITE_KEY="suite7tssbigaaqsejgth";
 
     /**
      * 应用的SuiteSecret,登录开发者后台,点击应用管理,进入应用详情可见
      */
-    public static final String SUITE_SECRET="znq4BDYYX4vFLxtjfknEoId6j84LT2xpW7gkRTvpVZVoMfbfHXZvZX3cQ2cLQzON";
+    public static final String SUITE_SECRET="uyyAvh0ivBSByZ8s3dfCT6FHqZv1z4PytMpe8mfF_zcLBIAceeNmJ-oe_OPwiCcw";
 
     /**
      * 回调URL签名用。应用的签名Token, 登录开发者后台,点击应用管理,进入应用详情可见
      */
-    public static final String TOKEN = "foHrAyOAp4kIeNvi";
+    public static final String TOKEN = "nBwmOcCQIKV9Buo5zeDDpH1VwltLoEEtGrUNsD5bmO7uK";
 
     /**
      * 回调URL加解密用。应用的"数据加密密钥",登录开发者后台,点击应用管理,进入应用详情可见
      */
-    public static final String ENCODING_AES_KEY = "o13UDrSomxLeKRhwWtOgjjYpwLkzFS10HXz408Jt7OI";
+    public static final String ENCODING_AES_KEY = "RE8Vy85z5LsqYLv5GLhnyGiAJGj1EifrfajnoJb6hN1";
 
 }

+ 151 - 27
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dingding/DingThirdAuthController.java

@@ -1,13 +1,28 @@
 package cn.iocoder.yudao.module.system.controller.admin.dingding;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.dingding.vo.AuthLoginRequest;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantJoinReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.dingding.DingUserTenantRelateDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.dal.mysql.dingding.DingUserTenantRelateMapper;
+import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
+import cn.iocoder.yudao.module.system.service.dept.DeptService;
 import cn.iocoder.yudao.module.system.service.dingding.DingAuthServiceInfo;
 import cn.iocoder.yudao.module.system.service.dingding.DingThirdTokenService;
+import cn.iocoder.yudao.module.system.service.tenant.TenantService;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
-import com.dingtalk.api.response.OapiV2UserGetResponse;
-import com.dingtalk.api.response.OapiV2UserGetuserinfoResponse;
+import com.aliyun.dingtalkcontact_1_0.models.GetUserResponse;
+import com.aliyun.dingtalkcontact_1_0.models.GetUserResponseBody;
+import com.aliyun.dingtalkoauth2_1_0.models.GetSsoUserInfoResponse;
+import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenResponse;
+import com.dingtalk.api.response.*;
 import com.fasterxml.jackson.databind.JsonNode;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,20 +31,20 @@ import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import javax.annotation.security.PermitAll;
+import java.util.*;
 
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.TENANT_NOT_EXISTS;
 
 /**
  * <p>DingLoginController 此类用于:钉钉企业内部应用免登(H5微应用)</p>
  * <p>@remark:钉钉企业内部微应用DEMO, 实现了身份验证(免登)功能</p>
  */
 @Tag(name = "管理后台 - 钉钉第三方企业应用免登")
-@Controller
-@RequestMapping(value = "/auth/ding")
+@RestController
+@RequestMapping(value = "/system/auth/ding")
 @Slf4j
 public class DingThirdAuthController {
 
@@ -39,7 +54,19 @@ public class DingThirdAuthController {
     @Resource
     private DingAuthServiceInfo dingAuthServiceInfo;
 
+    @Resource
+    private AdminAuthService authService;
+    @Resource
+    private AdminUserService userService;
+    @Resource
+    private TenantService tenantService;
+    @Resource
+    private DeptService deptService;
+    @Resource
+    private DingUserTenantRelateMapper dingUserTenantRelateMapper;
+
     @PostMapping(value = "/callback")
+    @PermitAll
     @ResponseBody
     public Map<String, String> callback(@RequestParam String signature,
                                         @RequestParam String timestamp,
@@ -54,35 +81,132 @@ public class DingThirdAuthController {
         return resultMap;
     }
 
+    @Operation(summary = "第三方企业应用用户登录")
+    @PostMapping("/authLogin")
+    public CommonResult<AuthLoginRespVO> authLogin(@RequestBody AuthLoginRequest authLoginRequest) {
+        String code = authLoginRequest.getCode();
+        String corpId =  authLoginRequest.getCorpId();
+        log.info("钉钉用户登录第三方企业应用code:{},corpId:{}", authLoginRequest.getCode(), authLoginRequest.getCorpId());
+        //调用接口获取第三方企业应用的access_token,详情请参考获取第三方应用授权企业的accessToken。
+//        String corpAccessToken = dingAuthTokenService.getCorpAccessToken(authLoginRequest.getCorpId());
+
+        GetUserTokenResponse userTokenResponse = dingAuthTokenService.getUserAccessToken(authLoginRequest.getCode());
+        log.info("用户登录信息corpAccessToken" + JSONObject.toJSONString(userTokenResponse));
+        String userAccessToken = userTokenResponse.getBody().getAccessToken();
+        GetUserResponse addressBookUserInfo = dingAuthTokenService.getAddressBookUserInfo(userAccessToken, "me");
+        log.info("用户通讯录信息" + JSONObject.toJSONString(addressBookUserInfo));
+
+        //查询租户
+        TenantDO tenant = tenantService.getTenantByCorpId(corpId);
+        if (tenant == null || tenant.getId() == null) {
+            throw exception(TENANT_NOT_EXISTS);
+        }
+        //todo 查询租户管理员信息
+        DingUserTenantRelateDO dingUserTenantRelateDO = dingUserTenantRelateMapper.selectOne(DingUserTenantRelateDO::getTenantId,tenant.getId());
+        //获取企业token。
+        String corpAccessToken = dingAuthTokenService.getThirdCorpAccessToken(corpId,tenant.getSuiteTicket());
+        System.out.println("企业" + JSONObject.toJSONString(corpAccessToken));
+        //根据管理员用户userid查询管理员信息
+        OapiV2UserGetResponse userUnfo = dingAuthTokenService.getUser(dingUserTenantRelateDO.getUserId(), corpAccessToken);
+        System.out.println("用户管理员信息" + JSONObject.toJSONString(userUnfo));
+
+        //-权限需开通
+        OapiV2DepartmentListsubResponse departmentListsubResponse = dingAuthTokenService.getDept(corpAccessToken);
+        System.out.println("部门组" + JSONObject.toJSONString(departmentListsubResponse.getResult()));
+
+        //todo 管理员登录时同步部门信息
+        deptService.updateDDingDept(departmentListsubResponse.getResult(),tenant.getId());
+
+        //todo 初始化用户
+        GetUserResponseBody result= addressBookUserInfo.getBody();
+        Boolean adminType = false;
+        //todo 判断是否是管理员用户
+        if(userUnfo.getResult().getUserid().equals(result.getUnionId())){
+            adminType = true;
+        }
+        //todo 根据授权登录人的UnionId获取用户详细- ——X——获取的内容不需要
+//        OapiUserGetbyunionidResponse authUserInfo = dingAuthTokenService.getUserUnionId(result.getUnionId(), corpAccessToken);
+//        System.out.println("登录用户信息详情" + JSONObject.toJSONString(authUserInfo));
+
+        //todo 获取用户详情。查询是否存在
+        AdminUserDO adminUserDO = userService.getUserByMobile(addressBookUserInfo.getBody().getMobile());
+        if (Objects.isNull(adminUserDO)) {
+            //创建用户信息
+            UserSaveReqVO userSaveReqVO = new UserSaveReqVO();
+            userSaveReqVO.setUsername(result.getUnionId());
+            userSaveReqVO.setNickname(result.getNick());
+            userSaveReqVO.setEmail(result.getEmail());
+            userSaveReqVO.setMobile(result.getMobile());
+            userSaveReqVO.setAvatar(result.getAvatarUrl());
+            userSaveReqVO.setPassword(result.getMobile());
+
+            adminUserDO = dingAuthServiceInfo.initUser(userSaveReqVO, corpId, adminType);
+        }
+        if(!adminUserDO.getTenantId().equals(tenant.getId())){
+            userService.updateUserTenantId(adminUserDO.getId(),tenant.getId());
+            //todo 更新用户租户操作
+            TenantJoinReqVO createReqVO = new TenantJoinReqVO();
+            createReqVO.setTenantId(tenant.getId());
+            createReqVO.setCorpId(corpId);
+            createReqVO.setUserId(adminUserDO.getId());
+            tenantService.joinDDingTenant(createReqVO);
+        }
+        TenantContextHolder.setTenantId(tenant.getId());
+        adminUserDO.setTenantId(tenant.getId());
+        return success(authService.DDinglogin(adminUserDO),tenant.getId().toString());
+    }
+
     @Operation(summary = "第三方企业应用授权")
     @PostMapping("/cropLogin")
-    @ResponseBody
-    public CommonResult<String> cropLogin(@RequestBody AuthLoginRequest authLoginRequest) {
+    @PermitAll
+    public CommonResult<AuthLoginRespVO> cropLogin(@RequestBody AuthLoginRequest authLoginRequest) {
 
-        log.info("钉钉用户登录第三方企业应用code:{},corpId:{}", authLoginRequest.getCode(), authLoginRequest.getCorpId());
+        //todo 功能不完善,可参考[第三方企业应用用户登录]应用授权
+
+        System.out.println("钉钉用户登录第三方企业应用code:{},corpId:{}" + authLoginRequest);
+        String code = authLoginRequest.getCode();
+        String corpId =  authLoginRequest.getCorpId();
+        System.out.println("钉钉用户登录第三方企业应用code:{},corpId:{}" + code + corpId);
         //调用接口获取第三方企业应用的access_token,详情请参考获取第三方应用授权企业的accessToken。
-        String corpAccessToken = dingAuthTokenService.getCorpAccessToken(authLoginRequest.getCorpId());
-//        GetUserTokenResponse userTokenResponse = dingAuthTokenService.getUserAccessToken(authLoginRequest.getCode());
-        log.info("用户登录信息corpAccessToken" + corpAccessToken);
+        String corpAccessToken = dingAuthTokenService.getCorpAccessToken(corpId);
+        GetUserTokenResponse userTokenResponse = dingAuthTokenService.getUserAccessToken(code);
+        System.out.println("用户登录信息corpAccessToken" + corpAccessToken);
         //获取用户userid。
-        OapiV2UserGetuserinfoResponse userUnfo = dingAuthTokenService.getUserUnfo(authLoginRequest.getCode(), corpAccessToken);
-        log.info("用户登录信息" + JSONObject.toJSONString(userUnfo));
+        OapiV2UserGetuserinfoResponse userUnfo = dingAuthTokenService.getUserUnfo(code, corpAccessToken);
+        System.out.println("用户登录信息" + JSONObject.toJSONString(userUnfo));
         //调用接口获取用户的userid,详情请参考通过免登码获取用户信息。
         OapiV2UserGetResponse authUser = dingAuthTokenService.getAuthUser(corpAccessToken, userUnfo.getResult().getUserid());
-
-        log.info("用户登录详细信息" + JSONObject.toJSONString(authUser.getResult()));
+        System.out.println("用户登录详细信息" + JSONObject.toJSONString(authUser.getResult()));
 
 //        GetUserResponse addressBookUserInfo = dingAuthTokenService.getAddressBookUserInfo(corpAccessToken, "me");
 //        log.info("用户通讯录信息" + JSONObject.toJSONString(addressBookUserInfo));
-        //获取用户详情。
-//        SysUser sysUser = dingAuthServiceInfo.getUserByUserNameAndCorpId(userUnfo.getResult().getUserid(), authLoginRequest.getCorpId());
-//        if (StringUtils.isNull(sysUser)) {
-        // todo 初始化用户
-//        dingAuthServiceInfo.initUser(authUser.getResult(), authLoginRequest.getCorpId());
-//        }
-        String token = "";
-//        token = loginService.authDingThirdLogin(authUser.getResult().getUserid(), authLoginRequest.getCorpId());
-        return success(token);
+
+        //查询租户
+        TenantDO tenant = tenantService.getTenantByCorpId(corpId);
+        if (tenant == null || tenant.getId() == null) {
+            throw exception(TENANT_NOT_EXISTS);
+        }
+        //todo 获取用户详情。查询是否存在
+        AdminUserDO adminUserDO = userService.getUserByUserIdAndCorpId(userUnfo.getResult().getUserid(), tenant.getId());
+        if (Objects.isNull(adminUserDO)) {
+            // todo 初始化用户
+
+            OapiV2UserGetResponse.UserGetResponse result = authUser.getResult();
+            //创建用户信息
+            UserSaveReqVO userSaveReqVO = new UserSaveReqVO();
+            userSaveReqVO.setUsername(authUser.getResult().getUserid());
+            userSaveReqVO.setNickname(result.getName());
+            userSaveReqVO.setEmail(result.getEmail());
+            userSaveReqVO.setMobile(result.getMobile());
+            userSaveReqVO.setRemark(result.getRemark());
+            userSaveReqVO.setAvatar(result.getAvatar());
+            userSaveReqVO.setPassword(result.getMobile());
+
+            adminUserDO = dingAuthServiceInfo.initUser(userSaveReqVO, corpId,true);
+        }
+        TenantContextHolder.setTenantId(tenant.getId());
+        adminUserDO.setTenantId(tenant.getId());
+        return success(authService.DDinglogin(adminUserDO),tenant.getId().toString());
     }
 
     @Operation(summary = "用户向企业管理员提交授权申请")

+ 4 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dingding/vo/AuthLoginRequest.java

@@ -8,7 +8,10 @@ import lombok.Data;
  * @date 2024年05月09日 17:27
  */
 @Data
-public class AuthLoginRequest  extends BaseRequest {
+public class AuthLoginRequest {
     //用户企业id
     private String corpId;
+    //用户登录code
+    private String code;
+
 }

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantDingSaveReqVO.java

@@ -35,4 +35,7 @@ public class TenantDingSaveReqVO {
     @Schema(description = "管理员ID")
     private String manageUserId;
 
+    @Schema(description = "管理员ID")
+    private String suiteTicket;
+
 }

+ 6 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantDO.java

@@ -87,4 +87,10 @@ public class TenantDO extends BaseDO {
      */
     private Integer accountMode;
 
+
+    /**
+     * 账号模式
+     */
+    private String suiteTicket;
+
 }

+ 1 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java

@@ -26,6 +26,7 @@ import java.util.Set;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
+
 public class AdminUserDO extends TenantBaseDO {
 
     /**

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/AdminUserMapper.java

@@ -53,4 +53,7 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
         return selectOne(AdminUserDO::getId, userId);
     }
 
+    default AdminUserDO getUserByUserIdAndCorpId(String userName, Long tenantId) {
+        return selectOne(AdminUserDO::getUsername, userName,AdminUserDO::getTenantId,tenantId);
+    }
 }

+ 9 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java

@@ -31,6 +31,15 @@ public interface AdminAuthService {
      */
     AuthLoginRespVO login(@Valid AuthLoginReqVO reqVO);
 
+
+    /**
+     * 账号登录
+     *
+     * @param reqVO 登录信息
+     * @return 登录结果
+     */
+    AuthLoginRespVO DDinglogin(AdminUserDO reqVO);
+
     /**
      * 基于 token 退出登录
      *

+ 33 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java

@@ -110,6 +110,21 @@ public class AdminAuthServiceImpl implements AdminAuthService {
         return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
     }
 
+    @Override
+    public AuthLoginRespVO DDinglogin(AdminUserDO reqVO) {
+
+        // 使用账号密码,进行登录
+//        AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword());
+
+        // 如果 socialType 非空,说明需要绑定社交用户
+//        if (reqVO.getSocialType() != null) {
+//            socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
+//                    reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
+//        }
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(reqVO.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME,reqVO.getTenantId());
+    }
+
     @Override
     public void sendSmsCode(AuthSmsSendReqVO reqVO) {
         // 登录场景,验证是否存在
@@ -245,6 +260,24 @@ public class AdminAuthServiceImpl implements AdminAuthService {
         return AuthConvert.INSTANCE.convert(accessTokenDO);
     }
 
+    /**
+     * 生成Token携带租户
+     * @param userId
+     * @param username
+     * @param logType
+     * @param tenantId
+     * @return
+     */
+    private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType, Long tenantId) {
+        // 插入登陆日志
+        createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
+        // 创建访问令牌
+        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
+                OAuth2ClientConstants.CLIENT_ID_DEFAULT, null,tenantId);
+        // 构建返回结果
+        return AuthConvert.INSTANCE.convert(accessTokenDO);
+    }
+
     @Override
     public AuthLoginRespVO refreshToken(String refreshToken) {
         OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);

+ 8 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
+import com.dingtalk.api.response.OapiV2DepartmentListsubResponse;
 
 import java.util.Collection;
 import java.util.List;
@@ -99,4 +100,11 @@ public interface DeptService {
      */
     void validateDeptList(Collection<Long> ids);
 
+    /**
+     * 钉钉部门数据同步更新
+     * @param result
+     * @param tenantId 租户ID
+     */
+    void updateDDingDept(List<OapiV2DepartmentListsubResponse.DeptBaseResponse> result,Long tenantId);
+
 }

+ 45 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java

@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqV
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
 import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
+import com.dingtalk.api.response.OapiV2DepartmentListsubResponse;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.cache.annotation.CacheEvict;
@@ -215,4 +216,48 @@ public class DeptServiceImpl implements DeptService {
         });
     }
 
+    @Override
+    public void updateDDingDept(List<OapiV2DepartmentListsubResponse.DeptBaseResponse> result,Long tenantId) {
+        //遍历部门组更新
+        if(!result.isEmpty()){
+            for (OapiV2DepartmentListsubResponse.DeptBaseResponse deptBaseResponse:
+                    result) {
+                //根据DDing的deptId查询创建时是否存在
+                DeptDO dept = deptMapper.selectOne(DeptDO::getCreator,deptBaseResponse.getDeptId());
+                if(dept == null){
+                    //todo不存在 新增部门
+                    dept = new DeptDO();
+                    dept.setName(deptBaseResponse.getName());
+                    //todo 查询父级ID
+                    if(deptBaseResponse.getParentId().equals(1L)){
+                        dept.setParentId(0L);
+                    } else {
+                        DeptDO parentDept= deptMapper.selectOne(DeptDO::getCreator,deptBaseResponse.getParentId());
+                        if(parentDept != null){
+                            dept.setParentId(parentDept.getId());
+                        }
+                    }
+                    dept.setCreator(String.valueOf(deptBaseResponse.getDeptId()));
+                    dept.setTenantId(tenantId);
+                    dept.setStatus(CommonStatusEnum.ENABLE.getStatus());
+                    deptMapper.insert(dept);
+                } else {
+                    dept.setName(deptBaseResponse.getName());
+                    //todo 查询父级ID
+                    if(deptBaseResponse.getParentId().equals(1L)){
+                        dept.setParentId(0L);
+                    } else {
+                        DeptDO parentDept= deptMapper.selectOne(DeptDO::getCreator,deptBaseResponse.getParentId());
+                        if(parentDept != null){
+                            dept.setParentId(parentDept.getId());
+                        }
+                    }
+                    dept.setCreator(String.valueOf(deptBaseResponse.getDeptId()));
+                    dept.setTenantId(tenantId);
+                    deptMapper.updateById(dept);
+                }
+            }
+        }
+    }
+
 }

+ 4 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dingding/DingAuthServiceInfo.java

@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.module.system.service.dingding;
 
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import com.alibaba.fastjson.JSONObject;
+import com.dingtalk.api.response.OapiV2UserGetResponse;
 
 import java.util.Map;
 
@@ -16,4 +19,5 @@ public interface DingAuthServiceInfo {
 
     Map<String, String> encryptText(String text);
 
+    AdminUserDO initUser(UserSaveReqVO userSaveReqVO, String corpId, Boolean adminType);
 }

+ 89 - 12
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dingding/DingAuthServiceInfoImpl.java

@@ -1,27 +1,48 @@
 package cn.iocoder.yudao.module.system.service.dingding;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
+import cn.iocoder.yudao.module.employee.api.EmployeeApi;
+import cn.iocoder.yudao.module.employee.api.dto.EmployeeCreateReqDTO;
+import cn.iocoder.yudao.module.employee.api.dto.EmployeeQueryReqDTO;
+import cn.iocoder.yudao.module.employee.api.dto.EmployeeRespDTO;
+import cn.iocoder.yudao.module.employee.api.dto.EmployeeSaveReqDTO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantDingSaveReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantJoinReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantSimpleRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.tenant.UserTenantRelateSaveReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.enums.dingding.Constants;
 import cn.iocoder.yudao.module.system.enums.dingding.SyncHttpConstant;
 import cn.iocoder.yudao.module.system.service.tenant.TenantService;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import cn.iocoder.yudao.module.system.service.user.UserTenantRelateService;
 import cn.iocoder.yudao.module.system.util.dingding.RedisCache;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.dingtalk.api.response.OapiV2UserGetResponse;
 import com.dingtalk.oapi.lib.aes.DingTalkEncryptException;
 import com.dingtalk.oapi.lib.aes.DingTalkEncryptor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
 
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.module.system.enums.dingding.CallbackConstant.*;
+import static java.util.Collections.singleton;
 
 
 /**
@@ -39,6 +60,10 @@ public class DingAuthServiceInfoImpl implements DingAuthServiceInfo {
     @Resource
     private TenantService tenantService;
 
+    @Resource
+    private AdminUserService userService;
+
+
     @Override
     public String decryptText(String signature, String timestamp, String nonce, String encryptMsg) {
         String plainText = "";
@@ -59,14 +84,16 @@ public class DingAuthServiceInfoImpl implements DingAuthServiceInfo {
         String corp_id = null;
         JSONObject biz_data = null;
         Integer bizType = 0;
-        for (Object bizDatum : bizData) {
-            JSONObject bizDataJson = (JSONObject) bizDatum;
-            log.info("[callback] 获取到授权bizDataJson:" + bizDataJson);
-            String biz_data_str = bizDataJson.getString("biz_data");
-            biz_data = JSONObject.parseObject(biz_data_str);
-            bizType = bizDataJson.getInteger("biz_type");
-            log.info("[callback] 获取到bizType:" + bizType);
-            corp_id = bizDataJson.getString("corp_id");
+        if(bizData != null){
+            for (Object bizDatum : bizData) {
+                JSONObject bizDataJson = (JSONObject) bizDatum;
+                log.info("[callback] 获取到授权bizDataJson:" + bizDataJson);
+                String biz_data_str = bizDataJson.getString("biz_data");
+                biz_data = JSONObject.parseObject(biz_data_str);
+                bizType = bizDataJson.getInteger("biz_type");
+                log.info("[callback] 获取到bizType:" + bizType);
+                corp_id = bizDataJson.getString("corp_id");
+            }
         }
 
         switch (eventType) {
@@ -124,8 +151,10 @@ public class DingAuthServiceInfoImpl implements DingAuthServiceInfo {
                 //套件票据
                 if (bizType == 2 && biz_data != null && corp_id != null) {
                     String suiteTicket = biz_data.getString("suiteTicket");
-                    String redisKeyPrefix = Constants.DAILY_DING_AUTH + corp_id + ":";
-                    redisCache.setCacheObject(redisKeyPrefix + Constants.DAILY_DING_SUITE_TICKET, suiteTicket, 5, TimeUnit.HOURS);
+//                    String redisKeyPrefix = Constants.DAILY_DING_AUTH + corp_id + ":";
+//                    redisCache.setCacheObject(redisKeyPrefix + Constants.DAILY_DING_SUITE_TICKET, suiteTicket, 5, TimeUnit.HOURS);
+                    //todo 更新票据-数据为第三方企业应用票据最新suiteTicket,定时每5个小时推送一次。
+                    tenantService.updateTenantSuiteTicket(corp_id,suiteTicket);
                 }
                 //套件票据授权
                 if (bizType == 4 && biz_data != null && corp_id != null) {
@@ -137,13 +166,19 @@ public class DingAuthServiceInfoImpl implements DingAuthServiceInfo {
                     String syncAction = biz_data.getString("syncAction");
                     if ("org_suite_auth".equals(syncAction)) {
                         log.info("[callback] 企业授权第三方企业应用:{}",corp_id);
-                        this.createTenant(biz_data, corp_id);
+                        TenantDO tenant = tenantService.getTenantByCorpId(corp_id);
+                        if (tenant == null || tenant.getId() == null) {
+                            this.createTenant(biz_data, corp_id);
+                        }else{
+                            this.updateTenant(biz_data, corp_id);
+                        }
                     } else if ("org_suite_change".equals(syncAction)) {
                         log.info("[callback] 表示企业变更授权范围:{}",corp_id);
                         this.updateTenant(biz_data, corp_id);
                     } else if ("org_suite_relieve".equals(syncAction)) {
                         log.info("[callback] 表示企业解除授权:{}",corp_id);
-                        this.deleteTenant(biz_data, corp_id);
+                        //todo 暂时不处理
+//                        this.deleteTenant(biz_data, corp_id);
                     }
                 }
                 resultMap = encryptText(CALLBACK_RETURN_SUCCESS);
@@ -172,6 +207,47 @@ public class DingAuthServiceInfoImpl implements DingAuthServiceInfo {
         return resultMap;
     }
 
+    /**
+     * 钉钉初始化用户单点登录接口
+     * @param userSaveReqVO
+     * @param corpId
+     * @param adminType 是否是管理员
+     */
+    @Override
+    public AdminUserDO initUser(UserSaveReqVO userSaveReqVO, String corpId, Boolean adminType) {
+
+        //查询租户
+        TenantDO tenant = tenantService.getTenantByCorpId(corpId);
+        if (tenant == null || tenant.getId() == null) {
+            throw exception(TENANT_NOT_EXISTS);
+        }
+        // 校验账号是否存在
+        AdminUserDO user = userService.getUserByUsername(userSaveReqVO.getMobile());
+        if (user != null) {
+            throw exception(USER_USERNAME_EXISTS);
+        }
+
+        Long id = userService.createDDingUser(userSaveReqVO,tenant.getId());
+        // 获得新用户信息
+        user = userService.getUserByMobile(userSaveReqVO.getMobile());
+        if (user == null) {
+            throw exception(USER_NOT_EXISTS);
+        }
+
+        if(adminType){
+            //执行租户信息初始化操作
+            tenantService.createDDingInit(user,tenant);
+        }else{
+            //todo 更新用户租户操作
+            TenantJoinReqVO createReqVO = new TenantJoinReqVO();
+            createReqVO.setTenantId(tenant.getId());
+            createReqVO.setCorpId(corpId);
+            createReqVO.setUserId(user.getId());
+            tenantService.joinDDingTenant(createReqVO);
+        }
+        return user;
+    }
+
 
     public static String getRandomStr(int count) {
         String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -207,6 +283,7 @@ public class DingAuthServiceInfoImpl implements DingAuthServiceInfo {
         respVO.setCorpId(authCorpInfo.getString("corpid"));
         respVO.setName(authCorpInfo.getString("full_corp_name"));
         respVO.setManageUserId(manager_userid);
+        respVO.setExpireTime(LocalDateTime.now().plusYears(10));// 默认设置10年有效期
         tenantService.createDingTenant(respVO);
     }
 

+ 77 - 14
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dingding/DingThirdTokenService.java

@@ -40,6 +40,8 @@ public class DingThirdTokenService {
     @Resource
     private RedisCache redisCache;
 
+
+
     public static Client createClient2_1_0() throws Exception {
         Config config = new Config();
         config.protocol = "https";
@@ -101,8 +103,9 @@ public class DingThirdTokenService {
                     .setAuthCorpId(corpId)
                     .setSuiteTicket(suiteTicket);
             GetCorpAccessTokenResponse response = client.getCorpAccessToken(getCorpAccessTokenRequest);
-            corpAccessToken = response.getBody().getAccessToken();
-            Long expireIn = response.getBody().getExpireIn();
+            GetCorpAccessTokenResponseBody body = response.getBody();
+            corpAccessToken = body.getAccessToken();
+            Long expireIn = body.getExpireIn();
             log.info("从Redis缓存中获取到的第三方企业{},corpAccessToken = {}",corpId, corpAccessToken);
             redisCache.setCacheObject(Constants.DAILY_DING_CORP_ACCESS_TOKEN, corpAccessToken, expireIn.intValue(), TimeUnit.MINUTES);
         } catch (TeaException err) {
@@ -124,25 +127,25 @@ public class DingThirdTokenService {
 
 
     //服务商获取第三方应用授权企业的access_token
-    public String getThirdCorpAccessToken(String corpId) {
-        String redisKeyPrefix = Constants.DAILY_DING_AUTH + corpId + ":";
+    public String getThirdCorpAccessToken(String corpId,String suiteTicket) {
+//        String redisKeyPrefix = Constants.DAILY_DING_AUTH + corpId + ":";
         // 从持久化存储中读取
-        String corpAccessToken = redisCache.getCacheObject(redisKeyPrefix + Constants.DAILY_DING_CORP_ACCESS_TOKEN);
-        log.info("从Redis缓存中获取到的第三方企业{},corpAccessToken = {}", corpAccessToken);
-        if (corpAccessToken != null) {
-            return corpAccessToken;
-        }
+        String corpAccessToken = null;
+//        String corpAccessToken = redisCache.getCacheObject(redisKeyPrefix + Constants.DAILY_DING_CORP_ACCESS_TOKEN);
+//        log.info("从Redis缓存中获取到的第三方企业{},corpAccessToken = {}", corpAccessToken);
+//        if (corpAccessToken != null) {
+//            return corpAccessToken;
+//        }
         try {
-            String suiteTicket = redisCache.getCacheObject(redisKeyPrefix + Constants.DAILY_DING_SUITE_TICKET);
-            log.info("从Redis缓存中获取到的第三方企业{},suiteTicket = {}", corpId, suiteTicket);
+            log.info("获取到的第三方企业{},suiteTicket = {}", corpId, suiteTicket);
             DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/service/get_corp_token");
             OapiServiceGetCorpTokenRequest req = new OapiServiceGetCorpTokenRequest();
             req.setAuthCorpid(corpId);
             OapiServiceGetCorpTokenResponse execute = client.execute(req, dingAppConfig.getAppKey(), dingAppConfig.getAppSecret(), suiteTicket);
             corpAccessToken = execute.getAccessToken();
             Long expireIn = execute.getExpiresIn();
-            log.info("从Redis缓存中获取到的第三方企业{},corpAccessToken = {}", execute.getAccessToken());
-            redisCache.setCacheObject(Constants.DAILY_DING_CORP_ACCESS_TOKEN, corpAccessToken, expireIn.intValue(), TimeUnit.MINUTES);
+//            log.info("从Redis缓存中获取到的第三方企业{},corpAccessToken = {}", execute.getAccessToken());
+//            redisCache.setCacheObject(Constants.DAILY_DING_CORP_ACCESS_TOKEN, corpAccessToken, expireIn.intValue(), TimeUnit.MINUTES);
         } catch (TeaException err) {
             if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                 // err 中含有 code 和 message 属性,可帮助开发定位问题
@@ -173,6 +176,66 @@ public class DingThirdTokenService {
         return rsp;
     }
 
+    /**
+     *
+     * @param userId
+     * @param access_token
+     * @return
+     */
+    public OapiV2UserGetResponse getUser(String userId, String access_token) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
+        OapiV2UserGetResponse rsp = null;
+        try {
+            OapiV2UserGetRequest req = new OapiV2UserGetRequest();
+            req.setUserid(userId);
+            req.setLanguage("zh_CN");
+            rsp = client.execute(req, access_token);
+        } catch (ApiException e) {
+            throw new RuntimeException(e);
+        }
+        return rsp;
+    }
+
+    /**
+     *
+     * @param UnionId
+     * @param access_token
+     * @return
+     */
+    public OapiUserGetbyunionidResponse getUserUnionId(String UnionId, String access_token) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/getbyunionid");
+        OapiUserGetbyunionidResponse rsp = null;
+        try {
+            OapiUserGetbyunionidRequest req = new OapiUserGetbyunionidRequest();
+            req.setUnionid(UnionId);
+            rsp = client.execute(req, access_token);
+        } catch (ApiException e) {
+            throw new RuntimeException(e);
+        }
+        return rsp;
+    }
+
+    /**
+     *
+     * @param access_token
+     * @return
+     */
+    public OapiV2DepartmentListsubResponse getDept(String access_token) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/department/listsub");
+        OapiV2DepartmentListsubResponse rsp = null;
+        try {
+            OapiV2DepartmentListsubRequest req = new OapiV2DepartmentListsubRequest();
+            req.setDeptId(1L);
+            req.setLanguage("zh_CN");
+            rsp = client.execute(req, access_token);
+        } catch (ApiException e) {
+            throw new RuntimeException(e);
+        }
+        return rsp;
+    }
+
+
+
     public OapiV2UserGetResponse getAuthUser(String accessToken, String userId) {
         DingTalkClient client = new DefaultDingTalkClient(DingUrlConstant.URL_USER_GET_V2);
         OapiV2UserGetResponse response;
@@ -189,7 +252,7 @@ public class DingThirdTokenService {
     }
 
     //获取应用管理后台免登的用户信息
-    public Object getSsoUserInfo(String xAcsDingtalkAccessToken, String code) {
+    public GetSsoUserInfoResponse getSsoUserInfo(String xAcsDingtalkAccessToken, String code) {
         GetSsoUserInfoResponse ssoUserInfoWithOptions = null;
         try {
             com.aliyun.dingtalkoauth2_1_0.Client client = this.createClient2_1_0();

+ 28 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java

@@ -5,8 +5,10 @@ import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.*;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler;
 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
+import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 
 import javax.validation.Valid;
 import java.util.List;
@@ -43,6 +45,14 @@ public interface TenantService {
      */
     TenantSimpleRespVO joinTenant(@Valid TenantJoinReqVO createReqVO);
 
+    /**
+     * 钉钉加入租户
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    void joinDDingTenant(@Valid TenantJoinReqVO createReqVO);
+
     /**
      * 更新租户
      *
@@ -177,6 +187,24 @@ public interface TenantService {
      */
     void validTenant(Long id);
 
+    /**
+     * 根据企业ID查询企业是否存在
+     * @param corpId
+     * @return
+     */
+    TenantDO getTenantByCorpId(String corpId);
 
+    /**
+     * 初始化钉钉相关信息
+     * @param user
+     * @param tenant
+     */
+    void createDDingInit(AdminUserDO user, TenantDO tenant);
 
+    /**
+     * 更新票据-数据为第三方企业应用票据最新suiteTicket,定时每5个小时推送一次。
+     * @param corpId
+     * @param suiteTicket
+     */
+    void updateTenantSuiteTicket(String corpId, String suiteTicket);
 }

+ 128 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java

@@ -39,6 +39,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.UserTenantRelateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.dingding.DingUserTenantRelateMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
 import cn.iocoder.yudao.module.system.enums.common.DeletedEnum;
@@ -61,6 +62,7 @@ import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import cn.iocoder.yudao.module.system.service.user.UserTenantRelateService;
 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
@@ -149,6 +151,77 @@ public class TenantServiceImpl implements TenantService {
         }
     }
 
+    @Override
+    public TenantDO getTenantByCorpId(String corpId) {
+        return tenantMapper.selectByCorpId(corpId);
+    }
+
+    @Override
+    @DSTransactional
+    public void createDDingInit(AdminUserDO user, TenantDO tenant) {
+        // 插入用户租户关系
+        userTenantRelateService.createUserTenantRelate(new UserTenantRelateSaveReqVO().setUserId(user.getId()).setTenantId(tenant.getId()).setActived(false));
+        // 先判断该租户下员工信息是否存在,如果不存在则创建
+        EmployeeRespDTO employeeRespDTO = employeeApi.getEmployee(new EmployeeQueryReqDTO().setUserId(user.getId()).setTenantId(tenant.getId()));
+        if (employeeRespDTO == null) {
+            // 创建员工信息
+            employeeApi.createEmployee(
+                    new EmployeeCreateReqDTO()
+                            .setUserId(user.getId())
+                            .setTenantId(tenant.getId())
+                            .setName(user.getNickname())
+                            .setAvatar(user.getAvatar())
+                            .setPhone(user.getMobile())
+                            .setStatus(CommonStatusEnum.ENABLE.getStatus())
+                            .setEmployeeStatus(CommonStatusEnum.ENABLE.getStatus())
+            );
+        } else {
+            throw exception(USER_TENANT_EMPLOYEE_DUPLICATE, tenant.getName());
+        }
+        //钉钉同步管理员ID
+        DingUserTenantRelateDO dingUserTenantRelateDO = dingUserTenantRelateMapper.selectOne(DingUserTenantRelateDO::getTenantId,tenant.getId());
+
+        AtomicLong roleIdHolder = new AtomicLong(0L);
+        TenantUtils.execute(tenant.getId(), () -> {
+            roleIdHolder.set(createSystemSuperAdminRole(tenant.getId()));
+            // 分配角色
+            permissionService.assignUserRole(user.getId(), singleton(roleIdHolder.get()));
+            if(null!=dingUserTenantRelateDO && dingUserTenantRelateDO.getUserId().equals(user.getUsername())){
+                // 修改租户的管理员
+                tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(user.getId()));
+            }
+        });
+        Long roleId = roleIdHolder.get(); // 在 lambda 表达式外部获取值
+        // 使用异步处理来创建其他内置角色和分配权限
+        CompletableFuture.runAsync(() -> {
+            try {
+                TenantUtils.execute(tenant.getId(), () -> {
+                    // 复制部署流程(目前共12个)
+                    bpmModelApi.copyAndDeploy(null, null);
+                    // 添加租户字典默认类型和数据
+                    dictTypeTenantService.initDictTypeAndDataForTenant(null);
+                    this.createSystemAdminRole(tenant.getId());
+                    this.createCommonEmployeeRole(tenant.getId());
+                    this.createDepartmentLeaderRole(tenant.getId());
+                });
+            } catch (Exception e) {
+                // 处理异步任务中的异常
+                log.error("Error creating system roles and permissions for tenant", e);
+                throw exception(ROLES_PERMISSIONS_CREATE_ERROR, e.getMessage());
+            }
+        });
+    }
+
+    @Override
+    public void updateTenantSuiteTicket(String corpId, String suiteTicket) {
+        // 创建一个LambdaUpdateWrapper实例
+        LambdaUpdateWrapper<TenantDO> updateWrapper = new LambdaUpdateWrapper<>();
+        // 使用Lambda表达式来指定更新条件和要更新的字段
+        updateWrapper.eq(TenantDO::getCorpId, corpId)
+                .set(TenantDO::getSuiteTicket, suiteTicket); // 注意:这里使用false,MyBatis-Plus会处理类型转换
+        tenantMapper.update(updateWrapper);
+    }
+
     @Override
     @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
     public TenantSimpleRespVO createTenant(TenantSaveReqVO createReqVO) {
@@ -286,6 +359,60 @@ public class TenantServiceImpl implements TenantService {
         return tenantSimpleRespVO;
     }
 
+    @Override
+    @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
+    public void joinDDingTenant(TenantJoinReqVO createReqVO) {
+        // 根据租户邀请码查询租户信息
+        TenantDO tenant = tenantMapper.selectByCorpId(createReqVO.getCorpId());
+        if (tenant == null || tenant.getId() == null) {
+            throw exception(TENANT_NOT_EXISTS);
+        }
+        // 校验该用户是否在该租户中
+        if (userTenantRelateService.checkUserHasTenant(createReqVO.getUserId(), tenant.getId())) {
+            throw exception(USER_TENANT_EXISTS, tenant.getName());
+        }
+        // 插入用户租户关系
+        userTenantRelateService.createUserTenantRelate(new UserTenantRelateSaveReqVO().setUserId(createReqVO.getUserId()).setTenantId(tenant.getId()).setActived(false));
+        // 自动根据账号创建对应人事信息,只保存姓名、头像、手机号
+        AdminUserDO adminUserDO = userService.getUser(createReqVO.getUserId());
+        if (adminUserDO == null) {
+            throw exception(USER_NOT_EXISTS);
+        }
+        // 先判断该租户下员工信息是否存在,如果不存在则创建
+        EmployeeRespDTO employeeRespDTO = employeeApi.getEmployee(new EmployeeQueryReqDTO().setUserId(createReqVO.getUserId()).setTenantId(tenant.getId()));
+        if (employeeRespDTO != null) {
+            throw exception(USER_TENANT_EMPLOYEE_DUPLICATE, tenant.getName());
+        }
+        EmployeeRespDTO employeeDTO = employeeApi.getEmployee(new EmployeeQueryReqDTO().setPhone(adminUserDO.getMobile()).setTenantId(tenant.getId()));
+        if (employeeDTO != null) {
+            EmployeeSaveReqDTO updateObj = BeanUtils.toBean(employeeDTO, EmployeeSaveReqDTO.class);
+            updateObj.setUserId(createReqVO.getUserId());
+            updateObj.setTenantId(tenant.getId());
+            // 更新员工信息
+            employeeApi.updateEmployee(updateObj);
+        } else {
+            // 创建员工信息
+            employeeApi.createEmployee(
+                    new EmployeeCreateReqDTO()
+                            .setUserId(createReqVO.getUserId())
+                            .setTenantId(tenant.getId())
+                            .setName(adminUserDO.getNickname())
+                            .setAvatar(adminUserDO.getAvatar())
+                            .setPhone(adminUserDO.getMobile())
+                            .setStatus(CommonStatusEnum.ENABLE.getStatus())
+                            .setEmployeeStatus(CommonStatusEnum.ENABLE.getStatus())
+            );
+        }
+        TenantUtils.execute(tenant.getId(), () -> {
+            RoleDO roleDO = roleService.selectByName(RoleCodeEnum.COMMON_EMPLOYEE.getName());
+            if (roleDO == null || roleDO.getId() == null) {
+                throw exception(ROLE_NOT_EXISTS);
+            }
+            // 分配角色
+            permissionService.assignUserRole(createReqVO.getUserId(), singleton(roleDO.getId()));
+        });
+    }
+
     private Long createUser(Long roleId, TenantSaveReqVO createReqVO) {
         // 创建用户
         Long userId = userService.createUser(TenantConvert.INSTANCE.convert02(createReqVO));
@@ -648,6 +775,7 @@ public class TenantServiceImpl implements TenantService {
         // 创建租户
         TenantDO tenant = BeanUtils.toBean(createReqVO, TenantDO.class);
         tenant.setExpireTime(LocalDateTime.now().plusYears(10));// 默认设置10年有效期
+        tenant.setCreator(createReqVO.getManageUserId());
         tenantMapper.insert(tenant);
         // 创建管理员与租户关系
         dingUserTenantRelateMapper.insert(new DingUserTenantRelateDO().setUserId(createReqVO.getManageUserId()).setTenantId(tenant.getId()));

+ 16 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java

@@ -37,6 +37,15 @@ public interface AdminUserService {
      */
     Long createUser(@Valid UserSaveReqVO createReqVO);
 
+    /**
+     * 创建钉钉用户
+     *
+     * @param createReqVO 用户信息
+     * @param tenantId 租户ID
+     * @return 用户编号
+     */
+    Long createDDingUser(@Valid UserSaveReqVO createReqVO,Long tenantId);
+
     /**
      * 修改用户
      *
@@ -235,4 +244,11 @@ public interface AdminUserService {
      */
     void updateUserTenantId(Long id, Long tenantId);
 
+    /**
+     * 查询用户是否存在
+     * @param userid
+     * @param tenantId 当前租户编码
+     */
+    AdminUserDO getUserByUserIdAndCorpId(String userid, Long tenantId);
+
 }

+ 31 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java

@@ -124,6 +124,32 @@ public class AdminUserServiceImpl implements AdminUserService {
                     postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));
         }
 
+        // 3. 记录操作日志上下文
+//        LogRecordContext.putVariable("user", user);
+        return user.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
+            success = SYSTEM_USER_CREATE_SUCCESS)
+    public Long createDDingUser(UserSaveReqVO createReqVO,Long tenantId) {
+
+        // 1 校验正确性
+        validateUserForCreateOrUpdate(null, createReqVO.getUsername(),
+                createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds());
+        // 2 插入用户
+        AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);
+        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
+        user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码
+        user.setTenantId(tenantId);
+        userMapper.insert(user);
+        // 2.1 插入关联岗位
+        if (CollectionUtil.isNotEmpty(user.getPostIds())) {
+            userPostMapper.insertBatch(convertList(user.getPostIds(),
+                    postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));
+        }
+
         // 3. 记录操作日志上下文
         LogRecordContext.putVariable("user", user);
         return user.getId();
@@ -551,4 +577,9 @@ public class AdminUserServiceImpl implements AdminUserService {
         userMapper.updateById(updateObj);
     }
 
+    @Override
+    public AdminUserDO getUserByUserIdAndCorpId(String userid, Long tenantId) {
+        return userMapper.getUserByUserIdAndCorpId(userid,tenantId);
+    }
+
 }

+ 6 - 4
yudao-server/src/main/resources/application.yaml

@@ -3,7 +3,7 @@ spring:
     name: yudao-server
 
   profiles:
-    active: local
+    active: test
 
   main:
     allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
@@ -155,6 +155,7 @@ yudao:
     permit-all_urls:
       - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录
       - /admin-api/system/agreement/** # 协议相关,不需要登录
+      - /admin-api/system/auth/ding/** # 钉钉接口相关,不需要登录
   websocket:
     enable: true # websocket的开关
     path: /infra/ws # 路径
@@ -207,6 +208,7 @@ yudao:
       - /admin-api/system/auth/getCurrentTenantIdByUsername # 通过用户账号获取租户列表,不携带租户编号
 #      - /admin-api/system/tenant/getOwnCreateTenants # 获取自己的创建的租户列表,不携带租户编号
 #      - /admin-api/system/tenant/getOwnJoinTenants # 获取自己的加入的租户列表,不携带租户编号
+      - /admin-api/system/auth/ding/** # 钉钉接口相关,不携带租户编号
     ignore-tables:
       - system_tenant
       - system_tenant_package
@@ -282,11 +284,11 @@ jeecg:
 # DingDing配置
 dingtalk:
   # 应用的唯一标识key。
-  app_key: suiteyjd6ikxpg8629ydr
+  app_key: suite7tssbigaaqsejgth
   # 应用的密钥。
-  app_secret: znq4BDYYX4vFLxtjfknEoId6j84LT2xpW7gkRTvpVZVoMfbfHXZvZX3cQ2cLQzON
+  app_secret: uyyAvh0ivBSByZ8s3dfCT6FHqZv1z4PytMpe8mfF_zcLBIAceeNmJ-oe_OPwiCcw
   # 应用的标识
-  agent_id: 2955933829
+  agent_id: 36188001
   # 企业ID
   corp_id: ding870ccf3c4d8fc1bc
   sso_secret: 123

+ 2 - 2
yudao-ui/yudao-ui-admin-vue2/package.json

@@ -6,7 +6,7 @@
   "license": "MIT",
   "scripts": {
     "local": "vue-cli-service serve --mode local",
-    "dev": "vue-cli-service serve --mode dev",
+    "dev": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --mode dev",
     "front": "vue-cli-service serve --mode front",
     "build:prod": "vue-cli-service build --mode prod",
     "build:stage": "vue-cli-service build --mode stage",
@@ -109,4 +109,4 @@
     "> 1%",
     "last 2 versions"
   ]
-}
+}

Разница между файлами не показана из-за своего большого размера
+ 925 - 512
yudao-ui/yudao-ui-admin-vue2/yarn.lock