✨ feat: 添加小程序企业测试授权功能及相关接口
This commit is contained in:
@@ -12,6 +12,7 @@ import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.web.config.BackDoorLoginProperties;
|
||||
import com.ruoyi.web.service.BackDoorLoginService;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.AppLoginSession;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.CompanyLoginSession;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.LoginSession;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
@@ -150,6 +151,62 @@ public class BackDoorLoginController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序企业测试授权列表,只返回可登录企业的最小化展示字段。
|
||||
*/
|
||||
@PostMapping("/app/backDoor/companies")
|
||||
public AjaxResult appBackDoorCompanies(@RequestBody(required = false) JSONObject body)
|
||||
{
|
||||
if (!matchesAppBackDoorPassword(body))
|
||||
{
|
||||
return AjaxResult.error("测试授权密码错误");
|
||||
}
|
||||
try
|
||||
{
|
||||
String keyword = body == null ? null : body.getString("keyword");
|
||||
return AjaxResult.success(
|
||||
backDoorLoginService.listAvailableCompanies(keyword, properties.getMaxUsers()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("加载小程序测试授权企业列表失败", e);
|
||||
return AjaxResult.error("加载企业列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序企业测试授权登录。服务层会再次确认 AppUser 仍为正常企业账号。
|
||||
*/
|
||||
@PostMapping("/app/backDoor/company/login")
|
||||
public AjaxResult appBackDoorCompanyLogin(@RequestBody(required = false) JSONObject body)
|
||||
{
|
||||
if (!matchesAppBackDoorPassword(body))
|
||||
{
|
||||
return AjaxResult.error("测试授权密码错误");
|
||||
}
|
||||
Long appUserId = body == null ? null : body.getLong("appUserId");
|
||||
try
|
||||
{
|
||||
CompanyLoginSession session = backDoorLoginService.loginCompany(appUserId);
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
ajax.put(Constants.TOKEN, session.getToken());
|
||||
ajax.put("appUserId", session.getAppUser().getUserId());
|
||||
ajax.put("isCompanyUser", session.getAppUser().getIsCompanyUser());
|
||||
ajax.put("companyName", session.getAppUser().getName());
|
||||
ajax.put("companyCode", session.getAppUser().getIdCard());
|
||||
return ajax;
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("小程序企业测试授权登录失败,appUserId={}", appUserId, e);
|
||||
return AjaxResult.error("企业授权登录失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchesAppBackDoorPassword(JSONObject body)
|
||||
{
|
||||
return properties.matchesPassword(body == null ? null : body.getString("password"));
|
||||
|
||||
@@ -29,6 +29,7 @@ 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 static final String COMPANY_USER_TYPE = "0";
|
||||
|
||||
private final SysUserMapper userMapper;
|
||||
private final UserDetailsServiceImpl userDetailsService;
|
||||
@@ -74,6 +75,21 @@ public class BackDoorLoginService
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前可用于小程序测试授权登录的企业账号。
|
||||
*
|
||||
* 企业登录态的主体是 AppUser,因此不能从 Company 表直接签发令牌。列表只
|
||||
* 包含正常的企业 AppUser,并转换为最小化展示对象。
|
||||
*/
|
||||
public List<CompanyLoginUser> listAvailableCompanies(String keyword, int limit)
|
||||
{
|
||||
String normalizedKeyword = normalizeKeyword(keyword);
|
||||
int safeLimit = normalizeLimit(limit);
|
||||
return userMapper.selectBackDoorCompanyList(normalizedKeyword, safeLimit).stream()
|
||||
.map(CompanyLoginUser::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public LoginSession login(Long userId)
|
||||
{
|
||||
SysUser user = getEffectiveUser(userId);
|
||||
@@ -112,6 +128,64 @@ public class BackDoorLoginService
|
||||
return new AppLoginSession(user, appUser, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅供测试环境小程序使用的企业后门授权。
|
||||
*
|
||||
* 即使账号来自刚加载的列表,这里仍重新查询并校验角色、删除状态和停用状态,
|
||||
* 防止列表加载后账号状态变化,或调用方绕过列表直接提交任意 AppUser ID。
|
||||
*/
|
||||
public CompanyLoginSession loginCompany(Long appUserId)
|
||||
{
|
||||
if (appUserId == null || appUserId <= 0)
|
||||
{
|
||||
throw new ServiceException("企业用户 ID 必须是正整数");
|
||||
}
|
||||
|
||||
AppUser appUser = sysUserService.selectAppUserById(appUserId);
|
||||
validateCompanyUser(appUser);
|
||||
|
||||
String token = loginService.loginUserIdApp(appUser);
|
||||
log.warn("测试环境小程序企业后门授权:appUserId={}, companyName={}",
|
||||
appUser.getUserId(), appUser.getName());
|
||||
return new CompanyLoginSession(appUser, token);
|
||||
}
|
||||
|
||||
private void validateCompanyUser(AppUser appUser)
|
||||
{
|
||||
if (appUser == null)
|
||||
{
|
||||
throw new ServiceException("企业账号不存在");
|
||||
}
|
||||
if (!UserStatus.OK.getCode().equals(appUser.getDelFlag()))
|
||||
{
|
||||
throw new ServiceException("企业账号已删除,不能登录");
|
||||
}
|
||||
if (StringUtils.isNotBlank(appUser.getStatus())
|
||||
&& !UserStatus.OK.getCode().equals(appUser.getStatus()))
|
||||
{
|
||||
throw new ServiceException("企业账号已停用,不能登录");
|
||||
}
|
||||
if (!COMPANY_USER_TYPE.equals(appUser.getIsCompanyUser()))
|
||||
{
|
||||
throw new ServiceException("所选账号不是企业账号");
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeKeyword(String keyword)
|
||||
{
|
||||
String normalizedKeyword = StringUtils.trimToNull(keyword);
|
||||
if (normalizedKeyword != null && normalizedKeyword.length() > MAX_KEYWORD_LENGTH)
|
||||
{
|
||||
return normalizedKeyword.substring(0, MAX_KEYWORD_LENGTH);
|
||||
}
|
||||
return normalizedKeyword;
|
||||
}
|
||||
|
||||
private int normalizeLimit(int limit)
|
||||
{
|
||||
return Math.max(1, Math.min(limit, MAX_USER_LIMIT));
|
||||
}
|
||||
|
||||
private SysUser getEffectiveUser(Long userId)
|
||||
{
|
||||
if (userId == null || userId <= 0)
|
||||
@@ -248,4 +322,72 @@ public class BackDoorLoginService
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
/** 企业选择页最小化展示字段,不返回手机号、完整信用代码或浪潮用户标识。 */
|
||||
public static class CompanyLoginUser
|
||||
{
|
||||
private final Long appUserId;
|
||||
private final String companyName;
|
||||
private final String companyCodeMasked;
|
||||
|
||||
public CompanyLoginUser(AppUser appUser)
|
||||
{
|
||||
this.appUserId = appUser.getUserId();
|
||||
this.companyName = StringUtils.defaultIfBlank(appUser.getName(),
|
||||
"企业账号 " + appUser.getUserId());
|
||||
this.companyCodeMasked = maskCompanyCode(appUser.getIdCard());
|
||||
}
|
||||
|
||||
public Long getAppUserId()
|
||||
{
|
||||
return appUserId;
|
||||
}
|
||||
|
||||
public String getCompanyName()
|
||||
{
|
||||
return companyName;
|
||||
}
|
||||
|
||||
public String getCompanyCodeMasked()
|
||||
{
|
||||
return companyCodeMasked;
|
||||
}
|
||||
|
||||
private static String maskCompanyCode(String companyCode)
|
||||
{
|
||||
String normalized = StringUtils.trimToNull(companyCode);
|
||||
if (normalized == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (normalized.length() <= 8)
|
||||
{
|
||||
return "****";
|
||||
}
|
||||
return normalized.substring(0, 4) + "**********"
|
||||
+ normalized.substring(normalized.length() - 4);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CompanyLoginSession
|
||||
{
|
||||
private final AppUser appUser;
|
||||
private final String token;
|
||||
|
||||
public CompanyLoginSession(AppUser appUser, String token)
|
||||
{
|
||||
this.appUser = appUser;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public AppUser getAppUser()
|
||||
{
|
||||
return appUser;
|
||||
}
|
||||
|
||||
public String getToken()
|
||||
{
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.ruoyi.web.config.BackDoorLoginProperties;
|
||||
import com.ruoyi.web.service.BackDoorLoginService;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.AppLoginUser;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.AppLoginSession;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.CompanyLoginSession;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.CompanyLoginUser;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.LoginSession;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -155,4 +157,76 @@ class BackDoorLoginControllerTest
|
||||
assertEquals("测试授权密码错误", response.get("msg"));
|
||||
verify(loginService, never()).loginApp(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void appBackDoorCompaniesRequiresPasswordBeforeLoadingData()
|
||||
{
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("keyword", "测试企业");
|
||||
body.put("password", "wrong-password");
|
||||
|
||||
AjaxResult response = controller.appBackDoorCompanies(body);
|
||||
|
||||
assertEquals(500, response.get("code"));
|
||||
assertEquals("测试授权密码错误", response.get("msg"));
|
||||
verify(loginService, never()).listAvailableCompanies(org.mockito.ArgumentMatchers.any(),
|
||||
org.mockito.ArgumentMatchers.anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void appBackDoorCompaniesReturnsMinimalOptionsAfterAuthorization()
|
||||
{
|
||||
AppUser company = new AppUser();
|
||||
company.setUserId(66L);
|
||||
company.setName("测试企业");
|
||||
company.setIdCard("91659001MA77ABCD12");
|
||||
when(loginService.listAvailableCompanies("测试企业", 100))
|
||||
.thenReturn(Collections.singletonList(new CompanyLoginUser(company)));
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("keyword", "测试企业");
|
||||
body.put("password", "zkr2024");
|
||||
|
||||
AjaxResult response = controller.appBackDoorCompanies(body);
|
||||
|
||||
assertEquals(200, response.get("code"));
|
||||
assertEquals(1, ((java.util.List<?>) response.get("data")).size());
|
||||
verify(loginService).listAvailableCompanies("测试企业", 100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void appBackDoorCompanyLoginReturnsCompanySiteToken()
|
||||
{
|
||||
AppUser company = new AppUser();
|
||||
company.setUserId(66L);
|
||||
company.setName("测试企业");
|
||||
company.setIdCard("91659001MA77ABCD12");
|
||||
company.setIsCompanyUser("0");
|
||||
when(loginService.loginCompany(66L))
|
||||
.thenReturn(new CompanyLoginSession(company, "company-site-token"));
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("appUserId", 66L);
|
||||
body.put("password", "zkr2024");
|
||||
|
||||
AjaxResult response = controller.appBackDoorCompanyLogin(body);
|
||||
|
||||
assertEquals(200, response.get("code"));
|
||||
assertEquals("company-site-token", response.get("token"));
|
||||
assertEquals(66L, response.get("appUserId"));
|
||||
assertEquals("0", response.get("isCompanyUser"));
|
||||
assertEquals("测试企业", response.get("companyName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void appBackDoorCompanyLoginRequiresPasswordBeforeCreatingToken()
|
||||
{
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("appUserId", 66L);
|
||||
body.put("password", "wrong-password");
|
||||
|
||||
AjaxResult response = controller.appBackDoorCompanyLogin(body);
|
||||
|
||||
assertEquals(500, response.get("code"));
|
||||
assertEquals("测试授权密码错误", response.get("msg"));
|
||||
verify(loginService, never()).loginCompany(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import com.ruoyi.framework.web.service.UserDetailsServiceImpl;
|
||||
import com.ruoyi.system.mapper.SysUserMapper;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.AppLoginSession;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.CompanyLoginSession;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.CompanyLoginUser;
|
||||
import com.ruoyi.web.service.BackDoorLoginService.LoginSession;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -153,6 +155,77 @@ class BackDoorLoginServiceTest
|
||||
verify(sysLoginService, never()).loginUserIdApp(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void listAvailableCompaniesReturnsMinimalMaskedOptions()
|
||||
{
|
||||
AppUser company = activeCompany(66L);
|
||||
company.setIdCard("91659001MA77ABCD12");
|
||||
when(userMapper.selectBackDoorCompanyList("测试企业", 200))
|
||||
.thenReturn(Collections.singletonList(company));
|
||||
|
||||
List<CompanyLoginUser> companies = service.listAvailableCompanies(" 测试企业 ", 500);
|
||||
|
||||
assertEquals(1, companies.size());
|
||||
assertEquals(66L, companies.get(0).getAppUserId());
|
||||
assertEquals("测试企业", companies.get(0).getCompanyName());
|
||||
assertEquals("9165**********CD12", companies.get(0).getCompanyCodeMasked());
|
||||
verify(userMapper).selectBackDoorCompanyList("测试企业", 200);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listAvailableCompaniesUsesAccountIdWhenCompanyNameIsMissing()
|
||||
{
|
||||
AppUser company = activeCompany(67L);
|
||||
company.setName(null);
|
||||
when(userMapper.selectBackDoorCompanyList(null, 100))
|
||||
.thenReturn(Collections.singletonList(company));
|
||||
|
||||
List<CompanyLoginUser> companies = service.listAvailableCompanies(null, 100);
|
||||
|
||||
assertEquals("企业账号 67", companies.get(0).getCompanyName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginCompanyCreatesSiteTokenForActiveCompany()
|
||||
{
|
||||
AppUser company = activeCompany(66L);
|
||||
when(sysUserService.selectAppUserById(66L)).thenReturn(company);
|
||||
when(sysLoginService.loginUserIdApp(company)).thenReturn("company-site-token");
|
||||
|
||||
CompanyLoginSession session = service.loginCompany(66L);
|
||||
|
||||
assertSame(company, session.getAppUser());
|
||||
assertEquals("company-site-token", session.getToken());
|
||||
verify(sysLoginService).loginUserIdApp(company);
|
||||
verify(tokenService, never()).createTokenHourTwo(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginCompanyRejectsNonCompanyAccount()
|
||||
{
|
||||
AppUser jobSeeker = activeCompany(66L);
|
||||
jobSeeker.setIsCompanyUser("1");
|
||||
when(sysUserService.selectAppUserById(66L)).thenReturn(jobSeeker);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class, () -> service.loginCompany(66L));
|
||||
|
||||
assertEquals("所选账号不是企业账号", exception.getMessage());
|
||||
verify(sysLoginService, never()).loginUserIdApp(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginCompanyRejectsDisabledCompany()
|
||||
{
|
||||
AppUser company = activeCompany(66L);
|
||||
company.setStatus("1");
|
||||
when(sysUserService.selectAppUserById(66L)).thenReturn(company);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class, () -> service.loginCompany(66L));
|
||||
|
||||
assertEquals("企业账号已停用,不能登录", exception.getMessage());
|
||||
verify(sysLoginService, never()).loginUserIdApp(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
private SysUser activeUser(Long userId)
|
||||
{
|
||||
SysUser user = new SysUser();
|
||||
@@ -163,4 +236,15 @@ class BackDoorLoginServiceTest
|
||||
user.setDelFlag("0");
|
||||
return user;
|
||||
}
|
||||
|
||||
private AppUser activeCompany(Long userId)
|
||||
{
|
||||
AppUser company = new AppUser();
|
||||
company.setUserId(userId);
|
||||
company.setName("测试企业");
|
||||
company.setIsCompanyUser("0");
|
||||
company.setStatus("0");
|
||||
company.setDelFlag("0");
|
||||
return company;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,15 @@ public interface SysUserMapper
|
||||
*/
|
||||
List<SysUser> selectBackDoorUserList(@Param("keyword") String keyword, @Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* 查询小程序测试授权可选择的正常企业账号。
|
||||
*
|
||||
* @param keyword 企业用户 ID、企业名称或统一社会信用代码关键字
|
||||
* @param limit 最大返回数量
|
||||
* @return 可登录的企业 AppUser
|
||||
*/
|
||||
List<AppUser> selectBackDoorCompanyList(@Param("keyword") String keyword, @Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* 查询指定账号在后门登录规则下的最新有效用户 ID。
|
||||
*
|
||||
|
||||
@@ -111,6 +111,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<!--
|
||||
企业小程序会话以 app_user 为主体。这里只返回未删除、未停用的企业账号;
|
||||
列表响应还会在服务层转换为最小化 DTO,不直接暴露手机号等个人信息。
|
||||
-->
|
||||
<select id="selectBackDoorCompanyList" resultType="com.ruoyi.common.core.domain.entity.AppUser">
|
||||
select u.user_id,
|
||||
u.name,
|
||||
u.id_card,
|
||||
u.status,
|
||||
u.del_flag,
|
||||
u.is_company_user
|
||||
from app_user u
|
||||
where u.del_flag = '0'
|
||||
and u.is_company_user = '0'
|
||||
and (u.status is null or u.status = '' or u.status = '0')
|
||||
<if test="keyword != null and keyword != ''">
|
||||
and (
|
||||
cast(u.user_id as varchar) like concat('%', cast(#{keyword} as varchar), '%')
|
||||
or u.name like concat('%', cast(#{keyword} as varchar), '%')
|
||||
or u.id_card like concat('%', cast(#{keyword} as varchar), '%')
|
||||
)
|
||||
</if>
|
||||
order by u.name asc, u.user_id desc
|
||||
limit #{limit}
|
||||
</select>
|
||||
|
||||
<sql id="selectUserVo">
|
||||
select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
|
||||
d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
|
||||
|
||||
Reference in New Issue
Block a user