From 6f0e0587ab78f2e57feccdf22ced6976c0aafeb8 Mon Sep 17 00:00:00 2001 From: dzq Date: Mon, 31 Mar 2025 09:41:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E9=80=9A=E8=AE=AF=E5=BD=95?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 + .../controller/common/LoginController.java | 38 ++ .../qywx/QyDepartmentController.java | 45 ++- .../controller/qywx/QyUserController.java | 24 +- .../customize/config/SecurityConfig.java | 2 +- .../customize/service/QywxScheduleJob.java | 357 +++++++++++++++++- .../login/FakeUserDetailsServiceImpl.java | 104 +++++ .../customize/service/login/LoginService.java | 79 +++- .../service/login/command/LoginCommand.java | 4 + .../admin/config/AgileBootConfigTest.java | 39 ++ .../api/controller/QywxController.java | 22 +- .../AccessTokenApplicationService.java | 9 +- .../accessToken/db/QyAccessTokenMapper.java | 4 +- .../accessToken/db/QyAccessTokenService.java | 2 +- .../db/QyAccessTokenServiceImpl.java | 4 +- .../domain/qywx/api/QywxApiUtil.java | 72 +++- .../qywx/api/response/OpenidResponse.java | 10 + .../qywx/api/response/UserIdResponse.java | 10 + .../qywx/api/response/UserListResponse.java | 83 ++++ .../AuthCorpInfoApplicationService.java | 2 +- .../authCorpInfo/db/QyAuthCorpInfoMapper.java | 4 +- .../db/QyAuthCorpInfoService.java | 2 +- .../db/QyAuthCorpInfoServiceImpl.java | 2 +- .../db/QyDepartmentServiceImpl.java | 2 +- .../template/db/QyTemplateServiceImpl.java | 3 - .../qywx/user/QyUserApplicationService.java | 12 +- ...UserCommand.java => AddQyUserCommand.java} | 2 +- ...rCommand.java => UpdateQyUserCommand.java} | 2 +- .../qywx/user/db/QyUserServiceImpl.java | 1 + .../domain/qywx/user/model/UserModel.java | 8 +- .../SysUserQyUserApplicationService.java | 69 ++++ .../command/AddSysUserQyUserCommand.java | 11 + .../command/UpdateSysUserQyUserCommand.java | 17 + .../userQySys/db/SysUserQyUserEntity.java | 52 +++ .../userQySys/db/SysUserQyUserMapper.java | 49 +++ .../userQySys/db/SysUserQyUserService.java | 29 ++ .../db/SysUserQyUserServiceImpl.java | 52 +++ .../qywx/userQySys/dto/SysUserQyUserDTO.java | 33 ++ .../userQySys/model/SysUserQyUserModel.java | 39 ++ .../model/SysUserQyUserModelFactory.java | 27 ++ .../query/SearchSysUserQyUserQuery.java | 34 ++ .../shop/order/OrderApplicationService.java | 7 + .../domain/system/user/db/SysUserService.java | 4 +- .../mapper/qy/SysUserQyUserMapper.xml | 5 + .../mybatisplus/CodeGenerator.java | 2 +- ...部门成员详情 - 文档 - 企业微信开发者中心.md | 108 ++++++ sql/20250327.sql | 17 + 47 files changed, 1430 insertions(+), 76 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/FakeUserDetailsServiceImpl.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/api/response/OpenidResponse.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/api/response/UserIdResponse.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/api/response/UserListResponse.java rename agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/{AddUserCommand.java => AddQyUserCommand.java} (78%) rename agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/{UpdateUserCommand.java => UpdateQyUserCommand.java} (83%) create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/SysUserQyUserApplicationService.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/command/AddSysUserQyUserCommand.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/command/UpdateSysUserQyUserCommand.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserEntity.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserMapper.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserService.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserServiceImpl.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/dto/SysUserQyUserDTO.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/model/SysUserQyUserModel.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/model/SysUserQyUserModelFactory.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/query/SearchSysUserQyUserQuery.java create mode 100644 agileboot-domain/src/main/resources/mapper/qy/SysUserQyUserMapper.xml create mode 100644 doc/simpread-获取部门成员详情 - 文档 - 企业微信开发者中心.md create mode 100644 sql/20250327.sql diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b0b2909 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable" +} \ No newline at end of file diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java index ff22eda..3c99a70 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java @@ -1,12 +1,18 @@ package com.agileboot.admin.controller.common; import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; import com.agileboot.common.config.AgileBootConfig; +import com.agileboot.common.constant.WeixinConstants; import com.agileboot.common.core.dto.ResponseDTO; import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode.Business; import com.agileboot.domain.common.dto.CurrentLoginUserDTO; import com.agileboot.domain.common.dto.TokenDTO; +import com.agileboot.domain.qywx.accessToken.AccessTokenApplicationService; +import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity; import com.agileboot.domain.system.menu.MenuApplicationService; import com.agileboot.domain.system.menu.dto.RouterDTO; import com.agileboot.domain.system.user.UserApplicationService; @@ -23,12 +29,18 @@ import com.agileboot.infrastructure.annotations.ratelimit.RateLimitKey; import com.agileboot.admin.customize.service.login.LoginService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.HashMap; import java.util.List; +import java.util.Map; + import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClientException; /** * 首页 @@ -37,6 +49,7 @@ import org.springframework.web.bind.annotation.RestController; */ @Tag(name = "登录API", description = "登录相关接口") @RestController +@Slf4j @RequiredArgsConstructor public class LoginController { @@ -48,6 +61,8 @@ public class LoginController { private final AgileBootConfig agileBootConfig; + private final AccessTokenApplicationService accessTokenApplicationService; + /** * 访问首页,提示语 */ @@ -137,4 +152,27 @@ public class LoginController { return ResponseDTO.fail(new ApiException(Business.COMMON_UNSUPPORTED_OPERATION)); } + @GetMapping("/getQyUserinfo") + public ResponseDTO getQyUserinfo(String corpid, String code) { + try { + QyAccessTokenEntity qyAccessToken = accessTokenApplicationService.getByAppid("QWTONG_YS_WXSHOP", corpid); + String url = String.format( + "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=%s&code=%s", + qyAccessToken.getAccessToken(), code); + + String response = HttpUtil.get(url); + log.info("微信getuserinfo接口返回: {}", response); + Map result = JSONUtil.toBean(response, HashMap.class); + + if (result.containsKey("errcode") && !result.get("errcode").equals(0)) { + log.error("微信接口返回错误: {}", result); + return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "无效的code参数")); + } + + return ResponseDTO.ok(String.valueOf(result.get("userid"))); + } catch (RestClientException e) { + log.error("获取openid失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "微信服务调用失败")); + } + } } diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QyDepartmentController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QyDepartmentController.java index db0fba8..1fdd814 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QyDepartmentController.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QyDepartmentController.java @@ -7,6 +7,8 @@ import com.agileboot.common.core.dto.ResponseDTO; import com.agileboot.common.core.page.PageDTO; import com.agileboot.common.enums.common.BusinessTypeEnum; import com.agileboot.domain.common.command.BulkOperationCommand; +import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService; +import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity; import com.agileboot.domain.qywx.department.DepartmentApplicationService; import com.agileboot.domain.qywx.department.command.AddDepartmentCommand; import com.agileboot.domain.qywx.department.command.UpdateDepartmentCommand; @@ -15,13 +17,17 @@ import com.agileboot.domain.qywx.department.dto.QyDepartmentDTO; import com.agileboot.domain.qywx.department.query.SearchQyDepartmentQuery; import com.agileboot.domain.qywx.template.command.UpdateTemplateCommand; import com.agileboot.domain.qywx.template.db.QyTemplateEntity; +import com.agileboot.domain.system.dept.dto.DeptDTO; +import com.agileboot.domain.system.dept.query.DeptQuery; import io.swagger.v3.oas.annotations.Operation; import java.util.List; +import java.util.stream.Collectors; import javax.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -42,6 +48,7 @@ public class QyDepartmentController extends BaseController { private final DepartmentApplicationService departmentApplicationService; private final QywxScheduleJob qywxScheduleJob; + private final AuthCorpInfoApplicationService authCorpInfoApplicationService; @Operation(summary = "部门列表") @GetMapping @@ -79,11 +86,47 @@ public class QyDepartmentController extends BaseController { @GetMapping("/syncDepartmentInfo") public String syncDepartmentInfo(@RequestParam String appid) { try { - qywxScheduleJob.syncDepartmentInfo(appid); + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + if (null == authCorpInfoList || authCorpInfoList.isEmpty()) { + return "appid not exist"; + } + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + qywxScheduleJob.syncDepartmentInfo(appid, authCorpInfo); + } return "success"; } catch (Exception e) { log.error("syncDepartmentInfo error", e); return e.getLocalizedMessage(); } } + + /** + * 用户管理获取部门列表 + */ + @Operation(summary = "部门列表") + @GetMapping("/depts") + public ResponseDTO> depts(@RequestParam String corpid) { + List qyDepartmentEntityList = departmentApplicationService.getDepartmentList(); + if (StringUtils.isNoneBlank(corpid)) { + qyDepartmentEntityList = qyDepartmentEntityList.stream() + .filter(item -> StringUtils.equals(item.getCorpid(), corpid)) + .collect(Collectors.toList()); + } + List deptList = qyDepartmentEntityList.stream().map(entity -> { + DeptDTO dto = new DeptDTO(null); // 使用null初始化,后续手动设置字段 + dto.setId(entity.getId().longValue()); + dto.setParentId(Long.parseLong(entity.getParentid())); + dto.setDeptName(entity.getName()); + dto.setOrderNum(0); + dto.setLeaderName(""); + dto.setPhone(""); + dto.setEmail(""); + dto.setStatus(1); + dto.setStatusStr(""); + dto.setCreateTime(entity.getCreateTime()); + // 其他字段根据业务需求补充 + return dto; + }).collect(Collectors.toList()); + return ResponseDTO.ok(deptList); + } } \ No newline at end of file diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QyUserController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QyUserController.java index 9c4015a..a532178 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QyUserController.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/QyUserController.java @@ -1,14 +1,15 @@ package com.agileboot.admin.controller.qywx; import com.agileboot.admin.customize.aop.accessLog.AccessLog; +import com.agileboot.admin.customize.service.QywxScheduleJob; import com.agileboot.common.core.base.BaseController; import com.agileboot.common.core.dto.ResponseDTO; import com.agileboot.common.core.page.PageDTO; import com.agileboot.common.enums.common.BusinessTypeEnum; import com.agileboot.domain.common.command.BulkOperationCommand; import com.agileboot.domain.qywx.user.QyUserApplicationService; -import com.agileboot.domain.qywx.user.command.AddUserCommand; -import com.agileboot.domain.qywx.user.command.UpdateUserCommand; +import com.agileboot.domain.qywx.user.command.AddQyUserCommand; +import com.agileboot.domain.qywx.user.command.UpdateQyUserCommand; import com.agileboot.domain.qywx.user.db.QyUserEntity; import com.agileboot.domain.qywx.user.dto.QyUserDTO; import com.agileboot.domain.qywx.user.query.SearchQyUserQuery; @@ -33,6 +34,7 @@ import org.springframework.web.bind.annotation.RestController; public class QyUserController extends BaseController { private final QyUserApplicationService qyUserApplicationService; + private final QywxScheduleJob qywxScheduleJob; @Operation(summary = "用户列表") @GetMapping @@ -44,7 +46,7 @@ public class QyUserController extends BaseController { @Operation(summary = "新增用户") @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.ADD) @PostMapping - public ResponseDTO add(@Validated @RequestBody AddUserCommand command) { + public ResponseDTO add(@Validated @RequestBody AddQyUserCommand command) { qyUserApplicationService.addUser(command); return ResponseDTO.ok(); } @@ -52,7 +54,7 @@ public class QyUserController extends BaseController { @Operation(summary = "修改用户") @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY) @PutMapping("/{id}") - public ResponseDTO edit(@PathVariable Integer id, @Validated @RequestBody UpdateUserCommand command) { + public ResponseDTO edit(@PathVariable Integer id, @Validated @RequestBody UpdateQyUserCommand command) { command.setId(id); qyUserApplicationService.updateUser(command); return ResponseDTO.ok(); @@ -65,4 +67,18 @@ public class QyUserController extends BaseController { qyUserApplicationService.deleteUser(new BulkOperationCommand<>(ids)); return ResponseDTO.ok(); } + + @Operation(summary = "同步用户信息") + @PostMapping("/sync") + public ResponseDTO syncUserInfo() { + qywxScheduleJob.syncUserInfoTask(); + return ResponseDTO.ok(); + } + + @Operation(summary = "同步创建系统用户信息") + @PostMapping("/syncSysUser") + public ResponseDTO syncSysUser() { + qywxScheduleJob.syncUserBindings(); + return ResponseDTO.ok(); + } } \ No newline at end of file diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java index be9dda3..c35bbe1 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java @@ -137,7 +137,7 @@ public class SecurityConfig { .antMatchers("/login", "/register", "/getConfig", "/captchaImage", "/api/**", "/file/**").anonymous() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() - .antMatchers("/qywx/**", "/test/**").permitAll() + .antMatchers("/qywx/**", "/test/**", "/getQyUserinfo").permitAll() // TODO this is danger. .antMatchers("/swagger-ui.html").anonymous() .antMatchers("/swagger-resources/**").anonymous() diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/QywxScheduleJob.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/QywxScheduleJob.java index ceead50..84d5020 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/QywxScheduleJob.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/QywxScheduleJob.java @@ -9,6 +9,7 @@ import com.agileboot.domain.qywx.api.QywxApiUtil; import com.agileboot.domain.qywx.api.response.DepartmentInfoResponse; import com.agileboot.domain.qywx.api.response.DepartmentInfoResponse.Department; import com.agileboot.domain.qywx.api.response.DepartmentListResponse; +import com.agileboot.domain.qywx.api.response.UserListResponse; import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService; import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity; import com.agileboot.domain.qywx.department.DepartmentApplicationService; @@ -22,8 +23,22 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; + +import com.agileboot.domain.qywx.user.QyUserApplicationService; +import com.agileboot.domain.qywx.user.command.AddQyUserCommand; +import com.agileboot.domain.qywx.user.command.UpdateQyUserCommand; +import com.agileboot.domain.qywx.user.db.QyUserEntity; +import com.agileboot.domain.qywx.userQySys.SysUserQyUserApplicationService; +import com.agileboot.domain.qywx.userQySys.command.AddSysUserQyUserCommand; +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserEntity; +import com.agileboot.domain.system.user.UserApplicationService; + +import com.agileboot.domain.system.user.command.AddUserCommand; +import com.agileboot.domain.system.user.model.UserModel; +import com.agileboot.domain.system.user.model.UserModelFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -39,17 +54,20 @@ public class QywxScheduleJob { private final AuthCorpInfoApplicationService authCorpInfoApplicationService; private final AccessTokenApplicationService accessTokenApplicationService; private final DepartmentApplicationService departmentApplicationService; + private final QyUserApplicationService qyUserApplicationService; + private final SysUserQyUserApplicationService sysUserQyUserApplicationService; + private final UserModelFactory userModelFactory; - private static final String appid = "QYTONG_YS_WXSHOP"; +// private static final String appid = "QYTONG_YS_WXSHOP"; private static final String appid2 = "QWTONG_YS_WXSHOP"; @Scheduled(cron = "0 10 * * * *") public void getSuiteAccessTokenTask() { - try { + /*try { getSuiteAccessToken(appid); } catch (Exception e) { log.error("getSuiteAccessTokenTask appid: " + appid, e); - } + }*/ try { getSuiteAccessToken(appid2); @@ -81,11 +99,11 @@ public class QywxScheduleJob { @Scheduled(cron = "0 20 * * * *") public void getAccessTokenTask() { - try { + /*try { getAccessToken(appid); } catch (Exception e) { log.error("getAccessTokenTask error appid: " + appid, e); - } + }*/ try { getAccessToken(appid2); @@ -98,12 +116,18 @@ public class QywxScheduleJob { log.info("getAccessToken Current Thread : {}, Fixed Rate Task : The time is now {}", Thread.currentThread().getName(), DateUtil.formatTime(new Date())); try { - QyAuthCorpInfoEntity authCorpInfo = authCorpInfoApplicationService.getByAppid(appid); - if (null == authCorpInfo) { + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + if (null == authCorpInfoList || authCorpInfoList.isEmpty()) { return; } - accessTokenApplicationService.getAccessToken(authCorpInfo.getCorpid(), authCorpInfo.getPermanentCode(), appid); + try { + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + accessTokenApplicationService.getAccessToken(authCorpInfo.getCorpid(), authCorpInfo.getPermanentCode(), appid); + } + } catch (Exception e) { + log.error("getAccessToken error", e); + } } catch (Exception e) { log.error("getAccessToken error", e); } @@ -111,31 +135,42 @@ public class QywxScheduleJob { @Scheduled(cron = "0 30 * * * *") public void syncDepartmentInfoTask() { - try { - syncDepartmentInfo(appid); + /*try { + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + if (null == authCorpInfoList || authCorpInfoList.isEmpty()) { + return; + } + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + syncDepartmentInfo(appid, authCorpInfo); + } } catch (Exception e) { log.error("syncDepartmentInfoTask error appid: " + appid, e); - } + }*/ try { - syncDepartmentInfo(appid2); + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid2); + if (null == authCorpInfoList || authCorpInfoList.isEmpty()) { + return; + } + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + syncDepartmentInfo(appid2, authCorpInfo); + } } catch (Exception e) { log.error("syncDepartmentInfoTask error appid: " + appid2, e); } } - public void syncDepartmentInfo(String appid) { + public void syncDepartmentInfo(String appid, QyAuthCorpInfoEntity authCorpInfo) { log.info("syncDepartmentInfo Current Thread : {}, Fixed Rate Task : The time is now {}", Thread.currentThread().getName(), DateUtil.formatTime(new Date())); try { - QyAuthCorpInfoEntity authCorpInfo = authCorpInfoApplicationService.getByAppid(appid); if (authCorpInfo == null) { return; } - QyAccessTokenEntity accessToken = accessTokenApplicationService.getByAppid(appid); + QyAccessTokenEntity accessToken = accessTokenApplicationService.getByAppid(appid, authCorpInfo.getCorpid()); if (null == accessToken || StringUtils.isBlank(accessToken.getAccessToken())) { log.error("syncDepartmentInfo accessToken is null"); return; @@ -164,6 +199,10 @@ public class QywxScheduleJob { if (null == departmentEntities) { departmentEntities = new ArrayList<>(); } + departmentEntities = departmentEntities.stream() + .filter(d -> Objects.equals(d.getCorpid(), authCorpInfo.getCorpid())) + .collect(Collectors.toList()); + // 转换为Map结构 Map localDeptMap = departmentEntities.stream() .collect(Collectors.toMap(QyDepartmentEntity::getDepartmentId, Function.identity())); @@ -202,6 +241,7 @@ public class QywxScheduleJob { List newDeptList = toAdd.stream() .map(d -> { AddDepartmentCommand command = new AddDepartmentCommand(); + command.setCorpid(authCorpInfo.getCorpid()); command.setDepartmentId(String.valueOf(d.getId())); command.setName(d.getName()); command.setNameEn(d.getName_en()); @@ -254,5 +294,292 @@ public class QywxScheduleJob { log.error("syncDepartmentInfo error", e); } } + + @Scheduled(cron = "0 40 * * * *") + public void syncUserInfoTask() { + /*try { + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + if (null == authCorpInfoList || authCorpInfoList.isEmpty()) { + return; + } + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + syncUserInfo(appid, authCorpInfo); + } + } catch (Exception e) { + log.error("syncUserInfoTask error appid: " + appid, e); + }*/ + + try { + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid2); + if (null == authCorpInfoList || authCorpInfoList.isEmpty()) { + return; + } + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + syncUserInfo(appid2, authCorpInfo); + } + } catch (Exception e) { + log.error("syncUserInfoTask error appid: " + appid2, e); + } + } + + public void syncUserInfo(String appid, QyAuthCorpInfoEntity authCorpInfo) { + log.info("syncUserInfo Current Thread : {}, Fixed Rate Task : The time is now {}", + Thread.currentThread().getName(), DateUtil.formatTime(new Date())); + + try { + if (authCorpInfo == null) { + return; + } + + QyAccessTokenEntity accessToken = accessTokenApplicationService.getByAppid(appid, authCorpInfo.getCorpid()); + if (null == accessToken || StringUtils.isBlank(accessToken.getAccessToken())) { + log.error("syncUserInfo accessToken is null"); + return; + } + + List departmentList = departmentApplicationService.getDepartmentList(); + if (null == departmentList || departmentList.isEmpty()) { + return; + } + departmentList = departmentList.stream() + .filter(d -> Objects.equals(d.getCorpid(), authCorpInfo.getCorpid())) + .collect(Collectors.toList()); + + List qyUserList = qyUserApplicationService.selectAll(); + if (null == qyUserList) { + qyUserList = new ArrayList<>(); + } + qyUserList = qyUserList.stream() + .filter(u -> u.getCorpid().equals(authCorpInfo.getCorpid())) + .collect(Collectors.toList()); + + for (QyDepartmentEntity department : departmentList) { + // 获取部门用户列表 + UserListResponse userList = QywxApiUtil.getUserList(accessToken.getAccessToken(), department.getDepartmentId()); + if (null == userList) { + log.error("获取部门用户列表失败: null"); + continue; + } + if (null != userList.getErrcode() && userList.getErrcode() != 0) { + log.error("获取部门用户列表失败: {}", userList.getErrmsg()); + continue; + } + log.info("获取部门用户列表成功: {}", JSONUtil.toJsonStr(userList)); + + List wxUsers = userList.getUserlist(); + if (wxUsers == null || wxUsers.isEmpty()) { + continue; + } + + Map wxUserMap = wxUsers.stream() + .collect(Collectors.toMap(UserListResponse.UserInfo::getUserid, Function.identity())); + + List localUsers = qyUserList.stream() + .filter(u -> u.getAppid().equals(appid)) + .collect(Collectors.toList()); + Map localUserMap = localUsers.stream() + .collect(Collectors.toMap(QyUserEntity::getUserid, Function.identity())); + + // 识别需要新增的用户 + List toAdd = wxUsers.stream() + .filter(wxUser -> !localUserMap.containsKey(wxUser.getUserid())) + .collect(Collectors.toList()); + log.info("syncUserInfo toAdd: {}", JSONUtil.toJsonStr(toAdd)); + + // 识别需要删除的用户 + List toRemove = localUsers.stream() + .filter(localUser -> !wxUserMap.containsKey(localUser.getUserid())) + .collect(Collectors.toList()); + log.info("syncUserInfo toRemove: {}", JSONUtil.toJsonStr(toRemove)); + + // 识别需要更新的用户 + List toUpdate = localUsers.stream() + .filter(localUser -> wxUserMap.containsKey(localUser.getUserid())) + .filter(localUser -> { + UserListResponse.UserInfo wxUser = wxUserMap.get(localUser.getUserid()); + return !Objects.equals(localUser.getName(), wxUser.getName()) + || !Objects.equals(localUser.getDepartment(), StringUtils.join(wxUser.getDepartment(), ",")) + || !Objects.equals(localUser.getPosition(), wxUser.getPosition()) + || !Objects.equals(localUser.getUserOrder(), StringUtils.join(wxUser.getOrder(), ",")) + || !Objects.equals(localUser.getMobile(), wxUser.getMobile()) + || !Objects.equals(localUser.getGender(), String.valueOf(wxUser.getGender())) + || !Objects.equals(localUser.getEmail(), wxUser.getEmail()) + || !Objects.equals(localUser.getBizMail(), wxUser.getBiz_mail()) + || !Objects.equals(localUser.getDirectLeader(), StringUtils.join(wxUser.getDirect_leader(), ",")) + || !Objects.equals(localUser.getIsLeaderInDept(), StringUtils.join(wxUser.getIs_leader_in_dept(), ",")) + || !Objects.equals(localUser.getTelephone(), wxUser.getTelephone()) + || !Objects.equals(localUser.getAlias(), wxUser.getAlias()) + || !Objects.equals(localUser.getAddress(), wxUser.getAddress()) + || !Objects.equals(localUser.getMainDepartment(), String.valueOf(wxUser.getMain_department())) + || !Objects.equals(localUser.getAvatar(), wxUser.getAvatar()) + || !Objects.equals(localUser.getThumbAvatar(), wxUser.getThumb_avatar()) + || !Objects.equals(localUser.getStatus(), String.valueOf(wxUser.getStatus())) + || !Objects.equals(localUser.getQrCode(), wxUser.getQr_code()) + || !Objects.equals(localUser.getExternalPosition(), wxUser.getExternal_position()); + }) + .peek(localUser -> { + UserListResponse.UserInfo wxUser = wxUserMap.get(localUser.getUserid()); + localUser.setName(wxUser.getName()); + localUser.setDepartment(StringUtils.join(wxUser.getDepartment(), ",")); + localUser.setUserOrder(StringUtils.join(wxUser.getOrder(), ",")); + localUser.setPosition(wxUser.getPosition()); + localUser.setMobile(wxUser.getMobile()); + localUser.setGender(String.valueOf(wxUser.getGender())); + localUser.setEmail(wxUser.getEmail()); + localUser.setBizMail(wxUser.getBiz_mail()); + localUser.setDirectLeader(StringUtils.join(wxUser.getDirect_leader(), ",")); + localUser.setIsLeaderInDept(StringUtils.join(wxUser.getIs_leader_in_dept(), ",")); + localUser.setTelephone(wxUser.getTelephone()); + localUser.setAlias(wxUser.getAlias()); + localUser.setAddress(wxUser.getAddress()); + localUser.setMainDepartment(String.valueOf(wxUser.getMain_department())); + localUser.setAvatar(wxUser.getAvatar()); + localUser.setThumbAvatar(wxUser.getThumb_avatar()); + localUser.setStatus(String.valueOf(wxUser.getStatus())); + localUser.setQrCode(wxUser.getQr_code()); + localUser.setExternalPosition(wxUser.getExternal_position()); + localUser.setUpdateTime(new Date()); + }) + .map(localUser -> { + UpdateQyUserCommand command = new UpdateQyUserCommand(); + BeanUtils.copyProperties(localUser, command); + return command; + }) + .collect(Collectors.toList()); + log.info("syncUserInfo toUpdate: {}", JSONUtil.toJsonStr(toUpdate)); + + // 新增用户 + if (!toAdd.isEmpty()) { + toAdd.forEach(wxUser -> { + AddQyUserCommand newUser = new AddQyUserCommand(); + newUser.setUserid(wxUser.getUserid()); + newUser.setOpenUserid(wxUser.getOpen_userid()); + newUser.setName(wxUser.getName()); + newUser.setDepartment(StringUtils.join(wxUser.getDepartment(), ",")); + newUser.setUserOrder(StringUtils.join(wxUser.getOrder(), ",")); + newUser.setPosition(wxUser.getPosition()); + newUser.setMobile(wxUser.getMobile()); + newUser.setGender(String.valueOf(wxUser.getGender())); + newUser.setEmail(wxUser.getEmail()); + newUser.setBizMail(wxUser.getBiz_mail()); + newUser.setDirectLeader(StringUtils.join(wxUser.getDirect_leader(), ",")); + newUser.setIsLeaderInDept(StringUtils.join(wxUser.getIs_leader_in_dept(), ",")); + newUser.setTelephone(wxUser.getTelephone()); + newUser.setAlias(wxUser.getAlias()); + newUser.setAddress(wxUser.getAddress()); + newUser.setMainDepartment(String.valueOf(wxUser.getMain_department())); + newUser.setAvatar(wxUser.getAvatar()); + newUser.setThumbAvatar(wxUser.getThumb_avatar()); + newUser.setStatus(String.valueOf(wxUser.getStatus())); + newUser.setQrCode(wxUser.getQr_code()); + newUser.setExternalPosition(wxUser.getExternal_position()); + newUser.setEnable(String.valueOf(1)); + newUser.setCorpid(authCorpInfo.getCorpid()); + newUser.setAppid(appid); + newUser.setDeleted(false); + newUser.setCreatorId(0L); + newUser.setCreateTime(new Date()); + newUser.setUpdaterId(0L); + newUser.setUpdateTime(new Date()); + qyUserApplicationService.addUser(newUser); + }); + } + + // 更新用户 + if (!toUpdate.isEmpty()) { + toUpdate.forEach(qyUserApplicationService::updateUser); + } + + // 删除用户 + if (!toRemove.isEmpty()) { + BulkOperationCommand command = new BulkOperationCommand<>( + toRemove.stream().map(QyUserEntity::getId).collect(Collectors.toList()) + ); + qyUserApplicationService.deleteUser(command); + } + } + } catch (Exception e) { + log.error("syncUserInfo error", e); + } + } + + @Scheduled(cron = "0 50 * * * *") + public void syncUserBindingsTask() { + try { + syncUserBindings(); + } catch (Exception e) { + log.error("syncUserBindingsTask error appid: " + appid2, e); + } + } + + public void syncUserBindings() { + log.info("syncUserBindings Current Thread : {}, Fixed Rate Task : The time is now {}", + Thread.currentThread().getName(), DateUtil.formatTime(new Date())); + + try { + List qyUserList = qyUserApplicationService.selectAll(); + if (null == qyUserList || qyUserList.isEmpty()) { + log.error("syncUserBindings qyUserList is null"); + return; + } + + List sysUserQyUserEntityList = sysUserQyUserApplicationService.getAll(); + + // 比对绑定关系 + List toAdd = new ArrayList<>(); + List toRemove = new ArrayList<>(); + + // 遍历企业用户匹配系统用户 + qyUserList.forEach(qyUser -> { + boolean exists = sysUserQyUserEntityList.stream() + .anyMatch(binding -> binding.getQyUserId().equals(qyUser.getId())); + + if (!exists) { + AddSysUserQyUserCommand newBinding = new AddSysUserQyUserCommand(); + newBinding.setQyUserId(qyUser.getId()); + newBinding.setCreatorId(0L); + newBinding.setCreateTime(new Date()); + newBinding.setUpdaterId(0L); + newBinding.setUpdateTime(new Date()); + newBinding.setDeleted(false); + toAdd.add(newBinding); + } + }); + + for (AddSysUserQyUserCommand binding : toAdd) { + AddUserCommand command = new AddUserCommand(); + command.setUsername("qywx" + binding.getQyUserId()); + command.setNickname(qyUserList.stream() + .filter(u -> u.getId().equals(binding.getQyUserId())) + .findFirst().get().getName()); + // 生成随机手机号(1开头) + 后几位是qyUserId + String randomPrefix = "1" + String.format("%08d", (int)(Math.random() * 100000000)); + String phoneNumber = randomPrefix.substring(0, 11 - String.valueOf(binding.getQyUserId()).length()) + + binding.getQyUserId(); + command.setPhoneNumber(phoneNumber); + command.setEmail(phoneNumber + "@qywx.com"); + command.setPassword("123456"); + command.setStatus(1); + UserModel userModel = addUser(command); + + binding.setSysUserId(userModel.getUserId()); + sysUserQyUserApplicationService.addSysUserQyUser(binding); + } + } catch (Exception e) { + log.error("syncUserBindings error", e); + } + } + + private UserModel addUser(AddUserCommand command) { + UserModel model = userModelFactory.create(); + model.loadAddUserCommand(command); + model.checkUsernameIsUnique(); + model.checkPhoneNumberIsUnique(); + model.checkEmailIsUnique(); + model.checkFieldRelatedEntityExist(); + model.resetPassword(command.getPassword()); + model.insert(); + return model; + } } diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/FakeUserDetailsServiceImpl.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/FakeUserDetailsServiceImpl.java new file mode 100644 index 0000000..080c257 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/FakeUserDetailsServiceImpl.java @@ -0,0 +1,104 @@ +package com.agileboot.admin.customize.service.login; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.enums.BasicEnumUtil; +import com.agileboot.common.enums.common.UserStatusEnum; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.system.menu.db.SysMenuEntity; +import com.agileboot.domain.system.menu.db.SysMenuService; +import com.agileboot.domain.system.role.db.SysRoleEntity; +import com.agileboot.domain.system.role.db.SysRoleService; +import com.agileboot.domain.system.user.db.SysUserEntity; +import com.agileboot.domain.system.user.db.SysUserService; +import com.agileboot.infrastructure.user.web.DataScopeEnum; +import com.agileboot.infrastructure.user.web.RoleInfo; +import com.agileboot.infrastructure.user.web.SystemLoginUser; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.SetUtils; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Service +@Slf4j +@RequiredArgsConstructor +public class FakeUserDetailsServiceImpl { + private final SysUserService userService; + + private final SysMenuService menuService; + + private final SysRoleService roleService; + + private final TokenService tokenService; + + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + SysUserEntity userEntity = userService.getUserByUserName(username); + if (userEntity == null) { + log.info("登录用户:{} 不存在.", username); + throw new ApiException(ErrorCode.Business.USER_NON_EXIST, username); + } + if (!Objects.equals(UserStatusEnum.NORMAL.getValue(), userEntity.getStatus())) { + log.info("登录用户:{} 已被停用.", username); + throw new ApiException(ErrorCode.Business.USER_IS_DISABLE, username); + } + + RoleInfo roleInfo = getRoleInfo(userEntity.getRoleId(), userEntity.getIsAdmin()); + + SystemLoginUser loginUser = new SystemLoginUser(userEntity.getUserId(), userEntity.getIsAdmin(), userEntity.getUsername(), + userEntity.getPassword(), roleInfo, userEntity.getDeptId()); + loginUser.fillLoginInfo(); + loginUser.setAutoRefreshCacheTime(loginUser.getLoginInfo().getLoginTime() + + TimeUnit.MINUTES.toMillis(tokenService.getAutoRefreshTime())); + return loginUser; + } + + public RoleInfo getRoleInfo(Long roleId, boolean isAdmin) { + if (roleId == null) { + return RoleInfo.EMPTY_ROLE; + } + + if (isAdmin) { + LambdaQueryWrapper menuQuery = Wrappers.lambdaQuery(); + menuQuery.select(SysMenuEntity::getMenuId); + List allMenus = menuService.list(menuQuery); + + Set allMenuIds = allMenus.stream().map(SysMenuEntity::getMenuId).collect(Collectors.toSet()); + + return new RoleInfo(RoleInfo.ADMIN_ROLE_ID, RoleInfo.ADMIN_ROLE_KEY, DataScopeEnum.ALL, SetUtils.emptySet(), + RoleInfo.ADMIN_PERMISSIONS, allMenuIds); + + } + + SysRoleEntity roleEntity = roleService.getById(roleId); + + if (roleEntity == null) { + return RoleInfo.EMPTY_ROLE; + } + + List menuList = roleService.getMenuListByRoleId(roleId); + + Set menuIds = menuList.stream().map(SysMenuEntity::getMenuId).collect(Collectors.toSet()); + Set permissions = menuList.stream().map(SysMenuEntity::getPermission).collect(Collectors.toSet()); + + DataScopeEnum dataScopeEnum = BasicEnumUtil.fromValue(DataScopeEnum.class, roleEntity.getDataScope()); + + Set deptIdSet = SetUtils.emptySet(); + if (StrUtil.isNotEmpty(roleEntity.getDeptIdSet())) { + deptIdSet = StrUtil.split(roleEntity.getDeptIdSet(), ",").stream() + .map(Convert::toLong).collect(Collectors.toSet()); + } + + return new RoleInfo(roleId, roleEntity.getRoleKey(), dataScopeEnum, deptIdSet, permissions, menuIds); + } +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/LoginService.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/LoginService.java index 0282bf1..fa60657 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/LoginService.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/LoginService.java @@ -10,8 +10,11 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; import com.agileboot.common.config.AgileBootConfig; import com.agileboot.common.constant.Constants.Captcha; +import com.agileboot.common.core.dto.ResponseDTO; import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode.Business; @@ -21,6 +24,9 @@ import com.agileboot.domain.common.cache.GuavaCacheService; import com.agileboot.domain.common.cache.MapCache; import com.agileboot.domain.common.cache.RedisCacheService; import com.agileboot.admin.customize.async.AsyncTaskFactory; +import com.agileboot.domain.qywx.accessToken.AccessTokenApplicationService; +import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity; +import com.agileboot.domain.qywx.userQySys.SysUserQyUserApplicationService; import com.agileboot.infrastructure.thread.ThreadPoolManager; import com.agileboot.admin.customize.service.login.dto.CaptchaDTO; import com.agileboot.admin.customize.service.login.dto.ConfigDTO; @@ -31,6 +37,8 @@ import com.agileboot.common.enums.common.LoginStatusEnum; import com.agileboot.domain.system.user.db.SysUserEntity; import com.google.code.kaptcha.Producer; import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; import javax.annotation.Resource; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -40,8 +48,10 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio 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.UserDetails; import org.springframework.stereotype.Component; import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.web.client.RestClientException; /** * 登录校验方法 @@ -61,6 +71,12 @@ public class LoginService { private final AuthenticationManager authenticationManager; + private final AccessTokenApplicationService accessTokenApplicationService; + + private final FakeUserDetailsServiceImpl fakeUserDetailsService; + + private final SysUserQyUserApplicationService sysUserQyUserApplicationService; + @Resource(name = "captchaProducer") private Producer captchaProducer; @@ -80,22 +96,33 @@ public class LoginService { } // 用户验证 Authentication authentication; - String decryptPassword = decryptPassword(loginCommand.getPassword()); - try { - // 该方法会去调用UserDetailsServiceImpl#loadUserByUsername 校验用户名和密码 认证鉴权 - authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( - loginCommand.getUsername(), decryptPassword)); - } catch (BadCredentialsException e) { - ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, - MessageUtils.message("Business.LOGIN_WRONG_USER_PASSWORD"))); - throw new ApiException(e, ErrorCode.Business.LOGIN_WRONG_USER_PASSWORD); - } catch (AuthenticationException e) { - ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage())); - throw new ApiException(e, ErrorCode.Business.LOGIN_ERROR, e.getMessage()); - } catch (Exception e) { - ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage())); - throw new ApiException(e, Business.LOGIN_ERROR, e.getMessage()); + + if (StrUtil.isNotEmpty(loginCommand.getCode())) { + String userid = getQyUserinfo(loginCommand.getCorpid(), loginCommand.getCode()); + String username = sysUserQyUserApplicationService.getUsernameByQyUserid(userid); + + UserDetails user = fakeUserDetailsService.loadUserByUsername(username); + authentication = new UsernamePasswordAuthenticationToken( + user, null, user.getAuthorities()); + } else { + String decryptPassword = decryptPassword(loginCommand.getPassword()); + try { + // 该方法会去调用UserDetailsServiceImpl#loadUserByUsername 校验用户名和密码 认证鉴权 + authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( + loginCommand.getUsername(), decryptPassword)); + } catch (BadCredentialsException e) { + ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, + MessageUtils.message("Business.LOGIN_WRONG_USER_PASSWORD"))); + throw new ApiException(e, ErrorCode.Business.LOGIN_WRONG_USER_PASSWORD); + } catch (AuthenticationException e) { + ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage())); + throw new ApiException(e, ErrorCode.Business.LOGIN_ERROR, e.getMessage()); + } catch (Exception e) { + ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage())); + throw new ApiException(e, Business.LOGIN_ERROR, e.getMessage()); + } } + // 把当前登录用户 放入上下文中 SecurityContextHolder.getContext().setAuthentication(authentication); // 这里获取的loginUser是UserDetailsServiceImpl#loadUserByUsername方法返回的LoginUser @@ -219,4 +246,26 @@ public class LoginService { return Convert.toBool(guavaCache.configCache.get(ConfigKeyEnum.CAPTCHA.getValue())); } + private String getQyUserinfo(String corpid, String code) { + try { + QyAccessTokenEntity qyAccessToken = accessTokenApplicationService.getByAppid("QWTONG_YS_WXSHOP", corpid); + String url = String.format( + "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=%s&code=%s", + qyAccessToken.getAccessToken(), code); + + String response = HttpUtil.get(url); + log.info("微信getuserinfo接口返回: {}", response); + Map result = JSONUtil.toBean(response, HashMap.class); + + if (result.containsKey("errcode") && !result.get("errcode").equals(0)) { + log.error("微信接口返回错误: {}", result); + throw new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "无效的code参数"); + } + + return String.valueOf(result.get("userid")); + } catch (RestClientException e) { + log.error("获取openid失败", e); + throw new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "微信服务调用失败"); + } + } } diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/command/LoginCommand.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/command/LoginCommand.java index a0e6e28..169a759 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/command/LoginCommand.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/command/LoginCommand.java @@ -30,4 +30,8 @@ public class LoginCommand { */ private String captchaCodeKey; + // 企业id + private String corpid; + // 企业授权码 + private String code; } diff --git a/agileboot-admin/src/test/java/com/agileboot/admin/config/AgileBootConfigTest.java b/agileboot-admin/src/test/java/com/agileboot/admin/config/AgileBootConfigTest.java index 52dd503..ca5016b 100644 --- a/agileboot-admin/src/test/java/com/agileboot/admin/config/AgileBootConfigTest.java +++ b/agileboot-admin/src/test/java/com/agileboot/admin/config/AgileBootConfigTest.java @@ -1,17 +1,29 @@ package com.agileboot.admin.config; +import cn.hutool.json.JSONUtil; import com.agileboot.admin.AgileBootAdminApplication; +import com.agileboot.admin.customize.service.login.LoginService; import com.agileboot.common.config.AgileBootConfig; import com.agileboot.common.constant.Constants.UploadSubDir; import java.io.File; +import java.util.List; import javax.annotation.Resource; + +import com.agileboot.domain.qywx.accessToken.AccessTokenApplicationService; +import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity; +import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService; +import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity; +import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.jupiter.api.Assertions; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.test.context.junit4.SpringRunner; +@Slf4j @SpringBootTest(classes = AgileBootAdminApplication.class) @RunWith(SpringRunner.class) public class AgileBootConfigTest { @@ -19,6 +31,15 @@ public class AgileBootConfigTest { @Resource private AgileBootConfig config; + @Autowired + private AccessTokenApplicationService accessTokenApplicationService; + + @Autowired + private AuthCorpInfoApplicationService authCorpInfoApplicationService; + + @Autowired + private LoginService loginService; + @Test public void testConfig() { String fileBaseDir = "D:\\agileboot\\profile"; @@ -41,4 +62,22 @@ public class AgileBootConfigTest { AgileBootConfig.getFileBaseDir() + File.separator + UploadSubDir.UPLOAD_PATH); } + @Test + public void testAccessToken() { + String appid = "QWTONG_YS_WXSHOP"; + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + for (QyAuthCorpInfoEntity authCorpInfo : authCorpInfoList) { + QyAccessTokenEntity accessToken = accessTokenApplicationService.getByAppid(appid, authCorpInfo.getCorpid()); + log.info(JSONUtil.toJsonStr(accessToken)); + } + } + + @Test + public void testDecryptPassword() { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + String res = passwordEncoder.encode("admin123"); + Boolean match = passwordEncoder.matches("admin123", "$2a$10$o55UFZAtyWnDpRV6dvQe8.c/MjlFacC49ASj2usNXm9BY74SYI/uG"); + log.info(res); + log.info(String.valueOf(match)); + } } diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/QywxController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/QywxController.java index 127d6a7..8dd8433 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/QywxController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/QywxController.java @@ -15,6 +15,7 @@ import com.agileboot.domain.qywx.template.command.UpdateTemplateCommand; import com.agileboot.domain.qywx.template.db.QyTemplateEntity; import com.agileboot.infrastructure.thread.ThreadPoolManager; import java.io.StringReader; +import java.util.List; import javax.annotation.PostConstruct; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -108,13 +109,20 @@ public class QywxController { @RequestParam String echostr, @RequestParam String appid) { try { - QyAuthCorpInfoEntity qyAuthCorpInfo = authCorpInfoApplicationService.getByAppid(appid); - QyTemplateEntity template = templateApplicationService.getByAppid(appid); - String token = template.getToken(); - String encodingAesKey = template.getEncodingAESKey(); - String corpId = qyAuthCorpInfo.getCorpid(); - WXBizMsgCrypt wxCrypt = new WXBizMsgCrypt(token, encodingAesKey, corpId); - return wxCrypt.VerifyURL(msg_signature, timestamp, nonce, echostr); + List authCorpInfoList = authCorpInfoApplicationService.getByAppid(appid); + for (QyAuthCorpInfoEntity qyAuthCorpInfo : authCorpInfoList) { + try { + QyTemplateEntity template = templateApplicationService.getByAppid(appid); + String token = template.getToken(); + String encodingAesKey = template.getEncodingAESKey(); + String corpId = qyAuthCorpInfo.getCorpid(); + WXBizMsgCrypt wxCrypt = new WXBizMsgCrypt(token, encodingAesKey, corpId); + return wxCrypt.VerifyURL(msg_signature, timestamp, nonce, echostr); + } catch (Exception ignored) { + } + } + log.error("验证失败"); + throw new ApiException(ErrorCode.FAILED, "验证失败"); } catch (Exception e) { log.error("验证失败", e); throw new ApiException(ErrorCode.FAILED, "验证失败", e); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/AccessTokenApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/AccessTokenApplicationService.java index f729a44..47f5452 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/AccessTokenApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/AccessTokenApplicationService.java @@ -56,8 +56,8 @@ public class AccessTokenApplicationService { } } - public QyAccessTokenEntity getByAppid(String appid) { - return accessTokenService.getByAppid(appid); + public QyAccessTokenEntity getByAppid(String appid, String corpid) { + return accessTokenService.getByAppid(appid, corpid); } public void getAccessToken(String corpid, String corpsecret, String appid) { @@ -73,7 +73,7 @@ public class AccessTokenApplicationService { return; } - QyAccessTokenEntity accessToken = accessTokenService.getByAppid(appid); + QyAccessTokenEntity accessToken = accessTokenService.getByAppid(appid, corpid); if (null == accessToken) { AddAccessTokenCommand command = new AddAccessTokenCommand(); command.setAppid(appid); @@ -82,13 +82,14 @@ public class AccessTokenApplicationService { command.setCorpid(corpid); command.setSecret(corpsecret); command.setGettokenTime(new Date()); + command.setAuthState("1"); + command.setEnable("1"); addAccessToken(command); } else { UpdateAccessTokenCommand command = new UpdateAccessTokenCommand(); command.setId(accessToken.getId()); command.setAccessToken(access_token); command.setExpiresIn(expires_in); - command.setCorpid(corpid); command.setSecret(corpsecret); command.setGettokenTime(new Date()); updateAccessToken(command); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenMapper.java index bf998a7..e3e6f75 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenMapper.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenMapper.java @@ -32,6 +32,6 @@ public interface QyAccessTokenMapper extends BaseMapper { "ORDER BY gettoken_time DESC") List selectAll(); - @Select("SELECT * FROM qy_access_token WHERE appid = #{appid} LIMIT 1") - QyAccessTokenEntity selectByAppid(String appid); + @Select("SELECT * FROM qy_access_token WHERE appid = #{appid} AND corpid = #{corpid} LIMIT 1") + QyAccessTokenEntity selectByAppid(@Param("appid")String appid, @Param("corpid")String corpid); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenService.java index 3f28f24..f55c8a2 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenService.java @@ -19,5 +19,5 @@ public interface QyAccessTokenService extends IService { List selectAll(); - QyAccessTokenEntity getByAppid(String appid); + QyAccessTokenEntity getByAppid(String appid, String corpid); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenServiceImpl.java index 579b62a..e5f4896 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/accessToken/db/QyAccessTokenServiceImpl.java @@ -34,7 +34,7 @@ public class QyAccessTokenServiceImpl extends ServiceImpl userlist; + + @Data + public static class UserInfo { + private String userid; + private String name; + private List department; + private List order; + private String position; + private String mobile; + private String gender; + private String email; + private String biz_mail; + private List is_leader_in_dept; + private List direct_leader; + private Integer status; + private String telephone; + private String alias; + private String qr_code; + private String open_userid; + private String external_position; + private String address; + private String english_name; + private Integer main_department; + private String avatar; + private String thumb_avatar; +// private ExtAttr extattr; +// private ExternalProfile external_profile; + + /*@Data + public static class ExtAttr { + private List attrs; + + @Data + public static class Attr { + private Integer type; + private String name; + private Text text; + private Web web; + + @Data + public static class Text { + private String value; + } + + @Data + public static class MiniProgram { + private String appid; + private String pagepath; + private String title; + } + + @Data + public static class Web { + private String url; + private String title; + } + } + } + + @Data + public static class ExternalProfile { + private String external_corp_name; + private WechatChannels wechat_channels; + private List external_attr; + + @Data + public static class WechatChannels { + private String nickname; + private Integer status; + } + }*/ + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/AuthCorpInfoApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/AuthCorpInfoApplicationService.java index a7037b1..bf0efac 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/AuthCorpInfoApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/AuthCorpInfoApplicationService.java @@ -51,7 +51,7 @@ public class AuthCorpInfoApplicationService { } - public QyAuthCorpInfoEntity getByAppid(String appid) { + public List getByAppid(String appid) { return authCorpInfoService.getByAppid(appid); } } \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoMapper.java index 44f8788..d8901bf 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoMapper.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoMapper.java @@ -32,6 +32,6 @@ public interface QyAuthCorpInfoMapper extends BaseMapper { List selectAll(); - @Select("SELECT * FROM qy_auth_corp_info WHERE appid = #{appid} LIMIT 1") - QyAuthCorpInfoEntity selectByAppid(String appid); + @Select("SELECT * FROM qy_auth_corp_info WHERE appid = #{appid}") + List selectByAppid(@Param("appid")String appid); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoService.java index ab400c1..07ae72b 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoService.java @@ -19,5 +19,5 @@ public interface QyAuthCorpInfoService extends IService { List selectAll(); - QyAuthCorpInfoEntity getByAppid(String appid); + List getByAppid(String appid); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoServiceImpl.java index 7f56bfb..27feb26 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/authCorpInfo/db/QyAuthCorpInfoServiceImpl.java @@ -30,7 +30,7 @@ public class QyAuthCorpInfoServiceImpl extends ServiceImpl getByAppid(String appid) { return baseMapper.selectByAppid(appid); } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentServiceImpl.java index d6c726f..0827754 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/department/db/QyDepartmentServiceImpl.java @@ -20,7 +20,7 @@ public class QyDepartmentServiceImpl extends ServiceImpl selectAll() { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - + wrapper.eq(QyDepartmentEntity::getDeleted, false); return this.list(wrapper); } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateServiceImpl.java index 9e624a3..c5f2517 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/template/db/QyTemplateServiceImpl.java @@ -1,9 +1,6 @@ package com.agileboot.domain.qywx.template.db; import com.agileboot.common.core.page.AbstractPageQuery; -import com.agileboot.domain.qywx.template.db.QyTemplateService; -import com.agileboot.domain.qywx.template.db.QyTemplateEntity; -import com.agileboot.domain.qywx.template.db.QyTemplateMapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/QyUserApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/QyUserApplicationService.java index f4e08e8..455ae55 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/QyUserApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/QyUserApplicationService.java @@ -2,8 +2,8 @@ package com.agileboot.domain.qywx.user; import com.agileboot.common.core.page.PageDTO; import com.agileboot.domain.common.command.BulkOperationCommand; -import com.agileboot.domain.qywx.user.command.AddUserCommand; -import com.agileboot.domain.qywx.user.command.UpdateUserCommand; +import com.agileboot.domain.qywx.user.command.AddQyUserCommand; +import com.agileboot.domain.qywx.user.command.UpdateQyUserCommand; import com.agileboot.domain.qywx.user.db.QyUserEntity; import com.agileboot.domain.qywx.user.db.QyUserService; import com.agileboot.domain.qywx.user.dto.QyUserDTO; @@ -30,13 +30,17 @@ public class QyUserApplicationService { return new PageDTO<>(dtoList, page.getTotal()); } - public void addUser(AddUserCommand command) { + public List selectAll() { + return userService.selectAll(); + } + + public void addUser(AddQyUserCommand command) { UserModel model = qyUserModelFactory.create(); model.loadAddCommand(command); model.insert(); } - public void updateUser(UpdateUserCommand command) { + public void updateUser(UpdateQyUserCommand command) { UserModel model = qyUserModelFactory.loadById(command.getId()); model.loadUpdateCommand(command); model.updateById(); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/AddUserCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/AddQyUserCommand.java similarity index 78% rename from agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/AddUserCommand.java rename to agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/AddQyUserCommand.java index a11bff8..976f18a 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/AddUserCommand.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/AddQyUserCommand.java @@ -6,6 +6,6 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @Data -public class AddUserCommand extends QyUserEntity { +public class AddQyUserCommand extends QyUserEntity { } \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/UpdateUserCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/UpdateQyUserCommand.java similarity index 83% rename from agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/UpdateUserCommand.java rename to agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/UpdateQyUserCommand.java index 4b9ebe8..beb5e32 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/UpdateUserCommand.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/command/UpdateQyUserCommand.java @@ -7,7 +7,7 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @Data -public class UpdateUserCommand extends AddUserCommand { +public class UpdateQyUserCommand extends AddQyUserCommand { @NotNull @PositiveOrZero diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserServiceImpl.java index c4a52d9..8f2d947 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/db/QyUserServiceImpl.java @@ -28,6 +28,7 @@ public class QyUserServiceImpl extends ServiceImpl i public List selectAll() { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(QyUserEntity::getEnable, "1") + .eq(QyUserEntity::getDeleted, false) .orderByDesc(QyUserEntity::getId); return this.list(wrapper); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/model/UserModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/model/UserModel.java index c617106..9c6e87d 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/model/UserModel.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/user/model/UserModel.java @@ -1,8 +1,8 @@ package com.agileboot.domain.qywx.user.model; import cn.hutool.core.bean.BeanUtil; -import com.agileboot.domain.qywx.user.command.AddUserCommand; -import com.agileboot.domain.qywx.user.command.UpdateUserCommand; +import com.agileboot.domain.qywx.user.command.AddQyUserCommand; +import com.agileboot.domain.qywx.user.command.UpdateQyUserCommand; import com.agileboot.domain.qywx.user.db.QyUserEntity; import com.agileboot.domain.qywx.user.db.QyUserService; import lombok.Data; @@ -25,13 +25,13 @@ public class UserModel extends QyUserEntity { this.userService = userService; } - public void loadAddCommand(AddUserCommand command) { + public void loadAddCommand(AddQyUserCommand command) { if (command != null) { BeanUtil.copyProperties(command, this, "id"); } } - public void loadUpdateCommand(UpdateUserCommand command) { + public void loadUpdateCommand(UpdateQyUserCommand command) { if (command != null) { loadAddCommand(command); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/SysUserQyUserApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/SysUserQyUserApplicationService.java new file mode 100644 index 0000000..d1ca427 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/SysUserQyUserApplicationService.java @@ -0,0 +1,69 @@ +package com.agileboot.domain.qywx.userQySys; + +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.domain.common.command.BulkOperationCommand; +import com.agileboot.domain.qywx.userQySys.command.AddSysUserQyUserCommand; +import com.agileboot.domain.qywx.userQySys.command.UpdateSysUserQyUserCommand; +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserEntity; +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserService; +import com.agileboot.domain.qywx.userQySys.dto.SysUserQyUserDTO; +import com.agileboot.domain.qywx.userQySys.model.SysUserQyUserModel; +import com.agileboot.domain.qywx.userQySys.model.SysUserQyUserModelFactory; +import com.agileboot.domain.qywx.userQySys.query.SearchSysUserQyUserQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class SysUserQyUserApplicationService { + private final SysUserQyUserService sysUserQyUserService; + private final SysUserQyUserModelFactory sysUserQyUserModelFactory; + + public PageDTO getSysUserQyUserList(SearchSysUserQyUserQuery query) { + Page page = sysUserQyUserService.getUserQyUserList(query); + List dtoList = page.getRecords().stream() + .map(SysUserQyUserDTO::new) + .collect(Collectors.toList()); + return new PageDTO<>(dtoList, page.getTotal()); + } + + public void addSysUserQyUser(AddSysUserQyUserCommand command) { + SysUserQyUserModel model = sysUserQyUserModelFactory.create(); + model.loadAddCommand(command); + model.insert(); + } + + public void updateSysUserQyUser(UpdateSysUserQyUserCommand command) { + SysUserQyUserModel model = sysUserQyUserModelFactory.loadById(command.getId()); + model.loadUpdateCommand(command); + model.updateById(); + } + + public void deleteSysUserQyUser(BulkOperationCommand command) { + for (Long id : command.getIds()) { + SysUserQyUserModel model = sysUserQyUserModelFactory.loadById(id); + model.deleteById(); + } + } + + public SysUserQyUserEntity getBySysUserId(Long sysUserId) { + return sysUserQyUserService.getBySysUserId(sysUserId); + } + + public SysUserQyUserEntity getByQyUserId(Integer qyUserId) { + return sysUserQyUserService.getByQyUserId(qyUserId); + } + + public List getAll() { + return sysUserQyUserService.selectAll(); + } + + public String getUsernameByQyUserid(String userid) { + return sysUserQyUserService.getUsernameByQyUserid(userid); + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/command/AddSysUserQyUserCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/command/AddSysUserQyUserCommand.java new file mode 100644 index 0000000..b18e07d --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/command/AddSysUserQyUserCommand.java @@ -0,0 +1,11 @@ +package com.agileboot.domain.qywx.userQySys.command; + +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class AddSysUserQyUserCommand extends SysUserQyUserEntity { + +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/command/UpdateSysUserQyUserCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/command/UpdateSysUserQyUserCommand.java new file mode 100644 index 0000000..218580a --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/command/UpdateSysUserQyUserCommand.java @@ -0,0 +1,17 @@ +package com.agileboot.domain.qywx.userQySys.command; + +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserEntity; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.PositiveOrZero; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class UpdateSysUserQyUserCommand extends AddSysUserQyUserCommand { + + @NotNull + @PositiveOrZero + private Long id; + +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserEntity.java new file mode 100644 index 0000000..4087f9d --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserEntity.java @@ -0,0 +1,52 @@ +package com.agileboot.domain.qywx.userQySys.db; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 系统用户与企业微信用户关联表 + *

+ * + * @author valarchie + * @since 2025-03-27 + */ +@Getter +@Setter +@TableName("sys_user_qy_user") +@ApiModel(value = "SysUserQyUserEntity对象", description = "系统用户与企业微信用户关联表") +public class SysUserQyUserEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("关联ID") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty("系统用户ID") + @TableField("sys_user_id") + private Long sysUserId; + + @ApiModelProperty("企业微信用户ID") + @TableField("qy_user_id") + private Integer qyUserId; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + + @Override + public Serializable pkVal() { + return this.id; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserMapper.java new file mode 100644 index 0000000..759b198 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserMapper.java @@ -0,0 +1,49 @@ +package com.agileboot.domain.qywx.userQySys.db; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/** + *

+ * 系统用户与企业微信用户关联表 Mapper 接口 + *

+ * + * @author valarchie + * @since 2025-03-27 + */ +public interface SysUserQyUserMapper extends BaseMapper { + + @Select("SELECT id, sys_user_id, qy_user_id, remark " + + "FROM sys_user_qy_user " + + "${ew.customSqlSegment}") + Page getUserQyUserList( + Page page, + @Param(Constants.WRAPPER) Wrapper queryWrapper + ); + + @Select("SELECT * " + + "FROM sys_user_qy_user " + + "WHERE sys_user_id = #{sysUserId}") + List selectBySysUserId(Long sysUserId); + + @Select("SELECT * " + + "FROM sys_user_qy_user " + + "WHERE qy_user_id = #{qyUserId}") + List selectByQyUserId(Integer qyUserId); + + @Select("SELECT * FROM sys_user_qy_user WHERE sys_user_id = #{sysUserId} AND qy_user_id = #{qyUserId} LIMIT 1") + SysUserQyUserEntity selectBySysUserIdAndQyUserId(@Param("sysUserId") Long sysUserId, @Param("qyUserId") Integer qyUserId); + + @Select("SELECT su.username " + + "FROM sys_user_qy_user suqy " + + "JOIN qy_user qu ON suqy.qy_user_id = qu.id " + + "JOIN sys_user su ON suqy.sys_user_id = su.user_id " + + "WHERE qu.userid = #{userid}") + String selectUsernameByQyUserid(@Param("userid") String userid); + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserService.java new file mode 100644 index 0000000..42f89d8 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserService.java @@ -0,0 +1,29 @@ +package com.agileboot.domain.qywx.userQySys.db; + +import com.agileboot.common.core.page.AbstractPageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + *

+ * 系统用户与企业微信用户关联表 服务类 + *

+ * + * @author valarchie + * @since 2025-03-27 + */ +public interface SysUserQyUserService extends IService { + Page getUserQyUserList(AbstractPageQuery query); + + List selectAll(); + + SysUserQyUserEntity getBySysUserId(Long sysUserId); + + SysUserQyUserEntity getByQyUserId(Integer qyUserId); + + SysUserQyUserEntity getBySysUserIdAndQyUserId(Long sysUserId, Integer qyUserId); + + String getUsernameByQyUserid(String userid); +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserServiceImpl.java new file mode 100644 index 0000000..449447b --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/db/SysUserQyUserServiceImpl.java @@ -0,0 +1,52 @@ +package com.agileboot.domain.qywx.userQySys.db; + +import com.agileboot.common.core.page.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

+ * 系统用户与企业微信用户关联表 服务实现类 + *

+ * + * @author valarchie + * @since 2025-03-27 + */ +@Service +public class SysUserQyUserServiceImpl extends ServiceImpl implements SysUserQyUserService { + + @Override + public Page getUserQyUserList(AbstractPageQuery query) { + return this.page(query.toPage(), query.toQueryWrapper()); + } + + @Override + public List selectAll() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + return this.list(wrapper); + } + + @Override + public SysUserQyUserEntity getBySysUserId(Long sysUserId) { + return baseMapper.selectBySysUserId(sysUserId).stream().findFirst().orElse(null); + } + + @Override + public SysUserQyUserEntity getByQyUserId(Integer qyUserId) { + return baseMapper.selectByQyUserId(qyUserId).stream().findFirst().orElse(null); + } + + @Override + public SysUserQyUserEntity getBySysUserIdAndQyUserId(Long sysUserId, Integer qyUserId) { + return baseMapper.selectBySysUserIdAndQyUserId(sysUserId, qyUserId); + } + + @Override + public String getUsernameByQyUserid(String userid) { + return baseMapper.selectUsernameByQyUserid(userid); + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/dto/SysUserQyUserDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/dto/SysUserQyUserDTO.java new file mode 100644 index 0000000..a27b8c5 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/dto/SysUserQyUserDTO.java @@ -0,0 +1,33 @@ +package com.agileboot.domain.qywx.userQySys.dto; + +import cn.hutool.core.bean.BeanUtil; +import com.agileboot.common.annotation.ExcelColumn; +import com.agileboot.common.annotation.ExcelSheet; +import com.agileboot.domain.common.cache.CacheCenter; +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserEntity; +import com.agileboot.domain.system.user.db.SysUserEntity; +import lombok.Data; + +@ExcelSheet(name = "系统用户与企业微信用户关联列表") +@Data +public class SysUserQyUserDTO { + + public SysUserQyUserDTO(SysUserQyUserEntity entity) { + if (entity != null) { + BeanUtil.copyProperties(entity, this); + } + } + + @ExcelColumn(name = "关联ID") + private Long id; + + @ExcelColumn(name = "系统用户ID") + private Long sysUserId; + + @ExcelColumn(name = "企业微信用户ID") + private Integer qyUserId; + + @ExcelColumn(name = "备注") + private String remark; + +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/model/SysUserQyUserModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/model/SysUserQyUserModel.java new file mode 100644 index 0000000..1d9cc0d --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/model/SysUserQyUserModel.java @@ -0,0 +1,39 @@ +package com.agileboot.domain.qywx.userQySys.model; + +import cn.hutool.core.bean.BeanUtil; +import com.agileboot.domain.qywx.userQySys.command.AddSysUserQyUserCommand; +import com.agileboot.domain.qywx.userQySys.command.UpdateSysUserQyUserCommand; +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserEntity; +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserService; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class SysUserQyUserModel extends SysUserQyUserEntity { + + private SysUserQyUserService sysUserQyUserService; + + public SysUserQyUserModel(SysUserQyUserEntity entity, SysUserQyUserService sysUserQyUserService) { + this(sysUserQyUserService); + if (entity != null) { + BeanUtil.copyProperties(entity, this); + } + } + + public SysUserQyUserModel(SysUserQyUserService sysUserQyUserService) { + this.sysUserQyUserService = sysUserQyUserService; + } + + public void loadAddCommand(AddSysUserQyUserCommand command) { + if (command != null) { + BeanUtil.copyProperties(command, this, "id"); + } + } + + public void loadUpdateCommand(UpdateSysUserQyUserCommand command) { + if (command != null) { + loadAddCommand(command); + } + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/model/SysUserQyUserModelFactory.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/model/SysUserQyUserModelFactory.java new file mode 100644 index 0000000..fe650be --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/model/SysUserQyUserModelFactory.java @@ -0,0 +1,27 @@ +package com.agileboot.domain.qywx.userQySys.model; + +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserEntity; +import com.agileboot.domain.qywx.userQySys.db.SysUserQyUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class SysUserQyUserModelFactory { + + private final SysUserQyUserService sysUserQyUserService; + + public SysUserQyUserModel loadById(Long id) { + SysUserQyUserEntity entity = sysUserQyUserService.getById(id); + if (entity == null) { + throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, id, "系统用户与企业微信用户关联"); + } + return new SysUserQyUserModel(entity, sysUserQyUserService); + } + + public SysUserQyUserModel create() { + return new SysUserQyUserModel(sysUserQyUserService); + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/query/SearchSysUserQyUserQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/query/SearchSysUserQyUserQuery.java new file mode 100644 index 0000000..5294e09 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/userQySys/query/SearchSysUserQyUserQuery.java @@ -0,0 +1,34 @@ +package com.agileboot.domain.qywx.userQySys.query; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.core.page.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import java.util.Date; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class SearchSysUserQyUserQuery extends AbstractPageQuery { + + private Long sysUserId; + private Integer qyUserId; + private String remark; + private Date startTime; + private Date endTime; + + @Override + public QueryWrapper addQueryCondition() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + queryWrapper + .eq(sysUserId != null, "sys_user_id", sysUserId) + .eq(qyUserId != null, "qy_user_id", qyUserId) + .like(StrUtil.isNotEmpty(remark), "remark", remark) + .between(startTime != null && endTime != null, "create_time", startTime, endTime); + + this.timeRangeColumn = "create_time"; + + return queryWrapper; + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java index 6bce2af..15ef26d 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java @@ -63,6 +63,13 @@ public class OrderApplicationService { if (orderModel.getStatus() != 2) { throw new ApiException(ErrorCode.Client.COMMON_FORBIDDEN_TO_CALL, "订单状态不允许操作"); } + Date payTime = orderModel.getPayTime(); + if (payTime == null) + payTime = orderModel.getCreateTime(); + // 验证payTime是否在24小时内 + if (DateUtil.offsetHour(payTime, 24).before(new Date())) { + throw new ApiException(ErrorCode.Client.COMMON_FORBIDDEN_TO_CALL, "订单支付时间超过24小时,不允许操作"); + } ShopOrderGoodsEntity goodsEntity = orderGoodsService.getById(orderGoodsId); if (null == goodsEntity || !orderId.equals(goodsEntity.getOrderId())) { diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserService.java index 631d4e1..028c3b1 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserService.java @@ -5,6 +5,8 @@ import com.agileboot.domain.system.post.db.SysPostEntity; import com.agileboot.domain.system.role.db.SysRoleEntity; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import java.util.Set; @@ -87,6 +89,4 @@ public interface SysUserService extends IService { * @return 用户信息集合信息 */ Page getUserList(AbstractPageQuery query); - - } diff --git a/agileboot-domain/src/main/resources/mapper/qy/SysUserQyUserMapper.xml b/agileboot-domain/src/main/resources/mapper/qy/SysUserQyUserMapper.xml new file mode 100644 index 0000000..863d0cf --- /dev/null +++ b/agileboot-domain/src/main/resources/mapper/qy/SysUserQyUserMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java index 09b0cc5..263d92f 100644 --- a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java @@ -61,7 +61,7 @@ public class CodeGenerator { //生成的类 放在orm子模块下的/target/generated-code目录底下 .module("/agileboot-orm/target/generated-code") .parentPackage("com.agileboot") - .tableName("cabinet_cell") + .tableName("sys_user_qy_user") // 决定是否继承基类 .isExtendsFromBaseEntity(true) .build(); diff --git a/doc/simpread-获取部门成员详情 - 文档 - 企业微信开发者中心.md b/doc/simpread-获取部门成员详情 - 文档 - 企业微信开发者中心.md new file mode 100644 index 0000000..00b5b99 --- /dev/null +++ b/doc/simpread-获取部门成员详情 - 文档 - 企业微信开发者中心.md @@ -0,0 +1,108 @@ +> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [developer.work.weixin.qq.com](https://developer.work.weixin.qq.com/document/path/96260) 获取部门成员详情 最后更新:2024/07/24 + +应用只能获取可见范围内的成员信息,且每种应用获取的字段有所不同,在返回结果说明中会逐个说明。企业通讯录安全特别重要,企业微信持续升级加固通讯录接口的安全机制,以下是关键的变更点: + +* 从 2022 年 6 月 20 号 20 点开始,除通讯录同步以外的基础应用(如客户联系、微信客服、会话存档、日程等),以及新创建的自建应用与代开发应用,调用该接口时,不再返回以下字段:头像、性别、手机、邮箱、企业邮箱、员工个人二维码、地址,应用需要通过 [oauth2 手工授权](#15232)的方式获取管理员与员工本人授权的字段。 + +* **【重要】**从 2022 年 8 月 15 日 10 点开始,“企业管理后台 - 管理工具 - 通讯录同步” 的新增 IP 将不能再调用此接口,企业可通过「[获取成员 ID 列表](#40412)」和「[获取部门 ID 列表](#36259)」接口获取 userid 和部门 ID 列表。[查看调整详情](#40802)。 + +**请求方式:**GET(**HTTPS**) +**请求地址:**https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID + +**参数说明:** + +
参数必须说明
access_token调用接口凭证
department_id获取的部门 id
+ +> 如需获取该部门及其子部门的所有成员,需先获取该部门下的子部门,然后再获取子部门下的部门成员,逐层递归获取。 + +**权限说明:** + +应用须拥有指定部门的查看权限。 + +**返回结果:** + +``` +{ + "errcode": 0, + "errmsg": "ok", + "userlist": [{ + "userid": "zhangsan", + "name": "李四", + "department": [1, 2], + "order": [1, 2], + "position": "后台工程师", + "mobile": "13800000000", + "gender": "1", + "email": "zhangsan@gzdev.com", + "biz_mail":"zhangsan@qyycs2.wecom.work", + "is_leader_in_dept": [1, 0], + "direct_leader":["lisi"], + "avatar": "http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0", + "thumb_avatar": "http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/100", + "telephone": "020-123456", + "alias": "jackzhang", + "status": 1, + "address": "广州市海珠区新港中路", + "english_name" : "jacky", + "open_userid": "xxxxxx", + "main_department": 1, + "extattr": { + "attrs": [ + { + "type": 0, + "name": "文本名称", + "text": { + "value": "文本" + } + }, + { + "type": 1, + "name": "网页名称", + "web": { + "url": "http://www.test.com", + "title": "标题" + } + } + ] + }, + "qr_code": "https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx", + "external_position": "产品经理", + "external_profile": { + "external_corp_name": "企业简称", + "wechat_channels": { + "nickname": "视频号名称", + "status": 1 + }, + "external_attr": [{ + "type": 0, + "name": "文本名称", + "text": { + "value": "文本" + } + }, + { + "type": 1, + "name": "网页名称", + "web": { + "url": "http://www.test.com", + "title": "标题" + } + }, + { + "type": 2, + "name": "测试app", + "miniprogram": { + "appid": "wx8bd80126147dFAKE", + "pagepath": "/index", + "title": "miniprogram" + } + } + ] + } + }] +} +``` + +**参数说明 :** + +
参数说明
errcode返回码
errmsg对返回码的文本描述内容
userlist成员列表
userid成员 UserID。对应管理端的账号
name成员名称;第三方不可获取,调用时返回 userid 以代替 name;代开发自建应用需要管理员授权才返回;对于非第三方创建的成员,第三方通讯录应用也不可获取;未返回名称的情况需要通过通讯录展示组件来展示名字
mobile手机号码,代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
department成员所属部门 id 列表,仅返回该应用有查看权限的部门 id。对授权了 “组织架构信息” 的第三方应用或授权了 “组织架构信息”-“部门及父部门 ID、部门负责人” 的代开发应用,返回成员所属的全部部门 id 列表
order部门内的排序值,默认为 0。数量必须和 department 一致,数值越大排序越前面。值范围是 [0, 2^32)
position职务信息;代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
gender性别。0 表示未定义,1 表示男性,2 表示女性。第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段。注:不可获取指返回值为 0
email邮箱,代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
biz_mail企业邮箱,代开发自建应用不返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
is_leader_in_dept表示在所在的部门内是否为部门负责人。0 - 否;1 - 是。是一个列表,数量必须与 department 一致。第三方通讯录应用或者授权了 “组织架构信息 - 应用可获取企业的部门组织架构信息 - 部门负责人” 权限的第三方应用和代开发应用可获取;对于非第三方创建的成员,第三方通讯录应用不可获取;上游企业不可获取下游企业成员该字段
direct_leader直属上级 UserID,返回在应用可见范围内的直属上级列表,最多有 1 个直属上级;第三方通讯录应用或者授权了 “组织架构信息 - 应用可获取可见范围内成员组织架构信息 - 直属上级” 权限的第三方应用和代开发应用可获取;对于非第三方创建的成员,第三方通讯录应用不可获取;上游企业不可获取下游企业成员该字段;代开发自建应用不可获取该字段
avatar头像 url。 第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
thumb_avatar头像缩略图 url。第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
telephone座机。代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
alias别名;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
extattr扩展属性,字段详见成员扩展属性。代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
status激活状态: 1 = 已激活,2 = 已禁用,4 = 未激活,5 = 退出企业。
已激活代表已激活企业微信或已关注微信插件(原企业号)。未激活代表既未激活企业微信又未关注微信插件(原企业号)。
qr_code员工个人二维码,扫描可添加为外部联系人 (注意返回的是一个 url,可在浏览器上打开该 url 以展示二维码);第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
external_profile成员对外属性,字段详情见对外属性;代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
external_position对外职务,如果设置了该值,则以此作为对外展示的职务,否则以 position 来展示。代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
address地址。代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
open_userid全局唯一。对于同一个服务商,不同应用获取到企业内同一个成员的 open_userid 是相同的,最多 64 个字节。仅第三方应用可获取
main_department主部门,仅当应用对主部门有查看权限时返回。
\ No newline at end of file diff --git a/sql/20250327.sql b/sql/20250327.sql new file mode 100644 index 0000000..3f1247f --- /dev/null +++ b/sql/20250327.sql @@ -0,0 +1,17 @@ +CREATE TABLE `sys_user_qy_user` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '关联ID', + `sys_user_id` bigint NOT NULL COMMENT '系统用户ID', + `qy_user_id` int NOT NULL COMMENT '企业微信用户ID', + `creator_id` bigint NULL DEFAULT '0' COMMENT '创建者ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater_id` bigint NULL DEFAULT '0' COMMENT '更新者ID', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `remark` varchar(512) DEFAULT NULL COMMENT '备注', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除标志(0存在 1删除)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_sys_user_qy_user` (`sys_user_id`,`qy_user_id`) COMMENT '防止重复绑定', + KEY `idx_sys_user_id` (`sys_user_id`), + KEY `idx_qy_user_id` (`qy_user_id`), + CONSTRAINT `fk_sys_user_qy_user_sys` FOREIGN KEY (`sys_user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE CASCADE, + CONSTRAINT `fk_sys_user_qy_user_qy` FOREIGN KEY (`qy_user_id`) REFERENCES `qy_user` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户与企业微信用户关联表'; \ No newline at end of file