## **软件发布及追溯系统** #### **项目背景** 随着产品不断升级,产品复杂度不断升级,软件烧录和写入技术难度和产品使用的涉及面及软件更新频次不断增加,以往老的管理方式已经无法支撑目前的软件管理的新常态要求 近期集团内各家工厂的几个软件重大投诉如软件版本代码写错和客户使用的IP地址以及ECU代码写错问题频发,引起客户强烈不满和管理层的高度重视 经过讨论,拟在集团工厂层面导入“软件发布及追溯系统”,对嵌入式软件的发布和生产使用进行全面管控 ![image-20231017191643319](./img/image-20231017191643319.png) #### **人员情况** 由我本人为主要开发人员,项目管理由经理把控。 #### **技术栈** 前端:Vue2 Quasar \ 后端:Java Mybatis Plus SpringBoot \ 中间件:Redis \ 文件上传:阿里云OSS \ 数据库:SqlServer \ 其他:C# WindowsService后台程序 #### **计划用时** [cinwell website](./软件发布%20Timing%20Schedule.html ':include :type=iframe width=100% height=400px ') #### **主要工作** 负责前期需求调研,后端框架搭建,数据库建模,基础代码开发,Vue前端框架搭建,页面功能开发。 以及核心功能:软件发布上传,下载,推送,通知等功能开发。 包括进度汇报等。 #### **问题与难点** 集团领导派发的项目经理影响力不够,项目难以在TC(技术中心)推进,导致项目在一段时间内停滞,需求反复变更。 #### **成果** 30W 交付,每年10W 维护费用。 高可用部署于阿里云。 #### **预览(初版-测试数据)** 软件上传界面(测试数据) ![Alt text](./img/image-1.png) ![Alt text](./img/image-3.png) 产品簇界面(测试数据) ![Alt text](./img/image-2.png) 详情回顾 ![Alt text](./img/image-4.png) #### **代码片段(初版)**

软件发布

```java package com.ciemis.svms.service.impl; import cn.hutool.core.bean.BeanUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.aliyun.oss.OSSClient; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ciemis.svms.base.exception.BusinessException; import com.ciemis.svms.base.util.DateUtil; import com.ciemis.svms.base.util.OssClient; import com.ciemis.svms.base.ws.SocketManager; import com.ciemis.svms.entity.*; import com.ciemis.svms.mapper.SoftItemSoftwareMapper; import com.ciemis.svms.param.ApprovalSoftwareParam; import com.ciemis.svms.param.FileParam; import com.ciemis.svms.param.SoftwareParam; import com.ciemis.svms.param.SoftwareQuery; import com.ciemis.svms.properties.OssProperties; import com.ciemis.svms.properties.RepositoryProperties; import com.ciemis.svms.service.*; import com.ciemis.svms.vo.SoftItemSoftwareVo; import com.ciemis.svms.vo.SoftwareServiceResultVo; import com.ciemis.svms.vo.SysUserVo; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.socket.WebSocketSession; import java.net.URL; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; /** *

* 软件ITEM 服务实现类 *

* * @author 杨超杰 * @since 2021-04-09 */ @Service public class SoftItemSoftwareServiceImpl extends ServiceImpl implements ISoftItemSoftwareService { @Autowired RedisService redisService; @Autowired ISoftItemSoftwareService softwareService; @Autowired ISoftSoftwareRelationService softwareRelationService; @Autowired IOssFileRecordService ossFileRecordService; @Autowired private SimpMessagingTemplate template; @Autowired private ISysMenuService menuService; @Autowired private ISysNoticeService noticeService; @Autowired private ISysUserService userService; @Autowired OssProperties properties; @Autowired IDataPlantService plantService; @Autowired RepositoryProperties repositoryProperties; @Autowired IDataProjectService projectService; @Autowired ISysItemApprovalService approvalService; @Autowired ISoftPushMachineService pushMachineService; @Override @Transactional(rollbackFor = Exception.class) public void saveReleaseSoftware(SysUserVo currentUser, SoftwareParam softwareParam) { SoftItemSoftware softItemSoftware = new SoftItemSoftware(); QueryWrapper checkId = new QueryWrapper<>(); checkId.eq("SoftwareCode", softwareParam.getSoftwareCode()); if (softwareService.count(checkId) > 0) { throw new BusinessException(500, "软件ID重复"); } BeanUtil.copyProperties(softwareParam, softItemSoftware); SysUser user = userService.getAccount(softwareParam.getReceiveUser()); if (Objects.isNull(user)) { throw new BusinessException(500, "没有找到制造工程师用户"); } softItemSoftware.setFlag(SoftItemSoftware.Status.CREATE.name()); softItemSoftware.setPublishDate(LocalDateTime.now()); softItemSoftware.setPublisher(currentUser.getCode()); softItemSoftware.setPublisherName(currentUser.getName()); softItemSoftware.setUploadType(softwareParam.getUploadType()); softItemSoftware.setServiceStatus(SoftItemSoftware.WindowsServiceStatus.PENDING.name()); softItemSoftware.setTargetPlant(user.getPlant()); softItemSoftware.setReceiveUser(user.getCode()); softItemSoftware.setReceiveName(user.getName()); softwareService.save(softItemSoftware); SysNotice sysNotice = new SysNotice(); sysNotice.setCreateTime(LocalDateTime.now()); sysNotice.setFromUser(currentUser.getCode()); sysNotice.setStatus(SysNotice.Status.CREATE.name()); sysNotice.setToUser(softItemSoftware.getUploadFileUser()); sysNotice.setRelationField(String.valueOf(softItemSoftware.getSoftwareId())); sysNotice.setType(SysNotice.Type.A.name()); if (SoftItemSoftware.Type.UPGRADE.name().equals(softwareParam.getUploadType())) { sysNotice.setSubject(softItemSoftware.getSoftwareName() + "USB升级包上传"); sysNotice.setMessage(softItemSoftware.getSoftwareName() + "USB升级包 已经创建,请上传文件"); } else { sysNotice.setSubject(softItemSoftware.getSoftwareName() + "软件上传"); sysNotice.setMessage(softItemSoftware.getSoftwareName() + "已经创建,请上传文件"); } sysNotice.setFromUsername(currentUser.getName()); QueryWrapper menuQueryWrapper = new QueryWrapper<>(); menuQueryWrapper.eq("Code", "uploadSoftware"); SysMenu sysMenu = menuService.getOne(menuQueryWrapper); sysNotice.setHandleUrl(sysMenu.getPath() + "/" + softItemSoftware.getSoftwareId()); noticeService.save(sysNotice); redisService.addNotice(softItemSoftware.getUploadFileUser(), sysNotice); //通知 WebSocketSession webSocketSession = SocketManager.get(softItemSoftware.getUploadFileUser()); if (webSocketSession != null) { /** * 主要防止broken pipe */ JSONObject noticeResult = new JSONObject(); noticeResult.put("add", sysNotice); template.convertAndSendToUser(softItemSoftware.getUploadFileUser(), "/queue/notice", noticeResult.toJSONString()); } } @Override @Transactional(rollbackFor = Exception.class) public void uploadFileBySoftware(SysUserVo currentUser, SoftwareParam softwareParam) { if (softwareParam.getFiles().length <= 0) { throw new BusinessException(500, "文件不能为空"); } if (!currentUser.getCode().equals(softwareParam.getUploadFileUser())) { throw new BusinessException(500, "当前上传用户与指定上传用户不一致"); } //添加文件至OSS File SoftItemSoftware softItemSoftware = softwareService.getById(softwareParam.getSoftwareId()); if (Objects.isNull(softItemSoftware)) { throw new BusinessException(500, "软件信息不存在!"); } BeanUtil.copyProperties(softwareParam, softItemSoftware); List ossFileRecordList = new ArrayList<>(); List softSoftwareRelations = new ArrayList<>(); for (int i = 0; i < softwareParam.getFiles().length; i++) { FileParam fileParam = softwareParam.getFiles()[i]; OssFileRecord ossFileRecord = new OssFileRecord(); ossFileRecord.setFileId(fileParam.getSignature()); ossFileRecord.setFileName(fileParam.getFileName()); ossFileRecord.setCreateTime(softItemSoftware.getPublishDate()); ossFileRecord.setCreateUser(currentUser.getCode()); ossFileRecord.setSize(fileParam.getSize()); ossFileRecord.setStatus(OssFileRecord.FileStatus.CREATE.name()); ossFileRecord.setFileType(fileParam.getSuffix()); ossFileRecord.setOssUrl(fileParam.getUrl()); ossFileRecordList.add(ossFileRecord); //关系映射 SoftSoftwareRelation relation = new SoftSoftwareRelation(); relation.setFileId(ossFileRecord.getFileId()); relation.setSoftwareCode(softItemSoftware.getSoftwareCode()); relation.setType(fileParam.getType()); softSoftwareRelations.add(relation); } softItemSoftware.setFlag(SoftItemSoftware.Status.UPLOADING.name()); softwareService.updateById(softItemSoftware); //保存oss file record ossFileRecordService.saveBatch(ossFileRecordList); //保存文件映射 softwareRelationService.saveBatch(softSoftwareRelations); //保存软件信息 //缓存待上传文件 List currentWaitFiles = Arrays.stream(softwareParam.getFiles()).collect(Collectors.toList()); currentWaitFiles.forEach((fileParam -> { fileParam.setSoftwareCode(softItemSoftware.getSoftwareCode()); })); redisService.addFile(currentUser.getCode(), currentWaitFiles); } @Override public List selectSoftwareItem(SoftwareQuery softItemSoftware) { return baseMapper.selectSoftwareItem(softItemSoftware); } @Override public List selectSoftwareManagement(SoftwareQuery condition) { return baseMapper.selectSoftwareManagement(condition); } @Override @Transactional(rollbackFor = Exception.class) public void deleteSoftware(String userCode, Integer id) { SoftItemSoftware softItemSoftware = softwareService.getById(id); if (Objects.isNull(softItemSoftware)) { throw new BusinessException(500, "找不到软件"); } if (softItemSoftware.getFlag().equals(SoftItemSoftware.Status.CREATE.name()) || softItemSoftware.getFlag().equals(SoftItemSoftware.Status.PENDING.name())) { //删除软件记录 softItemSoftware.setFlag(SoftItemSoftware.Status.DELETE.name()); softwareService.updateById(softItemSoftware); //删除OSS文件 noticeService.deleteNoticeByRelation(String.valueOf(softItemSoftware.getSoftwareId())); //删除通知 redisService.removeNoticeBySoftwareId(String.valueOf(softItemSoftware.getSoftwareId()), softItemSoftware.getUploadFileUser()); //通知 WebSocketSession webSocketSession = SocketManager.get(softItemSoftware.getUploadFileUser()); if (webSocketSession != null) { /** * 主要防止broken pipe */ List sysNotices = redisService.getAllNotice(userCode); sysNotices.removeIf((file) -> file.getRelationField().equals(String.valueOf(softItemSoftware.getSoftwareId()))); JSONObject noticeResult = new JSONObject(); noticeResult.put("override", sysNotices); template.convertAndSendToUser(softItemSoftware.getUploadFileUser(), "/queue/notice", noticeResult.toJSONString()); } } else { throw new BusinessException(500, "当前状态不允许删除"); } } @Override @Transactional(rollbackFor = Exception.class) public void updateSoftware(SysUserVo currentUser, SoftwareParam softwareParam) { SoftItemSoftware softItemSoftware = softwareService.getById(softwareParam.getSoftwareId()); if (Objects.isNull(softItemSoftware)) { throw new BusinessException(500, "软件信息不存在"); } if (softwareParam.getFiles().length == 0) { BeanUtil.copyProperties(softwareParam, softItemSoftware); softwareService.updateById(softItemSoftware); } else { if (!currentUser.getCode().equals(softwareParam.getUploadFileUser())) { throw new BusinessException(500, "当前上传用户与指定上传用户不一致"); } if (softItemSoftware.getFlag().equals(SoftItemSoftware.Status.CREATE.name()) || softItemSoftware.getFlag().equals(SoftItemSoftware.Status.PENDING.name())) { List newFiles = Arrays.stream(softwareParam.getFiles()).filter(FileParam::isCreate).collect(Collectors.toList()); List removeFiles = Arrays.stream(softwareParam.getFiles()).filter(FileParam::isDelete).collect(Collectors.toList()); List removeRecords = removeFiles.stream().map(FileParam::getSignature).collect(Collectors.toList()); List newRelations = new ArrayList<>(); List newRecords = new ArrayList<>(); newFiles.forEach((item) -> { SoftSoftwareRelation relation = new SoftSoftwareRelation(); relation.setFileId(item.getSignature()); relation.setSoftwareCode(softItemSoftware.getSoftwareCode()); relation.setType(item.getType()); newRelations.add(relation); OssFileRecord ossFileRecord = new OssFileRecord(); ossFileRecord.setFileId(item.getSignature()); ossFileRecord.setFileName(item.getFileName()); ossFileRecord.setCreateTime(softItemSoftware.getPublishDate()); ossFileRecord.setCreateUser(currentUser.getCode()); ossFileRecord.setSize(item.getSize()); ossFileRecord.setStatus(OssFileRecord.FileStatus.CREATE.name()); ossFileRecord.setFileType(item.getSuffix()); ossFileRecord.setOssUrl(item.getUrl()); newRecords.add(ossFileRecord); }); if (!removeFiles.isEmpty()) { softwareRelationService.removeByIds(removeRecords); }//添加新的记录 softwareRelationService.saveBatch(newRelations); ossFileRecordService.saveBatch(newRecords); if (!removeFiles.isEmpty()) { //删除阿里云OSS旧文件 OSSClient client = OssClient.initOSS(properties); List urls = new ArrayList<>(); removeFiles.forEach(oss -> { urls.add(softItemSoftware.getBurnAfterItemCode() + '/' + oss.getFileName()); }); urls.forEach((url) -> { client.deleteObject(properties.getBucket(), url); }); //删除旧文件记录 ossFileRecordService.removeByIds(removeRecords); } redisService.addFile(currentUser.getCode(), newFiles); } else { throw new BusinessException(500, "当前状态不允许修改"); } } } @Override public SoftItemSoftware selectSoftwareByCode(String code) { return baseMapper.selectSoftwareByCode(code); } @Override @Transactional(rollbackFor = Exception.class) public void publishTest(SysUserVo currentUser, Integer id) { SoftItemSoftware softItemSoftware = softwareService.getById(id); if (Objects.isNull(softItemSoftware)) { throw new NullPointerException("软件不存在"); } if (!softItemSoftware.getFlag().equals(SoftItemSoftware.Status.PENDING.name())) { throw new BusinessException(500, "当前状态不允许发布"); } softItemSoftware.setFlag(SoftItemSoftware.Status.TEST.name()); softItemSoftware.setServiceStatus(SoftItemSoftware.WindowsServiceStatus.TEST.name()); SysNotice sysNotice = new SysNotice(); sysNotice.setType(SysNotice.Type.B.name()); sysNotice.setHandleUrl(null); sysNotice.setSubject(softItemSoftware.getSoftwareName() + " 已发布"); sysNotice.setToUser(softItemSoftware.getReceiveUser()); sysNotice.setStatus(SysNotice.Status.CREATE.name()); sysNotice.setMessage(softItemSoftware.getSoftwareName() + " 已发布,请接收"); sysNotice.setFromUsername(currentUser.getName()); sysNotice.setFromUser(currentUser.getCode()); sysNotice.setToUser(softItemSoftware.getReceiveUser()); sysNotice.setCreateTime(LocalDateTime.now()); sysNotice.setRelationField(String.valueOf(softItemSoftware.getSoftwareId())); //审批单 SysItemApproval approval = new SysItemApproval(); approval.setFlag(SysItemApproval.Status.TEST.name()); approval.setSoftwareId(softItemSoftware.getSoftwareId()); approval.setTopFgItem(softItemSoftware.getBurnAfterItemCode()); approval.setLastModifyDate(LocalDateTime.now()); approval.setLastModifyUser(currentUser.getCode()); approvalService.save(approval); softItemSoftware.setApprovalId(approval.getId()); softwareService.updateById(softItemSoftware); noticeService.save(sysNotice); //通知 WebSocketSession webSocketSession = SocketManager.get(softItemSoftware.getReceiveUser()); if (webSocketSession != null) { /** * 主要防止broken pipe */ JSONObject noticeResult = new JSONObject(); noticeResult.put("add", sysNotice); template.convertAndSendToUser(softItemSoftware.getUploadFileUser(), "/queue/notice", noticeResult.toJSONString()); } redisService.addNotice(softItemSoftware.getReceiveUser(), sysNotice); } @Override public List selectTestSoftwareByME(String status, String userCode) { return baseMapper.selectTestSoftwareByME(status, userCode); } @Override public List selectSoftwareByPlant(String status, String plant) { return baseMapper.selectSoftwareByPlant(status, plant); } @Override public JSONObject getReceiveSoftwareDetail(SysUserVo sysUserVo, Integer softwareId) { //检查软件状态 SoftItemSoftware softItemSoftware = softwareService.getById(softwareId); if (Objects.isNull(softItemSoftware)) { throw new NullPointerException("没有找到软件信息"); } if (!softItemSoftware.getReceiveUser().equals(sysUserVo.getCode())) { throw new BusinessException(500, "你不是指定的接收人"); } if (!softItemSoftware.getFlag().equals(SoftItemSoftware.Status.TEST.name())) { throw new BusinessException(500, "该软件当前不能接收"); } DataPlant plant = plantService.getById(sysUserVo.getPlant()); if (Objects.isNull(plant)) { throw new NullPointerException("接收人工厂没有配置"); } Date expiration = new Date(new Date().getTime() + 3600 * 1000); //生成文件下载地址 List fileParams = ossFileRecordService.selectOssFileParam(softwareId); JSONArray files = new JSONArray(); for (FileParam fileParam : fileParams) { JSONObject fileItem = JSON.parseObject(JSON.toJSONString(fileParam)); //oss 下载地址 URL ossUrl = OssClient.initOSS(properties).generatePresignedUrl(properties.getBucket(), softItemSoftware.getBurnAfterItemCode() + '/' + fileParam.getFileName(), expiration); //本地下载地址 fileItem.put("onlineUrl", ossUrl.toString()); files.add(fileItem); } JSONObject result = new JSONObject(); JSONObject softItemSoftwareObj = JSON.parseObject(JSON.toJSONString(softItemSoftware)); DataProject project = projectService.getById(softItemSoftware.getProjectId()); softItemSoftwareObj.put("projectName", project.getCode() + "/" + project.getName()); result.put("software", softItemSoftwareObj); result.put("files", files); return result; } @Override @Transactional(rollbackFor = Exception.class) public void approveSoftware(SysUserVo currentUser, ApprovalSoftwareParam param) { SoftItemSoftware softItemSoftware = softwareService.getById(param.getSoftwareId()); if (Objects.isNull(softItemSoftware)) { throw new NullPointerException("没有找到软件信息"); } SysItemApproval approval = approvalService.getById(softItemSoftware.getApprovalId()); if (Objects.isNull(approval)) { throw new NullPointerException("审批单不存在"); } if (!StringUtils.isEmpty(param.getRemark())) { approval.setComment(param.getRemark()); } if (!StringUtils.isEmpty(param.getTryOutFileName())) { approval.setTestReportFileName(param.getTryOutFileName()); } approval.setLastModifyUser(currentUser.getCode()); approval.setLastModifyDate(LocalDateTime.now()); if (param.getStatus().equals(SysItemApproval.Status.REJECT.name())) { approval.setFlag(SysItemApproval.Status.REJECT.name()); softItemSoftware.setFlag(SoftItemSoftware.Status.PENDING.name()); softItemSoftware.setServiceStatus(SoftItemSoftware.WindowsServiceStatus.DELETE.name()); softItemSoftware.setLastModifyPushDate(null); SysNotice notice = new SysNotice(); notice.setType(SysNotice.Type.B.name()); notice.setFromUser(currentUser.getCode()); notice.setFromUsername(currentUser.getName()); notice.setToUser(softItemSoftware.getPublisher()); notice.setStatus(SysNotice.Status.CREATE.name()); notice.setSubject(softItemSoftware.getSoftwareName() + " 审批被退回"); notice.setMessage("退回原因 : " + param.getRemark()); notice.setRelationField(softItemSoftware.getSoftwareId().toString()); notice.setCreateTime(LocalDateTime.now()); pushMachineService.deleteBySoftwareId(softItemSoftware.getSoftwareId()); approvalService.updateById(approval); softwareService.updateById(softItemSoftware); noticeService.save(notice); redisService.addNotice(softItemSoftware.getPublisher(), notice); WebSocketSession webSocketSession = SocketManager.get(softItemSoftware.getReceiveUser()); if (webSocketSession != null) { /** * 主要防止broken pipe */ JSONObject noticeResult = new JSONObject(); noticeResult.put("add", notice); template.convertAndSendToUser(softItemSoftware.getUploadFileUser(), "/queue/notice", noticeResult.toJSONString()); } } else if (param.getStatus().equals(SysItemApproval.Status.APPROVE.name())) { //审批通过 if (param.getEffectiveDate().isEmpty()) { throw new NullPointerException("发布时间不能为空"); } if (param.getTryOutFileName().isEmpty()) { throw new NullPointerException("试装结论不能为空"); } softItemSoftware.setFlag(SoftItemSoftware.Status.PASS.name()); softItemSoftware.setEffectiveDate(DateUtil.strToLocalDateTime(param.getEffectiveDate())); approval.setFlag(SysItemApproval.Status.APPROVE.name()); approval.setReleaseDate(DateUtil.strToLocalDateTime(param.getEffectiveDate())); approval.setTestReportFileName(param.getTryOutFileName()); SysNotice notice = new SysNotice(); notice.setType(SysNotice.Type.B.name()); notice.setFromUser(currentUser.getCode()); notice.setFromUsername(currentUser.getName()); notice.setToUser(softItemSoftware.getPublisher()); notice.setStatus(SysNotice.Status.CREATE.name()); notice.setSubject(softItemSoftware.getSoftwareName() + " 已被接收"); notice.setMessage("正式发布时间:" + param.getEffectiveDate()); notice.setRelationField(softItemSoftware.getSoftwareId().toString()); notice.setCreateTime(LocalDateTime.now()); softwareService.updateById(softItemSoftware); approvalService.updateById(approval); noticeService.save(notice); redisService.addNotice(softItemSoftware.getPublisher(), notice); WebSocketSession webSocketSession = SocketManager.get(softItemSoftware.getReceiveUser()); if (webSocketSession != null) { /** * 主要防止broken pipe */ JSONObject noticeResult = new JSONObject(); noticeResult.put("add", notice); template.convertAndSendToUser(softItemSoftware.getUploadFileUser(), "/queue/notice", noticeResult.toJSONString()); } } else { throw new BusinessException(500, "状态异常"); } } @Override public List pingServiceMachine(String plant, String windowsStatus, String status) { return baseMapper.pingServiceMachine(plant, windowsStatus, status); } @Override public List pingServiceMachine(String plant, String status) { return getBaseMapper().pingServiceMachineRelease(plant, status); } @Override @Transactional(rollbackFor = Exception.class) public void pushMachine(SysUserVo currentUser, Integer softwareId, Integer[] machineIds) { SoftItemSoftware softItemSoftware = softwareService.getById(softwareId); if (Objects.isNull(softItemSoftware)) { throw new NullPointerException("软件信息不存在"); } softItemSoftware.setSoftwareId(softwareId); List softPushMachines = new ArrayList<>(); for (Integer machineId : machineIds) { SoftPushMachine machine = new SoftPushMachine(); machine.setMachineId(machineId); machine.setSoftwareId(softwareId); machine.setPublishDate(LocalDateTime.now()); machine.setPublishName(currentUser.getName()); machine.setPublisher(currentUser.getCode()); machine.setPlant(softItemSoftware.getTargetPlant()); softPushMachines.add(machine); } softItemSoftware.setLastModifyPushDate(LocalDateTime.now()); pushMachineService.deleteBySoftwareId(softwareId); pushMachineService.batchInsert(softPushMachines); softwareService.updateById(softItemSoftware); } @Override public List selectBurnItemCode() { return baseMapper.selectBurnItemCode(); } @Override public List pingServiceDelete(String plant, String windowsServiceStatus) { return baseMapper.pingServiceDelete(plant, windowsServiceStatus); } @Override public List pingServiceMachineRelease(String plant, String status) { return baseMapper.pingServiceMachineRelease(plant, status); } @Override public SoftItemSoftware selectPreviousVersionSoftware(String plant, String itemCode) { return baseMapper.selectPreviousVersionSoftware(plant, itemCode); } @Override public void firstSerialItemCode(Integer softwareId, String firstSerialNumber) { SoftItemSoftware softItemSoftware = softwareService.getById(softwareId); softItemSoftware.setFirstProductSerial(firstSerialNumber); softwareService.updateById(softItemSoftware); } } ```