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 @@ + + + + +