# 管理后台开发文档 - Part 2(AdminService 接口 + 实现) ## 一、AdminService 接口 ```java package com.openclaw.service.admin; import com.baomidou.mybatisplus.core.metadata.IPage; import com.openclaw.dto.admin.*; import com.openclaw.entity.PointsRule; import com.openclaw.vo.admin.*; import java.util.List; public interface AdminService { // 看板 DashboardVO getDashboard(); // 用户 IPage listUsers(AdminUserQueryDTO query); AdminUserVO getUserDetail(Long userId); void banUser(Long userId, String reason); void unbanUser(Long userId); void adjustPoints(Long userId, int delta, String remark); // Skill 审核 IPage listSkills(AdminSkillQueryDTO query); void auditSkill(SkillAuditDTO dto, Long auditorId); void offlineSkill(Long skillId, String reason); // 订单 / 退款 IPage listOrders(AdminOrderQueryDTO query); void processRefund(Long refundId, String action, String remark, Long operatorId); // 积分规则 List listPointsRules(); void updatePointsRule(Long ruleId, int points); } ``` ## 二、AdminServiceImpl.java ```java package com.openclaw.service.admin.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.openclaw.constant.ErrorCode; import com.openclaw.dto.admin.*; import com.openclaw.entity.*; import com.openclaw.exception.BusinessException; import com.openclaw.repository.*; import com.openclaw.service.PointsService; import com.openclaw.service.admin.AdminService; import com.openclaw.vo.admin.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @Slf4j @Service @RequiredArgsConstructor public class AdminServiceImpl implements AdminService { private final UserRepository userRepo; private final SkillRepository skillRepo; private final OrderRepository orderRepo; private final OrderRefundRepository refundRepo; private final PointsRuleRepository pointsRuleRepo; private final SkillDownloadRepository downloadRepo; private final PointsService pointsService; // ---------- 看板 ---------- @Override public DashboardVO getDashboard() { DashboardVO vo = new DashboardVO(); LocalDateTime dayStart = LocalDate.now().atStartOfDay(); vo.setTotalUsers(userRepo.selectCount(null)); vo.setTodayNewUsers(userRepo.selectCount( new LambdaQueryWrapper().ge(User::getCreatedAt, dayStart))); vo.setActiveUsersLast7d( userRepo.countActiveUsersAfter(LocalDateTime.now().minusDays(7))); vo.setTotalOrders(orderRepo.selectCount(null)); vo.setOrdersToday(orderRepo.selectCount( new LambdaQueryWrapper().ge(Order::getCreatedAt, dayStart))); BigDecimal rev = orderRepo.sumCashAmount("paid"); vo.setTotalRevenue(rev == null ? BigDecimal.ZERO : rev); BigDecimal revToday = orderRepo.sumCashAmountAfter("paid", dayStart); vo.setRevenueToday(revToday == null ? BigDecimal.ZERO : revToday); vo.setTotalSkills(skillRepo.selectCount(null)); vo.setPendingAuditSkills(skillRepo.selectCount( new LambdaQueryWrapper().eq(Skill::getStatus, "pending"))); vo.setTotalDownloads(downloadRepo.selectCount(null)); return vo; } // ---------- 用户 ---------- @Override public IPage listUsers(AdminUserQueryDTO q) { return userRepo.selectPage(new Page<>(q.getPageNum(), q.getPageSize()), new LambdaQueryWrapper() .and(q.getKeyword() != null, w -> w .like(User::getNickname, q.getKeyword()).or() .like(User::getPhone, q.getKeyword())) .eq(q.getStatus() != null, User::getStatus, q.getStatus()) .orderByDesc(User::getCreatedAt) ).convert(this::toUserVO); } @Override public AdminUserVO getUserDetail(Long userId) { User u = userRepo.selectById(userId); if (u == null) throw new BusinessException(ErrorCode.USER_NOT_FOUND); return toUserVO(u); } @Override @Transactional public void banUser(Long userId, String reason) { User u = requireUser(userId); u.setStatus("banned"); u.setBanReason(reason); userRepo.updateById(u); } @Override @Transactional public void unbanUser(Long userId) { User u = requireUser(userId); u.setStatus("active"); u.setBanReason(null); userRepo.updateById(u); } @Override @Transactional public void adjustPoints(Long userId, int delta, String remark) { String type = delta > 0 ? "admin_add" : "admin_deduct"; String desc = remark != null ? remark : (delta > 0 ? "管理员补积分" : "管理员扣积分"); pointsService.addPointsDirectly(userId, Math.abs(delta), type, null, desc); } // ---------- Skill ---------- @Override public IPage listSkills(AdminSkillQueryDTO q) { return skillRepo.selectPage(new Page<>(q.getPageNum(), q.getPageSize()), new LambdaQueryWrapper() .like(q.getKeyword() != null, Skill::getName, q.getKeyword()) .eq(q.getStatus() != null, Skill::getStatus, q.getStatus()) .eq(q.getCategoryId() != null, Skill::getCategoryId, q.getCategoryId()) .orderByDesc(Skill::getCreatedAt) ).convert(this::toSkillVO); } @Override @Transactional public void auditSkill(SkillAuditDTO dto, Long auditorId) { Skill s = skillRepo.selectById(dto.getSkillId()); if (s == null) throw new BusinessException(ErrorCode.SKILL_NOT_FOUND); if (!"pending".equals(s.getStatus())) throw new BusinessException(ErrorCode.SKILL_STATUS_ERROR); switch (dto.getAction()) { case "approve" -> s.setStatus("approved"); case "reject" -> { s.setStatus("rejected"); s.setRejectReason(dto.getRejectReason()); } default -> throw new BusinessException(ErrorCode.PARAM_ERROR); } s.setAuditorId(auditorId); s.setAuditedAt(LocalDateTime.now()); skillRepo.updateById(s); log.info("Skill审核 id={} action={} auditor={}", dto.getSkillId(), dto.getAction(), auditorId); } @Override @Transactional public void offlineSkill(Long skillId, String reason) { Skill s = skillRepo.selectById(skillId); if (s == null) throw new BusinessException(ErrorCode.SKILL_NOT_FOUND); s.setStatus("offline"); s.setRejectReason(reason); skillRepo.updateById(s); } // ---------- 订单 ---------- @Override public IPage listOrders(AdminOrderQueryDTO q) { return orderRepo.selectPage(new Page<>(q.getPageNum(), q.getPageSize()), new LambdaQueryWrapper() .like(q.getKeyword() != null, Order::getOrderNo, q.getKeyword()) .eq(q.getStatus() != null, Order::getStatus, q.getStatus()) .ge(q.getStartDate() != null, Order::getCreatedAt, q.getStartDate() != null ? q.getStartDate().atStartOfDay() : null) .le(q.getEndDate() != null, Order::getCreatedAt, q.getEndDate() != null ? q.getEndDate().plusDays(1).atStartOfDay() : null) .orderByDesc(Order::getCreatedAt) ).convert(this::toOrderVO); } @Override @Transactional public void processRefund(Long refundId, String action, String remark, Long operatorId) { OrderRefund rf = refundRepo.selectById(refundId); if (rf == null) throw new BusinessException(ErrorCode.REFUND_NOT_FOUND); if (!"pending".equals(rf.getStatus())) throw new BusinessException(ErrorCode.REFUND_STATUS_ERROR); Order o = orderRepo.selectById(rf.getOrderId()); switch (action) { case "approve" -> { rf.setStatus("approved"); o.setStatus("refunded"); if (rf.getRefundPoints() != null && rf.getRefundPoints() > 0) pointsService.addPointsDirectly( o.getUserId(), rf.getRefundPoints(), "refund", rf.getId(), "退款返还积分"); // TODO: 调用支付渠道退款 } case "reject" -> { rf.setStatus("rejected"); o.setStatus("paid"); } default -> throw new BusinessException(ErrorCode.PARAM_ERROR); } rf.setRemark(remark); rf.setOperatorId(operatorId); rf.setProcessedAt(LocalDateTime.now()); refundRepo.updateById(rf); orderRepo.updateById(o); } // ---------- 积分规则 ---------- @Override public List listPointsRules() { return pointsRuleRepo.selectList(null); } @Override @Transactional public void updatePointsRule(Long ruleId, int points) { PointsRule r = pointsRuleRepo.selectById(ruleId); if (r == null) throw new BusinessException(ErrorCode.POINTS_RULE_NOT_FOUND); r.setPoints(points); pointsRuleRepo.updateById(r); } // ---------- 私有辅助 ---------- private User requireUser(Long id) { User u = userRepo.selectById(id); if (u == null) throw new BusinessException(ErrorCode.USER_NOT_FOUND); return u; } private AdminUserVO toUserVO(User u) { AdminUserVO vo = new AdminUserVO(); vo.setId(u.getId()); vo.setPhone(u.getPhone()); vo.setNickname(u.getNickname()); vo.setAvatarUrl(u.getAvatarUrl()); vo.setStatus(u.getStatus()); vo.setCreatedAt(u.getCreatedAt()); return vo; } private AdminSkillVO toSkillVO(Skill s) { AdminSkillVO vo = new AdminSkillVO(); vo.setId(s.getId()); vo.setName(s.getName()); vo.setCoverImageUrl(s.getCoverImageUrl()); vo.setPrice(s.getPrice()); vo.setIsFree(s.getIsFree()); vo.setStatus(s.getStatus()); vo.setCreatorId(s.getCreatorId()); vo.setCreatedAt(s.getCreatedAt()); vo.setAuditedAt(s.getAuditedAt()); vo.setRejectReason(s.getRejectReason()); return vo; } private AdminOrderVO toOrderVO(Order o) { AdminOrderVO vo = new AdminOrderVO(); vo.setId(o.getId()); vo.setOrderNo(o.getOrderNo()); vo.setUserId(o.getUserId()); vo.setTotalAmount(o.getTotalAmount()); vo.setCashAmount(o.getCashAmount()); vo.setPointsUsed(o.getPointsUsed()); vo.setStatus(o.getStatus()); vo.setPaymentMethod(o.getPaymentMethod()); vo.setCreatedAt(o.getCreatedAt()); vo.setPaidAt(o.getPaidAt()); return vo; } } ``` --- **文档版本**:v1.0 | **创建日期**:2026-03-16