|
|
@@ -13,9 +13,16 @@ import com.fs.common.core.redis.RedisCache;
|
|
|
import com.fs.common.exception.ServiceException;
|
|
|
import com.fs.common.utils.StringUtils;
|
|
|
import com.fs.company.domain.Company;
|
|
|
+import com.fs.company.domain.CompanySms;
|
|
|
+import com.fs.company.domain.CompanySmsLogs;
|
|
|
+import com.fs.company.domain.CompanySmsTemp;
|
|
|
import com.fs.company.domain.CompanyUser;
|
|
|
import com.fs.company.mapper.CompanyMapper;
|
|
|
import com.fs.company.mapper.CompanyUserMapper;
|
|
|
+import com.fs.company.service.ICompanySmsLogsService;
|
|
|
+import com.fs.company.service.ICompanySmsService;
|
|
|
+import com.fs.company.service.ICompanySmsTempService;
|
|
|
+import com.fs.course.config.CourseConfig;
|
|
|
import com.fs.course.domain.FsCourseWatchLog;
|
|
|
import com.fs.course.domain.FsUserCompanyUser;
|
|
|
import com.fs.course.domain.FsUserCourse;
|
|
|
@@ -27,6 +34,7 @@ import com.fs.course.mapper.FsCourseWatchLogMapper;
|
|
|
import com.fs.course.mapper.FsUserCompanyUserMapper;
|
|
|
import com.fs.course.mapper.FsUserCourseMapper;
|
|
|
import com.fs.fastGpt.service.AiHookService;
|
|
|
+import com.fs.his.config.FsSmsConfig;
|
|
|
import com.fs.his.domain.FsDoctor;
|
|
|
import com.fs.his.domain.FsFollow;
|
|
|
import com.fs.his.domain.FsUser;
|
|
|
@@ -34,18 +42,26 @@ import com.fs.his.dto.PayloadDTO;
|
|
|
import com.fs.his.mapper.FsDoctorMapper;
|
|
|
import com.fs.his.mapper.FsFollowMapper;
|
|
|
import com.fs.his.mapper.FsUserMapper;
|
|
|
+import com.fs.his.utils.PhoneUtil;
|
|
|
import com.fs.im.config.IMConfig;
|
|
|
import com.fs.im.domain.FsImMsgSendDetail;
|
|
|
import com.fs.im.domain.FsImMsgSendLog;
|
|
|
+import com.fs.im.domain.FsSmsShortLink;
|
|
|
import com.fs.im.domain.ImSendLog;
|
|
|
import com.fs.im.dto.*;
|
|
|
import com.fs.im.mapper.FsImMsgSendDetailMapper;
|
|
|
import com.fs.im.mapper.FsImMsgSendLogMapper;
|
|
|
+import com.fs.im.mapper.FsSmsShortLinkMapper;
|
|
|
import com.fs.im.mapper.ImSendLogMapper;
|
|
|
import com.fs.im.service.OpenIMService;
|
|
|
import com.fs.im.vo.OpenImMsgCallBackVO;
|
|
|
import com.fs.im.vo.OpenImResponseDTOTest;
|
|
|
import com.fs.qw.mapper.QwExternalContactMapper;
|
|
|
+import com.fs.sms.domain.SendSmsReturn;
|
|
|
+import com.fs.sms.service.impl.SmsTServiceImpl;
|
|
|
+import com.fs.system.domain.SysConfig;
|
|
|
+import com.fs.system.mapper.SysConfigMapper;
|
|
|
+import com.fs.system.service.ISysConfigService;
|
|
|
import com.github.pagehelper.util.StringUtil;
|
|
|
import com.google.common.base.Joiner;
|
|
|
import lombok.Data;
|
|
|
@@ -61,6 +77,7 @@ import org.springframework.scheduling.annotation.Async;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.util.CollectionUtils;
|
|
|
+import java.net.URLEncoder;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.*;
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
@@ -69,6 +86,7 @@ import java.util.stream.Collectors;
|
|
|
@Service
|
|
|
@Slf4j
|
|
|
public class OpenIMServiceImpl implements OpenIMService {
|
|
|
+ private static final String SMS_SHORT_LINK_DOMAIN = "https://a.hbhdt.top";
|
|
|
@Autowired
|
|
|
IMConfig imConfig;
|
|
|
@Autowired
|
|
|
@@ -104,6 +122,20 @@ public class OpenIMServiceImpl implements OpenIMService {
|
|
|
private FsImMsgSendDetailServiceImpl fsImMsgSendDetailServiceImpl;
|
|
|
@Autowired
|
|
|
private FsCourseWatchLogMapper fsCourseWatchLogMapper;
|
|
|
+ @Autowired
|
|
|
+ private FsSmsShortLinkMapper fsSmsShortLinkMapper;
|
|
|
+ @Autowired
|
|
|
+ private ICompanySmsService companySmsService;
|
|
|
+ @Autowired
|
|
|
+ private ICompanySmsLogsService companySmsLogsService;
|
|
|
+ @Autowired
|
|
|
+ private ICompanySmsTempService companySmsTempService;
|
|
|
+ @Autowired
|
|
|
+ private SmsTServiceImpl smsTService;
|
|
|
+ @Autowired
|
|
|
+ private SysConfigMapper sysConfigMapper;
|
|
|
+ @Autowired
|
|
|
+ private ISysConfigService configService;
|
|
|
|
|
|
|
|
|
// @Value("${openIM.prefix}")
|
|
|
@@ -1257,6 +1289,290 @@ public class OpenIMServiceImpl implements OpenIMService {
|
|
|
return finalResponseDTO;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public OpenImResponseDTO batchSendSmsLink(BatchSmsLinkDTO batchSmsLinkDTO) {
|
|
|
+ OpenImResponseDTO responseDTO = new OpenImResponseDTO();
|
|
|
+ if (batchSmsLinkDTO == null || batchSmsLinkDTO.getType() == null || CollectionUtils.isEmpty(batchSmsLinkDTO.getUserIds())) {
|
|
|
+ responseDTO.setErrCode(400);
|
|
|
+ responseDTO.setErrMsg("参数不能为空");
|
|
|
+ return responseDTO;
|
|
|
+ }
|
|
|
+ if (!Objects.equals(batchSmsLinkDTO.getType(), 1) && !Objects.equals(batchSmsLinkDTO.getType(), 2)) {
|
|
|
+ responseDTO.setErrCode(400);
|
|
|
+ responseDTO.setErrMsg("短信链接类型不正确");
|
|
|
+ return responseDTO;
|
|
|
+ }
|
|
|
+ String tempCode = getSmsLinkTempCode(batchSmsLinkDTO.getType());
|
|
|
+ if (Objects.equals(batchSmsLinkDTO.getType(), 1)
|
|
|
+ && (batchSmsLinkDTO.getPeriodId() == null || batchSmsLinkDTO.getVideoId() == null || batchSmsLinkDTO.getCourseId() == null || batchSmsLinkDTO.getCompanyUserId() == null)) {
|
|
|
+ responseDTO.setErrCode(400);
|
|
|
+ responseDTO.setErrMsg("短信发课参数不完整");
|
|
|
+ return responseDTO;
|
|
|
+ }
|
|
|
+ CompanySmsTemp smsTemp = companySmsTempService.selectCompanySmsTempByCode(tempCode);
|
|
|
+ if (smsTemp == null) {
|
|
|
+ responseDTO.setErrCode(400);
|
|
|
+ responseDTO.setErrMsg("短信模板不存在");
|
|
|
+ return responseDTO;
|
|
|
+ }
|
|
|
+ if (!Integer.valueOf(1).equals(smsTemp.getStatus()) || (smsTemp.getIsAudit() != null && !Integer.valueOf(1).equals(smsTemp.getIsAudit()))) {
|
|
|
+ responseDTO.setErrCode(400);
|
|
|
+ responseDTO.setErrMsg("短信模板未启用或未审核");
|
|
|
+ return responseDTO;
|
|
|
+ }
|
|
|
+ int successCount = 0;
|
|
|
+ int failCount = 0;
|
|
|
+ List<Map<String, Object>> resultList = new ArrayList<>();
|
|
|
+ for (Long userId : batchSmsLinkDTO.getUserIds()) {
|
|
|
+ FsUser fsUser = fsUserMapper.selectFsUserByUserId(userId);
|
|
|
+ String phone = getUserPlainPhone(fsUser);
|
|
|
+ if (fsUser == null || StringUtils.isEmpty(phone)) {
|
|
|
+ failCount++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ FsSmsShortLink shortLink = createSmsShortLink(batchSmsLinkDTO, fsUser, phone);
|
|
|
+ fsSmsShortLinkMapper.insertFsSmsShortLink(shortLink);
|
|
|
+
|
|
|
+ String smsContent = buildSmsContent(smsTemp, shortLink.getShortLink());
|
|
|
+ SendSmsResult sendSmsResult = sendSmsByConfig(smsTemp, batchSmsLinkDTO.getCompanyId(), phone, smsContent);
|
|
|
+ String actualSmsContent = StringUtils.isEmpty(sendSmsResult.content) ? smsContent : sendSmsResult.content;
|
|
|
+ shortLink.setSmsContent(actualSmsContent);
|
|
|
+ shortLink.setSendStatus(sendSmsResult.success ? 1 : 2);
|
|
|
+ shortLink.setSendMsgId(sendSmsResult.msgId);
|
|
|
+ shortLink.setSendResult(sendSmsResult.message);
|
|
|
+ shortLink.setUpdateTime(new Date());
|
|
|
+ fsSmsShortLinkMapper.updateFsSmsShortLink(shortLink);
|
|
|
+
|
|
|
+ saveSmsLog(smsTemp, batchSmsLinkDTO, fsUser, phone, actualSmsContent, sendSmsResult);
|
|
|
+ if (sendSmsResult.success) {
|
|
|
+ companySmsService.subCompanySms(batchSmsLinkDTO.getCompanyId(), getSmsCount(actualSmsContent));
|
|
|
+ successCount++;
|
|
|
+ } else {
|
|
|
+ failCount++;
|
|
|
+ }
|
|
|
+ Map<String, Object> item = new HashMap<>();
|
|
|
+ item.put("userId", userId);
|
|
|
+ item.put("phone", phone);
|
|
|
+ item.put("linkCode", shortLink.getLinkCode());
|
|
|
+ item.put("shortLink", shortLink.getShortLink());
|
|
|
+ item.put("success", sendSmsResult.success);
|
|
|
+ item.put("message", sendSmsResult.message);
|
|
|
+ resultList.add(item);
|
|
|
+ }
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ data.put("successCount", successCount);
|
|
|
+ data.put("failCount", failCount);
|
|
|
+ data.put("list", resultList);
|
|
|
+ responseDTO.setErrCode(0);
|
|
|
+ responseDTO.setErrMsg("短信发送完成");
|
|
|
+ responseDTO.setData(data);
|
|
|
+ return responseDTO;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R getSmsShortLinkDetail(String linkCode) {
|
|
|
+ if (StringUtils.isEmpty(linkCode)) {
|
|
|
+ return R.error("短链参数不能为空");
|
|
|
+ }
|
|
|
+ FsSmsShortLink shortLink = fsSmsShortLinkMapper.selectFsSmsShortLinkByCode(linkCode);
|
|
|
+ if (shortLink == null) {
|
|
|
+ return R.error("短链不存在");
|
|
|
+ }
|
|
|
+ if (shortLink.getExpireTime() != null && shortLink.getExpireTime().before(new Date())) {
|
|
|
+ return R.error("短链已过期");
|
|
|
+ }
|
|
|
+ return R.ok().put("data", shortLink);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getSmsLinkTempCode(Integer type) {
|
|
|
+ return Objects.equals(type, 1) ? "sms_course" : "sms_link";
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getUserPlainPhone(FsUser fsUser) {
|
|
|
+ if (fsUser == null || StringUtils.isEmpty(fsUser.getPhone())) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String phone = fsUser.getPhone();
|
|
|
+ if (phone.length() > 11) {
|
|
|
+ String decryptPhone = PhoneUtil.decryptPhone(phone);
|
|
|
+ return StringUtils.isEmpty(decryptPhone) ? phone : decryptPhone;
|
|
|
+ }
|
|
|
+ return phone;
|
|
|
+ }
|
|
|
+
|
|
|
+ private FsSmsShortLink createSmsShortLink(BatchSmsLinkDTO param, FsUser fsUser, String phone) {
|
|
|
+ FsSmsShortLink shortLink = new FsSmsShortLink();
|
|
|
+ shortLink.setLinkType(param.getType());
|
|
|
+ shortLink.setCompanyId(param.getCompanyId());
|
|
|
+ shortLink.setCompanyUserId(param.getCompanyUserId());
|
|
|
+ shortLink.setUserId(fsUser.getUserId());
|
|
|
+ shortLink.setPhone(phone);
|
|
|
+ shortLink.setPeriodId(param.getPeriodId());
|
|
|
+ shortLink.setCourseId(param.getCourseId());
|
|
|
+ shortLink.setVideoId(param.getVideoId());
|
|
|
+ shortLink.setPeriodCourseId(param.getId());
|
|
|
+ shortLink.setProjectId(param.getProjectId());
|
|
|
+ shortLink.setLinkCode(generateSmsLinkCode(param.getType()));
|
|
|
+ String customerAcquisitionLink = null;
|
|
|
+ if (Objects.equals(param.getType(), 2)) {
|
|
|
+ CompanyUser companyUser = companyUserMapper.selectCompanyUserById(param.getCompanyUserId());
|
|
|
+ customerAcquisitionLink = companyUser == null ? null : companyUser.getCustomerAcquisitionLink();
|
|
|
+ shortLink.setCustomerAcquisitionLink(customerAcquisitionLink);
|
|
|
+ }
|
|
|
+ shortLink.setRealLink(buildSmsRealLink(param, customerAcquisitionLink));
|
|
|
+ shortLink.setShortLink(buildSmsShortLink(shortLink.getLinkCode()));
|
|
|
+ shortLink.setSendStatus(0);
|
|
|
+ Date now = new Date();
|
|
|
+ shortLink.setCreateTime(now);
|
|
|
+ shortLink.setUpdateTime(now);
|
|
|
+ if (param.getEffectiveDuration() != null && param.getEffectiveDuration() > 0) {
|
|
|
+ shortLink.setExpireTime(new Date(now.getTime() + TimeUnit.MINUTES.toMillis(param.getEffectiveDuration())));
|
|
|
+ }
|
|
|
+ return shortLink;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateSmsLinkCode(Integer type) {
|
|
|
+ String prefix = Objects.equals(type, 1) ? "W" : "U";
|
|
|
+ for (int i = 0; i < 5; i++) {
|
|
|
+ String code = prefix + Long.toString(System.currentTimeMillis(), 36) + randomShortCode(4);
|
|
|
+ if (fsSmsShortLinkMapper.selectFsSmsShortLinkByCode(code) == null) {
|
|
|
+ return code;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return prefix + Long.toString(System.nanoTime(), 36) + randomShortCode(4);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String randomShortCode(int length) {
|
|
|
+ String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
+ StringBuilder builder = new StringBuilder(length);
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
+ builder.append(chars.charAt(ThreadLocalRandom.current().nextInt(chars.length())));
|
|
|
+ }
|
|
|
+ return builder.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildSmsRealLink(BatchSmsLinkDTO param, String customerAcquisitionLink) {
|
|
|
+ if (Objects.equals(param.getType(), 2)) {
|
|
|
+ return StringUtils.isEmpty(customerAcquisitionLink) ? param.getH5Url() : customerAcquisitionLink;
|
|
|
+ }
|
|
|
+ return "/courseH5/pages/course/learning?periodId=" + param.getPeriodId()
|
|
|
+ + "&videoId=" + param.getVideoId()
|
|
|
+ + "&courseId=" + param.getCourseId()
|
|
|
+ + "&companyUserId=" + param.getCompanyUserId()
|
|
|
+ + (param.getId() == null ? "" : "&id=" + param.getId())
|
|
|
+ + (param.getProjectId() == null ? "" : "&projectId=" + param.getProjectId());
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildSmsShortLink(String linkCode) {
|
|
|
+ return SMS_SHORT_LINK_DOMAIN + "/" + linkCode;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildSmsContent(CompanySmsTemp smsTemp, String shortLink) {
|
|
|
+ String content = smsTemp.getContent();
|
|
|
+ if (StringUtils.isEmpty(content)) {
|
|
|
+ content = "${sms.link}";
|
|
|
+ }
|
|
|
+ content = content.replace("${sms.link}", shortLink);
|
|
|
+ content = content.replace("${link}", shortLink);
|
|
|
+ if (!content.contains(shortLink)) {
|
|
|
+ content = content + shortLink;
|
|
|
+ }
|
|
|
+ return content;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildSmsSendContent(String content, FsSmsConfig sms) {
|
|
|
+ String sign = "rf".equals(sms.getType()) ? sms.getRfSign() : sms.getDhSign();
|
|
|
+ if (StringUtils.isEmpty(sign)) {
|
|
|
+ sign = "";
|
|
|
+ }
|
|
|
+ if (content.contains("${sms.sign}")) {
|
|
|
+ return content.replace("${sms.sign}", sign);
|
|
|
+ }
|
|
|
+ return sign + content;
|
|
|
+ }
|
|
|
+
|
|
|
+ private SendSmsResult sendSmsByConfig(CompanySmsTemp smsTemp, Long companyId, String phone, String content) {
|
|
|
+ SendSmsResult result = new SendSmsResult();
|
|
|
+ try {
|
|
|
+ SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.sms");
|
|
|
+ FsSmsConfig sms = JSON.parseObject(sysConfig.getConfigValue(), FsSmsConfig.class);
|
|
|
+ result.smsType = sms.getType();
|
|
|
+ String sendContent = buildSmsSendContent(content, sms);
|
|
|
+ result.content = sendContent;
|
|
|
+ if ("rf".equals(sms.getType())) {
|
|
|
+ String urls;
|
|
|
+ if (Integer.valueOf(1).equals(smsTemp.getTempType())) {
|
|
|
+ urls = sms.getRfUrl1() + "sms?action=send&account=" + sms.getRfAccount1()
|
|
|
+ + "&password=" + sms.getRfPassword1()
|
|
|
+ + "&mobile=" + phone
|
|
|
+ + "&content=" + URLEncoder.encode(sendContent, "UTF-8")
|
|
|
+ + "&extno=" + sms.getRfCode1()
|
|
|
+ + "&rt=json";
|
|
|
+ } else {
|
|
|
+ urls = sms.getRfUrl2() + "sms?action=send&account=" + sms.getRfAccount2()
|
|
|
+ + "&password=" + sms.getRfPassword2()
|
|
|
+ + "&mobile=" + phone
|
|
|
+ + "&content=" + URLEncoder.encode(sendContent + "拒收请回复R", "UTF-8")
|
|
|
+ + "&extno=" + sms.getRfCode2()
|
|
|
+ + "&rt=json";
|
|
|
+ }
|
|
|
+ String post = HttpRequest.get(urls).execute().body();
|
|
|
+ cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(post);
|
|
|
+ result.success = Objects.equals(jsonObject.getInt("status"), 0);
|
|
|
+ result.message = post;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ SendSmsReturn sendSmsReturn;
|
|
|
+ if (Integer.valueOf(1).equals(smsTemp.getTempType())) {
|
|
|
+ sendSmsReturn = smsTService.sendSms(sms.getDhAccount1(), sms.getDhPassword1(), sendContent, phone);
|
|
|
+ } else {
|
|
|
+ sendSmsReturn = smsTService.sendSms(sms.getDhAccount2(), sms.getDhPassword2(), sendContent + "拒收请回复R", phone);
|
|
|
+ }
|
|
|
+ result.success = sendSmsReturn != null && "0".equals(sendSmsReturn.getResult());
|
|
|
+ result.msgId = sendSmsReturn == null ? null : sendSmsReturn.getMsgid();
|
|
|
+ result.message = sendSmsReturn == null ? "短信发送无响应" : sendSmsReturn.getDesc();
|
|
|
+ } catch (Exception e) {
|
|
|
+ result.success = false;
|
|
|
+ result.message = e.getMessage();
|
|
|
+ log.error("短信短链发送失败,companyId:{},phone:{}", companyId, phone, e);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void saveSmsLog(CompanySmsTemp smsTemp, BatchSmsLinkDTO param, FsUser fsUser, String phone, String content, SendSmsResult sendSmsResult) {
|
|
|
+ CompanySmsLogs logs = new CompanySmsLogs();
|
|
|
+ logs.setPhone(phone);
|
|
|
+ logs.setContent(content);
|
|
|
+ logs.setTempCode(smsTemp.getTempCode());
|
|
|
+ logs.setTempId(smsTemp.getTempId());
|
|
|
+ logs.setSendTime(new Date());
|
|
|
+ logs.setStatus(sendSmsResult.success ? 0 : -1);
|
|
|
+ logs.setMid(sendSmsResult.msgId);
|
|
|
+ logs.setNumber(getSmsCount(content));
|
|
|
+ logs.setType(sendSmsResult.smsType);
|
|
|
+ companySmsLogsService.insertCompanySmsLogs(logs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private int getSmsCount(String content) {
|
|
|
+ if (StringUtils.isEmpty(content)) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ int counts = content.length() / 67;
|
|
|
+ if (content.length() % 67 > 0) {
|
|
|
+ counts++;
|
|
|
+ }
|
|
|
+ return counts == 0 ? 1 : counts;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class SendSmsResult {
|
|
|
+ private boolean success;
|
|
|
+ private String msgId;
|
|
|
+ private String message;
|
|
|
+ private String smsType;
|
|
|
+ private String content;
|
|
|
+ }
|
|
|
+
|
|
|
private OpenImBatchMsgDTO makeOpenImBatchMsgDTO(BatchSendCourseDTO batchSendCourseDTO, String courseUrl, ObjectMapper objectMapper, List<String> userIds, long planSendTimeStamp, String logType) throws JsonProcessingException {
|
|
|
PayloadDTO.Extension extension = new PayloadDTO.Extension();
|
|
|
OpenImBatchMsgDTO openImBatchMsgDTO = new OpenImBatchMsgDTO();
|