feat: 添加工单提醒任务

This commit is contained in:
曹鹏飞 2025-02-03 16:01:20 +08:00
parent e9e9762424
commit 233cd58630
19 changed files with 334 additions and 71 deletions

View File

@ -0,0 +1,21 @@
package com.nflg.mobilebroken.admin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
@EnableScheduling
public class TaskSchedulerConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 设置线程池大小
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.initialize();
return scheduler;
}
}

View File

@ -0,0 +1,24 @@
package com.nflg.mobilebroken.admin.service;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import com.nflg.mobilebroken.starter.service.INotifyPushService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component
public class NotificationService {
@Resource
private List<INotifyPushService> notifyPushServices;
public void sendNotifications(UserDTO user, String subject, String content) {
for (INotifyPushService service : notifyPushServices) {
if (service.check(user)) {
continue;
}
service.push(user, subject, content);
}
}
}

View File

@ -1,10 +1,11 @@
package com.nflg.mobilebroken.starter.service.impl;
package com.nflg.mobilebroken.admin.service.impl;
import cn.hutool.core.util.StrUtil;
import com.nflg.mobilebroken.common.constant.STATE;
import com.nflg.mobilebroken.common.exception.NflgException;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import com.nflg.mobilebroken.starter.service.EmailService;
import com.nflg.mobilebroken.starter.service.NotifyPushService;
import com.nflg.mobilebroken.starter.service.INotifyPushService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -12,7 +13,7 @@ import javax.annotation.Resource;
@Service
@Slf4j
public class EmailNotifyPushService implements NotifyPushService {
public class EmailINotifyPushService implements INotifyPushService {
@Resource
private EmailService emailService;
@ -26,4 +27,9 @@ public class EmailNotifyPushService implements NotifyPushService {
throw new NflgException(STATE.BusinessError, "发送邮件失败");
}
}
@Override
public boolean check(UserDTO user) {
return StrUtil.isNotBlank(user.getEmail());
}
}

View File

@ -0,0 +1,22 @@
package com.nflg.mobilebroken.admin.service.impl;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import com.nflg.mobilebroken.starter.service.INotifyPushService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class SSEINotifyPushService implements INotifyPushService {
@Override
public void push(UserDTO user, String subject, String content) {
//TODO
log.error("尚未实现");
}
@Override
public boolean check(UserDTO user) {
return false;
}
}

View File

@ -0,0 +1,47 @@
package com.nflg.mobilebroken.admin.service.impl;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import com.nflg.mobilebroken.starter.service.INotifyPushService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class WXINotifyPushService implements INotifyPushService {
@Value("${wx.mp.templateMessage.config.templateId}")
private String templateId;
@Resource
private WxMpService wxMpService;
@Resource
private WxMpConfigStorage wxMpConfigStorage;
@Override
public void push(UserDTO user,String subject, String content) {
//TODO
log.error("尚未实现");
// WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
// .toUser(user.getWxOpenId()) // 接收用户的 OpenID
// .templateId(templateId) // 模板消息 ID
// .build();
// templateMessage.addData(new WxMpTemplateData("first", "您好,这是一条测试消息", "#FF0000"));
// try {
// wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
// }catch (Exception ex){
// log.error("发送微信模板消息失败",ex);
// throw new NflgException(STATE.BusinessError, "发送微信模板消息失败");
// }
}
@Override
public boolean check(UserDTO user) {
return false;
}
}

View File

@ -0,0 +1,126 @@
package com.nflg.mobilebroken.admin.task;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.nflg.mobilebroken.admin.service.NotificationService;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import com.nflg.mobilebroken.repository.entity.AdminUser;
import com.nflg.mobilebroken.repository.entity.AppUser;
import com.nflg.mobilebroken.repository.entity.Ticket;
import com.nflg.mobilebroken.repository.service.IAdminUserService;
import com.nflg.mobilebroken.repository.service.IAppUserService;
import com.nflg.mobilebroken.repository.service.ITicketFollowService;
import com.nflg.mobilebroken.repository.service.ITicketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Component
@Slf4j
public class TicketScheduledTasks {
@Resource
private ITicketService ticketService;
@Resource
private IAdminUserService adminUserService;
@Resource
private IAppUserService appUserService;
@Resource
private ITicketFollowService ticketFollowService;
@Resource
private NotificationService notificationService;
/**
* 工单超时提醒
* 每天午夜12点执行一次
*/
@Scheduled(cron = "0 0 0 * * ?")
public void timeoutRemind() {
log.info("开始执行工单超时提醒");
emergencyRemind();
generalRemind();
nonemergencyRemind();
log.info("执行工单超时提醒完成");
}
private void emergencyRemind() {
int days = 3;
log.info("获取状态为紧急且{}天未解决的工单", days);
List<Ticket> tickets = ticketService.getEmergencys(days);
log.info("共{}条数据", tickets.size());
tickets.forEach(this::ticketHande);
}
private void generalRemind() {
int days = 7;
log.info("获取状态为普通且{}天未解决的工单", days);
List<Ticket> tickets = ticketService.getGenerals(days);
log.info("共{}条数据", tickets.size());
tickets.forEach(this::ticketHande);
}
private void nonemergencyRemind() {
int days = 15;
log.info("获取状态为普通且{}天未解决的工单", days);
List<Ticket> tickets = ticketService.getNonemergency(days);
log.info("共{}条数据", tickets.size());
tickets.forEach(this::ticketHande);
}
private void ticketHande(Ticket ticket) {
//给处理人发提醒
List<Integer> handleUserIds = Arrays.stream(ticket.getHandle().split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());
List<AdminUser> adminUsers = adminUserService.listByIds(handleUserIds);
if (CollectionUtil.isNotEmpty(adminUsers)) {
String subject = "该问题工单截止目前还未关闭,请跟进处理!";
String content = "工单({})已超时未处理";
adminUsers.forEach(adminUser -> {
sendToAdmin(adminUser, subject, StrUtil.format(content, ticket.getTitle()));
});
}
//给创建人发提醒
AppUser appUser = appUserService.getById(ticket.getUserId());
if (Objects.nonNull(appUser)) {
String subject = "该问题工单截止目前还未关闭,请跟进处理!";
String content = "工单({})已超时未处理";
sendToApp(appUser, subject, StrUtil.format(content, ticket.getTitle()));
}
//给关注人发提醒
List<Integer> followUserIds = ticketFollowService.getUsers(ticket.getId());
followUserIds.remove(appUser.getId());
if (CollectionUtil.isNotEmpty(followUserIds)) {
List<AppUser> followUsers = appUserService.listByIds(followUserIds);
String subject = "该问题工单截止目前还未关闭,请跟进处理!";
String content = "工单({})已超时未处理";
followUsers.forEach(u -> {
sendToApp(u, subject, StrUtil.format(content, ticket.getTitle()));
});
}
}
private void sendToAdmin(AdminUser user, String subject, String content) {
notificationService.sendNotifications(new UserDTO()
.setName(user.getUserName())
.setEmail(user.getEmail())
.setWxOpenId(user.getOpenid()), subject, content);
}
private void sendToApp(AppUser user, String subject, String content) {
notificationService.sendNotifications(new UserDTO()
.setName(user.getName())
.setEmail(user.getEmail())
.setWxOpenId(user.getOpenid()), subject, content);
}
}

View File

@ -4,7 +4,6 @@ import cn.hutool.core.util.StrUtil;
import com.nflg.mobilebroken.common.constant.TicketState;
import com.nflg.mobilebroken.common.pojo.ApiResult;
import com.nflg.mobilebroken.common.pojo.PageData;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import com.nflg.mobilebroken.common.pojo.request.*;
import com.nflg.mobilebroken.common.pojo.vo.*;
import com.nflg.mobilebroken.common.util.AppUserUtil;
@ -99,7 +98,7 @@ public class TiketController extends ControllerBase {
**/
@PostMapping("searchTickets")
public ApiResult<PageData<TicketVO>> searchTickets(@Valid @RequestBody TicketSearchRequest request){
return ApiResult.success(ticketService.search(request, new UserDTO().setId(1).setCompanyId(1)));
return ApiResult.success(ticketService.search(request, AppUserUtil.getUser()));
}
/**

View File

@ -3,6 +3,8 @@ package com.nflg.mobilebroken.common.pojo.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
public class UserDTO {
@ -20,5 +22,5 @@ public class UserDTO {
private String email;
//公司id
private Integer companyId;
private List<Integer> companyIds;
}

View File

@ -1,5 +1,9 @@
package com.nflg.mobilebroken.common.util;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import java.util.List;
public class AppUserUtil {
public static Integer getUserId() {
@ -16,4 +20,18 @@ public class AppUserUtil {
// return (String) SaTokenAppUtil.getExtra("email");
return "aa@gmail.com";
}
public static List<Integer> getCompanyIds() {
// return (List<Integer>) SaTokenAppUtil.getExtra("companyIds");
return List.of(1);
}
public static UserDTO getUser() {
UserDTO user = new UserDTO();
user.setId(getUserId());
user.setName(getUserName());
user.setEmail(getEmail());
user.setCompanyIds(getCompanyIds());
return user;
}
}

View File

@ -70,6 +70,11 @@ public class AppUser implements Serializable {
*/
private Integer areaId;
/**
* 微信openid
*/
private String openid;
/**
* 公司id
*/

View File

@ -24,7 +24,7 @@ public interface TicketMapper extends BaseMapper<Ticket> {
IPage<TicketVO> searchFollow(IPage<?> page, TicketSearchRequest request, Integer userId);
IPage<TicketVO> searchArea(IPage<?> page, TicketSearchRequest request, Integer companyId);
IPage<TicketVO> searchArea(IPage<?> page, TicketSearchRequest request, List<Integer> companyIds);
void searchFromAdmin(AdminTicketSearchRequest request, IPage<AdminTicketVO> page);

View File

@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.nflg.mobilebroken.common.pojo.request.FollowRequest;
import com.nflg.mobilebroken.repository.entity.TicketFollow;
import java.util.List;
/**
* <p>
* 工单-关注 服务类
@ -15,4 +17,6 @@ import com.nflg.mobilebroken.repository.entity.TicketFollow;
public interface ITicketFollowService extends IService<TicketFollow> {
void handle(FollowRequest request, Integer userId);
List<Integer> getUsers(Integer id);
}

View File

@ -36,4 +36,10 @@ public interface ITicketService extends IService<Ticket> {
void closeTicket(List<Integer> ids);
List<AdminTicketVO> search(AdminTicketSearchRequest request);
List<Ticket> getEmergencys(int days);
List<Ticket> getGenerals(int days);
List<Ticket> getNonemergency(int days);
}

View File

@ -2,7 +2,6 @@ package com.nflg.mobilebroken.repository.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import com.nflg.mobilebroken.common.pojo.request.FollowRequest;
import com.nflg.mobilebroken.repository.entity.TicketFollow;
import com.nflg.mobilebroken.repository.mapper.TicketFollowMapper;
@ -10,7 +9,9 @@ import com.nflg.mobilebroken.repository.service.ITicketFollowService;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* <p>
@ -44,4 +45,14 @@ public class TicketFollowServiceImpl extends ServiceImpl<TicketFollowMapper, Tic
this.remove(lambdaQueryWrapper);
}
}
@Override
public List<Integer> getUsers(Integer id) {
return lambdaQuery()
.eq(TicketFollow::getTicketId, id)
.list()
.stream()
.map(TicketFollow::getUserId)
.collect(Collectors.toList());
}
}

View File

@ -65,7 +65,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
}else if (request.getType()==2){
return baseMapper.searchFollow(new Page<>(request.getPageNum(), request.getPageSize()), request, user.getId());
}else if (request.getType()==3) {
return baseMapper.searchArea(new Page<>(request.getPageNum(), request.getPageSize()), request, user.getCompanyId());
return baseMapper.searchArea(new Page<>(request.getPageNum(), request.getPageSize()), request, user.getCompanyIds());
}
return null;
}
@ -112,4 +112,31 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
return baseMapper.searchAllFromAdmin(request);
}
}
@Override
public List<Ticket> getEmergencys(int days) {
return lambdaQuery()
.eq(Ticket::getState, TicketState.Processing.getState())
.eq(Ticket::getUrgency, TicketUrgency.URGENCY.getState().byteValue())
.ge(Ticket::getCreateTime, LocalDateTime.now().minusDays(days))
.list();
}
@Override
public List<Ticket> getGenerals(int days) {
return lambdaQuery()
.eq(Ticket::getState, TicketState.Processing.getState())
.eq(Ticket::getUrgency, TicketUrgency.GENERAL.getState().byteValue())
.ge(Ticket::getCreateTime, LocalDateTime.now().minusDays(days))
.list();
}
@Override
public List<Ticket> getNonemergency(int days) {
return lambdaQuery()
.eq(Ticket::getState, TicketState.Processing.getState())
.eq(Ticket::getUrgency, TicketUrgency.NONEMERGENCY.getState().byteValue())
.ge(Ticket::getCreateTime, LocalDateTime.now().minusDays(days))
.list();
}
}

View File

@ -88,7 +88,10 @@
FROM ticket t
LEFT JOIN app_user u ON t.user_id=u.id AND u.enable=1
LEFT JOIN app_area a ON u.area_id=a.id AND a.`enable`=1
WHERE u.company_id=#{companyId}
WHERE u.company_id IN
<foreach collection="companyIds" item="companyId" open="(" separator="," close=")">
#{companyId}
</foreach>
<include refid="searchWhereCondition"/>
</select>

View File

@ -5,7 +5,9 @@ import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
/**
* 通知推送服务
*/
public interface NotifyPushService {
public interface INotifyPushService {
boolean check(UserDTO user);
/**
* 推送消息

View File

@ -1,16 +0,0 @@
package com.nflg.mobilebroken.starter.service.impl;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import com.nflg.mobilebroken.starter.service.NotifyPushService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class SSENotifyPushService implements NotifyPushService {
@Override
public void push(UserDTO user, String subject, String content) {
}
}

View File

@ -1,44 +0,0 @@
package com.nflg.mobilebroken.starter.service.impl;
import com.nflg.mobilebroken.common.constant.STATE;
import com.nflg.mobilebroken.common.exception.NflgException;
import com.nflg.mobilebroken.common.pojo.dto.UserDTO;
import com.nflg.mobilebroken.starter.service.NotifyPushService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class WXNotifyPushService implements NotifyPushService {
@Value("${wx.mp.templateMessage.config.templateId}")
private String templateId;
@Resource
private WxMpService wxMpService;
@Resource
private WxMpConfigStorage wxMpConfigStorage;
@Override
public void push(UserDTO user,String subject, String content) {
WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
.toUser(user.getWxOpenId()) // 接收用户的 OpenID
.templateId(templateId) // 模板消息 ID
.build();
templateMessage.addData(new WxMpTemplateData("first", "您好,这是一条测试消息", "#FF0000"));
try {
wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
}catch (Exception ex){
log.error("发送微信模板消息失败",ex);
throw new NflgException(STATE.BusinessError, "发送微信模板消息失败");
}
}
}