Python实现建造微服务商城后台

不好意思发晚了

```text

shopping-mall-backend/

├── pom.xml

├── src/

│ ├── main/

│ │ ├── java/

│ │ │ └── com/

│ │ │ └── mall/

│ │ │ ├── MallApplication.java

│ │ │ ├── common/

│ │ │ │ ├── config/

│ │ │ │ │ ├── MybatisPlusConfig.java

│ │ │ │ │ ├── RedisConfig.java

│ │ │ │ │ ├── SwaggerConfig.java

│ │ │ │ │ └── WebConfig.java

│ │ │ │ ├── exception/

│ │ │ │ │ ├── BusinessException.java

│ │ │ │ │ ├── GlobalExceptionHandler.java

│ │ │ │ │ └── ResultCode.java

│ │ │ │ ├── interceptor/

│ │ │ │ │ └── JwtInterceptor.java

│ │ │ │ ├── utils/

│ │ │ │ │ ├── JwtUtil.java

│ │ │ │ │ └── RedisUtil.java

│ │ │ │ └── vo/

│ │ │ │ └── Result.java

│ │ │ ├── module/

│ │ │ │ ├── user/

│ │ │ │ │ ├── controller/

│ │ │ │ │ │ └── UserController.java

│ │ │ │ │ ├── entity/

│ │ │ │ │ │ └── User.java

│ │ │ │ │ ├── mapper/

│ │ │ │ │ │ └── UserMapper.java

│ │ │ │ │ ├── service/

│ │ │ │ │ │ ├── UserService.java

│ │ │ │ │ │ └── impl/

│ │ │ │ │ │ └── UserServiceImpl.java

│ │ │ │ │ └── dto/

│ │ │ │ │ ├── LoginDTO.java

│ │ │ │ │ └── UserDTO.java

│ │ │ │ ├── order/

│ │ │ │ │ ├── controller/

│ │ │ │ │ │ └── OrderController.java

│ │ │ │ │ ├── entity/

│ │ │ │ │ │ └── Order.java

│ │ │ │ │ ├── mapper/

│ │ │ │ │ │ └── OrderMapper.java

│ │ │ │ │ ├── service/

│ │ │ │ │ │ ├── OrderService.java

│ │ │ │ │ │ └── impl/

│ │ │ │ │ │ └── OrderServiceImpl.java

│ │ │ │ │ └── dto/

│ │ │ │ │ └── OrderDTO.java

│ │ │ │ └── inventory/

│ │ │ │ ├── controller/

│ │ │ │ │ └── InventoryController.java

│ │ │ │ ├── entity/

│ │ │ │ │ └── Inventory.java

│ │ │ │ ├── mapper/

│ │ │ │ │ └── InventoryMapper.java

│ │ │ │ ├── service/

│ │ │ │ │ ├── InventoryService.java

│ │ │ │ │ └── impl/

│ │ │ │ │ └── InventoryServiceImpl.java

│ │ │ │ └── dto/

│ │ │ │ └── InventoryDTO.java

│ │ └── resources/

│ │ ├── application.yml

│ │ ├── application-dev.yml

│ │ └── mapper/

│ │ ├── UserMapper.xml

│ │ ├── OrderMapper.xml

│ │ └── InventoryMapper.xml

│ └── test/

│ └── java/

│ └── com/

│ └── mall/

│ └── MallApplicationTests.java

└── .gitignore

```

核心代码文件

  1. pom.xml

```xml

<?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

https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>3.1.5</version>

<relativePath/>

</parent>

<groupId>com.mall</groupId>

<artifactId>shopping-mall-backend</artifactId>

<version>1.0.0</version>

<name>shopping-mall-backend</name>

<description>Shopping Mall Backend with Spring Boot 3</description>

<properties>

<java.version>17</java.version>

<mybatis-plus.version>3.5.3.1</mybatis-plus.version>

<jwt.version>0.11.5</jwt.version>

<swagger.version>2.4.0</swagger.version>

</properties>

<dependencies>

<!-- Spring Boot Starters -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-validation</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-aop</artifactId>

</dependency>

<!-- Database -->

<dependency>

<groupId>com.mysql</groupId>

<artifactId>mysql-connector-j</artifactId>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>com.baomidou</groupId>

<artifactId>mybatis-plus-spring-boot3-starter</artifactId>

<version>${mybatis-plus.version}</version>

</dependency>

<!-- Redis -->

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-pool2</artifactId>

</dependency>

<!-- JWT -->

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt-api</artifactId>

<version>${jwt.version}</version>

</dependency>

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt-impl</artifactId>

<version>${jwt.version}</version>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt-jackson</artifactId>

<version>${jwt.version}</version>

<scope>runtime</scope>

</dependency>

<!-- Swagger -->

<dependency>

<groupId>org.springdoc</groupId>

<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>

<version>${swagger.version}</version>

</dependency>

<!-- Utils -->

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<optional>true</optional>

</dependency>

<dependency>

<groupId>cn.hutool</groupId>

<artifactId>hutool-all</artifactId>

<version>5.8.20</version>

</dependency>

<!-- Test -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<configuration>

<excludes>

<exclude>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

</exclude>

</excludes>

</configuration>

</plugin>

</plugins>

</build>

</project>

```

  1. application.yml

```yaml

spring:

profiles:

active: dev

application:

name: shopping-mall-backend

server:

port: 8080

servlet:

context-path: /api

mybatis-plus:

mapper-locations: classpath*:mapper/**/*.xml

global-config:

db-config:

id-type: auto

logic-delete-field: deleted

logic-delete-value: 1

logic-not-delete-value: 0

configuration:

map-underscore-to-camel-case: true

log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

jwt:

secret: your-256-bit-secret-key-for-jwt-signing

expiration: 86400000 # 24 hours in milliseconds

swagger:

enabled: true

title: Shopping Mall API

description: Shopping Mall Backend API Documentation

version: 1.0.0

```

  1. application-dev.yml

```yaml

spring:

datasource:

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://localhost:3306/mall_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false

username: root

password: 123456

hikari:

maximum-pool-size: 10

minimum-idle: 5

connection-timeout: 30000

data:

redis:

host: localhost

port: 6379

database: 0

password:

timeout: 5000ms

lettuce:

pool:

max-active: 8

max-idle: 8

min-idle: 0

logging:

level:

com.mall: debug

```

  1. MallApplication.java

```java

package com.mall;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class MallApplication {

public static void main(String\[\] args) {

SpringApplication.run(MallApplication.class, args);

}

}

```

  1. common/config/MybatisPlusConfig.java

```java

package com.mall.common.config;

import com.baomidou.mybatisplus.annotation.DbType;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;

import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class MybatisPlusConfig {

@Bean

public MybatisPlusInterceptor mybatisPlusInterceptor() {

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

return interceptor;

}

}

```

  1. common/config/RedisConfig.java

```java

package com.mall.common.config;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;

import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration

public class RedisConfig {

@Bean

public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {

RedisTemplate<String, Object> template = new RedisTemplate<>();

template.setConnectionFactory(connectionFactory);

// Key serializer

template.setKeySerializer(new StringRedisSerializer());

template.setHashKeySerializer(new StringRedisSerializer());

// Value serializer

GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();

template.setValueSerializer(serializer);

template.setHashValueSerializer(serializer);

template.afterPropertiesSet();

return template;

}

}

```

  1. common/config/SwaggerConfig.java

```java

package com.mall.common.config;

import io.swagger.v3.oas.models.OpenAPI;

import io.swagger.v3.oas.models.info.Contact;

import io.swagger.v3.oas.models.info.Info;

import io.swagger.v3.oas.models.security.SecurityRequirement;

import io.swagger.v3.oas.models.security.SecurityScheme;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class SwaggerConfig {

@Value("${swagger.enabled:true}")

private boolean enabled;

@Value("${swagger.title:Shopping Mall API}")

private String title;

@Value("${swagger.description:Shopping Mall Backend API}")

private String description;

@Value("${swagger.version:1.0.0}")

private String version;

@Bean

public OpenAPI customOpenAPI() {

final String securitySchemeName = "BearerAuth";

return new OpenAPI()

.info(new Info()

.title(title)

.description(description)

.version(version)

.contact(new Contact()

.name("Mall Team")

.email("support@mall.com")))

.addSecurityItem(new SecurityRequirement().addList(securitySchemeName))

.schemaRequirement(securitySchemeName, new SecurityScheme()

.name(securitySchemeName)

.type(SecurityScheme.Type.HTTP)

.scheme("bearer")

.bearerFormat("JWT"));

}

}

```

  1. common/config/WebConfig.java

```java

package com.mall.common.config;

import com.mall.common.interceptor.JwtInterceptor;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration

public class WebConfig implements WebMvcConfigurer {

@Autowired

private JwtInterceptor jwtInterceptor;

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(jwtInterceptor)

.addPathPatterns("/**")

.excludePathPatterns(

"/user/login",

"/user/register",

"/swagger-ui/**",

"/v3/api-docs/**",

"/swagger-ui.html"

);

}

}

```

  1. common/utils/JwtUtil.java

```java

package com.mall.common.utils;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.security.Keys;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;

import java.nio.charset.StandardCharsets;

import java.util.Date;

import java.util.HashMap;

import java.util.Map;

@Component

public class JwtUtil {

@Value("${jwt.secret}")

private String secret;

@Value("${jwt.expiration}")

private Long expiration;

private SecretKey getSigningKey() {

return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));

}

public String generateToken(Long userId, String username) {

Map<String, Object> claims = new HashMap<>();

claims.put("userId", userId);

claims.put("username", username);

return Jwts.builder()

.setClaims(claims)

.setSubject(username)

.setIssuedAt(new Date())

.setExpiration(new Date(System.currentTimeMillis() + expiration))

.signWith(getSigningKey())

.compact();

}

public Claims parseToken(String token) {

return Jwts.parserBuilder()

.setSigningKey(getSigningKey())

.build()

.parseClaimsJws(token)

.getBody();

}

public Long getUserIdFromToken(String token) {

Claims claims = parseToken(token);

return claims.get("userId", Long.class);

}

public String getUsernameFromToken(String token) {

Claims claims = parseToken(token);

return claims.getSubject();

}

public boolean validateToken(String token) {

try {

parseToken(token);

return true;

} catch (Exception e) {

return false;

}

}

}

```

  1. common/interceptor/JwtInterceptor.java

```java

package com.mall.common.interceptor;

import com.mall.common.utils.JwtUtil;

import com.mall.common.exception.BusinessException;

import com.mall.common.exception.ResultCode;

import jakarta.servlet.http.HttpServletRequest;

import jakarta.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import org.springframework.util.StringUtils;

import org.springframework.web.servlet.HandlerInterceptor;

@Component

public class JwtInterceptor implements HandlerInterceptor {

@Autowired

private JwtUtil jwtUtil;

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

// OPTIONS requests pass through

if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {

return true;

}

String token = request.getHeader("Authorization");

if (!StringUtils.hasText(token)) {

throw new BusinessException(ResultCode.UNAUTHORIZED, "Token is required");

}

// Remove "Bearer " prefix if present

if (token.startsWith("Bearer ")) {

token = token.substring(7);

}

if (!jwtUtil.validateToken(token)) {

throw new BusinessException(ResultCode.UNAUTHORIZED, "Invalid token");

}

// Set user info in request attributes

request.setAttribute("userId", jwtUtil.getUserIdFromToken(token));

request.setAttribute("username", jwtUtil.getUsernameFromToken(token));

return true;

}

}

```

  1. common/vo/Result.java

```java

package com.mall.common.vo;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

@Data

@NoArgsConstructor

@AllArgsConstructor

public class Result<T> {

private Integer code;

private String message;

private T data;

private Long timestamp;

public static <T> Result<T> success(T data) {

return new Result<>(200, "Success", data, System.currentTimeMillis());

}

public static <T> Result<T> success(String message, T data) {

return new Result<>(200, message, data, System.currentTimeMillis());

}

public static <T> Result<T> error(Integer code, String message) {

return new Result<>(code, message, null, System.currentTimeMillis());

}

public static <T> Result<T> error(String message) {

return new Result<>(500, message, null, System.currentTimeMillis());

}

}

```

  1. common/exception/ResultCode.java

```java

package com.mall.common.exception;

import lombok.AllArgsConstructor;

import lombok.Getter;

@Getter

@AllArgsConstructor

public enum ResultCode {

SUCCESS(200, "Success"),

BAD_REQUEST(400, "Bad Request"),

UNAUTHORIZED(401, "Unauthorized"),

FORBIDDEN(403, "Forbidden"),

NOT_FOUND(404, "Not Found"),

CONFLICT(409, "Conflict"),

INTERNAL_ERROR(500, "Internal Server Error"),

// Business Errors

USER_NOT_FOUND(1001, "User not found"),

USER_EXISTS(1002, "User already exists"),

INVALID_PASSWORD(1003, "Invalid password"),

INSUFFICIENT_STOCK(2001, "Insufficient stock"),

ORDER_NOT_FOUND(2002, "Order not found"),

INVENTORY_NOT_FOUND(3001, "Inventory not found");

private final Integer code;

private final String message;

}

```

  1. common/exception/BusinessException.java

```java

package com.mall.common.exception;

import lombok.Getter;

@Getter

public class BusinessException extends RuntimeException {

private final Integer code;

private final String message;

public BusinessException(ResultCode resultCode) {

super(resultCode.getMessage());

this.code = resultCode.getCode();

this.message = resultCode.getMessage();

}

public BusinessException(ResultCode resultCode, String message) {

super(message);

this.code = resultCode.getCode();

this.message = message;

}

public BusinessException(Integer code, String message) {

super(message);

this.code = code;

this.message = message;

}

}

```

  1. common/exception/GlobalExceptionHandler.java

```java

package com.mall.common.exception;

import com.mall.common.vo.Result;

import lombok.extern.slf4j.Slf4j;

import org.springframework.validation.BindException;

import org.springframework.validation.FieldError;

import org.springframework.web.bind.MethodArgumentNotValidException;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.stream.Collectors;

@Slf4j

@RestControllerAdvice

public class GlobalExceptionHandler {

@ExceptionHandler(BusinessException.class)

public Result<?> handleBusinessException(BusinessException e) {

log.error("Business exception: {}", e.getMessage());

return Result.error(e.getCode(), e.getMessage());

}

@ExceptionHandler(MethodArgumentNotValidException.class)

public Result<?> handleValidationException(MethodArgumentNotValidException e) {

String message = e.getBindingResult().getFieldErrors().stream()

.map(FieldError::getDefaultMessage)

.collect(Collectors.joining(", "));

log.error("Validation exception: {}", message);

return Result.error(ResultCode.BAD_REQUEST.getCode(), message);

}

@ExceptionHandler(BindException.class)

public Result<?> handleBindException(BindException e) {

String message = e.getFieldErrors().stream()

.map(FieldError::getDefaultMessage)

.collect(Collectors.joining(", "));

log.error("Bind exception: {}", message);

return Result.error(ResultCode.BAD_REQUEST.getCode(), message);

}

@ExceptionHandler(Exception.class)

public Result<?> handleException(Exception e) {

log.error("Internal error: ", e);

return Result.error(ResultCode.INTERNAL_ERROR.getCode(), "System error, please try again later");

}

}

```

  1. module/user/entity/User.java

```java

package com.mall.module.user.entity;

import com.baomidou.mybatisplus.annotation.*;

import lombok.AllArgsConstructor;

import lombok.Builder;

import lombok.Data;

import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data

@Builder

@NoArgsConstructor

@AllArgsConstructor

@TableName("user")

public class User {

@TableId(type = IdType.AUTO)

private Long id;

private String username;

private String password;

private String email;

private String phone;

private String fullName;

private Integer status; // 0: disabled, 1: enabled

@TableLogic

private Integer deleted;

@TableField(fill = FieldFill.INSERT)

private LocalDateTime createdAt;

@TableField(fill = FieldFill.INSERT_UPDATE)

private LocalDateTime updatedAt;

}

```

  1. module/user/dto/LoginDTO.java

```java

package com.mall.module.user.dto;

import io.swagger.v3.oas.annotations.media.Schema;

import jakarta.validation.constraints.NotBlank;

import lombok.Data;

@Data

@Schema(description = "Login Request")

public class LoginDTO {

@NotBlank(message = "Username is required")

@Schema(description = "Username", required = true)

private String username;

@NotBlank(message = "Password is required")

@Schema(description = "Password", required = true)

private String password;

}

```

  1. module/user/dto/UserDTO.java

```java

package com.mall.module.user.dto;

import io.swagger.v3.oas.annotations.media.Schema;

import jakarta.validation.constraints.Email;

import jakarta.validation.constraints.NotBlank;

import lombok.Data;

@Data

@Schema(description = "User Request")

public class UserDTO {

@NotBlank(message = "Username is required")

@Schema(description = "Username", required = true)

private String username;

@NotBlank(message = "Password is required")

@Schema(description = "Password", required = true)

private String password;

@Email(message = "Invalid email format")

@Schema(description = "Email")

private String email;

@Schema(description = "Phone number")

private String phone;

@Schema(description = "Full name")

private String fullName;

}

```

  1. module/user/mapper/UserMapper.java

```java

package com.mall.module.user.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import com.mall.module.user.entity.User;

import org.apache.ibatis.annotations.Mapper;

import org.apache.ibatis.annotations.Select;

@Mapper

public interface UserMapper extends BaseMapper<User> {

@Select("SELECT * FROM user WHERE username = #{username}")

User findByUsername(String username);

}

```

  1. module/user/service/UserService.java

```java

package com.mall.module.user.service;

import com.baomidou.mybatisplus.extension.service.IService;

import com.mall.module.user.entity.User;

import com.mall.module.user.dto.LoginDTO;

import com.mall.module.user.dto.UserDTO;

import java.util.Map;

public interface UserService extends IService<User> {

Map<String, String> login(LoginDTO loginDTO);

boolean register(UserDTO userDTO);

User getByUsername(String username);

User getById(Long id);

}

```

  1. module/user/service/impl/UserServiceImpl.java

```java

package com.mall.module.user.service.impl;

import cn.hutool.crypto.digest.BCrypt;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.mall.common.exception.BusinessException;

import com.mall.common.exception.ResultCode;

import com.mall.common.utils.JwtUtil;

import com.mall.module.user.dto.LoginDTO;

import com.mall.module.user.dto.UserDTO;

import com.mall.module.user.entity.User;

import com.mall.module.user.mapper.UserMapper;

import com.mall.module.user.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import java.util.HashMap;

import java.util.Map;

@Service

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

@Autowired

private JwtUtil jwtUtil;

@Override

public Map<String, String> login(LoginDTO loginDTO) {

User user = baseMapper.findByUsername(loginDTO.getUsername());

if (user == null) {

throw new BusinessException(ResultCode.USER_NOT_FOUND);

}

if (!BCrypt.checkpw(loginDTO.getPassword(), user.getPassword())) {

throw new BusinessException(ResultCode.INVALID_PASSWORD);

}

if (user.getStatus() == 0) {

throw new BusinessException(ResultCode.FORBIDDEN, "User account is disabled");

}

String token = jwtUtil.generateToken(user.getId(), user.getUsername());

Map<String, String> result = new HashMap<>();

result.put("token", token);

result.put("username", user.getUsername());

return result;

}

@Override

public boolean register(UserDTO userDTO) {

User existing = baseMapper.findByUsername(userDTO.getUsername());

if (existing != null) {

throw new BusinessException(ResultCode.USER_EXISTS);

}

User user = User.builder()

.username(userDTO.getUsername())

.password(BCrypt.hashpw(userDTO.getPassword()))

.email(userDTO.getEmail())

.phone(userDTO.getPhone())

.fullName(userDTO.getFullName())

.status(1)

.build();

return save(user);

}

@Override

public User getByUsername(String username) {

return baseMapper.findByUsername(username);

}

@Override

public User getById(Long id) {

User user = super.getById(id);

if (user == null) {

throw new BusinessException(ResultCode.USER_NOT_FOUND);

}

return user;

}

}

```

  1. module/user/controller/UserController.java

```java

package com.mall.module.user.controller;

import com.mall.common.vo.Result;

import com.mall.module.user.dto.LoginDTO;

import com.mall.module.user.dto.UserDTO;

import com.mall.module.user.entity.User;

import com.mall.module.user.service.UserService;

import io.swagger.v3.oas.annotations.Operation;

import io.swagger.v3.oas.annotations.Parameter;

import io.swagger.v3.oas.annotations.tags.Tag;

import jakarta.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController

@RequestMapping("/user")

@Tag(name = "User Management", description = "User registration, login and management APIs")

public class UserController {

@Autowired

private UserService userService;

@PostMapping("/login")

@Operation(summary = "User login", description = "Authenticate user and return JWT token")

public Result<Map<String, String>> login(@Valid @RequestBody LoginDTO loginDTO) {

return Result.success(userService.login(loginDTO));

}

@PostMapping("/register")

@Operation(summary = "User registration", description = "Register a new user")

public Result<Boolean> register(@Valid @RequestBody UserDTO userDTO) {

return Result.success("Registration successful", userService.register(userDTO));

}

@GetMapping("/{id}")

@Operation(summary = "Get user by ID", description = "Retrieve user information by user ID")

public Result<User> getById(@Parameter(description = "User ID") @PathVariable Long id) {

return Result.success(userService.getById(id));

}

@GetMapping("/profile")

@Operation(summary = "Get current user profile", description = "Retrieve current authenticated user's profile")

public Result<User> getProfile(@RequestAttribute("userId") Long userId) {

return Result.success(userService.getById(userId));

}

}

```

  1. module/order/entity/Order.java

```java

package com.mall.module.order.entity;

import com.baomidou.mybatisplus.annotation.*;

import lombok.AllArgsConstructor;

import lombok.Builder;

import lombok.Data;

import lombok.NoArgsConstructor;

import java.math.BigDecimal;

import java.time.LocalDateTime;

@Data

@Builder

@NoArgsConstructor

@AllArgsConstructor

@TableName("`order`")

public class Order {

@TableId(type = IdType.AUTO)

private Long id;

private String orderNo;

private Long userId;

private Long productId;

private Integer quantity;

private BigDecimal amount;

private Integer status; // 0: pending, 1: paid, 2: shipped, 3: completed, 4: cancelled

private String address;

private String remark;

@TableLogic

private Integer deleted;

@TableField(fill = FieldFill.INSERT)

private LocalDateTime createdAt;

@TableField(fill = FieldFill.INSERT_UPDATE)

private LocalDateTime updatedAt;

}

```

  1. module/order/dto/OrderDTO.java

```java

package com.mall.module.order.dto;

import io.swagger.v3.oas.annotations.media.Schema;

import jakarta.validation.constraints.Min;

import jakarta.validation.constraints.NotNull;

import lombok.Data;

import java.math.BigDecimal;

@Data

@Schema(description = "Order Request")

public class OrderDTO {

@NotNull(message = "Product ID is required")

@Schema(description = "Product ID", required = true)

private Long productId;

@NotNull(message = "Quantity is required")

@Min(value = 1, message = "Quantity must be at least 1")

@Schema(description = "Quantity", required = true, minimum = "1")

private Integer quantity;

@Schema(description = "Shipping address")

private String address;

@Schema(description = "Order remark")

private String remark;

}

```

  1. module/order/mapper/OrderMapper.java

```java

package com.mall.module.order.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import com.mall.module.order.entity.Order;

import org.apache.ibatis.annotations.Mapper;

import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper

public interface OrderMapper extends BaseMapper<Order> {

@Select("SELECT * FROM `order` WHERE user_id = #{userId} ORDER BY created_at DESC")

List<Order> findByUserId(Long userId);

@Select("SELECT * FROM `order` WHERE order_no = #{orderNo}")

Order findByOrderNo(String orderNo);

}

```

  1. module/order/service/OrderService.java

```java

package com.mall.module.order.service;

import com.baomidou.mybatisplus.extension.service.IService;

import com.mall.module.order.entity.Order;

import com.mall.module.order.dto.OrderDTO;

import java.util.List;

public interface OrderService extends IService<Order> {

Order createOrder(Long userId, OrderDTO orderDTO);

Order getByOrderNo(String orderNo);

List<Order> getByUserId(Long userId);

boolean cancelOrder(Long orderId, Long userId);

boolean updateOrderStatus(Long orderId, Integer status);

}

```

  1. module/order/service/impl/OrderServiceImpl.java

```java

package com.mall.module.order.service.impl;

import cn.hutool.core.util.IdUtil;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.mall.common.exception.BusinessException;

import com.mall.common.exception.ResultCode;

import com.mall.module.inventory.entity.Inventory;

import com.mall.module.inventory.service.InventoryService;

import com.mall.module.order.dto.OrderDTO;

import com.mall.module.order.entity.Order;

import com.mall.module.order.mapper.OrderMapper;

import com.mall.module.order.service.OrderService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

import java.util.List;

@Service

public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

@Autowired

private InventoryService inventoryService;

@Override

@Transactional(rollbackFor = Exception.class)

public Order createOrder(Long userId, OrderDTO orderDTO) {

// Check inventory

Inventory inventory = inventoryService.getByProductId(orderDTO.getProductId());

if (inventory == null || inventory.getStock() < orderDTO.getQuantity()) {

throw new BusinessException(ResultCode.INSUFFICIENT_STOCK);

}

// Deduct stock

inventoryService.deductStock(orderDTO.getProductId(), orderDTO.getQuantity());

// Create order

Order order = Order.builder()

.orderNo(IdUtil.getSnowflakeNextIdStr())

.userId(userId)

.productId(orderDTO.getProductId())

.quantity(orderDTO.getQuantity())

.amount(BigDecimal.valueOf(inventory.getPrice() * orderDTO.getQuantity()))

.address(orderDTO.getAddress())

.remark(orderDTO.getRemark())

.status(0) // pending

.build();

save(order);

return order;

}

@Override

public Order getByOrderNo(String orderNo) {

Order order = baseMapper.findByOrderNo(orderNo);

if (order == null) {

throw new BusinessException(ResultCode.ORDER_NOT_FOUND);

}

return order;

}

@Override

public List<Order> getByUserId(Long userId) {

return baseMapper.findByUserId(userId);

}

@Override

@Transactional(rollbackFor = Exception.class)

public boolean cancelOrder(Long orderId, Long userId) {

Order order = getById(orderId);

if (order == null) {

throw new BusinessException(ResultCode.ORDER_NOT_FOUND);

}

if (!order.getUserId().equals(userId)) {

throw new BusinessException(ResultCode.FORBIDDEN, "You are not authorized to cancel this order");

}

if (order.getStatus() >= 1) {

throw new BusinessException(ResultCode.CONFLICT, "Order cannot be cancelled in its current status");

}

// Restore stock

inventoryService.addStock(order.getProductId(), order.getQuantity());

order.setStatus(4); // cancelled

return updateById(order);

}

@Override

public boolean updateOrderStatus(Long orderId, Integer status) {

Order order = getById(orderId);

if (order == null) {

throw new BusinessException(ResultCode.ORDER_NOT_FOUND);

}

order.setStatus(status);

return updateById(order);

}

}

```

  1. module/order/controller/OrderController.java

```java

package com.mall.module.order.controller;

import com.mall.common.vo.Result;

import com.mall.module.order.dto.OrderDTO;

import com.mall.module.order.entity.Order;

import com.mall.module.order.service.OrderService;

import io.swagger.v3.oas.annotations.Operation;

import io.swagger.v3.oas.annotations.Parameter;

import io.swagger.v3.oas.annotations.tags.Tag;

import jakarta.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController

@RequestMapping("/order")

@Tag(name = "Order Management", description = "Order creation, query and management APIs")

public class OrderController {

@Autowired

private OrderService orderService;

@PostMapping

@Operation(summary = "Create order", description = "Create a new order and deduct inventory")

public Result<Order> createOrder(

@RequestAttribute("userId") Long userId,

@Valid @RequestBody OrderDTO orderDTO) {

return Result.success("Order created successfully", orderService.createOrder(userId, orderDTO));

}

@GetMapping("/my")

@Operation(summary = "Get user orders", description = "Retrieve all orders for current user")

public Result<List<Order>> getMyOrders(@RequestAttribute("userId") Long userId) {

return Result.success(orderService.getByUserId(userId));

}

@GetMapping("/{id}")

@Operation(summary = "Get order by ID", description = "Retrieve order details by order ID")

public Result<Order> getById(@Parameter(description = "Order ID") @PathVariable Long id) {

return Result.success(orderService.getById(id));

}

@GetMapping("/order-no/{orderNo}")

@Operation(summary = "Get order by order number", description = "Retrieve order details by order number")

public Result<Order> getByOrderNo(@Parameter(description = "Order Number") @PathVariable String orderNo) {

return Result.success(orderService.getByOrderNo(orderNo));

}

@PutMapping("/cancel/{id}")

@Operation(summary = "Cancel order", description = "Cancel an order and restore inventory")

public Result<Boolean> cancelOrder(

@Parameter(description = "Order ID") @PathVariable Long id,

@RequestAttribute("userId") Long userId) {

return Result.success("Order cancelled successfully", orderService.cancelOrder(id, userId));

}

}

```

  1. module/inventory/entity/Inventory.java

```java

package com.mall.module.inventory.entity;

import com.baomidou.mybatisplus.annotation.*;

import lombok.AllArgsConstructor;

import lombok.Builder;

import lombok.Data;

import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data

@Builder

@NoArgsConstructor

@AllArgsConstructor

@TableName("inventory")

public class Inventory {

@TableId(type = IdType.AUTO)

private Long id;

private Long productId;

private String productName;

private Integer stock;

private Double price;

private Integer reservedStock;

@TableLogic

private Integer deleted;

@TableField(fill = FieldFill.INSERT)

private LocalDateTime createdAt;

@TableField(fill = FieldFill.INSERT_UPDATE)

private LocalDateTime updatedAt;

}

```

  1. module/inventory/dto/InventoryDTO.java

```java

package com.mall.module.inventory.dto;

import io.swagger.v3.oas.annotations.media.Schema;

import jakarta.validation.constraints.Min;

import jakarta.validation.constraints.NotBlank;

import jakarta.validation.constraints.NotNull;

import lombok.Data;

@Data

@Schema(description = "Inventory Request")

public class InventoryDTO {

@NotNull(message = "Product ID is required")

@Schema(description = "Product ID", required = true)

private Long productId;

@NotBlank(message = "Product name is required")

@Schema(description = "Product name", required = true)

private String productName;

@NotNull(message = "Stock is required")

@Min(value = 0, message = "Stock must be at least 0")

@Schema(description = "Stock quantity", required = true, minimum = "0")

private Integer stock;

@NotNull(message = "Price is required")

@Min(value = 0, message = "Price must be at least 0")

@Schema(description = "Product price", required = true, minimum = "0")

private Double price;

}

```

  1. module/inventory/mapper/InventoryMapper.java

```java

package com.mall.module.inventory.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import com.mall.module.inventory.entity.Inventory;

import org.apache.ibatis.annotations.Mapper;

import org.apache.ibatis.annotations.Select;

import org.apache.ibatis.annotations.Update;

@Mapper

public interface InventoryMapper extends BaseMapper<Inventory> {

@Select("SELECT * FROM inventory WHERE product_id = #{productId}")

Inventory findByProductId(Long productId);

@Update("UPDATE inventory SET stock = stock - #{quantity}, reserved_stock = reserved_stock + #{quantity} " +

"WHERE product_id = #{productId} AND stock >= #{quantity}")

int deductStock(Long productId, Integer quantity);

@Update("UPDATE inventory SET stock = stock + #{quantity}, reserved_stock = reserved_stock - #{quantity} " +

"WHERE product_id = #{productId}")

int addStock(Long productId, Integer quantity);

}

```

  1. module/inventory/service/InventoryService.java

```java

package com.mall.module.inventory.service;

import com.baomidou.mybatisplus.extension.service.IService;

import com.mall.module.inventory.entity.Inventory;

import com.mall.module.inventory.dto.InventoryDTO;

public interface InventoryService extends IService<Inventory> {

Inventory getByProductId(Long productId);

boolean deductStock(Long productId, Integer quantity);

boolean addStock(Long productId, Integer quantity);

Inventory createInventory(InventoryDTO inventoryDTO);

boolean updateInventory(Long id, InventoryDTO inventoryDTO);

}

```

  1. module/inventory/service/impl/InventoryServiceImpl.java

```java

package com.mall.module.inventory.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.mall.common.exception.BusinessException;

import com.mall.common.exception.ResultCode;

import com.mall.module.inventory.dto.InventoryDTO;

import com.mall.module.inventory.entity.Inventory;

import com.mall.module.inventory.mapper.InventoryMapper;

import com.mall.module.inventory.service.InventoryService;

import org.springframework.cache.annotation.CacheEvict;

import org.springframework.cache.annotation.CachePut;

import org.springframework.cache.annotation.Cacheable;

import org.springframework.stereotype.Service;

@Service

public class InventoryServiceImpl extends ServiceImpl<InventoryMapper, Inventory> implements InventoryService {

@Override

@Cacheable(value = "inventory", key = "#productId")

public Inventory getByProductId(Long productId) {

Inventory inventory = baseMapper.findByProductId(productId);

if (inventory == null) {

throw new BusinessException(ResultCode.INVENTORY_NOT_FOUND);

}

return inventory;

}

@Override

public boolean deductStock(Long productId, Integer quantity) {

int result = baseMapper.deductStock(productId, quantity);

if (result <= 0) {

throw new BusinessException(ResultCode.INSUFFICIENT_STOCK);

}

return true;

}

@Override

public boolean addStock(Long productId, Integer quantity) {

int result = baseMapper.addStock(productId, quantity);

if (result <= 0) {

throw new BusinessException(ResultCode.INVENTORY_NOT_FOUND);

}

return true;

}

@Override

public Inventory createInventory(InventoryDTO inventoryDTO) {

Inventory existing = baseMapper.findByProductId(inventoryDTO.getProductId());

if (existing != null) {

throw new BusinessException(ResultCode.CONFLICT, "Inventory already exists for this product");

}

Inventory inventory = Inventory.builder()

.productId(inventoryDTO.getProductId())

.productName(inventoryDTO.getProductName())

.stock(inventoryDTO.getStock())

.price(inventoryDTO.getPrice())

.reservedStock(0)

.build();

save(inventory);

return inventory;

}

@Override

@CachePut(value = "inventory", key = "#result.productId")

public boolean updateInventory(Long id, InventoryDTO inventoryDTO) {

Inventory inventory = getById(id);

if (inventory == null) {

throw new BusinessException(ResultCode.INVENTORY_NOT_FOUND);

}

inventory.setProductName(inventoryDTO.getProductName());

inventory.setStock(inventoryDTO.getStock());

inventory.setPrice(inventoryDTO.getPrice());

return updateById(inventory);

}

}

```

  1. module/inventory/controller/InventoryController.java

```java

package com.mall.module.inventory.controller;

import com.mall.common.vo.Result;

import com.mall.module.inventory.dto.InventoryDTO;

import com.mall.module.inventory.entity.Inventory;

import com.mall.module.inventory.service.InventoryService;

import io.swagger.v3.oas.annotations.Operation;

import io.swagger.v3.oas.annotations.Parameter;

import io.swagger.v3.oas.annotations.tags.Tag;

import jakarta.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;

@RestController

@RequestMapping("/inventory")

@Tag(name = "Inventory Management", description = "Inventory query and management APIs")

public class InventoryController {

@Autowired

private InventoryService inventoryService;

@GetMapping("/product/{productId}")

@Operation(summary = "Get inventory by product ID", description = "Retrieve inventory information by product ID")

public Result<Inventory> getByProductId(@Parameter(description = "Product ID") @PathVariable Long productId) {

return Result.success(inventoryService.getByProductId(productId));

}

@GetMapping("/{id}")

@Operation(summary = "Get inventory by ID", description = "Retrieve inventory information by inventory ID")

public Result<Inventory> getById(@Parameter(description = "Inventory ID") @PathVariable Long id) {

return Result.success(inventoryService.getById(id));

}

@PostMapping

@Operation(summary = "Create inventory", description = "Create new inventory record for a product")

public Result<Inventory> createInventory(@Valid @RequestBody InventoryDTO inventoryDTO) {

return Result.success("Inventory created successfully", inventoryService.createInventory(inventoryDTO));

}

@PutMapping("/{id}")

@Operation(summary = "Update inventory", description = "Update existing inventory information")

public Result<Boolean> updateInventory(

@Parameter(description = "Inventory ID") @PathVariable Long id,

@Valid @RequestBody InventoryDTO inventoryDTO) {

return Result.success("Inventory updated successfully", inventoryService.updateInventory(id, inventoryDTO));

}

@PostMapping("/deduct")

@Operation(summary = "Deduct stock", description = "Deduct stock from inventory")

public Result<Boolean> deductStock(

@Parameter(description = "Product ID") @RequestParam Long productId,

@Parameter(description = "Quantity") @RequestParam Integer quantity) {

return Result.success("Stock deducted successfully", inventoryService.deductStock(productId, quantity));

}

@PostMapping("/add")

@Operation(summary = "Add stock", description = "Add stock to inventory")

public Result<Boolean> addStock(

@Parameter(description = "Product ID") @RequestParam Long productId,

@Parameter(description = "Quantity") @RequestParam Integer quantity) {

return Result.success("Stock added successfully", inventoryService.addStock(productId, quantity));

}

}

```

数据库初始化SQL

```sql

-- Create database

CREATE DATABASE IF NOT EXISTS mall_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE mall_db;

-- User table

CREATE TABLE IF NOT EXISTS `user` (

`id` BIGINT NOT NULL AUTO_INCREMENT,

`username` VARCHAR(50) NOT NULL UNIQUE COMMENT 'Username',

`password` VARCHAR(255) NOT NULL COMMENT 'Password (BCrypt)',

`email` VARCHAR(100) COMMENT 'Email',

`phone` VARCHAR(20) COMMENT 'Phone number',

`full_name` VARCHAR(100) COMMENT 'Full name',

`status` TINYINT DEFAULT 1 COMMENT '0: disabled, 1: enabled',

`deleted` TINYINT DEFAULT 0 COMMENT 'Logic delete flag',

`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,

`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

PRIMARY KEY (`id`),

INDEX idx_username (`username`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='User table';

-- Order table

CREATE TABLE IF NOT EXISTS `order` (

`id` BIGINT NOT NULL AUTO_INCREMENT,

`order_no` VARCHAR(32) NOT NULL UNIQUE COMMENT 'Order number',

`user_id` BIGINT NOT NULL COMMENT 'User ID',

`product_id` BIGINT NOT NULL COMMENT 'Product ID',

`quantity` INT NOT NULL COMMENT 'Quantity',

`amount` DECIMAL(10,2) NOT NULL COMMENT 'Total amount',

`status` TINYINT DEFAULT 0 COMMENT '0: pending, 1: paid, 2: shipped, 3: completed, 4: cancelled',

`address` VARCHAR(255) COMMENT 'Shipping address',

`remark` VARCHAR(255) COMMENT 'Order remark',

`deleted` TINYINT DEFAULT 0 COMMENT 'Logic delete flag',

`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,

`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

PRIMARY KEY (`id`),

INDEX idx_user_id (`user_id`),

INDEX idx_order_no (`order_no`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Order table';

-- Inventory table

CREATE TABLE IF NOT EXISTS `inventory` (

`id` BIGINT NOT NULL AUTO_INCREMENT,

`product_id` BIGINT NOT NULL UNIQUE COMMENT 'Product ID',

`product_name` VARCHAR(100) NOT NULL COMMENT 'Product name',

`stock` INT NOT NULL DEFAULT 0 COMMENT 'Stock quantity',

`price` DECIMAL(10,2) NOT NULL COMMENT 'Product price',

`reserved_stock` INT NOT NULL DEFAULT 0 COMMENT 'Reserved stock',

`deleted` TINYINT DEFAULT 0 COMMENT 'Logic delete flag',

`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,

`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

PRIMARY KEY (`id`),

INDEX idx_product_id (`product_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Inventory table';

```

说明

  1. 技术栈: Spring Boot 3.1.5, Java 17, MyBatis-Plus 3.5.3.1

  2. JWT鉴权: 使用jjwt 0.11.5,拦截器验证Token

  3. Redis缓存: 集成Lettuce连接池,用于缓存Inventory查询

  4. Swagger文档: 使用springdoc-openapi 2.4.0,自动生成API文档

  5. 异常处理: 全局异常处理,统一返回格式

  6. 密码加密: BCrypt加密存储

  7. 事务管理: 订单创建时包含库存扣减的事务操作

API文档访问

启动项目后访问: http://localhost:8080/api/swagger-ui/index.html

相关推荐
凉云生烟1 小时前
机器学习 02- KNN算法
人工智能·算法·机器学习
fenglllle1 小时前
chromadb emmbedding 向量检索
人工智能·python·embedding
wabs6661 小时前
关于动态规划【力扣583.两个字符串的删除操作的思考】
算法·leetcode·动态规划
cui_ruicheng2 小时前
Python从入门到实战(六):非序列容器
开发语言·python
可编程芯片开发2 小时前
空气流量和空气压力参数解耦系统simulink建模与仿真
算法
西西学代码2 小时前
Flutter---底部导航栏(2)
开发语言·javascript·flutter
legendary_1632 小时前
SINK芯片:Type-C统一供电时代的小家电核心方案
c语言·开发语言·人工智能·智能手机
momo2 小时前
JAVA基础知识
java·开发语言
_Doubletful2 小时前
妙用位运算:解构汉明距离至100%(提供分析与多解)
c语言·算法·leetcode