cloudroam
2024-12-29 2390d668f77641f9b62e1e210d855df87ee7d390
src/main/java/com/mzl/flower/service/impl/sms/SmsTaskServiceImpl.java
@@ -1,25 +1,55 @@
package com.mzl.flower.service.impl.sms;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mzl.flower.config.OssProperties;
import com.mzl.flower.config.exception.ValidationException;
import com.mzl.flower.config.security.SecurityUtils;
import com.mzl.flower.constant.Constants;
import com.mzl.flower.dto.request.sms.SmsTaskDTO;
import com.mzl.flower.dto.request.sms.SmsTaskQueryDTO;
import com.mzl.flower.dto.request.sms.SmsUserDTO;
import com.mzl.flower.dto.response.sms.SmsSelectVO;
import com.mzl.flower.dto.response.sms.SmsTaskVO;
import com.mzl.flower.entity.SmsTaskDO;
import com.mzl.flower.entity.SmsTaskDetailDO;
import com.mzl.flower.entity.SmsTemplateDO;
import com.mzl.flower.mapper.SmsTaskDetailMapper;
import com.mzl.flower.mapper.SmsTaskMapper;
import com.mzl.flower.mapper.SmsTemplateMapper;
import com.mzl.flower.mapper.system.UserMapper;
import com.mzl.flower.service.sms.SmsTaskDetailService;
import com.mzl.flower.service.sms.SmsTaskService;
import com.mzl.flower.service.system.UserService;
import com.mzl.flower.utils.SmsUtil;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
 * <p>
 *  服务实现类
 * 服务实现类
 * </p>
 *
 * @author @TaoJie
@@ -29,20 +59,216 @@
@Transactional
@RequiredArgsConstructor
public class SmsTaskServiceImpl extends ServiceImpl<SmsTaskMapper, SmsTaskDO> implements SmsTaskService {
    private final OssProperties ossProperties;
    private final SmsTaskMapper smsTaskMapper;
    private final UserMapper userMapper;
    private final SmsTemplateMapper smsTemplateMapper;
    private final SmsTaskDetailMapper smsTaskDetailMapper;
    private final SmsTaskDetailService smsTaskDetailService;
    private static final Pattern PHONE_NUMBER_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
    private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
    public static boolean isValidPhoneNumber(String phoneNumber) {
        if (phoneNumber == null || phoneNumber.length() != 11) {
            return false;
        }
        Pattern pattern = Pattern.compile(PHONE_REGEX);
        Matcher matcher = pattern.matcher(phoneNumber);
        return matcher.matches();
    }
    @Override
    public void saveSmsTask(SmsTaskDTO smsTaskDTO) {
        //校验
        if (StringUtils.isEmpty(smsTaskDTO.getName())) {
            throw new ValidationException("短信名称不能为空");
        }
        if (StringUtils.isEmpty(smsTaskDTO.getSmsTemplateId())) {
            throw new ValidationException("短信模板不能为空");
        }
        if (StringUtils.isEmpty(smsTaskDTO.getType())) {
            throw new ValidationException("接收号码类型不能为空");
        }
        if (Constants.SMS_RECEIVE_TYPE.INPUT.name().equals(smsTaskDTO.getType()) && StringUtils.isEmpty(smsTaskDTO.getPhones())) {
            throw new ValidationException("手机号不能为空");
        }
        if (Constants.SMS_RECEIVE_TYPE.IMPORT.name().equals(smsTaskDTO.getType()) && StringUtils.isEmpty(smsTaskDTO.getFileUrl())) {
            throw new ValidationException("导入文件不能为空");
        }
        if (Constants.SMS_RECEIVE_TYPE.INPUT.name().equals(smsTaskDTO.getType()) ) {
            //解析手机号,包含不同平台的换行符
            String text = smsTaskDTO.getPhones();
            // 使用正则表达式匹配所有类型的换行符
            String[] lines = text.split("\\r?\\n|\\r");
            // 将数组转换为 List
            List<String> lineList = Arrays.asList(lines);
            lineList.forEach(l -> {
                boolean validPhoneNumber = isValidPhoneNumber(l);
                if (!validPhoneNumber) {
                    throw new ValidationException(l + "不是合法的手机号");
                }
            });
            smsTaskDTO.setNum((long) lineList.size());
        }
        if (Constants.SMS_RECEIVE_TYPE.IMPORT.name().equals(smsTaskDTO.getType())) {
            dealImportExcel(smsTaskDTO);
        }
        List<SmsUserDTO> smsUserDTOS = smsTaskDTO.getSmsUserDTOS();
        SmsTaskDO smsTaskDO = new SmsTaskDO();
        BeanUtils.copyProperties(smsTaskDTO, smsTaskDO);
        if(!CollectionUtils.isEmpty(smsUserDTOS)){
            List<String> userIds = smsUserDTOS.stream()
                    .map(SmsUserDTO::getUserId)
                    .collect(Collectors.toList());
            List<String> userPhones = smsUserDTOS.stream()
                    .map(SmsUserDTO::getUserPhone)
                    .collect(Collectors.toList());
            if(!CollectionUtils.isEmpty(userPhones)){
                String phones = userPhones.stream()
                        .map(Object::toString) // 确保每个元素都转换为字符串
                        .collect(Collectors.joining("\n")); // 使用换行符连接字符串
                smsTaskDTO.setPhones(phones); // 假设有一个setPhones方法用于设置phones字段
            }
            if (!CollectionUtils.isEmpty(userIds)) {
                String userIdInfos = userIds.stream().map(Object::toString) // 确保每个元素都转换为字符串
                        .collect(Collectors.joining(";")); // 使用换行符连接字符串
                smsTaskDO.setUserIds(userIdInfos);
            }
        }
        smsTaskDO.setStatus(Constants.SMS_TASK_STATUS.wait_publish.name());
        smsTaskDO.setPhones(smsTaskDTO.getPhones());
        smsTaskDO.create(SecurityUtils.getUserId());
        smsTaskMapper.insert(smsTaskDO);
    }
    private void dealImportExcel(SmsTaskDTO smsTaskDTO) {
        String endPoint = ossProperties.getEndpoint();
        String accessKeyId = ossProperties.getKeyid();
        String accessKeySecret = ossProperties.getKeysecret();
        String bucketName = ossProperties.getBucketname();
        OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
        try {
            // 下载Excel文件到本地临时文件
            File tempFile = File.createTempFile("temp", ".xlsx");
            String fileUrl = smsTaskDTO.getFileUrl();
            String objectKey = fileUrl.replaceFirst("^https?://[^/]+/", ""); // 去掉协议部分
            ossClient.getObject(new GetObjectRequest(bucketName, objectKey), tempFile);
            // 解析Excel文件
            try (FileInputStream inputStream = new FileInputStream(tempFile);
                 Workbook workbook = new XSSFWorkbook(inputStream)) {
                Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表
                int rowCount = sheet.getPhysicalNumberOfRows();
                if (rowCount > 101) {
                    throw new ValidationException("一次导入手机号最多100行");
                }
                boolean isValid = true;
                StringBuffer message = new StringBuffer();
                StringBuffer phones = new StringBuffer();
                for (int i = 1; i < rowCount; i++) { // 跳过标题行,从第二行开始
                    Row row = sheet.getRow(i);
                    if (row != null) {
                        Cell cell = row.getCell(0); // 假设手机号在第一列
                        if (cell != null ) {
                            String phoneNumber = "";
                            if (cell.getCellType() == CellType.STRING) {
                                phoneNumber = cell.getStringCellValue();
                            } else if (cell.getCellType() == CellType.NUMERIC) {
                                // 将数字类型的手机号转换为字符串
                                phoneNumber = String.valueOf((long) cell.getNumericCellValue());
                            }
                            if (!PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches()) {
                                message.append("第" + (i + 1) + "行手机号" + phoneNumber + "格式不正确");
                                isValid = false;
                                break; // 退出循环
                            } else {
                                phones.append(phoneNumber).append("\n");
                            }
                        } else {
                            message.append("第" + (i + 1)+ "行上的单元格为空或无效 ");
                            isValid = false;
                            break; // 退出循环
                        }
                    } else {
                        message.append("空行 " + (i + 1));
                        isValid = false;
                        break; // 退出循环
                    }
                }
                if (!isValid) {
                    throw new ValidationException(message.toString());
                } else {
                    smsTaskDTO.setPhones(phones.toString());
                    smsTaskDTO.setNum((long) rowCount - 1);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 删除临时文件
                if (tempFile.exists()) {
                    tempFile.delete();
                }
            }
        } catch (OSSException | ClientException | IOException e) {
            e.printStackTrace();
        } finally {
            ossClient.shutdown();
        }
    }
    @Override
    public void updateSmsTask(SmsTaskDTO smsTaskDTO) {
        SmsTaskDO smsTaskDO = smsTaskMapper.selectById(smsTaskDTO.getId());
        if (!smsTaskDO.getStatus().equals(Constants.SMS_TASK_STATUS.wait_publish.name())) {
            throw new ValidationException("非待发布的任务不可编辑");
        }
        if (StringUtils.isEmpty(smsTaskDTO.getName())) {
            throw new ValidationException("短信名称不能为空");
        }
        if (StringUtils.isEmpty(smsTaskDTO.getSmsTemplateId())) {
            throw new ValidationException("短信模板不能为空");
        }
        if (StringUtils.isEmpty(smsTaskDTO.getType())) {
            throw new ValidationException("接收号码类型不能为空");
        }
        if ((Constants.SMS_RECEIVE_TYPE.INPUT.name().equals(smsTaskDTO.getType()) || Constants.SMS_RECEIVE_TYPE.SELECT.name().equals(smsTaskDTO.getType())) && StringUtils.isEmpty(smsTaskDTO.getPhones())) {
            throw new ValidationException("手机号不能为空");
        }
        if (Constants.SMS_RECEIVE_TYPE.IMPORT.name().equals(smsTaskDTO.getType()) && StringUtils.isEmpty(smsTaskDTO.getFileUrl())) {
            throw new ValidationException("导入文件不能为空");
        }
        if(!smsTaskDTO.getFileUrl().equals(smsTaskDO.getFileUrl())){
            dealImportExcel(smsTaskDTO);
        }
        BeanUtils.copyProperties(smsTaskDTO, smsTaskDO);
        smsTaskDO.update(SecurityUtils.getUserId());
        smsTaskDO.setPhones(smsTaskDTO.getPhones());
        smsTaskMapper.updateById(smsTaskDO);
    }
@@ -51,6 +277,9 @@
        SmsTaskDO smsTaskDO = smsTaskMapper.selectById(id);
        if (smsTaskDO == null) {
            throw new ValidationException("短信任务ID不存在");
        }
        if(!smsTaskDO.getStatus().equals(Constants.SMS_TASK_STATUS.wait_publish.name())){
            throw new ValidationException("非待发布的任务不可删除");
        }
        smsTaskMapper.deleteById(id);
    }
@@ -61,4 +290,81 @@
        page.setRecords(list);
        return page;
    }
    @Override
    public void publishSmsTask(SmsTaskDTO smsTaskDTO) {
        if (StringUtils.isEmpty(smsTaskDTO.getId())) {
            throw new ValidationException("任务ID不能为空");
        }
        SmsTaskDO smsTaskDO = smsTaskMapper.selectById(smsTaskDTO.getId());
        if(StringUtils.isEmpty(smsTaskDO.getPhones())){
            throw new ValidationException("任务手机号不能为空");
        }
        //解析手机号,包含不同平台的换行符
        String text = smsTaskDO.getPhones();
        // 使用正则表达式匹配所有类型的换行符
        String[] lines = text.split("\\r?\\n|\\r");
        // 将数组转换为 List
        List<String> lineList = Arrays.asList(lines);
        List<SmsTaskDetailDO> smsTaskDetailDOList = createSmsTaskDetails(smsTaskDO, lineList);
        smsTaskDO.setStatus(Constants.SMS_TASK_STATUS.in_execution.name());
        smsTaskDO.update(SecurityUtils.getUserId());
        smsTaskMapper.updateById(smsTaskDO);
        // 异步保存任务明细信息并发送短信
        CompletableFuture.runAsync(() -> {
            smsTaskDetailService.saveBatch(smsTaskDetailDOList);
            sendSmsToAll(smsTaskDetailDOList, smsTaskDO.getSmsTemplateId());
        });
    }
    @Override
    public List<SmsSelectVO> getSelectList(Long id) {
        List<SmsSelectVO> smsSelectVOList = null;
        SmsTaskDO smsTaskDO = smsTaskMapper.selectById(id);
        String ids = smsTaskDO.getUserIds();
        if (StringUtils.isEmpty(ids)) {
            return smsSelectVOList;
        } else {
            String[] idArray = ids.split(";");
            List<String> idList = Arrays.asList(idArray);
            return userMapper.getSelectList(idList);
        }
    }
    private List<SmsTaskDetailDO> createSmsTaskDetails(SmsTaskDO smsTaskDO, List<String> phoneNumbers) {
        return phoneNumbers.stream().map(phone -> {
            SmsTaskDetailDO detail = new SmsTaskDetailDO();
            detail.setSmsTaskId(smsTaskDO.getId());
            detail.setSmsTemplateId(smsTaskDO.getSmsTemplateId());
            detail.setPhone(phone);
            detail.create();
            return detail;
        }).collect(Collectors.toList());
    }
    private void sendSmsToAll(List<SmsTaskDetailDO> smsTaskDetailDOList, Long smsTemplateId) {
        SmsTemplateDO smsTemplateDO = smsTemplateMapper.selectById(smsTemplateId);
        String templateCode = smsTemplateDO.getCode();
        for (SmsTaskDetailDO detail : smsTaskDetailDOList) {
            try {
                SendSmsResponse sendSmsResponse = SmsUtil.sendSms(detail.getPhone(), templateCode, null);
                if("OK".equals(sendSmsResponse.getCode())){
                    detail.setResult(Constants.SMS_SEND_RESULT.success.name());
                }else{
                    detail.setFailReason(sendSmsResponse.getMessage());
                    detail.setResult(Constants.SMS_SEND_RESULT.failure.name());
                }
                detail.setResponseResult(sendSmsResponse.toString());
            } catch (Exception e) {
                detail.setResult(Constants.SMS_SEND_RESULT.failure.name());
                System.err.println("Failed to send SMS to " + detail.getPhone() + ": " + e.getMessage());
            } finally {
                //无论如何都更新结果
                smsTaskDetailMapper.updateById(detail);
            }
        }
    }
}