From 96327dd07958a3eeeed49fc8cd9300387f64b608 Mon Sep 17 00:00:00 2001 From: guoyuqi Date: Fri, 31 May 2024 11:52:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=B3=BB=E7=BB=9F=E8=AE=BE=E7=BD=AE):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E6=89=AB?= =?UTF-8?q?=E7=A0=81=E7=99=BB=E9=99=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/domain/PlatformSource.java | 104 +++++ .../system/domain/PlatformSourceExample.java | 390 ++++++++++++++++++ .../system/mapper/PlatformSourceMapper.java | 40 ++ .../system/mapper/PlatformSourceMapper.xml | 269 ++++++++++++ .../migration/3.0.0/ddl/V3.0.0_13__ga_ddl.sql | 19 + .../metersphere/sdk/constants/UserSource.java | 2 +- .../sdk/util/FilterChainUtils.java | 3 + .../config/interceptor/SystemInterceptor.java | 2 + .../log/constants/OperationLogType.java | 1 + frontend/package.json | 1 + frontend/src/api/modules/user/index.ts | 19 + frontend/src/api/requrls/user.ts | 3 + frontend/src/assets/svg/scan_code.svg | 3 + frontend/src/models/user.ts | 8 + frontend/src/store/modules/user/index.ts | 17 + .../src/views/login/components/login-form.vue | 93 +++-- .../src/views/login/components/tabQrCode.vue | 99 +++++ .../views/login/components/weComQrCode.vue | 87 ++++ .../messageManagement/locale/en-US.ts | 1 + .../messageManagement/locale/zh-CN.ts | 1 + 20 files changed, 1137 insertions(+), 25 deletions(-) create mode 100644 backend/framework/domain/src/main/java/io/metersphere/system/domain/PlatformSource.java create mode 100644 backend/framework/domain/src/main/java/io/metersphere/system/domain/PlatformSourceExample.java create mode 100644 backend/framework/domain/src/main/java/io/metersphere/system/mapper/PlatformSourceMapper.java create mode 100644 backend/framework/domain/src/main/java/io/metersphere/system/mapper/PlatformSourceMapper.xml create mode 100644 backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_13__ga_ddl.sql create mode 100644 frontend/src/assets/svg/scan_code.svg create mode 100644 frontend/src/views/login/components/tabQrCode.vue create mode 100644 frontend/src/views/login/components/weComQrCode.vue diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/domain/PlatformSource.java b/backend/framework/domain/src/main/java/io/metersphere/system/domain/PlatformSource.java new file mode 100644 index 0000000000..616e4e82a1 --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/system/domain/PlatformSource.java @@ -0,0 +1,104 @@ +package io.metersphere.system.domain; + +import io.metersphere.validation.groups.*; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import lombok.Data; + +@Data +public class PlatformSource implements Serializable { + @Schema(description = "平台名称(国际飞书:LARK_SUITE,飞书:LARK,钉钉:DING_TALK,企业微信:WE_COM)", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{platform_source.platform.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 50, message = "{platform_source.platform.length_range}", groups = {Created.class, Updated.class}) + private String platform; + + @Schema(description = "是否开启") + private Boolean enable; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{platform_source.valid.not_blank}", groups = {Created.class}) + private Boolean valid; + + @Schema(description = "平台信息配置", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{platform_source.config.not_blank}", groups = {Created.class}) + private byte[] config; + + private static final long serialVersionUID = 1L; + + public enum Column { + platform("platform", "platform", "VARCHAR", false), + enable("enable", "enable", "BIT", true), + valid("valid", "valid", "BIT", true), + config("config", "config", "LONGVARBINARY", false); + + private static final String BEGINNING_DELIMITER = "`"; + + private static final String ENDING_DELIMITER = "`"; + + private final String column; + + private final boolean isColumnNameDelimited; + + private final String javaProperty; + + private final String jdbcType; + + public String value() { + return this.column; + } + + public String getValue() { + return this.column; + } + + public String getJavaProperty() { + return this.javaProperty; + } + + public String getJdbcType() { + return this.jdbcType; + } + + Column(String column, String javaProperty, String jdbcType, boolean isColumnNameDelimited) { + this.column = column; + this.javaProperty = javaProperty; + this.jdbcType = jdbcType; + this.isColumnNameDelimited = isColumnNameDelimited; + } + + public String desc() { + return this.getEscapedColumnName() + " DESC"; + } + + public String asc() { + return this.getEscapedColumnName() + " ASC"; + } + + public static Column[] excludes(Column ... excludes) { + ArrayList columns = new ArrayList<>(Arrays.asList(Column.values())); + if (excludes != null && excludes.length > 0) { + columns.removeAll(new ArrayList<>(Arrays.asList(excludes))); + } + return columns.toArray(new Column[]{}); + } + + public static Column[] all() { + return Column.values(); + } + + public String getEscapedColumnName() { + if (this.isColumnNameDelimited) { + return new StringBuilder().append(BEGINNING_DELIMITER).append(this.column).append(ENDING_DELIMITER).toString(); + } else { + return this.column; + } + } + + public String getAliasedEscapedColumnName() { + return this.getEscapedColumnName(); + } + } +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/domain/PlatformSourceExample.java b/backend/framework/domain/src/main/java/io/metersphere/system/domain/PlatformSourceExample.java new file mode 100644 index 0000000000..8efc7cf8f8 --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/system/domain/PlatformSourceExample.java @@ -0,0 +1,390 @@ +package io.metersphere.system.domain; + +import java.util.ArrayList; +import java.util.List; + +public class PlatformSourceExample { + protected String orderByClause; + + protected boolean distinct; + + protected List oredCriteria; + + public PlatformSourceExample() { + oredCriteria = new ArrayList(); + } + + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; + } + + public String getOrderByClause() { + return orderByClause; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public boolean isDistinct() { + return distinct; + } + + public List getOredCriteria() { + return oredCriteria; + } + + public void or(Criteria criteria) { + oredCriteria.add(criteria); + } + + public Criteria or() { + Criteria criteria = createCriteriaInternal(); + oredCriteria.add(criteria); + return criteria; + } + + public Criteria createCriteria() { + Criteria criteria = createCriteriaInternal(); + if (oredCriteria.size() == 0) { + oredCriteria.add(criteria); + } + return criteria; + } + + protected Criteria createCriteriaInternal() { + Criteria criteria = new Criteria(); + return criteria; + } + + public void clear() { + oredCriteria.clear(); + orderByClause = null; + distinct = false; + } + + protected abstract static class GeneratedCriteria { + protected List criteria; + + protected GeneratedCriteria() { + super(); + criteria = new ArrayList(); + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List getCriteria() { + return criteria; + } + + protected void addCriterion(String condition) { + if (condition == null) { + throw new RuntimeException("Value for condition cannot be null"); + } + criteria.add(new Criterion(condition)); + } + + protected void addCriterion(String condition, Object value, String property) { + if (value == null) { + throw new RuntimeException("Value for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value)); + } + + protected void addCriterion(String condition, Object value1, Object value2, String property) { + if (value1 == null || value2 == null) { + throw new RuntimeException("Between values for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value1, value2)); + } + + public Criteria andPlatformIsNull() { + addCriterion("platform is null"); + return (Criteria) this; + } + + public Criteria andPlatformIsNotNull() { + addCriterion("platform is not null"); + return (Criteria) this; + } + + public Criteria andPlatformEqualTo(String value) { + addCriterion("platform =", value, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformNotEqualTo(String value) { + addCriterion("platform <>", value, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformGreaterThan(String value) { + addCriterion("platform >", value, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformGreaterThanOrEqualTo(String value) { + addCriterion("platform >=", value, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformLessThan(String value) { + addCriterion("platform <", value, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformLessThanOrEqualTo(String value) { + addCriterion("platform <=", value, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformLike(String value) { + addCriterion("platform like", value, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformNotLike(String value) { + addCriterion("platform not like", value, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformIn(List values) { + addCriterion("platform in", values, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformNotIn(List values) { + addCriterion("platform not in", values, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformBetween(String value1, String value2) { + addCriterion("platform between", value1, value2, "platform"); + return (Criteria) this; + } + + public Criteria andPlatformNotBetween(String value1, String value2) { + addCriterion("platform not between", value1, value2, "platform"); + return (Criteria) this; + } + + public Criteria andEnableIsNull() { + addCriterion("`enable` is null"); + return (Criteria) this; + } + + public Criteria andEnableIsNotNull() { + addCriterion("`enable` is not null"); + return (Criteria) this; + } + + public Criteria andEnableEqualTo(Boolean value) { + addCriterion("`enable` =", value, "enable"); + return (Criteria) this; + } + + public Criteria andEnableNotEqualTo(Boolean value) { + addCriterion("`enable` <>", value, "enable"); + return (Criteria) this; + } + + public Criteria andEnableGreaterThan(Boolean value) { + addCriterion("`enable` >", value, "enable"); + return (Criteria) this; + } + + public Criteria andEnableGreaterThanOrEqualTo(Boolean value) { + addCriterion("`enable` >=", value, "enable"); + return (Criteria) this; + } + + public Criteria andEnableLessThan(Boolean value) { + addCriterion("`enable` <", value, "enable"); + return (Criteria) this; + } + + public Criteria andEnableLessThanOrEqualTo(Boolean value) { + addCriterion("`enable` <=", value, "enable"); + return (Criteria) this; + } + + public Criteria andEnableIn(List values) { + addCriterion("`enable` in", values, "enable"); + return (Criteria) this; + } + + public Criteria andEnableNotIn(List values) { + addCriterion("`enable` not in", values, "enable"); + return (Criteria) this; + } + + public Criteria andEnableBetween(Boolean value1, Boolean value2) { + addCriterion("`enable` between", value1, value2, "enable"); + return (Criteria) this; + } + + public Criteria andEnableNotBetween(Boolean value1, Boolean value2) { + addCriterion("`enable` not between", value1, value2, "enable"); + return (Criteria) this; + } + + public Criteria andValidIsNull() { + addCriterion("`valid` is null"); + return (Criteria) this; + } + + public Criteria andValidIsNotNull() { + addCriterion("`valid` is not null"); + return (Criteria) this; + } + + public Criteria andValidEqualTo(Boolean value) { + addCriterion("`valid` =", value, "valid"); + return (Criteria) this; + } + + public Criteria andValidNotEqualTo(Boolean value) { + addCriterion("`valid` <>", value, "valid"); + return (Criteria) this; + } + + public Criteria andValidGreaterThan(Boolean value) { + addCriterion("`valid` >", value, "valid"); + return (Criteria) this; + } + + public Criteria andValidGreaterThanOrEqualTo(Boolean value) { + addCriterion("`valid` >=", value, "valid"); + return (Criteria) this; + } + + public Criteria andValidLessThan(Boolean value) { + addCriterion("`valid` <", value, "valid"); + return (Criteria) this; + } + + public Criteria andValidLessThanOrEqualTo(Boolean value) { + addCriterion("`valid` <=", value, "valid"); + return (Criteria) this; + } + + public Criteria andValidIn(List values) { + addCriterion("`valid` in", values, "valid"); + return (Criteria) this; + } + + public Criteria andValidNotIn(List values) { + addCriterion("`valid` not in", values, "valid"); + return (Criteria) this; + } + + public Criteria andValidBetween(Boolean value1, Boolean value2) { + addCriterion("`valid` between", value1, value2, "valid"); + return (Criteria) this; + } + + public Criteria andValidNotBetween(Boolean value1, Boolean value2) { + addCriterion("`valid` not between", value1, value2, "valid"); + return (Criteria) this; + } + } + + public static class Criteria extends GeneratedCriteria { + + protected Criteria() { + super(); + } + } + + public static class Criterion { + private String condition; + + private Object value; + + private Object secondValue; + + private boolean noValue; + + private boolean singleValue; + + private boolean betweenValue; + + private boolean listValue; + + private String typeHandler; + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } + + protected Criterion(String condition) { + super(); + this.condition = condition; + this.typeHandler = null; + this.noValue = true; + } + + protected Criterion(String condition, Object value, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.typeHandler = typeHandler; + if (value instanceof List) { + this.listValue = true; + } else { + this.singleValue = true; + } + } + + protected Criterion(String condition, Object value) { + this(condition, value, null); + } + + protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.secondValue = secondValue; + this.typeHandler = typeHandler; + this.betweenValue = true; + } + + protected Criterion(String condition, Object value, Object secondValue) { + this(condition, value, secondValue, null); + } + } +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/mapper/PlatformSourceMapper.java b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/PlatformSourceMapper.java new file mode 100644 index 0000000000..5c81c2c87d --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/PlatformSourceMapper.java @@ -0,0 +1,40 @@ +package io.metersphere.system.mapper; + +import io.metersphere.system.domain.PlatformSource; +import io.metersphere.system.domain.PlatformSourceExample; +import java.util.List; +import org.apache.ibatis.annotations.Param; + +public interface PlatformSourceMapper { + long countByExample(PlatformSourceExample example); + + int deleteByExample(PlatformSourceExample example); + + int deleteByPrimaryKey(String platform); + + int insert(PlatformSource record); + + int insertSelective(PlatformSource record); + + List selectByExampleWithBLOBs(PlatformSourceExample example); + + List selectByExample(PlatformSourceExample example); + + PlatformSource selectByPrimaryKey(String platform); + + int updateByExampleSelective(@Param("record") PlatformSource record, @Param("example") PlatformSourceExample example); + + int updateByExampleWithBLOBs(@Param("record") PlatformSource record, @Param("example") PlatformSourceExample example); + + int updateByExample(@Param("record") PlatformSource record, @Param("example") PlatformSourceExample example); + + int updateByPrimaryKeySelective(PlatformSource record); + + int updateByPrimaryKeyWithBLOBs(PlatformSource record); + + int updateByPrimaryKey(PlatformSource record); + + int batchInsert(@Param("list") List list); + + int batchInsertSelective(@Param("list") List list, @Param("selective") PlatformSource.Column ... selective); +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/mapper/PlatformSourceMapper.xml b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/PlatformSourceMapper.xml new file mode 100644 index 0000000000..bef886618c --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/PlatformSourceMapper.xml @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + platform, `enable`, `valid` + + + config + + + + + + delete from platform_source + where platform = #{platform,jdbcType=VARCHAR} + + + delete from platform_source + + + + + + insert into platform_source (platform, `enable`, `valid`, + config) + values (#{platform,jdbcType=VARCHAR}, #{enable,jdbcType=BIT}, #{valid,jdbcType=BIT}, + #{config,jdbcType=LONGVARBINARY}) + + + insert into platform_source + + + platform, + + + `enable`, + + + `valid`, + + + config, + + + + + #{platform,jdbcType=VARCHAR}, + + + #{enable,jdbcType=BIT}, + + + #{valid,jdbcType=BIT}, + + + #{config,jdbcType=LONGVARBINARY}, + + + + + + update platform_source + + + platform = #{record.platform,jdbcType=VARCHAR}, + + + `enable` = #{record.enable,jdbcType=BIT}, + + + `valid` = #{record.valid,jdbcType=BIT}, + + + config = #{record.config,jdbcType=LONGVARBINARY}, + + + + + + + + update platform_source + set platform = #{record.platform,jdbcType=VARCHAR}, + `enable` = #{record.enable,jdbcType=BIT}, + `valid` = #{record.valid,jdbcType=BIT}, + config = #{record.config,jdbcType=LONGVARBINARY} + + + + + + update platform_source + set platform = #{record.platform,jdbcType=VARCHAR}, + `enable` = #{record.enable,jdbcType=BIT}, + `valid` = #{record.valid,jdbcType=BIT} + + + + + + update platform_source + + + `enable` = #{enable,jdbcType=BIT}, + + + `valid` = #{valid,jdbcType=BIT}, + + + config = #{config,jdbcType=LONGVARBINARY}, + + + where platform = #{platform,jdbcType=VARCHAR} + + + update platform_source + set `enable` = #{enable,jdbcType=BIT}, + `valid` = #{valid,jdbcType=BIT}, + config = #{config,jdbcType=LONGVARBINARY} + where platform = #{platform,jdbcType=VARCHAR} + + + update platform_source + set `enable` = #{enable,jdbcType=BIT}, + `valid` = #{valid,jdbcType=BIT} + where platform = #{platform,jdbcType=VARCHAR} + + + insert into platform_source + (platform, `enable`, `valid`, config) + values + + (#{item.platform,jdbcType=VARCHAR}, #{item.enable,jdbcType=BIT}, #{item.valid,jdbcType=BIT}, + #{item.config,jdbcType=LONGVARBINARY}) + + + + insert into platform_source ( + + ${column.escapedColumnName} + + ) + values + + ( + + + #{item.platform,jdbcType=VARCHAR} + + + #{item.enable,jdbcType=BIT} + + + #{item.valid,jdbcType=BIT} + + + #{item.config,jdbcType=LONGVARBINARY} + + + ) + + + \ No newline at end of file diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_13__ga_ddl.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_13__ga_ddl.sql new file mode 100644 index 0000000000..68b6b6d49b --- /dev/null +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_13__ga_ddl.sql @@ -0,0 +1,19 @@ +-- set innodb lock wait timeout +SET SESSION innodb_lock_wait_timeout = 7200; + +DROP TABLE IF EXISTS platform_source; +CREATE TABLE platform_source( + `platform` VARCHAR(50) NOT NULL COMMENT '平台名称(国际飞书:LARK_SUITE,飞书:LARK,钉钉:DING_TALK,企业微信:WE_COM)' , + `config` BLOB NOT NULL COMMENT '平台信息配置' , + `enable` BIT NOT NULL DEFAULT 1 COMMENT '是否开启' , + `valid` BIT NOT NULL DEFAULT 0 COMMENT '名称' , + PRIMARY KEY (platform) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci COMMENT = '平台对接保存参数'; + + +-- set innodb lock wait timeout to default +SET SESSION innodb_lock_wait_timeout = DEFAULT; + + diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/UserSource.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/UserSource.java index df9eaba027..5a09d920a1 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/UserSource.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/UserSource.java @@ -1,5 +1,5 @@ package io.metersphere.sdk.constants; public enum UserSource { - LOCAL, LDAP, CAS, OIDC, OAUTH2 + LOCAL, LDAP, CAS, OIDC, OAUTH2, QR_CODE } diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java index ec16d8d2cc..a5a7fb1005 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java @@ -11,6 +11,9 @@ public class FilterChainUtils { filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/ldap/login", "anon"); filterChainDefinitionMap.put("/authentication/get-list", "anon"); + filterChainDefinitionMap.put("/we_com/info", "anon"); + filterChainDefinitionMap.put("/sso/callback/we_com", "anon"); + filterChainDefinitionMap.put("/setting/get/platform/param", "anon"); filterChainDefinitionMap.put("/signout", "anon"); filterChainDefinitionMap.put("/is-login", "anon"); filterChainDefinitionMap.put("/get-key", "anon"); diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/SystemInterceptor.java b/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/SystemInterceptor.java index cedc863384..0853dde40f 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/SystemInterceptor.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/config/interceptor/SystemInterceptor.java @@ -21,6 +21,8 @@ public class SystemInterceptor { 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(PlatformSource.class, "config", CompressUtils.class, "zip", "unzip")); + return configList; } diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogType.java b/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogType.java index 06166200bc..390f7288d2 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogType.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogType.java @@ -18,6 +18,7 @@ public enum OperationLogType { LOGOUT, DISASSOCIATE, ASSOCIATE, + QRCODE, ARCHIVED; public boolean contains(OperationLogType keyword) { diff --git a/frontend/package.json b/frontend/package.json index d2bac26eb7..bb8c154d74 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,6 +50,7 @@ "@types/color": "^3.0.4", "@types/node": "^20.11.19", "@vueuse/core": "^10.9.0", + "@wecom/jssdk": "^1.4.5", "@xmldom/xmldom": "^0.8.10", "ace-builds": "^1.24.2", "ahooks-vue": "^0.15.1", diff --git a/frontend/src/api/modules/user/index.ts b/frontend/src/api/modules/user/index.ts index f378b38ec4..a9c61283e8 100644 --- a/frontend/src/api/modules/user/index.ts +++ b/frontend/src/api/modules/user/index.ts @@ -14,8 +14,11 @@ import { GetMenuListUrl, GetPlatformAccountUrl, GetPlatformOrgOptionUrl, + GetPlatformParamUrl, GetPlatformUrl, GetPublicKeyUrl, + GetWeComCallbackUrl, + GetWeComInfoUrl, isLoginUrl, ldapLoginUrl, LoginUrl, @@ -43,6 +46,7 @@ import type { UpdateLocalConfigParams, UpdatePswParams, } from '@/models/user'; +import { WecomInfo } from '@/models/user'; import type { RouteRecordNormalized } from 'vue-router'; @@ -64,6 +68,21 @@ export function getAuthenticationList() { return MSR.get({ url: getAuthenticationUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' }); } +export function getPlatformParamUrl() { + return MSR.get({ url: GetPlatformParamUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' }); +} + +export function getWeComInfo() { + return MSR.get({ url: GetWeComInfoUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' }); +} + +export function getWeComCallback(code: string) { + return MSR.get( + { url: GetWeComCallbackUrl, params: { code } }, + { ignoreCancelToken: true, errorMessageMode: 'none' } + ); +} + export function logout() { return MSR.get({ url: LogoutUrl }); } diff --git a/frontend/src/api/requrls/user.ts b/frontend/src/api/requrls/user.ts index 8bcf63cb18..d78a4b0c2f 100644 --- a/frontend/src/api/requrls/user.ts +++ b/frontend/src/api/requrls/user.ts @@ -26,3 +26,6 @@ export const SavePlatformUrl = '/user/platform/save'; // 个人信息-保存三 export const GetPlatformUrl = '/user/platform/get'; // 个人信息-获取三方平台账号信息 export const GetPlatformAccountUrl = '/user/platform/account/info'; // 个人信息-获取三方平台账号信息-插件信息 export const GetPlatformOrgOptionUrl = '/user/platform/switch-option'; // 个人信息-获取三方平台账号信息-插件信息 +export const GetWeComInfoUrl = '/we_com/info'; // 获取企业微信登陆的配置信息 +export const GetWeComCallbackUrl = '/sso/callback/we_com'; // 获取企业微信登陆的回调信息 +export const GetPlatformParamUrl = '/setting/get/platform/param'; diff --git a/frontend/src/assets/svg/scan_code.svg b/frontend/src/assets/svg/scan_code.svg new file mode 100644 index 0000000000..66d7dcc4f5 --- /dev/null +++ b/frontend/src/assets/svg/scan_code.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/models/user.ts b/frontend/src/models/user.ts index de53e45bde..37ea8ab5ea 100644 --- a/frontend/src/models/user.ts +++ b/frontend/src/models/user.ts @@ -13,6 +13,14 @@ export interface LoginRes extends UserState { sessionId: string; token: string; } +// 企业微信对接信息 +export interface WecomInfo { + corpId?: string; + agentId?: string; + state?: string; + callBack?: string; +} + // 更新本地执行配置 export interface UpdateLocalConfigParams { id: string; diff --git a/frontend/src/store/modules/user/index.ts b/frontend/src/store/modules/user/index.ts index 58ea5b193b..dc06f8bd83 100644 --- a/frontend/src/store/modules/user/index.ts +++ b/frontend/src/store/modules/user/index.ts @@ -20,6 +20,7 @@ import { composePermissions, getFirstRouteNameByPermission } from '@/utils/permi import { removeRouteListener } from '@/utils/route-listener'; import type { LoginData } from '@/models/user'; +import { LoginRes } from '@/models/user'; import useAppStore from '../app'; import { UserState } from './types'; @@ -119,6 +120,22 @@ const useUserStore = defineStore('user', { throw err; } }, + + qrCodeLogin(res: LoginRes) { + try { + const appStore = useAppStore(); + setToken(res.sessionId, res.csrfToken); + + appStore.setCurrentOrgId(res.lastOrganizationId || ''); + appStore.setCurrentProjectId(res.lastProjectId || ''); + this.setInfo(res); + this.initLocalConfig(); // 获取本地执行配置 + } catch (err) { + clearToken(); + throw err; + } + }, + // 获取登录认证方式 async getAuthentication() { try { diff --git a/frontend/src/views/login/components/login-form.vue b/frontend/src/views/login/components/login-form.vue index 9471cb1c1c..862dbb14af 100644 --- a/frontend/src/views/login/components/login-form.vue +++ b/frontend/src/views/login/components/login-form.vue @@ -11,7 +11,7 @@ -
+
{{ t('login.form.accountLogin') }}
@@ -20,7 +20,7 @@ class="mb-7 text-[18px] font-medium text-[rgb(var(--primary-5))]" >{{ t('login.form.LDAPLogin') }}
- +
- - {{ t('login.form.modeLoginMethods') }} - -
-
- LDAP -
-
-
-
- OIDC -
-
- OAUTH -
-
+
+ +
+ + {{ t('login.form.modeLoginMethods') }} + +
+
+ +
+
+ LDAP +
+
+
+
+ OIDC +
+
+ OAUTH +
+
+
@@ -99,9 +115,13 @@ import { computed, ref } from 'vue'; import { useRouter } from 'vue-router'; import { useStorage } from '@vueuse/core'; - import { Message } from '@arco-design/web-vue'; + import { Message, SelectOptionData } from '@arco-design/web-vue'; + + import MsIcon from '@/components/pure/ms-icon-font/index.vue'; + import TabQrCode from '@/views/login/components/tabQrCode.vue'; import { getProjectInfo } from '@/api/modules/project-management/basicInfo'; + import { getPlatformParamUrl } from '@/api/modules/user'; import { GetLoginLogoUrl } from '@/api/requrls/setting/config'; import { useI18n } from '@/hooks/useI18n'; import useLoading from '@/hooks/useLoading'; @@ -123,6 +143,8 @@ const appStore = useAppStore(); const licenseStore = useLicenseStore(); + const orgOptions = ref([]); + const props = defineProps<{ isPreview?: boolean; slogan?: string; @@ -158,8 +180,15 @@ password: '', }); + const showQrCodeTab = ref(false); + function switchLoginType(type: string) { userInfo.value.authenticate = type; + if (type === 'QR_CODE') { + showQrCodeTab.value = showQrCodeTab.value === false; + } else { + showQrCodeTab.value = false; + } } const handleSubmit = async ({ @@ -244,8 +273,24 @@ return userStore.loginType.includes('OAuth2'); }); + const isShowQRCode = ref(true); + + async function initPlatformInfo() { + try { + const res = await getPlatformParamUrl(); + orgOptions.value = res.map((e) => ({ + label: e.name, + value: e.id, + })); + } catch (error) { + // eslint-disable-next-line no-console + console.log(error); + } + } + onMounted(() => { userStore.getAuthentication(); + initPlatformInfo(); }); @@ -288,7 +333,7 @@ .login-input { padding-right: 0; padding-left: 0; - width: 336px; + width: 400px; height: 36px; } .login-input :deep(.arco-input) { @@ -299,7 +344,7 @@ position: relative; padding-right: 0; padding-left: 0; - width: 336px; + width: 400px; height: 36px; } .login-password-input :deep(.arco-input) { @@ -310,12 +355,12 @@ position: absolute; top: 10px; float: right; - margin-left: 290px; + margin-left: 350px; } .login-password-input :deep(.arco-input-suffix) { position: absolute; top: 10px; float: right; - margin-left: 300px; + margin-left: 360px; } diff --git a/frontend/src/views/login/components/tabQrCode.vue b/frontend/src/views/login/components/tabQrCode.vue new file mode 100644 index 0000000000..e35f4c27e2 --- /dev/null +++ b/frontend/src/views/login/components/tabQrCode.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/frontend/src/views/login/components/weComQrCode.vue b/frontend/src/views/login/components/weComQrCode.vue new file mode 100644 index 0000000000..c84713fc18 --- /dev/null +++ b/frontend/src/views/login/components/weComQrCode.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/frontend/src/views/project-management/messageManagement/locale/en-US.ts b/frontend/src/views/project-management/messageManagement/locale/en-US.ts index d1f7f7c7fe..eff7fa2625 100644 --- a/frontend/src/views/project-management/messageManagement/locale/en-US.ts +++ b/frontend/src/views/project-management/messageManagement/locale/en-US.ts @@ -16,6 +16,7 @@ export default { 'project.messageManagement.WE_COM': 'Enterprise WeChat', 'project.messageManagement.DING_TALK': 'DingTalk', 'project.messageManagement.LARK': 'Lark', + 'project.messageManagement.LARK_SUITE': 'SuiteLark', 'project.messageManagement.CUSTOM': 'custom', 'project.messageManagement.business': '(Enterprise Edition)', 'project.messageManagement.name': 'Robot name', diff --git a/frontend/src/views/project-management/messageManagement/locale/zh-CN.ts b/frontend/src/views/project-management/messageManagement/locale/zh-CN.ts index f125b51e60..86099ba621 100644 --- a/frontend/src/views/project-management/messageManagement/locale/zh-CN.ts +++ b/frontend/src/views/project-management/messageManagement/locale/zh-CN.ts @@ -17,6 +17,7 @@ export default { 'project.messageManagement.WE_COM': '企业微信', 'project.messageManagement.DING_TALK': '钉钉', 'project.messageManagement.LARK': '飞书', + 'project.messageManagement.LARK_SUITE': '国际飞书', 'project.messageManagement.CUSTOM': '自定义', 'project.messageManagement.business': '(企业版)', 'project.messageManagement.name': '机器人名称',