自动化立体仓库 - WMS系统
lty
14 小时以前 01fa93b5dd9f20286ef8f22515caf55307f76a9d
Merge remote-tracking branch 'origin/jxhcasrs' into jxhcasrs
3个文件已删除
12个文件已添加
41个文件已修改
3127 ■■■■ 已修改文件
license.lic 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/api/controller/HWmsApiController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/api/controller/HmesApiController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/api/service/impl/HmesApiServiceImpl.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/api/service/impl/WcsApiServiceImpl.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/LocDetlController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/OutController.java 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/LocAroundBind.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/WrkMast.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/WrkMastMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/WorkService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/WrkMastService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/WorkServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/WrkMastServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/OrderSyncScheduler.java 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/WorkMastScheduler.java 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/handler/WorkMastHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/config/AdminInterceptor.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/service/CommonService.java 249 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/web/BaseController.java 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/web/WcsController.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/config/LicenseSchemaInitializer.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/controller/LicenseCreatorController.java 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/LicenseInfos.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/AbstractServerInfos.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/CustomLicenseManager.java 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseBindModel.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseBindingSupport.java 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseCheckListener.java 110 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseCreator.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseCreatorParam.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseNodeCheck.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseUploadParam.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseUtils.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseVerify.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseVerifyParam.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LinuxServerInfos.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/WindowsServerInfos.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/mapper/LicenseInfosMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/service/LicenseInfosService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/timer/LicenseTimer.java 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-prod.yml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/license.lic 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/LicenseInfosMapper.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/LocDetlMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/OrderDetlPakoutMapper.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/WrkMastMapper.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/locAroundBind/locAroundBind.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/locDetl/locDetl.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/wrkMast/wrkMast.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/basDevice/basDevice.html 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/index.html 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/locDetl/locDetl.html 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/login.html 664 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
license.lic
Binary files differ
src/main/java/com/zy/api/controller/HWmsApiController.java
@@ -1,6 +1,7 @@
package com.zy.api.controller;
import com.alibaba.fastjson.JSONObject;
import com.zy.api.controller.params.PageRequestParams;
import com.zy.api.entity.PubOrderParams;
import com.zy.api.entity.ReportOrderParam;
@@ -44,6 +45,7 @@
    @ApiOperation("入库单下发")
    @PostMapping("/sendInDispatch")
    public XSR receiveOrders(@RequestBody List<PubOrderParams> params) {
        log.info(JSONObject.toJSONString(params));
        if (Objects.isNull(params)) {
            return XSR.error("参数不能为空!!");
        }
src/main/java/com/zy/api/controller/HmesApiController.java
@@ -1,6 +1,7 @@
package com.zy.api.controller;
import com.alibaba.fastjson.JSONObject;
import com.core.annotations.ManagerAuth;
import com.core.common.Cools;
import com.core.common.R;
@@ -9,6 +10,7 @@
import com.zy.asrs.entity.param.OpenOrderPakoutParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -19,6 +21,7 @@
@Api(value = "HMES对接")
@RestController
@Slf4j
@RequestMapping("/api")
public class HmesApiController {
@@ -35,6 +38,7 @@
    @ApiOperation("下发生产任务")
    @PostMapping("/work/tasks")
    public R menauWork(@RequestBody OpenOrderPakoutParam params) {
        log.info("下发生产任务={}", JSONObject.toJSONString(params));
        if (Objects.isNull(params)) {
            return R.error("参数不能为空!!");
        }
src/main/java/com/zy/api/service/impl/HmesApiServiceImpl.java
@@ -17,6 +17,7 @@
import com.zy.asrs.service.impl.OrderDetlPakoutServiceImpl;
import com.zy.asrs.service.impl.OrderPakoutServiceImpl;
import com.zy.common.model.DetlDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -25,6 +26,7 @@
import java.util.stream.Collectors;
@Service
@Slf4j
public class HmesApiServiceImpl implements HmesApiService {
    @Autowired
@@ -75,12 +77,60 @@
        // 如果单据不存在则添加;如果单据存在,作业中无法修改,反之则修改单据
        if (!Cools.isEmpty(order)) {
            if (order.getSettle() > 1L) {
                log.error(param.getOrderNo() + "正在出库,无法修改单据");
                throw new CoolException(param.getOrderNo() + "正在出库,无法修改单据");
            }
            orderPakoutService.remove(order.getId());
        }
        DocType docType = docTypeService.selectOrAdd(param.getBillType(), Boolean.FALSE);
        Date now = new Date();
        // 单据明细档
        List<DetlDto> list = new ArrayList<>();
        List<DetlDto> orderDetails = param.getMatList();
        for (DetlDto detail : orderDetails) {
            DetlDto dto = new DetlDto(detail.getMatnr(), detail.getBatch(),detail.getBrand(),detail.getDevNo(),detail.getStandby2(),detail.getStandby3(),detail.getLineNumber(),
                    detail.getBoxType1(),detail.getBoxType2(),detail.getBoxType3(), detail.getAnfme());
            if (DetlDto.hasLineNumber(list, dto)) {
                DetlDto detlDto = DetlDto.findLineNumber(list, dto.getMatnr(), dto.getBatch(),dto.getBrand(),dto.getDevNo(),dto.getStandby2(),dto.getStandby3(),dto.getLineNumber(),
                        dto.getBoxType1(),dto.getBoxType2(),dto.getBoxType3());
                assert detlDto != null;
                detlDto.setAnfme(detlDto.getAnfme() + detail.getAnfme());
            } else {
                list.add(dto);
            }
        }
        //下发的订单明细,查看库存是否有足够的库存,排除机台的库位
         for (DetlDto detail : list) {
            List<LocDetl> matnr = locDetlService.selectList(new EntityWrapper<LocDetl>().eq("matnr", detail.getMatnr()));
            Double count=0.0;
            for (LocDetl detl : matnr) {
                LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_no", detl.getLocNo()).eq("loc_sts","F"));
                if (locMast != null) {
                    LocAroundBind bLocNo = locAroundBindService.selectOne(new EntityWrapper<LocAroundBind>().eq("b_loc_no", locMast.getLocNo()));
                    if (Cools.isEmpty(bLocNo)) {
                        count=detl.getAnfme()+count;
                    }
                }
            }
            if (count<detail.getAnfme()) {
                return R.error("物料="+detail.getMatnr()+"库存不足无法生成订单");
            }
            //机台有冻结时禁止呼叫物料
            BasDevice basDevice = basDeviceService.selectOne(new EntityWrapper<BasDevice>()
                    .eq("status", 1)
                    .eq("dev_no", detail.getStandby1()));
            if (Objects.isNull(basDevice)) {
                return R.error(detail.getStandby1()+"机台信息不存在或已禁用!!");
            }
            List<LocAroundBind> binds = locAroundBindService.selectList(new EntityWrapper<LocAroundBind>()
                    .eq("dev_no", basDevice.getType())
                    .eq("freeze", 1));
            if (!Cools.isEmpty(binds)) {
                return  R.error(detail.getStandby1()+"机台被冻结无法呼叫物料");
            }
        }
        // 单据主档
        order = new OrderPakout(
                String.valueOf(snowflakeIdWorker.nextId()),    // 编号[非空]
@@ -120,38 +170,6 @@
        );
        if (!orderPakoutService.insert(order)) {
            throw new CoolException("生成单据主档失败,请联系管理员");
        }
        // 单据明细档
        List<DetlDto> list = new ArrayList<>();
        List<DetlDto> orderDetails = param.getMatList();
        for (DetlDto detail : orderDetails) {
            DetlDto dto = new DetlDto(detail.getMatnr(), detail.getBatch(),detail.getBrand(),detail.getDevNo(),detail.getStandby2(),detail.getStandby3(),detail.getLineNumber(),
                    detail.getBoxType1(),detail.getBoxType2(),detail.getBoxType3(), detail.getAnfme());
            if (DetlDto.hasLineNumber(list, dto)) {
                DetlDto detlDto = DetlDto.findLineNumber(list, dto.getMatnr(), dto.getBatch(),dto.getBrand(),dto.getDevNo(),dto.getStandby2(),dto.getStandby3(),dto.getLineNumber(),
                        dto.getBoxType1(),dto.getBoxType2(),dto.getBoxType3());
                assert detlDto != null;
                detlDto.setAnfme(detlDto.getAnfme() + detail.getAnfme());
            } else {
                list.add(dto);
            }
        }
        //下发的订单明细,查看库存是否有足够的库存,排除机台的库位
        for (DetlDto detail : list) {
            List<LocDetl> matnr = locDetlService.selectList(new EntityWrapper<LocDetl>().eq("matnr", detail.getMatnr()));
            Double count=0.0;
            for (LocDetl detl : matnr) {
                LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_no", detl.getLocNo()).eq("loc_sts","F"));
                if (locMast != null) {
                    LocAroundBind bLocNo = locAroundBindService.selectOne(new EntityWrapper<LocAroundBind>().eq("b_loc_no", locMast.getLocNo()));
                    if (Cools.isEmpty(bLocNo)) {
                        count=detl.getAnfme()+count;
                    }
                }
            }
            if (count<detail.getAnfme()) {
                return R.error("物料="+detail.getMatnr()+"库存不足无法生成订单");
            }
        }
        for (DetlDto detlDto : list) {
            Mat mat = matService.selectByMatnr(detlDto.getMatnr());
src/main/java/com/zy/api/service/impl/WcsApiServiceImpl.java
@@ -123,9 +123,9 @@
//       if (!wrkMastService.updateById(mast)) {
//           throw new CoolException("任务状态更新失败!!");
//       }
        workService.backLocOperation(mast.getWrkNo() + "", mast.getAppeUser());
        return R.ok("接收成功,执行回库中...");
        return workService.backLocOperation(mast.getWrkNo() + "", mast.getAppeUser());
    }
    /**
@@ -153,7 +153,7 @@
        }else if (!Objects.isNull(params.getType()) && params.getType().equals("move")) {
            url = createLocMoveTask;
        }
        String response;
        String response = null;
        R r = R.ok();
        try {
            log.info("下发搬运任务给wcs="+JSON.toJSONString(params));
@@ -201,7 +201,7 @@
                                }
                            }
                        }
                    }else if (wrkMast.getIoType()==101){
                    }else if (wrkMast.getIoType()==101 || wrkMast.getIoType()==110) {
                        wrkMast.setWrkSts(12L);
                        wrkMast.setModiTime(new Date());
                        wrkMastService.updateById(wrkMast);
@@ -233,15 +233,21 @@
                }
                //TODO 上报是否成功
            }else {
                r =R.error();
                String msg = jsonObject.getString("msg");
                if (Cools.isEmpty(msg)) {
                    msg = jsonObject.getString("message");
                }
                r = R.error(Cools.isEmpty(msg) ? "下发任务失败" : msg);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
            log.error("下发任务给wcs异常, request={}", JSON.toJSONString(params), e);
            return R.error("调用WCS接口失败:" + e.getMessage());
        } catch (Exception e) {
            log.error("解析WCS下发结果异常, request={}, response={}", JSON.toJSONString(params), response, e);
            return R.error("WCS返回结果异常:" + e.getMessage());
        }
        return r;
    }
    /**
     * 上报锁定/释放库位信息
     *
@@ -258,7 +264,7 @@
                url = MesConstant.RELEASE_LOCS_URL;
            }
        }
        String response;
        String response = null;
        try {
            response = new HttpHandler.Builder()
                    .setUri(MesConstant.URL)
@@ -370,6 +376,17 @@
                                    }
                                } else {
                                    throw new CoolException("任务号截取失败,请检查主任务档任档wrkCode字段");
                                }
                            }else{
                                String wrkNo = mast.getWrkCode();
                                WrkMast orgWrk = wrkMastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", wrkNo));
                                if (Objects.isNull(orgWrk)) {
                                    throw new CoolException("数据错误,主任务档不存在或已删除!!");
                                }
                                mast.setOveMk("Y");
                                orgWrk.setOveMk("Y");
                                if (!wrkMastService.updateById(orgWrk)) {
                                    throw new CoolException("任务档修改失败!!");
                                }
                            }
                        }
@@ -509,3 +526,5 @@
        return locAround;
    }
}
src/main/java/com/zy/asrs/controller/LocDetlController.java
@@ -189,6 +189,7 @@
//            }
//        }
        excludeTrash(param);
        applyBeBatchFilter(param, wrapper);
        convert(param, wrapper);
        allLike(LocDetl.class, param.keySet(), wrapper, condition);
        if (!Cools.isEmpty(orderByField)){wrapper.orderBy(humpToLine(orderByField), "asc".equals(orderByType));}
@@ -215,6 +216,29 @@
                }
            }
        }
    }
    private <T> void applyBeBatchFilter(Map<String, Object> map, EntityWrapper<T> wrapper) {
        Object beBatchObj = map.remove("beBatch");
        if (beBatchObj == null) {
            beBatchObj = map.remove("be_batch");
        }
        if (beBatchObj == null) {
            return;
        }
        String beBatch = String.valueOf(beBatchObj).trim();
        if (Cools.isEmpty(beBatch)) {
            return;
        }
        if ("1".equals(beBatch)) {
            wrapper.eq("be_batch", 1);
            return;
        }
        if ("0".equals(beBatch)) {
            wrapper.andNew().isNull("be_batch").or().eq("be_batch", 0);
            return;
        }
        wrapper.eq("be_batch", beBatch);
    }
    @RequestMapping(value = "/locDetl/add/auth")
@@ -290,6 +314,7 @@
                map.remove("row");
            }
        }
        applyBeBatchFilter(map, wrapper);
        convert(map, wrapper);
        if (!row.equals("")) {
            wrapper.and()
src/main/java/com/zy/asrs/controller/OutController.java
@@ -14,8 +14,8 @@
import com.zy.common.model.LocDto;
import com.zy.common.model.TaskDto;
import com.zy.common.web.BaseController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
@@ -29,6 +29,7 @@
 * Created by vincent on 2022/3/26
 */
@Slf4j
@RestController
public class OutController extends BaseController {
@@ -88,8 +89,8 @@
                if (issued <= 0.0D) {
                    continue;
                }
                List<LocDetl> locDetls = locDetlService.queryStockAll(null, exist,orderDetl.getMatnr(), orderDetl.getBatch(),
                        orderDetl.getBrand(),orderDetl.getStandby1(),orderDetl.getStandby2(),orderDetl.getStandby3(),orderDetl.getBoxType1(),orderDetl.getBoxType2(),orderDetl.getBoxType3());
                List<LocDetl> locDetls = locDetlService.queryStockAll(null, exist, orderDetl.getMatnr(), orderDetl.getBatch(),
                        orderDetl.getBrand(), orderDetl.getStandby1(), orderDetl.getStandby2(), orderDetl.getStandby3(), orderDetl.getBoxType1(), orderDetl.getBoxType2(), orderDetl.getBoxType3());
                for (LocDetl locDetl : locDetls) {
                    if (issued > 0) {
                        LocDto locDto = new LocDto(locDetl.getLocNo(), locDetl.getMatnr(), locDetl.getMaktx(), locDetl.getBatch(), orderDetl.getOrderNo(),
@@ -136,8 +137,8 @@
                if (issued <= 0.0D) {
                    continue;
                }
                List<LocDetl> locDetls = locDetlService.queryStockAll(null, exist,orderDetl.getMatnr(), orderDetl.getBatch(),
                        orderDetl.getBrand(),orderDetl.getStandby1(),orderDetl.getStandby2(),orderDetl.getStandby3(),orderDetl.getBoxType1(),orderDetl.getBoxType2(),orderDetl.getBoxType3());
                List<LocDetl> locDetls = locDetlService.queryStockAll(null, exist, orderDetl.getMatnr(), orderDetl.getBatch(),
                        orderDetl.getBrand(), orderDetl.getStandby1(), orderDetl.getStandby2(), orderDetl.getStandby3(), orderDetl.getBoxType1(), orderDetl.getBoxType2(), orderDetl.getBoxType3());
                for (LocDetl locDetl : locDetls) {
                    if (issued > 0) {
                        LocDto locDto = new LocDto(locDetl.getLocNo(), locDetl.getMatnr(), locDetl.getMaktx(), locDetl.getBatch(), orderDetl.getOrderNo(),
@@ -181,14 +182,13 @@
    @PostMapping("/out/pakout/auth")
    @ManagerAuth(memo = "订单出库")
    @Transactional
    public synchronized R pakout(@RequestBody List<LocDto> locDtos) throws InterruptedException {
        if (Cools.isEmpty(locDtos)) {
            return R.parse(BaseRes.PARAM);
        }
        List<LocDto> locDtoArrayList = new ArrayList<>();
        for (LocDto locDto : locDtos){
            if (locDto.getFrozen()!=1 && locDto.getFrozenLoc()!=1){
        for (LocDto locDto : locDtos) {
            if (locDto.getFrozen() != 1 && locDto.getFrozenLoc() != 1) {
                locDtoArrayList.add(locDto);
            }
        }
@@ -212,7 +212,9 @@
        List<TaskDto> taskDtos = new ArrayList<>();
        // 根据 (库位 & 出库站) 分组; 理想状态:一组为一次出库任务
        for (LocDto locDto : locDtos) {
            if (locDto.isLack()) { continue; }
            if (locDto.isLack()) {
                continue;
            }
            TaskDto taskDto = new TaskDto(locDto.getLocNo(), locDto.getStaNo(), locDto, locDto.getStandby1());
            if (TaskDto.has(taskDtos, taskDto)) {
                TaskDto dto = TaskDto.find(taskDtos, taskDto);
@@ -223,16 +225,46 @@
            }
        }
        // -----------------------------------------------------------------------------------------------
        int failCount = 0;
        for (TaskDto taskDto : taskDtos) {
            BasDevp staNo = basDevpService.checkSiteStatus(taskDto.getStaNo());
            if (!Objects.isNull(taskDto.getDeviceNo())) {
                //生成出库任务
                workService.stockOut(staNo, taskDto, taskDto.getDeviceNo(), getUserId());
            } else  {
                workService.stockOut(staNo, taskDto, null, getUserId());
            if (!stockOutSafely(taskDto, getUserId(), "手动订单出库")) {
                failCount++;
            }
        }
        if (failCount > 0) {
            return R.ok("部分出库任务已生成,成功" + (taskDtos.size() - failCount) + "条,失败" + failCount + "条,请查看日志");
        }
        return R.ok();
    }
    private boolean stockOutSafely(TaskDto taskDto, Long userId, String scene) {
        try {
            BasDevp staNo = basDevpService.checkSiteStatus(taskDto.getStaNo());
            workService.stockOut(staNo, taskDto, taskDto.getDeviceNo(), userId);
            return true;
        } catch (Exception e) {
            log.error("{}失败[locNo={}, staNo={}, deviceNo={}, orderNos={}],原因: {}",
                    scene,
                    taskDto.getLocNo(),
                    taskDto.getStaNo(),
                    taskDto.getDeviceNo(),
                    collectOrderNos(taskDto),
                    getErrorMessage(e));
            return false;
        }
    }
    private String collectOrderNos(TaskDto taskDto) {
        Set<String> orderNos = new LinkedHashSet<>();
        for (LocDto locDto : taskDto.getLocDtos()) {
            if (!Cools.isEmpty(locDto.getOrderNo())) {
                orderNos.add(locDto.getOrderNo());
            }
        }
        return orderNos.toString();
    }
    private String getErrorMessage(Exception e) {
        return Cools.isEmpty(e.getMessage()) ? e.getClass().getSimpleName() : e.getMessage();
    }
}
src/main/java/com/zy/asrs/entity/LocAroundBind.java
@@ -2,9 +2,11 @@
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;import com.baomidou.mybatisplus.annotations.TableField;
import com.core.common.SpringUtils;
import com.zy.asrs.service.BasDeviceService;
import com.zy.asrs.service.BasLocStsService;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -85,6 +87,15 @@
            return "默认";
        }
    }
    public String getDevNo$() {
        BasDeviceService bean = SpringUtils.getBean(BasDeviceService.class);
        BasDevice type = bean.selectOne(new EntityWrapper<BasDevice>().eq("type", devNo));
        if (type == null) {
            return "<UNK>";
        }else {
            return type.getDevNo();
        }
    }
    public String getLocType$() {
        if (this.locType == null) {
src/main/java/com/zy/asrs/entity/WrkMast.java
@@ -32,6 +32,8 @@
    private Integer wrkNo;
    @ApiModelProperty("任务类型: agv, crn")
    @TableField("task_type")
    @TableId(value = "task_type")
    private String taskType;
    @ApiModelProperty("任务编码")
src/main/java/com/zy/asrs/mapper/WrkMastMapper.java
@@ -7,6 +7,7 @@
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.Date;
import java.util.List;
@Mapper
@@ -25,4 +26,9 @@
            , @Param("standby1")String standby1, @Param("standby2")String standby2, @Param("standby3")String standby3
            , @Param("boxType1")String boxType1, @Param("boxType2")String boxType2, @Param("boxType3")String boxType3, @Param("crnNo") Integer crnNo);
    int updatePublishError(@Param("wrkNo") Integer wrkNo,
                           @Param("updMk") String updMk,
                           @Param("errorTime") Date errorTime,
                           @Param("errorMemo") String errorMemo);
}
src/main/java/com/zy/asrs/service/WorkService.java
@@ -1,5 +1,6 @@
package com.zy.asrs.service;
import com.core.common.R;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.WaitPakin;
import com.zy.asrs.entity.WrkMast;
@@ -97,7 +98,7 @@
     * @param workNo
     * @param userId
     */
    void backLocOperation(String workNo, Long userId);
    R backLocOperation(String workNo, Long userId);
    /**
     * 通知档手动生成任务
src/main/java/com/zy/asrs/service/WrkMastService.java
@@ -4,6 +4,7 @@
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.entity.result.FindLocNoAttributeVo;
import java.util.Date;
import java.util.List;
public interface WrkMastService extends IService<WrkMast> {
@@ -27,4 +28,6 @@
    List<WrkMast> selectWrkMastWrkDetl(Integer ioType, FindLocNoAttributeVo findLocNoAttributeVo, Integer crnNo);
    int updatePublishError(Integer wrkNo, String updMk, Date errorTime, String errorMemo);
}
src/main/java/com/zy/asrs/service/impl/WorkServiceImpl.java
@@ -2,10 +2,7 @@
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.core.common.BaseRes;
import com.core.common.Cools;
import com.core.common.DateUtils;
import com.core.common.SnowflakeIdWorker;
import com.core.common.*;
import com.core.exception.CoolException;
import com.zy.asrs.entity.*;
import com.zy.asrs.entity.param.EmptyPlateOutParam;
@@ -350,6 +347,7 @@
            wrkMast.setCrnNo(locMast.getCrnNo());
            wrkMast.setSourceStaNo(staDesc.getCrnStn() + ""); // 源站
            wrkMast.setStaNo(staDesc.getStnNo() + ""); // 目标站
            wrkMast.setOveMk(staDesc.getStnNo()==1076? "Y":"N");
            wrkMast.setSourceLocNo(dto.getLocNo()); // 源库位
            wrkMast.setFullPlt("Y"); // 满板:Y
            wrkMast.setPicking("N"); // 拣料
@@ -458,7 +456,8 @@
                .eq("dev_no", basDevice.getType()).orderBy("order_no"));
        if (Objects.isNull(binds)) {
            throw new CoolException("机台未设置默认工作位!!");
            log.error(deviceNo+"---"+basDevice.getType()+"--没有可用机台");
            return;
        }
//
//        Set<String> locs = binds.stream().map(LocAroundBind::getBlocNo).collect(Collectors.toSet());
@@ -723,6 +722,8 @@
            wrkMast.setExitMk("N"); // 退出
            wrkMast.setEmptyMk("Y"); // 空板
            wrkMast.setLinkMis("N");
            wrkMast.setOveMk("Y");
            wrkMast.setBarcode(locMast.getBarcode());
            wrkMast.setAppeUser(userId);
            wrkMast.setAppeTime(now);
            wrkMast.setModiUser(userId);
@@ -1441,13 +1442,13 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void backLocOperation(String workNo, Long userId) {
    public R backLocOperation(String workNo, Long userId) {
        WrkMast wrkMast = wrkMastService.selectById(workNo);
        if (Cools.isEmpty(wrkMast)) {
            throw new CoolException(workNo + "工作档不存在");
            return R.error(workNo+"工作档不存在");
        }
        if (wrkMast.getWrkSts() != 15) {
            throw new CoolException("当前任务状态不能执行此操作!");
            return R.error("当前任务状态不能执行此操作!");
        }
        Integer ioType = wrkMast.getIoType() - 50;
@@ -1459,7 +1460,7 @@
//            ioType = 10;
//        }
        if (Objects.isNull(wrkMast.getIsSuplus())) {
            throw new CoolException("回库类型不能为空!!");
            return R.error("回库类型不能为空!!");
        }
        if (wrkMast.getIsSuplus() == 0) {
@@ -1477,12 +1478,12 @@
                .eq("crn_no", locMast1.getCrnNo()); // 堆垛机号
        StaDesc staDesc = staDescService.selectOne(wrapper);
        if (Cools.isEmpty(staDesc)) {
            throw new CoolException("入库路径不存在");
            return R.error("入库路径不存在");
        }
        int count = wrkMastService.selectCount(new EntityWrapper<WrkMast>().eq("barcode", wrkMast.getBarcode()));
        if (count >= 2) {
            throw new CoolException("任务档已生成,不可以重复生成相同的子任务!!");
            return R.error("任务档已生成,不可以重复生成相同的子任务!!");
        }
        WrkDetl detl = wrkDetlService.selectOne(new EntityWrapper<WrkDetl>().eq("wrk_no", wrkMast.getWrkNo()));
@@ -1512,11 +1513,11 @@
        mast.setModiTime(now);
        mast.setModiUser(userId);
        if (!wrkMastService.insert(mast)) {
            throw new CoolException("更新工作档数据状态失败");
            return R.error("更新工作档数据状态失败");
        }
        List<WrkDetl> wrkDetls = wrkDetlService.selectList(new EntityWrapper<WrkDetl>().eq("wrk_no", wrkMast.getWrkNo()));
        if (Cools.isEmpty(wrkDetls)) {
            throw new CoolException("数据错误,任务档明细不存在 !!");
            return R.error("数据错误,任务档明细不存在 !!");
        }
        // 修改库位状态 Q.拣料/盘点/并板再入库
@@ -1525,14 +1526,14 @@
        inLoc.setModiTime(now);
        inLoc.setModiUser(userId);
        if (!locMastService.updateById(inLoc)) {
            throw new CoolException("修改库位状态失败");
            return R.error("修改库位状态失败");
        }
        LocAroundBind aroundBind = locAroundBindService.selectOne(new EntityWrapper<LocAroundBind>().eq("b_loc_no", mast.getSourceLocNo()));
        if (!Objects.isNull(aroundBind)) {
            aroundBind.setLocType(LocStsType.LOC_STS_TYPE_R.type);
            if (!locAroundBindService.updateById(aroundBind)) {
                throw new CoolException("工位状态修改失败!!");
                return R.error("工位状态修改失败!!");
            }
        }
@@ -1555,7 +1556,7 @@
        locMast.setModiTime(now);
        locMast.setModiUser(userId);
        if (!locMastService.updateById(locMast)) {
            throw new CoolException("修改库位状态失败");
            return R.error("修改库位状态失败");
        }
//        wrkMast.setWrkCode(null);
@@ -1563,6 +1564,10 @@
//        if (!wrkMastService.updateById(wrkMast)) {
//            throw new CoolException("任务档更新失败!!");
//        }
        if (Cools.isEmpty(mast)) {
            return R.error("没有生成任务");
        }
        return R.ok("生成回库任务成功!!");
    }
src/main/java/com/zy/asrs/service/impl/WrkMastServiceImpl.java
@@ -10,6 +10,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Slf4j
@@ -58,4 +59,9 @@
                ,findLocNoAttributeVo.getBoxType1(),findLocNoAttributeVo.getBoxType2(),findLocNoAttributeVo.getBoxType3()
                ,crnNo);
    }
    @Override
    public int updatePublishError(Integer wrkNo, String updMk, Date errorTime, String errorMemo) {
        return this.baseMapper.updatePublishError(wrkNo, updMk, errorTime, errorMemo);
    }
}
src/main/java/com/zy/asrs/task/OrderSyncScheduler.java
@@ -2,7 +2,6 @@
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;
import com.core.common.R;
import com.zy.asrs.entity.*;
import com.zy.asrs.service.*;
import com.zy.asrs.task.core.ReturnT;
@@ -90,7 +89,7 @@
                            List<LocDetl> locDetls = locDetlService.queryStockAll(null, exist, detl.getMatnr(), detl.getBatch(),
                                    detl.getBrand(), detl.getStandby1(), detl.getStandby2(), detl.getStandby3(), detl.getBoxType1(), detl.getBoxType2(), detl.getBoxType3());
                            for (LocDetl locDetl : locDetls) {
                                LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_no", locDetl.getLocNo()).eq("loc_sts","F"));
                                LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_no", locDetl.getLocNo()).eq("loc_sts", "F"));
                                if (locMast != null) {
                                    LocAroundBind bLocNo = locAroundBindService.selectOne(new EntityWrapper<LocAroundBind>().eq("b_loc_no", locMast.getLocNo()));
                                    if (!Cools.isEmpty(bLocNo)) {
@@ -132,7 +131,9 @@
                        List<TaskDto> taskDtos = new ArrayList<>();
                        // 根据 (库位 & 出库站) 分组; 理想状态:一组为一次出库任务
                        for (LocDto locDto : locDtos) {
                            if (locDto.isLack()) { continue; }
                            if (locDto.isLack()) {
                                continue;
                            }
                            TaskDto taskDto = new TaskDto(locDto.getLocNo(), locDto.getStaNo(), locDto, locDto.getStandby1());
                            if (TaskDto.has(taskDtos, taskDto)) {
                                TaskDto dto = TaskDto.find(taskDtos, taskDto);
@@ -143,14 +144,15 @@
                            }
                        }
                        // -----------------------------------------------------------------------------------------------
                        int failCount = 0;
                        for (TaskDto taskDto : taskDtos) {
                            BasDevp staNo = basDevpService.checkSiteStatus(taskDto.getStaNo());
                            if (!Objects.isNull(taskDto.getDeviceNo())) {
                                //生成出库任务
                                workService.stockOut(staNo, taskDto, taskDto.getDeviceNo(), 9995L);
                            } else  {
                                workService.stockOut(staNo, taskDto, null, 9995L);
                            if (!stockOutSafely(taskDto, 9995L, "自动订单出库")) {
                                failCount++;
                            }
                        }
                        if (failCount > 0) {
                            log.warn("自动订单出库部分失败[orderNo={}],成功{}条,失败{}条",
                                    order.getOrderNo(), taskDtos.size() - failCount, failCount);
                        }
                    }
                }
@@ -201,4 +203,35 @@
            }
        }
    }
    private boolean stockOutSafely(TaskDto taskDto, Long userId, String scene) {
        try {
            BasDevp staNo = basDevpService.checkSiteStatus(taskDto.getStaNo());
            workService.stockOut(staNo, taskDto, taskDto.getDeviceNo(), userId);
            return true;
        } catch (Exception e) {
            log.error("{}失败[locNo={}, staNo={}, deviceNo={}, orderNos={}],原因: {}",
                    scene,
                    taskDto.getLocNo(),
                    taskDto.getStaNo(),
                    taskDto.getDeviceNo(),
                    collectOrderNos(taskDto),
                    getErrorMessage(e));
            return false;
        }
    }
    private String collectOrderNos(TaskDto taskDto) {
        Set<String> orderNos = new LinkedHashSet<>();
        for (LocDto locDto : taskDto.getLocDtos()) {
            if (!Cools.isEmpty(locDto.getOrderNo())) {
                orderNos.add(locDto.getOrderNo());
            }
        }
        return orderNos.toString();
    }
    private String getErrorMessage(Exception e) {
        return Cools.isEmpty(e.getMessage()) ? e.getClass().getSimpleName() : e.getMessage();
    }
}
src/main/java/com/zy/asrs/task/WorkMastScheduler.java
@@ -8,9 +8,7 @@
import com.zy.api.service.WcsApiService;
import com.zy.asrs.entity.LocAroundBind;
import com.zy.asrs.entity.LocMast;
import com.zy.asrs.entity.Task;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.mapper.TaskMapper;
import com.zy.asrs.service.LocAroundBindService;
import com.zy.asrs.service.TaskService;
import com.zy.asrs.service.WrkMastService;
@@ -88,77 +86,115 @@
        }
        Collections.shuffle(wrkMasts);
        for (WrkMast wrkMast : wrkMasts) {
            //查看下发任务是否为冻结库位,是冻结库位则跳过下发任务
            LocAroundBind locAroundBind = locAroundBindService.selectOne(new EntityWrapper<LocAroundBind>()
                    .eq("b_loc_no", wrkMast.getLocNo())
                    .eq("freeze", 1));
            if (Cools.isEmpty(locAroundBind)) {
                locAroundBind = locAroundBindService.selectOne(new EntityWrapper<LocAroundBind>()
                        .eq("b_loc_no", wrkMast.getSourceLocNo())
            try {
                //查看下发任务是否为冻结库位,是冻结库位则跳过下发任务
                LocAroundBind locAroundBind = locAroundBindService.selectOne(new EntityWrapper<LocAroundBind>()
                        .eq("b_loc_no", wrkMast.getLocNo())
                        .eq("freeze", 1));
                if (!Cools.isEmpty(locAroundBind)) {
                if (Cools.isEmpty(locAroundBind)) {
                    locAroundBind = locAroundBindService.selectOne(new EntityWrapper<LocAroundBind>()
                            .eq("b_loc_no", wrkMast.getSourceLocNo())
                            .eq("freeze", 1));
                    if (!Cools.isEmpty(locAroundBind)) {
                        markPublishError(wrkMast.getWrkNo(), String.valueOf("源库位被冻结"));
                        continue;
                    }
                } else {
                    markPublishError(wrkMast.getWrkNo(), String.valueOf("目标库位被冻结"));
                    continue;
                }
            }else {
                continue;
            }
            Integer crnNo = wrkMast.getCrnNo();
            //该堆垛机已经下发任务给wcs后不再下发新的搬运任务
            List<WrkMast> wrkMasts1 = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("crn_no", crnNo).in("wrk_sts", Arrays.asList(2L, 12L)));
            if (!wrkMasts1.isEmpty()) {
                continue;
            }
            //源库位为冻结库位时禁止下发搬运任务给堆垛机
            if (!Cools.isEmpty(wrkMast.getSourceLocNo())){
                LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>()
                        .eq("loc_no", wrkMast.getSourceLocNo())
                        .eq("frozen",0));
                if (Cools.isEmpty(locMast)) {
                    continue;
                }
            }
            //如果任务是移库任务时:该堆垛机已经下发任务给wcs后不再下发新的搬运任务
                if(wrkMast.getIoType()==101&&!Cools.isEmpty(wrkMast.getLocNo())){
                    LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("crn_no", crnNo).eq("loc_no", wrkMast.getLocNo()));
                    if (!Cools.isEmpty(locMast)) {
                        List<WrkMast> wrkMasts1 = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("crn_no", crnNo).in("wrk_sts", Arrays.asList(12L)));
                        if (!wrkMasts1.isEmpty()) {
                            continue;
                        }
                    }
            //目标库位为冻结库位时禁止下发搬运任务给堆垛机
            if (!Cools.isEmpty(wrkMast.getLocNo())){
                LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>()
                        .eq("loc_no", wrkMast.getLocNo())
                        .eq("frozen",0));
                if (Cools.isEmpty(locMast)) {
                    continue;
                }
            }
            String wcsSourceLocNo = Cools.isEmpty(wrkMast.getSourceLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getSourceLocNo());
            String wcsLocNo = Cools.isEmpty(wrkMast.getLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getLocNo());
            WorkTaskParams params = new WorkTaskParams();
            //出库和移库
            if(wrkMast.getIoType()==101&&!Cools.isEmpty(wrkMast.getStaNo())&& !wrkMast.getStaNo().equals("0")) {
                params.setType("out")
                        .setTaskNo(wrkMast.getWrkNo()+"")
                        .setSourceLocNo(wcsSourceLocNo)
                        .setLocNo(wcsLocNo)
                        .setStaNo(wrkMast.getStaNo())
                        .setTaskPri(wrkMast.getIoPri().intValue())
                        .setBarcode(wrkMast.getBarcode());
            }else if(wrkMast.getIoType()==2&& !Cools.isEmpty(wrkMast.getSourceStaNo())){
                params.setType("in")
                        .setTaskNo(wrkMast.getWrkNo()+"")
                        .setSourceStaNo(wrkMast.getSourceStaNo())
                        .setLocNo(wcsLocNo)
                        .setTaskPri(wrkMast.getIoPri().intValue())
                        .setBarcode(wrkMast.getBarcode());
            } else {
                params.setType("move")
                        .setTaskNo(wrkMast.getWrkNo()+"")
                        .setSourceLocNo(wcsSourceLocNo)
                        .setLocNo(wcsLocNo)
                        .setBarcode(wrkMast.getBarcode());
                //源库位为冻结库位时禁止下发搬运任务给堆垛机
                if (!Cools.isEmpty(wrkMast.getSourceLocNo())) {
                    LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>()
                            .eq("loc_no", wrkMast.getSourceLocNo())
                            .eq("frozen", 0));
                    if (Cools.isEmpty(locMast)) {
                        continue;
                    }
                }
                //目标库位为冻结库位时禁止下发搬运任务给堆垛机
                if (!Cools.isEmpty(wrkMast.getLocNo())) {
                    LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>()
                            .eq("loc_no", wrkMast.getLocNo())
                            .eq("frozen", 0));
                    if (Cools.isEmpty(locMast)) {
                        continue;
                    }
                }
                String wcsSourceLocNo = Cools.isEmpty(wrkMast.getSourceLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getSourceLocNo());
                String wcsLocNo = Cools.isEmpty(wrkMast.getLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getLocNo());
                Integer taskPri = wrkMast.getIoPri() == null ? null : wrkMast.getIoPri().intValue();
                WorkTaskParams params = new WorkTaskParams();
                //出库和移库
                if ((wrkMast.getIoType() == 101 || wrkMast.getIoType() == 110) && !Cools.isEmpty(wrkMast.getStaNo()) && !wrkMast.getStaNo().equals("0")) {
                    params.setType("out")
                            .setTaskNo(wrkMast.getWrkNo() + "")
                            .setSourceLocNo(wcsSourceLocNo)
                            .setLocNo(wcsLocNo)
                            .setStaNo(wrkMast.getStaNo())
                            .setTaskPri(taskPri)
                            .setBarcode(wrkMast.getBarcode());
                } else if (wrkMast.getIoType() == 2 && !Cools.isEmpty(wrkMast.getSourceStaNo())) {
                    params.setType("in")
                            .setTaskNo(wrkMast.getWrkNo() + "")
                            .setSourceStaNo(wrkMast.getSourceStaNo())
                            .setStaNo(wrkMast.getStaNo())
                            .setLocNo(wcsLocNo)
                            .setTaskPri(taskPri)
                            .setBarcode(wrkMast.getBarcode());
                } else {
                    params.setType("move")
                            .setTaskNo(wrkMast.getWrkNo() + "")
                            .setSourceLocNo(wcsSourceLocNo)
                            .setLocNo(wcsLocNo)
                            .setBarcode(wrkMast.getBarcode());
                }
                R r = wcsApiService.pubWrkToWcs(params);
                if (isSuccess(r)) {
                    clearPublishError(wrkMast.getWrkNo());
                    break;
                }
                markPublishError(wrkMast.getWrkNo(), String.valueOf(r.get("msg")));
            } catch (Exception e) {
                log.error("工作档[wrkNo={}]下发任务异常", wrkMast.getWrkNo(), e);
                markPublishError(wrkMast.getWrkNo(), e.getMessage());
            }
            R r = wcsApiService.pubWrkToWcs(params);
            if (r.get("code").equals(200)){
                break;
            }
        };
        }
    }
    private boolean isSuccess(R r) {
        return r != null && "200".equals(String.valueOf(r.get("code")));
    }
    private void markPublishError(Integer wrkNo, String msg) {
        if (wrkMastService.updatePublishError(wrkNo, "X", new Date(), buildPublishErrorMsg(msg)) <= 0) {
            log.error("工作档[wrkNo={}]回写下发异常失败", wrkNo);
        }
    }
    private void clearPublishError(Integer wrkNo) {
        if (wrkMastService.updatePublishError(wrkNo, null, null, null) <= 0) {
            log.error("工作档[wrkNo={}]清理下发异常失败", wrkNo);
        }
    }
    private String buildPublishErrorMsg(String msg) {
        return Cools.isEmpty(msg) || "null".equalsIgnoreCase(msg) ? "下发任务失败" : "下发任务失败:" + msg;
    }
    /**
src/main/java/com/zy/asrs/task/handler/WorkMastHandler.java
@@ -145,7 +145,7 @@
                            Double v = Math.round((selectOne.getStockQty() - selectOne.getAnfme()) * 10000) / 10000.0;
                            OrderDetlPakout orderDetlPakout = orderDetlPakoutService.selectItem(wrkDetl.getOrderNo(), wrkDetl.getMatnr(), wrkDetl.getBatch(), null, null, null, null,
                            OrderDetlPakout orderDetlPakout = orderDetlPakoutService.selectItem(wrkDetl.getOrderNo(), wrkDetl.getMatnr(), wrkDetl.getBatch(), "", "", "", "",
                                    null, null, null);
                            if (!Objects.isNull(orderDetlPakout)) {
src/main/java/com/zy/common/config/AdminInterceptor.java
@@ -123,7 +123,7 @@
//            String deToken = Cools.deTokn(token, user.getPassword());
//            long timestamp = Long.parseLong(deToken.substring(0, 13));
            // 15分钟后过期
            if (System.currentTimeMillis() - userLogin.getCreateTime().getTime() > 900000){
            if (System.currentTimeMillis() - userLogin.getCreateTime().getTime() > 3600000){
                Http.response(response, BaseRes.DENIED);
                return false;
            }
src/main/java/com/zy/common/service/CommonService.java
@@ -162,8 +162,8 @@
    public StartupDto getLocNo1(Integer crnNo) {
        LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>()
                .eq("crn_no", crnNo)
                .eq("loc_sts","O")
                .eq("whs_type",1)
                .eq("loc_sts", "O")
                .eq("whs_type", 1)
                .orderBy("lev1,bay1"));
        if (Cools.isEmpty(locMast)) {
            return null;
@@ -196,239 +196,51 @@
     */
    @Transactional
    public StartupDto getLocNoRun(Integer whsType, Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, Integer moveCrnNo, LocTypeDto locTypeDto, int times) {
        StartupDto startupDto = new StartupDto();
        // 初始化参数
        int crnNo = 0;      //堆垛机号
        int nearRow = 0;    //最浅库位排
        int curRow = 0;     //最深库位排
        int rowCount = 0;   //轮询轮次
        LocMast locMast = null;     // 目标库位
        StartupDto startupDto = new StartupDto();
        RowLastno rowLastno = rowLastnoService.selectById(whsType);
        if (Cools.isEmpty(rowLastno)) {
            throw new CoolException("数据异常,请联系管理员===>库位规则未知");
        }
        RowLastnoType rowLastnoType = rowLastnoTypeService.selectById(rowLastno.getTypeId());
        if (Cools.isEmpty(rowLastnoType)) {
            throw new CoolException("数据异常,请联系管理员===》库位规则类型未知");
        }
        int sRow = rowLastno.getsRow();
        int eRow = rowLastno.geteRow();
        int crnNumber = rowLastno.getCrnQty();
        // ===============>>>> 开始执行
        curRow = rowLastno.getCurrentRow();
        if (!Cools.isEmpty(moveCrnNo) && moveCrnNo != 0) {
            crnNumber = moveCrnNo;
            if (times == 0) {
                curRow = moveCrnNo * 4 - 1;
            } else {
                curRow = moveCrnNo * 4 - 2;
            }
        }
        //此程序用于优化堆垛机异常时的运行时间
        for (int i = times; i < crnNumber * 2; i++) {
            int[] locNecessaryParameters = Utils.LocNecessaryParameters(rowLastno, curRow, crnNumber);
            curRow = locNecessaryParameters[1];
            crnNo = locNecessaryParameters[2];
            if (basCrnpService.checkSiteError(crnNo, true)) {
                rowCount = locNecessaryParameters[0];
                nearRow = locNecessaryParameters[3];
                List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>()
                        .eq("row1", nearRow)
                        .eq("loc_sts", "O")
                        .eq("frozen", 0)
                        .eq("deleted", 0)
                        .eq("whs_type", rowLastnoType.getType().longValue()));
                int crnCountO = wrkMastService.selectCount(new EntityWrapper<WrkMast>().eq("crn_no", crnNo).le("io_type", 100));
                if (locMasts.size() - crnCountO <= 2) {
                    log.error(crnNo + "号堆垛机没有空库位!!! 尺寸规格: {}, 轮询次数:{}", JSON.toJSONString(locTypeDto), times);
                    nearRow = 0;
                    times++;
                    continue;
                }
                break;
            } else {
                times++;
            }
        }
        if (nearRow == 0) {
            throw new CoolException("无可用堆垛机");
        }
        boolean signRule1 = false;
        boolean signRule2 = false;
        if (Utils.BooleanWhsTypeStaIoType(rowLastno)) {
            // 靠近摆放规则 --- 同天同规格物料 //分离版
            if (!Cools.isEmpty(findLocNoAttributeVo.getMatnr()) && staDescId == 1) {
                signRule1 = true;
            }
            // 靠近摆放规则 --- 同天同规格物料 //互通版
            if (!Cools.isEmpty(findLocNoAttributeVo.getMatnr()) && staDescId == 1) {
                signRule2 = true;
            }
            if (!Cools.isEmpty(findLocNoAttributeVo.getMatnr()) && (staDescId == 11 || staDescId == 111)) {
                signRule1 = true;
            }
        }
        if (signRule1) {
            if (nearRow != curRow) {
                List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>()
                        .eq("row1", nearRow)
                        .eq("frozen", 0).eq("deleted", 0)
                        .eq("loc_sts", "O")
                        .eq("whs_type", rowLastnoType.getType().longValue()));
                for (LocMast locMast1 : locMasts) {
                    //获取巷道
//                    List<String> groupOutsideLocCrn = Utils.getGroupOutLocCrn(curRow,nearRow,locMast1.getLocNo(), curRow>nearRow);
//                    LocMast locMastGro = locMastService.selectById(wrkMast.getLocNo());
                    //获取目标库位所在巷道最浅非空库位
                    LocMast locMastF = locMastService.selectLocByLocStsPakInF(curRow, nearRow, locMast1, rowLastnoType.getType().longValue());
                    if (!Cools.isEmpty(locMastF) && locMastF.getLocSts().equals("F")) {
                        LocDetl locDetl = locDetlService.selectOne(new EntityWrapper<LocDetl>().eq("loc_no", locMastF.getLocNo()));
                        if (!Cools.isEmpty(locDetl) && findLocNoAttributeVo.beSimilar(locDetl)) {
                            //获取目标库位所在巷道最深空库位
                            locMast = locMastService.selectLocByLocStsPakInO(curRow, nearRow, locMast1, rowLastnoType.getType().longValue());
                            break;
                        }
                    }
                }
            }
        } else if (signRule2) {
            List<String> locNos = locDetlService.getSameDetlToday(findLocNoAttributeVo.getMatnr(), sRow, eRow);
            for (String locNo : locNos) {
                if (Utils.isShallowLoc(slaveProperties, locNo)) {
                    continue;
                }
                String shallowLocNo = Utils.getShallowLoc(slaveProperties, locNo);
                // 检测目标库位是否为空库位
                LocMast shallowLoc = locMastService.selectById(shallowLocNo);
                if (shallowLoc != null && shallowLoc.getLocSts().equals("O")) {
                    if (VersionUtils.locMoveCheckLocTypeComplete(shallowLoc, locTypeDto)) {
                        if (basCrnpService.checkSiteError(shallowLoc.getCrnNo(), true)) {
                            locMast = shallowLoc;
                            crnNo = locMast.getCrnNo();
                            break;
                        }
                    }
                }
            }
        }
//        // 靠近摆放规则 --- 空托  //分离版
//        if (staDescId == 10 && Utils.BooleanWhsTypeStaIoType(whsType)) {
//            List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>().eq("row1", nearRow).eq("loc_sts", "O"));
//            for (LocMast locMast1:locMasts){
//                if (VersionUtils.locMoveCheckLocTypeComplete(locMast1, locTypeDto)) {
//                    continue;
//                }
//                String shallowLoc = Utils.getDeepLoc(slaveProperties,locMast1.getLocNo());
//                LocMast locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_no",shallowLoc));
//                if (!Cools.isEmpty(locMast2) && locMast2.getLocSts().equals("D")){
//                    locMast = locMast1;
//                    break;
//                }
//            }
//        }
        // 靠近摆放规则 --- 空托 //互通版
        if (staDescId == 10 && Utils.BooleanWhsTypeStaIoType(rowLastno)) {
        RowLastno whsType1 = rowLastnoService.selectOne(new EntityWrapper<RowLastno>().eq("whs_type", 2));
        crnNo = whsType1.getCrnQty();
        if (basCrnpService.checkSiteError(crnNo, true)) {
            BasCrnp basCrnp = basCrnpService.selectById(crnNo);
            List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>()
                    .eq("loc_sts", "D").eq("frozen", 0).eq("deleted", 0)
                    .ge("row1", sRow).le("row1", eRow).eq("whs_type", rowLastnoType.getType().longValue()));
            if (!locMasts.isEmpty()) {
                for (LocMast loc : locMasts) {
                    if (Utils.isShallowLoc(slaveProperties, loc.getLocNo())) {
                        continue;
                    }
                    String shallowLocNo = Utils.getShallowLoc(slaveProperties, loc.getLocNo());
                    // 检测目标库位是否为空库位
                    LocMast shallowLoc = locMastService.selectById(shallowLocNo);
                    if (shallowLoc != null && shallowLoc.getLocSts().equals("O")) {
                        if (VersionUtils.locMoveCheckLocTypeComplete(shallowLoc, locTypeDto)) {
                            if (basCrnpService.checkSiteError(shallowLoc.getCrnNo(), true)) {
                                locMast = shallowLoc;
                                crnNo = locMast.getCrnNo();
                                break;
                            }
                        }
                    }
                }
                    .eq("crn_no", basCrnp.getCrnNo())
                    .eq("loc_sts", "O")
                    .eq("frozen", 0)
                    .eq("deleted", 0)
                    .eq("whs_type", 1)
                    .orderBy("lev1,bay1"));
            if (Cools.isEmpty(locMasts) || locMasts.size() > 15) {
                locMast = locMasts.get(0);
            }
        }
        int crnNo1 = crnNo + 1;
        if (crnNo1> whsType1.geteCrnNo()) {
            crnNo1 = whsType1.getsCrnNo();
        }
        whsType1.setCrnQty(crnNo1);
        rowLastnoService.updateById(whsType1);
        Wrapper<StaDesc> wrapper = null;
        StaDesc staDesc = null;
        BasDevp staNo = null;
        if (Utils.BooleanWhsTypeSta(rowLastno, staDescId)) {
            // 获取目标站
            wrapper = new EntityWrapper<StaDesc>()
                    .eq("type_no", staDescId)
                    .eq("stn_no", sourceStaNo)
                    .eq("crn_no", crnNo);
            staDesc = staDescService.selectOne(wrapper);
            if (Cools.isEmpty(staDesc)) {
                log.error("type_no={},stn_no={},crn_no={}", staDescId, sourceStaNo, crnNo);
//                throw new CoolException("入库路径不存在");
                crnNo = 0;
            } else {
                staNo = basDevpService.selectById(staDesc.getCrnStn());
                if (!staNo.getAutoing().equals("Y")) {
                    log.error("目标站" + staDesc.getCrnStn() + "不可用");
//                throw new CoolException("目标站"+staDesc.getCrnStn()+"不可用");
                    crnNo = 0;
                }
                startupDto.setStaNo(staNo.getDevNo());
            }
            // 更新库位排号
            if (Cools.isEmpty(locMast)) {
                rowLastno.setCurrentRow(curRow);
                rowLastnoService.updateById(rowLastno);
            }
        // 获取目标站
        wrapper = new EntityWrapper<StaDesc>()
                .eq("type_no", staDescId)
                .eq("stn_no", sourceStaNo)
                .eq("crn_no", crnNo);
        staDesc = staDescService.selectOne(wrapper);
        if (Cools.isEmpty(staDesc)) {
            log.error("入库路径不存在type_no={},stn_no={},crn_no={}", staDescId, sourceStaNo, crnNo);
        }
        // 开始查找库位 ==============================>>
        // 1.按规则查找库位
        if (Cools.isEmpty(locMast) && crnNo != 0) {
            List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>().eq("loc_sts", "O").eq("crn_no", crnNo).eq("whs_type", rowLastnoType.getType().longValue()).orderBy("lev1").orderBy("bay1"));
            if (!locMasts.isEmpty()) {
                for (LocMast locMast1 : locMasts) {
                    if (basCrnpService.checkSiteError(crnNo, true)) {
                        locMast = locMast1;
                        break;
                    }
                }
            }
        }
        if (!Cools.isEmpty(locMast) && !basCrnpService.checkSiteError(crnNo, true)) {
            locMast = null;
        }
        // 递归查询
        if (Cools.isEmpty(locMast) || !locMast.getLocSts().equals("O")) {
            // 当前巷道无空库位时,递归调整至下一巷道,检索全部巷道无果后,跳出递归
            if (times < rowCount * 2) {
            if (times < 5) {
                times = times + 1;
                return getLocNoRun(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, locTypeDto, times);
            }
//            // 2.库位当前所属尺寸无空库位时,调整尺寸参数,向上兼容检索库位
//            if (locTypeDto.getLocType1() < 2) {
//                int i = locTypeDto.getLocType1() + 1;
//                locTypeDto.setLocType1((short)i);
//                return getLocNo(1, staDescId, sourceStaNo, matnr,batch,grade, locTypeDto, 0);
//            }
            log.error("系统没有空库位!!! 尺寸规格: {}, 轮询次数:{}", JSON.toJSONString(locTypeDto), times);
            throw new CoolException("没有空库位");
        }
@@ -441,6 +253,7 @@
        startupDto.setCrnNo(crnNo);
        startupDto.setSourceStaNo(sourceStaNo);
        startupDto.setLocNo(locNo);
        startupDto.setStaNo(staDesc.getCrnStn());
        return startupDto;
    }
src/main/java/com/zy/common/web/BaseController.java
@@ -21,6 +21,9 @@
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.temporal.Temporal;
import java.util.*;
/**
@@ -154,7 +157,11 @@
        if (Cools.isEmpty(condition)) {
            return;
        }
        List<String> columns = new ArrayList<>();
        String keyword = String.valueOf(condition).trim();
        if (keyword.isEmpty()) {
            return;
        }
        boolean appended = false;
        for (Field field :Cools.getAllFields(cls)){
            if (Modifier.isFinal(field.getModifiers())
                    || Modifier.isStatic(field.getModifiers())
@@ -169,19 +176,83 @@
                column = field.getName();
            }
            if (!set.contains(column)) {
                columns.add(column);
                if (supportsLikeSearch(field.getType())) {
                    if (!appended) {
                        wrapper.andNew();
                    } else {
                        wrapper.or();
                    }
                    wrapper.like(column, keyword);
                    appended = true;
                    continue;
                }
                Object exactValue = parseExactSearchValue(field.getType(), keyword);
                if (exactValue != null) {
                    if (!appended) {
                        wrapper.andNew();
                    } else {
                        wrapper.or();
                    }
                    wrapper.eq(column, exactValue);
                    appended = true;
                }
            }
        }
        if (columns.isEmpty()) {
            return;
    }
    private boolean supportsLikeSearch(Class<?> fieldType) {
        return CharSequence.class.isAssignableFrom(fieldType)
                || fieldType == Character.class
                || fieldType == char.class
                || Date.class.isAssignableFrom(fieldType)
                || Temporal.class.isAssignableFrom(fieldType);
    }
    private Object parseExactSearchValue(Class<?> fieldType, String keyword) {
        if (!isNumericField(fieldType)) {
            return null;
        }
        for (int i=0;i<columns.size();i++){
            if (i==0){
                wrapper.andNew();
            } else {
                wrapper.or();
        try {
            BigDecimal numericValue = new BigDecimal(keyword);
            if (fieldType == Integer.class || fieldType == int.class) {
                return numericValue.stripTrailingZeros().scale() <= 0 ? numericValue.intValueExact() : null;
            }
            wrapper.like(columns.get(i), condition);
            if (fieldType == Long.class || fieldType == long.class) {
                return numericValue.stripTrailingZeros().scale() <= 0 ? numericValue.longValueExact() : null;
            }
            if (fieldType == Short.class || fieldType == short.class) {
                return numericValue.stripTrailingZeros().scale() <= 0 ? numericValue.shortValueExact() : null;
            }
            if (fieldType == Byte.class || fieldType == byte.class) {
                return numericValue.stripTrailingZeros().scale() <= 0 ? numericValue.byteValueExact() : null;
            }
            if (fieldType == BigInteger.class) {
                return numericValue.stripTrailingZeros().scale() <= 0 ? numericValue.toBigIntegerExact() : null;
            }
            if (fieldType == Float.class || fieldType == float.class) {
                float value = numericValue.floatValue();
                return Float.isFinite(value) ? value : null;
            }
            if (fieldType == Double.class || fieldType == double.class) {
                double value = numericValue.doubleValue();
                return Double.isFinite(value) ? value : null;
            }
            if (fieldType == BigDecimal.class) {
                return numericValue;
            }
        } catch (Exception ignored) {
            return null;
        }
        return null;
    }
    private boolean isNumericField(Class<?> fieldType) {
        return Number.class.isAssignableFrom(fieldType)
                || fieldType == byte.class
                || fieldType == short.class
                || fieldType == int.class
                || fieldType == long.class
                || fieldType == float.class
                || fieldType == double.class;
    }
}
src/main/java/com/zy/common/web/WcsController.java
@@ -62,6 +62,7 @@
    @PostMapping("/pakin/loc/v1")
    @ResponseBody
    public synchronized R getLocNo(@RequestBody SearchLocParam param) {
        StartupDto dto = null;
        log.info("收到WCS入库接口请求====>>入参:{}", param);
        if (Cools.isEmpty(param.getIoType())) {
            return R.error("入出库类型不能为空");
@@ -75,7 +76,7 @@
            //3S19000677895,M22003772,3  ,KP, 0A2030  ,0A2030202306050023
            //3S箱码号      ,端子物料号 ,数量,单位,供应商编号,供应商批次号
            //数量和重量转换出来的是否差不多
            //weight 为 43.39  去皮2.35  = 41.05
            //weight 为 43.39  去皮2.45  = 40.95
            //托盘码例子  845000820308,M11000787,1000,MT,0A1263,0A126320260120
            //换算需要41.05*24.36约等于1000
            if (Cools.isEmpty(param.getBarcode())) {
@@ -91,8 +92,22 @@
            String threeCode = param.getBarcode();
            param.setBarcode(barcode);
            //实际称重数量
            Double m = param.getWeight() -2.35;
            Double m = param.getWeight() -2.45;
            WrkMast wrkMast1 = wrkMastService.selectByBarcode(barcode);
            if (!Cools.isEmpty(wrkMast1)&&wrkMast1.getWrkSts()==2) {
                StartupDto dto1 =new StartupDto();
                dto1.setBarcode(barcode);
                dto1.setSourceStaNo(Integer.valueOf(wrkMast1.getSourceStaNo()));
                dto1.setStaNo(Integer.valueOf(wrkMast1.getStaNo()));
                String s = Utils.WMSLocToWCSLoc(wrkMast1.getLocNo());
                dto1.setLocNo(s);
                dto1.setCrnNo(wrkMast1.getCrnNo());
                dto1.setTaskNo(wrkMast1.getWrkNo());
                dto1.setTaskPri((int) Math.round(wrkMast1.getIoPri()));
                return R.ok(dto1);
            }
            Mat mat = matService.selectByMatnr(matnr);
            if(mat==null){
                log.info("没有找到该物料档案--"+matnr+"组托时自动新增物料,默认24.36KG/M");
@@ -132,6 +147,7 @@
                waitPakin.setZpallet(param.getBarcode());
                waitPakin.setAnfme(m);
                waitPakin.setThreeCode(threeCode);
                waitPakins.add(waitPakin);
                waitPakinService.insert(waitPakin);
            }else {
                for(WaitPakin waitPakin : waitPakins){
@@ -148,7 +164,9 @@
            if(Cools.isEmpty(waitPakin.getBeBatch())||waitPakin.getBeBatch()!=1){
                flag = true;
            }
            if (flag && (weight - m < -2.0 || weight - m > 2.0)) {
            //2.5KG * 每公斤长度 = 误差值
            Double dou= 2.5*mat.getVolume();
            if (flag && (weight - m < -dou || weight - m > dou)) {
                return R.error("二维码="+threeCode+"的物料实际称重转换成的米数和物料标签提供的米数相差较大需要退回或者标记后才能入库!");
            }
@@ -167,7 +185,6 @@
        sourceStaNo.setLocType1(param.getLocType1());
        LocTypeDto locTypeDto = new LocTypeDto(sourceStaNo);
        StartupDto dto = null;
        switch (param.getIoType()) {
            case 1://满托盘入库
                assert waitPakins != null;
src/main/java/com/zy/system/config/LicenseSchemaInitializer.java
New file
@@ -0,0 +1,79 @@
package com.zy.system.config;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.Statement;
@Component
public class LicenseSchemaInitializer {
    private final DataSource dataSource;
    public LicenseSchemaInitializer(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    @PostConstruct
    public void init() {
        ensureTable();
        ensureColumn("sys_license_infos", "request_code", "NVARCHAR(2048) NULL");
    }
    private void ensureTable() {
        try (Connection connection = dataSource.getConnection()) {
            if (hasTable(connection, "sys_license_infos")) {
                return;
            }
            try (Statement statement = connection.createStatement()) {
                statement.executeUpdate("CREATE TABLE sys_license_infos ("
                        + "id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, "
                        + "[license] NVARCHAR(MAX) NULL, "
                        + "license_time NVARCHAR(255) NULL, "
                        + "request_code NVARCHAR(2048) NULL, "
                        + "create_time DATETIME NULL)");
            }
        } catch (Exception ignored) {
        }
    }
    private void ensureColumn(String tableName, String columnName, String columnDefinition) {
        try (Connection connection = dataSource.getConnection()) {
            if (!hasTable(connection, tableName) || hasColumn(connection, tableName, columnName)) {
                return;
            }
            try (Statement statement = connection.createStatement()) {
                statement.executeUpdate("ALTER TABLE " + tableName + " ADD " + columnName + " " + columnDefinition);
            }
        } catch (Exception ignored) {
        }
    }
    private boolean hasTable(Connection connection, String tableName) throws Exception {
        DatabaseMetaData metaData = connection.getMetaData();
        try (ResultSet resultSet = metaData.getTables(connection.getCatalog(), null, tableName, new String[]{"TABLE"})) {
            while (resultSet.next()) {
                if (tableName.equalsIgnoreCase(resultSet.getString("TABLE_NAME"))) {
                    return true;
                }
            }
        }
        return false;
    }
    private boolean hasColumn(Connection connection, String tableName, String columnName) throws Exception {
        DatabaseMetaData metaData = connection.getMetaData();
        try (ResultSet resultSet = metaData.getColumns(connection.getCatalog(), null, tableName, columnName)) {
            while (resultSet.next()) {
                if (columnName.equalsIgnoreCase(resultSet.getString("COLUMN_NAME"))) {
                    return true;
                }
            }
        }
        return false;
    }
}
src/main/java/com/zy/system/controller/LicenseCreatorController.java
@@ -1,109 +1,161 @@
package com.zy.system.controller;
import com.core.common.Cools;
import com.core.annotations.ManagerAuth;
import com.core.common.R;
import com.zy.system.entity.license.*;
import com.zy.system.entity.LicenseInfos;
import com.zy.system.entity.license.LicenseCheckListener;
import com.zy.system.entity.license.LicenseUploadParam;
import com.zy.system.entity.license.LicenseUtils;
import com.zy.system.entity.license.LicenseVerify;
import com.zy.system.entity.license.LicenseVerifyParam;
import com.zy.system.service.LicenseInfosService;
import com.zy.system.timer.LicenseTimer;
import de.schlichtherle.license.LicenseContent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
/**
 *
 * 用于生成证书文件,不能放在给客户部署的代码里
 * 许可证运行时管理
 */
@RestController
@RequestMapping("/license")
public class LicenseCreatorController {
    @Value("${license.licensePath}")
    private String licensePath;
    @Value("${license.subject}")
    private String licenseSubject;
    @Value("${license.publicAlias}")
    private String publicAlias;
    @Value("${license.storePass}")
    private String storePass;
    @Value("${license.publicKeysStorePath}")
    private String publicKeysStorePath;
    @Autowired
    private LicenseCheckListener licenseCheckListener;
    @Autowired
    private LicenseTimer licenseTimer;
    @Autowired
    private LicenseInfosService licenseInfosService;
    /**
     * 获取服务器硬件信息
     * @param osName 操作系统类型,如果为空则自动判断
     * 获取请求码。
     */
    @RequestMapping(value = "/getServerInfos",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public LicenseCheck getServerInfos(@RequestParam(value = "osName",required = false) String osName) {
        //操作系统类型
        if(Cools.isEmpty(osName)){
            osName = System.getProperty("os.name");
        }
        osName = osName.toLowerCase();
    @ManagerAuth(value = ManagerAuth.Auth.NONE)
    @RequestMapping(value = "/getRequestCode", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public R getRequestCode() {
        return R.ok(LicenseUtils.buildRequestCode(licenseSubject));
    }
        AbstractServerInfos abstractServerInfos = null;
        //根据不同操作系统类型选择不同的数据获取方法
        if (osName.startsWith("windows")) {
            abstractServerInfos = new WindowsServerInfos();
        } else if (osName.startsWith("linux")) {
//            abstractServerInfos = new LinuxServerInfos();
        }else{//其他服务器类型
            abstractServerInfos = new WindowsServerInfos();
        }
        return abstractServerInfos.getServerInfos();
    /**
     * 获取系统配置。
     */
    @ManagerAuth(value = ManagerAuth.Auth.NONE)
    @RequestMapping(value = "/getServerInfos", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public Object getServerInfos(@RequestParam(value = "osName", required = false) String osName) {
        return LicenseUtils.getServerInfos();
    }
    /**
     * 获取许可证有效期天数
     */
    @RequestMapping(value = "/getLicenseDays")
    @ManagerAuth(value = ManagerAuth.Auth.NONE)
    @RequestMapping(value = "/getLicenseDays", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public R getLicenseDays() {
        return R.ok(licenseTimer.getLicenseDays());
        int licenseDays = licenseTimer.getLicenseDays();
        if (!licenseTimer.getSystemSupport()) {
            licenseDays = -1;
        }
        return R.ok(licenseDays);
    }
    @RequestMapping(value = "/updateLicense")
    public R updateLicense(@RequestParam("file") MultipartFile[] files){
        MultipartFile file = files[0];
        String licensePathFileName = this.getClass().getClassLoader().getResource(licensePath).getPath();
        File licensePathFile = new File(licensePathFileName);
        //服务器端保存的文件对象
        File serverFile = new File(licensePathFile.getPath());
        if (serverFile.exists()) {
            try {
                serverFile.delete();//存在文件,删除
            } catch (Exception e) {
                e.printStackTrace();
            }
    @ManagerAuth(value = ManagerAuth.Auth.NONE)
    @RequestMapping(value = "/updateLicense", consumes = MediaType.APPLICATION_JSON_VALUE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public R updateLicense(@RequestBody LicenseUploadParam param){
        if (param == null || param.getLicense() == null || param.getLicense().trim().isEmpty()) {
            return R.error("许可证内容不能为空");
        }
        return saveAndActivateLicense(param.getLicense().trim());
    }
    @ManagerAuth(value = ManagerAuth.Auth.NONE)
    @RequestMapping(value = "/updateLicense", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public R updateLicense(@RequestParam("file") MultipartFile[] files) {
        if (files == null || files.length == 0 || files[0] == null || files[0].isEmpty()) {
            return R.error("许可证文件不能为空");
        }
        try {
            //创建文件
            serverFile.createNewFile();
            //将上传的文件写入到服务器端文件内
            file.transferTo(serverFile);
        } catch (IOException e) {
            e.printStackTrace();
            String licenseBase64 = Base64.getEncoder().encodeToString(files[0].getBytes());
            return saveAndActivateLicense(licenseBase64);
        } catch (Exception e) {
            return R.error("许可证读取失败");
        }
        //重新加载许可证
        boolean loadedLicense = licenseCheckListener.loadLicense();
        if (loadedLicense) {
            return R.ok();
        }
        return R.error("许可证更新失败");
    }
    @RequestMapping(value = "/activate")
    @ManagerAuth(value = ManagerAuth.Auth.NONE)
    @RequestMapping(value = "/activate", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public R activate() {
        licenseTimer.timer();
        if (!licenseTimer.getSystemSupport()) {
            return R.error("许可证激活失败");
        }
        return R.ok();
    }
}
    @ManagerAuth(value = ManagerAuth.Auth.NONE)
    @RequestMapping(value = "/getProjectName", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public R getProjectName() {
        return R.ok(licenseSubject);
    }
    private R saveAndActivateLicense(String licenseBase64) {
        LicenseVerifyParam verifyParam = buildVerifyParam();
        LicenseVerify licenseVerify = new LicenseVerify();
        LicenseContent install = licenseVerify.install(verifyParam, licenseBase64);
        if (install == null) {
            return R.error("许可证内容无效");
        }
        LicenseInfos licenseInfos = new LicenseInfos();
        licenseInfos.setLicense(licenseBase64);
        licenseInfos.setLicenseTime(formatLicenseTime(install));
        licenseInfos.setRequestCode(LicenseUtils.buildRequestCode(licenseSubject));
        licenseInfos.setCreateTime(new Date());
        if (!licenseInfosService.insert(licenseInfos)) {
            return R.error("许可证保存失败");
        }
        boolean loadedLicense = licenseCheckListener.loadLicense(false);
        if (!loadedLicense) {
            return R.error("许可证激活失败");
        }
        licenseTimer.verify();
        if (!licenseTimer.getSystemSupport()) {
            return R.error("许可证校验失败");
        }
        return R.ok();
    }
    private LicenseVerifyParam buildVerifyParam() {
        LicenseVerifyParam param = new LicenseVerifyParam();
        param.setSubject(licenseSubject);
        param.setPublicAlias(publicAlias);
        param.setStorePass(storePass);
        param.setPublicKeysStorePath(publicKeysStorePath);
        return param;
    }
    private String formatLicenseTime(LicenseContent install) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(install.getNotBefore()) + "  -  " + format.format(install.getNotAfter());
    }
}
src/main/java/com/zy/system/entity/LicenseInfos.java
New file
@@ -0,0 +1,42 @@
package com.zy.system.entity;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import com.core.common.Cools;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
@Data
@TableName("sys_license_infos")
public class LicenseInfos implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String license;
    @TableField("license_time")
    private String licenseTime;
    @TableField("request_code")
    private String requestCode;
    @TableField("create_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    public String getCreateTime$() {
        if (Cools.isEmpty(this.createTime)) {
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
    }
}
src/main/java/com/zy/system/entity/license/AbstractServerInfos.java
@@ -24,11 +24,26 @@
        try {
            result.setIpAddress(this.getIpAddress());
        }catch (Exception e){
            logger.error("获取服务器IP失败",e);
        }
        try {
            result.setMacAddress(this.getMacAddress());
        }catch (Exception e){
            logger.error("获取服务器MAC失败",e);
        }
        try {
            result.setCpuSerial(this.getCPUSerial());
        }catch (Exception e){
            logger.error("获取服务器CPU序列号失败",e);
        }
        try {
            result.setMainBoardSerial(this.getMainBoardSerial());
        }catch (Exception e){
            logger.error("获取服务器硬件信息失败",e);
            logger.error("获取服务器主板序列号失败",e);
        }
        return result;
@@ -84,6 +99,9 @@
    protected String getMacByInetAddress(InetAddress inetAddr){
        try {
            byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress();
            if (mac == null) {
                return null;
            }
            StringBuffer stringBuffer = new StringBuffer();
            for(int i=0;i<mac.length;i++){
@@ -108,4 +126,4 @@
        return null;
    }
}
}
src/main/java/com/zy/system/entity/license/CustomLicenseManager.java
@@ -1,6 +1,5 @@
package com.zy.system.entity.license;
import com.core.common.Cools;
import de.schlichtherle.license.*;
import de.schlichtherle.xml.GenericCertificate;
import org.apache.logging.log4j.LogManager;
@@ -11,7 +10,6 @@
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;
/**
 * 自定义LicenseManager,用于增加额外的服务器硬件信息校验
@@ -120,34 +118,20 @@
        //1. 首先调用父类的validate方法
        super.validate(content);
        //2. 然后校验自定义的License参数
        //License中可被允许的参数信息
        LicenseCheck expectedCheckModel = (LicenseCheck) content.getExtra();
        //当前服务器真实的参数信息
        LicenseCheck serverCheckModel = getServerInfos();
        Object extra = content.getExtra();
        LicenseCheck serverCheckModel = LicenseUtils.getServerInfos();
        if(expectedCheckModel != null && serverCheckModel != null){
            //校验IP地址
            if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){
                throw new LicenseContentException("当前服务器的IP没在授权范围内");
            }
            //校验Mac地址
            if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){
                throw new LicenseContentException("当前服务器的Mac地址没在授权范围内");
            }
            //校验主板序列号
            if(!checkSerial(expectedCheckModel.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){
                throw new LicenseContentException("当前服务器的主板序列号没在授权范围内");
            }
            //校验CPU序列号
            if(!checkSerial(expectedCheckModel.getCpuSerial(),serverCheckModel.getCpuSerial())){
                throw new LicenseContentException("当前服务器的CPU序列号没在授权范围内");
            }
        }else{
        if (serverCheckModel == null) {
            throw new LicenseContentException("不能获取服务器硬件信息");
        }
        if (!LicenseBindingSupport.isV2Extra(extra)) {
            throw new LicenseContentException("许可证格式不支持");
        }
        LicenseBindModel bindModel = LicenseBindingSupport.parseBindModel(extra);
        if (!LicenseBindingSupport.matches(bindModel, serverCheckModel)) {
            throw new LicenseContentException("当前服务器不在授权节点范围内");
        }
    }
@@ -182,60 +166,4 @@
        return null;
    }
    /**
     * 获取当前服务器需要额外校验的License参数
     */
    private LicenseCheck getServerInfos(){
        //操作系统类型
        String osName = System.getProperty("os.name").toLowerCase();
        AbstractServerInfos abstractServerInfos = null;
        //根据不同操作系统类型选择不同的数据获取方法
        if (osName.startsWith("windows")) {
            abstractServerInfos = new WindowsServerInfos();
        } else if (osName.startsWith("linux")) {
//            abstractServerInfos = new LinuxServerInfos();
        }else{//其他服务器类型
            abstractServerInfos = new WindowsServerInfos();
        }
        return abstractServerInfos.getServerInfos();
    }
    /**
     * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内<br/>
     * 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
     */
    private boolean checkIpAddress(List<String> expectedList,List<String> serverList){
        if(expectedList != null && expectedList.size() > 0){
            if(serverList != null && serverList.size() > 0){
                for(String expected : expectedList){
                    if(serverList.contains(expected.trim())){
                        return true;
                    }
                }
            }
            return false;
        }else {
            return true;
        }
    }
    /**
     * 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内
     */
    private boolean checkSerial(String expectedSerial,String serverSerial){
        if(!Cools.isEmpty(expectedSerial)){
            if(!Cools.isEmpty(serverSerial)){
                if(expectedSerial.equals(serverSerial)){
                    return true;
                }
            }
            return false;
        }else{
            return true;
        }
    }
}
}
src/main/java/com/zy/system/entity/license/LicenseBindModel.java
New file
@@ -0,0 +1,21 @@
package com.zy.system.entity.license;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
public class LicenseBindModel implements Serializable {
    private static final long serialVersionUID = 7064744215406459726L;
    private Integer version = 2;
    private String bindMode = "MULTI_NODE";
    private String matchMode = "ANY";
    private List<LicenseNodeCheck> nodes = new ArrayList<>();
}
src/main/java/com/zy/system/entity/license/LicenseBindingSupport.java
New file
@@ -0,0 +1,176 @@
package com.zy.system.entity.license;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class LicenseBindingSupport {
    private LicenseBindingSupport() {
    }
    public static boolean isV2Extra(Object extra) {
        if (extra == null) {
            return false;
        }
        if (extra instanceof LicenseBindModel) {
            return true;
        }
        if (extra instanceof JSONObject) {
            return isV2Json((JSONObject) extra);
        }
        if (extra instanceof String) {
            String text = ((String) extra).trim();
            if (isBlank(text) || !text.startsWith("{")) {
                return false;
            }
            return isV2Json(JSON.parseObject(text));
        }
        return false;
    }
    public static LicenseBindModel parseBindModel(Object extra) {
        if (extra == null) {
            return null;
        }
        if (extra instanceof LicenseBindModel) {
            return normalizeBindModel((LicenseBindModel) extra);
        }
        if (extra instanceof JSONObject) {
            return normalizeBindModel(((JSONObject) extra).toJavaObject(LicenseBindModel.class));
        }
        if (extra instanceof String) {
            String text = ((String) extra).trim();
            if (isBlank(text)) {
                return null;
            }
            return normalizeBindModel(JSON.parseObject(text, LicenseBindModel.class));
        }
        return normalizeBindModel(JSON.parseObject(JSON.toJSONString(extra), LicenseBindModel.class));
    }
    public static boolean matches(LicenseBindModel licenseBind, LicenseCheck serverCheck) {
        if (licenseBind == null) {
            return false;
        }
        if ("UNLIMITED".equalsIgnoreCase(trimToEmpty(licenseBind.getBindMode()))) {
            return true;
        }
        List<LicenseNodeCheck> nodes = licenseBind.getNodes();
        if (nodes == null || nodes.isEmpty()) {
            return true;
        }
        for (LicenseNodeCheck node : nodes) {
            if (matchesNode(node, serverCheck)) {
                return true;
            }
        }
        return false;
    }
    private static boolean isV2Json(JSONObject jsonObject) {
        if (jsonObject == null) {
            return false;
        }
        return jsonObject.containsKey("nodes")
                || jsonObject.containsKey("bindMode")
                || jsonObject.containsKey("matchMode")
                || Integer.valueOf(2).equals(jsonObject.getInteger("version"));
    }
    private static LicenseBindModel normalizeBindModel(LicenseBindModel source) {
        if (source == null) {
            return null;
        }
        LicenseBindModel target = new LicenseBindModel();
        target.setVersion(source.getVersion() == null ? 2 : source.getVersion());
        target.setBindMode(isBlank(source.getBindMode()) ? "MULTI_NODE" : source.getBindMode().trim());
        target.setMatchMode(isBlank(source.getMatchMode()) ? "ANY" : source.getMatchMode().trim());
        List<LicenseNodeCheck> normalizedNodes = new ArrayList<>();
        if (source.getNodes() != null) {
            for (LicenseNodeCheck node : source.getNodes()) {
                if (node == null) {
                    continue;
                }
                LicenseNodeCheck normalizedNode = new LicenseNodeCheck();
                normalizedNode.setNodeId(trimToEmpty(node.getNodeId()));
                normalizedNode.setIpAddress(normalizeList(node.getIpAddress(), false));
                normalizedNode.setMacAddress(normalizeList(node.getMacAddress(), true));
                normalizedNode.setCpuSerial(trimToEmpty(node.getCpuSerial()));
                normalizedNode.setMainBoardSerial(trimToEmpty(node.getMainBoardSerial()));
                normalizedNodes.add(normalizedNode);
            }
        }
        target.setNodes(normalizedNodes);
        return target;
    }
    private static boolean matchesNode(LicenseNodeCheck node, LicenseCheck serverCheck) {
        if (node == null || serverCheck == null) {
            return false;
        }
        if (!checkList(node.getIpAddress(), serverCheck.getIpAddress())) {
            return false;
        }
        if (!checkList(node.getMacAddress(), serverCheck.getMacAddress())) {
            return false;
        }
        if (!checkSerial(node.getMainBoardSerial(), serverCheck.getMainBoardSerial())) {
            return false;
        }
        return checkSerial(node.getCpuSerial(), serverCheck.getCpuSerial());
    }
    private static boolean checkList(List<String> expectedList, List<String> actualList) {
        if (expectedList == null || expectedList.isEmpty()) {
            return true;
        }
        if (actualList == null || actualList.isEmpty()) {
            return false;
        }
        for (String expected : expectedList) {
            if (!isBlank(expected) && actualList.contains(expected.trim())) {
                return true;
            }
        }
        return false;
    }
    private static boolean checkSerial(String expectedSerial, String actualSerial) {
        if (isBlank(expectedSerial)) {
            return true;
        }
        return !isBlank(actualSerial) && expectedSerial.equals(actualSerial);
    }
    private static List<String> normalizeList(List<String> values, boolean upperCase) {
        List<String> result = new ArrayList<>();
        if (values == null || values.isEmpty()) {
            return result;
        }
        for (String value : values) {
            if (isBlank(value)) {
                continue;
            }
            String normalized = value.trim();
            normalized = upperCase ? normalized.toUpperCase() : normalized.toLowerCase();
            if (!result.contains(normalized)) {
                result.add(normalized);
            }
        }
        Collections.sort(result);
        return result;
    }
    private static boolean isBlank(String value) {
        return value == null || value.trim().isEmpty();
    }
    private static String trimToEmpty(String value) {
        return value == null ? "" : value.trim();
    }
}
src/main/java/com/zy/system/entity/license/LicenseCheckListener.java
@@ -1,6 +1,7 @@
package com.zy.system.entity.license;
import com.core.common.Cools;
import com.zy.system.entity.LicenseInfos;
import com.zy.system.service.LicenseInfosService;
import com.zy.system.timer.LicenseTimer;
import de.schlichtherle.license.LicenseContent;
import org.apache.logging.log4j.LogManager;
@@ -12,7 +13,6 @@
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.Date;
/**
@@ -40,19 +40,12 @@
    @Value("${license.storePass}")
    private String storePass;
    /**
     * 证书生成路径
     */
    @Value("${license.licensePath}")
    private String licensePath;
    /**
     * 密钥库存储路径
     */
    @Value("${license.publicKeysStorePath}")
    private String publicKeysStorePath;
    @Autowired
    private LicenseTimer licenseTimer;
    @Autowired
    private LicenseInfosService licenseInfosService;
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
@@ -65,53 +58,58 @@
    //加载证书
    public boolean loadLicense() {
        if(!Cools.isEmpty(licensePath)){
            logger.info("++++++++ 开始加载许可证 ++++++++");
        return loadLicense(true);
    }
    public boolean loadLicense(boolean fetchRemote) {
        logger.info("++++++++ 开始加载许可证 ++++++++");
        if (fetchRemote) {
            try {
//                String publicKeysStoreFileName = this.getClass().getClassLoader().getResource(publicKeysStorePath).getPath();
//                File publicKeysStoreFile = new File(publicKeysStoreFileName);
//
//                String licensePathFileName = this.getClass().getClassLoader().getResource(licensePath).getPath();
//                File licensePathFile = new File(licensePathFileName);
                LicenseVerifyParam param = new LicenseVerifyParam();
                param.setSubject(subject);
                param.setPublicAlias(publicAlias);
                param.setStorePass(storePass);
                param.setLicensePath(licensePath);
                param.setPublicKeysStorePath(publicKeysStorePath);
                LicenseVerify licenseVerify = new LicenseVerify();
                //安装证书
//                LicenseContent install = licenseVerify.install(param);
                logger.info("++++++++ 许可证加载结束 ++++++++");
                logger.info("++++++++ 许可证加载标记,搜索修改 ++++++++");
                licenseTimer.setSystemSupport(true);
                licenseTimer.setLicenseDays(9999);
                return true;
//                licenseTimer.setSystemSupport(install!=null);
//
//                if (install != null) {
//                    Date start = new Date();
//                    Date end = install.getNotAfter();
//                    Long starTime = start.getTime();
//                    Long endTime = end.getTime();
//                    Long num = endTime - starTime;//时间戳相差的毫秒数
//                    int day = (int) (num / 24 / 60 / 60 / 1000);
//                    licenseTimer.setLicenseDays(day);
//                }
//
//
//                return install != null;
            } catch (Exception e) {
                return false;
                licenseTimer.getRemoteLicense();
            } catch (Exception ignored) {
            }
        }
        licenseTimer.setSystemSupport(false);
        return false;
        try {
            LicenseVerifyParam param = new LicenseVerifyParam();
            param.setSubject(subject);
            param.setPublicAlias(publicAlias);
            param.setStorePass(storePass);
            param.setPublicKeysStorePath(publicKeysStorePath);
            LicenseVerify licenseVerify = new LicenseVerify();
            String requestCode = LicenseUtils.buildRequestCode(subject);
            LicenseInfos latestLicense = licenseInfosService.getLatestLicenseByRequestCode(requestCode);
            LicenseContent install = null;
            if (latestLicense != null && latestLicense.getLicense() != null && !latestLicense.getLicense().trim().isEmpty()) {
                install = licenseVerify.install(param, latestLicense.getLicense().trim());
            }
            if (install == null) {
                logger.info("许可证不存在");
                licenseTimer.setSystemSupport(false);
                licenseTimer.setLicenseDays(0);
                return false;
            }
            logger.info("++++++++ 许可证加载结束 ++++++++");
            licenseTimer.setSystemSupport(true);
            Date start = new Date();
            Date end = install.getNotAfter();
            Long starTime = start.getTime();
            Long endTime = end.getTime();
            Long num = endTime - starTime;//时间戳相差的毫秒数
            int day = (int) (num / 24 / 60 / 60 / 1000);
            licenseTimer.setLicenseDays(day);
            return true;
        } catch (Exception e) {
            logger.error("许可证加载失败", e);
            licenseTimer.setSystemSupport(false);
            licenseTimer.setLicenseDays(0);
            return false;
        }
    }
}
}
src/main/java/com/zy/system/entity/license/LicenseCreator.java
File was deleted
src/main/java/com/zy/system/entity/license/LicenseCreatorParam.java
File was deleted
src/main/java/com/zy/system/entity/license/LicenseNodeCheck.java
New file
@@ -0,0 +1,22 @@
package com.zy.system.entity.license;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class LicenseNodeCheck implements Serializable {
    private static final long serialVersionUID = 3629488116939928951L;
    private String nodeId;
    private List<String> ipAddress;
    private List<String> macAddress;
    private String cpuSerial;
    private String mainBoardSerial;
}
src/main/java/com/zy/system/entity/license/LicenseUploadParam.java
New file
@@ -0,0 +1,9 @@
package com.zy.system.entity.license;
import lombok.Data;
@Data
public class LicenseUploadParam {
    private String license;
}
src/main/java/com/zy/system/entity/license/LicenseUtils.java
New file
@@ -0,0 +1,121 @@
package com.zy.system.entity.license;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
public class LicenseUtils {
    public static LicenseCheck getServerInfos() {
        String osName = System.getProperty("os.name").toLowerCase();
        AbstractServerInfos abstractServerInfos;
        if (osName.startsWith("windows")) {
            abstractServerInfos = new WindowsServerInfos();
        } else if (osName.startsWith("linux")) {
            abstractServerInfos = new LinuxServerInfos();
        } else {
            abstractServerInfos = new LinuxServerInfos();
        }
        return abstractServerInfos.getServerInfos();
    }
    public static String buildRequestCode(String subject) {
        return buildRequestCode(subject, getServerInfos());
    }
    public static String buildRequestCode(String subject, LicenseCheck licenseCheck) {
        if (isBlank(subject)) {
            throw new IllegalArgumentException("许可证名称不能为空");
        }
        LicenseCheck normalized = normalizeLicenseCheck(licenseCheck == null ? new LicenseCheck() : licenseCheck);
        JSONObject payload = new JSONObject(true);
        payload.put("version", 2);
        payload.put("subject", subject);
        payload.put("licenseBind", buildBindModel(normalized));
        return Base64.getEncoder().encodeToString(JSON.toJSONString(payload).getBytes(StandardCharsets.UTF_8));
    }
    private static JSONObject buildBindModel(LicenseCheck licenseCheck) {
        JSONObject bindModel = new JSONObject(true);
        bindModel.put("version", 2);
        bindModel.put("bindMode", "MULTI_NODE");
        bindModel.put("matchMode", "ANY");
        JSONObject node = new JSONObject(true);
        node.put("nodeId", getNodeId());
        node.put("ipAddress", toJsonArray(licenseCheck.getIpAddress()));
        node.put("macAddress", toJsonArray(licenseCheck.getMacAddress()));
        node.put("cpuSerial", trimToEmpty(licenseCheck.getCpuSerial()));
        node.put("mainBoardSerial", trimToEmpty(licenseCheck.getMainBoardSerial()));
        JSONArray nodes = new JSONArray();
        nodes.add(node);
        bindModel.put("nodes", nodes);
        return bindModel;
    }
    private static LicenseCheck normalizeLicenseCheck(LicenseCheck source) {
        LicenseCheck target = new LicenseCheck();
        target.setIpAddress(normalizeList(source.getIpAddress(), false));
        target.setMacAddress(normalizeList(source.getMacAddress(), true));
        target.setCpuSerial(trimToEmpty(source.getCpuSerial()));
        target.setMainBoardSerial(trimToEmpty(source.getMainBoardSerial()));
        return target;
    }
    private static List<String> normalizeList(List<String> values, boolean upperCase) {
        List<String> result = new ArrayList<>();
        if (values == null || values.isEmpty()) {
            return result;
        }
        for (String value : values) {
            if (isBlank(value)) {
                continue;
            }
            String normalized = value.trim();
            normalized = upperCase ? normalized.toUpperCase() : normalized.toLowerCase();
            if (!result.contains(normalized)) {
                result.add(normalized);
            }
        }
        Collections.sort(result);
        return result;
    }
    private static JSONArray toJsonArray(List<String> values) {
        JSONArray array = new JSONArray();
        if (values != null && !values.isEmpty()) {
            array.addAll(values);
        }
        return array;
    }
    private static String getNodeId() {
        try {
            String hostName = InetAddress.getLocalHost().getHostName();
            if (!isBlank(hostName)) {
                return hostName.trim();
            }
        } catch (Exception ignored) {
        }
        return "node-1";
    }
    private static boolean isBlank(String value) {
        return value == null || value.trim().isEmpty();
    }
    private static String trimToEmpty(String value) {
        return value == null ? "" : value.trim();
    }
}
src/main/java/com/zy/system/entity/license/LicenseVerify.java
@@ -3,14 +3,15 @@
import de.schlichtherle.license.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.prefs.Preferences;
/**
@@ -22,25 +23,19 @@
    /**
     * 安装License证书
     */
    public synchronized LicenseContent install(LicenseVerifyParam param){
    public synchronized LicenseContent install(LicenseVerifyParam param, String license) {
        LicenseContent result = null;
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //1. 安装证书
        try{
        try {
            LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param));
            licenseManager.uninstall();
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(param.getLicensePath());
            File file = new File(param.getLicensePath());
            try (FileOutputStream out = new FileOutputStream(file)) {
                IOUtils.copy(inputStream, out);
            }
            result = licenseManager.install(new File(param.getLicensePath()));
            logger.info(MessageFormat.format("许可证加载成功,许可证有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter())));
        }catch (Exception e){
            logger.error("许可证加载失败!",e);
            File tempFileFromBase64 = createTempFileFromBase64(license);
            result = licenseManager.install(tempFileFromBase64);
            logger.info(MessageFormat.format("许可证加载成功,许可证有效期:{0} - {1}", format.format(result.getNotBefore()), format.format(result.getNotAfter())));
        } catch (Exception e) {
            logger.error("许可证加载失败!", e);
        }
        return result;
@@ -53,11 +48,6 @@
        try {
            LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            if (!updateSystemTime()) {
                //时间更新失败,系统时间被更改
                return false;
            }
            LicenseContent licenseContent = licenseManager.verify();
            logger.info(MessageFormat.format("许可证校验通过,许可证有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter())));
@@ -73,11 +63,6 @@
     */
    public LicenseContent getVerifyInfo(){
        LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);
        if (!updateSystemTime()) {
            //时间更新失败,系统时间被更改
            return null;
        }
        //校验证书
        try {
@@ -111,32 +96,17 @@
                ,cipherParam);
    }
    /**
     * 更新时间到注册表中
     */
    private boolean updateSystemTime() {
        // 获取用户根节点
        Preferences userRoot = Preferences.userRoot();
        // 获取指定路径下的节点
        Preferences node = userRoot.node("/zhongyang");
        String key = "time";
        // 读取注册表
        String value = node.get(key, null);
        if (value != null) {
            long originTime = Long.parseLong(value);
            long now = System.currentTimeMillis();
            long diff = now - originTime;//现在时间 - 源时间 = 时间差
            if (diff > 0) {
                //时间差大于0才允许更新注册表时间
                node.put(key, String.valueOf(System.currentTimeMillis()));
                return true;
            }
        }else {
            // 写入注册表
            node.put(key, String.valueOf(System.currentTimeMillis()));
            return true;
        }
        return false;
    public File base64ToTempFile(String base64String, String filePrefix, String fileSuffix)
            throws IOException {
        byte[] decodedBytes = Base64.getDecoder().decode(base64String);
        Path tempPath = Files.createTempFile(filePrefix, fileSuffix);
        Files.write(tempPath, decodedBytes);
        tempPath.toFile().deleteOnExit();
        return tempPath.toFile();
    }
}
    public File createTempFileFromBase64(String base64Data) throws IOException {
        return base64ToTempFile(base64Data, "temp_license_", ".bin");
    }
}
src/main/java/com/zy/system/entity/license/LicenseVerifyParam.java
@@ -24,11 +24,6 @@
    private String storePass;
    /**
     * 证书生成路径
     */
    private String licensePath;
    /**
     * 密钥库存储路径
     */
    private String publicKeysStorePath;
@@ -37,11 +32,10 @@
    }
    public LicenseVerifyParam(String subject, String publicAlias, String storePass, String licensePath, String publicKeysStorePath) {
    public LicenseVerifyParam(String subject, String publicAlias, String storePass, String publicKeysStorePath) {
        this.subject = subject;
        this.publicAlias = publicAlias;
        this.storePass = storePass;
        this.licensePath = licensePath;
        this.publicKeysStorePath = publicKeysStorePath;
    }
src/main/java/com/zy/system/entity/license/LinuxServerInfos.java
New file
@@ -0,0 +1,65 @@
package com.zy.system.entity.license;
import com.core.common.Cools;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.util.List;
import java.util.stream.Collectors;
public class LinuxServerInfos extends AbstractServerInfos {
    @Override
    protected List<String> getIpAddress() throws Exception {
        List<InetAddress> inetAddresses = getLocalAllInetAddress();
        if (inetAddresses == null || inetAddresses.isEmpty()) {
            return null;
        }
        return inetAddresses.stream()
                .map(InetAddress::getHostAddress)
                .distinct()
                .map(String::toLowerCase)
                .collect(Collectors.toList());
    }
    @Override
    protected List<String> getMacAddress() throws Exception {
        List<InetAddress> inetAddresses = getLocalAllInetAddress();
        if (inetAddresses == null || inetAddresses.isEmpty()) {
            return null;
        }
        return inetAddresses.stream()
                .map(this::getMacByInetAddress)
                .filter(mac -> mac != null && !mac.trim().isEmpty())
                .distinct()
                .collect(Collectors.toList());
    }
    @Override
    protected String getCPUSerial() throws Exception {
        return readFirstNonBlankLine(new String[]{"/bin/bash", "-c", "dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"});
    }
    @Override
    protected String getMainBoardSerial() throws Exception {
        return readFirstNonBlankLine(new String[]{"/bin/bash", "-c", "dmidecode | grep 'Serial Number' | awk -F ':' '{print $2}' | head -n 1"});
    }
    private String readFirstNonBlankLine(String[] shell) throws Exception {
        Process process = Runtime.getRuntime().exec(shell);
        process.getOutputStream().close();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (!Cools.isEmpty(line)) {
                    return line;
                }
            }
        }
        return "";
    }
}
src/main/java/com/zy/system/entity/license/WindowsServerInfos.java
@@ -1,8 +1,10 @@
package com.zy.system.entity.license;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;
/**
@@ -33,7 +35,11 @@
        if(inetAddresses != null && inetAddresses.size() > 0){
            //2. 获取所有网络接口的Mac地址
            result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());
            result = inetAddresses.stream()
                    .map(this::getMacByInetAddress)
                    .filter(mac -> mac != null && !mac.trim().isEmpty())
                    .distinct()
                    .collect(Collectors.toList());
        }
        return result;
@@ -41,45 +47,72 @@
    @Override
    protected String getCPUSerial() throws Exception {
        //序列号
        String serialNumber = "";
        //使用WMIC获取CPU序列号
        Process process = Runtime.getRuntime().exec("wmic cpu get processorid");
        process.getOutputStream().close();
        Scanner scanner = new Scanner(process.getInputStream());
        if(scanner.hasNext()){
            scanner.next();
        String serialNumber = readCommandValue("wmic", "cpu", "get", "processorid");
        if (!isBlank(serialNumber)) {
            return serialNumber;
        }
        if(scanner.hasNext()){
            serialNumber = scanner.next().trim();
        }
        scanner.close();
        return serialNumber;
        return readCommandValue("powershell", "-NoProfile", "-Command",
                "(Get-CimInstance Win32_Processor | Select-Object -First 1 -ExpandProperty ProcessorId)");
    }
    @Override
    protected String getMainBoardSerial() throws Exception {
        //序列号
        String serialNumber = "";
        //使用WMIC获取主板序列号
        Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber");
        process.getOutputStream().close();
        Scanner scanner = new Scanner(process.getInputStream());
        if(scanner.hasNext()){
            scanner.next();
        String serialNumber = readCommandValue("wmic", "baseboard", "get", "serialnumber");
        if (!isBlank(serialNumber)) {
            return serialNumber;
        }
        if(scanner.hasNext()){
            serialNumber = scanner.next().trim();
        }
        scanner.close();
        return serialNumber;
        return readCommandValue("powershell", "-NoProfile", "-Command",
                "(Get-CimInstance Win32_BaseBoard | Select-Object -First 1 -ExpandProperty SerialNumber)");
    }
}
    private String readCommandValue(String... command) {
        try {
            ProcessBuilder processBuilder = new ProcessBuilder(command);
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();
            process.getOutputStream().close();
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), Charset.forName("GBK")))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    String value = normalizeCommandOutput(line);
                    if (!isBlank(value)) {
                        return value;
                    }
                }
            }
        } catch (Exception ignored) {
        }
        return "";
    }
    private String normalizeCommandOutput(String line) {
        if (line == null) {
            return "";
        }
        String value = line.trim();
        if (isBlank(value)) {
            return "";
        }
        String lower = value.toLowerCase();
        if ("processorid".equals(lower) || "serialnumber".equals(lower)) {
            return "";
        }
        if (lower.contains("access denied")
                || lower.contains("拒绝访问")
                || lower.contains("get-ciminstance")
                || lower.contains("fullyqualifiederrorid")
                || lower.contains("at line:")
                || lower.contains("categoryinfo")
                || lower.contains("cimexception")
                || lower.contains("createprocess error")) {
            return "";
        }
        return value;
    }
    private boolean isBlank(String value) {
        return value == null || value.trim().isEmpty();
    }
}
src/main/java/com/zy/system/mapper/LicenseInfosMapper.java
New file
@@ -0,0 +1,14 @@
package com.zy.system.mapper;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.zy.system.entity.LicenseInfos;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface LicenseInfosMapper extends BaseMapper<LicenseInfos> {
    LicenseInfos getLatestLicenseByRequestCode(@Param("requestCode") String requestCode);
}
src/main/java/com/zy/system/service/LicenseInfosService.java
New file
@@ -0,0 +1,9 @@
package com.zy.system.service;
import com.baomidou.mybatisplus.service.IService;
import com.zy.system.entity.LicenseInfos;
public interface LicenseInfosService extends IService<LicenseInfos> {
    LicenseInfos getLatestLicenseByRequestCode(String requestCode);
}
src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java
New file
@@ -0,0 +1,16 @@
package com.zy.system.service.impl;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.zy.system.entity.LicenseInfos;
import com.zy.system.mapper.LicenseInfosMapper;
import com.zy.system.service.LicenseInfosService;
import org.springframework.stereotype.Service;
@Service("licenseInfosService")
public class LicenseInfosServiceImpl extends ServiceImpl<LicenseInfosMapper, LicenseInfos> implements LicenseInfosService {
    @Override
    public LicenseInfos getLatestLicenseByRequestCode(String requestCode) {
        return this.baseMapper.getLatestLicenseByRequestCode(requestCode);
    }
}
src/main/java/com/zy/system/timer/LicenseTimer.java
@@ -1,24 +1,142 @@
package com.zy.system.timer;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.core.common.Cools;
import com.zy.common.utils.HttpHandler;
import com.zy.system.entity.LicenseInfos;
import com.zy.system.entity.license.LicenseUtils;
import com.zy.system.entity.license.LicenseVerify;
import com.zy.system.entity.license.LicenseVerifyParam;
import com.zy.system.service.LicenseInfosService;
import de.schlichtherle.license.LicenseContent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
@Component
public class LicenseTimer {
    private static boolean SYSTEM_SUPPORT = true;//系统激活状态,默认关闭
    private static boolean SYSTEM_SUPPORT = false;//系统激活状态,默认关闭
    private static int LICENSE_DAYS = 0;//许可证天数
    @Value("${license.subject}")
    private String subject;
    @Value("${license.publicAlias}")
    private String publicAlias;
    @Value("${license.storePass}")
    private String storePass;
    @Value("${license.publicKeysStorePath}")
    private String publicKeysStorePath;
    @Value("${license.remoteServerUrl:http://net.zoneyung.net:9999/license}")
    private String remoteServerUrl;
    @Autowired
    private LicenseInfosService licenseInfosService;
    //每天晚上11点更新系统激活状态
    @Scheduled(cron = "0 0 23 * * ? ")
    public void timer() {
//        System.out.println(SYSTEM_SUPPORT);
        //验证许可证是否有效
        try {
            getRemoteLicense();
        } catch (Exception ignored) {
        }
        try {
            verify();
        } catch (Exception ignored) {
        }
    }
    public void getRemoteLicense() {
        try {
            String requestCode = LicenseUtils.buildRequestCode(subject);
            JSONObject response = requestRemoteLicense(buildRequestCodePayload(requestCode));
            if (isSuccess(response)) {
                String license = response.getString("data");
                if (Cools.isEmpty(license)) {
                    return;
                }
                LicenseInfos latestLicense = licenseInfosService.getLatestLicenseByRequestCode(requestCode);
                if (latestLicense != null && Cools.eq(latestLicense.getLicense(), license)) {
                    return;
                }
                LicenseInfos licenseInfos = new LicenseInfos();
                licenseInfos.setLicense(license);
                licenseInfos.setCreateTime(new Date());
                licenseInfos.setLicenseTime(response.getString("licenseTime"));
                licenseInfos.setRequestCode(requestCode);
                licenseInfosService.insert(licenseInfos);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private JSONObject requestRemoteLicense(String json) {
        try {
            String response = new HttpHandler.Builder()
                    .setUri(remoteServerUrl)
                    .setPath("/remoteQueryLicense")
                    .setJson(json)
                    .build()
                    .doPost();
            if (response == null || response.trim().isEmpty()) {
                return null;
            }
            return JSON.parseObject(response);
        } catch (Exception e) {
            return null;
        }
    }
    private String buildRequestCodePayload(String requestCode) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("subject", subject);
        map.put("requestCode", requestCode);
        return JSON.toJSONString(map);
    }
    private boolean isSuccess(JSONObject jsonObject) {
        return jsonObject != null && "ok".equalsIgnoreCase(jsonObject.getString("result"));
    }
    public void verify() {
        LicenseVerifyParam param = new LicenseVerifyParam();
        param.setSubject(subject);
        param.setPublicAlias(publicAlias);
        param.setStorePass(storePass);
        param.setPublicKeysStorePath(publicKeysStorePath);
        String requestCode = LicenseUtils.buildRequestCode(subject);
        LicenseInfos latestLicense = licenseInfosService.getLatestLicenseByRequestCode(requestCode);
        LicenseVerify licenseVerify = new LicenseVerify();
        boolean verify = licenseVerify.verify();
        setSystemSupport(verify);//更新系统激活状态
        LicenseContent install = null;
        if (latestLicense != null && !Cools.isEmpty(latestLicense.getLicense())) {
            install = licenseVerify.install(param, latestLicense.getLicense());
        }
        if (install != null) {
            Date start = new Date();
            Date end = install.getNotAfter();
            long num = end.getTime() - start.getTime();
            int day = (int) (num / 24 / 60 / 60 / 1000);
            setLicenseDays(day);
            setSystemSupport(true);
        } else {
            setLicenseDays(0);
            setSystemSupport(false);
        }
    }
    public boolean getSystemSupport() {
src/main/resources/application-dev.yml
@@ -48,10 +48,9 @@
#License相关配置
license:
  subject: integrationasrs
  subject: jxhcasrs
  publicAlias: publicCert
  storePass: public_zhongyang_123456789
  licensePath: license.lic
  publicKeysStorePath: publicCerts.keystore
# 下位机配置
src/main/resources/application-prod.yml
@@ -63,7 +63,6 @@
  subject: integrationasrs
  publicAlias: publicCert
  storePass: public_zhongyang_123456789
  licensePath: license.lic
  publicKeysStorePath: publicCerts.keystore
# 下位机配置
@@ -122,4 +121,4 @@
    acctID: "647e849ab6fa0f"
    username: "llw"
    password: "666666"
    lcid: 2052
    lcid: 2052
src/main/resources/license.lic
File was deleted
src/main/resources/mapper/LicenseInfosMapper.xml
New file
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zy.system.mapper.LicenseInfosMapper">
    <resultMap id="BaseResultMap" type="com.zy.system.entity.LicenseInfos">
        <id column="id" property="id"/>
        <result column="license" property="license"/>
        <result column="license_time" property="licenseTime"/>
        <result column="request_code" property="requestCode"/>
        <result column="create_time" property="createTime"/>
    </resultMap>
    <select id="getLatestLicenseByRequestCode" resultMap="BaseResultMap">
        select top 1 * from sys_license_infos where request_code = #{requestCode} order by create_time desc
    </select>
</mapper>
src/main/resources/mapper/LocDetlMapper.xml
@@ -51,6 +51,8 @@
        <result column="box_type1" property="boxType1" />
        <result column="box_type2" property="boxType2" />
        <result column="box_type3" property="boxType3" />
        <result column="area_id" property="areaId" />
    </resultMap>
    <sql id="batchSeq">
src/main/resources/mapper/OrderDetlPakoutMapper.xml
@@ -60,9 +60,9 @@
        <if test="brand != null and brand != ''">
            and brand = #{brand}
        </if>
<!--        <if test="standby1 != null and standby1 != ''">-->
<!--            and standby1 = #{standby1}-->
<!--        </if>-->
        <if test="standby1 != null and standby1 != ''">
            and standby1 = #{standby1}
        </if>
        <if test="standby2 != null and standby2 != ''">
            and standby2 = #{standby2}
        </if>
@@ -84,7 +84,7 @@
    </select>
    <select id="selectItemByOrderNo" resultMap="BaseResultMap">
        select * from man_order_detl_pakout
        select top 1 * from man_order_detl_pakout
        where 1=1
        and order_no = #{orderNo}
        and matnr = #{matnr}
src/main/resources/mapper/WrkMastMapper.xml
@@ -60,6 +60,7 @@
        <result column="full_plt" property="fullPlt" />
        <result column="pre_have" property="preHave" />
        <result column="take_none" property="takeNone" />
        <result column="task_type" property="taskType" />
    </resultMap>
    <select id="selectToBeCompleteData" resultMap="BaseResultMap">
@@ -148,4 +149,12 @@
        </choose>
    </select>
    <update id="updatePublishError">
        update asr_wrk_mast
        set upd_mk = #{updMk},
            error_time = #{errorTime},
            error_memo = #{errorMemo}
        where wrk_no = #{wrkNo}
    </update>
</mapper>
src/main/webapp/static/js/locAroundBind/locAroundBind.js
@@ -23,13 +23,14 @@
        cols: [[
            {type: 'checkbox'}
            , {field: 'id', align: 'center', title: 'ID', hide: true}
            , {field: 'devId', align: 'center', title: '机台ID', hide: true}
            , {field: 'devNo', align: 'center', title: '机台号'}
            , {field: 'devName', align: 'center', title: '设备名称'}
            , {field: 'devNo$', align: 'center', title: '机台号'}
            , {field: 'devId', align: 'center', title: '机台ID'}
            , {field: 'devName', align: 'center', title: '设备名称', hide: true }
            , {field: 'blocId', align: 'center', title: '库位ID', hide: true}
            , {field: 'blocNo', align: 'center', title: '工位'}
            , {field: 'locType$', align: 'center', title: '工位状态'}
            , {field: 'def$', align: 'center', title: '工位类型'}
            , {field: 'freeze', align: 'center', title: '是否冻结'}
            , {fixed: 'right', title: '操作', align: 'center', toolbar: '#operate', width: 240}
        ]],
        request: {
src/main/webapp/static/js/locDetl/locDetl.js
@@ -1,4 +1,9 @@
var pageCurr;
function formatBeBatch(beBatch) {
    return beBatch === 1 ? '强制入库' : '正常入库';
}
function getCol() {
    var cols = [
        {type: 'checkbox'},
@@ -8,6 +13,9 @@
        ,{field: 'maktx', align: 'center',title: '商品名称', sort:true}
        ,{field: 'orderNo', align: 'center',title: '单据编号', hide: true}
        ,{field: 'batch', align: 'center',title: '批号', sort:true}
        ,{field: 'beBatch', align: 'center',title: '入库类型', templet: function(d) {
                return formatBeBatch(d.beBatch);
            }}
        ,{field: 'anfme', align: 'center',title: '数量'}
        ,{field: 'zpallet', align: 'center',title: 'SN'}
        ,{field: 'specs', align: 'center',title: '规格'}
@@ -533,6 +541,7 @@
    form.on('submit(reset)', function (data) {
        pageCurr = 1;
        clearFormVal($('#search-box'));
        form.render('select');
        tableReload(false);
    });
src/main/webapp/static/js/wrkMast/wrkMast.js
@@ -36,6 +36,7 @@
            ,{field: 'preHave', align: 'center',title: '先入品', hide: true}
            ,{field: 'takeNone', align: 'center',title: '空操作', hide: true}
            ,{field: 'isSuplus$', align: 'center', title: '回库类型', hide: false, width: 120}
            ,{field: 'errorMemo', align: 'center',title: '下发/异常信息', minWidth: 220}
            ,{field: 'modiUser$', align: 'center',title: '修改人员', hide:true}
            ,{field: 'modiTime$', align: 'center',title: '修改时间', hide:true, width: 160}
            ,{fixed: 'right', title:'操作', align: 'center', toolbar: '#operate', width:350}
@@ -683,3 +684,4 @@
        $("#search").click();
    }
});
src/main/webapp/views/basDevice/basDevice.html
@@ -18,9 +18,15 @@
            <div class="layui-form toolbar" id="search-box">
                <div class="layui-form-item">
                    <div class="layui-inline">
                        <label class="layui-form-label">编号:</label>
                        <label class="layui-form-label">机台号:</label>
                        <div class="layui-input-inline">
                            <input class="layui-input" type="text" name="id" placeholder="编号" autocomplete="off">
                            <input class="layui-input" type="text" name="dev_no" placeholder="机台号" autocomplete="off">
                        </div>
                    </div>
                    <div class="layui-inline">
                        <label class="layui-form-label">内部编号:</label>
                        <div class="layui-input-inline">
                            <input class="layui-input" type="text" name="type" placeholder="内部编号" autocomplete="off">
                        </div>
                    </div>
                    <div class="layui-inline">&emsp;
src/main/webapp/views/index.html
@@ -92,6 +92,31 @@
<script type="text/javascript" src="../static/js/common.js"></script>
<script>
  // console.log('%c 中扬立库平台 %c 1.0.0','background-color:rgb(53,73,94);color: #fff;border-radius:2px 0 0 2px;padding:2px 4px;','background-color:rgb(25,190,107);color: #fff;border-radius:0 2px 2px 0;padding:2px 4px;font: 9pt "Apercu Regular", Georgia, "Times New Roman", Times, serif;');
  function getResponseValue(res) {
    if (!res) {
      return "";
    }
    if (typeof res.data !== "undefined" && res.data !== null && res.data !== "") {
      return res.data;
    }
    if (typeof res.msg !== "undefined" && res.msg !== null && res.msg !== "") {
      return res.msg;
    }
    return "";
  }
  function redirectToLicensePage(message) {
    sessionStorage.setItem("licensePanelAutoOpen", "1");
    sessionStorage.setItem("licenseInvalidReason", message || "系统当前许可证无效,请重新导入新许可证。");
    localStorage.removeItem('token');
    top.location.href = "login.html?license=invalid";
  }
  function clearLicensePromptState() {
    sessionStorage.removeItem("licensePanelAutoOpen");
    sessionStorage.removeItem("licenseInvalidReason");
  }
  $(function () {
    if ("" === localStorage.getItem('token')) {
      top.location.href = baseUrl + "/login";
@@ -146,13 +171,20 @@
      method: 'POST',
      success: function (res) {
        if (res.code == 200) {
          let days = res.data
          let days = Number(getResponseValue(res))
          if (days < 0) {
            redirectToLicensePage('系统当前未检测到可用许可证,请重新导入新的许可证。');
            return;
          }
          clearLicensePromptState();
          if (days <= 30) {
            $("#licenseShow").show()
            $("#licenseDays").html(days)
          } else {
            $("#licenseShow").hide()
          }
        }else {
          top.location.href = baseUrl + "/login";
          redirectToLicensePage(res.msg || '许可证校验失败,请重新导入新的许可证。');
        }
      }
    });
src/main/webapp/views/locDetl/locDetl.html
@@ -44,6 +44,15 @@
    </div>
    <div class="layui-inline">
        <div class="layui-input-inline">
            <select name="beBatch" class="layui-input" autocomplete="off">
                <option value="">入库类型</option>
                <option value="1">强制入库</option>
                <option value="0">正常入库</option>
            </select>
        </div>
    </div>
    <div class="layui-inline">
        <div class="layui-input-inline">
            <select name="frozen" class="layui-input" type="text" autocomplete="off">
                <!--                    <option style="display: none"></option>-->
                <option value="">冻结否</option>
src/main/webapp/views/login.html
@@ -46,6 +46,178 @@
            text-align: center;
        }
        .license-entry-text {
            width: 80%;
            margin: 0 auto 12px;
            color: #5e5e5e;
            font-size: 12px;
            line-height: 1.7;
            text-align: left;
        }
        .system-tool-shell {
            background: #ffffff;
            border-radius: 22px;
            overflow: hidden;
        }
        .system-tool-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 14px 20px;
            border-bottom: 1px solid #dbe4ef;
        }
        .system-tool-title {
            color: #243447;
            font-size: 18px;
            font-weight: 700;
        }
        .system-tool-close {
            color: #97a5b4;
            font-size: 24px;
            line-height: 1;
            cursor: pointer;
            user-select: none;
        }
        .system-tool-body {
            padding: 18px 20px 22px;
            background: #ffffff;
        }
        .system-tool-group + .system-tool-group {
            margin-top: 18px;
            padding-top: 18px;
            border-top: 1px solid #e8eef5;
        }
        .system-tool-group-title {
            color: #34495e;
            font-size: 14px;
            font-weight: 700;
            margin-bottom: 14px;
        }
        .system-tool-actions {
            display: flex;
            flex-wrap: wrap;
            gap: 12px 18px;
        }
        .system-tool-btn {
            min-width: 104px;
            height: 34px;
            padding: 0 18px;
            border: 1px solid #d7dfea;
            border-radius: 4px;
            background: #ffffff;
            color: #4d5d6d;
            font-size: 14px;
            cursor: pointer;
            box-sizing: border-box;
        }
        .system-tool-btn.primary {
            color: #ffffff;
            background: #5da8ff;
            border-color: #5da8ff;
        }
        .system-tool-btn.secondary {
            color: #7eb4ef;
            background: #d9ecff;
            border-color: #aed4ff;
        }
        .system-tool-desc {
            margin-top: 12px;
            color: #9aa8b6;
            font-size: 12px;
            line-height: 1.7;
        }
        .system-dialog-shell {
            background: #ffffff;
            border-radius: 18px;
            overflow: hidden;
        }
        .system-dialog-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 14px 18px;
            border-bottom: 1px solid #e1e8f0;
            background: #f8fbff;
        }
        .system-dialog-title {
            color: #243447;
            font-size: 16px;
            font-weight: 700;
        }
        .system-dialog-close {
            color: #97a5b4;
            font-size: 22px;
            line-height: 1;
            cursor: pointer;
            user-select: none;
        }
        .system-dialog-body {
            padding: 18px;
            background: #ffffff;
        }
        .system-dialog-label {
            color: #43576b;
            font-size: 13px;
            font-weight: 700;
            margin-bottom: 8px;
        }
        .system-dialog-tip {
            margin-bottom: 10px;
            color: #8c9aac;
            font-size: 12px;
            line-height: 1.7;
        }
        .system-dialog-textarea {
            width: 100%;
            min-height: 220px;
            resize: none;
            border: 1px solid #d7dfea;
            border-radius: 8px;
            padding: 10px 12px;
            box-sizing: border-box;
            color: #4d5d6d;
            background: #fbfdff;
            font-size: 13px;
            line-height: 1.7;
        }
        .system-dialog-footer {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
            margin-top: 14px;
        }
        body .system-tool-popup {
            border-radius: 22px !important;
            overflow: hidden !important;
        }
        body .system-tool-popup .layui-layer-content {
            overflow: hidden !important;
            background: transparent;
        }
    </style>
</head>
<body>
@@ -90,10 +262,8 @@
                <button class="login100-form-btn login-btn">Login</button>
            </div>
            <div class="container-login100-form-btn p-t-10" style="display: none;margin-top: 50px;" id="updateLicense">
                <form enctype="multipart/form-data" style="display: none;">
                    <input id="license" type="file" name="file">
                </form>
                <button class="login100-form-btn" id="submitLicense">更新许可证</button>
                <div class="license-entry-text" id="licenseEntryMessage">系统当前未检测到可用许可证,请打开系统工具处理许可证。</div>
                <button class="login100-form-btn" id="openLicenseTool" type="button">系统工具</button>
            </div>
        </div>
    </div>
@@ -115,10 +285,458 @@
        if (oldPass) {
            $('#password').val(oldPass);
        }
        autoShowLicenseTool();
    })
    window.onload = function () {
        document.getElementById("username").focus();
    }
    var licenseToolState = {
        toolLayerIndex: null,
        textLayerIndex: null,
        uploadLayerIndex: null
    };
    function getQueryValue(name) {
        var query = window.location.search.substring(1).split("&");
        for (var i = 0; i < query.length; i++) {
            var item = query[i].split("=");
            if (item[0] === name) {
                return decodeURIComponent(item[1] || "");
            }
        }
        return "";
    }
    function autoShowLicenseTool() {
        var needOpen = getQueryValue("license") === "invalid" || sessionStorage.getItem("licensePanelAutoOpen") === "1";
        if (!needOpen) {
            return;
        }
        verifyLicenseAvailability(function (isValid) {
            if (isValid) {
                clearLicensePromptState();
                hideLicenseEntry();
                clearLicenseQueryFlag();
                return;
            }
            var message = sessionStorage.getItem("licenseInvalidReason") || "系统当前未检测到可用许可证,请先导入新的许可证。";
            showLicenseEntry(message);
            openLicenseTool(message);
        });
    }
    function showLicenseEntry(message) {
        $("#updateLicense").show();
        if (message) {
            $("#licenseEntryMessage").text(message);
        }
    }
    function hideLicenseEntry() {
        $("#updateLicense").hide();
    }
    function clearLicensePromptState() {
        sessionStorage.removeItem("licensePanelAutoOpen");
        sessionStorage.removeItem("licenseInvalidReason");
    }
    function clearLicenseQueryFlag() {
        if (window.history && window.history.replaceState && getQueryValue("license") === "invalid") {
            window.history.replaceState(null, document.title, window.location.pathname);
        }
    }
    function verifyLicenseAvailability(callback) {
        $.ajax({
            url: baseUrl + "/license/getLicenseDays",
            method: 'GET',
            success: function (res) {
                if (res.code == 200) {
                    var days = Number(getResponseValue(res));
                    callback(!isNaN(days) && days >= 0);
                    return;
                }
                callback(false);
            },
            error: function () {
                callback(false);
            }
        });
    }
    function escapeHtml(text) {
        return String(text || "")
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/\"/g, "&quot;")
            .replace(/'/g, "&#39;");
    }
    function buildLicenseToolContent() {
        return ''
            + '<div class="system-tool-shell">'
            + '  <div class="system-tool-header">'
            + '    <div class="system-tool-title">系统工具</div>'
            + '    <span class="system-tool-close" id="toolClose">×</span>'
            + '  </div>'
            + '  <div class="system-tool-body">'
            + '    <div class="system-tool-group">'
            + '      <div class="system-tool-group-title">推荐操作</div>'
            + '      <div class="system-tool-actions">'
            + '        <button type="button" class="system-tool-btn primary" id="toolRequestCode">获取请求码</button>'
            + '        <button type="button" class="system-tool-btn secondary" id="toolActivate">一键激活</button>'
            + '      </div>'
            + '      <div class="system-tool-desc">优先使用“获取请求码”和“一键激活”完成许可证申请与激活。</div>'
            + '    </div>'
            + '    <div class="system-tool-group">'
            + '      <div class="system-tool-group-title">其他工具</div>'
            + '      <div class="system-tool-actions">'
            + '        <button type="button" class="system-tool-btn" id="toolProjectName">获取项目名称</button>'
            + '        <button type="button" class="system-tool-btn" id="toolServerInfo">获取系统配置</button>'
            + '        <button type="button" class="system-tool-btn" id="toolInputLicense">录入许可证</button>'
            + '      </div>'
            + '    </div>'
            + '  </div>'
            + '</div>';
    }
    function openLicenseTool(message) {
        var toolMessage = message || $("#licenseEntryMessage").text() || "系统当前未检测到可用许可证,请先导入新的许可证。";
        var toolWidth = Math.min($(window).width() - 32, 760);
        showLicenseEntry(toolMessage);
        if (licenseToolState.toolLayerIndex !== null) {
            layer.close(licenseToolState.toolLayerIndex);
        }
        licenseToolState.toolLayerIndex = layer.open({
            type: 1,
            title: false,
            closeBtn: 0,
            area: [Math.min(toolWidth, 560) + 'px', 'auto'],
            shadeClose: false,
            skin: 'system-tool-popup',
            content: buildLicenseToolContent(),
            success: function (layero, index) {
                licenseToolState.toolLayerIndex = index;
                bindLicenseToolEvents(layero);
            },
            end: function () {
                licenseToolState.toolLayerIndex = null;
            }
        });
    }
    function bindLicenseToolEvents(layero) {
        layero.find("#toolClose").on("click", function () {
            layer.close(licenseToolState.toolLayerIndex);
        });
        layero.find("#toolRequestCode").on("click", function () {
            requestCode();
        });
        layero.find("#toolActivate").on("click", function () {
            activateLicense();
        });
        layero.find("#toolProjectName").on("click", function () {
            getProjectName();
        });
        layero.find("#toolServerInfo").on("click", function () {
            getServerInfo();
        });
        layero.find("#toolInputLicense").on("click", function () {
            openLicenseInputDialog();
        });
    }
    function getResponseValue(res) {
        if (!res) {
            return "";
        }
        if (typeof res.data !== "undefined" && res.data !== null && res.data !== "") {
            return res.data;
        }
        if (typeof res.msg !== "undefined" && res.msg !== null && res.msg !== "") {
            return res.msg;
        }
        return "";
    }
    function openTextDialog(title, label, text, tip) {
        var prettyText = "";
        try {
            prettyText = typeof text === "string" ? text : JSON.stringify(text, null, 2);
        } catch (e) {
            prettyText = String(text || "");
        }
        if (licenseToolState.textLayerIndex !== null) {
            layer.close(licenseToolState.textLayerIndex);
        }
        licenseToolState.textLayerIndex = layer.open({
            type: 1,
            title: false,
            closeBtn: 0,
            area: [Math.min($(window).width() - 32, 720) + 'px', 'auto'],
            shadeClose: false,
            skin: 'system-tool-popup',
            content: ''
                + '<div class="system-dialog-shell">'
                + '  <div class="system-dialog-header">'
                + '    <div class="system-dialog-title">' + escapeHtml(title) + '</div>'
                + '    <span class="system-dialog-close" id="systemTextClose">×</span>'
                + '  </div>'
                + '  <div class="system-dialog-body">'
                + '    <div class="system-dialog-label">' + escapeHtml(label) + '</div>'
                + (tip ? '<div class="system-dialog-tip">' + escapeHtml(tip) + '</div>' : '')
                + '    <textarea class="system-dialog-textarea" id="systemDialogText" readonly></textarea>'
                + '    <div class="system-dialog-footer">'
                + '      <button type="button" class="system-tool-btn" id="systemTextCloseBtn">关闭</button>'
                + '      <button type="button" class="system-tool-btn primary" id="systemTextCopy">复制</button>'
                + '    </div>'
                + '  </div>'
                + '</div>',
            success: function (layero, index) {
                licenseToolState.textLayerIndex = index;
                layero.find("#systemDialogText").val(prettyText);
                layero.find("#systemTextClose, #systemTextCloseBtn").on("click", function () {
                    layer.close(index);
                });
                layero.find("#systemTextCopy").on("click", function () {
                    copyRequestCodeText(prettyText);
                });
            },
            end: function () {
                licenseToolState.textLayerIndex = null;
            }
        });
    }
    function openLicenseInputDialog() {
        if (licenseToolState.uploadLayerIndex !== null) {
            layer.close(licenseToolState.uploadLayerIndex);
        }
        licenseToolState.uploadLayerIndex = layer.open({
            type: 1,
            title: false,
            closeBtn: 0,
            area: [Math.min($(window).width() - 32, 760) + 'px', 'auto'],
            shadeClose: false,
            skin: 'system-tool-popup',
            content: ''
                + '<div class="system-dialog-shell">'
                + '  <div class="system-dialog-header">'
                + '    <div class="system-dialog-title">录入许可证</div>'
                + '    <span class="system-dialog-close" id="licenseInputClose">×</span>'
                + '  </div>'
                + '  <div class="system-dialog-body">'
                + '    <div class="system-dialog-label">许可证 Base64</div>'
                + '    <div class="system-dialog-tip">将许可证服务端返回的 license 字段完整粘贴到这里。</div>'
                + '    <textarea class="system-dialog-textarea" id="licenseInputValue"></textarea>'
                + '    <div class="system-dialog-footer">'
                + '      <button type="button" class="system-tool-btn" id="licenseInputCancel">取消</button>'
                + '      <button type="button" class="system-tool-btn primary" id="licenseInputSubmit">提交</button>'
                + '    </div>'
                + '  </div>'
                + '</div>',
            success: function (layero, index) {
                licenseToolState.uploadLayerIndex = index;
                layero.find("#licenseInputClose, #licenseInputCancel").on("click", function () {
                    layer.close(index);
                });
                layero.find("#licenseInputSubmit").on("click", function () {
                    submitLicense(layero.find("#licenseInputValue").val());
                });
            },
            end: function () {
                licenseToolState.uploadLayerIndex = null;
            }
        });
    }
    function requestCode() {
        fetchRequestCode(function (value, errorMsg) {
            if (value) {
                openTextDialog("获取请求码", "请求码", value, "请求码中已包含项目名称,直接发给许可证服务端即可。");
                return;
            }
            layer.msg(errorMsg || '获取请求码失败', {time: 2000});
        });
    }
    function fetchRequestCode(callback) {
        $.ajax({
            url: baseUrl + "/license/getRequestCode",
            method: 'GET',
            success: function (res) {
                var value = getResponseValue(res);
                if (res.code == 200) {
                    callback(value ? String(value) : "", "");
                    return;
                }
                callback("", res.msg || '获取请求码失败');
            },
            error: function (xhr) {
                callback("", xhr && xhr.status === 404 ? "获取请求码接口不存在" : "获取请求码失败");
            }
        })
    }
    function getProjectName() {
        $.ajax({
            url: baseUrl + "/license/getProjectName",
            method: 'GET',
            success: function (res) {
                var value = getResponseValue(res);
                if (res.code == 200 && value) {
                    layer.alert(escapeHtml(String(value)), {
                        title: '项目名称'
                    });
                    return;
                }
                layer.msg(res.msg || '获取项目名称失败', {time: 2000});
            },
            error: function () {
                layer.msg('获取项目名称失败', {time: 2000});
            }
        })
    }
    function getServerInfo() {
        $.ajax({
            url: baseUrl + "/license/getServerInfos",
            method: 'GET',
            success: function (res) {
                openTextDialog("获取系统配置", "系统配置信息", res, "新许可证模式下可用于排查当前节点硬件绑定信息。");
            },
            error: function () {
                fallbackServerInfoFromRequestCode();
            }
        })
    }
    function fallbackServerInfoFromRequestCode() {
        fetchRequestCode(function (requestCode, errorMsg) {
            if (!requestCode) {
                layer.msg(errorMsg || '获取系统配置信息失败', {time: 2000});
                return;
            }
            try {
                var decoded = decodeRequestCodePayload(requestCode);
                var node = (((decoded || {}).licenseBind || {}).nodes || [])[0] || {};
                var serverInfo = {
                    subject: decoded.subject || "",
                    nodeId: node.nodeId || "",
                    ipAddress: node.ipAddress || [],
                    macAddress: node.macAddress || [],
                    cpuSerial: node.cpuSerial || "",
                    mainBoardSerial: node.mainBoardSerial || ""
                };
                openTextDialog("获取系统配置", "系统配置信息", serverInfo, "当前接口不可用,已按请求码解析出本机节点配置。");
            } catch (e) {
                layer.msg('获取系统配置信息失败', {time: 2000});
            }
        });
    }
    function decodeRequestCodePayload(requestCode) {
        var normalized = String(requestCode || "").replace(/\s+/g, "");
        if (!normalized) {
            return {};
        }
        var binary = window.atob(normalized);
        var bytes = [];
        for (var i = 0; i < binary.length; i++) {
            bytes.push("%" + ("00" + binary.charCodeAt(i).toString(16)).slice(-2));
        }
        return JSON.parse(decodeURIComponent(bytes.join("")));
    }
    function activateLicense() {
        layer.confirm('确定执行一键激活吗?', {
            title: '提示'
        }, function (index) {
            layer.close(index);
            $.ajax({
                url: baseUrl + "/license/activate",
                method: 'POST',
                success: function (res) {
                    if (res.code == 200) {
                        layer.msg('激活成功', {time: 1500});
                        return;
                    }
                    layer.msg(res.msg || '激活失败', {time: 2000});
                },
                error: function () {
                    layer.msg('激活失败', {time: 2000});
                }
            })
        });
    }
    function submitLicense(licenseBase64) {
        if (!licenseBase64 || !licenseBase64.trim()) {
            layer.msg('许可证内容不能为空', {time: 1500});
            return;
        }
        $.ajax({
            url: baseUrl + "/license/updateLicense",
            headers: {'Content-Type': 'application/json'},
            data: JSON.stringify({license: licenseBase64.trim()}),
            method: 'POST',
            success: function (res) {
                if (res.code == 200) {
                    clearLicensePromptState();
                    clearLicenseQueryFlag();
                    if (licenseToolState.uploadLayerIndex !== null) {
                        layer.close(licenseToolState.uploadLayerIndex);
                    }
                    layer.msg('许可证更新成功', {time: 1000}, function () {
                        parent.location.reload();
                    });
                    return;
                }
                layer.msg(res.msg || '许可证更新失败', {time: 2000});
            },
            error: function (xhr) {
                if (xhr && xhr.status === 403) {
                    layer.msg('许可证录入接口被拦截,请重启服务后重试', {time: 2500});
                    return;
                }
                layer.msg('许可证录入失败', {time: 2000});
            }
        });
    }
    function copyRequestCodeText(text) {
        if (!text) {
            layer.msg('请求码为空', {time: 1500});
            return;
        }
        if (navigator.clipboard && window.isSecureContext) {
            navigator.clipboard.writeText(text).then(function () {
                layer.msg('请求码已复制', {time: 1200});
            }, function () {
                fallbackCopy(text);
            });
            return;
        }
        fallbackCopy(text);
    }
    function fallbackCopy(text) {
        var copyInput = $("<textarea>").val(text).css({
            position: "fixed",
            top: "-1000px"
        }).appendTo("body");
        copyInput[0].select();
        try {
            document.execCommand("copy");
            layer.msg('请求码已复制', {time: 1200});
        } catch (e) {
            layer.msg('复制失败,请手动复制', {time: 1500});
        }
        copyInput.remove();
    }
    $(document).on('click', '.login-btn', function () {
@@ -140,6 +758,8 @@
            method: 'POST',
            success: function (res) {
                if (res.code === 200) {
                    clearLicensePromptState();
                    clearLicenseQueryFlag();
                    localStorage.setItem("token", res.data.token);
                    localStorage.setItem("username", res.data.username);
                    window.location.href = "index.html";
@@ -151,7 +771,8 @@
                    layer.tips(res.msg, '#password', {tips: [4, '#ff0000']});
                } else if (res.code == 20001) {
                    layer.tips(res.msg, '.login-btn', {tips: [3, '#ff0000']});
                    $("#updateLicense").show()
                    showLicenseEntry(res.msg)
                    openLicenseTool(res.msg)
                } else {
                    layer.tips(res.msg, '.login-btn', {tips: [3, '#ff0000']});
                }
@@ -165,37 +786,8 @@
        }
    });
    //更新许可证
    $("#submitLicense").on("click", () => {
        $("#license").click()
    })
    //上传并更新许可证
    $("#license").on("change", (evt) => {
        var files = evt.target.files;
        let formData = new FormData();
        formData.append("file", files[0])
        $.ajax({
            url: baseUrl + "/license/updateLicense",
            headers: {'token': localStorage.getItem('token')},
            data: formData,
            method: 'POST',
            cache: false,
            processData: false,
            contentType: false,
            success: function (res) {
                if (res.code == 200) {
                    layer.msg('更新成功', {time: 1000}, () => {
                        parent.location.reload()
                    });
                } else {
                    layer.msg(res.msg, {time: 2000}, () => {
                        parent.location.reload()
                    })
                }
            }
        })
    $("#openLicenseTool").on("click", () => {
        openLicenseTool($("#licenseEntryMessage").text());
    })
</script>
</body>