新增 AuthUtil 工具类,优化微信及会员服务实现

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