Browse Source

完成 FileConfig 的前端模块

YunaiV 3 years ago
parent
commit
659023bb35

+ 3 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -80,4 +80,7 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
         entities.forEach(this::insert);
     }
 
+    default void updateBatch(T update) {
+        update(update, new QueryWrapper<>());
+    }
 }

+ 1 - 0
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java

@@ -47,5 +47,6 @@ public interface ErrorCodeConstants {
 
     // ========== 文件配置 1001006000 ==========
     ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1001006000, "文件配置不存在");
+    ErrorCode FILE_CONFIG_DELETE_FAIL_MASTER = new ErrorCode(1001006001, "该文件配置不允许删除,原因:它是主配置,删除会导致无法上传文件");
 
 }

+ 6 - 2
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java

@@ -19,13 +19,17 @@ public class FileConfigRespVO extends FileConfigBaseVO {
     @ApiModelProperty(value = "编号", required = true, example = "1")
     private Long id;
 
-    @ApiModelProperty(value = "存储配置", required = true)
-    private FileClientConfig config;
+    @ApiModelProperty(value = "存储器", required = true, example = "1", notes = "参见 FileStorageEnum 枚举类")
+    @NotNull(message = "存储器不能为空")
+    private Integer storage;
 
     @ApiModelProperty(value = "是否为主配置", required = true, example = "true")
     @NotNull(message = "是否为主配置不能为空")
     private Boolean master;
 
+    @ApiModelProperty(value = "存储配置", required = true)
+    private FileClientConfig config;
+
     @ApiModelProperty(value = "创建时间", required = true)
     private Date createTime;
 

+ 6 - 10
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java

@@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
 import cn.iocoder.yudao.module.infra.dal.mysql.codegen.SchemaColumnMapper;
 import cn.iocoder.yudao.module.infra.dal.mysql.codegen.SchemaTableMapper;
 import cn.iocoder.yudao.module.infra.enums.codegen.CodegenImportTypeEnum;
-import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
 import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
 import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
 import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
@@ -26,7 +25,10 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -81,10 +83,7 @@ public class CodegenServiceImpl implements CodegenService {
         codegenTableMapper.insert(table);
         // 构建 CodegenColumnDO 数组,插入到 DB 中
         List<CodegenColumnDO> columns = codegenBuilder.buildColumns(schemaColumns);
-        columns.forEach(column -> {
-            column.setTableId(table.getId());
-            codegenColumnMapper.insert(column); // TODO 批量插入
-        });
+        codegenColumnMapper.insertBatch(columns);
         return table.getId();
     }
 
@@ -198,10 +197,7 @@ public class CodegenServiceImpl implements CodegenService {
 
         // 插入新增的字段
         List<CodegenColumnDO> columns = codegenBuilder.buildColumns(schemaColumns);
-        columns.forEach(column -> {
-            column.setTableId(tableId);
-            codegenColumnMapper.insert(column); // TODO 批量插入
-        });
+        codegenColumnMapper.insertBatch(columns);
         // 删除不存在的字段
         if (CollUtil.isNotEmpty(deleteColumnIds)) {
             codegenColumnMapper.deleteBatchIds(deleteColumnIds);

+ 19 - 2
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java

@@ -22,6 +22,9 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.PostConstruct;
@@ -33,6 +36,7 @@ import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;
 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS;
 
 /**
@@ -151,13 +155,23 @@ public class FileConfigServiceImpl implements FileConfigService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateFileConfigMaster(Long id) {
         // 校验存在
         this.validateFileConfigExists(id);
+        // 更新其它为非 master
+        fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false));
         // 更新
         fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));
         // 发送刷新配置的消息
-        fileConfigProducer.sendFileConfigRefreshMessage();
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+
+            @Override
+            public void afterCommit() {
+                fileConfigProducer.sendFileConfigRefreshMessage();
+            }
+
+        });
     }
 
     private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {
@@ -174,7 +188,10 @@ public class FileConfigServiceImpl implements FileConfigService {
     @Override
     public void deleteFileConfig(Long id) {
         // 校验存在
-        this.validateFileConfigExists(id);
+        FileConfigDO config = this.validateFileConfigExists(id);
+        if (Boolean.TRUE.equals(config.getMaster())) {
+             throw exception(FILE_CONFIG_DELETE_FAIL_MASTER);
+        }
         // 删除
         fileConfigMapper.deleteById(id);
         // 发送刷新配置的消息

+ 1 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java

@@ -25,8 +25,7 @@ public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
             entity.setMenuId(menuId);
             return entity;
         }).collect(Collectors.toList());
-        // TODO 芋艿,mybatis plus 增加批量插入的功能
-        list.forEach(this::insert);
+        insertBatch(list);
     }
 
     default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {

+ 59 - 0
yudao-ui-admin/src/api/infra/fileConfig.js

@@ -0,0 +1,59 @@
+import request from '@/utils/request'
+
+// 创建文件配置
+export function createFileConfig(data) {
+  return request({
+    url: '/infra/file-config/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新文件配置
+export function updateFileConfig(data) {
+  return request({
+    url: '/infra/file-config/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 更新文件配置为主配置
+export function updateFileConfigMaster(id) {
+  return request({
+    url: '/infra/file-config/update-master?id=' + id,
+    method: 'put'
+  })
+}
+
+// 删除文件配置
+export function deleteFileConfig(id) {
+  return request({
+    url: '/infra/file-config/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得文件配置
+export function getFileConfig(id) {
+  return request({
+    url: '/infra/file-config/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得文件配置分页
+export function getFileConfigPage(query) {
+  return request({
+    url: '/infra/file-config/page',
+    method: 'get',
+    params: query
+  })
+}
+
+export function testFileConfig(id) {
+  return request({
+    url: '/infra/file-config/test?id=' + id,
+    method: 'get'
+  })
+}

+ 1 - 1
yudao-ui-admin/src/components/DictTag/index.vue

@@ -17,7 +17,7 @@ export default {
   name: "DictTag",
   props: {
     type: String,
-    value: [Number, String, Array],
+    value: [Number, String, Boolean, Array],
   },
 };
 </script>

+ 2 - 0
yudao-ui-admin/src/utils/dict.js

@@ -25,6 +25,7 @@ export const DICT_TYPE = {
   SYSTEM_ERROR_CODE_TYPE: 'system_error_code_type',
 
   // ========== INFRA 模块 ==========
+  INFRA_BOOLEAN_STRING: 'infra_boolean_string',
   INFRA_REDIS_TIMEOUT_TYPE: 'infra_redis_timeout_type',
   INFRA_JOB_STATUS: 'infra_job_status',
   INFRA_JOB_LOG_STATUS: 'infra_job_log_status',
@@ -32,6 +33,7 @@ export const DICT_TYPE = {
   INFRA_CONFIG_TYPE: 'infra_config_type',
   INFRA_CODEGEN_TEMPLATE_TYPE: 'infra_codegen_template_type',
   INFRA_CODEGEN_SCENE: 'infra_codegen_scene',
+  INFRA_FILE_STORAGE: 'infra_file_storage',
 
   // ========== BPM 模块 ==========
   BPM_MODEL_CATEGORY: 'bpm_model_category',

+ 313 - 0
yudao-ui-admin/src/views/infra/fileConfig/index.vue

@@ -0,0 +1,313 @@
+<template>
+  <div class="app-container">
+
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="配置名" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入配置名" clearable size="small" @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="存储器" prop="storage">
+        <el-select v-model="queryParams.storage" placeholder="请选择存储器" clearable size="small">
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_FILE_STORAGE)"
+                     :key="dict.value" :label="dict.label" :value="dict.value"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间">
+        <el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
+                        type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+                   v-hasPermi="['infra:file-config:create']">新增</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="编号" align="center" prop="id" />
+      <el-table-column label="配置名" align="center" prop="name" />
+      <el-table-column label="存储器" align="center" prop="storage">
+        <template slot-scope="scope">
+          <dict-tag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="scope.row.storage" />
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="主配置" align="center" prop="primary">
+        <template slot-scope="scope">
+          <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+                     v-hasPermi="['infra:file-config:update']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-attract" @click="handleMaster(scope.row)"
+                     :disabled="scope.row.master" v-hasPermi="['infra:file-config:update']">主配置</el-button>
+          <el-button size="mini" type="text" icon="el-icon-share" @click="handleTest(scope.row)">测试</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-hasPermi="['infra:file-config:delete']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="配置名" prop="name">
+          <el-input v-model="form.name" placeholder="请输入配置名" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+        <el-form-item label="存储器" prop="storage">
+          <el-select v-model="form.storage" placeholder="请选择存储器" :disabled="form.id">
+            <el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_FILE_STORAGE)"
+                       :key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
+          </el-select>
+        </el-form-item>
+        <!-- DB -->
+        <!-- Local / FTP / SFTP -->
+        <el-form-item v-if="form.storage >= 10 && form.storage <= 12" label="基础路径" prop="config.basePath">
+          <el-input v-model="form.config.basePath" placeholder="请输入基础路径" />
+        </el-form-item>
+        <el-form-item v-if="form.storage >= 11 && form.storage <= 12" label="主机地址" prop="config.host">
+          <el-input v-model="form.config.host" placeholder="请输入主机地址" />
+        </el-form-item>
+        <el-form-item v-if="form.storage >= 11 && form.storage <= 12" label="主机端口" prop="config.port">
+          <el-input-number min="0" v-model="form.config.port" placeholder="请输入主机端口" />
+        </el-form-item>
+        <el-form-item v-if="form.storage >= 11 && form.storage <= 12" label="用户名" prop="config.username">
+          <el-input v-model="form.config.username" placeholder="请输入密码" />
+        </el-form-item>
+        <el-form-item v-if="form.storage >= 11 && form.storage <= 12" label="密码" prop="config.password">
+          <el-input v-model="form.config.password" placeholder="请输入密码" />
+        </el-form-item>
+        <el-form-item v-if="form.storage === 11" label="连接模式" prop="config.mode">
+          <el-radio-group v-model="form.config.mode">
+            <el-radio key="Active" label="Active">主动模式</el-radio>
+            <el-radio key="Passive" label="Passive">主动模式</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <!-- S3 -->
+        <el-form-item v-if="form.storage === 20" label="节点地址" prop="config.endpoint">
+          <el-input v-model="form.config.endpoint" placeholder="请输入节点地址" />
+        </el-form-item>
+        <el-form-item v-if="form.storage === 20" label="区域" prop="config.region">
+          <el-input v-model="form.config.region" placeholder="请输入区域" />
+        </el-form-item>
+        <el-form-item v-if="form.storage === 20" label="存储 bucket" prop="config.bucket">
+          <el-input v-model="form.config.bucket" placeholder="请输入 bucket" />
+        </el-form-item>
+        <el-form-item v-if="form.storage === 20" label="accessKey" prop="config.accessKey">
+          <el-input v-model="form.config.accessKey" placeholder="请输入 accessKey" />
+        </el-form-item>
+        <el-form-item v-if="form.storage === 20" label="accessSecret" prop="config.accessSecret">
+          <el-input v-model="form.config.accessSecret" placeholder="请输入 accessSecret" />
+        </el-form-item>
+        <!-- 通用 -->
+        <el-form-item v-if="form.storage === 20" label="自定义域名"> <!-- 无需参数校验,所以去掉 prop -->
+          <el-input v-model="form.config.domain" placeholder="请输入自定义域名" />
+        </el-form-item>
+        <el-form-item v-else-if="form.storage" label="自定义域名" prop="config.domain">
+          <el-input v-model="form.config.domain" placeholder="请输入自定义域名" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  createFileConfig,
+  updateFileConfig,
+  deleteFileConfig,
+  getFileConfig,
+  getFileConfigPage,
+  testFileConfig, updateFileConfigMaster
+} from "@/api/infra/fileConfig";
+
+export default {
+  name: "FileConfig",
+  components: {
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 文件配置列表
+      list: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      dateRangeCreateTime: [],
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        name: null,
+        storage: null,
+      },
+      // 表单参数
+      form: {
+        storage: undefined,
+        config: {}
+      },
+      // 表单校验
+      rules: {
+        name: [{ required: true, message: "配置名不能为空", trigger: "blur" }],
+        storage: [{ required: true, message: "存储器不能为空", trigger: "change" }],
+        config: {
+          basePath: [{ required: true, message: "基础路径不能为空", trigger: "blur" }],
+          host: [{ required: true, message: "主机地址不能为空", trigger: "blur" }],
+          port: [{ required: true, message: "主机端口不能为空", trigger: "blur" }],
+          username: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
+          password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
+          mode: [{ required: true, message: "连接模式不能为空", trigger: "change" }],
+          endpoint: [{ required: true, message: "节点地址不能为空", trigger: "blur" }],
+          region: [{ required: true, message: "区域名不能为空", trigger: "blur" }],
+          bucket: [{ required: true, message: "存储 bucket 不能为空", trigger: "blur" }],
+          accessKey: [{ required: true, message: "accessKey 不能为空", trigger: "blur" }],
+          accessSecret: [{ required: true, message: "accessSecret 不能为空", trigger: "blur" }],
+          domain: [{ required: true, message: "自定义域名不能为空", trigger: "blur" }],
+        },
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      // 处理查询参数
+      let params = {...this.queryParams};
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行查询
+      getFileConfigPage(params).then(response => {
+        this.list = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 取消按钮 */
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        id: undefined,
+        name: undefined,
+        storage: undefined,
+        remark: undefined,
+        config: {},
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRangeCreateTime = [];
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加文件配置";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id;
+      getFileConfig(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改文件配置";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (!valid) {
+          return;
+        }
+        // 修改的提交
+        if (this.form.id != null) {
+          updateFileConfig(this.form).then(response => {
+            this.$modal.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
+          });
+          return;
+        }
+        // 添加的提交
+        createFileConfig(this.form).then(response => {
+          this.$modal.msgSuccess("新增成功");
+          this.open = false;
+          this.getList();
+        });
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const id = row.id;
+      this.$modal.confirm('是否确认删除文件配置编号为"' + id + '"的数据项?').then(function() {
+        return deleteFileConfig(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    /** 主配置按钮操作 */
+    handleMaster(row) {
+      const id = row.id;
+      this.$modal.confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?').then(function() {
+        return updateFileConfigMaster(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("修改成功");
+      }).catch(() => {});
+    },
+    /** 测试按钮操作 */
+    handleTest(row) {
+      testFileConfig(row.id).then((response) => {
+        this.$modal.alert("测试通过,上传文件成功!访问地址:" + response.data);
+      }).catch(() => {});
+    },
+  }
+};
+</script>