Kaynağa Gözat

feat: 用户绑定企微账号

wuwenyi 1 hafta önce
ebeveyn
işleme
952d7e27ef

+ 13 - 0
src/main/java/com/qqflow/engine/domain/ewechat/api/EnterpriseWechatApi.java

@@ -75,6 +75,19 @@ public class EnterpriseWechatApi {
         return this.commonPost(uri, req, EnterpriseWechatListIdRsp.class);
         return this.commonPost(uri, req, EnterpriseWechatListIdRsp.class);
     }
     }
 
 
+    /** 获取部门列表(递归获取指定部门及其子部门,不填id则获取全量) */
+    public EnterpriseWechatDepartmentListRsp listDepartment(String accessToken, Long deptId) {
+        String uri = "/cgi-bin/department/list?access_token=" + accessToken;
+        if (deptId != null) uri += "&id=" + deptId;
+        return this.commonGet(uri, EnterpriseWechatDepartmentListRsp.class);
+    }
+
+    /** 获取部门成员(仅返回本部门直属成员,不含子部门) */
+    public EnterpriseWechatUserListRsp listDepartmentUsers(String accessToken, Long departmentId) {
+        String uri = "/cgi-bin/user/simplelist?access_token=" + accessToken + "&department_id=" + departmentId;
+        return this.commonGet(uri, EnterpriseWechatUserListRsp.class);
+    }
+
     /** 通过手机号获取userid */
     /** 通过手机号获取userid */
     public EnterpriseWechatUseridByMobileRsp getUseridByMobile(String accessToken, String mobile) {
     public EnterpriseWechatUseridByMobileRsp getUseridByMobile(String accessToken, String mobile) {
         EnterpriseWechatUseridByMobileReq req = EnterpriseWechatUseridByMobileReq.of(mobile);
         EnterpriseWechatUseridByMobileReq req = EnterpriseWechatUseridByMobileReq.of(mobile);

+ 36 - 0
src/main/java/com/qqflow/engine/domain/ewechat/controller/WecomContactController.java

@@ -0,0 +1,36 @@
+package com.qqflow.engine.domain.ewechat.controller;
+
+import com.qqflow.engine.common.Result;
+import com.qqflow.engine.domain.ewechat.po.EnterpriseWechatDepartment;
+import com.qqflow.engine.domain.ewechat.po.EnterpriseWechatMember;
+import com.qqflow.engine.domain.ewechat.service.WecomContactSyncService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@Tag(name = "企微通讯录")
+@RestController
+@RequestMapping("/wecom/contact")
+@RequiredArgsConstructor
+public class WecomContactController {
+
+    private final WecomContactSyncService syncService;
+
+    @GetMapping("/departments")
+    @Operation(summary = "获取子部门列表(懒加载同步)")
+    public Result<List<EnterpriseWechatDepartment>> departments(@RequestParam(defaultValue = "0") Long parentId) {
+        return Result.ok(syncService.listSubDepartments(parentId));
+    }
+
+    @GetMapping("/members")
+    @Operation(summary = "获取部门成员列表(懒加载同步)")
+    public Result<List<EnterpriseWechatMember>> members(@RequestParam Long departmentId) {
+        return Result.ok(syncService.listDepartmentMembers(departmentId));
+    }
+}

+ 9 - 0
src/main/java/com/qqflow/engine/domain/ewechat/mapper/EnterpriseWechatDepartmentMapper.java

@@ -0,0 +1,9 @@
+package com.qqflow.engine.domain.ewechat.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qqflow.engine.domain.ewechat.po.EnterpriseWechatDepartment;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface EnterpriseWechatDepartmentMapper extends BaseMapper<EnterpriseWechatDepartment> {
+}

+ 9 - 0
src/main/java/com/qqflow/engine/domain/ewechat/mapper/EnterpriseWechatMemberMapper.java

@@ -0,0 +1,9 @@
+package com.qqflow.engine.domain.ewechat.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qqflow.engine.domain.ewechat.po.EnterpriseWechatMember;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface EnterpriseWechatMemberMapper extends BaseMapper<EnterpriseWechatMember> {
+}

+ 31 - 0
src/main/java/com/qqflow/engine/domain/ewechat/model/enterprise/rsp/EnterpriseWechatDepartmentListRsp.java

@@ -0,0 +1,31 @@
+package com.qqflow.engine.domain.ewechat.model.enterprise.rsp;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class EnterpriseWechatDepartmentListRsp extends EnterpriseWechatBaseRsp {
+    /** 部门列表 */
+    private List<Department> department;
+
+    @Data
+    @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+    public static class Department {
+        /** 部门id */
+        private Long id;
+        /** 部门名称 */
+        private String name;
+        /** 英文名称 */
+        private String nameEn;
+        /** 部门负责人UserID列表 */
+        private List<String> departmentLeader;
+        /** 父部门id,根部门为1 */
+        private Long parentid;
+        /** 排序值 */
+        private Long order;
+    }
+}

+ 27 - 0
src/main/java/com/qqflow/engine/domain/ewechat/model/enterprise/rsp/EnterpriseWechatUserListRsp.java

@@ -0,0 +1,27 @@
+package com.qqflow.engine.domain.ewechat.model.enterprise.rsp;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class EnterpriseWechatUserListRsp extends EnterpriseWechatBaseRsp {
+    /** 成员列表 */
+    private List<User> userlist;
+
+    @Data
+    @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+    public static class User {
+        /** 成员UserID */
+        private String userid;
+        /** 成员名称 */
+        private String name;
+        /** 所属部门ID列表 */
+        private List<Long> department;
+        /** 全局唯一open_userid */
+        private String openUserid;
+    }
+}

+ 14 - 0
src/main/java/com/qqflow/engine/domain/ewechat/po/EnterpriseWechatDepartment.java

@@ -0,0 +1,14 @@
+package com.qqflow.engine.domain.ewechat.po;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qqflow.engine.common.po.BaseModel;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@TableName("enterprise_wechat_department")
+public class EnterpriseWechatDepartment extends BaseModel {
+    private String name;
+    private Long parentId;
+}

+ 16 - 0
src/main/java/com/qqflow/engine/domain/ewechat/po/EnterpriseWechatMember.java

@@ -0,0 +1,16 @@
+package com.qqflow.engine.domain.ewechat.po;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qqflow.engine.common.po.BaseUpdateModel;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@TableName("enterprise_wechat_member")
+public class EnterpriseWechatMember extends BaseUpdateModel {
+    private String userid;
+    private String name;
+    private Long departmentId;
+    private String openUserid;
+}

+ 2 - 1
src/main/java/com/qqflow/engine/domain/ewechat/service/EnterpriseWechatMsgService.java

@@ -76,7 +76,8 @@ public class EnterpriseWechatMsgService {
                 .collect(Collectors.toList());
                 .collect(Collectors.toList());
     }
     }
 
 
-    private String getCorpAccessToken() {
+    /** 获取企业 access_token(供同步服务等复用) */
+    public String getCorpAccessToken() {
         List<EnterpriseWechatCorp> corps = wechatCorpMapper.selectList(
         List<EnterpriseWechatCorp> corps = wechatCorpMapper.selectList(
                 new LambdaQueryWrapper<EnterpriseWechatCorp>()
                 new LambdaQueryWrapper<EnterpriseWechatCorp>()
                         .eq(EnterpriseWechatCorp::getSuiteId, wechatProperties.getSuiteId())
                         .eq(EnterpriseWechatCorp::getSuiteId, wechatProperties.getSuiteId())

+ 61 - 0
src/main/java/com/qqflow/engine/domain/ewechat/service/WecomContactSyncService.java

@@ -0,0 +1,61 @@
+package com.qqflow.engine.domain.ewechat.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.qqflow.engine.domain.ewechat.api.EnterpriseWechatApi;
+import com.qqflow.engine.domain.ewechat.mapper.EnterpriseWechatDepartmentMapper;
+import com.qqflow.engine.domain.ewechat.mapper.EnterpriseWechatMemberMapper;
+import com.qqflow.engine.domain.ewechat.model.enterprise.rsp.EnterpriseWechatUserListRsp;
+import com.qqflow.engine.domain.ewechat.po.EnterpriseWechatDepartment;
+import com.qqflow.engine.domain.ewechat.po.EnterpriseWechatMember;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 企微通讯录同步服务(懒加载:先查本地DB,没有再调企微接口同步后返回)
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class WecomContactSyncService {
+
+    private final EnterpriseWechatDepartmentMapper departmentMapper;
+    private final EnterpriseWechatMemberMapper memberMapper;
+    private final EnterpriseWechatApi enterpriseWechatApi;
+    private final EnterpriseWechatMsgService wechatMsgService;
+
+    /** 获取子部门列表(从本地DB查询) */
+    public List<EnterpriseWechatDepartment> listSubDepartments(Long parentId) {
+        return departmentMapper.selectList(
+                new LambdaQueryWrapper<EnterpriseWechatDepartment>().eq(EnterpriseWechatDepartment::getParentId, parentId));
+    }
+
+    /** 获取部门成员(懒加载) */
+    public List<EnterpriseWechatMember> listDepartmentMembers(Long departmentId) {
+        List<EnterpriseWechatMember> cached = memberMapper.selectList(
+                new LambdaQueryWrapper<EnterpriseWechatMember>().eq(EnterpriseWechatMember::getDepartmentId, departmentId));
+        if (!cached.isEmpty()) return cached;
+
+        String accessToken = wechatMsgService.getCorpAccessToken();
+        if (accessToken == null) return List.of();
+
+        EnterpriseWechatUserListRsp rsp = enterpriseWechatApi.listDepartmentUsers(accessToken, departmentId);
+        if (rsp == null || rsp.getUserlist() == null) return List.of();
+
+        List<EnterpriseWechatMember> members = new ArrayList<>();
+        for (var u : rsp.getUserlist()) {
+            EnterpriseWechatMember wm = new EnterpriseWechatMember();
+            wm.setUserid(u.getUserid());
+            wm.setName(u.getName() != null ? u.getName() : u.getUserid());
+            wm.setDepartmentId(departmentId);
+            wm.setOpenUserid(u.getOpenUserid());
+            memberMapper.insert(wm);
+            members.add(wm);
+        }
+        return members;
+    }
+
+}

+ 3 - 0
src/main/java/com/qqflow/engine/domain/system/dto/UserDTO.java

@@ -41,6 +41,9 @@ public class UserDTO {
     @Schema(description = "企业微信用户ID")
     @Schema(description = "企业微信用户ID")
     private String wecomUserId;
     private String wecomUserId;
 
 
+    @Schema(description = "企业微信用户名称")
+    private String wecomUserName;
+
     @Schema(description = "是否开启企微提醒:0-否 1-是")
     @Schema(description = "是否开启企微提醒:0-否 1-是")
     private Integer wecomRemindEnabled;
     private Integer wecomRemindEnabled;
 
 

+ 18 - 0
src/main/java/com/qqflow/engine/domain/system/service/impl/SysUserServiceImpl.java

@@ -19,6 +19,8 @@ import com.qqflow.engine.domain.system.mapper.SysUserMapper;
 import com.qqflow.engine.domain.system.mapper.SysUserRoleMapper;
 import com.qqflow.engine.domain.system.mapper.SysUserRoleMapper;
 import com.qqflow.engine.domain.system.service.SysDeptService;
 import com.qqflow.engine.domain.system.service.SysDeptService;
 import com.qqflow.engine.domain.system.service.SysUserService;
 import com.qqflow.engine.domain.system.service.SysUserService;
+import com.qqflow.engine.domain.ewechat.po.EnterpriseWechatMember;
+import com.qqflow.engine.domain.ewechat.mapper.EnterpriseWechatMemberMapper;
 import jakarta.annotation.Resource;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.context.annotation.Lazy;
@@ -59,6 +61,9 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
     private SysDeptService sysDeptService;
     private SysDeptService sysDeptService;
 
 
     @Resource
     @Resource
+    private EnterpriseWechatMemberMapper wecomMemberMapper;
+
+    @Resource
     private PasswordEncoder passwordEncoder;
     private PasswordEncoder passwordEncoder;
 
 
     @Lazy
     @Lazy
@@ -236,6 +241,19 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
             dto.setRoleIds(roleIds);
             dto.setRoleIds(roleIds);
             return dto;
             return dto;
         }).collect(Collectors.toList());
         }).collect(Collectors.toList());
+
+        // 批量查询企微成员名称
+        List<String> wecomUserIds = dtoList.stream().map(UserDTO::getWecomUserId)
+                .filter(Objects::nonNull).filter(s -> !s.isBlank()).distinct().toList();
+        if (!wecomUserIds.isEmpty()) {
+            Map<String, String> nameMap = wecomMemberMapper.selectList(
+                    new LambdaQueryWrapper<EnterpriseWechatMember>().in(EnterpriseWechatMember::getUserid, wecomUserIds))
+                    .stream().collect(Collectors.toMap(EnterpriseWechatMember::getUserid, EnterpriseWechatMember::getName, (a, b) -> a));
+            dtoList.forEach(dto -> {
+                if (dto.getWecomUserId() != null) dto.setWecomUserName(nameMap.get(dto.getWecomUserId()));
+            });
+        }
+
         Page<UserDTO> resultPage = new Page<>(userPage.getCurrent(), userPage.getSize(), userPage.getTotal());
         Page<UserDTO> resultPage = new Page<>(userPage.getCurrent(), userPage.getSize(), userPage.getTotal());
         resultPage.setRecords(dtoList);
         resultPage.setRecords(dtoList);
         return resultPage;
         return resultPage;

+ 14 - 0
src/test/java/com/qqflow/engine/domain/ewechat/api/EnterpriseWechatApiTest.java

@@ -102,4 +102,18 @@ class EnterpriseWechatApiTest {
         EnterpriseWechatSendTextMsgRsp sendTextMsgRsp = enterpriseWechatApi.sendTextMsg(accessToken, textMsgReq);
         EnterpriseWechatSendTextMsgRsp sendTextMsgRsp = enterpriseWechatApi.sendTextMsg(accessToken, textMsgReq);
         System.out.println(JsonUtil.entity2Json(sendTextMsgRsp));
         System.out.println(JsonUtil.entity2Json(sendTextMsgRsp));
     }
     }
+
+    @Test
+    void listDepartment() {
+        String accessToken = enterpriseWechatService.getAccessToken(corpId, suiteId);
+        EnterpriseWechatDepartmentListRsp departmentList = enterpriseWechatApi.listDepartment(accessToken, 0L);
+        System.out.println(JsonUtil.entity2Json(departmentList));
+    }
+
+    @Test
+    void listDepartmentUsers() {
+        String accessToken = enterpriseWechatService.getAccessToken(corpId, suiteId);
+        EnterpriseWechatUserListRsp departmentUsers = enterpriseWechatApi.listDepartmentUsers(accessToken, 1L);
+        System.out.println(JsonUtil.entity2Json(departmentUsers));
+    }
 }
 }