全栈(Java + Vue + MySQL)开发商城系统教程(三)

简介: 教程来源 https://tmywi.cn 本节详解mall-swarm微服务商城系统核心API实现:涵盖用户注册/登录、商品分页查询与详情、购物车增删改查、订单创建与状态管理、订单号生成器及管理员后台接口,代码规范、事务安全、权限控制完备,具备高可用与可扩展性。

第三部分:核心API实现详解

3.1 用户模块API
3.1.1 用户注册接口

package com.mall.controller;

import com.mall.common.Result;
import com.mall.dto.UserRegisterDTO;
import com.mall.entity.User;
import com.mall.mapper.UserMapper;
import com.mall.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

/**
 * 用户控制器
 * 处理用户注册、登录、个人信息管理等功能
 */
@Slf4j
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;
    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;

    /**
     * 用户注册接口
     * 
     * 业务流程:
     * 1. 验证用户名是否已存在
     * 2. 验证手机号是否已存在
     * 3. 加密密码(BCrypt)
     * 4. 保存用户到数据库
     * 5. 返回注册成功信息
     * 
     * @param registerDTO 注册信息(用户名、密码、手机号等)
     * @return 注册结果
     */
    @PostMapping("/register")
    public Result<Void> register(@Valid @RequestBody UserRegisterDTO registerDTO) {
        log.info("用户注册请求: username={}, phone={}", registerDTO.getUsername(), registerDTO.getPhone());

        // 1. 检查用户名是否已存在
        com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User> wrapper = 
            new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, registerDTO.getUsername());
        if (userMapper.selectCount(wrapper) > 0) {
            return Result.error(400, "用户名已存在");
        }

        // 2. 检查手机号是否已存在
        wrapper = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
        wrapper.eq(User::getPhone, registerDTO.getPhone());
        if (userMapper.selectCount(wrapper) > 0) {
            return Result.error(400, "手机号已被注册");
        }

        // 3. 创建新用户
        User user = new User();
        user.setUsername(registerDTO.getUsername());
        // BCrypt加密密码(Spring Security自动处理)
        user.setPassword(passwordEncoder.encode(registerDTO.getPassword()));
        user.setPhone(registerDTO.getPhone());
        user.setEmail(registerDTO.getEmail());
        user.setNickname(registerDTO.getNickname() != null ? registerDTO.getNickname() : registerDTO.getUsername());
        user.setRole("user");
        user.setStatus(1);

        // 4. 保存用户
        userMapper.insert(user);

        log.info("用户注册成功: userId={}, username={}", user.getId(), user.getUsername());
        return Result.success();
    }

    /**
     * 用户登录接口
     * 
     * 业务流程:
     * 1. 验证用户名和密码
     * 2. 检查用户状态是否被禁用
     * 3. 生成JWT令牌
     * 4. 更新最后登录时间和IP
     * 5. 返回用户信息和令牌
     * 
     * @param loginDTO 登录信息(用户名、密码)
     * @return 登录结果(包含用户信息和JWT令牌)
     */
    @PostMapping("/login")
    public Result<LoginResponseDTO> login(@Valid @RequestBody UserLoginDTO loginDTO, 
                                           HttpServletRequest request) {
        log.info("用户登录请求: username={}", loginDTO.getUsername());

        // 1. 根据用户名查询用户
        com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User> wrapper = 
            new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, loginDTO.getUsername());
        User user = userMapper.selectOne(wrapper);

        if (user == null) {
            return Result.error(401, "用户名或密码错误");
        }

        // 2. 验证密码
        if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
            return Result.error(401, "用户名或密码错误");
        }

        // 3. 检查用户状态
        if (user.getStatus() != 1) {
            return Result.error(401, "账号已被禁用,请联系管理员");
        }

        // 4. 更新最后登录时间和IP
        user.setLastLoginTime(LocalDateTime.now());
        user.setLastLoginIp(getClientIp(request));
        userMapper.updateById(user);

        // 5. 生成JWT令牌
        String token = jwtUtils.generateToken(user.getId(), user.getUsername(), user.getRole());

        // 6. 构建响应
        LoginResponseDTO response = new LoginResponseDTO();
        response.setUserId(user.getId());
        response.setUsername(user.getUsername());
        response.setNickname(user.getNickname());
        response.setRole(user.getRole());
        response.setToken(token);
        response.setAvatar(user.getAvatar());

        log.info("用户登录成功: userId={}, username={}", user.getId(), user.getUsername());
        return Result.success(response);
    }

    /**
     * 获取当前用户信息
     * 需要在请求头中携带JWT令牌:Authorization: Bearer <token>
     */
    @GetMapping("/info")
    public Result<UserInfoDTO> getUserInfo() {
        // 从SecurityContext中获取当前用户信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username = authentication.getName();

        // 查询用户信息
        com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User> wrapper = 
            new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        User user = userMapper.selectOne(wrapper);

        if (user == null) {
            return Result.error(404, "用户不存在");
        }

        // 构建响应(不返回密码字段)
        UserInfoDTO userInfo = new UserInfoDTO();
        userInfo.setId(user.getId());
        userInfo.setUsername(user.getUsername());
        userInfo.setNickname(user.getNickname());
        userInfo.setRealName(user.getRealName());
        userInfo.setPhone(user.getPhone());
        userInfo.setEmail(user.getEmail());
        userInfo.setAvatar(user.getAvatar());
        userInfo.setGender(user.getGender());
        userInfo.setRole(user.getRole());

        return Result.success(userInfo);
    }

    /**
     * 获取客户端IP地址
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

3.2 商品模块API

package com.mall.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mall.common.PageResult;
import com.mall.common.Result;
import com.mall.entity.Category;
import com.mall.entity.Product;
import com.mall.mapper.CategoryMapper;
import com.mall.mapper.ProductMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;

/**
 * 商品控制器
 * 处理商品浏览、搜索、分类等公开接口
 * 管理端需要管理员权限
 */
@Slf4j
@RestController
@RequestMapping("/product")
@RequiredArgsConstructor
public class ProductController {

    private final ProductMapper productMapper;
    private final CategoryMapper categoryMapper;

    /**
     * 商品列表接口(分页)
     * 
     * 请求参数说明:
     * - page: 页码,从1开始,默认1
     * - size: 每页大小,默认10
     * - keyword: 搜索关键词,在商品名称、副标题中模糊匹配
     * - categoryId: 分类ID,筛选指定分类下的商品
     * - orderBy: 排序字段(price/sales/createTime)
     * - orderDir: 排序方向(asc/desc)
     * 
     * @return 分页的商品列表
     */
    @GetMapping("/list")
    public Result<PageResult<Product>> list(
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) Long categoryId,
            @RequestParam(required = false) String orderBy,
            @RequestParam(defaultValue = "desc") String orderDir) {

        log.info("商品列表查询: page={}, size={}, keyword={}, categoryId={}", 
                 page, size, keyword, categoryId);

        // 创建分页对象
        Page<Product> pageParam = new Page<>(page, size);

        // 构建查询条件
        LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();

        // 只查询上架的商品
        wrapper.eq(Product::getStatus, 1);

        // 关键词搜索
        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w
                .like(Product::getName, keyword)
                .or()
                .like(Product::getSubtitle, keyword)
            );
        }

        // 分类筛选(支持子分类)
        if (categoryId != null && categoryId > 0) {
            wrapper.eq(Product::getCategoryId, categoryId);
        }

        // 排序
        if (StringUtils.hasText(orderBy)) {
            switch (orderBy) {
                case "price":
                    wrapper.orderBy(true, "asc".equals(orderDir), Product::getPrice);
                    break;
                case "sales":
                    wrapper.orderBy(true, "desc".equals(orderDir), Product::getSales);
                    break;
                default:
                    wrapper.orderBy(true, "desc".equals(orderDir), Product::getCreateTime);
            }
        } else {
            wrapper.orderByDesc(Product::getCreateTime);
        }

        // 执行查询
        Page<Product> result = productMapper.selectPage(pageParam, wrapper);

        return Result.success(new PageResult<>(result.getRecords(), result.getTotal(), page, size));
    }

    /**
     * 商品详情接口
     * 
     * 业务流程:
     * 1. 根据商品ID查询商品信息
     * 2. 查询商品所属分类信息
     * 3. 增加商品浏览次数(可选)
     * 
     * @param id 商品ID
     * @return 商品详情
     */
    @GetMapping("/detail/{id}")
    public Result<Product> detail(@PathVariable Long id) {
        log.info("商品详情查询: id={}", id);

        Product product = productMapper.selectById(id);
        if (product == null) {
            return Result.error(404, "商品不存在");
        }

        // 检查商品状态
        if (product.getStatus() != 1) {
            return Result.error(404, "商品已下架");
        }

        return Result.success(product);
    }

    /**
     * 获取商品分类列表
     * 
     * 返回树形结构的分类数据,支持多级分类
     */
    @GetMapping("/categories")
    public Result<List<Category>> getCategories() {
        // 查询所有启用的分类
        LambdaQueryWrapper<Category> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Category::getStatus, 1);
        wrapper.orderByAsc(Category::getSortOrder);
        List<Category> categories = categoryMapper.selectList(wrapper);

        // 构建树形结构
        List<Category> tree = buildCategoryTree(categories, 0L);

        return Result.success(tree);
    }

    /**
     * 构建分类树
     * 将平铺的分类数据转换为树形结构,便于前端渲染多级菜单
     */
    private List<Category> buildCategoryTree(List<Category> all, Long parentId) {
        List<Category> children = new ArrayList<>();
        for (Category category : all) {
            if (category.getParentId().equals(parentId)) {
                category.setChildren(buildCategoryTree(all, category.getId()));
                children.add(category);
            }
        }
        return children;
    }

    /**
     * 管理员添加商品
     * 需要管理员权限
     */
    @PreAuthorize("hasRole('admin')")
    @PostMapping
    public Result<Void> addProduct(@Valid @RequestBody Product product) {
        log.info("管理员添加商品: name={}, price={}", product.getName(), product.getPrice());

        // 校验分类是否存在
        Category category = categoryMapper.selectById(product.getCategoryId());
        if (category == null) {
            return Result.error(400, "分类不存在");
        }

        product.setStatus(1);
        product.setSales(0);
        productMapper.insert(product);

        return Result.success();
    }

    /**
     * 管理员更新商品信息
     */
    @PreAuthorize("hasRole('admin')")
    @PutMapping
    public Result<Void> updateProduct(@RequestBody Product product) {
        log.info("管理员更新商品: id={}", product.getId());

        Product existing = productMapper.selectById(product.getId());
        if (existing == null) {
            return Result.error(404, "商品不存在");
        }

        productMapper.updateById(product);
        return Result.success();
    }

    /**
     * 管理员上下架商品
     */
    @PreAuthorize("hasRole('admin')")
    @PutMapping("/status")
    public Result<Void> updateStatus(@RequestParam Long id, @RequestParam Integer status) {
        log.info("管理员更新商品状态: id={}, status={}", id, status);

        Product product = new Product();
        product.setId(id);
        product.setStatus(status);
        productMapper.updateById(product);

        return Result.success();
    }
}

3.3 购物车模块API

package com.mall.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mall.common.Result;
import com.mall.dto.CartItemDTO;
import com.mall.entity.Cart;
import com.mall.entity.Product;
import com.mall.entity.User;
import com.mall.mapper.CartMapper;
import com.mall.mapper.ProductMapper;
import com.mall.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 购物车控制器
 * 
 * 购物车逻辑说明:
 * 1. 购物车以用户为单位存储,每个用户一个购物车
 * 2. 同一商品在购物车中只有一条记录,通过quantity字段记录数量
 * 3. checked字段表示该商品是否被选中(用于结算)
 */
@Slf4j
@RestController
@RequestMapping("/cart")
@RequiredArgsConstructor
public class CartController {

    private final CartMapper cartMapper;
    private final ProductMapper productMapper;
    private final UserMapper userMapper;

    /**
     * 获取当前用户ID
     * 从SecurityContext中获取认证信息
     */
    private Long getCurrentUserId() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        // 从JWT中获取userId
        // 这里简化处理,实际应从Token中解析
        String username = auth.getName();
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        return userMapper.selectOne(wrapper).getId();
    }

    /**
     * 获取购物车列表
     * 
     * 业务流程:
     * 1. 查询当前用户的所有购物车记录
     * 2. 关联查询商品信息(名称、价格、图片)
     * 3. 计算每件商品的小计金额
     * 4. 统计购物车总金额和选中商品数量
     * 
     * @return 购物车列表及统计信息
     */
    @GetMapping("/list")
    public Result<CartListVO> list() {
        Long userId = getCurrentUserId();
        log.info("获取购物车列表: userId={}", userId);

        // 1. 查询购物车记录
        LambdaQueryWrapper<Cart> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Cart::getUserId, userId);
        wrapper.orderByDesc(Cart::getUpdateTime);
        List<Cart> carts = cartMapper.selectList(wrapper);

        if (carts.isEmpty()) {
            CartListVO emptyVO = new CartListVO();
            emptyVO.setItems(new ArrayList<>());
            emptyVO.setTotalAmount(BigDecimal.ZERO);
            emptyVO.setSelectedCount(0);
            return Result.success(emptyVO);
        }

        // 2. 批量查询商品信息
        List<Long> productIds = carts.stream()
            .map(Cart::getProductId)
            .collect(Collectors.toList());
        List<Product> products = productMapper.selectBatchIds(productIds);

        // 转换为Map便于快速查找
        java.util.Map<Long, Product> productMap = products.stream()
            .collect(Collectors.toMap(Product::getId, p -> p));

        // 3. 构建购物车项列表
        List<CartItemDTO> items = new ArrayList<>();
        BigDecimal totalAmount = BigDecimal.ZERO;
        int selectedCount = 0;

        for (Cart cart : carts) {
            Product product = productMap.get(cart.getProductId());
            if (product == null || product.getStatus() != 1) {
                // 商品已下架或不存在,跳过
                continue;
            }

            CartItemDTO item = new CartItemDTO();
            item.setId(cart.getId());
            item.setProductId(product.getId());
            item.setProductName(product.getName());
            item.setProductImage(product.getMainImage());
            item.setProductPrice(product.getPrice());
            item.setQuantity(cart.getQuantity());
            item.setChecked(cart.getChecked());
            item.setTotalPrice(product.getPrice().multiply(BigDecimal.valueOf(cart.getQuantity())));

            items.add(item);

            if (cart.getChecked() == 1) {
                totalAmount = totalAmount.add(item.getTotalPrice());
                selectedCount++;
            }
        }

        // 4. 构建返回结果
        CartListVO vo = new CartListVO();
        vo.setItems(items);
        vo.setTotalAmount(totalAmount);
        vo.setSelectedCount(selectedCount);

        return Result.success(vo);
    }

    /**
     * 添加商品到购物车
     * 
     * 业务流程:
     * 1. 检查商品是否存在且已上架
     * 2. 检查购物车是否已有该商品
     *    - 如果有:增加数量
     *    - 如果没有:新增记录
     * 3. 检查库存是否充足
     * 
     * @param productId 商品ID
     * @param quantity 数量
     */
    @PostMapping("/add")
    public Result<Void> add(@RequestParam Long productId, @RequestParam(defaultValue = "1") Integer quantity) {
        Long userId = getCurrentUserId();
        log.info("添加商品到购物车: userId={}, productId={}, quantity={}", userId, productId, quantity);

        // 1. 检查商品是否存在
        Product product = productMapper.selectById(productId);
        if (product == null || product.getStatus() != 1) {
            return Result.error(400, "商品不存在或已下架");
        }

        // 2. 检查库存
        if (product.getStock() < quantity) {
            return Result.error(400, "库存不足,当前库存:" + product.getStock());
        }

        // 3. 查询购物车中是否已有该商品
        LambdaQueryWrapper<Cart> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Cart::getUserId, userId)
               .eq(Cart::getProductId, productId);
        Cart existing = cartMapper.selectOne(wrapper);

        if (existing != null) {
            // 已有:增加数量
            int newQuantity = existing.getQuantity() + quantity;
            // 再次检查库存
            if (product.getStock() < newQuantity) {
                return Result.error(400, "库存不足,当前库存:" + product.getStock());
            }
            existing.setQuantity(newQuantity);
            cartMapper.updateById(existing);
        } else {
            // 没有:新增记录
            Cart cart = new Cart();
            cart.setUserId(userId);
            cart.setProductId(productId);
            cart.setQuantity(quantity);
            cart.setChecked(1);
            cartMapper.insert(cart);
        }

        return Result.success();
    }

    /**
     * 更新购物车商品数量
     * 
     * @param cartId 购物车记录ID
     * @param quantity 新数量
     */
    @PutMapping("/update")
    public Result<Void> update(@RequestParam Long cartId, @RequestParam Integer quantity) {
        Long userId = getCurrentUserId();
        log.info("更新购物车商品数量: cartId={}, quantity={}", cartId, quantity);

        // 1. 检查购物车记录是否存在且属于当前用户
        Cart cart = cartMapper.selectById(cartId);
        if (cart == null || !cart.getUserId().equals(userId)) {
            return Result.error(404, "购物车记录不存在");
        }

        // 2. 检查库存
        Product product = productMapper.selectById(cart.getProductId());
        if (product.getStock() < quantity) {
            return Result.error(400, "库存不足,当前库存:" + product.getStock());
        }

        // 3. 更新数量
        if (quantity <= 0) {
            // 数量为0则删除
            cartMapper.deleteById(cartId);
        } else {
            cart.setQuantity(quantity);
            cartMapper.updateById(cart);
        }

        return Result.success();
    }

    /**
     * 删除购物车商品
     */
    @DeleteMapping("/remove/{cartId}")
    public Result<Void> remove(@PathVariable Long cartId) {
        Long userId = getCurrentUserId();

        Cart cart = cartMapper.selectById(cartId);
        if (cart == null || !cart.getUserId().equals(userId)) {
            return Result.error(404, "购物车记录不存在");
        }

        cartMapper.deleteById(cartId);
        return Result.success();
    }

    /**
     * 清空购物车(删除所有商品)
     */
    @DeleteMapping("/clear")
    public Result<Void> clear() {
        Long userId = getCurrentUserId();

        LambdaQueryWrapper<Cart> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Cart::getUserId, userId);
        cartMapper.delete(wrapper);

        return Result.success();
    }

    /**
     * 切换商品选中状态
     */
    @PutMapping("/check")
    public Result<Void> check(@RequestParam Long cartId, @RequestParam Integer checked) {
        Long userId = getCurrentUserId();

        Cart cart = cartMapper.selectById(cartId);
        if (cart == null || !cart.getUserId().equals(userId)) {
            return Result.error(404, "购物车记录不存在");
        }

        cart.setChecked(checked);
        cartMapper.updateById(cart);

        return Result.success();
    }

    /**
     * 全选/全不选
     */
    @PutMapping("/checkAll")
    public Result<Void> checkAll(@RequestParam Integer checked) {
        Long userId = getCurrentUserId();

        Cart cart = new Cart();
        cart.setChecked(checked);

        LambdaQueryWrapper<Cart> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Cart::getUserId, userId);
        cartMapper.update(cart, wrapper);

        return Result.success();
    }
}

3.4 订单模块API

package com.mall.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mall.common.PageResult;
import com.mall.common.Result;
import com.mall.dto.OrderCreateDTO;
import com.mall.dto.OrderInfoDTO;
import com.mall.dto.OrderItemDTO;
import com.mall.entity.*;
import com.mall.mapper.*;
import com.mall.utils.OrderNoGenerator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 订单控制器
 * 
 * 订单核心逻辑:
 * 1. 创建订单时锁定库存(扣减商品库存)
 * 2. 订单超时未支付自动取消(由定时任务处理)
 * 3. 订单状态流转:待支付 -> 已支付 -> 已发货 -> 已完成
 */
@Slf4j
@RestController
@RequestMapping("/order")
@RequiredArgsConstructor
public class OrderController {

    private final OrderMapper orderMapper;
    private final OrderItemMapper orderItemMapper;
    private final CartMapper cartMapper;
    private final ProductMapper productMapper;
    private final AddressMapper addressMapper;
    private final UserMapper userMapper;

    private Long getCurrentUserId() {
        // 从SecurityContext获取当前用户ID
        // 实际实现中应从JWT获取
        return 1L;
    }

    /**
     * 创建订单
     * 
     * 业务流程(事务性操作):
     * 1. 验证收货地址
     * 2. 获取购物车中选中的商品
     * 3. 二次验证商品库存
     * 4. 扣减商品库存
     * 5. 生成订单号和订单记录
     * 6. 生成订单商品明细
     * 7. 清空购物车中已选中的商品
     * 8. 返回订单信息
     * 
     * @param createDTO 订单创建参数(地址ID、备注)
     * @return 订单信息
     */
    @PostMapping("/create")
    @Transactional(rollbackFor = Exception.class)
    public Result<OrderInfoDTO> createOrder(@Valid @RequestBody OrderCreateDTO createDTO) {
        Long userId = getCurrentUserId();
        log.info("创建订单: userId={}, addressId={}", userId, createDTO.getAddressId());

        // 1. 验证收货地址
        Address address = addressMapper.selectById(createDTO.getAddressId());
        if (address == null || !address.getUserId().equals(userId)) {
            return Result.error(400, "收货地址不存在");
        }

        // 2. 获取购物车中选中的商品
        LambdaQueryWrapper<Cart> cartWrapper = new LambdaQueryWrapper<>();
        cartWrapper.eq(Cart::getUserId, userId)
                   .eq(Cart::getChecked, 1);
        List<Cart> cartItems = cartMapper.selectList(cartWrapper);

        if (cartItems.isEmpty()) {
            return Result.error(400, "请选择要购买的商品");
        }

        // 3. 提取商品ID列表并批量查询商品信息
        List<Long> productIds = cartItems.stream()
            .map(Cart::getProductId)
            .collect(Collectors.toList());
        List<Product> products = productMapper.selectBatchIds(productIds);
        java.util.Map<Long, Product> productMap = products.stream()
            .collect(Collectors.toMap(Product::getId, p -> p));

        // 4. 计算订单总金额并验证库存
        BigDecimal totalAmount = BigDecimal.ZERO;
        List<Cart> validCartItems = new ArrayList<>();

        for (Cart cart : cartItems) {
            Product product = productMap.get(cart.getProductId());
            if (product == null || product.getStatus() != 1) {
                return Result.error(400, "商品【" + cart.getProductId() + "】已下架");
            }
            if (product.getStock() < cart.getQuantity()) {
                return Result.error(400, "商品【" + product.getName() + "】库存不足,当前库存:" + product.getStock());
            }
            validCartItems.add(cart);
            totalAmount = totalAmount.add(product.getPrice().multiply(BigDecimal.valueOf(cart.getQuantity())));
        }

        // 5. 扣减库存
        for (Cart cart : validCartItems) {
            Product product = productMap.get(cart.getProductId());
            product.setStock(product.getStock() - cart.getQuantity());
            product.setSales(product.getSales() + cart.getQuantity());
            productMapper.updateById(product);
        }

        // 6. 生成订单号
        String orderNo = OrderNoGenerator.generate();

        // 7. 创建订单主记录
        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setUserId(userId);
        order.setTotalAmount(totalAmount);
        order.setPayAmount(totalAmount);
        order.setFreightAmount(BigDecimal.ZERO);
        order.setOrderStatus(0);  // 待支付
        order.setPayStatus(0);
        order.setShippingStatus(0);
        order.setReceiverName(address.getReceiverName());
        order.setReceiverPhone(address.getReceiverPhone());
        order.setReceiverProvince(address.getProvince());
        order.setReceiverCity(address.getCity());
        order.setReceiverDistrict(address.getDistrict());
        order.setReceiverAddress(address.getDetailAddress());
        order.setUserNote(createDTO.getUserNote());
        orderMapper.insert(order);

        // 8. 创建订单商品明细
        for (Cart cart : validCartItems) {
            Product product = productMap.get(cart.getProductId());
            OrderItem orderItem = new OrderItem();
            orderItem.setOrderId(order.getId());
            orderItem.setOrderNo(orderNo);
            orderItem.setProductId(product.getId());
            orderItem.setProductName(product.getName());
            orderItem.setProductImage(product.getMainImage());
            orderItem.setProductPrice(product.getPrice());
            orderItem.setQuantity(cart.getQuantity());
            orderItem.setTotalAmount(product.getPrice().multiply(BigDecimal.valueOf(cart.getQuantity())));
            orderItemMapper.insert(orderItem);
        }

        // 9. 清空购物车中已选中的商品
        cartMapper.delete(cartWrapper);

        // 10. 构建返回结果
        OrderInfoDTO result = buildOrderInfoDTO(order);

        log.info("订单创建成功: orderId={}, orderNo={}, totalAmount={}", order.getId(), orderNo, totalAmount);
        return Result.success(result);
    }

    /**
     * 获取订单列表(分页)
     */
    @GetMapping("/list")
    public Result<PageResult<OrderInfoDTO>> listOrders(
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size) {
        Long userId = getCurrentUserId();

        // 分页查询订单
        Page<Order> pageParam = new Page<>(page, size);
        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Order::getUserId, userId);
        wrapper.orderByDesc(Order::getCreateTime);
        Page<Order> orderPage = orderMapper.selectPage(pageParam, wrapper);

        // 构建订单DTO列表
        List<OrderInfoDTO> orderInfos = new ArrayList<>();
        for (Order order : orderPage.getRecords()) {
            orderInfos.add(buildOrderInfoDTO(order));
        }

        return Result.success(new PageResult<>(orderInfos, orderPage.getTotal(), page, size));
    }

    /**
     * 获取订单详情
     */
    @GetMapping("/detail/{id}")
    public Result<OrderInfoDTO> getOrderDetail(@PathVariable Long id) {
        Long userId = getCurrentUserId();

        Order order = orderMapper.selectById(id);
        if (order == null || !order.getUserId().equals(userId)) {
            return Result.error(404, "订单不存在");
        }

        return Result.success(buildOrderInfoDTO(order));
    }

    /**
     * 取消订单(仅限待支付状态)
     */
    @PutMapping("/cancel/{id}")
    @Transactional(rollbackFor = Exception.class)
    public Result<Void> cancelOrder(@PathVariable Long id) {
        Long userId = getCurrentUserId();

        Order order = orderMapper.selectById(id);
        if (order == null || !order.getUserId().equals(userId)) {
            return Result.error(404, "订单不存在");
        }

        if (order.getOrderStatus() != 0) {
            return Result.error(400, "当前订单状态无法取消");
        }

        // 恢复库存
        LambdaQueryWrapper<OrderItem> itemWrapper = new LambdaQueryWrapper<>();
        itemWrapper.eq(OrderItem::getOrderId, id);
        List<OrderItem> items = orderItemMapper.selectList(itemWrapper);

        for (OrderItem item : items) {
            Product product = productMapper.selectById(item.getProductId());
            if (product != null) {
                product.setStock(product.getStock() + item.getQuantity());
                productMapper.updateById(product);
            }
        }

        // 更新订单状态
        order.setOrderStatus(4);  // 已取消
        order.setCancelTime(LocalDateTime.now());
        orderMapper.updateById(order);

        log.info("订单已取消: orderId={}", id);
        return Result.success();
    }

    /**
     * 确认收货
     */
    @PutMapping("/confirm/{id}")
    public Result<Void> confirmReceipt(@PathVariable Long id) {
        Long userId = getCurrentUserId();

        Order order = orderMapper.selectById(id);
        if (order == null || !order.getUserId().equals(userId)) {
            return Result.error(404, "订单不存在");
        }

        if (order.getOrderStatus() != 2) {
            return Result.error(400, "当前订单状态无法确认收货");
        }

        order.setOrderStatus(3);  // 已完成
        order.setReceiveTime(LocalDateTime.now());
        orderMapper.updateById(order);

        return Result.success();
    }

    /**
     * 管理员发货
     */
    @PreAuthorize("hasRole('admin')")
    @PutMapping("/ship/{id}")
    public Result<Void> shipOrder(@PathVariable Long id) {
        Order order = orderMapper.selectById(id);
        if (order == null) {
            return Result.error(404, "订单不存在");
        }

        if (order.getOrderStatus() != 1) {
            return Result.error(400, "当前订单状态无法发货");
        }

        order.setOrderStatus(2);  // 已发货
        order.setShippingStatus(1);
        order.setDeliveryTime(LocalDateTime.now());
        orderMapper.updateById(order);

        log.info("订单已发货: orderId={}", id);
        return Result.success();
    }

    /**
     * 构建订单DTO(包含商品明细和地址信息)
     */
    private OrderInfoDTO buildOrderInfoDTO(Order order) {
        OrderInfoDTO dto = new OrderInfoDTO();
        dto.setId(order.getId());
        dto.setOrderNo(order.getOrderNo());
        dto.setTotalAmount(order.getTotalAmount());
        dto.setPayAmount(order.getPayAmount());
        dto.setOrderStatus(order.getOrderStatus());
        dto.setOrderStatusText(getOrderStatusText(order.getOrderStatus()));
        dto.setCreateTime(order.getCreateTime());

        // 查询订单商品明细
        LambdaQueryWrapper<OrderItem> itemWrapper = new LambdaQueryWrapper<>();
        itemWrapper.eq(OrderItem::getOrderId, order.getId());
        List<OrderItem> items = orderItemMapper.selectList(itemWrapper);

        List<OrderItemDTO> itemDTOs = items.stream().map(item -> {
            OrderItemDTO itemDTO = new OrderItemDTO();
            itemDTO.setProductId(item.getProductId());
            itemDTO.setProductName(item.getProductName());
            itemDTO.setProductImage(item.getProductImage());
            itemDTO.setProductPrice(item.getProductPrice());
            itemDTO.setQuantity(item.getQuantity());
            itemDTO.setTotalAmount(item.getTotalAmount());
            return itemDTO;
        }).collect(Collectors.toList());
        dto.setItems(itemDTOs);

        return dto;
    }

    private String getOrderStatusText(Integer status) {
        switch (status) {
            case 0: return "待支付";
            case 1: return "已支付";
            case 2: return "已发货";
            case 3: return "已完成";
            case 4: return "已取消";
            default: return "未知状态";
        }
    }
}

3.5 订单号生成器

package com.mall.utils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 订单号生成器
 * 
 * 订单号规则:时间戳(14位) + 机器ID(2位) + 序列号(6位)
 * 格式示例:20240501123456 + 01 + 000001 = 2024050112345601000001
 */
public class OrderNoGenerator {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    // 序列号计数器(原子操作,线程安全)
    private static final AtomicLong SEQUENCE = new AtomicLong(0);

    // 机器ID(实际部署时从配置读取)
    private static final int MACHINE_ID = 1;

    /**
     * 生成订单号
     */
    public static synchronized String generate() {
        // 时间戳部分
        String timestampPart = LocalDateTime.now().format(FORMATTER);

        // 机器ID部分(2位)
        String machinePart = String.format("%02d", MACHINE_ID);

        // 序列号部分(6位)
        long seq = SEQUENCE.incrementAndGet() % 1000000;
        String seqPart = String.format("%06d", seq);

        // 重置序列号(避免溢出后重复)
        if (seq == 999999) {
            SEQUENCE.set(0);
        }

        return timestampPart + machinePart + seqPart;
    }
}

3.6 管理员API

package com.mall.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mall.common.PageResult;
import com.mall.common.Result;
import com.mall.entity.*;
import com.mall.mapper.OrderMapper;
import com.mall.mapper.OrderItemMapper;
import com.mall.mapper.ProductMapper;
import com.mall.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;

/**
 * 管理员接口
 * 所有接口都需要admin权限
 */
@Slf4j
@RestController
@RequestMapping("/admin")
@PreAuthorize("hasRole('admin')")
@RequiredArgsConstructor
public class AdminController {

    private final UserMapper userMapper;
    private final ProductMapper productMapper;
    private final OrderMapper orderMapper;
    private final OrderItemMapper orderItemMapper;

    /**
     * 用户管理 - 查询用户列表
     */
    @GetMapping("/users")
    public Result<PageResult<User>> userList(
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size,
            @RequestParam(required = false) String keyword) {

        Page<User> pageParam = new Page<>(page, size);
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

        if (keyword != null && !keyword.isEmpty()) {
            wrapper.like(User::getUsername, keyword)
                   .or()
                   .like(User::getPhone, keyword);
        }
        wrapper.orderByDesc(User::getCreateTime);

        Page<User> result = userMapper.selectPage(pageParam, wrapper);
        return Result.success(new PageResult<>(result.getRecords(), result.getTotal(), page, size));
    }

    /**
     * 用户管理 - 启用/禁用用户
     */
    @PutMapping("/user/status")
    public Result<Void> updateUserStatus(@RequestParam Long userId, @RequestParam Integer status) {
        User user = new User();
        user.setId(userId);
        user.setStatus(status);
        userMapper.updateById(user);

        log.info("管理员更新用户状态: userId={}, status={}", userId, status);
        return Result.success();
    }

    /**
     * 订单管理 - 查询所有订单
     */
    @GetMapping("/orders")
    public Result<PageResult<Order>> orderList(
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size,
            @RequestParam(required = false) Integer status) {

        Page<Order> pageParam = new Page<>(page, size);
        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();

        if (status != null) {
            wrapper.eq(Order::getOrderStatus, status);
        }
        wrapper.orderByDesc(Order::getCreateTime);

        Page<Order> result = orderMapper.selectPage(pageParam, wrapper);
        return Result.success(new PageResult<>(result.getRecords(), result.getTotal(), page, size));
    }

    /**
     * 订单管理 - 订单统计
     */
    @GetMapping("/statistics")
    public Result<StatisticsVO> getStatistics() {
        StatisticsVO vo = new StatisticsVO();

        // 用户总数
        vo.setTotalUsers(userMapper.selectCount(null));

        // 商品总数(上架)
        LambdaQueryWrapper<Product> productWrapper = new LambdaQueryWrapper<>();
        productWrapper.eq(Product::getStatus, 1);
        vo.setOnSaleProducts(productMapper.selectCount(productWrapper));

        // 今日订单数
        LambdaQueryWrapper<Order> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.ge(Order::getCreateTime, LocalDateTime.now().withHour(0).withMinute(0).withSecond(0));
        vo.setTodayOrders(orderMapper.selectCount(orderWrapper));

        return Result.success(vo);
    }
}

来源:
https://yyvgt.cn

相关文章
|
8天前
|
缓存 人工智能 自然语言处理
我对比了8个Claude API中转站,踩了不少坑,总结给你
本文是个人开发者耗时1周实测的8大Claude中转平台横向评测,聚焦Claude Code真实体验:以加权均价(¥/M token)、内部汇率、缓存支持、模型真实性及稳定性为核心指标。
3370 20
|
20天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
17857 60
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
1天前
|
SQL 人工智能 弹性计算
阿里云发布 Agentic NDR,威胁检测与响应进入智能体时代
欢迎前往阿里云云防火墙控制台体验!
1154 2
|
4天前
|
人工智能 JSON BI
DeepSeek V4 来了!超越 Claude Sonnet 4.5,赶紧对接 Claude Code 体验一把
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro 的真实体验与避坑记录 本文记录我将 Claude Code 对接 DeepSeek 最新模型(V4Pro)后的真实体验,测试了 Skills 自动化查询和积木报表 AI 建表两个场景——有惊喜,也踩
1785 8
|
15天前
|
人工智能 JavaScript Ubuntu
低成本搭建AIP自动化写作系统:Hermes保姆级使用教程,长文和逐步实操贴图
我带着怀疑的态度,深度使用了几天,聚焦微信公众号AIP自动化写作场景,写出来的几篇文章,几乎没有什么修改,至少合乎我本人的意愿,而且排版风格,也越来越完善,同样是起码过得了我自己这一关。 这个其实OpenClaw早可以实现了,但是目前我觉得最大的区别是,Hermes会自主总结提炼,并更新你的写作技能。 相信就冲这一点,就值得一试。 这篇帖子主要就Hermes部署使用,作一个非常详细的介绍,几乎一步一贴图。 关于Hermes,无论你赞成哪种声音,我希望都是你自己动手行动过,发自内心的选择!
3159 29
|
3天前
|
人工智能 缓存 BI
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro,跑完 Skills —— OA 审批、大屏、报表、部署 5 大实战场景后的真实体验 ![](https://oscimg.oschina.net/oscnet/up608d34aeb6bafc47f
1416 3
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
|
4天前
|
机器学习/深度学习 缓存 测试技术
DeepSeek-V4开源:百万上下文,Agent能力比肩顶级闭源模型
DeepSeek-V4正式开源!含V4-Pro(1.6T参数)与V4-Flash(284B参数)双版本,均支持百万token上下文。首创混合注意力架构,Agent能力、世界知识与推理性能全面领先开源模型,数学/代码评测比肩顶级闭源模型。
1712 6
|
5天前
|
人工智能 测试技术 API
阿里Qwen3.6-27B正式开源:网友直呼“太牛了”!
阿里云千问3.6系列重磅开源Qwen3.6-27B稠密大模型!官网:https://t.aliyun.com/U/JbblVp 仅270亿参数,编程能力媲美千亿模型,在SWE-bench等权威基准中表现卓越。支持多模态理解、本地部署及OpenClaw等智能体集成,已开放Hugging Face与ModelScope下载。