用户模块与商品模块开发完成

This commit is contained in:
puzvv
2025-12-18 00:05:35 +08:00
parent 2268b02466
commit d179c9d96b
46 changed files with 1635 additions and 6 deletions

18
pom.xml
View File

@@ -33,6 +33,7 @@
<lombok.version>1.18.30</lombok.version> <lombok.version>1.18.30</lombok.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version> <mybatis-plus.version>3.5.5</mybatis-plus.version>
<dynamic-datasource.version>4.3.1</dynamic-datasource.version> <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
<mysql.version>8.0.33</mysql.version>
</properties> </properties>
<dependencies> <dependencies>
<!-- 核心依赖 --> <!-- 核心依赖 -->
@@ -49,7 +50,7 @@
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope> <version>${mysql.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
@@ -69,6 +70,21 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<!-- API文档 --> <!-- API文档 -->
<dependency> <dependency>
<groupId>org.springdoc</groupId> <groupId>org.springdoc</groupId>

View File

@@ -0,0 +1,6 @@
package icu.sunway.ai_spring_example.Common.Constant;
public class DeletedConstant {
public static final Integer DELETED = 1;
public static final Integer NOT_DELETED = 0;
}

View File

@@ -0,0 +1,10 @@
package icu.sunway.ai_spring_example.Common.Constant;
public class JwtClaimsConstant {
public static final String USER_ID = "userId";
public static final String PHONE = "phone";
public static final String USERNAME = "username";
public static final String NAME = "name";
}

View File

@@ -0,0 +1,21 @@
package icu.sunway.ai_spring_example.Common.Constant;
/**
* 信息提示常量类
*/
public class MessageConstant {
public static final String ALREADY_EXISTS = "账号已存在";
public static final String PASSWORD_ERROR = "密码错误";
public static final String ACCOUNT_NOT_FOUND = "账号不存在";
public static final String ACCOUNT_LOCKED = "账号被锁定";
public static final String UNKNOWN_ERROR = "未知错误";
public static final String USER_NOT_LOGIN = "用户未登录";
public static final String SHOPPING_CART_IS_NULL = "购物车数据为空,不能下单";
public static final String ADDRESS_BOOK_IS_NULL = "用户地址为空,不能下单";
public static final String LOGIN_FAILED = "登录失败";
public static final String UPLOAD_FAILED = "文件上传失败";
public static final String PASSWORD_EDIT_FAILED = "密码修改失败";
public static final String ORDER_STATUS_ERROR = "订单状态错误";
public static final String ORDER_NOT_FOUND = "订单不存在";
public static final String PRODUCT_NOT_FOUND = "商品不存在";
}

View File

@@ -0,0 +1,14 @@
package icu.sunway.ai_spring_example.Common.Constant;
/**
* 状态常量,启用或者禁用
*/
public class StatusConstant {
//启用
public static final Integer ENABLE = 1;
//禁用
public static final Integer DISABLE = 0;
}

View File

@@ -0,0 +1,15 @@
package icu.sunway.ai_spring_example.Common.Exception;
/**
* 账号不存在异常
*/
public class AccountNotFoundException extends BaseException {
public AccountNotFoundException() {
}
public AccountNotFoundException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,15 @@
package icu.sunway.ai_spring_example.Common.Exception;
/**
* 业务异常
*/
public class BaseException extends RuntimeException {
public BaseException() {
}
public BaseException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,11 @@
package icu.sunway.ai_spring_example.Common.Exception;
/**
* 交易异常
*/
public class BusinessException extends BaseException {
public BusinessException() {
}
public BusinessException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,18 @@
package icu.sunway.ai_spring_example.Common.Properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "zhiwei.jwt")
@Data
public class JwtProperties {
/**
* 用户生成jwt令牌相关配置
*/
private String userSecretKey;
private long userTtl;
private String userTokenName;
}

View File

@@ -1,4 +1,4 @@
package icu.sunway.ai_spring_example.Common; package icu.sunway.ai_spring_example.Common.Response;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;

View File

@@ -0,0 +1,69 @@
package icu.sunway.ai_spring_example.Common.Utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定秘钥
*
* @param secretKey jwt秘钥
* @param ttlMillis jwt过期时间(毫秒)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 生成安全密钥
SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
// 指定签名的时候使用的签名算法也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明一定要先设置这个自己创建的私有的声明这个是给builder的claim赋值一旦写在标准的声明赋值之后就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(key, signatureAlgorithm)
// 设置过期时间
.setExpiration(exp);
return builder.compact();
}
/**
* Token解密
*
* @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
// 生成安全密钥
SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
// 得到DefaultJwtParser
Claims claims = Jwts.parserBuilder()
// 设置签名的秘钥
.setSigningKey(key)
// 设置需要解析的jwt
.build()
.parseClaimsJws(token)
.getBody();
return claims;
}
}

View File

@@ -0,0 +1,22 @@
package icu.sunway.ai_spring_example.Config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis-Plus核心配置分页插件等
*/
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

View File

@@ -0,0 +1,26 @@
package icu.sunway.ai_spring_example.Config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
/**
* MyBatis-Plus字段自动填充配置
*/
@Configuration
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 插入时填充创建时间和更新时间
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时填充更新时间
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}

View File

@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import icu.sunway.ai_spring_example.Common.ResponseEntity; import icu.sunway.ai_spring_example.Common.Response.ResponseEntity;
/** /**
* 用户管理后台接口 * 用户管理后台接口

View File

@@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import icu.sunway.ai_spring_example.Common.ResponseEntity; import icu.sunway.ai_spring_example.Common.Response.ResponseEntity;
/** /**
* 认证控制器 * 认证控制器

View File

@@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import icu.sunway.ai_spring_example.Common.ResponseEntity; import icu.sunway.ai_spring_example.Common.Response.ResponseEntity;
/** /**
* 课程控制器 * 课程控制器

View File

@@ -0,0 +1,86 @@
package icu.sunway.ai_spring_example.Controllers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import icu.sunway.ai_spring_example.Common.Response.ResponseEntity;
import icu.sunway.ai_spring_example.Service.ProductService;
import icu.sunway.ai_spring_example.pojo.Dto.ProductDTO;
import icu.sunway.ai_spring_example.pojo.Vo.ProductListVO;
import icu.sunway.ai_spring_example.pojo.Vo.ProductVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/product")
@Slf4j
public class ProductController {
@Autowired
private ProductService productService;
/**
* 新增商品
*/
@PostMapping
public ResponseEntity save(@RequestBody ProductDTO productDTO) {
log.info("新增商品: {}", productDTO);
productService.saveProduct(productDTO);
return ResponseEntity.success("商品新增成功");
}
/**
* 修改商品
*/
@PutMapping
public ResponseEntity update(@RequestBody ProductDTO productDTO) {
log.info("修改商品: {}", productDTO);
productService.updateProduct(productDTO);
return ResponseEntity.success("商品修改成功");
}
/**
* 商品上下架
*/
@PutMapping("/status/{id}/{status}")
public ResponseEntity updateStatus(@PathVariable Long id, @PathVariable Integer status) {
log.info("商品上下架: id={}, status={}", id, status);
productService.updateStatus(id, status);
return ResponseEntity.success("状态更新成功");
}
/**
* 分页查询商品列表
*/
@GetMapping("/page")
public ResponseEntity<Map<String, Object>> page(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
Long categoryId,
String name,
Integer status
) {
log.info("分页查询商品列表: page={}, size={}, categoryId={}, name={}, status={}",
page, size, categoryId, name, status);
Page<ProductListVO> productPage = productService.getProductPage(page, size, categoryId, name, status);
Map<String, Object> result = new HashMap<>();
result.put("list", productPage.getRecords());
result.put("total", productPage.getTotal());
result.put("page", page);
result.put("size", size);
return ResponseEntity.success(result);
}
/**
* 查询商品详情
*/
@GetMapping("/{id}")
public ResponseEntity<ProductVO> detail(@PathVariable Long id) {
log.info("查询商品详情: id={}", id);
ProductVO productVO = productService.getProductDetail(id);
return ResponseEntity.success(productVO);
}
}

View File

@@ -0,0 +1,79 @@
package icu.sunway.ai_spring_example.Controllers;
import icu.sunway.ai_spring_example.Common.Constant.DeletedConstant;
import icu.sunway.ai_spring_example.Common.Constant.JwtClaimsConstant;
import icu.sunway.ai_spring_example.Common.Constant.StatusConstant;
import icu.sunway.ai_spring_example.Common.Properties.JwtProperties;
import icu.sunway.ai_spring_example.Common.Response.ResponseEntity;
import icu.sunway.ai_spring_example.Common.Utils.JwtUtil;
import icu.sunway.ai_spring_example.Service.UserService;
import icu.sunway.ai_spring_example.pojo.Dto.UserDTO;
import icu.sunway.ai_spring_example.pojo.Entity.User;
import icu.sunway.ai_spring_example.pojo.Vo.UserLoginVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
@PostMapping("/login")
public ResponseEntity<UserLoginVO> login(@RequestBody UserDTO userDTO){
log.info("用户登录:{}",userDTO);
User user = userService.login(userDTO);
//登录成功后生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.USER_ID, user.getId());
String token = JwtUtil.createJWT(
jwtProperties.getUserSecretKey(),
jwtProperties.getUserTtl(),
claims);
UserLoginVO userLoginVO = UserLoginVO.builder()
.id(user.getId())
.username(user.getUsername())
.nickname(user.getNickname())
.token(token)
.build();
return ResponseEntity.success(userLoginVO);
}
@PostMapping("/logout")
public ResponseEntity<String> logout(){
return ResponseEntity.success("注销成功");
}
@PostMapping("/register")
public ResponseEntity register(@RequestBody UserDTO userDTO){
log.info("用户注册:{}",userDTO);
User user = new User();
BeanUtils.copyProperties(userDTO,user);
user.setStatus(StatusConstant.ENABLE);
user.setIsDeleted(DeletedConstant.NOT_DELETED);
user.setPassword(DigestUtils.md5DigestAsHex(userDTO.getPassword().getBytes()));
userService.save(user);
return ResponseEntity.success("注册成功");
}
@PutMapping("/update")
public ResponseEntity update(@RequestBody UserDTO userDTO){
log.info("编辑用户信息:{}",userDTO);
userService.update(userDTO);
return ResponseEntity.success("更新成功");
}
}

View File

@@ -0,0 +1,21 @@
package icu.sunway.ai_spring_example.Handler;
import icu.sunway.ai_spring_example.Common.Response.ResponseEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 捕获业务异常
* @param e
* @return
*/
@ExceptionHandler
public ResponseEntity exceptionHandler(Exception e) {
log.error("全局异常捕获:{}", e.getMessage());
return ResponseEntity.error(e.getMessage());
}
}

View File

@@ -0,0 +1,17 @@
package icu.sunway.ai_spring_example.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import icu.sunway.ai_spring_example.pojo.Entity.ProductCategory;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ProductCategoryMapper extends BaseMapper<ProductCategory> {
/**
* 查询所有分类(树形结构)
* @return 分类列表
*/
List<ProductCategory> selectAllCategories();
}

View File

@@ -0,0 +1,29 @@
package icu.sunway.ai_spring_example.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import icu.sunway.ai_spring_example.pojo.Entity.Product;
import icu.sunway.ai_spring_example.pojo.Vo.ProductListVO;
import icu.sunway.ai_spring_example.pojo.Vo.ProductVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
/**
* 分页查询商品列表
* @param page 分页参数
* @param categoryId 分类ID可选
* @param name 商品名称(模糊查询,可选)
* @param status 状态(可选)
* @return 分页商品列表
*/
IPage<ProductListVO> selectProductPage(
Page<ProductListVO> page,
@Param("categoryId") Long categoryId,
@Param("name") String name,
@Param("status") Integer status
);
}

View File

@@ -0,0 +1,26 @@
package icu.sunway.ai_spring_example.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import icu.sunway.ai_spring_example.pojo.Entity.ProductSku;
import icu.sunway.ai_spring_example.pojo.Vo.ProductSkuVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ProductSkuMapper extends BaseMapper<ProductSku> {
/**
* 根据商品ID查询规格列表
* @param productId 商品ID
* @return 规格VO列表
*/
List<ProductSkuVO> selectByProductId(@Param("productId") Long productId);
/**
* 批量插入规格
* @param skuList 规格列表
*/
void batchInsert(@Param("list") List<ProductSku> skuList);
}

View File

@@ -0,0 +1,20 @@
package icu.sunway.ai_spring_example.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import icu.sunway.ai_spring_example.pojo.Entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 根据用户名查询用户
* @param username
* @return
*/
@Select("select * from sys_user where username = #{username}")
User getByUsername(String username);
void update(User user);
}

View File

@@ -0,0 +1,136 @@
package icu.sunway.ai_spring_example.Service.Impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import icu.sunway.ai_spring_example.Common.Exception.BusinessException;
import icu.sunway.ai_spring_example.Common.Constant.MessageConstant;
import icu.sunway.ai_spring_example.Mapper.ProductMapper;
import icu.sunway.ai_spring_example.Mapper.ProductSkuMapper;
import icu.sunway.ai_spring_example.Service.ProductService;
import icu.sunway.ai_spring_example.pojo.Dto.ProductDTO;
import icu.sunway.ai_spring_example.pojo.Dto.ProductSkuDTO;
import icu.sunway.ai_spring_example.pojo.Entity.Product;
import icu.sunway.ai_spring_example.pojo.Entity.ProductSku;
import icu.sunway.ai_spring_example.pojo.Vo.ProductListVO;
import icu.sunway.ai_spring_example.pojo.Vo.ProductVO;
import lombok.extern.slf4j.Slf4j;
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.ArrayList;
import java.util.List;
@Service
@Slf4j
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private ProductSkuMapper productSkuMapper;
@Override
@Transactional
public void saveProduct(ProductDTO productDTO) {
// 1. 保存商品基本信息
Product product = new Product();
BeanUtils.copyProperties(productDTO, product);
productMapper.insert(product);
// 2. 保存商品规格
List<ProductSku> skuList = new ArrayList<>();
for (ProductSkuDTO skuDTO : productDTO.getSkuList()) {
ProductSku sku = new ProductSku();
BeanUtils.copyProperties(skuDTO, sku);
sku.setProductId(product.getId()); // 设置商品ID关联
skuList.add(sku);
}
if (!skuList.isEmpty()) {
productSkuMapper.batchInsert(skuList);
}
}
@Override
@Transactional
public void updateProduct(ProductDTO productDTO) {
// 1. 验证商品是否存在
Product product = productMapper.selectById(productDTO.getId());
if (product == null) {
throw new BusinessException(MessageConstant.PRODUCT_NOT_FOUND);
}
// 2. 更新商品基本信息
BeanUtils.copyProperties(productDTO, product);
productMapper.updateById(product);
// 3. 先删除原有规格
LambdaQueryWrapper<ProductSku> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ProductSku::getProductId, product.getId());
productSkuMapper.delete(queryWrapper);
// 4. 重新插入新规格
List<ProductSku> skuList = new ArrayList<>();
for (ProductSkuDTO skuDTO : productDTO.getSkuList()) {
ProductSku sku = new ProductSku();
BeanUtils.copyProperties(skuDTO, sku);
sku.setProductId(product.getId());
skuList.add(sku);
}
if (!skuList.isEmpty()) {
productSkuMapper.batchInsert(skuList);
}
}
@Override
public Page<ProductListVO> getProductPage(Integer page, Integer size, Long categoryId, String name, Integer status) {
Page<ProductListVO> pageInfo = new Page<>(page, size);
IPage<ProductListVO> result = productMapper.selectProductPage(pageInfo, categoryId, name, status);
return (Page<ProductListVO>) result;
}
@Override
public ProductVO getProductDetail(Long id) {
QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", id);
Product product = productMapper.selectOne(queryWrapper);
ProductVO productVO = ProductVO.builder()
.id(product.getId())
.categoryId(product.getCategoryId())
.name(product.getName())
.subtitle(product.getSubtitle())
.mainImage(product.getMainImage())
.subImages(product.getSubImages())
.description(product.getDescription())
.price(product.getPrice())
.stock(product.getStock())
.sort(product.getSort())
.sales(product.getSales())
.status(product.getStatus())
//TODO:查询分类名称
.categoryName(null)
.createTime(product.getCreateTime())
.updateTime(product.getUpdateTime())
.build();
if (productVO == null) {
throw new BusinessException(MessageConstant.PRODUCT_NOT_FOUND);
}
// 查询规格列表
productVO.setSkuList(productSkuMapper.selectByProductId(id));
return productVO;
}
@Override
public void updateStatus(Long id, Integer status) {
Product product = productMapper.selectById(id);
if (product == null) {
throw new BusinessException(MessageConstant.PRODUCT_NOT_FOUND);
}
product.setStatus(status);
productMapper.updateById(product);
}
}

View File

@@ -0,0 +1,54 @@
package icu.sunway.ai_spring_example.Service.Impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import icu.sunway.ai_spring_example.Common.Constant.MessageConstant;
import icu.sunway.ai_spring_example.Common.Constant.StatusConstant;
import icu.sunway.ai_spring_example.Common.Exception.AccountNotFoundException;
import icu.sunway.ai_spring_example.Mapper.UserMapper;
import icu.sunway.ai_spring_example.Service.UserService;
import icu.sunway.ai_spring_example.pojo.Dto.UserDTO;
import icu.sunway.ai_spring_example.pojo.Entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User login(UserDTO userDTO) {
String username = userDTO.getUsername();
String password = userDTO.getPassword();
User user = userMapper.getByUsername(username);
if(user == null){
throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
}
password = DigestUtils.md5DigestAsHex(password.getBytes());
log.info("用户密码:{}",password);
if(!password.equals(user.getPassword())){
throw new AccountNotFoundException(MessageConstant.PASSWORD_ERROR);
}
if(user.getStatus() == StatusConstant.DISABLE){
throw new AccountNotFoundException(MessageConstant.ACCOUNT_LOCKED);
}
return user;
}
@Override
public void update(UserDTO userDTO) {
User user = new User();
BeanUtils.copyProperties(userDTO,user);
userMapper.update(user);
}
}

View File

@@ -0,0 +1,48 @@
package icu.sunway.ai_spring_example.Service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import icu.sunway.ai_spring_example.pojo.Dto.ProductDTO;
import icu.sunway.ai_spring_example.pojo.Entity.Product;
import icu.sunway.ai_spring_example.pojo.Vo.ProductListVO;
import icu.sunway.ai_spring_example.pojo.Vo.ProductVO;
public interface ProductService extends IService<Product> {
/**
* 新增商品(包含规格)
* @param productDTO 商品信息
*/
void saveProduct(ProductDTO productDTO);
/**
* 修改商品(包含规格)
* @param productDTO 商品信息
*/
void updateProduct(ProductDTO productDTO);
/**
* 分页查询商品列表
* @param page 页码
* @param size 每页条数
* @param categoryId 分类ID
* @param name 商品名称
* @param status 状态
* @return 分页结果
*/
Page<ProductListVO> getProductPage(Integer page, Integer size, Long categoryId, String name, Integer status);
/**
* 根据ID查询商品详情
* @param id 商品ID
* @return 商品详情
*/
ProductVO getProductDetail(Long id);
/**
* 上下架商品
* @param id 商品ID
* @param status 状态0-下架1-上架)
*/
void updateStatus(Long id, Integer status);
}

View File

@@ -0,0 +1,21 @@
package icu.sunway.ai_spring_example.pojo.Dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class ProductDTO {
private Long id; // 商品ID修改时使用
private Long categoryId; // 分类ID
private String name; // 商品名称
private String subtitle; // 副标题
private String mainImage; // 主图URL
private String subImages; // 子图URL逗号分隔
private String description; // 商品描述
private BigDecimal price; // 价格
private Integer stock; // 库存
private Integer status; // 状态 0-下架 1-上架
private Integer sort; // 排序
private List<ProductSkuDTO> skuList; // 规格列表
}

View File

@@ -0,0 +1,14 @@
package icu.sunway.ai_spring_example.pojo.Dto;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class ProductSkuDTO {
private Long id; // 规格ID修改时使用
private String skuName; // 规格名称(如"红色-XL"
private String skuCode; // 规格编码
private BigDecimal price; // 规格价格
private Integer stock; // 规格库存
private String specJson; // 规格参数JSON
}

View File

@@ -0,0 +1,15 @@
package icu.sunway.ai_spring_example.pojo.Dto;
import lombok.Data;
@Data
public class UserDTO {
private Long id;
private String username;
private String password;
private String phone;
private String email;
private String avatar;
private String nickname;
private Integer gender;
}

View File

@@ -0,0 +1,106 @@
package icu.sunway.ai_spring_example.pojo.Entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单表实体类
*
* @author ZhiWei
* @since 2025-12-17
*/
@Data
@TableName("order_info")
public class OrderInfo {
/**
* 订单ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 订单编号(唯一)
*/
private String orderNo;
/**
* 用户ID
*/
private Long userId;
/**
* 订单总金额
*/
private BigDecimal totalAmount;
/**
* 实付金额
*/
private BigDecimal payAmount;
/**
* 运费
*/
private BigDecimal freightAmount;
/**
* 支付方式 0-未支付 1-微信 2-支付宝
*/
private Integer payType;
/**
* 订单状态 0-待付款 1-待发货 2-待收货 3-已完成 4-已取消
*/
private Integer status;
/**
* 收货人姓名
*/
private String receiverName;
/**
* 收货人手机号
*/
private String receiverPhone;
/**
* 收货人地址
*/
private String receiverAddress;
/**
* 支付时间
*/
private LocalDateTime payTime;
/**
* 发货时间
*/
private LocalDateTime deliveryTime;
/**
* 收货时间
*/
private LocalDateTime receiveTime;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除 0-未删 1-已删
*/
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,91 @@
package icu.sunway.ai_spring_example.pojo.Entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单项表实体类
*
* @author ZhiWei
* @since 2025-12-17
*/
@Data
@TableName("order_item")
public class OrderItem {
/**
* 订单项ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 订单ID
*/
private Long orderId;
/**
* 订单编号
*/
private String orderNo;
/**
* 商品ID
*/
private Long productId;
/**
* SKU ID
*/
private Long skuId;
/**
* 商品名称
*/
private String productName;
/**
* SKU名称
*/
private String skuName;
/**
* 商品图片
*/
private String productImage;
/**
* 单价
*/
private BigDecimal price;
/**
* 数量
*/
private Integer quantity;
/**
* 小计金额
*/
private BigDecimal totalPrice;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除 0-未删 1-已删
*/
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,96 @@
package icu.sunway.ai_spring_example.pojo.Entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 商品表实体类
*
* @author ZhiWei
* @since 2025-12-17
*/
@Data
@TableName("product")
public class Product {
/**
* 商品ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 分类ID
*/
private Long categoryId;
/**
* 商品名称
*/
private String name;
/**
* 副标题
*/
private String subtitle;
/**
* 主图URL
*/
private String mainImage;
/**
* 子图URL逗号分隔
*/
private String subImages;
/**
* 商品描述
*/
private String description;
/**
* 价格
*/
private BigDecimal price;
/**
* 库存
*/
private Integer stock;
/**
* 销量
*/
private Integer sales;
/**
* 状态 0-下架 1-上架
*/
private Integer status;
/**
* 排序
*/
private Integer sort;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除 0-未删 1-已删
*/
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,65 @@
package icu.sunway.ai_spring_example.pojo.Entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 商品分类表实体类
*
* @author ZhiWei
* @since 2025-12-17
*/
@Data
@TableName("product_category")
public class ProductCategory {
/**
* 分类ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 父分类ID0为一级分类
*/
private Long parentId;
/**
* 分类名称
*/
private String name;
/**
* 排序(升序)
*/
private Integer sort;
/**
* 图标URL
*/
private String icon;
/**
* 层级1/2/3
*/
private Integer level;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除 0-未删 1-已删
*/
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,71 @@
package icu.sunway.ai_spring_example.pojo.Entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 商品规格表实体类
*
* @author ZhiWei
* @since 2025-12-17
*/
@Data
@TableName("product_sku")
public class ProductSku {
/**
* SKU ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 商品ID
*/
private Long productId;
/**
* SKU名称如"红色-XL"
*/
private String skuName;
/**
* SKU编码唯一
*/
private String skuCode;
/**
* SKU价格
*/
private BigDecimal price;
/**
* SKU库存
*/
private Integer stock;
/**
* 规格JSON{"颜色":"红","尺寸":"XL"}
*/
private String specJson;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除 0-未删 1-已删
*/
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,65 @@
package icu.sunway.ai_spring_example.pojo.Entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 购物车表实体类
*
* @author ZhiWei
* @since 2025-12-17
*/
@Data
@TableName("shopping_cart")
public class ShoppingCart {
/**
* 购物车ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 商品ID
*/
private Long productId;
/**
* SKU ID
*/
private Long skuId;
/**
* 数量
*/
private Integer count;
/**
* 勾选状态 0-未勾选 1-已勾选
*/
private Integer checked;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除 0-未删 1-已删
*/
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,78 @@
package icu.sunway.ai_spring_example.pojo.Entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户表实体类
*
* @author ZhiWei
* @since 2025-12-17
*/
@Data
@TableName("sys_user")
public class User {
/**
* 用户ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名(唯一)
*/
private String username;
/**
* 加密密码BCrypt
*/
private String password;
/**
* 手机号(唯一)
*/
private String phone;
/**
* 邮箱(可选)
*/
private String email;
/**
* 头像URL
*/
private String avatar;
/**
* 昵称
*/
private String nickname;
/**
* 性别 0-未知 1-男 2-女
*/
private Integer gender;
/**
* 状态 0-禁用 1-正常
*/
private Integer status;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除 0-未删 1-已删
*/
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,80 @@
package icu.sunway.ai_spring_example.pojo.Entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户地址表实体类
*
* @author ZhiWei
* @since 2025-12-17
*/
@Data
@TableName("user_address")
public class UserAddress {
/**
* 地址ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 收货人姓名
*/
private String receiverName;
/**
* 收货人手机号
*/
private String receiverPhone;
/**
* 省
*/
private String province;
/**
* 市
*/
private String city;
/**
* 区/县
*/
private String district;
/**
* 详细地址
*/
private String detailAddress;
/**
* 是否默认 0-非默认 1-默认
*/
private Integer isDefault;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除 0-未删 1-已删
*/
@TableLogic
private Integer isDeleted;
}

View File

@@ -0,0 +1,18 @@
package icu.sunway.ai_spring_example.pojo.Vo;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
@Data
@Builder
//用于商品列表页面的数据展示
public class ProductListVO {
private Long id;
private String name;
private String mainImage;
private BigDecimal price;
private Integer sales;
private Integer status;
private String categoryName;
}

View File

@@ -0,0 +1,17 @@
package icu.sunway.ai_spring_example.pojo.Vo;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
@Data
@Builder
public class ProductSkuVO {
private Long id;
private Long productId;
private String skuName;
private String skuCode;
private BigDecimal price;
private Integer stock;
private String specJson;
}

View File

@@ -0,0 +1,32 @@
package icu.sunway.ai_spring_example.pojo.Vo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
@TableName("product")
public class ProductVO {
private Long id;
private Long categoryId;
private String categoryName; // 分类名称
private String name;
private String subtitle;
private String mainImage;
private String subImages;
private String description;
private BigDecimal price;
private Integer stock;
private Integer sales;
private Integer status;
private Integer sort;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private List<ProductSkuVO> skuList; // 规格列表
}

View File

@@ -0,0 +1,17 @@
package icu.sunway.ai_spring_example.pojo.Vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserLoginVO {
private Long id;
private String username;
private String nickname;
private String token;
}

View File

@@ -0,0 +1,17 @@
package icu.sunway.ai_spring_example.pojo.Vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserVO {
private Long id;
private String username;
private String nickname;
private String token;
}

View File

@@ -8,7 +8,7 @@ spring:
strict: false strict: false
datasource: datasource:
master: master:
url: jdbc:mysql://182.92.242.196:3306/sys_user?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai url: jdbc:mysql://182.92.242.196:3306/ZhiWeiMarket?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: remote_user username: remote_user
password: 123456 password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
@@ -27,3 +27,12 @@ spring:
server: server:
port: 8080 port: 8080
zhiwei:
jwt:
# 设置jwt签名加密时使用的秘钥
user-secret-key: itcast_hmac_sha_key_minimum_length_32_bytes_required
# 设置jwt过期时间
user-ttl: 7200000
# 设置前端传递过来的令牌名称
user-token-name: token

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="icu.sunway.ai_spring_example.Mapper.ProductMapper">
<select id="selectProductPage" resultType="icu.sunway.ai_spring_example.pojo.Vo.ProductListVO">
SELECT
p.id, p.name, p.main_image as mainImage, p.price, p.sales, p.status,
c.name as categoryName
FROM product p
LEFT JOIN product_category c ON p.category_id = c.id
WHERE p.is_deleted = 0
<if test="categoryId != null">AND p.category_id = #{categoryId}</if>
<if test="name != null and name != ''">AND p.name LIKE CONCAT('%', #{name}, '%')</if>
<if test="status != null">AND p.status = #{status}</if>
ORDER BY p.sort ASC, p.create_time DESC
</select>
</mapper>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="icu.sunway.ai_spring_example.Mapper.ProductSkuMapper">
<select id="selectByProductId" resultType="icu.sunway.ai_spring_example.pojo.Vo.ProductSkuVO">
SELECT * FROM product_sku
WHERE product_id = #{productId} AND is_deleted = 0
</select>
<insert id="batchInsert">
INSERT INTO product_sku (
product_id, sku_name, sku_code, price, stock, spec_json, create_time, update_time
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.productId}, #{item.skuName}, #{item.skuCode}, #{item.price},
#{item.stock}, #{item.specJson}, NOW(), NOW()
)
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="icu.sunway.ai_spring_example.Mapper.UserMapper">
<update id="update" parameterType="icu.sunway.ai_spring_example.pojo.Entity.User">
UPDATE sys_user
<set>
<if test="username != null">username = #{username},</if>
<if test="nickname != null">nickname = #{nickname},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="email != null">email = #{email},</if>
<if test="avatar != null">avatar = #{avatar},</if>
<if test="nickname !=null">nickname = #{nickname},</if>
<if test="gender != null">gender = #{gender},</if>
update_time = NOW()
</set>
WHERE id = #{id}
</update>
</mapper>