diff --git a/admin/src/main/java/org/jiayunet/admin/controller/AdminOrderController.java b/admin/src/main/java/org/jiayunet/admin/controller/AdminOrderController.java new file mode 100644 index 0000000..bd2edad --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/controller/AdminOrderController.java @@ -0,0 +1,36 @@ +package org.jiayunet.admin.controller; + +import lombok.AllArgsConstructor; +import org.jiayunet.admin.pojo.dto.order.OrderListDto; +import org.jiayunet.admin.pojo.dto.order.OrderStatsDto; +import org.jiayunet.admin.pojo.param.order.OrderPageParam; +import org.jiayunet.admin.service.AdminOrderService; +import org.jiayunet.pojo.PageResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * 管理后台-订单管理控制类 + * + * @author zk + */ +@RestController +@RequestMapping("/order") +@AllArgsConstructor +@Validated +public class AdminOrderController { + + private AdminOrderService adminOrderService; + + /** 订单分页列表 */ + @PostMapping("/list") + public PageResult list(@Validated @RequestBody OrderPageParam param) { + return adminOrderService.pageList(param); + } + + /** 订单统计 */ + @GetMapping("/stats") + public OrderStatsDto stats() { + return adminOrderService.stats(); + } +} diff --git a/admin/src/main/java/org/jiayunet/admin/controller/AdminProductController.java b/admin/src/main/java/org/jiayunet/admin/controller/AdminProductController.java new file mode 100644 index 0000000..7a31182 --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/controller/AdminProductController.java @@ -0,0 +1,48 @@ +package org.jiayunet.admin.controller; + +import lombok.AllArgsConstructor; +import org.jiayunet.admin.pojo.param.product.ProductPageParam; +import org.jiayunet.admin.pojo.param.product.ProductSaveParam; +import org.jiayunet.admin.service.AdminProductService; +import org.jiayunet.pojo.PageResult; +import org.jiayunet.pojo.po.MemberProduct; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * 管理后台-商品管理控制类 + * + * @author zk + */ +@RestController +@RequestMapping("/product") +@AllArgsConstructor +@Validated +public class AdminProductController { + + private AdminProductService adminProductService; + + /** 商品分页列表 */ + @PostMapping("/list") + public PageResult list(@Validated @RequestBody ProductPageParam param) { + return adminProductService.pageList(param); + } + + /** 新增/编辑商品 */ + @PostMapping("/save") + public void save(@Validated @RequestBody ProductSaveParam param) { + adminProductService.save(param); + } + + /** 上架/下架 */ + @PostMapping("/updateStatus") + public void updateStatus(@RequestParam Long id, @RequestParam Integer status) { + adminProductService.updateStatus(id, status); + } + + /** 删除商品 */ + @PostMapping("/delete") + public void delete(@RequestParam Long id) { + adminProductService.delete(id); + } +} diff --git a/admin/src/main/java/org/jiayunet/admin/controller/AdminUserController.java b/admin/src/main/java/org/jiayunet/admin/controller/AdminUserController.java new file mode 100644 index 0000000..2b71a98 --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/controller/AdminUserController.java @@ -0,0 +1,42 @@ +package org.jiayunet.admin.controller; + +import lombok.AllArgsConstructor; +import org.jiayunet.admin.pojo.dto.user.UserListDto; +import org.jiayunet.admin.pojo.dto.user.UserStatsDto; +import org.jiayunet.admin.pojo.param.user.UserPageParam; +import org.jiayunet.admin.service.AdminUserService; +import org.jiayunet.pojo.PageResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * 管理后台-用户管理控制类 + * + * @author zk + */ +@RestController +@RequestMapping("/user") +@AllArgsConstructor +@Validated +public class AdminUserController { + + private AdminUserService adminUserService; + + /** 用户分页列表 */ + @PostMapping("/list") + public PageResult list(@Validated @RequestBody UserPageParam param) { + return adminUserService.pageList(param); + } + + /** 用户统计(总数/今日/本周/本月) */ + @GetMapping("/stats") + public UserStatsDto stats() { + return adminUserService.stats(); + } + + /** 禁用/启用用户 */ + @PostMapping("/updateStatus") + public void updateStatus(@RequestParam Long userId, @RequestParam Integer status) { + adminUserService.updateStatus(userId, status); + } +} diff --git a/admin/src/main/java/org/jiayunet/admin/pojo/dto/order/OrderListDto.java b/admin/src/main/java/org/jiayunet/admin/pojo/dto/order/OrderListDto.java new file mode 100644 index 0000000..84e832a --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/pojo/dto/order/OrderListDto.java @@ -0,0 +1,35 @@ +package org.jiayunet.admin.pojo.dto.order; + +import lombok.Data; + +import java.time.Instant; + +/** + * 订单列表出参 + * + * @author zk + */ +@Data +public class OrderListDto { + + /** 订单ID */ + private Long id; + /** 订单编号 */ + private String orderNo; + /** 用户昵称 */ + private String userNick; + /** 用户手机号 */ + private String mobileNumber; + /** 商品名称 */ + private String productName; + /** 实付金额(分) */ + private Integer payAmount; + /** 支付渠道 1=微信 2=支付宝 */ + private Integer payChannel; + /** 订单状态 0=待支付 1=已支付 2=已退款 3=已关闭 */ + private Integer status; + /** 下单时间 */ + private Instant createTime; + /** 支付时间 */ + private Instant payTime; +} diff --git a/admin/src/main/java/org/jiayunet/admin/pojo/dto/order/OrderStatsDto.java b/admin/src/main/java/org/jiayunet/admin/pojo/dto/order/OrderStatsDto.java new file mode 100644 index 0000000..f1725af --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/pojo/dto/order/OrderStatsDto.java @@ -0,0 +1,29 @@ +package org.jiayunet.admin.pojo.dto.order; + +import lombok.Data; + +/** + * 订单统计出参 + * + * @author zk + */ +@Data +public class OrderStatsDto { + + /** 总订单数 */ + private Long totalCount; + /** 已支付订单数 */ + private Long paidCount; + /** 今日已支付 */ + private Long todayPaid; + /** 本周已支付 */ + private Long weekPaid; + /** 本月已支付 */ + private Long monthPaid; + /** 今日收入(分) */ + private Long todayIncome; + /** 本周收入(分) */ + private Long weekIncome; + /** 本月收入(分) */ + private Long monthIncome; +} diff --git a/admin/src/main/java/org/jiayunet/admin/pojo/dto/user/UserListDto.java b/admin/src/main/java/org/jiayunet/admin/pojo/dto/user/UserListDto.java new file mode 100644 index 0000000..3b50bdb --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/pojo/dto/user/UserListDto.java @@ -0,0 +1,29 @@ +package org.jiayunet.admin.pojo.dto.user; + +import lombok.Data; + +import java.time.Instant; + +/** + * 用户列表出参 + * + * @author zk + */ +@Data +public class UserListDto { + + /** 用户ID */ + private Long id; + /** 昵称 */ + private String nick; + /** 真实姓名 */ + private String realName; + /** 手机号 */ + private String mobileNumber; + /** 状态 0=正常 1=禁用 */ + private Integer status; + /** 简历数量 */ + private Integer resumeCount; + /** 注册时间 */ + private Instant createTime; +} diff --git a/admin/src/main/java/org/jiayunet/admin/pojo/dto/user/UserStatsDto.java b/admin/src/main/java/org/jiayunet/admin/pojo/dto/user/UserStatsDto.java new file mode 100644 index 0000000..f9a2074 --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/pojo/dto/user/UserStatsDto.java @@ -0,0 +1,21 @@ +package org.jiayunet.admin.pojo.dto.user; + +import lombok.Data; + +/** + * 用户统计出参 + * + * @author zk + */ +@Data +public class UserStatsDto { + + /** 用户总数 */ + private Long total; + /** 今日新增 */ + private Long today; + /** 本周新增 */ + private Long thisWeek; + /** 本月新增 */ + private Long thisMonth; +} diff --git a/admin/src/main/java/org/jiayunet/admin/pojo/param/order/OrderPageParam.java b/admin/src/main/java/org/jiayunet/admin/pojo/param/order/OrderPageParam.java new file mode 100644 index 0000000..7867408 --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/pojo/param/order/OrderPageParam.java @@ -0,0 +1,28 @@ +package org.jiayunet.admin.pojo.param.order; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.jiayunet.pojo.PageParam; + +import java.time.Instant; + +/** + * 订单分页查询入参 + * + * @author zk + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class OrderPageParam extends PageParam { + + /** 订单状态 0=待支付 1=已支付 2=已退款 3=已关闭 */ + private Integer status; + /** 支付渠道 1=微信 2=支付宝 */ + private Integer payChannel; + /** 用户手机号(模糊) */ + private String mobileNumber; + /** 下单时间-起 */ + private Instant startTime; + /** 下单时间-止 */ + private Instant endTime; +} diff --git a/admin/src/main/java/org/jiayunet/admin/pojo/param/product/ProductPageParam.java b/admin/src/main/java/org/jiayunet/admin/pojo/param/product/ProductPageParam.java new file mode 100644 index 0000000..2e5becd --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/pojo/param/product/ProductPageParam.java @@ -0,0 +1,18 @@ +package org.jiayunet.admin.pojo.param.product; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.jiayunet.pojo.PageParam; + +/** + * 商品分页查询入参 + * + * @author zk + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProductPageParam extends PageParam { + + /** 状态筛选 0=下架 1=上架 */ + private Integer status; +} diff --git a/admin/src/main/java/org/jiayunet/admin/pojo/param/product/ProductSaveParam.java b/admin/src/main/java/org/jiayunet/admin/pojo/param/product/ProductSaveParam.java new file mode 100644 index 0000000..d6b5568 --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/pojo/param/product/ProductSaveParam.java @@ -0,0 +1,51 @@ +package org.jiayunet.admin.pojo.param.product; + +import lombok.Data; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 商品新增/编辑入参 + * + * @author zk + */ +@Data +public class ProductSaveParam { + + /** 商品ID(编辑时必传,新增时不传) */ + private Long id; + + @NotBlank(message = "商品名称不能为空") + private String productName; + + /** 标签,如"限时优惠" */ + private String tag; + + /** 主推标识 0=否 1=是 */ + private Integer isFeatured; + + /** 购买按钮文字 */ + private String buyButtonText; + + @NotNull(message = "价格不能为空") + @Min(value = 1, message = "价格必须大于0") + private Integer price; + + /** 划线价(分) */ + private Integer originalPrice; + + /** 折算月价(分) */ + private Integer monthlyPrice; + + @NotNull(message = "有效天数不能为空") + @Min(value = 1, message = "有效天数必须大于0") + private Integer durationDays; + + /** 排序 */ + private Integer sortOrder; + + /** 状态 0=下架 1=上架 */ + private Integer status; +} diff --git a/admin/src/main/java/org/jiayunet/admin/pojo/param/user/UserPageParam.java b/admin/src/main/java/org/jiayunet/admin/pojo/param/user/UserPageParam.java new file mode 100644 index 0000000..02233d3 --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/pojo/param/user/UserPageParam.java @@ -0,0 +1,21 @@ +package org.jiayunet.admin.pojo.param.user; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.jiayunet.pojo.PageParam; + +/** + * 用户分页查询入参 + * + * @author zk + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class UserPageParam extends PageParam { + + /** 手机号(模糊搜索) */ + private String mobileNumber; + + /** 状态筛选 0=正常 1=禁用 */ + private Integer status; +} diff --git a/admin/src/main/java/org/jiayunet/admin/service/AdminOrderService.java b/admin/src/main/java/org/jiayunet/admin/service/AdminOrderService.java new file mode 100644 index 0000000..23e0f02 --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/service/AdminOrderService.java @@ -0,0 +1,119 @@ +package org.jiayunet.admin.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.jiayunet.admin.pojo.dto.order.OrderListDto; +import org.jiayunet.admin.pojo.dto.order.OrderStatsDto; +import org.jiayunet.admin.pojo.param.order.OrderPageParam; +import org.jiayunet.mapper.MemberOrderMapper; +import org.jiayunet.mapper.MemberProductMapper; +import org.jiayunet.mapper.UserMapper; +import org.jiayunet.pojo.PageResult; +import org.jiayunet.pojo.po.MemberOrder; +import org.jiayunet.pojo.po.MemberProduct; +import org.jiayunet.pojo.po.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.*; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 管理后台-订单管理服务 + *

依赖:MemberOrderMapper、UserMapper、MemberProductMapper

+ *

使用表:bg_member_order(订单列表/统计)、bg_user(关联用户信息)、bg_member_product(关联商品名)

+ * + * @author zk + */ +@Service +@Slf4j +public class AdminOrderService { + + @Autowired + private MemberOrderMapper memberOrderMapper; + @Autowired + private UserMapper userMapper; + @Autowired + private MemberProductMapper memberProductMapper; + + /** + * 订单分页列表 + *

1. 如果按手机号筛选,先查用户ID集合 2. 分页查订单 3. 批量关联用户和商品信息 4. 组装DTO

+ */ + public PageResult pageList(OrderPageParam param) { + // 如果传了手机号,先查对应用户ID + Set userIdFilter = null; + if (StringUtils.hasText(param.getMobileNumber())) { + List users = userMapper.selectList(new LambdaQueryWrapper().like(User::getMobileNumber, param.getMobileNumber()).select(User::getId)); + userIdFilter = users.stream().map(User::getId).collect(Collectors.toSet()); + if (userIdFilter.isEmpty()) return new PageResult<>(param.getPageNum().longValue(), param.getPageSize().longValue(), 0L, List.of()); + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(param.getStatus() != null, MemberOrder::getStatus, param.getStatus()) + .eq(param.getPayChannel() != null, MemberOrder::getPayChannel, param.getPayChannel()) + .ge(param.getStartTime() != null, MemberOrder::getCreateTime, param.getStartTime()) + .le(param.getEndTime() != null, MemberOrder::getCreateTime, param.getEndTime()) + .in(userIdFilter != null, MemberOrder::getUserId, userIdFilter) + .orderByDesc(MemberOrder::getCreateTime); + + Page page = memberOrderMapper.selectPage(param.toPage(), wrapper); + if (page.getRecords().isEmpty()) return PageResult.from((Page) page.convert(o -> new OrderListDto())); + + // 批量查用户和商品 + Set userIds = page.getRecords().stream().map(MemberOrder::getUserId).collect(Collectors.toSet()); + Set productIds = page.getRecords().stream().map(MemberOrder::getProductId).collect(Collectors.toSet()); + Map userMap = userMapper.selectBatchIds(userIds).stream().collect(Collectors.toMap(User::getId, u -> u)); + Map productMap = memberProductMapper.selectBatchIds(productIds).stream().collect(Collectors.toMap(MemberProduct::getId, p -> p)); + + Page dtoPage = (Page) page.convert(order -> { + OrderListDto dto = new OrderListDto(); + dto.setId(order.getId()); + dto.setOrderNo(order.getOrderNo()); + dto.setPayAmount(order.getPayAmount()); + dto.setPayChannel(order.getPayChannel()); + dto.setStatus(order.getStatus()); + dto.setCreateTime(order.getCreateTime()); + dto.setPayTime(order.getPayTime()); + User user = userMap.get(order.getUserId()); + if (user != null) { dto.setUserNick(user.getNick()); dto.setMobileNumber(user.getMobileNumber()); } + MemberProduct product = productMap.get(order.getProductId()); + if (product != null) { dto.setProductName(product.getProductName()); } + return dto; + }); + return PageResult.from(dtoPage); + } + + /** + * 订单统计:总数/已支付数/今日本周本月已支付数和收入 + */ + public OrderStatsDto stats() { + OrderStatsDto dto = new OrderStatsDto(); + ZoneId zone = ZoneId.of("Asia/Shanghai"); + LocalDate now = LocalDate.now(zone); + Instant todayStart = now.atStartOfDay(zone).toInstant(); + Instant weekStart = now.with(DayOfWeek.MONDAY).atStartOfDay(zone).toInstant(); + Instant monthStart = now.withDayOfMonth(1).atStartOfDay(zone).toInstant(); + + dto.setTotalCount(memberOrderMapper.selectCount(new LambdaQueryWrapper())); + dto.setPaidCount(memberOrderMapper.selectCount(new LambdaQueryWrapper().eq(MemberOrder::getStatus, 1))); + + // 今日/本周/本月已支付订单 + List todayOrders = memberOrderMapper.selectList(new LambdaQueryWrapper().eq(MemberOrder::getStatus, 1).ge(MemberOrder::getPayTime, todayStart)); + List weekOrders = memberOrderMapper.selectList(new LambdaQueryWrapper().eq(MemberOrder::getStatus, 1).ge(MemberOrder::getPayTime, weekStart)); + List monthOrders = memberOrderMapper.selectList(new LambdaQueryWrapper().eq(MemberOrder::getStatus, 1).ge(MemberOrder::getPayTime, monthStart)); + + dto.setTodayPaid((long) todayOrders.size()); + dto.setWeekPaid((long) weekOrders.size()); + dto.setMonthPaid((long) monthOrders.size()); + dto.setTodayIncome(todayOrders.stream().mapToLong(o -> o.getPayAmount() == null ? 0 : o.getPayAmount()).sum()); + dto.setWeekIncome(weekOrders.stream().mapToLong(o -> o.getPayAmount() == null ? 0 : o.getPayAmount()).sum()); + dto.setMonthIncome(monthOrders.stream().mapToLong(o -> o.getPayAmount() == null ? 0 : o.getPayAmount()).sum()); + return dto; + } +} diff --git a/admin/src/main/java/org/jiayunet/admin/service/AdminProductService.java b/admin/src/main/java/org/jiayunet/admin/service/AdminProductService.java new file mode 100644 index 0000000..616b87b --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/service/AdminProductService.java @@ -0,0 +1,67 @@ +package org.jiayunet.admin.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.jiayunet.admin.pojo.param.product.ProductPageParam; +import org.jiayunet.admin.pojo.param.product.ProductSaveParam; +import org.jiayunet.mapper.MemberProductMapper; +import org.jiayunet.pojo.PageResult; +import org.jiayunet.pojo.po.MemberProduct; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +/** + * 管理后台-商品管理服务 + *

依赖:MemberProductMapper

+ *

使用表:bg_member_product(CRUD)

+ * + * @author zk + */ +@Service +@Slf4j +public class AdminProductService { + + @Autowired + private MemberProductMapper memberProductMapper; + + /** 商品分页列表 */ + public PageResult pageList(ProductPageParam param) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(param.getStatus() != null, MemberProduct::getStatus, param.getStatus()) + .orderByAsc(MemberProduct::getSortOrder); + Page page = memberProductMapper.selectPage(param.toPage(), wrapper); + return PageResult.from(page); + } + + /** 新增/编辑商品 */ + public void save(ProductSaveParam param) { + MemberProduct product = new MemberProduct(); + BeanUtils.copyProperties(param, product); + if (param.getId() == null) { + if (product.getStatus() == null) product.setStatus(0); + if (product.getSortOrder() == null) product.setSortOrder(0); + if (product.getIsFeatured() == null) product.setIsFeatured(0); + memberProductMapper.insert(product); + } else { + product.setId(param.getId()); + memberProductMapper.updateById(product); + } + } + + /** 上架/下架商品 */ + public void updateStatus(Long id, Integer status) { + Assert.notNull(id, "商品ID不能为空"); + Assert.isTrue(status == 0 || status == 1, "状态值非法"); + memberProductMapper.update(null, new LambdaUpdateWrapper().eq(MemberProduct::getId, id).set(MemberProduct::getStatus, status)); + } + + /** 删除商品(逻辑删除) */ + public void delete(Long id) { + Assert.notNull(id, "商品ID不能为空"); + memberProductMapper.deleteById(id); + } +} diff --git a/admin/src/main/java/org/jiayunet/admin/service/AdminUserService.java b/admin/src/main/java/org/jiayunet/admin/service/AdminUserService.java new file mode 100644 index 0000000..bd19ed4 --- /dev/null +++ b/admin/src/main/java/org/jiayunet/admin/service/AdminUserService.java @@ -0,0 +1,102 @@ +package org.jiayunet.admin.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.jiayunet.admin.pojo.dto.user.UserListDto; +import org.jiayunet.admin.pojo.dto.user.UserStatsDto; +import org.jiayunet.admin.pojo.param.user.UserPageParam; +import org.jiayunet.mapper.UserMapper; +import org.jiayunet.mapper.UserResumeMapper; +import org.jiayunet.pojo.PageResult; +import org.jiayunet.pojo.po.User; +import org.jiayunet.pojo.po.UserResume; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.time.*; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 管理后台-用户管理服务 + *

依赖:UserMapper(用户查询/更新)、UserResumeMapper(简历数量统计)

+ *

使用表:bg_user(用户列表/统计/禁用)、bg_user_resume(简历数量)

+ * + * @author zk + */ +@Service +@Slf4j +public class AdminUserService { + + @Autowired + private UserMapper userMapper; + @Autowired + private UserResumeMapper userResumeMapper; + + /** + * 用户分页列表 + *

1. 分页查询用户 2. 批量查询每个用户的简历数量 3. 组装返回

+ */ + public PageResult pageList(UserPageParam param) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(StringUtils.hasText(param.getMobileNumber()), User::getMobileNumber, param.getMobileNumber()) + .like(StringUtils.hasText(param.getKeyword()), User::getNick, param.getKeyword()) + .eq(param.getStatus() != null, User::getStatus, param.getStatus()) + .orderByDesc(User::getCreateTime); + + Page page = userMapper.selectPage(param.toPage(), wrapper); + if (page.getRecords().isEmpty()) return PageResult.from((Page) page.convert(u -> new UserListDto())); + + // 批量查询简历数量 + List userIds = page.getRecords().stream().map(User::getId).collect(Collectors.toList()); + List resumes = userResumeMapper.selectList(new LambdaQueryWrapper().in(UserResume::getUserId, userIds).select(UserResume::getUserId)); + Map resumeCountMap = resumes.stream().collect(Collectors.groupingBy(UserResume::getUserId, Collectors.counting())); + + // 组装DTO + Page dtoPage = (Page) page.convert(user -> { + UserListDto dto = new UserListDto(); + dto.setId(user.getId()); + dto.setNick(user.getNick()); + dto.setRealName(user.getRealName()); + dto.setMobileNumber(user.getMobileNumber()); + dto.setStatus(user.getStatus()); + dto.setResumeCount(resumeCountMap.getOrDefault(user.getId(), 0L).intValue()); + dto.setCreateTime(user.getCreateTime()); + return dto; + }); + return PageResult.from(dtoPage); + } + + /** + * 用户统计:总数 / 今日 / 本周 / 本月 新增 + */ + public UserStatsDto stats() { + UserStatsDto dto = new UserStatsDto(); + ZoneId zone = ZoneId.of("Asia/Shanghai"); + LocalDate now = LocalDate.now(zone); + + Instant todayStart = now.atStartOfDay(zone).toInstant(); + Instant weekStart = now.with(DayOfWeek.MONDAY).atStartOfDay(zone).toInstant(); + Instant monthStart = now.withDayOfMonth(1).atStartOfDay(zone).toInstant(); + + dto.setTotal(userMapper.selectCount(new LambdaQueryWrapper())); + dto.setToday(userMapper.selectCount(new LambdaQueryWrapper().ge(User::getCreateTime, todayStart))); + dto.setThisWeek(userMapper.selectCount(new LambdaQueryWrapper().ge(User::getCreateTime, weekStart))); + dto.setThisMonth(userMapper.selectCount(new LambdaQueryWrapper().ge(User::getCreateTime, monthStart))); + return dto; + } + + /** + * 禁用/启用用户 + */ + public void updateStatus(Long userId, Integer status) { + Assert.notNull(userId, "用户ID不能为空"); + Assert.isTrue(status == 0 || status == 1, "状态值非法"); + userMapper.update(null, new LambdaUpdateWrapper().eq(User::getId, userId).set(User::getStatus, status)); + } +}