创建服务模块

概述

针对在macula-samples平台中创建业务微服务macula-samples-service1案例的开发过程进行说明。

(1)业务需求说明

实现一个简单的应用与秘钥的管理功能。

(2)后端开发流程

  • 创建macula-samples-service1

(pom文件引入需要的starter依赖、修改macula-samples平台上下文的PaaS组件配置)

<!-- web应用基础依赖包, 统一处理返回对象及异常等 -->
<dependency>
    <groupId>dev.macula.boot</groupId>
    <artifactId>macula-boot-starter-web</artifactId>
</dependency>
<!-- 应用安全基础依赖包, 主要获取用户上下文及接口安全拦截等 -->
<dependency>
    <groupId>dev.macula.boot</groupId>
    <artifactId>macula-boot-starter-security</artifactId>
</dependency>
<!-- 基于swgger的doc 基础依赖包 -->
<dependency>
    <groupId>dev.macula.boot</groupId>
    <artifactId>macula-boot-starter-springdoc</artifactId>
</dependency>
<!-- mysql,druid,mybatis-plus 组成的基础依赖包 -->
<dependency>
    <groupId>dev.macula.boot</groupId>
    <artifactId>macula-boot-starter-mybatis-plus</artifactId>
</dependency>
<!-- 基于mapstruct 基础依赖包 -->
<dependency>
    <groupId>dev.macula.boot</groupId>
    <artifactId>macula-boot-starter-mapstruct</artifactId>
</dependency>
<!-- 基于redission组成的基础依赖包 -->
<dependency>
    <groupId>dev.macula.boot</groupId>
    <artifactId>macula-boot-starter-redis</artifactId>
</dependency>
<!-- alibaba 微服务治理体系组成的基础依赖包 -->
<dependency>
    <groupId>dev.macula.boot</groupId>
    <artifactId>macula-boot-starter-cloud-alibaba</artifactId>
</dependency>
<!-- Macula V5 获取应用菜单及登录用户相关信息必须依赖 -->
<dependency>
    <groupId>dev.macula.boot</groupId>
    <artifactId>macula-boot-starter-system</artifactId>
</dependency>
<!-- Macula V5 规范的应用分层实践依赖 -->
<dependency>
    <groupId>dev.macula.samples</groupId>
    <artifactId>macula-samples-service1-api</artifactId>
    <version>${project.version}</version>
</dependency>
  • 数据库表设计

应用实体表:

image

CREATE TABLE `application`
(
    `id` bigint NOT NULL AUTO_INCREMENT,
    `application_name` varchar(55) NOT NULL,
    `code`             varchar(55) NOT NULL COMMENT '应用编码',
    `sk`               varchar(55) NOT NULL COMMENT '应用密钥',
    `homepage`         varchar(55)          DEFAULT NULL,
    `manager`          varchar(55) NOT NULL COMMENT '负责人',
    `maintainer`       varchar(255)         DEFAULT NULL COMMENT '维护人',
    `mobile`           varchar(20) NOT NULL COMMENT '联系方式',
    `access_path`      varchar(255)         DEFAULT NULL COMMENT '可访问路径',
    `create_by`        varchar(50) NOT NULL DEFAULT '*SYSADM' COMMENT '创建人',
    `create_time`      datetime    NOT NULL COMMENT '创建时间',
    `last_update_by`   varchar(50) NOT NULL DEFAULT '*SYSADM' COMMENT '更新人',
    `last_update_time` datetime    NOT NULL COMMENT '更新时间',    
    PRIMARY KEY (`id`),
    UNIQUE KEY `sys_application_tenant_code_IDX` (`code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='应用信息表';

秘钥实体表:

image

 CREATE TABLE `kms` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '表id,自增主键',
  `app_id` varchar(100) NOT NULL COMMENT '应用id,密钥所属应用',
  `key_name` varchar(30) NOT NULL COMMENT '密钥名称',
  `public_key` longtext NOT NULL COMMENT '密钥公钥',
  `private_key` longtext COMMENT '密钥私钥',
  `create_by` varchar(50) NOT NULL COMMENT '创建人, 自动填充字段',
  `create_time` datetime NOT NULL COMMENT '创建时间, 自动填充字段',
  `last_update_by` varchar(50) NOT NULL COMMENT '最后修改人, 自动填充字段',
  `last_update_time` datetime NOT NULL COMMENT '最后修改时间, 自动填充字段',
  PRIMARY KEY (`id`),
  KEY `key_name` (`key_name`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='密钥表';
  • 数据存取层开发

(query、form、entity和bo对象的定义,mapper的method定义及实现)

entity常用注解说明:

@TableName("kms") // 类注解、标识该entity对应的数据库表为kms
@TableLogic(value = "0", delval = "1") //字段注解、标识作为逻辑删除字段,当使用框架Dao方法查询时默认过滤该字段为1的数据
@TableField(exist = false) //字段注解、标识字段对应的字段名、填充支持及不检查字段存在等等
@TableId( value = "id", type = IdType.AUTO ) // 字段注解、标识字段作为实体主键

entity编码:

// 实体类基类
@Getter
@Setter
public class BaseEntity implements Serializable { 
    private static final long serialVersionUID = 1L;
    
    @JsonSerialize(using = ToStringSerializer.class)
    @Schema(description = "主键id")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private String createBy;

    @Schema(description = "更新人")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String lastUpdateBy;

    @Schema(description = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime lastUpdateTime;

}
//应用实体类
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("application")
public class Application extends BaseEntity {

    /**
     * 应用名称
     */
    private String applicationName;

    /**
     * 应用主页URL
     */
    private String homepage;

    /**
     * 应用SecretKey
     */
    private String sk;

    /**
     * 负责人
     */
    private String manager;

    /**
     * 维护人
     */
    private String maintainer;

    /**
     * 移动电话
     */
    private String mobile;

    /**
     * 应用编码
     */
    private String code;

    /**
     * 可访问路径正则
     */
    private String accessPath;
    
    /**
     * 是否允许回传属性
     */
    private boolean useAttrs;

    /**
     * 允许回传的属性
     */
    private String allowedAttrs;
}
//秘钥实体类
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("kms")
public class Kms  extends BaseEntity {
    /**
     * 应用id,密钥所属应用
     */
    private Long appId;

    @Schema(
            description = "密钥名称"
    )
    /**
     * 密钥名称
     */
    private String keyName;

    @Schema(
            description = "密钥公钥"
    )
    @TableField(fill = FieldFill.INSERT, updateStrategy = FieldStrategy.NEVER)
    private String publicKey;

    @Schema(
            description = "密钥私钥"
    )
    @TableField(fill = FieldFill.INSERT, updateStrategy = FieldStrategy.NEVER)
    private String privateKey;
}

bo编码:

// ApplicationBo
@Data
public class ApplicationBO {

    private Long id;

    /**
     * 应用名
     */
    private String applicationName;

    private String sk;

    /**
     * 主页
     */
    private String homepage;

    /**
     * 负责人
     */
    private String manager;

    /**
     * 维护人
     */
    private String maintainer;

    /**
     * 手机号
     */
    private String mobile;

    /**
     * 应用编码
     */
    private String code;

    /**
     * 可访问url
     */
    private String accessPath;

    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date createTime;

    /**
     * 是否回传属性
     */
    private boolean useAttrs;

    /**
     * 回传属性列表
     */
    private String allowedAttrs;
}

// KmsBo 
@Schema(description = "密钥应用多表查询对象")
@Data
public class KmsBo implements Serializable {

    @Schema(description = "ID")
    private Long id;

    @Schema(description = "应用对应ID")
    private Long appId;

    @Schema(description = "应用编码")
    private String appCode;

    @Schema(description = "应用铝盘")
    private String appName;

    @Schema(description = "密钥名称")
    private String keyName;
}

Mapper编码:

// ApplicationMapper
@Mapper
public interface ApplicationMapper extends BaseMapper<Application> {
    Page<ApplicationBO> listApplicationPages(IPage<Application> page, ApplicationPageQuery queryParams);
}

// KmsMapper
@Mapper
public interface KmsMapper extends BaseMapper<Kms> {
    Page<KmsBo> listKmsPages(IPage<KmsBo> page, KmsPageQuery queryParams);
}

自定义sql编写:

在resources下创建文件mapper/KmsMapper.xml,编写内容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dev.macula.samples.service2.mapper.KmsMapper">

    <select id="listKmsPages" resultType="dev.macula.samples.service2.pojo.bo.KmsBo">
        SELECT
        skt.id as id,
        sat.id as app_id,
        code as app_code,
        sat.application_name as app_name,
        key_name
        FROM
        kms skt
        LEFT JOIN
        application sat
        ON skt.app_id = sat.id
        <where>
            <if test='queryParams.keywords !=null  and queryParams.keywords.trim() neq ""'>
                AND (skt.key_name like concat('%',#{queryParams.keywords},'%')) OR
                (sat.code like concat('%',#{queryParams.keywords},'%'))
            </if>
        </where>
    </select>
</mapper>

在resources下创建文件mapper/ApplicationMapper.xml,编写内容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dev.macula.samples.service2.mapper.ApplicationMapper">

    <select id="listApplicationPages" resultType="dev.macula.samples.service2.pojo.bo.ApplicationBO">
        SELECT
        id,
        application_name,
        sk,
        homepage,
        code,
        access_path,
        manager,
        maintainer,
        mobile,
        create_time,
        use_attrs,
        allowed_attrs
        FROM
        application
        <where>
            <if test='queryParams.keywords !=null  and queryParams.keywords.trim() neq ""'>
                AND (application_name like concat('%',#{queryParams.keywords},'%')) OR
                (code like concat('%',#{queryParams.keywords},'%'))
            </if>
        </where>
    </select>
</mapper>
  • 业务逻辑层开发

(query、form、vo对象定义,service的method定义及实现)

service接口编码:

// Application
/**
 * 应用业务接口
 */
public interface ApplicationService extends IService<Application> {

    /**
     * 应用分页列表
     *
     * @return 应用列表
     */
    Page<ApplicationVO> listApplicationPages(ApplicationPageQuery queryParams);

    /**
     * 新增应用
     *
     * @param appForm 应用表单对象
     * @return 是否成功, boolean
     */
    boolean saveApplication(ApplicationForm appForm);

    /**
     * 修改应用
     *
     * @param appId   应用ID
     * @param appForm 应用表单对象
     * @return 是否成功,boolean
     */
    boolean updateApplication(Long appId, ApplicationForm appForm);

    /**
     * 删除应用
     *
     * @param idsStr 应用ID,多个以英文逗号(,)分割
     * @return 是否成功,boolean
     */
    boolean deleteApplications(String idsStr);

    /**
     * 管理维护人
     *
     * @param appId
     * @param appForm
     * @return 是否成功,boolean
     */
    boolean addMaintainer(Long appId, ApplicationForm appForm);
}

// KmsService
 /**
 * 密钥业务接口
 */
public interface KmsService extends IService<Kms> {
    /**
     * 密钥分页列表
     *
     * @return 密钥列表
     */
    Page<KmsVo> listKmss(KmsPageQuery queryParams);

    /**
     * 新增密钥
     *
     * @param kmsForm 密钥表单对象
     * @return 是否成功, boolean
     */
    boolean saveKms(KmsForm kmsForm);

    /**
     * 修改密钥
     *
     * @param kmsId   密钥ID
     * @param kmsForm 密钥表单对象
     * @return 是否成功,boolean
     */
    boolean updateKms(Long kmsId, KmsForm kmsForm);

    /**
     * 删除密钥
     *
     * @param idsStr 密钥ID,多个以英文逗号(,)分割
     * @return 是否成功,boolean
     */
    boolean deleteKmss(String idsStr);

    /**
     * 应用id中是否存在绑定kms密钥
     * @param idsStr 应用ID,多个以英文逗号(,)分割
     * @return
     */
    boolean existsApp(String idsStr);
}

serviceImpl接口实现编码:

// ApplicationServcieImpl
@Service
@RequiredArgsConstructor
public class ApplicationServiceImpl extends ServiceImpl<ApplicationMapper, Application> implements ApplicationService {

    private final ApplicationConverter applicationConverter;
    private final KmsService kmsService;
    @Override
    public Page<ApplicationVO> listApplicationPages(ApplicationPageQuery queryParams) {
        Page<Application> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
        Page<ApplicationBO> bo = this.baseMapper.listApplicationPages(page, queryParams);;
        return applicationConverter.bo2Vo(bo);
    }

    @Override
    @Transactional
    public boolean saveApplication(ApplicationForm appForm) {
        Application application = applicationConverter.form2Entity(appForm);
        return this.save(application);
    }

    @Override
    @Transactional
    public boolean updateApplication(Long appId, ApplicationForm appForm) {
        Application application = applicationConverter.form2Entity(appForm);
        application.setId(appId);
        return this.updateById(application);
    }

    @Override
    @Transactional
    public boolean deleteApplications(String idsStr) {
        Assert.isTrue(StrUtil.isNotBlank(idsStr), "删除的应用数据为空");
        Assert.isFalse(kmsService.existsApp(idsStr), "应用数据被绑定删除失败");
        // 逻辑删除
        List<Long> ids = Arrays.stream(idsStr.split(",")).map(Long::parseLong).collect(Collectors.toList());
        return this.removeByIds(ids);
    }

    @Override
    @Transactional
    public boolean addMaintainer(Long appId, ApplicationForm appForm) {
        Application application = this.getById(appId);
        application.setMaintainer(appForm.getMaintainer());
        return this.updateById(application);
    }
}

// KmsServiceImpl
@Service
@RequiredArgsConstructor
public class KmsServiceImpl extends ServiceImpl<KmsMapper, Kms> implements KmsService {

    private final KmsConverter kmsConverter;

    @Override
    public Page<KmsVo> listKmss(KmsPageQuery queryParams) {
        Page<KmsBo> page = new Page(queryParams.getPageNum(), queryParams.getPageSize());
        Page<KmsBo> bo = this.baseMapper.listKmsPages(page, queryParams);
        return kmsConverter.bo2Vo(bo);
    }

    @Override
    @Transactional
    public boolean saveKms(KmsForm kmsForm) {
        Kms kms = kmsConverter.form2Entity(kmsForm);
        Map<String, String> keyPair = RSAUtil.getKeyPair();
        Assert.notEmpty(keyPair, "生成RSA密钥对失败!");
        kms.setPublicKey(keyPair.get("publicKey"));
        kms.setPrivateKey(keyPair.get("privateKey"));
        return this.save(kms);
    }

    @Override
    @Transactional
    public boolean updateKms(Long kmsId, KmsForm kmsForm) {
        Kms kms = kmsConverter.form2Entity(kmsForm);
        kms.setId(kmsId);
        return this.updateById(kms);
    }

    @Override
    @Transactional
    public boolean deleteKmss(String idsStr) {
        Assert.isTrue(StrUtil.isNotBlank(idsStr), "删除的密钥数据为空");
        // 逻辑删除
        List<Long> ids = Arrays.stream(idsStr.split(",")).map(Long::parseLong).collect(Collectors.toList());
        return this.removeByIds(ids);
    }

    @Override
    public boolean existsApp(String idsStr) {
        List<Long> ids = Arrays.stream(idsStr.split(",")).map(Long::parseLong).collect(Collectors.toList());
        return exists(new LambdaQueryWrapper<Kms>().in(Kms::getAppId, ids));
    }
}
  • 展示层开发

(query、form、vo对象定义,controller的method定义及实现)

将传递进来的数据进行持久化以及将处理好的数据显示回去。传递进入的数据结构按业务分为1、Form对象(表单对象处理新增、修改)2、Query对象(查询对象处理查询入参)。显示对象包含基础响应对象加显示层返回对象,显示层返回对象主要分基础数据类型及Vo对象。

Form对象编码:

// ApplicationForm
@Schema(description = "应用表单对象")
@Data
public class ApplicationForm {

    @Schema(description = "应用名称")
    @NotBlank(message = "应用名称不能为空")
    private String applicationName;

    @Schema(description = "ak")
    private String ak;

    @Schema(description = "sk")
    private String sk;

    @Schema(description = "主页")
    private String homepage;

    @Schema(description = "应用编码")
    @NotBlank(message = "应用编码不能为空")
    private String code;

    @Schema(description = "可访问路径")
    private String accessPath;

    @Schema(description = "负责人")
    @NotBlank(message = "负责人不能为空")
    private String manager;

    @Schema(description = "维护人")
    private String maintainer;

    @Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "{phone.valid}")
    private String mobile;

    @Schema(description = "是否回传属性, true:回传否则不回传")
    private boolean useAttrs;

    @Schema(description = "回传属性列表")
    private String allowedAttrs;
}

// KmsForm
@Schema(description = "密钥表单对象")
@Data
public class KmsForm {
    @Schema(description = "ID")
    private Long id;
    @Schema(description = "应用ID")
    private Long appId;
    @Schema(description = "密钥名称")
    private String keyName;
}

Query对象编码:

// 基础分页query对象
@Data
@Schema
public class BasePageQuery {

    @Schema(description = "页码", example = "1")
    private int pageNum = 1;

    @Schema(description = "每页记录数", example = "10")
    private int pageSize = 10;
}

@Data
public class KmsPageQuery extends BasePageQuery {
    @Schema(description = "关键字(密钥名称)")
    private String keywords;
}

@EqualsAndHashCode(callSuper = true)
@Data
public class ApplicationPageQuery extends BasePageQuery {
    @Schema(description = "关键字(应用名称/应用编码)")
    private String keywords;
}

基础响应对象编码:

@Data
public class Result<T> implements Serializable {

    private boolean success;
    private String code;
    private String msg;
    private T data;

    public static <T> Result<T> success() {
        return success(null);
    }
    public static <T> Result<T> success(T data) {
        return success(data, ApiResultCode.SUCCESS.getMsg());
    }
    public static <T> Result<T> success(T data, String msg) {
        Result<T> result = new Result<>();
        result.setSuccess(true);
        result.setCode(ApiResultCode.SUCCESS.getCode());
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
    public static <T> Result<T> failed() {
        return failed(ApiResultCode.FAILED);
    }
    public static <T> Result<T> failed(ResultCode resultCode) {
        return failed(resultCode, null);
    }
    public static <T> Result<T> failed(ResultCode resultCode, T data) {
        // data是错误原因
        return failed(resultCode.getCode(), resultCode.getMsg(), data);
    }
    public static <T> Result<T> failed(String code, String msg, T data) {
        // data是错误原因
        Result<T> result = new Result<>();
        result.setSuccess(false);
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
    public static <T> Result<T> judge(boolean status) {
        if (status) {
            return success();
        } else {
            return failed();
        }
    }
}

vo对象编码:

// ApplicationVo
@Schema(description = "应用分页视图对象")
@Data
public class ApplicationVO {
    @Schema(description = "应用ID")
    private Long id;

    @Schema(description = "应用名")
    private String applicationName;

    private String sk;

    @Schema(description = "主页")
    private String homepage;

    @Schema(description = "负责人")
    private String manager;

    @Schema(description = "维护人")
    private String maintainer;

    @Schema(description = "联系方式")
    private String mobile;

    @Schema(description = "应用编码")
    private String code;

    @Schema(description = "可访问url")
    private String accessPath;

    @Schema(description = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH24:mm:ss")
    private LocalDateTime createTime;

    @Schema(description = "是否回传属性")
    private boolean useAttrs;

    @Schema(description = "回传属性列表")
    private String allowedAttrs;
}


// KmsVo
@Schema(description = "密钥视图对象")
@Data
public class KmsVo implements Serializable {

    @Schema(description = "ID")
    private Long id;

    @Schema(description = "应用对应ID")
    private Long appId;

    @Schema(description = "应用编码")
    private String appCode;

    @Schema(description = "应用铝盘")
    private String appName;

    @Schema(description = "密钥名称")
    private String keyName;
}

展示层编码:

// KmsController
@Tag(name = "密钥管理", description = "密钥管理")
@RestController
@RequestMapping("/api/v1/kms")
@RequiredArgsConstructor
public class KmsController {

    private final KmsService kmsService;

    @Operation(summary = "获取密钥列表分页")
    @Parameter(name = "查询参数")
    @GetMapping
    public Page<KmsVo> listKmss(KmsPageQuery queryParams) {
        return kmsService.listKmss(queryParams);
    }

    @Operation(summary = "新增密钥")
    @Parameter(name = "密钥表单数据")
    @PostMapping
    public boolean saveKms(@Valid @RequestBody KmsForm formData) {
        return kmsService.saveKms(formData);
    }

    @Operation(summary = "修改密钥")
    @Parameter(name = "密钥ID")
    @Parameter(name = "密钥表单数据")
    @PutMapping(value = "/{kmsId}")
    public boolean updateKms(@PathVariable Long kmsId, @Valid @RequestBody KmsForm formData) {
        return kmsService.updateKms(kmsId, formData);
    }

    @Operation(summary = "删除密钥")
    @Parameter(name = "密钥ID,多个以英文逗号(,)分割")
    @DeleteMapping("/{ids}")
    public boolean deleteKmss(@PathVariable("ids") String ids) {
        return kmsService.deleteKmss(ids);
    }

}

// ApplicationController
@Tag(name = "应用管理", description = "应用管理")
@RestController
@RequestMapping("/api/v1/app")
@RequiredArgsConstructor
public class ApplicationController {

    private final ApplicationService applicationService;

    @Operation(summary = "获取应用列表分页")
    @Parameter(name = "查询参数")
    @GetMapping
    public Page<ApplicationVO> listApplications(ApplicationPageQuery queryParams) {
        return applicationService.listApplicationPages(queryParams);
    }

    @Operation(summary = "新增应用")
    @Parameter(name = "应用表单数据")
    @PostMapping
    public boolean saveApplication(@Valid @RequestBody ApplicationForm formData) {
        return applicationService.saveApplication(formData);
    }

    @Operation(summary = "修改应用")
    @Parameter(name = "应用ID")
    @Parameter(name = "应用表单数据")
    @PutMapping(value = "/{appId}")
    public boolean updateApplication(@PathVariable Long appId, @Valid @RequestBody ApplicationForm formData) {
        return applicationService.updateApplication(appId, formData);
    }

    @Operation(summary = "删除应用")
    @Parameter(name = "应用ID,多个以英文逗号(,)分割")
    @DeleteMapping("/{ids}")
    public boolean deleteApplications(@PathVariable("ids") String ids) {
        boolean result = applicationService.deleteApplications(ids);
        return result;
    }

    @Operation(summary = "添加维护人")
    @Parameter(name = "应用ID")
    @Parameter(name = "应用表单数据")
    @PutMapping("/addMaintainer/{appId}")
    public boolean addMaintainer(@PathVariable Long appId, @RequestBody ApplicationForm formData) {
        return applicationService.addMaintainer(appId, formData);
    }
}

(3)前端开发流程

  • 项目结构

image

  • VUE页面

    • 应用管理

      app/index.vue

      <template>
      <el-container>
          <el-header>
          <div class="left-panel">
              <el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
              <el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0"
                      @click="batch_del"></el-button>
          </div>
          <div class="right-panel">
              <div class="right-panel-search">
              <el-input v-model="search.keywords" placeholder="应用名称 / 应用编码" clearable></el-input>
              <el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
              </div>
          </div>
          </el-header>
          <el-main class="nopadding">
          <scTable ref="table" :apiObj="apiObj" @selection-change="selectionChange" stripe>
              <el-table-column type="selection" width="50"></el-table-column>
              <el-table-column label="应用名称" prop="applicationName" width="150"></el-table-column>
              <el-table-column label="应用编码" prop="code" width="150"></el-table-column>
              <el-table-column label="主页" prop="homepage" width="170"></el-table-column>
              <el-table-column label="可访问路径" prop="accessPath" width="170"></el-table-column>
              <el-table-column label="secretKey" prop="sk" width="170"></el-table-column>
              <el-table-column label="负责人" prop="manager" width="120"></el-table-column>
              <el-table-column label="联系方式" prop="mobile" width="150"></el-table-column>
              <el-table-column label="创建时间" prop="createTime" width="170"></el-table-column>
              <el-table-column label="操作" fixed="right" align="right" width="120">
              <template #default="scope">
                  <el-button-group>
                  <el-button text type="primary" size="small" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
                  <el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
                      <template #reference>
                      <el-button text type="primary" size="small">删除</el-button>
                      </template>
                  </el-popconfirm>
                  </el-button-group>
              </template>
              </el-table-column>
          </scTable>
          </el-main>
      </el-container>
      <save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSuccess" @closed="dialog.save=false"></save-dialog>
      
      </template>
      
      <script>
      import saveDialog from './save'
      export default{
          name: "app",
          components: {
              saveDialog
          },
          data () {
              return {
                  dialog: {
                      save: false,
                      list: false,
                      show: false,
                  },
                  apiObj: this.$API.service2_application.application.listPages,
                  selection: [],
                  search: {
                      keywords: null,
                  }  
              }
          },
          methods: {
              //添加
              add() {
              this.dialog.save = true
              this.$nextTick(() => {
                  this.$refs.saveDialog.open()
              })
              },
              //编辑
              table_edit(row) {
              this.dialog.save = true
              this.$nextTick(() => {
                  this.$refs.saveDialog.open('edit').setData(row)
              })
              },
              //删除
              async table_del(row, index) {
              var reqData = row.id
              var res = await this.$API.service2_application.application.del.delete(reqData);
              if (res.success) {
                  //这里选择刷新整个表格 OR 插入/编辑现有表格数据
                  // this.$refs.table.tableData.splice(index, 1);
                  this.$refs.table.refresh()
                  ElMessage.success("删除成功")
              } else {
                  ElMessageBox.alert(res.cause || res.msg, "提示", {type: 'error'})
              }
              },
              //表格选择后回调事件
              selectionChange(selection) {
              this.selection = selection;
              },
              //批量删除
              async batch_del() {
              ElMessageBox.confirm(`确定删除选中的 ${this.selection.length} 项吗?`, '提示', {
                  type: 'warning'
              }).then(() => {
                  let ids = this.selection.map(item => item.id).join(",")
                  this.selection.forEach(item => {
                  this.$refs.table.tableData.forEach((itemI, indexI) => {
                      if (item.id === itemI.id) {
                      this.$refs.table.tableData.splice(indexI, 1)
                      }
                  })
                  })
                  if(ids){
                  var res = this.$API.service2_application.application.del.delete(ids)
                  }
                  //loading.close();
                  ElMessage.success("操作成功")
              }).catch(() => {
              })
              },
              //搜索
              async upsearch() {
              this.$refs.table.upData(this.search)
              },
              //本地更新数据
              handleSuccess(data, mode) {
              this.$refs.table.refresh()
              },
          }
      }
      </script>
      

      app/save.vue

      <template>
      <el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
          <el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px"
                  label-position="left">
          <el-form-item label="应用名称" prop="applicationName">
              <el-input v-model="form.applicationName" clearable></el-input>
          </el-form-item>
          <el-form-item label="应用编码" prop="code">
              <el-input v-model="form.code" clearable></el-input>
          </el-form-item>
          <el-form-item label="appKey" prop="ak">
              <el-input v-model="form.ak" disabled></el-input>
          </el-form-item>
          <el-form-item label="secretKey" prop="sk">
              <el-input v-model="form.sk" disabled></el-input>
          </el-form-item>
          <el-form-item label="主页" prop="code">
              <el-input v-model="form.homepage" clearable></el-input>
          </el-form-item>
          <el-form-item label="可访问路径" prop="accessPath">
              <el-input v-model="form.accessPath" clearable></el-input>
              <div class="el-form-item-msg">多个使用英文逗号,隔开,例: /a,/b,/c</div>
          </el-form-item>
          <el-form-item label="负责人" prop="manager">
              <el-input v-model="form.manager" clearable></el-input>
          </el-form-item>
          <el-form-item label="联系方式" prop="mobile">
              <el-input v-model="form.mobile" clearable></el-input>
          </el-form-item>
          </el-form>
          <template #footer>
          <el-button @click="visible=false">取 消</el-button>
          <el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
          </template>
      </el-dialog>
      </template>
      
      
      <script>
      export default {
      emits: ['success', 'closed'],
      data() {
          return {
          mode: "add",
          titleMap: {
              add: '新增应用',
              edit: '编辑应用'
          },
          visible: false,
          isSaveing: false,
          //表单数据
          form: {
              id: "",
              applicationName: "",
              ak: "",
              sk: "",
              homepage: "",
              code: "",
              manager: "",
              mobile: "",
              accessPath: ""
          },
          //验证规则
          rules: {
              applicationName: [
              {required: true, message: '请输入应用名称'}
              ],
              code: [
              {required: true, message: '请输入应用编码'}
              ],
              manager: [
              {required: true, message: '请输入负责人'}
              ],
              mobile: [
              {required: true, message: '请输入联系方式'}
              ]
          },
      
          }
      },
      methods: {
          //显示
          open(mode = 'add') {
          this.mode = mode;
          this.visible = true;
          if (this.mode == 'add') {
              this.form.ak = this.generateAkSK()
              this.form.sk = this.generateAkSK()
          }
          return this
          },
          //表单提交方法
          submit() {
          this.$refs.dialogForm.validate(async (valid) => {
              if (valid) {
              this.isSaveing = true;
              if (this.mode == 'add') {
                  // 调用相关接口
                  var res = await this.$API.service2_application.application.add.post(this.form);
              } else {
                  var res = await this.$API.service2_application.application.edit.put(this.form, this.form.id);
              }
              this.isSaveing = false;
              if (res.success) {
                  this.$emit('success', this.form, this.mode)
                  this.visible = false;
                  ElMessage.success("操作成功")
              } else {
                  ElMessageBox.alert(res.cause || res.msg, "提示", {type: 'error'})
              }
              } else {
              return false;
              }
          })
          },
          generateAkSK() {
          return this.generateHexString(26) + (new Date()).getTime();
          },
          generateHexString(length) {
          var ret = "";
          while (ret.length < length) {
              ret += Math.random().toString(16).substring(2);
          }
          return ret.substring(0, length);
          },
          //表单注入数据
          setData(data) {
          this.form.id = data.id
          this.form.applicationName = data.applicationName
          this.form.homepage = data.homepage
          this.form.code = data.code
          this.form.manager = data.manager
          this.form.accessPath = data.accessPath
          this.form.mobile = data.mobile
          this.form.ak = data.ak
          this.form.sk = data.sk
          //可以和上面一样单个注入,也可以像下面一样直接合并进去
          //Object.assign(this.form, data)
          }
      }
      }
      </script>
      
      <style>
      </style>
      
    • 应用管理

      kms/index.vue

      <template>
      <el-container>
          <el-header>
          <div class="left-panel">
              <el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
              <el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0"
                      @click="batch_del"></el-button>
          </div>
          <div class="right-panel">
              <div class="right-panel-search">
              <el-input v-model="search.keywords" placeholder="密钥名称 / 应用名称 / 应用编码" clearable></el-input>
              <el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
              </div>
          </div>
          </el-header>
          <el-main class="nopadding">
          <scTable ref="table" :apiObj="apiObj" @selection-change="selectionChange" stripe>
              <el-table-column type="selection" width="50"></el-table-column>
              <el-table-column label="密钥名称" prop="keyName" width="170"></el-table-column>
              <el-table-column label="应用名称" prop="appName" width="150"></el-table-column>
              <el-table-column label="应用编码" prop="appCode" width="150"></el-table-column>
              <el-table-column label="操作" fixed="right" align="right" width="120">
              <template #default="scope">
                  <el-button-group>
                  <el-button text type="primary" size="small" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
                  <el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
                      <template #reference>
                      <el-button text type="primary" size="small">删除</el-button>
                      </template>
                  </el-popconfirm>
                  </el-button-group>
              </template>
              </el-table-column>
          </scTable>
          </el-main>
      </el-container>
      <save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSuccess" @closed="dialog.save=false"></save-dialog>
      </template>
      
      <script>
      import saveDialog from './save'
      export default{
          name: "kms",
          components: {
          saveDialog
          },
          data () {
              return {
                  selection: [],
                  search: {
                      keywords: ''
                  },
                  dialog: {
                  save: false,
                  list: false,
                  show: false
                  },
                  apiObj: this.$API.service2_kms.kms.listPages
              }
          },
          methods: {
              //添加
              add() {
              this.dialog.save = true
              this.$nextTick(() => {
                  this.$refs.saveDialog.open()
              })
              },
              //编辑
              table_edit(row) {
              this.dialog.save = true
              this.$nextTick(() => {
                  this.$refs.saveDialog.open('edit').setData(row)
              })
              },
              //删除
              async table_del(row, index) {
              var reqData = row.id
              var res = await this.$API.service2_kms.kms.del.delete(reqData);
              if (res.success) {
                  //这里选择刷新整个表格 OR 插入/编辑现有表格数据
                  // this.$refs.table.tableData.splice(index, 1);
                  this.$refs.table.refresh()
                  ElMessage.success("删除成功")
              } else {
                  ElMessageBox.alert(res.cause || res.msg, "提示", {type: 'error'})
              }
              },
              //表格选择后回调事件
              selectionChange(selection) {
              this.selection = selection;
              },
              //批量删除
              async batch_del() {
              ElMessageBox.confirm(`确定删除选中的 ${this.selection.length} 项吗?`, '提示', {
                  type: 'warning'
              }).then(() => {
                  let ids = this.selection.map(item => item.id).join(",")
                  this.selection.forEach(item => {
                  this.$refs.table.tableData.forEach((itemI, indexI) => {
                      if (item.id === itemI.id) {
                      this.$refs.table.tableData.splice(indexI, 1)
                      }
                  })
                  })
                  if(ids){
                  var res = this.$API.service2_kms.kms.del.delete(ids)
                  }
                  //loading.close();
                  ElMessage.success("操作成功")
              }).catch(() => {
              })
              },
              //搜索
              async upsearch() {
              this.$refs.table.upData(this.search)
              },
              //本地更新数据
              handleSuccess(data, mode) {
              this.$refs.table.refresh()
              },
          }
      }
      </script>
      
      <style scoped>
      
      </style>
      

      kms/save.vue

      <template>
      <el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
          <el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px"
                  label-position="left">
          <el-form-item label="密钥名称" prop="keyName">
              <el-input v-model="form.keyName" placeholder="请输入密钥名称" clearable></el-input>
          </el-form-item>
          <el-form-item label="所属应用" prop="appId">
                      <sc-table-select :placeholder="form.appName" v-model="form.scTableApp" :disabled="mode=='edit'" :apiObj="apiObj" :table-width="250" :props="props">
                          <el-table-column prop="code" label="应用编码" width="200"></el-table-column>
              <el-table-column prop="applicationName" label="应用名称" width="200"></el-table-column>
                      </sc-table-select>
                  </el-form-item>
          </el-form>
          <template #footer>
          <el-button @click="visible=false">取 消</el-button>
          <el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
          </template>
      </el-dialog>
      </template>
      
      
      <script>
      export default {
      emits: ['success', 'closed'],
      data() {
          return {
          mode: "add",
          titleMap: {
              add: '新增密钥',
              edit: '编辑密钥'
          },
          visible: false,
          isSaveing: false,
          apiObj: this.$API.service2_application.application.listPages,
          //表单数据
          form: {
              id: "",
              keyName: "",
              appId: "",
              appName: "",
              appCode: "",
              scTableApp: {}
          },
          //验证规则
          rules: {
              keyName: [
              {required: true, message: '请输入密钥名称'}
              ],
              applicationId: [
              {required: true, message: '请选择应用编码'}
              ]
          },
          props: {
              label: 'applicationName',
              value: 'id',
          },
          }
      },
      methods: {
          //显示
          open(mode = 'add') {
          this.mode = mode;
          this.visible = true;
          return this
          },
          //表单提交方法
          submit() {
          this.$refs.dialogForm.validate(async (valid) => {
              if (valid) {
              this.isSaveing = true;
              this.form.appId = this.form.scTableApp.id
              if (this.mode == 'add') {
                  // 调用相关接口
                  var res = await this.$API.service2_kms.kms.add.post(this.form);
              } else {
                  var res = await this.$API.service2_kms.kms.edit.put(this.form, this.form.id);
              }
              this.isSaveing = false;
              if (res.success) {
                  this.$emit('success', this.form, this.mode)
                  this.visible = false;
                  ElMessage.success("操作成功")
              } else {
                  ElMessageBox.alert(res.cause || res.msg, "提示", {type: 'error'})
              }
              } else {
              return false;
              }
          })
          },
          //表单注入数据
          setData(data) {
          this.form.id = data.id
          this.form.keyName = data.keyName
          this.form.appId = data.appId
          this.form.appCode = data.appCode
          this.form.appName = data.appName
          this.form.scTableApp = {
              id: this.form.appId,
              applicationName: this.form.appName,
              code: this.form.appCode
          }
          //可以和上面一样单个注入,也可以像下面一样直接合并进去
          //Object.assign(this.form, data)
          }
      }
      }
      </script>
      
      <style>
      </style>
      
  • JS代码

config/index.js

    ......
    MODEL: {
        system: 'system',
        oss: 'oss',
        admin: 'admin',
        service2: 'service2',
    },
    ......

application.js

import config from "@/config"
import http from "@/utils/request"

export default {
    application: {
        listPages: {
            url: `${config.API_URL}/${config.MODEL.service2}/api/v1/app`,
            name: "应用分页列表",
            get: async function (params) {
                return await http.get(this.url, params);
            }
        },
        add: {
            url: `${config.API_URL}/${config.MODEL.service2}/api/v1/app`,
            name: "添加应用",
            post: async function (data = {}) {
                return await http.post(this.url, data)
            }
        },
        edit: {
            url: `${config.API_URL}/${config.MODEL.service2}/api/v1/app`,
            name: "编辑应用",
            put: async function (data = {}, id) {
                var reqUrl = this.url + '/' + id
                return await http.put(reqUrl, data)
            }
        },
        del: {
            url: `${config.API_URL}/${config.MODEL.service2}/api/v1/app`,
            name: "删除应用",
            delete: async function (data = {}) {
                var reqUrl = this.url + '/' + data
                return await http.delete(reqUrl, data)
            }
        },
    },
}

kms.js

import config from '@/config'
import http from '@/utils/request'

export default{
    kms: {
        listPages: {
            url: `${config.API_URL}/${config.MODEL.service2}/api/v1/kms`,
            name: "密钥分页列表",
            get: async function (params) {
                return await http.get(this.url, params);
            }
        },
        add: {
            url: `${config.API_URL}/${config.MODEL.service2}/api/v1/kms`,
            name: "添加密钥",
            post: async function (data = {}) {
                return await http.post(this.url, data)
            }
        },
        edit: {
            url: `${config.API_URL}/${config.MODEL.service2}/api/v1/kms`,
            name: "编辑密钥",
            put: async function (data = {}, id) {
                var reqUrl = this.url + '/' + id
                return await http.put(reqUrl, data)
            }
        },
        del: {
            url: `${config.API_URL}/${config.MODEL.service2}/api/v1/kms`,
            name: "删除密钥",
            delete: async function (data = {}) {
                var reqUrl = this.url + '/' + data
                return await http.delete(reqUrl, data)
            }
        },
    }
}