Skip to content

Commit 0ab31ba

Browse files
KAICharles7c
authored andcommitted
feat(system/client) :客户端管理扩展登录配置
1 parent ea9bc02 commit 0ab31ba

File tree

9 files changed

+223
-12
lines changed

9 files changed

+223
-12
lines changed

continew-server/src/main/resources/config/application.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ sa-token:
249249
# 是否允许同一账号多地同时登录(为 true 时允许一起登录,为 false 时新登录挤掉旧登录)
250250
is-concurrent: true
251251
# 在多人登录同一账号时,是否共用一个 Token(为 true 时所有登录共用一个 Token,为 false 时每次登录新建一个 Token)
252+
# 使用 jwt-simple 模式后,is-share 恒等于 false(目前本项目使用 jwt-simple 模式,可通过 sa-token.extension.enableJwt: false 关闭并自行提供 StpLogic 配置)
252253
is-share: false
253254
# 是否输出操作日志
254255
is-log: false

continew-server/src/main/resources/db/changelog/mysql/main_table.sql

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,4 +398,12 @@ CREATE TABLE IF NOT EXISTS `sys_sms_log` (
398398
PRIMARY KEY (`id`),
399399
INDEX `idx_config_id`(`config_id`),
400400
INDEX `idx_create_user`(`create_user`)
401-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短信日志表';
401+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短信日志表';
402+
403+
-- changeset KAI:20251125-01
404+
-- comment 追加sys_client表字段
405+
ALTER TABLE `sys_client`
406+
ADD COLUMN `is_concurrent` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否允许同一账号多地同时登录',
407+
ADD COLUMN `max_login_count` int NOT NULL DEFAULT -1 COMMENT '同一账号最大登录数量,-1代表不限',
408+
ADD COLUMN `replaced_range` varchar(20) DEFAULT 'ALL_DEVICE_TYPE' COMMENT '顶人下线的范围(CURR_DEVICE_TYPE=当前设备类型端;ALL_DEVICE_TYPE=所有设备类型端)',
409+
ADD COLUMN `overflow_logout_mode` varchar(20) DEFAULT 'KICKOUT' COMMENT '溢出人数的注销方式(LOGOUT=注销下线;KICKOUT=踢人下线;REPLACED=顶人下线)';

continew-server/src/main/resources/db/changelog/postgresql/main_table.sql

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,4 +665,17 @@ COMMENT ON COLUMN "sys_sms_log"."status" IS '发送状态(1:成功;2
665665
COMMENT ON COLUMN "sys_sms_log"."res_msg" IS '返回数据';
666666
COMMENT ON COLUMN "sys_sms_log"."create_user" IS '创建人';
667667
COMMENT ON COLUMN "sys_sms_log"."create_time" IS '创建时间';
668-
COMMENT ON TABLE "sys_sms_log" IS '短信日志表';
668+
COMMENT ON TABLE "sys_sms_log" IS '短信日志表';
669+
670+
-- changeset kai:20251125-01
671+
-- comment 追加sys_client表字段
672+
ALTER TABLE "sys_client"
673+
ADD COLUMN "is_concurrent" bool NOT NULL DEFAULT false,
674+
ADD COLUMN "max_login_count" int4 NOT NULL DEFAULT -1,
675+
ADD COLUMN "replaced_range" varchar(20) NOT NULL DEFAULT 'ALL_DEVICE_TYPE',
676+
ADD COLUMN "overflow_logout_mode" varchar(20) NOT NULL DEFAULT 'KICKOUT';
677+
678+
COMMENT ON COLUMN "sys_client"."is_concurrent" IS '是否允许同一账号多地同时登录';
679+
COMMENT ON COLUMN "sys_client"."max_login_count" IS '同一账号最大登录数量,-1代表不限';
680+
COMMENT ON COLUMN "sys_client"."replaced_range" IS '顶人下线的范围(CURR_DEVICE_TYPE=当前设备类型端;ALL_DEVICE_TYPE=所有设备类型端)';
681+
COMMENT ON COLUMN "sys_client"."overflow_logout_mode" IS '溢出人数的注销方式(LOGOUT=注销下线;KICKOUT=踢人下线;REPLACED=顶人下线)';

continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import cn.dev33.satoken.stp.StpUtil;
2020
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
21+
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
22+
import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange;
2123
import cn.hutool.core.bean.BeanUtil;
2224
import jakarta.annotation.Resource;
2325
import jakarta.servlet.http.HttpServletRequest;
@@ -110,28 +112,33 @@ protected LoginResp authenticate(UserDO user, ClientResp client) {
110112
return roles;
111113
}, threadPoolTaskExecutor);
112114
CompletableFuture<Integer> passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService
113-
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()), threadPoolTaskExecutor);
115+
.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name()), threadPoolTaskExecutor);
114116
CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture);
115117
UserContext userContext = new UserContext(permissionFuture.join(), roleFuture
116-
.join(), passwordExpirationDaysFuture.join());
118+
.join(), passwordExpirationDaysFuture.join());
117119
BeanUtil.copyProperties(user, userContext);
118120
// 设置登录配置参数
119121
SaLoginParameter loginParameter = new SaLoginParameter();
120122
loginParameter.setActiveTimeout(client.getActiveTimeout());
121123
loginParameter.setTimeout(client.getTimeout());
122124
loginParameter.setDeviceType(client.getClientType());
123-
userContext.setClientType(client.getClientType());
124125
loginParameter.setExtra(CLIENT_ID, client.getClientId());
126+
// 设置并发登录相关参数
127+
loginParameter.setIsConcurrent(client.getIsConcurrent());
128+
loginParameter.setMaxLoginCount(client.getMaxLoginCount());
129+
loginParameter.setOverflowLogoutMode(SaLogoutMode.valueOf(client.getOverflowLogoutMode().getValue()));
130+
loginParameter.setReplacedRange(SaReplacedRange.valueOf(client.getReplacedRange().getValue()));
131+
userContext.setClientType(client.getClientType());
125132
userContext.setClientId(client.getClientId());
126133
userContext.setTenantId(tenantId);
127134
// 登录并缓存用户信息
128135
StpUtil.login(userContext.getId(), loginParameter.setExtraData(BeanUtil
129-
.beanToMap(new UserExtraContext(ServletUtils.getRequest()))));
136+
.beanToMap(new UserExtraContext(ServletUtils.getRequest()))));
130137
UserContextHolder.setContext(userContext);
131138
return LoginResp.builder()
132-
.token(StpUtil.getTokenValue())
133-
.tenantId(TenantContextHolder.isTenantEnabled() ? TenantContextHolder.getTenantId() : null)
134-
.build();
139+
.token(StpUtil.getTokenValue())
140+
.tenantId(TenantContextHolder.isTenantEnabled() ? TenantContextHolder.getTenantId() : null)
141+
.build();
135142
}
136143

137144
/**
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package top.continew.admin.system.enums;
18+
19+
import lombok.Getter;
20+
import lombok.RequiredArgsConstructor;
21+
import top.continew.starter.core.enums.BaseEnum;
22+
23+
/**
24+
* 注销模式枚举
25+
*
26+
* @author KAI
27+
* @since 2025-10-28 14:04
28+
*/
29+
@Getter
30+
@RequiredArgsConstructor
31+
public enum LogoutModeEnum implements BaseEnum<String> {
32+
33+
/**
34+
* 注销下线
35+
*/
36+
LOGOUT("LOGOUT", "注销下线"),
37+
38+
/**
39+
* 踢人下线
40+
*/
41+
KICKOUT("KICKOUT", "踢人下线"),
42+
43+
/**
44+
* 顶人下线
45+
*/
46+
REPLACED("REPLACED", "顶人下线");
47+
48+
private final String value;
49+
private final String description;
50+
51+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package top.continew.admin.system.enums;
18+
19+
import lombok.Getter;
20+
import lombok.RequiredArgsConstructor;
21+
import top.continew.starter.core.enums.BaseEnum;
22+
23+
/**
24+
* 顶人下线的范围枚举
25+
*
26+
* @author KAI
27+
* @since 2025-10-28 14:05
28+
*/
29+
@Getter
30+
@RequiredArgsConstructor
31+
public enum ReplacedRangeEnum implements BaseEnum<String> {
32+
33+
/**
34+
* 当前指定的设备类型端
35+
*/
36+
CURR_DEVICE_TYPE("CURR_DEVICE_TYPE", "当前指定的设备类型端"),
37+
38+
/**
39+
* 所有设备类型端
40+
*/
41+
ALL_DEVICE_TYPE("ALL_DEVICE_TYPE", "所有设备类型端");
42+
43+
private final String value;
44+
private final String description;
45+
46+
}

continew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import com.baomidou.mybatisplus.annotation.TableName;
2121
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
2222
import lombok.Data;
23-
import top.continew.admin.common.enums.DisEnableStatusEnum;
2423
import top.continew.admin.common.base.model.entity.BaseDO;
24+
import top.continew.admin.common.enums.DisEnableStatusEnum;
25+
import top.continew.admin.system.enums.LogoutModeEnum;
26+
import top.continew.admin.system.enums.ReplacedRangeEnum;
2527

2628
import java.io.Serial;
2729
import java.util.List;
@@ -66,6 +68,26 @@ public class ClientDO extends BaseDO {
6668
*/
6769
private Long timeout;
6870

71+
/**
72+
* 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
73+
*/
74+
private Boolean isConcurrent;
75+
76+
/**
77+
* 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
78+
*/
79+
private int maxLoginCount;
80+
81+
/**
82+
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
83+
*/
84+
private ReplacedRangeEnum replacedRange;
85+
86+
/**
87+
* 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
88+
*/
89+
private LogoutModeEnum overflowLogoutMode;
90+
6991
/**
7092
* 状态
7193
*/

continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import io.swagger.v3.oas.annotations.media.Schema;
2020
import jakarta.validation.constraints.NotBlank;
2121
import jakarta.validation.constraints.NotEmpty;
22+
import jakarta.validation.constraints.NotNull;
2223
import lombok.Data;
2324
import org.hibernate.validator.constraints.Length;
2425
import top.continew.admin.common.enums.DisEnableStatusEnum;
26+
import top.continew.admin.system.enums.LogoutModeEnum;
27+
import top.continew.admin.system.enums.ReplacedRangeEnum;
2528

2629
import java.io.Serial;
2730
import java.io.Serializable;
@@ -60,14 +63,44 @@ public class ClientReq implements Serializable {
6063
* Token 最低活跃频率(单位:秒,-1:不限制,永不冻结)
6164
*/
6265
@Schema(description = "Token 最低活跃频率(单位:秒,-1:不限制,永不冻结)", example = "1800")
66+
@NotNull(message = "Token 最低活跃频率不能为空")
6367
private Long activeTimeout;
6468

6569
/**
6670
* Token 有效期(单位:秒,-1:永不过期)
6771
*/
6872
@Schema(description = "Token 有效期(单位:秒,-1:永不过期)", example = "86400")
73+
@NotNull(message = "Token 有效期不能为空")
6974
private Long timeout;
7075

76+
/**
77+
* 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
78+
*/
79+
@Schema(description = "是否允许同一账号多地同时登录", example = "true")
80+
@NotNull(message = "是否运行同一账号多地同时登录不能为空")
81+
private Boolean isConcurrent;
82+
83+
/**
84+
* 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
85+
*/
86+
@Schema(description = "同一账号最大登录数量, -1代表不限", example = "-1")
87+
@NotNull(message = "同一账号最大登录数量不能为空")
88+
private int maxLoginCount;
89+
90+
/**
91+
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
92+
*/
93+
@Schema(description = "顶人下线的范围", example = "ALL_DEVICE_TYPE")
94+
@NotNull(message = "顶人下线的范围不能为空")
95+
private ReplacedRangeEnum replacedRange;
96+
97+
/**
98+
* 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
99+
*/
100+
@Schema(description = "溢出人数的注销方式", example = "KICKOUT")
101+
@NotNull(message = "溢出人数的注销方式不能为空")
102+
private LogoutModeEnum overflowLogoutMode;
103+
71104
/**
72105
* 状态
73106
*/

continew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
import cn.idev.excel.annotation.ExcelProperty;
2121
import io.swagger.v3.oas.annotations.media.Schema;
2222
import lombok.Data;
23+
import top.continew.admin.common.base.model.resp.BaseDetailResp;
2324
import top.continew.admin.common.config.excel.DictExcelProperty;
2425
import top.continew.admin.common.config.excel.ExcelDictConverter;
2526
import top.continew.admin.common.enums.DisEnableStatusEnum;
26-
import top.continew.admin.common.base.model.resp.BaseDetailResp;
27+
import top.continew.admin.system.enums.LogoutModeEnum;
28+
import top.continew.admin.system.enums.ReplacedRangeEnum;
2729
import top.continew.starter.excel.converter.ExcelBaseEnumConverter;
2830
import top.continew.starter.excel.converter.ExcelListConverter;
2931

@@ -81,10 +83,38 @@ public class ClientResp extends BaseDetailResp {
8183
@ExcelProperty(value = "Token 有效期", order = 7)
8284
private Long timeout;
8385

86+
/**
87+
* 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
88+
*/
89+
@Schema(description = "是否允许同一账号多地同时登录", example = "true")
90+
@ExcelProperty(value = "是否允许同一账号多地同时登录", order = 8)
91+
private Boolean isConcurrent;
92+
93+
/**
94+
* 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
95+
*/
96+
@Schema(description = "同一账号最大登录数量, -1代表不限", example = "-1")
97+
@ExcelProperty(value = "同一账号最大登录数量", order = 10)
98+
private int maxLoginCount;
99+
100+
/**
101+
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
102+
*/
103+
@Schema(description = "顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)", example = "ALL_DEVICE_TYPE")
104+
@ExcelProperty(value = "顶人下线的范围", converter = ExcelBaseEnumConverter.class, order = 11)
105+
private ReplacedRangeEnum replacedRange;
106+
107+
/**
108+
* 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
109+
*/
110+
@Schema(description = "溢出人数的注销方式", example = "KICKOUT")
111+
@ExcelProperty(value = "溢出人数的注销方式", converter = ExcelBaseEnumConverter.class, order = 12)
112+
private LogoutModeEnum overflowLogoutMode;
113+
84114
/**
85115
* 状态
86116
*/
87117
@Schema(description = "状态", example = "1")
88-
@ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class, order = 8)
118+
@ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class, order = 13)
89119
private DisEnableStatusEnum status;
90120
}

0 commit comments

Comments
 (0)