新增答辩用后台管理系统

This commit is contained in:
2026-06-29 03:08:42 +08:00
parent efd4d03037
commit e61fa6de00
98 changed files with 16022 additions and 420 deletions
@@ -0,0 +1,59 @@
package cn.novalon.gym.manage.member.dto;
import cn.novalon.gym.manage.member.enums.GenderEnum;
import lombok.Data;
import java.time.LocalDate;
/**
* 管理员编辑会员信息DTO(含密码验证)
*
* @author system
* @date 2026-06-24
*/
@Data
public class AdminEditMemberDto {
/**
* 管理员密码(必填,用于验证身份)
*/
private String adminPassword;
/**
* 昵称
*/
private String nickname;
/**
* 性别
*/
private GenderEnum gender;
/**
* 生日
*/
private LocalDate birthday;
/**
* 地址
*/
private String address;
/**
* 头像
*/
private String avatar;
/**
* 转换为 UpdateMemberInfoDto
*/
public UpdateMemberInfoDto toUpdateMemberInfoDto() {
UpdateMemberInfoDto dto = new UpdateMemberInfoDto();
dto.setNickname(nickname);
dto.setGender(gender);
dto.setBirthday(birthday);
dto.setAddress(address);
dto.setAvatar(avatar);
return dto;
}
}
@@ -2,16 +2,21 @@ package cn.novalon.gym.manage.member.handler;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.service.IMemberCardService;
import cn.novalon.gym.manage.sys.core.service.ISysUserService;
import cn.novalon.gym.manage.sys.util.AuthUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.Map;
/**
* 会员卡管理处理器
*
@@ -24,9 +29,13 @@ import reactor.core.publisher.Mono;
public class MemberCardHandler {
private final IMemberCardService memberCardService;
private final ISysUserService sysUserService;
private final AuthUtil authUtil;
public MemberCardHandler(IMemberCardService memberCardService) {
public MemberCardHandler(IMemberCardService memberCardService, ISysUserService sysUserService, AuthUtil authUtil) {
this.memberCardService = memberCardService;
this.sysUserService = sysUserService;
this.authUtil = authUtil;
}
@Operation(summary = "根据ID查询会员卡类型", description = "查询指定ID的会员卡类型详情")
@@ -60,27 +69,72 @@ public class MemberCardHandler {
.flatMap(card -> ServerResponse.status(HttpStatus.CREATED).bodyValue(card));
}
@Operation(summary = "更新会员卡类型", description = "更新会员卡类型信息")
@Operation(summary = "更新会员卡类型", description = "更新会员卡类型信息,需验证管理员密码")
public Mono<ServerResponse> updateMemberCard(ServerRequest request) {
Long adminId = authUtil.getMemberIdOrThrow(request);
Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(MemberCard.class)
.flatMap(card -> {
card.setMemberCardId(id);
return memberCardService.save(card);
})
.flatMap(updated -> ServerResponse.ok().bodyValue(updated))
.switchIfEmpty(ServerResponse.notFound().build());
String adminPassword = request.queryParam("adminPassword").orElse(null);
if (adminPassword == null || adminPassword.isBlank()) {
return ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 400, "message", "管理员密码不能为空"))
.flatMap(resp -> Mono.just(resp));
}
return sysUserService.verifyPassword(adminId, adminPassword)
.flatMap(valid -> {
if (!valid) {
return ServerResponse.status(HttpStatus.FORBIDDEN)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 403, "message", "管理员密码错误"))
.flatMap(resp -> Mono.just(resp));
}
return request.bodyToMono(MemberCard.class)
.flatMap(body -> memberCardService.findByMemberCardIdAndDeletedAtIsNull(id)
.flatMap(existing -> {
existing.setMemberCardName(body.getMemberCardName());
existing.setMemberCardType(body.getMemberCardType());
existing.setMemberCardPrice(body.getMemberCardPrice());
existing.setMemberCardValidityDays(body.getMemberCardValidityDays());
existing.setMemberCardTotalTimes(body.getMemberCardTotalTimes());
existing.setMemberCardAmount(body.getMemberCardAmount());
existing.setMemberCardStatus(body.getMemberCardStatus());
return memberCardService.save(existing);
})
.flatMap(updated -> ServerResponse.ok().bodyValue(updated))
.switchIfEmpty(ServerResponse.notFound().build()));
});
}
@Operation(summary = "删除会员卡类型", description = "逻辑删除会员卡类型")
@Operation(summary = "删除会员卡类型", description = "逻辑删除会员卡类型,需验证管理员密码")
public Mono<ServerResponse> deleteMemberCard(ServerRequest request) {
Long adminId = authUtil.getMemberIdOrThrow(request);
Long id = Long.valueOf(request.pathVariable("id"));
return memberCardService.logicalDelete(id)
.flatMap(rows -> {
if (rows > 0) {
return ServerResponse.noContent().build();
String adminPassword = request.queryParam("adminPassword").orElse(null);
if (adminPassword == null || adminPassword.isBlank()) {
return ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 400, "message", "管理员密码不能为空"))
.flatMap(resp -> Mono.just(resp));
}
return sysUserService.verifyPassword(adminId, adminPassword)
.flatMap(valid -> {
if (!valid) {
return ServerResponse.status(HttpStatus.FORBIDDEN)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 403, "message", "管理员密码错误"))
.flatMap(resp -> Mono.just(resp));
}
return ServerResponse.notFound().build();
return memberCardService.logicalDelete(id)
.flatMap(rows -> {
if (rows > 0) {
return ServerResponse.noContent().build();
}
return ServerResponse.notFound().build();
});
});
}
@@ -1,7 +1,7 @@
package cn.novalon.gym.manage.member.handler;
import cn.novalon.gym.manage.common.exception.NotFoundException;
import cn.novalon.gym.manage.member.config.WechatProperties;
import cn.novalon.gym.manage.member.dto.AdminEditMemberDto;
import cn.novalon.gym.manage.member.dto.AdminUpdatePhoneDto;
import cn.novalon.gym.manage.member.dto.SearchMemberDto;
import cn.novalon.gym.manage.member.dto.UpdateMemberInfoDto;
@@ -10,8 +10,8 @@ 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.WechatPhoneUtil;
import cn.novalon.gym.manage.sys.core.service.ISysUserService;
import cn.novalon.gym.manage.sys.util.AuthUtil;
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
@@ -24,6 +24,8 @@ import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.Map;
/**
* 会员信息处理器
*
@@ -41,6 +43,7 @@ public class MemberHandler {
private final WechatAuthService wechatAuthService;
private final WechatOfficialService wechatOfficialService;
private final AuthUtil authUtil;
private final ISysUserService sysUserService;
@Operation(summary = "获取会员信息", description = "根据当前登录用户获取会员基本信息")
public Mono<ServerResponse> getMemberInfo(ServerRequest request) {
@@ -165,7 +168,7 @@ public class MemberHandler {
});
}
@Operation(summary = "管理员编辑会员信息", description = "后台管理员编辑会员信息")
@Operation(summary = "管理员编辑会员信息", description = "后台管理员编辑会员信息,需验证管理员密码")
public Mono<ServerResponse> adminUpdateMemberInfo(ServerRequest request) {
Long adminId = authUtil.getMemberIdOrThrow(request);
@@ -174,14 +177,30 @@ public class MemberHandler {
long memberId = NumberUtils.toLong(memberIdStr, 0L);
if(memberId <= 0L) throw new IllegalArgumentException("会员ID格式错误");
// TODO: 补充签到记录
log.info("前台编辑会员信息, adminId: {}, memberId: {}", adminId, memberId);
return request.bodyToMono(UpdateMemberInfoDto.class)
.flatMap(updateDto -> memberService.adminUpdateMemberInfo(memberId, updateDto))
.flatMap(detail -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(detail));
return request.bodyToMono(AdminEditMemberDto.class)
.flatMap(dto -> {
if (dto.getAdminPassword() == null || dto.getAdminPassword().isBlank()) {
return ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 400, "message", "管理员密码不能为空"))
.flatMap(resp -> Mono.<ServerResponse>just(resp));
}
return sysUserService.verifyPassword(adminId, dto.getAdminPassword())
.flatMap(valid -> {
if (!valid) {
return ServerResponse.status(HttpStatus.FORBIDDEN)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 403, "message", "管理员密码错误"))
.flatMap(resp -> Mono.<ServerResponse>just(resp));
}
return memberService.adminUpdateMemberInfo(memberId, dto.toUpdateMemberInfoDto())
.flatMap(result -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(result));
});
});
}
@Operation(summary = "搜索会员列表", description = "后台管理员按关键词搜索会员,支持性别筛选和分页")
@@ -254,45 +254,58 @@ public class MemberServiceImpl implements MemberService {
log.debug("从缓存获取会员详情, memberId: {}", memberId);
return Mono.just(cached);
}
return memberRepository.findById(memberId)
.zipWith(
memberRepository.findCardRecordsWithCardInfoByMemberId(memberId)
.collectList(),
(baseInfo, cardList) -> {
MemberDetailVO memberDetailVO = BeanConvertUtil.toBean(baseInfo, MemberDetailVO.class);
// 缓存反序列化异常,查数据库
return queryMemberDetailFromDb(memberId, cacheKey);
})
.switchIfEmpty(Mono.defer(() -> {
// 缓存不存在,查数据库
return queryMemberDetailFromDb(memberId, cacheKey);
}));
}
GenderEnum genderEnum = GenderEnum.fromCode(baseInfo.getGender());
memberDetailVO.setGenderDesc(genderEnum.getDesc());
private Mono<MemberDetailVO> queryMemberDetailFromDb(Long memberId, String cacheKey) {
return memberRepository.findById(memberId)
.zipWith(
memberRepository.findCardRecordsWithCardInfoByMemberId(memberId)
.collectList(),
(baseInfo, cardList) -> {
MemberDetailVO memberDetailVO = BeanConvertUtil.toBean(baseInfo, MemberDetailVO.class);
List<MemberCardInfoVO> enrichedCards = cardList.stream()
.peek(vo -> {
if (vo.getMemberCardType() != null) {
try {
MemberCardType cardType = MemberCardType.valueOf(vo.getMemberCardType());
vo.setMemberCardTypeDesc(cardType.getDesc());
} catch (IllegalArgumentException e) {
vo.setMemberCardTypeDesc(vo.getMemberCardType());
}
}
if (vo.getMemberCardStatus() != null) {
vo.setMemberCardStatusDesc(vo.getMemberCardStatus() == 1 ? "上架" : "下架");
}
})
.collect(Collectors.toList());
memberDetailVO.setMemberCards(enrichedCards);
GenderEnum genderEnum = GenderEnum.fromCode(baseInfo.getGender());
memberDetailVO.setGenderDesc(genderEnum.getDesc());
long activeCount = enrichedCards.stream()
.filter(card -> card.getMemberCardStatus() != null && card.getMemberCardStatus() == 1)
.count();
memberDetailVO.setActiveCardCount((int) activeCount);
memberDetailVO.setInactiveCardCount(enrichedCards.size() - (int) activeCount);
List<MemberCardInfoVO> enrichedCards = cardList.stream()
.peek(vo -> {
if (vo.getMemberCardType() != null) {
try {
MemberCardType cardType = MemberCardType.valueOf(vo.getMemberCardType());
vo.setMemberCardTypeDesc(cardType.getDesc());
} catch (IllegalArgumentException e) {
vo.setMemberCardTypeDesc(vo.getMemberCardType());
}
}
if (vo.getMemberCardStatus() != null) {
vo.setMemberCardStatusDesc(vo.getMemberCardStatus() == 1 ? "上架" : "下架");
}
})
.collect(Collectors.toList());
memberDetailVO.setMemberCards(enrichedCards);
return memberDetailVO;
}
)
.flatMap(vo -> redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
.then(Mono.just(vo)));
});
long activeCount = enrichedCards.stream()
.filter(card -> card.getMemberCardStatus() != null && card.getMemberCardStatus() == 1)
.count();
memberDetailVO.setActiveCardCount((int) activeCount);
memberDetailVO.setInactiveCardCount(enrichedCards.size() - (int) activeCount);
return memberDetailVO;
}
)
.flatMap(vo -> redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
.then(Mono.just(vo)))
.switchIfEmpty(Mono.error(() -> {
log.error("会员不存在: memberId={}", memberId);
return new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
}));
}