")
+ .append("| ").append(user.getUserId()).append(" | ")
+ .append("").append(html(user.getUserName())).append(" | ")
+ .append("").append(html(StringUtils.defaultIfBlank(user.getNickName(), "-"))).append(" | ")
+ .append("").append(html(maskPhone(user.getPhonenumber()))).append(" | ")
+ .append("").append(userKind).append(" ")
+ .append(html(deptName)).append(" | ")
+ .append("选择并登录 | ")
+ .append("
");
+ }
+ return rows.toString();
+ }
+
+ private String maskPhone(String phone)
+ {
+ String value = StringUtils.trimToEmpty(phone);
+ if (value.length() >= 7)
+ {
+ return value.substring(0, 3) + "****" + value.substring(value.length() - 4);
+ }
+ return StringUtils.defaultIfBlank(value, "-");
+ }
+
+ private String firstNotBlank(String... values)
+ {
+ for (String value : values)
+ {
+ if (StringUtils.isNotBlank(value))
+ {
+ return value;
+ }
+ }
+ return "未知用户";
+ }
+
+ private String html(String value)
+ {
+ return HtmlUtils.htmlEscape(StringUtils.defaultString(value));
+ }
+
+ private String javascriptString(String value)
+ {
+ return JSON.toJSONString(StringUtils.defaultString(value))
+ .replace("<", "\\u003c")
+ .replace(">", "\\u003e")
+ .replace("&", "\\u0026")
+ .replace("\u2028", "\\u2028")
+ .replace("\u2029", "\\u2029");
+ }
+
+ private String loadTemplate(String path)
+ {
+ ClassPathResource resource = new ClassPathResource(path);
+ try
+ {
+ return StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
+ }
+ catch (IOException e)
+ {
+ throw new IllegalStateException("无法加载后门登录页面模板:" + path, e);
+ }
+ }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/service/BackDoorLoginService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/service/BackDoorLoginService.java
new file mode 100644
index 0000000..7ecc71b
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/service/BackDoorLoginService.java
@@ -0,0 +1,116 @@
+package com.ruoyi.web.service;
+
+import java.util.List;
+
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.enums.UserStatus;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.framework.web.service.SysLoginService;
+import com.ruoyi.framework.web.service.TokenService;
+import com.ruoyi.framework.web.service.UserDetailsServiceImpl;
+import com.ruoyi.system.mapper.SysUserMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+
+/**
+ * 仅在测试环境显式开启的 PC 用户后门登录服务。
+ */
+@Service
+@ConditionalOnProperty(prefix = "backdoor.login", name = "enabled", havingValue = "true")
+public class BackDoorLoginService
+{
+ private static final Logger log = LoggerFactory.getLogger(BackDoorLoginService.class);
+ private static final int MAX_KEYWORD_LENGTH = 50;
+ private static final int MAX_USER_LIMIT = 200;
+
+ private final SysUserMapper userMapper;
+ private final UserDetailsServiceImpl userDetailsService;
+ private final TokenService tokenService;
+ private final SysLoginService loginService;
+
+ public BackDoorLoginService(SysUserMapper userMapper,
+ UserDetailsServiceImpl userDetailsService,
+ TokenService tokenService,
+ SysLoginService loginService)
+ {
+ this.userMapper = userMapper;
+ this.userDetailsService = userDetailsService;
+ this.tokenService = tokenService;
+ this.loginService = loginService;
+ }
+
+ public List