前置准备:
Redis (高性能)+MongoDB(海量数据)+Elasticsearch/HBase(大数据数据库)
pom引入:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mook</groupId>
<artifactId>shoppingmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shoppingmall</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.springboot</groupId>
<artifactId>best-pay-sdk</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
实体类:
表单数据:
//用于接收前端参数
@Data
public class CarAddForm {
@NotNull
private Integer productId;
private Boolean selected = true;
}
pojo类:
//购物车需要参数,具体数量需要根据实际情况查询数据库,所以用不到
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
private Integer productId;
private Integer quantity;
private Boolean productSelected;
}
返回Vo:
//购物车类
@Data
public class CarVo {
private List<CarProductVo> carProductVoList;
private Boolean selectedAll;
private BigDecimal carTotalPrice;
private Integer carTotalQuantity;
}
//购物车商品类
@Data
public class CarProductVo {
private Integer productId;
private Integer quantity;
private String productName;
private String productSubtitle;
private String productMainImage;
private String detail;
private String subImage;
private BigDecimal productPrice;
private BigDecimal productTotalPrice;
private Integer productStock;
private Integer productStatus;
private Boolean productSelected;
}
Controller层:
@RestController
public class CarController {
@Autowired
private ICarService carService;
/**
* 获取购物车列表
*/
@GetMapping("/carList")
public ResponseVo<CarVo> list(HttpSession session){
//注意这里参数校验异常已经在RuntimeException.notValidExceptionHandle中捕获,不用在做参数校验
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return carService.list(user.getId());
}
/**
* 购物车添加
*/
@PostMapping("/car")
public ResponseVo<CarVo> add(@Valid @RequestBody CarAddForm carAddForm, HttpSession session){
//注意这里参数校验异常已经在RuntimeException.notValidExceptionHandle中捕获,不用在做参数校验
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return carService.add(user.getId(),carAddForm);
}
@PutMapping("/car/{poductId}")
public ResponseVo<CarVo> update(@PathVariable Integer productId, Integer quantity, Boolean selected, HttpSession session){
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return carService.update(user.getId(),productId,quantity,selected);
}
@Delete("/deleteCar/{poductId}")
public ResponseVo<CarVo> delete(@PathVariable Integer productId, HttpSession session){
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return carService.delete(user.getId(),productId);
}
@PutMapping("/car/selectAll")
public ResponseVo<CarVo> selectAll(HttpSession session){
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return carService.selectAll(user.getId());
}
@PutMapping("/car/unSelectAll")
public ResponseVo<CarVo> unSelectAll(HttpSession session){
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return carService.unSelectAll(user.getId());
}
@GetMapping("/car/products/sum")
public ResponseVo<Integer> getSum(HttpSession session){
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return carService.sum(user.getId());
}
}
参数异常捕获:
@ControllerAdvice
public class RunTimeExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
@ResponseStatus(HttpStatus.FORBIDDEN) //设置相应状态码
public ResponseVo handle(RuntimeException e) {
return ResponseVo.error(ResponseEnum.MASHINE_ERROR, e.getMessage());
}
//捕获自定义异常
@ExceptionHandler(UserLoginException.class)
@ResponseBody
public ResponseVo userLoginHandle(){
return ResponseVo.error(ResponseEnum.FAIL_NON_LOGIN);
}
//捕获参数不能为null异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ResponseVo notValidExceptionHandle(MethodArgumentNotValidException e){
String msg = e.getBindingResult().getFieldError().getField() + e.getBindingResult().getFieldError().getDefaultMessage();
return ResponseVo.error(ResponseEnum.PARAMS_ERROR,msg);
}
}
service层:
注意:查询时数量等商品变化信息要实时从数据库中获取最新值。
@Service
public class ICarService {
private final static String CAR_REDIS_KEY_TEMPLATE = "car_%d";
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private IProductMapper productMapper;
private Gson gson = new Gson();
/**
* 添加
*/
public ResponseVo<CarVo> add(Integer uid, CarAddForm carAddForm) {
Integer quantity = 1;
ProductDetailVo product = productMapper.selectByPrimaryKey(carAddForm.getProductId());
//商品是否存在
if (product == null) {
return ResponseVo.error(ResponseEnum.PRODUCT_NON);
}
//商品是否正常在售
if (!product.getStatus().equals(ProductStatusEnum.ON_SALE.getI())) {
return ResponseVo.error(ResponseEnum.PRODUCT_NON);
}
//商品库存是否充足
if (product.getStock() <= 0) {
return ResponseVo.error(ResponseEnum.PRODUCT_NON);
}
//写入到Redis , 使用gson进行序列化
HashOperations<String, String, String> opsForHash = redisTemplate.opsForHash();
//先要从redis中读取数量
String redisKey = String.format(CAR_REDIS_KEY_TEMPLATE, uid);
String value = opsForHash.get(redisKey, String.valueOf(product.getId()));
Car car;
if (StringUtils.isEmpty(value)) {
//没有该商品,新增
car = new Car(product.getId(), quantity, carAddForm.getSelected());
} else {
//有商品,数量+1
car = gson.fromJson(value, Car.class);
car.setQuantity(car.getQuantity() + quantity);
}
//list需要遍历,所以不用list,用hash进行存储
//redisTemplate.opsForValue().set(String.format(CAR_REDIS_KEY_TEMPLATE,uid),gson.toJson(new Car(product.getId(),quantity,carAddForm.getSelected())));
opsForHash.put(String.format(CAR_REDIS_KEY_TEMPLATE, uid), String.valueOf(product.getId()), gson.toJson(car));
return null;
}
/**
* 列表
*/
public ResponseVo<CarVo> list(Integer uid) {
HashOperations<String, String, String> opsForHash = redisTemplate.opsForHash();
String redisKey = String.format(CAR_REDIS_KEY_TEMPLATE, uid);
//获取所有数据进行遍历
Map<String, String> entries = opsForHash.entries(redisKey);
boolean selectAll = true;
Integer carTotalQuantity = 0;
BigDecimal carTotalPrice = BigDecimal.ZERO;
CarVo carVo = new CarVo();
List<CarProductVo> carProductVoList = new ArrayList<>();
for (Map.Entry<String, String> entry : entries.entrySet()) {
Integer productId = Integer.valueOf(entry.getKey());
Car car = gson.fromJson(entry.getValue(), Car.class);
//TODO: 需要优化,使用in
ProductDetailVo productDetailVo = productMapper.selectByPrimaryKey(productId);
if (productDetailVo != null) {
CarProductVo carProductVo = new CarProductVo();
carProductVo.setProductId(productId);
carProductVo.setQuantity(car.getQuantity());
carProductVo.setProductName(productDetailVo.getName());
carProductVo.setProductSubtitle(productDetailVo.getSubtitle());
carProductVo.setProductMainImage(productDetailVo.getMainImage());
carProductVo.setProductPrice(productDetailVo.getPrice().multiply(BigDecimal.valueOf(car.getQuantity())));
carProductVo.setProductStock(productDetailVo.getStock());
carProductVo.setProductSelected(car.getProductSelected());
carProductVoList.add(carProductVo);
if (!car.getProductSelected()) {
selectAll = false;
}
//计算总价只计算选中的
if (car.getProductSelected()) {
carTotalPrice = carTotalPrice.add(carProductVo.getProductTotalPrice());
}
}
carTotalQuantity += car.getQuantity();
}
//有一个没有选中就不叫全选
carVo.setSelectedAll(selectAll);
carVo.setCarTotalQuantity(carTotalQuantity);
//总价 = 购物车选中商品价格 * 数量
carVo.setCarTotalPrice(carTotalPrice);
carVo.setCarProductVoList(carProductVoList);
return ResponseVo.successByCommonData(carVo);
}
/**
* 更新
*/
public ResponseVo<CarVo> update(Integer uid, Integer productId, Integer quantity, Boolean selected) {
HashOperations<String, String, String> opsForHash = redisTemplate.opsForHash();
String redisKey = String.format(CAR_REDIS_KEY_TEMPLATE, uid);
String value = opsForHash.get(redisKey, String.valueOf(productId));
if (StringUtils.isEmpty(value)) {
//没有,报错
return ResponseVo.error(ResponseEnum.PRODUCT_NON);
} else {
//有商品,数量+1
Car car = gson.fromJson(value, Car.class);
if (quantity != null && quantity >= 0) {
car.setQuantity(car.getQuantity() + quantity);
}
if (selected != null) {
car.setProductSelected(selected);
}
opsForHash.put(redisKey, String.valueOf(productId), gson.toJson(car));
}
return list(uid);
}
/**
* 删除
*/
public ResponseVo<CarVo> delete(Integer uid, Integer productId) {
HashOperations<String, String, String> opsForHash = redisTemplate.opsForHash();
String redisKey = String.format(CAR_REDIS_KEY_TEMPLATE, uid);
String value = opsForHash.get(redisKey, String.valueOf(productId));
if (StringUtils.isEmpty(value)) {
//没有,报错
return ResponseVo.error(ResponseEnum.PRODUCT_NON);
}
opsForHash.delete(redisKey, String.valueOf(productId));
return list(uid);
}
/**
* 全选
*/
public ResponseVo<CarVo> selectAll(Integer uid){
HashOperations<String, String, String> opsForHash = redisTemplate.opsForHash();
String redisKey = String.format(CAR_REDIS_KEY_TEMPLATE, uid);
for (Car car : listForCar(uid)) {
car.setProductSelected(true);
opsForHash.put(redisKey,String.valueOf(car.getProductId()),gson.toJson(car));
}
return list(uid);
}
/**
* 全不选中
*/
public ResponseVo<CarVo> unSelectAll(Integer uid){
HashOperations<String, String, String> opsForHash = redisTemplate.opsForHash();
String redisKey = String.format(CAR_REDIS_KEY_TEMPLATE, uid);
for (Car car : listForCar(uid)) {
car.setProductSelected(false);
opsForHash.put(redisKey,String.valueOf(car.getProductId()),gson.toJson(car));
}
return list(uid);
}
/**
* 总数
*/
public ResponseVo<Integer> sum(Integer uid){
Integer sum = listForCar(uid).stream().map(Car::getQuantity).reduce(0, Integer::sum);
return ResponseVo.successByCommonData(sum);
}
/**
* 遍历购物车
*/
private List<Car> listForCar(Integer uid){
HashOperations<String, String, String> opsForHash = redisTemplate.opsForHash();
String redisKey = String.format(CAR_REDIS_KEY_TEMPLATE, uid);
//获取所有数据进行遍历
Map<String, String> entries = opsForHash.entries(redisKey);
ArrayList<Car> carList = new ArrayList<>();
for (Map.Entry<String, String> entry : entries.entrySet()) {
carList.add(gson.fromJson(entry.getValue(),Car.class));
}
return carList;
}
}
收获地址模块
收获地址Bean对象实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Shipping {
private Integer id;
private Integer userId;
private String receiverName;
private String receiverPhone;
private String receiverMobile;
private String receiverProvince;
private String receiverCity;
private String receiverDistrict;
private String receiverAddress;
private String receiverZip; //邮编
private Date createTime;
private Date updateTime;
}
表单参数类
@Data
public class ShippingForm {
@NotBlank
private String receiverName;
@NotBlank
private String receiverPhone;
@NotBlank
private String receiverMobile;
@NotBlank
private String receiverProvince;
@NotBlank
private String receiverCity;
@NotBlank
private String receiverDistrict;
@NotBlank
private String receiverAddress;
@NotBlank
private String receiverZip;
}
Controller
@RestController
public class ShippingController {
@Autowired
private IShippingService shippingService;
@PostMapping("/shippings")
public ResponseVo add(@Valid @RequestBody ShippingForm form, HttpSession session) {
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return shippingService.add(user.getId(), form);
}
@DeleteMapping("/shippings/{shippingId}")
public ResponseVo delete(@PathVariable Integer shippingId, HttpSession session) {
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return shippingService.delete(user.getId(), shippingId);
}
@DeleteMapping("/shippings/{shippingId}")
public ResponseVo update(@PathVariable Integer shippingId, @Valid @RequestBody ShippingForm form, HttpSession session) {
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return shippingService.update(user.getId(), shippingId, form);
}
@GetMapping("/shippings/{shippingId}")
public ResponseVo list(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
HttpSession session) {
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return shippingService.list(user.getId(), pageNum, pageSize);
}
}
Service
@Service
public class IShippingService {
@Autowired
private IShippingMapper shippingMapper;
/**
* 添加地址
*/
public ResponseVo<Map<String, Integer>> add(Integer uid, ShippingForm form) {
Shipping shipping = new Shipping();
BeanUtils.copyProperties(form, shipping);
int row = shippingMapper.insertSelective(shipping);
if (row == 0) {
return ResponseVo.error(ResponseEnum.MASHINE_ERROR);
}
HashMap<String, Integer> map = new HashMap<>();
//注意:这里需要插入后返回的自增id,需要在mapper文件中配置 useGeneratedKeys="true" keyProperty="id" 否则需要在查询一次
map.put("shippingId", shipping.getId());
return ResponseVo.successByCommonData(map);
}
/**
* 删除地址
*/
public ResponseVo delete(Integer uid, Integer shippingId) {
int row = shippingMapper.deleteByIdAndUid(uid, shippingId);
if (row == 0) {
return ResponseVo.error(ResponseEnum.PARAMS_ERROR);
}
return ResponseVo.success();
}
/**
* 更新地址
*/
public ResponseVo update(Integer uid, Integer shippingId, ShippingForm form) {
Shipping shipping = new Shipping();
BeanUtils.copyProperties(form, shipping);
shipping.setUserId(uid);
shipping.setId(shippingId);
int row = shippingMapper.updateByPrimaryKeySelective(shipping);
if (row == 0) {
return ResponseVo.error(ResponseEnum.PARAMS_ERROR);
}
return ResponseVo.success();
}
/**
* 地址列表
*/
public ResponseVo<PageInfo> list(Integer uid, Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Shipping> shippings = shippingMapper.selectByUid(uid);
PageInfo pageInfo = new PageInfo(shippings);
return ResponseVo.successByCommonData(pageInfo);
}
}