第二部分:后端开发
2.1 创建Spring Boot项目
使用Spring Initializr创建项目,选择以下依赖:
<?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>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.mall</groupId>
<artifactId>mall-system</artifactId>
<version>1.0.0</version>
<name>mall-system</name>
<description>商城系统后端服务</description>
<properties>
<java.version>11</java.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<jjwt.version>0.9.1</jjwt.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 测试 -->
<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>
</plugin>
</plugins>
</build>
</project>
2.2 配置文件
# application.yml
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mall_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: 123456
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
type-aliases-package: com.mall.entity
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: mall-secret-key-for-jwt-token-generation
expiration: 86400000 # 24小时
cors:
allowed-origins: http://localhost:5173
allowed-methods: GET,POST,PUT,DELETE,OPTIONS
allowed-headers: '*'
allow-credentials: true
2.3 实体类
package com.mall.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 用户实体类
*/
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String nickname;
private String realName;
private String phone;
private String email;
private String avatar;
private Integer gender;
private String role;
private Integer status;
private LocalDateTime lastLoginTime;
private String lastLoginIp;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.mall.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 商品实体类
*/
@Data
@TableName("product")
public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private Long categoryId;
private String name;
private String subtitle;
private String mainImage;
private String images;
private String detail;
private BigDecimal price;
private BigDecimal originalPrice;
private Integer stock;
private Integer sales;
private Integer status;
private Integer sortOrder;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.mall.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 购物车实体类
*/
@Data
@TableName("cart")
public class Cart {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long productId;
private Integer quantity;
private Integer checked;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.mall.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体类
*/
@Data
@TableName("order")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
private BigDecimal totalAmount;
private BigDecimal payAmount;
private BigDecimal freightAmount;
private Integer orderStatus;
private Integer payStatus;
private Integer shippingStatus;
private LocalDateTime paymentTime;
private LocalDateTime deliveryTime;
private LocalDateTime receiveTime;
private LocalDateTime closeTime;
private LocalDateTime cancelTime;
private String receiverName;
private String receiverPhone;
private String receiverProvince;
private String receiverCity;
private String receiverDistrict;
private String receiverAddress;
private String userNote;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.mall.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单商品实体类
*/
@Data
@TableName("order_item")
public class OrderItem {
@TableId(type = IdType.AUTO)
private Long id;
private Long orderId;
private String orderNo;
private Long productId;
private String productName;
private String productImage;
private BigDecimal productPrice;
private Integer quantity;
private BigDecimal totalAmount;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
package com.mall.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 收货地址实体类
*/
@Data
@TableName("address")
public class Address {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private String receiverName;
private String receiverPhone;
private String province;
private String city;
private String district;
private String detailAddress;
private Integer isDefault;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
2.4 DTO类
package com.mall.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* 用户注册请求DTO
*/
@Data
public class UserRegisterDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度3-20位")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度6-20位")
private String password;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
private String email;
private String nickname;
}
/**
* 用户登录请求DTO
*/
@Data
public class UserLoginDTO {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
/**
* 用户信息响应DTO
*/
@Data
public class UserInfoDTO {
private Long id;
private String username;
private String nickname;
private String realName;
private String phone;
private String email;
private String avatar;
private Integer gender;
private String role;
}
/**
* 登录响应DTO
*/
@Data
public class LoginResponseDTO {
private Long userId;
private String username;
private String nickname;
private String role;
private String token;
private String avatar;
}
/**
* 购物车商品DTO
*/
@Data
public class CartItemDTO {
private Long id;
private Long productId;
private String productName;
private String productImage;
private BigDecimal productPrice;
private Integer quantity;
private Integer checked;
private BigDecimal totalPrice;
}
/**
* 创建订单请求DTO
*/
@Data
public class OrderCreateDTO {
@NotNull(message = "地址ID不能为空")
private Long addressId;
private String userNote;
}
/**
* 订单信息DTO
*/
@Data
public class OrderInfoDTO {
private Long id;
private String orderNo;
private BigDecimal totalAmount;
private BigDecimal payAmount;
private Integer orderStatus;
private String orderStatusText;
private LocalDateTime createTime;
private List<OrderItemDTO> items;
private Address address;
}
2.5 JWT工具类
package com.mall.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT工具类
* 负责生成和解析JWT令牌
*
* JWT结构:header.payload.signature
* - header:算法和令牌类型
* - payload:用户信息(userId, username, role)
* - signature:签名,防篡改
*/
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 生成JWT令牌
* @param userId 用户ID
* @param username 用户名
* @param role 用户角色
* @return JWT令牌字符串
*/
public String generateToken(Long userId, String username, String role) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
claims.put("role", role);
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Long getUserIdFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.get("userId", Long.class);
}
public String getUsernameFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getSubject();
}
public String getRoleFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.get("role", String.class);
}
public boolean validateToken(String token) {
try {
Claims claims = getClaimsFromToken(token);
return !claims.getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
private Claims getClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
}