feat(系统设置): 新增企业微信扫码登陆

This commit is contained in:
guoyuqi 2024-05-31 11:52:28 +08:00 committed by 刘瑞斌
parent 13ed85e91e
commit 96327dd079
20 changed files with 1137 additions and 25 deletions

View File

@ -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<Column> 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();
}
}
}

View File

@ -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<Criteria> oredCriteria;
public PlatformSourceExample() {
oredCriteria = new ArrayList<Criteria>();
}
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<Criteria> 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<Criterion> criteria;
protected GeneratedCriteria() {
super();
criteria = new ArrayList<Criterion>();
}
public boolean isValid() {
return criteria.size() > 0;
}
public List<Criterion> getAllCriteria() {
return criteria;
}
public List<Criterion> 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<String> values) {
addCriterion("platform in", values, "platform");
return (Criteria) this;
}
public Criteria andPlatformNotIn(List<String> 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<Boolean> values) {
addCriterion("`enable` in", values, "enable");
return (Criteria) this;
}
public Criteria andEnableNotIn(List<Boolean> 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<Boolean> values) {
addCriterion("`valid` in", values, "valid");
return (Criteria) this;
}
public Criteria andValidNotIn(List<Boolean> 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);
}
}
}

View File

@ -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<PlatformSource> selectByExampleWithBLOBs(PlatformSourceExample example);
List<PlatformSource> 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<PlatformSource> list);
int batchInsertSelective(@Param("list") List<PlatformSource> list, @Param("selective") PlatformSource.Column ... selective);
}

View File

@ -0,0 +1,269 @@
<?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.PlatformSourceMapper">
<resultMap id="BaseResultMap" type="io.metersphere.system.domain.PlatformSource">
<id column="platform" jdbcType="VARCHAR" property="platform" />
<result column="enable" jdbcType="BIT" property="enable" />
<result column="valid" jdbcType="BIT" property="valid" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.system.domain.PlatformSource">
<result column="config" jdbcType="LONGVARBINARY" property="config" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause">
<where>
<foreach collection="example.oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List">
platform, `enable`, `valid`
</sql>
<sql id="Blob_Column_List">
config
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.system.domain.PlatformSourceExample" resultMap="ResultMapWithBLOBs">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
from platform_source
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByExample" parameterType="io.metersphere.system.domain.PlatformSourceExample" resultMap="BaseResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from platform_source
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="ResultMapWithBLOBs">
select
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
from platform_source
where platform = #{platform,jdbcType=VARCHAR}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.String">
delete from platform_source
where platform = #{platform,jdbcType=VARCHAR}
</delete>
<delete id="deleteByExample" parameterType="io.metersphere.system.domain.PlatformSourceExample">
delete from platform_source
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.system.domain.PlatformSource">
insert into platform_source (platform, `enable`, `valid`,
config)
values (#{platform,jdbcType=VARCHAR}, #{enable,jdbcType=BIT}, #{valid,jdbcType=BIT},
#{config,jdbcType=LONGVARBINARY})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.system.domain.PlatformSource">
insert into platform_source
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="platform != null">
platform,
</if>
<if test="enable != null">
`enable`,
</if>
<if test="valid != null">
`valid`,
</if>
<if test="config != null">
config,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="platform != null">
#{platform,jdbcType=VARCHAR},
</if>
<if test="enable != null">
#{enable,jdbcType=BIT},
</if>
<if test="valid != null">
#{valid,jdbcType=BIT},
</if>
<if test="config != null">
#{config,jdbcType=LONGVARBINARY},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.system.domain.PlatformSourceExample" resultType="java.lang.Long">
select count(*) from platform_source
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByExampleSelective" parameterType="map">
update platform_source
<set>
<if test="record.platform != null">
platform = #{record.platform,jdbcType=VARCHAR},
</if>
<if test="record.enable != null">
`enable` = #{record.enable,jdbcType=BIT},
</if>
<if test="record.valid != null">
`valid` = #{record.valid,jdbcType=BIT},
</if>
<if test="record.config != null">
config = #{record.config,jdbcType=LONGVARBINARY},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExampleWithBLOBs" parameterType="map">
update platform_source
set platform = #{record.platform,jdbcType=VARCHAR},
`enable` = #{record.enable,jdbcType=BIT},
`valid` = #{record.valid,jdbcType=BIT},
config = #{record.config,jdbcType=LONGVARBINARY}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExample" parameterType="map">
update platform_source
set platform = #{record.platform,jdbcType=VARCHAR},
`enable` = #{record.enable,jdbcType=BIT},
`valid` = #{record.valid,jdbcType=BIT}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.system.domain.PlatformSource">
update platform_source
<set>
<if test="enable != null">
`enable` = #{enable,jdbcType=BIT},
</if>
<if test="valid != null">
`valid` = #{valid,jdbcType=BIT},
</if>
<if test="config != null">
config = #{config,jdbcType=LONGVARBINARY},
</if>
</set>
where platform = #{platform,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.system.domain.PlatformSource">
update platform_source
set `enable` = #{enable,jdbcType=BIT},
`valid` = #{valid,jdbcType=BIT},
config = #{config,jdbcType=LONGVARBINARY}
where platform = #{platform,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.system.domain.PlatformSource">
update platform_source
set `enable` = #{enable,jdbcType=BIT},
`valid` = #{valid,jdbcType=BIT}
where platform = #{platform,jdbcType=VARCHAR}
</update>
<insert id="batchInsert" parameterType="map">
insert into platform_source
(platform, `enable`, `valid`, config)
values
<foreach collection="list" item="item" separator=",">
(#{item.platform,jdbcType=VARCHAR}, #{item.enable,jdbcType=BIT}, #{item.valid,jdbcType=BIT},
#{item.config,jdbcType=LONGVARBINARY})
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
insert into platform_source (
<foreach collection="selective" item="column" separator=",">
${column.escapedColumnName}
</foreach>
)
values
<foreach collection="list" item="item" separator=",">
(
<foreach collection="selective" item="column" separator=",">
<if test="'platform'.toString() == column.value">
#{item.platform,jdbcType=VARCHAR}
</if>
<if test="'enable'.toString() == column.value">
#{item.enable,jdbcType=BIT}
</if>
<if test="'valid'.toString() == column.value">
#{item.valid,jdbcType=BIT}
</if>
<if test="'config'.toString() == column.value">
#{item.config,jdbcType=LONGVARBINARY}
</if>
</foreach>
)
</foreach>
</insert>
</mapper>

View File

@ -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;

View File

@ -1,5 +1,5 @@
package io.metersphere.sdk.constants; package io.metersphere.sdk.constants;
public enum UserSource { public enum UserSource {
LOCAL, LDAP, CAS, OIDC, OAUTH2 LOCAL, LDAP, CAS, OIDC, OAUTH2, QR_CODE
} }

View File

@ -11,6 +11,9 @@ public class FilterChainUtils {
filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/ldap/login", "anon"); filterChainDefinitionMap.put("/ldap/login", "anon");
filterChainDefinitionMap.put("/authentication/get-list", "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("/signout", "anon");
filterChainDefinitionMap.put("/is-login", "anon"); filterChainDefinitionMap.put("/is-login", "anon");
filterChainDefinitionMap.put("/get-key", "anon"); filterChainDefinitionMap.put("/get-key", "anon");

View File

@ -21,6 +21,8 @@ public class SystemInterceptor {
configList.add(new MybatisInterceptorConfig(ServiceIntegration.class, "configuration", 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(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"));
configList.add(new MybatisInterceptorConfig(PlatformSource.class, "config", CompressUtils.class, "zip", "unzip"));
return configList; return configList;
} }

View File

@ -18,6 +18,7 @@ public enum OperationLogType {
LOGOUT, LOGOUT,
DISASSOCIATE, DISASSOCIATE,
ASSOCIATE, ASSOCIATE,
QRCODE,
ARCHIVED; ARCHIVED;
public boolean contains(OperationLogType keyword) { public boolean contains(OperationLogType keyword) {

View File

@ -50,6 +50,7 @@
"@types/color": "^3.0.4", "@types/color": "^3.0.4",
"@types/node": "^20.11.19", "@types/node": "^20.11.19",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"@wecom/jssdk": "^1.4.5",
"@xmldom/xmldom": "^0.8.10", "@xmldom/xmldom": "^0.8.10",
"ace-builds": "^1.24.2", "ace-builds": "^1.24.2",
"ahooks-vue": "^0.15.1", "ahooks-vue": "^0.15.1",

View File

@ -14,8 +14,11 @@ import {
GetMenuListUrl, GetMenuListUrl,
GetPlatformAccountUrl, GetPlatformAccountUrl,
GetPlatformOrgOptionUrl, GetPlatformOrgOptionUrl,
GetPlatformParamUrl,
GetPlatformUrl, GetPlatformUrl,
GetPublicKeyUrl, GetPublicKeyUrl,
GetWeComCallbackUrl,
GetWeComInfoUrl,
isLoginUrl, isLoginUrl,
ldapLoginUrl, ldapLoginUrl,
LoginUrl, LoginUrl,
@ -43,6 +46,7 @@ import type {
UpdateLocalConfigParams, UpdateLocalConfigParams,
UpdatePswParams, UpdatePswParams,
} from '@/models/user'; } from '@/models/user';
import { WecomInfo } from '@/models/user';
import type { RouteRecordNormalized } from 'vue-router'; import type { RouteRecordNormalized } from 'vue-router';
@ -64,6 +68,21 @@ export function getAuthenticationList() {
return MSR.get<string[]>({ url: getAuthenticationUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' }); return MSR.get<string[]>({ url: getAuthenticationUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' });
} }
export function getPlatformParamUrl() {
return MSR.get<OrgOptionItem[]>({ url: GetPlatformParamUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' });
}
export function getWeComInfo() {
return MSR.get<WecomInfo>({ url: GetWeComInfoUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' });
}
export function getWeComCallback(code: string) {
return MSR.get<LoginRes>(
{ url: GetWeComCallbackUrl, params: { code } },
{ ignoreCancelToken: true, errorMessageMode: 'none' }
);
}
export function logout() { export function logout() {
return MSR.get<LoginRes>({ url: LogoutUrl }); return MSR.get<LoginRes>({ url: LogoutUrl });
} }

View File

@ -26,3 +26,6 @@ export const SavePlatformUrl = '/user/platform/save'; // 个人信息-保存三
export const GetPlatformUrl = '/user/platform/get'; // 个人信息-获取三方平台账号信息 export const GetPlatformUrl = '/user/platform/get'; // 个人信息-获取三方平台账号信息
export const GetPlatformAccountUrl = '/user/platform/account/info'; // 个人信息-获取三方平台账号信息-插件信息 export const GetPlatformAccountUrl = '/user/platform/account/info'; // 个人信息-获取三方平台账号信息-插件信息
export const GetPlatformOrgOptionUrl = '/user/platform/switch-option'; // 个人信息-获取三方平台账号信息-插件信息 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';

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.25033 1.6665C6.71056 1.6665 7.08366 2.0396 7.08366 2.49984C7.08366 2.96007 6.71056 3.33317 6.25033 3.33317H3.33366V6.24984C3.33366 6.6772 3.01196 7.02943 2.59751 7.07756L2.50033 7.08317C2.04009 7.08317 1.66699 6.71007 1.66699 6.24984V2.49984C1.66699 2.0396 2.04009 1.6665 2.50033 1.6665H6.25033ZM2.50033 12.9165C2.96056 12.9165 3.33366 13.2896 3.33366 13.7498V16.6665H6.25033C6.67769 16.6665 7.02991 16.9882 7.07805 17.4027L7.08366 17.4998C7.08366 17.9601 6.71056 18.3332 6.25033 18.3332H2.50033C2.04009 18.3332 1.66699 17.9601 1.66699 17.4998V13.7498C1.66699 13.2896 2.04009 12.9165 2.50033 12.9165ZM17.5003 1.6665C17.9606 1.6665 18.3337 2.0396 18.3337 2.49984V6.24984C18.3337 6.71007 17.9606 7.08317 17.5003 7.08317C17.0401 7.08317 16.667 6.71007 16.667 6.24984V3.33317H13.7503C13.323 3.33317 12.9707 3.01147 12.9226 2.59702L12.917 2.49984C12.917 2.0396 13.2901 1.6665 13.7503 1.6665H17.5003ZM15.8337 9.1665C16.2939 9.1665 16.667 9.5396 16.667 9.99984C16.667 10.4601 16.2939 10.8332 15.8337 10.8332H4.16699C3.70675 10.8332 3.33366 10.4601 3.33366 9.99984C3.33366 9.5396 3.70675 9.1665 4.16699 9.1665H15.8337ZM17.5003 12.9165C17.9606 12.9165 18.3337 13.2896 18.3337 13.7498V17.4998C18.3337 17.9601 17.9606 18.3332 17.5003 18.3332H13.7503C13.2901 18.3332 12.917 17.9601 12.917 17.4998C12.917 17.0396 13.2901 16.6665 13.7503 16.6665H16.667V13.7498C16.667 13.3225 16.9887 12.9702 17.4031 12.9221L17.5003 12.9165Z" fill="#811FA3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -13,6 +13,14 @@ export interface LoginRes extends UserState {
sessionId: string; sessionId: string;
token: string; token: string;
} }
// 企业微信对接信息
export interface WecomInfo {
corpId?: string;
agentId?: string;
state?: string;
callBack?: string;
}
// 更新本地执行配置 // 更新本地执行配置
export interface UpdateLocalConfigParams { export interface UpdateLocalConfigParams {
id: string; id: string;

View File

@ -20,6 +20,7 @@ import { composePermissions, getFirstRouteNameByPermission } from '@/utils/permi
import { removeRouteListener } from '@/utils/route-listener'; import { removeRouteListener } from '@/utils/route-listener';
import type { LoginData } from '@/models/user'; import type { LoginData } from '@/models/user';
import { LoginRes } from '@/models/user';
import useAppStore from '../app'; import useAppStore from '../app';
import { UserState } from './types'; import { UserState } from './types';
@ -119,6 +120,22 @@ const useUserStore = defineStore('user', {
throw err; 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() { async getAuthentication() {
try { try {

View File

@ -11,7 +11,7 @@
</div> </div>
</div> </div>
<div class="form mt-[32px] min-w-[416px]"> <div class="form mt-[32px] min-w-[480px]">
<div v-if="userInfo.authenticate === 'LOCAL'" class="mb-7 text-[18px] font-medium text-[rgb(var(--primary-5))]"> <div v-if="userInfo.authenticate === 'LOCAL'" class="mb-7 text-[18px] font-medium text-[rgb(var(--primary-5))]">
{{ t('login.form.accountLogin') }} {{ t('login.form.accountLogin') }}
</div> </div>
@ -20,7 +20,7 @@
class="mb-7 text-[18px] font-medium text-[rgb(var(--primary-5))]" class="mb-7 text-[18px] font-medium text-[rgb(var(--primary-5))]"
>{{ t('login.form.LDAPLogin') }}</div >{{ t('login.form.LDAPLogin') }}</div
> >
<a-form ref="formRef" :model="userInfo" @submit="handleSubmit"> <a-form v-if="!showQrCodeTab" ref="formRef" :model="userInfo" @submit="handleSubmit">
<!-- TOTO 第一版本暂时只考虑普通登录&LDAP --> <!-- TOTO 第一版本暂时只考虑普通登录&LDAP -->
<a-form-item <a-form-item
class="login-form-item" class="login-form-item"
@ -72,24 +72,40 @@
</div> </div>
</div> </div>
</div> </div>
<a-divider v-if="isShowLDAP || isShowOIDC || isShowOAUTH" orientation="center" type="dashed" class="m-0 mb-2">
<span class="text-[12px] font-normal text-[var(--color-text-4)]">{{ t('login.form.modeLoginMethods') }}</span>
</a-divider>
<div v-if="userStore.loginType.length" class="mt-4 flex items-center justify-center">
<div v-if="userInfo.authenticate !== 'LDAP' && isShowLDAP" class="loginType" @click="switchLoginType('LDAP')">
<span class="type-text text-[10px]">LDAP</span>
</div>
<div v-if="userInfo.authenticate !== 'LOCAL'" class="loginType" @click="switchLoginType('LOCAL')">
<svg-icon width="18px" height="18px" name="userLogin"></svg-icon
></div>
<div v-if="isShowOIDC && userInfo.authenticate !== 'OIDC'" class="loginType">
<span class="type-text text-[10px]">OIDC</span>
</div>
<div v-if="isShowOAUTH && userInfo.authenticate !== 'OAuth2'" class="loginType">
<span class="type-text text-[7px]">OAUTH</span>
</div>
</div>
</a-form> </a-form>
<div v-if="showQrCodeTab">
<tab-qr-code tab-name="wecom"></tab-qr-code>
</div>
<a-divider
v-if="isShowLDAP || isShowOIDC || isShowOAUTH || (isShowQRCode && orgOptions.length > 0)"
orientation="center"
type="dashed"
class="m-0 mb-2"
>
<span class="text-[12px] font-normal text-[var(--color-text-4)]">{{ t('login.form.modeLoginMethods') }}</span>
</a-divider>
<div class="mt-4 flex items-center justify-center">
<div
v-if="isShowQRCode && !showQrCodeTab && orgOptions.length > 0"
class="loginType"
@click="switchLoginType('QR_CODE')"
>
<svg-icon name="scan_code" width="18px" height="18px" class="text-[rgb(var(--primary-6))]"></svg-icon>
</div>
<div v-if="userInfo.authenticate !== 'LDAP' && isShowLDAP" class="loginType" @click="switchLoginType('LDAP')">
<span class="type-text text-[10px]">LDAP</span>
</div>
<div v-if="userInfo.authenticate !== 'LOCAL'" class="loginType" @click="switchLoginType('LOCAL')">
<svg-icon width="18px" height="18px" name="userLogin"></svg-icon
></div>
<div v-if="isShowOIDC && userInfo.authenticate !== 'OIDC'" class="loginType">
<span class="type-text text-[10px]">OIDC</span>
</div>
<div v-if="isShowOAUTH && userInfo.authenticate !== 'OAuth2'" class="loginType">
<span class="type-text text-[7px]">OAUTH</span>
</div>
</div>
<div v-if="props.isPreview" class="mask"></div> <div v-if="props.isPreview" class="mask"></div>
</div> </div>
</div> </div>
@ -99,9 +115,13 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStorage } from '@vueuse/core'; 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 { getProjectInfo } from '@/api/modules/project-management/basicInfo';
import { getPlatformParamUrl } from '@/api/modules/user';
import { GetLoginLogoUrl } from '@/api/requrls/setting/config'; import { GetLoginLogoUrl } from '@/api/requrls/setting/config';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLoading from '@/hooks/useLoading'; import useLoading from '@/hooks/useLoading';
@ -123,6 +143,8 @@
const appStore = useAppStore(); const appStore = useAppStore();
const licenseStore = useLicenseStore(); const licenseStore = useLicenseStore();
const orgOptions = ref<SelectOptionData[]>([]);
const props = defineProps<{ const props = defineProps<{
isPreview?: boolean; isPreview?: boolean;
slogan?: string; slogan?: string;
@ -158,8 +180,15 @@
password: '', password: '',
}); });
const showQrCodeTab = ref(false);
function switchLoginType(type: string) { function switchLoginType(type: string) {
userInfo.value.authenticate = type; userInfo.value.authenticate = type;
if (type === 'QR_CODE') {
showQrCodeTab.value = showQrCodeTab.value === false;
} else {
showQrCodeTab.value = false;
}
} }
const handleSubmit = async ({ const handleSubmit = async ({
@ -244,8 +273,24 @@
return userStore.loginType.includes('OAuth2'); 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(() => { onMounted(() => {
userStore.getAuthentication(); userStore.getAuthentication();
initPlatformInfo();
}); });
</script> </script>
@ -288,7 +333,7 @@
.login-input { .login-input {
padding-right: 0; padding-right: 0;
padding-left: 0; padding-left: 0;
width: 336px; width: 400px;
height: 36px; height: 36px;
} }
.login-input :deep(.arco-input) { .login-input :deep(.arco-input) {
@ -299,7 +344,7 @@
position: relative; position: relative;
padding-right: 0; padding-right: 0;
padding-left: 0; padding-left: 0;
width: 336px; width: 400px;
height: 36px; height: 36px;
} }
.login-password-input :deep(.arco-input) { .login-password-input :deep(.arco-input) {
@ -310,12 +355,12 @@
position: absolute; position: absolute;
top: 10px; top: 10px;
float: right; float: right;
margin-left: 290px; margin-left: 350px;
} }
.login-password-input :deep(.arco-input-suffix) { .login-password-input :deep(.arco-input-suffix) {
position: absolute; position: absolute;
top: 10px; top: 10px;
float: right; float: right;
margin-left: 300px; margin-left: 360px;
} }
</style> </style>

View File

@ -0,0 +1,99 @@
<template>
<a-tabs v-model:active-key="activeName" class="tabPlatform" @change="handleClick">
<a-tab-pane key="wecom" :title="t('project.messageManagement.WE_COM')" class="font-[16px]"></a-tab-pane>
<!-- <a-tab-pane key="dingtalk" :title="t('project.messageManagement.DING_TALK')" ></a-tab-pane>
<a-tab-pane key="lark" :title="t('project.messageManagement.LARK')"></a-tab-pane>
<a-tab-pane key="larksuite" :title="t('project.messageManagement.LARK_SUITE')"></a-tab-pane>-->
</a-tabs>
<div v-if="activeName === 'wecom'" class="login-qrcode">
<div class="qrcode">
<wecom-qr v-if="activeName === 'wecom'" />
</div>
</div>
<!-- <div class="login-qrcode" v-if="activeName === 'dingtalk'">
<div class="qrcode">
<dingtalk-qr v-if="activeName === 'dingtalk'"/>
</div>
</div>
<div class="login-qrcode" v-if="activeName === 'lark'">
<div class="qrcode">
<lark-qr v-if="activeName === 'lark'"/>
</div>
</div>
<div class="login-qrcode" v-if="activeName === 'larksuite'">
<div class="qrcode">
<larksuite-qr v-if="activeName === 'larksuite'"/>
</div>
</div>-->
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import WecomQr from './weComQrCode.vue';
const { t } = useI18n();
const activeName = ref('');
const props = defineProps<{
tabName: string;
}>();
const initActive = () => {
const qrArray = ['wecom', 'dingtalk', 'lark', 'larksuite'];
for (let i = 0; i < qrArray.length; i++) {
const key = qrArray[i];
if (props.tabName === key) {
activeName.value = key;
break;
}
}
};
function handleClick(val: string | number) {
if (typeof val === 'string') {
activeName.value = val;
}
}
onMounted(() => {
initActive();
});
</script>
<style lang="less" scoped>
.tabPlatform {
width: 400px;
height: 40px;
}
.login-qrcode {
min-width: 480px;
display: flex;
align-items: center;
flex-direction: column;
margin-top: 24px;
.qrcode {
display: flex;
overflow: hidden;
justify-content: center;
align-items: center;
border-radius: 8px;
background: #fff;
}
.title {
display: flex;
align-items: center;
justify-content: center;
margin: 0px 0 16px 0;
overflow: hidden;
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 26px;
.ed-icon {
margin-right: 8px;
font-size: 24px;
}
}
}
</style>

View File

@ -0,0 +1,87 @@
<template>
<div id="wecom-qr" class="wecom-qr" />
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import { getProjectInfo } from '@/api/modules/project-management/basicInfo';
import { getWeComCallback, getWeComInfo } from '@/api/modules/user';
import { useI18n } from '@/hooks/useI18n';
import { NO_PROJECT_ROUTE_NAME, NO_RESOURCE_ROUTE_NAME } from '@/router/constants';
import { useAppStore, useUserStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { setLoginExpires } from '@/utils/auth';
import { getFirstRouteNameByPermission, routerNameHasPermission } from '@/utils/permission';
import * as ww from '@wecom/jssdk';
import { WWLoginRedirectType, WWLoginType } from '@wecom/jssdk';
const { t } = useI18n();
const userStore = useUserStore();
const appStore = useAppStore();
const licenseStore = useLicenseStore();
const router = useRouter();
const wwLogin = ref({});
const init = async () => {
const data = await getWeComInfo();
wwLogin.value = ww.createWWLoginPanel({
el: '#wecom-qr',
params: {
login_type: WWLoginType.corpApp,
appid: data.corpId ? data.corpId : '',
agentid: data.agentId,
redirect_uri: data.callBack ? data.callBack : '',
state: 'fit2cloud-wecom-qr',
redirect_type: WWLoginRedirectType.callback,
},
onCheckWeComLogin({ isWeComLogin }) {
console.log(isWeComLogin);
},
async onLoginSuccess({ code }) {
const weComCallback = getWeComCallback(code);
userStore.qrCodeLogin(await weComCallback);
Message.success(t('login.form.login.success'));
const { redirect, ...othersQuery } = router.currentRoute.value.query;
const redirectHasPermission =
redirect &&
![NO_RESOURCE_ROUTE_NAME, NO_PROJECT_ROUTE_NAME].includes(redirect as string) &&
routerNameHasPermission(redirect as string, router.getRoutes());
const currentRouteName = getFirstRouteNameByPermission(router.getRoutes());
const [res] = await Promise.all([getProjectInfo(appStore.currentProjectId), licenseStore.getValidateLicense()]); // license license
if (!res || res.deleted) {
router.push({
name: NO_PROJECT_ROUTE_NAME,
});
}
if (res) {
appStore.setCurrentMenuConfig(res?.moduleIds || []);
}
setLoginExpires();
router.push({
name: redirectHasPermission ? (redirect as string) : currentRouteName,
query: {
...othersQuery,
orgId: appStore.currentOrgId,
pId: appStore.currentProjectId,
},
});
},
onLoginFail(err) {
console.log(err);
},
});
};
init();
</script>
<style lang="less" scoped>
.wecom-qr {
margin-top: -20px;
}
</style>

View File

@ -16,6 +16,7 @@ export default {
'project.messageManagement.WE_COM': 'Enterprise WeChat', 'project.messageManagement.WE_COM': 'Enterprise WeChat',
'project.messageManagement.DING_TALK': 'DingTalk', 'project.messageManagement.DING_TALK': 'DingTalk',
'project.messageManagement.LARK': 'Lark', 'project.messageManagement.LARK': 'Lark',
'project.messageManagement.LARK_SUITE': 'SuiteLark',
'project.messageManagement.CUSTOM': 'custom', 'project.messageManagement.CUSTOM': 'custom',
'project.messageManagement.business': '(Enterprise Edition)', 'project.messageManagement.business': '(Enterprise Edition)',
'project.messageManagement.name': 'Robot name', 'project.messageManagement.name': 'Robot name',

View File

@ -17,6 +17,7 @@ export default {
'project.messageManagement.WE_COM': '企业微信', 'project.messageManagement.WE_COM': '企业微信',
'project.messageManagement.DING_TALK': '钉钉', 'project.messageManagement.DING_TALK': '钉钉',
'project.messageManagement.LARK': '飞书', 'project.messageManagement.LARK': '飞书',
'project.messageManagement.LARK_SUITE': '国际飞书',
'project.messageManagement.CUSTOM': '自定义', 'project.messageManagement.CUSTOM': '自定义',
'project.messageManagement.business': '(企业版)', 'project.messageManagement.business': '(企业版)',
'project.messageManagement.name': '机器人名称', 'project.messageManagement.name': '机器人名称',