yangys
2025-07-06 9064866a2c0c2988cba1d321d7af088d2647a4bc
增加固化流程
已修改14个文件
已添加1个文件
789 ■■■■■ 文件已修改
blade-service/blade-mdm/src/main/java/org/springblade/mdm/flow/controller/DispatchController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/flow/service/CureFlowService.java 111 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/flow/task/cure/CureFinishOperateTask.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/controller/DncSendBackController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/controller/NcProgramExportDNCController.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/entity/NcProgramExchange.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/entity/ProcessProgRef.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/DncSendBackService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/NcProgramApprovedService.java 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/NcProgramExchangeService.java 162 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/NcProgramExportDNCService.java 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/ProcessProgRefService.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/vo/DncSendBackData.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/resources/processesbpmn/program-cure.bpmn20.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/sql/mdm/mdm.mysql.all.create.sql 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
blade-service/blade-mdm/src/main/java/org/springblade/mdm/flow/controller/DispatchController.java
@@ -55,7 +55,7 @@
    @Operation(summary = "完成任务", description = "流向下一个节点")
    @PostMapping("completeTask")
    public R<Void> completeTask(String taskId, String processInstanceId, String comment,@Parameter(description = "对应的程序id,逗号分隔") String programIds, @Parameter(name = "variables", description = "任务变量") @RequestBody Map<String, Object> variables) {
    public R<Void> completeTask(String taskId, String processInstanceId, String comment,@Parameter(name = "variables", description = "任务变量") @RequestBody Map<String, Object> variables) {
        // å¢žåŠ è¯„è®º
        if (StringUtil.isNoneBlank(processInstanceId, comment)) {
            taskService.addComment(taskId, processInstanceId, comment);
@@ -63,6 +63,10 @@
        // éžç©ºåˆ¤æ–­
        if (Func.isEmpty(variables)) {
            variables = Kv.create();
        }
        String programIds = null;
        if(variables.containsKey("programIds")){
            programIds = variables.get("programIds").toString();
        }
        if(StringUtils.isNotEmpty(programIds)) {
            processProgRefService.addRelations(processInstanceId,Func.toLongList(programIds));
@@ -72,6 +76,7 @@
        variables.put("approveUserNickName",AuthUtil.getNickName());
        if(variables.containsKey("assignee")){
            addApproveRecord(taskId,processInstanceId,comment,programIds,variables);
            //指定了下一步执行人
            taskService.complete(taskId, variables);
blade-service/blade-mdm/src/main/java/org/springblade/mdm/flow/service/CureFlowService.java
@@ -1,8 +1,11 @@
package org.springblade.mdm.flow.service;
import lombok.AllArgsConstructor;
import org.flowable.engine.HistoryService;
import org.flowable.engine.IdentityService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.mdm.basesetting.machine.MachineService;
import org.springblade.mdm.basesetting.machine.entity.Machine;
@@ -10,33 +13,38 @@
import org.springblade.mdm.flow.vo.TaskAssignVO;
import org.springblade.mdm.program.entity.NcNode;
import org.springblade.mdm.program.entity.NcProgram;
import org.springblade.mdm.program.entity.ProcessProgRef;
import org.springblade.mdm.program.service.NcNodeService;
import org.springblade.mdm.program.service.NcProgramService;
import org.springblade.mdm.program.service.ProcessProgRefService;
import org.springblade.mdm.program.vo.DncSendBackData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * å›ºåŒ–流程服务
 */
@Service
@AllArgsConstructor
public class CureFlowService {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private AutoAssignUsersService autoAssignUsersService;
    @Autowired
    private IdentityService identityService;
    private final RuntimeService runtimeService;
    @Autowired
    private NcProgramService ncProgramService;
    @Autowired
    private NcNodeService ncNodeService;
    @Autowired
    private MachineService machineService;
    private final HistoryService historyService;
    private final IdentityService identityService;
    private final NcProgramService ncProgramService;
    private final MachineService machineService;
    private final ProcessProgRefService processProgRefService;
    /**
     * å›ºåŒ–流程的key
     */
@@ -47,24 +55,87 @@
     * @param programId ç¨‹åºid
     */
    public void start(long programId){
        //pinst.getProcessInstanceId();
        int a=1;
        //log.info()
    }
    @Transactional
    public void startCure(List<NcProgram> programs) {
        //DncSendBackData sendBackData;
        //List<Long> ncProgramIdList = dataList.stream().map(DncSendBackData::getId).toList();
        //Map<String,List<NcProgram>> groupedProgram = groupProgramsByMachineCode(ncProgramIdList);
        //List<NcProgram> programs = ncProgramService.listByIds(ncProgramIdList);
        //根据分组启动流程,并插入关联表
        programs.forEach(this::startOne);
    }
    /**
     * å¯åŠ¨ä¸€ä¸ªæ•°æŽ§ç¨‹åºçš„å›ºåŒ–æµç¨‹
     * @param prog æ•°æŽ§ç¨‹åºå®žä½“
     */
    void startOne(NcProgram prog){
        Map<String, Object> vars = new HashMap<>();
        NcProgram prog = ncProgramService.getById(programId);
        NcNode progNode = ncNodeService.getById(programId);
        Machine machine = machineService.getByCode(progNode.getMachineCode());
        vars.put("machineCode",machine.getCode());
        //NcProgram prog = programs.get(0);
        Machine machine = machineService.getByCode(prog.getMachineCode());
        vars.put("machineCode",prog.getMachineCode());
        vars.put("machineMode",machine.getName());
        vars.put("processEdition",prog.getProcessEdition());
        vars.put("programId",programId);
        vars.put("program",progNode);
        //该程序在计划任务时 èŽ·å–ç¼–åˆ¶ï¼Œæ ¡å¯¹ï¼Œå®¡æ‰¹çš„äººå‘˜ï¼Œä½œä¸ºé»˜è®¤ç”¨æˆ·ï¼Œå…¶ä¸­ç¼–åˆ¶æ—¶ç¬¬ä¸€ä¸ªå›ºå®šçš„ç”¨æˆ·
        ProcessProgRef preRef = processProgRefService.lastDispatchDataByNcProgramId(prog.getId());
        //获取审批用户
        List<HistoricTaskInstance> historicTasks = historyService.createHistoricTaskInstanceQuery()
            .processInstanceId(preRef.getProcessInstanceId())
            .orderByHistoricTaskInstanceEndTime().desc()
            .list();
        String programmer = null;
        String checker = null;
        String senior = null;
        for (HistoricTaskInstance task : historicTasks) {
            // èŽ·å–ä»»åŠ¡çš„åŠžç†äºº
            if(task.getTaskDefinitionKey().equals("programmingTask")){
                //编制
                programmer = task.getAssignee();
            }else if(task.getTaskDefinitionKey().equals("check")){
                //校对
                checker = task.getAssignee();
            }else if(task.getTaskDefinitionKey().equals("senior")){
                //审核
                senior = task.getAssignee();
            }
        }
        vars.put("programmer",programmer);
        vars.put("checker",checker);
        vars.put("senior",senior);
        String businessKey = "0";//业务表key
        identityService.setAuthenticatedUserId(String.valueOf(AuthUtil.getUserId()));//设置流程发起人
        ProcessInstance pinst = runtimeService.startProcessInstanceByKey(PROCESS_KEY,businessKey,vars);
        int a=1;
        //log.info()
        ProcessProgRef ref = new ProcessProgRef();
        ref.setProcessInstanceId(pinst.getId());
        ref.setNcProgramId(prog.getId());
        processProgRefService.save(ref);
    }
    /**
     * å°†ç¨‹åºæŒ‰ç…§æœºå™¨ä»£ç åˆ†ç»„
     * @param ncProgramIdList
     * @return
     */
    /*
    Map<String,List<NcProgram>> groupProgramsByMachineCode(List<Long> ncProgramIdList){
        List<NcProgram> programs = ncProgramService.listByIds(ncProgramIdList);
        return programs.stream().collect(Collectors.groupingBy(NcProgram::getMachineCode));
    }*/
}
blade-service/blade-mdm/src/main/java/org/springblade/mdm/flow/task/cure/CureFinishOperateTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package org.springblade.mdm.flow.task.cure;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.springblade.mdm.program.entity.NcProgram;
import org.springframework.stereotype.Component;
@Slf4j
@Component("cureFinishOperateTask")
public class CureFinishOperateTask {
    public void execute(DelegateExecution execution) {
        //固化程序检查有效期,
        String hasCuredProgram =  (String)execution.getVariable("hasCuredProgram");
        NcProgram ncProgram =  (NcProgram)execution.getVariable("curedNcProgram");
        System.out.println("执行固化程序任务服务");
    }
}
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/controller/DncSendBackController.java
@@ -33,7 +33,7 @@
@Slf4j
public class DncSendBackController {
    private final DncSendBackService dncSendBackService;
    //private final DncSendBackService dncSendBackService;
    private final NcProgramExchangeService ncProgramExchangeService;
    /**
     * ä¸Šä¼ DNC回传文件
@@ -42,30 +42,29 @@
     */
    @PostMapping("upload")
    @ApiOperationSupport(order = 1)
    @Operation(summary = "DNC回传文件导入,上传文件", description = "上传DNC回传文件")
    public R<Void> dncSendBackUpload(@RequestParam MultipartFile file) {
    @Operation(summary = "DNC回传文件导入,上传文件", description = "上传DNC回传文件,并返回解析结果")
    public R<List<DncSendBackData>> dncSendBackUpload(@RequestParam MultipartFile file) {
        try {
            ncProgramExchangeService.dncSendBackUpload(file);
            return R.data(ncProgramExchangeService.dncSendBackUpload(file));
        }catch (Exception e){
            log.error("导入错误",e);
            return R.fail(e.getMessage());
        }
        return R.success();
    }
    /*
    @Operation(summary = "dnc回传数据分页", description = "dnc回传数据分页")
    @GetMapping("/page")
    public R<IPage<DncSendBackData>> page(Query query) {
        IPage<DncSendBackData> pages = ncProgramExchangeService.dncSendBackPageQuery(query);
        return R.data(pages);
    }
    */
    @PostMapping("accept")
    @ApiOperationSupport(order = 2)
    @Operation(summary = "DNC回传数据入库", description = "入库,同时启动固化流程")
    @Operation(summary = "DNC回传数据入库", description = "入库,同时启动固化流程,入参为上传是解析的数据列表")
    public R<Void> dncSendBackAccept(@RequestParam String ids) {
        try {
            dncSendBackService.dncFileAccept(ids);
            ncProgramExchangeService.dncFileAccept(ids);
        }catch (Exception e){
            log.error("入库错误",e);
            return R.fail(e.getMessage());
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/controller/NcProgramExportDNCController.java
@@ -13,6 +13,7 @@
import org.springblade.core.tool.api.R;
import org.springblade.mdm.commons.vo.IdsVO;
import org.springblade.mdm.program.service.NcProgramApprovedService;
import org.springblade.mdm.program.service.NcProgramExportDNCService;
import org.springblade.mdm.program.service.NcProgramService;
import org.springblade.mdm.program.vo.NcNodeProgramQueryVO;
import org.springblade.mdm.program.vo.NcProgramExportDncPageVO;
@@ -37,6 +38,7 @@
    private final NcProgramService ncProgramService;
    private final NcProgramApprovedService ncProgramApprovedService;
    private final NcProgramExportDNCService ncProgramExportDNCService;
    @Operation(summary = "程序导出DNC分页查询", description = "分页查询,供用户选择数据")
    @GetMapping("/export-dnc-page")
    public R<IPage<NcProgramExportDncPageVO>> page(NcProgramExportDncQueryVO query) {
@@ -51,7 +53,7 @@
            throw new ServiceException("未选择文件导出");
        }
        try {
            ncProgramApprovedService.exportDnc(vo.getIds(),response.getOutputStream());
            ncProgramExportDNCService.exportDnc(vo.getIds(),response.getOutputStream());
        } catch (IOException e) {
            log.error("导出DNC异常", e);
            throw new RuntimeException(e);
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/entity/NcProgramExchange.java
@@ -12,7 +12,17 @@
@Getter
@TableName("mdm_nc_program_exchange")
public class NcProgramExchange extends BizEntity {
    /**
     * ç¨‹åºåç§°
     */
    private String name;
    /**
     * äº¤æ¢ç±»åž‹,1:下发;2:固化(dnc回传)
     */
    private int exchangeType;
    /**
     * æ•°æŽ§ç¨‹åºid
     */
    private Long ncProgramId;
}
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/entity/ProcessProgRef.java
@@ -7,16 +7,19 @@
import java.time.LocalDateTime;
/**
 * æµç¨‹å®žä¾‹ä¸Žç¨‹åºid的关联
 */
@Setter
@Getter
@TableName("mdm_process_prog_ref")
public class ProcessProgRef extends BizEntity {
    /**
     * æ–‡ä»¶å
     * æµç¨‹å®žä¾‹id
     */
    private String processInstanceId;
    /**
     * å¯¹è±¡å­˜å‚¨ä¸­çš„名称
     * æ•°æŽ§ç¨‹åºid
     */
    private Long ncProgramId;
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/DncSendBackService.java
@@ -13,9 +13,9 @@
import java.util.List;
/**
 * å·¥ä½œæµæœåŠ¡å®žçŽ°ç±»
 * DNC回传
 *
 * @author Chill
 * @author yangys
 */
@Slf4j
@Service
@@ -28,30 +28,7 @@
    /**
     * å…¥åº“回传文件,并启动固化流程
     * @param ids
     * @return
     */
    public void dncFileAccept(String ids) {
        List<Long> idList = Func.toLongList(ids);
        NcProgramExchange exchange;
        //NcProgram program;
        NcNode programNode;
        for (Long id : idList) {
            exchange = ncProgramExchangeMapper.selectById(id);
            exchange.setStatus(2);//已入库状态
            programNode = ncNodeService.getLastProgramNode(exchange.getName());
            //如果是已经固化的则不需要启动流程
            //启动固化流程
            if(programNode.getIsCured() !=1) {//未固化的程序,启动固化流程
                cureFlowService.start(programNode.getId());
            }
        }
    }
}
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/NcProgramApprovedService.java
@@ -63,190 +63,7 @@
    }
    /**
     * å¯¼å‡ºdnc
     * @param approvedIdArr å¾…导出审批表id数组
     */
    public void exportDnc(Long[] approvedIdArr, OutputStream os) throws IOException {
        ZipOutputStream zipOut = new ZipOutputStream(os);
        ArrayList<Long> programIdList = new ArrayList<Long>();
        for (Long approvedId : approvedIdArr) {
            NcProgramApproved approved = this.getById(approvedId);
            programIdList.add(approved.getNcProgramId());
            NcProgram prog = progService.getById(approved.getNcProgramId());
            String filename = prog.getOssName();
            InputStream inputStream = ossTemplate.statFileStream(filename);
            addInputStreamToZip(zipOut, inputStream, prog.getName());
        }
        addDataJson(zipOut, programIdList);
    }
    /**
     * å¯¼å…¥æ•°æ®æ–‡ä»¶
     * @param zipOut
     */
    void addDataJson(ZipOutputStream zipOut, List<Long> programIdList) throws IOException {
        addProgramDataJson(zipOut, programIdList);
        addApproveRecordDataJson(zipOut, programIdList);
        addNcNodeDataJson(zipOut, programIdList);
    }
    /**
     * å¯¼å…¥ç¨‹åºè®°å½•
     * @param zipOut
     * @param programIdList
     */
    void addProgramDataJson(ZipOutputStream zipOut, List<Long> programIdList) throws IOException {
        LambdaQueryWrapper<NcProgram> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(NcProgram::getId, programIdList);
        List<NcProgram> programList = progService.list(queryWrapper);
        JSONArray jsonArray = new JSONArray();
        for(NcProgram program : programList){
            JSONObject recObj = new JSONObject();
            recObj.put("id", program.getId());
            recObj.put("code", program.getCode());
            recObj.put("ossName",program.getOssName());
            recObj.put("isTextFile",program.getIsTextFile());
            recObj.put("category",program.getCategory());
            recObj.put("description",program.getDescription());
            recObj.put("name",program.getName());
            recObj.put("partNo",program.getPartNo());
            recObj.put("ncNodeId",program.getNcNodeId());
            recObj.put("url",program.getUrl());
            recObj.put("isCured",program.getIsCured());
            recObj.put("isLocked",program.getIsLocked());
            recObj.put("isTest",program.getIsTest());
            recObj.put("machineCode",program.getMachineCode());
            recObj.put("processEdition",program.getProcessEdition());
            recObj.put("taskAssignTime",program.getTaskAssignTime());
            addSuperProperties(recObj,program);
            jsonArray.add(recObj);
        }
        addInputStreamToZip(zipOut,new ByteArrayInputStream(jsonArray.toJSONString().getBytes(StandardCharsets.UTF_8)),"exp_mdm_nc_program.json");
    }
    /**
     * å¯¼å…¥å®¡æ‰¹è®°å½•
     * @param zipOut
     * @param programIdList
     */
    void addApproveRecordDataJson(ZipOutputStream zipOut, List<Long> programIdList) throws IOException {
        LambdaQueryWrapper<ApproveRecord> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ApproveRecord::getNcProgramId, programIdList);
        List<ApproveRecord> records = approvedService.list(queryWrapper);
        JSONArray jsonArray = new JSONArray();
        for(ApproveRecord record : records){
            JSONObject recObj = new JSONObject();
            recObj.put("id", record.getId());
            recObj.put("comment", record.getComment());
            recObj.put("userId",record.getUserId());
            recObj.put("userNickname",record.getUserNickname());
            recObj.put("operateTime",record.getOperateTime());
            recObj.put("operateResult",record.getOperateResult());
            recObj.put("taskName",record.getTaskName());
            recObj.put("ncProgramId",record.getNcProgramId());
            recObj.put("processInstanceId",record.getProcessInstanceId());
            addSuperProperties(recObj,record);
            jsonArray.add(recObj);
        }
        addInputStreamToZip(zipOut,new ByteArrayInputStream(jsonArray.toJSONString().getBytes(StandardCharsets.UTF_8)),"exp_mdm_approve_record.json");
    }
    /**
     * å¯¼å…¥èŠ‚ç‚¹
     * @param zipOut
     * @param programIdList
     */
    void addNcNodeDataJson(ZipOutputStream zipOut, List<Long> programIdList) throws IOException {
        LambdaQueryWrapper<NcProgram> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(NcProgram::getId, programIdList);
        List<NcProgram> programs = progService.list(queryWrapper);
        JSONArray jsonArray = new JSONArray();
        ArrayList<Long> exportNodeIdList     = new ArrayList<>();
        for(NcProgram program : programs){
            JSONObject recObj = new JSONObject();
            if(!exportNodeIdList.contains(program.getNcNodeId())){
                exportNodeIdList.add(program.getNcNodeId());
            }
            NcNode ncNode = ncNodeService.getById(program.getNcNodeId());
            if(StringUtils.isNotEmpty(ncNode.getParentIds())){
                List<Long> pids = Func.toLongList(ncNode.getParentIds());
                for(Long nodeId : pids){
                    if(!exportNodeIdList.contains(nodeId)){
                        exportNodeIdList.add(nodeId);
                    }
                }
            }
        }
        LambdaQueryWrapper<NcNode> nodeQueryWrapper = new LambdaQueryWrapper<>();
        nodeQueryWrapper.in(NcNode::getId, exportNodeIdList);
        List<NcNode> nodeList =ncNodeService.list(nodeQueryWrapper);
        for(NcNode node : nodeList){
            JSONObject recObj = new JSONObject();
            recObj.put("id", node.getId());
            recObj.put("nodeType", node.getNodeType());
            recObj.put("machineCode",node.getMachineCode());
            recObj.put("parentId",node.getParentId());
            recObj.put("description",node.getDescription());
            recObj.put("name",node.getName());
            recObj.put("remark",node.getRemark());
            recObj.put("partNo",node.getPartNo());
            recObj.put("parentIds",node.getParentIds());
            recObj.put("processName",node.getProcessName());
            addSuperProperties(recObj,node);
            jsonArray.add(recObj);
        }
        addInputStreamToZip(zipOut,new ByteArrayInputStream(jsonArray.toJSONString().getBytes(StandardCharsets.UTF_8)),"exp_mdm_nc_node.json");
    }
    void addSuperProperties(JSONObject recObj, BizEntity entity){
        recObj.put("tenantId",entity.getTenantId());
        recObj.put("createTime",entity.getCreateTime());
        recObj.put("updateTime",entity.getUpdateTime());
        recObj.put("createUser",entity.getCreateUser());
        recObj.put("updateUser",entity.getUpdateUser());
        recObj.put("status",entity.getStatus());
        recObj.put("createDept",entity.getCreateDept());
    }
    public void addInputStreamToZip(ZipOutputStream zipOut, InputStream inputStream, String entryName)
        throws IOException {
        // åˆ›å»ºæ–°çš„ ZIP æ¡ç›®
        ZipEntry zipEntry = new ZipEntry(entryName);
        zipOut.putNextEntry(zipEntry);
        // å°†è¾“入流写入 ZIP è¾“出流
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) >= 0) {
            zipOut.write(buffer, 0, length);
        }
        // å…³é—­å½“前条目
        zipOut.closeEntry();
    }
}
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/NcProgramExchangeService.java
@@ -1,16 +1,25 @@
package org.springblade.mdm.program.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
import org.apache.commons.io.IOUtils;
import org.springblade.core.mp.base.BizServiceImpl;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.utils.FileUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.IoUtil;
import org.springblade.mdm.flow.service.CureFlowService;
import org.springblade.mdm.program.entity.NcProgram;
import org.springblade.mdm.program.entity.NcProgramExchange;
import org.springblade.mdm.program.mapper.NcProgramExchangeMapper;
import org.springblade.mdm.program.vo.DncSendBackData;
@@ -23,10 +32,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.time.LocalDateTime;
import java.util.*;
/**
 * ç¨‹åºäº¤æ¢ï¼ˆdnc导入/导出)
@@ -37,30 +44,26 @@
@Service
@AllArgsConstructor
public class NcProgramExchangeService extends BizServiceImpl<NcProgramExchangeMapper, NcProgramExchange> {
    private final CureFlowService cureFlowService;
    private final NcProgramService ncProgramService;
    /**
     * dnc回传文件上传
     * @param file æ–‡ä»¶
     * dnc回传文件上传(解析后保存入upload表)
     * @param file DNC回传文件
     * @return
     */
    public List<DncSendBackData> dncSendBackUpload(MultipartFile file) {
        List<DncSendBackData> list ;
        List<DncSendBackData> list;
        try {
            String fileName = file.getOriginalFilename();
            //InputStream fileInputStream = file.getInputStream();
            InputStream zipFileInputStream = FileExchangeUtil.convertFileToZip(file.getInputStream());
            //String fileName = file.getOriginalFilename();
            //InputStream zipFileInputStream = FileExchangeUtil.convertFileToZip(file.getInputStream());
            InputStream zipFileInputStream = file.getInputStream();//test
            byte[] bytes = FileUtil.copyToByteArray(zipFileInputStream);
            list = parseDncZipFromByteArray(bytes);
            for(DncSendBackData dncSendBackData:list){
                NcProgramExchange exchange=new NcProgramExchange();
                exchange.setName(dncSendBackData.getProgramName());
                exchange.setStatus(1);//已导入
                this.save(exchange);
            }
        } catch (IOException e) {
            log.error("上传dnc文件失败",e);
            log.error("上传dnc回传文件失败",e);
            list = Collections.emptyList();
        }
        return list;
@@ -92,90 +95,93 @@
        return tempFile.toFile();
    }
    public static List<DncSendBackData> parseDncZipFromByteArray(byte[] zipData) throws IOException {
        //List<DncSendBackData> datas = new ArrayList<>();
        List<DncSendBackData> datas  = ZipFileDirectoryScanner.getFilesInDirectoryRecursive(zipData, "");
        List<DncSendBackData> list = new ArrayList<>();
        //List<DncSendBackData> datas  = ZipFileDirectoryScanner.getFilesInDirectoryRecursive(zipData, "");
        /*
        try (ByteArrayInputStream bis = new ByteArrayInputStream(zipData);
             ZipArchiveInputStream zis = new ZipArchiveInputStream(bis)) {
        Map<String,String> fileMd5Map = new HashMap<>();
        Map<String,DncSendBackData> fileDataMap = new HashMap<>();
        try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(zipData);
             ZipFile zipFile = new ZipFile(channel)) {
            ZipArchiveEntry entry;
            while ((entry = zis.getNextZipEntry()) != null) {
            Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
            while (entries.hasMoreElements()) {
            //while ((entry = zis.getNextZipEntry()) != null) {
                entry = entries.nextElement();
                DncSendBackData prog = new DncSendBackData();
                prog.setProgramName(entry.getName());
                String entryName = entry.getName();
                if (!entry.isDirectory()) {
                    //直接解析程序的json文件
                    if(entryName.equals(NcProgramExportDNCService.PROGRAM_JSON_FILE)){
                        try (InputStream inputStream = zipFile.getInputStream(entry)) {
                            String jsonStr = IoUtil.readToString(inputStream);
                            JSONArray jsonArray = JSONArray.parseArray(jsonStr);
                            for(int i=0;i<jsonArray.size();i++){
                                JSONObject jsonObject = jsonArray.getJSONObject(i);
                                DncSendBackData d = new DncSendBackData();
                                d.setProgramName(jsonObject.getString("name"));
                                d.setId(jsonObject.getLong("id"));
                                d.setProgramNo(jsonObject.getString("code"));
                                d.setFileBackTime(LocalDateTime.now());//到达时间
                                fileDataMap.put(d.getProgramName(),d);
                                list.add(d);
                            }
                        }
                    }else{
                        try (InputStream inputStream = zipFile.getInputStream(entry)) {
                            fileMd5Map.put(entryName,DigestUtils.md5Hex(inputStream));//获取文件MD5
                        }
                    }
                    System.out.println("文件名: " + entry.getName());
                    System.out.println("大小: " + entry.getSize());
                    // è¯»å–文件内容到字节数组
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    IOUtils.copy(zis, outputStream);
                    byte[] fileContent = outputStream.toByteArray();
                    // å¤„理文件内容...
                    System.out.println("内容长度: " + fileContent.length);
                }else{
                    //文件夹,读内部文件,获取文件列表
                    System.out.println("文件夹程序:"+entry.getName());
                    List<String> children = new ArrayList<>();
                    prog.setChildren(children);
                }
                datas.add(prog);
            }
        }*/
        return datas;
        }
        //设置md5值
        fileDataMap.forEach((k,v)->{
            if(fileMd5Map.containsKey(k)){
                v.setMd5(fileMd5Map.get(k));
            }
        });
        return list;
    }
    /**
     * ç¨‹åºä¸‹å‘统计分页查询
     * @param query æŸ¥è¯¢å‚æ•°
     * å…¥åº“回传文件,并启动固化流程
     * @param ids id列表逗号分隔
     * @return
     */
    public void dncFileAccept(String ids) {
        List<Long> idList = Func.toLongList(ids);
        List<NcProgram> progList = ncProgramService.listByIds(idList);
        NcProgramExchange exchange;
        //NcProgram program;
        //NcNode programNode;
    public IPage<DncSendBackData> dncSendBackPageQuery(Query query) {
        for(NcProgram prog:progList){
            exchange = new NcProgramExchange();
            exchange.setName(prog.getName());
            exchange.setExchangeType(2);//回传
            exchange.setNcProgramId(prog.getId());
        IPage<DncSendBackData> page = this.getBaseMapper().dncSendBackpageQuery(Condition.getPage(query),query);
            this.save(exchange);
        return page;
    }
}
class ZipFileDirectoryScanner {
    public static List<DncSendBackData> getFilesInDirectoryRecursive(byte[] zipData, String dirPath) throws IOException {
        List<DncSendBackData> list = new ArrayList<>();
        if (!dirPath.endsWith("/")) {
            dirPath += "/";
        }
        try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(zipData);
             ZipFile zipFile = new ZipFile(channel)) {
        cureFlowService.startCure(progList);
            Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
            while (entries.hasMoreElements()) {
                ZipArchiveEntry entry = entries.nextElement();
                String entryName = entry.getName();
                DncSendBackData d = new DncSendBackData();
                d.setProgramName(entryName);
                if(entry.isDirectory()){
                    d.setHasChildren(true);
                }
                list.add(d);
                //if (entryName.startsWith(dirPath) && !entry.isDirectory()) {
                //    fileList.add(entryName);
                //}
            }
        }
        return list;
    }
}
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/NcProgramExportDNCService.java
@@ -1,27 +1,41 @@
package org.springblade.mdm.program.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
import org.apache.commons.lang3.StringUtils;
import org.springblade.core.mp.base.BizEntity;
import org.springblade.core.mp.base.BizServiceImpl;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.oss.OssTemplate;
import org.springblade.core.tool.utils.FileUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.mdm.flow.entity.ApproveRecord;
import org.springblade.mdm.flow.service.ApproveRecordService;
import org.springblade.mdm.program.entity.NcNode;
import org.springblade.mdm.program.entity.NcProgram;
import org.springblade.mdm.program.entity.NcProgramApproved;
import org.springblade.mdm.program.entity.NcProgramExchange;
import org.springblade.mdm.program.mapper.NcProgramExchangeMapper;
import org.springblade.mdm.program.vo.DncSendBackData;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
 * ä¸‹å‘/回传程序统计
@@ -32,8 +46,13 @@
@Service
@AllArgsConstructor
public class NcProgramExportDNCService extends BizServiceImpl<NcProgramExchangeMapper, NcProgramExchange> {
    private final NcProgramService progService;
    private final NcProgramApprovedService approvedService;
    private final ApproveRecordService approveRecordService;
    private final NcNodeService ncNodeService;
    private final OssTemplate ossTemplate;
    public static final String PROGRAM_JSON_FILE = "exp_mdm_nc_program.json";
    /**
     * åˆ†é¡µæŸ¥è¯¢
     * @param query æŸ¥è¯¢å‚æ•°
@@ -46,5 +65,195 @@
        return page;
    }
    /**
     * å¯¼å‡ºdnc压缩包
     * @param approvedIdArr å¾…导出审批表id数组
     */
    public void exportDnc(Long[] approvedIdArr, OutputStream os) throws IOException {
        //FileOutputStream fos = new FileOutputStream("d:/exportDnc.zip");
        try (ZipOutputStream zipOut = new ZipOutputStream(os);) {//os
            ArrayList<Long> programIdList = new ArrayList<Long>();
            for (Long approvedId : approvedIdArr) {
                NcProgramApproved approved = approvedService.getById(approvedId);
                programIdList.add(approved.getNcProgramId());
                NcProgram prog = progService.getById(approved.getNcProgramId());
                String filename = prog.getOssName();
                InputStream inputStream = ossTemplate.statFileStream(filename);
                addInputStreamToZip(zipOut, inputStream, prog.getName());
            }
            addDataJson(zipOut, programIdList);
        }
    }
    /**
     * å¯¼å…¥æ•°æ®æ–‡ä»¶
     * @param zipOut
     */
    void addDataJson(ZipOutputStream zipOut, List<Long> programIdList) throws IOException {
        addProgramDataJson(zipOut, programIdList);
        addApproveRecordDataJson(zipOut, programIdList);
        addNcNodeDataJson(zipOut, programIdList);
    }
    /**
     * å¯¼å…¥ç¨‹åºè®°å½•
     * @param zipOut
     * @param programIdList
     */
    void addProgramDataJson(ZipOutputStream zipOut, List<Long> programIdList) throws IOException {
        LambdaQueryWrapper<NcProgram> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(NcProgram::getId, programIdList);
        List<NcProgram> programList = progService.list(queryWrapper);
        JSONArray jsonArray = new JSONArray();
        for(NcProgram program : programList){
            JSONObject recObj = new JSONObject();
            recObj.put("id", program.getId());
            recObj.put("code", program.getCode());
            recObj.put("ossName",program.getOssName());
            recObj.put("isTextFile",program.getIsTextFile());
            recObj.put("category",program.getCategory());
            recObj.put("description",program.getDescription());
            recObj.put("name",program.getName());
            recObj.put("partNo",program.getPartNo());
            recObj.put("ncNodeId",program.getNcNodeId());
            recObj.put("url",program.getUrl());
            recObj.put("isCured",program.getIsCured());
            recObj.put("isLocked",program.getIsLocked());
            recObj.put("isTest",program.getIsTest());
            recObj.put("machineCode",program.getMachineCode());
            recObj.put("processEdition",program.getProcessEdition());
            recObj.put("taskAssignTime",program.getTaskAssignTime());
            addSuperProperties(recObj,program);
            jsonArray.add(recObj);
        }
        addInputStreamToZip(zipOut,new ByteArrayInputStream(jsonArray.toJSONString().getBytes(StandardCharsets.UTF_8)),PROGRAM_JSON_FILE);
    }
    /**
     * å¯¼å…¥å®¡æ‰¹è®°å½•
     * @param zipOut
     * @param programIdList
     */
    void addApproveRecordDataJson(ZipOutputStream zipOut, List<Long> programIdList) throws IOException {
        LambdaQueryWrapper<ApproveRecord> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ApproveRecord::getNcProgramId, programIdList);
        List<ApproveRecord> records = approveRecordService.list(queryWrapper);
        JSONArray jsonArray = new JSONArray();
        for(ApproveRecord record : records){
            JSONObject recObj = new JSONObject();
            recObj.put("id", record.getId());
            recObj.put("comment", record.getComment());
            recObj.put("userId",record.getUserId());
            recObj.put("userNickname",record.getUserNickname());
            recObj.put("operateTime",record.getOperateTime());
            recObj.put("operateResult",record.getOperateResult());
            recObj.put("taskName",record.getTaskName());
            recObj.put("ncProgramId",record.getNcProgramId());
            recObj.put("processInstanceId",record.getProcessInstanceId());
            addSuperProperties(recObj,record);
            jsonArray.add(recObj);
        }
        addInputStreamToZip(zipOut,new ByteArrayInputStream(jsonArray.toJSONString().getBytes(StandardCharsets.UTF_8)),"exp_mdm_approve_record.json");
    }
    /**
     * å¯¼å…¥èŠ‚ç‚¹
     * @param zipOut
     * @param programIdList
     */
    void addNcNodeDataJson(ZipOutputStream zipOut, List<Long> programIdList) throws IOException {
        LambdaQueryWrapper<NcProgram> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(NcProgram::getId, programIdList);
        List<NcProgram> programs = progService.list(queryWrapper);
        JSONArray jsonArray = new JSONArray();
        ArrayList<Long> exportNodeIdList     = new ArrayList<>();
        for(NcProgram program : programs){
            JSONObject recObj = new JSONObject();
            if(!exportNodeIdList.contains(program.getNcNodeId())){
                exportNodeIdList.add(program.getNcNodeId());
            }
            NcNode ncNode = ncNodeService.getById(program.getNcNodeId());
            if(StringUtils.isNotEmpty(ncNode.getParentIds())){
                List<Long> pids = Func.toLongList(ncNode.getParentIds());
                for(Long nodeId : pids){
                    if(!exportNodeIdList.contains(nodeId)){
                        exportNodeIdList.add(nodeId);
                    }
                }
            }
        }
        LambdaQueryWrapper<NcNode> nodeQueryWrapper = new LambdaQueryWrapper<>();
        nodeQueryWrapper.in(NcNode::getId, exportNodeIdList);
        List<NcNode> nodeList =ncNodeService.list(nodeQueryWrapper);
        for(NcNode node : nodeList){
            JSONObject recObj = new JSONObject();
            recObj.put("id", node.getId());
            recObj.put("nodeType", node.getNodeType());
            recObj.put("machineCode",node.getMachineCode());
            recObj.put("parentId",node.getParentId());
            recObj.put("description",node.getDescription());
            recObj.put("name",node.getName());
            recObj.put("remark",node.getRemark());
            recObj.put("partNo",node.getPartNo());
            recObj.put("parentIds",node.getParentIds());
            recObj.put("processName",node.getProcessName());
            addSuperProperties(recObj,node);
            jsonArray.add(recObj);
        }
        addInputStreamToZip(zipOut,new ByteArrayInputStream(jsonArray.toJSONString().getBytes(StandardCharsets.UTF_8)),"exp_mdm_nc_node.json");
    }
    void addSuperProperties(JSONObject recObj, BizEntity entity){
        recObj.put("tenantId",entity.getTenantId());
        recObj.put("createTime",entity.getCreateTime());
        recObj.put("updateTime",entity.getUpdateTime());
        recObj.put("createUser",entity.getCreateUser());
        recObj.put("updateUser",entity.getUpdateUser());
        recObj.put("status",entity.getStatus());
        recObj.put("createDept",entity.getCreateDept());
    }
    public void addInputStreamToZip(ZipOutputStream zipOut, InputStream inputStream, String entryName)
        throws IOException {
        // åˆ›å»ºæ–°çš„ ZIP æ¡ç›®
        ZipEntry zipEntry = new ZipEntry(entryName);
        zipOut.putNextEntry(zipEntry);
        // å°†è¾“入流写入 ZIP è¾“出流
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) >= 0) {
            zipOut.write(buffer, 0, length);
        }
        // å…³é—­å½“前条目
        zipOut.closeEntry();
    }
}
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/service/ProcessProgRefService.java
@@ -25,6 +25,7 @@
 */
package org.springblade.mdm.program.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
@@ -94,11 +95,23 @@
    }
    /**
     * æŸ¥è¯¢æµç¨‹å…³è”的程序i二标
     * æŸ¥è¯¢æµç¨‹å…³è”的程序
     * @param processInstanceId
     * @return
     */
    public List<NcProgramVO> listByProcess(String processInstanceId) {
        return this.getBaseMapper().listByProcess(processInstanceId);
    }
    /**
     *获取最后一调任务分派的数据,根据程序id
     * @param ncProgramId æ•°æŽ§ç¨‹åºid
     */
    public ProcessProgRef lastDispatchDataByNcProgramId(Long ncProgramId) {
        LambdaQueryWrapper<ProcessProgRef> query = Wrappers.lambdaQuery();
        query.eq(ProcessProgRef::getNcProgramId, ncProgramId).orderByDesc(ProcessProgRef::getCreateTime).last("LIMIT 10");;
        List<ProcessProgRef> list = this.list(query);
        return list.isEmpty() ? null : list.get(0);
    }
}
blade-service/blade-mdm/src/main/java/org/springblade/mdm/program/vo/DncSendBackData.java
@@ -11,8 +11,9 @@
@Getter
@Schema(description = "DNC回传数据")
public class DncSendBackData {
    @Schema(description = "文件数据库编号")
    @Schema(description = "文件数据库编号(数控程序的id)")
    private Long id;
    @Schema(description = "程序编号")
    private String programNo;
    @Schema(description = "程序名称")
blade-service/blade-mdm/src/main/resources/processesbpmn/program-cure.bpmn20.xml
@@ -2,18 +2,18 @@
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
  <process id="program-cure" name="固化流程" isExecutable="true">
    <startEvent id="sid-abe970b9-1bee-49a1-91b4-1184c47c10b7"/>
    <userTask id="cureProgramTask" name="固化编制"/>
    <userTask id="cureProgramTask" name="固化编制" flowable:assignee="${programmer}"/>
    <sequenceFlow id="sid-910649a5-8dac-48a2-b42d-9f1132d61b26" sourceRef="cureProgramTask" targetRef="cureProgramTask"/>
    <sequenceFlow id="sid-09c7cf44-bb1a-40f4-b231-919afae5c02f" sourceRef="sid-abe970b9-1bee-49a1-91b4-1184c47c10b7" targetRef="cureProgramTask"/>
    <endEvent id="end" name="固化结束"/>
    <userTask id="seniorApproveTask" name="高师审核"/>
    <userTask id="seniorApproveTask" name="高师审核" flowable:assignee="${assignee}"/>
    <sequenceFlow id="sid-8dd21f02-ac10-4318-b897-19b4cdc558c7" sourceRef="cureProgramTask" targetRef="cureCheckTask">
      <conditionExpression>${approve=='Y'}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-504610fe-2b87-4df4-8f42-f10c8bf3ce01" sourceRef="seniorApproveTask" targetRef="cureOKOperateTask">
      <conditionExpression>${approve=='Y'</conditionExpression>
    </sequenceFlow>
    <userTask id="cureCheckTask" name="固化校对"/>
    <userTask id="cureCheckTask" name="固化校对" flowable:assignee="${assignee}"/>
    <sequenceFlow id="sid-b4ad2b36-5fcc-4449-924b-55b0425b1278" sourceRef="cureCheckTask" targetRef="cureProgramTask">
      <conditionExpression>${approve=='N'</conditionExpression>
    </sequenceFlow>
@@ -27,7 +27,7 @@
    <sequenceFlow id="sid-c04cc547-0baf-4514-8056-54b1612cdefd" sourceRef="seniorApproveTask" targetRef="cureCheckTask">
      <conditionExpression>${approve=='N'}</conditionExpression>
    </sequenceFlow>
    <serviceTask id="cureOKOperateTask" flowable:exclusive="true" name="固化处理任务">
    <serviceTask id="cureOKOperateTask" flowable:exclusive="true" name="固化处理任务" flowable:expression="cureFinishOperateTask.execute(execution)">
      <documentation>设置数据的固化标志,生成固化程序列表</documentation>
    </serviceTask>
    <sequenceFlow id="sid-253f5be1-3275-47d6-a3a1-5d1eaf01ec79" sourceRef="cureOKOperateTask" targetRef="end"/>
doc/sql/mdm/mdm.mysql.all.create.sql
@@ -186,6 +186,7 @@
  `id` bigint NOT NULL,
  `tenant_id` varchar(6) DEFAULT NULL COMMENT '所属租户',
  `name` varchar(100) NOT NULL COMMENT '程序名称',
  `nc_program_id` bigint DEFAULT NULL COMMENT '程序文件id',
  `status` int DEFAULT NULL COMMENT '业务状态',
  `exchange_type` int DEFAULT NULL COMMENT '交换类型,1:下发;2:固化(dnc回传)',
  `create_dept` bigint DEFAULT NULL COMMENT '创建单位',