diff --git a/cart-service/pom.xml b/cart-service/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..428e397f6e88fe8da6c9d40a2cfe293f4827a921
--- /dev/null
+++ b/cart-service/pom.xml
@@ -0,0 +1,97 @@
+
+
+
+ hmall-parent
+ com.hmall
+ 1.0.0
+
+ 4.0.0
+
+ cart-service
+
+
+ 11
+ 11
+
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+ io.github.openfeign
+ feign-okhttp
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-sentinel
+
+
+
+ com.hmall
+ hm-api
+ 1.0.0
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+
+ com.hmall
+ hm-common
+ 1.0.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+ ${project.artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/cart-service/src/main/java/com/hmall/cart/CartServiceApplication.java b/cart-service/src/main/java/com/hmall/cart/CartServiceApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cf5cf916b4b7ef572e86ee9ff6acbb947725b0f
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/CartServiceApplication.java
@@ -0,0 +1,17 @@
+package com.hmall.cart;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+@EnableFeignClients(basePackages = "com.hmall.api")
+@MapperScan("com.hmall.cart.mapper")
+@SpringBootApplication(scanBasePackages = {"com.hmall.cart","com.hmall.api"})
+public class CartServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CartServiceApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/cart-service/src/main/java/com/hmall/cart/config/CartProperties.java b/cart-service/src/main/java/com/hmall/cart/config/CartProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f8386b10615a740340e6bd7628e8886ffc67a4d
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/config/CartProperties.java
@@ -0,0 +1,12 @@
+package com.hmall.cart.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "hm.cart")
+public class CartProperties {
+ private Integer maxAmount;
+}
diff --git a/cart-service/src/main/java/com/hmall/cart/controller/CartController.java b/cart-service/src/main/java/com/hmall/cart/controller/CartController.java
new file mode 100644
index 0000000000000000000000000000000000000000..87a350bacae185fa0b69bd810e5cc39f0156d782
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/controller/CartController.java
@@ -0,0 +1,55 @@
+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 queryMyCarts(){
+ return cartService.queryMyCarts();
+ }
+ @ApiOperation("批量删除购物车中商品")
+ @ApiImplicitParam(name = "ids", value = "购物车条目id集合")
+ @DeleteMapping
+ public void deleteCartItemByIds(@RequestParam("ids") List ids){
+ cartService.removeByItemIds(ids);
+ }
+}
diff --git a/cart-service/src/main/java/com/hmall/cart/controller/inner/CartControllerInner.java b/cart-service/src/main/java/com/hmall/cart/controller/inner/CartControllerInner.java
new file mode 100644
index 0000000000000000000000000000000000000000..2de7c6a1408e6692b016e961cee3f4096680f974
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/controller/inner/CartControllerInner.java
@@ -0,0 +1,33 @@
+package com.hmall.cart.controller.inner;
+
+
+import com.hmall.cart.service.ICartService;
+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.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collection;
+
+@Api(tags = "购物车相关接口")
+@RestController
+@RequestMapping("inner/carts")
+@RequiredArgsConstructor
+public class CartControllerInner {
+ private final ICartService cartService;
+
+ @ApiOperation("批量删除购物车中商品")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "userId", value = "用户id"),
+ @ApiImplicitParam(name = "ids", value = "购物车条目id集合")
+ })
+ @DeleteMapping
+ public void deleteCartItemByIds(@RequestParam("userId") Long userId,@RequestParam("ids") Collection ids){
+ cartService.removeByItemIds(userId,ids);
+ }
+}
diff --git a/cart-service/src/main/java/com/hmall/cart/domain/dto/CartFormDTO.java b/cart-service/src/main/java/com/hmall/cart/domain/dto/CartFormDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb9cb9fe2edc202c873cfc773cc1043882c2ea16
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/domain/dto/CartFormDTO.java
@@ -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;
+}
diff --git a/cart-service/src/main/java/com/hmall/cart/domain/dto/ItemDTO.java b/cart-service/src/main/java/com/hmall/cart/domain/dto/ItemDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..5498dd3d0995fe8151f1a6974d1c181fd7015aaf
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/domain/dto/ItemDTO.java
@@ -0,0 +1,34 @@
+package com.hmall.cart.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;
+}
diff --git a/cart-service/src/main/java/com/hmall/cart/domain/po/Cart.java b/cart-service/src/main/java/com/hmall/cart/domain/po/Cart.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ab31e67ec3806804c3caa65ca79fcaae06b449d
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/domain/po/Cart.java
@@ -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;
+
+/**
+ *
+ * 订单详情表
+ *
+ *
+ * @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;
+
+
+}
diff --git a/cart-service/src/main/java/com/hmall/cart/domain/vo/CartVO.java b/cart-service/src/main/java/com/hmall/cart/domain/vo/CartVO.java
new file mode 100644
index 0000000000000000000000000000000000000000..27ce1e35c64be41bd966df0dd78dd62849acfc84
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/domain/vo/CartVO.java
@@ -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;
+
+/**
+ *
+ * 订单详情表
+ *
+ *
+ * @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;
+
+}
diff --git a/cart-service/src/main/java/com/hmall/cart/mapper/CartMapper.java b/cart-service/src/main/java/com/hmall/cart/mapper/CartMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..62019aecc4d60167ded0c5da54a10e057e434743
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/mapper/CartMapper.java
@@ -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;
+
+/**
+ *
+ * 订单详情表 Mapper 接口
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface CartMapper extends BaseMapper {
+
+ @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);
+}
diff --git a/cart-service/src/main/java/com/hmall/cart/service/ICartService.java b/cart-service/src/main/java/com/hmall/cart/service/ICartService.java
new file mode 100644
index 0000000000000000000000000000000000000000..386704aaed7d7860f6aef2f2cd5ef23b0344e475
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/service/ICartService.java
@@ -0,0 +1,29 @@
+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;
+
+/**
+ *
+ * 订单详情表 服务类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface ICartService extends IService {
+
+ void addItem2Cart(CartFormDTO cartFormDTO);
+
+ List queryMyCarts();
+
+ void removeByItemIds(Collection ids);
+
+ void removeByItemIds(Long userId, Collection ids);
+}
diff --git a/cart-service/src/main/java/com/hmall/cart/service/impl/CartServiceImpl.java b/cart-service/src/main/java/com/hmall/cart/service/impl/CartServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..80782d64d2c44b689d25b1f2856f20e8c50c1ad7
--- /dev/null
+++ b/cart-service/src/main/java/com/hmall/cart/service/impl/CartServiceImpl.java
@@ -0,0 +1,136 @@
+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.api.item.ItemClient;
+import com.hmall.api.item.dto.ItemDTO;
+import com.hmall.cart.config.CartProperties;
+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 lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * 订单详情表 服务实现类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+@Service
+@RequiredArgsConstructor
+public class CartServiceImpl extends ServiceImpl implements ICartService {
+
+ private final ItemClient itemClient;
+
+ private final CartProperties cartProperties;
+
+ @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 queryMyCarts() {
+ // 1.查询我的购物车列表
+ List carts = lambdaQuery().eq(Cart::getUserId, UserContext.getUser()).list();
+ if (CollUtils.isEmpty(carts)) {
+ return CollUtils.emptyList();
+ }
+
+ // 2.转换VO
+ List vos = BeanUtils.copyList(carts, CartVO.class);
+
+ // 3.处理VO中的商品信息
+ handleCartItems(vos);
+
+ // 4.返回
+ return vos;
+ }
+
+ private void handleCartItems(List vos) {
+ // 1.获取商品id
+ Set itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
+ // 2.查询商品
+ List items = itemClient.queryItemByIds(itemIds);
+ if (CollUtils.isEmpty(items)) {
+ return;
+ }
+ // 3.转为 id 到 item的map
+ Map 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 ids) {
+ Long userId = UserContext.getUser();
+ removeByItemIds(userId, ids);
+ }
+
+ @Override
+ public void removeByItemIds(Long userId, Collection ids) {
+ // 1.构建删除条件,userId和itemId
+ QueryWrapper queryWrapper = new QueryWrapper();
+ queryWrapper.lambda()
+ .eq(Cart::getUserId, userId)
+ .in(Cart::getItemId, ids);
+ // 2.删除
+ remove(queryWrapper);
+ }
+
+ private void checkCartsFull(Long userId) {
+ int count = lambdaQuery().eq(Cart::getUserId, userId).count();
+ Integer maxAmount = cartProperties.getMaxAmount();
+ if (count >= maxAmount) {
+ throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", maxAmount));
+ }
+ }
+
+ private boolean checkItemExists(Long itemId, Long userId) {
+ int count = lambdaQuery()
+ .eq(Cart::getUserId, userId)
+ .eq(Cart::getItemId, itemId)
+ .count();
+ return count > 0;
+ }
+}
diff --git a/cart-service/src/main/resources/application.yaml b/cart-service/src/main/resources/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2b50838877f57bf949eb9a2a37d0bb9abb0bb59b
--- /dev/null
+++ b/cart-service/src/main/resources/application.yaml
@@ -0,0 +1,6 @@
+server:
+ port: 8082
+hm:
+ swagger:
+ title: 购物车服务接口文档
+ package: com.hmall.cart.controller
\ No newline at end of file
diff --git a/cart-service/src/main/resources/backup/application1.yaml b/cart-service/src/main/resources/backup/application1.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4abd81b7ce9f32c5d33a7e887b85a7cd2a4f2f3a
--- /dev/null
+++ b/cart-service/src/main/resources/backup/application1.yaml
@@ -0,0 +1,67 @@
+server:
+ port: 8082
+spring:
+ application:
+ name: cart-service
+ cloud:
+ nacos:
+ server-addr: 192.168.101.68:8848 # nacos地址
+ sentinel:
+ transport:
+ dashboard: 192.168.101.68:9090
+ client-ip: 192.168.101.1
+ http-method-specify: true # 开启请求方式前缀可根据http请求方法区分簇点链路
+ profiles:
+ active: dev
+ 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: 1579670286@qq.com
+ concat: 栋哥
+ version: v1.0.0
+ group:
+ default:
+ group-name: default
+ api-rule: package
+ api-rule-resources:
+ - com.hmall.cart.controller
+hm:
+ jwt:
+ location: classpath:hmall.jks
+ alias: hmall
+ password: hmall123
+ tokenTTL: 30m
+ auth:
+ excludePaths:
+ - /search/**
+ - /users/login
+ - /items/**
+ - /hi
+feign:
+ okhttp:
+ enabled: true # 开启OKHttp功能
+ sentinel:
+ enabled: true # 开启feign对sentinel的支持
+
+# keytool -genkeypair -alias hmall -keyalg RSA -keypass hmall123 -keystore hmall.jks -storepass hmall123
diff --git a/cart-service/src/main/resources/bootstrap.yaml b/cart-service/src/main/resources/bootstrap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b3feff3d391570bdc670143c8d68007af05f4add
--- /dev/null
+++ b/cart-service/src/main/resources/bootstrap.yaml
@@ -0,0 +1,21 @@
+spring:
+ application:
+ name: cart-service # 服务名称
+ profiles:
+ active: dev
+ cloud:
+ nacos:
+ server-addr: ${NACOS_ADDR:192.168.101.68:8848} # nacos地址
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ config: #配置中心
+ file-extension: yaml # 文件后缀名
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ group: ${GROUP_NAME:DEFAULT_GROUP} # 配置分组
+ shared-configs: # 共享配置
+ - dataId: shared-jdbc.yaml # 共享mybatis配置
+ - dataId: shared-log.yaml # 共享日志配置
+ - dataId: shared-swagger.yaml # 共享日志配置
+ - dataId: shared-feign.yaml # 共享feign配置
+ - dataId: shared-seata.yaml # 共享seata配置
+ - dataId: shared-sentinel.yaml # 共享sentinel配置
\ No newline at end of file
diff --git a/cart-service/src/main/resources/mapper/CartMapper.xml b/cart-service/src/main/resources/mapper/CartMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d9c56a5d0498e273de51b4d6b2ca60455824efee
--- /dev/null
+++ b/cart-service/src/main/resources/mapper/CartMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/hm-api/pom.xml b/hm-api/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..57d57b442cacfc12501195a5330b878686465383
--- /dev/null
+++ b/hm-api/pom.xml
@@ -0,0 +1,43 @@
+
+
+
+ com.hmall
+ hmall-parent
+ 1.0.0
+
+ 4.0.0
+
+ hm-api
+
+
+ 11
+ 11
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+ com.hmall
+ hm-common
+ 1.0.0
+
+
+
+ io.swagger
+ swagger-annotations
+ 1.6.6
+ compile
+
+
+
\ No newline at end of file
diff --git a/hm-api/src/main/java/com/hmall/api/cart/CartClient.java b/hm-api/src/main/java/com/hmall/api/cart/CartClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff8b3bf42a4e5fd4a0d0e54b847fa4cf4e20f540
--- /dev/null
+++ b/hm-api/src/main/java/com/hmall/api/cart/CartClient.java
@@ -0,0 +1,13 @@
+package com.hmall.api.cart;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collection;
+
+@FeignClient(name = "cart-service")
+public interface CartClient {
+ @DeleteMapping("/inner/carts")
+ void deleteCartItemByIds(@RequestParam("userId") Long userId,@RequestParam("ids") Collection ids);
+
+}
diff --git a/hm-api/src/main/java/com/hmall/api/item/ItemClient.java b/hm-api/src/main/java/com/hmall/api/item/ItemClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..1726eeefedc03b95d645207c2604e9745306a147
--- /dev/null
+++ b/hm-api/src/main/java/com/hmall/api/item/ItemClient.java
@@ -0,0 +1,23 @@
+package com.hmall.api.item;
+
+import com.hmall.api.item.dto.ItemDTO;
+import com.hmall.api.item.dto.OrderDetailDTO;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.Collection;
+import java.util.List;
+
+@FeignClient(name = "item-service",path = "/items",fallbackFactory = ItemClientFallbackFactory.class )
+public interface ItemClient {
+ // 查询商品
+ @GetMapping
+ List queryItemByIds(@RequestParam("ids") Collection ids);
+ // 扣减库存
+ @PutMapping("/stock/deduct")
+ void deductStock(@RequestBody List items);
+
+}
diff --git a/hm-api/src/main/java/com/hmall/api/item/ItemClientFallbackFactory.java b/hm-api/src/main/java/com/hmall/api/item/ItemClientFallbackFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..131dac0d6eb42678a779428db4e2bd81e899809e
--- /dev/null
+++ b/hm-api/src/main/java/com/hmall/api/item/ItemClientFallbackFactory.java
@@ -0,0 +1,34 @@
+package com.hmall.api.item;
+
+import com.hmall.api.item.dto.ItemDTO;
+import com.hmall.api.item.dto.OrderDetailDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.openfeign.FallbackFactory;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+/**
+ * @author syd
+ * @description itemClient的通用降级策略
+ */
+@Component
+@Slf4j
+public class ItemClientFallbackFactory implements FallbackFactory {
+ @Override
+ public ItemClient create(Throwable cause) {
+ return new ItemClient() {
+ @Override
+ public List queryItemByIds(Collection ids) {
+ log.warn("远程调用ItemClient#queryItemByIds方法出现异常,走降级,参数:{}", ids, cause);
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void deductStock(List items) {
+ log.warn("远程调用ItemClient#deductStock扣减库存失败,走降级,参数:{}",items,cause);
+ }
+ };
+ }
+}
diff --git a/hm-api/src/main/java/com/hmall/api/item/dto/ItemDTO.java b/hm-api/src/main/java/com/hmall/api/item/dto/ItemDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..59718714e163d61eceb84fa4637a6857a16a00fb
--- /dev/null
+++ b/hm-api/src/main/java/com/hmall/api/item/dto/ItemDTO.java
@@ -0,0 +1,34 @@
+package com.hmall.api.item.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;
+}
diff --git a/hm-api/src/main/java/com/hmall/api/item/dto/OrderDetailDTO.java b/hm-api/src/main/java/com/hmall/api/item/dto/OrderDetailDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..94bb74c016a01d18e676e724ddd04f9c8e9adc9e
--- /dev/null
+++ b/hm-api/src/main/java/com/hmall/api/item/dto/OrderDetailDTO.java
@@ -0,0 +1,16 @@
+package com.hmall.api.item.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;
+}
diff --git a/hm-api/src/main/java/com/hmall/api/trade/TradeClient.java b/hm-api/src/main/java/com/hmall/api/trade/TradeClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..276e9e7eb866fe7bba464700cc6f1e97b6592a6f
--- /dev/null
+++ b/hm-api/src/main/java/com/hmall/api/trade/TradeClient.java
@@ -0,0 +1,14 @@
+package com.hmall.api.trade;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+
+@FeignClient(name = "trade-service",path = "/orders")
+public interface TradeClient {
+
+ // 标记订单已支付
+ @PutMapping("/{orderId}")
+ void markOrderPaySuccess(@PathVariable("userId") Long userId,@PathVariable("orderId") Long orderId);
+
+}
diff --git a/hm-api/src/main/java/com/hmall/api/user/UserClient.java b/hm-api/src/main/java/com/hmall/api/user/UserClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..24b0ed26e621185edbf02476928dad59f1e78fd1
--- /dev/null
+++ b/hm-api/src/main/java/com/hmall/api/user/UserClient.java
@@ -0,0 +1,14 @@
+package com.hmall.api.user;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@FeignClient(name = "user-service",path = "inner/users")
+public interface UserClient {
+
+ // 扣减余额
+ @PutMapping("/money/deduct")
+ void deductMoney(@RequestParam("userId") Long userId,@RequestParam("pw") String pw,@RequestParam("amount") Integer amount);
+
+}
diff --git a/hm-common/pom.xml b/hm-common/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..71cdebfb445bfa2c18c2bfbcf287b9c2d50249f4
--- /dev/null
+++ b/hm-common/pom.xml
@@ -0,0 +1,95 @@
+
+
+
+ hmall-parent
+ com.hmall
+ 1.0.0
+
+ 4.0.0
+
+ hm-common
+
+
+ 11
+ 11
+
+
+
+
+ org.apache.commons
+ commons-pool2
+
+
+
+ cn.hutool
+ hutool-all
+
+
+
+ org.springframework
+ spring-webmvc
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ 9.0.73
+ provided
+
+
+ com.baomidou
+ mybatis-plus-core
+ ${mybatis-plus.version}
+ provided
+
+
+ com.baomidou
+ mybatis-plus-extension
+ ${mybatis-plus.version}
+ provided
+
+
+ org.hibernate.validator
+ hibernate-validator
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+ com.github.xiaoymin
+ knife4j-openapi2-spring-boot-starter
+ 4.1.0
+
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+
+
+
+ org.springframework.amqp
+ spring-amqp
+ provided
+
+
+
+ org.springframework.amqp
+ spring-rabbit
+ provided
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ provided
+
+
+
\ No newline at end of file
diff --git a/hm-common/src/main/java/com/hmall/common/advice/CommonExceptionAdvice.java b/hm-common/src/main/java/com/hmall/common/advice/CommonExceptionAdvice.java
new file mode 100644
index 0000000000000000000000000000000000000000..75ae4add3a20ad17dbe08b79115ae7c408ffabcd
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/advice/CommonExceptionAdvice.java
@@ -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> processResponse(CommonException e){
+ return ResponseEntity.status(e.getCode()).body(R.error(e));
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/config/JsonConfig.java b/hm-common/src/main/java/com/hmall/common/config/JsonConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8fa781df33a511a21055a7f0b7a5e71f6907627
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/config/JsonConfig.java
@@ -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);
+ };
+ }
+}
\ No newline at end of file
diff --git a/hm-common/src/main/java/com/hmall/common/config/MvcConfig.java b/hm-common/src/main/java/com/hmall/common/config/MvcConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..803771e54abe9090f8c98c1e97920f5950b14e96
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/config/MvcConfig.java
@@ -0,0 +1,21 @@
+package com.hmall.common.config;
+
+import com.hmall.common.interceptor.UserInfoInterceptor;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+@RequiredArgsConstructor
+@ConditionalOnClass(DispatcherServlet.class)
+public class MvcConfig implements WebMvcConfigurer {
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // 1.添加拦截器
+ UserInfoInterceptor userInfoInterceptor = new UserInfoInterceptor();
+ registry.addInterceptor(userInfoInterceptor);
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/config/MyBatisConfig.java b/hm-common/src/main/java/com/hmall/common/config/MyBatisConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a2d463939a7ec8abd55c2fbfef2f2c4b7bc40ae
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/config/MyBatisConfig.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/hm-common/src/main/java/com/hmall/common/domain/PageDTO.java b/hm-common/src/main/java/com/hmall/common/domain/PageDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..98eaf2bfebc12a44daeeea55ea89f899a06d8007
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/domain/PageDTO.java
@@ -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 {
+ protected Long total;
+ protected Long pages;
+ protected List list;
+
+ public static PageDTO empty(Long total, Long pages) {
+ return new PageDTO<>(total, pages, CollUtils.emptyList());
+ }
+ public static PageDTO empty(Page> page) {
+ return new PageDTO<>(page.getTotal(), page.getPages(), CollUtils.emptyList());
+ }
+
+ public static PageDTO of(Page 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 PageDTO of(Page page, Function 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 PageDTO of(Page> page, List list) {
+ return new PageDTO<>(page.getTotal(), page.getPages(), list);
+ }
+
+ public static PageDTO of(Page page, Class clazz) {
+ return new PageDTO<>(page.getTotal(), page.getPages(), BeanUtils.copyList(page.getRecords(), clazz));
+ }
+
+ public static PageDTO of(Page page, Class clazz, Convert convert) {
+ return new PageDTO<>(page.getTotal(), page.getPages(), BeanUtils.copyList(page.getRecords(), clazz, convert));
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/domain/PageQuery.java b/hm-common/src/main/java/com/hmall/common/domain/PageQuery.java
new file mode 100644
index 0000000000000000000000000000000000000000..7abcd7513ff3361e1bb6f25ff38fa42a31ac32a9
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/domain/PageQuery.java
@@ -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 Page toMpPage(OrderItem... orderItems) {
+ Page 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 Page toMpPage(String defaultSortBy, boolean isAsc) {
+ if (StringUtils.isBlank(sortBy)){
+ sortBy = defaultSortBy;
+ this.isAsc = isAsc;
+ }
+ Page page = new Page<>(pageNo, pageSize);
+ OrderItem orderItem = new OrderItem();
+ orderItem.setAsc(this.isAsc);
+ orderItem.setColumn(sortBy);
+ page.addOrder(orderItem);
+ return page;
+ }
+ public Page toMpPageDefaultSortByCreateTimeDesc() {
+ return toMpPage("create_time", false);
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/domain/R.java b/hm-common/src/main/java/com/hmall/common/domain/R.java
new file mode 100644
index 0000000000000000000000000000000000000000..33bda25734dc5f7887b58795d2fdcb6e6b72ec1e
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/domain/R.java
@@ -0,0 +1,45 @@
+package com.hmall.common.domain;
+
+import com.hmall.common.exception.CommonException;
+import lombok.Data;
+
+
+@Data
+public class R {
+ private int code;
+ private String msg;
+ private T data;
+
+ public static R ok() {
+ return ok(null);
+ }
+
+ public static R ok(T data) {
+ return new R<>(200, "OK", data);
+ }
+
+ public static R error(String msg) {
+ return new R<>(500, msg, null);
+ }
+
+ public static R error(int code, String msg) {
+ return new R<>(code, msg, null);
+ }
+
+ public static R 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;
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/exception/BadRequestException.java b/hm-common/src/main/java/com/hmall/common/exception/BadRequestException.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0618e24c13726b9066393a02151abc7d7a052c6
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/exception/BadRequestException.java
@@ -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);
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/exception/BizIllegalException.java b/hm-common/src/main/java/com/hmall/common/exception/BizIllegalException.java
new file mode 100644
index 0000000000000000000000000000000000000000..584588e8ba48e40e45ae4f53f02122045b107518
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/exception/BizIllegalException.java
@@ -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);
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/exception/CommonException.java b/hm-common/src/main/java/com/hmall/common/exception/CommonException.java
new file mode 100644
index 0000000000000000000000000000000000000000..871dcf3c5258f3991a0bb969e2e619d8c7693e8b
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/exception/CommonException.java
@@ -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;
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/exception/DbException.java b/hm-common/src/main/java/com/hmall/common/exception/DbException.java
new file mode 100644
index 0000000000000000000000000000000000000000..635044a1f677c7c111a2df5fb938a8a5cec7701b
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/exception/DbException.java
@@ -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);
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/exception/ForbiddenException.java b/hm-common/src/main/java/com/hmall/common/exception/ForbiddenException.java
new file mode 100644
index 0000000000000000000000000000000000000000..05f923009f5b97c8eb374c08dcaa363b1dad5a55
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/exception/ForbiddenException.java
@@ -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);
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/exception/UnauthorizedException.java b/hm-common/src/main/java/com/hmall/common/exception/UnauthorizedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..704f3d1a80ab70deca652125915bda61e75ba823
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/exception/UnauthorizedException.java
@@ -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);
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/interceptor/UserInfoInterceptor.java b/hm-common/src/main/java/com/hmall/common/interceptor/UserInfoInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..88794e8b67a2095ecc3d3f1fc6f370515da626a3
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/interceptor/UserInfoInterceptor.java
@@ -0,0 +1,31 @@
+package com.hmall.common.interceptor;
+
+import cn.hutool.core.util.StrUtil;
+import com.hmall.common.utils.UserContext;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@RequiredArgsConstructor
+public class UserInfoInterceptor implements HandlerInterceptor {
+
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ // 1.获取请求头中的 token
+ String userId = request.getHeader("user-info");
+ // 2.判断是否为空
+ if (StrUtil.isNotEmpty(userId)) {
+ // 3.保存到ThreadLocal
+ UserContext.setUser(Long.parseLong(userId));
+ }
+ return true;
+ }
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+ // 清理用户
+ UserContext.removeUser();
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/utils/BeanUtils.java b/hm-common/src/main/java/com/hmall/common/utils/BeanUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb33c2e6b24a511dcb36abd1564b21da6b297653
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/utils/BeanUtils.java
@@ -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 原对象类型
+ * @param 目标对象类型
+ * @return 目标对象
+ */
+ public static T copyBean(R source, Class clazz, Convert convert) {
+ T target = copyBean(source, clazz);
+ if (convert != null) {
+ convert.convert(source, target);
+ }
+ return target;
+ }
+ /**
+ * 将原对象转换成目标对象,对于字段不匹配的字段可以使用转换器处理
+ *
+ * @param source 原对象
+ * @param clazz 目标对象的class
+ * @param 原对象类型
+ * @param 目标对象类型
+ * @return 目标对象
+ */
+ public static T copyBean(R source, Class clazz){
+ if (source == null) {
+ return null;
+ }
+ return toBean(source, clazz);
+ }
+
+ public static List copyList(List list, Class clazz) {
+ if (list == null || list.size() == 0) {
+ return CollUtils.emptyList();
+ }
+ return copyToList(list, clazz);
+ }
+
+ public static List copyList(List list, Class clazz, Convert convert) {
+ if (list == null || list.size() == 0) {
+ return CollUtils.emptyList();
+ }
+ return list.stream().map(r -> copyBean(r, clazz, convert)).collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/hm-common/src/main/java/com/hmall/common/utils/CollUtils.java b/hm-common/src/main/java/com/hmall/common/utils/CollUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..70121d48a50156a60cfda2204c8a30ccaa61b35c
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/utils/CollUtils.java
@@ -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 List emptyList() {
+ return Collections.emptyList();
+ }
+
+ public static Set emptySet() {
+ return Collections.emptySet();
+ }
+
+ public static Map emptyMap() {
+ return Collections.emptyMap();
+ }
+
+ public static Set singletonSet(T t) {
+ return Collections.singleton(t);
+ }
+
+ public static List singletonList(T t) {
+ return Collections.singletonList(t);
+ }
+
+ public static List convertToInteger(List originList){
+ return CollUtils.isNotEmpty(originList) ? originList.stream().map(NumberUtil::parseInt).collect(Collectors.toList()) : null;
+ }
+
+ public static List convertToLong(List originLIst){
+ return CollUtils.isNotEmpty(originLIst) ? originLIst.stream().map(NumberUtil::parseLong).collect(Collectors.toList()) : null;
+ }
+
+ /**
+ * 以 conjunction 为分隔符将集合转换为字符串 如果集合元素为数组、Iterable或Iterator,则递归组合其为字符串
+ * @param collection 集合
+ * @param conjunction 分隔符
+ * @param 集合元素类型
+ * @return 连接后的字符串
+ * See Also: IterUtil.join(Iterator, CharSequence)
+ */
+ public static String join(Collection collection, CharSequence conjunction) {
+ if (null == collection || collection.isEmpty()) {
+ return null;
+ }
+ return IterUtil.join(collection.iterator(), conjunction);
+ }
+
+ public static String joinIgnoreNull(Collection 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();
+ }
+}
\ No newline at end of file
diff --git a/hm-common/src/main/java/com/hmall/common/utils/Convert.java b/hm-common/src/main/java/com/hmall/common/utils/Convert.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f5688594dcc0f49e88e6137cfd817402d1732be
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/utils/Convert.java
@@ -0,0 +1,8 @@
+package com.hmall.common.utils;
+
+/**
+ * 对原对象进行计算,设置到目标对象中
+ **/
+public interface Convert{
+ void convert(R origin, T target);
+}
\ No newline at end of file
diff --git a/hm-common/src/main/java/com/hmall/common/utils/CookieBuilder.java b/hm-common/src/main/java/com/hmall/common/utils/CookieBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ac5dd56bdacb89940f872aa8558ede9f1796ec4
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/utils/CookieBuilder.java
@@ -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);
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/utils/DateUtils.java b/hm-common/src/main/java/com/hmall/common/utils/DateUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fea3f314dced208d2a74b583a4dd0336c594674
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/utils/DateUtils.java
@@ -0,0 +1,251 @@
+package com.hmall.common.utils;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * 时间工具类,用于本地时间操作,包含LocalDateTimeUtil的所有方法和自定义的LocalDateTime的操作方法及常量
+ *
+ * @author itheima
+ * @version 1.0.0 1.0
+ * @see 1.0
+ * @since 从哪个版本开始支持该类的功能
+ */
+public class DateUtils extends LocalDateTimeUtil {
+ public static final String DEFAULT_YEAR_FORMAT = "yyyy";
+ public static final String DEFAULT_MONTH_FORMAT = "yyyy-MM";
+ public static final String DEFAULT_MONTH_FORMAT_SLASH = "yyyy/MM";
+ public static final String DEFAULT_MONTH_FORMAT_EN = "yyyy年MM月";
+ public static final String DEFAULT_MONTH_FORMAT_COMPACT = "yyyyMM";
+ public static final String DEFAULT_WEEK_FORMAT = "yyyy-ww";
+ public static final String DEFAULT_WEEK_FORMAT_EN = "yyyy年ww周";
+ public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
+ public static final String DEFAULT_DATE_FORMAT_EN = "yyyy年MM月dd日";
+ public static final String DEFAULT_DATE_FORMAT_COMPACT = "yyyyMMdd";
+ public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+ public static final String DEFAULT_DATE_TIME_COMPACT = "yyyyMMddHHmmss";
+ public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
+ public static final String DATE_HOUR_FORMAT = "ddHH";
+ public static final String DAY = "DAY";
+ public static final String MONTH = "MONTH";
+ public static final String WEEK = "WEEK";
+ public static final long MAX_MONTH_DAY = 30L;
+ public static final long MAX_3_MONTH_DAY = 90L;
+ public static final long MAX_YEAR_DAY = 365L;
+
+
+ public static final DateTimeFormatter SIGN_DATE_SUFFIX_FORMATTER =
+ DateTimeFormatter.ofPattern(":yyyyMM");
+ public static final DateTimeFormatter POINTS_BOARD_SUFFIX_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyyMM");
+
+ public static final String TIME_ZONE_8 = "GMT+8";
+
+ /**
+ * 获取utc时间
+ *
+ * @param localDateTime 转化时间
+ * @return utc时间
+ */
+ public static LocalDateTime getUTCTime(LocalDateTime localDateTime) {
+ ZoneId australia = ZoneId.of("Asia/Shanghai");
+ ZonedDateTime dateAndTimeInSydney = ZonedDateTime.of(localDateTime, australia);
+ ZonedDateTime utcDate = dateAndTimeInSydney.withZoneSameInstant(ZoneOffset.UTC);
+ return utcDate.toLocalDateTime();
+ }
+
+ /**
+ * 获取Asia时间
+ *
+ * @param localDateTime 转化时间
+ * @return Asia时间
+ */
+ public static LocalDateTime getAsiaTime(LocalDateTime localDateTime) {
+ ZoneId australia = ZoneId.of("Asia/Shanghai");
+ ZonedDateTime dateAndTimeInSydney = ZonedDateTime.of(localDateTime, ZoneOffset.UTC);
+ ZonedDateTime utcDate = dateAndTimeInSydney.withZoneSameInstant(australia);
+ return utcDate.toLocalDateTime();
+ }
+
+ /**
+ * 获取某一天的开始:0点0分
+ *
+ * @param localDateTime 指定日期
+ * @return 转换后的时间
+ */
+ public static LocalDateTime getDayStartTime(LocalDateTime localDateTime) {
+ if (localDateTime == null) {
+ return null;
+ }
+ return localDateTime.toLocalDate().atStartOfDay();
+ }
+
+ /**
+ * 获取某一天的结束:23点 59分 59秒的时间
+ *
+ * @param localDateTime 指定日期
+ * @return 转换后的时间
+ */
+ public static LocalDateTime getDayEndTime(LocalDateTime localDateTime) {
+ if (localDateTime == null) {
+ return null;
+ }
+ return LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.MAX);
+ }
+
+ public static Date addDays(int i) {
+ Calendar c = Calendar.getInstance(TimeZone.getTimeZone(TIME_ZONE_8));
+ c.add(Calendar.DAY_OF_MONTH, i);
+ return c.getTime();
+ }
+
+
+ public static LocalDate getMonthBegin(LocalDate date) {
+ return LocalDate.of(date.getYear(), date.getMonth(), 1);
+ }
+
+ public static LocalDate getMonthEnd(LocalDate date) {
+ return LocalDate.of(date.getYear(), date.getMonthValue() + 1, 1).minusDays(1);
+ }
+
+ public static LocalDateTime getMonthBeginTime(LocalDate date) {
+ return LocalDate.of(date.getYear(), date.getMonth(), 1).atStartOfDay();
+ }
+
+ public static LocalDateTime getMonthEndTime(LocalDate date) {
+ return LocalDate.of(date.getYear(), date.getMonthValue() + 1, 1)
+ .minusDays(1).atTime(LocalTime.MAX);
+ }
+
+ public static LocalDateTime getWeekBeginTime(LocalDate now) {
+ return now.minusDays(now.getDayOfWeek().getValue() - 1).atStartOfDay();
+ }
+
+ public static LocalDateTime getWeekEndTime(LocalDate now) {
+ return LocalDateTime.of(now.plusDays(8 - now.getDayOfWeek().getValue()), LocalTime.MAX);
+ }
+
+ /**
+ * 获取最近15天日期(不包含当天),格式MM.dd
+ *
+ * @return
+ */
+ public static List last15Day() {
+ // 1.定义日期列表
+ List days = new ArrayList<>();
+ // 2.获取15天前的时间
+ LocalDateTime time = now().minusDays(15);
+ // 3.for循环遍历
+ for (int count = 0; count < 15; count++) {
+ // 3.1.格式化时间
+ days.add(String.format("%s.%s",
+ NumberUtils.repair0(time.getMonthValue(), 2), NumberUtils.repair0(time.getDayOfMonth(), 2)));
+ // 3.2.日期加1天
+ time = time.plusDays(1);
+ }
+ // 4.返回结果
+ return days;
+ }
+
+ /**
+ * 获取当前时间s
+ *
+ * @return
+ */
+ public static int getCurrentTime() {
+ return (int) (System.currentTimeMillis() / 1000);
+ }
+
+ public static int getDay() {
+ return getDay(null);
+ }
+
+ public static int getDay(LocalDateTime localDateTime) {
+ if (localDateTime == null) {
+ localDateTime = now();
+ }
+ String format = format(localDateTime, DEFAULT_DATE_FORMAT_COMPACT);
+ return NumberUtils.parseInt(format);
+ }
+
+ /**
+ * 获取数字格式的日志
+ *
+ * @param localDateTime 日期
+ * @param format 格式模板,只支持纯数字模板
+ * @return
+ */
+ public static Long getFormatDate(LocalDateTime localDateTime, String format) {
+ String date = format(localDateTime, format);
+ return date == null ? null : NumberUtils.parseLong(date);
+ }
+
+ /**
+ * 获取数字格式的日志
+ *
+ * @param localDateTime 日期
+ * @param format 格式模板,只支持纯数字模板
+ * @return
+ */
+ public static Integer getIntFormatDate(LocalDateTime localDateTime, String format) {
+ String date = format(localDateTime, format);
+ return date == null ? null : NumberUtils.parseInt(date);
+ }
+
+ /**
+ * 获取最小的一个时间
+ * @param localDateTimes
+ * @return
+ */
+ public static LocalDateTime getMin(LocalDateTime... localDateTimes) {
+ if(localDateTimes == null || localDateTimes.length <= 0) {
+ return null;
+ }
+ if(localDateTimes.length == 1) {
+ return localDateTimes[0];
+ }
+ List localDateTimeList = Arrays.asList(localDateTimes);
+ return localDateTimeList.stream().sorted().findFirst().orElse(null);
+ }
+
+ public static LocalDateTime getMax(LocalDateTime ... localDateTimes) {
+ if(localDateTimes == null || localDateTimes.length <= 0) {
+ return null;
+ }
+ if(localDateTimes.length == 1) {
+ return localDateTimes[0];
+ }
+ List localDateTimeList = Arrays.asList(localDateTimes);
+ return localDateTimeList.stream().sorted(Comparator.reverseOrder()).findFirst().orElse(null);
+ }
+
+ /**
+ * 将一个字符串转换成日期格式
+ *
+ * @param date 字符串日期
+ * @param pattern 日期格式
+ * @return date
+ */
+ public static Date toDate(String date, String pattern) {
+ if ("".equals("" + date)) {
+ return null;
+ }
+ if (pattern == null) {
+ pattern = DEFAULT_DATE_FORMAT;
+ }
+ SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.ENGLISH);
+ Date newDate = new Date();
+ try {
+ newDate = sdf.parse(date);
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ return newDate;
+ }
+
+}
diff --git a/hm-common/src/main/java/com/hmall/common/utils/NumberUtils.java b/hm-common/src/main/java/com/hmall/common/utils/NumberUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..46eaa14d9b68a94304d48814c756e7edecef5106
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/utils/NumberUtils.java
@@ -0,0 +1,151 @@
+package com.hmall.common.utils;
+
+import cn.hutool.core.util.NumberUtil;
+
+import java.math.BigDecimal;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class NumberUtils extends NumberUtil {
+
+
+ /**
+ * 如果number为空,将number转换为0,否则原数字返回
+ *
+ * @param number 原数值
+ * @return 整型数字,0或原数字
+ */
+ public static Integer null2Zero(Integer number){
+ return number == null ? 0 : number;
+ }
+
+ /**
+ * 如果number为空,将number转换为0,否则原数字返回
+ *
+ * @param number 原数值
+ * @return 整型数字,0或原数字
+ */
+ public static Double null2Zero(Double number){
+ return number == null ? 0 : number;
+ }
+
+ /**
+ * 如果是空值,返回默认数据;如果有值直接返回
+ * @param number
+ * @param defaultNumber
+ * @return
+ */
+ public static double null2Default(Double number, double defaultNumber) {
+ return number == null ? defaultNumber : number;
+ }
+
+ /**
+ * 如果number为空,将number转换为0L,否则原数字返回
+ *
+ * @param number 原数值
+ * @return 长整型数字,0L或原数字
+ */
+ public static Long null2Zero(Long number){
+ return number == null ? 0L : number;
+ }
+
+
+ public static Double setScale(Double number) {
+ return new BigDecimal(number)
+ .setScale(2, BigDecimal.ROUND_HALF_UP)
+ .doubleValue();
+ }
+ /**
+ * 比较两个数字是否相同,
+ * @param number1 数值1
+ * @param number2 数值2
+ * @return 是否一致
+ */
+ public static boolean equals(Integer number1, Integer number2) {
+ if(number1 == null || number2 == null){
+ return false;
+ }
+ return number1.equals(number2);
+ }
+
+ /**
+ * 数字除法保留指定小数位
+ * @param num1 被除数
+ * @param num2 除数
+ * @param scale 小数点位数
+ * @return 结果
+ */
+ public static Double divToDouble(Integer num1, Integer num2, int scale){
+ if(num2 == null || num2 ==0 || num1 == null || num1 == 0) {
+ return 0d;
+ }
+ return div(num1, num2, scale).doubleValue();
+ }
+
+ public static Double max(List data){
+ if(CollUtils.isEmpty(data)){
+ return null;
+ }
+ return data.stream()
+ .max(Comparator.comparingDouble(num -> num))
+ .orElse(0d);
+ }
+ public static Double min(List data){
+ if(CollUtils.isEmpty(data)){
+ return null;
+ }
+ return data.stream()
+ .min(Comparator.comparingDouble(num -> num))
+ .orElse(0d);
+ }
+
+ public static Double average(List data){
+ if(CollUtils.isEmpty(data)){
+ return 0d;
+ }
+ return data.stream()
+ .collect(Collectors.averagingDouble(Double::doubleValue));
+
+ }
+
+ public static Integer toInt(Object obj) {
+ return obj == null ? null
+ : obj instanceof Integer
+ ? (int) obj : null;
+ }
+
+ /**
+ * 取绝对值,如果为null,返回0
+ * @param number 数值
+ * @return 绝对值
+ */
+ public static int abs(Integer number) {
+ return number == null
+ ? 0
+ : Math.abs(number);
+ }
+
+ /**
+ * 数字格式化字符串,不足位数补0
+ *
+ * @param originNumber 原始数字
+ * @param digit 数字位数
+ * @return 字符串
+ */
+ public static String repair0(Integer originNumber, Integer digit){
+ StringBuilder number = new StringBuilder(originNumber + "");
+ while (number.length() < digit) {
+ number.insert(0, "0");
+ }
+ return number.toString();
+ }
+
+ public static Integer null2Default(Integer originNumber, int defaultNumber) {
+ return originNumber == null ? defaultNumber : originNumber;
+ }
+
+ public static Long null2Default(Long originNumber, long defaultNumber) {
+ return originNumber == null ? defaultNumber : originNumber;
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/utils/UserContext.java b/hm-common/src/main/java/com/hmall/common/utils/UserContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..d87af95bf9336c2fcf4af73ffb9dbe96a985211e
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/utils/UserContext.java
@@ -0,0 +1,28 @@
+package com.hmall.common.utils;
+
+public class UserContext {
+ private static final ThreadLocal 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();
+ }
+}
diff --git a/hm-common/src/main/java/com/hmall/common/utils/WebUtils.java b/hm-common/src/main/java/com/hmall/common/utils/WebUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..eaff08a3a9bf15b97c1d341d0e71c8ecf3f808cb
--- /dev/null
+++ b/hm-common/src/main/java/com/hmall/common/utils/WebUtils.java
@@ -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对应value1,key2对应value2,value3, key1=value1&key2=value2,value3
+ *
+ * @param request
+ * @return 返回拼接字符串
+ */
+ public static String getParameters(HttpServletRequest request) {
+ Map parameterMap = request.getParameterMap();
+ return getParameters(parameterMap);
+ }
+
+ /**
+ * 获取请求地址中的请求参数组装成 key1=value1&key2=value2
+ * 如果key对应多个值,中间使用逗号隔开例如 key1对应value1,key2对应value2,value3, key1=value1&key2=value2,value3
+ *
+ * @param queries
+ * @return
+ */
+ public static String getParameters(final Map queries) {
+ StringBuilder buffer = new StringBuilder();
+ for (Map.Entry 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)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());
+ }
+}
diff --git a/hm-common/src/main/resources/META-INF/spring-configuration-metadata.json b/hm-common/src/main/resources/META-INF/spring-configuration-metadata.json
new file mode 100644
index 0000000000000000000000000000000000000000..c939abe8f8285df430b20c1cb96c7e5f615fe192
--- /dev/null
+++ b/hm-common/src/main/resources/META-INF/spring-configuration-metadata.json
@@ -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": []
+}
\ No newline at end of file
diff --git a/hm-common/src/main/resources/META-INF/spring.factories b/hm-common/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000000000000000000000000000000000..ea50f477c0ffec5532d12b1149a1f05223230781
--- /dev/null
+++ b/hm-common/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,4 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.hmall.common.config.MyBatisConfig,\
+ com.hmall.common.config.JsonConfig,\
+ com.hmall.common.config.MvcConfig
\ No newline at end of file
diff --git a/hm-gateway/pom.xml b/hm-gateway/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0502a40dbb824fedffad31842a4a66303e8db173
--- /dev/null
+++ b/hm-gateway/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ hmall-parent
+ com.hmall
+ 1.0.0
+
+ 4.0.0
+
+ hm-gateway
+
+
+ 11
+ 11
+
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+ com.hmall
+ hm-common
+ 1.0.0
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+
+ ${project.artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/hm-gateway/src/main/java/com/hmall/gateway/GatewayApplication.java b/hm-gateway/src/main/java/com/hmall/gateway/GatewayApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0d7f690cae9ce61b8a4ce37436e13817a5d7389
--- /dev/null
+++ b/hm-gateway/src/main/java/com/hmall/gateway/GatewayApplication.java
@@ -0,0 +1,11 @@
+package com.hmall.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class GatewayApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(GatewayApplication.class, args);
+ }
+}
diff --git a/hm-gateway/src/main/java/com/hmall/gateway/config/AuthProperties.java b/hm-gateway/src/main/java/com/hmall/gateway/config/AuthProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c8e8877911c121cb8b1cc03f33ebea8f7d0c297
--- /dev/null
+++ b/hm-gateway/src/main/java/com/hmall/gateway/config/AuthProperties.java
@@ -0,0 +1,13 @@
+package com.hmall.gateway.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 includePaths;
+ private List excludePaths;
+}
diff --git a/hm-gateway/src/main/java/com/hmall/gateway/config/JwtProperties.java b/hm-gateway/src/main/java/com/hmall/gateway/config/JwtProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e572207af78f489d5ba796b75d96bed4368cda7
--- /dev/null
+++ b/hm-gateway/src/main/java/com/hmall/gateway/config/JwtProperties.java
@@ -0,0 +1,16 @@
+package com.hmall.gateway.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);
+}
diff --git a/hm-gateway/src/main/java/com/hmall/gateway/config/SecurityConfig.java b/hm-gateway/src/main/java/com/hmall/gateway/config/SecurityConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..0048618631b7526ba852c68f14b464b54103ffe5
--- /dev/null
+++ b/hm-gateway/src/main/java/com/hmall/gateway/config/SecurityConfig.java
@@ -0,0 +1,33 @@
+package com.hmall.gateway.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());
+ }
+}
\ No newline at end of file
diff --git a/hm-gateway/src/main/java/com/hmall/gateway/filter/AuthGlobalFilter.java b/hm-gateway/src/main/java/com/hmall/gateway/filter/AuthGlobalFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..b187853aa803a71b15bd8331c220e2fff1c8fc68
--- /dev/null
+++ b/hm-gateway/src/main/java/com/hmall/gateway/filter/AuthGlobalFilter.java
@@ -0,0 +1,69 @@
+package com.hmall.gateway.filter;
+
+import cn.hutool.core.util.StrUtil;
+import com.hmall.gateway.config.AuthProperties;
+import com.hmall.gateway.utils.JwtTool;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+@Slf4j
+@Component
+@EnableConfigurationProperties(AuthProperties.class)
+@RequiredArgsConstructor
+public class AuthGlobalFilter implements GlobalFilter, Ordered {
+ private final JwtTool jwtTool;
+
+ private final AuthProperties authProperties;
+
+ private final AntPathMatcher antPathMatcher = new AntPathMatcher();
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ //1. 获取request、response
+ ServerHttpRequest request = exchange.getRequest();
+ ServerHttpResponse response = exchange.getResponse();
+ // 请求路径
+ String path = request.getPath().toString();
+ // 白名单
+ List excludePaths = authProperties.getExcludePaths();
+ for (String excludePath : excludePaths) {
+ if (antPathMatcher.match(excludePath, path)) {
+ return chain.filter(exchange);
+ }
+ }
+ // 获取请求头的token
+ String token = exchange.getRequest().getHeaders().getFirst("authorization");
+ if (StrUtil.isEmpty(token)) {
+ // token为空则返回401报错
+ response.setRawStatusCode(401);
+ return response.setComplete();
+ }
+ // 如果不为空则校验token
+ Long userId;
+ try {
+ userId = jwtTool.parseToken(token);
+ } catch (Exception e) {
+ response.setRawStatusCode(401);
+ return response.setComplete();
+ }
+ // 5.如果有效,传递用户信息
+ request.mutate().header("user-info", userId.toString());
+ return chain.filter(exchange);
+ }
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+}
diff --git a/hm-gateway/src/main/java/com/hmall/gateway/utils/JwtTool.java b/hm-gateway/src/main/java/com/hmall/gateway/utils/JwtTool.java
new file mode 100644
index 0000000000000000000000000000000000000000..3717612f277368f37f566e3092161dd932b9f026
--- /dev/null
+++ b/hm-gateway/src/main/java/com/hmall/gateway/utils/JwtTool.java
@@ -0,0 +1,82 @@
+package com.hmall.gateway.utils;
+
+import cn.hutool.core.exceptions.ValidateException;
+import cn.hutool.jwt.JWT;
+import cn.hutool.jwt.JWTValidator;
+import cn.hutool.jwt.signers.JWTSigner;
+import cn.hutool.jwt.signers.JWTSignerUtil;
+import com.hmall.common.exception.UnauthorizedException;
+import org.springframework.stereotype.Component;
+
+import java.security.KeyPair;
+import java.time.Duration;
+import java.util.Date;
+
+@Component
+public class JwtTool {
+ private final JWTSigner jwtSigner;
+
+ public JwtTool(KeyPair keyPair) {
+ this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);
+ }
+
+ /**
+ * 创建 access-token
+ *
+ * @param userId 用户信息
+ * @return access-token
+ */
+ public String createToken(Long userId, Duration ttl) {
+ // 1.生成jws
+ return JWT.create()
+ .setPayload("user", userId)
+ .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis()))
+ .setSigner(jwtSigner)
+ .sign();
+ }
+
+ /**
+ * 解析token
+ *
+ * @param token token
+ * @return 解析刷新token得到的用户信息
+ */
+ public Long parseToken(String token) {
+ // 1.校验token是否为空
+ if (token == null) {
+ throw new UnauthorizedException("未登录");
+ }
+ // 2.校验并解析jwt
+ JWT jwt;
+ try {
+ jwt = JWT.of(token).setSigner(jwtSigner);
+ } catch (Exception e) {
+ throw new UnauthorizedException("无效的token", e);
+ }
+ // 2.校验jwt是否有效
+ if (!jwt.verify()) {
+ // 验证失败
+ throw new UnauthorizedException("无效的token");
+ }
+ // 3.校验是否过期
+ try {
+ JWTValidator.of(jwt).validateDate();
+ } catch (ValidateException e) {
+ throw new UnauthorizedException("token已经过期");
+ }
+ // 4.数据格式校验
+ Object userPayload = jwt.getPayload("user");
+ if (userPayload == null) {
+ // 数据为空
+ throw new UnauthorizedException("无效的token");
+ }
+
+ // 5.数据解析
+ try {
+ return Long.valueOf(userPayload.toString());
+ } catch (RuntimeException e) {
+ // 数据格式有误
+ throw new UnauthorizedException("无效的token");
+ }
+ }
+}
\ No newline at end of file
diff --git a/hm-gateway/src/main/resources/application.yml b/hm-gateway/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..47fbb02d121da8004a23275e648406db9b6bbf28
--- /dev/null
+++ b/hm-gateway/src/main/resources/application.yml
@@ -0,0 +1,2 @@
+server:
+ port: 8080
\ No newline at end of file
diff --git a/hm-gateway/src/main/resources/backup/application.yml b/hm-gateway/src/main/resources/backup/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..49cad114b807f2e38c0b2cee870781926e90e9fe
--- /dev/null
+++ b/hm-gateway/src/main/resources/backup/application.yml
@@ -0,0 +1,43 @@
+server:
+ port: 8080
+spring:
+ application:
+ name: gateway
+ cloud:
+ nacos:
+ server-addr: 192.168.101.68:8848
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间z
+ gateway:
+ routes:
+ - id: item # 路由规则id,自定义,唯一
+ uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
+ predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
+ - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
+ - id: cart
+ uri: lb://cart-service
+ predicates:
+ - Path=/carts/**
+ - id: user
+ uri: lb://user-service
+ predicates:
+ - Path=/users/**,/addresses/**
+ - id: trade
+ uri: lb://trade-service
+ predicates:
+ - Path=/orders/**
+ - id: pay
+ uri: lb://pay-service
+ predicates:
+ - Path=/pay-orders/**
+hm:
+ jwt:
+ location: classpath:hmall.jks # 秘钥地址
+ alias: hmall # 秘钥别名
+ password: hmall123 # 秘钥文件密码
+ tokenTTL: 30m # 登录有效期
+ auth:
+ excludePaths: # 无需身份校验的路径
+ - /search/**
+ - /users/login
+ - /items/**
\ No newline at end of file
diff --git a/hm-gateway/src/main/resources/bootstrap.yaml b/hm-gateway/src/main/resources/bootstrap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..df848328b2ad240832caf69d116a8ccfb3f20914
--- /dev/null
+++ b/hm-gateway/src/main/resources/bootstrap.yaml
@@ -0,0 +1,16 @@
+spring:
+ application:
+ name: gateway # 服务名称
+ profiles:
+ active: dev
+ cloud:
+ nacos:
+ server-addr: ${NACOS_ADDR:192.168.101.68:8848} # nacos地址
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ config: #配置中心
+ file-extension: yaml # 文件后缀名
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ group: ${GROUP_NAME:DEFAULT_GROUP} # 配置分组
+ shared-configs: # 共享配置
+ - dataId: shared-log.yaml # 共享日志配置
\ No newline at end of file
diff --git a/hm-gateway/src/main/resources/hmall.jks b/hm-gateway/src/main/resources/hmall.jks
new file mode 100644
index 0000000000000000000000000000000000000000..a34bd33d09d4c33b13769213e6c07c2267cce162
Binary files /dev/null and b/hm-gateway/src/main/resources/hmall.jks differ
diff --git a/item-service/pom.xml b/item-service/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4ddcc5084bb3621b4f51f0d54ae0b750085a8517
--- /dev/null
+++ b/item-service/pom.xml
@@ -0,0 +1,82 @@
+
+
+
+ hmall-parent
+ com.hmall
+ 1.0.0
+
+ 4.0.0
+
+ item-service
+
+
+ 11
+ 11
+
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-seata
+
+
+
+ com.hmall
+ hm-api
+ 1.0.0
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ com.hmall
+ hm-common
+ 1.0.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+ ${project.artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/item-service/src/main/java/com/hmall/item/ItemServiceApplication.java b/item-service/src/main/java/com/hmall/item/ItemServiceApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..b8f036f2be9079ddd67dae0b13b2fc2cbfca110c
--- /dev/null
+++ b/item-service/src/main/java/com/hmall/item/ItemServiceApplication.java
@@ -0,0 +1,15 @@
+package com.hmall.item;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@MapperScan("com.hmall.item.mapper")
+@SpringBootApplication
+public class ItemServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ItemServiceApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/item-service/src/main/java/com/hmall/item/controller/ItemController.java b/item-service/src/main/java/com/hmall/item/controller/ItemController.java
new file mode 100644
index 0000000000000000000000000000000000000000..0d48effb6238bd71bbc3b52c89cde3778a9c593c
--- /dev/null
+++ b/item-service/src/main/java/com/hmall/item/controller/ItemController.java
@@ -0,0 +1,90 @@
+package com.hmall.item.controller;
+
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.hmall.api.item.dto.ItemDTO;
+import com.hmall.api.item.dto.OrderDetailDTO;
+import com.hmall.common.domain.PageDTO;
+import com.hmall.common.domain.PageQuery;
+import com.hmall.common.utils.BeanUtils;
+import com.hmall.item.domain.po.Item;
+import com.hmall.item.service.IItemService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collection;
+import java.util.List;
+
+@Api(tags = "商品管理相关接口")
+@RestController
+@RequestMapping("/items")
+@RequiredArgsConstructor
+public class ItemController {
+
+ private final IItemService itemService;
+
+ @ApiOperation("分页查询商品")
+ @GetMapping("/page")
+ public PageDTO queryItemByPage(PageQuery query) {
+ // 1.分页查询
+ Page- result = itemService.page(query.toMpPage("update_time", false));
+ // 2.封装并返回
+ return PageDTO.of(result, ItemDTO.class);
+ }
+
+ @ApiOperation("根据id批量查询商品")
+ @GetMapping
+ public List queryItemByIds(@RequestParam("ids") Collection ids) {
+ /*try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }*/
+ 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 items) {
+ itemService.deductStock(items);
+ }
+}
diff --git a/item-service/src/main/java/com/hmall/item/controller/SearchController.java b/item-service/src/main/java/com/hmall/item/controller/SearchController.java
new file mode 100644
index 0000000000000000000000000000000000000000..eaa819c1078340e40a92f4508566d02764a3ac10
--- /dev/null
+++ b/item-service/src/main/java/com/hmall/item/controller/SearchController.java
@@ -0,0 +1,40 @@
+package com.hmall.item.controller;
+
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.hmall.api.item.dto.ItemDTO;
+import com.hmall.common.domain.PageDTO;
+import com.hmall.item.domain.po.Item;
+import com.hmall.item.domain.query.ItemPageQuery;
+import com.hmall.item.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 search(ItemPageQuery query) {
+ // 分页查询
+ Page
- 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);
+ }
+}
diff --git a/item-service/src/main/java/com/hmall/item/domain/po/Item.java b/item-service/src/main/java/com/hmall/item/domain/po/Item.java
new file mode 100644
index 0000000000000000000000000000000000000000..dda3a957e53e175d805b5370f6b9188a7f434904
--- /dev/null
+++ b/item-service/src/main/java/com/hmall/item/domain/po/Item.java
@@ -0,0 +1,113 @@
+package com.hmall.item.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;
+
+/**
+ *
+ * 商品表
+ *
+ *
+ * @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;
+
+
+}
diff --git a/item-service/src/main/java/com/hmall/item/domain/query/ItemPageQuery.java b/item-service/src/main/java/com/hmall/item/domain/query/ItemPageQuery.java
new file mode 100644
index 0000000000000000000000000000000000000000..bff2ca4c250aea4e8e0369a819d948037241d261
--- /dev/null
+++ b/item-service/src/main/java/com/hmall/item/domain/query/ItemPageQuery.java
@@ -0,0 +1,23 @@
+package com.hmall.item.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;
+}
diff --git a/item-service/src/main/java/com/hmall/item/mapper/ItemMapper.java b/item-service/src/main/java/com/hmall/item/mapper/ItemMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..2496dfa4566048ca7f3b78891a50d9eedfee1d2b
--- /dev/null
+++ b/item-service/src/main/java/com/hmall/item/mapper/ItemMapper.java
@@ -0,0 +1,21 @@
+package com.hmall.item.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.hmall.api.item.dto.OrderDetailDTO;
+import com.hmall.item.domain.po.Item;
+import org.apache.ibatis.annotations.Update;
+
+/**
+ *
+ * 商品表 Mapper 接口
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface ItemMapper extends BaseMapper- {
+
+ @Update("UPDATE item SET stock = stock - #{num} WHERE id = #{itemId} and stock >= #{num}")
+ int updateStock(OrderDetailDTO orderDetail);
+}
diff --git a/item-service/src/main/java/com/hmall/item/service/IItemService.java b/item-service/src/main/java/com/hmall/item/service/IItemService.java
new file mode 100644
index 0000000000000000000000000000000000000000..680ef833e52c0e5a6733ca548d035b94e3a3cadb
--- /dev/null
+++ b/item-service/src/main/java/com/hmall/item/service/IItemService.java
@@ -0,0 +1,25 @@
+package com.hmall.item.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.hmall.api.item.dto.ItemDTO;
+import com.hmall.api.item.dto.OrderDetailDTO;
+import com.hmall.item.domain.po.Item;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ *
+ * 商品表 服务类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface IItemService extends IService- {
+
+ void deductStock(List items);
+
+ List queryItemByIds(Collection ids);
+}
diff --git a/item-service/src/main/java/com/hmall/item/service/impl/ItemServiceImpl.java b/item-service/src/main/java/com/hmall/item/service/impl/ItemServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..88134ec2a4b959af619ad2d81c951efc4b65d368
--- /dev/null
+++ b/item-service/src/main/java/com/hmall/item/service/impl/ItemServiceImpl.java
@@ -0,0 +1,43 @@
+package com.hmall.item.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.hmall.api.item.dto.ItemDTO;
+import com.hmall.api.item.dto.OrderDetailDTO;
+import com.hmall.common.exception.BizIllegalException;
+import com.hmall.common.utils.BeanUtils;
+
+import com.hmall.item.domain.po.Item;
+import com.hmall.item.mapper.ItemMapper;
+import com.hmall.item.service.IItemService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ *
+ * 商品表 服务实现类
+ *
+ *
+ * @author 虎哥
+ */
+@Service
+public class ItemServiceImpl extends ServiceImpl implements IItemService {
+
+ @Transactional
+ @Override
+ public void deductStock(List items) {
+ for (OrderDetailDTO item : items) {
+ int i = baseMapper.updateStock(item);
+ if(i<=0){
+ throw new BizIllegalException("库存不足!");
+ }
+ }
+ }
+
+ @Override
+ public List queryItemByIds(Collection ids) {
+ return BeanUtils.copyList(listByIds(ids), ItemDTO.class);
+ }
+}
diff --git a/item-service/src/main/resources/application.yaml b/item-service/src/main/resources/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d91d6b03586f327740dc25e27fe8661a49621997
--- /dev/null
+++ b/item-service/src/main/resources/application.yaml
@@ -0,0 +1,6 @@
+server:
+ port: 8081
+hm:
+ swagger:
+ title: 商品服务接口文档
+ package: com.hmall.item.controller
\ No newline at end of file
diff --git a/item-service/src/main/resources/backup/application.yaml b/item-service/src/main/resources/backup/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fff4cbbe19a1f068d589c94189ed1a1c414837b0
--- /dev/null
+++ b/item-service/src/main/resources/backup/application.yaml
@@ -0,0 +1,70 @@
+server:
+ port: 8081
+spring:
+ application:
+ name: item-service
+ cloud:
+ nacos:
+ server-addr: 192.168.101.68:8848 # nacos地址
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ profiles:
+ active: dev
+ datasource:
+ url: jdbc:mysql://${hm.db.host}:3306/hm-item?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: 1579670286@qq.com
+ concat: 栋哥
+ version: v1.0.0
+ group:
+ default:
+ group-name: default
+ api-rule: package
+ api-rule-resources:
+ - com.hmall.user.controller
+hm:
+ jwt:
+ location: classpath:hmall.jks
+ alias: hmall
+ password: hmall123
+ tokenTTL: 30m
+ auth:
+ excludePaths:
+ - /search/**
+ - /users/login
+ - /items/**
+ - /hi
+seata:
+ registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
+ type: nacos # 注册中心类型 nacos
+ nacos:
+ server-addr: 192.168.101.68:8848 # nacos地址
+ namespace: "" # namespace,默认为空
+ group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
+ application: seata-server # seata服务名称
+ tx-service-group: hmall # 事务组名称
+ service:
+ vgroup-mapping: # 事务组与tc集群的映射关系
+ hmall: "default"
+# keytool -genkeypair -alias hmall -keyalg RSA -keypass hmall123 -keystore hmall.jks -storepass hmall123
\ No newline at end of file
diff --git a/item-service/src/main/resources/bootstrap.yaml b/item-service/src/main/resources/bootstrap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..9a4333cb9e0a5426c7bddacf7a9437b7bb791b16
--- /dev/null
+++ b/item-service/src/main/resources/bootstrap.yaml
@@ -0,0 +1,21 @@
+spring:
+ application:
+ name: item-service # 服务名称
+ profiles:
+ active: dev
+ cloud:
+ nacos:
+ server-addr: ${NACOS_ADDR:192.168.101.68:8848} # nacos地址
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ config: #配置中心
+ file-extension: yaml # 文件后缀名
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ group: ${GROUP_NAME:DEFAULT_GROUP} # 配置分组
+ shared-configs: # 共享配置
+ - dataId: shared-jdbc.yaml # 共享mybatis配置
+ - dataId: shared-log.yaml # 共享日志配置
+ - dataId: shared-swagger.yaml # 共享日志配置
+ - dataId: shared-feign.yaml # 共享feign配置
+ - dataId: shared-seata.yaml # 共享seata配置
+ - dataId: shared-sentinel.yaml # 共享sentinel配置
\ No newline at end of file
diff --git a/item-service/src/main/resources/mapper/ItemMapper.xml b/item-service/src/main/resources/mapper/ItemMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5a01cb5a61676565ec0fc0b9529aa579c3cc4bee
--- /dev/null
+++ b/item-service/src/main/resources/mapper/ItemMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/pay-service/pom.xml b/pay-service/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..54d2e78a9a16fd0e03d532c8be0288f9669ed268
--- /dev/null
+++ b/pay-service/pom.xml
@@ -0,0 +1,78 @@
+
+
+
+ hmall-parent
+ com.hmall
+ 1.0.0
+
+ 4.0.0
+
+ pay-service
+
+
+ 11
+ 11
+
+
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-seata
+
+
+
+ com.hmall
+ hm-common
+ 1.0.0
+
+
+
+ com.hmall
+ hm-api
+ 1.0.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ ${project.artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/pay-service/src/main/java/PayServiceApplication.java b/pay-service/src/main/java/PayServiceApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..4fe7fd6aee3f1cc1406d71001f3ca7307d482799
--- /dev/null
+++ b/pay-service/src/main/java/PayServiceApplication.java
@@ -0,0 +1,15 @@
+package com.hmall.pay;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+@EnableFeignClients(basePackages = "com.hmall.api")
+@MapperScan("com.hmall.pay.mapper")
+@SpringBootApplication
+public class PayServiceApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(com.hmall.pay.PayServiceApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/pay-service/src/main/java/com/hmall/pay/controller/PayController.java b/pay-service/src/main/java/com/hmall/pay/controller/PayController.java
new file mode 100644
index 0000000000000000000000000000000000000000..a03b8b72b00be97934ca3482a533cf2375b82082
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/controller/PayController.java
@@ -0,0 +1,40 @@
+package com.hmall.pay.controller;
+
+import com.hmall.common.exception.BizIllegalException;
+
+import com.hmall.pay.domain.dto.PayApplyDTO;
+import com.hmall.pay.domain.dto.PayOrderFormDTO;
+import com.hmall.pay.enums.PayType;
+import com.hmall.pay.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);
+ }
+}
diff --git a/pay-service/src/main/java/com/hmall/pay/domain/dto/PayApplyDTO.java b/pay-service/src/main/java/com/hmall/pay/domain/dto/PayApplyDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..49c24df438ff14072c5ee76288032d78d5d17bbe
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/domain/dto/PayApplyDTO.java
@@ -0,0 +1,30 @@
+package com.hmall.pay.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;
+}
\ No newline at end of file
diff --git a/pay-service/src/main/java/com/hmall/pay/domain/dto/PayOrderFormDTO.java b/pay-service/src/main/java/com/hmall/pay/domain/dto/PayOrderFormDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..214b79753091362d268f375fc523afd7dabc0bae
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/domain/dto/PayOrderFormDTO.java
@@ -0,0 +1,20 @@
+package com.hmall.pay.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;
+}
\ No newline at end of file
diff --git a/pay-service/src/main/java/com/hmall/pay/domain/po/PayOrder.java b/pay-service/src/main/java/com/hmall/pay/domain/po/PayOrder.java
new file mode 100644
index 0000000000000000000000000000000000000000..b79960a93b0499c74386c514794ea5ff0933aaa9
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/domain/po/PayOrder.java
@@ -0,0 +1,123 @@
+package com.hmall.pay.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;
+
+/**
+ *
+ * 支付订单
+ *
+ */
+@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;
+
+ /**
+ * 支付类型,1:h5,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;
+
+
+}
diff --git a/pay-service/src/main/java/com/hmall/pay/domain/vo/PayOrderVO.java b/pay-service/src/main/java/com/hmall/pay/domain/vo/PayOrderVO.java
new file mode 100644
index 0000000000000000000000000000000000000000..5523cd6308c45a7cd7e36baa5077ccca478d6fd1
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/domain/vo/PayOrderVO.java
@@ -0,0 +1,49 @@
+package com.hmall.pay.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 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("付类型,1:h5,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;
+}
diff --git a/pay-service/src/main/java/com/hmall/pay/enums/PayChannel.java b/pay-service/src/main/java/com/hmall/pay/enums/PayChannel.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d89a06f81d6c682b6401bdf4173d6acdb312d4f
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/enums/PayChannel.java
@@ -0,0 +1,25 @@
+package com.hmall.pay.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();
+ }
+}
diff --git a/pay-service/src/main/java/com/hmall/pay/enums/PayStatus.java b/pay-service/src/main/java/com/hmall/pay/enums/PayStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..e64e18429e82e9bb08563fd0ddef998f739301a2
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/enums/PayStatus.java
@@ -0,0 +1,27 @@
+package com.hmall.pay.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;
+ }
+}
\ No newline at end of file
diff --git a/pay-service/src/main/java/com/hmall/pay/enums/PayType.java b/pay-service/src/main/java/com/hmall/pay/enums/PayType.java
new file mode 100644
index 0000000000000000000000000000000000000000..af68b9b3ee0732024fc368c019a4619c3eec4d5e
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/enums/PayType.java
@@ -0,0 +1,27 @@
+package com.hmall.pay.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;
+ }
+}
diff --git a/pay-service/src/main/java/com/hmall/pay/mapper/PayOrderMapper.java b/pay-service/src/main/java/com/hmall/pay/mapper/PayOrderMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1156a5f55cf36469aaf1945abed90e129b186e7
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/mapper/PayOrderMapper.java
@@ -0,0 +1,17 @@
+package com.hmall.pay.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.hmall.pay.domain.po.PayOrder;
+
+/**
+ *
+ * 支付订单 Mapper 接口
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-16
+ */
+public interface PayOrderMapper extends BaseMapper {
+
+}
diff --git a/pay-service/src/main/java/com/hmall/pay/service/IPayOrderService.java b/pay-service/src/main/java/com/hmall/pay/service/IPayOrderService.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a3bb50ce8b0d83e590fb3f1a3fb6f7b076800d1
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/service/IPayOrderService.java
@@ -0,0 +1,22 @@
+package com.hmall.pay.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.hmall.pay.domain.dto.PayApplyDTO;
+import com.hmall.pay.domain.dto.PayOrderFormDTO;
+import com.hmall.pay.domain.po.PayOrder;
+
+/**
+ *
+ * 支付订单 服务类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-16
+ */
+public interface IPayOrderService extends IService {
+
+ String applyPayOrder(PayApplyDTO applyDTO);
+
+ void tryPayOrderByBalance(PayOrderFormDTO payOrderFormDTO);
+}
diff --git a/pay-service/src/main/java/com/hmall/pay/service/impl/PayOrderServiceImpl.java b/pay-service/src/main/java/com/hmall/pay/service/impl/PayOrderServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..e648723fc2b34162aff8d0d8c331fbee2458cfd4
--- /dev/null
+++ b/pay-service/src/main/java/com/hmall/pay/service/impl/PayOrderServiceImpl.java
@@ -0,0 +1,131 @@
+package com.hmall.pay.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.api.trade.TradeClient;
+import com.hmall.api.user.UserClient;
+import com.hmall.common.exception.BizIllegalException;
+import com.hmall.common.utils.BeanUtils;
+import com.hmall.common.utils.UserContext;
+
+import com.hmall.pay.domain.dto.PayApplyDTO;
+import com.hmall.pay.domain.dto.PayOrderFormDTO;
+import com.hmall.pay.domain.po.PayOrder;
+import com.hmall.pay.enums.PayStatus;
+import com.hmall.pay.mapper.PayOrderMapper;
+import com.hmall.pay.service.IPayOrderService;
+import io.seata.spring.annotation.GlobalTransactional;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+
+/**
+ *
+ * 支付订单 服务实现类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-16
+ */
+@Service
+@RequiredArgsConstructor
+public class PayOrderServiceImpl extends ServiceImpl implements IPayOrderService {
+
+ private final UserClient userClient;
+
+ private final TradeClient tradeClient;
+
+ @Override
+ public String applyPayOrder(PayApplyDTO applyDTO) {
+ // 1.幂等性校验
+ PayOrder payOrder = checkIdempotent(applyDTO);
+ // 2.返回结果
+ return payOrder.getId().toString();
+ }
+
+ @Override
+ @GlobalTransactional
+ public void tryPayOrderByBalance(PayOrderFormDTO payOrderFormDTO) {
+ // 1.查询支付单
+ PayOrder po = getById(payOrderFormDTO.getId());
+ // 2.判断状态
+ if(!PayStatus.WAIT_BUYER_PAY.equalsValue(po.getStatus())){
+ // 订单不是未支付,状态异常
+ throw new BizIllegalException("交易已支付或关闭!");
+ }
+ Long userId = UserContext.getUser();
+ // 3.尝试扣减余额
+ userClient.deductMoney(userId,payOrderFormDTO.getPw(), po.getAmount());
+ // 4.修改支付单状态
+ boolean success = markPayOrderSuccess(payOrderFormDTO.getId(), LocalDateTime.now());
+ if (!success) {
+ throw new BizIllegalException("交易已支付或关闭!");
+ }
+ // 5.修改订单状态
+ tradeClient.markOrderPaySuccess(userId,po.getBizOrderNo());
+ }
+
+ 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();
+ }
+}
diff --git a/pay-service/src/main/resources/application.yaml b/pay-service/src/main/resources/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..01d6c77049d013edc681048af74d983a19b6c9b7
--- /dev/null
+++ b/pay-service/src/main/resources/application.yaml
@@ -0,0 +1,6 @@
+server:
+ port: 8086
+hm:
+ swagger:
+ title: 支付服务接口文档
+ package: com.hmall.pay.controller
\ No newline at end of file
diff --git a/pay-service/src/main/resources/backup/application.yaml b/pay-service/src/main/resources/backup/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fba48eb08753cf2c1e12b77e9f660dd64ad7b39a
--- /dev/null
+++ b/pay-service/src/main/resources/backup/application.yaml
@@ -0,0 +1,56 @@
+server:
+ port: 8086
+spring:
+ application:
+ name: pay-service
+ profiles:
+ active: dev
+ datasource:
+ url: jdbc:mysql://${hm.db.host}:3306/hm-pay?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ username: root
+ password: ${hm.db.pw}
+ cloud:
+ nacos:
+ server-addr: 192.168.101.68
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+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: "支付服务接口文档"
+ url: https://www.itcast.cn
+ version: v1.0.0
+ group:
+ default:
+ group-name: default
+ api-rule: package
+ api-rule-resources:
+ - com.hmall.pay.controller
+seata:
+ registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
+ type: nacos # 注册中心类型 nacos
+ nacos:
+ server-addr: 192.168.101.68:8848 # nacos地址
+ namespace: "" # namespace,默认为空
+ group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
+ application: seata-server # seata服务名称
+ tx-service-group: hmall # 事务组名称
+ service:
+ vgroup-mapping: # 事务组与tc集群的映射关系
+ hmall: "default"
\ No newline at end of file
diff --git a/pay-service/src/main/resources/bootstrap.yaml b/pay-service/src/main/resources/bootstrap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4fb537f2ef1b87ee3da7ad2c0cbabfb6542a6721
--- /dev/null
+++ b/pay-service/src/main/resources/bootstrap.yaml
@@ -0,0 +1,21 @@
+spring:
+ application:
+ name: pay-service # 服务名称
+ profiles:
+ active: dev
+ cloud:
+ nacos:
+ server-addr: ${NACOS_ADDR:192.168.101.68:8848} # nacos地址
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ config: #配置中心
+ file-extension: yaml # 文件后缀名
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ group: ${GROUP_NAME:DEFAULT_GROUP} # 配置分组
+ shared-configs: # 共享配置
+ - dataId: shared-jdbc.yaml # 共享mybatis配置
+ - dataId: shared-log.yaml # 共享日志配置
+ - dataId: shared-swagger.yaml # 共享日志配置
+ - dataId: shared-feign.yaml # 共享feign配置
+ - dataId: shared-seata.yaml # 共享seata配置
+ - dataId: shared-sentinel.yaml # 共享sentinel配置
\ No newline at end of file
diff --git a/pay-service/src/main/resources/mapper/PayOrderMapper.xml b/pay-service/src/main/resources/mapper/PayOrderMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2cbe4a8d03bf898f33a1ca62913d0c61bd417058
--- /dev/null
+++ b/pay-service/src/main/resources/mapper/PayOrderMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bbd49479698ca44a107582a6815049b4ec33f86c
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,123 @@
+
+
+ 4.0.0
+
+ com.hmall
+ hmall-parent
+ pom
+
+ item-service
+ cart-service
+ hm-common
+ hm-api
+ user-service
+ trade-service
+ pay-service
+ hm-gateway
+
+ 1.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.12
+
+
+
+
+ 11
+ 11
+ UTF-8
+ UTF-8
+ 1.18.20
+ 2021.0.3
+ 2021.0.4.0
+ 3.4.3
+ 5.8.11
+ 8.0.23
+
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+
+ pom
+ import
+
+
+
+ com.alibaba.cloud
+ spring-cloud-alibaba-dependencies
+ ${spring-cloud-alibaba.version}
+ pom
+ import
+
+
+
+ mysql
+ mysql-connector-java
+ ${mysql.version}
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ ${mybatis-plus.version}
+
+
+
+ cn.hutool
+ hutool-all
+ ${hutool.version}
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+ com.alibaba.nacos
+ nacos-client
+ 2.4.0
+
+
+
+ org.projectlombok
+ lombok
+ ${org.projectlombok.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 11
+ 11
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trade-service/pom.xml b/trade-service/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..24c12264178245ab6c007c485cac2855443e2246
--- /dev/null
+++ b/trade-service/pom.xml
@@ -0,0 +1,84 @@
+
+
+
+ hmall-parent
+ com.hmall
+ 1.0.0
+
+ 4.0.0
+
+ trade-service
+
+
+ 11
+ 11
+
+
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-seata
+
+
+
+ com.hmall
+ hm-api
+ 1.0.0
+
+
+
+ com.hmall
+ hm-common
+ 1.0.0
+
+
+
+ com.hmall
+ hm-api
+ 1.0.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ ${project.artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/trade-service/src/main/java/com/hmall/trade/TradeApplication.java b/trade-service/src/main/java/com/hmall/trade/TradeApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..522c07e61dff7c7ab4009bc02624e9d473ffdba4
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/TradeApplication.java
@@ -0,0 +1,15 @@
+package com.hmall.trade;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+@EnableFeignClients(basePackages = "com.hmall.api")
+@MapperScan("com.hmall.trade.mapper")
+@SpringBootApplication
+public class TradeApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(TradeApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/trade-service/src/main/java/com/hmall/trade/controller/OrderController.java b/trade-service/src/main/java/com/hmall/trade/controller/OrderController.java
new file mode 100644
index 0000000000000000000000000000000000000000..593ce6a1893ed8cc954fd6c53689fe29d59724de
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/controller/OrderController.java
@@ -0,0 +1,40 @@
+package com.hmall.trade.controller;
+
+import com.hmall.common.utils.BeanUtils;
+
+import com.hmall.trade.domain.dto.OrderFormDTO;
+import com.hmall.trade.domain.vo.OrderVO;
+import com.hmall.trade.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);
+ }
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/domain/dto/OrderFormDTO.java b/trade-service/src/main/java/com/hmall/trade/domain/dto/OrderFormDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1f66ac862231be23cb2fb3cb0f6d1e8485ba084
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/domain/dto/OrderFormDTO.java
@@ -0,0 +1,19 @@
+package com.hmall.trade.domain.dto;
+
+import com.hmall.api.item.dto.OrderDetailDTO;
+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 details;
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/domain/po/Order.java b/trade-service/src/main/java/com/hmall/trade/domain/po/Order.java
new file mode 100644
index 0000000000000000000000000000000000000000..948e5e9a7a0e2481bdbed384759689eee602836e
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/domain/po/Order.java
@@ -0,0 +1,91 @@
+package com.hmall.trade.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;
+
+/**
+ *
+ *
+ *
+ *
+ * @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;
+
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/domain/po/OrderDetail.java b/trade-service/src/main/java/com/hmall/trade/domain/po/OrderDetail.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2a53f79864a35a85594d8a6107071532be838e8
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/domain/po/OrderDetail.java
@@ -0,0 +1,81 @@
+package com.hmall.trade.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;
+
+/**
+ *
+ * 订单详情表
+ *
+ *
+ * @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;
+
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/domain/po/OrderLogistics.java b/trade-service/src/main/java/com/hmall/trade/domain/po/OrderLogistics.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d9885ddc3f843c3a6af1dc10ab0798bab7a8a93
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/domain/po/OrderLogistics.java
@@ -0,0 +1,86 @@
+package com.hmall.trade.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;
+
+/**
+ *
+ *
+ *
+ *
+ * @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;
+
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/domain/vo/OrderVO.java b/trade-service/src/main/java/com/hmall/trade/domain/vo/OrderVO.java
new file mode 100644
index 0000000000000000000000000000000000000000..18b2a08e20223e2cfe11d981d4db2413a99479d9
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/domain/vo/OrderVO.java
@@ -0,0 +1,34 @@
+package com.hmall.trade.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;
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/mapper/OrderDetailMapper.java b/trade-service/src/main/java/com/hmall/trade/mapper/OrderDetailMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..084129b990902a2e998f2f198eae2f3c3a005cf6
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/mapper/OrderDetailMapper.java
@@ -0,0 +1,17 @@
+package com.hmall.trade.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.hmall.trade.domain.po.OrderDetail;
+
+/**
+ *
+ * 订单详情表 Mapper 接口
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface OrderDetailMapper extends BaseMapper {
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/mapper/OrderLogisticsMapper.java b/trade-service/src/main/java/com/hmall/trade/mapper/OrderLogisticsMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..bfc0277946753e5feef59e0a15ac09e9ccaf200b
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/mapper/OrderLogisticsMapper.java
@@ -0,0 +1,17 @@
+package com.hmall.trade.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.hmall.trade.domain.po.OrderLogistics;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface OrderLogisticsMapper extends BaseMapper {
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/mapper/OrderMapper.java b/trade-service/src/main/java/com/hmall/trade/mapper/OrderMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..42fcb94896d48df4598b480f7dca4f30c94c5aa3
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/mapper/OrderMapper.java
@@ -0,0 +1,17 @@
+package com.hmall.trade.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.hmall.trade.domain.po.Order;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface OrderMapper extends BaseMapper {
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/service/IOrderDetailService.java b/trade-service/src/main/java/com/hmall/trade/service/IOrderDetailService.java
new file mode 100644
index 0000000000000000000000000000000000000000..ede820a555a26361124ae2476a147f787cc8f02e
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/service/IOrderDetailService.java
@@ -0,0 +1,17 @@
+package com.hmall.trade.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.hmall.trade.domain.po.OrderDetail;
+
+/**
+ *
+ * 订单详情表 服务类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface IOrderDetailService extends IService {
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/service/IOrderLogisticsService.java b/trade-service/src/main/java/com/hmall/trade/service/IOrderLogisticsService.java
new file mode 100644
index 0000000000000000000000000000000000000000..ccec6b9a0a2a3d0cbd202d111825a3fd89ccdbdc
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/service/IOrderLogisticsService.java
@@ -0,0 +1,17 @@
+package com.hmall.trade.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.hmall.trade.domain.po.OrderLogistics;
+
+/**
+ *
+ * 服务类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface IOrderLogisticsService extends IService {
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/service/IOrderService.java b/trade-service/src/main/java/com/hmall/trade/service/IOrderService.java
new file mode 100644
index 0000000000000000000000000000000000000000..832636bca9900b653679127c8fb7edc4dd65ceda
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/service/IOrderService.java
@@ -0,0 +1,21 @@
+package com.hmall.trade.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.hmall.trade.domain.dto.OrderFormDTO;
+import com.hmall.trade.domain.po.Order;
+
+/**
+ *
+ * 服务类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface IOrderService extends IService {
+
+ Long createOrder(OrderFormDTO orderFormDTO);
+
+ void markOrderPaySuccess(Long orderId);
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/service/impl/OrderDetailServiceImpl.java b/trade-service/src/main/java/com/hmall/trade/service/impl/OrderDetailServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..a22f916644db34c166aeef50baa3a7f99326ac70
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/service/impl/OrderDetailServiceImpl.java
@@ -0,0 +1,21 @@
+package com.hmall.trade.service.impl;
+
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.hmall.trade.domain.po.OrderDetail;
+import com.hmall.trade.mapper.OrderDetailMapper;
+import com.hmall.trade.service.IOrderDetailService;
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * 订单详情表 服务实现类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+@Service
+public class OrderDetailServiceImpl extends ServiceImpl implements IOrderDetailService {
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/service/impl/OrderLogisticsServiceImpl.java b/trade-service/src/main/java/com/hmall/trade/service/impl/OrderLogisticsServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f31711831b10b3e34c5df0138b3203ccf30d95a
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/service/impl/OrderLogisticsServiceImpl.java
@@ -0,0 +1,21 @@
+package com.hmall.trade.service.impl;
+
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.hmall.trade.domain.po.OrderLogistics;
+import com.hmall.trade.mapper.OrderLogisticsMapper;
+import com.hmall.trade.service.IOrderLogisticsService;
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * 服务实现类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+@Service
+public class OrderLogisticsServiceImpl extends ServiceImpl implements IOrderLogisticsService {
+
+}
diff --git a/trade-service/src/main/java/com/hmall/trade/service/impl/OrderServiceImpl.java b/trade-service/src/main/java/com/hmall/trade/service/impl/OrderServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..73d22a25f5b738ed9800aa333906d832dee4d80a
--- /dev/null
+++ b/trade-service/src/main/java/com/hmall/trade/service/impl/OrderServiceImpl.java
@@ -0,0 +1,120 @@
+package com.hmall.trade.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.hmall.api.cart.CartClient;
+import com.hmall.api.item.ItemClient;
+import com.hmall.api.item.dto.ItemDTO;
+import com.hmall.api.item.dto.OrderDetailDTO;
+import com.hmall.common.exception.BadRequestException;
+import com.hmall.common.utils.UserContext;
+
+import com.hmall.trade.domain.dto.OrderFormDTO;
+import com.hmall.trade.domain.po.Order;
+import com.hmall.trade.domain.po.OrderDetail;
+import com.hmall.trade.mapper.OrderMapper;
+import com.hmall.trade.service.IOrderDetailService;
+import com.hmall.trade.service.IOrderService;
+import io.seata.spring.annotation.GlobalTransactional;
+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;
+
+/**
+ *
+ * 服务实现类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+@Service
+@RequiredArgsConstructor
+public class OrderServiceImpl extends ServiceImpl implements IOrderService {
+
+// private final IItemService itemService;
+ private final ItemClient itemClient;
+ private final IOrderDetailService detailService;
+ private final CartClient cartClient;
+
+ @Override
+// @Transactional
+ @GlobalTransactional
+ public Long createOrder(OrderFormDTO orderFormDTO) {
+ // 1.订单数据
+ Order order = new Order();
+ // 1.1.查询商品
+ List detailDTOS = orderFormDTO.getDetails();
+ // 1.2.获取商品id和数量的Map
+ Map itemNumMap = detailDTOS.stream()
+ .collect(Collectors.toMap(OrderDetailDTO::getItemId, OrderDetailDTO::getNum));
+ Set itemIds = itemNumMap.keySet();
+ // 1.3.查询商品
+ List items = itemClient.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());
+ Long userId = UserContext.getUser();
+ order.setUserId(userId);
+ order.setStatus(1);
+ // 1.6.将Order写入数据库order表中
+ save(order);
+
+ // 2.保存订单详情
+ List details = buildDetails(order.getId(), items, itemNumMap);
+ detailService.saveBatch(details);
+
+ // 3.清理购物车商品
+ cartClient.deleteCartItemByIds(userId,itemIds);
+
+ // 4.扣减库存
+ try {
+ itemClient.deductStock(detailDTOS);
+ } catch (Exception e) {
+ throw new RuntimeException("库存不足!");
+ }
+ /*if(true){
+ 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 buildDetails(Long orderId, List items, Map numMap) {
+ List 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;
+ }
+}
diff --git a/trade-service/src/main/resources/application.yaml b/trade-service/src/main/resources/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3421692e91a9368461bd1e72b9befa8908c0ac86
--- /dev/null
+++ b/trade-service/src/main/resources/application.yaml
@@ -0,0 +1,6 @@
+server:
+ port: 8085
+hm:
+ swagger:
+ title: 交易服务接口文档
+ package: com.hmall.trade.controller
\ No newline at end of file
diff --git a/trade-service/src/main/resources/backup/application.yaml b/trade-service/src/main/resources/backup/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6cae43afed54ef2736cbf328bcae1030f5101fc4
--- /dev/null
+++ b/trade-service/src/main/resources/backup/application.yaml
@@ -0,0 +1,56 @@
+server:
+ port: 8085
+spring:
+ application:
+ name: trade-service # 服务名称
+ profiles:
+ active: dev
+ datasource:
+ url: jdbc:mysql://${hm.db.host}:3306/hm-trade?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ username: root
+ password: ${hm.db.pw}
+ cloud:
+ nacos:
+ server-addr: 192.168.101.68 # nacos地址
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+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: "信息"
+ url: https://www.itcast.cn
+ version: v1.0.0
+ group:
+ default:
+ group-name: default
+ api-rule: package
+ api-rule-resources:
+ - com.hmall.trade.controller
+seata:
+ registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
+ type: nacos # 注册中心类型 nacos
+ nacos:
+ server-addr: 192.168.101.68:8848 # nacos地址
+ namespace: "" # namespace,默认为空
+ group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
+ application: seata-server # seata服务名称
+ tx-service-group: hmall # 事务组名称
+ service:
+ vgroup-mapping: # 事务组与tc集群的映射关系
+ hmall: "default"
\ No newline at end of file
diff --git a/trade-service/src/main/resources/bootstrap.yaml b/trade-service/src/main/resources/bootstrap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f2aa9af2647c61e8c81b89e6e81088baac4b2098
--- /dev/null
+++ b/trade-service/src/main/resources/bootstrap.yaml
@@ -0,0 +1,21 @@
+spring:
+ application:
+ name: trade-service # 服务名称
+ profiles:
+ active: dev
+ cloud:
+ nacos:
+ server-addr: ${NACOS_ADDR:192.168.101.68:8848} # nacos地址
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ config: #配置中心
+ file-extension: yaml # 文件后缀名
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ group: ${GROUP_NAME:DEFAULT_GROUP} # 配置分组
+ shared-configs: # 共享配置
+ - dataId: shared-jdbc.yaml # 共享mybatis配置
+ - dataId: shared-log.yaml # 共享日志配置
+ - dataId: shared-swagger.yaml # 共享日志配置
+ - dataId: shared-feign.yaml # 共享feign配置
+ - dataId: shared-seata.yaml # 共享seata配置
+ - dataId: shared-sentinel.yaml # 共享sentinel配置
\ No newline at end of file
diff --git a/user-service/pom.xml b/user-service/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7380f774d2d08a8d579aefe2beba753d8ded4d3c
--- /dev/null
+++ b/user-service/pom.xml
@@ -0,0 +1,78 @@
+
+
+
+ hmall-parent
+ com.hmall
+ 1.0.0
+
+ 4.0.0
+
+ user-service
+
+
+ 11
+ 11
+
+
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-seata
+
+
+
+ com.hmall
+ hm-common
+ 1.0.0
+
+
+
+ com.hmall
+ hm-api
+ 1.0.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ ${project.artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/user-service/src/main/java/com/hmall/user/UserServiceApplication.java b/user-service/src/main/java/com/hmall/user/UserServiceApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9dcaf798d121aa2363727cfda60549f04b92bc2
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/UserServiceApplication.java
@@ -0,0 +1,15 @@
+package com.hmall.user;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@MapperScan("com.hmall.user.mapper")
+@SpringBootApplication
+public class UserServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(UserServiceApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/user-service/src/main/java/com/hmall/user/config/JwtProperties.java b/user-service/src/main/java/com/hmall/user/config/JwtProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..c903e0b1b6bb5850cd8b33f238f067ea8fd7eeed
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/config/JwtProperties.java
@@ -0,0 +1,17 @@
+package com.hmall.user.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Component;
+
+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);
+}
diff --git a/user-service/src/main/java/com/hmall/user/config/SecurityConfig.java b/user-service/src/main/java/com/hmall/user/config/SecurityConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..28ddb3b4c59f3c2a6df4dfba3177e01ce8953211
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/config/SecurityConfig.java
@@ -0,0 +1,33 @@
+package com.hmall.user.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());
+ }
+}
\ No newline at end of file
diff --git a/user-service/src/main/java/com/hmall/user/controller/AddressController.java b/user-service/src/main/java/com/hmall/user/controller/AddressController.java
new file mode 100644
index 0000000000000000000000000000000000000000..d3858276fb810ad79862a1550b150cb935fe2c31
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/controller/AddressController.java
@@ -0,0 +1,62 @@
+package com.hmall.user.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.user.domain.dto.AddressDTO;
+import com.hmall.user.domain.po.Address;
+import com.hmall.user.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;
+
+/**
+ *
+ * 前端控制器
+ *
+ *
+ * @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 findMyAddresses() {
+ // 1.查询列表
+ List 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);
+ }
+}
diff --git a/user-service/src/main/java/com/hmall/user/controller/UserController.java b/user-service/src/main/java/com/hmall/user/controller/UserController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b49be302761cb0d71d46528d5979b736f67baa7
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/controller/UserController.java
@@ -0,0 +1,39 @@
+package com.hmall.user.controller;
+
+
+import com.hmall.user.domain.dto.LoginFormDTO;
+import com.hmall.user.domain.vo.UserLoginVO;
+import com.hmall.user.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);
+ }
+}
+
diff --git a/user-service/src/main/java/com/hmall/user/controller/inner/UserControllerInner.java b/user-service/src/main/java/com/hmall/user/controller/inner/UserControllerInner.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac24be7957139ff8aa69e84d4215f276a3384600
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/controller/inner/UserControllerInner.java
@@ -0,0 +1,36 @@
+package com.hmall.user.controller.inner;
+
+
+import com.hmall.user.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.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@Api(tags = "用户相关接口")
+@RestController
+@RequestMapping("inner/users")
+@RequiredArgsConstructor
+public class UserControllerInner {
+
+ private final IUserService userService;
+
+ @ApiOperation("扣减余额")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "userId", value = "用户id"),
+ @ApiImplicitParam(name = "pw", value = "支付密码"),
+ @ApiImplicitParam(name = "amount", value = "支付金额")
+ })
+ @PutMapping("/money/deduct")
+ public void deductMoney(@RequestParam("userId") Long userId,
+ @RequestParam("pw") String pw,
+ @RequestParam("amount") Integer amount) {
+ userService.deductMoney(userId, pw, amount);
+ }
+}
+
diff --git a/user-service/src/main/java/com/hmall/user/domain/dto/AddressDTO.java b/user-service/src/main/java/com/hmall/user/domain/dto/AddressDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..d21b7d015ed94301d8b8bdea464c46f93fbdc2d6
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/domain/dto/AddressDTO.java
@@ -0,0 +1,28 @@
+package com.hmall.user.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;
+}
diff --git a/user-service/src/main/java/com/hmall/user/domain/dto/LoginFormDTO.java b/user-service/src/main/java/com/hmall/user/domain/dto/LoginFormDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1b6122f5134c57cd4b399202408bafba7e937a4
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/domain/dto/LoginFormDTO.java
@@ -0,0 +1,20 @@
+package com.hmall.user.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;
+}
diff --git a/user-service/src/main/java/com/hmall/user/domain/po/Address.java b/user-service/src/main/java/com/hmall/user/domain/po/Address.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e1728c15a64928685db63f09b6215dd8827a70e
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/domain/po/Address.java
@@ -0,0 +1,77 @@
+package com.hmall.user.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;
+
+/**
+ *
+ *
+ *
+ *
+ * @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;
+
+
+}
diff --git a/user-service/src/main/java/com/hmall/user/domain/po/User.java b/user-service/src/main/java/com/hmall/user/domain/po/User.java
new file mode 100644
index 0000000000000000000000000000000000000000..495c97fc1cf3bab17fb360da5bb29af4405820ff
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/domain/po/User.java
@@ -0,0 +1,66 @@
+package com.hmall.user.domain.po;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.hmall.user.enums.UserStatus;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ *
+ * 用户表
+ *
+ *
+ * @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;
+
+
+}
diff --git a/user-service/src/main/java/com/hmall/user/domain/vo/UserLoginVO.java b/user-service/src/main/java/com/hmall/user/domain/vo/UserLoginVO.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1dbcfa916083c3b65f8235375f62a1229ef55c6
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/domain/vo/UserLoginVO.java
@@ -0,0 +1,11 @@
+package com.hmall.user.domain.vo;
+
+import lombok.Data;
+
+@Data
+public class UserLoginVO {
+ private String token;
+ private Long userId;
+ private String username;
+ private Integer balance;
+}
diff --git a/user-service/src/main/java/com/hmall/user/enums/UserStatus.java b/user-service/src/main/java/com/hmall/user/enums/UserStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad7f5266fa0a32d64ecbb6f49977b9cbe8c44277
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/enums/UserStatus.java
@@ -0,0 +1,30 @@
+package com.hmall.user.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("账户状态错误");
+ }
+}
\ No newline at end of file
diff --git a/user-service/src/main/java/com/hmall/user/mapper/AddressMapper.java b/user-service/src/main/java/com/hmall/user/mapper/AddressMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..eee64763a37382342e0289e93e7d559d61ec243a
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/mapper/AddressMapper.java
@@ -0,0 +1,17 @@
+package com.hmall.user.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.hmall.user.domain.po.Address;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface AddressMapper extends BaseMapper {
+
+}
diff --git a/user-service/src/main/java/com/hmall/user/mapper/UserMapper.java b/user-service/src/main/java/com/hmall/user/mapper/UserMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfd0bf1c750b6e234c041fbcb0d828a41f097180
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/mapper/UserMapper.java
@@ -0,0 +1,20 @@
+package com.hmall.user.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.hmall.user.domain.po.User;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
+
+/**
+ *
+ * 用户表 Mapper 接口
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface UserMapper extends BaseMapper {
+ @Update("update user set balance = balance - #{totalFee} where id = #{userId} and balance >= #{totalFee}")
+ int updateMoney(@Param("userId") Long userId, @Param("totalFee") Integer totalFee);
+}
diff --git a/user-service/src/main/java/com/hmall/user/service/IAddressService.java b/user-service/src/main/java/com/hmall/user/service/IAddressService.java
new file mode 100644
index 0000000000000000000000000000000000000000..a62ca76220f92569f474bf7885e2a56d5c346d2d
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/service/IAddressService.java
@@ -0,0 +1,17 @@
+package com.hmall.user.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.hmall.user.domain.po.Address;
+
+
+/**
+ *
+ * 服务类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface IAddressService extends IService {
+
+}
diff --git a/user-service/src/main/java/com/hmall/user/service/IUserService.java b/user-service/src/main/java/com/hmall/user/service/IUserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..768ccfb7bcf9f0da76200c107778fbcf757a9963
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/service/IUserService.java
@@ -0,0 +1,24 @@
+package com.hmall.user.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.hmall.user.domain.dto.LoginFormDTO;
+import com.hmall.user.domain.po.User;
+import com.hmall.user.domain.vo.UserLoginVO;
+
+/**
+ *
+ * 用户表 服务类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+public interface IUserService extends IService {
+
+ UserLoginVO login(LoginFormDTO loginFormDTO);
+
+ void deductMoney(String pw, Integer totalFee);
+
+ void deductMoney(Long userId,String pw, Integer totalFee);
+}
diff --git a/user-service/src/main/java/com/hmall/user/service/impl/AddressServiceImpl.java b/user-service/src/main/java/com/hmall/user/service/impl/AddressServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..76485c75cb4cc61dcbda13346c9ae7992f110abd
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/service/impl/AddressServiceImpl.java
@@ -0,0 +1,21 @@
+package com.hmall.user.service.impl;
+
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.hmall.user.domain.po.Address;
+import com.hmall.user.mapper.AddressMapper;
+import com.hmall.user.service.IAddressService;
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * 服务实现类
+ *
+ *
+ * @author 虎哥
+ * @since 2023-05-05
+ */
+@Service
+public class AddressServiceImpl extends ServiceImpl implements IAddressService {
+
+}
diff --git a/user-service/src/main/java/com/hmall/user/service/impl/UserServiceImpl.java b/user-service/src/main/java/com/hmall/user/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c1f3c23fc7377944cae237dce4f9a03dbe20adb
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/service/impl/UserServiceImpl.java
@@ -0,0 +1,95 @@
+package com.hmall.user.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.user.config.JwtProperties;
+import com.hmall.user.domain.dto.LoginFormDTO;
+import com.hmall.user.domain.po.User;
+import com.hmall.user.domain.vo.UserLoginVO;
+import com.hmall.user.enums.UserStatus;
+import com.hmall.user.mapper.UserMapper;
+import com.hmall.user.service.IUserService;
+import com.hmall.user.utils.JwtTool;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+/**
+ *
+ * 用户表 服务实现类
+ *
+ *
+ * @author 虎哥
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class UserServiceImpl extends ServiceImpl 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;
+ }
+ public static void main(String[] args) {
+ BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
+ String rawPassword = "123456";
+ String encodedPassword = encoder.encode(rawPassword);
+ System.out.println("Encoded password: " + encodedPassword);
+ }
+ @Override
+ public void deductMoney(String pw, Integer totalFee) {
+ log.info("开始扣款");
+ // 1.校验密码
+ Long userId = UserContext.getUser();
+ deductMoney(userId, pw, totalFee);
+ }
+
+ @Override
+ public void deductMoney(Long userId, String pw, Integer totalFee) {
+ User user = getById(userId);
+ if(user == null || !passwordEncoder.matches(pw, user.getPassword())){
+ // 密码错误
+ throw new BizIllegalException("用户密码错误");
+ }
+ // 2.尝试扣款
+ int i = baseMapper.updateMoney(userId, totalFee);
+ if (i <= 0) {
+ throw new RuntimeException("扣款失败,可能是余额不足!");
+ }
+ log.info("扣款成功");
+ }
+}
diff --git a/user-service/src/main/java/com/hmall/user/utils/JwtTool.java b/user-service/src/main/java/com/hmall/user/utils/JwtTool.java
new file mode 100644
index 0000000000000000000000000000000000000000..7261199ed934d7929fea106afb8e0b1761b70895
--- /dev/null
+++ b/user-service/src/main/java/com/hmall/user/utils/JwtTool.java
@@ -0,0 +1,82 @@
+package com.hmall.user.utils;
+
+import cn.hutool.core.exceptions.ValidateException;
+import cn.hutool.jwt.JWT;
+import cn.hutool.jwt.JWTValidator;
+import cn.hutool.jwt.signers.JWTSigner;
+import cn.hutool.jwt.signers.JWTSignerUtil;
+import com.hmall.common.exception.UnauthorizedException;
+import org.springframework.stereotype.Component;
+
+import java.security.KeyPair;
+import java.time.Duration;
+import java.util.Date;
+
+@Component
+public class JwtTool {
+ private final JWTSigner jwtSigner;
+
+ public JwtTool(KeyPair keyPair) {
+ this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);
+ }
+
+ /**
+ * 创建 access-token
+ *
+ * @param userId 用户信息
+ * @return access-token
+ */
+ public String createToken(Long userId, Duration ttl) {
+ // 1.生成jws
+ return JWT.create()
+ .setPayload("user", userId)
+ .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis()))
+ .setSigner(jwtSigner)
+ .sign();
+ }
+
+ /**
+ * 解析token
+ *
+ * @param token token
+ * @return 解析刷新token得到的用户信息
+ */
+ public Long parseToken(String token) {
+ // 1.校验token是否为空
+ if (token == null) {
+ throw new UnauthorizedException("未登录");
+ }
+ // 2.校验并解析jwt
+ JWT jwt;
+ try {
+ jwt = JWT.of(token).setSigner(jwtSigner);
+ } catch (Exception e) {
+ throw new UnauthorizedException("无效的token", e);
+ }
+ // 2.校验jwt是否有效
+ if (!jwt.verify()) {
+ // 验证失败
+ throw new UnauthorizedException("无效的token");
+ }
+ // 3.校验是否过期
+ try {
+ JWTValidator.of(jwt).validateDate();
+ } catch (ValidateException e) {
+ throw new UnauthorizedException("token已经过期");
+ }
+ // 4.数据格式校验
+ Object userPayload = jwt.getPayload("user");
+ if (userPayload == null) {
+ // 数据为空
+ throw new UnauthorizedException("无效的token");
+ }
+
+ // 5.数据解析
+ try {
+ return Long.valueOf(userPayload.toString());
+ } catch (RuntimeException e) {
+ // 数据格式有误
+ throw new UnauthorizedException("无效的token");
+ }
+ }
+}
\ No newline at end of file
diff --git a/user-service/src/main/resources/application.yaml b/user-service/src/main/resources/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e1cde396ce9cdf3494c7854e1224c412f06905c4
--- /dev/null
+++ b/user-service/src/main/resources/application.yaml
@@ -0,0 +1,11 @@
+server:
+ port: 8084
+hm:
+ swagger:
+ title: 用户服务接口文档
+ package: com.hmall.user.controller
+ jwt:
+ location: classpath:hmall.jks
+ alias: hmall
+ password: hmall123
+ tokenTTL: 30m
diff --git a/user-service/src/main/resources/backup/application.yaml b/user-service/src/main/resources/backup/application.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..96129b6a0dfa6a4cf0d1e069eb612117e165b293
--- /dev/null
+++ b/user-service/src/main/resources/backup/application.yaml
@@ -0,0 +1,62 @@
+server:
+ port: 8084
+spring:
+ application:
+ name: user-service # 服务名称
+ profiles:
+ active: dev
+ datasource:
+ url: jdbc:mysql://${hm.db.host}:3306/hm-user?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ username: root
+ password: ${hm.db.pw}
+ cloud:
+ nacos:
+ server-addr: 192.168.101.68 # nacos地址
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+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: "信息"
+ url: https://www.itcast.cn
+ version: v1.0.0
+ group:
+ default:
+ group-name: default
+ api-rule: package
+ api-rule-resources:
+ - com.hmall.user.controller
+hm:
+ jwt:
+ location: classpath:hmall.jks
+ alias: hmall
+ password: hmall123
+ tokenTTL: 30m
+seata:
+ registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
+ type: nacos # 注册中心类型 nacos
+ nacos:
+ server-addr: 192.168.101.68:8848 # nacos地址
+ namespace: "" # namespace,默认为空
+ group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
+ application: seata-server # seata服务名称
+ tx-service-group: hmall # 事务组名称
+ service:
+ vgroup-mapping: # 事务组与tc集群的映射关系
+ hmall: "default"
\ No newline at end of file
diff --git a/user-service/src/main/resources/bootstrap.yaml b/user-service/src/main/resources/bootstrap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..dee063a78448933b2648663e7ad6758797fa6557
--- /dev/null
+++ b/user-service/src/main/resources/bootstrap.yaml
@@ -0,0 +1,21 @@
+spring:
+ application:
+ name: user-service # 服务名称
+ profiles:
+ active: dev
+ cloud:
+ nacos:
+ server-addr: ${NACOS_ADDR:192.168.101.68:8848} # nacos地址
+ discovery: #注册中心
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ config: #配置中心
+ file-extension: yaml # 文件后缀名
+ namespace: ${NACOS_NAMESPACE:a58f68b9-414b-448c-b68a-5edfa8ced700} #命名空间
+ group: ${GROUP_NAME:DEFAULT_GROUP} # 配置分组
+ shared-configs: # 共享配置
+ - dataId: shared-jdbc.yaml # 共享mybatis配置
+ - dataId: shared-log.yaml # 共享日志配置
+ - dataId: shared-swagger.yaml # 共享日志配置
+ - dataId: shared-feign.yaml # 共享feign配置
+ - dataId: shared-seata.yaml # 共享seata配置
+ - dataId: shared-sentinel.yaml # 共享sentinel配置
\ No newline at end of file
diff --git a/user-service/src/main/resources/hmall.jks b/user-service/src/main/resources/hmall.jks
new file mode 100644
index 0000000000000000000000000000000000000000..a34bd33d09d4c33b13769213e6c07c2267cce162
Binary files /dev/null and b/user-service/src/main/resources/hmall.jks differ
diff --git a/user-service/src/main/resources/mapper/UserMapper.xml b/user-service/src/main/resources/mapper/UserMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..87bf648966226a38cbf41427eea728ed9494a44c
--- /dev/null
+++ b/user-service/src/main/resources/mapper/UserMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+