feat: 一些调整

This commit is contained in:
曹鹏飞 2025-02-10 19:03:44 +08:00
parent 14c4d7f2cd
commit 6bdc334c91
18 changed files with 452 additions and 19 deletions

View File

@ -6,24 +6,28 @@ import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.layout.font.FontProvider;
import com.nflg.mobilebroken.admin.annotation.ApiMark;
import com.nflg.mobilebroken.common.constant.STATE;
import com.nflg.mobilebroken.common.constant.TicketState;
import com.nflg.mobilebroken.common.exception.NflgException;
import com.nflg.mobilebroken.common.pojo.ApiResult;
import com.nflg.mobilebroken.common.pojo.PageData;
import com.nflg.mobilebroken.common.pojo.dto.ChatMessageDTO;
import com.nflg.mobilebroken.common.pojo.dto.SSEMessageDTO;
import com.nflg.mobilebroken.common.pojo.request.AddChatMessageRequest;
import com.nflg.mobilebroken.common.pojo.request.AdminTicketSearchRequest;
import com.nflg.mobilebroken.common.pojo.request.AssignmentTicketRequest;
import com.nflg.mobilebroken.common.pojo.request.FollowRequest;
import com.nflg.mobilebroken.common.pojo.vo.AdminTicketVO;
import com.nflg.mobilebroken.common.pojo.vo.DeviceInfoVO;
import com.nflg.mobilebroken.common.pojo.vo.TicketInfoVO;
import com.nflg.mobilebroken.common.pojo.vo.TicketPdfVO;
import com.nflg.mobilebroken.common.util.AdminUserUtil;
import com.nflg.mobilebroken.common.util.EecExcelUtil;
import com.nflg.mobilebroken.common.util.PageUtil;
import com.nflg.mobilebroken.repository.entity.AdminUser;
import com.nflg.mobilebroken.repository.entity.AppUser;
import com.nflg.mobilebroken.repository.entity.TBaseCustomer;
import com.nflg.mobilebroken.repository.entity.Ticket;
import com.nflg.mobilebroken.common.util.VUtils;
import com.nflg.mobilebroken.repository.entity.*;
import com.nflg.mobilebroken.repository.service.*;
import com.nflg.mobilebroken.starter.annotation.MethodInfoMark;
import com.nflg.mobilebroken.starter.service.impl.APPSSEManagerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@ -37,9 +41,11 @@ import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
@ -69,6 +75,15 @@ public class TicketController extends ControllerBase {
@Resource
private ITicketFollowService ticketFollowService;
@Resource
private TicketChatService ticketChatService;
@Resource
private APPSSEManagerService sseManagerService;
@Resource
private IAppAreaService appAreaService;
/**
* 获取问题类型
*
@ -230,4 +245,97 @@ public class TicketController extends ControllerBase {
throw new NflgException(STATE.BusinessError, "生成pdf出错");
}
}
/**
* 获取工单详情
*
* @param id 工单编号
* @return 工单详情
**/
@GetMapping("getTicket")
public ApiResult<TicketInfoVO> getTicket(@Valid @RequestParam @NotNull Integer id) {
Ticket ticket = ticketService.getById(id);
AppUser user = appUserService.getById(ticket.getUserId());
AppArea appArea = appAreaService.getById(user.getAreaId());
TBaseCustomer company = customerService.getById(Integer.valueOf(user.getCompanyId()));
DeviceInfoVO device = deviceService.getByDeviceNo(ticket.getDeviceNo());
String handle = ticket.getHandle();
if (StrUtil.isNotBlank(handle)) {
List<AdminUser> adminUsers = adminUserService.listByIds(Arrays.stream(handle.split(",")).map(Integer::parseInt).collect(Collectors.toList()));
handle = adminUsers.stream().map(AdminUser::getUserName).collect(Collectors.joining(","));
}
TicketInfoVO vo = new TicketInfoVO()
.setId(ticket.getId())
.setTitle(ticket.getTitle())
.setDeviceNo(ticket.getDeviceNo())
.setModelNo(device.getModelNo())
.setComponent(ticket.getComponent())
.setUseTime(ticket.getUseTime())
.setDescription(ticket.getDescription())
.setState(ticket.getState())
.setImages(StrUtil.isNotBlank(ticket.getImages()) ? StrUtil.split(ticket.getImages(), ",") : Collections.emptyList())
.setAttachments(StrUtil.isNotBlank(ticket.getAttachments()) ? StrUtil.split(ticket.getAttachments(), ",") : Collections.emptyList())
.setCreateUserId(ticket.getUserId())
.setCreateUserName(user.getName())
.setCreateUserAvatar(user.getAvatar())
.setCreateTime(ticket.getCreateTime())
.setAreaName(appArea.getName())
.setCompanyName(company.getAgencyCompanyName())
.setHandle(handle);
return ApiResult.success(vo);
}
/**
* 获取工单聊天记录
*
* @param ticketId 工单编号
* @return 聊天记录
**/
@GetMapping("getChatMessages")
@ApiMark(moduleName = "工单管理", apiName = "获取工单聊天记录")
public ApiResult<List<ChatMessageDTO>> getChatMessages(@Valid @RequestParam @NotNull Integer ticketId) {
return ApiResult.success(ticketChatService.getMessages(ticketId));
}
/**
* 添加聊天记录
*
* @param request 请求信息
**/
@PostMapping("addChatMessage")
@ApiMark(moduleName = "工单管理", apiName = "添加聊天记录")
public ApiResult<Void> addChatMessage(@Valid @RequestBody AddChatMessageRequest request) {
Ticket ticket = ticketService.getById(request.getTicketId());
VUtils.trueThrowBusinessError(Objects.isNull(ticket)).throwMessage("工单不存在");
VUtils.trueThrowBusinessError(Objects.equals(ticket.getState(), TicketState.Revoked.getState())
|| Objects.equals(ticket.getState(), TicketState.Closed.getState()))
.throwMessage("当前工单状态不允许发送消息");
AdminUser user = adminUserService.getById(AdminUserUtil.getUserId());
ChatMessageDTO message = new ChatMessageDTO()
.setId(cn.hutool.core.util.IdUtil.getSnowflakeNextId())
.setFrom("admin")
.setSenderId(user.getId())
.setSenderName(user.getUserName())
.setSenderAvatar(user.getAvatar())
.setContent(request.getContent())
.setCreateTime(Instant.now())
.setAttachments(request.getAttachments())
.setImages(request.getImages())
.setQuote(request.getQuote());
// ticketChatService.pushMessage(request.getTicketId(),message);
// TicketChat chat=ticketChatService.findByTicketId(request.getTicketId());
// chat.getMessages().add(message);
// ticketChatService.save(chat);
ticketChatService.addMessage(request.getTicketId(), message);
//推送消息
try {
SSEMessageDTO messageDTO = new SSEMessageDTO()
.setType(1)
.setData(message);
sseManagerService.send(ticket.getUserId(), messageDTO);
} catch (IOException e) {
log.error("发送SSE消息出错", e);
}
return ApiResult.success();
}
}

View File

@ -73,6 +73,11 @@
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.20</version>
</dependency>
</dependencies>
<build>

View File

@ -1,15 +1,19 @@
package com.nflg.mobilebroken.cfs.controller;
import cn.hutool.core.collection.CollectionUtil;
import com.nflg.mobilebroken.cfs.service.WXQRCodeService;
import com.nflg.mobilebroken.common.pojo.ApiResult;
import com.nflg.mobilebroken.common.pojo.vo.LanguageVO;
import com.nflg.mobilebroken.common.util.AppUserUtil;
import com.nflg.mobilebroken.repository.entity.Language;
import com.nflg.mobilebroken.repository.service.ILanguageService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ -26,6 +30,9 @@ public class SystemController extends ControllerBase {
@Resource
private ILanguageService languageService;
@Resource
private WXQRCodeService wxQRCodeService;
/**
* 获取语言列表
* @return 取语言列表
@ -47,4 +54,13 @@ public class SystemController extends ControllerBase {
.collect(Collectors.toList());
return ApiResult.success(vos);
}
/**
* 生成微信服务号关注二维码
*/
@GetMapping("generateQRCode")
public void generateQRCode(HttpServletResponse response) throws Exception {
response.setContentType(MediaType.IMAGE_PNG_VALUE);
response.getOutputStream().write(wxQRCodeService.generateQRCode(AppUserUtil.getUserId()));
}
}

View File

@ -17,7 +17,7 @@ import com.nflg.mobilebroken.common.util.PageUtil;
import com.nflg.mobilebroken.common.util.VUtils;
import com.nflg.mobilebroken.repository.entity.*;
import com.nflg.mobilebroken.repository.service.*;
import com.nflg.mobilebroken.starter.service.impl.APPSSEManagerService;
import com.nflg.mobilebroken.starter.service.impl.AdminSSEManagerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@ -77,7 +77,7 @@ public class TiketController extends ControllerBase {
private ITBaseCustomerService customerService;
@Resource
private APPSSEManagerService sseManagerService;
private AdminSSEManagerService sseManagerService;
/**
* 搜索设备
@ -190,7 +190,9 @@ public class TiketController extends ControllerBase {
.setCreateUserAvatar(user.getAvatar())
.setCreateTime(ticket.getCreateTime())
.setAreaName(appArea.getName())
.setCompanyName(company.getAgencyCompanyName());
.setCompanyName(company.getAgencyCompanyName())
.setHandle(handle);
;
return ApiResult.success(vo);
}

View File

@ -0,0 +1,114 @@
package com.nflg.mobilebroken.cfs.controller;
import com.nflg.mobilebroken.repository.entity.AdminUser;
import com.nflg.mobilebroken.repository.entity.AppUser;
import com.nflg.mobilebroken.repository.service.IAdminUserService;
import com.nflg.mobilebroken.repository.service.IAppUserService;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/wx")
public class WXController {
@Resource
private IAdminUserService adminUserService;
@Resource
private IAppUserService appUserService;
/**
* 处理微信事件推送
*
* @param request 请求对象
* @return 响应消息
* @throws IOException IO 异常
*/
@PostMapping("/event")
public ResponseEntity<String> handleWeChatEvent(HttpServletRequest request) throws IOException {
// 读取请求体中的 XML 数据
StringBuilder sb = new StringBuilder();
request.getReader().lines().forEach(sb::append);
String requestBody = sb.toString();
log.info("接收到微信推送事件,内容: " + requestBody);
// 解析 XML 数据为 Map
Map<String, String> eventMap = parseXml(requestBody);
// 获取事件类型
String msgType = eventMap.get("MsgType");
String eventType = eventMap.get("Event");
// 根据事件类型处理逻辑
String responseMessage = "";
if ("event".equals(msgType)) {
if ("subscribe".equals(eventType)) {
responseMessage = handleSubscribeEvent(eventMap);
} else if ("unsubscribe".equals(eventType)) {
responseMessage = handleUnsubscribeEvent(eventMap.get("FromUserName"));
} else {
log.info("忽略的事件: " + eventType);
}
} else {
responseMessage = "不是事件消息";
}
// 返回响应消息
return new ResponseEntity<>(responseMessage, HttpStatus.OK);
}
// 解析 XML 数据为 Map
private Map<String, String> parseXml(String xml) {
XStream xstream = new XStream(new DomDriver());
xstream.alias("xml", Map.class);
return (Map<String, String>) xstream.fromXML(xml);
}
// 处理关注事件
private String handleSubscribeEvent(Map<String, String> eventMap) {
// String fromUserName = eventMap.get("FromUserName");
String openId = eventMap.get("ToUserName");
String[] scene = eventMap.get("EventKey").split("_");
String from = scene[0];
Integer userId = Integer.parseInt(scene[1]);
if (from.equals("app")) {
appUserService.lambdaUpdate()
.set(AppUser::getOpenid, openId)
.eq(AppUser::getId, userId)
.update();
} else if (from.equals("admin")) {
adminUserService.lambdaUpdate()
.set(AdminUser::getOpenid, openId)
.eq(AdminUser::getId, userId)
.update();
} else {
log.error("不支持的来源: " + from);
}
// // 构造回复消息
// WXMessageBackDTO response = new WXMessageBackDTO();
// response.setToUserName(openId);
// response.setFromUserName(fromUserName);
// response.setCreateTime(System.currentTimeMillis() / 1000);
// response.setMsgType("text");
// response.setContent("");
// return textMessageToXml(response);
return "";
}
// 处理取消关注事件
private String handleUnsubscribeEvent(String openId) {
appUserService.lambdaUpdate()
.set(AppUser::getOpenid, "")
.eq(AppUser::getOpenid, openId);
return "";
}
}

View File

@ -0,0 +1,42 @@
package com.nflg.mobilebroken.cfs.service;
import cn.hutool.json.JSONUtil;
import com.nflg.mobilebroken.common.constant.Constant;
import com.nflg.mobilebroken.common.pojo.dto.WXTokenDTO;
import com.nflg.mobilebroken.common.pojo.request.WXQrcodeActionInfo;
import com.nflg.mobilebroken.common.pojo.request.WXQrcodeRequest;
import com.nflg.mobilebroken.common.pojo.request.WXQrcodeScene;
import com.nflg.mobilebroken.common.pojo.vo.WXQrcodeVO;
import com.nflg.mobilebroken.common.util.QRCodeUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.Objects;
@Component
public class WXQRCodeService {
@Resource
private RedisTemplate<String, String> redisTemplate;
public byte[] generateQRCode(Integer userId) throws Exception {
Object obj = redisTemplate.opsForHash().get("wx:url:follow", userId);
if (Objects.isNull(obj)) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response1 = restTemplate.getForEntity(Constant.WX_TOKEN_URL, String.class);
String text = response1.getBody().substring(1, response1.getBody().length() - 1).replace("\\", "");
WXTokenDTO token = JSONUtil.toBean(text, WXTokenDTO.class);
String accessToken = token.getAccess_token();
String url = Constant.WX_QRCODE + "?access_token=" + accessToken;
String scene_str = "app_" + userId;
WXQrcodeRequest req = new WXQrcodeRequest().setAction_info(new WXQrcodeActionInfo().setScene(new WXQrcodeScene().setScene_str(scene_str)));
WXQrcodeVO qvo = restTemplate.postForObject(url, req, WXQrcodeVO.class);
obj = qvo.getUrl();
redisTemplate.opsForHash().put("wx:url:follow", userId, obj);
}
return QRCodeUtil.generateQRCode(obj.toString(), 200, 200);
}
}

View File

@ -15,7 +15,6 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
@ -55,6 +54,14 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -19,4 +19,8 @@ public class Constant {
public static final String REDIS_KEY_MESSAGECONFIG_EMAIL = "emailNotifyEnabled";
public static final String REDIS_KEY_MESSAGECONFIG_APP = "appNotifyEnabled";
public static final String WX_TOKEN_URL = "http://sfc.nflg.net:8071/api/crm_api!getYdpAccessToken.action";
public static final String WX_QRCODE = "https://api.weixin.qq.com/cgi-bin/qrcode/create";
}

View File

@ -0,0 +1,13 @@
package com.nflg.mobilebroken.common.pojo.dto;
import lombok.Data;
@Data
public class WXMessageBackDTO {
private String ToUserName;
private String FromUserName;
private long CreateTime;
private String MsgType;
private String Content;
}

View File

@ -0,0 +1,11 @@
package com.nflg.mobilebroken.common.pojo.dto;
import lombok.Data;
@Data
public class WXTokenDTO {
private String jsapi_ticket;
private String access_token;
}

View File

@ -0,0 +1,12 @@
package com.nflg.mobilebroken.common.pojo.request;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class WXQrcodeActionInfo {
//场景值
private WXQrcodeScene scene;
}

View File

@ -0,0 +1,15 @@
package com.nflg.mobilebroken.common.pojo.request;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class WXQrcodeRequest {
//二维码类型QR_SCENE为临时的整型参数值QR_STR_SCENE为临时的字符串参数值QR_LIMIT_SCENE为永久的整型参数值QR_LIMIT_STR_SCENE为永久的字符串参数值
private String action_name = "QR_LIMIT_STR_SCENE";
//二维码详细信息
private WXQrcodeActionInfo action_info;
}

View File

@ -0,0 +1,12 @@
package com.nflg.mobilebroken.common.pojo.request;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class WXQrcodeScene {
//场景值ID字符串形式的ID字符串类型长度限制为1到64
private String scene_str;
}

View File

@ -52,6 +52,9 @@ public class TicketInfoVO {
//使用时长单位: 小时
private Integer useTime;
//处理人
private String handle;
//图片
private List<String> images;

View File

@ -0,0 +1,22 @@
package com.nflg.mobilebroken.common.pojo.vo;
import lombok.Data;
@Data
public class WXQrcodeVO {
/**
* 获取的二维码ticket凭借此ticket可以在有效时间内换取二维码
*/
private String ticket;
/**
* 该二维码有效时间以秒为单位 最大不超过2592000即30天
*/
private Integer expire_seconds;
/**
* 二维码图片解析后的地址开发者可根据该地址自行生成需要的二维码图片
*/
private String url;
}

View File

@ -0,0 +1,31 @@
package com.nflg.mobilebroken.common.util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.io.ByteArrayOutputStream;
import java.util.Hashtable;
public class QRCodeUtil {
public static byte[] generateQRCode(String text, int width, int height) throws Exception {
// 配置二维码参数
Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.MARGIN, 1);
// 生成二维码矩阵
BitMatrix bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
// 将二维码矩阵转换为字节数组
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "PNG", byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
}

View File

@ -44,18 +44,17 @@
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>javax.xml.bind</groupId>-->
<!-- <artifactId>jaxb-api</artifactId>-->
<!-- <version>2.3.1</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>javax.activation</groupId>-->
<!-- <artifactId>activation</artifactId>-->
<!-- <version>1.1.1</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId>

17
pom.xml
View File

@ -39,6 +39,8 @@
<freemarker.version>2.3.31</freemarker.version>
<sa-token.version>1.39.0</sa-token.version>
<weixin.version>4.7.0</weixin.version>
<zxing.version>3.5.3</zxing.version>
<oss.version>3.17.4</oss.version>
</properties>
<dependencies>
<dependency>
@ -153,6 +155,21 @@
<artifactId>weixin-java-mp</artifactId>
<version>${weixin.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>${zxing.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>${zxing.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${oss.version}</version>
</dependency>
</dependencies>
</dependencyManagement>