main主干首次提交,包含用户认证模块
This commit is contained in:
98
src/main/java/com/labelsys/backend/service/AuthService.java
Normal file
98
src/main/java/com/labelsys/backend/service/AuthService.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.labelsys.backend.common.ResultCode;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import com.labelsys.backend.common.exception.UnauthorizedException;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.dto.request.ChangePasswordRequest;
|
||||
import com.labelsys.backend.dto.request.LoginRequest;
|
||||
import com.labelsys.backend.dto.response.CompanyOptionResponse;
|
||||
import com.labelsys.backend.dto.response.LoginResponse;
|
||||
import com.labelsys.backend.entity.SysCompany;
|
||||
import com.labelsys.backend.entity.SysUser;
|
||||
import com.labelsys.backend.enums.CompanyStatus;
|
||||
import com.labelsys.backend.enums.UserStatus;
|
||||
import com.labelsys.backend.mapper.SysCompanyMapper;
|
||||
import com.labelsys.backend.mapper.SysUserMapper;
|
||||
import com.labelsys.backend.service.session.TokenSessionRepository;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuthService {
|
||||
|
||||
private final SysCompanyMapper sysCompanyMapper;
|
||||
private final SysUserMapper sysUserMapper;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final TokenSessionRepository tokenSessionRepository;
|
||||
|
||||
@Value("${labelsys.session.ttl:PT2H}")
|
||||
private Duration sessionTtl;
|
||||
|
||||
public List<CompanyOptionResponse> listAvailableCompanies(String phone) {
|
||||
return sysCompanyMapper.findEnabledCompaniesByPhone(phone).stream()
|
||||
.map(CompanyOptionResponse::from)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
SysCompany company = loadEnabledCompany(request.companyCode());
|
||||
SysUser user = loadEnabledUser(company.getId(), request.phone());
|
||||
if (!passwordEncoder.matches(request.password(), user.getPasswordHash())) {
|
||||
throw new UnauthorizedException("手机号、公司或密码错误");
|
||||
}
|
||||
LoginUser loginUser = LoginUser.from(user, company);
|
||||
String token = UUID.randomUUID().toString();
|
||||
tokenSessionRepository.save(token, loginUser, sessionTtl);
|
||||
return LoginResponse.from(token, loginUser, company);
|
||||
}
|
||||
|
||||
public LoginUser getCurrentUser(String token) {
|
||||
return tokenSessionRepository.find(token)
|
||||
.orElseThrow(() -> new UnauthorizedException("未登录或登录已过期"));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void changePassword(LoginUser currentUser, ChangePasswordRequest request) {
|
||||
if (!request.newPassword().equals(request.confirmPassword())) {
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "两次输入的新密码不一致");
|
||||
}
|
||||
SysUser user = sysUserMapper.findByIdAndCompanyId(currentUser.userId(), currentUser.companyId());
|
||||
if (user == null || user.getStatus() != UserStatus.ENABLED) {
|
||||
throw new UnauthorizedException("未登录或登录已过期");
|
||||
}
|
||||
if (!passwordEncoder.matches(request.oldPassword(), user.getPasswordHash())) {
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "旧密码错误");
|
||||
}
|
||||
sysUserMapper.updatePassword(user.getId(), user.getCompanyId(), passwordEncoder.encode(request.newPassword()), false);
|
||||
sysUserMapper.bumpSessionVersion(user.getId(), user.getCompanyId());
|
||||
tokenSessionRepository.removeAll(user.getId());
|
||||
}
|
||||
|
||||
public void logout(String token) {
|
||||
tokenSessionRepository.remove(token);
|
||||
}
|
||||
|
||||
private SysCompany loadEnabledCompany(String companyCode) {
|
||||
SysCompany company = sysCompanyMapper.findByCompanyCode(companyCode);
|
||||
if (company == null || company.getStatus() != CompanyStatus.ENABLED) {
|
||||
throw new UnauthorizedException("手机号、公司或密码错误");
|
||||
}
|
||||
return company;
|
||||
}
|
||||
|
||||
private SysUser loadEnabledUser(Long companyId, String phone) {
|
||||
SysUser user = sysUserMapper.findByCompanyIdAndPhone(companyId, phone);
|
||||
if (user == null || user.getStatus() != UserStatus.ENABLED) {
|
||||
throw new UnauthorizedException("手机号、公司或密码错误");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.labelsys.backend.common.ResultCode;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import com.labelsys.backend.common.exception.ForbiddenException;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.dto.request.CreateCompanyRequest;
|
||||
import com.labelsys.backend.dto.request.UpdateCompanyStatusRequest;
|
||||
import com.labelsys.backend.entity.SysCompany;
|
||||
import com.labelsys.backend.mapper.SysCompanyMapper;
|
||||
import com.labelsys.backend.util.IdGenerator;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CompanyService {
|
||||
|
||||
private final SysCompanyMapper sysCompanyMapper;
|
||||
|
||||
public List<SysCompany> listCompanies(LoginUser currentUser) {
|
||||
assertPlatformAdmin(currentUser);
|
||||
return sysCompanyMapper.listAll();
|
||||
}
|
||||
|
||||
public SysCompany createCompany(LoginUser currentUser, CreateCompanyRequest request) {
|
||||
assertPlatformAdmin(currentUser);
|
||||
if (sysCompanyMapper.findByCompanyCode(request.companyCode()) != null) {
|
||||
throw new BusinessException(ResultCode.CONFLICT, "公司编码已存在");
|
||||
}
|
||||
SysCompany company = SysCompany.builder()
|
||||
.id(IdGenerator.nextId())
|
||||
.companyCode(request.companyCode())
|
||||
.companyName(request.companyName())
|
||||
.status(com.labelsys.backend.enums.CompanyStatus.ENABLED)
|
||||
.build();
|
||||
sysCompanyMapper.insert(company);
|
||||
return company;
|
||||
}
|
||||
|
||||
public void updateStatus(LoginUser currentUser, Long companyId, UpdateCompanyStatusRequest request) {
|
||||
assertPlatformAdmin(currentUser);
|
||||
if (sysCompanyMapper.updateStatus(companyId, request.status()) == 0) {
|
||||
throw new BusinessException(ResultCode.NOT_FOUND, "公司不存在");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertPlatformAdmin(LoginUser currentUser) {
|
||||
if (!currentUser.isPlatformAdmin()) {
|
||||
throw new ForbiddenException("仅平台管理员可操作");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.entity.BizDataRecord;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import com.labelsys.backend.mapper.BizDataRecordMapper;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DataPermissionService {
|
||||
|
||||
private final BizDataRecordMapper bizDataRecordMapper;
|
||||
|
||||
public List<BizDataRecord> listVisibleRecords(LoginUser currentUser) {
|
||||
return switch (currentUser.role()) {
|
||||
case EMPLOYEE -> bizDataRecordMapper.listVisibleByEmployee(currentUser.companyId(), currentUser.userId());
|
||||
case MANAGER -> bizDataRecordMapper.listVisibleByManager(currentUser.companyId());
|
||||
case ENGINEER -> bizDataRecordMapper.listVisibleByEngineer(currentUser.companyId());
|
||||
};
|
||||
}
|
||||
|
||||
public boolean canAccessCreator(LoginUser currentUser, Long creatorId, UserRole creatorRole) {
|
||||
return switch (currentUser.role()) {
|
||||
case EMPLOYEE -> currentUser.userId().equals(creatorId);
|
||||
case MANAGER -> creatorRole == UserRole.EMPLOYEE || creatorRole == UserRole.MANAGER;
|
||||
case ENGINEER -> true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用数据过滤方法
|
||||
*
|
||||
* @param currentUser 当前登录用户
|
||||
* @param allRecords 待过滤的全量数据列表
|
||||
* @param roleExtractor 从数据对象中提取“关联角色”或“创建者角色”的函数
|
||||
* @param ownerIdExtractor 从数据对象中提取“所有者ID”的函数(用于员工只能看自己的情况)
|
||||
* @param <T> 数据类型
|
||||
* @return 过滤后的数据列表
|
||||
*/
|
||||
public <T> List<T> filterByRole(
|
||||
LoginUser currentUser,
|
||||
List<T> allRecords,
|
||||
Function<T, UserRole> roleExtractor,
|
||||
Function<T, Long> ownerIdExtractor) {
|
||||
|
||||
if (allRecords == null || allRecords.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
UserRole currentRole = currentUser.role();
|
||||
Long currentUserId = currentUser.userId();
|
||||
|
||||
return allRecords.stream()
|
||||
.filter(record -> {
|
||||
UserRole recordRole = roleExtractor.apply(record);
|
||||
Long recordOwnerId = ownerIdExtractor.apply(record);
|
||||
|
||||
return switch (currentRole) {
|
||||
case EMPLOYEE ->
|
||||
// 员工只能查看自己创建/拥有的数据
|
||||
currentUserId.equals(recordOwnerId);
|
||||
|
||||
case MANAGER ->
|
||||
// 经理可以查看员工和经理的数据,不能查看总工程师的数据
|
||||
recordRole == UserRole.EMPLOYEE || recordRole == UserRole.MANAGER;
|
||||
|
||||
case ENGINEER ->
|
||||
// 总工程师可以查看所有数据
|
||||
true;
|
||||
};
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 针对 BizDataRecord 的便捷调用方法
|
||||
*/
|
||||
// public List<BizDataRecord> listVisibleRecordsGeneric(LoginUser currentUser, List<BizDataRecord> allRecords) {
|
||||
// return filterByRole(
|
||||
// currentUser,
|
||||
// allRecords,
|
||||
// BizDataRecord::
|
||||
// BizDataRecord::getCreatorRole, // 提取创建者角色
|
||||
// BizDataRecord::getCreatorId // 提取创建者ID
|
||||
// );
|
||||
// }
|
||||
}
|
||||
26
src/main/java/com/labelsys/backend/service/MenuService.java
Normal file
26
src/main/java/com/labelsys/backend/service/MenuService.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.dto.response.MenuResponse;
|
||||
import com.labelsys.backend.mapper.SysMenuMapper;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MenuService {
|
||||
|
||||
private final SysMenuMapper sysMenuMapper;
|
||||
|
||||
public List<MenuResponse> listCurrentMenus(LoginUser currentUser) {
|
||||
List<String> positionCodes = java.util.Arrays.stream(com.labelsys.backend.enums.UserPosition.values())
|
||||
.filter(position -> currentUser.position().canAccess(position))
|
||||
.map(Enum::name)
|
||||
.toList();
|
||||
return sysMenuMapper.listCurrentMenus(currentUser.companyId(), positionCodes)
|
||||
.stream()
|
||||
.map(MenuResponse::from)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
134
src/main/java/com/labelsys/backend/service/UserService.java
Normal file
134
src/main/java/com/labelsys/backend/service/UserService.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.labelsys.backend.common.ResultCode;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import com.labelsys.backend.common.exception.ForbiddenException;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.dto.request.CreateCompanyAdminRequest;
|
||||
import com.labelsys.backend.dto.request.CreateUserRequest;
|
||||
import com.labelsys.backend.dto.request.UpdateUserAssignmentRequest;
|
||||
import com.labelsys.backend.dto.request.UpdateUserStatusRequest;
|
||||
import com.labelsys.backend.entity.SysCompany;
|
||||
import com.labelsys.backend.entity.SysUser;
|
||||
import com.labelsys.backend.enums.CompanyStatus;
|
||||
import com.labelsys.backend.enums.UserPosition;
|
||||
import com.labelsys.backend.enums.UserRole;
|
||||
import com.labelsys.backend.enums.UserStatus;
|
||||
import com.labelsys.backend.mapper.SysCompanyMapper;
|
||||
import com.labelsys.backend.mapper.SysUserMapper;
|
||||
import com.labelsys.backend.service.session.TokenSessionRepository;
|
||||
import com.labelsys.backend.util.IdGenerator;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
|
||||
private static final String DEFAULT_PASSWORD = "123456";
|
||||
|
||||
private final SysUserMapper sysUserMapper;
|
||||
private final SysCompanyMapper sysCompanyMapper;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final TokenSessionRepository tokenSessionRepository;
|
||||
|
||||
public List<SysUser> listCompanyUsers(LoginUser currentUser) {
|
||||
assertCompanyAdmin(currentUser);
|
||||
return sysUserMapper.listByCompanyId(currentUser.companyId());
|
||||
}
|
||||
|
||||
public List<SysUser> listCompanyAdmins(LoginUser currentUser, Long companyId) {
|
||||
assertPlatformAdmin(currentUser);
|
||||
return sysUserMapper.listCompanyAdmins(companyId);
|
||||
}
|
||||
|
||||
public SysUser createCompanyAdmin(LoginUser currentUser, CreateCompanyAdminRequest request) {
|
||||
assertPlatformAdmin(currentUser);
|
||||
ensureEnabledCompany(request.companyId());
|
||||
return createUser(
|
||||
request.companyId(),
|
||||
new CreateUserRequest(request.phone(), request.username(), request.realName(), UserRole.EMPLOYEE, UserPosition.ADMIN)
|
||||
);
|
||||
}
|
||||
|
||||
public SysUser createCompanyUser(LoginUser currentUser, CreateUserRequest request) {
|
||||
assertCompanyAdmin(currentUser);
|
||||
return createUser(currentUser.companyId(), request);
|
||||
}
|
||||
|
||||
public SysUser createCompanyUser(LoginUser currentUser, Long companyId, CreateUserRequest request) {
|
||||
assertPlatformAdmin(currentUser);
|
||||
ensureEnabledCompany(companyId);
|
||||
return createUser(companyId, request);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateAssignment(LoginUser currentUser, Long userId, UpdateUserAssignmentRequest request) {
|
||||
assertCompanyAdmin(currentUser);
|
||||
if (sysUserMapper.updateAssignment(userId, currentUser.companyId(), request.role(), request.position()) == 0) {
|
||||
throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在");
|
||||
}
|
||||
tokenSessionRepository.removeAll(userId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateStatus(LoginUser currentUser, Long userId, UpdateUserStatusRequest request) {
|
||||
assertCompanyAdmin(currentUser);
|
||||
if (sysUserMapper.updateStatus(userId, currentUser.companyId(), request.status()) == 0) {
|
||||
throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在");
|
||||
}
|
||||
tokenSessionRepository.removeAll(userId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateCompanyAdminStatus(LoginUser currentUser, Long companyId, Long userId, UpdateUserStatusRequest request) {
|
||||
assertPlatformAdmin(currentUser);
|
||||
if (sysUserMapper.updateStatus(userId, companyId, request.status()) == 0) {
|
||||
throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在");
|
||||
}
|
||||
tokenSessionRepository.removeAll(userId);
|
||||
}
|
||||
|
||||
private SysUser createUser(Long companyId, CreateUserRequest request) {
|
||||
if (sysUserMapper.findByCompanyIdAndPhone(companyId, request.phone()) != null) {
|
||||
throw new BusinessException(ResultCode.CONFLICT, "同一公司内手机号已存在");
|
||||
}
|
||||
SysUser user = SysUser.builder()
|
||||
.id(IdGenerator.nextId())
|
||||
.companyId(companyId)
|
||||
.phone(request.phone())
|
||||
.username(request.username())
|
||||
.realName(request.realName())
|
||||
.role(request.role())
|
||||
.position(request.position())
|
||||
.passwordHash(passwordEncoder.encode(DEFAULT_PASSWORD))
|
||||
.mustChangePassword(true)
|
||||
.status(UserStatus.ENABLED)
|
||||
.sessionVersion(1)
|
||||
.build();
|
||||
sysUserMapper.insert(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
private void ensureEnabledCompany(Long companyId) {
|
||||
SysCompany company = sysCompanyMapper.findById(companyId);
|
||||
if (company == null || company.getStatus() != CompanyStatus.ENABLED) {
|
||||
throw new BusinessException(ResultCode.NOT_FOUND, "公司不存在或已禁用");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertPlatformAdmin(LoginUser currentUser) {
|
||||
if (!currentUser.isPlatformAdmin()) {
|
||||
throw new ForbiddenException("仅平台管理员可操作");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertCompanyAdmin(LoginUser currentUser) {
|
||||
if (currentUser.isPlatformAdmin() || currentUser.position() != UserPosition.ADMIN) {
|
||||
throw new ForbiddenException("仅公司管理员可操作");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.labelsys.backend.service.session;
|
||||
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
@ConditionalOnProperty(prefix = "labelsys.session", name = "store-type", havingValue = "memory")
|
||||
public class InMemoryTokenSessionRepository implements TokenSessionRepository {
|
||||
|
||||
private final Map<String, SessionEntry> sessions = new ConcurrentHashMap<>();
|
||||
private final Map<Long, Set<String>> userTokens = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void save(String token, LoginUser loginUser, Duration ttl) {
|
||||
sessions.put(token, new SessionEntry(loginUser, Instant.now().plus(ttl)));
|
||||
userTokens.computeIfAbsent(loginUser.userId(), ignored -> ConcurrentHashMap.newKeySet()).add(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LoginUser> find(String token) {
|
||||
SessionEntry entry = sessions.get(token);
|
||||
if (entry == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (entry.expiresAt().isBefore(Instant.now())) {
|
||||
remove(token);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(entry.loginUser());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(String token, Duration ttl) {
|
||||
SessionEntry entry = sessions.get(token);
|
||||
if (entry != null) {
|
||||
sessions.put(token, new SessionEntry(entry.loginUser(), Instant.now().plus(ttl)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String token) {
|
||||
SessionEntry removed = sessions.remove(token);
|
||||
if (removed != null) {
|
||||
Set<String> tokens = userTokens.get(removed.loginUser().userId());
|
||||
if (tokens != null) {
|
||||
tokens.remove(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(Long userId) {
|
||||
Set<String> tokens = userTokens.remove(userId);
|
||||
if (tokens != null) {
|
||||
tokens.forEach(sessions::remove);
|
||||
}
|
||||
}
|
||||
|
||||
private record SessionEntry(LoginUser loginUser, Instant expiresAt) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.labelsys.backend.service.session;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import com.labelsys.backend.common.ResultCode;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
@ConditionalOnProperty(prefix = "labelsys.session", name = "store-type", havingValue = "redis", matchIfMissing = true)
|
||||
public class RedisTokenSessionRepository implements TokenSessionRepository {
|
||||
|
||||
private static final String TOKEN_PREFIX = "token:";
|
||||
private static final String USER_TOKENS_PREFIX = "user:tokens:";
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public RedisTokenSessionRepository(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(String token, LoginUser loginUser, Duration ttl) {
|
||||
try {
|
||||
redisTemplate.opsForValue().set(tokenKey(token), objectMapper.writeValueAsString(loginUser), ttl);
|
||||
redisTemplate.opsForSet().add(userTokensKey(loginUser.userId()), token);
|
||||
redisTemplate.expire(userTokensKey(loginUser.userId()), ttl);
|
||||
} catch (JsonProcessingException exception) {
|
||||
throw new BusinessException(ResultCode.ERROR, "登录态序列化失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LoginUser> find(String token) {
|
||||
String value = redisTemplate.opsForValue().get(tokenKey(token));
|
||||
if (value == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
return Optional.of(objectMapper.readValue(value, LoginUser.class));
|
||||
} catch (JsonProcessingException exception) {
|
||||
remove(token);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(String token, Duration ttl) {
|
||||
redisTemplate.expire(tokenKey(token), ttl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String token) {
|
||||
find(token).ifPresent(loginUser -> redisTemplate.opsForSet().remove(userTokensKey(loginUser.userId()), token));
|
||||
redisTemplate.delete(tokenKey(token));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(Long userId) {
|
||||
Set<String> tokens = redisTemplate.opsForSet().members(userTokensKey(userId));
|
||||
if (tokens != null && !tokens.isEmpty()) {
|
||||
redisTemplate.delete(tokens.stream().map(this::tokenKey).toList());
|
||||
}
|
||||
redisTemplate.delete(userTokensKey(userId));
|
||||
}
|
||||
|
||||
private String tokenKey(String token) {
|
||||
return TOKEN_PREFIX + token;
|
||||
}
|
||||
|
||||
private String userTokensKey(Long userId) {
|
||||
return USER_TOKENS_PREFIX + userId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.labelsys.backend.service.session;
|
||||
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface TokenSessionRepository {
|
||||
|
||||
void save(String token, LoginUser loginUser, Duration ttl);
|
||||
|
||||
Optional<LoginUser> find(String token);
|
||||
|
||||
void refresh(String token, Duration ttl);
|
||||
|
||||
void remove(String token);
|
||||
|
||||
void removeAll(Long userId);
|
||||
}
|
||||
Reference in New Issue
Block a user