新增 AuthUtil 工具类,优化微信及会员服务实现
This commit is contained in:
+16
-10
@@ -8,7 +8,7 @@ import cn.novalon.gym.manage.member.service.MemberService;
|
||||
import cn.novalon.gym.manage.member.service.WechatAuthService;
|
||||
import cn.novalon.gym.manage.member.service.WechatOfficialService;
|
||||
import cn.novalon.gym.manage.member.util.AesUtil;
|
||||
import cn.novalon.gym.manage.member.util.AuthUtil;
|
||||
import cn.novalon.gym.manage.sys.util.AuthUtil;
|
||||
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -42,7 +42,7 @@ public class MemberHandler {
|
||||
* 获取会员信息
|
||||
*
|
||||
* GET /api/member/info
|
||||
* Header: X-Member-Id: 123
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
*/
|
||||
public Mono<ServerResponse> getMemberInfo(ServerRequest request) {
|
||||
|
||||
@@ -51,18 +51,16 @@ public class MemberHandler {
|
||||
log.info("获取会员信息, memberId: {}", memberId);
|
||||
|
||||
return memberService.getMemberInfo(memberId)
|
||||
.flatMap(info -> {
|
||||
return ServerResponse.ok()
|
||||
.flatMap(info -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(info);
|
||||
});
|
||||
.bodyValue(info));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会员信息
|
||||
*
|
||||
* PUT /api/member/info
|
||||
* Header: X-Member-Id: 123
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
* Body: {
|
||||
* "nickname": "新昵称",
|
||||
* "gender": 1,
|
||||
@@ -86,9 +84,8 @@ public class MemberHandler {
|
||||
|
||||
/**
|
||||
* 绑定手机号(微信小程序)
|
||||
*
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
* POST /api/member/phone/bind?code=PHONE_CODE
|
||||
* Header: X-Member-Id: 123
|
||||
*/
|
||||
public Mono<ServerResponse> bindPhone(ServerRequest request) {
|
||||
|
||||
@@ -110,7 +107,6 @@ public class MemberHandler {
|
||||
* 查询服务号关注状态
|
||||
*
|
||||
* GET /api/member/subscribe/status
|
||||
* Header: X-Member-Id: 123
|
||||
*
|
||||
*/
|
||||
public Mono<ServerResponse> checkSubscribeStatus(ServerRequest request) {
|
||||
@@ -131,6 +127,7 @@ public class MemberHandler {
|
||||
* 管理员更新手机号
|
||||
*
|
||||
* POST /api/admin/member/123/phone
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
* Body: { "phone": "13800138000" }
|
||||
*
|
||||
*/
|
||||
@@ -180,6 +177,8 @@ public class MemberHandler {
|
||||
long memberId = NumberUtils.toLong(memberIdStr, 0L);
|
||||
if(memberId <= 0) throw new IllegalArgumentException("会员ID格式错误");
|
||||
|
||||
log.info("前台查看会员信息, adminId: {}, memberId: {}", adminId, memberId);
|
||||
|
||||
// TODO 多表查询:会员信息、团课信息、会员卡信息
|
||||
|
||||
return ServerResponse.ok()
|
||||
@@ -202,6 +201,8 @@ public class MemberHandler {
|
||||
long memberId = NumberUtils.toLong(memberIdStr, 0L);
|
||||
if(memberId <= 0L) throw new IllegalArgumentException("会员ID格式错误");
|
||||
|
||||
log.info("前台编辑会员信息, adminId: {}, memberId: {}", adminId, memberId);
|
||||
|
||||
// TODO 多表查询:会员信息、团课信息、会员卡信息
|
||||
|
||||
return ServerResponse.ok()
|
||||
@@ -224,6 +225,9 @@ public class MemberHandler {
|
||||
int pageNum = NumberUtils.toInt(request.queryParam("pageNum").orElse("1"), 1);
|
||||
int pageSize = NumberUtils.toInt(request.queryParam("pageSize").orElse("10"), 10);
|
||||
|
||||
log.info("前台搜索会员列表, adminId: {}, keyword: {}, filter: {}, pageNum: {}, pageSize: {}",
|
||||
adminId, keyword, filter, pageNum, pageSize);
|
||||
|
||||
return memberService.searchMember(new SearchMemberDto(keyword, filter, pageNum, pageSize))
|
||||
.map(member -> {
|
||||
// 解密手机号
|
||||
@@ -258,6 +262,8 @@ public class MemberHandler {
|
||||
int pageNum = NumberUtils.toInt(request.queryParam("pageNum").orElse("1"), 1);
|
||||
int pageSize = NumberUtils.toInt(request.queryParam("pageSize").orElse("10"), 10);
|
||||
|
||||
log.info("前台查看会员列表, adminId: {}, pageNum: {}, pageSize: {}", adminId, pageNum, pageSize);
|
||||
|
||||
return memberService.findAll(pageNum, pageSize)
|
||||
.map(member -> {
|
||||
// 解密手机号
|
||||
|
||||
+37
-42
@@ -1,5 +1,9 @@
|
||||
package cn.novalon.gym.manage.member.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.exception.ConflictException;
|
||||
import cn.novalon.gym.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.gym.manage.common.exception.NotFoundException;
|
||||
import cn.novalon.gym.manage.common.exception.SystemException;
|
||||
import cn.novalon.gym.manage.member.config.WechatProperties;
|
||||
import cn.novalon.gym.manage.member.dto.SearchMemberDto;
|
||||
import cn.novalon.gym.manage.member.dto.UpdateMemberInfoDto;
|
||||
@@ -14,13 +18,10 @@ import cn.novalon.gym.manage.member.vo.MemberInfoVO;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -28,7 +29,7 @@ import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 会员服务实现
|
||||
*
|
||||
*
|
||||
* @author 付嘉
|
||||
* @date 2026-05-01
|
||||
*/
|
||||
@@ -48,28 +49,22 @@ public class MemberServiceImpl implements MemberService {
|
||||
public void init() {
|
||||
this.memberSyncer = esSyncUtils.bind(Member.class, MemberES.class, memberESRepository);
|
||||
}
|
||||
|
||||
@Value("${wechat.aes.secret-key:}")
|
||||
private String aesSecretKey;
|
||||
|
||||
@Value("${wechat.aes.iv:}")
|
||||
private String aesIv;
|
||||
|
||||
@Override
|
||||
public Mono<MemberInfoVO> getMemberInfo(Long memberId) {
|
||||
return memberRepository.findById(memberId)
|
||||
.map(this::buildMemberInfoResponse);
|
||||
.map(this::buildMemberInfoResponse)
|
||||
.switchIfEmpty(Mono.error(() -> {
|
||||
log.error("会员不存在: memberId={}", memberId);
|
||||
throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MemberInfoVO> updateMemberInfo(Long memberId, UpdateMemberInfoDto updateDto) {
|
||||
log.info("会员更新个人信息, memberId: {}", memberId);
|
||||
|
||||
|
||||
return memberRepository.findById(memberId)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.error("会员不存在: memberId={}", memberId);
|
||||
return Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "会员不存在"));
|
||||
}))
|
||||
.flatMap(member -> {
|
||||
if (updateDto.getNickname() != null) {
|
||||
member.setNickname(updateDto.getNickname());
|
||||
@@ -86,17 +81,20 @@ public class MemberServiceImpl implements MemberService {
|
||||
if (updateDto.getAddress() != null) {
|
||||
member.setAddress(updateDto.getAddress());
|
||||
}
|
||||
|
||||
|
||||
return memberRepository.save(member);
|
||||
})
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.map(savedMember -> {
|
||||
log.info("会员信息更新成功, memberId: {}", savedMember.getId());
|
||||
return buildMemberInfoResponse(savedMember);
|
||||
});
|
||||
})
|
||||
.switchIfEmpty(Mono.error(() -> {
|
||||
log.error("会员不存在: memberId={}", memberId);
|
||||
throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
|
||||
}));
|
||||
}
|
||||
|
||||
// 会员信息响应
|
||||
private MemberInfoVO buildMemberInfoResponse(Member member) {
|
||||
String phone = member.getPhone();
|
||||
String maskedPhone = phone != null ? phone.replace(phone.substring(3, 7), "****") : null;
|
||||
@@ -116,34 +114,27 @@ public class MemberServiceImpl implements MemberService {
|
||||
@Override
|
||||
public Mono<Boolean> adminUpdatePhone(Long memberId, String phone) {
|
||||
log.info("管理端录入手机号, memberId: {}, phone: {}", memberId, phone);
|
||||
|
||||
|
||||
String encryptedPhone;
|
||||
try {
|
||||
encryptedPhone = AesUtil.encrypt(phone, aesSecretKey, aesIv);
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
encryptedPhone = AesUtil.encrypt(phone, secretKey, iv);
|
||||
log.info("手机号加密成功");
|
||||
} catch (Exception e) {
|
||||
log.error("手机号加密失败", e);
|
||||
return Mono.error(new ResponseStatusException(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"手机号加密失败: " + e.getMessage()
|
||||
));
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "手机号加密失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
return memberRepository.findByPhone(encryptedPhone)
|
||||
.<Boolean>flatMap(existingMember -> {
|
||||
if (existingMember.getId().equals(memberId)) {
|
||||
log.warn("手机号已是当前用户的: memberId={}", memberId);
|
||||
return Mono.error(new ResponseStatusException(
|
||||
HttpStatus.CONFLICT,
|
||||
"重复绑定"
|
||||
));
|
||||
throw new ConflictException(ErrorCode.CONFLICT_DUPLICATE_USER, "重复绑定");
|
||||
} else {
|
||||
log.warn("手机号已被其他用户绑定: memberId={}, existingMemberId={}",
|
||||
log.warn("手机号已被其他用户绑定: memberId={}, existingMemberId={}",
|
||||
memberId, existingMember.getId());
|
||||
return Mono.error(new ResponseStatusException(
|
||||
HttpStatus.CONFLICT,
|
||||
"该手机号已被其他会员绑定"
|
||||
));
|
||||
throw new ConflictException(ErrorCode.CONFLICT_DUPLICATE_USER, "该手机号已被其他会员绑定");
|
||||
}
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
@@ -154,24 +145,27 @@ public class MemberServiceImpl implements MemberService {
|
||||
|
||||
@Override
|
||||
public Flux<MemberES> searchMember(SearchMemberDto searchMemberDto) {
|
||||
log.info("搜索会员, searchValue: {}, filter: {}, pageNum: {}, pageSize: {}",
|
||||
searchMemberDto.getSearchValue(),
|
||||
searchMemberDto.getFilter(),
|
||||
searchMemberDto.getPageNum(),
|
||||
searchMemberDto.getPageSize());
|
||||
|
||||
String searchValue = searchMemberDto.getSearchValue();
|
||||
|
||||
// 1. 处理手机号加密
|
||||
if(searchValue != null && searchValue.matches("^1[3-9]\\d{9}$")){
|
||||
log.debug("搜索值为手机号格式,进行加密处理");
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
searchValue = AesUtil.encrypt(searchValue,secretKey,iv);
|
||||
}
|
||||
|
||||
// 2. 分页参数
|
||||
Pageable pageable = PageRequest.of(
|
||||
searchMemberDto.getPageNum() - 1,
|
||||
searchMemberDto.getPageSize(),
|
||||
Sort.by(Sort.Direction.DESC, "update_at")
|
||||
);
|
||||
|
||||
// 3. 调用 Repository 查询
|
||||
return memberESRepository.findByMemberNoOrPhoneOrNicknameContainingAndGender(
|
||||
searchValue,
|
||||
searchValue,
|
||||
@@ -183,6 +177,8 @@ public class MemberServiceImpl implements MemberService {
|
||||
|
||||
@Override
|
||||
public Flux<Member> findAll(Integer pageNum, Integer pageSize) {
|
||||
log.info("查询所有会员列表, pageNum: {}, pageSize: {}", pageNum, pageSize);
|
||||
|
||||
Pageable pageable = PageRequest.of(
|
||||
pageNum - 1,
|
||||
pageSize
|
||||
@@ -191,13 +187,12 @@ public class MemberServiceImpl implements MemberService {
|
||||
return memberRepository.findAllBy(pageable);
|
||||
}
|
||||
|
||||
// 更新会员手机号
|
||||
private Mono<Boolean> updateMemberPhone(Long memberId, String encryptedPhone) {
|
||||
return memberRepository.findById(memberId)
|
||||
.flatMap(member -> {
|
||||
member.setPhone(encryptedPhone);
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.map(savedMember -> {
|
||||
@@ -205,9 +200,9 @@ public class MemberServiceImpl implements MemberService {
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
.switchIfEmpty(Mono.error(() -> {
|
||||
log.error("会员不存在: memberId={}", memberId);
|
||||
return Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "会员不存在"));
|
||||
throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
+42
-81
@@ -1,5 +1,7 @@
|
||||
package cn.novalon.gym.manage.member.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.gym.manage.common.exception.SystemException;
|
||||
import cn.novalon.gym.manage.member.config.WechatProperties;
|
||||
import cn.novalon.gym.manage.member.service.WechatApiService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -17,7 +19,7 @@ import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信API服务实现
|
||||
*
|
||||
*
|
||||
* @author 付嘉
|
||||
* @date 2026-05-01
|
||||
*/
|
||||
@@ -28,33 +30,22 @@ import java.util.Map;
|
||||
public class WechatApiServiceImpl implements WechatApiService {
|
||||
|
||||
private final WechatProperties wechatProperties;
|
||||
|
||||
/**
|
||||
* WebClient实例 - 用于发送HTTP请求
|
||||
* 最大内存大小为10MB
|
||||
*/
|
||||
|
||||
private final WebClient webClient = WebClient.builder()
|
||||
.baseUrl("https://api.weixin.qq.com")
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024))
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 小程序
|
||||
*
|
||||
* @param code 小程序登录的code
|
||||
* @return Mono<Map<String, String>> session_keyopenidunionid响应格式
|
||||
*/
|
||||
@Override
|
||||
public Mono<Map<String, String>> jsCode2Session(String code) {
|
||||
log.info("微信jsCode2Session API");
|
||||
log.info("信息 - AppID: {}, AppSecret {}",
|
||||
log.info("信息 - AppID: {}, AppSecret {}",
|
||||
wechatProperties.getMiniapp().getAppId(),
|
||||
wechatProperties.getMiniapp().getAppSecret() != null ?
|
||||
wechatProperties.getMiniapp().getAppSecret() != null ?
|
||||
wechatProperties.getMiniapp().getAppSecret().substring(0, Math.min(4, wechatProperties.getMiniapp().getAppSecret().length())) + "***" : "null");
|
||||
log.info(" - code: {}", code);
|
||||
|
||||
|
||||
return webClient.get()
|
||||
// 构建URI
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/sns/jscode2session")
|
||||
.queryParam("appid", wechatProperties.getMiniapp().getAppId())
|
||||
@@ -62,44 +53,37 @@ public class WechatApiServiceImpl implements WechatApiService {
|
||||
.queryParam("js_code", code)
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.build())
|
||||
// 获取响应
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
// 处理响应
|
||||
.map(responseBody -> {
|
||||
log.info("微信API响应: {}", responseBody);
|
||||
|
||||
// 解析JSON响应
|
||||
|
||||
Map<String, Object> response;
|
||||
try {
|
||||
// 使用Jackson解析JSON响应
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
response = mapper.readValue(responseBody, Map.class);
|
||||
} catch (Exception e) {
|
||||
log.error("微信API响应失败", e);
|
||||
throw new RuntimeException("微信API响应失败: " + e.getMessage());
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "微信API响应失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
// 检查错误码
|
||||
|
||||
if (response.containsKey("errcode")) {
|
||||
Integer errcode = (Integer) response.get("errcode");
|
||||
String errmsg = (String) response.get("errmsg");
|
||||
log.error("微信API失败, errcode: {}, errmsg: {}", errcode, errmsg);
|
||||
throw new RuntimeException("微信API失败 [" + errcode + "]: " + errmsg);
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "微信API失败 [" + errcode + "]: " + errmsg);
|
||||
}
|
||||
|
||||
// 获取session_keyopenidunionid
|
||||
|
||||
result.put("session_key", (String) response.get("session_key"));
|
||||
result.put("openid", (String) response.get("openid"));
|
||||
result.put("unionid", (String) response.get("unionid"));
|
||||
|
||||
log.info("微信API响应成功, openid: {}, unionid: {}",
|
||||
|
||||
log.info("微信API响应成功, openid: {}, unionid: {}",
|
||||
result.get("openid"), result.get("unionid"));
|
||||
return result;
|
||||
})
|
||||
// 异常处理
|
||||
.onErrorResume(e -> {
|
||||
log.error("微信API响应异常 - URL: https://api.weixin.qq.com/sns/jscode2session");
|
||||
log.error("异常: {}", e.getClass().getName());
|
||||
@@ -107,26 +91,23 @@ public class WechatApiServiceImpl implements WechatApiService {
|
||||
if (e.getCause() != null) {
|
||||
log.error("异常原因: {}", e.getCause().getMessage());
|
||||
}
|
||||
return Mono.error(new RuntimeException("微信API响应异常 " + e.getMessage()));
|
||||
if (e instanceof SystemException) {
|
||||
return Mono.error(e);
|
||||
}
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "微信API响应异常 " + e.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手机号
|
||||
*
|
||||
* @param code 手机号或获取手机号的code
|
||||
* @return Mono<String> 手机号
|
||||
*/
|
||||
@Override
|
||||
public Mono<String> getPhoneNumber(String code) {
|
||||
log.debug("微信getPhoneNumber API, code: {}", code);
|
||||
|
||||
|
||||
return getAccessToken("miniapp")
|
||||
.flatMap(accessToken -> {
|
||||
|
||||
Map<String, String> requestBody = new HashMap<>();
|
||||
requestBody.put("code", code);
|
||||
|
||||
|
||||
return webClient.post()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/wxa/business/getuserphonenumber")
|
||||
@@ -137,43 +118,35 @@ public class WechatApiServiceImpl implements WechatApiService {
|
||||
.retrieve()
|
||||
.bodyToMono(Map.class)
|
||||
.map(response -> {
|
||||
if (response.containsKey("errcode") &&
|
||||
if (response.containsKey("errcode") &&
|
||||
(Integer) response.get("errcode") == 0) {
|
||||
|
||||
Map<String, Object> phoneInfo =
|
||||
|
||||
Map<String, Object> phoneInfo =
|
||||
(Map<String, Object>) response.get("phone_info");
|
||||
String phoneNumber = (String) phoneInfo.get("purePhoneNumber");
|
||||
|
||||
|
||||
log.info("获取手机号成功{}", phoneNumber);
|
||||
return phoneNumber;
|
||||
} else {
|
||||
String errmsg = (String) response.get("errmsg");
|
||||
log.error("获取手机号失败 {}", errmsg);
|
||||
throw new RuntimeException("获取手机号失败 " + errmsg);
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "获取手机号失败 " + errmsg);
|
||||
}
|
||||
});
|
||||
})
|
||||
//
|
||||
.onErrorResume(e -> {
|
||||
log.error("获取手机号失败", e);
|
||||
return Mono.error(new RuntimeException("获取手机号失败 " + e.getMessage()));
|
||||
if (e instanceof SystemException) {
|
||||
return Mono.error(e);
|
||||
}
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "获取手机号失败 " + e.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Access Token
|
||||
*
|
||||
* @param appType 应用类型
|
||||
* @return Mono<String> access_token
|
||||
*/
|
||||
@Override
|
||||
public Mono<String> getAccessToken(String appType) {
|
||||
log.debug("获取access_token, appType: {}", appType);
|
||||
|
||||
// TODO: 实现缓存逻辑
|
||||
// 使用 Caffeine 或 Redis 缓存
|
||||
// 目前直接调用微信API
|
||||
|
||||
|
||||
String appId, appSecret;
|
||||
if ("miniapp".equals(appType)) {
|
||||
appId = wechatProperties.getMiniapp().getAppId();
|
||||
@@ -182,7 +155,7 @@ public class WechatApiServiceImpl implements WechatApiService {
|
||||
appId = wechatProperties.getMp().getAppId();
|
||||
appSecret = wechatProperties.getMp().getAppSecret();
|
||||
}
|
||||
|
||||
|
||||
return webClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/cgi-bin/token")
|
||||
@@ -197,46 +170,34 @@ public class WechatApiServiceImpl implements WechatApiService {
|
||||
String accessToken = (String) response.get("access_token");
|
||||
Integer expiresIn = (Integer) response.get("expires_in");
|
||||
log.info("获取access_token成功, expires_in: {}s", expiresIn);
|
||||
|
||||
// TODO: 加入缓存
|
||||
// cache.put("wechat:token:" + appType, accessToken, expiresIn - 200, TimeUnit.SECONDS);
|
||||
|
||||
return accessToken;
|
||||
} else {
|
||||
String errmsg = (String) response.get("errmsg");
|
||||
log.error("获取access_token失败: {}", errmsg);
|
||||
throw new RuntimeException("获取access_token失败: " + errmsg);
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "获取access_token失败: " + errmsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证微信消息签名
|
||||
*
|
||||
* @param signature 微信消息签名
|
||||
* @param timestamp 创建时间戳
|
||||
* @param nonce 随机字符串
|
||||
* @return boolean true-签名有效false-签名无效
|
||||
*/
|
||||
@Override
|
||||
public boolean checkSignature(String signature, String timestamp, String nonce) {
|
||||
log.debug("验证微信消息签名, signature: {}, timestamp: {}, nonce: {}",
|
||||
log.debug("验证微信消息签名, signature: {}, timestamp: {}, nonce: {}",
|
||||
signature, timestamp, nonce);
|
||||
|
||||
|
||||
try {
|
||||
String token = wechatProperties.getMp().getToken();
|
||||
|
||||
|
||||
String[] arr = new String[]{token, timestamp, nonce};
|
||||
Arrays.sort(arr);
|
||||
|
||||
|
||||
StringBuilder content = new StringBuilder();
|
||||
for (String s : arr) {
|
||||
content.append(s);
|
||||
}
|
||||
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
byte[] digest = md.digest(content.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : digest) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
@@ -245,12 +206,12 @@ public class WechatApiServiceImpl implements WechatApiService {
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
|
||||
|
||||
String calculatedSignature = hexString.toString();
|
||||
|
||||
|
||||
boolean isValid = calculatedSignature.equals(signature);
|
||||
log.debug("验证微信消息签名结果: {}", isValid ? "通过" : "失败");
|
||||
|
||||
|
||||
return isValid;
|
||||
} catch (Exception e) {
|
||||
log.error("验证微信消息签名异常", e);
|
||||
|
||||
+48
-97
@@ -1,5 +1,9 @@
|
||||
package cn.novalon.gym.manage.member.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.exception.ConflictException;
|
||||
import cn.novalon.gym.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.gym.manage.common.exception.NotFoundException;
|
||||
import cn.novalon.gym.manage.common.exception.SystemException;
|
||||
import cn.novalon.gym.manage.member.config.WechatProperties;
|
||||
import cn.novalon.gym.manage.member.dto.WechatLoginDto;
|
||||
import cn.novalon.gym.manage.member.entity.Member;
|
||||
@@ -26,7 +30,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 微信认证服务实现
|
||||
*
|
||||
*
|
||||
* @author 付嘉
|
||||
* @date 2026-05-01
|
||||
*/
|
||||
@@ -52,34 +56,28 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 小程序登录 - 通过微信 code 完成登录
|
||||
*
|
||||
* @param request 微信登录请求
|
||||
* @return Mono<WechatLoginVO> 微信登录响应
|
||||
*/
|
||||
@Override
|
||||
public Mono<WechatLoginVO> miniappLogin(WechatLoginDto request) {
|
||||
log.info("开始小程序登录");
|
||||
|
||||
|
||||
return wechatApiService.jsCode2Session(request.getCode())
|
||||
.flatMap(sessionData -> {
|
||||
String openid = sessionData.get("openid");
|
||||
String unionId = sessionData.get("unionid");
|
||||
String sessionKey = sessionData.get("session_key");
|
||||
|
||||
|
||||
log.info("微信 API 返回: openid={}, unionid={}", openid, unionId);
|
||||
|
||||
|
||||
if (unionId != null && !unionId.isEmpty()) {
|
||||
return memberRepository.findByUnionId(unionId)
|
||||
.flatMap(member -> {
|
||||
log.info("找到会员, memberId: {}", member.getId());
|
||||
|
||||
|
||||
if (member.getMiniappOpenId() == null || member.getMiniappOpenId().isEmpty()) {
|
||||
log.info("用户已有 UnionID,补充小程序 OpenID, memberId: {}", member.getId());
|
||||
member.setMiniappOpenId(openid);
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(savedMember -> {
|
||||
@@ -89,7 +87,7 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
} else {
|
||||
log.info("老用户登录,更新最后登录时间, memberId: {}", member.getId());
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(savedMember -> {
|
||||
@@ -105,7 +103,7 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
log.info("找到会员, memberId: {}", member.getId());
|
||||
member.setUnionId(unionId);
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(savedMember -> {
|
||||
@@ -124,7 +122,7 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
.flatMap(member -> {
|
||||
log.info("找到会员, memberId: {}", member.getId());
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(savedMember -> {
|
||||
@@ -140,32 +138,28 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
})
|
||||
.onErrorResume(e -> {
|
||||
log.error("小程序登录失败", e);
|
||||
return Mono.error(new RuntimeException("登录失败: " + e.getMessage()));
|
||||
if (e instanceof SystemException) {
|
||||
return Mono.error(e);
|
||||
}
|
||||
return Mono.error(new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "登录失败: " + e.getMessage()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号 - 通过微信 code 获取并绑定手机号
|
||||
*
|
||||
* @param memberId 会员 ID
|
||||
* @param code 微信手机号 code
|
||||
* @return Mono<Boolean>
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> bindPhone(Long memberId, String code) {
|
||||
log.info("开始绑定手机号, memberId: {}", memberId);
|
||||
|
||||
|
||||
return wechatApiService.getPhoneNumber(code)
|
||||
.flatMap(phoneNumber -> {
|
||||
log.info("获取手机号: {}", phoneNumber);
|
||||
|
||||
String encryptedPhone = encryptPhone(phoneNumber);
|
||||
|
||||
|
||||
return memberRepository.findByPhone(encryptedPhone)
|
||||
.flatMap(existingMember -> {
|
||||
if (!existingMember.getId().equals(memberId)) {
|
||||
log.warn("手机号已被其他会员绑定, currentMemberId={}, existingMemberId={}", memberId, existingMember.getId());
|
||||
return Mono.error(new RuntimeException("手机号已被其他会员绑定"));
|
||||
throw new ConflictException(ErrorCode.CONFLICT_DUPLICATE_USER, "手机号已被其他会员绑定");
|
||||
} else {
|
||||
log.info("更新会员手机号, memberId: {}", memberId);
|
||||
return updateMemberPhone(memberId, encryptedPhone);
|
||||
@@ -178,17 +172,13 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
})
|
||||
.onErrorResume(e -> {
|
||||
log.error("绑定手机号失败", e);
|
||||
return Mono.error(new RuntimeException("绑定失败: " + e.getMessage()));
|
||||
if (e instanceof SystemException) {
|
||||
return Mono.error(e);
|
||||
}
|
||||
return Mono.error(new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "绑定失败: " + e.getMessage()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会员手机号
|
||||
*
|
||||
* @param memberId 会员 ID
|
||||
* @param encryptedPhone 加密后的手机号(Base64 编码)
|
||||
* @return Mono<Boolean> 是否更新成功
|
||||
*/
|
||||
|
||||
private Mono<Boolean> updateMemberPhone(Long memberId, String encryptedPhone) {
|
||||
return memberRepository.findById(memberId)
|
||||
.flatMap(member -> {
|
||||
@@ -201,95 +191,68 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
.switchIfEmpty(Mono.error(() -> {
|
||||
log.error("会员不存在, memberId={}", memberId);
|
||||
return Mono.error(new RuntimeException("会员不存在"));
|
||||
throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号加密
|
||||
*
|
||||
* @param phoneNumber 明文手机号
|
||||
* @return 加密后的手机号(Base64 编码)
|
||||
*/
|
||||
|
||||
private String encryptPhone(String phoneNumber) {
|
||||
try {
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
|
||||
|
||||
String encryptedPhone = AesUtil.encrypt(phoneNumber, secretKey, iv);
|
||||
|
||||
|
||||
log.debug("手机号加密成功");
|
||||
return encryptedPhone;
|
||||
} catch (Exception e) {
|
||||
log.error("手机号加密失败", e);
|
||||
throw new RuntimeException("手机号加密失败 " + e.getMessage());
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "手机号加密失败 " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AES 解密手机号
|
||||
*
|
||||
* @param encryptedPhone 加密后的手机号(Base64 编码)
|
||||
* @return 明文手机号
|
||||
*/
|
||||
public String decryptPhone(String encryptedPhone) {
|
||||
try {
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
|
||||
|
||||
String phoneNumber = AesUtil.decrypt(encryptedPhone, secretKey, iv);
|
||||
|
||||
|
||||
log.debug("手机号解密成功");
|
||||
return phoneNumber;
|
||||
} catch (Exception e) {
|
||||
log.error("手机号解密失败", e);
|
||||
throw new RuntimeException("手机号解密失败 " + e.getMessage());
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "手机号解密失败 " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新会员(首次登录)
|
||||
*
|
||||
* @param unionId 微信 UnionID
|
||||
* @param openid 小程序 OpenID
|
||||
* @param sessionKey 会话密钥
|
||||
* @param phoneCode 手机号 code(可选,前端调用 wx.getPhoneNumber())
|
||||
* @return Mono<WechatLoginVO> 登录响应
|
||||
*/
|
||||
private Mono<WechatLoginVO> createNewMember(String unionId, String openid, String sessionKey, String phoneCode) {
|
||||
log.info("开始创建新会员, unionId: {}, openid: {}", unionId, openid);
|
||||
|
||||
// Step 1: 生成会员号
|
||||
|
||||
String memberNo = MemberNoGenerator.generate();
|
||||
log.info("生成会员号: {}", memberNo);
|
||||
|
||||
// Step 2: 构建 Member 实体(仅保存标识信息)
|
||||
|
||||
Member member = Member.builder()
|
||||
.memberNo(memberNo) // 小程序注册时自动生成会员号
|
||||
.unionId(unionId) // 存储 UnionID 用于统一身份
|
||||
.miniappOpenId(openid) // 存储小程序 OpenID
|
||||
.lastLoginAt(LocalDateTime.now()) // 记录首次登录时间
|
||||
.memberNo(memberNo)
|
||||
.unionId(unionId)
|
||||
.miniappOpenId(openid)
|
||||
.lastLoginAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
|
||||
log.info("用户未注册,创建新会员(仅保存标识信息)");
|
||||
|
||||
// Step 3: 保存 Member
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(savedMember -> {
|
||||
log.info("保存 Member 成功, id: {}, memberNo: {}", savedMember.getId(), savedMember.getMemberNo());
|
||||
// Step 4: 如果有 phoneCode,尝试获取手机号
|
||||
if (phoneCode != null && !phoneCode.isEmpty()) {
|
||||
log.info("检测到 phoneCode,尝试获取手机号");
|
||||
return wechatPhoneUtil.getPhoneNumber(phoneCode)
|
||||
.flatMap(phoneNumber -> {
|
||||
if (phoneNumber != null && !phoneNumber.isEmpty()) {
|
||||
log.info("获取到手机号: {}", phoneNumber);
|
||||
// 加密手机号
|
||||
String encryptedPhone = encryptPhone(phoneNumber);
|
||||
// 为新会员绑定手机号
|
||||
savedMember.setPhone(encryptedPhone);
|
||||
return memberRepository.save(savedMember)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
@@ -303,40 +266,28 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 没有 phoneCode,直接返回
|
||||
return Mono.just(buildLoginResponse(savedMember, true, sessionKey));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建登录响应 - 封装返回数据(前端调用)
|
||||
*
|
||||
* @param member 会员实体
|
||||
* @param isNewUser 是否为新用户
|
||||
* @param sessionKey 会话密钥(后续可用于解密)
|
||||
* @return WechatLoginVO 登录响应
|
||||
*/
|
||||
private WechatLoginVO buildLoginResponse(Member member, boolean isNewUser, String sessionKey) {
|
||||
log.debug("构建登录响应, memberId: {}, isNewUser: {}", member.getId(), isNewUser);
|
||||
|
||||
|
||||
boolean needCompleteInfo = member.getNickname() == null || member.getPhone() == null;
|
||||
if (needCompleteInfo) {
|
||||
log.info("用户需要补全信息: nickname={}, phone={}",
|
||||
log.info("用户需要补全信息: nickname={}, phone={}",
|
||||
member.getNickname() != null ? "已有" : "未设置",
|
||||
member.getPhone() != null ? "已绑定" : "未绑定");
|
||||
}
|
||||
|
||||
// 统一使用 JwtTokenProvider 生成 Token
|
||||
// 使用空的角色列表,会员不需要角色
|
||||
|
||||
List<String> roles = new ArrayList<>();
|
||||
String accessToken = jwtTokenProvider.generateToken(String.valueOf(member.getId()), member.getId(), roles);
|
||||
|
||||
|
||||
log.info("JWT Token 生成成功, memberId: {}", member.getId());
|
||||
|
||||
// 使用 JwtTokenProvider 默认过期时间(86400 秒)
|
||||
|
||||
int expiresIn = 86400;
|
||||
|
||||
|
||||
return WechatLoginVO.builder()
|
||||
.memberId(member.getId())
|
||||
.accessToken(accessToken)
|
||||
|
||||
+5
-1
@@ -234,10 +234,14 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> checkSubscribeStatus(Long memberId) {
|
||||
log.info("查询用户关注状态, memberId: {}", memberId);
|
||||
|
||||
return memberRepository.findById(memberId)
|
||||
.map(member -> {
|
||||
Boolean subscribed = member.getSubscribed();
|
||||
return subscribed != null && subscribed;
|
||||
boolean result = subscribed != null && subscribed;
|
||||
log.info("查询用户关注状态结果, memberId: {}, subscribed: {}", memberId, result);
|
||||
return result;
|
||||
})
|
||||
.defaultIfEmpty(false);
|
||||
}
|
||||
|
||||
+22
-16
@@ -1,22 +1,23 @@
|
||||
package cn.novalon.gym.manage.app.config;
|
||||
|
||||
import cn.novalon.gym.manage.sys.handler.auth.SysAuthHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.auth.PasswordDiagnosticHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.config.SysConfigHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.dictionary.DictionaryHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.dict.SysDictHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.log.SysLogHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.log.OperationLogHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.menu.MenuHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.role.SysRoleHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.permission.SysPermissionHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.stats.StatsHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.user.SysUserHandler;
|
||||
import cn.novalon.gym.manage.checkIn.handler.CheckInHandler;
|
||||
import cn.novalon.gym.manage.file.handler.SysFileHandler;
|
||||
import cn.novalon.gym.manage.member.handler.MemberHandler;
|
||||
import cn.novalon.gym.manage.member.handler.WechatAuthHandler;
|
||||
import cn.novalon.gym.manage.notify.handler.SysNoticeHandler;
|
||||
import cn.novalon.gym.manage.notify.handler.SysUserMessageHandler;
|
||||
import cn.novalon.gym.manage.file.handler.SysFileHandler;
|
||||
import cn.novalon.gym.manage.member.handler.WechatAuthHandler;
|
||||
import cn.novalon.gym.manage.member.handler.MemberHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.auth.PasswordDiagnosticHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.auth.SysAuthHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.config.SysConfigHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.dict.SysDictHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.dictionary.DictionaryHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.log.OperationLogHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.log.SysLogHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.menu.MenuHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.permission.SysPermissionHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.role.SysRoleHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.stats.StatsHandler;
|
||||
import cn.novalon.gym.manage.sys.handler.user.SysUserHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
@@ -55,7 +56,8 @@ public class SystemRouter {
|
||||
SysPermissionHandler permissionHandler,
|
||||
MemberHandler memberHandler,
|
||||
WechatAuthHandler wechatAuthHandler,
|
||||
PasswordDiagnosticHandler passwordDiagnosticHandler) {
|
||||
PasswordDiagnosticHandler passwordDiagnosticHandler,
|
||||
CheckInHandler checkInHandler) {
|
||||
|
||||
return route()
|
||||
// ========== 诊断路由 ==========
|
||||
@@ -214,6 +216,10 @@ public class SystemRouter {
|
||||
.PUT("/api/admin/member/{id}", memberHandler::adminUpdateMemberInfo)
|
||||
.GET("/api/admin/members", memberHandler::searchMembers)
|
||||
.GET("/api/admin/members/all", memberHandler::getAllMembers)
|
||||
|
||||
// ========== 签到模块路由 ==========
|
||||
.POST("/api/checkin", checkInHandler::checkIn)
|
||||
.GET("/api/QRCode", checkInHandler::getQRCode)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.novalon.gym.manage.sys.util;
|
||||
|
||||
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthUtil {
|
||||
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
public String extractToken(ServerRequest request) {
|
||||
String authorization = request.headers().firstHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (authorization == null || !authorization.startsWith("Bearer ")) return null;
|
||||
return authorization.substring(7);
|
||||
}
|
||||
|
||||
public Long getMemberIdOrThrow(ServerRequest request) {
|
||||
String token = extractToken(request);
|
||||
if (token == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "缺少 Token");
|
||||
if (!jwtTokenProvider.validateToken(token)) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Token 无效或已过期");
|
||||
if (jwtTokenProvider.getUserIdFromToken(token) <= 0L) throw new IllegalArgumentException("ID无效");
|
||||
return jwtTokenProvider.getUserIdFromToken(token);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user