Parcourir la source

feat:service层改造为接口+实现类的编码风格;分类管理新增管理员专用的获取所有分类接口

yangyi il y a 4 mois
Parent
commit
1eef2f8fde

+ 2 - 2
src/main/java/com/campus/trade/controller/admin/CategoryController.java

@@ -24,8 +24,8 @@ public class CategoryController {
 
     @Operation(summary = "获取所有分类列表", operationId = "adminListCategories")
     @GetMapping
-    public Result<List<Category>> list() {
-        return Result.success(categoryService.list());
+    public Result<List<Category>> adminList() {
+        return Result.success(categoryService.adminList());
     }
 
     @Operation(summary = "新增分类", operationId = "createCategory")

+ 39 - 113
src/main/java/com/campus/trade/service/AuthService.java

@@ -1,121 +1,47 @@
 package com.campus.trade.service;
 
-import com.campus.trade.common.BusinessException;
-import com.campus.trade.common.FileUploadUtil;
-import com.campus.trade.common.StatusCode;
 import com.campus.trade.dto.ChangePasswordRequest;
 import com.campus.trade.dto.LoginRequest;
 import com.campus.trade.dto.RegisterRequest;
-import com.campus.trade.entity.User;
-import com.campus.trade.mapper.UserMapper;
 import com.campus.trade.vo.UserProfileVO;
 import jakarta.servlet.http.HttpSession;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.util.DigestUtils;
-
-import java.nio.charset.StandardCharsets;
-
-@Service
-public class AuthService {
-
-    @Autowired
-    private UserMapper userMapper;
-
-    @Autowired
-    private FileUploadUtil fileUploadUtil;
-
-    // 用户注册
-    public Long register(RegisterRequest req) {
-        // 检查学号是否已注册
-        User existing = userMapper.selectByStudentId(req.getStudentId());
-        if (existing != null) {
-            throw new BusinessException(StatusCode.CONFLICT, "学号已注册");
-        }
-        // 上传校园卡照片
-        String campusCardUrl = null;
-        if (req.getCampusCardImage() != null && !req.getCampusCardImage().isEmpty()) {
-            campusCardUrl = fileUploadUtil.upload(req.getCampusCardImage());
-        }
-        // MD5 加密密码
-        String passwordHash = DigestUtils.md5DigestAsHex(
-                req.getPassword().getBytes(StandardCharsets.UTF_8)
-        );
-        // 构建用户实体
-        User user = new User();
-        user.setStudentId(req.getStudentId());
-        user.setName(req.getName());
-        user.setPasswordHash(passwordHash);
-        user.setCampusCardUrl(campusCardUrl);
-        user.setAuthStatus("PENDING");     // 待审核
-        user.setStatus("NORMAL");
-        user.setRole("USER");
-        // 默认昵称为姓名
-        user.setNickname(req.getName());
-        userMapper.insert(user);
-        return user.getId();
-    }
-
-    // 用户登录
-    public UserProfileVO login(LoginRequest req, HttpSession session) {
-        User user = userMapper.selectByStudentId(req.getStudentId());
-        if (user == null) {
-            throw new BusinessException(StatusCode.BAD_REQUEST, "学号或密码错误");
-        }
-        // 检查是否被封禁
-        if ("BANNED".equals(user.getStatus())) {
-            throw new BusinessException(StatusCode.FORBIDDEN, "账号已被封禁");
-        }
-        // MD5 密码比对
-        String inputHash = DigestUtils.md5DigestAsHex(
-                req.getPassword().getBytes(StandardCharsets.UTF_8)
-        );
-        if (!inputHash.equals(user.getPasswordHash())) {
-            throw new BusinessException(StatusCode.BAD_REQUEST, "学号或密码错误");
-        }
-        // 创建 Session
-        session.setAttribute("userId", user.getId());
-        session.setAttribute("role", user.getRole());
-
-        UserProfileVO vo = new UserProfileVO();
-        BeanUtils.copyProperties(user, vo);
-        return vo;
-    }
-
-    // 提交实名认证(重新提交校园卡照片)
-    public void submitAuth(org.springframework.web.multipart.MultipartFile campusCardImage, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        User user = userMapper.selectById(userId);
-        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
-        // 已认证用户不可再次提交
-        if ("AUTHENTICATED".equals(user.getAuthStatus())) {
-            throw new BusinessException(StatusCode.BAD_REQUEST, "已通过实名认证,无需重复提交");
-        }
-        // 上传新校园卡照片
-        String campusCardUrl = fileUploadUtil.upload(campusCardImage);
-        // 更新校园卡照片并重新置为待审核
-        user.setCampusCardUrl(campusCardUrl);
-        user.setAuthStatus("PENDING");
-        userMapper.updateCampusCardAndAuthStatus(user);
-    }
-
-    // 修改密码
-    public void changePassword(ChangePasswordRequest req, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        User user = userMapper.selectById(userId);
-        // 验证旧密码
-        String oldHash = DigestUtils.md5DigestAsHex(
-                req.getOldPassword().getBytes(StandardCharsets.UTF_8)
-        );
-        if (!oldHash.equals(user.getPasswordHash())) {
-            throw new BusinessException(StatusCode.BAD_REQUEST, "旧密码错误");
-        }
-        // 更新为新密码
-        String newHash = DigestUtils.md5DigestAsHex(
-                req.getNewPassword().getBytes(StandardCharsets.UTF_8)
-        );
-        user.setPasswordHash(newHash);
-        userMapper.updatePassword(user);
-    }
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 认证服务接口
+ */
+public interface AuthService {
+
+    /**
+     * 用户注册
+     *
+     * @param req 注册请求
+     * @return 新用户ID
+     */
+    Long register(RegisterRequest req);
+
+    /**
+     * 用户登录
+     *
+     * @param req     登录请求
+     * @param session HTTP会话
+     * @return 用户资料VO
+     */
+    UserProfileVO login(LoginRequest req, HttpSession session);
+
+    /**
+     * 提交实名认证(重新提交校园卡照片)
+     *
+     * @param campusCardImage 校园卡照片
+     * @param session         HTTP会话
+     */
+    void submitAuth(MultipartFile campusCardImage, HttpSession session);
+
+    /**
+     * 修改密码
+     *
+     * @param req     修改密码请求
+     * @param session HTTP会话
+     */
+    void changePassword(ChangePasswordRequest req, HttpSession session);
 }

+ 74 - 167
src/main/java/com/campus/trade/service/ItemService.java

@@ -1,177 +1,84 @@
 package com.campus.trade.service;
 
-import com.campus.trade.common.BusinessException;
-import com.campus.trade.common.FileUploadUtil;
-import com.campus.trade.common.StatusCode;
 import com.campus.trade.dto.ItemPublishRequest;
-import com.campus.trade.entity.Category;
-import com.campus.trade.entity.Item;
-import com.campus.trade.entity.User;
-import com.campus.trade.mapper.CategoryMapper;
-import com.campus.trade.mapper.ItemMapper;
-import com.campus.trade.mapper.UserMapper;
 import com.campus.trade.vo.ItemVO;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import jakarta.servlet.http.HttpSession;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
 
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
-import java.util.stream.Collectors;
 
-@Service
-public class ItemService {
-
-    @Autowired
-    private ItemMapper itemMapper;
-
-    @Autowired
-    private UserMapper userMapper;
-
-    @Autowired
-    private CategoryMapper categoryMapper;
-
-    @Autowired
-    private FileUploadUtil fileUploadUtil;
-
-    private final ObjectMapper objectMapper = new ObjectMapper();
-
-    // 首页物品列表(分页,按时间倒序)
-    public PageInfo<ItemVO> list(int pageNum, int pageSize) {
-        PageHelper.startPage(pageNum, pageSize);
-        List<Item> items = itemMapper.selectByStatus("ON_SALE");
-        PageInfo<Item> pageInfo = new PageInfo<>(items);
-        return toVOPageInfo(pageInfo);
-    }
-
-    // 搜索物品
-    public PageInfo<ItemVO> search(String keyword, int pageNum, int pageSize) {
-        PageHelper.startPage(pageNum, pageSize);
-        List<Item> items = itemMapper.searchByKeyword(keyword);
-        PageInfo<Item> pageInfo = new PageInfo<>(items);
-        return toVOPageInfo(pageInfo);
-    }
-
-    // 按分类筛选
-    public PageInfo<ItemVO> byCategory(Long categoryId, int pageNum, int pageSize) {
-        PageHelper.startPage(pageNum, pageSize);
-        List<Item> items = itemMapper.selectByCategoryId(categoryId);
-        PageInfo<Item> pageInfo = new PageInfo<>(items);
-        return toVOPageInfo(pageInfo);
-    }
-
-    // 物品详情
-    public ItemVO detail(Long id) {
-        Item item = itemMapper.selectById(id);
-        if (item == null) {
-            throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
-        }
-        return toVO(item);
-    }
-
-    // 发布物品
-    public Long publish(ItemPublishRequest req, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        User user = userMapper.selectById(userId);
-        // 校验实名认证
-        if (!"AUTHENTICATED".equals(user.getAuthStatus())) {
-            throw new BusinessException(StatusCode.FORBIDDEN, "请先完成实名认证");
-        }
-        // 批量上传图片
-        List<String> imageUrls = fileUploadUtil.uploadBatch(req.getImages());
-        // 构建物品实体
-        Item item = new Item();
-        item.setSellerId(userId);
-        item.setCategoryId(req.getCategoryId());
-        item.setTitle(req.getTitle());
-        item.setDescription(req.getDescription());
-        item.setPrice(req.getPrice());
-        item.setTradeMethod(req.getTradeMethod());
-        item.setStatus("PENDING");  // 待审核
-        // 图片列表转为 JSON 字符串存储
-        try {
-            item.setImages(objectMapper.writeValueAsString(imageUrls));
-        } catch (JsonProcessingException e) {
-            item.setImages("[]");
-        }
-        itemMapper.insert(item);
-        return item.getId();
-    }
-
-    // 我的发布列表
-    public List<ItemVO> myItems(HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        List<Item> items = itemMapper.selectBySellerId(userId);
-        return items.stream().map(this::toVO).collect(Collectors.toList());
-    }
-
-    // 下架物品
-    public void offline(Long id, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        Item item = itemMapper.selectById(id);
-        if (item == null) {
-            throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
-        }
-        if (!item.getSellerId().equals(userId)) {
-            throw new BusinessException(StatusCode.FORBIDDEN, "无权操作该物品");
-        }
-        item.setStatus("OFF_SHELF");
-        itemMapper.updateStatus(item);
-    }
-
-    // 删除物品
-    public void delete(Long id, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        Item item = itemMapper.selectById(id);
-        if (item == null) {
-            throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
-        }
-        if (!item.getSellerId().equals(userId)) {
-            throw new BusinessException(StatusCode.FORBIDDEN, "无权操作该物品");
-        }
-        itemMapper.deleteById(id);
-    }
-
-    // --- 私有辅助方法 ---
-
-    private ItemVO toVO(Item item) {
-        ItemVO vo = new ItemVO();
-        BeanUtils.copyProperties(item, vo);
-        // 卖家信息
-        User seller = userMapper.selectById(item.getSellerId());
-        if (seller != null) {
-            vo.setSellerNickname(seller.getNickname());
-            vo.setSellerAvatar(seller.getAvatarUrl());
-        }
-        // 分类名
-        if (item.getCategoryId() != null) {
-            Category category = categoryMapper.selectById(item.getCategoryId());
-            if (category != null) {
-                vo.setCategoryName(category.getName());
-            }
-        }
-        // 图片 JSON 转 List
-        try {
-            String[] urls = objectMapper.readValue(item.getImages(), String[].class);
-            vo.setImages(Arrays.asList(urls));
-        } catch (Exception e) {
-            vo.setImages(Collections.emptyList());
-        }
-        return vo;
-    }
-
-    private PageInfo<ItemVO> toVOPageInfo(PageInfo<Item> pageInfo) {
-        List<ItemVO> voList = pageInfo.getList().stream()
-                .map(this::toVO).collect(Collectors.toList());
-        PageInfo<ItemVO> voPageInfo = new PageInfo<>();
-        BeanUtils.copyProperties(pageInfo, voPageInfo, "list");
-        voPageInfo.setList(voList);
-        return voPageInfo;
-    }
+/**
+ * 物品服务接口
+ */
+public interface ItemService {
+
+    /**
+     * 首页物品列表(分页,按时间倒序)
+     *
+     * @param pageNum  页码
+     * @param pageSize 每页大小
+     * @return 分页物品列表
+     */
+    PageInfo<ItemVO> list(int pageNum, int pageSize);
+
+    /**
+     * 搜索物品
+     *
+     * @param keyword  关键词
+     * @param pageNum  页码
+     * @param pageSize 每页大小
+     * @return 分页搜索结果
+     */
+    PageInfo<ItemVO> search(String keyword, int pageNum, int pageSize);
+
+    /**
+     * 按分类筛选物品
+     *
+     * @param categoryId 分类ID
+     * @param pageNum    页码
+     * @param pageSize   每页大小
+     * @return 分页物品列表
+     */
+    PageInfo<ItemVO> byCategory(Long categoryId, int pageNum, int pageSize);
+
+    /**
+     * 获取物品详情
+     *
+     * @param id 物品ID
+     * @return 物品详情VO
+     */
+    ItemVO detail(Long id);
+
+    /**
+     * 发布物品
+     *
+     * @param req     发布请求
+     * @param session HTTP会话
+     * @return 新发布物品的ID
+     */
+    Long publish(ItemPublishRequest req, HttpSession session);
+
+    /**
+     * 获取我的发布列表
+     *
+     * @param session HTTP会话
+     * @return 我的物品列表
+     */
+    List<ItemVO> myItems(HttpSession session);
+
+    /**
+     * 下架物品
+     *
+     * @param id      物品ID
+     * @param session HTTP会话
+     */
+    void offline(Long id, HttpSession session);
+
+    /**
+     * 删除物品
+     *
+     * @param id      物品ID
+     * @param session HTTP会话
+     */
+    void delete(Long id, HttpSession session);
 }

+ 32 - 69
src/main/java/com/campus/trade/service/MessageService.java

@@ -1,80 +1,43 @@
 package com.campus.trade.service;
 
-import com.campus.trade.entity.Message;
-import com.campus.trade.entity.User;
-import com.campus.trade.mapper.MessageMapper;
-import com.campus.trade.mapper.UserMapper;
 import com.campus.trade.dto.SendMessageRequest;
 import com.campus.trade.vo.ConversationVO;
 import com.campus.trade.vo.MessageVO;
-import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import jakarta.servlet.http.HttpSession;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
 
 import java.util.List;
-import java.util.stream.Collectors;
 
-@Service
-public class MessageService {
-
-    @Autowired
-    private MessageMapper messageMapper;
-
-    @Autowired
-    private UserMapper userMapper;
-
-    // 发送消息
-    public Long send(SendMessageRequest req, HttpSession session) {
-        Long fromUserId = (Long) session.getAttribute("userId");
-        Message message = new Message();
-        message.setFromUserId(fromUserId);
-        message.setToUserId(req.getToUserId());
-        message.setItemId(req.getItemId());
-        message.setContent(req.getContent());
-        message.setImageUrl(req.getImageUrl());
-        message.setIsRead(false);
-        messageMapper.insert(message);
-        return message.getId();
-    }
-
-    // 会话列表
-    public List<ConversationVO> conversations(HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        return messageMapper.selectConversations(userId);
-    }
-
-    // 聊天记录(分页)
-    public PageInfo<MessageVO> history(Long targetUserId, int pageNum, int pageSize, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        PageHelper.startPage(pageNum, pageSize);
-        List<Message> messages = messageMapper.selectHistory(userId, targetUserId);
-        PageInfo<Message> pageInfo = new PageInfo<>(messages);
-        // 标记为已读
-        messageMapper.markAsRead(userId, targetUserId);
-        // 转 VO
-        List<MessageVO> voList = messages.stream().map(this::toVO).collect(Collectors.toList());
-        PageInfo<MessageVO> voPageInfo = new PageInfo<>();
-        BeanUtils.copyProperties(pageInfo, voPageInfo, "list");
-        voPageInfo.setList(voList);
-        return voPageInfo;
-    }
-
-    private MessageVO toVO(Message message) {
-        MessageVO vo = new MessageVO();
-        BeanUtils.copyProperties(message, vo);
-        User fromUser = userMapper.selectById(message.getFromUserId());
-        if (fromUser != null) {
-            vo.setFromUserNickname(fromUser.getNickname());
-            vo.setFromUserAvatar(fromUser.getAvatarUrl());
-        }
-        User toUser = userMapper.selectById(message.getToUserId());
-        if (toUser != null) {
-            vo.setToUserNickname(toUser.getNickname());
-            vo.setToUserAvatar(toUser.getAvatarUrl());
-        }
-        return vo;
-    }
+/**
+ * 消息服务接口
+ */
+public interface MessageService {
+
+    /**
+     * 发送消息
+     *
+     * @param req     发送消息请求
+     * @param session HTTP会话
+     * @return 消息ID
+     */
+    Long send(SendMessageRequest req, HttpSession session);
+
+    /**
+     * 获取会话列表
+     *
+     * @param session HTTP会话
+     * @return 会话列表
+     */
+    List<ConversationVO> conversations(HttpSession session);
+
+    /**
+     * 获取聊天记录(分页)
+     *
+     * @param targetUserId 对方用户ID
+     * @param pageNum      页码
+     * @param pageSize     每页大小
+     * @param session      HTTP会话
+     * @return 分页消息记录
+     */
+    PageInfo<MessageVO> history(Long targetUserId, int pageNum, int pageSize, HttpSession session);
 }

+ 65 - 163
src/main/java/com/campus/trade/service/OrderService.java

@@ -1,170 +1,72 @@
 package com.campus.trade.service;
 
-import com.campus.trade.common.BusinessException;
-import com.campus.trade.common.OrderStatusMachine;
-import com.campus.trade.common.StatusCode;
-import com.campus.trade.entity.Item;
-import com.campus.trade.entity.Order;
-import com.campus.trade.entity.User;
-import com.campus.trade.mapper.ItemMapper;
-import com.campus.trade.mapper.OrderMapper;
-import com.campus.trade.mapper.UserMapper;
 import com.campus.trade.vo.OrderVO;
-import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import jakarta.servlet.http.HttpSession;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
-import java.util.List;
-import java.util.stream.Collectors;
-
-@Service
-public class OrderService {
-
-    @Autowired
-    private OrderMapper orderMapper;
-
-    @Autowired
-    private ItemMapper itemMapper;
-
-    @Autowired
-    private UserMapper userMapper;
-
-    @Autowired
-    private OrderStatusMachine statusMachine;
-
-    // 生成订单
-    @Transactional
-    public Long create(Long itemId, HttpSession session) {
-        Long buyerId = (Long) session.getAttribute("userId");
-        Item item = itemMapper.selectById(itemId);
-        if (item == null) {
-            throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
-        }
-        if (!"ON_SALE".equals(item.getStatus())) {
-            throw new BusinessException(StatusCode.BAD_REQUEST, "物品已下架或已售出");
-        }
-        // 不能买自己的物品
-        if (item.getSellerId().equals(buyerId)) {
-            throw new BusinessException(StatusCode.BAD_REQUEST, "不能购买自己的物品");
-        }
-        // 创建订单
-        Order order = new Order();
-        order.setItemId(itemId);
-        order.setBuyerId(buyerId);
-        order.setSellerId(item.getSellerId());
-        order.setTradeMethod(item.getTradeMethod());
-        order.setStatus("PENDING");
-        orderMapper.insert(order);
-        // 物品标记为已售出
-        item.setStatus("SOLD");
-        itemMapper.updateStatus(item);
-        return order.getId();
-    }
-
-    // 我买到的
-    public PageInfo<OrderVO> bought(int pageNum, int pageSize, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        PageHelper.startPage(pageNum, pageSize);
-        List<Order> orders = orderMapper.selectByBuyerId(userId);
-        return toVOPageInfo(new PageInfo<>(orders));
-    }
-
-    // 我卖出的
-    public PageInfo<OrderVO> sold(int pageNum, int pageSize, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        PageHelper.startPage(pageNum, pageSize);
-        List<Order> orders = orderMapper.selectBySellerId(userId);
-        return toVOPageInfo(new PageInfo<>(orders));
-    }
-
-    // 卖家接单
-    @Transactional
-    public void accept(Long orderId, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        Order order = orderMapper.selectById(orderId);
-        if (order == null) throw new BusinessException(StatusCode.NOT_FOUND, "订单不存在");
-        if (!order.getSellerId().equals(userId)) throw new BusinessException(StatusCode.FORBIDDEN, "无权操作");
-        statusMachine.validateTransition(order.getStatus(), "CONFIRMED");
-        order.setStatus("CONFIRMED");
-        orderMapper.updateStatus(order);
-    }
-
-    // 卖家拒单
-    @Transactional
-    public void reject(Long orderId, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        Order order = orderMapper.selectById(orderId);
-        if (order == null) throw new BusinessException(StatusCode.NOT_FOUND, "订单不存在");
-        if (!order.getSellerId().equals(userId)) throw new BusinessException(StatusCode.FORBIDDEN, "无权操作");
-        statusMachine.validateTransition(order.getStatus(), "CANCELLED");
-        order.setStatus("CANCELLED");
-        orderMapper.updateStatus(order);
-        // 物品恢复在售
-        Item item = itemMapper.selectById(order.getItemId());
-        if (item != null) {
-            item.setStatus("ON_SALE");
-            itemMapper.updateStatus(item);
-        }
-    }
-
-    // 卖家确认交货
-    @Transactional
-    public void deliver(Long orderId, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        Order order = orderMapper.selectById(orderId);
-        if (order == null) throw new BusinessException(StatusCode.NOT_FOUND, "订单不存在");
-        if (!order.getSellerId().equals(userId)) throw new BusinessException(StatusCode.FORBIDDEN, "无权操作");
-        statusMachine.validateTransition(order.getStatus(), "DELIVERED");
-        order.setStatus("DELIVERED");
-        orderMapper.updateStatus(order);
-    }
-
-    // 买家确认收货
-    @Transactional
-    public void confirm(Long orderId, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        Order order = orderMapper.selectById(orderId);
-        if (order == null) throw new BusinessException(StatusCode.NOT_FOUND, "订单不存在");
-        if (!order.getBuyerId().equals(userId)) throw new BusinessException(StatusCode.FORBIDDEN, "无权操作");
-        statusMachine.validateTransition(order.getStatus(), "COMPLETED");
-        order.setStatus("COMPLETED");
-        orderMapper.updateStatus(order);
-    }
-
-    // --- 私有辅助 ---
-
-    private OrderVO toVO(Order order) {
-        OrderVO vo = new OrderVO();
-        BeanUtils.copyProperties(order, vo);
-        Item item = itemMapper.selectById(order.getItemId());
-        if (item != null) {
-            vo.setItemTitle(item.getTitle());
-            vo.setItemPrice(item.getPrice());
-            // 取第一张图作为缩略图
-            if (item.getImages() != null && !item.getImages().isEmpty()) {
-                try {
-                    String[] urls = new com.fasterxml.jackson.databind.ObjectMapper()
-                            .readValue(item.getImages(), String[].class);
-                    if (urls.length > 0) vo.setItemImage(urls[0]);
-                } catch (Exception ignored) {}
-            }
-        }
-        User buyer = userMapper.selectById(order.getBuyerId());
-        if (buyer != null) vo.setBuyerNickname(buyer.getNickname());
-        User seller = userMapper.selectById(order.getSellerId());
-        if (seller != null) vo.setSellerNickname(seller.getNickname());
-        return vo;
-    }
-
-    private PageInfo<OrderVO> toVOPageInfo(PageInfo<Order> pageInfo) {
-        List<OrderVO> voList = pageInfo.getList().stream().map(this::toVO).collect(Collectors.toList());
-        PageInfo<OrderVO> voPageInfo = new PageInfo<>();
-        BeanUtils.copyProperties(pageInfo, voPageInfo, "list");
-        voPageInfo.setList(voList);
-        return voPageInfo;
-    }
+/**
+ * 订单服务接口
+ */
+public interface OrderService {
+
+    /**
+     * 生成订单
+     *
+     * @param itemId  物品ID
+     * @param session HTTP会话
+     * @return 新订单ID
+     */
+    Long create(Long itemId, HttpSession session);
+
+    /**
+     * 获取我买到的订单列表(分页)
+     *
+     * @param pageNum  页码
+     * @param pageSize 每页大小
+     * @param session  HTTP会话
+     * @return 分页订单列表
+     */
+    PageInfo<OrderVO> bought(int pageNum, int pageSize, HttpSession session);
+
+    /**
+     * 获取我卖出的订单列表(分页)
+     *
+     * @param pageNum  页码
+     * @param pageSize 每页大小
+     * @param session  HTTP会话
+     * @return 分页订单列表
+     */
+    PageInfo<OrderVO> sold(int pageNum, int pageSize, HttpSession session);
+
+    /**
+     * 卖家接单
+     *
+     * @param orderId 订单ID
+     * @param session HTTP会话
+     */
+    void accept(Long orderId, HttpSession session);
+
+    /**
+     * 卖家拒单
+     *
+     * @param orderId 订单ID
+     * @param session HTTP会话
+     */
+    void reject(Long orderId, HttpSession session);
+
+    /**
+     * 卖家确认交货
+     *
+     * @param orderId 订单ID
+     * @param session HTTP会话
+     */
+    void deliver(Long orderId, HttpSession session);
+
+    /**
+     * 买家确认收货
+     *
+     * @param orderId 订单ID
+     * @param session HTTP会话
+     */
+    void confirm(Long orderId, HttpSession session);
 }

+ 19 - 53
src/main/java/com/campus/trade/service/UserService.java

@@ -1,62 +1,28 @@
 package com.campus.trade.service;
 
-import com.campus.trade.common.BusinessException;
-import com.campus.trade.common.FileUploadUtil;
-import com.campus.trade.common.StatusCode;
 import com.campus.trade.dto.UserProfileRequest;
-import com.campus.trade.entity.User;
-import com.campus.trade.mapper.UserMapper;
 import com.campus.trade.vo.UserProfileVO;
 import jakarta.servlet.http.HttpSession;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
 
-@Service
-public class UserService {
+/**
+ * 用户服务接口
+ */
+public interface UserService {
 
-    @Autowired
-    private UserMapper userMapper;
+    /**
+     * 获取个人资料
+     *
+     * @param session HTTP会话
+     * @return 用户资料VO
+     */
+    UserProfileVO getProfile(HttpSession session);
 
-    @Autowired
-    private FileUploadUtil fileUploadUtil;
-
-    // 获取个人资料
-    public UserProfileVO getProfile(HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        User user = userMapper.selectById(userId);
-        if (user == null) {
-            throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
-        }
-        UserProfileVO vo = new UserProfileVO();
-        BeanUtils.copyProperties(user, vo);
-        return vo;
-    }
-
-    // 修改个人资料
-    public UserProfileVO updateProfile(UserProfileRequest req, HttpSession session) {
-        Long userId = (Long) session.getAttribute("userId");
-        User user = userMapper.selectById(userId);
-        if (user == null) {
-            throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
-        }
-        // 更新昵称
-        if (req.getNickname() != null && !req.getNickname().isEmpty()) {
-            user.setNickname(req.getNickname());
-        }
-        // 更新签名
-        if (req.getSignature() != null) {
-            user.setSignature(req.getSignature());
-        }
-        // 更新头像
-        if (req.getAvatar() != null && !req.getAvatar().isEmpty()) {
-            String avatarUrl = fileUploadUtil.upload(req.getAvatar());
-            user.setAvatarUrl(avatarUrl);
-        }
-        userMapper.updateProfile(user);
-
-        UserProfileVO vo = new UserProfileVO();
-        BeanUtils.copyProperties(user, vo);
-        return vo;
-    }
+    /**
+     * 修改个人资料
+     *
+     * @param req     修改请求
+     * @param session HTTP会话
+     * @return 更新后的用户资料VO
+     */
+    UserProfileVO updateProfile(UserProfileRequest req, HttpSession session);
 }

+ 42 - 47
src/main/java/com/campus/trade/service/admin/CategoryService.java

@@ -1,55 +1,50 @@
 package com.campus.trade.service.admin;
 
-import com.campus.trade.common.BusinessException;
-import com.campus.trade.common.StatusCode;
 import com.campus.trade.dto.CategoryRequest;
 import com.campus.trade.entity.Category;
-import com.campus.trade.mapper.CategoryMapper;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
 
 import java.util.List;
 
-@Service
-public class CategoryService {
-
-    @Autowired
-    private CategoryMapper categoryMapper;
-
-    // 分类列表
-    public List<Category> adminList() {
-        return categoryMapper.selectAll();
-    }
-
-    // 分类列表(只返回启用的分类)
-    public List<Category> list() {
-        return categoryMapper.selectEnabled();
-    }
-
-    // 新增分类
-    public Long create(CategoryRequest req) {
-        Category category = new Category();
-        category.setName(req.getName());
-        category.setSortOrder(req.getSortOrder() != null ? req.getSortOrder() : 0);
-        category.setStatus("ENABLED");
-        categoryMapper.insert(category);
-        return category.getId();
-    }
-
-    // 修改分类
-    public void update(Long id, CategoryRequest req) {
-        Category category = categoryMapper.selectById(id);
-        if (category == null) throw new BusinessException(StatusCode.NOT_FOUND, "分类不存在");
-        category.setName(req.getName());
-        if (req.getSortOrder() != null) category.setSortOrder(req.getSortOrder());
-        categoryMapper.update(category);
-    }
-
-    // 启用/禁用分类
-    public void toggleStatus(Long id, String status) {
-        Category category = categoryMapper.selectById(id);
-        if (category == null) throw new BusinessException(StatusCode.NOT_FOUND, "分类不存在");
-        category.setStatus(status);
-        categoryMapper.updateStatus(category);
-    }
+/**
+ * 分类服务接口(管理员)
+ */
+public interface CategoryService {
+
+    /**
+     * 获取所有分类列表(管理员)
+     *
+     * @return 分类列表
+     */
+    List<Category> adminList();
+
+    /**
+     * 获取启用的分类列表
+     *
+     * @return 启用的分类列表
+     */
+    List<Category> list();
+
+    /**
+     * 新增分类
+     *
+     * @param req 分类请求
+     * @return 新分类ID
+     */
+    Long create(CategoryRequest req);
+
+    /**
+     * 修改分类
+     *
+     * @param id  分类ID
+     * @param req 分类请求
+     */
+    void update(Long id, CategoryRequest req);
+
+    /**
+     * 启用/禁用分类
+     *
+     * @param id     分类ID
+     * @param status 状态
+     */
+    void toggleStatus(Long id, String status);
 }

+ 40 - 138
src/main/java/com/campus/trade/service/admin/ReviewService.java

@@ -1,147 +1,49 @@
 package com.campus.trade.service.admin;
 
-import com.campus.trade.common.BusinessException;
-import com.campus.trade.common.StatusCode;
 import com.campus.trade.dto.ReviewRequest;
-import com.campus.trade.entity.Category;
-import com.campus.trade.entity.Item;
-import com.campus.trade.entity.Review;
 import com.campus.trade.entity.User;
-import com.campus.trade.mapper.CategoryMapper;
-import com.campus.trade.mapper.ItemMapper;
-import com.campus.trade.mapper.ReviewMapper;
-import com.campus.trade.mapper.UserMapper;
 import com.campus.trade.vo.ItemVO;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import jakarta.servlet.http.HttpSession;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
-@Service
-public class ReviewService {
-
-    @Autowired
-    private UserMapper userMapper;
-
-    @Autowired
-    private ItemMapper itemMapper;
-
-    @Autowired
-    private ReviewMapper reviewMapper;
-
-    @Autowired
-    private CategoryMapper categoryMapper;
-
-    private final ObjectMapper objectMapper = new ObjectMapper();
-
-    // 待审核用户列表
-    public PageInfo<User> pendingUsers(int pageNum, int pageSize) {
-        PageHelper.startPage(pageNum, pageSize);
-        return new PageInfo<>(userMapper.selectByAuthStatus("PENDING"));
-    }
-
-    // 审核用户实名认证
-    @Transactional
-    public void reviewUser(Long userId, ReviewRequest req, HttpSession session) {
-        Long adminId = (Long) session.getAttribute("userId");
-        User user = userMapper.selectById(userId);
-        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
-        if (!"PENDING".equals(user.getAuthStatus())) {
-            throw new BusinessException(StatusCode.BAD_REQUEST, "该用户不在待审核状态");
-        }
-        // 更新用户认证状态
-        if ("APPROVED".equals(req.getResult())) {
-            user.setAuthStatus("AUTHENTICATED");
-        } else {
-            user.setAuthStatus("UNAUTHENTICATED");
-        }
-        userMapper.updateAuthStatus(user);
-        // 记录审核记录
-        Review review = new Review();
-        review.setAdminId(adminId);
-        review.setReviewType("AUTH");
-        review.setTargetId(userId);
-        review.setResult(req.getResult());
-        review.setReason(req.getReason());
-        reviewMapper.insert(review);
-    }
-
-    // 待审核物品列表
-    public PageInfo<ItemVO> pendingItems(int pageNum, int pageSize) {
-        PageHelper.startPage(pageNum, pageSize);
-        PageInfo<Item> pageInfo = new PageInfo<>(itemMapper.selectByStatus("PENDING"));
-        return toVOPageInfo(pageInfo);
-    }
-
-    // 审核物品上架
-    @Transactional
-    public void reviewItem(Long itemId, ReviewRequest req, HttpSession session) {
-        Long adminId = (Long) session.getAttribute("userId");
-        Item item = itemMapper.selectById(itemId);
-        if (item == null) throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
-        if (!"PENDING".equals(item.getStatus())) {
-            throw new BusinessException(StatusCode.BAD_REQUEST, "该物品不在待审核状态");
-        }
-        // 更新物品状态
-        if ("APPROVED".equals(req.getResult())) {
-            item.setStatus("ON_SALE");
-        } else {
-            item.setStatus("OFF_SHELF");
-        }
-        itemMapper.updateStatus(item);
-        // 记录审核记录
-        Review review = new Review();
-        review.setAdminId(adminId);
-        review.setReviewType("ITEM");
-        review.setTargetId(itemId);
-        review.setResult(req.getResult());
-        review.setReason(req.getReason());
-        reviewMapper.insert(review);
-    }
-
-    // --- 私有辅助 ---
-
-    private ItemVO toVO(Item item) {
-        ItemVO vo = new ItemVO();
-        BeanUtils.copyProperties(item, vo);
-        // 卖家信息
-        User seller = userMapper.selectById(item.getSellerId());
-        if (seller != null) {
-            vo.setSellerNickname(seller.getNickname());
-            vo.setSellerAvatar(seller.getAvatarUrl());
-        }
-        // 分类名
-        if (item.getCategoryId() != null) {
-            Category category = categoryMapper.selectById(item.getCategoryId());
-            if (category != null) {
-                vo.setCategoryName(category.getName());
-            }
-        }
-        // 图片 JSON 转 List
-        try {
-            String[] urls = objectMapper.readValue(item.getImages(), String[].class);
-            vo.setImages(Arrays.asList(urls));
-        } catch (Exception e) {
-            vo.setImages(Collections.emptyList());
-        }
-        return vo;
-    }
-
-    private PageInfo<ItemVO> toVOPageInfo(PageInfo<Item> pageInfo) {
-        List<ItemVO> voList = pageInfo.getList().stream()
-                .map(this::toVO).collect(Collectors.toList());
-        PageInfo<ItemVO> voPageInfo = new PageInfo<>();
-        BeanUtils.copyProperties(pageInfo, voPageInfo, "list");
-        voPageInfo.setList(voList);
-        return voPageInfo;
-    }
+/**
+ * 审核服务接口(管理员)
+ */
+public interface ReviewService {
+
+    /**
+     * 获取待审核用户列表
+     *
+     * @param pageNum  页码
+     * @param pageSize 每页大小
+     * @return 分页用户列表
+     */
+    PageInfo<User> pendingUsers(int pageNum, int pageSize);
+
+    /**
+     * 审核用户实名认证
+     *
+     * @param userId  用户ID
+     * @param req     审核请求
+     * @param session HTTP会话
+     */
+    void reviewUser(Long userId, ReviewRequest req, HttpSession session);
+
+    /**
+     * 获取待审核物品列表
+     *
+     * @param pageNum  页码
+     * @param pageSize 每页大小
+     * @return 分页物品列表
+     */
+    PageInfo<ItemVO> pendingItems(int pageNum, int pageSize);
+
+    /**
+     * 审核物品上架
+     *
+     * @param itemId  物品ID
+     * @param req     审核请求
+     * @param session HTTP会话
+     */
+    void reviewItem(Long itemId, ReviewRequest req, HttpSession session);
 }

+ 38 - 57
src/main/java/com/campus/trade/service/admin/UserManageService.java

@@ -1,64 +1,45 @@
 package com.campus.trade.service.admin;
 
-import com.campus.trade.common.BusinessException;
-import com.campus.trade.common.StatusCode;
 import com.campus.trade.entity.User;
-import com.campus.trade.mapper.UserMapper;
-import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import jakarta.servlet.http.HttpSession;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.util.DigestUtils;
 
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
-@Service
-public class UserManageService {
-
-    @Autowired
-    private UserMapper userMapper;
-
-    // 用户列表(支持按用户名模糊搜索)
-    public PageInfo<User> list(int pageNum, int pageSize, String username) {
-        PageHelper.startPage(pageNum, pageSize);
-        List<User> users;
-        if (username != null && !username.isEmpty()) {
-            users = userMapper.selectByName(username);
-        } else {
-            users = userMapper.selectAll();
-        }
-        return new PageInfo<>(users);
-    }
-
-    // 封禁用户
-    public void ban(Long userId, HttpSession session) {
-        User user = userMapper.selectById(userId);
-        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
-        if ("ADMIN".equals(user.getRole())) {
-            throw new BusinessException(StatusCode.FORBIDDEN, "不能封禁管理员");
-        }
-        user.setStatus("BANNED");
-        userMapper.updateStatus(user);
-    }
-
-    // 解封用户
-    public void unban(Long userId, HttpSession session) {
-        User user = userMapper.selectById(userId);
-        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
-        user.setStatus("NORMAL");
-        userMapper.updateStatus(user);
-    }
-
-    // 重置密码(重置为 123456)
-    public void resetPassword(Long userId, HttpSession session) {
-        User user = userMapper.selectById(userId);
-        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
-        String defaultHash = DigestUtils.md5DigestAsHex(
-                "123456".getBytes(StandardCharsets.UTF_8)
-        );
-        user.setPasswordHash(defaultHash);
-        userMapper.updatePassword(user);
-    }
+/**
+ * 用户管理服务接口(管理员)
+ */
+public interface UserManageService {
+
+    /**
+     * 获取用户列表(支持按用户名模糊搜索)
+     *
+     * @param pageNum  页码
+     * @param pageSize 每页大小
+     * @param username 用户名(可选,模糊搜索)
+     * @return 分页用户列表
+     */
+    PageInfo<User> list(int pageNum, int pageSize, String username);
+
+    /**
+     * 封禁用户
+     *
+     * @param userId  用户ID
+     * @param session HTTP会话
+     */
+    void ban(Long userId, HttpSession session);
+
+    /**
+     * 解封用户
+     *
+     * @param userId  用户ID
+     * @param session HTTP会话
+     */
+    void unban(Long userId, HttpSession session);
+
+    /**
+     * 重置密码(重置为 123456)
+     *
+     * @param userId  用户ID
+     * @param session HTTP会话
+     */
+    void resetPassword(Long userId, HttpSession session);
 }

+ 56 - 0
src/main/java/com/campus/trade/service/admin/impl/CategoryServiceImpl.java

@@ -0,0 +1,56 @@
+package com.campus.trade.service.admin.impl;
+
+import com.campus.trade.common.BusinessException;
+import com.campus.trade.common.StatusCode;
+import com.campus.trade.dto.CategoryRequest;
+import com.campus.trade.entity.Category;
+import com.campus.trade.mapper.CategoryMapper;
+import com.campus.trade.service.admin.CategoryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class CategoryServiceImpl implements CategoryService {
+
+    @Autowired
+    private CategoryMapper categoryMapper;
+
+    @Override
+    public List<Category> adminList() {
+        return categoryMapper.selectAll();
+    }
+
+    @Override
+    public List<Category> list() {
+        return categoryMapper.selectEnabled();
+    }
+
+    @Override
+    public Long create(CategoryRequest req) {
+        Category category = new Category();
+        category.setName(req.getName());
+        category.setSortOrder(req.getSortOrder() != null ? req.getSortOrder() : 0);
+        category.setStatus("ENABLED");
+        categoryMapper.insert(category);
+        return category.getId();
+    }
+
+    @Override
+    public void update(Long id, CategoryRequest req) {
+        Category category = categoryMapper.selectById(id);
+        if (category == null) throw new BusinessException(StatusCode.NOT_FOUND, "分类不存在");
+        category.setName(req.getName());
+        if (req.getSortOrder() != null) category.setSortOrder(req.getSortOrder());
+        categoryMapper.update(category);
+    }
+
+    @Override
+    public void toggleStatus(Long id, String status) {
+        Category category = categoryMapper.selectById(id);
+        if (category == null) throw new BusinessException(StatusCode.NOT_FOUND, "分类不存在");
+        category.setStatus(status);
+        categoryMapper.updateStatus(category);
+    }
+}

+ 148 - 0
src/main/java/com/campus/trade/service/admin/impl/ReviewServiceImpl.java

@@ -0,0 +1,148 @@
+package com.campus.trade.service.admin.impl;
+
+import com.campus.trade.common.BusinessException;
+import com.campus.trade.common.StatusCode;
+import com.campus.trade.dto.ReviewRequest;
+import com.campus.trade.entity.Category;
+import com.campus.trade.entity.Item;
+import com.campus.trade.entity.Review;
+import com.campus.trade.entity.User;
+import com.campus.trade.mapper.CategoryMapper;
+import com.campus.trade.mapper.ItemMapper;
+import com.campus.trade.mapper.ReviewMapper;
+import com.campus.trade.mapper.UserMapper;
+import com.campus.trade.service.admin.ReviewService;
+import com.campus.trade.vo.ItemVO;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import jakarta.servlet.http.HttpSession;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class ReviewServiceImpl implements ReviewService {
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private ItemMapper itemMapper;
+
+    @Autowired
+    private ReviewMapper reviewMapper;
+
+    @Autowired
+    private CategoryMapper categoryMapper;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    public PageInfo<User> pendingUsers(int pageNum, int pageSize) {
+        PageHelper.startPage(pageNum, pageSize);
+        return new PageInfo<>(userMapper.selectByAuthStatus("PENDING"));
+    }
+
+    @Override
+    @Transactional
+    public void reviewUser(Long userId, ReviewRequest req, HttpSession session) {
+        Long adminId = (Long) session.getAttribute("userId");
+        User user = userMapper.selectById(userId);
+        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
+        if (!"PENDING".equals(user.getAuthStatus())) {
+            throw new BusinessException(StatusCode.BAD_REQUEST, "该用户不在待审核状态");
+        }
+        // 更新用户认证状态
+        if ("APPROVED".equals(req.getResult())) {
+            user.setAuthStatus("AUTHENTICATED");
+        } else {
+            user.setAuthStatus("UNAUTHENTICATED");
+        }
+        userMapper.updateAuthStatus(user);
+        // 记录审核记录
+        Review review = new Review();
+        review.setAdminId(adminId);
+        review.setReviewType("AUTH");
+        review.setTargetId(userId);
+        review.setResult(req.getResult());
+        review.setReason(req.getReason());
+        reviewMapper.insert(review);
+    }
+
+    @Override
+    public PageInfo<ItemVO> pendingItems(int pageNum, int pageSize) {
+        PageHelper.startPage(pageNum, pageSize);
+        PageInfo<Item> pageInfo = new PageInfo<>(itemMapper.selectByStatus("PENDING"));
+        return toVOPageInfo(pageInfo);
+    }
+
+    @Override
+    @Transactional
+    public void reviewItem(Long itemId, ReviewRequest req, HttpSession session) {
+        Long adminId = (Long) session.getAttribute("userId");
+        Item item = itemMapper.selectById(itemId);
+        if (item == null) throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
+        if (!"PENDING".equals(item.getStatus())) {
+            throw new BusinessException(StatusCode.BAD_REQUEST, "该物品不在待审核状态");
+        }
+        // 更新物品状态
+        if ("APPROVED".equals(req.getResult())) {
+            item.setStatus("ON_SALE");
+        } else {
+            item.setStatus("OFF_SHELF");
+        }
+        itemMapper.updateStatus(item);
+        // 记录审核记录
+        Review review = new Review();
+        review.setAdminId(adminId);
+        review.setReviewType("ITEM");
+        review.setTargetId(itemId);
+        review.setResult(req.getResult());
+        review.setReason(req.getReason());
+        reviewMapper.insert(review);
+    }
+
+    // --- 私有辅助 ---
+
+    private ItemVO toVO(Item item) {
+        ItemVO vo = new ItemVO();
+        BeanUtils.copyProperties(item, vo);
+        // 卖家信息
+        User seller = userMapper.selectById(item.getSellerId());
+        if (seller != null) {
+            vo.setSellerNickname(seller.getNickname());
+            vo.setSellerAvatar(seller.getAvatarUrl());
+        }
+        // 分类名
+        if (item.getCategoryId() != null) {
+            Category category = categoryMapper.selectById(item.getCategoryId());
+            if (category != null) {
+                vo.setCategoryName(category.getName());
+            }
+        }
+        // 图片 JSON 转 List
+        try {
+            String[] urls = objectMapper.readValue(item.getImages(), String[].class);
+            vo.setImages(Arrays.asList(urls));
+        } catch (Exception e) {
+            vo.setImages(Collections.emptyList());
+        }
+        return vo;
+    }
+
+    private PageInfo<ItemVO> toVOPageInfo(PageInfo<Item> pageInfo) {
+        List<ItemVO> voList = pageInfo.getList().stream()
+                .map(this::toVO).collect(Collectors.toList());
+        PageInfo<ItemVO> voPageInfo = new PageInfo<>();
+        BeanUtils.copyProperties(pageInfo, voPageInfo, "list");
+        voPageInfo.setList(voList);
+        return voPageInfo;
+    }
+}

+ 65 - 0
src/main/java/com/campus/trade/service/admin/impl/UserManageServiceImpl.java

@@ -0,0 +1,65 @@
+package com.campus.trade.service.admin.impl;
+
+import com.campus.trade.common.BusinessException;
+import com.campus.trade.common.StatusCode;
+import com.campus.trade.entity.User;
+import com.campus.trade.mapper.UserMapper;
+import com.campus.trade.service.admin.UserManageService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import jakarta.servlet.http.HttpSession;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+@Service
+public class UserManageServiceImpl implements UserManageService {
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Override
+    public PageInfo<User> list(int pageNum, int pageSize, String username) {
+        PageHelper.startPage(pageNum, pageSize);
+        List<User> users;
+        if (username != null && !username.isEmpty()) {
+            users = userMapper.selectByName(username);
+        } else {
+            users = userMapper.selectAll();
+        }
+        return new PageInfo<>(users);
+    }
+
+    @Override
+    public void ban(Long userId, HttpSession session) {
+        User user = userMapper.selectById(userId);
+        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
+        if ("ADMIN".equals(user.getRole())) {
+            throw new BusinessException(StatusCode.FORBIDDEN, "不能封禁管理员");
+        }
+        user.setStatus("BANNED");
+        userMapper.updateStatus(user);
+    }
+
+    @Override
+    public void unban(Long userId, HttpSession session) {
+        User user = userMapper.selectById(userId);
+        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
+        user.setStatus("NORMAL");
+        userMapper.updateStatus(user);
+    }
+
+    @Override
+    public void resetPassword(Long userId, HttpSession session) {
+        User user = userMapper.selectById(userId);
+        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
+        String defaultHash = DigestUtils.md5DigestAsHex(
+                "123456".getBytes(StandardCharsets.UTF_8)
+        );
+        user.setPasswordHash(defaultHash);
+        userMapper.updatePassword(user);
+    }
+}

+ 123 - 0
src/main/java/com/campus/trade/service/impl/AuthServiceImpl.java

@@ -0,0 +1,123 @@
+package com.campus.trade.service.impl;
+
+import com.campus.trade.common.BusinessException;
+import com.campus.trade.common.FileUploadUtil;
+import com.campus.trade.common.StatusCode;
+import com.campus.trade.dto.ChangePasswordRequest;
+import com.campus.trade.dto.LoginRequest;
+import com.campus.trade.dto.RegisterRequest;
+import com.campus.trade.entity.User;
+import com.campus.trade.mapper.UserMapper;
+import com.campus.trade.service.AuthService;
+import com.campus.trade.vo.UserProfileVO;
+import jakarta.servlet.http.HttpSession;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.nio.charset.StandardCharsets;
+
+@Service
+public class AuthServiceImpl implements AuthService {
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private FileUploadUtil fileUploadUtil;
+
+    @Override
+    public Long register(RegisterRequest req) {
+        // 检查学号是否已注册
+        User existing = userMapper.selectByStudentId(req.getStudentId());
+        if (existing != null) {
+            throw new BusinessException(StatusCode.CONFLICT, "学号已注册");
+        }
+        // 上传校园卡照片
+        String campusCardUrl = null;
+        if (req.getCampusCardImage() != null && !req.getCampusCardImage().isEmpty()) {
+            campusCardUrl = fileUploadUtil.upload(req.getCampusCardImage());
+        }
+        // MD5 加密密码
+        String passwordHash = DigestUtils.md5DigestAsHex(
+                req.getPassword().getBytes(StandardCharsets.UTF_8)
+        );
+        // 构建用户实体
+        User user = new User();
+        user.setStudentId(req.getStudentId());
+        user.setName(req.getName());
+        user.setPasswordHash(passwordHash);
+        user.setCampusCardUrl(campusCardUrl);
+        user.setAuthStatus("PENDING");     // 待审核
+        user.setStatus("NORMAL");
+        user.setRole("USER");
+        // 默认昵称为姓名
+        user.setNickname(req.getName());
+        userMapper.insert(user);
+        return user.getId();
+    }
+
+    @Override
+    public UserProfileVO login(LoginRequest req, HttpSession session) {
+        User user = userMapper.selectByStudentId(req.getStudentId());
+        if (user == null) {
+            throw new BusinessException(StatusCode.BAD_REQUEST, "学号或密码错误");
+        }
+        // 检查是否被封禁
+        if ("BANNED".equals(user.getStatus())) {
+            throw new BusinessException(StatusCode.FORBIDDEN, "账号已被封禁");
+        }
+        // MD5 密码比对
+        String inputHash = DigestUtils.md5DigestAsHex(
+                req.getPassword().getBytes(StandardCharsets.UTF_8)
+        );
+        if (!inputHash.equals(user.getPasswordHash())) {
+            throw new BusinessException(StatusCode.BAD_REQUEST, "学号或密码错误");
+        }
+        // 创建 Session
+        session.setAttribute("userId", user.getId());
+        session.setAttribute("role", user.getRole());
+
+        UserProfileVO vo = new UserProfileVO();
+        BeanUtils.copyProperties(user, vo);
+        return vo;
+    }
+
+    @Override
+    public void submitAuth(MultipartFile campusCardImage, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        User user = userMapper.selectById(userId);
+        if (user == null) throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
+        // 已认证用户不可再次提交
+        if ("AUTHENTICATED".equals(user.getAuthStatus())) {
+            throw new BusinessException(StatusCode.BAD_REQUEST, "已通过实名认证,无需重复提交");
+        }
+        // 上传新校园卡照片
+        String campusCardUrl = fileUploadUtil.upload(campusCardImage);
+        // 更新校园卡照片并重新置为待审核
+        user.setCampusCardUrl(campusCardUrl);
+        user.setAuthStatus("PENDING");
+        userMapper.updateCampusCardAndAuthStatus(user);
+    }
+
+    @Override
+    public void changePassword(ChangePasswordRequest req, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        User user = userMapper.selectById(userId);
+        // 验证旧密码
+        String oldHash = DigestUtils.md5DigestAsHex(
+                req.getOldPassword().getBytes(StandardCharsets.UTF_8)
+        );
+        if (!oldHash.equals(user.getPasswordHash())) {
+            throw new BusinessException(StatusCode.BAD_REQUEST, "旧密码错误");
+        }
+        // 更新为新密码
+        String newHash = DigestUtils.md5DigestAsHex(
+                req.getNewPassword().getBytes(StandardCharsets.UTF_8)
+        );
+        user.setPasswordHash(newHash);
+        userMapper.updatePassword(user);
+    }
+}

+ 178 - 0
src/main/java/com/campus/trade/service/impl/ItemServiceImpl.java

@@ -0,0 +1,178 @@
+package com.campus.trade.service.impl;
+
+import com.campus.trade.common.BusinessException;
+import com.campus.trade.common.FileUploadUtil;
+import com.campus.trade.common.StatusCode;
+import com.campus.trade.dto.ItemPublishRequest;
+import com.campus.trade.entity.Category;
+import com.campus.trade.entity.Item;
+import com.campus.trade.entity.User;
+import com.campus.trade.mapper.CategoryMapper;
+import com.campus.trade.mapper.ItemMapper;
+import com.campus.trade.mapper.UserMapper;
+import com.campus.trade.service.ItemService;
+import com.campus.trade.vo.ItemVO;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import jakarta.servlet.http.HttpSession;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class ItemServiceImpl implements ItemService {
+
+    @Autowired
+    private ItemMapper itemMapper;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private CategoryMapper categoryMapper;
+
+    @Autowired
+    private FileUploadUtil fileUploadUtil;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    public PageInfo<ItemVO> list(int pageNum, int pageSize) {
+        PageHelper.startPage(pageNum, pageSize);
+        List<Item> items = itemMapper.selectByStatus("ON_SALE");
+        PageInfo<Item> pageInfo = new PageInfo<>(items);
+        return toVOPageInfo(pageInfo);
+    }
+
+    @Override
+    public PageInfo<ItemVO> search(String keyword, int pageNum, int pageSize) {
+        PageHelper.startPage(pageNum, pageSize);
+        List<Item> items = itemMapper.searchByKeyword(keyword);
+        PageInfo<Item> pageInfo = new PageInfo<>(items);
+        return toVOPageInfo(pageInfo);
+    }
+
+    @Override
+    public PageInfo<ItemVO> byCategory(Long categoryId, int pageNum, int pageSize) {
+        PageHelper.startPage(pageNum, pageSize);
+        List<Item> items = itemMapper.selectByCategoryId(categoryId);
+        PageInfo<Item> pageInfo = new PageInfo<>(items);
+        return toVOPageInfo(pageInfo);
+    }
+
+    @Override
+    public ItemVO detail(Long id) {
+        Item item = itemMapper.selectById(id);
+        if (item == null) {
+            throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
+        }
+        return toVO(item);
+    }
+
+    @Override
+    public Long publish(ItemPublishRequest req, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        User user = userMapper.selectById(userId);
+        // 校验实名认证
+        if (!"AUTHENTICATED".equals(user.getAuthStatus())) {
+            throw new BusinessException(StatusCode.FORBIDDEN, "请先完成实名认证");
+        }
+        // 批量上传图片
+        List<String> imageUrls = fileUploadUtil.uploadBatch(req.getImages());
+        // 构建物品实体
+        Item item = new Item();
+        item.setSellerId(userId);
+        item.setCategoryId(req.getCategoryId());
+        item.setTitle(req.getTitle());
+        item.setDescription(req.getDescription());
+        item.setPrice(req.getPrice());
+        item.setTradeMethod(req.getTradeMethod());
+        item.setStatus("PENDING");  // 待审核
+        // 图片列表转为 JSON 字符串存储
+        try {
+            item.setImages(objectMapper.writeValueAsString(imageUrls));
+        } catch (JsonProcessingException e) {
+            item.setImages("[]");
+        }
+        itemMapper.insert(item);
+        return item.getId();
+    }
+
+    @Override
+    public List<ItemVO> myItems(HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        List<Item> items = itemMapper.selectBySellerId(userId);
+        return items.stream().map(this::toVO).collect(Collectors.toList());
+    }
+
+    @Override
+    public void offline(Long id, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        Item item = itemMapper.selectById(id);
+        if (item == null) {
+            throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
+        }
+        if (!item.getSellerId().equals(userId)) {
+            throw new BusinessException(StatusCode.FORBIDDEN, "无权操作该物品");
+        }
+        item.setStatus("OFF_SHELF");
+        itemMapper.updateStatus(item);
+    }
+
+    @Override
+    public void delete(Long id, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        Item item = itemMapper.selectById(id);
+        if (item == null) {
+            throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
+        }
+        if (!item.getSellerId().equals(userId)) {
+            throw new BusinessException(StatusCode.FORBIDDEN, "无权操作该物品");
+        }
+        itemMapper.deleteById(id);
+    }
+
+    // --- 私有辅助方法 ---
+
+    private ItemVO toVO(Item item) {
+        ItemVO vo = new ItemVO();
+        BeanUtils.copyProperties(item, vo);
+        // 卖家信息
+        User seller = userMapper.selectById(item.getSellerId());
+        if (seller != null) {
+            vo.setSellerNickname(seller.getNickname());
+            vo.setSellerAvatar(seller.getAvatarUrl());
+        }
+        // 分类名
+        if (item.getCategoryId() != null) {
+            Category category = categoryMapper.selectById(item.getCategoryId());
+            if (category != null) {
+                vo.setCategoryName(category.getName());
+            }
+        }
+        // 图片 JSON 转 List
+        try {
+            String[] urls = objectMapper.readValue(item.getImages(), String[].class);
+            vo.setImages(Arrays.asList(urls));
+        } catch (Exception e) {
+            vo.setImages(Collections.emptyList());
+        }
+        return vo;
+    }
+
+    private PageInfo<ItemVO> toVOPageInfo(PageInfo<Item> pageInfo) {
+        List<ItemVO> voList = pageInfo.getList().stream()
+                .map(this::toVO).collect(Collectors.toList());
+        PageInfo<ItemVO> voPageInfo = new PageInfo<>();
+        BeanUtils.copyProperties(pageInfo, voPageInfo, "list");
+        voPageInfo.setList(voList);
+        return voPageInfo;
+    }
+}

+ 81 - 0
src/main/java/com/campus/trade/service/impl/MessageServiceImpl.java

@@ -0,0 +1,81 @@
+package com.campus.trade.service.impl;
+
+import com.campus.trade.dto.SendMessageRequest;
+import com.campus.trade.entity.Message;
+import com.campus.trade.entity.User;
+import com.campus.trade.mapper.MessageMapper;
+import com.campus.trade.mapper.UserMapper;
+import com.campus.trade.service.MessageService;
+import com.campus.trade.vo.ConversationVO;
+import com.campus.trade.vo.MessageVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import jakarta.servlet.http.HttpSession;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class MessageServiceImpl implements MessageService {
+
+    @Autowired
+    private MessageMapper messageMapper;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Override
+    public Long send(SendMessageRequest req, HttpSession session) {
+        Long fromUserId = (Long) session.getAttribute("userId");
+        Message message = new Message();
+        message.setFromUserId(fromUserId);
+        message.setToUserId(req.getToUserId());
+        message.setItemId(req.getItemId());
+        message.setContent(req.getContent());
+        message.setImageUrl(req.getImageUrl());
+        message.setIsRead(false);
+        messageMapper.insert(message);
+        return message.getId();
+    }
+
+    @Override
+    public List<ConversationVO> conversations(HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        return messageMapper.selectConversations(userId);
+    }
+
+    @Override
+    public PageInfo<MessageVO> history(Long targetUserId, int pageNum, int pageSize, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        PageHelper.startPage(pageNum, pageSize);
+        List<Message> messages = messageMapper.selectHistory(userId, targetUserId);
+        PageInfo<Message> pageInfo = new PageInfo<>(messages);
+        // 标记为已读
+        messageMapper.markAsRead(userId, targetUserId);
+        // 转 VO
+        List<MessageVO> voList = messages.stream().map(this::toVO).collect(Collectors.toList());
+        PageInfo<MessageVO> voPageInfo = new PageInfo<>();
+        BeanUtils.copyProperties(pageInfo, voPageInfo, "list");
+        voPageInfo.setList(voList);
+        return voPageInfo;
+    }
+
+    private MessageVO toVO(Message message) {
+        MessageVO vo = new MessageVO();
+        BeanUtils.copyProperties(message, vo);
+        User fromUser = userMapper.selectById(message.getFromUserId());
+        if (fromUser != null) {
+            vo.setFromUserNickname(fromUser.getNickname());
+            vo.setFromUserAvatar(fromUser.getAvatarUrl());
+        }
+        User toUser = userMapper.selectById(message.getToUserId());
+        if (toUser != null) {
+            vo.setToUserNickname(toUser.getNickname());
+            vo.setToUserAvatar(toUser.getAvatarUrl());
+        }
+        return vo;
+    }
+}

+ 171 - 0
src/main/java/com/campus/trade/service/impl/OrderServiceImpl.java

@@ -0,0 +1,171 @@
+package com.campus.trade.service.impl;
+
+import com.campus.trade.common.BusinessException;
+import com.campus.trade.common.OrderStatusMachine;
+import com.campus.trade.common.StatusCode;
+import com.campus.trade.entity.Item;
+import com.campus.trade.entity.Order;
+import com.campus.trade.entity.User;
+import com.campus.trade.mapper.ItemMapper;
+import com.campus.trade.mapper.OrderMapper;
+import com.campus.trade.mapper.UserMapper;
+import com.campus.trade.service.OrderService;
+import com.campus.trade.vo.OrderVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import jakarta.servlet.http.HttpSession;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class OrderServiceImpl implements OrderService {
+
+    @Autowired
+    private OrderMapper orderMapper;
+
+    @Autowired
+    private ItemMapper itemMapper;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private OrderStatusMachine statusMachine;
+
+    @Override
+    @Transactional
+    public Long create(Long itemId, HttpSession session) {
+        Long buyerId = (Long) session.getAttribute("userId");
+        Item item = itemMapper.selectById(itemId);
+        if (item == null) {
+            throw new BusinessException(StatusCode.NOT_FOUND, "物品不存在");
+        }
+        if (!"ON_SALE".equals(item.getStatus())) {
+            throw new BusinessException(StatusCode.BAD_REQUEST, "物品已下架或已售出");
+        }
+        // 不能买自己的物品
+        if (item.getSellerId().equals(buyerId)) {
+            throw new BusinessException(StatusCode.BAD_REQUEST, "不能购买自己的物品");
+        }
+        // 创建订单
+        Order order = new Order();
+        order.setItemId(itemId);
+        order.setBuyerId(buyerId);
+        order.setSellerId(item.getSellerId());
+        order.setTradeMethod(item.getTradeMethod());
+        order.setStatus("PENDING");
+        orderMapper.insert(order);
+        // 物品标记为已售出
+        item.setStatus("SOLD");
+        itemMapper.updateStatus(item);
+        return order.getId();
+    }
+
+    @Override
+    public PageInfo<OrderVO> bought(int pageNum, int pageSize, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        PageHelper.startPage(pageNum, pageSize);
+        List<Order> orders = orderMapper.selectByBuyerId(userId);
+        return toVOPageInfo(new PageInfo<>(orders));
+    }
+
+    @Override
+    public PageInfo<OrderVO> sold(int pageNum, int pageSize, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        PageHelper.startPage(pageNum, pageSize);
+        List<Order> orders = orderMapper.selectBySellerId(userId);
+        return toVOPageInfo(new PageInfo<>(orders));
+    }
+
+    @Override
+    @Transactional
+    public void accept(Long orderId, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        Order order = orderMapper.selectById(orderId);
+        if (order == null) throw new BusinessException(StatusCode.NOT_FOUND, "订单不存在");
+        if (!order.getSellerId().equals(userId)) throw new BusinessException(StatusCode.FORBIDDEN, "无权操作");
+        statusMachine.validateTransition(order.getStatus(), "CONFIRMED");
+        order.setStatus("CONFIRMED");
+        orderMapper.updateStatus(order);
+    }
+
+    @Override
+    @Transactional
+    public void reject(Long orderId, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        Order order = orderMapper.selectById(orderId);
+        if (order == null) throw new BusinessException(StatusCode.NOT_FOUND, "订单不存在");
+        if (!order.getSellerId().equals(userId)) throw new BusinessException(StatusCode.FORBIDDEN, "无权操作");
+        statusMachine.validateTransition(order.getStatus(), "CANCELLED");
+        order.setStatus("CANCELLED");
+        orderMapper.updateStatus(order);
+        // 物品恢复在售
+        Item item = itemMapper.selectById(order.getItemId());
+        if (item != null) {
+            item.setStatus("ON_SALE");
+            itemMapper.updateStatus(item);
+        }
+    }
+
+    @Override
+    @Transactional
+    public void deliver(Long orderId, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        Order order = orderMapper.selectById(orderId);
+        if (order == null) throw new BusinessException(StatusCode.NOT_FOUND, "订单不存在");
+        if (!order.getSellerId().equals(userId)) throw new BusinessException(StatusCode.FORBIDDEN, "无权操作");
+        statusMachine.validateTransition(order.getStatus(), "DELIVERED");
+        order.setStatus("DELIVERED");
+        orderMapper.updateStatus(order);
+    }
+
+    @Override
+    @Transactional
+    public void confirm(Long orderId, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        Order order = orderMapper.selectById(orderId);
+        if (order == null) throw new BusinessException(StatusCode.NOT_FOUND, "订单不存在");
+        if (!order.getBuyerId().equals(userId)) throw new BusinessException(StatusCode.FORBIDDEN, "无权操作");
+        statusMachine.validateTransition(order.getStatus(), "COMPLETED");
+        order.setStatus("COMPLETED");
+        orderMapper.updateStatus(order);
+    }
+
+    // --- 私有辅助 ---
+
+    private OrderVO toVO(Order order) {
+        OrderVO vo = new OrderVO();
+        BeanUtils.copyProperties(order, vo);
+        Item item = itemMapper.selectById(order.getItemId());
+        if (item != null) {
+            vo.setItemTitle(item.getTitle());
+            vo.setItemPrice(item.getPrice());
+            // 取第一张图作为缩略图
+            if (item.getImages() != null && !item.getImages().isEmpty()) {
+                try {
+                    String[] urls = new com.fasterxml.jackson.databind.ObjectMapper()
+                            .readValue(item.getImages(), String[].class);
+                    if (urls.length > 0) vo.setItemImage(urls[0]);
+                } catch (Exception ignored) {}
+            }
+        }
+        User buyer = userMapper.selectById(order.getBuyerId());
+        if (buyer != null) vo.setBuyerNickname(buyer.getNickname());
+        User seller = userMapper.selectById(order.getSellerId());
+        if (seller != null) vo.setSellerNickname(seller.getNickname());
+        return vo;
+    }
+
+    private PageInfo<OrderVO> toVOPageInfo(PageInfo<Order> pageInfo) {
+        List<OrderVO> voList = pageInfo.getList().stream().map(this::toVO).collect(Collectors.toList());
+        PageInfo<OrderVO> voPageInfo = new PageInfo<>();
+        BeanUtils.copyProperties(pageInfo, voPageInfo, "list");
+        voPageInfo.setList(voList);
+        return voPageInfo;
+    }
+}

+ 63 - 0
src/main/java/com/campus/trade/service/impl/UserServiceImpl.java

@@ -0,0 +1,63 @@
+package com.campus.trade.service.impl;
+
+import com.campus.trade.common.BusinessException;
+import com.campus.trade.common.FileUploadUtil;
+import com.campus.trade.common.StatusCode;
+import com.campus.trade.dto.UserProfileRequest;
+import com.campus.trade.entity.User;
+import com.campus.trade.mapper.UserMapper;
+import com.campus.trade.service.UserService;
+import com.campus.trade.vo.UserProfileVO;
+import jakarta.servlet.http.HttpSession;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UserServiceImpl implements UserService {
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private FileUploadUtil fileUploadUtil;
+
+    @Override
+    public UserProfileVO getProfile(HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
+        }
+        UserProfileVO vo = new UserProfileVO();
+        BeanUtils.copyProperties(user, vo);
+        return vo;
+    }
+
+    @Override
+    public UserProfileVO updateProfile(UserProfileRequest req, HttpSession session) {
+        Long userId = (Long) session.getAttribute("userId");
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            throw new BusinessException(StatusCode.NOT_FOUND, "用户不存在");
+        }
+        // 更新昵称
+        if (req.getNickname() != null && !req.getNickname().isEmpty()) {
+            user.setNickname(req.getNickname());
+        }
+        // 更新签名
+        if (req.getSignature() != null) {
+            user.setSignature(req.getSignature());
+        }
+        // 更新头像
+        if (req.getAvatar() != null && !req.getAvatar().isEmpty()) {
+            String avatarUrl = fileUploadUtil.upload(req.getAvatar());
+            user.setAvatarUrl(avatarUrl);
+        }
+        userMapper.updateProfile(user);
+
+        UserProfileVO vo = new UserProfileVO();
+        BeanUtils.copyProperties(user, vo);
+        return vo;
+    }
+}