Initial commit
This commit is contained in:
272
后端架构设计/10-管理后台-part2-Service.md
Normal file
272
后端架构设计/10-管理后台-part2-Service.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# 管理后台开发文档 - 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<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
|
||||
|
||||
```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
|
||||
Reference in New Issue
Block a user