ludc
2024-02-24 156e136377680ac2dd5ad89735b7273db6f6d1d5
远程部署功能完善
已修改23个文件
已添加2个文件
1241 ■■■■■ 文件已修改
Source/UBCS-WEB/src/api/GetItem.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS-WEB/src/api/system/deploy.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS-WEB/src/components/Theme/ThemeImport.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS-WEB/src/components/service-deploy/uploadServiceJarDialog.vue 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS-WEB/src/const/code/codeSynonym.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS-WEB/src/main.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS-WEB/src/views/system/PasswordManagement/passwords.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS-WEB/src/views/system/deploy.vue 272 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/pagemodel/CodeImportResultVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service-api/ubcs-deploy-api/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service-api/ubcs-deploy-api/src/main/java/com/vci/ubcs/deploy/entity/CmdConfig.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service-api/ubcs-deploy-api/src/main/java/com/vci/ubcs/deploy/entity/DeployApps.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service-api/ubcs-deploy-api/src/main/java/com/vci/ubcs/deploy/enumpack/CmdConfigEnum.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service-api/ubcs-deploy-api/src/main/java/com/vci/ubcs/deploy/vo/DeployAppsVO.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service-api/ubcs-util-api/src/main/java/com/vci/ubcs/starter/util/HttpUtils.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-code/pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/DockingManagementController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/MdmEngineService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/impl/CodeDuckingSyncServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/impl/MdmEngineServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/impl/MdmIOServiceImpl.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-code/src/test/java/util.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-deploy/src/main/java/com/vci/ubcs/deploy/controller/DeployAppsController.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-deploy/src/main/java/com/vci/ubcs/deploy/service/IDeployAppsService.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-deploy/src/main/java/com/vci/ubcs/deploy/service/impl/DeployAppsServiceImpl.java 314 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS-WEB/src/api/GetItem.js
@@ -138,10 +138,10 @@
}
//手动同步
export const syncSearch = (data) => {
export const syncSearch = (params) => {
  return request({
    url: '/api/ubcs-code/dockingManagement/searchItemDataForERP',
    method: 'post',
    data
    params
  })
}
Source/UBCS-WEB/src/api/system/deploy.js
@@ -2,10 +2,39 @@
export const getApplications = () => {
  return request({
    // headers: {'Accept': 'application/json'},
    // url: '/api/ubcs-admin/applications',
    url: '/api/ubcs-deploy/deploy/applications',
    method: 'get',
  })
}
export const saveOrGetServiceConfInfo = (row) => {
  return request({
    url: '/api/ubcs-deploy/deploy/saveOrGetServiceConfInfo',
    method: 'post',
    data: row
  })
}
export const saveOrUpdateServiceInfo = (row) => {
  return request({
    url: '/api/ubcs-deploy/deploy/saveOrUpdateServiceInfo',
    method: 'post',
    data: row
  })
}
export const addSave = (row) => {
  return request({
    url: '/api/ubcs-deploy/deploy/addSave',
    method: 'post',
    data: row
  })
}
export const executeCmd = (row) => {
  return request({
    url: '/api/ubcs-deploy/deploy/cmdExecute',
    method: 'post',
    data: row
  })
}
Source/UBCS-WEB/src/components/Theme/ThemeImport.vue
@@ -68,7 +68,7 @@
  watch:{
    visible:{
      handler(newval,oldval){
        console.log('newval',newval)
        // console.log('newval',newval)
      }
    }
  },
Source/UBCS-WEB/src/components/service-deploy/uploadServiceJarDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,154 @@
<template>
    <el-dialog
      title="服务更新jar包导入"
      :visible.sync="dialogVisible"
      append-to-body
    >
      <Divider text="更新提示" left="30px"></Divider>
      <ul>
        <li>
          1.上传jar文件时请确定jar文件名称正确,打好的jar包,<span style="font-weight: bold;color: red;">请勿随便更改jar文件名和文件后缀名</span>。
        </li>
        <li>
          2.上传多个文件时请压缩成<span style="font-weight: bold;color: red;">.zip</span>再上传。
        </li>
        <li>
          3.上传成功之后会自动重启指定服务,并且该界面中会暂时无法查询到该服务信息,请稍等1~2分钟再查看验证该服务是否更新成功。
        </li>
      </ul>
      <Divider text="文件,选择文件后会自动上传" left="30px"></Divider>
      <el-upload
        ref="uploadFiles"
        action="/api/ubcs-deploy/deploy/importUpdateServiceJar"
        :on-success="onSuccess"
        :multiple="true"
        :on-error="onError"
        :headers="uploadHeaders"
        :show-file-list="false"
        :before-upload="beforeUpload"
        :on-change="uploadChange"
        :directory="true"
        :data="{'serverName':serverName}"
        name="files"
        class="upload-demo">
        <el-button size="small" type="primary" style="margin: 15px 10px 15px 35px" @click="handleAddFolder('file')">浏览文件</el-button>
        <!-- <el-button size="small" type="primary" @click="handleAddFolder('dir')">浏览文件夹</el-button> -->
      </el-upload>
      <template #footer>
        <el-button size="small" @click="dialogVisible = false">关闭</el-button>
      </template>
    </el-dialog>
  </template>
<script>
import {downloadErrorFile} from '@/api/template/templateAttr'
import {getToken} from "@/util/auth";
import func from "@/util/func";
export default {
  name: "uploadServiceJarDialog",
  props: {
      visible: {
        type: Boolean,
        default: false,
      },
      serverName: {
        type: String,
        default: false,
      },
  },
  data(){
      return{
      flga:true,
      pageLoading: null,
      downloadLoading: false,
      }
  },
  watch:{
      visible:{
        handler(newval,oldval){
            // console.log('newval',newval)
        }
      }
  },
  computed:{
      uploadHeaders() {
        return {
            "Blade-Auth": "bearer " + getToken(),
        };
      },
      dialogVisible: {
        get() {
            return this.visible;
        },
        set(val) {
            this.$emit("update:visible", val);
        },
      },
  },
  methods:{
      //文件上传前
      async beforeUpload(file) {
        // console.log(file);
        // console.log(this.serviceName);
        this.pageLoading = this.$loading({
            lock: true,
            text: "文件上传中",
            spinner: "el-icon-loading",
            background: "rgba(0, 0, 0, 0.7)",
        });
        return true;
      },
      // æ–‡ä»¶ä¸Šä¼ æˆåŠŸ
      onSuccess(resbonse){
        if (Object.keys(resbonse.data).length === 0) {
            this.$message.success( "主题库分类导入成功!");
            this.dialogVisible = false;
            return;
        }
        if (resbonse.data.fileOid) {
            const fileName = resbonse.data.filePath.split("/").pop();
            this.$message.error("请下载错误信息文件进行查看!");
            downloadErrorFile({ uuid: resbonse.data.fileOid }).then((res) => {
            func.downloadFileByBlobHandler(res);
            });
        }
      },
      //文件上传失败
      onError(res){
        this.pageLoading.close();
      },
      //文件状态改变
      uploadChange(file){
        if (file.status === "success" || file.status === "error") {
            this.pageLoading.close();
        }
      },
      // ç‚¹å‡»æ–‡ä»¶å¤¹è·¯å¾„上传按钮
      // handleAddFolder (type) {
      //   if(type==='file'){
      //     this.$nextTick(() => {
      //       this.$refs.uploadFiles.$children[0].$refs.input.webkitdirectory = false;
      //     })
      //   }else{
      //     this.$nextTick(() => {
      //       this.$refs.uploadFiles.$children[0].$refs.input.webkitdirectory = true;
      //     })
      //   }
      // },
  }
}
</script>
<style scoped lang="scss">
ul {
  color: rgb(188, 188, 188);
  margin: 20px 0 20px 0;
  padding: 0 0 0 30px;
  list-style: none;
  li {
    margin-bottom: 5px;
    font-size: 13px;
  }
}
</style>
Source/UBCS-WEB/src/const/code/codeSynonym.js
@@ -21,6 +21,11 @@
      search: true,
      searchLabelWidth: 45,
      searchSpan: 4,
      rules: [{
        required: true,
        message: "请输入编号",
        trigger: "blur",
      }]
    },
    {
      label: "名称",
@@ -29,6 +34,11 @@
      search: true,
      searchLabelWidth: 45,
      searchSpan: 4,
      // rules: [{
      //   required: true,
      //   message: "名称",
      //   trigger: "blur",
      // }]
    },
    {
      label: "源值",
@@ -37,6 +47,11 @@
      search: true,
      searchLabelWidth: 45,
      searchSpan: 4,
      rules: [{
        required: true,
        message: "请输入源值",
        trigger: "blur",
      }]
    },
    {
      label: "近义词",
@@ -45,6 +60,11 @@
      search: true,
      searchLabelWidth: 60,
      searchSpan: 4,
      rules: [{
        required: true,
        message: "请输入近义词",
        trigger: "blur",
      }]
    },
    {
      label: "状态",
Source/UBCS-WEB/src/main.js
@@ -60,6 +60,7 @@
import businesswork from "@/components/work/BusinessWork"
import MasterTransfer from "@/components/Master/MasterTransfer";
import ThemeImport from "@/components/Theme/ThemeImport";
import uploadServiceJarDialog from "@/components/service-deploy/uploadServiceJarDialog";
import maxSerialnumDialog from '@/components/code-dialog-page/maxSerialnumDialog'
import VciDockingSearch from '@/components/dockingSearch/VciDockingSearch'
// å°†å·¥å…·å‡½æ•°æ·»åŠ åˆ°å…¨å±€
@@ -128,6 +129,7 @@
Vue.component('ThemeImport', ThemeImport)
Vue.component('maxSerialnumDialog', maxSerialnumDialog)
Vue.component('VciDockingSearch', VciDockingSearch)
Vue.component('uploadServiceJarDialog', uploadServiceJarDialog)
// åŠ è½½ç›¸å…³url地址
Object.keys(urls).forEach(key => {
Source/UBCS-WEB/src/views/system/PasswordManagement/passwords.vue
@@ -37,7 +37,6 @@
  getadd,
  getupdata,
  getremove,
  combination
} from "@/api/system/passwords";
import {mapGetters} from "vuex";
Source/UBCS-WEB/src/views/system/deploy.vue
@@ -1,28 +1,40 @@
<template>
    <basic-container>
        <avue-crud :option="option"
                :table-loading="loading"
                :data="data"
                :page.sync="page"
                :permission="permissionList"
                :before-open="beforeOpen"
                v-model="form"
                ref="crud"
                :cell-style="cellStyle"
                :row-style="rowStyle"
                @on-load="onLoad">
            <template slot-scope="scope" slot="menu">
            :table-loading="loading"
            :data="data"
            :page.sync="page"
            :permission="permissionList"
            :before-open="beforeOpen"
            v-model="form"
            ref="crud"
            :cell-style="cellStyle"
            :row-style="rowStyle"
            @row-update="rowUpdate"
            @row-save="addServiceInfo"
            @on-load="onLoad">
            <template slot="menu" slot-scope="{ row, index }">
                <el-button type="text"
                        icon="el-icon-upload"
                        size="small"
                        v-if="permissionList.upload"
                        @click="handleDownload(scope.row)">上 ä¼ 
                    icon="el-icon-upload"
                    size="small"
                    v-if="permissionList.upload"
                    @click.native="importServiceJar(row)">上 ä¼ 
                </el-button>
                <el-button type="text"
                        icon="el-icon-refresh"
                        size="small"
                        v-if="permissionList.restart"
                        @click="test">重 å¯
                    :disabled="row.name === 'ubcs-deploy'"
                    :icon="row.status === 'UP' ? 'el-icon-refresh':'el-icon-video-play'"
                    size="small"
                    v-if="permissionList.restart"
                    @click="cmdExecute(row)">
                    {{ row.status === 'UP' ?  '重 å¯': '启 åЍ' }}
                </el-button>
                <el-button type="text"
                    v-if="permissionList.editBtn"
                    icon="el-icon-edit"
                    size="small"
                    @click="handleEdit(row, index)"
                    >
                    ç¼–辑
                </el-button>
            </template>
            <template slot-scope="{row}"
@@ -34,56 +46,21 @@
                <el-tag>{{`${row.serviceNum}`}}</el-tag>
            </template>
        </avue-crud>
        <uploadServiceJarDialog :serverName="serviceName" :visible.sync="uploadDialogVisible"></uploadServiceJarDialog>
    </basic-container>
</template>
<script>
import { getApplications } from "@/api/system/deploy";
import moment from 'moment';
import { getApplications,saveOrGetServiceConfInfo,saveOrUpdateServiceInfo,addSave,executeCmd } from "@/api/system/deploy";
import moment from "moment";
export default {
    data() {
        return {
            option: {
                columnBtn:false,
                height: 'auto',
                calcHeight: 30,
                tip: false,
                searchShow: false,
                border: true,
                index: true,
                viewBtn: false,
                addBtn: false,
                editBtn: false,
                delBtn: false,
                selection: true,
                refreshBtn: false,
                dialogClickModal: false,
                column: [
                    {
                        label: "实例名称",
                        prop: "name",
                        sortable: true,
                    },
                    {
                        label: "运行状态",
                        prop: "status",
                    },
                    {
                        label: "启动时间",
                        prop: "statusTimestamp",
                    },
                    {
                        label: "端口号",
                        prop: "port",
                    },
                    {
                        label: "实例数量",
                        prop: "serviceNum",
                        width: 120,
                    },
                ]
            },
            data: [],
            uploadDialogVisible: false,
            loading: true,
            isDisabled: true,
            serviceName: "",
        }
    },
    created() {
@@ -94,16 +71,124 @@
            return {
                upload: true,
                restart: true,
                // viewBtn: this.vaildData(this.permission, true),
                editBtn: true,// this.vaildData(this.permission, true),
                addBtn: true,
            };
        },
        option(){
            return {
                columnBtn:false,
                height: 'auto',
                calcHeight: 30,
                tip: false,
                searchShow: false,
                border: true,
                index: true,
                viewBtn: false,
                addBtn: true,
                editBtn: false,
                delBtn: false,
                selection: true,
                refreshBtn: false,
                dialogClickModal: false,
                column: [
                    {
                        label: "实例名称",
                        prop: "name",
                        sortable: true,
                        display: false,
                    }, {
                        label: "运行状态",
                        prop: "status",
                        display: false,
                    }, {
                        label: "启动时间",
                        prop: "statusTimestamp",
                        sortable: true,
                        display: false,
                    }, {
                        label: "端口号",
                        prop: "port",
                        display: false,
                    }, {
                        label: "实例数量",
                        prop: "serviceNum",
                        width: 120,
                        display: false,
                    },
                    {
                        label: "实例名称",
                        prop: "serverName",
                        labelWidth: 140,
                        hide: true,
                        disabled: this.isDisabled,
                    },{
                        label: "服务jar存放的路径",
                        prop: "serverPath",
                        labelWidth: 140,
                        hide: true,
                        rules: [{
                            required: true,
                            message: "请输入服务jar存放的路径",
                            trigger: "blur"
                        }]
                    },{
                        label: "日志文件存放位置",
                        prop: "logPath",
                        labelWidth: 140,
                        hide: true,
                        disabled: true,
                        rules: [{
                            required: false,
                            message: "请输入服务jar存放的路径",
                            trigger: "blur"
                        }]
                    },{
                        label: "文件备份路径",
                        prop: "fileBack",
                        labelWidth: 140,
                        hide: true,
                        rules: [{
                            required: false,
                            message: "请输入文件备份路径",
                            trigger: "blur"
                        }]
                    },
                ],
                group: [
                    {
                        icon: 'el-icon-info',
                        label: '基础命令配置',
                        collapse: true,
                        labelWidth: 140,
                        prop: 'cmdCofig',
                        column: [
                            {
                            label: '开始命令',
                            prop: 'startCmd',
                            }, {
                            label: '重启命令',
                            prop: 'restartCmd',
                            }, {
                            label: '停止命令',
                            prop: 'stopCmd',
                            },
                        ]
                    },
                ]
            }
        },
    },
    methods: {
        onLoad(){
            this.loading = true;
            getApplications().then(res=>{
                let serviceData = res.data.data;
                if(serviceData.length>0){
                    // å…ˆæ¸…空
                    this.data = [];
                    serviceData.forEach(element => {
                        //console.log("element",element);
                        // å°†æ—¶é—´è½¬æ¢ä¸ºæœ¬åœ°æ—¶é—´
@@ -114,6 +199,7 @@
                        this.data.push(element)
                    });
                }
                this.loading = false;
                //console.log(res.data.data);
            });
        },
@@ -142,6 +228,68 @@
            }
            }
        },
        // æŽ§åˆ¶ç¤ºä¾‹åç§°æ˜¯å¦å¯è¾“å…¥
        beforeOpen(done, type) {
            // console.log(type)
            if(type === "edit"){
                this.isDisabled = true;
            }else {
                this.isDisabled = false;
            }
            done();
        },
        // å…³é—­å¼¹çª—
        /** å¯¼å…¥ */
        importServiceJar(row) {
            // console.log(row);
            this.serviceName = row.name;
            this.uploadDialogVisible = true;
        },
        // æ–°å¢ž
        addServiceInfo(row, done, loading){
            addSave(row).then(() => {
                this.onLoad();
                this.$message({
                    type: "success",
                    message: "操作成功!"
                });
                done();
            }, error => {
                window.console.log(error);
                loading();
            });
        },
        // ç¼–辑
        handleEdit(row) {
            saveOrGetServiceConfInfo(row).then(res=>{
                // console.log(res.data.data);
                // è¯·æ±‚后端接口,获取到该服务的基础配置信息
                this.$refs.crud.rowEdit(res.data.data, row.$index);
            })
        },
        // ä¿®æ”¹
        rowUpdate(row, index, done) {
            saveOrUpdateServiceInfo(row).then(() => {
                this.onLoad()
                this.$message({
                    type: "success",
                    message: "修改成功!"
                });
                done()
            }).catch(res => {
            })
        },
        // å‘½ä»¤æ‰§è¡Œ
        cmdExecute(row){
            // console.log(row);
            executeCmd(row).then(res => {
                this.onLoad()
                this.$message({
                    type: "success",
                    message: res.data.msg
                });
            });
        },
    }
}
Source/UBCS/ubcs-service-api/ubcs-code-api/src/main/java/com/vci/ubcs/code/vo/pagemodel/CodeImportResultVO.java
@@ -21,6 +21,7 @@
     * æ˜¯å¦æˆåŠŸçš„
     */
    private boolean success ;
    /**
     * å…³é”®å±žæ€§æ ¡éªŒè§„则的信息
     */
@@ -30,14 +31,17 @@
     * å…³é”®å±žæ€§æ ¡éªŒåŽï¼Œé‡å¤çš„行号
     */
    private Set<String> keyAttrRepeatRowIndexList  = new HashSet<>();
    /***
     * å…³é”®å±žæ€§ç›¸åŒçš„属性oid与其重复的数据oid
     */
    private Map<String,List<String>> keyAttrOkOidTORepeatOidMap=new HashMap<>();
    /***
     * æ ¹æ®å…³é”®ç†Ÿæ‚‰æŸ¥è¯¢çš„重复的数据对象
     */
    private Map<String,  List<BaseModel>> indexTODataMap=new HashMap<>();
    /***
     * æ ¹æ®å…³é”®ç†Ÿæ‚‰æŸ¥è¯¢çš„重复的数据对象
     */
Source/UBCS/ubcs-service-api/ubcs-deploy-api/pom.xml
@@ -21,6 +21,12 @@
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
        </dependency>
        <dependency>
            <groupId>com.vci.ubcs</groupId>
            <artifactId>ubcs-util-api</artifactId>
            <version>3.0.1.RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
Source/UBCS/ubcs-service-api/ubcs-deploy-api/src/main/java/com/vci/ubcs/deploy/entity/CmdConfig.java
@@ -1,6 +1,8 @@
package com.vci.ubcs.deploy.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * è¿œç¨‹éƒ¨ç½²èƒ½å¤Ÿæ‰§è¡Œçš„命令
@@ -8,12 +10,23 @@
 * @date 2024/1/7 21:37
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CmdConfig {
    /**
     * å¯åЍ
     */
    private String startCmd;
    /**
     * é‡å¯
     */
    private String restartCmd;
    /**
     * åœæ­¢
     */
    private String stopCmd;
}
Source/UBCS/ubcs-service-api/ubcs-deploy-api/src/main/java/com/vci/ubcs/deploy/entity/DeployApps.java
@@ -39,8 +39,23 @@
    private String logPath;
    /**
     * å‘½ä»¤é…ç½®
     * å¯åЍ
     */
    private CmdConfig cmdCofig;
    private String startCmd;
    /**
     * é‡å¯
     */
    private String restartCmd;
    /**
     * åœæ­¢
     */
    private String stopCmd;
    /**
     * æ–‡ä»¶å¤‡ä»½è·¯å¾„
     */
    private String fileBack;
}
Source/UBCS/ubcs-service-api/ubcs-deploy-api/src/main/java/com/vci/ubcs/deploy/enumpack/CmdConfigEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,136 @@
package com.vci.ubcs.deploy.enumpack;
import com.vci.ubcs.starter.annotation.VciEnum;
import com.vci.ubcs.starter.web.constant.MdmEnumIdConstant;
import com.vci.ubcs.starter.web.enumpck.BaseEnum;
/**
 * å¯åœå‘½ä»¤é…ç½®æžšä¸¾
 * @author ludc
 * @date 2024/2/20 8:59
 */
@VciEnum(name = MdmEnumIdConstant.CODE_SEC_TYPE,text = "启停命令配置枚举",description = "")
public enum CmdConfigEnum implements BaseEnum {
    /**
     * å¯åЍ
     */
    START_CMD("systemctl start ","启动"),
    /**
     * åœæ­¢
     */
    STOP_CMD("systemctl stop ","停止"),
    /**
     * é‡å¯
     */
    RESTART_CMD("systemctl restart ","重启")
    ;
    /**
     * æžšä¸¾çš„值
     */
    private String value;
    /**
     * æžšä¸¾æ˜¾ç¤ºæ–‡æœ¬
     */
    private String text;
    /**
     * èŽ·å–æžšä¸¾å€¼
     *
     * @return æžšä¸¾å€¼
     */
    @Override
    public String getValue() {
        return value;
    }
    /**
     * è®¾ç½®æžšä¸¾å€¼
     *
     * @param value æžšä¸¾å€¼
     */
    public void setValue(String value) {
        this.value = value;
    }
    /**
     * èŽ·å–æžšä¸¾æ˜¾ç¤ºæ–‡æœ¬
     *
     * @return æ˜¾ç¤ºæ–‡æœ¬
     */
    @Override
    public String getText() {
        return text;
    }
    /**
     * è®¾ç½®æ˜¾ç¤ºæ–‡æœ¬
     *
     * @param text æ˜¾ç¤ºæ–‡æœ¬
     */
    public void setText(String text) {
        this.text = text;
    }
    /**
     * æž„造函数
     *
     * @param value å€¼
     * @param text  æ˜¾ç¤ºæ–‡æœ¬
     */
    private CmdConfigEnum(String value, String text) {
        this.value = value;
        this.text = text;
    }
    /**
     * æ ¹æ®åç§°èŽ·å–å¯¹åº”çš„æžšä¸¾å€¼
     *
     * @param text åç§°
     * @return æžšä¸¾å€¼
     */
    public static String getValueByText(String text) {
        for (CmdConfigEnum wenum : CmdConfigEnum.values()) {
            if (wenum.getText().equalsIgnoreCase(text)) {
                return wenum.getValue();
            }
        }
        return "";
    }
    /**
     * æ ¹æ®æžšä¸¾å€¼èŽ·å–åç§°
     *
     * @param value æžšä¸¾å€¼
     * @return åç§°
     */
    public static String getTextByValue(String value) {
        for (CmdConfigEnum wenum : CmdConfigEnum.values()) {
            if (wenum.getValue().equalsIgnoreCase(value)) {
                return wenum.getText();
            }
        }
        return "";
    }
    /**
     * æ ¹æ®æžšä¸¾å€¼èŽ·å–æžšä¸¾å¯¹è±¡
     *
     * @param value æžšä¸¾å€¼
     * @return æžšä¸¾å¯¹è±¡ï¼Œä¸å­˜åœ¨æ—¶å€™è¿”回null
     */
    public static CmdConfigEnum forValue(String value) {
        for (CmdConfigEnum wenum : CmdConfigEnum.values()) {
            if (wenum.getValue().equalsIgnoreCase(value)) {
                return wenum;
            }
        }
        return null;
    }
}
Source/UBCS/ubcs-service-api/ubcs-deploy-api/src/main/java/com/vci/ubcs/deploy/vo/DeployAppsVO.java
@@ -1,5 +1,6 @@
package com.vci.ubcs.deploy.vo;
import com.vci.ubcs.deploy.entity.CmdConfig;
import com.vci.ubcs.deploy.entity.DeployApps;
import lombok.AllArgsConstructor;
import lombok.Data;
Source/UBCS/ubcs-service-api/ubcs-util-api/src/main/java/com/vci/ubcs/starter/util/HttpUtils.java
@@ -1,5 +1,7 @@
package com.vci.ubcs.starter.util;
import com.alibaba.fastjson.JSON;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
Source/UBCS/ubcs-service/ubcs-code/pom.xml
@@ -153,10 +153,6 @@
            <version>3.0.1.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
    </dependencies>
    <build>
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/DockingManagementController.java
@@ -22,6 +22,7 @@
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/MdmEngineService.java
@@ -96,6 +96,16 @@
                               boolean trimAll, Map<String, String> conditionMap);
    /**
     * å°è£…近义词查询规则属性的查询语句
     * @param value å½“前的值
     * @param keyRuleVO å±žæ€§çš„编号
     * @param attrId
     * @param conditionMap æŸ¥è¯¢æ¡ä»¶
     */
    void wrapperSynonymAttrConditionMap(String value, CodeKeyAttrRepeatVO keyRuleVO, String attrId,
                                        Map<String, String> conditionMap);
    /**
     * åˆå§‹åŒ–业务类型
     * --创建人默认为当前用户,如果需要修改,可以在获取后自行处理
     * @param btmName ä¸šåŠ¡ç±»åž‹çš„åç§°ï¼Œä¼šè‡ªåŠ¨å˜æˆå°å†™
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/impl/CodeDuckingSyncServiceImpl.java
@@ -75,6 +75,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/impl/MdmEngineServiceImpl.java
@@ -1288,6 +1288,20 @@
    }
    /**
     * å°è£…近义词查询规则属性的查询语句
     * @param value å½“前的值
     * @param keyRuleVO å±žæ€§çš„编号
     * @param attrId
     * @param conditionMap æŸ¥è¯¢æ¡ä»¶
     */
    @Override
    public void wrapperSynonymAttrConditionMap(String value, CodeKeyAttrRepeatVO keyRuleVO, String attrId, Map<String, String> conditionMap) {
        // map构造思路:1、先完成正向替换sql值
        // 2、再完成反向替换sql值
    }
    /**
     * æ ¡éªŒæžšä¸¾çš„内容
     *
     * @param templateVO æ¨¡æ¿çš„æ˜¾ç¤ºå¯¹è±¡ï¼Œéœ€è¦åŒ…含属性
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/service/impl/MdmIOServiceImpl.java
@@ -68,6 +68,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import javax.xml.bind.ValidationEvent;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
@@ -4381,7 +4382,7 @@
        }
    }
    /**
     * æ ¡éªŒå…³é”®å±žæ€§
     * æ ¡éªŒå…³é”®å±žæ€§ï¼Œå’Œè¿‘义词查询规则
     * @param classifyFullInfo åˆ†ç±»çš„全部信息
     * @param templateVO æ¨¡æ¿çš„内容,必须包含模板属性
     * @param cboList æ‰¹é‡çš„æ•°æ®
@@ -4396,13 +4397,13 @@
        Map<String/**属性的编号**/, CodeClassifyTemplateAttrVO> keyAttrMap = templateVO.getAttributes().stream().filter(s -> VciBaseUtil.getBoolean(s.getKeyAttrFlag())).collect(Collectors.toMap(s -> s.getId().toLowerCase(Locale.ROOT), t -> t));
        // TODO:2024-02-01 å…ˆèŽ·å–é…ç½®äº†è¿‘ä¹‰è¯æŸ¥è¯¢è§„åˆ™çš„å±žæ€§ï¼Œä¸åŒäºŽå…³é”®å±žæ€§ï¼Œè®¾ç½®äº†è¿‘ä¹‰è¯æŸ¥è¯¢è§„åˆ™çš„å±žæ€§å¯èƒ½æ˜¯å¤šæ¡ä¸åŒçš„è¿‘ä¹‰è¯æŸ¥è¯¢è§„åˆ™
//        Map<String, CodeClassifyTemplateAttrVO> sysonymAttrMaps = templateVO.getAttributes().stream().filter(item -> Func.isNotBlank(item.getSysonymRuleOids())).collect(Collectors.toMap(s -> s.getId().toLowerCase(Locale.ROOT), t -> t));
        //有配置近义词查询规则属性
//        Map<String, List<CodeSynonym>> codeSynonymMaps = new HashMap<>();
//        if(!sysonymAttrMaps.isEmpty()){
//            // æŸ¥è¯¢è¿‘义词规则
//            codeSynonymMaps = codeSynonymService.getCodeSynonymByOids(sysonymAttrMaps);
//        }
        Map<String, CodeClassifyTemplateAttrVO> sysonymAttrMaps = templateVO.getAttributes().stream().filter(item -> Func.isNotBlank(item.getSysonymRuleOids())).collect(Collectors.toMap(s -> s.getId().toLowerCase(Locale.ROOT), t -> t));
        // æœ‰é…ç½®è¿‘义词查询规则属性
        Map<String, List<CodeSynonym>> codeSynonymMaps = new HashMap<>();
        if(!sysonymAttrMaps.isEmpty()){
            // æŸ¥è¯¢è¿‘义词规则,存储方式key:属性id,value近义词查询规则列表
            codeSynonymMaps = codeSynonymService.getCodeSynonymByOids(sysonymAttrMaps);
        }
        boolean trimAll =keyRuleVO ==null?false: VciBaseUtil.getBoolean(keyRuleVO.getIgnoreallspaceflag());
        //全部去空的优先级大于去空
@@ -4415,7 +4416,12 @@
        resultVO.setKeyAttrRuleInfo(String.format(keyRuleVO ==null?"":"查询规则:去除空格--{0},忽略大小写--{1},忽略全半角--{2},忽略全部空格--{3}",
            new String[]{trim?"是":"否",ignoreCase?"是":"否",ignoreWidth?"是":"否",trimAll?"是":"否"}));
        //resultVO.setSelfRepeatRowIndexList(getSelfRepeatRowIndex(keyAttrMap,cboList,keyRuleVO));
        // å…ˆåœ¨è¡¨æ ¼ä¸­æŸ¥è¯¢å…³é”®å±žæ€§é‡å¤çš„列
        getSelfRepeatRowIndex(keyAttrMap,cboList,keyRuleVO,resultVO);
        // å†åœ¨è¡¨æ ¼ä¸­æŸ¥è¯¢è¿‘义词查询规则的列
        //getSelfRepeatSysnomRowIndex(sysonymAttrMaps,cboList,codeSynonymMaps,resultVO);
        if(!CollectionUtils.isEmpty(resultVO.getSelfRepeatRowIndexList())){
            //我们移除本身重复的数据
            cboList = cboList.stream().filter(s->!resultVO.getSelfRepeatRowIndexList().contains(s.getAttributeValue(IMPORT_ROW_INDEX))).collect(Collectors.toList());
@@ -4440,8 +4446,12 @@
                        value = "";
                    }
                    value= value.replace(REQUIRED_CHAR,SPECIAL_CHAR);
                    // å…³é”®å±žæ€§æŸ¥è¯¢æ¡ä»¶map获取
                    engineService.wrapperKeyAttrConditionMap(value, keyRuleVO, attrId, trim, ignoreCase, ignoreWidth, trimAll, conditionMap);
                    // è¿‘义词查询规则条件map获取
                    //engineService.wrapperSynonymAttrConditionMap(value);
                });
                if (!CollectionUtils.isEmpty(keyAttrMap)) {
                    // æ·»åŠ ä¸å‚ä¸Žå…³é”®å±žæ€§æ ¡éªŒçš„åˆ†ç±»oid判断
                    if(Func.isNotBlank(isParticipateCheckOids)){
@@ -4616,6 +4626,7 @@
            String rowIndex = cbo.getAttributeValue(IMPORT_ROW_INDEX);
            String oid=cbo.getOid();
            StringBuilder sb = new StringBuilder();
            // å…³é”®å±žæ€§é‡å¤æ ¡éªŒå€¼å¤„理
            for (int i = 0; i < attrVOList.size(); i++) {
                CodeClassifyTemplateAttrVO attrVO = attrVOList.get(i);
                String attrId = attrVO.getId().toLowerCase(Locale.ROOT);
@@ -4662,6 +4673,75 @@
    }
    /**
     * èŽ·å–è¿‘ä¹‰è¯æŸ¥è¯¢å­˜åœ¨ç›¸åŒçš„è¡Œå·
     * @param sysonymAttr
     * @param dataList
     * @param CodeSynonymMaps
     * @param resultVO
     */
    private void getSelfRepeatSysnomRowIndex(Map<String/**属性的编号**/, CodeClassifyTemplateAttrVO> sysonymAttr,
                                             List<ClientBusinessObject> dataList, Map<String, List<CodeSynonym>> CodeSynonymMaps,CodeImportResultVO resultVO) {
        Set<String> selfRepeatRowIndexList = new CopyOnWriteArraySet<>();
        Map<String,List<String>> keyAttrOkOidTORepeatOidMap=new HashMap<>();
        //必须将属性按照顺序排序好
        List<CodeClassifyTemplateAttrVO> attrVOList = sysonymAttr.values().stream().sorted(((o1, o2) -> o1.getOrderNum().compareTo(o2.getOrderNum()))).collect(Collectors.toList());
        Map<String/**行号**/,String/**关键属性的组合内容**/> rowIndexKeyStringMap = new HashMap<>();
        Map<String/**关键属性的组合内容**/,String/**第一个关键属性的数据oid**/> okOidKeyStringMap = new HashMap<>();
        dataList.parallelStream().forEach(cbo-> {
            String rowIndex = cbo.getAttributeValue(IMPORT_ROW_INDEX);
            String oid = cbo.getOid();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < attrVOList.size(); i++) {
                CodeClassifyTemplateAttrVO attrVO = attrVOList.get(i);
                String attrId = attrVO.getId().toLowerCase(Locale.ROOT);
                String value = cbo.getAttributeValue(attrId);
                List<CodeSynonym> codeSynonyms = CodeSynonymMaps.get(attrId);
                // å¯¹è¿‘义词查询值进行转换
                value = synonymConversion(codeSynonyms,value);
                // å¦‚æžœcodeSynonyms是多个就需要考虑组合问题
                sb.append(value).append("${sys}");
            }
            String keyString = sb.toString();
            if(rowIndexKeyStringMap.containsValue(keyString) && StringUtils.isNotBlank(keyString)){
                selfRepeatRowIndexList.add(rowIndex);
                String okOid=okOidKeyStringMap.getOrDefault(keyString,"");
                if(StringUtils.isNotBlank(okOid)){
                    List<String>newOidList=new ArrayList<>();
                    newOidList.add(oid);
                    if(keyAttrOkOidTORepeatOidMap.containsKey(okOid)){
                        List<String> oldOidList = keyAttrOkOidTORepeatOidMap.get(okOid);
                        newOidList.addAll(oldOidList);
                    }
                    keyAttrOkOidTORepeatOidMap.put(okOid,newOidList);
                }
            }else {
                okOidKeyStringMap.put(sb.toString(),oid);
                rowIndexKeyStringMap.put(rowIndex, sb.toString());
            }
        });
        //因为只是关键属性重复,所以我们不能重复的多条选一条来报错
        resultVO.setKeyAttrRepeatRowIndexList(selfRepeatRowIndexList);
        resultVO.setKeyAttrOkOidTORepeatOidMap(keyAttrOkOidTORepeatOidMap);
    }
    /**
     * è¿‘义词与源值相互转换
     * @param codeSynonyms
     * @param value
     * @return
     */
    private String synonymConversion(List<CodeSynonym> codeSynonyms,String value){
        // åªæœ‰ä¸€æ¡è¿‘义词查询规则时
        if(codeSynonyms.isEmpty() || codeSynonyms.size() == 1){
            return value.replace(codeSynonyms.get(0).getSourceValue(),codeSynonyms.get(0).getSynonymValue());
        }
        // è¿‘义词规则可能出现的情况是一个源值对应多个近义词,而反查时同样会出现一个近义词对应多个源值
//        codeSynonyms.stream().map()
        return value;
    }
    /**
     * excel的标题上获取字段所在的位置
     * @param titleRowData æ ‡é¢˜çš„内容
     * @param attrNameIdMap æ¨¡æ¿ä¸­å±žæ€§åç§°å’Œè‹±æ–‡çš„æ˜ å°„
@@ -4686,6 +4766,7 @@
            }
        }
    }
    private List<ClientBusinessObject> ChangeMapTOClientBusinessObjects(List<Map<String,String>> oldDataMap){
        List<ClientBusinessObject> clientBusinessObjectList=new ArrayList<>();
        DefaultAttrAssimtUtil.mapToLowerCase(oldDataMap,true);
Source/UBCS/ubcs-service/ubcs-code/src/test/java/util.java
@@ -11,9 +11,6 @@
//列出File的一些常用操作
public class util {
    /**
     * éåŽ†æŒ‡å®šç›®å½•ä¸‹ï¼ˆåŒ…æ‹¬å…¶å­ç›®å½•ï¼‰çš„æ‰€æœ‰æ–‡ä»¶ï¼Œå¹¶åˆ é™¤ä»¥ lastUpdated ç»“尾的文件
     * @param dir ç›®å½•的位置 path
Source/UBCS/ubcs-service/ubcs-deploy/src/main/java/com/vci/ubcs/deploy/controller/DeployAppsController.java
@@ -4,17 +4,24 @@
import com.vci.ubcs.deploy.entity.DeployApps;
import com.vci.ubcs.deploy.service.IDeployAppsService;
import com.vci.ubcs.deploy.vo.DeployAppsVO;
import com.vci.ubcs.starter.util.LocalFileUtil;
import com.vci.ubcs.starter.web.util.ControllerUtil;
import com.vci.ubcs.starter.web.util.LangBaseUtil;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.auth.AuthenticationException;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springblade.core.tool.utils.Func;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.annotations.ApiIgnore;
import javax.servlet.ServletRequest;
import java.io.File;
import java.util.List;
/**
@@ -24,17 +31,82 @@
 */
@NonDS
@RestController
@AllArgsConstructor
@RequiredArgsConstructor
@RequestMapping("/deploy")
@ApiIgnore
@Api(value = "应用管理", tags = "接口")
@Slf4j
public class DeployAppsController {
    private final IDeployAppsService deployAppsService;
    /**
     * èŽ·å–æœåŠ¡è¿è¡Œåˆ—è¡¨
     * @param servletRequest
     * @return
     * @throws ServiceException
     */
    @GetMapping("/applications")
    public R<List<DeployAppsVO>> getApplications(ServletRequest servletRequest) throws ServiceException {
        return R.data(deployAppsService.getApplications(servletRequest));
    }
    /**
     * èŽ·å–æœåŠ¡é…ç½®ä¿¡æ¯
     * @param deployAppsVO
     * @return
     * @throws ServiceException
     */
    @PostMapping("/saveOrGetServiceConfInfo")
    public R<DeployApps> saveOrGetServiceConfInfo(@RequestBody DeployAppsVO deployAppsVO) throws ServiceException {
        return R.data(deployAppsService.saveOrGetServiceConfInfo(deployAppsVO));
    }
    /**
     * æ–°å¢žæˆ–获取服务默认配置信息
     * @param deployAppsVO
     * @return
     * @throws ServiceException
     */
    @PostMapping("/saveOrUpdateServiceInfo")
    public R saveOrUpdateDefault(@RequestBody DeployAppsVO deployAppsVO) throws ServiceException {
        return R.status(deployAppsService.saveOrUpdateServiceInfo(deployAppsVO));
    }
    /**
     * æ·»åŠ æœåŠ¡ä¿¡æ¯
     * @param deployApps
     * @return
     * @throws ServiceException
     */
    @PostMapping("/addSave")
    public R addSave(@RequestBody DeployApps deployApps) throws ServiceException {
        return R.status(deployAppsService.addSave(deployApps));
    }
    /**
     * æœåŠ¡æ›´æ–°åŒ…
     * @param files æ›´æ–°çš„jar或文件夹
     * @param serverName æœåŠ¡åç§°
     * @return
     * @throws ServiceException
     */
    @PostMapping("/importUpdateServiceJar")
    public R importClassify(@RequestParam("files") MultipartFile[] files,@RequestParam String serverName) throws ServiceException {
        if(Func.isBlank(serverName)){
            return R.fail("Mandatory parameter service name not found!");
        }
        return deployAppsService.importClassify(files,serverName);
    }
    /**
     * æ‰§è¡Œå‘½ä»¤
     * @param deployAppsVO
     * @return
     */
    @PostMapping("/cmdExecute")
    public R cmdExecute(@RequestBody DeployAppsVO deployAppsVO) throws ServiceException {
        return deployAppsService.cmdExecute(deployAppsVO);
    }
}
Source/UBCS/ubcs-service/ubcs-deploy/src/main/java/com/vci/ubcs/deploy/service/IDeployAppsService.java
@@ -5,6 +5,9 @@
import com.vci.ubcs.deploy.entity.DeployApps;
import com.vci.ubcs.deploy.vo.DeployAppsVO;
import org.apache.http.auth.AuthenticationException;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import java.util.List;
@@ -15,6 +18,51 @@
 */
public interface IDeployAppsService extends IService<DeployApps> {
    /**
     * èŽ·å–æ­£åœ¨è¿è¡Œçš„æœåŠ¡ç›¸å…³ä¿¡æ¯
     * @param servletRequest
     * @return
     * @throws ServiceException
     */
    List<DeployAppsVO> getApplications(ServletRequest servletRequest) throws ServiceException;
    /**
     * æ ¹æ®æœåŠ¡åç§°èŽ·å–æˆ–æœåŠ¡é…ç½®ä¿¡æ¯
     * @param deployAppsVO
     * @return
     * @throws ServiceException
     */
    DeployApps saveOrGetServiceConfInfo(DeployAppsVO deployAppsVO) throws ServiceException;
    /**
     * ä¿®æ”¹æˆ–保存
     * @param deployAppsVO
     * @return
     * @throws ServiceException
     */
    boolean saveOrUpdateServiceInfo(DeployAppsVO deployAppsVO) throws ServiceException;
    /**
     * æ–°å¢žæœåŠ¡ä¿¡æ¯
     * @param deployApps
     * @return
     * @throws ServiceException
     */
    boolean addSave(DeployApps deployApps) throws ServiceException;
    /**
     * æ›´æ–°æ–‡ä»¶ä¸Šä¼ 
     * @param files
     * @param serverName
     * @return
     */
    R importClassify(MultipartFile[] files, String serverName) throws ServiceException;
    /**
     * æ‰§è¡Œå‘½ä»¤
     * @param deployAppsVO
     * @return
     */
    R cmdExecute(DeployAppsVO deployAppsVO)throws ServiceException;
}
Source/UBCS/ubcs-service/ubcs-deploy/src/main/java/com/vci/ubcs/deploy/service/impl/DeployAppsServiceImpl.java
@@ -5,45 +5,98 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.shaded.com.google.protobuf.ServiceException;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.vci.ubcs.deploy.entity.CmdConfig;
import com.vci.ubcs.deploy.entity.DeployApps;
import com.vci.ubcs.deploy.entity.Instance;
import com.vci.ubcs.deploy.enumpack.CmdConfigEnum;
import com.vci.ubcs.deploy.mapper.DeployAppsMapper;
import com.vci.ubcs.deploy.service.IDeployAppsService;
import com.vci.ubcs.deploy.vo.DeployAppsVO;
import com.vci.ubcs.starter.util.HttpUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.UrlUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
/**
 * @author ludc
 * @date 2024/1/7 19:54
 */
@Service
public class DeployAppsServiceImpl extends ServiceImpl<DeployAppsMapper, DeployApps> implements IDeployAppsService {
@RequiredArgsConstructor
@Slf4j
public class DeployAppsServiceImpl extends ServiceImpl<DeployAppsMapper, DeployApps> implements IDeployAppsService, EnvironmentAware {
    @Autowired
    private DeployAppsMapper deployAppsMapper;
    private final DeployAppsMapper deployAppsMapper;
    @Value("${password-free.pwd-free-addr:localhost}")
    private String pwdFreeAddr;
    // é€šè¿‡æœåŠ¡æ³¨å†Œä¸­å¿ƒèŽ·å–ç½‘å…³çš„ç«¯å£å·
    /**
     * é€šè¿‡æœåŠ¡æ³¨å†Œä¸­å¿ƒèŽ·å–ç½‘å…³çš„ç«¯å£å·
     */
    @Autowired
    private DiscoveryClient discoveryClient;
    /**
     * å„个服务存放的的父路径
     */
    @Value("${local-log.parent-path:/data1/ubcs/ubcs-server}")
    private String PARENTPATH;
    /**
     * æ—¥å¿—文件的具体位置
     */
    @Value("${local-log.log-path:/target/log}")
    private String LOGPATH;
    /**
     * æ ¹æ®æ“ä½œç³»ç»Ÿç”Ÿæˆåˆ†éš”符
     */
    private String SEPARATOR = "/";
    /**
     * æ ¹æ®å½“前运行的环境,对配置的日志路径格式进行调整
     * @param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        String os = environment.getProperty("os.name").toLowerCase();
        if (!os.contains("win")) {
            this.SEPARATOR = "/";
        }else{
            this.SEPARATOR = "\\";
        }
    }
    @Override
    public List<DeployAppsVO> getApplications(ServletRequest servletRequest) throws ServiceException {
@@ -80,12 +133,252 @@
                    deployAppsVO.setStatusTimestamp(jsonObject.get("statusTimestamp").toString());
                    deployAppsVOList.add(deployAppsVO);
                }
            }
        }catch (Exception e){
            throw new ServiceException("调用ubcs-admin获取服务信息失败,原因:"+e.getMessage());
        }
        // å†æŸ¥è¯¢åº“中已经存在的服务配置信息,进行是否已经启动的判断
        List<DeployApps> deployApps = deployAppsMapper.selectList(null);
        // åº“中未配置,直接返回正在运行的服务信息
        if(deployApps.isEmpty()){
            return deployAppsVOList;
        }
        // ç­›é€‰å‡ºä¸åœ¨è¿è¡Œçš„并生成默认的服务信息
        List<DeployAppsVO> deployAppsVOS1 = deployApps.stream()
            .filter(deployApp -> deployAppsVOList.stream()
                .noneMatch(deployAppVO -> deployApp.getServerName().equals(deployAppVO.getName()) &&
                    deployApp.getServerName().equals(deployAppVO.getName())))
            .map(deployApp -> {
                DeployAppsVO deployAppsVO2 = new DeployAppsVO(deployApp.getServerName(), "DOWN", "", "", 0);
                if(deployApp.getServerName().equals("web")){
                    deployAppsVO2.setStatus("UP");
                    deployAppsVO2.setPort("8080");
                    deployAppsVO2.setServiceNum(1);
                }
                return deployAppsVO2;
            })
            .collect(Collectors.toList());
        deployAppsVOList.addAll(deployAppsVOS1);
        return deployAppsVOList;
    }
    @Override
    public DeployApps saveOrGetServiceConfInfo(DeployAppsVO deployAppsVO) throws ServiceException {
        if(deployAppsVO.getName().isEmpty()){
            throw new ServiceException("缺少必传参数name");
        }
        DeployApps deployApps = deployAppsMapper.selectOne(Wrappers.<DeployApps>query().lambda().eq(DeployApps::getServerName, deployAppsVO.getName()));
        if(Func.isNotEmpty(deployApps)){
            return deployApps;
        }
        // æœªä»Žåº“中查询到,需要生成服务信息保存默认信息到库中
        DeployApps defaultDeployApps = new DeployApps();
        defaultDeployApps.setLogPath(PARENTPATH + "\\" + deployAppsVO.getName().replace("-","_") + LOGPATH);
        defaultDeployApps.setServerName(deployAppsVO.getName());
        defaultDeployApps.setStartCmd(CmdConfigEnum.START_CMD.getValue() + deployAppsVO.getName());
        defaultDeployApps.setStopCmd(CmdConfigEnum.STOP_CMD.getValue() + deployAppsVO.getName());
        defaultDeployApps.setRestartCmd(CmdConfigEnum.RESTART_CMD.getValue() + deployAppsVO.getName());
        defaultDeployApps.setServerPath(PARENTPATH + "\\" + deployAppsVO.getName().replace("-","_"));
        int eft = deployAppsMapper.insert(defaultDeployApps);
        if (!SqlHelper.retBool(eft)) {
            throw new ServiceException("生成默认服务信息到库中时失败!");
        }
        return defaultDeployApps;
    }
    /**
     * ä¿®æ”¹æˆ–保存
     * @param deployAppsVO
     * @return
     * @throws ServiceException
     */
    @Override
    public boolean saveOrUpdateServiceInfo(DeployAppsVO deployAppsVO) throws ServiceException {
        boolean b = this.saveOrUpdate(deployAppsVO);
        return b;
    }
    /**
     * æ–°å¢žæœåŠ¡ä¿¡æ¯
     * @param deployApps
     * @return
     * @throws ServiceException
     */
    @Override
    public boolean addSave(DeployApps deployApps) throws ServiceException {
        if (Func.isBlank(deployApps.getServerName()) || Func.isBlank(deployApps.getServerPath())) {
            throw new ServiceException("必传参数[服务名称,服务存放路径]不能为空");
        }
        return SqlHelper.retBool(deployAppsMapper.insert(deployApps));
    }
    /**
     * æ›´æ–°æ–‡ä»¶ä¸Šä¼ 
     * @param files
     * @param serverName
     * @return
     */
    @Override
    public R importClassify(MultipartFile[] files, String serverName) throws ServiceException {
        // æ ¹æ®æœåŠ¡åæŸ¥çœ‹åˆ°æœåŠ¡ç›¸å…³ä¿¡æ¯
        List<DeployApps> deployAppsDB = deployAppsMapper.selectList(Wrappers.<DeployApps>query().lambda().eq(DeployApps::getServerName, serverName));
        if(deployAppsDB.isEmpty()){
            return R.fail("No configuration information related to "+ serverName +" service found");
        }
        // éåކMultipartFile数组,逐个处理文件
        try {
            for (MultipartFile file : files) {
                // é…ç½®äº†å¤‡ä»½æ–‡ä»¶è·¯å¾„,先备份再替换
                if(Func.isNotEmpty(deployAppsDB.get(0).getFileBack())){
                    File backFile = new File(deployAppsDB.get(0).getFileBack());
                    // è·¯å¾„不存在就创建
                    if (!backFile.exists()) {
                        backFile.mkdirs();
                    }
                    String backName = "";
                    String fileType = "file";
                    // æ˜¯åŽ‹ç¼©æ–‡ä»¶,因为只会存在两种情况,文件名是压缩文件,或者文件(.jar类型的文件)
                    if (file.getContentType().equals("application/zip") || file.getContentType().equals("application/x-zip-compressed")) {
                        backName = file.getOriginalFilename().replace(".zip","_"+Func.formatDate(new Date()));
                        fileType = "zip";
                    }else{
                        backName = file.getOriginalFilename().replace(".","_"+Func.formatDate(new Date())+".");
                        fileType = "file";
                    }
                    File source = new File(deployAppsDB.get(0).getServerPath() + this.SEPARATOR + file.getOriginalFilename().replace(".zip", ""));
                    File destination = new File(deployAppsDB.get(0).getFileBack() + this.SEPARATOR + backName);
                    copyFolder(source, destination);
                }
                Path filePath = Paths.get(deployAppsDB.get(0).getServerPath(), file.getOriginalFilename());
                Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
                // æ£€æŸ¥æ–‡ä»¶ç±»åž‹ï¼Œå¦‚果是压缩文件则解压缩
                if (file.getContentType().equals("application/zip") || file.getContentType().equals("application/x-zip-compressed")) {
                    //sourcePath压缩包文件路径
                    try (ZipFile zipFile = new ZipFile(new File(deployAppsDB.get(0).getServerPath()+ this.SEPARATOR +file.getOriginalFilename()))) {
                        Enumeration enumeration = zipFile.entries();
                        while (enumeration.hasMoreElements()) {
                            //依次获取压缩包内的文件实体对象
                            ZipEntry entry = (ZipEntry) enumeration.nextElement();
                            String name = entry.getName();
                            if (entry.isDirectory()) {
                                continue;
                            }
                            try (BufferedInputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
                                // éœ€è¦åˆ¤æ–­æ–‡ä»¶æ‰€åœ¨çš„目录是否存在,处理压缩包里面有文件夹的情况
                                String outName = deployAppsDB.get(0).getServerPath() + this.SEPARATOR + name;
                                File outFile = new File(outName);
                                File tempFile = new File(outName.substring(0, outName.lastIndexOf("/")));
                                if (!tempFile.exists()) {
                                    tempFile.mkdirs();
                                }
                                try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outFile))) {
                                    int len;
                                    byte[] buffer = new byte[1024];
                                    while ((len = inputStream.read(buffer)) > 0) {
                                        outputStream.write(buffer, 0, len);
                                    }
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    File file1 = new File(deployAppsDB.get(0).getServerPath() + SEPARATOR + file.getOriginalFilename());
                    // åŽ‹ç¼©æ–‡ä»¶ä¸Šä¼ æˆåŠŸä¹‹åŽï¼Œåˆ é™¤è§£åŽ‹æ–‡ä»¶
                    file1.delete();
                }
            }
            String output = excute(deployAppsDB.get(0),"UP");
            return R.success(output.toString());
        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
            return R.fail("Failed to upload files");
        }
    }
    /**
     * æ‰§è¡Œå‘½ä»¤
     * @param deployAppsVO
     * @return
     */
    @Override
    public R cmdExecute(DeployAppsVO deployAppsVO) throws ServiceException {
        String excuteRes = "";
        try {
            List<DeployApps> deployAppsDB = deployAppsMapper.selectList(Wrappers.<DeployApps>query().lambda().eq(DeployApps::getServerName, deployAppsVO.getName()));
            if(deployAppsDB.isEmpty()){
                return R.fail("命令执行出错,库中未找到"+ deployAppsVO.getName() +"服务相关配置:" );
            }
            excuteRes = excute(deployAppsDB.get(0),deployAppsVO.getStatus());
            return R.success("命令执行结束:"+excuteRes);
        }catch (Exception e){
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * æ‰§è¡Œå‘½ä»¤
     * @param deployApps
     * @return
     * @throws ServiceException
     */
    private String excute(DeployApps deployApps,String type) throws ServiceException {
        // å¤„理上传文件的逻辑
        StringBuilder output = new StringBuilder();
        try {
            String cmd = "";
            if(type.equalsIgnoreCase("UP")){
                cmd = deployApps.getRestartCmd();
            }else {
                cmd = deployApps.getStartCmd();
            }
            if(Func.isEmpty(cmd)){
                return "The executed command is empty";
            }
            // æ‰§è¡ŒLinux命令
            Process process = Runtime.getRuntime().exec(cmd);
            // è¯»å–命令执行结果
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
            // ç­‰å¾…命令执行完成
            int exitCode = process.waitFor();
            log.info("命令执行结果:" + output.toString());
            return output.toString();
        }catch (IOException | InterruptedException e){
            e.printStackTrace();
            log.error("命令执行出错,原因:" + e.getMessage());
            throw new ServiceException("Command execution failed"+e.getMessage());
        }
    }
    /**
     * æ–‡ä»¶å¤‡ä»½æ“ä½œ
     * @param source æºæ–‡ä»¶
     * @param destination æ–‡ä»¶å¤‡ä»½è·¯å¾„
     * @throws IOException
     */
    private void copyFolder(File source, File destination) throws IOException {
        // æ–‡ä»¶å­˜åœ¨æ‰éœ€è¦å¤‡ä»½
        if(source.exists()){
            if (source.isDirectory()) {
                if (!destination.exists()) {
                    destination.mkdir();
                }
                String[] files = source.list();
                for (String file : files) {
                    File srcFile = new File(source, file);
                    File destFile = new File(destination, file);
                    copyFolder(srcFile, destFile);
                }
            } else {
                Files.copy(source.toPath(), destination.toPath());
            }
        }
    }
    /**
@@ -93,7 +386,7 @@
     * @param serviceId
     * @return
     */
    public String getGatewayPort(String serviceId) {
    private String getGatewayPort(String serviceId) {
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
        if (!instances.isEmpty()) {
            ServiceInstance gatewayInstance = instances.get(0);
@@ -101,6 +394,5 @@
        }
        return "8080";
    }
}