zhujie
2025-04-11 c6bb4daa335c7615610ca0f7e404ca7aa2825dce
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
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.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.ObjectUtils;
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
 * @since 2024-12-25
 */
@Service
@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) throws IOException {
        //校验
        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);
                smsTaskDTO.setNum((long) userIds.size());
            }
        }
        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) throws IOException {
        String fileUrlMessage = "";
        // 创建ObjectMapper实例
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = objectMapper.readTree(smsTaskDTO.getFileUrl());
        if (rootNode.isArray()) {
            JsonNode firstElement = rootNode.get(0);
            if (firstElement.has("url")) {
                 fileUrlMessage = firstElement.get("url").asText();
            } else {
                throw new ValidationException("URL字段不存在");
            }
        } else {
            throw new ValidationException("JSON数组为空或不是数组");
        }
 
        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 = fileUrlMessage;
            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) throws IOException {
        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()) && 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()) && !smsTaskDTO.getFileUrl().equals(smsTaskDO.getFileUrl())){
            dealImportExcel(smsTaskDTO);
        }
        List<SmsUserDTO> smsUserDTOS = smsTaskDTO.getSmsUserDTOS();
        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);
                smsTaskDTO.setNum((long) userIds.size());
            }
        }
        BeanUtils.copyProperties(smsTaskDTO, smsTaskDO,"userIds");
        smsTaskDO.update(SecurityUtils.getUserId());
        smsTaskDO.setPhones(smsTaskDTO.getPhones());
        smsTaskMapper.updateById(smsTaskDO);
    }
 
    @Override
    public void deleteSmsTask(Long id) {
        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);
    }
 
    @Override
    public Page<SmsTaskVO> queryPage(SmsTaskQueryDTO dto, Page page) {
        List<SmsTaskVO> list = smsTaskMapper.queryPage(dto, page);
        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(),smsTaskDTO.getId());
        });
    }
 
    @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);
        }
    }
 
    @Override
    public SmsTaskVO getDetailById(Long id) {
        SmsTaskDO smsTaskDO = smsTaskMapper.selectById(id);
        SmsTaskVO smsTaskVO=new SmsTaskVO();
        BeanUtils.copyProperties(smsTaskDO,smsTaskVO);
        if(!ObjectUtils.isEmpty(smsTaskDO)){
            List<SmsSelectVO> selectList = getSelectList(id);
            smsTaskVO.setSmsUserDTOS(selectList);
            return smsTaskVO;
        }
        return null;
    }
 
    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,Long id) {
        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);
                SmsTaskDO smsTaskDO = smsTaskMapper.selectById(id);
                smsTaskDO.setStatus(Constants.SMS_TASK_STATUS.complete.name());
                smsTaskMapper.updateById(smsTaskDO);
            }
        }
    }
 
}