第三部分:核心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);
}
}