Initial commit

This commit is contained in:
Developer
2026-03-17 12:09:43 +08:00
commit 70bedcf241
211 changed files with 31464 additions and 0 deletions

View File

@@ -0,0 +1,272 @@
# 管理后台开发文档 - Part 2AdminService 接口 + 实现)
## 一、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