Files
number/后端架构设计/10-管理后台-part2-Service.md
2026-03-17 12:09:43 +08:00

11 KiB
Raw Blame History

管理后台开发文档 - Part 2AdminService 接口 + 实现)

一、AdminService 接口

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<AdminUserVO> 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<AdminSkillVO> listSkills(AdminSkillQueryDTO query);
    void                auditSkill(SkillAuditDTO dto, Long auditorId);
    void                offlineSkill(Long skillId, String reason);

    // 订单 / 退款
    IPage<AdminOrderVO> listOrders(AdminOrderQueryDTO query);
    void                processRefund(Long refundId, String action, String remark, Long operatorId);

    // 积分规则
    List<PointsRule> listPointsRules();
    void             updatePointsRule(Long ruleId, int points);
}

二、AdminServiceImpl.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<User>().ge(User::getCreatedAt, dayStart)));
        vo.setActiveUsersLast7d(
            userRepo.countActiveUsersAfter(LocalDateTime.now().minusDays(7)));

        vo.setTotalOrders(orderRepo.selectCount(null));
        vo.setOrdersToday(orderRepo.selectCount(
            new LambdaQueryWrapper<Order>().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<Skill>().eq(Skill::getStatus, "pending")));
        vo.setTotalDownloads(downloadRepo.selectCount(null));
        return vo;
    }

    // ---------- 用户 ----------

    @Override
    public IPage<AdminUserVO> listUsers(AdminUserQueryDTO q) {
        return userRepo.selectPage(new Page<>(q.getPageNum(), q.getPageSize()),
            new LambdaQueryWrapper<User>()
                .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<AdminSkillVO> listSkills(AdminSkillQueryDTO q) {
        return skillRepo.selectPage(new Page<>(q.getPageNum(), q.getPageSize()),
            new LambdaQueryWrapper<Skill>()
                .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<AdminOrderVO> listOrders(AdminOrderQueryDTO q) {
        return orderRepo.selectPage(new Page<>(q.getPageNum(), q.getPageSize()),
            new LambdaQueryWrapper<Order>()
                .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<PointsRule> 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