first commit

This commit is contained in:
zhangsan 2025-05-20 19:21:02 +08:00
commit a3e7faed94
131 changed files with 5088 additions and 0 deletions

72
.gitignore vendored Normal file
View File

@ -0,0 +1,72 @@
# ========== IDEA ==========
# IntelliJ IDEA 项目文件
.idea/
# IDEA 模块文件
*.iml
*.iws
# IDEA 生成的工作区文件
workspace.xml
# 代码样式、运行配置等
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
.idea/vcs.xml
# 防止提交本地历史
.idea/**/localHistory/
# ========== 编译输出 ==========
# Maven 默认输出目录
target/
# Gradle 默认输出目录
build/
# ========== 操作系统 ==========
# Windows 缓存文件
Thumbs.db
ehthumbs.db
# macOS 缓存文件
.DS_Store
.AppleDouble
.LSOverride
# Linux 缓存文件
*~
# ========== 日志和临时文件 ==========
*.log
*.tmp
*.temp
*.swp
# ========== Test 输出 ==========
test-output/
surefire-reports/
failsafe-reports/
# ========== 依赖管理 ==========
# Maven 本地仓库
.mvn/repository/
# Gradle 缓存(可选)
.gradle/
# ========== 打包文件 ==========
# Spring Boot 可执行包
*.jar
*.war
# Spring Boot 可执行包目录
BOOT-INF/
# ========== IDE 外插件 ==========
# Lombok 插件生成文件
*.class
# JRebel 缓存
rebel.xml
# ========== 其它 ==========
# 环境变量文件(如包含敏感配置)
*.env
# Docker 相关
docker-compose.override.yml
# 如果你用到 IDEA 自带的 File-Based Storage8+版本默认),可以添加:
# .idea/.name
# .idea/gradle.xml

58
cart-service/pom.xml Normal file
View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hmall</artifactId>
<groupId>com.heima</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cart-service</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!--common-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-service</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,14 @@
package com.hmall.cart;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
public class CartApplication {
public static void main(String[] args) {
SpringApplication.run(CartApplication.class, args);
}
}

View File

@ -0,0 +1,53 @@
package com.hmall.cart.controller;
import com.hmall.cart.domain.dto.CartFormDTO;
import com.hmall.cart.domain.po.Cart;
import com.hmall.cart.domain.vo.CartVO;
import com.hmall.cart.service.ICartService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.annotations.Param;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@Api(tags = "购物车相关接口")
@RestController
@RequestMapping("/carts")
@RequiredArgsConstructor
public class CartController {
private final ICartService cartService;
@ApiOperation("添加商品到购物车")
@PostMapping
public void addItem2Cart(@Valid @RequestBody CartFormDTO cartFormDTO){
cartService.addItem2Cart(cartFormDTO);
}
@ApiOperation("更新购物车数据")
@PutMapping
public void updateCart(@RequestBody Cart cart){
cartService.updateById(cart);
}
@ApiOperation("删除购物车中商品")
@DeleteMapping("{id}")
public void deleteCartItem(@Param ("购物车条目id")@PathVariable("id") Long id){
cartService.removeById(id);
}
@ApiOperation("查询购物车列表")
@GetMapping
public List<CartVO> queryMyCarts(){
return cartService.queryMyCarts();
}
@ApiOperation("批量删除购物车中商品")
@ApiImplicitParam(name = "ids", value = "购物车条目id集合")
@DeleteMapping
public void deleteCartItemByIds(@RequestParam("ids") List<Long> ids){
cartService.removeByItemIds(ids);
}
}

View File

@ -0,0 +1,20 @@
package com.hmall.cart.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "新增购物车商品表单实体")
public class CartFormDTO {
@ApiModelProperty("商品id")
private Long itemId;
@ApiModelProperty("商品标题")
private String name;
@ApiModelProperty("商品动态属性键值集")
private String spec;
@ApiModelProperty("价格,单位:分")
private Integer price;
@ApiModelProperty("商品图片")
private String image;
}

View File

@ -0,0 +1,81 @@
package com.hmall.cart.domain.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 订单详情表
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("cart")
public class Cart implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 购物车条目id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户id
*/
private Long userId;
/**
* sku商品id
*/
private Long itemId;
/**
* 购买数量
*/
private Integer num;
/**
* 商品标题
*/
private String name;
/**
* 商品动态属性键值集
*/
private String spec;
/**
* 价格,单位
*/
private Integer price;
/**
* 商品图片
*/
private String image;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,43 @@
package com.hmall.cart.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* <p>
* 订单详情表
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@ApiModel(description = "购物车VO实体")
public class CartVO {
@ApiModelProperty("购物车条目id ")
private Long id;
@ApiModelProperty("sku商品id")
private Long itemId;
@ApiModelProperty("购买数量")
private Integer num;
@ApiModelProperty("商品标题")
private String name;
@ApiModelProperty("商品动态属性键值集")
private String spec;
@ApiModelProperty("价格,单位:分")
private Integer price;
@ApiModelProperty("商品最新价格")
private Integer newPrice;
@ApiModelProperty("商品最新状态")
private Integer status = 1;
@ApiModelProperty("商品最新库存")
private Integer stock = 10;
@ApiModelProperty("商品图片")
private String image;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,21 @@
package com.hmall.cart.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hmall.cart.domain.po.Cart;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/**
* <p>
* 订单详情表 Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface CartMapper extends BaseMapper<Cart> {
@Update("UPDATE cart SET num = num + 1 WHERE user_id = #{userId} AND item_id = #{itemId}")
void updateNum(@Param("itemId") Long itemId, @Param("userId") Long userId);
}

View File

@ -0,0 +1,27 @@
package com.hmall.cart.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hmall.cart.domain.dto.CartFormDTO;
import com.hmall.cart.domain.po.Cart;
import com.hmall.cart.domain.vo.CartVO;
import java.util.Collection;
import java.util.List;
/**
* <p>
* 订单详情表 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface ICartService extends IService<Cart> {
void addItem2Cart(CartFormDTO cartFormDTO);
List<CartVO> queryMyCarts();
void removeByItemIds(Collection<Long> itemIds);
}

View File

@ -0,0 +1,134 @@
package com.hmall.cart.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.cart.domain.dto.CartFormDTO;
import com.hmall.cart.domain.po.Cart;
import com.hmall.cart.domain.vo.CartVO;
import com.hmall.cart.mapper.CartMapper;
import com.hmall.cart.service.ICartService;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.UserContext;
import com.hmall.domain.dto.ItemDTO;
import com.hmall.service.IItemService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* <p>
* 订单详情表 服务实现类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Service
@RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
// private final IItemService itemService;
@Override
public void addItem2Cart(CartFormDTO cartFormDTO) {
// 1.获取登录用户
Long userId = UserContext.getUser();
// 2.判断是否已经存在
if(checkItemExists(cartFormDTO.getItemId(), userId)){
// 2.1.存在则更新数量
baseMapper.updateNum(cartFormDTO.getItemId(), userId);
return;
}
// 2.2.不存在判断是否超过购物车数量
checkCartsFull(userId);
// 3.新增购物车条目
// 3.1.转换PO
Cart cart = BeanUtils.copyBean(cartFormDTO, Cart.class);
// 3.2.保存当前用户
cart.setUserId(userId);
// 3.3.保存到数据库
save(cart);
}
@Override
public List<CartVO> queryMyCarts() {
// 1.查询我的购物车列表
List<Cart> carts = lambdaQuery().eq(Cart::getUserId,1L /*UserContext.getUser()*/).list();
if (CollUtils.isEmpty(carts)) {
return CollUtils.emptyList();
}
// 2.转换VO
List<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);
// 3.处理VO中的商品信息
handleCartItems(vos);
// 4.返回
return vos;
}
private void handleCartItems(List<CartVO> vos) {
// // 1.获取商品id
// Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// // 2.查询商品
// List<ItemDTO> items = itemService.queryItemByIds(itemIds);
// if (CollUtils.isEmpty(items)) {
// return;
// }
// // 3.转为 id item的map
// Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
// // 4.写入vo
// for (CartVO v : vos) {
// ItemDTO item = itemMap.get(v.getItemId());
// if (item == null) {
// continue;
// }
// v.setNewPrice(item.getPrice());
// v.setStatus(item.getStatus());
// v.setStock(item.getStock());
// }
}
@Override
public void removeByItemIds(Collection<Long> itemIds) {
// 1.构建删除条件userId和itemId
QueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>();
queryWrapper.lambda()
.eq(Cart::getUserId, UserContext.getUser())
.in(Cart::getItemId, itemIds);
// 2.删除
remove(queryWrapper);
}
private void checkCartsFull(Long userId) {
long count = lambdaQuery()
.eq(Cart::getUserId, userId)
.count(); // 这里返回 long
if (count >= 10) {
throw new BizIllegalException(
StrUtil.format("用户购物车课程不能超过{}", 10)
);
}
}
private boolean checkItemExists(Long itemId, Long userId) {
long count = lambdaQuery()
.eq(Cart::getUserId, userId)
.eq(Cart::getItemId, itemId)
.count();
return count > 0;
}
}

View File

@ -0,0 +1,4 @@
hm:
db:
host: mysql
pw: 123

View File

@ -0,0 +1,4 @@
hm:
db:
host: localhost # 修改为你自己的虚拟机IP地址
pw: 123456 # 修改为docker中的MySQL密码

View File

@ -0,0 +1,43 @@
server:
port: 8082
spring:
application:
name: cart-service
profiles:
active: local
datasource:
url: jdbc:mysql://${hm.db.host}:3306/hm-cart?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: ${hm.db.pw}
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
global-config:
db-config:
update-strategy: not_null
id-type: auto
logging:
level:
com.hmall: debug
pattern:
dateformat: HH:mm:ss:SSS
file:
path: "logs/${spring.application.name}"
knife4j:
enable: true
openapi:
title: 黑马商城购物车接口文档
description: "黑马商城购物车接口文档"
email: zhangsan@itcast.cn
concat: 宇哥
url: https://www.itcast.cn
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- com.hmall.cart.controller
# keytool -genkeypair -alias hmall -keyalg RSA -keypass hmall123 -keystore hmall.jks -storepass hmall123

View File

@ -0,0 +1,5 @@
<?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="com.hmall.mapper.CartMapper">
</mapper>

95
hm-common/pom.xml Normal file
View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hmall</artifactId>
<groupId>com.heima</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hm-common</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--完成SpringMVC自动装配-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<scope>provided</scope>
</dependency>
<!--日志-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.73</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<version>${mybatis-plus.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>${mybatis-plus.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
<!--caffeine-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!--AMQP依赖-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
<scope>provided</scope>
</dependency>
<!--Spring整合Rabbit依赖-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<scope>provided</scope>
</dependency>
<!--json处理-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,68 @@
package com.hmall.common.advice;
import com.hmall.common.domain.R;
import com.hmall.common.exception.BadRequestException;
import com.hmall.common.exception.CommonException;
import com.hmall.common.exception.DbException;
import com.hmall.common.utils.WebUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.util.NestedServletException;
import java.net.BindException;
import java.util.stream.Collectors;
@RestControllerAdvice
@Slf4j
public class CommonExceptionAdvice {
@ExceptionHandler(DbException.class)
public Object handleDbException(DbException e) {
log.error("mysql数据库操作异常 -> ", e);
return processResponse(e);
}
@ExceptionHandler(CommonException.class)
public Object handleBadRequestException(CommonException e) {
log.error("自定义异常 -> {} , 异常原因:{} ",e.getClass().getName(), e.getMessage());
log.debug("", e);
return processResponse(e);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getAllErrors()
.stream().map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("|"));
log.error("请求参数校验异常 -> {}", msg);
log.debug("", e);
return processResponse(new BadRequestException(msg));
}
@ExceptionHandler(BindException.class)
public Object handleBindException(BindException e) {
log.error("请求参数绑定异常 ->BindException {}", e.getMessage());
log.debug("", e);
return processResponse(new BadRequestException("请求参数格式错误"));
}
@ExceptionHandler(NestedServletException.class)
public Object handleNestedServletException(NestedServletException e) {
log.error("参数异常 -> NestedServletException{}", e.getMessage());
log.debug("", e);
return processResponse(new BadRequestException("请求参数处理异常"));
}
@ExceptionHandler(Exception.class)
public Object handleRuntimeException(Exception e) {
log.error("其他异常 uri : {} -> ", WebUtils.getRequest().getRequestURI(), e);
return processResponse(new CommonException("服务器内部异常", 500));
}
private ResponseEntity<R<Void>> processResponse(CommonException e){
return ResponseEntity.status(e.getCode()).body(R.error(e));
}
}

View File

@ -0,0 +1,23 @@
package com.hmall.common.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.math.BigInteger;
@Configuration
@ConditionalOnClass(ObjectMapper.class)
public class JsonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return jacksonObjectMapperBuilder -> {
// long -> string
jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
jacksonObjectMapperBuilder.serializerByType(BigInteger.class, ToStringSerializer.instance);
};
}
}

View File

@ -0,0 +1,25 @@
package com.hmall.common.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass({MybatisPlusInterceptor.class, BaseMapper.class})
public class MyBatisConfig {
@Bean
@ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 1.分页拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(1000L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}

View File

@ -0,0 +1,61 @@
package com.hmall.common.domain;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.Convert;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<T> {
protected Long total;
protected Long pages;
protected List<T> list;
public static <T> PageDTO<T> empty(Long total, Long pages) {
return new PageDTO<>(total, pages, CollUtils.emptyList());
}
public static <T> PageDTO<T> empty(Page<?> page) {
return new PageDTO<>(page.getTotal(), page.getPages(), CollUtils.emptyList());
}
public static <T> PageDTO<T> of(Page<T> page) {
if(page == null){
return new PageDTO<>();
}
if (CollUtils.isEmpty(page.getRecords())) {
return empty(page);
}
return new PageDTO<>(page.getTotal(), page.getPages(), page.getRecords());
}
public static <T,R> PageDTO<T> of(Page<R> page, Function<R, T> mapper) {
if(page == null){
return new PageDTO<>();
}
if (CollUtils.isEmpty(page.getRecords())) {
return empty(page);
}
return new PageDTO<>(page.getTotal(), page.getPages(),
page.getRecords().stream().map(mapper).collect(Collectors.toList()));
}
public static <T> PageDTO<T> of(Page<?> page, List<T> list) {
return new PageDTO<>(page.getTotal(), page.getPages(), list);
}
public static <T, R> PageDTO<T> of(Page<R> page, Class<T> clazz) {
return new PageDTO<>(page.getTotal(), page.getPages(), BeanUtils.copyList(page.getRecords(), clazz));
}
public static <T, R> PageDTO<T> of(Page<R> page, Class<T> clazz, Convert<R, T> convert) {
return new PageDTO<>(page.getTotal(), page.getPages(), BeanUtils.copyList(page.getRecords(), clazz, convert));
}
}

View File

@ -0,0 +1,69 @@
package com.hmall.common.domain;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.Min;
@Data
@ApiModel(description = "分页查询条件")
@Accessors(chain = true)
public class PageQuery {
public static final Integer DEFAULT_PAGE_SIZE = 20;
public static final Integer DEFAULT_PAGE_NUM = 1;
@ApiModelProperty("页码")
@Min(value = 1, message = "页码不能小于1")
private Integer pageNo = DEFAULT_PAGE_NUM;
@ApiModelProperty("页码")
@Min(value = 1, message = "每页查询数量不能小于1")
private Integer pageSize = DEFAULT_PAGE_SIZE;
@ApiModelProperty("是否升序")
private Boolean isAsc = true;
@ApiModelProperty("排序方式")
private String sortBy;
public int from(){
return (pageNo - 1) * pageSize;
}
public <T> Page<T> toMpPage(OrderItem... orderItems) {
Page<T> page = new Page<>(pageNo, pageSize);
// 是否手动指定排序方式
if (orderItems != null && orderItems.length > 0) {
for (OrderItem orderItem : orderItems) {
page.addOrder(orderItem);
}
return page;
}
// 前端是否有排序字段
if (StrUtil.isNotEmpty(sortBy)){
OrderItem orderItem = new OrderItem();
orderItem.setAsc(isAsc);
orderItem.setColumn(sortBy);
page.addOrder(orderItem);
}
return page;
}
public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc) {
if (StringUtils.isBlank(sortBy)){
sortBy = defaultSortBy;
this.isAsc = isAsc;
}
Page<T> page = new Page<>(pageNo, pageSize);
OrderItem orderItem = new OrderItem();
orderItem.setAsc(this.isAsc);
orderItem.setColumn(sortBy);
page.addOrder(orderItem);
return page;
}
public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
return toMpPage("create_time", false);
}
}

View File

@ -0,0 +1,45 @@
package com.hmall.common.domain;
import com.hmall.common.exception.CommonException;
import lombok.Data;
@Data
public class R<T> {
private int code;
private String msg;
private T data;
public static R<Void> ok() {
return ok(null);
}
public static <T> R<T> ok(T data) {
return new R<>(200, "OK", data);
}
public static <T> R<T> error(String msg) {
return new R<>(500, msg, null);
}
public static <T> R<T> error(int code, String msg) {
return new R<>(code, msg, null);
}
public static <T> R<T> error(CommonException e) {
return new R<>(e.getCode(), e.getMessage(), null);
}
public R() {
}
public R(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public boolean success(){
return code == 200;
}
}

View File

@ -0,0 +1,16 @@
package com.hmall.common.exception;
public class BadRequestException extends CommonException{
public BadRequestException(String message) {
super(message, 400);
}
public BadRequestException(String message, Throwable cause) {
super(message, cause, 400);
}
public BadRequestException(Throwable cause) {
super(cause, 400);
}
}

View File

@ -0,0 +1,16 @@
package com.hmall.common.exception;
public class BizIllegalException extends CommonException{
public BizIllegalException(String message) {
super(message, 500);
}
public BizIllegalException(String message, Throwable cause) {
super(message, cause, 500);
}
public BizIllegalException(Throwable cause) {
super(cause, 500);
}
}

View File

@ -0,0 +1,23 @@
package com.hmall.common.exception;
import lombok.Getter;
@Getter
public class CommonException extends RuntimeException{
private int code;
public CommonException(String message, int code) {
super(message);
this.code = code;
}
public CommonException(String message, Throwable cause, int code) {
super(message, cause);
this.code = code;
}
public CommonException(Throwable cause, int code) {
super(cause);
this.code = code;
}
}

View File

@ -0,0 +1,16 @@
package com.hmall.common.exception;
public class DbException extends CommonException{
public DbException(String message) {
super(message, 500);
}
public DbException(String message, Throwable cause) {
super(message, cause, 500);
}
public DbException(Throwable cause) {
super(cause, 500);
}
}

View File

@ -0,0 +1,16 @@
package com.hmall.common.exception;
public class ForbiddenException extends CommonException{
public ForbiddenException(String message) {
super(message, 403);
}
public ForbiddenException(String message, Throwable cause) {
super(message, cause, 403);
}
public ForbiddenException(Throwable cause) {
super(cause, 403);
}
}

View File

@ -0,0 +1,16 @@
package com.hmall.common.exception;
public class UnauthorizedException extends CommonException{
public UnauthorizedException(String message) {
super(message, 401);
}
public UnauthorizedException(String message, Throwable cause) {
super(message, cause, 401);
}
public UnauthorizedException(Throwable cause) {
super(cause, 401);
}
}

View File

@ -0,0 +1,59 @@
package com.hmall.common.utils;
import cn.hutool.core.bean.BeanUtil;
import java.util.List;
import java.util.stream.Collectors;
/**
* 继承自 hutool 的BeanUtil增加了bean转换时自定义转换器的功能
*/
public class BeanUtils extends BeanUtil {
/**
* 将原对象转换成目标对象对于字段不匹配的字段可以使用转换器处理
*
* @param source 原对象
* @param clazz 目标对象的class
* @param convert 转换器
* @param <R> 原对象类型
* @param <T> 目标对象类型
* @return 目标对象
*/
public static <R, T> T copyBean(R source, Class<T> clazz, Convert<R, T> convert) {
T target = copyBean(source, clazz);
if (convert != null) {
convert.convert(source, target);
}
return target;
}
/**
* 将原对象转换成目标对象对于字段不匹配的字段可以使用转换器处理
*
* @param source 原对象
* @param clazz 目标对象的class
* @param <R> 原对象类型
* @param <T> 目标对象类型
* @return 目标对象
*/
public static <R, T> T copyBean(R source, Class<T> clazz){
if (source == null) {
return null;
}
return toBean(source, clazz);
}
public static <R, T> List<T> copyList(List<R> list, Class<T> clazz) {
if (list == null || list.size() == 0) {
return CollUtils.emptyList();
}
return copyToList(list, clazz);
}
public static <R, T> List<T> copyList(List<R> list, Class<T> clazz, Convert<R, T> convert) {
if (list == null || list.size() == 0) {
return CollUtils.emptyList();
}
return list.stream().map(r -> copyBean(r, clazz, convert)).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,72 @@
package com.hmall.common.utils;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.util.NumberUtil;
import java.util.*;
import java.util.stream.Collectors;
/**
* 继承自 hutool 的集合工具类
*/
public class CollUtils extends CollectionUtil {
public static <T> List<T> emptyList() {
return Collections.emptyList();
}
public static <T> Set<T> emptySet() {
return Collections.emptySet();
}
public static <K,V> Map<K, V> emptyMap() {
return Collections.emptyMap();
}
public static <T> Set<T> singletonSet(T t) {
return Collections.singleton(t);
}
public static <T> List<T> singletonList(T t) {
return Collections.singletonList(t);
}
public static List<Integer> convertToInteger(List<String> originList){
return CollUtils.isNotEmpty(originList) ? originList.stream().map(NumberUtil::parseInt).collect(Collectors.toList()) : null;
}
public static List<Long> convertToLong(List<String> originLIst){
return CollUtils.isNotEmpty(originLIst) ? originLIst.stream().map(NumberUtil::parseLong).collect(Collectors.toList()) : null;
}
/**
* conjunction 为分隔符将集合转换为字符串 如果集合元素为数组Iterable或Iterator则递归组合其为字符串
* @param collection 集合
* @param conjunction 分隔符
* @param <T> 集合元素类型
* @return 连接后的字符串
* See Also: IterUtil.join(Iterator, CharSequence)
*/
public static <T> String join(Collection<T> collection, CharSequence conjunction) {
if (null == collection || collection.isEmpty()) {
return null;
}
return IterUtil.join(collection.iterator(), conjunction);
}
public static <T> String joinIgnoreNull(Collection<T> collection, CharSequence conjunction) {
if (null == collection || collection.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
for (T t : collection) {
if(t == null) continue;
sb.append(t).append(",");
}
if(sb.length() <= 0){
return null;
}
return sb.deleteCharAt(sb.length() - 1).toString();
}
}

View File

@ -0,0 +1,8 @@
package com.hmall.common.utils;
/**
* 对原对象进行计算设置到目标对象中
**/
public interface Convert<R,T>{
void convert(R origin, T target);
}

View File

@ -0,0 +1,67 @@
package com.hmall.common.utils;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@Slf4j
@Data
@Accessors(chain = true, fluent = true)
public class CookieBuilder {
private Charset charset = StandardCharsets.UTF_8;
private int maxAge = -1;
private String path = "/";
private boolean httpOnly;
private String name;
private String value;
private String domain;
private final HttpServletRequest request;
private final HttpServletResponse response;
public CookieBuilder(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
/**
* 构建cookie会对cookie值用UTF-8做URL编码避免中文乱码
*/
public void build(){
if (response == null) {
log.error("response为null无法写入cookie");
return;
}
Cookie cookie = new Cookie(name, URLEncoder.encode(value, charset));
if(StrUtil.isNotBlank(domain)) {
cookie.setDomain(domain);
}else if (request != null) {
String serverName = request.getServerName();
serverName = StrUtil.subAfter(serverName, ".", false);
cookie.setDomain("." + serverName);
}
cookie.setHttpOnly(httpOnly);
cookie.setMaxAge(maxAge);
cookie.setPath(path);
log.debug("生成cookie编码方式:{},【{}={}domain:{};maxAge={};path={};httpOnly={}】",
charset.name(), name, value, domain, maxAge, path, httpOnly);
response.addCookie(cookie);
}
/**
* 利用UTF-8对cookie值解码避免中文乱码问题
* @param cookieValue cookie原始值
* @return 解码后的值
*/
public String decode(String cookieValue){
return URLDecoder.decode(cookieValue, charset);
}
}

View File

@ -0,0 +1,28 @@
package com.hmall.common.utils;
public class UserContext {
private static final ThreadLocal<Long> tl = new ThreadLocal<>();
/**
* 保存当前登录用户信息到ThreadLocal
* @param userId 用户id
*/
public static void setUser(Long userId) {
tl.set(userId);
}
/**
* 获取当前登录用户信息
* @return 用户id
*/
public static Long getUser() {
return tl.get();
}
/**
* 移除当前登录用户信息
*/
public static void removeUser(){
tl.remove();
}
}

View File

@ -0,0 +1,151 @@
package com.hmall.common.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Map;
@Slf4j
public class WebUtils {
/**
* 获取ServletRequestAttributes
*
* @return ServletRequestAttributes
*/
public static ServletRequestAttributes getServletRequestAttributes() {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
if (ra == null) {
return null;
}
return (ServletRequestAttributes) ra;
}
/**
* 获取request
*
* @return HttpServletRequest
*/
public static HttpServletRequest getRequest() {
ServletRequestAttributes servletRequestAttributes = getServletRequestAttributes();
return servletRequestAttributes == null ? null : servletRequestAttributes.getRequest();
}
/**
* 获取response
*
* @return HttpServletResponse
*/
public static HttpServletResponse getResponse() {
ServletRequestAttributes servletRequestAttributes = getServletRequestAttributes();
return servletRequestAttributes == null ? null : servletRequestAttributes.getResponse();
}
/**
* 获取request header中的内容
*
* @param headerName 请求头名称
* @return 请求头的值
*/
public static String getHeader(String headerName) {
HttpServletRequest request = getRequest();
if (request == null) {
return null;
}
return getRequest().getHeader(headerName);
}
public static void setResponseHeader(String key, String value){
HttpServletResponse response = getResponse();
if (response == null) {
return;
}
response.setHeader(key, value);
}
public static boolean isSuccess() {
HttpServletResponse response = getResponse();
return response != null && response.getStatus() < 300;
}
/**
* 获取请求地址中的请求参数组装成 key1=value1&key2=value2
* 如果key对应多个值中间使用逗号隔开例如 key1对应value1key2对应value2value3 key1=value1&key2=value2,value3
*
* @param request
* @return 返回拼接字符串
*/
public static String getParameters(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
return getParameters(parameterMap);
}
/**
* 获取请求地址中的请求参数组装成 key1=value1&key2=value2
* 如果key对应多个值中间使用逗号隔开例如 key1对应value1key2对应value2value3 key1=value1&key2=value2,value3
*
* @param queries
* @return
*/
public static <T> String getParameters(final Map<String, T> queries) {
StringBuilder buffer = new StringBuilder();
for (Map.Entry<String, T> entry : queries.entrySet()) {
if(entry.getValue() instanceof String[]){
buffer.append(entry.getKey()).append(String.join(",", ((String[])entry.getValue())))
.append("&");
}else if(entry.getValue() instanceof Collection){
buffer.append(entry.getKey()).append(
CollUtil.join(((Collection<String>)entry.getValue()),",")
).append("&");
}
}
return buffer.length() > 0 ? buffer.substring(0, buffer.length() - 1) : StrUtil.EMPTY;
}
/**
* 获取请求url中的uri
*
* @param url
* @return
*/
public static String getUri(String url){
if(StringUtils.isEmpty(url)) {
return null;
}
String uri = url;
//uri中去掉 http:// 或者https
if(uri.contains("http://") ){
uri = uri.replace("http://", StrUtil.EMPTY);
}else if(uri.contains("https://")){
uri = uri.replace("https://", StrUtil.EMPTY);
}
int endIndex = uri.length(); //uri 在url中的最后一个字符的序号+1
if(uri.contains("?")){
endIndex = uri.indexOf("?");
}
return uri.substring(uri.indexOf("/"), endIndex);
}
public static String getRemoteAddr() {
HttpServletRequest request = getRequest();
if (request == null) {
return "";
}
return request.getRemoteAddr();
}
public static CookieBuilder cookieBuilder(){
return new CookieBuilder(getRequest(), getResponse());
}
}

View File

@ -0,0 +1,171 @@
{
"groups": [
{
"name": "hm.db"
},
{
"name": "hm.mq"
},
{
"name": "hm.swagger"
},
{
"name": "hm.jwt",
"type": "com.hmall.config.SecurityConfig",
"sourceType": "com.hmall.config.JwtProperties"
},
{
"name": "hm.auth",
"type": "com.hmall.config.MvcConfig",
"sourceType": "com.hmall.config.AuthProperties"
}
],
"properties": [
{
"name": "hm.mq.host",
"type": "java.lang.String",
"description": "rabbitmq的地址",
"defaultValue": "192.168.150.101"
},
{
"name": "hm.mq.port",
"type": "java.lang.Integer",
"description": "rabbitmq的端口",
"defaultValue": "5672"
},
{
"name": "hm.mq.vhost",
"type": "java.lang.String",
"description": "rabbitmq的virtual-host地址",
"defaultValue": "/hmxt"
},
{
"name": "hm.mq.username",
"type": "java.lang.String",
"description": "rabbitmq的用户名",
"defaultValue": "hmxt"
},
{
"name": "hm.mq.password",
"type": "java.lang.String",
"description": "rabbitmq的密码",
"defaultValue": "123321"
},
{
"name": "hm.mq.listener.retry.enable",
"type": "java.lang.Boolean",
"description": "是否开启rabbitmq的消费者重试机制",
"defaultValue": "true"
},
{
"name": "hm.mq.listener.retry.interval",
"type": "java.time.Duration",
"description": "消费者重试初始失败等待时长",
"defaultValue": "1000ms"
},
{
"name": "hm.mq.listener.retry.multiplier",
"type": "java.lang.Integer",
"description": "失败等待时长的递增倍数",
"defaultValue": "1"
},
{
"name": "hm.mq.listener.retry.max-attempts",
"type": "java.lang.Integer",
"description": "消费者重试最大重试次数",
"defaultValue": "3"
},
{
"name": "hm.mq.listener.retry.stateless",
"type": "java.lang.Boolean",
"description": "是否是无状态默认true",
"defaultValue": "true"
},
{
"name": "hm.db.host",
"type": "java.lang.String",
"description": "数据库地址",
"defaultValue": "192.168.150.101"
},
{
"name": "hm.db.port",
"type": "java.lang.Integer",
"description": "数据库端口",
"defaultValue": "3306"
},
{
"name": "hm.db.database",
"type": "java.lang.String",
"description": "数据库database名",
"defaultValue": ""
},
{
"name": "hm.db.un",
"type": "java.lang.String",
"description": "数据库用户名",
"defaultValue": "root"
},
{
"name": "hm.db.pw",
"type": "java.lang.String",
"description": "数据库密码",
"defaultValue": "123"
},
{
"name": "hm.swagger.title",
"type": "java.lang.String",
"description": "接口文档标题"
},
{
"name": "hm.swagger.description",
"type": "java.lang.String",
"description": "接口文档描述"
},
{
"name": "hm.swagger.email",
"type": "java.lang.String",
"description": "接口文档联系人邮箱"
},
{
"name": "hm.swagger.concat",
"type": "java.lang.String",
"description": "接口文档联系人"
},
{
"name": "hm.swagger.package",
"type": "java.lang.String",
"description": "接口controller扫描包"
},
{
"name": "hm.jwt.location",
"type": "java.lang.String",
"description": "秘钥存储地址"
},
{
"name": "hm.jwt.alias",
"type": "java.lang.String",
"description": "秘钥别名"
},
{
"name": "hm.jwt.password",
"type": "java.lang.String",
"description": "秘钥文件密码"
},
{
"name": "hm.jwt.tokenTTL",
"type": "java.time.Duration",
"description": "登录有效期"
},
{
"name": "hm.auth.excludePaths",
"type": "java.util.List",
"description": "登录放行的路径"
},
{
"name": "hm.auth.includePaths",
"type": "java.util.List",
"description": "登录拦截的路径"
}
],
"hints": []
}

View File

@ -0,0 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hmall.common.config.MyBatisConfig,\
com.hmall.common.config.JsonConfig

9
hm-service/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY hm-service.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]

71
hm-service/pom.xml Normal file
View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hmall</artifactId>
<groupId>com.heima</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hm-service</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!--common-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--加密-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,13 @@
package com.hmall;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.hmall.mapper")
@SpringBootApplication
public class HMallApplication {
public static void main(String[] args) {
SpringApplication.run(HMallApplication.class, args);
}
}

View File

@ -0,0 +1,13 @@
package com.hmall.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@Data
@ConfigurationProperties(prefix = "hm.auth")
public class AuthProperties {
private List<String> includePaths;
private List<String> excludePaths;
}

View File

@ -0,0 +1,16 @@
package com.hmall.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;
import java.time.Duration;
@Data
@ConfigurationProperties(prefix = "hm.jwt")
public class JwtProperties {
private Resource location;
private String password;
private String alias;
private Duration tokenTTL = Duration.ofMinutes(10);
}

View File

@ -0,0 +1,54 @@
package com.hmall.config;
import cn.hutool.core.collection.CollUtil;
import com.hmall.interceptor.LoginInterceptor;
import com.hmall.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class MvcConfig implements WebMvcConfigurer {
private final JwtTool jwtTool;
private final AuthProperties authProperties;
/* @Bean
public CommonExceptionAdvice commonExceptionAdvice(){
return new CommonExceptionAdvice();
}*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 1.添加拦截器
LoginInterceptor loginInterceptor = new LoginInterceptor(jwtTool);
InterceptorRegistration registration = registry.addInterceptor(loginInterceptor);
// 2.配置拦截路径
List<String> includePaths = authProperties.getIncludePaths();
if (CollUtil.isNotEmpty(includePaths)) {
registration.addPathPatterns(includePaths);
}
// 3.配置放行路径
List<String> excludePaths = authProperties.getExcludePaths();
if (CollUtil.isNotEmpty(excludePaths)) {
registration.excludePathPatterns(excludePaths);
}
registration.excludePathPatterns(
"/error",
"/favicon.ico",
"/v2/**",
"/v3/**",
"/swagger-resources/**",
"/webjars/**",
"/doc.html"
);
}
}

View File

@ -0,0 +1,33 @@
package com.hmall.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import java.security.KeyPair;
@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public KeyPair keyPair(JwtProperties properties){
// 获取秘钥工厂
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(
properties.getLocation(),
properties.getPassword().toCharArray());
//读取钥匙对
return keyStoreKeyFactory.getKeyPair(
properties.getAlias(),
properties.getPassword().toCharArray());
}
}

View File

@ -0,0 +1,61 @@
package com.hmall.controller;
import com.hmall.common.exception.BadRequestException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.UserContext;
import com.hmall.domain.dto.AddressDTO;
import com.hmall.domain.po.Address;
import com.hmall.service.IAddressService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* <p>
* 前端控制器
* </p>
*
* @author 虎哥
*/
@RestController
@RequestMapping("/addresses")
@RequiredArgsConstructor
@Api(tags = "收货地址管理接口")
public class AddressController {
private final IAddressService addressService;
@ApiOperation("根据id查询地址")
@GetMapping("{addressId}")
public AddressDTO findAddressById(@ApiParam("地址id") @PathVariable("addressId") Long id) {
// 1.根据id查询
Address address = addressService.getById(id);
// 2.判断当前用户
Long userId = UserContext.getUser();
if(!address.getUserId().equals(userId)){
throw new BadRequestException("地址不属于当前登录用户");
}
return BeanUtils.copyBean(address, AddressDTO.class);
}
@ApiOperation("查询当前用户地址列表")
@GetMapping
public List<AddressDTO> findMyAddresses() {
// 1.查询列表
List<Address> list = addressService.query().eq("user_id", UserContext.getUser()).list();
// 2.判空
if (CollUtils.isEmpty(list)) {
return CollUtils.emptyList();
}
// 3.转vo
return BeanUtils.copyList(list, AddressDTO.class);
}
}

View File

@ -0,0 +1,54 @@
package com.hmall.controller;
import com.hmall.domain.dto.CartFormDTO;
import com.hmall.domain.po.Cart;
import com.hmall.domain.vo.CartVO;
import com.hmall.service.ICartService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.annotations.Param;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@Api(tags = "购物车相关接口")
@RestController
@RequestMapping("/carts")
@RequiredArgsConstructor
public class CartController {
private final ICartService cartService;
@ApiOperation("添加商品到购物车")
@PostMapping
public void addItem2Cart(@Valid @RequestBody CartFormDTO cartFormDTO){
cartService.addItem2Cart(cartFormDTO);
}
@ApiOperation("更新购物车数据")
@PutMapping
public void updateCart(@RequestBody Cart cart){
cartService.updateById(cart);
}
@ApiOperation("删除购物车中商品")
@DeleteMapping("{id}")
public void deleteCartItem(@Param ("购物车条目id")@PathVariable("id") Long id){
cartService.removeById(id);
}
@ApiOperation("查询购物车列表")
@GetMapping
public List<CartVO> queryMyCarts(){
return cartService.queryMyCarts();
}
@ApiOperation("批量删除购物车中商品")
@ApiImplicitParam(name = "ids", value = "购物车条目id集合")
@DeleteMapping
public void deleteCartItemByIds(@RequestParam("ids") List<Long> ids){
cartService.removeByItemIds(ids);
}
}

View File

@ -0,0 +1,29 @@
package com.hmall.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@RestController
@RequestMapping("hi")
public class HelloController {
private final Map<String, AtomicInteger> countMap = new HashMap<>();
@GetMapping
public String hello(HttpServletRequest request) throws InterruptedException {
Thread.sleep(300);
String ip = request.getRemoteAddr();
AtomicInteger ai = countMap.get(ip);
if (ai == null) {
ai = new AtomicInteger(0);
countMap.put(ip, ai);
}
return String.format("<h5>欢迎访问黑马商城, 这是您第%d次访问<h5>", ai.incrementAndGet());
}
}

View File

@ -0,0 +1,83 @@
package com.hmall.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmall.common.domain.PageDTO;
import com.hmall.common.domain.PageQuery;
import com.hmall.common.utils.BeanUtils;
import com.hmall.domain.dto.ItemDTO;
import com.hmall.domain.dto.OrderDetailDTO;
import com.hmall.domain.po.Item;
import com.hmall.service.IItemService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "商品管理相关接口")
@RestController
@RequestMapping("/items")
@RequiredArgsConstructor
public class ItemController {
private final IItemService itemService;
@ApiOperation("分页查询商品")
@GetMapping("/page")
public PageDTO<ItemDTO> queryItemByPage(PageQuery query) {
// 1.分页查询
Page<Item> result = itemService.page(query.toMpPage("update_time", false));
// 2.封装并返回
return PageDTO.of(result, ItemDTO.class);
}
@ApiOperation("根据id批量查询商品")
@GetMapping
public List<ItemDTO> queryItemByIds(@RequestParam("ids") List<Long> ids){
return itemService.queryItemByIds(ids);
}
@ApiOperation("根据id查询商品")
@GetMapping("{id}")
public ItemDTO queryItemById(@PathVariable("id") Long id) {
return BeanUtils.copyBean(itemService.getById(id), ItemDTO.class);
}
@ApiOperation("新增商品")
@PostMapping
public void saveItem(@RequestBody ItemDTO item) {
// 新增
itemService.save(BeanUtils.copyBean(item, Item.class));
}
@ApiOperation("更新商品状态")
@PutMapping("/status/{id}/{status}")
public void updateItemStatus(@PathVariable("id") Long id, @PathVariable("status") Integer status){
Item item = new Item();
item.setId(id);
item.setStatus(status);
itemService.updateById(item);
}
@ApiOperation("更新商品")
@PutMapping
public void updateItem(@RequestBody ItemDTO item) {
// 不允许修改商品状态所以强制设置为null更新时就会忽略该字段
item.setStatus(null);
// 更新
itemService.updateById(BeanUtils.copyBean(item, Item.class));
}
@ApiOperation("根据id删除商品")
@DeleteMapping("{id}")
public void deleteItemById(@PathVariable("id") Long id) {
itemService.removeById(id);
}
@ApiOperation("批量扣减库存")
@PutMapping("/stock/deduct")
public void deductStock(@RequestBody List<OrderDetailDTO> items){
itemService.deductStock(items);
}
}

View File

@ -0,0 +1,39 @@
package com.hmall.controller;
import com.hmall.common.utils.BeanUtils;
import com.hmall.domain.dto.OrderFormDTO;
import com.hmall.domain.vo.OrderVO;
import com.hmall.service.IOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.annotations.Param;
import org.springframework.web.bind.annotation.*;
@Api(tags = "订单管理接口")
@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrderController {
private final IOrderService orderService;
@ApiOperation("根据id查询订单")
@GetMapping("{id}")
public OrderVO queryOrderById(@Param ("订单id")@PathVariable("id") Long orderId) {
return BeanUtils.copyBean(orderService.getById(orderId), OrderVO.class);
}
@ApiOperation("创建订单")
@PostMapping
public Long createOrder(@RequestBody OrderFormDTO orderFormDTO){
return orderService.createOrder(orderFormDTO);
}
@ApiOperation("标记订单已支付")
@ApiImplicitParam(name = "orderId", value = "订单id", paramType = "path")
@PutMapping("/{orderId}")
public void markOrderPaySuccess(@PathVariable("orderId") Long orderId) {
orderService.markOrderPaySuccess(orderId);
}
}

View File

@ -0,0 +1,39 @@
package com.hmall.controller;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.domain.dto.PayApplyDTO;
import com.hmall.domain.dto.PayOrderFormDTO;
import com.hmall.enums.PayType;
import com.hmall.service.IPayOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Api(tags = "支付相关接口")
@RestController
@RequestMapping("pay-orders")
@RequiredArgsConstructor
public class PayController {
private final IPayOrderService payOrderService;
@ApiOperation("生成支付单")
@PostMapping
public String applyPayOrder(@RequestBody PayApplyDTO applyDTO){
if(!PayType.BALANCE.equalsValue(applyDTO.getPayType())){
// 目前只支持余额支付
throw new BizIllegalException("抱歉,目前只支持余额支付");
}
return payOrderService.applyPayOrder(applyDTO);
}
@ApiOperation("尝试基于用户余额支付")
@ApiImplicitParam(value = "支付单id", name = "id")
@PostMapping("{id}")
public void tryPayOrderByBalance(@PathVariable("id") Long id, @RequestBody PayOrderFormDTO payOrderFormDTO){
payOrderFormDTO.setId(id);
payOrderService.tryPayOrderByBalance(payOrderFormDTO);
}
}

View File

@ -0,0 +1,39 @@
package com.hmall.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmall.common.domain.PageDTO;
import com.hmall.domain.dto.ItemDTO;
import com.hmall.domain.po.Item;
import com.hmall.domain.query.ItemPageQuery;
import com.hmall.service.IItemService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "搜索相关接口")
@RestController
@RequestMapping("/search")
@RequiredArgsConstructor
public class SearchController {
private final IItemService itemService;
@ApiOperation("搜索商品")
@GetMapping("/list")
public PageDTO<ItemDTO> search(ItemPageQuery query) {
// 分页查询
Page<Item> result = itemService.lambdaQuery()
.like(StrUtil.isNotBlank(query.getKey()), Item::getName, query.getKey())
.eq(StrUtil.isNotBlank(query.getBrand()), Item::getBrand, query.getBrand())
.eq(StrUtil.isNotBlank(query.getCategory()), Item::getCategory, query.getCategory())
.eq(Item::getStatus, 1)
.between(query.getMaxPrice() != null, Item::getPrice, query.getMinPrice(), query.getMaxPrice())
.page(query.toMpPage("update_time", false));
// 封装并返回
return PageDTO.of(result, ItemDTO.class);
}
}

View File

@ -0,0 +1,38 @@
package com.hmall.controller;
import com.hmall.domain.dto.LoginFormDTO;
import com.hmall.domain.vo.UserLoginVO;
import com.hmall.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@Api(tags = "用户相关接口")
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;
@ApiOperation("用户登录接口")
@PostMapping("login")
public UserLoginVO login(@RequestBody @Validated LoginFormDTO loginFormDTO){
return userService.login(loginFormDTO);
}
@ApiOperation("扣减余额")
@ApiImplicitParams({
@ApiImplicitParam(name = "pw", value = "支付密码"),
@ApiImplicitParam(name = "amount", value = "支付金额")
})
@PutMapping("/money/deduct")
public void deductMoney(@RequestParam("pw") String pw,@RequestParam("amount") Integer amount){
userService.deductMoney(pw, amount);
}
}

View File

@ -0,0 +1,28 @@
package com.hmall.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "收货地址实体")
public class AddressDTO {
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("")
private String province;
@ApiModelProperty("")
private String city;
@ApiModelProperty("县/区")
private String town;
@ApiModelProperty("手机")
private String mobile;
@ApiModelProperty("详细地址")
private String street;
@ApiModelProperty("联系人")
private String contact;
@ApiModelProperty("是否是默认 1默认 0否")
private Integer isDefault;
@ApiModelProperty("备注")
private String notes;
}

View File

@ -0,0 +1,20 @@
package com.hmall.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "新增购物车商品表单实体")
public class CartFormDTO {
@ApiModelProperty("商品id")
private Long itemId;
@ApiModelProperty("商品标题")
private String name;
@ApiModelProperty("商品动态属性键值集")
private String spec;
@ApiModelProperty("价格,单位:分")
private Integer price;
@ApiModelProperty("商品图片")
private String image;
}

View File

@ -0,0 +1,34 @@
package com.hmall.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "商品实体")
public class ItemDTO {
@ApiModelProperty("商品id")
private Long id;
@ApiModelProperty("SKU名称")
private String name;
@ApiModelProperty("价格(分)")
private Integer price;
@ApiModelProperty("库存数量")
private Integer stock;
@ApiModelProperty("商品图片")
private String image;
@ApiModelProperty("类目名称")
private String category;
@ApiModelProperty("品牌名称")
private String brand;
@ApiModelProperty("规格")
private String spec;
@ApiModelProperty("销量")
private Integer sold;
@ApiModelProperty("评论数")
private Integer commentCount;
@ApiModelProperty("是否是推广广告true/false")
private Boolean isAD;
@ApiModelProperty("商品状态 1-正常2-下架3-删除")
private Integer status;
}

View File

@ -0,0 +1,20 @@
package com.hmall.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
@ApiModel(description = "登录表单实体")
public class LoginFormDTO {
@ApiModelProperty(value = "用户名", required = true)
@NotNull(message = "用户名不能为空")
private String username;
@NotNull(message = "密码不能为空")
@ApiModelProperty(value = "用户名", required = true)
private String password;
@ApiModelProperty(value = "是否记住我", required = false)
private Boolean rememberMe = false;
}

View File

@ -0,0 +1,16 @@
package com.hmall.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
@ApiModel(description = "订单明细条目")
@Data
@Accessors(chain = true)
public class OrderDetailDTO {
@ApiModelProperty("商品id")
private Long itemId;
@ApiModelProperty("商品购买数量")
private Integer num;
}

View File

@ -0,0 +1,18 @@
package com.hmall.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(description = "交易下单表单实体")
public class OrderFormDTO {
@ApiModelProperty("收货地址id")
private Long addressId;
@ApiModelProperty("支付类型")
private Integer paymentType;
@ApiModelProperty("下单商品列表")
private List<OrderDetailDTO> details;
}

View File

@ -0,0 +1,30 @@
package com.hmall.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
@Builder
@ApiModel(description = "支付下单表单实体")
public class PayApplyDTO {
@ApiModelProperty("业务订单id不能为空")
@NotNull(message = "业务订单id不能为空")
private Long bizOrderNo;
@ApiModelProperty("支付金额必须为正数")
@Min(value = 1, message = "支付金额必须为正数")
private Integer amount;
@ApiModelProperty("支付渠道编码不能为空")
@NotNull(message = "支付渠道编码不能为空")
private String payChannelCode;
@ApiModelProperty("支付方式不能为空")
@NotNull(message = "支付方式不能为空")
private Integer payType;
@ApiModelProperty("订单中的商品信息不能为空")
@NotNull(message = "订单中的商品信息不能为空")
private String orderInfo;
}

View File

@ -0,0 +1,20 @@
package com.hmall.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
@Builder
@ApiModel(description = "支付确认表单实体")
public class PayOrderFormDTO {
@ApiModelProperty("支付订单id不能为空")
@NotNull(message = "支付订单id不能为空")
private Long id;
@ApiModelProperty("支付密码")
@NotNull(message = "支付密码")
private String pw;
}

View File

@ -0,0 +1,77 @@
package com.hmall.domain.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* <p>
*
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("address")
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
*
*/
private String province;
/**
*
*/
private String city;
/**
* /
*/
private String town;
/**
* 手机
*/
private String mobile;
/**
* 详细地址
*/
private String street;
/**
* 联系人
*/
private String contact;
/**
* 是否是默认 1默认 0否
*/
private Integer isDefault;
/**
* 备注
*/
private String notes;
}

View File

@ -0,0 +1,80 @@
package com.hmall.domain.po;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
* 订单详情表
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("cart")
public class Cart implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 购物车条目id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户id
*/
private Long userId;
/**
* sku商品id
*/
private Long itemId;
/**
* 购买数量
*/
private Integer num;
/**
* 商品标题
*/
private String name;
/**
* 商品动态属性键值集
*/
private String spec;
/**
* 价格,单位
*/
private Integer price;
/**
* 商品图片
*/
private String image;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,113 @@
package com.hmall.domain.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 商品表
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("item")
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 商品id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* SKU名称
*/
private String name;
/**
* 价格
*/
private Integer price;
/**
* 库存数量
*/
private Integer stock;
/**
* 商品图片
*/
private String image;
/**
* 类目名称
*/
private String category;
/**
* 品牌名称
*/
private String brand;
/**
* 规格
*/
private String spec;
/**
* 销量
*/
private Integer sold;
/**
* 评论数
*/
private Integer commentCount;
/**
* 是否是推广广告true/false
*/
@TableField("isAD")
private Boolean isAD;
/**
* 商品状态 1-正常2-下架3-删除
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 创建人
*/
private Long creater;
/**
* 修改人
*/
private Long updater;
}

View File

@ -0,0 +1,91 @@
package com.hmall.domain.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("`order`")
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 订单id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 总金额单位为分
*/
private Integer totalFee;
/**
* 支付类型1支付宝2微信3扣减余额
*/
private Integer paymentType;
/**
* 用户id
*/
private Long userId;
/**
* 订单的状态1未付款 2已付款,未发货 3已发货,未确认 4确认收货交易成功 5交易取消订单关闭 6交易结束已评价
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 支付时间
*/
private LocalDateTime payTime;
/**
* 发货时间
*/
private LocalDateTime consignTime;
/**
* 交易完成时间
*/
private LocalDateTime endTime;
/**
* 交易关闭时间
*/
private LocalDateTime closeTime;
/**
* 评价时间
*/
private LocalDateTime commentTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,80 @@
package com.hmall.domain.po;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
* 订单详情表
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("order_detail")
public class OrderDetail implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 订单详情id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 订单id
*/
private Long orderId;
/**
* sku商品id
*/
private Long itemId;
/**
* 购买数量
*/
private Integer num;
/**
* 商品标题
*/
private String name;
/**
* 商品动态属性键值集
*/
private String spec;
/**
* 价格,单位
*/
private Integer price;
/**
* 商品图片
*/
private String image;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,86 @@
package com.hmall.domain.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("order_logistics")
public class OrderLogistics implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 订单id与订单表一对一
*/
@TableId(value = "order_id", type = IdType.INPUT)
private Long orderId;
/**
* 物流单号
*/
private String logisticsNumber;
/**
* 物流公司名称
*/
private String logisticsCompany;
/**
* 收件人
*/
private String contact;
/**
* 收件人手机号码
*/
private String mobile;
/**
*
*/
private String province;
/**
*
*/
private String city;
/**
*
*/
private String town;
/**
* 街道
*/
private String street;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,123 @@
package com.hmall.domain.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 支付订单
* </p>
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("pay_order")
public class PayOrder implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 业务订单号
*/
private Long bizOrderNo;
/**
* 支付单号
*/
private Long payOrderNo;
/**
* 支付用户id
*/
private Long bizUserId;
/**
* 支付渠道编码
*/
private String payChannelCode;
/**
* 支付金额单位分
*/
private Integer amount;
/**
* 支付类型1h5,2:小程序3公众号4扫码5余额支付
*/
private Integer payType;
/**
* 支付状态0待提交1:待支付2支付超时或取消3支付成功
*/
private Integer status;
/**
* 拓展字段用于传递不同渠道单独处理的字段
*/
private String expandJson;
/**
* 第三方返回业务码
*/
private String resultCode;
/**
* 第三方返回提示信息
*/
private String resultMsg;
/**
* 支付成功时间
*/
private LocalDateTime paySuccessTime;
/**
* 支付超时时间
*/
private LocalDateTime payOverTime;
/**
* 支付二维码链接
*/
private String qrCodeUrl;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 创建人
*/
private Long creater;
/**
* 更新人
*/
private Long updater;
/**
* 逻辑删除
*/
private Boolean isDelete;
}

View File

@ -0,0 +1,66 @@
package com.hmall.domain.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.hmall.enums.UserStatus;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 用户表
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码加密存储
*/
private String password;
/**
* 注册手机号
*/
private String phone;
/**
* 创建时间
*/
private LocalDateTime createTime;
private LocalDateTime updateTime;
/**
* 使用状态1正常 2冻结
*/
private UserStatus status;
/**
* 账户余额
*/
private Integer balance;
}

View File

@ -0,0 +1,23 @@
package com.hmall.domain.query;
import com.hmall.common.domain.PageQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "商品分页查询条件")
public class ItemPageQuery extends PageQuery {
@ApiModelProperty("搜索关键字")
private String key;
@ApiModelProperty("商品分类")
private String category;
@ApiModelProperty("商品品牌")
private String brand;
@ApiModelProperty("价格最小值")
private Integer minPrice;
@ApiModelProperty("价格最大值")
private Integer maxPrice;
}

View File

@ -0,0 +1,43 @@
package com.hmall.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* <p>
* 订单详情表
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Data
@ApiModel(description = "购物车VO实体")
public class CartVO {
@ApiModelProperty("购物车条目id ")
private Long id;
@ApiModelProperty("sku商品id")
private Long itemId;
@ApiModelProperty("购买数量")
private Integer num;
@ApiModelProperty("商品标题")
private String name;
@ApiModelProperty("商品动态属性键值集")
private String spec;
@ApiModelProperty("价格,单位:分")
private Integer price;
@ApiModelProperty("商品最新价格")
private Integer newPrice;
@ApiModelProperty("商品最新状态")
private Integer status = 1;
@ApiModelProperty("商品最新库存")
private Integer stock = 10;
@ApiModelProperty("商品图片")
private String image;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,34 @@
package com.hmall.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@ApiModel(description = "订单页面VO")
public class OrderVO {
@ApiModelProperty("订单id")
private Long id;
@ApiModelProperty("总金额,单位为分")
private Integer totalFee;
@ApiModelProperty("支付类型1、支付宝2、微信3、扣减余额")
private Integer paymentType;
@ApiModelProperty("用户id")
private Long userId;
@ApiModelProperty("订单的状态1、未付款 2、已付款,未发货 3、已发货,未确认 4、确认收货交易成功 5、交易取消订单关闭 6、交易结束已评价")
private Integer status;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("支付时间")
private LocalDateTime payTime;
@ApiModelProperty("发货时间")
private LocalDateTime consignTime;
@ApiModelProperty("交易完成时间")
private LocalDateTime endTime;
@ApiModelProperty("交易关闭时间")
private LocalDateTime closeTime;
@ApiModelProperty("评价时间")
private LocalDateTime commentTime;
}

View File

@ -0,0 +1,49 @@
package com.hmall.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* <p>
* 支付订单
* </p>
*/
@Data
@ApiModel(description = "支付单vo实体")
public class PayOrderVO {
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("业务订单号")
private Long bizOrderNo;
@ApiModelProperty("支付单号")
private Long payOrderNo;
@ApiModelProperty("支付用户id")
private Long bizUserId;
@ApiModelProperty("支付渠道编码")
private String payChannelCode;
@ApiModelProperty("支付金额,单位分")
private Integer amount;
@ApiModelProperty("付类型1h5,2:小程序3公众号4扫码5余额支付")
private Integer payType;
@ApiModelProperty("付状态0待提交1:待支付2支付超时或取消3支付成功")
private Integer status;
@ApiModelProperty("拓展字段,用于传递不同渠道单独处理的字段")
private String expandJson;
@ApiModelProperty("第三方返回业务码")
private String resultCode;
@ApiModelProperty("第三方返回提示信息")
private String resultMsg;
@ApiModelProperty("支付成功时间")
private LocalDateTime paySuccessTime;
@ApiModelProperty("支付超时时间")
private LocalDateTime payOverTime;
@ApiModelProperty("支付二维码链接")
private String qrCodeUrl;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,11 @@
package com.hmall.domain.vo;
import lombok.Data;
@Data
public class UserLoginVO {
private String token;
private Long userId;
private String username;
private Integer balance;
}

View File

@ -0,0 +1,25 @@
package com.hmall.enums;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
@Getter
public enum PayChannel {
wxPay("微信支付"),
aliPay("支付宝支付"),
balance("余额支付"),
;
private final String desc;
PayChannel(String desc) {
this.desc = desc;
}
public static String desc(String value){
if (StrUtil.isBlank(value)) {
return "";
}
return PayChannel.valueOf(value).getDesc();
}
}

View File

@ -0,0 +1,27 @@
package com.hmall.enums;
import lombok.Getter;
@Getter
public enum PayStatus {
NOT_COMMIT(0, "未提交"),
WAIT_BUYER_PAY(1, "待支付"),
TRADE_CLOSED(2, "已关闭"),
TRADE_SUCCESS(3, "支付成功"),
TRADE_FINISHED(3, "支付成功"),
;
private final int value;
private final String desc;
PayStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
public boolean equalsValue(Integer value){
if (value == null) {
return false;
}
return getValue() == value;
}
}

View File

@ -0,0 +1,27 @@
package com.hmall.enums;
import lombok.Getter;
@Getter
public enum PayType{
JSAPI(1, "网页支付JS"),
MINI_APP(2, "小程序支付"),
APP(3, "APP支付"),
NATIVE(4, "扫码支付"),
BALANCE(5, "余额支付"),
;
private final int value;
private final String desc;
PayType(int value, String desc) {
this.value = value;
this.desc = desc;
}
public boolean equalsValue(Integer value){
if (value == null) {
return false;
}
return getValue() == value;
}
}

View File

@ -0,0 +1,30 @@
package com.hmall.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.hmall.common.exception.BadRequestException;
import lombok.Getter;
@Getter
public enum UserStatus {
FROZEN(0, "禁止使用"),
NORMAL(1, "已激活"),
;
@EnumValue
int value;
String desc;
UserStatus(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
public static UserStatus of(int value) {
if (value == 0) {
return FROZEN;
}
if (value == 1) {
return NORMAL;
}
throw new BadRequestException("账户状态错误");
}
}

View File

@ -0,0 +1,33 @@
package com.hmall.interceptor;
import com.hmall.common.utils.UserContext;
import com.hmall.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RequiredArgsConstructor
public class LoginInterceptor implements HandlerInterceptor {
private final JwtTool jwtTool;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的 token
String token = request.getHeader("authorization");
// 2.校验token
Long userId = jwtTool.parseToken(token);
// 3.存入上下文
UserContext.setUser(userId);
// 4.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理用户
UserContext.removeUser();
}
}

View File

@ -0,0 +1,16 @@
package com.hmall.mapper;
import com.hmall.domain.po.Address;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface AddressMapper extends BaseMapper<Address> {
}

View File

@ -0,0 +1,20 @@
package com.hmall.mapper;
import com.hmall.domain.po.Cart;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/**
* <p>
* 订单详情表 Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface CartMapper extends BaseMapper<Cart> {
@Update("UPDATE cart SET num = num + 1 WHERE user_id = #{userId} AND item_id = #{itemId}")
void updateNum(@Param("itemId") Long itemId, @Param("userId") Long userId);
}

View File

@ -0,0 +1,21 @@
package com.hmall.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hmall.domain.dto.OrderDetailDTO;
import com.hmall.domain.po.Item;
import org.apache.ibatis.annotations.Update;
/**
* <p>
* 商品表 Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface ItemMapper extends BaseMapper<Item> {
@Update("UPDATE item SET stock = stock - #{num} WHERE id = #{itemId}")
void updateStock(OrderDetailDTO orderDetail);
}

View File

@ -0,0 +1,16 @@
package com.hmall.mapper;
import com.hmall.domain.po.OrderDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 订单详情表 Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface OrderDetailMapper extends BaseMapper<OrderDetail> {
}

View File

@ -0,0 +1,16 @@
package com.hmall.mapper;
import com.hmall.domain.po.OrderLogistics;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface OrderLogisticsMapper extends BaseMapper<OrderLogistics> {
}

View File

@ -0,0 +1,16 @@
package com.hmall.mapper;
import com.hmall.domain.po.Order;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface OrderMapper extends BaseMapper<Order> {
}

View File

@ -0,0 +1,16 @@
package com.hmall.mapper;
import com.hmall.domain.po.PayOrder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 支付订单 Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-16
*/
public interface PayOrderMapper extends BaseMapper<PayOrder> {
}

View File

@ -0,0 +1,19 @@
package com.hmall.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hmall.domain.po.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/**
* <p>
* 用户表 Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface UserMapper extends BaseMapper<User> {
@Update("update user set balance = balance - ${totalFee} where id = #{userId}")
void updateMoney(@Param("userId") Long userId, @Param("totalFee") Integer totalFee);
}

View File

@ -0,0 +1,16 @@
package com.hmall.service;
import com.hmall.domain.po.Address;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface IAddressService extends IService<Address> {
}

View File

@ -0,0 +1,26 @@
package com.hmall.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hmall.domain.dto.CartFormDTO;
import com.hmall.domain.po.Cart;
import com.hmall.domain.vo.CartVO;
import java.util.Collection;
import java.util.List;
/**
* <p>
* 订单详情表 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface ICartService extends IService<Cart> {
void addItem2Cart(CartFormDTO cartFormDTO);
List<CartVO> queryMyCarts();
void removeByItemIds(Collection<Long> itemIds);
}

View File

@ -0,0 +1,24 @@
package com.hmall.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hmall.domain.dto.ItemDTO;
import com.hmall.domain.dto.OrderDetailDTO;
import com.hmall.domain.po.Item;
import java.util.Collection;
import java.util.List;
/**
* <p>
* 商品表 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface IItemService extends IService<Item> {
void deductStock(List<OrderDetailDTO> items);
List<ItemDTO> queryItemByIds(Collection<Long> ids);
}

View File

@ -0,0 +1,16 @@
package com.hmall.service;
import com.hmall.domain.po.OrderDetail;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 订单详情表 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface IOrderDetailService extends IService<OrderDetail> {
}

View File

@ -0,0 +1,16 @@
package com.hmall.service;
import com.hmall.domain.po.OrderLogistics;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface IOrderLogisticsService extends IService<OrderLogistics> {
}

View File

@ -0,0 +1,20 @@
package com.hmall.service;
import com.hmall.domain.dto.OrderFormDTO;
import com.hmall.domain.po.Order;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface IOrderService extends IService<Order> {
Long createOrder(OrderFormDTO orderFormDTO);
void markOrderPaySuccess(Long orderId);
}

View File

@ -0,0 +1,21 @@
package com.hmall.service;
import com.hmall.domain.dto.PayApplyDTO;
import com.hmall.domain.dto.PayOrderFormDTO;
import com.hmall.domain.po.PayOrder;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 支付订单 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-16
*/
public interface IPayOrderService extends IService<PayOrder> {
String applyPayOrder(PayApplyDTO applyDTO);
void tryPayOrderByBalance(PayOrderFormDTO payOrderFormDTO);
}

View File

@ -0,0 +1,21 @@
package com.hmall.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hmall.domain.dto.LoginFormDTO;
import com.hmall.domain.po.User;
import com.hmall.domain.vo.UserLoginVO;
/**
* <p>
* 用户表 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface IUserService extends IService<User> {
UserLoginVO login(LoginFormDTO loginFormDTO);
void deductMoney(String pw, Integer totalFee);
}

View File

@ -0,0 +1,20 @@
package com.hmall.service.impl;
import com.hmall.domain.po.Address;
import com.hmall.mapper.AddressMapper;
import com.hmall.service.IAddressService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Service
public class AddressServiceImpl extends ServiceImpl<AddressMapper, Address> implements IAddressService {
}

View File

@ -0,0 +1,133 @@
package com.hmall.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.UserContext;
import com.hmall.domain.dto.CartFormDTO;
import com.hmall.domain.dto.ItemDTO;
import com.hmall.domain.po.Cart;
import com.hmall.domain.vo.CartVO;
import com.hmall.mapper.CartMapper;
import com.hmall.service.ICartService;
import com.hmall.service.IItemService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* <p>
* 订单详情表 服务实现类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Service
@RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
private final IItemService itemService;
@Override
public void addItem2Cart(CartFormDTO cartFormDTO) {
// 1.获取登录用户
Long userId = UserContext.getUser();
// 2.判断是否已经存在
if(checkItemExists(cartFormDTO.getItemId(), userId)){
// 2.1.存在则更新数量
baseMapper.updateNum(cartFormDTO.getItemId(), userId);
return;
}
// 2.2.不存在判断是否超过购物车数量
checkCartsFull(userId);
// 3.新增购物车条目
// 3.1.转换PO
Cart cart = BeanUtils.copyBean(cartFormDTO, Cart.class);
// 3.2.保存当前用户
cart.setUserId(userId);
// 3.3.保存到数据库
save(cart);
}
@Override
public List<CartVO> queryMyCarts() {
// 1.查询我的购物车列表
List<Cart> carts = lambdaQuery().eq(Cart::getUserId, UserContext.getUser()).list();
if (CollUtils.isEmpty(carts)) {
return CollUtils.emptyList();
}
// 2.转换VO
List<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);
// 3.处理VO中的商品信息
handleCartItems(vos);
// 4.返回
return vos;
}
private void handleCartItems(List<CartVO> vos) {
// 1.获取商品id
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 2.查询商品
List<ItemDTO> items = itemService.queryItemByIds(itemIds);
if (CollUtils.isEmpty(items)) {
return;
}
// 3.转为 id item的map
Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
// 4.写入vo
for (CartVO v : vos) {
ItemDTO item = itemMap.get(v.getItemId());
if (item == null) {
continue;
}
v.setNewPrice(item.getPrice());
v.setStatus(item.getStatus());
v.setStock(item.getStock());
}
}
@Override
public void removeByItemIds(Collection<Long> itemIds) {
// 1.构建删除条件userId和itemId
QueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>();
queryWrapper.lambda()
.eq(Cart::getUserId, UserContext.getUser())
.in(Cart::getItemId, itemIds);
// 2.删除
remove(queryWrapper);
}
private void checkCartsFull(Long userId) {
long count = lambdaQuery()
.eq(Cart::getUserId, userId)
.count(); // 这里返回 long
if (count >= 10) {
throw new BizIllegalException(
StrUtil.format("用户购物车课程不能超过{}", 10)
);
}
}
private boolean checkItemExists(Long itemId, Long userId) {
long count = lambdaQuery()
.eq(Cart::getUserId, userId)
.eq(Cart::getItemId, itemId)
.count();
return count > 0;
}
}

View File

@ -0,0 +1,46 @@
package com.hmall.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.domain.dto.ItemDTO;
import com.hmall.domain.dto.OrderDetailDTO;
import com.hmall.domain.po.Item;
import com.hmall.mapper.ItemMapper;
import com.hmall.service.IItemService;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
/**
* <p>
* 商品表 服务实现类
* </p>
*
* @author 虎哥
*/
@Service
public class ItemServiceImpl extends ServiceImpl<ItemMapper, Item> implements IItemService {
@Override
public void deductStock(List<OrderDetailDTO> items) {
String sqlStatement = "com.hmall.item.mapper.ItemMapper.updateStock";
boolean r = false;
try {
r = executeBatch(items, (sqlSession, entity) -> sqlSession.update(sqlStatement, entity));
} catch (Exception e) {
throw new BizIllegalException("更新库存异常,可能是库存不足!", e);
}
if (!r) {
throw new BizIllegalException("库存不足!");
}
}
@Override
public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
return BeanUtils.copyList(listByIds(ids), ItemDTO.class);
}
}

View File

@ -0,0 +1,20 @@
package com.hmall.service.impl;
import com.hmall.domain.po.OrderDetail;
import com.hmall.mapper.OrderDetailMapper;
import com.hmall.service.IOrderDetailService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 订单详情表 服务实现类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Service
public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements IOrderDetailService {
}

View File

@ -0,0 +1,20 @@
package com.hmall.service.impl;
import com.hmall.domain.po.OrderLogistics;
import com.hmall.mapper.OrderLogisticsMapper;
import com.hmall.service.IOrderLogisticsService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Service
public class OrderLogisticsServiceImpl extends ServiceImpl<OrderLogisticsMapper, OrderLogistics> implements IOrderLogisticsService {
}

View File

@ -0,0 +1,112 @@
package com.hmall.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.common.exception.BadRequestException;
import com.hmall.common.utils.UserContext;
import com.hmall.domain.dto.ItemDTO;
import com.hmall.domain.dto.OrderDetailDTO;
import com.hmall.domain.dto.OrderFormDTO;
import com.hmall.domain.po.Order;
import com.hmall.domain.po.OrderDetail;
import com.hmall.mapper.OrderMapper;
import com.hmall.service.ICartService;
import com.hmall.service.IItemService;
import com.hmall.service.IOrderDetailService;
import com.hmall.service.IOrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* <p>
* 服务实现类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
private final IItemService itemService;
private final IOrderDetailService detailService;
private final ICartService cartService;
@Override
@Transactional
public Long createOrder(OrderFormDTO orderFormDTO) {
// 1.订单数据
Order order = new Order();
// 1.1.查询商品
List<OrderDetailDTO> detailDTOS = orderFormDTO.getDetails();
// 1.2.获取商品id和数量的Map
Map<Long, Integer> itemNumMap = detailDTOS.stream()
.collect(Collectors.toMap(OrderDetailDTO::getItemId, OrderDetailDTO::getNum));
Set<Long> itemIds = itemNumMap.keySet();
// 1.3.查询商品
List<ItemDTO> items = itemService.queryItemByIds(itemIds);
if (items == null || items.size() < itemIds.size()) {
throw new BadRequestException("商品不存在");
}
// 1.4.基于商品价格购买数量计算商品总价totalFee
int total = 0;
for (ItemDTO item : items) {
total += item.getPrice() * itemNumMap.get(item.getId());
}
order.setTotalFee(total);
// 1.5.其它属性
order.setPaymentType(orderFormDTO.getPaymentType());
order.setUserId(UserContext.getUser());
order.setStatus(1);
// 1.6.将Order写入数据库order表中
save(order);
// 2.保存订单详情
List<OrderDetail> details = buildDetails(order.getId(), items, itemNumMap);
detailService.saveBatch(details);
// 3.清理购物车商品
cartService.removeByItemIds(itemIds);
// 4.扣减库存
try {
itemService.deductStock(detailDTOS);
} catch (Exception e) {
throw new RuntimeException("库存不足!");
}
return order.getId();
}
@Override
public void markOrderPaySuccess(Long orderId) {
Order order = new Order();
order.setId(orderId);
order.setStatus(2);
order.setPayTime(LocalDateTime.now());
updateById(order);
}
private List<OrderDetail> buildDetails(Long orderId, List<ItemDTO> items, Map<Long, Integer> numMap) {
List<OrderDetail> details = new ArrayList<>(items.size());
for (ItemDTO item : items) {
OrderDetail detail = new OrderDetail();
detail.setName(item.getName());
detail.setSpec(item.getSpec());
detail.setPrice(item.getPrice());
detail.setNum(numMap.get(item.getId()));
detail.setItemId(item.getId());
detail.setImage(item.getImage());
detail.setOrderId(orderId);
details.add(detail);
}
return details;
}
}

View File

@ -0,0 +1,133 @@
package com.hmall.service.impl;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.UserContext;
import com.hmall.domain.dto.PayApplyDTO;
import com.hmall.domain.dto.PayOrderFormDTO;
import com.hmall.domain.po.Order;
import com.hmall.domain.po.PayOrder;
import com.hmall.enums.PayStatus;
import com.hmall.mapper.PayOrderMapper;
import com.hmall.service.IOrderService;
import com.hmall.service.IPayOrderService;
import com.hmall.service.IUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
/**
* <p>
* 支付订单 服务实现类
* </p>
*
* @author 虎哥
* @since 2023-05-16
*/
@Service
@RequiredArgsConstructor
public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements IPayOrderService {
private final IUserService userService;
private final IOrderService orderService;
@Override
public String applyPayOrder(PayApplyDTO applyDTO) {
// 1.幂等性校验
PayOrder payOrder = checkIdempotent(applyDTO);
// 2.返回结果
return payOrder.getId().toString();
}
@Override
@Transactional
public void tryPayOrderByBalance(PayOrderFormDTO payOrderFormDTO) {
// 1.查询支付单
PayOrder po = getById(payOrderFormDTO.getId());
// 2.判断状态
if(!PayStatus.WAIT_BUYER_PAY.equalsValue(po.getStatus())){
// 订单不是未支付状态异常
throw new BizIllegalException("交易已支付或关闭!");
}
// 3.尝试扣减余额
userService.deductMoney(payOrderFormDTO.getPw(), po.getAmount());
// 4.修改支付单状态
boolean success = markPayOrderSuccess(payOrderFormDTO.getId(), LocalDateTime.now());
if (!success) {
throw new BizIllegalException("交易已支付或关闭!");
}
// 5.修改订单状态
Order order = new Order();
order.setId(po.getBizOrderNo());
order.setStatus(2);
order.setPayTime(LocalDateTime.now());
orderService.updateById(order);
}
public boolean markPayOrderSuccess(Long id, LocalDateTime successTime) {
return lambdaUpdate()
.set(PayOrder::getStatus, PayStatus.TRADE_SUCCESS.getValue())
.set(PayOrder::getPaySuccessTime, successTime)
.eq(PayOrder::getId, id)
// 支付状态的乐观锁判断
.in(PayOrder::getStatus, PayStatus.NOT_COMMIT.getValue(), PayStatus.WAIT_BUYER_PAY.getValue())
.update();
}
private PayOrder checkIdempotent(PayApplyDTO applyDTO) {
// 1.首先查询支付单
PayOrder oldOrder = queryByBizOrderNo(applyDTO.getBizOrderNo());
// 2.判断是否存在
if (oldOrder == null) {
// 不存在支付单说明是第一次写入新的支付单并返回
PayOrder payOrder = buildPayOrder(applyDTO);
payOrder.setPayOrderNo(IdWorker.getId());
save(payOrder);
return payOrder;
}
// 3.旧单已经存在判断是否支付成功
if (PayStatus.TRADE_SUCCESS.equalsValue(oldOrder.getStatus())) {
// 已经支付成功抛出异常
throw new BizIllegalException("订单已经支付!");
}
// 4.旧单已经存在判断是否已经关闭
if (PayStatus.TRADE_CLOSED.equalsValue(oldOrder.getStatus())) {
// 已经关闭抛出异常
throw new BizIllegalException("订单已关闭");
}
// 5.旧单已经存在判断支付渠道是否一致
if (!StringUtils.equals(oldOrder.getPayChannelCode(), applyDTO.getPayChannelCode())) {
// 支付渠道不一致需要重置数据然后重新申请支付单
PayOrder payOrder = buildPayOrder(applyDTO);
payOrder.setId(oldOrder.getId());
payOrder.setQrCodeUrl("");
updateById(payOrder);
payOrder.setPayOrderNo(oldOrder.getPayOrderNo());
return payOrder;
}
// 6.旧单已经存在且可能是未支付或未提交且支付渠道一致直接返回旧数据
return oldOrder;
}
private PayOrder buildPayOrder(PayApplyDTO payApplyDTO) {
// 1.数据转换
PayOrder payOrder = BeanUtils.toBean(payApplyDTO, PayOrder.class);
// 2.初始化数据
payOrder.setPayOverTime(LocalDateTime.now().plusMinutes(120L));
payOrder.setStatus(PayStatus.WAIT_BUYER_PAY.getValue());
payOrder.setBizUserId(UserContext.getUser());
return payOrder;
}
public PayOrder queryByBizOrderNo(Long bizOrderNo) {
return lambdaQuery()
.eq(PayOrder::getBizOrderNo, bizOrderNo)
.one();
}
}

View File

@ -0,0 +1,85 @@
package com.hmall.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.common.exception.BadRequestException;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.exception.ForbiddenException;
import com.hmall.common.utils.UserContext;
import com.hmall.config.JwtProperties;
import com.hmall.domain.dto.LoginFormDTO;
import com.hmall.domain.po.User;
import com.hmall.domain.vo.UserLoginVO;
import com.hmall.enums.UserStatus;
import com.hmall.mapper.UserMapper;
import com.hmall.service.IUserService;
import com.hmall.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
/**
* <p>
* 用户表 服务实现类
* </p>
*
* @author 虎哥
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
private final PasswordEncoder passwordEncoder;
private final JwtTool jwtTool;
private final JwtProperties jwtProperties;
@Override
public UserLoginVO login(LoginFormDTO loginDTO) {
// 1.数据校验
String username = loginDTO.getUsername();
String password = loginDTO.getPassword();
// 2.根据用户名或手机号查询
User user = lambdaQuery().eq(User::getUsername, username).one();
Assert.notNull(user, "用户名错误");
// 3.校验是否禁用
if (user.getStatus() == UserStatus.FROZEN) {
throw new ForbiddenException("用户被冻结");
}
// 4.校验密码
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadRequestException("用户名或密码错误");
}
// 5.生成TOKEN
String token = jwtTool.createToken(user.getId(), jwtProperties.getTokenTTL());
// 6.封装VO返回
UserLoginVO vo = new UserLoginVO();
vo.setUserId(user.getId());
vo.setUsername(user.getUsername());
vo.setBalance(user.getBalance());
vo.setToken(token);
return vo;
}
@Override
public void deductMoney(String pw, Integer totalFee) {
log.info("开始扣款");
// 1.校验密码
User user = getById(UserContext.getUser());
if(user == null || !passwordEncoder.matches(pw, user.getPassword())){
// 密码错误
throw new BizIllegalException("用户密码错误");
}
// 2.尝试扣款
try {
baseMapper.updateMoney(UserContext.getUser(), totalFee);
} catch (Exception e) {
throw new RuntimeException("扣款失败,可能是余额不足!", e);
}
log.info("扣款成功");
}
}

Some files were not shown because too many files have changed in this diff Show More