ソースを参照

短信回填

tags/24080901
WendyYang 8ヶ月前
コミット
5909f0d3e1
43個のファイルの変更1189行の追加737行の削除
  1. +0
    -5
      hz-pm-api/pom.xml
  2. +184
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/common/helper/MsgCallHelper.java
  3. +10
    -34
      hz-pm-api/src/main/java/com/hz/pm/api/expert/service/impl/ExpertInfoServiceImpl.java
  4. +6
    -6
      hz-pm-api/src/main/java/com/hz/pm/api/expert/utils/SensitiveModifySegmentUtil.java
  5. +1
    -1
      hz-pm-api/src/main/java/com/hz/pm/api/external/MhFileClient.java
  6. +105
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/external/MobileCallClient.java
  7. +24
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/external/model/dto/CallRetDTO.java
  8. +147
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/external/sms/MhSmsClient.java
  9. +0
    -86
      hz-pm-api/src/main/java/com/hz/pm/api/external/sms/SmsServiceClient.java
  10. +14
    -1
      hz-pm-api/src/main/java/com/hz/pm/api/external/sms/dto/SmsDTO.java
  11. +9
    -1
      hz-pm-api/src/main/java/com/hz/pm/api/external/sms/vo/SmsReplyDTO.java
  12. +9
    -1
      hz-pm-api/src/main/java/com/hz/pm/api/external/sms/vo/SmsSendDTO.java
  13. +3
    -3
      hz-pm-api/src/main/java/com/hz/pm/api/leave/manage/LeaveManage.java
  14. +8
    -10
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/controller/ExpertDashboardController.java
  15. +2
    -3
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/entity/domain/MeetingExpert.java
  16. +43
    -53
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/helper/MeetingNotifyHelper.java
  17. +0
    -69
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/helper/SmsOrCallClient.java
  18. +0
    -46
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/helper/YxtClientHelper.java
  19. +3
    -5
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/manage/ExpertInviteManage.java
  20. +4
    -4
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/manage/MeetingManage.java
  21. +82
    -303
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/task/ExpertCallResultRewriteTask.java
  22. +5
    -6
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/task/ExpertRandomInviteTask.java
  23. +171
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/meeting/task/MsgCallReplyRewriteTask.java
  24. +26
    -24
      hz-pm-api/src/main/java/com/hz/pm/api/projectdeclared/manage/ConstructionManage.java
  25. +0
    -2
      hz-pm-api/src/main/java/com/hz/pm/api/projectdeclared/model/req/XcfhxApplyReq.java
  26. +8
    -16
      hz-pm-api/src/main/java/com/hz/pm/api/sms/manage/VerificationCodeManage.java
  27. +0
    -28
      hz-pm-api/src/main/java/com/hz/pm/api/sms/task/YxtPollingTask.java
  28. +18
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/sys/controller/NdMsgCallRecordController.java
  29. +0
    -23
      hz-pm-api/src/main/java/com/hz/pm/api/sys/manage/EarlyWarningManage.java
  30. +16
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/sys/mapper/MsgCallRecordMapper.java
  31. +5
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/sys/mapper/MsgCallRecordMapper.xml
  32. +124
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/sys/model/entity/MsgCallRecord.java
  33. +32
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/sys/model/enumeration/BizTypeEnum.java
  34. +17
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/sys/model/enumeration/SubmitTypeEnum.java
  35. +16
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/sys/service/IMsgCallRecordService.java
  36. +20
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/sys/service/impl/MsgCallRecordServiceImpl.java
  37. +9
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/workbench/manage/WorkbenchManage.java
  38. +14
    -0
      hz-pm-api/src/main/java/com/hz/pm/api/workbench/model/WorkbenchProjectLibReq.java
  39. +3
    -4
      hz-pm-api/src/main/resources/application-dev.yml
  40. +4
    -0
      hz-pm-api/src/main/resources/application-prod.yml
  41. +42
    -0
      hz-pm-api/src/test/java/com/hz/pm/api/external/MobileCallClientTest.java
  42. +4
    -2
      hz-pm-api/src/test/resources/application-dev.yml
  43. +1
    -1
      hz-pm-gen/src/main/java/com/hz/pm/gen/config/CodeGen.java

+ 0
- 5
hz-pm-api/pom.xml ファイルの表示

@@ -163,11 +163,6 @@
<artifactId>nd-cache-starter</artifactId>
</dependency>
<dependency>
<groupId>com.ningdatech</groupId>
<artifactId>nd-yxt-starter</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel-core</artifactId>
</dependency>


+ 184
- 0
hz-pm-api/src/main/java/com/hz/pm/api/common/helper/MsgCallHelper.java ファイルの表示

@@ -0,0 +1,184 @@
package com.hz.pm.api.common.helper;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.StrUtil;
import com.hz.pm.api.external.MobileCallClient;
import com.hz.pm.api.external.MobileCallClient.ReplyCallDTO;
import com.hz.pm.api.external.MobileCallClient.SendCallDTO;
import com.hz.pm.api.external.model.dto.CallRetDTO;
import com.hz.pm.api.external.sms.MhSmsClient;
import com.hz.pm.api.external.sms.dto.SmsDTO;
import com.hz.pm.api.external.sms.vo.SmsReplyDTO;
import com.hz.pm.api.external.sms.vo.SmsSendDTO;
import com.hz.pm.api.sys.model.entity.MsgCallRecord;
import com.hz.pm.api.sys.model.enumeration.BizTypeEnum;
import com.hz.pm.api.sys.model.enumeration.SubmitTypeEnum;
import com.hz.pm.api.sys.service.IMsgCallRecordService;
import com.ningdatech.basic.exception.BizException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.View;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static com.hz.pm.api.sys.model.entity.MsgCallRecord.ReplyStatus;
import static com.hz.pm.api.sys.model.entity.MsgCallRecord.SendStatus;

/**
* <p>
* MsgCallHelper
* </p>
*
* @author WendyYang
* @since 15:49 2024/4/25
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class MsgCallHelper {

private final IMsgCallRecordService msgCallRecordService;
private final View error;

/**
* 发送短信
*
* @param phone 手机号
* @param content 短信内容
* @param bizType 业务类型
* @return submitKey
**/
public String sendMsg(String phone, String content, BizTypeEnum bizType) {
return sendMsg(Collections.singletonList(phone), content, bizType);
}

/**
* 批量发送短信
*
* @param phones 手机号
* @param content 短信内容
* @param bizType 业务类型
* @return submitKey
**/
public String sendMsg(Collection<String> phones, String content, BizTypeEnum bizType) {
log.info("sendMsgCall:{},{}", phones, content);
Assert.notEmpty(phones, "手机号不能为空");
phones.forEach(w -> {
if (!PhoneUtil.isMobile(w)) {
throw BizException.wrap("手机号格式错误:%s", w);
}
});
SmsDTO<SmsSendDTO> ret = MhSmsClient.getClient().smsSend(content, phones);
if (!ret.isOk()) {
log.error("短信发送失败:{}", ret);
throw BizException.wrap("短信发送失败");
}
SmsSendDTO data = ret.getData();
List<String> errorPhones = StrUtil.split(data.getErrorPhone(), ",");
List<MsgCallRecord> records = phones.stream().map(w -> {
MsgCallRecord mcr = new MsgCallRecord();
mcr.setContent(content);
mcr.setReceivePhone(w);
mcr.setSubmitKey(data.getResult());
mcr.setBizType(bizType);
mcr.setSubmitType(SubmitTypeEnum.SMS);
mcr.setSendStatus(errorPhones.contains(w) ? SendStatus.SUCCESS : SendStatus.FAILED);
buildReplyStatusBySend(mcr, bizType);
return mcr;
}).collect(Collectors.toList());
msgCallRecordService.saveBatch(records);
return data.getResult();
}

private void buildReplyStatusBySend(MsgCallRecord mcr, BizTypeEnum bizType) {
if (SendStatus.SUCCESS.equals(mcr.getSendStatus())) {
switch (bizType) {
case EXPERT_INVITE:
mcr.setNeedReply(Boolean.TRUE);
mcr.setReplyStatus(ReplyStatus.WAITING);
break;
default:
mcr.setNeedReply(Boolean.FALSE);
break;
}
}
}

/**
* 获取短信回复内容
*
* @param submitKey submitKey
* @return 回复内容
*/
public SmsReplyDTO smsReply(String submitKey) {
return MhSmsClient.getClient().smsReply(submitKey);
}

/**
* 批量提交电话任务
*
* @param phones 手机号
* @param content 内容
* @param bizType 业务类型
* @return submitKey submitKey
*/
public String sendCall(Collection<String> phones, String content, BizTypeEnum bizType) {
log.info("sendCall:{},{}", phones, content);
Assert.notEmpty(phones, "手机号不能为空");
phones.forEach(phone -> {
if (!PhoneUtil.isMobile(phone)) {
throw BizException.wrap("手机号格式错误:%s", phone);
}
});
CallRetDTO<SendCallDTO> ret = MobileCallClient.getClient().sendCall(phones, content);
if (!ret.isOk()) {
log.error("电话拨打失败:{}", ret);
throw BizException.wrap("电话拨打失败");
}
SendCallDTO data = ret.getData();
List<MsgCallRecord> records = phones.stream().map(w -> {
MsgCallRecord mcr = new MsgCallRecord();
mcr.setContent(content);
mcr.setReceivePhone(w);
mcr.setSubmitKey(data.getRequestId());
mcr.setBizType(bizType);
mcr.setSubmitType(SubmitTypeEnum.CALL);
mcr.setSendStatus(SendStatus.SUCCESS);
buildReplyStatusBySend(mcr, bizType);
return mcr;
}).collect(Collectors.toList());
msgCallRecordService.saveBatch(records);
return data.getRequestId();
}

/**
* 提交电话任务
*
* @param phone 手机号
* @param content 内容
* @param bizType 业务类型
* @return submitKey submitKey
*/
public String sendCall(String phone, String content, BizTypeEnum bizType) {
return sendCall(Collections.singletonList(phone), content, bizType);
}

public List<ReplyCallDTO> callReply(String submitKey) {
CallRetDTO<List<ReplyCallDTO>> retCallReplies = MobileCallClient.getClient().replyCall(submitKey);
if (retCallReplies.isOk()) {
List<ReplyCallDTO> replies = retCallReplies.getData();
if (replies == null || replies.isEmpty()) {
return Collections.emptyList();
}
return replies;
}
log.error("获取电话回执失败:{} {}", submitKey, retCallReplies);
throw BizException.wrap("获取电话回执失败");
}

}

+ 10
- 34
hz-pm-api/src/main/java/com/hz/pm/api/expert/service/impl/ExpertInfoServiceImpl.java ファイルの表示

@@ -2,8 +2,7 @@ package com.hz.pm.api.expert.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -24,8 +23,7 @@ import com.hz.pm.api.expert.model.bo.ExpertInfoSensitiveFieldCheckBO;
import com.hz.pm.api.expert.model.cmd.*;
import com.hz.pm.api.expert.model.dto.*;
import com.hz.pm.api.expert.service.*;
import com.hz.pm.api.expert.utils.SensitiveModifySegmentUtils;
import com.hz.pm.api.meeting.helper.YxtClientHelper;
import com.hz.pm.api.expert.utils.SensitiveModifySegmentUtil;
import com.hz.pm.api.meta.constant.ExpertDictTypeEnum;
import com.hz.pm.api.meta.model.ExpertRegionInfo;
import com.hz.pm.api.meta.model.entity.ExpertDictionary;
@@ -36,19 +34,16 @@ import com.hz.pm.api.sys.model.entity.Role;
import com.hz.pm.api.sys.model.entity.UserRole;
import com.hz.pm.api.sys.service.IRoleService;
import com.hz.pm.api.sys.service.IUserRoleService;
import com.hz.pm.api.user.model.enumeration.UserAvailableEnum;
import com.hz.pm.api.user.model.entity.UserInfo;
import com.hz.pm.api.user.model.enumeration.RoleEnum;
import com.hz.pm.api.user.security.model.UserInfoDetails;
import com.hz.pm.api.user.model.enumeration.UserAvailableEnum;
import com.hz.pm.api.user.service.IUserInfoService;
import com.hz.pm.api.user.util.LoginUserUtil;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.yxt.utils.JSONUtils;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@@ -57,10 +52,6 @@ import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.hz.pm.api.expert.model.constant.ExpertAuditMsgTemplate.EXPERT_AUDIT_FAIL;
import static com.hz.pm.api.expert.model.constant.ExpertAuditMsgTemplate.EXPERT_AUDIT_PASS;
import static com.ningdatech.yxt.model.cmd.SendSmsCmd.SendSmsContext;

/**
* @author liuxinxin
* @date 2022/7/22 下午4:39
@@ -79,16 +70,12 @@ public class ExpertInfoServiceImpl implements ExpertInfoService {
private final IUserRoleService userRoleService;
private final IExpertSensitiveInfoModifyDetailRecordService iExpertSensitiveInfoModifyDetailRecordService;
private final IUserInfoService userInfoService;
private final YxtClientHelper yxtClientHelper;
private final IExpertGovBusinessStripService expertGovBusinessStripService;

@Value("${login.url:}")
private String loginUrl;

/**
* 用户第一次申请修改,仅在专家用户状态为applying 状态时调用
*
* @param cmd
* @param cmd \
*/
@Override
@Transactional(rollbackFor = Exception.class)
@@ -365,8 +352,6 @@ public class ExpertInfoServiceImpl implements ExpertInfoService {
public void expertStorageDeal(ExpertStorageDealCmd cmd) {
Long expertUserId = cmd.getExpertUserId();
ExpertUserFullInfo expertInfo = iExpertUserFullInfoService.getByUserId(expertUserId);
UserInfoDetails userDetail = LoginUserUtil.loginUserDetail();
String content;
if (Boolean.TRUE.equals(cmd.getApplyResult())) {
// 修改专家状态为可用
// 账号启用
@@ -396,17 +381,8 @@ public class ExpertInfoServiceImpl implements ExpertInfoService {
userRoleService.save(saveUserRole);
}
}
content = String.format(EXPERT_AUDIT_PASS, expertInfo.getExpertName(), loginUrl, userDetail.getRealName(), userDetail.getMobile());
} else {
content = String.format(EXPERT_AUDIT_FAIL, expertInfo.getExpertName(), userDetail.getRealName(), userDetail.getMobile());
}
iExpertUserFullInfoService.saveOrUpdate(expertInfo);
if (PhoneUtil.isMobile(expertInfo.getPhoneNo())) {
SendSmsContext smsCtx = new SendSmsContext();
smsCtx.setContent(content);
smsCtx.setReceiveNumber(expertInfo.getPhoneNo());
yxtClientHelper.sendSms(smsCtx);
}
}


@@ -631,8 +607,8 @@ public class ExpertInfoServiceImpl implements ExpertInfoService {
* 创建层级变更审核
*
* @param expertRegionFieldSegmentList \
* @param originalExpertUserFullInfo \
* @param modifyApplyExtraInfo \
* @param originalExpertUserFullInfo \
* @param modifyApplyExtraInfo \
*/
private Long createExpertRegionModifyApply(List<SensitiveModifySegment> expertRegionFieldSegmentList
, ExpertUserFullInfo originalExpertUserFullInfo, ModifyApplyExtraInfoDTO modifyApplyExtraInfo) {
@@ -674,7 +650,7 @@ public class ExpertInfoServiceImpl implements ExpertInfoService {
Boolean applyResult = expertInfoModifyApplyDealCmd.getApplyResult();
Long expertUserId = expertInfoModifyApplyDealCmd.getExpertUserId();
Long applyId = expertInfoModifyApplyDealCmd.getApplyId();
if (applyResult) {
if (Boolean.TRUE.equals(applyResult)) {
ExpertUserFullInfo originalExpertUserFullInfo = iExpertUserFullInfoService.getByUserId(expertUserId);

LambdaQueryWrapper<ExpertSensitiveInfoModifyDetailRecord> recordQuery = Wrappers.lambdaQuery(ExpertSensitiveInfoModifyDetailRecord.class)
@@ -685,7 +661,7 @@ public class ExpertInfoServiceImpl implements ExpertInfoService {
if (StringUtils.isNotEmpty(modifyJson)) {
modifyJson = GzipUtil.unzip(modifyJson);
}
List<SensitiveModifySegment> sensitiveModifySegments = JSONUtils.parseArray(modifyJson, SensitiveModifySegment.class);
List<SensitiveModifySegment> sensitiveModifySegments = JSONUtil.toList(modifyJson, SensitiveModifySegment.class);
Map<ExpertUserInfoSensitiveFieldEnum, SensitiveModifySegment> collectMap = sensitiveModifySegments
.stream().collect(Collectors.toMap(SensitiveModifySegment::getFieldName, Function.identity()));

@@ -720,7 +696,7 @@ public class ExpertInfoServiceImpl implements ExpertInfoService {
case title_level: {
SensitiveModifySegment segment = collectMap.get(sensitiveFieldEnum);
if (Objects.nonNull(segment)) {
List<DictFieldInfoDTO> dictionaryFieldInfoList = SensitiveModifySegmentUtils.getDictionaryFieldInfoListApply(segment);
List<DictFieldInfoDTO> dictionaryFieldInfoList = SensitiveModifySegmentUtil.getDictionaryFieldInfoListApply(segment);
updateExpertDictionaryList(expertUserId, dictionaryFieldInfoList, sensitiveFieldEnum.getKey());
}
}
@@ -742,7 +718,7 @@ public class ExpertInfoServiceImpl implements ExpertInfoService {
case other: {
SensitiveModifySegment segment = collectMap.get(sensitiveFieldEnum);
if (Objects.nonNull(segment)) {
List<TagFieldInfo> tagFieldInfoList = SensitiveModifySegmentUtils.getTagFieldInfoListApply(segment);
List<TagFieldInfo> tagFieldInfoList = SensitiveModifySegmentUtil.getTagFieldInfoListApply(segment);
updateExpertTagList(expertUserId, tagFieldInfoList, sensitiveFieldEnum.getKey());
}
}


hz-pm-api/src/main/java/com/hz/pm/api/expert/utils/SensitiveModifySegmentUtils.java → hz-pm-api/src/main/java/com/hz/pm/api/expert/utils/SensitiveModifySegmentUtil.java ファイルの表示

@@ -1,11 +1,11 @@
package com.hz.pm.api.expert.utils;


import com.ningdatech.basic.exception.BizException;
import cn.hutool.json.JSONUtil;
import com.hz.pm.api.expert.model.DictFieldInfoDTO;
import com.hz.pm.api.expert.model.SensitiveModifySegment;
import com.hz.pm.api.expert.model.TagFieldInfo;
import com.ningdatech.yxt.utils.JSONUtils;
import com.ningdatech.basic.exception.BizException;

import java.util.ArrayList;
import java.util.List;
@@ -16,9 +16,9 @@ import java.util.Objects;
* @date 2022/8/24 下午3:36
*/

public class SensitiveModifySegmentUtils {
public class SensitiveModifySegmentUtil {

private SensitiveModifySegmentUtils() {
private SensitiveModifySegmentUtil() {
}

public static List<TagFieldInfo> getTagFieldInfoListApply(SensitiveModifySegment segment) {
@@ -28,7 +28,7 @@ public class SensitiveModifySegmentUtils {
case industry_sector:
case other:
if (Objects.nonNull(segment.getApply())) {
return JSONUtils.parseArray(segment.getApply().toString(), TagFieldInfo.class);
return JSONUtil.toList(segment.getApply().toString(), TagFieldInfo.class);
}
return new ArrayList<>();
default:
@@ -42,7 +42,7 @@ public class SensitiveModifySegmentUtils {
case administrative_rank:
case title_level: {
if (Objects.nonNull(segment.getApply())) {
return JSONUtils.parseArray(segment.getApply().toString(), DictFieldInfoDTO.class);
return JSONUtil.toList(segment.getApply().toString(), DictFieldInfoDTO.class);
}
return new ArrayList<>();
}

+ 1
- 1
hz-pm-api/src/main/java/com/hz/pm/api/external/MhFileClient.java ファイルの表示

@@ -88,7 +88,7 @@ public class MhFileClient {
}
ClassPathResource resource = new ClassPathResource("/response/ret-xcfhx-report-file.pdf");
File destFile = new File(FileUtil.getTmpDirPath() + RandomUtil.randomString(20) + ".pdf");
return FileUtil.copy(resource.getFile(), destFile, true);
return FileUtil.writeFromStream(resource.getStream(), destFile, true);
}
}



+ 105
- 0
hz-pm-api/src/main/java/com/hz/pm/api/external/MobileCallClient.java ファイルの表示

@@ -0,0 +1,105 @@
package com.hz.pm.api.external;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hz.pm.api.external.model.dto.CallRetDTO;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;

import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
* <p>
* MobileCallClient
* </p>
*
* @author WendyYang
* @since 17:20 2024/4/25
*/
@Slf4j
public class MobileCallClient {

@Data
public static class SendCallDTO {

private String requestId;

}

@Data
public static class ReplyCallDTO {

private String remoteNumber;

private String dtmf;

private Date sendDate;

}

//==================================================================================================================

private static final Object LOCK = new Object();

private static final String CALL_HOST_KEY = "mobile-call.host";
private static final String CALL_OPEN_KEY = "mobile-call.open";

//==================================================================================================================

private static volatile MobileCallClient client;

private static volatile boolean mobileCallOpen = Boolean.TRUE;

public static MobileCallClient getClient() {
if (mobileCallOpen && client == null) {
synchronized (LOCK) {
if (client == null) {
Environment environment = SpringUtil.getBean(Environment.class);
mobileCallOpen = environment.getProperty(CALL_OPEN_KEY, Boolean.class, Boolean.TRUE);
if (mobileCallOpen) {
String callHost = environment.getProperty(CALL_HOST_KEY);
Assert.notBlank(callHost, "语音服务地址不能为空");
client = new MobileCallClient(callHost);
}
}
}
}
Assert.isTrue(mobileCallOpen, "语音服务未开启");
return client;
}

private final String callHost;

private MobileCallClient(String callHost) {
this.callHost = callHost;
}


private static final String MOBILE_CALL_URL = "/send_batch_voice_sms";
private static final String MOBILE_REPLY_URL = "/query_voice_sms";

public CallRetDTO<SendCallDTO> sendCall(Collection<String> phones, String content) {
String url = callHost + MOBILE_CALL_URL;
JSONObject bodyObj = new JSONObject();
bodyObj.set("remote_numbers", phones);
bodyObj.set("content", content);
String retStr = HttpUtil.post(url, bodyObj.toString());
return JSONUtil.toBean(retStr, new TypeReference<CallRetDTO<SendCallDTO>>() {
}, false);
}

public CallRetDTO<List<ReplyCallDTO>> replyCall(String requestId) {
String url = callHost + MOBILE_REPLY_URL + "?request_id=" + requestId;
String retStr = HttpUtil.get(url);
return JSONUtil.toBean(retStr, new TypeReference<CallRetDTO<List<ReplyCallDTO>>>() {
}, false);
}

}

+ 24
- 0
hz-pm-api/src/main/java/com/hz/pm/api/external/model/dto/CallRetDTO.java ファイルの表示

@@ -0,0 +1,24 @@
package com.hz.pm.api.external.model.dto;

import lombok.Data;

/**
* <p>
* CallRetDTO
* </p>
*
* @author WendyYang
* @since 17:38 2024/4/25
*/
@Data
public class CallRetDTO<T> {

private Boolean success;

private T data;

public boolean isOk() {
return Boolean.TRUE.equals(success);
}

}

+ 147
- 0
hz-pm-api/src/main/java/com/hz/pm/api/external/sms/MhSmsClient.java ファイルの表示

@@ -0,0 +1,147 @@
package com.hz.pm.api.external.sms;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hz.pm.api.external.sms.dto.SmsDTO;
import com.hz.pm.api.external.sms.vo.SmsReceipt;
import com.hz.pm.api.external.sms.vo.SmsReplyDTO;
import com.hz.pm.api.external.sms.vo.SmsSendDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
* <p>
* MhSmsClient
* </p>
*
* @author WendyYang
* @since 16:38 2024/4/25
*/
@Slf4j
public class MhSmsClient {

private static volatile MhSmsClient client;

private static volatile boolean smsSendOpen = Boolean.TRUE;

private static final String SMS_HOST_KEY = "sms-client.host";
private static final String SMS_OPEN_KEY = "sms-client.open";

private static final Object LOCK = new Object();

public static MhSmsClient getClient() {
if (smsSendOpen && client == null) {
synchronized (LOCK) {
if (client == null) {
Environment environment = SpringUtil.getBean(Environment.class);
smsSendOpen = environment.getProperty(SMS_OPEN_KEY, Boolean.class, Boolean.TRUE);
if (smsSendOpen) {
String smsHost = environment.getProperty(SMS_HOST_KEY);
Assert.notBlank(smsHost, "短信服务地址不能为空");
client = new MhSmsClient(smsHost);
}
}
}
}
Assert.isTrue(smsSendOpen, "短信服务未开启");
return client;
}

//==================================================================================================================

private MhSmsClient(String smsUrl) {
this.smsUrl = smsUrl;
}

/**
* 短信服务地址前缀
*/
private final String smsUrl;
/**
* 获取短信回复结果
*/
private static final String SMS_REPLY = "/sms/reply";
/**
* 短信发送
*/
private static final String SMS_SEND = "/sms/send";
/**
* 短信状态
*/
private static final String SMS_RECEIPT = "/sms/status";

/**
* 短信回复
*
* @param result \
* @return \
*/
public SmsReplyDTO smsReply(String result) {
String refreshUrl = smsUrl + SMS_REPLY;
Map<String, Object> map = new HashMap<>();
map.put("result", result);
String responseResult = HttpUtil.post(refreshUrl, map);
JSONObject responseJson = JSON.parseObject(responseResult, JSONObject.class);
String fileData = responseJson.getString("data");
return JSONUtil.toBean(fileData, new TypeReference<SmsReplyDTO>() {
}, false);
}

/**
* 短信发送
*
* @return \
*/
public SmsDTO<SmsSendDTO> smsSend(String content, Collection<String> phones) {
String phonesSplit = String.join(",", phones);
String refreshUrl = smsUrl + SMS_SEND;
Map<String, Object> map = new HashMap<>();
map.put("content", content);
map.put("phones", phonesSplit);
String responseResult = HttpUtil.post(refreshUrl, JSON.toJSONString(map));
return JSONUtil.toBean(responseResult, new TypeReference<SmsDTO<SmsSendDTO>>() {
}, false);
}

/**
* 短信发送
*
* @return \
*/
public SmsDTO<SmsSendDTO> smsSend(String content, String phone) {
return smsSend(content, Collections.singletonList(phone));

}

/**
* 短信回执
*
* @param result \
* @return \
*/
public SmsReceipt smsReceipt(String result) {
String refreshUrl = smsUrl + SMS_RECEIPT;
HashMap<String, Object> map = new HashMap<>();
map.put("result", result);
String responseResult = HttpUtil.post(refreshUrl, map);
JSONObject responseJson = JSON.parseObject(responseResult, JSONObject.class);
String fileData = responseJson.getString("data");
JSONObject jsonObject = JSON.parseObject(fileData);
Map<String, Object> dataMap = jsonObject.getInnerMap();
if (dataMap.get("result") instanceof Boolean) {
return SmsReceipt.builder().error(dataMap.get("error").toString()).build();
}
return JSONObject.parseObject(fileData, SmsReceipt.class);
}

}

+ 0
- 86
hz-pm-api/src/main/java/com/hz/pm/api/external/sms/SmsServiceClient.java ファイルの表示

@@ -1,86 +0,0 @@
package com.hz.pm.api.external.sms;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hz.pm.api.external.sms.dto.SmsDTO;
import com.hz.pm.api.external.sms.vo.SmsReceipt;
import com.hz.pm.api.external.sms.vo.SmsReplyResponse;
import com.hz.pm.api.external.sms.vo.SmsSendResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Component
@RequiredArgsConstructor
public class SmsServiceClient {

/**
* 短信服务
*/
@Value("${sms.client-url:}")
private String smsUrl;

public static final String SMS_REPLY = "/sms/reply";
public static final String SMS_SEND = "/sms/send";
public static final String SMS_RECEIPT = "/sms/status";
/**
* 短信回复
* @param result
* @return
*/
public SmsReplyResponse smsReply(String result) {
String refreshUrl = smsUrl + SMS_REPLY;
HashMap<String,Object> map = new HashMap<>();
map.put("result",result);
String responseResult = HttpUtil.post(refreshUrl, map);
JSONObject responseJson = JSON.parseObject(responseResult, JSONObject.class);
String fileData = responseJson.getString("data");
return JSONUtil.toBean(fileData, new TypeReference<SmsReplyResponse>() {
}, false);
}

/**
* 短信发送
* @return
*/
public SmsDTO<SmsSendResponse> smsSend(String content, List<String> phones) {
String phonesSplit = String.join(",", phones);
String refreshUrl = smsUrl + SMS_SEND;
Map<String,Object> map = new HashMap<>();
map.put("content",content);
map.put("phones",phonesSplit);
String responseResult = HttpUtil.post(refreshUrl, JSON.toJSONString(map));
return JSONUtil.toBean(responseResult, new TypeReference<SmsDTO<SmsSendResponse>>() {
}, false);
}

/**
* 短信回执
* @param result
* @return
*/
public SmsReceipt smsReceipt(String result) {
String refreshUrl = smsUrl + SMS_RECEIPT;
HashMap<String,Object> map = new HashMap<>();
map.put("result",result);
String responseResult = HttpUtil.post(refreshUrl, map);
JSONObject responseJson = JSON.parseObject(responseResult, JSONObject.class);
String fileData = responseJson.getString("data");
JSONObject jsonObject = JSON.parseObject(fileData);
Map<String, Object> dataMap = jsonObject.getInnerMap();
if(dataMap.get("result") instanceof Boolean){
return SmsReceipt.builder().error(dataMap.get("error").toString()).build();
}
return JSONObject.parseObject(fileData, SmsReceipt.class);
}

}

+ 14
- 1
hz-pm-api/src/main/java/com/hz/pm/api/external/sms/dto/SmsDTO.java ファイルの表示

@@ -2,13 +2,21 @@ package com.hz.pm.api.external.sms.dto;

import lombok.Data;

/**
* <p>
* SmsDTO
* </p>
*
* @author WendyYang
* @since 15:56 2024/4/25
*/
@Data
public class SmsDTO<T> {

/**
* 成功状态码
*/
private static final int OK_CODE = 200;
private static final Integer OK_CODE = 200;
/**
* 失败状态码(未找到资源)
*/
@@ -19,4 +27,9 @@ public class SmsDTO<T> {
private String msg;

private T data;

public boolean isOk() {
return OK_CODE.equals(this.getCode());
}

}

hz-pm-api/src/main/java/com/hz/pm/api/external/sms/vo/SmsReplyResponse.java → hz-pm-api/src/main/java/com/hz/pm/api/external/sms/vo/SmsReplyDTO.java ファイルの表示

@@ -6,9 +6,17 @@ import lombok.Data;

import java.util.List;

/**
* <p>
* SmsReplyDTO
* </p>
*
* @author WendyYang
* @since 17:05 2024/4/25
*/
@Data
@ApiModel("短信回复响应内容")
public class SmsReplyResponse {
public class SmsReplyDTO {

@ApiModelProperty("参数校验失败的错误提示")
private String error;

hz-pm-api/src/main/java/com/hz/pm/api/external/sms/vo/SmsSendResponse.java → hz-pm-api/src/main/java/com/hz/pm/api/external/sms/vo/SmsSendDTO.java ファイルの表示

@@ -2,8 +2,16 @@ package com.hz.pm.api.external.sms.vo;

import lombok.Data;

/**
* <p>
* SmsSendDTO
* </p>
*
* @author WendyYang
* @since 16:56 2024/4/25
*/
@Data
public class SmsSendResponse {
public class SmsSendDTO {

//发送失败的手机号
private String errorPhone;

+ 3
- 3
hz-pm-api/src/main/java/com/hz/pm/api/leave/manage/LeaveManage.java ファイルの表示

@@ -32,7 +32,7 @@ import com.hz.pm.api.meeting.entity.domain.MeetingExpert;
import com.hz.pm.api.meeting.entity.enumeration.ExpertAttendStatusEnum;
import com.hz.pm.api.meeting.entity.enumeration.ExpertInviteTypeEnum;
import com.hz.pm.api.meeting.entity.enumeration.MeetingStatusEnum;
import com.hz.pm.api.meeting.helper.MeetingCallOrMsgHelper;
import com.hz.pm.api.meeting.helper.MeetingNotifyHelper;
import com.hz.pm.api.meeting.service.IMeetingExpertService;
import com.hz.pm.api.meeting.service.IMeetingService;
import com.hz.pm.api.meeting.task.ExpertRandomInviteTask;
@@ -75,7 +75,7 @@ public class LeaveManage {
private final IExpertLeaveDetailService leaveDetailService;
private final IExpertMetaApplyService metaApplyService;
private final FileService fileService;
private final MeetingCallOrMsgHelper meetingCallOrMsgHelper;
private final MeetingNotifyHelper meetingNotifyHelper;

private static final int HOURS_BEFORE_MEETING = 2;

@@ -202,7 +202,7 @@ public class LeaveManage {
.eq(Meeting::getId, meeting.getId());
meetingService.update(mUpdate);
}
meetingCallOrMsgHelper.sendExpertLeaveMsg(expert, meeting);
meetingNotifyHelper.sendExpertLeaveMsg(expert, meeting);
// 临时请假无需审核
leave.setAuditId(0L);
leave.setStatus(LeaveStatusEnum.PASSED.getCode());


+ 8
- 10
hz-pm-api/src/main/java/com/hz/pm/api/meeting/controller/ExpertDashboardController.java ファイルの表示

@@ -1,10 +1,10 @@
package com.hz.pm.api.meeting.controller;

import com.hz.pm.api.external.sms.SmsServiceClient;
import com.hz.pm.api.external.sms.MhSmsClient;
import com.hz.pm.api.external.sms.dto.SmsDTO;
import com.hz.pm.api.external.sms.vo.SmsReceipt;
import com.hz.pm.api.external.sms.vo.SmsReplyResponse;
import com.hz.pm.api.external.sms.vo.SmsSendResponse;
import com.hz.pm.api.external.sms.vo.SmsReplyDTO;
import com.hz.pm.api.external.sms.vo.SmsSendDTO;
import com.hz.pm.api.meeting.entity.req.MeetingCalenderReq;
import com.hz.pm.api.meeting.entity.vo.*;
import com.hz.pm.api.meeting.manage.DashboardManage;
@@ -39,24 +39,22 @@ public class ExpertDashboardController {

private final DashboardManage dashboardManage;

private final SmsServiceClient smsServiceClient;

@ApiOperation("短信发送")
@GetMapping("/sendSms")
public SmsDTO<SmsSendResponse> test(String phone) {
return smsServiceClient.smsSend(VoiceSmsTemplateConst.EXPERT_INVITE, Collections.singletonList(phone));
public SmsDTO<SmsSendDTO> test(String phone) {
return MhSmsClient.getClient().smsSend(VoiceSmsTemplateConst.EXPERT_INVITE, Collections.singletonList(phone));
}

@ApiOperation("短信回复")
@GetMapping("/aplySms")
public SmsReplyResponse smsReply(String uuid) {
return smsServiceClient.smsReply(uuid);
public SmsReplyDTO smsReply(String uuid) {
return MhSmsClient.getClient().smsReply(uuid);
}

@ApiOperation("短信回执")
@GetMapping("/status")
public SmsReceipt smsReceipt(String uuid) {
return smsServiceClient.smsReceipt(uuid);
return MhSmsClient.getClient().smsReceipt(uuid);
}

@ApiOperation("会议日历")


+ 2
- 3
hz-pm-api/src/main/java/com/hz/pm/api/meeting/entity/domain/MeetingExpert.java ファイルの表示

@@ -26,6 +26,7 @@ public class MeetingExpert implements Serializable {

@Tolerate
public MeetingExpert() {
// 默认无参构造
}

private static final long serialVersionUID = 1L;
@@ -61,11 +62,9 @@ public class MeetingExpert implements Serializable {
@ApiModelProperty("是否已确认名单")
private Boolean confirmedRoster;

@ApiModelProperty("提交标识")
private String submitKey;

@ApiModelProperty("短信发送返回的uuid")
private String smsUuid;

@ApiModelProperty("通知方式:1电话 2短信")
private String notifyWay;



hz-pm-api/src/main/java/com/hz/pm/api/meeting/helper/MeetingCallOrMsgHelper.java → hz-pm-api/src/main/java/com/hz/pm/api/meeting/helper/MeetingNotifyHelper.java ファイルの表示

@@ -3,37 +3,34 @@ package com.hz.pm.api.meeting.helper;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hz.pm.api.common.helper.MsgCallHelper;
import com.hz.pm.api.meeting.constant.MeetingMsgTemplateConst;
import com.hz.pm.api.meeting.entity.domain.Meeting;
import com.hz.pm.api.meeting.entity.domain.MeetingExpert;
import com.hz.pm.api.meeting.entity.enumeration.ExpertInviteTypeEnum;
import com.hz.pm.api.meeting.entity.enumeration.ExpertNotifyTypeEnum;
import com.hz.pm.api.meeting.entity.enumeration.MeetingReviewTypeEnum;
import com.hz.pm.api.meeting.task.ExpertCallResultRewriteTask;
import com.hz.pm.api.organization.model.entity.DingEmployeeInfo;
import com.hz.pm.api.organization.model.entity.DingOrganization;
import com.hz.pm.api.organization.service.IDingEmployeeInfoService;
import com.hz.pm.api.organization.service.IDingOrganizationService;
import com.hz.pm.api.sms.constant.VoiceSmsTemplateConst;
import com.hz.pm.api.sms.utils.DateUtil;
import com.hz.pm.api.staging.enums.MsgTypeEnum;
import com.hz.pm.api.staging.service.INdWorkNoticeStagingService;
import com.hz.pm.api.sys.model.entity.Notify;
import com.hz.pm.api.sys.model.enumeration.BizTypeEnum;
import com.hz.pm.api.sys.service.INotifyService;
import com.hz.pm.api.todocenter.bean.entity.WorkNoticeInfo;
import com.hz.pm.api.user.model.entity.UserInfo;
import com.hz.pm.api.user.service.IUserInfoService;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.yxt.model.cmd.SendSmsCmd.SendSmsContext;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.*;

/**
* <p>
@@ -45,15 +42,14 @@ import java.util.stream.Collectors;
**/
@Component
@AllArgsConstructor
public class MeetingCallOrMsgHelper {
public class MeetingNotifyHelper {

private final IUserInfoService userInfoService;
private final YxtClientHelper yxtClientHelper;
private final MsgCallHelper msgCallHelper;
private final INdWorkNoticeStagingService workNoticeStagingService;
private final IDingEmployeeInfoService dingEmployeeInfoService;
private final IDingOrganizationService dingOrganizationService;
private final INotifyService notifyService;
private final ExpertCallResultRewriteTask expertCallResultRewriteTask;

private static String officialTime(LocalDateTime time) {
return time.format(DateUtil.DTF_YMD_HM);
@@ -100,10 +96,7 @@ public class MeetingCallOrMsgHelper {
UserInfo info = userInfoService.getById(userId);
String msgContent = String.format(MeetingMsgTemplateConst.INVITE_END, meetingName);
// 音信通消息
SendSmsContext yxtContent = new SendSmsContext();
yxtContent.setContent(msgContent);
yxtContent.setReceiveNumber(info.getMobile());
yxtClientHelper.sendSms(yxtContent);
msgCallHelper.sendMsg(info.getMobile(), msgContent, BizTypeEnum.EXPERT_INVITE_STOP);
// 发送工作通知
if (info.getAccountId() != null) {
WorkNoticeInfo swn = getSendWorkNoticeInfo(info.getAccountId());
@@ -124,7 +117,6 @@ public class MeetingCallOrMsgHelper {
String sTime = officialTime(meeting.getStartTime());
String eTime = officialTime(meeting.getEndTime());
String meetingTime = sTime + "至" + eTime;
List<SendSmsContext> yxtContents = new ArrayList<>();
List<Notify> notifies = new ArrayList<>();
List<WorkNoticeInfo> workingNotices = new ArrayList<>();
experts.forEach(w -> {
@@ -132,10 +124,7 @@ public class MeetingCallOrMsgHelper {
w.getExpertName(), officialTime(w.getCreateOn()), meetingTime, meeting.getMeetingAddress(),
meeting.getConnecter(), meeting.getContact());
// 音信通消息
SendSmsContext yxtContent = new SendSmsContext();
yxtContent.setContent(msgContent);
yxtContent.setReceiveNumber(w.getMobile());
yxtContents.add(yxtContent);
msgCallHelper.sendMsg(w.getMobile(), msgContent, BizTypeEnum.MEETING_ROSTER_CONFIRM);
UserInfo info = userMap.get(w.getExpertId());
// 发送工作通知
if (info.getAccountId() != null) {
@@ -149,7 +138,6 @@ public class MeetingCallOrMsgHelper {
}
});
notifyService.saveBatch(notifies);
yxtClientHelper.sendSms(yxtContents);
workNoticeStagingService.addByWorkNotice(workingNotices, MsgTypeEnum.EXPERT_REVIEW);
}

@@ -157,15 +145,11 @@ public class MeetingCallOrMsgHelper {
public void sendCancelMeetingMsg(List<MeetingExpert> experts, Meeting meeting) {
String startTime = officialTime(meeting.getStartTime());
String meetingType = MeetingReviewTypeEnum.getValue(meeting.getType());
List<SendSmsContext> contexts = experts.stream().map(w -> {
for (MeetingExpert expert : experts) {
String content = String.format(MeetingMsgTemplateConst.MEETING_CANCEL,
w.getExpertName(), startTime, meetingType, meeting.getContact());
SendSmsContext context = new SendSmsContext();
context.setReceiveNumber(w.getMobile());
context.setContent(content);
return context;
}).collect(Collectors.toList());
yxtClientHelper.sendSms(contexts);
expert.getExpertName(), startTime, meetingType, meeting.getContact());
msgCallHelper.sendMsg(expert.getMobile(), content, BizTypeEnum.MEETING_CANCEL);
}
}

/**
@@ -175,27 +159,36 @@ public class MeetingCallOrMsgHelper {
* @param experts 待通知专家
* @author WendyYang
**/
// public void callExpertByMeeting(Meeting meeting, List<MeetingExpert> experts) {
// String voiceContent = String.format(VoiceSmsTemplateConst.EXPERT_INVITE,
// meeting.getHoldOrg(), meeting.getName(), officialTime(meeting.getStartTime()),
// meeting.getMeetingAddress());
// List<SubmitTaskCallContext> callContexts = CollUtils.convert(experts, w -> {
// SubmitTaskCallContext context = new SubmitTaskCallContext();
// context.setContent(voiceContent);
// context.setReceiveNumber(w.getMobile());
// return context;
// });
// String submitKey = yxtClientHelper.submitCallTask(callContexts);
// experts.forEach(w -> w.setSubmitKey(submitKey));
// }

public void smsOrCallExpertByMeeting(Meeting meeting, List<MeetingExpert> experts) {
if(ExpertNotifyTypeEnum.CALL.eq(meeting.getNotifyWay())){
// todo 电话通知
}else if(ExpertNotifyTypeEnum.SMS.eq(meeting.getNotifyWay())){
// 短信通知
String smsUuid = expertCallResultRewriteTask.sendExpertSms(experts, meeting);
experts.forEach(w -> w.setSmsUuid(smsUuid));
private void callExperts(Meeting meeting, List<MeetingExpert> experts) {
String content = String.format(VoiceSmsTemplateConst.EXPERT_INVITE,
meeting.getHoldOrg(), meeting.getName(), officialTime(meeting.getStartTime()),
meeting.getMeetingAddress());
Set<String> phones = CollUtils.fieldSet(experts, MeetingExpert::getMobile);
String submitKey = msgCallHelper.sendCall(phones, content, BizTypeEnum.EXPERT_INVITE);
experts.forEach(w -> w.setSubmitKey(submitKey));
}

/**
* 发送专家短信任务
*
* @param experts 专家
* @param meeting 会议
*/
private void smsExperts(Meeting meeting, List<MeetingExpert> experts) {
String content = String.format(VoiceSmsTemplateConst.EXPERT_INVITE,
meeting.getHoldOrg(), meeting.getName(), officialTime(meeting.getStartTime()),
meeting.getMeetingAddress());
Set<String> phones = CollUtils.fieldSet(experts, MeetingExpert::getMobile);
String submitKey = msgCallHelper.sendMsg(phones, content, BizTypeEnum.EXPERT_INVITE);
// 短信发送成功返回的UUID
experts.forEach(w -> w.setSubmitKey(submitKey));
}

public void notifyExperts(Meeting meeting, List<MeetingExpert> experts) {
if (ExpertNotifyTypeEnum.CALL.eq(meeting.getNotifyWay())) {
callExperts(meeting, experts);
} else if (ExpertNotifyTypeEnum.SMS.eq(meeting.getNotifyWay())) {
smsExperts(meeting, experts);
}
}

@@ -209,10 +202,7 @@ public class MeetingCallOrMsgHelper {
msgContent = String.format(MeetingMsgTemplateConst.EXPERT_LEAVE_APPOINT, meeting.getName(), expert.getExpertName());
}
UserInfo info = userInfoService.getById(userId);
SendSmsContext yxtContent = new SendSmsContext();
yxtContent.setContent(msgContent);
yxtContent.setReceiveNumber(info.getMobile());
yxtClientHelper.sendSms(yxtContent);
msgCallHelper.sendMsg(info.getMobile(), msgContent, BizTypeEnum.EXPERT_LEAVE);
// 发送工作通知
if (info.getAccountId() != null) {
WorkNoticeInfo swn = getSendWorkNoticeInfo(info.getAccountId());

+ 0
- 69
hz-pm-api/src/main/java/com/hz/pm/api/meeting/helper/SmsOrCallClient.java ファイルの表示

@@ -1,69 +0,0 @@
package com.hz.pm.api.meeting.helper;

import com.alibaba.fastjson.JSONObject;
import com.hz.pm.api.external.sms.SmsServiceClient;
import com.hz.pm.api.external.sms.dto.SmsDTO;
import com.hz.pm.api.external.sms.vo.SmsSendResponse;
import com.hz.pm.api.meeting.entity.domain.MeetingExpertSms;
import com.hz.pm.api.meeting.mapper.MeetingExpertSmsMapper;
import com.ningdatech.yxt.model.cmd.SendSmsCmd;
import com.ningdatech.yxt.model.cmd.SubmitTaskCallCmd;
import com.ningdatech.yxt.model.cmd.SubmitTaskCallResponse;
import com.ningdatech.yxt.model.response.SendSmsResponse;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Arrays;

/**
* @author wangrenkang
* @date 2024-02-22 16:39:35
*/
@Component
@AllArgsConstructor
@Primary
public class SmsOrCallClient implements com.ningdatech.yxt.client.YxtClient {

private final SmsServiceClient smsServiceClient;
private final MeetingExpertSmsMapper meetingExpertSmsMapper;

/**
* 发送短信
* @param sendSmsCmd 短信内容,短信手机号
* @return
*/
@Override
public SendSmsResponse submitSmsTask(SendSmsCmd sendSmsCmd) {
sendSmsCmd.getContextList().forEach(sms ->{
SmsDTO<SmsSendResponse> smsSendResponseSmsDto = smsServiceClient.smsSend(sms.getContent(), Arrays.asList(sms.getReceiveNumber()));
String resultUuid = smsSendResponseSmsDto.getData().getResult();
// 保存短信发送记录
MeetingExpertSms meetingExpertSms = MeetingExpertSms.builder()
.createOn(LocalDateTime.now())
.smsUuid(resultUuid)
.content(sms.getContent())
.phones(sms.getReceiveNumber())
.smsResult(smsSendResponseSmsDto.toString())
.build();
meetingExpertSmsMapper.insert(meetingExpertSms);
});
return null;
}

@Override
public SubmitTaskCallResponse submitTaskCall(SubmitTaskCallCmd submitTaskCallCmd) {
return null;
}

@Override
public JSONObject getSentResultSms(String transactionId) {
return null;
}

@Override
public JSONObject getSentResultCall(String transactionId) {
return null;
}
}

+ 0
- 46
hz-pm-api/src/main/java/com/hz/pm/api/meeting/helper/YxtClientHelper.java ファイルの表示

@@ -1,46 +0,0 @@
package com.hz.pm.api.meeting.helper;

import com.ningdatech.yxt.client.YxtClient;
import com.ningdatech.yxt.model.cmd.SendSmsCmd;
import com.ningdatech.yxt.model.cmd.SendSmsCmd.SendSmsContext;
import com.ningdatech.yxt.model.cmd.SubmitTaskCallResponse;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;

import static com.ningdatech.yxt.model.cmd.SubmitTaskCallCmd.SubmitTaskCallContext;
import static com.ningdatech.yxt.model.cmd.SubmitTaskCallCmd.of;

/**
* <p>
* YxtCallOrSmsHelper
* </p>
*
* @author WendyYang
* @since 23:59 2022/8/31
*/
@Component
@AllArgsConstructor
public class YxtClientHelper {

private final YxtClient yxtClient;

public String submitCallTask(List<SubmitTaskCallContext> callContexts) {
SubmitTaskCallResponse callResponse = yxtClient.submitTaskCall(of(callContexts));
return callResponse.getSubmitKey();
}

public void sendSms(List<SendSmsContext> smsList) {
SendSmsCmd cmd = new SendSmsCmd();
cmd.setContextList(smsList);
// cmd.setSmsSignEnum(YxtSmsSignEnum.LS_BIG_DATA_BUREAU);
yxtClient.submitSmsTask(cmd);
}

public void sendSms(SendSmsContext sms) {
sendSms(Collections.singletonList(sms));
}

}

+ 3
- 5
hz-pm-api/src/main/java/com/hz/pm/api/meeting/manage/ExpertInviteManage.java ファイルの表示

@@ -20,7 +20,7 @@ import com.hz.pm.api.meeting.entity.enumeration.AvoidTypeEnum;
import com.hz.pm.api.meeting.entity.enumeration.ExpertAttendStatusEnum;
import com.hz.pm.api.meeting.entity.enumeration.MeetingStatusEnum;
import com.hz.pm.api.meeting.helper.ExpertInviteHelper;
import com.hz.pm.api.meeting.helper.MeetingCallOrMsgHelper;
import com.hz.pm.api.meeting.helper.MeetingNotifyHelper;
import com.hz.pm.api.meeting.service.IExpertInviteRuleService;
import com.hz.pm.api.meeting.service.IMeetingExpertService;
import com.hz.pm.api.meeting.service.IMeetingService;
@@ -68,7 +68,7 @@ public class ExpertInviteManage {
private final IExpertUserFullInfoService expertUserFullInfoService;
private final IMeetingService meetingService;
private final IExpertAvoidCompanyService expertAvoidCompanyService;
private final MeetingCallOrMsgHelper meetingCallOrMsgHelper;
private final MeetingNotifyHelper meetingNotifyHelper;
private final IExpertGovBusinessStripService expertGovBusinessStripService;

@Value("#{randomInviteProperties.recentMeetingCount}")
@@ -576,9 +576,7 @@ public class ExpertInviteManage {
expertInserts.add(expert);
});
}
// meetingCallOrMsgHelper.callExpertByMeeting(meeting, expertInserts);
// 短信或电话提醒
meetingCallOrMsgHelper.smsOrCallExpertByMeeting(meeting, expertInserts);
meetingNotifyHelper.notifyExperts(meeting, expertInserts);
}
meetingExpertService.saveBatch(expertInserts);
}


+ 4
- 4
hz-pm-api/src/main/java/com/hz/pm/api/meeting/manage/MeetingManage.java ファイルの表示

@@ -29,7 +29,7 @@ import com.hz.pm.api.meeting.entity.enumeration.MeetingStatusEnum;
import com.hz.pm.api.meeting.entity.req.*;
import com.hz.pm.api.meeting.entity.vo.*;
import com.hz.pm.api.meeting.helper.ExpertInviteHelper;
import com.hz.pm.api.meeting.helper.MeetingCallOrMsgHelper;
import com.hz.pm.api.meeting.helper.MeetingNotifyHelper;
import com.hz.pm.api.meeting.helper.MeetingManageHelper;
import com.hz.pm.api.meeting.service.*;
import com.hz.pm.api.meeting.task.ExpertRandomInviteTask;
@@ -91,7 +91,7 @@ public class MeetingManage {
private final IBelongOrgService belongOrgService;
private final IDingOrganizationService dingOrganizationService;
private final ExpertInviteHelper expertInviteHelper;
private final MeetingCallOrMsgHelper meetingCallOrMsgHelper;
private final MeetingNotifyHelper meetingNotifyHelper;
private final IMeetingExpertJudgeService expertJudgeService;
private final IMeetingExpertJudgeService meetingExpertJudgeService;

@@ -686,7 +686,7 @@ public class MeetingManage {
// 发送通知给专家
List<MeetingExpert> experts = meetingExpertService.listAgreedExperts(meetingId);
if (!experts.isEmpty()) {
meetingCallOrMsgHelper.sendCancelMeetingMsg(experts, meeting);
meetingNotifyHelper.sendCancelMeetingMsg(experts, meeting);
}
} finally {
distributedLock.releaseLock(key);
@@ -846,7 +846,7 @@ public class MeetingManage {
.in(MeetingExpert::getId, currConfirmedMeIds)
.set(MeetingExpert::getConfirmedRoster, Boolean.TRUE);
meetingExpertService.update(meUpdate);
meetingCallOrMsgHelper.sendConfirmedRosterMsg(expertNoticing, meeting);
meetingNotifyHelper.sendConfirmedRosterMsg(expertNoticing, meeting);
} finally {
distributedLock.releaseLock(key);
}


+ 82
- 303
hz-pm-api/src/main/java/com/hz/pm/api/meeting/task/ExpertCallResultRewriteTask.java ファイルの表示

@@ -1,47 +1,33 @@
package com.hz.pm.api.meeting.task;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hz.pm.api.common.util.StrUtils;
import com.hz.pm.api.external.sms.SmsServiceClient;
import com.hz.pm.api.external.sms.dto.SmsDTO;
import com.hz.pm.api.external.sms.vo.SmsReply;
import com.hz.pm.api.external.sms.vo.SmsReplyResponse;
import com.hz.pm.api.external.sms.vo.SmsSendResponse;
import com.hz.pm.api.common.util.ThreadPoolUtil;
import com.hz.pm.api.meeting.entity.domain.ExpertInviteRule;
import com.hz.pm.api.meeting.entity.domain.Meeting;
import com.hz.pm.api.meeting.entity.domain.MeetingExpert;
import com.hz.pm.api.meeting.entity.domain.MeetingExpertSms;
import com.hz.pm.api.meeting.entity.dto.RandomInviteRuleDTO;
import com.hz.pm.api.meeting.entity.dto.SmsReplyDetails;
import com.hz.pm.api.meeting.entity.dto.YxtCallBackDTO;
import com.hz.pm.api.meeting.entity.enumeration.ExpertAttendStatusEnum;
import com.hz.pm.api.meeting.entity.enumeration.ExpertInviteTypeEnum;
import com.hz.pm.api.meeting.mapper.MeetingExpertSmsMapper;
import com.hz.pm.api.meeting.service.IExpertInviteRuleService;
import com.hz.pm.api.meeting.service.IMeetingExpertService;
import com.hz.pm.api.sms.constant.VoiceSmsTemplateConst;
import com.ningdatech.basic.util.StrPool;
import com.ningdatech.yxt.entity.SysMsgRecordDetail;
import com.ningdatech.yxt.service.ISysMsgRecordDetailService;
import com.hz.pm.api.sys.model.entity.MsgCallRecord;
import com.hz.pm.api.sys.model.entity.MsgCallRecord.ReplyStatus;
import com.hz.pm.api.sys.service.IMsgCallRecordService;
import com.ningdatech.basic.util.CollUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static com.hz.pm.api.meeting.entity.enumeration.ExpertAttendStatusEnum.*;
import static com.hz.pm.api.meeting.entity.enumeration.ExpertAttendStatusEnum.NOTICING;

/**
* <p>
@@ -58,321 +44,114 @@ import static com.hz.pm.api.meeting.entity.enumeration.ExpertAttendStatusEnum.*;
@AllArgsConstructor
public class ExpertCallResultRewriteTask {

private final RandomInviteProperties randomInviteProperties;

private final ThreadPoolTaskScheduler scheduler;
private final RandomInviteProperties properties;
private final IMeetingExpertService meetingExpertService;
private final IExpertInviteRuleService inviteRuleService;
private final ISysMsgRecordDetailService msgRecordDetailService;
private final SmsServiceClient smsServiceClient;
private final MeetingExpertSmsMapper meetingExpertSmsMapper;
private final IMsgCallRecordService msgCallRecordService;

private final static int MINUTES_CALL_RESULT_FEEDBACK = 15;
private static final int OVERTIME = 30;

private static final String AGREE_KEY = "1";
private static final String REFUSE_KEY = "2";

@PostConstruct
public void initTask() {
if (!randomInviteProperties.getEnable()) {
if (Boolean.FALSE.equals(properties.getEnable())) {
log.warn("随机邀请已关闭……");
return;
}
// 处理电话结果回填
Instant startTime = Instant.now().plus(randomInviteProperties.getResultRewriteFixedRate(), ChronoUnit.MINUTES);
Duration fixedRate = Duration.ofMinutes(randomInviteProperties.getResultRewriteFixedRate());
// scheduler.scheduleAtFixedRate(this::rewritePhoneCallResult, startTime, fixedRate);

Instant startTime = Instant.now().plus(properties.getResultRewriteFixedRate(), ChronoUnit.MINUTES);
Duration fixedRate = Duration.ofMinutes(properties.getResultRewriteFixedRate());
// 处理短信结果回填
Instant smsStartTime = Instant.now().plus(randomInviteProperties.getResultRewriteFixedRate(), ChronoUnit.MINUTES);
scheduler.scheduleAtFixedRate(this::expertSmsReply, smsStartTime, fixedRate);
ThreadPoolUtil.SCHEDULER.scheduleAtFixedRate(this::expertReplyRewrite, startTime, fixedRate);
}

public void expertSmsReply() {
log.info("开始执行短信回复查询任务:{}", Thread.currentThread().getName());
private void expertReplyRewrite() {
log.info("开始执行短信回复查询任务");
// 查询所有邀请的专家信息 状态为通话中的
LambdaQueryWrapper<MeetingExpert> meQuery = Wrappers.lambdaQuery(MeetingExpert.class)
Wrapper<MeetingExpert> meQuery = Wrappers.lambdaQuery(MeetingExpert.class)
.eq(MeetingExpert::getStatus, NOTICING.getCode())
.isNotNull(MeetingExpert::getSubmitKey)
.eq(MeetingExpert::getInviteType, ExpertInviteTypeEnum.RANDOM.getCode());
List<MeetingExpert> experts = meetingExpertService.list(meQuery);
if (experts.isEmpty()) {
log.info("暂无短信回复任务执行");
return;
}
// 所有随机邀请的短信Uuid
List<String> smsUuids = experts.stream()
.map(expert -> expert.getSmsUuid())
.filter(uuid -> uuid != null)
.distinct()
.collect(Collectors.toList());
Set<Long> randomRuleIds = experts.stream()
.map(MeetingExpert::getRuleId).collect(Collectors.toSet());
List<String> submitKeys = CollUtils.fieldList(experts, MeetingExpert::getSubmitKey);
List<String> mobiles = CollUtils.fieldList(experts, MeetingExpert::getMobile);
Set<Long> ruleIds = CollUtils.fieldSet(experts, MeetingExpert::getRuleId);
// 查询随机邀请回调等待时间
Map<Long, Integer> callbackMinutes = new HashMap<>(randomRuleIds.size());
if (!randomRuleIds.isEmpty()) {
List<ExpertInviteRule> inviteRules = inviteRuleService.listByIds(randomRuleIds);
inviteRules.forEach(expert -> {
RandomInviteRuleDTO rule = JSONObject.parseObject(expert.getInviteRule(), RandomInviteRuleDTO.class);
callbackMinutes.put(expert.getId(), rule.getWaitForCallbackMinutes());
});
Map<Long, Integer> callbackMinutes = new HashMap<>(ruleIds.size());
List<ExpertInviteRule> rules = inviteRuleService.listByIds(ruleIds);
for (ExpertInviteRule rule : rules) {
RandomInviteRuleDTO ruleDetail = JSONUtil.toBean(rule.getInviteRule(), RandomInviteRuleDTO.class);
callbackMinutes.put(rule.getId(), ruleDetail.getWaitForCallbackMinutes());
}

// 获取专家回复内容
SmsReplyDetails smsReplyDetails = viewSmsReplies(smsUuids);

List<MeetingExpert> updates = new ArrayList<>();

// 查询短信语音回执结果
Wrapper<MsgCallRecord> mcrQuery = Wrappers.lambdaQuery(MsgCallRecord.class)
.in(MsgCallRecord::getSubmitKey, submitKeys)
.in(MsgCallRecord::getReceivePhone, mobiles)
.isNotNull(MsgCallRecord::getReplyOn);
List<MsgCallRecord> records = msgCallRecordService.list(mcrQuery);
Map<String, MsgCallRecord> replayMap = CollUtils.listToMap(records,
w -> w.getSubmitKey() + "#" + w.getReceivePhone());

List<MeetingExpert> changes = new ArrayList<>();
for (MeetingExpert expert : experts) {

Integer minutes = ObjectUtil.defaultIfNull(callbackMinutes.get(expert.getRuleId()), MINUTES_CALL_RESULT_FEEDBACK);
// 判断回复状态
Optional<Integer> status = getStatusByMsgRecordDetail(smsReplyDetails, minutes, expert);
if (status.isPresent()) {
MeetingExpert update = new MeetingExpert();
update.setUpdateBy(0L);
update.setUpdateOn(LocalDateTime.now());
update.setId(expert.getId());
update.setStatus(status.get());
updates.add(update);
}
}
meetingExpertService.updateBatchById(updates);
}


public void rewritePhoneCallResult() {
log.info("开始执行电话结果回填任务:{}", Thread.currentThread().getName());
// 查询所有邀请的专家信息 状态为通话中的
LambdaQueryWrapper<MeetingExpert> meQuery = Wrappers.lambdaQuery(MeetingExpert.class)
.eq(MeetingExpert::getStatus, NOTICING.getCode())
.eq(MeetingExpert::getInviteType, ExpertInviteTypeEnum.RANDOM.getCode());
List<MeetingExpert> experts = meetingExpertService.list(meQuery);
if (experts.isEmpty()) {
log.info("暂无电话结果回填任务执行");
return;
}
// 所有随机邀请的规则ID
Map<Long, String> submitKeys = new HashMap<>(experts.size());
Set<Long> randomRuleIds = experts.stream().peek(w -> submitKeys.put(w.getId(), w.getSubmitKey()))
.map(MeetingExpert::getRuleId).collect(Collectors.toSet());
// 查询随机邀请回调等待时间
Map<Long, Integer> callbackMinutes = new HashMap<>(randomRuleIds.size());
if (!randomRuleIds.isEmpty()) {
List<ExpertInviteRule> inviteRules = inviteRuleService.listByIds(randomRuleIds);
inviteRules.forEach(w -> {
RandomInviteRuleDTO rule = JSONObject.parseObject(w.getInviteRule(), RandomInviteRuleDTO.class);
callbackMinutes.put(w.getId(), rule.getWaitForCallbackMinutes());
String submitKeyMobile = expert.getSubmitKey() + "#" + expert.getMobile();
Integer overtime = callbackMinutes.getOrDefault(expert.getRuleId(), OVERTIME);
MsgCallRecord mcr = replayMap.get(submitKeyMobile);
buildStatus(mcr, overtime, expert).ifPresent(w -> {
expert.setStatus(w.getCode());
changes.add(expert);
});
}
LambdaQueryWrapper<SysMsgRecordDetail> msgRecordDetailQuery = Wrappers.lambdaQuery(SysMsgRecordDetail.class)
.in(SysMsgRecordDetail::getSubmitKey, submitKeys.values());
List<SysMsgRecordDetail> recordDetailList = msgRecordDetailService.list(msgRecordDetailQuery);
if (recordDetailList.isEmpty()) {
return;
}
Map<String, SysMsgRecordDetail> recordDetailMap = recordDetailList.stream()
.collect(Collectors.toMap(w -> w.getSubmitKey() + StrPool.UNDERSCORE + w.getReceiveNumber(), w -> w));
List<MeetingExpert> updates = new ArrayList<>(), agrees = new ArrayList<>();
for (MeetingExpert expert : experts) {
String key = expert.getSubmitKey() + StrPool.UNDERSCORE + expert.getMobile();
SysMsgRecordDetail msgRecordDetail = recordDetailMap.get(key);
if (msgRecordDetail == null) {
// 极端情况下获取不到submitKey异常情况
continue;
}
Integer minutes = ObjectUtil.defaultIfNull(callbackMinutes.get(expert.getRuleId()), MINUTES_CALL_RESULT_FEEDBACK);
Optional<Integer> status = getStatusByMsgRecordDetail(msgRecordDetail, minutes, expert.getCreateOn());
if (status.isPresent()) {
MeetingExpert update = new MeetingExpert();
update.setUpdateBy(0L);
update.setUpdateOn(LocalDateTime.now());
update.setId(expert.getId());
update.setStatus(status.get());
if (AGREED.eq(update.getStatus())) {
// 发送专家确认参加的短信通知
// sendAgreeNotice(expert);
agrees.add(expert);
}
updates.add(update);
}
}
meetingExpertService.updateBatchById(updates);
meetingExpertService.updateBatchById(changes);
}

private static Optional<Integer> getStatusByMsgRecordDetail(SysMsgRecordDetail mrd, int minutes, LocalDateTime createOn) {
LocalDateTime limitTime = LocalDateTime.now().minusMinutes(minutes);
String callBackJson = mrd.getCallBackJson();
boolean waiting = limitTime.isBefore(createOn);
boolean hasCallBack = StrUtils.isNotBlank(callBackJson);
if (!hasCallBack && waiting) {
return Optional.empty();
}
/**
* 构建专家状态
*
* @param mcr 回复记录
* @param overtime 超时时间
* @param expert 专家
* @return 专家状态
*/
private static Optional<ExpertAttendStatusEnum> buildStatus(MsgCallRecord mcr,
Integer overtime,
MeetingExpert expert) {
LocalDateTime now = LocalDateTime.now();
boolean isOvertime = expert.getCreateOn().plusMinutes(overtime).isBefore(now);
ExpertAttendStatusEnum status;
if (hasCallBack) {
try {
YxtCallBackDTO callback = JSONObject.parseObject(callBackJson, YxtCallBackDTO.class);
LocalDateTime dialBeginTime = callback.getDialBeginTime();
if (dialBeginTime == null) {
return Optional.empty();
}
Integer resultCode = callback.getResultCode();
if (resultCode != null && resultCode == 0) {
String pressKey = callback.getPressKey();
if (pressKey != null) {
pressKey = pressKey.replaceAll("\\*", "").trim();
}
if (StrUtils.isBlank(pressKey) && waiting) {
return Optional.empty();
}
status = AGREE_KEY.equals(pressKey) ? AGREED : REFUSED;
} else {
if (waiting) {
return Optional.empty();
}
status = REFUSED;
if (mcr != null || isOvertime) {
if (isOvertime) {
status = ExpertAttendStatusEnum.UNANSWERED;
} else {
ReplyStatus replyStatus = mcr.getReplyStatus();
switch (replyStatus) {
case OVERTIME:
case FAILED:
status = ExpertAttendStatusEnum.UNANSWERED;
break;
case SUCCESS:
String reply = StrUtil.trim(mcr.getReplyContent());
if (AGREE_KEY.equals(reply)) {
status = ExpertAttendStatusEnum.AGREED;
} else {
status = ExpertAttendStatusEnum.REFUSED;
}
break;
default:
status = null;
break;
}
} catch (Exception e) {
log.error("获取电话回调结果异常{}", mrd, e);
status = UNANSWERED;
}
} else {
// 超时未回复设置为拒绝参加
status = REFUSED;
status = null;
}
return Optional.of(status.getCode());
return Optional.ofNullable(status);
}

private static Optional<Integer> getStatusByMsgRecordDetail(SmsReplyDetails smsReplyDetails, int minutes, MeetingExpert expert) {
Map<String, List<SmsReply>> accept = smsReplyDetails.getAccept() //回复1同意参加的手机号
, reject = smsReplyDetails.getReject() //回复2拒绝参加的手机号
, other = smsReplyDetails.getOther(); //回复其他的专家
Map<String, List<String>> errorPhones = new HashMap<>();//发送失败的专家
// 专家最长响应时间
LocalDateTime limitTime = LocalDateTime.now().minusMinutes(minutes);
// 专家抽取时间
boolean waiting = limitTime.isBefore(expert.getCreateOn());
ExpertAttendStatusEnum status = NOTICING;
boolean hasCallBack = ObjectUtil.isEmpty(smsReplyDetails);
if (hasCallBack && waiting) {
return Optional.empty();
}
// if (!waiting) {
// status = REFUSED;
// }
List<String> errorPhoneList = errorPhones.get(expert.getSmsUuid());
if(ObjectUtil.isNotEmpty(errorPhoneList) && errorPhoneList.contains(expert.getMobile())){
// 未应答
status = UNANSWERED;
}
if(ObjectUtil.isNotEmpty(accept)) {
List<SmsReply> smsRepliesAccept = accept.get(expert.getSmsUuid());
if (ObjectUtil.isNotEmpty(smsRepliesAccept)) {
boolean containsReplyMobile = smsRepliesAccept.stream()
.anyMatch(reply -> reply.getReplyMobile().equals(expert.getMobile()));
if (containsReplyMobile) {
SmsReply filteredReplies = smsRepliesAccept.stream()
.filter(reply -> reply.getReplyMobile().equals(expert.getMobile()))
.collect(Collectors.toList()).get(0);
// 回复时间
Instant instant = Instant.ofEpochMilli(filteredReplies.getReplyReplytime());
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
if (localDateTime.isBefore(expert.getCreateOn().plusMinutes(minutes))) {
status = AGREED;
} else {
status = REFUSED;
}
}
}
}
if(ObjectUtil.isNotEmpty(reject)) {
List<SmsReply> smsRepliesReject = reject.get(expert.getSmsUuid());
if (ObjectUtil.isNotEmpty(smsRepliesReject)) {
boolean containsReplyMobile = smsRepliesReject.stream()
.anyMatch(reply -> reply.getReplyMobile().equals(expert.getMobile()));
if (containsReplyMobile) {
status = REFUSED;
}
}
}
if(ObjectUtil.isNotEmpty(other)) {
List<SmsReply> smsRepliesOther = other.get(expert.getSmsUuid());
if (ObjectUtil.isNotEmpty(smsRepliesOther)) {
boolean containsReplyMobile = smsRepliesOther.stream()
.anyMatch(reply -> reply.getReplyMobile().equals(expert.getMobile()));
if (containsReplyMobile) {
status = REFUSED;
}
}
}
return Optional.of(status.getCode());
}

// 发送短信提醒
public String sendExpertSms(List<MeetingExpert> expertMeetings, Meeting meeting) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 短信内容
String replacedContent = String.format(VoiceSmsTemplateConst.EXPERT_INVITE,
meeting.getCreator(), meeting.getName(), meeting.getStartTime().format(formatter) + "至" + meeting.getEndTime().format(formatter), meeting.getMeetingAddress());

List<String> phones = expertMeetings.stream().map(obj -> obj.getMobile()).collect(Collectors.toList());
SmsDTO<SmsSendResponse> smsSendResponseSmsDto = smsServiceClient.smsSend(replacedContent, phones);
// 短信发送成功返回的UUID
String resultUuid = smsSendResponseSmsDto.getData().getResult();
// 保存短信发送记录
MeetingExpertSms meetingExpertSms = MeetingExpertSms.builder()
.meetingId(meeting.getId().toString())
.createOn(LocalDateTime.now())
.smsUuid(resultUuid)
.content(replacedContent)
.phones(phones.stream()
.collect(Collectors.joining(", ")))
.smsResult(smsSendResponseSmsDto.toString())
.build();
meetingExpertSmsMapper.insert(meetingExpertSms);
return resultUuid;
}

// 查看短信回复内容并更改专家回复状态
public SmsReplyDetails viewSmsReplies(List<String> uuids) {
Map<String, List<SmsReply>> accept = new HashMap<>()
, reject = new HashMap<>()
, other = new HashMap<>();
Map<String, List<String>> errorPhones = new HashMap<>();
uuids.forEach(uuid -> {
SmsReplyResponse response = smsServiceClient.smsReply(uuid);
List<SmsReply> smsReplies = response.getResult();
// 成功回复的手机信息
if (ObjectUtil.isNotEmpty(smsReplies)){
// 只有第一条回复的内容有效
smsReplies = smsReplies.stream()
.collect(Collectors.groupingBy(SmsReply::getReplyMobile,
Collectors.minBy(Comparator.comparing(SmsReply::getReplyReplytime))))
.values().stream()
.map(Optional::get)
.collect(Collectors.toList());
accept.put(uuid, smsReplies.stream()
.filter(obj -> ObjectUtil.isNotEmpty(obj))
.filter(smsReply -> "1".equals(smsReply.getReplyContent()))
.collect(Collectors.toList()));
reject.put(uuid, smsReplies.stream()
.filter(obj -> ObjectUtil.isNotEmpty(obj))
.filter(smsReply -> "2".equals(smsReply.getReplyContent()))
.collect(Collectors.toList()));
other.put(uuid, smsReplies.stream()
.filter(obj -> ObjectUtil.isNotEmpty(obj))
.filter(smsReply -> !("1".equals(smsReply.getReplyContent()) || "2".equals(smsReply.getReplyContent())))
.collect(Collectors.toList()));
}
// 发送失败的手机号
if (StrUtils.isNotBlank(response.getErrorPhone())){
errorPhones.put(uuid, Arrays.asList(response.getErrorPhone().split(",")));
}
});

SmsReplyDetails smsReplyDetails = SmsReplyDetails.builder()
.accept(accept)
.reject(reject)
.errorPhones(errorPhones).build();

return smsReplyDetails;
}
}

+ 5
- 6
hz-pm-api/src/main/java/com/hz/pm/api/meeting/task/ExpertRandomInviteTask.java ファイルの表示

@@ -14,7 +14,7 @@ import com.hz.pm.api.meeting.entity.dto.InviteCacheDTO;
import com.hz.pm.api.meeting.entity.dto.RandomInviteRuleDTO;
import com.hz.pm.api.meeting.entity.enumeration.ExpertAttendStatusEnum;
import com.hz.pm.api.meeting.entity.req.ConfirmedRosterReq;
import com.hz.pm.api.meeting.helper.MeetingCallOrMsgHelper;
import com.hz.pm.api.meeting.helper.MeetingNotifyHelper;
import com.hz.pm.api.meeting.manage.ExpertInviteManage;
import com.hz.pm.api.meeting.manage.MeetingManage;
import com.hz.pm.api.meeting.service.IExpertInviteAvoidRuleService;
@@ -72,7 +72,7 @@ public class ExpertRandomInviteTask {
private final IMeetingService meetingService;
private final ExpertInviteManage expertInviteManage;
private final IExpertInviteAvoidRuleService inviteAvoidRuleService;
private final MeetingCallOrMsgHelper meetingCallOrMsgHelper;
private final MeetingNotifyHelper meetingNotifyHelper;

/**
* 用来存入线程执行句柄, 停止定时任务时使用
@@ -227,7 +227,7 @@ public class ExpertRandomInviteTask {
public void invite(Long meetingId, Boolean reInvite, LocalDateTime tsTime) {
log.info("开始进行专家后台抽取:{}", meetingId);
if (!inInviteTimeRange()) {
log.warn("不在会议抽取执行时间:{}",meetingId);
log.warn("不在会议抽取执行时间:{}", meetingId);
return;
}
Meeting meeting = meetingService.getById(meetingId);
@@ -268,9 +268,8 @@ public class ExpertRandomInviteTask {
expert.setStatus(ExpertAttendStatusEnum.NOTICING.getCode());
return expert;
});
// meetingCallOrMsgHelper.callExpertByMeeting(meeting, expertMeetings);
// 短信或电话提醒
meetingCallOrMsgHelper.smsOrCallExpertByMeeting(meeting, expertMeetings);
meetingNotifyHelper.notifyExperts(meeting, expertMeetings);
log.info("会议:{} 后台抽取专家:{}名", meetingId, expertMeetings.size());
meetingExpertService.saveBatch(expertMeetings);
} else {
@@ -283,7 +282,7 @@ public class ExpertRandomInviteTask {
meetingService.stopRandomInvite(meetingId);
if (notIgnoreCnt.get() == notSupportCnt.get() && notIgnoreCnt.get() > 0) {
// 当未完成抽取且无专家可抽取时
meetingCallOrMsgHelper.sendInviteStopMsg(meeting.getCreateBy(), meetingId, meeting.getName());
meetingNotifyHelper.sendInviteStopMsg(meeting.getCreateBy(), meetingId, meeting.getName());
}
}
// 所有抽取规则抽取人数满足 自动召开会议


+ 171
- 0
hz-pm-api/src/main/java/com/hz/pm/api/meeting/task/MsgCallReplyRewriteTask.java ファイルの表示

@@ -0,0 +1,171 @@
package com.hz.pm.api.meeting.task;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hz.pm.api.common.helper.MsgCallHelper;
import com.hz.pm.api.common.util.BizUtils;
import com.hz.pm.api.common.util.MDCThreadPoolTaskExecutor;
import com.hz.pm.api.external.sms.vo.SmsReply;
import com.hz.pm.api.external.sms.vo.SmsReplyDTO;
import com.hz.pm.api.sys.model.entity.MsgCallRecord;
import com.hz.pm.api.sys.model.entity.MsgCallRecord.ReplyStatus;
import com.hz.pm.api.sys.model.entity.MsgCallRecord.SendStatus;
import com.hz.pm.api.sys.model.enumeration.SubmitTypeEnum;
import com.hz.pm.api.sys.service.IMsgCallRecordService;
import com.ningdatech.basic.util.CollUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.hz.pm.api.external.MobileCallClient.ReplyCallDTO;

/**
* <p>
* MsgCallReplyRewriteTask
* </p>
*
* @author WendyYang
* @since 21:29 2024/4/25
*/
@Slf4j
@Component
public class MsgCallReplyRewriteTask {

private static final ThreadPoolTaskExecutor EXECUTOR;

static {
// 初始化线程池
EXECUTOR = new MDCThreadPoolTaskExecutor();
EXECUTOR.setCorePoolSize(2);
EXECUTOR.setMaxPoolSize(4);
EXECUTOR.setQueueCapacity(100);
EXECUTOR.setKeepAliveSeconds(120);
EXECUTOR.setThreadPriority(Thread.NORM_PRIORITY);
EXECUTOR.setWaitForTasksToCompleteOnShutdown(true);
EXECUTOR.setThreadNamePrefix("callRewriteExecutor-");
EXECUTOR.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
}

private final IMsgCallRecordService msgCallRecordService;
private final MsgCallHelper msgCallHelper;

public MsgCallReplyRewriteTask(IMsgCallRecordService msgCallRecordService, MsgCallHelper msgCallHelper) {
this.msgCallRecordService = msgCallRecordService;
this.msgCallHelper = msgCallHelper;
EXECUTOR.initialize();
}


@Scheduled(fixedRateString = "${msg-call-reply-write.fixed-rate:2}", timeUnit = TimeUnit.MINUTES)
public void replyRewrite() {
log.info("开始获取短信电话回执");
Wrapper<MsgCallRecord> query = Wrappers.lambdaQuery(MsgCallRecord.class)
.eq(MsgCallRecord::getNeedReply, Boolean.TRUE)
.eq(MsgCallRecord::getSendStatus, SendStatus.SUCCESS)
.eq(MsgCallRecord::getReplyStatus, ReplyStatus.WAITING)
.isNull(MsgCallRecord::getReplyOn);
List<MsgCallRecord> records = msgCallRecordService.list(query);
if (records.isEmpty()) {
return;
}
LocalDateTime startTime = LocalDateTime.now().minusMinutes(60);
Map<Boolean, List<MsgCallRecord>> map = CollUtils.group(records, w -> w.getCreateOn().isBefore(startTime));
List<MsgCallRecord> overtimeRecords = map.get(Boolean.TRUE);
if (CollUtil.isNotEmpty(overtimeRecords)) {
overtimeRecords.forEach(w -> {
w.setReplyStatus(ReplyStatus.OVERTIME);
w.setReplyOn(LocalDateTime.now());
});
msgCallRecordService.updateBatchById(overtimeRecords);
}
List<MsgCallRecord> waitingRecords = map.get(Boolean.FALSE);
if (CollUtil.isNotEmpty(waitingRecords)) {
CollUtils.group(waitingRecords, MsgCallRecord::getSubmitType)
.forEach((submitType, recordsBySummitType) -> {
if (SubmitTypeEnum.CALL.equals(submitType)) {
rewriteCallReply(recordsBySummitType);
} else if (SubmitTypeEnum.SMS.equals(submitType)) {
rewriteSmsReply(recordsBySummitType);
}
});
}
}

private void rewriteSmsReply(List<MsgCallRecord> records) {
CollUtils.group(records, MsgCallRecord::getSubmitKey)
.forEach((submitKey, recordsBySubmitKey) -> {
Runnable function = () -> {
try {
SmsReplyDTO retReply = msgCallHelper.smsReply(submitKey);
String errorPhoneStr = retReply.getErrorPhone();
List<String> errorPhones = StrUtil.split(errorPhoneStr, ",");
List<SmsReply> replies = retReply.getResult();
Map<String, SmsReply> currReplyMap = new HashMap<>();
if (replies != null && !replies.isEmpty()) {
replies.sort(Comparator.comparing(SmsReply::getReplyReplytime));
replies.forEach(w -> currReplyMap.putIfAbsent(w.getReplyMobile(), w));
}
List<MsgCallRecord> updates = new ArrayList<>();
for (MsgCallRecord w : recordsBySubmitKey) {
if (errorPhones.contains(w.getReceivePhone())) {
w.setReplyStatus(ReplyStatus.FAILED);
w.setReplyOn(LocalDateTime.now());
} else if (currReplyMap.containsKey(w.getReceivePhone())) {
SmsReply reply = currReplyMap.get(w.getReceivePhone());
w.setReplyOn(LocalDateTime.from(Instant.ofEpochMilli(reply.getReplyReplytime())));
w.setReplyContent(reply.getReplyContent());
w.setReplyStatus(ReplyStatus.SUCCESS);
}
if (w.getReplyOn() != null) {
updates.add(w);
}
}
msgCallRecordService.updateBatchById(updates);
} catch (Exception e) {
log.error("回复结果回填失败:{}", submitKey, e);
}
};
EXECUTOR.execute(function);
});
}

private void rewriteCallReply(List<MsgCallRecord> records) {
CollUtils.group(records, MsgCallRecord::getSubmitKey)
.forEach((submitKey, recordsBySubmitKey) -> {
Runnable function = () -> {
try {
List<ReplyCallDTO> retReplies = msgCallHelper.callReply(submitKey);
Map<String, ReplyCallDTO> replyMap = BizUtils.groupFirstMap(retReplies,
ReplyCallDTO::getRemoteNumber,
Comparator.comparing(ReplyCallDTO::getSendDate));
List<MsgCallRecord> updates = new ArrayList<>();
for (MsgCallRecord w : recordsBySubmitKey) {
ReplyCallDTO reply = replyMap.get(w.getReceivePhone());
if (reply != null) {
w.setReplyOn(LocalDateTimeUtil.of(reply.getSendDate()));
w.setReplyContent(reply.getDtmf());
w.setReplyStatus(ReplyStatus.SUCCESS);
updates.add(w);
}
}
msgCallRecordService.updateBatchById(updates);
} catch (Exception e) {
log.error("回复结果回填失败:{}", submitKey, e);
}
};
EXECUTOR.execute(function);
});
}

}

+ 26
- 24
hz-pm-api/src/main/java/com/hz/pm/api/projectdeclared/manage/ConstructionManage.java ファイルの表示

@@ -599,33 +599,35 @@ public class ConstructionManage {
purchase.setXcfhxApplyFiles(req.getXcfhxApplyFiles());
purchase.setMatchXcfhx(req.getMatchXcfhx());
purchase.setXcfhxApplyRemark(req.getXcfhxApplyRemark());
if (StrUtils.isBlank(purchase.getMhXcfhxReportFile()) ||
req.getMhXcfhxReportFile().equals(purchase.getMhXcfhxReportFile())) {
ApiResponse<FileResultVO> retFileInfo;
File tmpFile = null;
FileInputStream fis = null;
try {
tmpFile = mhFileClient.downloadToTmpFile(req.getMhXcfhxReportFile());
String fileName = purchase.getBidName() + "-信创符合性测评报告." + FileUtil.getSuffix(tmpFile);
fis = new FileInputStream(tmpFile);
String mimeType = FileUtil.getMimeType(tmpFile.getPath());
MockMultipartFile multipartFile = new MockMultipartFile(fileName, fileName, mimeType, fis);
retFileInfo = fileController.upload(multipartFile, "default");
} catch (IOException e) {
log.error("信创报告上传失败", e);
throw BizException.wrap("信创符合性测评报告上传失败");
} finally {
if (tmpFile != null) {
tmpFile.deleteOnExit();
}
if (fis != null) {
IOUtils.closeQuietly(fis);
if (Boolean.TRUE.equals(req.getMatchXcfhx())) {
if (StrUtils.isBlank(purchase.getMhXcfhxReportFile()) ||
req.getMhXcfhxReportFile().equals(purchase.getMhXcfhxReportFile())) {
ApiResponse<FileResultVO> retFileInfo;
File tmpFile = null;
FileInputStream fis = null;
try {
tmpFile = mhFileClient.downloadToTmpFile(req.getMhXcfhxReportFile());
String fileName = purchase.getBidName() + "-信创符合性测评报告." + FileUtil.getSuffix(tmpFile);
fis = new FileInputStream(tmpFile);
String mimeType = FileUtil.getMimeType(tmpFile.getPath());
MockMultipartFile multipartFile = new MockMultipartFile(fileName, fileName, mimeType, fis);
retFileInfo = fileController.upload(multipartFile, "default");
} catch (IOException e) {
log.error("信创报告上传失败", e);
throw BizException.wrap("信创符合性测评报告上传失败");
} finally {
if (tmpFile != null) {
tmpFile.deleteOnExit();
}
if (fis != null) {
IOUtils.closeQuietly(fis);
}
}
purchase.setXcfhxReportFiles(JSONUtil.toJsonStr(retFileInfo));
}
purchase.setXcfhxReportFiles(JSONUtil.toJsonStr(retFileInfo));
purchase.setMhXcfhxReportRecordId(req.getMhXcfhxReportRecordId());
purchase.setMhXcfhxReportFile(req.getMhXcfhxReportFile());
}
purchase.setMhXcfhxReportRecordId(req.getMhXcfhxReportRecordId());
purchase.setMhXcfhxReportFile(req.getMhXcfhxReportFile());

xcfhxStateMachineUtil.pass(purchase);
purchaseService.updateById(purchase);


+ 0
- 2
hz-pm-api/src/main/java/com/hz/pm/api/projectdeclared/model/req/XcfhxApplyReq.java ファイルの表示

@@ -26,11 +26,9 @@ public class XcfhxApplyReq {
private Long bidId;

@ApiModelProperty("信创符合性测评报告文件")
@NotBlank(message = "信创符合性测评报告文件不能为空")
private String mhXcfhxReportFile;

@ApiModelProperty("信创符合性测评记录ID")
@NotBlank(message = "信创符合性测评记录ID不能为空")
private String mhXcfhxReportRecordId;

@ApiModelProperty("是否符合信创符合性要求")


+ 8
- 16
hz-pm-api/src/main/java/com/hz/pm/api/sms/manage/VerificationCodeManage.java ファイルの表示

@@ -2,18 +2,18 @@ package com.hz.pm.api.sms.manage;

import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import com.hz.pm.api.common.helper.MsgCallHelper;
import com.hz.pm.api.external.sms.MhSmsClient;
import com.hz.pm.api.sms.constant.VerificationCodeType;
import com.hz.pm.api.sms.constant.VoiceSmsTemplateConst;
import com.hz.pm.api.sms.model.dto.VerifyCodeCacheDTO;
import com.hz.pm.api.sms.model.po.ReqVerificationCodePO;
import com.hz.pm.api.sms.utils.DateUtil;
import com.hz.pm.api.sms.utils.SmsRedisKeyUtils;
import com.hz.pm.api.sys.model.enumeration.BizTypeEnum;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.cache.model.cache.CacheKey;
import com.ningdatech.cache.repository.CachePlusOps;
import com.ningdatech.yxt.client.YxtClient;
import com.ningdatech.yxt.model.cmd.SendSmsCmd;
import com.ningdatech.yxt.model.cmd.SendSmsCmd.SendSmsContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -34,7 +34,7 @@ import java.util.Objects;
@RequiredArgsConstructor
public class VerificationCodeManage {

private final YxtClient yxtClient;
private final MsgCallHelper msgCallHelper;
private final CachePlusOps cachePlusOps;

public void sendVerificationCode(ReqVerificationCodePO req) {
@@ -62,22 +62,15 @@ public class VerificationCodeManage {
.build();

// 创建短信内容
SendSmsCmd sendSmsCmd = new SendSmsCmd();
switch (codeType) {
case LOGIN: {
SendSmsContext sendSmsCtx = new SendSmsContext();
sendSmsCtx.setReceiveNumber(req.getMobile());
sendSmsCtx.setContent(String.format(VoiceSmsTemplateConst.SMS_VERIFY_CODE, code, codeType.getExpireTime()));
sendSmsCmd.setContextList(Collections.singletonList(sendSmsCtx));
// sendSmsCmd.setSmsSignEnum(YxtSmsSignEnum.LS_BIG_DATA_BUREAU);
String content = String.format(VoiceSmsTemplateConst.SMS_VERIFY_CODE, code, codeType.getExpireTime());
msgCallHelper.sendMsg(content, req.getMobile(), BizTypeEnum.VERIFY_CODE);
}
break;
case EXPERT_REGISTER: {
SendSmsContext sendSmsCtx = new SendSmsContext();
sendSmsCtx.setReceiveNumber(req.getMobile());
sendSmsCtx.setContent(String.format(VoiceSmsTemplateConst.EXPERT_REGISTER, code, codeType.getExpireTime()));
sendSmsCmd.setContextList(Collections.singletonList(sendSmsCtx));
// sendSmsCmd.setSmsSignEnum(YxtSmsSignEnum.LS_BIG_DATA_BUREAU);
String content = String.format(VoiceSmsTemplateConst.EXPERT_REGISTER, code, codeType.getExpireTime());
msgCallHelper.sendMsg(content, req.getMobile(), BizTypeEnum.VERIFY_CODE);
}
break;
default:
@@ -85,7 +78,6 @@ public class VerificationCodeManage {
}

// 发送 短信
yxtClient.submitSmsTask(sendSmsCmd);
log.info("发送短信验证码:{} -> {}", req.getMobile(), code);

cachePlusOps.set(new CacheKey(cacheKey, Duration.ofMinutes(codeType.getExpireTime())), cache);


+ 0
- 28
hz-pm-api/src/main/java/com/hz/pm/api/sms/task/YxtPollingTask.java ファイルの表示

@@ -1,28 +0,0 @@
package com.hz.pm.api.sms.task;

import com.ningdatech.yxt.client.YxtContext;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
* @author liuxinxin
* @date 2022/8/9 下午3:58
* 音信通定时检查电话结果
*/
@Component
@RequiredArgsConstructor
public class YxtPollingTask {


private final YxtContext yxtContext;

/**
* 校验音信通结果数据
* 每5分钟执行一次
*/
@Scheduled(cron = "0 */1 * * * ?")
public void smsMsgResultCheck() {
yxtContext.smsMsgResultCheck();
}
}

+ 18
- 0
hz-pm-api/src/main/java/com/hz/pm/api/sys/controller/NdMsgCallRecordController.java ファイルの表示

@@ -0,0 +1,18 @@
package com.hz.pm.api.sys.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;

/**
* <p>
* 前端控制器
* </p>
*
* @author WendyYang
* @since 2024-04-25
*/
@Controller
@RequestMapping("/api.sys/ndMsgCallRecord")
public class NdMsgCallRecordController {

}

+ 0
- 23
hz-pm-api/src/main/java/com/hz/pm/api/sys/manage/EarlyWarningManage.java ファイルの表示

@@ -1,8 +1,6 @@
package com.hz.pm.api.sys.manage;

import com.hz.pm.api.common.enumeration.CommonEnum;
import com.hz.pm.api.common.helper.UserInfoHelper;
import com.hz.pm.api.meeting.helper.YxtClientHelper;
import com.hz.pm.api.projectlib.model.entity.Project;
import com.hz.pm.api.projectlib.model.enumeration.InstTypeEnum;
import com.hz.pm.api.projectlib.model.enumeration.WarningFlowTypeEnum;
@@ -13,7 +11,6 @@ import com.hz.pm.api.sys.model.entity.WflowEarlyWarningRecords;
import com.hz.pm.api.sys.service.IEarlyWarningRecordsService;
import com.hz.pm.api.sys.service.INotifyService;
import com.hz.pm.api.user.security.model.UserFullInfoDTO;
import com.ningdatech.yxt.model.cmd.SendSmsCmd.SendSmsContext;
import com.wflow.enums.WarningNoticeTypeEnum;
import com.wflow.enums.WarningRuleTypeEnum;
import lombok.AllArgsConstructor;
@@ -37,13 +34,8 @@ import java.util.Objects;
public class EarlyWarningManage {

private final UserInfoHelper userInfoHelper;

private final NoticeManage noticeManage;

private final IEarlyWarningRecordsService earlyWarningRecordsService;

private final YxtClientHelper yxtClientHelper;

private final INotifyService notifyService;

/**
@@ -147,14 +139,6 @@ public class EarlyWarningManage {
Notify notify = noticeManage.assemblyAuditNotify(user.getUserId(), project, content);
notify.setType(MsgTypeEnum.PROJECT_REVIEW.name());
notifyService.save(notify);

//3.发短信
if (noticeMethod.contains(String.valueOf(CommonEnum.MOBILE.getCode()))) {
SendSmsContext context = new SendSmsContext();
context.setReceiveNumber(user.getMobile());
context.setContent(content);
yxtClientHelper.sendSms(context);
}
}

/**
@@ -263,13 +247,6 @@ public class EarlyWarningManage {
notify.setType(MsgTypeEnum.PROJECT_REVIEW.name());
notifyService.save(notify);

//3.发短信
if (noticeMethod.contains(String.valueOf(CommonEnum.MOBILE.getCode()))) {
SendSmsContext context = new SendSmsContext();
context.setReceiveNumber(user.getMobile());
context.setContent(content);
yxtClientHelper.sendSms(context);
}
}

/**


+ 16
- 0
hz-pm-api/src/main/java/com/hz/pm/api/sys/mapper/MsgCallRecordMapper.java ファイルの表示

@@ -0,0 +1,16 @@
package com.hz.pm.api.sys.mapper;

import com.hz.pm.api.sys.model.entity.MsgCallRecord;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
* <p>
* Mapper 接口
* </p>
*
* @author WendyYang
* @since 2024-04-25
*/
public interface MsgCallRecordMapper extends BaseMapper<MsgCallRecord> {

}

+ 5
- 0
hz-pm-api/src/main/java/com/hz/pm/api/sys/mapper/MsgCallRecordMapper.xml ファイルの表示

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hz.pm.api.sys.mapper.MsgCallRecordMapper">

</mapper>

+ 124
- 0
hz-pm-api/src/main/java/com/hz/pm/api/sys/model/entity/MsgCallRecord.java ファイルの表示

@@ -0,0 +1,124 @@
package com.hz.pm.api.sys.model.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.hz.pm.api.sys.model.enumeration.BizTypeEnum;
import com.hz.pm.api.sys.model.enumeration.SubmitTypeEnum;
import io.swagger.annotations.ApiModel;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
* <p>
* 短信语音记录表
* </p>
*
* @author WendyYang
* @since 2024-04-25
*/
@Data
@TableName("ND_MSG_CALL_RECORD")
@ApiModel(value = "NdMsgCallRecord对象")
public class MsgCallRecord implements Serializable {

private static final long serialVersionUID = 1L;

@TableId(value = "ID", type = IdType.AUTO)
private Long id;

@TableField(fill = FieldFill.INSERT)
private LocalDateTime createOn;

@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateOn;

@TableField(fill = FieldFill.INSERT)
private Long createBy;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;

/**
* 业务类型
*/
private BizTypeEnum bizType;

/**
* 提交标识
*/
private String submitKey;

/**
* 提交类型
*/
private SubmitTypeEnum submitType;

/**
* 接收手机号
*/
private String receivePhone;

/**
* 短信内容
*/
private String content;

/**
* 模板编码
*/
private String templateCode;

/**
* 是否需要回复
*/
private Boolean needReply;

/**
* 发送状态:
*/
private SendStatus sendStatus;

/**
* 回复状态
*/
private ReplyStatus replyStatus;

/**
* 回复内容
*/
private String replyContent;

/**
* 回复时间
*/
private LocalDateTime replyOn;


/**
* 发送状态
*/
public enum SendStatus {

SUCCESS,

FAILED

}

/**
* 发送状态
*/
public enum ReplyStatus {

SUCCESS,

WAITING,

OVERTIME,

FAILED

}

}

+ 32
- 0
hz-pm-api/src/main/java/com/hz/pm/api/sys/model/enumeration/BizTypeEnum.java ファイルの表示

@@ -0,0 +1,32 @@
package com.hz.pm.api.sys.model.enumeration;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
* <p>
* BizTypeEnum
* </p>
*
* @author WendyYang
* @since 16:13 2024/4/25
*/
@Getter
@AllArgsConstructor
public enum BizTypeEnum {

VERIFY_CODE("验证码"),

MEETING_ROSTER_CONFIRM("会议名单确认"),

EXPERT_LEAVE("专家请假"),

MEETING_CANCEL("会议取消"),

EXPERT_INVITE_STOP("会议取消"),

EXPERT_INVITE("专家邀请");

private final String desc;

}

+ 17
- 0
hz-pm-api/src/main/java/com/hz/pm/api/sys/model/enumeration/SubmitTypeEnum.java ファイルの表示

@@ -0,0 +1,17 @@
package com.hz.pm.api.sys.model.enumeration;

/**
* <p>
* MsgTypeEnum
* </p>
*
* @author WendyYang
* @since 15:26 2024/4/25
*/
public enum SubmitTypeEnum {

SMS,

CALL

}

+ 16
- 0
hz-pm-api/src/main/java/com/hz/pm/api/sys/service/IMsgCallRecordService.java ファイルの表示

@@ -0,0 +1,16 @@
package com.hz.pm.api.sys.service;

import com.hz.pm.api.sys.model.entity.MsgCallRecord;
import com.baomidou.mybatisplus.extension.service.IService;

/**
* <p>
* 服务类
* </p>
*
* @author WendyYang
* @since 2024-04-25
*/
public interface IMsgCallRecordService extends IService<MsgCallRecord> {

}

+ 20
- 0
hz-pm-api/src/main/java/com/hz/pm/api/sys/service/impl/MsgCallRecordServiceImpl.java ファイルの表示

@@ -0,0 +1,20 @@
package com.hz.pm.api.sys.service.impl;

import com.hz.pm.api.sys.model.entity.MsgCallRecord;
import com.hz.pm.api.sys.mapper.MsgCallRecordMapper;
import com.hz.pm.api.sys.service.IMsgCallRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

/**
* <p>
* 服务实现类
* </p>
*
* @author WendyYang
* @since 2024-04-25
*/
@Service
public class MsgCallRecordServiceImpl extends ServiceImpl<MsgCallRecordMapper, MsgCallRecord> implements IMsgCallRecordService {

}

+ 9
- 0
hz-pm-api/src/main/java/com/hz/pm/api/workbench/manage/WorkbenchManage.java ファイルの表示

@@ -319,6 +319,12 @@ public class WorkbenchManage {
.like(StrUtil.isNotBlank(req.getBuildOrg()), Project::getBuildOrgName, req.getBuildOrg())
.like(StrUtil.isNotBlank(req.getProjectName()), Project::getProjectName, req.getProjectName())
.in(Project::getBuildOrgCode, CollUtils.convert(viewUnitIds, String::valueOf))
// 评审金额
.ge(req.getReviewAmountMin() != null, Project::getReviewAmount, req.getReviewAmountMin())
.le(req.getReviewAmountMax() != null, Project::getReviewAmount, req.getReviewAmountMax())
// 下达金额
.ge(req.getApproveAmountMin() != null, Project::getApprovalAmount, req.getApproveAmountMin())
.ge(req.getApproveAmountMax() != null, Project::getApprovalAmount, req.getApproveAmountMax())
.eq(Project::getNewest, Boolean.TRUE)
.orderByDesc(Project::getUpdateOn);
switch (req.getProcessNode()) {
@@ -367,6 +373,8 @@ public class WorkbenchManage {
ProjectLibListItemVO item = new ProjectLibListItemVO();
item.setId(w.getId());
item.setProjectName(w.getProjectName());
// 项目申报
item.setFromType("1");
item.setProjectCode(w.getProjectCode());
item.setArea(w.getArea());
item.setAreaCode(w.getAreaCode());
@@ -382,6 +390,7 @@ public class WorkbenchManage {
item.setInstCode(w.getInstCode());
item.setIsHigherSuperOrg(w.getIsHigherSuperOrg());
item.setApprovedAmount(w.getApprovalAmount());
item.setReviewAmount(w.getReviewAmount());
item.setPrePlanProjectId(w.getPrePlanProjectId());
return item;
});


+ 14
- 0
hz-pm-api/src/main/java/com/hz/pm/api/workbench/model/WorkbenchProjectLibReq.java ファイルの表示

@@ -5,6 +5,8 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.math.BigDecimal;

import static com.hz.pm.api.projectlib.handle.AbstractProcessHandle.ProcessNode;

/**
@@ -34,4 +36,16 @@ public class WorkbenchProjectLibReq extends PagePo {
@ApiModelProperty("申报单位名称")
private String buildOrg;

@ApiModelProperty("评审金额最小值")
private BigDecimal reviewAmountMin;

@ApiModelProperty("评审金额最大值")
private BigDecimal reviewAmountMax;

@ApiModelProperty("批复金额")
private BigDecimal approveAmountMin;

@ApiModelProperty("批复金额")
private BigDecimal approveAmountMax;

}

+ 3
- 4
hz-pm-api/src/main/resources/application-dev.yml ファイルの表示

@@ -154,7 +154,7 @@ sa-token:
is-log: false

yxt:
# wsdl-url: http://115.239.137.23:9501/ws/v1?wsdl
# wsdl-url: http://115.239.137.23:9501/ws/v1?wsdl
wsdl-url: classpath:/wsdl.xml
#账号
user-code: hzndkj
@@ -194,9 +194,8 @@ auth-code:
agent-login:
proxy:
secret-key: nqkwiqojg7g4eiypr3rb8s7nb4noa8b2
sms:
client-url: http://10.54.38.13:8081/mh-gateway/auth-single

sms-send:
host: http://10.54.38.13:8081/mh-gateway/auth-single

# 提醒任务
reminder-task:


+ 4
- 0
hz-pm-api/src/main/resources/application-prod.yml ファイルの表示

@@ -194,6 +194,10 @@ mh:
upload-url: http://10.54.38.13:8081/mh-gateway/oss/oss/uploadFileSkipLogin
purchase-notice:
open: false

sms-send:
host: http://10.54.38.13:8081/mh-gateway/auth-single

sync-mh-company:
open: false
sync-mh-user:


+ 42
- 0
hz-pm-api/src/test/java/com/hz/pm/api/external/MobileCallClientTest.java ファイルの表示

@@ -0,0 +1,42 @@
package com.hz.pm.api.external;

import com.hz.pm.api.AppTests;
import com.hz.pm.api.common.helper.MsgCallHelper;
import com.hz.pm.api.common.util.EnvironmentUtil;
import com.hz.pm.api.external.model.dto.CallRetDTO;
import com.hz.pm.api.sys.model.enumeration.BizTypeEnum;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;

import java.util.Collection;
import java.util.Collections;

import static org.junit.jupiter.api.Assertions.*;

/**
* <p>
* MobileCallClientTest
* </p>
*
* @author WendyYang
* @since 16:34 2024/4/26
*/
class MobileCallClientTest extends AppTests {

@Autowired
private MsgCallHelper msgCallHelper;

@Test
void sendCall() {
String format = String.format("【杭州数字信创】尊敬的专家您好,%s现邀请您作为专家参加%s会议,会议时间:%s,会议地点:%s。 确认参加请按 1,拒绝参加请按 2。请您选择",
"信创办", "测试语音", "2024年1月1日 12:30 至 2024年1月1日 12:30", "杭州市");
String ret = msgCallHelper.sendCall("19530651430", format, BizTypeEnum.EXPERT_INVITE);
System.out.println(ret);
}

@Test
void replyCall() {
System.out.println(msgCallHelper.callReply("3086259928697016320"));
}
}

+ 4
- 2
hz-pm-api/src/test/resources/application-dev.yml ファイルの表示

@@ -193,5 +193,7 @@ auth-code:
agent-login:
proxy:
secret-key: nqkwiqojg7g4eiypr3rb8s7nb4noa8b2
sms:
client-url: http://10.54.38.13:8081/mh-gateway/auth-single
sms-send:
host: http://10.54.38.13:8081/mh-gateway/auth-single
mobile-call:
host: http://36.20.16.36:18181/blue_esl_api

+ 1
- 1
hz-pm-gen/src/main/java/com/hz/pm/gen/config/CodeGen.java ファイルの表示

@@ -55,7 +55,7 @@ public class CodeGen {
}

public static void main(String[] args) {
generate("WendyYang", "projectdeclared", PATH_YYD, "nd_project_review");
generate("WendyYang", "sys", PATH_YYD, "nd_msg_call_record");
}

}

読み込み中…
キャンセル
保存