SpringBoot
侧边栏壁纸
  • 累计撰写 36 篇文章
  • 累计收到 1 条评论

SpringBoot

ASN__
2026-04-03 / 0 评论 / 12 阅读 / 正在检测是否收录...

Spring Boot 2.7 入门到精通笔记(苍穹外卖实战版)

一、Spring Boot 概述

1.1 什么是 Spring Boot

Spring Boot 是 Spring 框架的“脚手架”,核心设计理念是 约定优于配置

与传统 Spring 的区别

对比项传统 SpringSpring Boot
配置方式繁琐的 XML 配置文件注解 + 自动配置
依赖管理手动管理版本、处理冲突起步依赖(Starter)自动引入
部署方式打包成 WAR 部署到 Tomcat内嵌 Tomcat,直接 java -jar
开发效率需要大量样板代码只需关心业务逻辑

1.2 Spring Boot 核心特性

  • 自动配置:根据类路径中的依赖自动配置 Bean
  • 起步依赖:通过 Starter 机制简化依赖管理
  • 内嵌容器:默认集成 Tomcat/Jetty/Undertow
  • 生产就绪:Actuator 提供监控、健康检查等能力

1.3 Spring Boot 2.7 新特性(重点)

Spring Boot 2.7 是一个重要版本,2.5 已停止维护[reference:0]。

自动配置变更

  • 自动配置注册文件从 META-INF/spring.factories 迁移到 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  • 新增 @AutoConfiguration 注解,用于标识自动配置类[reference:1]
  • 支持 after/before 属性进行配置排序

其他新特性

  • 支持 GraphQL(spring-boot-starter-graphql
  • 支持 Podman 容器引擎
  • Web Server SSL 增强[reference:2]

1.4 苍穹外卖项目概述

苍穹外卖是一个企业级 O2O 外卖平台,分为管理端用户端(微信小程序)

角色功能
管理端员工管理、菜品/套餐管理、订单管理、数据统计
用户端登录授权、浏览点餐、购物车结算、订单追踪

技术栈:Spring Boot 2.7 + Spring MVC + MyBatis-Plus + MySQL + Redis + OSS(阿里云)+ AOP + Swagger(Knife4j)+ WebSocket[reference:3]

二、快速开始

2.1 项目结构(苍穹外卖)

苍穹外卖采用 Maven 多模块结构,各模块职责明确,便于维护和扩展:

sky-take-out(父工程)
├── pom.xml                    # 统一依赖版本管理
├── sky-common/                # 公共模块:工具类、常量、异常类
│   ├── constant/              # 常量定义(如JwtClaimsConstant)
│   ├── context/               # ThreadLocal上下文
│   ├── exception/             # 自定义异常
│   ├── properties/            # 配置属性类
│   └── utils/                 # 工具类(JWT、阿里OSS、微信支付等)
├── sky-pojo/                  # 实体模块:Entity、DTO、VO
│   ├── entity/                # 数据库实体类(与表一一对应)
│   ├── dto/                   # 数据传输对象(层间传递)
│   └── vo/                    # 视图对象(返回给前端)
└── sky-server/                # 后端服务模块(核心业务代码)
    ├── config/                # 配置类(Redis、Swagger、WebMvc等)
    ├── controller/            # 控制器层
    ├── service/               # 业务逻辑层
    ├── mapper/                # 数据访问层
    ├── interceptor/           # 拦截器(如JWT认证拦截器)
    ├── aspect/                # AOP切面(公共字段填充)
    └── SkyApplication.java    # 启动类

POJO 分类说明

  • Entity:实体类,与数据库表一一对应
  • DTO(Data Transfer Object):数据传输对象,用于层间传递数据
  • VO(View Object):视图对象,专门返回给前端展示[reference:4]

2.2 父工程 POM 配置(版本管理)

<!-- sky-take-out/pom.xml -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.0</version>
    <relativePath/>
</parent>

<groupId>com.sky</groupId>
<artifactId>sky-take-out</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<properties>
    <java.version>1.8</java.version>
    <mybatis-plus.version>3.5.2</mybatis-plus.version>
</properties>

<dependencies>
    <!-- 统一管理的依赖在这里声明版本 -->
</dependencies>

2.3 子模块依赖配置

<!-- sky-server/pom.xml -->
<dependencies>
    <!-- Spring Boot Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- MyBatis-Plus Starter -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>
    
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- 引用公共模块 -->
    <dependency>
        <groupId>com.sky</groupId>
        <artifactId>sky-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

2.4 启动类

package com.sky;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Slf4j
@SpringBootApplication
@EnableTransactionManagement  // 开启事务管理
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("苍穹外卖后端服务启动成功!");
    }
}

@SpringBootApplication 复合注解解析

注解作用
@SpringBootConfiguration标识为配置类(@Configuration 的派生)
@ComponentScan开启组件扫描,扫描启动类所在包及子包
@EnableAutoConfiguration开启自动配置(核心)

自动配置原理(5 步必背)

  1. @SpringBootApplication@EnableAutoConfiguration
  2. @Import(AutoConfigurationImportSelector.class) 导入选择器
  3. 加载 META-INF/spring.factories(或 2.7 新路径 AutoConfiguration.imports
  4. 读取自动配置类的全限定类名
  5. 通过 @ConditionalOnXxx 条件注解判断是否加载

三、配置文件

3.1 application.yml(苍穹外卖示例)

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/sky_take_out?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  
  redis:
    host: localhost
    port: 6379
    database: 0

  main:
    allow-circular-references: true  # 允许循环依赖

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印 SQL 日志
  global-config:
    db-config:
      id-type: auto  # 主键自增
  mapper-locations: classpath:mapper/*.xml

sky:
  jwt:
    admin-secret-key: "itcast"
    admin-ttl: 7200000
    admin-token-name: "token"

3.2 多环境配置(必背)

创建多个配置文件,通过 spring.profiles.active 激活:

文件环境
application.yml默认配置(公共)
application-dev.yml开发环境
application-prod.yml生产环境
# application.yml(主配置)
spring:
  profiles:
    active: dev  # 激活开发环境

# application-dev.yml
server:
  port: 8081
spring:
  config:
    activate:
      on-profile: dev

# application-prod.yml
server:
  port: 80
spring:
  config:
    activate:
      on-profile: prod

激活方式

# 命令行激活
java -jar sky-server.jar --spring.profiles.active=prod

# IDEA VM options
-Dspring.profiles.active=prod

四、分层架构(苍穹外卖实战)

4.1 分层架构图解(外卖公司比喻)

层级角色苍穹外卖示例
Controller客服经理(接电话)接收 HTTP 请求,返回 JSON 响应
Service业务主管(管流程)实现业务逻辑,调用 Mapper
Mapper仓库管理员(管库存)执行 SQL,操作数据库
Entity货物清单与数据库表一一对应的实体

4.2 Entity 层(实体类)

package com.sky.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Long id;
    private String username;
    private String name;
    private String password;
    private String phone;
    private String sex;
    private String idNumber;
    private Integer status;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    private Long createUser;
    private Long updateUser;
}

4.3 Mapper 层(数据访问层)

package com.sky.mapper;

import com.sky.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface EmployeeMapper {
    
    @Select("select * from employee where username = #{username}")
    Employee getByUsername(String username);
    
    // 分页查询员工
    Page<Employee> pageQuery(EmployeePageQueryDTO pageQueryDTO);
    
    // 更新员工信息
    void update(Employee employee);
}

MyBatis-Plus 增强版(推荐)

// 继承 BaseMapper,自动拥有 CRUD 方法
@Mapper
public interface DishMapper extends BaseMapper<Dish> {
    // 无需写任何基础 CRUD,BaseMapper 已提供:
    // insert(), deleteById(), updateById(), selectById(), selectList() 等
}

// Service 层还可以继承 IService
@Service
public class DishService extends ServiceImpl<DishMapper, Dish> {
    // 自动拥有 saveBatch(), updateBatchById() 等批量方法
}

4.4 Service 层(业务逻辑层)

package com.sky.service.impl;

import com.sky.context.BaseContext;
import com.sky.dto.EmployeeDTO;
import com.sky.entity.Employee;
import com.sky.mapper.EmployeeMapper;
import com.sky.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

@Slf4j
@Service
public class EmployeeServiceImpl implements EmployeeService {
    
    @Autowired
    private EmployeeMapper employeeMapper;
    
    @Override
    @Transactional  // 开启事务
    public void save(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        // 拷贝属性(DTO → Entity)
        BeanUtils.copyProperties(employeeDTO, employee);
        
        // 设置默认密码(MD5 加密)
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        employee.setStatus(1);
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        
        // 从 ThreadLocal 获取当前操作用户 ID
        employee.setCreateUser(BaseContext.getCurrentId());
        employee.setUpdateUser(BaseContext.getCurrentId());
        
        employeeMapper.insert(employee);
    }
}

4.5 Controller 层(控制层)

package com.sky.controller.admin;

import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.EmployeeService;
import com.sky.vo.EmployeeLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/admin/employee")
@Api(tags = "员工管理接口")
@Slf4j
public class EmployeeController {
    
    @Autowired
    private EmployeeService employeeService;
    
    @PostMapping("/login")
    @ApiOperation("员工登录")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO loginDTO) {
        log.info("员工登录:{}", loginDTO.getUsername());
        EmployeeLoginVO employeeLoginVO = employeeService.login(loginDTO);
        return Result.success(employeeLoginVO);
    }
    
    @PostMapping
    @ApiOperation("新增员工")
    public Result save(@RequestBody EmployeeDTO employeeDTO) {
        log.info("新增员工:{}", employeeDTO);
        employeeService.save(employeeDTO);
        return Result.success();
    }
    
    @GetMapping("/page")
    @ApiOperation("员工分页查询")
    public Result<PageResult> page(EmployeePageQueryDTO pageQueryDTO) {
        log.info("员工分页查询:{}", pageQueryDTO);
        PageResult pageResult = employeeService.pageQuery(pageQueryDTO);
        return Result.success(pageResult);
    }
}

4.6 分层调用链路总结

HTTP 请求
    ↓
Controller(接收参数,调用 Service)
    ↓
Service(业务逻辑,调用 Mapper)
    ↓
Mapper(执行 SQL,操作数据库)
    ↓
Entity(数据载体)
    ↓
返回结果 → VO → JSON 响应

⚠️ 常见错误

  • ❌ Controller 直接调用 Mapper(越级)
  • ❌ Mapper 层写业务判断逻辑
  • ❌ Service 层写 SQL 拼接

五、核心注解速查表

5.1 组件注解

注解用途层级
@Component通用组件,交给 Spring 管理任意
@ControllerWeb 控制器(配合 @ResponseBody 返回 JSON)表现层
@RestController@Controller + @ResponseBody 的合并表现层
@Service业务逻辑层组件业务层
@Repository数据访问层组件,会转换持久层异常数据层
@Repository 的特殊功能:将数据库操作抛出的异常自动转换为 Spring 统一的数据访问异常体系[reference:5]。

5.2 请求映射注解

注解作用
@RequestMapping通用请求映射,可指定 method
@GetMapping处理 GET 请求
@PostMapping处理 POST 请求
@PutMapping处理 PUT 请求
@DeleteMapping处理 DELETE 请求

5.3 参数绑定注解

注解作用示例
@PathVariable获取 URL 路径参数/user/{id}@PathVariable Long id
@RequestParam获取 URL 查询参数?name=张三@RequestParam String name
@RequestBody获取请求体(JSON → 对象)@RequestBody EmployeeDTO dto
@RequestHeader获取请求头@RequestHeader("token") String token

5.4 依赖注入注解

注解作用
@Autowired按类型自动注入(Spring 原生)
@Resource按名称自动注入(JSR-250)
@Qualifier配合 @Autowired,指定 Bean 名称
@Value注入配置文件中的值(如 ${server.port}
@ConfigurationProperties批量注入配置属性

5.5 配置与条件注解

注解作用
@Configuration标识配置类
@Bean在配置类中声明 Bean
@ConditionalOnClass类路径存在指定类时才生效
@ConditionalOnMissingBean容器中没有指定 Bean 时才生效
@ConditionalOnProperty配置文件存在指定属性时才生效

六、全局异常处理

6.1 苍穹外卖全局异常处理器

package com.sky.handler;

import com.sky.constant.MessageConstant;
import com.sky.exception.BaseException;
import com.sky.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    // 捕获业务异常
    @ExceptionHandler(BaseException.class)
    public Result exceptionHandler(BaseException ex) {
        log.error("业务异常:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }
    
    // 捕获 SQL 异常(如用户名重复)
    @ExceptionHandler(DuplicateKeyException.class)
    public Result handleDuplicateKeyException(DuplicateKeyException ex) {
        log.error("SQL 异常:{}", ex.getMessage());
        return Result.error(MessageConstant.ALREADY_EXISTS);
    }
    
    // 兜底异常处理
    @ExceptionHandler(Exception.class)
    public Result exceptionHandler(Exception ex) {
        log.error("系统异常:{}", ex.getMessage());
        return Result.error(MessageConstant.UNKNOWN_ERROR);
    }
}

6.2 统一响应对象

package com.sky.result;

import lombok.Data;
import java.io.Serializable;

@Data
public class Result<T> implements Serializable {
    private Integer code;   // 1 成功,0 失败
    private String msg;
    private T data;
    
    public static <T> Result<T> success() {
        Result<T> result = new Result<>();
        result.code = 1;
        return result;
    }
    
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.code = 1;
        result.data = data;
        return result;
    }
    
    public static <T> Result<T> error(String msg) {
        Result<T> result = new Result<>();
        result.code = 0;
        result.msg = msg;
        return result;
    }
}

七、JWT 认证与拦截器

7.1 JWT 工具类

package com.sky.utils;

import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Map;

@Data
@Component
@ConfigurationProperties(prefix = "sky.jwt")
public class JwtUtil {
    private String adminSecretKey;
    private Long adminTtl;
    private String adminTokenName;
    
    // 生成 JWT 令牌
    public String createJWT(Map<String, Object> claims, Long ttlMillis) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        
        JwtBuilder builder = Jwts.builder()
            .setClaims(claims)
            .setIssuedAt(now)
            .signWith(SignatureAlgorithm.HS256, adminSecretKey);
        
        if (ttlMillis > 0) {
            builder.setExpiration(new Date(nowMillis + ttlMillis));
        }
        return builder.compact();
    }
    
    // 解析 JWT 令牌
    public Claims parseJWT(String token) {
        return Jwts.parser()
            .setSigningKey(adminSecretKey)
            .parseClaimsJws(token)
            .getBody();
    }
}

7.2 JWT 认证拦截器

package com.sky.interceptor;

import com.sky.constant.JwtClaimsConstant;
import com.sky.context.BaseContext;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 判断当前拦截的是否为 Controller 方法
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        // 从请求头中获取令牌
        String token = request.getHeader(jwtUtil.getAdminTokenName());
        
        // 解析令牌
        try {
            Claims claims = jwtUtil.parseJWT(token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            
            // 将员工 ID 存入 ThreadLocal(供后续业务使用)
            BaseContext.setCurrentId(empId);
            
            log.info("当前员工 ID:{}", empId);
            return true;
        } catch (Exception ex) {
            log.error("JWT 解析失败:{}", ex.getMessage());
            response.setStatus(401);
            return false;
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 请求结束后清除 ThreadLocal,防止内存泄漏
        BaseContext.removeCurrentId();
    }
}

7.3 ThreadLocal 工具类(苍穹外卖)

package com.sky.context;

public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    
    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }
    
    public static Long getCurrentId() {
        return threadLocal.get();
    }
    
    public static void removeCurrentId() {
        threadLocal.remove();
    }
}
ThreadLocal 原理:每个线程拥有自己的变量副本,线程隔离。用完必须 remove(),否则在线程池环境下会导致内存泄漏[reference:6]。

7.4 注册拦截器

package com.sky.config;

import com.sky.interceptor.JwtTokenAdminInterceptor;
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.WebMvcConfigurationSupport;

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    
    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
    
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")           // 拦截所有 /admin 开头的请求
                .excludePathPatterns("/admin/employee/login")  // 排除登录接口
                .excludePathPatterns("/admin/employee/logout");
    }
}

八、AOP 切面编程(公共字段自动填充)

8.1 自定义注解

package com.sky.annotation;

import com.sky.enumeration.OperationType;
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    OperationType value();  // UPDATE 或 INSERT
}

8.2 切面类实现

package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    
    // 切点:标注了 @AutoFill 注解的方法
    @Pointcut("@annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut() {}
    
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        // 获取当前被拦截的方法上的注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        OperationType operationType = autoFill.value();
        
        // 获取方法的参数(实体对象)
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) return;
        
        Object entity = args[0];
        
        // 准备要赋的值
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();
        
        // 根据操作类型设置不同字段
        if (operationType == OperationType.INSERT) {
            // 插入操作:设置 createTime, updateTime, createUser, updateUser
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                
                setCreateTime.invoke(entity, now);
                setUpdateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                log.error("公共字段填充失败:{}", e.getMessage());
            }
        } else if (operationType == OperationType.UPDATE) {
            // 更新操作:设置 updateTime, updateUser
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                log.error("公共字段填充失败:{}", e.getMessage());
            }
        }
    }
}

8.3 使用示例

@Mapper
public interface EmployeeMapper {
    
    @AutoFill(OperationType.INSERT)
    void insert(Employee employee);
    
    @AutoFill(OperationType.UPDATE)
    void update(Employee employee);
}

九、缓存与 Redis

9.1 Spring Cache 配置

package com.sky.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;

@Configuration
@EnableCaching
public class RedisConfig {
    
    @Bean
    public RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))           // 缓存过期时间 30 分钟
                .disableCachingNullValues()                 // 不缓存 null 值
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()));
    }
}

9.2 缓存注解使用

@Service
@Slf4j
public class DishServiceImpl implements DishService {
    
    @Autowired
    private DishMapper dishMapper;
    
    // 查询时缓存:key = "dish:categoryId:" + categoryId
    @Cacheable(value = "dish", key = "'dish:categoryId:' + #categoryId")
    public List<Dish> getByCategoryId(Long categoryId) {
        return dishMapper.getByCategoryId(categoryId);
    }
    
    // 更新时清空缓存
    @CacheEvict(value = "dish", key = "'dish:categoryId:' + #dishDTO.categoryId")
    public void updateWithFlavor(DishDTO dishDTO) {
        // 更新业务逻辑
    }
    
    // 批量清空缓存(key 模糊匹配)
    @CacheEvict(value = "dish", allEntries = true)
    public void deleteBatch(List<Long> ids) {
        // 删除业务逻辑
    }
}

9.3 常用 Redis 操作(StringRedisTemplate)

@Service
public class ShopStatusService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String SHOP_STATUS_KEY = "SHOP_STATUS";
    
    public void setStatus(Integer status) {
        redisTemplate.opsForValue().set(SHOP_STATUS_KEY, String.valueOf(status));
    }
    
    public Integer getStatus() {
        String status = redisTemplate.opsForValue().get(SHOP_STATUS_KEY);
        return status == null ? 0 : Integer.parseInt(status);
    }
}

十、Spring Task 定时任务

10.1 开启定时任务

@SpringBootApplication
@EnableScheduling  // 开启定时任务
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
    }
}

10.2 订单状态定时处理

package com.sky.task;

import com.sky.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class OrderTask {
    
    @Autowired
    private OrderService orderService;
    
    // 每天凌晨 1 点处理超时未支付的订单
    @Scheduled(cron = "0 0 1 * * ?")
    public void processTimeoutOrder() {
        log.info("定时任务:处理超时未支付订单");
        orderService.processTimeoutOrders();
    }
    
    // 每分钟执行一次,处理配送中的订单(送达后自动完成)
    @Scheduled(cron = "0 * * * * ?")
    public void processDeliveryOrder() {
        log.info("定时任务:处理配送中订单");
        orderService.processDeliveryOrders();
    }
}

10.3 Cron 表达式速查

字段允许值特殊字符
0-59, - * /
0-59, - * /
0-23, - * /
1-31, - * ? / L W
1-12, - * /
0-7(0 或 7 都代表周日), - * ? / L #
年(可选)空或 1970-2099, - * /

常用表达式

  • 0 0 1 * * ? → 每天凌晨 1 点
  • 0 */5 * * * ? → 每 5 分钟一次
  • 0 0 12 * * MON-FRI → 工作日中午 12 点

十一、WebSocket 实时通信

11.1 WebSocket 配置

package com.sky.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

11.2 WebSocket 服务端

package com.sky.websocket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;

@Component
@ServerEndpoint("/ws/{sid}")
@Slf4j
public class WebSocketServer {
    
    // 存储所有在线会话(线程安全)
    private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
    
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        sessionMap.put(sid, session);
        log.info("WebSocket 连接建立:{}", sid);
    }
    
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        sessionMap.remove(sid);
        log.info("WebSocket 连接关闭:{}", sid);
    }
    
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到消息:{}", message);
    }
    
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket 错误:{}", error.getMessage());
    }
    
    // 发送消息给指定用户
    public void sendToClient(String sid, String message) {
        Session session = sessionMap.get(sid);
        if (session != null && session.isOpen()) {
            try {
                session.getBasicRemote().sendText(message);
                log.info("发送消息给 {}:{}", sid, message);
            } catch (Exception e) {
                log.error("发送失败:{}", e.getMessage());
            }
        }
    }
}

11.3 来单提醒/催单场景

@Service
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private WebSocketServer webSocketServer;
    
    // 用户下单 → 来单提醒(推送给管理端)
    public void submit(OrdersSubmitDTO submitDTO) {
        // ... 下单业务逻辑
        
        // 来单提醒
        Map<String, Object> reminder = new HashMap<>();
        reminder.put("type", 1);  // 1=来单提醒
        reminder.put("orderId", order.getId());
        reminder.put("content", "您有一笔新订单");
        
        webSocketServer.sendToClient("admin", JSON.toJSONString(reminder));
    }
    
    // 用户催单 → 推送给管理端
    public void reminder(Long orderId) {
        Map<String, Object> reminder = new HashMap<>();
        reminder.put("type", 2);  // 2=用户催单
        reminder.put("orderId", orderId);
        reminder.put("content", "用户催单啦");
        
        webSocketServer.sendToClient("admin", JSON.toJSONString(reminder));
    }
}

十二、文件上传(阿里云 OSS)

12.1 OSS 配置属性类

package com.sky.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "sky.alioss")
public class AliOssProperties {
    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
}

12.2 OSS 工具类

package com.sky.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.sky.properties.AliOssProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;

@Component
@Slf4j
public class AliOssUtil {
    
    @Autowired
    private AliOssProperties aliOssProperties;
    
    public String upload(MultipartFile file) {
        String endpoint = aliOssProperties.getEndpoint();
        String accessKeyId = aliOssProperties.getAccessKeyId();
        String accessKeySecret = aliOssProperties.getAccessKeySecret();
        String bucketName = aliOssProperties.getBucketName();
        
        // 生成唯一文件名(防止覆盖)
        String originalFilename = file.getOriginalFilename();
        String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
        String objectName = UUID.randomUUID().toString() + extension;
        
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
        try {
            ossClient.putObject(bucketName, objectName, file.getInputStream());
            // 返回文件访问 URL
            return "https://" + bucketName + "." + endpoint + "/" + objectName;
        } catch (IOException e) {
            log.error("文件上传失败:{}", e.getMessage());
            throw new RuntimeException(e);
        } finally {
            ossClient.shutdown();
        }
    }
}

12.3 通用上传接口

@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {
    
    @Autowired
    private AliOssUtil aliOssUtil;
    
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file) {
        log.info("文件上传:{}", file.getOriginalFilename());
        String url = aliOssUtil.upload(file);
        return Result.success(url);
    }
}

十三、Knife4j + Swagger 接口文档

13.1 添加依赖

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

13.2 Swagger 配置类

package com.sky.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableKnife4j
public class SwaggerConfig {
    
    @Bean
    public Docket adminApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("管理端接口")
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
                .paths(PathSelectors.any())
                .build();
    }
    
    @Bean
    public Docket userApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("用户端接口")
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
                .paths(PathSelectors.any())
                .build();
    }
    
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("苍穹外卖 API 文档")
                .version("1.0")
                .description("苍穹外卖后端接口文档")
                .build();
    }
}

13.3 使用示例

@Api(tags = "员工管理接口")
@RestController
@RequestMapping("/admin/employee")
public class EmployeeController {
    
    @PostMapping("/login")
    @ApiOperation("员工登录")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "username", value = "用户名", required = true),
        @ApiImplicitParam(name = "password", value = "密码", required = true)
    })
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO loginDTO) {
        // ...
    }
}
访问地址http://localhost:8080/doc.html

十四、常用工具类速查

14.1 BeanUtils(属性拷贝)

// DTO → Entity
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);

14.2 DigestUtils(MD5 加密)

// 密码 MD5 加密
String encryptedPassword = DigestUtils.md5DigestAsHex("123456".getBytes());

14.3 Jackson(JSON 序列化)

// 对象 → JSON 字符串
String jsonStr = JSON.toJSONString(object);

// JSON 字符串 → 对象
User user = JSON.parseObject(jsonStr, User.class);

14.4 LocalDateTime(时间处理)

// 获取当前时间
LocalDateTime now = LocalDateTime.now();

// 时间格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(formatter);

十五、项目部署与运行

15.1 打包

# 在项目根目录执行
mvn clean package

# 跳过测试打包
mvn clean package -DskipTests

15.2 运行

# 运行 JAR 包
java -jar sky-server.jar

# 指定环境运行
java -jar sky-server.jar --spring.profiles.active=prod

# 后台运行
nohup java -jar sky-server.jar > app.log 2>&1 &

15.3 Nginx 反向代理配置

server {
    listen 80;
    server_name api.sky.com;
    
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

十六、快速记忆口诀

启动流程

SpringApplication.run → 初始化上下文 → 加载自动配置 → 启动内嵌容器 → 注册 Controller → 应用就绪

自动配置

@SpringBootApplication → @EnableAutoConfiguration → @Import → spring.factories → @ConditionalOnXxx

分层架构

Controller 接请求,Service 写逻辑,Mapper 查数据库,Entity 存数据

JWT 认证

登录签发 Token → 请求携带 Header → 拦截器校验 → ThreadLocal 存用户 ID → 业务使用 → 请求结束清除

缓存注解

@Cacheable 查缓存,@CachePut 更新缓存,@CacheEvict 清缓存

定时任务

@EnableScheduling 开启,@Scheduled(cron) 标记,Cron 表达式定时间

全局异常

@RestControllerAdvice 全局处理,@ExceptionHandler 分类捕获

本笔记基于 Spring Boot 2.7 + 苍穹外卖实战项目整理,覆盖了从项目搭建到部署上线的完整流程,适合日常开发查阅和面试前背诵。建议结合源码实践加深理解。
0

评论

博主关闭了所有页面的评论