feat(系统设置): 插件管理接口实现
--task=1012390 --user=陈建星 系统设置-系统-插件管理-后台 https://www.tapd.cn/55049933/s/1401201
This commit is contained in:
parent
05d5dec709
commit
7e4182549a
|
@ -2,8 +2,10 @@ package io.metersphere.listener;
|
|||
|
||||
import io.metersphere.api.event.APIEventSource;
|
||||
import io.metersphere.plan.listener.ExecEventListener;
|
||||
import io.metersphere.sdk.service.PluginLoadService;
|
||||
import io.metersphere.sdk.util.CommonBeanFactory;
|
||||
import io.metersphere.sdk.util.LogUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -11,6 +13,9 @@ import org.springframework.stereotype.Component;
|
|||
@Component
|
||||
public class AppStartListener implements ApplicationRunner {
|
||||
|
||||
@Resource
|
||||
private PluginLoadService pluginLoadService;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
LogUtils.info("================= 应用启动 =================");
|
||||
|
@ -32,5 +37,8 @@ public class AppStartListener implements ApplicationRunner {
|
|||
// 触发事件
|
||||
apiEventSource.fireEvent("API", "Event after removing the listener test.");
|
||||
//loadEventSource.fireEvent("LOAD","Event after removing the listener.");
|
||||
|
||||
// 加载插件
|
||||
pluginLoadService.loadPlugins();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,26 +9,30 @@ import java.util.Arrays;
|
|||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PluginFrontScript implements Serializable {
|
||||
public class PluginScript implements Serializable {
|
||||
@Schema(title = "插件的ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{plugin_front_script.plugin_id.not_blank}", groups = {Created.class})
|
||||
@Size(min = 1, max = 50, message = "{plugin_front_script.plugin_id.length_range}", groups = {Created.class, Updated.class})
|
||||
@NotBlank(message = "{plugin_script.plugin_id.not_blank}", groups = {Created.class})
|
||||
@Size(min = 1, max = 50, message = "{plugin_script.plugin_id.length_range}", groups = {Created.class, Updated.class})
|
||||
private String pluginId;
|
||||
|
||||
@Schema(title = "插件中对应表单配置的ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{plugin_front_script.script_id.not_blank}", groups = {Created.class})
|
||||
@Size(min = 1, max = 50, message = "{plugin_front_script.script_id.length_range}", groups = {Created.class, Updated.class})
|
||||
@NotBlank(message = "{plugin_script.script_id.not_blank}", groups = {Created.class})
|
||||
@Size(min = 1, max = 50, message = "{plugin_script.script_id.length_range}", groups = {Created.class, Updated.class})
|
||||
private String scriptId;
|
||||
|
||||
@Schema(title = "插件中对应表单配置的名称")
|
||||
private String name;
|
||||
|
||||
@Schema(title = "脚本内容")
|
||||
private String script;
|
||||
private byte[] script;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum Column {
|
||||
pluginId("plugin_id", "pluginId", "VARCHAR", false),
|
||||
scriptId("script_id", "scriptId", "VARCHAR", false),
|
||||
script("script", "script", "LONGVARCHAR", false);
|
||||
name("name", "name", "VARCHAR", true),
|
||||
script("script", "script", "LONGVARBINARY", false);
|
||||
|
||||
private static final String BEGINNING_DELIMITER = "`";
|
||||
|
|
@ -3,14 +3,14 @@ package io.metersphere.system.domain;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PluginFrontScriptExample {
|
||||
public class PluginScriptExample {
|
||||
protected String orderByClause;
|
||||
|
||||
protected boolean distinct;
|
||||
|
||||
protected List<Criteria> oredCriteria;
|
||||
|
||||
public PluginFrontScriptExample() {
|
||||
public PluginScriptExample() {
|
||||
oredCriteria = new ArrayList<Criteria>();
|
||||
}
|
||||
|
||||
|
@ -243,6 +243,76 @@ public class PluginFrontScriptExample {
|
|||
addCriterion("script_id not between", value1, value2, "scriptId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameIsNull() {
|
||||
addCriterion("`name` is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameIsNotNull() {
|
||||
addCriterion("`name` is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameEqualTo(String value) {
|
||||
addCriterion("`name` =", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameNotEqualTo(String value) {
|
||||
addCriterion("`name` <>", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameGreaterThan(String value) {
|
||||
addCriterion("`name` >", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameGreaterThanOrEqualTo(String value) {
|
||||
addCriterion("`name` >=", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameLessThan(String value) {
|
||||
addCriterion("`name` <", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameLessThanOrEqualTo(String value) {
|
||||
addCriterion("`name` <=", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameLike(String value) {
|
||||
addCriterion("`name` like", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameNotLike(String value) {
|
||||
addCriterion("`name` not like", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameIn(List<String> values) {
|
||||
addCriterion("`name` in", values, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameNotIn(List<String> values) {
|
||||
addCriterion("`name` not in", values, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameBetween(String value1, String value2) {
|
||||
addCriterion("`name` between", value1, value2, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameNotBetween(String value1, String value2) {
|
||||
addCriterion("`name` not between", value1, value2, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Criteria extends GeneratedCriteria {
|
|
@ -1,38 +0,0 @@
|
|||
package io.metersphere.system.mapper;
|
||||
|
||||
import io.metersphere.system.domain.PluginFrontScript;
|
||||
import io.metersphere.system.domain.PluginFrontScriptExample;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface PluginFrontScriptMapper {
|
||||
long countByExample(PluginFrontScriptExample example);
|
||||
|
||||
int deleteByExample(PluginFrontScriptExample example);
|
||||
|
||||
int deleteByPrimaryKey(@Param("pluginId") String pluginId, @Param("scriptId") String scriptId);
|
||||
|
||||
int insert(PluginFrontScript record);
|
||||
|
||||
int insertSelective(PluginFrontScript record);
|
||||
|
||||
List<PluginFrontScript> selectByExampleWithBLOBs(PluginFrontScriptExample example);
|
||||
|
||||
List<PluginFrontScript> selectByExample(PluginFrontScriptExample example);
|
||||
|
||||
PluginFrontScript selectByPrimaryKey(@Param("pluginId") String pluginId, @Param("scriptId") String scriptId);
|
||||
|
||||
int updateByExampleSelective(@Param("record") PluginFrontScript record, @Param("example") PluginFrontScriptExample example);
|
||||
|
||||
int updateByExampleWithBLOBs(@Param("record") PluginFrontScript record, @Param("example") PluginFrontScriptExample example);
|
||||
|
||||
int updateByExample(@Param("record") PluginFrontScript record, @Param("example") PluginFrontScriptExample example);
|
||||
|
||||
int updateByPrimaryKeySelective(PluginFrontScript record);
|
||||
|
||||
int updateByPrimaryKeyWithBLOBs(PluginFrontScript record);
|
||||
|
||||
int batchInsert(@Param("list") List<PluginFrontScript> list);
|
||||
|
||||
int batchInsertSelective(@Param("list") List<PluginFrontScript> list, @Param("selective") PluginFrontScript.Column ... selective);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package io.metersphere.system.mapper;
|
||||
|
||||
import io.metersphere.system.domain.PluginScript;
|
||||
import io.metersphere.system.domain.PluginScriptExample;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface PluginScriptMapper {
|
||||
long countByExample(PluginScriptExample example);
|
||||
|
||||
int deleteByExample(PluginScriptExample example);
|
||||
|
||||
int deleteByPrimaryKey(@Param("pluginId") String pluginId, @Param("scriptId") String scriptId);
|
||||
|
||||
int insert(PluginScript record);
|
||||
|
||||
int insertSelective(PluginScript record);
|
||||
|
||||
List<PluginScript> selectByExampleWithBLOBs(PluginScriptExample example);
|
||||
|
||||
List<PluginScript> selectByExample(PluginScriptExample example);
|
||||
|
||||
PluginScript selectByPrimaryKey(@Param("pluginId") String pluginId, @Param("scriptId") String scriptId);
|
||||
|
||||
int updateByExampleSelective(@Param("record") PluginScript record, @Param("example") PluginScriptExample example);
|
||||
|
||||
int updateByExampleWithBLOBs(@Param("record") PluginScript record, @Param("example") PluginScriptExample example);
|
||||
|
||||
int updateByExample(@Param("record") PluginScript record, @Param("example") PluginScriptExample example);
|
||||
|
||||
int updateByPrimaryKeySelective(PluginScript record);
|
||||
|
||||
int updateByPrimaryKeyWithBLOBs(PluginScript record);
|
||||
|
||||
int updateByPrimaryKey(PluginScript record);
|
||||
|
||||
int batchInsert(@Param("list") List<PluginScript> list);
|
||||
|
||||
int batchInsertSelective(@Param("list") List<PluginScript> list, @Param("selective") PluginScript.Column ... selective);
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
<?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="io.metersphere.system.mapper.PluginFrontScriptMapper">
|
||||
<resultMap id="BaseResultMap" type="io.metersphere.system.domain.PluginFrontScript">
|
||||
<mapper namespace="io.metersphere.system.mapper.PluginScriptMapper">
|
||||
<resultMap id="BaseResultMap" type="io.metersphere.system.domain.PluginScript">
|
||||
<id column="plugin_id" jdbcType="VARCHAR" property="pluginId" />
|
||||
<id column="script_id" jdbcType="VARCHAR" property="scriptId" />
|
||||
<result column="name" jdbcType="VARCHAR" property="name" />
|
||||
</resultMap>
|
||||
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.system.domain.PluginFrontScript">
|
||||
<result column="script" jdbcType="LONGVARCHAR" property="script" />
|
||||
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.system.domain.PluginScript">
|
||||
<result column="script" jdbcType="LONGVARBINARY" property="script" />
|
||||
</resultMap>
|
||||
<sql id="Example_Where_Clause">
|
||||
<where>
|
||||
|
@ -67,12 +68,12 @@
|
|||
</where>
|
||||
</sql>
|
||||
<sql id="Base_Column_List">
|
||||
plugin_id, script_id
|
||||
plugin_id, script_id, `name`
|
||||
</sql>
|
||||
<sql id="Blob_Column_List">
|
||||
script
|
||||
</sql>
|
||||
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.system.domain.PluginFrontScriptExample" resultMap="ResultMapWithBLOBs">
|
||||
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.system.domain.PluginScriptExample" resultMap="ResultMapWithBLOBs">
|
||||
select
|
||||
<if test="distinct">
|
||||
distinct
|
||||
|
@ -80,7 +81,7 @@
|
|||
<include refid="Base_Column_List" />
|
||||
,
|
||||
<include refid="Blob_Column_List" />
|
||||
from plugin_front_script
|
||||
from plugin_script
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
|
@ -88,13 +89,13 @@
|
|||
order by ${orderByClause}
|
||||
</if>
|
||||
</select>
|
||||
<select id="selectByExample" parameterType="io.metersphere.system.domain.PluginFrontScriptExample" resultMap="BaseResultMap">
|
||||
<select id="selectByExample" parameterType="io.metersphere.system.domain.PluginScriptExample" resultMap="BaseResultMap">
|
||||
select
|
||||
<if test="distinct">
|
||||
distinct
|
||||
</if>
|
||||
<include refid="Base_Column_List" />
|
||||
from plugin_front_script
|
||||
from plugin_script
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
|
@ -107,29 +108,29 @@
|
|||
<include refid="Base_Column_List" />
|
||||
,
|
||||
<include refid="Blob_Column_List" />
|
||||
from plugin_front_script
|
||||
from plugin_script
|
||||
where plugin_id = #{pluginId,jdbcType=VARCHAR}
|
||||
and script_id = #{scriptId,jdbcType=VARCHAR}
|
||||
</select>
|
||||
<delete id="deleteByPrimaryKey" parameterType="map">
|
||||
delete from plugin_front_script
|
||||
delete from plugin_script
|
||||
where plugin_id = #{pluginId,jdbcType=VARCHAR}
|
||||
and script_id = #{scriptId,jdbcType=VARCHAR}
|
||||
</delete>
|
||||
<delete id="deleteByExample" parameterType="io.metersphere.system.domain.PluginFrontScriptExample">
|
||||
delete from plugin_front_script
|
||||
<delete id="deleteByExample" parameterType="io.metersphere.system.domain.PluginScriptExample">
|
||||
delete from plugin_script
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
</delete>
|
||||
<insert id="insert" parameterType="io.metersphere.system.domain.PluginFrontScript">
|
||||
insert into plugin_front_script (plugin_id, script_id, script
|
||||
)
|
||||
values (#{pluginId,jdbcType=VARCHAR}, #{scriptId,jdbcType=VARCHAR}, #{script,jdbcType=LONGVARCHAR}
|
||||
)
|
||||
<insert id="insert" parameterType="io.metersphere.system.domain.PluginScript">
|
||||
insert into plugin_script (plugin_id, script_id, `name`,
|
||||
script)
|
||||
values (#{pluginId,jdbcType=VARCHAR}, #{scriptId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
|
||||
#{script,jdbcType=LONGVARBINARY})
|
||||
</insert>
|
||||
<insert id="insertSelective" parameterType="io.metersphere.system.domain.PluginFrontScript">
|
||||
insert into plugin_front_script
|
||||
<insert id="insertSelective" parameterType="io.metersphere.system.domain.PluginScript">
|
||||
insert into plugin_script
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="pluginId != null">
|
||||
plugin_id,
|
||||
|
@ -137,6 +138,9 @@
|
|||
<if test="scriptId != null">
|
||||
script_id,
|
||||
</if>
|
||||
<if test="name != null">
|
||||
`name`,
|
||||
</if>
|
||||
<if test="script != null">
|
||||
script,
|
||||
</if>
|
||||
|
@ -148,19 +152,22 @@
|
|||
<if test="scriptId != null">
|
||||
#{scriptId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="name != null">
|
||||
#{name,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="script != null">
|
||||
#{script,jdbcType=LONGVARCHAR},
|
||||
#{script,jdbcType=LONGVARBINARY},
|
||||
</if>
|
||||
</trim>
|
||||
</insert>
|
||||
<select id="countByExample" parameterType="io.metersphere.system.domain.PluginFrontScriptExample" resultType="java.lang.Long">
|
||||
select count(*) from plugin_front_script
|
||||
<select id="countByExample" parameterType="io.metersphere.system.domain.PluginScriptExample" resultType="java.lang.Long">
|
||||
select count(*) from plugin_script
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
</select>
|
||||
<update id="updateByExampleSelective" parameterType="map">
|
||||
update plugin_front_script
|
||||
update plugin_script
|
||||
<set>
|
||||
<if test="record.pluginId != null">
|
||||
plugin_id = #{record.pluginId,jdbcType=VARCHAR},
|
||||
|
@ -168,8 +175,11 @@
|
|||
<if test="record.scriptId != null">
|
||||
script_id = #{record.scriptId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.name != null">
|
||||
`name` = #{record.name,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.script != null">
|
||||
script = #{record.script,jdbcType=LONGVARCHAR},
|
||||
script = #{record.script,jdbcType=LONGVARBINARY},
|
||||
</if>
|
||||
</set>
|
||||
<if test="_parameter != null">
|
||||
|
@ -177,49 +187,61 @@
|
|||
</if>
|
||||
</update>
|
||||
<update id="updateByExampleWithBLOBs" parameterType="map">
|
||||
update plugin_front_script
|
||||
update plugin_script
|
||||
set plugin_id = #{record.pluginId,jdbcType=VARCHAR},
|
||||
script_id = #{record.scriptId,jdbcType=VARCHAR},
|
||||
script = #{record.script,jdbcType=LONGVARCHAR}
|
||||
`name` = #{record.name,jdbcType=VARCHAR},
|
||||
script = #{record.script,jdbcType=LONGVARBINARY}
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
</if>
|
||||
</update>
|
||||
<update id="updateByExample" parameterType="map">
|
||||
update plugin_front_script
|
||||
update plugin_script
|
||||
set plugin_id = #{record.pluginId,jdbcType=VARCHAR},
|
||||
script_id = #{record.scriptId,jdbcType=VARCHAR}
|
||||
script_id = #{record.scriptId,jdbcType=VARCHAR},
|
||||
`name` = #{record.name,jdbcType=VARCHAR}
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
</if>
|
||||
</update>
|
||||
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.system.domain.PluginFrontScript">
|
||||
update plugin_front_script
|
||||
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.system.domain.PluginScript">
|
||||
update plugin_script
|
||||
<set>
|
||||
<if test="name != null">
|
||||
`name` = #{name,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="script != null">
|
||||
script = #{script,jdbcType=LONGVARCHAR},
|
||||
script = #{script,jdbcType=LONGVARBINARY},
|
||||
</if>
|
||||
</set>
|
||||
where plugin_id = #{pluginId,jdbcType=VARCHAR}
|
||||
and script_id = #{scriptId,jdbcType=VARCHAR}
|
||||
</update>
|
||||
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.system.domain.PluginFrontScript">
|
||||
update plugin_front_script
|
||||
set script = #{script,jdbcType=LONGVARCHAR}
|
||||
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.system.domain.PluginScript">
|
||||
update plugin_script
|
||||
set `name` = #{name,jdbcType=VARCHAR},
|
||||
script = #{script,jdbcType=LONGVARBINARY}
|
||||
where plugin_id = #{pluginId,jdbcType=VARCHAR}
|
||||
and script_id = #{scriptId,jdbcType=VARCHAR}
|
||||
</update>
|
||||
<update id="updateByPrimaryKey" parameterType="io.metersphere.system.domain.PluginScript">
|
||||
update plugin_script
|
||||
set `name` = #{name,jdbcType=VARCHAR}
|
||||
where plugin_id = #{pluginId,jdbcType=VARCHAR}
|
||||
and script_id = #{scriptId,jdbcType=VARCHAR}
|
||||
</update>
|
||||
<insert id="batchInsert" parameterType="map">
|
||||
insert into plugin_front_script
|
||||
(plugin_id, script_id, script)
|
||||
insert into plugin_script
|
||||
(plugin_id, script_id, `name`, script)
|
||||
values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.pluginId,jdbcType=VARCHAR}, #{item.scriptId,jdbcType=VARCHAR}, #{item.script,jdbcType=LONGVARCHAR}
|
||||
)
|
||||
(#{item.pluginId,jdbcType=VARCHAR}, #{item.scriptId,jdbcType=VARCHAR}, #{item.name,jdbcType=VARCHAR},
|
||||
#{item.script,jdbcType=LONGVARBINARY})
|
||||
</foreach>
|
||||
</insert>
|
||||
<insert id="batchInsertSelective" parameterType="map">
|
||||
insert into plugin_front_script (
|
||||
insert into plugin_script (
|
||||
<foreach collection="selective" item="column" separator=",">
|
||||
${column.escapedColumnName}
|
||||
</foreach>
|
||||
|
@ -234,8 +256,11 @@
|
|||
<if test="'script_id'.toString() == column.value">
|
||||
#{item.scriptId,jdbcType=VARCHAR}
|
||||
</if>
|
||||
<if test="'name'.toString() == column.value">
|
||||
#{item.name,jdbcType=VARCHAR}
|
||||
</if>
|
||||
<if test="'script'.toString() == column.value">
|
||||
#{item.script,jdbcType=LONGVARCHAR}
|
||||
#{item.script,jdbcType=LONGVARBINARY}
|
||||
</if>
|
||||
</foreach>
|
||||
)
|
|
@ -157,11 +157,12 @@ CREATE TABLE IF NOT EXISTS plugin
|
|||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT = '插件';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS plugin_front_script
|
||||
CREATE TABLE IF NOT EXISTS plugin_script
|
||||
(
|
||||
`plugin_id` VARCHAR(50) NOT NULL COMMENT '插件的ID' ,
|
||||
`script_id` VARCHAR(50) NOT NULL COMMENT '插件中对应表单配置的ID' ,
|
||||
`script` TEXT COMMENT '脚本内容' ,
|
||||
`name` VARCHAR(255) COMMENT '插件中对应表单配置的名称' ,
|
||||
`script` LONGBLOB COMMENT '脚本内容' ,
|
||||
PRIMARY KEY (plugin_id,script_id)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
|
|
|
@ -4,10 +4,6 @@ import io.metersphere.plugin.sdk.api.AbstractMsPlugin;
|
|||
|
||||
public abstract class AbstractApiPlugin extends AbstractMsPlugin {
|
||||
private static final String API_PLUGIN_TYPE = "API";
|
||||
public AbstractApiPlugin(ClassLoader pluginClassLoader) {
|
||||
super(pluginClassLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return API_PLUGIN_TYPE;
|
||||
|
|
|
@ -4,10 +4,6 @@ import io.metersphere.plugin.sdk.api.AbstractMsPlugin;
|
|||
|
||||
public abstract class AbstractPlatformPlugin extends AbstractMsPlugin {
|
||||
private static final String PLATFORM_PLUGIN_TYPE = "PLATFORM";
|
||||
public AbstractPlatformPlugin(ClassLoader pluginClassLoader) {
|
||||
super(pluginClassLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return PLATFORM_PLUGIN_TYPE;
|
||||
|
|
|
@ -1,75 +1,16 @@
|
|||
package io.metersphere.plugin.sdk.api;
|
||||
|
||||
import io.metersphere.plugin.sdk.util.PluginLogUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractMsPlugin implements MsPlugin {
|
||||
|
||||
private static final String SCRIPT_DIR = "script";
|
||||
private ClassLoader pluginClassLoader;
|
||||
|
||||
public AbstractMsPlugin(ClassLoader pluginClassLoader) {
|
||||
this.pluginClassLoader = pluginClassLoader;
|
||||
}
|
||||
|
||||
protected InputStream readResource(String name) {
|
||||
return pluginClassLoader.getResourceAsStream(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return 返回该加载前端配置文件的目录,默认是 script
|
||||
* @return 返回默认的前端配置文件的目录
|
||||
* 可以重写定制
|
||||
*/
|
||||
protected String getScriptDir() {
|
||||
return SCRIPT_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 返回 resources下的 script 下的 json 文件
|
||||
*/
|
||||
@Override
|
||||
public List<String> getFrontendScript() {
|
||||
List<String> scriptList = new ArrayList<>();
|
||||
String scriptDirName = getScriptDir();
|
||||
URL scriptDir = pluginClassLoader.getResource(scriptDirName);
|
||||
if (scriptDir != null) {
|
||||
File resourceDir = new File(scriptDir.getFile());
|
||||
List<String> filePaths = getFilePaths(resourceDir);
|
||||
for (String filePath : filePaths) {
|
||||
InputStream in = readResource(scriptDirName + "/" + filePath);
|
||||
try {
|
||||
if (in != null) {
|
||||
scriptList.add(IOUtils.toString(in));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
PluginLogUtils.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return scriptList;
|
||||
}
|
||||
|
||||
private static List<String> getFilePaths(File directory) {
|
||||
List<String> filePaths = new ArrayList<>();
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
filePaths.addAll(getFilePaths(file));
|
||||
} else {
|
||||
filePaths.add(file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
return filePaths;
|
||||
public String getScriptDir() {
|
||||
return SCRIPT_DIR;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,6 +20,6 @@ public abstract class AbstractMsPlugin implements MsPlugin {
|
|||
|
||||
@Override
|
||||
public String getPluginId() {
|
||||
return getName().toLowerCase() + "-" + getVersion();
|
||||
return getKey().toLowerCase() + "-" + getVersion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package io.metersphere.plugin.sdk.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 插件的基本信息
|
||||
*
|
||||
|
@ -37,14 +35,14 @@ public interface MsPlugin {
|
|||
*/
|
||||
String getPluginId();
|
||||
|
||||
/**
|
||||
* @return 返回前端渲染需要的数据
|
||||
* 默认会返回 resources下的 script 下的 json 文件
|
||||
*/
|
||||
List<String> getFrontendScript();
|
||||
|
||||
/**
|
||||
* @return 返回插件的版本
|
||||
*/
|
||||
String getVersion();
|
||||
|
||||
/**
|
||||
* @return 返回该加载前端配置文件的目录,默认是 script
|
||||
* 可以重写定制
|
||||
*/
|
||||
String getScriptDir();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ public class SystemInterceptor {
|
|||
configList.add(new MybatisInterceptorConfig(NoviceStatistics.class, "dataOption", CompressUtils.class, "zip", "unzip"));
|
||||
configList.add(new MybatisInterceptorConfig(ServiceIntegration.class, "configuration", CompressUtils.class, "zip", "unzip"));
|
||||
configList.add(new MybatisInterceptorConfig(UserExtend.class, "platformInfo", CompressUtils.class, "zip", "unzip"));
|
||||
configList.add(new MybatisInterceptorConfig(PluginScript.class, "script", CompressUtils.class, "zip", "unzip"));
|
||||
configList.add(new MybatisInterceptorConfig(PluginScript.class, "script", CompressUtils.class, "zip", "unzip"));
|
||||
|
||||
return configList;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package io.metersphere.sdk.constants;
|
||||
|
||||
public class KafkaPluginTopicType {
|
||||
public static final String ADD = "ADD";
|
||||
public static final String DELETE = "DELETE";
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.metersphere.sdk.constants;
|
||||
|
||||
public class KafkaTopicConstants {
|
||||
public static final String PLUGIN = "PLUGIN";
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
package io.metersphere.sdk.constants;
|
||||
|
||||
public enum PluginScenarioType {
|
||||
PAI, PLATFORM
|
||||
API, PLATFORM
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
package io.metersphere.sdk.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class OrganizationOptionDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(title = "组织ID")
|
||||
private String id;
|
||||
|
||||
@Schema(title = "组织编号")
|
||||
private Long num;
|
||||
|
||||
@Schema(title = "组织名称")
|
||||
private String name;
|
||||
}
|
|
@ -11,6 +11,10 @@ public class MSException extends RuntimeException {
|
|||
super(message);
|
||||
}
|
||||
|
||||
public MSException(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
public MSException(IResultCode errorCode) {
|
||||
super(StringUtils.EMPTY);
|
||||
this.errorCode = errorCode;
|
||||
|
@ -26,6 +30,10 @@ public class MSException extends RuntimeException {
|
|||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public MSException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
|
||||
public IResultCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ package io.metersphere.sdk.file;
|
|||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
public interface FileRepository {
|
||||
/**
|
||||
* 保存文件
|
||||
|
@ -11,7 +14,7 @@ public interface FileRepository {
|
|||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public String saveFile(MultipartFile file, FileRequest request) throws Exception;
|
||||
String saveFile(MultipartFile file, FileRequest request) throws Exception;
|
||||
|
||||
/**
|
||||
* 保存文件
|
||||
|
@ -21,7 +24,7 @@ public interface FileRepository {
|
|||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public String saveFile(byte[] bytes, FileRequest request) throws Exception;
|
||||
String saveFile(byte[] bytes, FileRequest request) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
|
@ -29,7 +32,7 @@ public interface FileRepository {
|
|||
* @param request
|
||||
* @throws Exception
|
||||
*/
|
||||
public void delete(FileRequest request) throws Exception;
|
||||
void delete(FileRequest request) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除文件夹
|
||||
|
@ -37,7 +40,7 @@ public interface FileRepository {
|
|||
* @param request
|
||||
* @throws Exception
|
||||
*/
|
||||
public void deleteFolder(FileRequest request) throws Exception;
|
||||
void deleteFolder(FileRequest request) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -47,7 +50,16 @@ public interface FileRepository {
|
|||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public byte[] getFile(FileRequest request) throws Exception;
|
||||
byte[] getFile(FileRequest request) throws Exception;
|
||||
|
||||
/**
|
||||
* 获取文件字输入流
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
InputStream getFileAsStream(FileRequest request) throws Exception;
|
||||
|
||||
/**
|
||||
* 流式处理方式,通过逐块地下载文件
|
||||
|
@ -56,6 +68,14 @@ public interface FileRepository {
|
|||
* @param localPath
|
||||
* @throws Exception
|
||||
*/
|
||||
public void downloadFile(FileRequest request, String localPath) throws Exception;
|
||||
void downloadFile(FileRequest request, String localPath) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的文件名列表
|
||||
*
|
||||
* @param request
|
||||
* @throws Exception
|
||||
*/
|
||||
List<String> getFolderFileNames(FileRequest request) throws Exception;
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ public class MinioRepository implements FileRepository {
|
|||
// 文件存储路径
|
||||
return StringUtils.join(
|
||||
request.getProjectId(),
|
||||
File.separator,
|
||||
StringUtils.isNotBlank(request.getResourceId()) ? request.getResourceId() + File.separator : StringUtils.EMPTY,
|
||||
"/",
|
||||
StringUtils.isNotBlank(request.getResourceId()) ? request.getResourceId() + "/" : StringUtils.EMPTY,
|
||||
request.getFileName());
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,11 @@ public class MinioRepository implements FileRepository {
|
|||
removeObjects(MinioConfig.BUCKET, filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getFolderFileNames(FileRequest request) throws Exception {
|
||||
return listObjects(MinioConfig.BUCKET, getPath(request));
|
||||
}
|
||||
|
||||
private boolean removeObject(String bucketName, String objectName) throws Exception {
|
||||
client.removeObject(RemoveObjectArgs.builder()
|
||||
.bucket(bucketName) // 存储桶
|
||||
|
@ -128,6 +133,7 @@ public class MinioRepository implements FileRepository {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getFileAsStream(FileRequest request) throws Exception {
|
||||
String fileName = getPath(request);
|
||||
return client.getObject(GetObjectArgs.builder()
|
||||
|
|
|
@ -49,7 +49,7 @@ public class OperationLogModule {
|
|||
public static final String UI_AUTOMATION = "UI_AUTOMATION";
|
||||
public static final String UI_AUTOMATION_REPORT = "UI_AUTOMATION_REPORT";
|
||||
public static final String UI_AUTOMATION_SCHEDULE = "UI_AUTOMATION_SCHEDULE";
|
||||
public static final String PLUGIN_MANAGE = "PLUGIN_MANAGE";
|
||||
public static final String SYSTEM_PLUGIN = "SYSTEM_PLUGIN";
|
||||
public static final String SYSTEM_PROJECT = "SYSTEM_PROJECT";
|
||||
public static final String SYSTEM_PROJECT_MEMBER = "SYSTEM_PROJECT_MEMBER";
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.metersphere.sdk.plugin.loader;
|
||||
|
||||
import io.metersphere.sdk.plugin.storage.StorageStrategy;
|
||||
import io.metersphere.sdk.util.LogUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
@ -25,12 +26,12 @@ public class PluginClassLoader extends ClassLoader {
|
|||
/**
|
||||
* 保存加载失败的类,之后重试
|
||||
*/
|
||||
protected Map<String, byteArrayWrapper> loadErrorMap = new HashMap<>();
|
||||
protected Map<String, ByteArrayWrapper> loadErrorMap = new HashMap<>();
|
||||
|
||||
private class byteArrayWrapper {
|
||||
private class ByteArrayWrapper {
|
||||
private byte[] values;
|
||||
|
||||
public byteArrayWrapper(byte[] values) {
|
||||
public ByteArrayWrapper(byte[] values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
|
@ -49,14 +50,21 @@ public class PluginClassLoader extends ClassLoader {
|
|||
*/
|
||||
protected StorageStrategy storageStrategy;
|
||||
|
||||
protected boolean isNeedUploadFile;
|
||||
|
||||
public PluginClassLoader() {
|
||||
// 将父加载器设置成当前的类加载器,目的是由父加载器加载接口,实现类由该加载器加载
|
||||
super(PluginClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
public PluginClassLoader(StorageStrategy storageStrategy) {
|
||||
this(storageStrategy, true);
|
||||
}
|
||||
|
||||
public PluginClassLoader(StorageStrategy storageStrategy, boolean isNeedUploadFile) {
|
||||
this();
|
||||
this.storageStrategy = storageStrategy;
|
||||
this.isNeedUploadFile = isNeedUploadFile;
|
||||
}
|
||||
|
||||
public StorageStrategy getStorageStrategy() {
|
||||
|
@ -106,7 +114,7 @@ public class PluginClassLoader extends ClassLoader {
|
|||
*
|
||||
* @param in
|
||||
*/
|
||||
public void loadJar(InputStream in) throws IOException {
|
||||
public void loadJar(InputStream in) throws Exception {
|
||||
if (in != null) {
|
||||
try (JarInputStream jis = new JarInputStream(in)) {
|
||||
JarEntry je;
|
||||
|
@ -132,8 +140,8 @@ public class PluginClassLoader extends ClassLoader {
|
|||
JarEntry je = en.nextElement();
|
||||
try (InputStream in = jar.getInputStream(je)) {
|
||||
loadJar(in, je);
|
||||
} catch (IOException e) {
|
||||
// LogUtils.error(e);
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
}
|
||||
}
|
||||
reloadErrorClazz();
|
||||
|
@ -146,7 +154,7 @@ public class PluginClassLoader extends ClassLoader {
|
|||
* @param je
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void loadJar(InputStream in, JarEntry je) throws IOException {
|
||||
protected void loadJar(InputStream in, JarEntry je) throws Exception {
|
||||
je.getName();
|
||||
String name = je.getName();
|
||||
if (name.endsWith(".class")) {
|
||||
|
@ -167,13 +175,13 @@ public class PluginClassLoader extends ClassLoader {
|
|||
Class<?> clazz = defineClass(className, bytes, 0, bytes.length);
|
||||
clazzSet.add(clazz);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
loadErrorMap.put(className, new byteArrayWrapper(bytes));
|
||||
loadErrorMap.put(className, new ByteArrayWrapper(bytes));
|
||||
} catch (Throwable e) {
|
||||
// LogUtils.error(e);
|
||||
LogUtils.error(e);
|
||||
}
|
||||
} else if (!name.endsWith("/")) {
|
||||
// 非目录即静态资源
|
||||
if (storageStrategy != null) {
|
||||
if (storageStrategy != null && isNeedUploadFile) {
|
||||
storageStrategy.store(name, in);
|
||||
}
|
||||
}
|
||||
|
@ -190,13 +198,13 @@ public class PluginClassLoader extends ClassLoader {
|
|||
while (iterator.hasNext()) {
|
||||
String className = iterator.next();
|
||||
try {
|
||||
// LogUtils.info("reload class: " + className);
|
||||
LogUtils.info("reload class: " + className);
|
||||
byte[] bytes = loadErrorMap.get(className).getValues();
|
||||
Class<?> clazz = defineClass(className, bytes, 0, bytes.length);
|
||||
clazzSet.add(clazz);
|
||||
iterator.remove();
|
||||
} catch (Throwable e) {
|
||||
// LogUtils.error(e);
|
||||
LogUtils.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -212,8 +220,8 @@ public class PluginClassLoader extends ClassLoader {
|
|||
if (null != storageStrategy) {
|
||||
try {
|
||||
return storageStrategy.get(name);
|
||||
} catch (IOException e) {
|
||||
// LogUtils.error(e, logger);
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,15 +55,15 @@ public class PluginManager {
|
|||
*
|
||||
* @param in
|
||||
*/
|
||||
public PluginManager loadJar(String pluginId, InputStream in, StorageStrategy storageStrategy) throws IOException {
|
||||
PluginClassLoader pluginClassLoader = new PluginClassLoader(storageStrategy);
|
||||
public PluginManager loadJar(String pluginId, InputStream in, StorageStrategy storageStrategy, boolean isNeedUploadFile) throws Exception {
|
||||
PluginClassLoader pluginClassLoader = new PluginClassLoader(storageStrategy, isNeedUploadFile);
|
||||
classLoaderMap.put(pluginId, pluginClassLoader);
|
||||
pluginClassLoader.loadJar(in);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PluginManager loadJar(String pluginId, InputStream in) throws IOException {
|
||||
return this.loadJar(pluginId, in, null);
|
||||
public PluginManager loadJar(String pluginId, InputStream in, boolean isNeedUploadFile) throws Exception {
|
||||
return this.loadJar(pluginId, in, null, isNeedUploadFile);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,6 +87,9 @@ public class PluginManager {
|
|||
public <T> T getImplInstance(String pluginId, Class<T> superClazz) {
|
||||
try {
|
||||
Class<T> clazz = getImplClass(pluginId, superClazz);
|
||||
if (clazz == null) {
|
||||
throw new MSException("未找到插件实现类");
|
||||
}
|
||||
return clazz.getConstructor().newInstance();
|
||||
} catch (InvocationTargetException e) {
|
||||
LogUtils.error(e);
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
package io.metersphere.sdk.plugin.storage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* jar包静态资源存储策略,存储在 Minio
|
||||
*/
|
||||
public class MinioStorageStrategy implements StorageStrategy {
|
||||
|
||||
private String pluginId;
|
||||
|
||||
public static final String DIR_PATH = "system/plugin";
|
||||
|
||||
public MinioStorageStrategy(String pluginId) {
|
||||
this.pluginId = pluginId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String store(String name, InputStream in) throws IOException {
|
||||
// todo 上传到 minio
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream get(String name) {
|
||||
// todo 获取文件
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
// todo 删除文件
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package io.metersphere.sdk.plugin.storage;
|
||||
|
||||
import io.metersphere.sdk.file.FileCenter;
|
||||
import io.metersphere.sdk.file.FileRepository;
|
||||
import io.metersphere.sdk.file.FileRequest;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* jar包静态资源存储策略
|
||||
* @author jianxing
|
||||
*/
|
||||
public class MsStorageStrategy implements StorageStrategy {
|
||||
|
||||
|
||||
private final FileRepository fileRepository;
|
||||
private final String pluginId;
|
||||
|
||||
public static final String DIR_PATH = "system/plugin";
|
||||
|
||||
public MsStorageStrategy(String pluginId) {
|
||||
this.pluginId = pluginId;
|
||||
fileRepository = FileCenter.getDefaultRepository();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String store(String name, InputStream in) throws Exception {
|
||||
FileRequest request = getFileRequest(name);
|
||||
return fileRepository.saveFile(in.readAllBytes(), request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream get(String name) throws Exception {
|
||||
FileRequest request = getFileRequest(name);
|
||||
return fileRepository.getFileAsStream(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getFolderFileNames(String dirName) throws Exception {
|
||||
FileRequest request = getFileRequest(dirName);
|
||||
List<String> fileNames = fileRepository.getFolderFileNames(request);
|
||||
return fileNames.stream().map(s -> s.replace(getPluginDir(), StringUtils.EMPTY)).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws Exception {
|
||||
FileRequest request = new FileRequest();
|
||||
request.setProjectId(getPluginDir());
|
||||
fileRepository.deleteFolder(request);
|
||||
}
|
||||
|
||||
private FileRequest getFileRequest(String name) {
|
||||
FileRequest request = new FileRequest();
|
||||
request.setProjectId(getPluginDir());
|
||||
request.setFileName(name);
|
||||
return request;
|
||||
}
|
||||
|
||||
private String getPluginDir() {
|
||||
return DIR_PATH + "/" + this.pluginId;
|
||||
}
|
||||
}
|
|
@ -2,9 +2,11 @@ package io.metersphere.sdk.plugin.storage;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* jar包、图片、前端配置文件等静态资源存储策略
|
||||
* @author jianxing
|
||||
*/
|
||||
public interface StorageStrategy {
|
||||
|
||||
|
@ -15,7 +17,7 @@ public interface StorageStrategy {
|
|||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
String store(String name, InputStream in) throws IOException;
|
||||
String store(String name, InputStream in) throws Exception;
|
||||
|
||||
/**
|
||||
* 获取文件
|
||||
|
@ -23,12 +25,19 @@ public interface StorageStrategy {
|
|||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
InputStream get(String path) throws IOException;
|
||||
InputStream get(String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的文件名列表
|
||||
*
|
||||
* @param dirName
|
||||
* @throws Exception
|
||||
*/
|
||||
List<String> getFolderFileNames(String dirName) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @throws IOException
|
||||
*/
|
||||
void delete() throws IOException;
|
||||
void delete() throws Exception;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
package io.metersphere.sdk.service;
|
||||
|
||||
import io.metersphere.plugin.sdk.api.MsPlugin;
|
||||
import io.metersphere.sdk.exception.MSException;
|
||||
import io.metersphere.sdk.plugin.loader.PluginClassLoader;
|
||||
import io.metersphere.sdk.plugin.loader.PluginManager;
|
||||
import io.metersphere.sdk.plugin.storage.MsStorageStrategy;
|
||||
import io.metersphere.sdk.plugin.storage.StorageStrategy;
|
||||
import io.metersphere.sdk.util.LogUtils;
|
||||
import io.metersphere.system.domain.Plugin;
|
||||
import io.metersphere.system.domain.PluginExample;
|
||||
import io.metersphere.system.mapper.PluginMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.codehaus.plexus.util.IOUtil;
|
||||
import org.codehaus.plexus.util.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
*/
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class PluginLoadService {
|
||||
|
||||
private final PluginManager pluginManager = new PluginManager();
|
||||
|
||||
@Resource
|
||||
private PluginMapper pluginMapper;
|
||||
|
||||
/**
|
||||
* 上传插件到 minio
|
||||
*/
|
||||
public void uploadPlugin(String id, MultipartFile file) {
|
||||
try {
|
||||
getStorageStrategy(id).store(file.getOriginalFilename(), file.getInputStream());
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
throw new MSException("文件上传异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 返回前端渲染需要的数据
|
||||
* 默认会返回 resources下的 script 下的 json 文件
|
||||
*/
|
||||
public List<String> getFrontendScripts(String pluginId) {
|
||||
MsPlugin msPluginInstance = getMsPluginInstance(pluginId);
|
||||
String scriptDir = msPluginInstance.getScriptDir();
|
||||
StorageStrategy storageStrategy = pluginManager.getClassLoader(pluginId).getStorageStrategy();
|
||||
try {
|
||||
// 查询脚本文件名
|
||||
List<String> folderFileNames = storageStrategy.getFolderFileNames(scriptDir);
|
||||
// 获取脚本内容
|
||||
List<String> scripts = new ArrayList<>(folderFileNames.size());
|
||||
for (String folderFileName : folderFileNames) {
|
||||
InputStream in = storageStrategy.get(folderFileName);
|
||||
if (in == null) {
|
||||
continue;
|
||||
}
|
||||
scripts.add(IOUtil.toString(storageStrategy.get(folderFileName)));
|
||||
}
|
||||
return scripts;
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
throw new MSException("获取脚本异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static StorageStrategy getStorageStrategy(String id) {
|
||||
return new MsStorageStrategy(id);
|
||||
}
|
||||
|
||||
public void loadPlugin(String id, MultipartFile file) {
|
||||
// 加载 jar
|
||||
InputStream inputStream;
|
||||
try {
|
||||
inputStream = file.getInputStream();
|
||||
} catch (IOException e) {
|
||||
LogUtils.error(e);
|
||||
throw new MSException("获取文件输入流异常", e);
|
||||
}
|
||||
loadPlugin(id, inputStream, true);
|
||||
}
|
||||
|
||||
public void loadPlugin(String pluginId, String fileName) {
|
||||
PluginClassLoader classLoader = pluginManager.getClassLoader(pluginId);
|
||||
if (classLoader != null) {
|
||||
return;
|
||||
}
|
||||
// 加载 jar
|
||||
InputStream inputStream;
|
||||
try {
|
||||
inputStream = classLoader.getStorageStrategy().get(fileName);
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
throw new MSException("下载文件异常", e);
|
||||
}
|
||||
loadPlugin(pluginId, inputStream, false);
|
||||
}
|
||||
|
||||
public void loadPlugin(String id, InputStream inputStream, boolean isNeedUploadFile) {
|
||||
if (inputStream == null) {
|
||||
return;
|
||||
}
|
||||
loadPlugin(id, inputStream, new MsStorageStrategy(id), isNeedUploadFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载插件
|
||||
*
|
||||
* @param id 插件ID
|
||||
* @param inputStream 输入流
|
||||
* @param storageStrategy 静态文件及jar包存储策略
|
||||
*/
|
||||
public void loadPlugin(String id, InputStream inputStream, StorageStrategy storageStrategy, boolean isNeedUploadFile) {
|
||||
if (inputStream == null || pluginManager.getClassLoader(id) != null) {
|
||||
return;
|
||||
}
|
||||
// 加载 jar
|
||||
try {
|
||||
pluginManager.loadJar(id, inputStream,
|
||||
storageStrategy == null ? getStorageStrategy(id) : storageStrategy, isNeedUploadFile);
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
throw new MSException("加载插件异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目启动时加载插件
|
||||
*/
|
||||
public synchronized void loadPlugins() {
|
||||
List<Plugin> plugins = pluginMapper.selectByExample(new PluginExample());
|
||||
plugins.forEach(plugin -> {
|
||||
String id = plugin.getId();
|
||||
StorageStrategy storageStrategy = getStorageStrategy(id);
|
||||
try {
|
||||
InputStream inputStream = storageStrategy.get(plugin.getFileName());
|
||||
loadPlugin(id, inputStream, storageStrategy, false);
|
||||
} catch (Exception e) {
|
||||
LogUtils.error("初始化插件异常" + plugin.getFileName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载插件
|
||||
*/
|
||||
public void unloadPlugin(String pluginId) {
|
||||
pluginManager.deletePlugin(pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除插件
|
||||
*/
|
||||
public void deletePlugin(String pluginId) {
|
||||
// 删除文件
|
||||
PluginClassLoader classLoader = pluginManager.getClassLoader(pluginId);
|
||||
try {
|
||||
if (classLoader != null) {
|
||||
classLoader.getStorageStrategy().delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
throw new MSException("删除插件异常 ", e);
|
||||
}
|
||||
unloadPlugin(pluginId);
|
||||
}
|
||||
|
||||
public MsPlugin getMsPluginInstance(String id) {
|
||||
return pluginManager.getImplInstance(id, MsPlugin.class);
|
||||
}
|
||||
|
||||
public boolean hasPluginKey(String currentPluginId, String pluginKey) {
|
||||
for (String pluginId : pluginManager.getClassLoaderMap().keySet()) {
|
||||
MsPlugin msPlugin = getMsPluginInstance(pluginId);
|
||||
if (!StringUtils.equals(currentPluginId, pluginId) && StringUtils.equals(msPlugin.getKey(), pluginKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -168,7 +168,10 @@ permission.system_plugin.read=READ
|
|||
permission.system_plugin.add=CREATE
|
||||
permission.system_plugin.edit=UPDATE
|
||||
permission.system_plugin.delete=DELETE
|
||||
|
||||
plugin.exist=plugin name or filename already exists
|
||||
plugin.type.exist=plugin type already exists
|
||||
plugin.script.exist=duplicate script id
|
||||
plugin.script.format=malformed script
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -167,3 +167,7 @@ permission.system_plugin.read=查看插件
|
|||
permission.system_plugin.add=创建插件
|
||||
permission.system_plugin.edit=更新插件
|
||||
permission.system_plugin.delete=删除插件
|
||||
plugin.exist=插件名称或文件名已存在
|
||||
plugin.type.exist=插件类型已存在
|
||||
plugin.script.exist=脚本id重复
|
||||
plugin.script.format=脚本格式错误
|
|
@ -167,4 +167,8 @@ permission.system_plugin.read=查看插件
|
|||
permission.system_plugin.add=創建插件
|
||||
permission.system_plugin.edit=更新插件
|
||||
permission.system_plugin.delete=刪除插件
|
||||
plugin.exist=插件名稱或文件名已存在
|
||||
plugin.type.exist=插件類型已存在
|
||||
plugin.script.exist=腳本id重複
|
||||
plugin.script.format=腳本格式錯誤
|
||||
|
||||
|
|
|
@ -163,6 +163,12 @@ public abstract class BaseTest {
|
|||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
protected ResultActions requestMultipart(String url, MultiValueMap<String, Object> paramMap, Object... uriVariables) throws Exception {
|
||||
MockHttpServletRequestBuilder requestBuilder = getMultipartRequestBuilder(url, paramMap, uriVariables);
|
||||
return mockMvc.perform(requestBuilder)
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
|
||||
}
|
||||
|
||||
protected MvcResult requestMultipartWithOkAndReturn(String url, MultiValueMap<String, Object> paramMap, Object... uriVariables) throws Exception {
|
||||
return this.requestMultipartWithOk(url, paramMap, uriVariables).andReturn();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import io.metersphere.sdk.util.SessionUtils;
|
|||
import io.metersphere.system.domain.Plugin;
|
||||
import io.metersphere.system.dto.PluginDTO;
|
||||
import io.metersphere.system.request.PluginUpdateRequest;
|
||||
import io.metersphere.system.service.PluginLogService;
|
||||
import io.metersphere.system.service.PluginService;
|
||||
import io.metersphere.validation.groups.Created;
|
||||
import io.metersphere.validation.groups.Updated;
|
||||
|
@ -40,17 +41,10 @@ public class PluginController {
|
|||
return pluginService.list();
|
||||
}
|
||||
|
||||
@GetMapping("/get/{id}")
|
||||
@Operation(summary = "获取插件详情")
|
||||
@RequiresPermissions(PermissionConstants.SYSTEM_PLUGIN_READ)
|
||||
public PluginDTO get(@PathVariable String id) {
|
||||
return pluginService.get(id);
|
||||
}
|
||||
|
||||
@PostMapping("/add")
|
||||
@Operation(summary = "创建插件")
|
||||
@RequiresPermissions(PermissionConstants.SYSTEM_PLUGIN_ADD)
|
||||
@Log(type = OperationLogType.UPDATE, expression = "#msClass.addLog(#request)", msClass = PluginService.class)
|
||||
@Log(type = OperationLogType.UPDATE, expression = "#msClass.addLog(#request)", msClass = PluginLogService.class)
|
||||
public Plugin add(@Validated({Created.class}) @RequestPart(value = "request") PluginUpdateRequest request,
|
||||
@RequestPart(value = "file") MultipartFile file) {
|
||||
request.setCreateUser(SessionUtils.getUserId());
|
||||
|
@ -60,26 +54,25 @@ public class PluginController {
|
|||
@PostMapping("/update")
|
||||
@Operation(summary = "更新插件")
|
||||
@RequiresPermissions(PermissionConstants.SYSTEM_PLUGIN_UPDATE)
|
||||
@Log(type = OperationLogType.ADD, expression = "#msClass.updateLog(#request)", msClass = PluginService.class)
|
||||
public Plugin update(@Validated({Updated.class}) @RequestPart(value = "request") PluginUpdateRequest request,
|
||||
@RequestPart(value = "file", required = false) MultipartFile file) {
|
||||
@Log(type = OperationLogType.ADD, expression = "#msClass.updateLog(#request)", msClass = PluginLogService.class)
|
||||
public Plugin update(@Validated({Updated.class}) @RequestBody PluginUpdateRequest request) {
|
||||
Plugin plugin = new Plugin();
|
||||
BeanUtils.copyBean(plugin, request);
|
||||
return pluginService.update(request, file);
|
||||
return pluginService.update(request);
|
||||
}
|
||||
|
||||
@GetMapping("/delete/{id}")
|
||||
@Operation(summary = "删除插件")
|
||||
@RequiresPermissions(PermissionConstants.SYSTEM_PLUGIN_DELETE)
|
||||
@Log(type = OperationLogType.DELETE, expression = "#msClass.deleteLog(#id)", msClass = PluginService.class)
|
||||
public String delete(@PathVariable String id) {
|
||||
return pluginService.delete(id);
|
||||
@Log(type = OperationLogType.DELETE, expression = "#msClass.deleteLog(#id)", msClass = PluginLogService.class)
|
||||
public void delete(@PathVariable String id) {
|
||||
pluginService.delete(id);
|
||||
}
|
||||
|
||||
@GetMapping("/script/get/{pluginId}/{scriptId}")
|
||||
@Operation(summary = "获取插件对应表单的脚本内容")
|
||||
@RequiresPermissions(PermissionConstants.SYSTEM_PLUGIN_READ)
|
||||
public String getScript(@PathVariable String pluginId, String scriptId) {
|
||||
public String getScript(@PathVariable String pluginId, @PathVariable String scriptId) {
|
||||
return pluginService.getScript(pluginId, scriptId);
|
||||
}
|
||||
}
|
|
@ -16,7 +16,11 @@ public enum SystemResultCode implements IResultCode {
|
|||
/**
|
||||
* 获取/编辑组织自定义用户组,如果非组织自定义用户组,会返回该响应码
|
||||
*/
|
||||
NO_ORG_USER_ROLE_PERMISSION(101007, "organization_user_role_permission_error");
|
||||
NO_ORG_USER_ROLE_PERMISSION(101007, "organization_user_role_permission_error"),
|
||||
PLUGIN_EXIST(101008, "plugin.exist"),
|
||||
PLUGIN_TYPE_EXIST(101009, "plugin.type.exist"),
|
||||
PLUGIN_SCRIPT_EXIST(101010, "plugin.script.exist"),
|
||||
PLUGIN_SCRIPT_FORMAT(101011, "plugin.script.format");
|
||||
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.metersphere.system.dto;
|
||||
|
||||
import io.metersphere.sdk.dto.OrganizationOptionDTO;
|
||||
import io.metersphere.sdk.dto.OptionDTO;
|
||||
import io.metersphere.system.domain.Plugin;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
@ -15,14 +15,8 @@ public class PluginDTO extends Plugin implements Serializable {
|
|||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(title = "插件前端表单配置项列表")
|
||||
private List<PluginDTO.PluginForm> pluginForms;
|
||||
private List<OptionDTO> pluginForms;
|
||||
|
||||
@Schema(title = "关联的组织列表")
|
||||
private List<OrganizationOptionDTO> organizations;
|
||||
|
||||
@Data
|
||||
class PluginForm {
|
||||
private String id;
|
||||
private String name;
|
||||
}
|
||||
private List<OptionDTO> organizations;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package io.metersphere.system.listener;
|
||||
|
||||
|
||||
import io.metersphere.sdk.constants.KafkaPluginTopicType;
|
||||
import io.metersphere.sdk.constants.KafkaTopicConstants;
|
||||
import io.metersphere.sdk.service.PluginLoadService;
|
||||
import io.metersphere.sdk.util.LogUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
import org.springframework.kafka.annotation.KafkaListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class PluginListener {
|
||||
|
||||
public static final String PLUGIN_CONSUMER = "plugin_consumer";
|
||||
|
||||
@Resource
|
||||
private PluginLoadService pluginLoadService;
|
||||
|
||||
// groupId 必须是每个实例唯一
|
||||
@KafkaListener(id = PLUGIN_CONSUMER, topics = KafkaTopicConstants.PLUGIN, groupId = PLUGIN_CONSUMER + "_" + "${random.uuid}")
|
||||
public void handlePluginChange(ConsumerRecord<?, String> record) {
|
||||
LogUtils.info("Service consume platform_plugin message: " + record);
|
||||
String[] info = record.value().split(":");
|
||||
String operate = info[0];
|
||||
String pluginId = info[1];
|
||||
switch (operate) {
|
||||
case KafkaPluginTopicType.ADD:
|
||||
String pluginName = info[2];
|
||||
pluginLoadService.loadPlugin(pluginId, pluginName);
|
||||
break;
|
||||
case KafkaPluginTopicType.DELETE:
|
||||
pluginLoadService.unloadPlugin(pluginId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package io.metersphere.system.mapper;
|
||||
|
||||
import io.metersphere.sdk.dto.OptionDTO;
|
||||
import io.metersphere.system.domain.User;
|
||||
import io.metersphere.system.dto.OrgUserExtend;
|
||||
import io.metersphere.system.dto.OrganizationDTO;
|
||||
|
@ -75,4 +76,6 @@ public interface ExtOrganizationMapper {
|
|||
* @return 组织列表数据
|
||||
*/
|
||||
List<OrganizationProjectOptionsDTO> selectOrganizationOptions();
|
||||
|
||||
List<OptionDTO> getOptionsByIds(@Param("ids") List<String> ids);
|
||||
}
|
||||
|
|
|
@ -77,6 +77,12 @@
|
|||
<select id="selectOrganizationOptions" resultType="io.metersphere.system.dto.OrganizationProjectOptionsDTO">
|
||||
select id, name from organization order by create_time desc
|
||||
</select>
|
||||
<select id="getOptionsByIds" resultType="io.metersphere.sdk.dto.OptionDTO">
|
||||
select id, name from organization where id in
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<sql id="queryWhereCondition">
|
||||
<where>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package io.metersphere.system.mapper;
|
||||
|
||||
import io.metersphere.system.dto.PluginDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ExtPluginMapper {
|
||||
List<PluginDTO> getPlugins();
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?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="io.metersphere.system.mapper.ExtPluginMapper">
|
||||
<select id="getPlugins" resultType="io.metersphere.system.dto.PluginDTO">
|
||||
SELECT
|
||||
<include refid="io.metersphere.system.mapper.PluginMapper.Base_Column_List"/>
|
||||
FROM
|
||||
plugin
|
||||
</select>
|
||||
</mapper>
|
|
@ -0,0 +1,10 @@
|
|||
package io.metersphere.system.mapper;
|
||||
|
||||
import io.metersphere.system.domain.PluginScript;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ExtPluginScriptMapper {
|
||||
List<PluginScript> getOptionByPluginIds(@Param("pluginIds") List<String> pluginIds);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?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="io.metersphere.system.mapper.ExtPluginScriptMapper">
|
||||
<select id="getOptionByPluginIds" resultType="io.metersphere.system.domain.PluginScript">
|
||||
select plugin_id, script_id, name from plugin_script where plugin_id in
|
||||
<foreach collection="pluginIds" item="pluginId" open="(" separator="," close=")">
|
||||
#{pluginId}
|
||||
</foreach>
|
||||
</select>
|
||||
</mapper>
|
|
@ -13,7 +13,7 @@ import java.util.List;
|
|||
public class PluginUpdateRequest {
|
||||
@Schema(title = "ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{plugin.id.not_blank}", groups = {Updated.class})
|
||||
@Size(min = 1, max = 50, message = "{plugin.id.length_range}", groups = {Created.class, Updated.class})
|
||||
@Size(min = 1, max = 50, message = "{plugin.id.length_range}", groups = {Updated.class})
|
||||
private String id;
|
||||
|
||||
@Schema(title = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.apache.ibatis.session.ExecutorType;
|
|||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.mybatis.spring.SqlSessionUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
@ -62,6 +63,9 @@ public class OrganizationService {
|
|||
private SystemProjectService systemProjectService;
|
||||
@Resource
|
||||
private UserRolePermissionMapper userRolePermissionMapper;
|
||||
@Resource
|
||||
@Lazy
|
||||
private PluginOrganizationService pluginOrganizationService;
|
||||
|
||||
private static final String ADD_MEMBER_PATH = "/system/organization/add-member";
|
||||
private static final String REMOVE_MEMBER_PATH = "/system/organization/remove-member";
|
||||
|
@ -119,6 +123,9 @@ public class OrganizationService {
|
|||
userRolePermissionMapper.deleteByExample(userRolePermissionExample);
|
||||
}
|
||||
|
||||
// 删除组织和插件的关联关系
|
||||
pluginOrganizationService.deleteByOrgId(organizationId);
|
||||
|
||||
// TODO: 删除环境组, 删除定时任务
|
||||
// 删除组织
|
||||
organizationMapper.deleteByPrimaryKey(organizationId);
|
||||
|
@ -723,4 +730,11 @@ public class OrganizationService {
|
|||
dto.setModifiedValue(JSON.toJSONBytes(modifiedValue));
|
||||
logs.add(dto);
|
||||
}
|
||||
|
||||
public List<OptionDTO> getOptionsByIds(List<String> orgIds) {
|
||||
if (CollectionUtils.isEmpty(orgIds)) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
return extOrganizationMapper.getOptionsByIds(orgIds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package io.metersphere.system.service;
|
||||
|
||||
import io.metersphere.system.domain.Plugin;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import io.metersphere.sdk.constants.OperationLogConstants;
|
||||
import io.metersphere.sdk.log.constants.OperationLogModule;
|
||||
import io.metersphere.sdk.log.constants.OperationLogType;
|
||||
import io.metersphere.sdk.dto.LogDTO;
|
||||
import io.metersphere.sdk.util.JSON;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import io.metersphere.system.request.PluginUpdateRequest;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
* @date : 2023-8-3
|
||||
*/
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class PluginLogService {
|
||||
|
||||
@Resource
|
||||
private PluginService pluginService;
|
||||
|
||||
public LogDTO addLog(PluginUpdateRequest request) {
|
||||
LogDTO dto = new LogDTO(
|
||||
OperationLogConstants.SYSTEM,
|
||||
OperationLogConstants.SYSTEM,
|
||||
null,
|
||||
null,
|
||||
OperationLogType.ADD.name(),
|
||||
OperationLogModule.SYSTEM_PLUGIN,
|
||||
request.getName());
|
||||
dto.setOriginalValue(JSON.toJSONBytes(request));
|
||||
return dto;
|
||||
}
|
||||
|
||||
public LogDTO updateLog(PluginUpdateRequest request) {
|
||||
Plugin plugin = pluginService.get(request.getId());
|
||||
LogDTO dto = new LogDTO(
|
||||
OperationLogConstants.SYSTEM,
|
||||
OperationLogConstants.SYSTEM,
|
||||
plugin.getId(),
|
||||
null,
|
||||
OperationLogType.UPDATE.name(),
|
||||
OperationLogModule.SYSTEM_PLUGIN,
|
||||
plugin.getName());
|
||||
dto.setOriginalValue(JSON.toJSONBytes(plugin));
|
||||
return dto;
|
||||
}
|
||||
|
||||
public LogDTO deleteLog(String id) {
|
||||
Plugin plugin = pluginService.get(id);
|
||||
if (plugin == null) {
|
||||
return null;
|
||||
}
|
||||
LogDTO dto = new LogDTO(
|
||||
OperationLogConstants.SYSTEM,
|
||||
OperationLogConstants.SYSTEM,
|
||||
plugin.getId(),
|
||||
null,
|
||||
OperationLogType.DELETE.name(),
|
||||
OperationLogModule.SYSTEM_PLUGIN,
|
||||
plugin.getName());
|
||||
dto.setOriginalValue(JSON.toJSONBytes(plugin));
|
||||
return dto;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package io.metersphere.system.service;
|
||||
|
||||
import io.metersphere.sdk.dto.OptionDTO;
|
||||
import io.metersphere.system.domain.PluginOrganization;
|
||||
import io.metersphere.system.domain.PluginOrganizationExample;
|
||||
import io.metersphere.system.mapper.PluginOrganizationMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class PluginOrganizationService {
|
||||
|
||||
@Resource
|
||||
private PluginOrganizationMapper pluginOrganizationMapper;
|
||||
@Resource
|
||||
private OrganizationService organizationService;
|
||||
|
||||
public void add(String pluginId, List<String> orgIds) {
|
||||
if (CollectionUtils.isEmpty(orgIds)) {
|
||||
return;
|
||||
}
|
||||
List<PluginOrganization> pluginOrganizations = new ArrayList<>(orgIds.size());
|
||||
for (String orgId : orgIds) {
|
||||
PluginOrganization pluginOrganization = new PluginOrganization();
|
||||
pluginOrganization.setPluginId(pluginId);
|
||||
pluginOrganization.setOrganizationId(orgId);
|
||||
pluginOrganizations.add(pluginOrganization);
|
||||
}
|
||||
pluginOrganizationMapper.batchInsert(pluginOrganizations);
|
||||
}
|
||||
|
||||
public void deleteByPluginId(String pluginId) {
|
||||
PluginOrganizationExample example = new PluginOrganizationExample();
|
||||
example.createCriteria().andPluginIdEqualTo(pluginId);
|
||||
pluginOrganizationMapper.deleteByExample(example);
|
||||
}
|
||||
|
||||
public void deleteByOrgId(String orgId) {
|
||||
PluginOrganizationExample example = new PluginOrganizationExample();
|
||||
example.createCriteria().andOrganizationIdEqualTo(orgId);
|
||||
pluginOrganizationMapper.deleteByExample(example);
|
||||
}
|
||||
|
||||
public void update(String pluginId, List<String> organizationIds) {
|
||||
if (organizationIds == null) {
|
||||
// 如果参数没填,则不更新
|
||||
return;
|
||||
}
|
||||
// 先删除关联关系
|
||||
deleteByPluginId(pluginId);
|
||||
// 重新添加关联关系
|
||||
add(pluginId, organizationIds);
|
||||
}
|
||||
|
||||
public Map<String, List<OptionDTO>> getOrgMap(List<String> pluginIds) {
|
||||
if (CollectionUtils.isEmpty(pluginIds)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
// 查询插件和组织的关联关系
|
||||
PluginOrganizationExample example = new PluginOrganizationExample();
|
||||
example.createCriteria().andPluginIdIn(pluginIds);
|
||||
List<PluginOrganization> pluginOrganizations = pluginOrganizationMapper.selectByExample(example);
|
||||
|
||||
// 查询组织信息
|
||||
List<String> orgIds = pluginOrganizations.stream().map(PluginOrganization::getOrganizationId).toList();
|
||||
List<OptionDTO> orgList = organizationService.getOptionsByIds(orgIds);
|
||||
Map<String, OptionDTO> orgInfoMap = orgList.stream().collect(Collectors.toMap(OptionDTO::getId, i -> i));
|
||||
|
||||
// 组装成 map
|
||||
Map<String, List<OptionDTO>> orgMap = new HashMap<>();
|
||||
for (PluginOrganization pluginOrganization : pluginOrganizations) {
|
||||
String pluginId = pluginOrganization.getPluginId();
|
||||
String orgId = pluginOrganization.getOrganizationId();
|
||||
OptionDTO orgInfo = orgInfoMap.get(orgId);
|
||||
if (orgInfo == null) {
|
||||
continue;
|
||||
}
|
||||
orgMap.computeIfAbsent(pluginId, k -> new ArrayList<>())
|
||||
.add(orgInfo);
|
||||
}
|
||||
return orgMap;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package io.metersphere.system.service;
|
||||
|
||||
import io.metersphere.sdk.dto.OptionDTO;
|
||||
import io.metersphere.sdk.exception.MSException;
|
||||
import io.metersphere.sdk.util.JSON;
|
||||
import io.metersphere.system.domain.PluginScript;
|
||||
import io.metersphere.system.domain.PluginScriptExample;
|
||||
import io.metersphere.system.mapper.ExtPluginScriptMapper;
|
||||
import io.metersphere.system.mapper.PluginScriptMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_SCRIPT_EXIST;
|
||||
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_SCRIPT_FORMAT;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class PluginScriptService {
|
||||
|
||||
@Resource
|
||||
private PluginScriptMapper pluginScriptMapper;
|
||||
@Resource
|
||||
private ExtPluginScriptMapper extPluginScriptMapper;
|
||||
|
||||
public void add(String pluginId, List<String> frontendScript) {
|
||||
Set<String> ids = new HashSet<>();
|
||||
List<PluginScript> pluginScripts = new ArrayList<>(frontendScript.size());
|
||||
for (String script : frontendScript) {
|
||||
PluginScript pluginScript = new PluginScript();
|
||||
OptionDTO scriptInfo;
|
||||
try {
|
||||
scriptInfo = JSON.parseObject(script, OptionDTO.class);
|
||||
} catch (Exception e) {
|
||||
throw new MSException(PLUGIN_SCRIPT_FORMAT);
|
||||
}
|
||||
// ID 判重
|
||||
if (ids.contains(scriptInfo.getId())) {
|
||||
throw new MSException(PLUGIN_SCRIPT_EXIST);
|
||||
}
|
||||
ids.add(scriptInfo.getId());
|
||||
pluginScript.setPluginId(pluginId);
|
||||
pluginScript.setScriptId(
|
||||
StringUtils.isBlank(scriptInfo.getId()) ? UUID.randomUUID().toString() : scriptInfo.getId()
|
||||
);
|
||||
pluginScript.setName(scriptInfo.getName());
|
||||
pluginScript.setScript(script.getBytes());
|
||||
pluginScripts.add(pluginScript);
|
||||
}
|
||||
pluginScriptMapper.batchInsert(pluginScripts);
|
||||
}
|
||||
|
||||
public void deleteByPluginId(String pluginId) {
|
||||
PluginScriptExample example = new PluginScriptExample();
|
||||
example.createCriteria().andPluginIdEqualTo(pluginId);
|
||||
pluginScriptMapper.deleteByExample(example);
|
||||
}
|
||||
|
||||
public String get(String pluginId, String scriptId) {
|
||||
PluginScript frontScript = pluginScriptMapper.selectByPrimaryKey(pluginId, scriptId);
|
||||
return frontScript == null ? null : new String(frontScript.getScript());
|
||||
}
|
||||
|
||||
public Map<String, List<OptionDTO>> getScripteMap(List<String> pluginIds) {
|
||||
if (CollectionUtils.isEmpty(pluginIds)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<PluginScript> scripts = extPluginScriptMapper.getOptionByPluginIds(pluginIds);
|
||||
Map<String, List<OptionDTO>> scriptMap = new HashMap<>();
|
||||
for (PluginScript script : scripts) {
|
||||
List<OptionDTO> scriptList = scriptMap.computeIfAbsent(script.getPluginId(), k -> new ArrayList<>());
|
||||
OptionDTO optionDTO = new OptionDTO();
|
||||
optionDTO.setId(script.getScriptId());
|
||||
optionDTO.setName(script.getName());
|
||||
scriptList.add(optionDTO);
|
||||
}
|
||||
return scriptMap;
|
||||
|
||||
}
|
||||
}
|
|
@ -1,23 +1,34 @@
|
|||
package io.metersphere.system.service;
|
||||
|
||||
|
||||
import io.metersphere.sdk.constants.PluginScenarioType;
|
||||
import io.metersphere.plugin.sdk.api.MsPlugin;
|
||||
import io.metersphere.sdk.constants.KafkaPluginTopicType;
|
||||
import io.metersphere.sdk.constants.KafkaTopicConstants;
|
||||
import io.metersphere.sdk.dto.OptionDTO;
|
||||
import io.metersphere.sdk.exception.MSException;
|
||||
import io.metersphere.sdk.service.PluginLoadService;
|
||||
import io.metersphere.sdk.util.BeanUtils;
|
||||
import io.metersphere.system.domain.Plugin;
|
||||
import io.metersphere.system.domain.PluginFrontScript;
|
||||
import io.metersphere.system.domain.PluginExample;
|
||||
import io.metersphere.system.dto.PluginDTO;
|
||||
import io.metersphere.system.mapper.PluginFrontScriptMapper;
|
||||
import io.metersphere.system.mapper.ExtPluginMapper;
|
||||
import io.metersphere.system.mapper.PluginMapper;
|
||||
import io.metersphere.system.request.PluginUpdateRequest;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_EXIST;
|
||||
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_TYPE_EXIST;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
* @date : 2023-7-13
|
||||
|
@ -29,50 +40,157 @@ public class PluginService {
|
|||
@Resource
|
||||
private PluginMapper pluginMapper;
|
||||
@Resource
|
||||
private PluginFrontScriptMapper pluginFrontScriptMapper;
|
||||
private ExtPluginMapper extPluginMapper;
|
||||
@Resource
|
||||
private PluginScriptService pluginScriptService;
|
||||
@Resource
|
||||
private PluginOrganizationService pluginOrganizationService;
|
||||
@Resource
|
||||
private PluginLoadService pluginLoadService;
|
||||
@Resource
|
||||
private KafkaTemplate<String, String> kafkaTemplate;
|
||||
|
||||
public List<PluginDTO> list() {
|
||||
return new ArrayList<>();
|
||||
List<PluginDTO> plugins = extPluginMapper.getPlugins();
|
||||
List<String> pluginIds = plugins.stream().map(Plugin::getId).toList();
|
||||
Map<String, List<OptionDTO>> scripteMap = pluginScriptService.getScripteMap(pluginIds);
|
||||
Map<String, List<OptionDTO>> orgMap = pluginOrganizationService.getOrgMap(pluginIds);
|
||||
plugins.forEach(plugin -> {
|
||||
plugin.setPluginForms(scripteMap.get(plugin.getId()));
|
||||
plugin.setOrganizations(orgMap.get(plugin.getId()));
|
||||
});
|
||||
return plugins;
|
||||
}
|
||||
|
||||
public PluginDTO get(String id) {
|
||||
Plugin plugin = pluginMapper.selectByPrimaryKey(id);
|
||||
PluginDTO pluginDTO = new PluginDTO();
|
||||
BeanUtils.copyBean(plugin, pluginDTO);
|
||||
return pluginDTO;
|
||||
public Plugin get(String id) {
|
||||
return pluginMapper.selectByPrimaryKey(id);
|
||||
}
|
||||
|
||||
public Plugin add(PluginUpdateRequest request, MultipartFile file) {
|
||||
String id = UUID.randomUUID().toString();
|
||||
Plugin plugin = new Plugin();
|
||||
BeanUtils.copyBean(plugin, request);
|
||||
plugin.setId(UUID.randomUUID().toString());
|
||||
plugin.setPluginId(UUID.randomUUID().toString());
|
||||
plugin.setFileName(file.getName());
|
||||
plugin.setId(id);
|
||||
plugin.setFileName(file.getOriginalFilename());
|
||||
plugin.setCreateTime(System.currentTimeMillis());
|
||||
plugin.setUpdateTime(System.currentTimeMillis());
|
||||
plugin.setXpack(false);
|
||||
plugin.setScenario(PluginScenarioType.PAI.name());
|
||||
|
||||
// 校验重名
|
||||
checkPluginAddExist(plugin);
|
||||
|
||||
try {
|
||||
// 加载插件
|
||||
pluginLoadService.loadPlugin(id, file);
|
||||
// 上传插件
|
||||
pluginLoadService.uploadPlugin(id, file);
|
||||
// 获取插件前端配置脚本
|
||||
List<String> frontendScript = pluginLoadService.getFrontendScripts(id);
|
||||
|
||||
MsPlugin msPlugin = pluginLoadService.getMsPluginInstance(id);
|
||||
plugin.setScenario(msPlugin.getType());
|
||||
plugin.setXpack(msPlugin.isXpack());
|
||||
plugin.setPluginId(msPlugin.getPluginId());
|
||||
|
||||
// 校验插件类型是否重复
|
||||
checkPluginKeyExist(id, msPlugin.getKey());
|
||||
|
||||
// 保存插件脚本
|
||||
pluginScriptService.add(id, frontendScript);
|
||||
|
||||
// 保存插件和组织的关联关系
|
||||
if (!request.getGlobal()) {
|
||||
pluginOrganizationService.add(id, request.getOrganizationIds());
|
||||
}
|
||||
|
||||
pluginMapper.insert(plugin);
|
||||
|
||||
// 通知其他节点加载插件
|
||||
notifiedPluginAdd(id, plugin.getFileName());
|
||||
} catch (Exception e) {
|
||||
// 删除插件
|
||||
pluginLoadService.deletePlugin(id);
|
||||
throw e;
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public Plugin update(PluginUpdateRequest request, MultipartFile file) {
|
||||
private void checkPluginKeyExist(String pluginId, String pluginKey) {
|
||||
if (pluginLoadService.hasPluginKey(pluginId, pluginKey)) {
|
||||
throw new MSException(PLUGIN_TYPE_EXIST);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPluginAddExist(Plugin plugin) {
|
||||
PluginExample example = new PluginExample();
|
||||
example.createCriteria()
|
||||
.andNameEqualTo(plugin.getName());
|
||||
PluginExample.Criteria criteria = example.createCriteria()
|
||||
.andFileNameEqualTo(plugin.getFileName());
|
||||
example.or(criteria);
|
||||
if (CollectionUtils.isNotEmpty(pluginMapper.selectByExample(example))) {
|
||||
throw new MSException(PLUGIN_EXIST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知其他节点加载插件
|
||||
* 这里需要传一下 fileName,事务未提交,查询不到文件名
|
||||
* @param pluginId
|
||||
* @param fileName
|
||||
*/
|
||||
public void notifiedPluginAdd(String pluginId, String fileName) {
|
||||
// 初始化项目默认节点
|
||||
kafkaTemplate.send(KafkaTopicConstants.PLUGIN, String.format("%s:%s:%s", KafkaPluginTopicType.ADD, pluginId, fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知其他节点卸载插件
|
||||
* @param pluginId
|
||||
*/
|
||||
public void notifiedPluginDelete(String pluginId) {
|
||||
// 初始化项目默认节点
|
||||
kafkaTemplate.send(KafkaTopicConstants.PLUGIN, String.format("%s:%s", KafkaPluginTopicType.DELETE, pluginId));
|
||||
}
|
||||
|
||||
public Plugin update(PluginUpdateRequest request) {
|
||||
request.setCreateUser(null);
|
||||
Plugin plugin = new Plugin();
|
||||
BeanUtils.copyBean(plugin, request);
|
||||
plugin.setCreateTime(null);
|
||||
plugin.setUpdateTime(null);
|
||||
plugin.setCreateUser(null);
|
||||
// 校验重名
|
||||
checkPluginUpdateExist(plugin);
|
||||
pluginMapper.updateByPrimaryKeySelective(plugin);
|
||||
if (request.getGlobal()) {
|
||||
// 全局插件,删除和组织的关联关系
|
||||
request.setOrganizationIds(new ArrayList<>(0));
|
||||
}
|
||||
pluginOrganizationService.update(plugin.getId(), request.getOrganizationIds());
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public String delete(String id) {
|
||||
private void checkPluginUpdateExist(Plugin plugin) {
|
||||
PluginExample example = new PluginExample();
|
||||
example.createCriteria()
|
||||
.andIdNotEqualTo(plugin.getId())
|
||||
.andNameEqualTo(plugin.getName());
|
||||
if (CollectionUtils.isNotEmpty(pluginMapper.selectByExample(example))) {
|
||||
throw new MSException(PLUGIN_EXIST);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(String id) {
|
||||
pluginMapper.deleteByPrimaryKey(id);
|
||||
return id;
|
||||
// 删除插件脚本
|
||||
pluginScriptService.deleteByPluginId(id);
|
||||
// 删除和组织的关联关系
|
||||
pluginOrganizationService.deleteByPluginId(id);
|
||||
// 删除和卸载插件
|
||||
pluginLoadService.deletePlugin(id);
|
||||
notifiedPluginDelete(id);
|
||||
}
|
||||
|
||||
public String getScript(String pluginId, String scriptId) {
|
||||
PluginFrontScript frontScript = pluginFrontScriptMapper.selectByPrimaryKey(pluginId, scriptId);
|
||||
return frontScript == null ? null : frontScript.getScript();
|
||||
return pluginScriptService.get(pluginId, scriptId);
|
||||
}
|
||||
}
|
|
@ -78,7 +78,7 @@
|
|||
<table tableName="operating_log"/>
|
||||
<table tableName="operating_log_resource"/>
|
||||
<table tableName="plugin"/>
|
||||
<table tableName="plugin_front_script"/>
|
||||
<table tableName="plugin_script"/>
|
||||
<table tableName="plugin_organization"/>
|
||||
<table tableName="schedule"/>
|
||||
<table tableName="service_integration"/>
|
||||
|
|
|
@ -2,20 +2,31 @@ package io.metersphere.system.controller;
|
|||
|
||||
import io.metersphere.sdk.base.BaseTest;
|
||||
import io.metersphere.sdk.constants.PermissionConstants;
|
||||
import io.metersphere.system.domain.Plugin;
|
||||
import io.metersphere.sdk.constants.PluginScenarioType;
|
||||
import io.metersphere.sdk.dto.OptionDTO;
|
||||
import io.metersphere.sdk.log.constants.OperationLogType;
|
||||
import io.metersphere.sdk.util.JSON;
|
||||
import io.metersphere.system.controller.param.PluginUpdateRequestDefinition;
|
||||
import io.metersphere.system.domain.*;
|
||||
import io.metersphere.system.dto.OrganizationDTO;
|
||||
import io.metersphere.system.dto.PluginDTO;
|
||||
import io.metersphere.system.mapper.PluginMapper;
|
||||
import io.metersphere.system.mapper.PluginOrganizationMapper;
|
||||
import io.metersphere.system.mapper.PluginScriptMapper;
|
||||
import io.metersphere.system.request.PluginUpdateRequest;
|
||||
import io.metersphere.system.service.OrganizationService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
import static io.metersphere.system.controller.result.SystemResultCode.*;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
|
@ -29,99 +40,240 @@ public class PluginControllerTests extends BaseTest {
|
|||
private static final String SCRIPT_GET = "script/get/{0}/{1}";
|
||||
@Resource
|
||||
private PluginMapper pluginMapper;
|
||||
@Resource
|
||||
private PluginOrganizationMapper pluginOrganizationMapper;
|
||||
@Resource
|
||||
private PluginScriptMapper pluginScriptMapper;
|
||||
@Resource
|
||||
private OrganizationService organizationService;
|
||||
private static Plugin addPlugin;
|
||||
private static Plugin anotherAddPlugin;
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return BASE_PATH;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list() throws Exception {
|
||||
// @@请求成功
|
||||
this.requestGetWithOk(DEFAULT_LIST)
|
||||
.andReturn();
|
||||
// List<Plugin> pluginList = getResultDataArray(mvcResult, Plugin.class);
|
||||
// todo 校验数据是否正确
|
||||
// @@校验权限
|
||||
requestGetPermissionTest(PermissionConstants.SYSTEM_PLUGIN_READ, DEFAULT_LIST);
|
||||
@Order(0)
|
||||
public void listEmpty() throws Exception {
|
||||
// @@没有数据是校验是否成功
|
||||
this.requestGetWithOkAndReturn(DEFAULT_LIST);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(0)
|
||||
@Order(1)
|
||||
public void add() throws Exception {
|
||||
// @@请求成功
|
||||
PluginUpdateRequest request = new PluginUpdateRequest();
|
||||
OrganizationDTO org = organizationService.getDefault();
|
||||
File jarFile = new File(
|
||||
this.getClass().getClassLoader().getResource("file/metersphere-mqtt-plugin-3.x.jar")
|
||||
.getPath()
|
||||
);
|
||||
File anotherJarFile = new File(
|
||||
this.getClass().getClassLoader().getResource("file/metersphere-jira-plugin-3.x.jar")
|
||||
.getPath()
|
||||
);
|
||||
|
||||
request.setName("test");
|
||||
request.setDescription("test desc");
|
||||
MultiValueMap<String, Object> multiValueMap = getDefaultMultiPartParam(request,
|
||||
new File("src/test/resources/application.properties"));
|
||||
request.setGlobal(false);
|
||||
request.setEnable(false);
|
||||
request.setOrganizationIds(Arrays.asList(org.getId()));
|
||||
MultiValueMap<String, Object> multiValueMap = getDefaultMultiPartParam(request, jarFile);
|
||||
|
||||
MvcResult mvcResult = this.requestMultipartWithOkAndReturn(DEFAULT_ADD, multiValueMap);
|
||||
// 校验数据是否正确
|
||||
Plugin resultData = getResultData(mvcResult, Plugin.class);
|
||||
Plugin plugin = pluginMapper.selectByPrimaryKey(resultData.getId());
|
||||
Assertions.assertEquals(plugin.getName(), request.getName());
|
||||
Assertions.assertEquals(plugin.getDescription(), request.getDescription());
|
||||
Assertions.assertEquals(plugin.getEnable(), request.getEnable());
|
||||
Assertions.assertEquals(plugin.getGlobal(), request.getGlobal());
|
||||
Assertions.assertEquals(plugin.getXpack(), false);
|
||||
Assertions.assertEquals(plugin.getFileName(), jarFile.getName());
|
||||
Assertions.assertEquals(plugin.getScenario(), PluginScenarioType.API.name());
|
||||
Assertions.assertEquals(Arrays.asList(org.getId()), getOrgIdsByPlugId(plugin.getId()));
|
||||
Assertions.assertEquals(Arrays.asList("connect", "disconnect", "pub", "sub"), getScriptIdsByPlugId(plugin.getId()));
|
||||
addPlugin = plugin;
|
||||
|
||||
// @@重名校验异常
|
||||
// 校验插件名称重名
|
||||
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
|
||||
getDefaultMultiPartParam(request, anotherJarFile)), PLUGIN_EXIST);
|
||||
|
||||
// 校验文件名重名
|
||||
request.setName("test1");
|
||||
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
|
||||
getDefaultMultiPartParam(request, jarFile)), PLUGIN_EXIST);
|
||||
|
||||
// 校验插件 key 重复
|
||||
File typeRepeatFile = new File(
|
||||
this.getClass().getClassLoader().getResource("file/metersphere-mqtt-plugin-repeat-key.jar")
|
||||
.getPath()
|
||||
);
|
||||
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
|
||||
getDefaultMultiPartParam(request, typeRepeatFile)), PLUGIN_TYPE_EXIST);
|
||||
|
||||
// @@校验插件脚本解析失败
|
||||
File scriptParseFile = new File(
|
||||
this.getClass().getClassLoader().getResource("file/metersphere-plugin-script-parse-error.jar")
|
||||
.getPath()
|
||||
);
|
||||
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
|
||||
getDefaultMultiPartParam(request, scriptParseFile)), PLUGIN_SCRIPT_FORMAT);
|
||||
|
||||
// @@校验插件脚本ID重复
|
||||
File scriptIdRepeatFile = new File(
|
||||
this.getClass().getClassLoader().getResource("file/metersphere-plugin-script-id-repeat-error.jar")
|
||||
.getPath()
|
||||
);
|
||||
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
|
||||
getDefaultMultiPartParam(request, scriptIdRepeatFile)), PLUGIN_SCRIPT_EXIST);
|
||||
|
||||
request.setGlobal(true);
|
||||
request.setEnable(true);
|
||||
request.setName("test2");
|
||||
MvcResult antoherMvcResult = this.requestMultipartWithOkAndReturn(DEFAULT_ADD,
|
||||
getDefaultMultiPartParam(request, anotherJarFile));
|
||||
// 校验 global 为 tru e时,organizationIds 为空
|
||||
Plugin antoherPlugin = pluginMapper.selectByPrimaryKey(getResultData(antoherMvcResult, Plugin.class).getId());
|
||||
Assertions.assertEquals(antoherPlugin.getEnable(), request.getEnable());
|
||||
Assertions.assertEquals(antoherPlugin.getGlobal(), request.getGlobal());
|
||||
Assertions.assertEquals(new ArrayList<>(0), getOrgIdsByPlugId(antoherPlugin.getId()));
|
||||
anotherAddPlugin = antoherPlugin;
|
||||
|
||||
this.addPlugin = plugin;
|
||||
// todo 校验请求成功数据
|
||||
|
||||
// @@校验日志
|
||||
// checkLog(this.addPlugin.getId(), OperationLogType.ADD);
|
||||
// @@异常参数校验
|
||||
// createdGroupParamValidateTest(PluginUpdateRequestDefinition.class, ADD);
|
||||
checkLog(this.addPlugin.getId(), OperationLogType.ADD);
|
||||
// @@校验权限
|
||||
requestMultipartPermissionTest(PermissionConstants.SYSTEM_PLUGIN_ADD, DEFAULT_ADD, multiValueMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void get() throws Exception {
|
||||
// @@请求成功
|
||||
this.requestGetWithOk(DEFAULT_GET, this.addPlugin.getId())
|
||||
.andReturn();
|
||||
// Plugin plugin = getResultData(mvcResult, Plugin.class);
|
||||
// todo 校验数据是否正确
|
||||
// @@校验权限
|
||||
requestGetPermissionTest(PermissionConstants.SYSTEM_PLUGIN_READ, DEFAULT_GET, this.addPlugin.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void getScript() throws Exception {
|
||||
// @@请求成功
|
||||
this.requestGetWithOk(SCRIPT_GET, this.addPlugin.getId(), "script id")
|
||||
.andReturn();
|
||||
// Plugin plugin = getResultData(mvcResult, Plugin.class);
|
||||
// todo 校验数据是否正确
|
||||
// @@校验权限
|
||||
requestGetPermissionTest(PermissionConstants.SYSTEM_PLUGIN_READ, SCRIPT_GET, this.addPlugin.getId(), "script id");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void update() throws Exception {
|
||||
// @@请求成功
|
||||
PluginUpdateRequest request = new PluginUpdateRequest();
|
||||
OrganizationDTO org = organizationService.getDefault();
|
||||
request.setId(addPlugin.getId());
|
||||
request.setName("test update");
|
||||
request.setCreateUser("test update user");
|
||||
request.setDescription("test update desc");
|
||||
request.setEnable(true);
|
||||
request.setGlobal(true);
|
||||
request.setOrganizationIds(Arrays.asList(org.getId()));
|
||||
|
||||
MultiValueMap<String, Object> multiValueMap = getDefaultMultiPartParam(request,
|
||||
new File("src/test/resources/application.properties"));
|
||||
this.requestMultipartWithOk(DEFAULT_UPDATE, multiValueMap);
|
||||
this.requestPostWithOk(DEFAULT_UPDATE, request);
|
||||
// 校验请求成功数据
|
||||
// Plugin plugin = pluginMapper.selectByPrimaryKey(request.getId());
|
||||
// todo 校验请求成功数据
|
||||
Plugin plugin = pluginMapper.selectByPrimaryKey(request.getId());
|
||||
Assertions.assertEquals(plugin.getName(), request.getName());
|
||||
Assertions.assertEquals(plugin.getDescription(), request.getDescription());
|
||||
Assertions.assertEquals(plugin.getEnable(), request.getEnable());
|
||||
Assertions.assertEquals(plugin.getGlobal(), request.getGlobal());
|
||||
Assertions.assertEquals(plugin.getXpack(), false);
|
||||
// 校验 global 为 true 时,organizationIds 为空
|
||||
Assertions.assertEquals(new ArrayList<>(0), getOrgIdsByPlugId(plugin.getId()));
|
||||
|
||||
// 这些数据不能修改
|
||||
Assertions.assertEquals(plugin.getFileName(), addPlugin.getFileName());
|
||||
Assertions.assertEquals(plugin.getScenario(), addPlugin.getScenario());
|
||||
Assertions.assertEquals(plugin.getCreateUser(), addPlugin.getCreateUser());
|
||||
|
||||
// 校验 global 为 false 时,organizationIds 数据
|
||||
request.setGlobal(false);
|
||||
this.requestPostWithOk(DEFAULT_UPDATE, request);
|
||||
Assertions.assertEquals(Arrays.asList(org.getId()), getOrgIdsByPlugId(plugin.getId()));
|
||||
|
||||
// 校验组织为null,不修改关联关系
|
||||
request.setOrganizationIds(null);
|
||||
this.requestPostWithOk(DEFAULT_UPDATE, request);
|
||||
Assertions.assertEquals(Arrays.asList(org.getId()), getOrgIdsByPlugId(plugin.getId()));
|
||||
|
||||
// @@重名校验异常
|
||||
request.setName(anotherAddPlugin.getName());
|
||||
assertErrorCode( this.requestPost(DEFAULT_UPDATE, request), PLUGIN_EXIST);
|
||||
|
||||
// @@校验日志
|
||||
// checkLog(request.getId(), OperationLogType.UPDATE);
|
||||
checkLog(request.getId(), OperationLogType.UPDATE);
|
||||
// @@异常参数校验
|
||||
// updatedGroupParamValidateTest(PluginUpdateRequestDefinition.class, UPDATE);
|
||||
updatedGroupParamValidateTest(PluginUpdateRequestDefinition.class, DEFAULT_UPDATE);
|
||||
// @@校验权限
|
||||
requestMultipartPermissionTest(PermissionConstants.SYSTEM_PLUGIN_UPDATE, DEFAULT_UPDATE, multiValueMap);
|
||||
requestPostPermissionTest(PermissionConstants.SYSTEM_PLUGIN_UPDATE, DEFAULT_UPDATE, request);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void getScript() throws Exception {
|
||||
// @@请求成功
|
||||
MvcResult mvcResult = this.requestGetWithOk(SCRIPT_GET, this.addPlugin.getId(), "connect").andReturn();
|
||||
// 校验数据是否正确
|
||||
Assertions.assertTrue(StringUtils.isNotBlank(getResultData(mvcResult, String.class)));
|
||||
// @@校验权限
|
||||
requestGetPermissionTest(PermissionConstants.SYSTEM_PLUGIN_READ, SCRIPT_GET, this.addPlugin.getId(), "connect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void list() throws Exception {
|
||||
// @@请求成功
|
||||
MvcResult mvcResult = this.requestGetWithOkAndReturn(DEFAULT_LIST);
|
||||
// 校验数据是否正确
|
||||
List<PluginDTO> pluginList = getResultDataArray(mvcResult, PluginDTO.class);
|
||||
Assertions.assertEquals(2, pluginList.size());
|
||||
for (PluginDTO pluginDTO : pluginList) {
|
||||
Plugin comparePlugin = null;
|
||||
if (StringUtils.equals(pluginDTO.getId(), addPlugin.getId())) {
|
||||
comparePlugin = pluginMapper.selectByPrimaryKey(addPlugin.getId());
|
||||
} else if (StringUtils.equals(pluginDTO.getId(), anotherAddPlugin.getId())) {
|
||||
comparePlugin = pluginMapper.selectByPrimaryKey(anotherAddPlugin.getId());
|
||||
}
|
||||
Plugin plugin = JSON.parseObject(JSON.toJSONString(pluginDTO), Plugin.class);
|
||||
List<String> scriptIds = pluginDTO.getPluginForms().stream().map(OptionDTO::getId).toList();
|
||||
Assertions.assertEquals(plugin, comparePlugin);
|
||||
Assertions.assertEquals(scriptIds, getScriptIdsByPlugId(plugin.getId()));
|
||||
List<OptionDTO> orgList = Optional.ofNullable(pluginDTO.getOrganizations()).orElse(new ArrayList<>(0));
|
||||
Assertions.assertEquals(orgList.stream().map(OptionDTO::getId).toList(), getOrgIdsByPlugId(plugin.getId()));
|
||||
}
|
||||
|
||||
// @@校验权限
|
||||
requestGetPermissionTest(PermissionConstants.SYSTEM_PLUGIN_READ, DEFAULT_LIST);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void delete() throws Exception {
|
||||
// @@请求成功
|
||||
this.requestGetWithOk(DEFAULT_DELETE, addPlugin.getId());
|
||||
// todo 校验请求成功数据
|
||||
// 校验请求成功数据
|
||||
Plugin plugin = pluginMapper.selectByPrimaryKey(addPlugin.getId());
|
||||
Assertions.assertNull(plugin);
|
||||
Assertions.assertEquals(new ArrayList<>(0), getOrgIdsByPlugId(addPlugin.getId()));
|
||||
Assertions.assertEquals(new ArrayList<>(0), getScriptIdsByPlugId(addPlugin.getId()));
|
||||
|
||||
// @@校验日志
|
||||
// checkLog(addPlugin.getId(), OperationLogType.DELETE);
|
||||
checkLog(addPlugin.getId(), OperationLogType.DELETE);
|
||||
// @@校验权限
|
||||
requestGetPermissionTest(PermissionConstants.SYSTEM_PLUGIN_DELETE, DEFAULT_DELETE, addPlugin.getId());
|
||||
}
|
||||
|
||||
private List<String> getOrgIdsByPlugId(String pluginId) {
|
||||
PluginOrganizationExample example = new PluginOrganizationExample();
|
||||
example.createCriteria().andPluginIdEqualTo(pluginId);
|
||||
return pluginOrganizationMapper.selectByExample(example)
|
||||
.stream()
|
||||
.map(PluginOrganization::getOrganizationId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<String> getScriptIdsByPlugId(String pluginId) {
|
||||
PluginScriptExample example = new PluginScriptExample();
|
||||
example.createCriteria().andPluginIdEqualTo(pluginId);
|
||||
return pluginScriptMapper.selectByExample(example)
|
||||
.stream()
|
||||
.map(PluginScript::getScriptId)
|
||||
.toList();
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import lombok.Data;
|
|||
@Data
|
||||
public class PluginUpdateRequestDefinition {
|
||||
@NotBlank(message = "{plugin.id.not_blank}", groups = {Updated.class})
|
||||
@Size(min = 1, max = 50, message = "{plugin.id.length_range}", groups = {Created.class, Updated.class})
|
||||
@Size(min = 1, max = 50, message = "{plugin.id.length_range}", groups = {Updated.class})
|
||||
private String id;
|
||||
|
||||
@NotBlank(groups = {Created.class})
|
||||
|
@ -18,8 +18,4 @@ public class PluginUpdateRequestDefinition {
|
|||
|
||||
@Size(min = 1, max = 500, groups = {Created.class, Updated.class})
|
||||
private String description;
|
||||
|
||||
@NotBlank(groups = {Created.class})
|
||||
@Size(min = 1, max = 50, groups = {Created.class, Updated.class})
|
||||
private String scenario;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,6 @@ embedded.mysql.collation=utf8mb4_general_ci
|
|||
# redis
|
||||
embedded.redis.enabled=true
|
||||
# kafka
|
||||
embedded.kafka.enabled=false
|
||||
embedded.kafka.enabled=true
|
||||
# minio
|
||||
embedded.minio.enabled=true
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue