用户与商品服务
约 711 字大约 2 分钟
布欧-Lewyon
2026-05-15
首页 › Spring Cloud › 实战项目:微服务电商系统
用户服务
实体
@Data
@TableName("user")
public class User {
private Long id;
private String username;
private String password; // 加密存储
private String phone;
private String email;
private BigDecimal balance;
private LocalDateTime createTime;
}注册与登录
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public Result<User> register(RegisterRequest req) {
// 检查用户是否已存在
User existing = userMapper.findByUsername(req.getUsername());
if (existing != null) {
return Result.error("用户名已存在");
}
// 密码加密
User user = new User();
user.setUsername(req.getUsername());
user.setPassword(BCrypt.hashpw(req.getPassword(), BCrypt.gensalt()));
user.setCreateTime(LocalDateTime.now());
userMapper.insert(user);
return Result.success(user);
}
public Result<LoginResponse> login(LoginRequest req) {
User user = userMapper.findByUsername(req.getUsername());
if (user == null || !BCrypt.checkpw(req.getPassword(), user.getPassword())) {
return Result.error("用户名或密码错误");
}
String token = JwtUtil.generateToken(user.getId(), user.getUsername());
return Result.success(new LoginResponse(token, user));
}
}JWT 工具
@Component
public class JwtUtil {
@Value("${app.jwt.secret}")
private String secret;
public String generateToken(Long userId, String username) {
return Jwts.builder()
.claim("userId", userId)
.claim("username", username)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 86400_000))
.signWith(getKey())
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.verifyWith(getKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
private SecretKey getKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
}Feign 拦截器透传 Token
@Component
public class TokenFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 从当前请求上下文获取 Token
ServletRequestAttributes attrs = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
if (attrs != null) {
String token = attrs.getRequest().getHeader("Authorization");
if (token != null) {
template.header("Authorization", token);
}
}
}
}商品服务
实体
@Data
@TableName("product")
public class Product {
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
private String description;
private String imageUrl;
private Integer status; // 1:上架 0:下架
}商品 CRUD
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
@SentinelResource(value = "getProduct",
fallback = "getProductFallback")
public Result<Product> getProduct(@PathVariable Long id) {
Product product = productService.getById(id);
return product != null ? Result.success(product) : Result.error("商品不存在");
}
public Result<Product> getProductFallback(Long id, Throwable t) {
return Result.error("商品服务繁忙");
}
@GetMapping("/search")
@SentinelResource(value = "searchProduct",
blockHandler = "searchBlockHandler")
public Result<List<Product>> search(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") Integer page) {
Page<Product> result = productService.search(keyword, page);
return Result.success(result.getRecords());
}
public Result<List<Product>> searchBlockHandler(
String keyword, Integer page, BlockException e) {
return Result.error("搜索太频繁");
}
@PutMapping("/{id}/stock")
@SentinelResource(value = "deductStock")
public Result<Void> deductStock(@PathVariable Long id,
@RequestParam Integer quantity) {
boolean success = productService.deductStock(id, quantity);
return success ? Result.success(null) : Result.error("库存不足");
}
}扣减库存(Redis 缓存 + 数据库)
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private StringRedisTemplate redis;
@Transactional
public boolean deductStock(Long productId, Integer quantity) {
// Redis 预扣(提高性能)
String stockKey = "stock:product:" + productId;
Long remain = redis.opsForValue().decrement(stockKey, quantity);
if (remain == null || remain < 0) {
// 回滚 Redis
redis.opsForValue().increment(stockKey, quantity);
return false;
}
// 数据库扣减
int affected = productMapper.deductStock(productId, quantity);
if (affected == 0) {
// 回滚 Redis
redis.opsForValue().increment(stockKey, quantity);
return false;
}
return true;
}
}Feign 接口
// shop-order 中调用商品服务
@FeignClient(name = "product-service", path = "/api/products",
fallbackFactory = ProductFeignClientFallbackFactory.class)
public interface ProductFeignClient {
@GetMapping("/{id}")
Result<Product> getProduct(@PathVariable("id") Long id);
@PutMapping("/{id}/stock")
Result<Void> deductStock(@PathVariable("id") Long id,
@RequestParam("quantity") Integer quantity);
}小结
- 用户服务:注册(BCrypt 加密)、登录(JWT Token)、Feign 拦截器透传 Token。
- 商品服务:CRUD + 搜索 + 扣库存(Redis 预扣 + 数据库保障)。
@SentinelResource保护商品搜索和详情接口。- Token 通过
RequestInterceptor在 Feign 调用间自动传递。 - 库存使用 Redis 缓存热点数据,提升查询性能。
