5.22 拆分用户微服务

This commit is contained in:
zhangsan 2025-05-22 14:37:12 +08:00
parent a3e7faed94
commit 9bd4c9beaf
36 changed files with 915 additions and 28 deletions

View File

@ -10,6 +10,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>cart-service</artifactId> <artifactId>cart-service</artifactId>
<version>1.0.0</version>
<properties> <properties>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
@ -44,7 +45,11 @@
<version>1.0.0</version> <version>1.0.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-api</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>

View File

@ -3,8 +3,10 @@ package com.hmall.cart;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@MapperScan("com.hmall.cart.mapper") @MapperScan("com.hmall.cart.mapper")
@EnableFeignClients(basePackages= "com.hmall.api.client")
@SpringBootApplication @SpringBootApplication
public class CartApplication { public class CartApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -3,6 +3,9 @@ package com.hmall.cart.service.impl;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.cart.domain.dto.CartFormDTO; import com.hmall.cart.domain.dto.CartFormDTO;
import com.hmall.cart.domain.po.Cart; import com.hmall.cart.domain.po.Cart;
import com.hmall.cart.domain.vo.CartVO; import com.hmall.cart.domain.vo.CartVO;
@ -12,9 +15,6 @@ import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils; import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils; import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.UserContext; import com.hmall.common.utils.UserContext;
import com.hmall.domain.dto.ItemDTO;
import com.hmall.service.IItemService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -37,7 +37,8 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService { public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
// private final IItemService itemService; private final ItemClient itemClient; //@RequiredArgsConstructor会为final字段生成构造器
@Override @Override
public void addItem2Cart(CartFormDTO cartFormDTO) { public void addItem2Cart(CartFormDTO cartFormDTO) {
@ -81,25 +82,25 @@ public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements IC
} }
private void handleCartItems(List<CartVO> vos) { private void handleCartItems(List<CartVO> vos) {
// // 1.获取商品id // 1.获取商品id
// Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet()); Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// // 2.查询商品 // 2.查询商品
// List<ItemDTO> items = itemService.queryItemByIds(itemIds); List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
// if (CollUtils.isEmpty(items)) { if (CollUtils.isEmpty(items)) {
// return; return;
// } }
// // 3.转为 id item的map // 3.转为 id item的map
// Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity())); Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
// // 4.写入vo // 4.写入vo
// for (CartVO v : vos) { for (CartVO v : vos) {
// ItemDTO item = itemMap.get(v.getItemId()); ItemDTO item = itemMap.get(v.getItemId());
// if (item == null) { if (item == null) {
// continue; continue;
// } }
// v.setNewPrice(item.getPrice()); v.setNewPrice(item.getPrice());
// v.setStatus(item.getStatus()); v.setStatus(item.getStatus());
// v.setStock(item.getStock()); v.setStock(item.getStock());
// } }
} }
@Override @Override

View File

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

View File

@ -40,4 +40,8 @@ knife4j:
api-rule-resources: api-rule-resources:
- com.hmall.cart.controller - com.hmall.cart.controller
feign:
httpclient:
enabled: true # 使用 Apache HttpClient默认关闭
# keytool -genkeypair -alias hmall -keyalg RSA -keypass hmall123 -keystore hmall.jks -storepass hmall123 # keytool -genkeypair -alias hmall -keyalg RSA -keypass hmall123 -keystore hmall.jks -storepass hmall123

41
hm-api/pom.xml Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hmall</artifactId>
<groupId>com.heima</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hm-api</artifactId>
<dependencies>
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.6</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>

View File

@ -0,0 +1,16 @@
package com.hmall.api.client;
import com.hmall.api.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Collection;
import java.util.List;
@FeignClient("item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

View File

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

View File

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

View File

@ -44,7 +44,11 @@
<version>1.0.0</version> <version>1.0.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>

View File

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

View File

@ -3,6 +3,10 @@ server:
spring: spring:
application: application:
name: item-service name: item-service
cloud:
nacos:
server-addr: 124.71.159.195:8848 # nacos地址
profiles: profiles:
active: local active: local
datasource: datasource:

View File

@ -14,6 +14,7 @@
<module>item-service</module> <module>item-service</module>
<module>cart-service</module> <module>cart-service</module>
<module>user-service</module> <module>user-service</module>
<module>hm-api</module>
</modules> </modules>
<parent> <parent>

View File

@ -15,5 +15,53 @@
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
</properties> </properties>
<dependencies>
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-api</artifactId>
<version>1.0.0</version>
</dependency>
<!--common-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-service</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>

View File

@ -0,0 +1,16 @@
package com.hmall.user;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@MapperScan("com.hmall.user.mapper")
@EnableFeignClients(basePackages= "com.hmall.api.client")
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}

View File

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

View File

@ -0,0 +1,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());
}
}

View File

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

View File

@ -0,0 +1,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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

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

View File

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

View File

@ -0,0 +1,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;
}

View File

@ -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("账户状态错误");
}
}

View File

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

View File

@ -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;
/**
* <p>
* 用户表 Mapper 接口
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface UserMapper extends BaseMapper<User> {
@Update("update user set balance = balance - ${totalFee} where id = #{userId}")
void updateMoney(@Param("userId") Long userId, @Param("totalFee") Integer totalFee);
}

View File

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

View File

@ -0,0 +1,22 @@
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;
/**
* <p>
* 用户表 服务类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
public interface IUserService extends IService<User> {
UserLoginVO login(LoginFormDTO loginFormDTO);
void deductMoney(String pw, Integer totalFee);
}

View File

@ -0,0 +1,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;
/**
* <p>
* 服务实现类
* </p>
*
* @author 虎哥
* @since 2023-05-05
*/
@Service
public class AddressServiceImpl extends ServiceImpl<AddressMapper, Address> implements IAddressService {
}

View File

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

View File

@ -0,0 +1,81 @@
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
*
* @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");
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,57 @@
server:
port: 8084
spring:
application:
name: user-service
cloud:
nacos:
server-addr: 124.71.159.195:8848 # nacos地址
profiles:
active: local
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}
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
global-config:
db-config:
update-strategy: not_null
id-type: auto
logging:
level:
com.hmall: debug
pattern:
dateformat: HH:mm:ss:SSS
file:
path: "logs/${spring.application.name}"
knife4j:
enable: true
openapi:
title: 黑马商城用户服务接口文档
description: "黑马商城用户服务接口文档"
email: zhangsan@itcast.cn
concat: 宇哥
url: https://www.itcast.cn
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- com.hmall.user.controller
feign:
httpclient:
enabled: true # 使用 Apache HttpClient默认关闭
hm:
jwt:
location: classpath:hmall.jks
alias: hmall
password: hmall123
tokenTTL: 30m
# keytool -genkeypair -alias hmall -keyalg RSA -keypass hmall123 -keystore hmall.jks -storepass hmall123

Binary file not shown.