diff --git a/core/pom.xml b/core/pom.xml index 6e16813..8d32f0d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -197,12 +197,11 @@ jaxb-api 2.3.1 - - + + - commons-io - commons-io - 2.11.0 + org.springframework.boot + spring-boot-starter-websocket diff --git a/core/src/main/java/com/dite/znpt/config/WebSocketConfig.java b/core/src/main/java/com/dite/znpt/config/WebSocketConfig.java new file mode 100644 index 0000000..8635b51 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/config/WebSocketConfig.java @@ -0,0 +1,27 @@ +package com.dite.znpt.config; + +import com.dite.znpt.websocket.SimpleWebSocketHandler; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +/** + * WebSocket配置 + */ +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + private final SimpleWebSocketHandler simpleWebSocketHandler; + + public WebSocketConfig(SimpleWebSocketHandler simpleWebSocketHandler) { + this.simpleWebSocketHandler = simpleWebSocketHandler; + } + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(simpleWebSocketHandler, "/websocket") + .setAllowedOrigins("*"); // 在生产环境中应该限制允许的源 + } +} diff --git a/core/src/main/java/com/dite/znpt/domain/vo/EquipmentProcurementApplyReq.java b/core/src/main/java/com/dite/znpt/domain/vo/EquipmentProcurementApplyReq.java new file mode 100644 index 0000000..769d2e2 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/EquipmentProcurementApplyReq.java @@ -0,0 +1,70 @@ +package com.dite.znpt.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * @author Bear.G + * @date 2025/1/8/周三 18:00 + * @description 设备采购申请请求VO + */ +@Data +@ApiModel(value = "EquipmentProcurementApplyReq", description = "设备采购申请请求") +public class EquipmentProcurementApplyReq { + + @ApiModelProperty("设备ID") + @NotBlank(message = "设备ID不能为空") + private String equipmentId; + + @ApiModelProperty("设备名称") + @NotBlank(message = "设备名称不能为空") + private String equipmentName; + + @ApiModelProperty("设备类型") + @NotBlank(message = "设备类型不能为空") + private String equipmentType; + + @ApiModelProperty("设备型号") + private String equipmentModel; + + @ApiModelProperty("品牌") + private String brand; + + @ApiModelProperty("供应商名称") + private String supplierName; + + @ApiModelProperty("预算金额") + @NotNull(message = "预算金额不能为空") + private BigDecimal budgetAmount; + + @ApiModelProperty("数量") + @NotNull(message = "数量不能为空") + private Integer quantity; + + @ApiModelProperty("紧急程度:LOW-低,NORMAL-普通,HIGH-高,URGENT-紧急") + @NotBlank(message = "紧急程度不能为空") + private String urgencyLevel; + + @ApiModelProperty("申请原因") + @NotBlank(message = "申请原因不能为空") + private String applyReason; + + @ApiModelProperty("技术要求") + private String technicalRequirements; + + @ApiModelProperty("业务合理性说明") + private String businessJustification; + + @ApiModelProperty("预期交付日期") + private LocalDate expectedDeliveryDate; + + @ApiModelProperty("采购类型:NEW_PURCHASE-新采购,REPLACEMENT-更换,UPGRADE-升级") + @NotBlank(message = "采购类型不能为空") + private String procurementType; +} diff --git a/core/src/main/java/com/dite/znpt/service/EquipmentApprovalService.java b/core/src/main/java/com/dite/znpt/service/EquipmentApprovalService.java index 685c7c4..2164186 100644 --- a/core/src/main/java/com/dite/znpt/service/EquipmentApprovalService.java +++ b/core/src/main/java/com/dite/znpt/service/EquipmentApprovalService.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.dite.znpt.domain.vo.EquipmentApprovalListReq; import com.dite.znpt.domain.vo.EquipmentApprovalReq; import com.dite.znpt.domain.vo.EquipmentApprovalResp; +import com.dite.znpt.domain.vo.EquipmentProcurementApplyReq; /** * @author Bear.G @@ -41,4 +42,19 @@ public interface EquipmentApprovalService { * 获取审批统计信息 */ Object getApprovalStats(); + + /** + * 提交采购申请 + */ + void submitProcurementApplication(EquipmentProcurementApplyReq req); + + /** + * 获取我的采购申请 + */ + IPage getMyProcurementApplications(EquipmentApprovalListReq req); + + /** + * 撤回采购申请 + */ + void withdrawProcurementApplication(String approvalId); } diff --git a/core/src/main/java/com/dite/znpt/service/impl/EquipmentApprovalServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/EquipmentApprovalServiceImpl.java index 6d1fced..1fa86dd 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/EquipmentApprovalServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/EquipmentApprovalServiceImpl.java @@ -8,7 +8,10 @@ import com.dite.znpt.domain.mapper.EquipmentApprovalMapper; import com.dite.znpt.domain.vo.EquipmentApprovalListReq; import com.dite.znpt.domain.vo.EquipmentApprovalReq; import com.dite.znpt.domain.vo.EquipmentApprovalResp; +import com.dite.znpt.domain.vo.EquipmentProcurementApplyReq; import com.dite.znpt.service.EquipmentApprovalService; +import com.dite.znpt.websocket.SimpleWebSocketHandler; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -25,6 +28,7 @@ import java.util.stream.Collectors; * @date 2025/1/8/周三 17:55 * @description 设备审批服务实现类 */ +@Slf4j @Service public class EquipmentApprovalServiceImpl implements EquipmentApprovalService { @@ -138,38 +142,61 @@ public class EquipmentApprovalServiceImpl implements EquipmentApprovalService { * 添加查询条件 */ private void addQueryConditions(LambdaQueryWrapper wrapper, EquipmentApprovalListReq req) { + log.info("开始构建查询条件,请求参数: {}", req); + + // 添加搜索条件并记录日志 + int conditionCount = 0; + if (StringUtils.hasText(req.getEquipmentName())) { wrapper.like(EquipmentApprovalEntity::getEquipmentName, req.getEquipmentName()); + log.info("添加设备名称查询条件: {}", req.getEquipmentName()); + conditionCount++; } if (StringUtils.hasText(req.getApplicantName())) { wrapper.like(EquipmentApprovalEntity::getApplicantName, req.getApplicantName()); + log.info("添加申请人查询条件: {}", req.getApplicantName()); + conditionCount++; } if (StringUtils.hasText(req.getBusinessType())) { wrapper.eq(EquipmentApprovalEntity::getBusinessType, req.getBusinessType()); + log.info("添加业务类型查询条件: {}", req.getBusinessType()); + conditionCount++; } if (StringUtils.hasText(req.getApprovalStatus())) { wrapper.eq(EquipmentApprovalEntity::getApprovalStatus, req.getApprovalStatus()); + log.info("添加审批状态查询条件: {}", req.getApprovalStatus()); + conditionCount++; } if (StringUtils.hasText(req.getApplyTimeStart())) { wrapper.ge(EquipmentApprovalEntity::getApplyTime, req.getApplyTimeStart()); + log.info("添加申请时间开始查询条件: {}", req.getApplyTimeStart()); + conditionCount++; } if (StringUtils.hasText(req.getApplyTimeEnd())) { wrapper.le(EquipmentApprovalEntity::getApplyTime, req.getApplyTimeEnd()); + log.info("添加申请时间结束查询条件: {}", req.getApplyTimeEnd()); + conditionCount++; } if (StringUtils.hasText(req.getApprovalTimeStart())) { wrapper.ge(EquipmentApprovalEntity::getApprovalTime, req.getApprovalTimeStart()); + log.info("添加审批时间开始查询条件: {}", req.getApprovalTimeStart()); + conditionCount++; } if (StringUtils.hasText(req.getApprovalTimeEnd())) { wrapper.le(EquipmentApprovalEntity::getApprovalTime, req.getApprovalTimeEnd()); + log.info("添加审批时间结束查询条件: {}", req.getApprovalTimeEnd()); + conditionCount++; } + log.info("查询条件构建完成,共添加 {} 个条件", conditionCount); + // 排序 wrapper.orderByDesc(EquipmentApprovalEntity::getCreateTime); } @@ -196,4 +223,141 @@ public class EquipmentApprovalServiceImpl implements EquipmentApprovalService { return respPage; } + + @Override + public void submitProcurementApplication(EquipmentProcurementApplyReq req) { + log.info("开始提交采购申请,请求参数: {}", req); + + // 创建审批实体 + EquipmentApprovalEntity entity = new EquipmentApprovalEntity(); + + // 设置基本信息 + entity.setEquipmentId(req.getEquipmentId()); // 添加设备ID + entity.setEquipmentName(req.getEquipmentName()); + entity.setEquipmentType(req.getEquipmentType()); + entity.setEquipmentModel(req.getEquipmentModel()); + entity.setBrand(req.getBrand()); + entity.setSupplierName(req.getSupplierName()); + entity.setPurchasePrice(req.getBudgetAmount()); + entity.setTotalPrice(req.getBudgetAmount().multiply(new java.math.BigDecimal(req.getQuantity()))); + entity.setQuantity(req.getQuantity()); + + // 设置申请信息 + entity.setApplicantName(getCurrentUserName()); // 获取当前用户名 + entity.setApplicantId(getCurrentUserId()); // 获取当前用户ID + entity.setApplyTime(LocalDateTime.now()); + entity.setApplyReason(req.getApplyReason()); + + // 设置业务类型和审批状态 + entity.setBusinessType("PROCUREMENT"); + entity.setApprovalStatus("PENDING"); + + // 设置扩展字段(如果有的话) + // entity.setProcurementType(req.getProcurementType()); + // entity.setUrgencyLevel(req.getUrgencyLevel()); + // entity.setTechnicalRequirements(req.getTechnicalRequirements()); + // entity.setBusinessJustification(req.getBusinessJustification()); + // entity.setExpectedDeliveryDate(req.getExpectedDeliveryDate()); + + // 保存到数据库 + equipmentApprovalMapper.insert(entity); + + // 发送通知 - 使用日志记录,后续可以扩展为WebSocket通知 + log.info("采购申请提交成功,设备名称: {}, 申请人: {}", + req.getEquipmentName(), getCurrentUserName()); + + // 发送WebSocket通知 + try { + SimpleWebSocketHandler.sendProcurementNotification( + req.getEquipmentName(), + getCurrentUserName() + ); + log.info("WebSocket通知发送成功"); + } catch (Exception e) { + log.error("WebSocket通知发送失败", e); + } + + log.info("采购申请提交成功,审批ID: {}", entity.getApprovalId()); + } + + @Override + public IPage getMyProcurementApplications(EquipmentApprovalListReq req) { + log.info("开始获取我的采购申请,请求参数: {}", req); + + // 创建分页对象 + Integer pageNum = req.getPage() != null ? req.getPage() : 1; + Integer pageSize = req.getPageSize() != null ? req.getPageSize() : 10; + Page page = new Page<>(pageNum, pageSize); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 只查询当前用户的申请 + wrapper.eq(EquipmentApprovalEntity::getApplicantId, getCurrentUserId()); + wrapper.eq(EquipmentApprovalEntity::getBusinessType, "PROCUREMENT"); + + // 添加查询条件 + addQueryConditions(wrapper, req); + + IPage result = equipmentApprovalMapper.selectPage(page, wrapper); + + return convertToRespPage(result); + } + + @Override + public void withdrawProcurementApplication(String approvalId) { + log.info("开始撤回采购申请,审批ID: {}", approvalId); + + EquipmentApprovalEntity entity = equipmentApprovalMapper.selectById(approvalId); + if (entity == null) { + throw new RuntimeException("审批记录不存在"); + } + + // 检查是否是当前用户的申请 + if (!entity.getApplicantId().equals(getCurrentUserId())) { + throw new RuntimeException("只能撤回自己的申请"); + } + + // 检查状态是否可以撤回 + if (!"PENDING".equals(entity.getApprovalStatus())) { + throw new RuntimeException("只能撤回待审批状态的申请"); + } + + // 更新状态为已撤回 + entity.setApprovalStatus("WITHDRAWN"); + equipmentApprovalMapper.updateById(entity); + + log.info("采购申请撤回成功,审批ID: {}", approvalId); + } + + /** + * 获取当前用户名 + */ + private String getCurrentUserName() { + try { + // 从Sa-Token上下文获取当前用户名 + Object loginId = cn.dev33.satoken.stp.StpUtil.getLoginId(); + if (loginId != null) { + return loginId.toString(); + } + } catch (Exception e) { + log.warn("获取当前用户名失败: {}", e.getMessage()); + } + return "未知用户"; + } + + /** + * 获取当前用户ID + */ + private String getCurrentUserId() { + try { + // 从Sa-Token上下文获取当前用户ID + Object loginId = cn.dev33.satoken.stp.StpUtil.getLoginId(); + if (loginId != null) { + return loginId.toString(); + } + } catch (Exception e) { + log.warn("获取当前用户ID失败: {}", e.getMessage()); + } + return "unknown_user_id"; + } } diff --git a/core/src/main/java/com/dite/znpt/websocket/SimpleWebSocketHandler.java b/core/src/main/java/com/dite/znpt/websocket/SimpleWebSocketHandler.java new file mode 100644 index 0000000..4851e0c --- /dev/null +++ b/core/src/main/java/com/dite/znpt/websocket/SimpleWebSocketHandler.java @@ -0,0 +1,96 @@ +package com.dite.znpt.websocket; + +import org.springframework.stereotype.Component; +import org.springframework.web.socket.*; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; + +/** + * 简单的WebSocket处理器 + */ +@Component +public class SimpleWebSocketHandler implements WebSocketHandler { + + // 存储所有连接的会话 + private static final Map sessions = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + String sessionId = session.getId(); + sessions.put(sessionId, session); + System.out.println("WebSocket连接建立,sessionId: " + sessionId); + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { + // 处理接收到的消息 + System.out.println("收到WebSocket消息: " + message.getPayload()); + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + System.err.println("WebSocket传输错误,sessionId: " + session.getId()); + exception.printStackTrace(); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { + String sessionId = session.getId(); + sessions.remove(sessionId); + System.out.println("WebSocket连接关闭,sessionId: " + sessionId); + } + + @Override + public boolean supportsPartialMessages() { + return false; + } + + /** + * 发送简单消息给所有连接的客户端 + */ + public static void sendMessageToAll(String message) { + System.out.println("准备发送WebSocket消息给 " + sessions.size() + " 个连接的客户端"); + System.out.println("消息内容: " + message); + + final int[] successCount = {0}; + final int[] failCount = {0}; + + sessions.values().forEach(session -> { + try { + if (session.isOpen()) { + session.sendMessage(new TextMessage(message)); + successCount[0]++; + System.out.println("成功发送消息给sessionId: " + session.getId()); + } else { + System.out.println("跳过已关闭的sessionId: " + session.getId()); + } + } catch (IOException e) { + failCount[0]++; + System.err.println("发送消息失败,sessionId: " + session.getId()); + e.printStackTrace(); + } + }); + + System.out.println("WebSocket消息发送完成 - 成功: " + successCount[0] + ", 失败: " + failCount[0]); + } + + /** + * 发送采购申请通知 + */ + public static void sendProcurementNotification(String equipmentName, String applicantName) { + String notificationMessage = String.format( + "{\"type\":\"PROCUREMENT_APPLICATION\",\"title\":\"新的采购申请\",\"content\":\"收到来自 %s 的设备采购申请:%s\"}", + applicantName, equipmentName + ); + + System.out.println("=== 发送采购申请通知 ==="); + System.out.println("设备名称: " + equipmentName); + System.out.println("申请人: " + applicantName); + System.out.println("通知消息: " + notificationMessage); + System.out.println("当前连接数: " + sessions.size()); + + sendMessageToAll(notificationMessage); + } +} diff --git a/web/src/main/java/com/dite/znpt/web/controller/EquipmentApprovalController.java b/web/src/main/java/com/dite/znpt/web/controller/EquipmentApprovalController.java index a49fa5f..ef16e0a 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/EquipmentApprovalController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/EquipmentApprovalController.java @@ -6,6 +6,7 @@ import com.dite.znpt.domain.Result; import com.dite.znpt.domain.vo.EquipmentApprovalListReq; import com.dite.znpt.domain.vo.EquipmentApprovalReq; import com.dite.znpt.domain.vo.EquipmentApprovalResp; +import com.dite.znpt.domain.vo.EquipmentProcurementApplyReq; import com.dite.znpt.service.EquipmentApprovalService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -34,6 +35,21 @@ public class EquipmentApprovalController { @ApiOperation(value = "分页查询待审批的设备采购申请", httpMethod = "GET") @GetMapping("/pending") public PageResult getPendingApprovals(EquipmentApprovalListReq req) { + log.info("=== 设备审批待审批查询接口被调用 ==="); + log.info("接收到的请求参数: {}", req); + log.info("设备名称: {}", req.getEquipmentName()); + log.info("申请人: {}", req.getApplicantName()); + log.info("业务类型: {}", req.getBusinessType()); + log.info("审批状态: {}", req.getApprovalStatus()); + log.info("申请时间开始: {}", req.getApplyTimeStart()); + log.info("申请时间结束: {}", req.getApplyTimeEnd()); + log.info("审批时间开始: {}", req.getApprovalTimeStart()); + log.info("审批时间结束: {}", req.getApprovalTimeEnd()); + log.info("页码: {}", req.getPage()); + log.info("每页大小: {}", req.getPageSize()); + log.info("排序字段: {}", req.getOrderBy()); + log.info("排序方向: {}", req.getOrderDirection()); + IPage page = equipmentApprovalService.getPendingApprovals(req); return PageResult.ok(page.getRecords(), page.getTotal()); } @@ -41,6 +57,21 @@ public class EquipmentApprovalController { @ApiOperation(value = "分页查询已审批的设备采购申请", httpMethod = "GET") @GetMapping("/approved") public PageResult getApprovedApprovals(EquipmentApprovalListReq req) { + log.info("=== 设备审批已审批查询接口被调用 ==="); + log.info("接收到的请求参数: {}", req); + log.info("设备名称: {}", req.getEquipmentName()); + log.info("申请人: {}", req.getApplicantName()); + log.info("业务类型: {}", req.getBusinessType()); + log.info("审批状态: {}", req.getApprovalStatus()); + log.info("申请时间开始: {}", req.getApplyTimeStart()); + log.info("申请时间结束: {}", req.getApplyTimeEnd()); + log.info("审批时间开始: {}", req.getApprovalTimeStart()); + log.info("审批时间结束: {}", req.getApprovalTimeEnd()); + log.info("页码: {}", req.getPage()); + log.info("每页大小: {}", req.getPageSize()); + log.info("排序字段: {}", req.getOrderBy()); + log.info("排序方向: {}", req.getOrderDirection()); + IPage page = equipmentApprovalService.getApprovedApprovals(req); return PageResult.ok(page.getRecords(), page.getTotal()); } @@ -70,4 +101,38 @@ public class EquipmentApprovalController { public Result getApprovalStats() { return Result.ok(equipmentApprovalService.getApprovalStats()); } + + @ApiOperation(value = "提交采购申请", httpMethod = "POST") + @PostMapping("/procurement/apply") + public Result submitProcurementApplication(@Validated @RequestBody EquipmentProcurementApplyReq req) { + log.info("=== 提交采购申请接口被调用 ==="); + log.info("接收到的请求参数: {}", req); + log.info("设备名称: {}", req.getEquipmentName()); + log.info("设备类型: {}", req.getEquipmentType()); + log.info("预算金额: {}", req.getBudgetAmount()); + log.info("申请原因: {}", req.getApplyReason()); + + equipmentApprovalService.submitProcurementApplication(req); + return Result.ok(); + } + + @ApiOperation(value = "获取我的采购申请", httpMethod = "GET") + @GetMapping("/procurement/my-applications") + public PageResult getMyProcurementApplications(EquipmentApprovalListReq req) { + log.info("=== 获取我的采购申请接口被调用 ==="); + log.info("接收到的请求参数: {}", req); + + IPage page = equipmentApprovalService.getMyProcurementApplications(req); + return PageResult.ok(page.getRecords(), page.getTotal()); + } + + @ApiOperation(value = "撤回采购申请", httpMethod = "POST") + @PostMapping("/procurement/{approvalId}/withdraw") + public Result withdrawProcurementApplication(@PathVariable String approvalId) { + log.info("=== 撤回采购申请接口被调用 ==="); + log.info("审批ID: {}", approvalId); + + equipmentApprovalService.withdrawProcurementApplication(approvalId); + return Result.ok(); + } } diff --git a/web/src/main/resources/application-dev.yml b/web/src/main/resources/application-dev.yml index ead266b..155f1d2 100644 --- a/web/src/main/resources/application-dev.yml +++ b/web/src/main/resources/application-dev.yml @@ -14,7 +14,7 @@ spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://39.99.201.243:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://39.99.201.243:3306/test01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: BUw8YW6%@^8q druid: