XuqmGroup-Server/demo-service/src/main/java/com/xuqm/demo/service/DemoAuthService.java
XuqmGroup 962e1dc722 feat(sample): 添加示例应用的核心功能模块
- 实现环境配置管理,支持外部和本地主机模式切换
- 集成Demo API接口,包含登录、注册、文件上传等功能
- 构建附件处理仓库,支持图片、视频、音频和文件发送
- 开发认证仓库,管理用户会话和IM令牌刷新机制
- 添加语音录制功能,支持实时音频消息录制
- 创建依赖注入容器,统一管理应用组件实例
- 实现登录界面,提供用户认证交互功能
- 开发聊天界面,集成消息收发和媒体处理功能
2026-04-28 16:08:07 +08:00

190 行
7.9 KiB
Java

package com.xuqm.demo.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.xuqm.common.exception.BusinessException;
import com.xuqm.common.security.AppRequestSignatureUtil;
import com.xuqm.common.security.JwtUtil;
import com.xuqm.demo.entity.DemoUserEntity;
import com.xuqm.demo.repository.DemoUserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
@Service
public class DemoAuthService {
private static final Logger log = LoggerFactory.getLogger(DemoAuthService.class);
private final DemoUserRepository userRepository;
private final JwtUtil jwtUtil;
private final PasswordEncoder passwordEncoder;
private final RestTemplate restTemplate;
private final DemoAppSecretClient appSecretClient;
@Value("${demo.im-service-url:http://192.168.116.9:8082}")
private String imServiceUrl;
public DemoAuthService(DemoUserRepository userRepository,
JwtUtil jwtUtil,
PasswordEncoder passwordEncoder,
RestTemplate restTemplate,
DemoAppSecretClient appSecretClient) {
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
this.passwordEncoder = passwordEncoder;
this.restTemplate = restTemplate;
this.appSecretClient = appSecretClient;
}
public record AuthResult(String demoToken, long demoTokenExpiresAt, String imToken, long imTokenExpiresAt, UserProfile profile) {}
public record ImCredential(String token, long expiresAt) {}
public record UserProfile(String appId, String userId, String nickname, String avatar, String gender) {}
@Transactional
public AuthResult register(String appId, String userId, String password, String nickname) {
if (userRepository.existsByAppIdAndUserId(appId, userId)) {
throw new BusinessException(409, "User already exists: " + userId);
}
DemoUserEntity user = new DemoUserEntity();
user.setId(UUID.randomUUID().toString());
user.setAppId(appId);
user.setUserId(userId);
user.setPasswordHash(passwordEncoder.encode(password));
user.setNickname(nickname != null ? nickname : userId);
user.setGender(DemoUserEntity.Gender.UNKNOWN);
user.setCreatedAt(Instant.now());
userRepository.save(user);
String demoToken = generateDemoToken(appId, userId);
ImCredential imCredential = callImServiceLogin(appId, userId, user.getNickname());
return new AuthResult(
demoToken,
tokenExpiresAt(),
imCredential.token(),
imCredential.expiresAt(),
toProfile(user)
);
}
@Transactional(readOnly = true)
public AuthResult login(String appId, String userId, String password) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId)
.orElseThrow(() -> new BusinessException(401, "Invalid credentials"));
if (!passwordEncoder.matches(password, user.getPasswordHash())) {
throw new BusinessException(401, "Invalid credentials");
}
String demoToken = generateDemoToken(appId, userId);
ImCredential imCredential = callImServiceLogin(appId, userId, user.getNickname());
return new AuthResult(
demoToken,
tokenExpiresAt(),
imCredential.token(),
imCredential.expiresAt(),
toProfile(user)
);
}
@Transactional
public void resetPassword(String appId, String userId, String newPassword) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId)
.orElseThrow(() -> new BusinessException(404, "User not found: " + userId));
user.setPasswordHash(passwordEncoder.encode(newPassword));
userRepository.save(user);
}
private String generateDemoToken(String appId, String userId) {
return jwtUtil.generate(userId, Map.of("appId", appId, "role", "USER"));
}
private long tokenExpiresAt() {
return Instant.now().toEpochMilli() + jwtUtil.getExpirationMillis();
}
public ImCredential refreshImToken(String appId, String userId) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId)
.orElseThrow(() -> new BusinessException(404, "User not found: " + userId));
return callImServiceLogin(appId, userId, user.getNickname());
}
/**
* Calls im-service to ensure the IM account exists and obtain an IM token.
* POST {imServiceUrl}/api/im/auth/login?appId={appId}&userId={userId}&nickname={nickname}
* Response: {"code":200,"data":{"token":"..."}}
*/
private ImCredential callImServiceLogin(String appId, String userId, String nickname) {
long timestamp = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString();
String effectiveNickname = nickname != null ? nickname : userId;
String appSecret = appSecretClient.getAppSecret(appId);
String payload = AppRequestSignatureUtil.payload(appId, userId, effectiveNickname, null, timestamp, nonce);
String signature = AppRequestSignatureUtil.sign(appSecret, payload);
String url = UriComponentsBuilder.fromHttpUrl(imServiceUrl)
.path("/api/im/auth/login")
.queryParam("appId", appId)
.queryParam("userId", userId)
.queryParam("nickname", effectiveNickname)
.toUriString();
try {
HttpHeaders headers = new HttpHeaders();
headers.set("X-App-Timestamp", String.valueOf(timestamp));
headers.set("X-App-Nonce", nonce);
headers.set("X-App-Signature", signature);
ResponseEntity<JsonNode> response = restTemplate.exchange(
url,
HttpMethod.POST,
new HttpEntity<>(headers),
JsonNode.class
);
JsonNode body = response.getBody();
if (body != null && body.path("code").asInt() == 200) {
JsonNode data = body.path("data");
String token = data.path("token").asText(null);
if (token == null || token.isBlank()) {
throw new BusinessException(502, "Failed to refresh IM token");
}
return new ImCredential(
token,
data.path("expiresAt").asLong(tokenExpiresAt())
);
}
log.warn("im-service login returned unexpected response for appId={} userId={}: {}", appId, userId, body);
throw new BusinessException(502, "Failed to refresh IM token");
} catch (RestClientException e) {
log.error("Failed to call im-service login for appId={} userId={}: {}", appId, userId, e.getMessage());
throw new BusinessException(502, "Failed to refresh IM token");
}
}
private UserProfile toProfile(DemoUserEntity user) {
return new UserProfile(
user.getAppId(),
user.getUserId(),
user.getNickname(),
user.getAvatar(),
user.getGender() != null ? user.getGender().name() : DemoUserEntity.Gender.UNKNOWN.name()
);
}
}