From 957641393cddae50459942b1453c311ca1f504bc Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Fri, 8 May 2026 19:54:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=99=E6=94=BF=E9=92=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppUserAutoLoginController.java | 106 +-------- .../controller/NoticeController.java | 40 ++++ .../controller/SysBannerController.java | 35 ++- .../feign/RemoterThirdPartyAuthService.java | 9 +- .../pjcloud/thirdparty/dto/UrlTokenDTO.java | 2 +- .../pjcloud/thirdparty/dto/ZZDUserDTO.java | 5 + .../thirdparty/dto/ZzdPhoneConfirmDTO.java | 16 ++ .../thirdparty/dto/ZzdUserSnapshot.java | 47 ++++ .../bsd/pjcloud/thirdparty/ThirdPartyApp.java | 3 + .../thirdparty/config/OAuth2ClientConfig.java | 12 +- .../ThirdPartyZzdSmsBindProperties.java | 22 ++ .../config/ZzdOpenPlatformProperties.java | 2 +- .../controller/ThirdPartyOAth2Cotroller.java | 209 +++++++++--------- .../entity/ThirdPartyZzdPhoneBindEntity.java | 63 ++++++ .../mapper/ThirdPartyZzdPhoneBindMapper.java | 9 + .../service/ThirdPartyOAuth2Service.java | 56 +---- .../service/ThirdPartyZzdSmsBindService.java | 104 +++++++++ .../src/main/resources/application.yml | 15 +- .../bsd/pjcloud/admin/api/entity/SysUser.java | 1 + 19 files changed, 473 insertions(+), 283 deletions(-) create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdPhoneConfirmDTO.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdUserSnapshot.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/ThirdPartyZzdSmsBindProperties.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPartyZzdPhoneBindEntity.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/ThirdPartyZzdPhoneBindMapper.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyZzdSmsBindService.java diff --git a/pjcloud-app-server/pjcloud-app-server-biz/src/main/java/com/bsd/pjcloud/app/controller/AppUserAutoLoginController.java b/pjcloud-app-server/pjcloud-app-server-biz/src/main/java/com/bsd/pjcloud/app/controller/AppUserAutoLoginController.java index 589d0e7db..a3c22af2c 100644 --- a/pjcloud-app-server/pjcloud-app-server-biz/src/main/java/com/bsd/pjcloud/app/controller/AppUserAutoLoginController.java +++ b/pjcloud-app-server/pjcloud-app-server-biz/src/main/java/com/bsd/pjcloud/app/controller/AppUserAutoLoginController.java @@ -38,10 +38,9 @@ import com.bsd.pjcloud.common.log.annotation.SysLog; import com.bsd.pjcloud.common.security.annotation.Inner; import com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdPartyAuthService; import com.bsd.pjcloud.thirdparty.dto.SHJNSSODTO; +import com.bsd.pjcloud.thirdparty.dto.UrlTokenDTO; import com.bsd.pjcloud.thirdparty.dto.ZZDUserDTO; import com.bsd.pjcloud.thirdparty.entity.ThirdPartyUserEntity; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -90,111 +89,18 @@ public class AppUserAutoLoginController { private final RemoteOnlineCanteenSpuService remoteOnlineCanteenSpuService; private final RemoteDailyMenuService remoteDailyMenuService; - /* - * - * @param token 浙政钉前端获取的token - * @return + /** + * 路径参数历史命名为 token,实为浙政钉 H5 {@code authCode},转发 thirdParty 统一单点(含短信核验等逻辑)。 */ @GetMapping("/zzdsso/{token}") @Operation(summary = "浙政钉单点登录") @Inner(value = false) @GlobalTransactional @Transactional(rollbackFor = Exception.class) - //todo 引入seata分布式事务 public R zzdsso(@PathVariable(name = "token") String token) { - - //获取数字政务token通过浙政钉前端获取的token,调试阶段先写死 - String json = "{" + - "\"username\": \"李宗相\"," + - "\"organizationId\": 2," + - "\"userId\": 1340," + - "\"nickName\": \"李宗相\"," + - "\"mobile\": \"18576496089\"," + - "\"regionCode\": null," + - "\"ext\": {" + - "\"employeeCode\": \"GE_ae12bdf21598498eb48bf2cf2c45a3e1\"," + - "\"organizationCode\": \"GO_63e77077d5e041789bbbe1d0d20bcf41\"," + - "\"nickNameCn\": \"李宗相\"," + - "\"openid\": \"1b51f0e11fc73b8adddcbaf7982daab6\"," + - "\"realmId\": \"196729\"," + - "\"mobile\": null" + - "}" + - "}"; - - - ObjectMapper objectMapper = new ObjectMapper(); - ZZDUserDTO zzdUserDTO; - try { - zzdUserDTO = objectMapper.readValue(json, ZZDUserDTO.class); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - //暂时先注释,模拟一下就行 -// R zzdsso = remoterThirdPartyAuthService.zzdsso(token); -// ZZDUserDTO zzdUserDTO = (ZZDUserDTO) zzdsso.getData(); - ZZDUserDTO.Ext ext = zzdUserDTO.getExt(); - String employeeCode = ext.getEmployeeCode(); - String mobile = zzdUserDTO.getMobile(); - - //两种方式,手机号+员工钉钉号,查到相同就视为同一个人,然后登录 - //先系统表里面查看是否有,在第三方数据库表里面看下是否有 - - //1.根据手机号或者钉钉号先查询是否有这个人 - R> userByMobileOrEmployeeCode = remoteUserService.getUserByMobileOrEmployeeCode(employeeCode, mobile); - List sysUserList = userByMobileOrEmployeeCode.getData(); - - if(!CollectionUtils.isEmpty(sysUserList)){ - //登录成功生成token - UserTokenDTO userTokenDTO = new UserTokenDTO(); - userTokenDTO.setSysUser(sysUserList.get(0)); - remoteTokenService.generateToken(userTokenDTO); - } - //2.根据浙政钉的员工钉钉号查询是否有这个人 - R> thirdPartyUserEntityR=remoterThirdPartyAuthService.getUserByEmployeeCode(employeeCode); - List thirdPartyUserEntityList = thirdPartyUserEntityR.getData(); - - UserDTO userDTO = new UserDTO(); - //写死,新加入到部门表的全部归类到其他部门里面,不按照浙政钉的部门 - userDTO.setDeptId(1865990541445263361L); - userDTO.setUsername(zzdUserDTO.getUsername()); - userDTO.setName(zzdUserDTO.getUsername()); - userDTO.setNickname(zzdUserDTO.getUsername()); - //浙里办用户 - userDTO.setSystemType("1"); - //取上面的手机号 - userDTO.setPhone(mobile); - userDTO.setDingtalkEmployeeCode(employeeCode); - //随便写死给个密码 - userDTO.setPassword("pj"+mobile); -/* if(!CollectionUtils.isEmpty(thirdPartyUserEntityList)){ - //todo 这一步现在是没用的,这个最终要考虑是否通过浙政钉里面的人员来限制移动端的使用用户来决定 - //第三方数据库里面有 - //这里只在第三方库里查到,所以要对系统用户表进行新增当前用户,插入一条信息进去 - R booleanR = remoteUserService.registerUser(userDTO); - //通过唯一手机号查询到刚注册的用户信息,生成token - R> userByMobile=remoteUserService.getUserByMobile(mobile); - SysUser sysUser = userByMobile.getData().get(0); - UserTokenDTO userTokenDTO = new UserTokenDTO(); - userTokenDTO.setSysUser(sysUser); - //登录成功,根据新注册的用户信息返回token - remoteTokenService.generateToken(userTokenDTO); - } - else { - //两个表都没查到人员不能够使用移动端的系统 - }*/ - -// log.info("用户不存在,尝试创建用户"); - - R booleanR = remoteUserService.registerUser(userDTO); - //通过唯一手机号查询到刚注册的用户信息,生成token - - R> userByMobile=remoteUserService.getUserByMobile(mobile); - SysUser sysUser = userByMobile.getData().get(0); - int i=8/0; - UserTokenDTO userTokenDTO = new UserTokenDTO(); - userTokenDTO.setSysUser(sysUser); - //登录成功,根据新注册的用户信息返回token - return remoteTokenService.generateToken(userTokenDTO); + UrlTokenDTO body = new UrlTokenDTO(); + body.setAuthCode(token); + return remoterThirdPartyAuthService.zzdsso(body); } diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/NoticeController.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/NoticeController.java index 880e68e20..3d6811c82 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/NoticeController.java +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/NoticeController.java @@ -171,6 +171,46 @@ public class NoticeController { return R.ok(notice); } + /** + * IRS / 纯静态托管 H5:与 {@link #getAppNoticePage} 数据一致,凭 X-App-Id、X-App-Secret({@code pjcloud.external-app})访问,不依赖用户 OAuth2 Token。 + */ + @Operation(summary = "开放-公告分页", description = "凭应用凭证访问,供无法携带登录 Token 的 H5") + @GetMapping("/open/page") + @Inner(value = false) + public R getOpenNoticePage( + @RequestHeader(value = "X-App-Id", required = false) String appId, + @RequestHeader(value = "X-App-Secret", required = false) String appSecret, + @ParameterObject Page page, + @RequestParam(required = false) String title) { + if (!isValidAppCredentials(appId, appSecret)) { + return R.failed("无效的应用ID或密钥"); + } + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(NoticeEntity::getStatus, "0"); + wrapper.like(StrUtil.isNotBlank(title), NoticeEntity::getTitle, title); + wrapper.and(w -> w.isNull(NoticeEntity::getExpireTime) + .or() + .gt(NoticeEntity::getExpireTime, LocalDateTime.now())); + wrapper.orderByDesc(NoticeEntity::getIsTop, NoticeEntity::getCreateTime); + return R.ok(noticeService.page(page, wrapper)); + } + + /** + * IRS / 纯静态托管 H5:与 {@link #getAppNoticeDetail} 一致,凭应用凭证访问。 + */ + @Operation(summary = "开放-公告详情", description = "凭应用凭证访问") + @GetMapping("/open/{id}") + @Inner(value = false) + public R getOpenNoticeDetail( + @RequestHeader(value = "X-App-Id", required = false) String appId, + @RequestHeader(value = "X-App-Secret", required = false) String appSecret, + @PathVariable("id") Long id) { + if (!isValidAppCredentials(appId, appSecret)) { + return R.failed("无效的应用ID或密钥"); + } + return getAppNoticeDetail(id); + } + /** * App公告浏览量+1 * @param id 公告ID diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/SysBannerController.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/SysBannerController.java index 5f084ce2f..79762b736 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/SysBannerController.java +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/SysBannerController.java @@ -136,15 +136,43 @@ public class SysBannerController { return R.failed("无效的应用ID或密钥"); } + return R.ok(buildAppBannerVoList()); + } + + /** + * H5 / 浙政钉工作台(zzdhome)轮播:与 {@link #appList} 数据一致,无需 X-App-Id/Secret,便于前端直接拉取。 + * 若需对外收口鉴权,可在网关按路径单独配置。 + */ + @Operation(summary = "H5/App轮播图列表", description = "上架且在有效期内,字段同外部 app 接口;图片 URL 规则与 /app/list 一致") + @GetMapping("/app/slides") + @Inner(value = false) + public R> appSlides() { + return R.ok(buildAppBannerVoList()); + } + + /** + * IRS / 无法携带用户 Token 的 H5:与 {@link #appList} 相同鉴权与数据,路径便于与公告 {@code /notice/open/*} 一并加入网关放行。 + */ + @Operation(summary = "开放-轮播图列表", description = "凭 X-App-Id、X-App-Secret 访问,同 /app/list") + @GetMapping("/open/slides") + @Inner(value = false) + public R> openSlides( + @RequestHeader(value = "X-App-Id", required = false) String appId, + @RequestHeader(value = "X-App-Secret", required = false) String appSecret) { + if (!isValidAppCredentials(appId, appSecret)) { + return R.failed("无效的应用ID或密钥"); + } + return R.ok(buildAppBannerVoList()); + } + + private List buildAppBannerVoList() { String imageBaseUrl = bannerAppConfig.getImageBaseUrl(); boolean needPrefix = StrUtil.isNotBlank(imageBaseUrl); String baseUrl = needPrefix ? imageBaseUrl.trim() : ""; - List list = sysBannerService.listAppBanners(); - List voList = list.stream() + return list.stream() .map(e -> { SysBannerAppVo vo = BeanUtils.toBean(e, SysBannerAppVo.class); - // imageUrl 前拼接统一基础路径 if (needPrefix && StrUtil.isNotBlank(e.getImageUrl())) { String path = e.getImageUrl().trim(); if (!path.startsWith("http://") && !path.startsWith("https://")) { @@ -159,7 +187,6 @@ public class SysBannerController { return vo; }) .collect(Collectors.toList()); - return R.ok(voList); } /** diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/api/feign/RemoterThirdPartyAuthService.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/api/feign/RemoterThirdPartyAuthService.java index c4f00fb7f..61187b414 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/api/feign/RemoterThirdPartyAuthService.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/api/feign/RemoterThirdPartyAuthService.java @@ -21,6 +21,7 @@ package com.bsd.pjcloud.thirdparty.api.feign; import com.bsd.pjcloud.common.core.constant.ServiceNameConstants; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.feign.annotation.NoToken; +import com.bsd.pjcloud.thirdparty.dto.UrlTokenDTO; import com.bsd.pjcloud.thirdparty.entity.ThirdPartyUserEntity; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; @@ -35,13 +36,11 @@ import java.util.List; public interface RemoterThirdPartyAuthService { /** - * zzd单点 - * @param token - * @return + * 浙政钉单点(与 thirdPartyOAth2/zzdsso 一致,body.authCode 为 H5 免登码) */ @NoToken - @GetMapping("/thirdPartyOAth2/zzd/sso") - R zzdsso(@RequestParam("token") String token); + @PostMapping("/thirdPartyOAth2/zzdsso") + R zzdsso(@RequestBody UrlTokenDTO body); @NoToken diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/UrlTokenDTO.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/UrlTokenDTO.java index eae2c2cc4..88a81ef4a 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/UrlTokenDTO.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/UrlTokenDTO.java @@ -6,7 +6,7 @@ import lombok.Data; @Data public class UrlTokenDTO { /** - * 旧数字政务/第三方网关下发的 uabaseToken(逐步淘汰) + * 已废弃:浙政钉单点仅使用 authCode,不再传网关 uabaseToken */ private String token; /** diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZZDUserDTO.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZZDUserDTO.java index 3d849dc2c..489b08250 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZZDUserDTO.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZZDUserDTO.java @@ -2,6 +2,11 @@ package com.bsd.pjcloud.thirdparty.dto; import lombok.Data; +/** + * 第三方 SSO 在本服务内的统一用户视图(非开放平台原始报文体)。 + * 浙政钉:由 {@code dingtalk_app_user} 等适配层填入;诗画:由网关用户信息接口填入。 + * 后续业务(发 token、短信绑定快照等)只依赖本结构,与走网关还是走开放平台无关。 + */ @Data public class ZZDUserDTO { private String username; diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdPhoneConfirmDTO.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdPhoneConfirmDTO.java new file mode 100644 index 000000000..210db4b20 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdPhoneConfirmDTO.java @@ -0,0 +1,16 @@ +package com.bsd.pjcloud.thirdparty.dto; + +import lombok.Data; + +@Data +public class ZzdPhoneConfirmDTO { + + /** + * /zzdsso 返回的 bindSessionId + */ + private String bindSessionId; + + private String mobilePhone; + + private String smsCode; +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdUserSnapshot.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdUserSnapshot.java new file mode 100644 index 000000000..42d3df9a9 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdUserSnapshot.java @@ -0,0 +1,47 @@ +package com.bsd.pjcloud.thirdparty.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 浙政钉会话快照(可 JSON 序列化入 Redis),避免 ZZDUserDTO 内部类反序列化问题。 + */ +@Data +public class ZzdUserSnapshot implements Serializable { + + private static final long serialVersionUID = 1L; + + private String username; + + private String nickName; + + /** 浙政钉接口若返回的顶层手机号 */ + private String zzdMobile; + + private String employeeCode; + + private String openid; + + private String organizationCode; + + private String realmId; + + public static ZzdUserSnapshot from(ZZDUserDTO dto) { + ZzdUserSnapshot s = new ZzdUserSnapshot(); + if (dto == null) { + return s; + } + s.setUsername(dto.getUsername()); + s.setNickName(dto.getNickName()); + s.setZzdMobile(dto.getMobile()); + ZZDUserDTO.Ext ext = dto.getExt(); + if (ext != null) { + s.setEmployeeCode(ext.getEmployeeCode()); + s.setOpenid(ext.getOpenid()); + s.setOrganizationCode(ext.getOrganizationCode()); + s.setRealmId(ext.getRealmId()); + } + return s; + } +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/ThirdPartyApp.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/ThirdPartyApp.java index 4c92e0396..88f0a2699 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/ThirdPartyApp.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/ThirdPartyApp.java @@ -3,8 +3,10 @@ package com.bsd.pjcloud.thirdparty; import com.bsd.pjcloud.common.feign.annotation.EnablePjcloudFeignClients; import com.bsd.pjcloud.common.security.annotation.EnablePjcloudResourceServer; import com.bsd.pjcloud.common.swagger.annotation.EnableOpenApi; +import com.bsd.pjcloud.thirdparty.config.ThirdPartyZzdSmsBindProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** @@ -16,6 +18,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnablePjcloudFeignClients @EnableDiscoveryClient @EnablePjcloudResourceServer +@EnableConfigurationProperties(ThirdPartyZzdSmsBindProperties.class) @SpringBootApplication public class ThirdPartyApp { public static void main(String[] args) { diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/OAuth2ClientConfig.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/OAuth2ClientConfig.java index 51dbd6290..c45e61a9a 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/OAuth2ClientConfig.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/OAuth2ClientConfig.java @@ -23,25 +23,15 @@ public class OAuth2ClientConfig { private String userDetailUrl; - /** - * 浙政钉获取token的接口 - */ - private String zzdTokenUrl; - /** * 诗画江南的token接口 */ private String shjnTokenUrl; /** - * 浙政钉跟诗画的获取用户信息的url + * 诗画江南 uabaseToken 换用户信息(网关 GET ?token=) */ private String zzdShUserInfoUrl; - /** - * 浙政钉官方 H5 authCode 换取用户信息(GET ?authCode=),响应体需与 {@code ApiResponse} 一致 - */ - private String zzdAuthCodeUserInfoUrl; - } diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/ThirdPartyZzdSmsBindProperties.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/ThirdPartyZzdSmsBindProperties.java new file mode 100644 index 000000000..9e2df8ccf --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/ThirdPartyZzdSmsBindProperties.java @@ -0,0 +1,22 @@ +package com.bsd.pjcloud.thirdparty.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 浙政钉无手机号时:首次短信核验 + 与本系统用户手机号比对后再发 token。 + */ +@Data +@ConfigurationProperties(prefix = "pjcloud.thirdparty.zzd-sms-bind") +public class ThirdPartyZzdSmsBindProperties { + + /** + * 是否开启(关闭时 /zzdsso 在拿到浙政钉用户后直接按手机号+员工编码查用户并发 token) + */ + private boolean enabled = false; + + /** + * 绑定会话在 Redis 中的有效时间(分钟) + */ + private int bindSessionTtlMinutes = 15; +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/ZzdOpenPlatformProperties.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/ZzdOpenPlatformProperties.java index 530eb1914..cab441374 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/ZzdOpenPlatformProperties.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/ZzdOpenPlatformProperties.java @@ -15,7 +15,7 @@ import org.springframework.util.StringUtils; public class ZzdOpenPlatformProperties { /** - * 为 false 时不走开放平台直连,仍使用 oauth2.client.zzd-auth-code-user-info-url 等旧网关配置 + * 为 false 时禁用开放平台 SDK;浙政钉 authCode 单点不可用(需为 true 且配置 appKey/appSecret) */ private boolean enabled = true; diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/ThirdPartyOAth2Cotroller.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/ThirdPartyOAth2Cotroller.java index d6cc79d4b..aca574839 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/ThirdPartyOAth2Cotroller.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/ThirdPartyOAth2Cotroller.java @@ -21,13 +21,17 @@ import com.bsd.pjcloud.thirdparty.exception.ThirdPartyException; import com.bsd.pjcloud.thirdparty.vo.ZzdJsapiConfigVO; import com.bsd.pjcloud.thirdparty.service.ThirdPartyOAuth2Service; import com.bsd.pjcloud.thirdparty.service.ThirdPartyUserService; +import com.bsd.pjcloud.thirdparty.service.ThirdPartyZzdSmsBindService; +import com.fasterxml.jackson.core.JsonProcessingException; import com.bsd.pjcloud.thirdparty.utils.SmsCodeUtil; +import com.alibaba.fastjson.JSON; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.seata.spring.annotation.GlobalTransactional; import org.springframework.beans.factory.annotation.Autowired; @@ -43,6 +47,7 @@ import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.TimeUnit; +@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/thirdPartyOAth2") @@ -70,12 +75,7 @@ public class ThirdPartyOAth2Cotroller { private final RemoteUserTrustedDeviceService remoteUserTrustedDeviceService; -// @Operation(summary = "浙政钉token" ) -// @GetMapping("/zzd/sso" ) -// public R getThirdPartyzzdsso(@RequestParam("token")String token) { -// return R.ok(thirdPartyOAuth2Service.getZZDUserInfo(token)); -// -// } + private final ThirdPartyZzdSmsBindService thirdPartyZzdSmsBindService; @Operation(summary = "诗画token") @GetMapping("/sh/sso") @@ -84,11 +84,6 @@ public class ThirdPartyOAth2Cotroller { } - /* - * - * @param token 浙政钉前端获取的token - * @return - */ @GetMapping("/zzdJsapiConfig") @Operation(summary = "政务钉钉H5 JSAPI鉴权(dd.authConfig 所需 ticket)") @Inner(value = false) @@ -107,111 +102,113 @@ public class ThirdPartyOAth2Cotroller { @GlobalTransactional @Transactional(rollbackFor = Exception.class) public R zzdsso(@RequestBody UrlTokenDTO urlTokenDTO) { - String authCode = urlTokenDTO.getAuthCode(); - String token = urlTokenDTO.getToken(); - if (StringUtils.isBlank(authCode) && StringUtils.isBlank(token)) { - return R.failed("token与authCode不能均为空"); + String authCode = urlTokenDTO != null ? urlTokenDTO.getAuthCode() : null; + if (StringUtils.isBlank(authCode)) { + return R.failed("请传入浙政钉 authCode(H5 免登临时码)"); } - //获取数字政务token通过浙政钉前端获取的token,调试阶段先写死 -// String json = "{" + -// "\"username\": \"李宗相\"," + -// "\"organizationId\": 2," + -// "\"userId\": 1340," + -// "\"nickName\": \"李宗相\"," + -// "\"mobile\": \"18576496089\"," + -// "\"regionCode\": null," + -// "\"ext\": {" + -// "\"employeeCode\": \"GE_ae12bdf21598498eb48bf2cf2c45a3e1\"," + -// "\"organizationCode\": \"GO_63e77077d5e041789bbbe1d0d20bcf41\"," + -// "\"nickNameCn\": \"李宗相\"," + -// "\"openid\": \"1b51f0e11fc73b8adddcbaf7982daab6\"," + -// "\"realmId\": \"196729\"," + -// "\"mobile\": null" + -// "}" + -// "}"; -// -// -// ObjectMapper objectMapper = new ObjectMapper(); -// ZZDUserDTO zzdUserDTO; -// try { -// zzdUserDTO = objectMapper.readValue(json, ZZDUserDTO.class); -// } catch (JsonProcessingException e) { -// throw new RuntimeException(e); -// } - //暂时先注释,模拟一下就行 - ZZDUserDTO zzdUserDTO = StringUtils.isNotBlank(authCode) - ? thirdPartyOAuth2Service.zzdssoByAuthCode(authCode) - : thirdPartyOAuth2Service.zzdsso(token); + ZZDUserDTO zzdUserDTO = thirdPartyOAuth2Service.zzdssoByAuthCode(authCode.trim()); + log.warn("[zzdsso][ZZDUserDTO_JSON] 浙政钉开放平台映射后的对象,可整段复制做本地写死调试(含姓名手机号等敏感信息,调试完建议关日志或删此行) body={}", JSON.toJSONString(zzdUserDTO)); ZZDUserDTO.Ext ext = zzdUserDTO.getExt(); - String employeeCode = ext.getEmployeeCode(); + String employeeCode = ext != null ? ext.getEmployeeCode() : null; String mobile = zzdUserDTO.getMobile(); - //两种方式,手机号+员工钉钉号,查到相同就视为同一个人,然后登录 - //先系统表里面查看是否有,在第三方数据库表里面看下是否有 - - R r; - //1.根据手机号或者钉钉号先查询是否有这个人 - R> userByMobileOrEmployeeCode = remoteUserService.getUserByMobileOrEmployeeCode(employeeCode, mobile); - List sysUserList = userByMobileOrEmployeeCode.getData(); - if(!CollectionUtils.isEmpty(sysUserList)){ - //登录成功生成token - UserTokenDTO userTokenDTO = new UserTokenDTO(); - userTokenDTO.setSysUser(sysUserList.get(0)); - r = remoteTokenService.generateToken(userTokenDTO); - }else { - //2.根据浙政钉的员工钉钉号查询是否有这个人 -// List thirdPartyUserEntityList = thirdPartyUserService.getUserByEmployeeCode(employeeCode); -// UserDTO userDTO = new UserDTO(); -// //写死,新加入到部门表的全部归类到其他部门里面,不按照浙政钉的部门 -// userDTO.setDeptId(1865990541445263361L); -// userDTO.setUsername(zzdUserDTO.getUsername()); -// userDTO.setName(zzdUserDTO.getUsername()); -// userDTO.setNickname(zzdUserDTO.getUsername()); -// //浙里办用户 -// userDTO.setSystemType("1"); -// //取上面的手机号 -// userDTO.setPhone(mobile); -// userDTO.setDingtalkEmployeeCode(employeeCode); -// //随便写死给个密码 -// userDTO.setPassword("pj"+mobile); -// if(!CollectionUtils.isEmpty(thirdPartyUserEntityList)){ -// //todo 这一步现在是没用的,这个最终要考虑是否通过浙政钉里面的人员来限制移动端的使用用户来决定 -// //第三方数据库里面有 -// //这里只在第三方库里查到,所以要对系统用户表进行新增当前用户,插入一条信息进去 -// R booleanR = remoteUserService.registerUser(userDTO); -// //通过唯一手机号查询到刚注册的用户信息,生成token -// R> userByMobile=remoteUserService.getUserByMobile(mobile); -// SysUser sysUser = userByMobile.getData().get(0); -// UserTokenDTO userTokenDTO = new UserTokenDTO(); -// userTokenDTO.setSysUser(sysUser); -// //登录成功,根据新注册的用户信息返回token -// r=remoteTokenService.generateToken(userTokenDTO); -// }else { -// return R.failed("没有找到当前用户信息,无法访问!"); -// } - return R.failed("系统中没有当前用户信息,请联系管理员进行申请!"); + if (thirdPartyZzdSmsBindService.isEnabled()) { + if (StringUtils.isBlank(employeeCode)) { + return R.failed("浙政钉未返回员工编码,无法登录"); + } + var boundOpt = thirdPartyZzdSmsBindService.findByEmployeeCode(employeeCode); + if (boundOpt.isPresent()) { + return buildZzdSsoTokenResult(employeeCode, boundOpt.get().getVerifiedPhone()); + } + try { + ZzdUserSnapshot snap = ThirdPartyZzdSmsBindService.snapshotOf(zzdUserDTO); + if (StringUtils.isBlank(snap.getEmployeeCode())) { + return R.failed("浙政钉未返回员工编码,无法登录"); + } + String bindSessionId = thirdPartyZzdSmsBindService.createBindSession(snap); + HashMap need = new HashMap<>(); + need.put("needSmsVerify", Boolean.TRUE); + need.put("bindSessionId", bindSessionId); + need.put("displayName", StringUtils.defaultIfBlank(zzdUserDTO.getNickName(), zzdUserDTO.getUsername())); + return R.ok(need); + } + catch (JsonProcessingException e) { + return R.failed("创建绑定会话失败"); + } } - //两个表都没查到人员不能够使用移动端的系统 + return buildZzdSsoTokenResult(employeeCode, mobile); + } -// log.info("用户不存在,尝试创建用户"); + /** + * 浙政钉短信核验:校验验证码、写入绑定表、与本系统用户手机号+员工编码比对后返回 token。 + */ + @PostMapping("/zzdsso/confirmPhone") + @Operation(summary = "浙政钉单点-短信核验手机号") + @SysLog("浙政钉单点短信核验") + @Inner(value = false) + @Transactional(rollbackFor = Exception.class) + public R confirmZzdPhoneBind(@RequestBody ZzdPhoneConfirmDTO dto) { + if (dto == null || StringUtils.isAnyBlank(dto.getBindSessionId(), dto.getMobilePhone(), dto.getSmsCode())) { + return R.failed("参数不完整"); + } + try { + ZzdUserSnapshot snap = thirdPartyZzdSmsBindService.getBindSession(dto.getBindSessionId()); + if (snap == null) { + return R.failed("绑定会话已过期,请重新登录"); + } + if (StringUtils.isBlank(snap.getEmployeeCode())) { + return R.failed("会话数据异常"); + } + String redisKey = CacheConstants.SMS_CODE_KEY + dto.getMobilePhone(); + String storedCode = (String) redisTemplate.opsForValue().get(redisKey); + if (!dto.getSmsCode().equals(storedCode)) { + return R.failed("验证码错误"); + } + redisTemplate.delete(redisKey); -// R booleanR = remoteUserService.registerUser(userDTO); -// //通过唯一手机号查询到刚注册的用户信息,生成token -// -// R> userByMobile=remoteUserService.getUserByMobile(mobile); -// SysUser sysUser = userByMobile.getData().get(0); -//// int i=8/0; -// UserTokenDTO userTokenDTO = new UserTokenDTO(); -// userTokenDTO.setSysUser(sysUser); -// //登录成功,根据新注册的用户信息返回token -// R r = remoteTokenService.generateToken(userTokenDTO); + R> byPhone = remoteUserService.getUserByMobile(dto.getMobilePhone()); + List users = byPhone.getData(); + if (CollectionUtils.isEmpty(users)) { + return R.failed("本系统不存在该手机号用户"); + } + SysUser sysUser = users.get(0); + if (StringUtils.isNotBlank(sysUser.getDingtalkEmployeeCode()) + && !sysUser.getDingtalkEmployeeCode().equals(snap.getEmployeeCode())) { + return R.failed("浙政钉账号与本系统用户不一致"); + } + + thirdPartyZzdSmsBindService.recordVerifiedBind(snap, dto.getMobilePhone()); + thirdPartyZzdSmsBindService.removeBindSession(dto.getBindSessionId()); + + UserTokenDTO userTokenDTO = new UserTokenDTO(); + userTokenDTO.setSysUser(sysUser); + R r = remoteTokenService.generateToken(userTokenDTO); + HashMap out = new HashMap<>(); + out.put("refreshToken", r.getData().get("refreshToken")); + out.put("accessToken", r.getData().get("accessToken")); + return R.ok(out); + } + catch (JsonProcessingException e) { + return R.failed("保存绑定信息失败"); + } + } + + private R buildZzdSsoTokenResult(String employeeCode, String mobileForLookup) { + R> userByMobileOrEmployeeCode = remoteUserService.getUserByMobileOrEmployeeCode( + StringUtils.defaultString(employeeCode), + StringUtils.defaultString(mobileForLookup)); + List sysUserList = userByMobileOrEmployeeCode.getData(); + if (CollectionUtils.isEmpty(sysUserList)) { + return R.failed("系统中没有当前用户信息,请联系管理员进行申请!"); + } + UserTokenDTO userTokenDTO = new UserTokenDTO(); + userTokenDTO.setSysUser(sysUserList.get(0)); + R r = remoteTokenService.generateToken(userTokenDTO); HashMap objectObjectHashMap = new HashMap<>(); - Object accessToken = r.getData().get("accessToken"); - Object refreshToken = r.getData().get("refreshToken"); - objectObjectHashMap.put("refreshToken",refreshToken); - objectObjectHashMap.put("accessToken",accessToken); + objectObjectHashMap.put("refreshToken", r.getData().get("refreshToken")); + objectObjectHashMap.put("accessToken", r.getData().get("accessToken")); return R.ok(objectObjectHashMap); } diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPartyZzdPhoneBindEntity.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPartyZzdPhoneBindEntity.java new file mode 100644 index 000000000..4bd75fe6f --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPartyZzdPhoneBindEntity.java @@ -0,0 +1,63 @@ +package com.bsd.pjcloud.thirdparty.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +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 com.baomidou.mybatisplus.extension.activerecord.Model; +import com.bsd.pjcloud.common.core.util.TenantTable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 浙政钉用户短信核验手机号绑定 + */ +@Data +@TenantTable +@TableName("third_party_zzd_phone_bind") +@EqualsAndHashCode(callSuper = true) +@Schema(description = "浙政钉短信核验手机号绑定") +public class ThirdPartyZzdPhoneBindEntity extends Model { + + @TableId(type = IdType.ASSIGN_ID) + @Schema(description = "主键") + private Long id; + + @Schema(description = "租户ID") + private Long tenantId; + + @Schema(description = "浙政钉员工编码") + private String employeeCode; + + @Schema(description = "openid") + private String openId; + + @Schema(description = "核验手机号") + private String verifiedPhone; + + @Schema(description = "浙政钉信息快照JSON") + private String zzdProfileJson; + + @Schema(description = "删除标记") + private String delFlag; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建人") + private String createBy; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "更新人") + private String updateBy; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "更新时间") + private LocalDateTime updateTime; +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/ThirdPartyZzdPhoneBindMapper.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/ThirdPartyZzdPhoneBindMapper.java new file mode 100644 index 000000000..8665ac532 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/ThirdPartyZzdPhoneBindMapper.java @@ -0,0 +1,9 @@ +package com.bsd.pjcloud.thirdparty.mapper; + +import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; +import com.bsd.pjcloud.thirdparty.entity.ThirdPartyZzdPhoneBindEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ThirdPartyZzdPhoneBindMapper extends PjcloudBaseMapper { +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyOAuth2Service.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyOAuth2Service.java index 853a4992f..8b8849c69 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyOAuth2Service.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyOAuth2Service.java @@ -42,7 +42,6 @@ import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.RestTemplate; -import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -388,27 +387,13 @@ public class ThirdPartyOAuth2Service { /** - * 获取浙政钉的token - * @param token - * @return + * 浙政钉 H5:仅通过开放平台 authCode + dingtalk_app_user 换用户信息(已移除旧网关 uabaseToken / 网关 authCode 接口)。 */ - public ZZDUserDTO zzdsso(String token) { -// String zzdToken = getZZDToken(token); - - //todo 根据是诗画还是浙政钉分别去获取不同的token - - //使用token获取当前登录人的信息(浙政钉和诗画的在一起) -// ZZDUserDTO zzdUser = getZZDUserInfo(zzdToken); - ZZDUserDTO zzdUser = getZZDUserInfo(token, UserTypeEnum.ZZD.getType(), false); - - return zzdUser; - } - public ZZDUserDTO zzdssoByAuthCode(String authCode) { if (zzdOpenPlatform.isEnabled() && zzdOpenPlatform.isAppCredentialConfigured()) { return getZzdUserByOpenPlatformAuthCode(authCode); } - return getZZDUserInfo(authCode, UserTypeEnum.ZZD.getType(), true); + throw new ThirdPartyException("浙政钉单点需配置 zzd.open.enabled 与 app-key/app-secret(开放平台企业内应用)", "ZZD_OPEN_CONFIG"); } /** @@ -577,42 +562,15 @@ public class ThirdPartyOAuth2Service { } - public String getZZDToken(String token) { - String url = baseUrl+config.getZzdTokenUrl()+token; - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity requestEntity = new HttpEntity<>(null, headers); - ResponseEntity response = restTemplate.exchange( - url, HttpMethod.POST, requestEntity, ZZDResponse.class - ); - String zzdToken = response.getBody().getData(); - return zzdToken; - } - /** - * 浙证钉和诗画工用 - * @param zzdToken - * @return + * 诗画江南 token 换用户信息(与浙政钉共用网关 {@code oauth2.client.zzd-sh-user-info-url})。 */ - public ZZDUserDTO getZZDUserInfo(String zzdToken,String type) { - return getZZDUserInfo(zzdToken, type, false); - } - - /** - * @param zzdAuthCodeFlow true 时走 oauth2.client.zzd-auth-code-user-info-url,query 为 authCode - */ - public ZZDUserDTO getZZDUserInfo(String credential, String type, boolean zzdAuthCodeFlow) { + public ZZDUserDTO getZZDUserInfo(String credential, String type) { // 构建请求URL try { - String requestPath = zzdAuthCodeFlow ? config.getZzdAuthCodeUserInfoUrl() : config.getZzdShUserInfoUrl(); - if (zzdAuthCodeFlow && !StringUtils.hasText(requestPath)) { - throw new ThirdPartyException("未配置浙政钉 authCode 换取用户:请设置 zzd.open.app-key/app-secret(开放平台)或 oauth2.client.zzd-auth-code-user-info-url(网关)", "ZZD_AUTH_CODE_CONFIG"); - } - String param = zzdAuthCodeFlow ? "authCode" : "token"; - String url = zzdAuthCodeFlow - ? baseUrl + requestPath + "?" + param + "=" + URLEncoder.encode(credential, StandardCharsets.UTF_8) - : baseUrl + requestPath + "?" + param + "=" + credential; + String requestPath = config.getZzdShUserInfoUrl(); + String url = baseUrl + requestPath + "?token=" + credential; // 获取 Bearer Token String accessToken = getAccessToken().getAccess_token(); @@ -828,7 +786,7 @@ public class ThirdPartyOAuth2Service { if (StringUtils.isEmpty(shjnToken)){ throw new RuntimeException("获取诗画江南token失败"); } - ZZDUserDTO zzdUserDTO = getZZDUserInfo(shjnToken,UserTypeEnum.SH.getType()); + ZZDUserDTO zzdUserDTO = getZZDUserInfo(shjnToken, UserTypeEnum.SH.getType()); return zzdUserDTO; } } \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyZzdSmsBindService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyZzdSmsBindService.java new file mode 100644 index 000000000..016bb158d --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyZzdSmsBindService.java @@ -0,0 +1,104 @@ +package com.bsd.pjcloud.thirdparty.service; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.bsd.pjcloud.thirdparty.config.ThirdPartyZzdSmsBindProperties; +import com.bsd.pjcloud.thirdparty.dto.ZZDUserDTO; +import com.bsd.pjcloud.thirdparty.dto.ZzdUserSnapshot; +import com.bsd.pjcloud.thirdparty.entity.ThirdPartyZzdPhoneBindEntity; +import com.bsd.pjcloud.thirdparty.mapper.ThirdPartyZzdPhoneBindMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Service +@RequiredArgsConstructor +public class ThirdPartyZzdSmsBindService { + + public static final String REDIS_BIND_SESSION_PREFIX = "thirdparty:zzd:bind:session:"; + + private final ThirdPartyZzdSmsBindProperties properties; + + private final StringRedisTemplate stringRedisTemplate; + + private final ThirdPartyZzdPhoneBindMapper thirdPartyZzdPhoneBindMapper; + + private final ObjectMapper objectMapper; + + public boolean isEnabled() { + return properties.isEnabled(); + } + + public Optional findByEmployeeCode(String employeeCode) { + if (StringUtils.isBlank(employeeCode)) { + return Optional.empty(); + } + ThirdPartyZzdPhoneBindEntity one = thirdPartyZzdPhoneBindMapper.selectOne(Wrappers.lambdaQuery() + .eq(ThirdPartyZzdPhoneBindEntity::getEmployeeCode, employeeCode) + .eq(ThirdPartyZzdPhoneBindEntity::getDelFlag, "0") + .last("limit 1")); + return Optional.ofNullable(one); + } + + public String createBindSession(ZzdUserSnapshot snapshot) throws JsonProcessingException { + String id = UUID.randomUUID().toString().replace("-", ""); + String key = REDIS_BIND_SESSION_PREFIX + id; + stringRedisTemplate.opsForValue() + .set(key, objectMapper.writeValueAsString(snapshot), properties.getBindSessionTtlMinutes(), TimeUnit.MINUTES); + return id; + } + + public ZzdUserSnapshot getBindSession(String bindSessionId) throws JsonProcessingException { + if (StringUtils.isBlank(bindSessionId)) { + return null; + } + String json = stringRedisTemplate.opsForValue().get(REDIS_BIND_SESSION_PREFIX + bindSessionId); + if (StringUtils.isBlank(json)) { + return null; + } + return objectMapper.readValue(json, ZzdUserSnapshot.class); + } + + public void removeBindSession(String bindSessionId) { + if (StringUtils.isBlank(bindSessionId)) { + return; + } + stringRedisTemplate.delete(REDIS_BIND_SESSION_PREFIX + bindSessionId); + } + + public void recordVerifiedBind(ZzdUserSnapshot snapshot, String verifiedPhone) throws JsonProcessingException { + upsertBindRecord(snapshot, verifiedPhone, objectMapper.writeValueAsString(snapshot)); + } + + public void upsertBindRecord(ZzdUserSnapshot snapshot, String verifiedPhone, String profileJson) { + ThirdPartyZzdPhoneBindEntity exist = thirdPartyZzdPhoneBindMapper.selectOne(Wrappers.lambdaQuery() + .eq(ThirdPartyZzdPhoneBindEntity::getEmployeeCode, snapshot.getEmployeeCode()) + .eq(ThirdPartyZzdPhoneBindEntity::getDelFlag, "0") + .last("limit 1")); + if (exist == null) { + ThirdPartyZzdPhoneBindEntity e = new ThirdPartyZzdPhoneBindEntity(); + e.setEmployeeCode(snapshot.getEmployeeCode()); + e.setOpenId(snapshot.getOpenid()); + e.setVerifiedPhone(verifiedPhone); + e.setZzdProfileJson(profileJson); + e.setDelFlag("0"); + thirdPartyZzdPhoneBindMapper.insert(e); + } + else { + exist.setVerifiedPhone(verifiedPhone); + exist.setOpenId(snapshot.getOpenid()); + exist.setZzdProfileJson(profileJson); + thirdPartyZzdPhoneBindMapper.updateById(exist); + } + } + + public static ZzdUserSnapshot snapshotOf(ZZDUserDTO dto) { + return ZzdUserSnapshot.from(dto); + } +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml index 8dcec131c..943125ef0 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml @@ -34,16 +34,12 @@ oauth2: dept-url: /prod-api/oauth2/apis/orguser/v1/list_sub_depts dept-users-url: /prod-api/oauth2/apis/orguser/v1/list_dept_users user_detail-url: /prod-api/oauth2/apis/orguser/v1/user_detail - # 数字政务单点接口 - zzd_token_url: /prod-api/login/szzw/ # 诗画的获取token的接口 shjn_token_url: /prod-api/login/shpj - # 浙证钉和诗画公用的获取用户信息接口 + # 诗画江南 uabaseToken 换用户信息(浙政钉单点已改走下方 zzd.open 开放平台,不再使用本 URL) zzd_sh_user_info_url: /prod-api/oauth2/apis/sso/v1/fetch_user_by_uabase_token - # 浙政钉官方免登:前端 authCode 换用户信息(按实际网关/开放平台对接后填写,留空则 authCode 登录会报错) - # zzd_auth_code_user_info_url: /prod-api/... -# 浙政钉开放平台直连(配置 app-key/app-secret 后 authCode 走 gettoken + dingtalk_app_user,无需 zzd_auth_code_user_info_url) +# 浙政钉:H5 authCode → gettoken + dingtalk_app_user(需 app-key / app-secret) zzd: open: enabled: true @@ -203,3 +199,10 @@ hikvision: # base-url: https://api.mch.weixin.qq.com # ali-pay-config: # app-id: + +# 浙政钉无手机号时:首次短信核验 + 绑定表 third_party_zzd_phone_bind,与本系统用户手机号比对后再发 token(关闭则直接按手机号/员工编码查用户并发 token) +pjcloud: + thirdparty: + zzd-sms-bind: + enabled: false + bind-session-ttl-minutes: 15 diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/entity/SysUser.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/entity/SysUser.java index 60373b7ae..f51ef4d5a 100644 --- a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/entity/SysUser.java +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/entity/SysUser.java @@ -267,4 +267,5 @@ public class SysUser extends Model implements Serializable { @Schema(description = "性别") private Integer gender; + }