一、前言
本文把项目中常用的中间件、框架与技术点做一个汇总:包括 Nginx、JWT、Redis、Spring Cache、AOP、事务、定时任务、WebSocket、OSS、POI 等
二、技术点
每个条目格式:概念、场景、实现要点 / 快速示例。
1. Nginx
专门写了一篇,请移至多功能服务器 - Nginx
- 概念:通过调度算法把请求分发到多台应用服务器,实现水平扩展与容灾。
- 场景:流量分发、静态资源加速、反向代理
- 要点:常用算法:轮询、最小连接、IP hash。配合 health check 与 SSL。
1.1 正向代理
- 概念:客户端通过代理访问外网。
1.2 反向代理
- 概念:对外表现为单一服务器,内部转发到后端服务
2. MD5 加密
- 特点:固定长度、不可逆
注意单纯 MD5 不够安全,一定要配合盐值等加强的算法。
- 示例:
private static final String SALT = "qi";
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
3. Swagger
Swagger的详细介绍在接口文档生成 - Swagger
作用:自动生成在线 API 文档。
集成要点:
- 引入 Knife4j(swagger的ui增强版)依赖
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> </dependency>- 在配置类中添加配置
@Bean public Docket docket1(){ log.info("准备生成接口文档..."); ApiInfo apiInfo = new ApiInfoBuilder() .title("小柒味来项目接口文档") .version("2.0") .description("小柒味来项目接口文档") .build(); Docket docket = new Docket(DocumentationType.SWAGGER_2) .groupName("管理端接口") .apiInfo(apiInfo) .select() //指定生成接口需要扫描的包 .apis(RequestHandlerSelectors.basePackage("com.qi.controller.admin")) .paths(PathSelectors.any()) .build(); return docket; }- 使用注解标注接口:
@Api(tags = "用户模块") @ApiModel @ApiModelProperty @ApiOperation("查询用户")- Knife4j的访问地址为:http://localhost:8080/doc.html
4. JWT
- 组成:Header(类型+算法) + Payload(载荷) + Signature(签名)。
- 场景:身份认证、授权、服务间信息传递(无状态)。
- 实现要点:
- 引入 JWT 库;
- 封装工具类负责生成或解析 token;
- 在拦截器或过滤器中验证 token 并从载荷取用户信息。
5. 拦截器 Interceptor
概念:基于 Spring 的请求拦截(本质与 AOP 相关)。
场景:登录校验、权限控制、性能监控、请求日志。
实现:
- 实现
HandlerInterceptor,重写preHandle、postHandle、afterCompletion;
@Component @Slf4j public class JwtTokenUserInterceptor implements HandlerInterceptor { @Autowired private JwtProperties jwtProperties; /** * 校验jwt * * @param request * @param response * @param handler * @return * @throws Exception */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //判断当前拦截到的是Controller的方法还是其他资源 if (!(handler instanceof HandlerMethod)) { //当前拦截到的不是动态方法,直接放行 return true; } //1、从请求头中获取令牌 String token = request.getHeader(jwtProperties.getUserTokenName()); //2、校验令牌 try { log.info("jwt校验:{}", token); Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token); Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString()); log.info("当前用户id:", userId); BaseContext.setCurrentId(userId); //3、通过,放行 return true; } catch (Exception ex) { //4、不通过,响应401状态码 response.setStatus(401); return false; } } }- 在配置类中注册并设置拦截路径。
@Autowired private JwtTokenAdminInterceptor jwtTokenAdminInterceptor; /** * 注册自定义拦截器 * * @param registry */ protected void addInterceptors(InterceptorRegistry registry) { log.info("开始注册自定义拦截器..."); registry.addInterceptor(jwtTokenAdminInterceptor) .addPathPatterns("/admin/**") .excludePathPatterns("/admin/employee/login"); }preHandle、postHandle、afterCompletion详解
方法名 调用时机 功能 典型用途 返回值 preHandleController 方法执行前 权限校验、日志记录、请求参数处理 登录验证、Token 校验、请求限流 返回 true放行,返回false拦截请求postHandleController 方法执行后,视图渲染前 调整返回数据或视图 统一响应包装、添加公共数据 无 afterCompletion整个请求处理完成后(视图渲染结束) 清理资源、记录日志、处理异常 性能监控、日志记录、异常处理 无返回值,即使 preHandle返回 false 也可能调用- 实现
7. 异常处理 — 全局异常处理器
注解:
@RestControllerAdvice+@ExceptionHandler。作用:统一捕获异常、返回规范化错误响应、避免暴露堆栈信息。
实例
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 捕获业务异常
*
* @param ex
* @return
*/
@ExceptionHandler
public Result exceptionHandler(BaseException ex) {
log.error("异常信息:{}", ex.getMessage());
return Result.error(ex.getMessage());
}
/*
* 捕获SQL异常
* */
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
String message = ex.getMessage();
log.error("异常信息:{}", message);
if (message.contains("Duplicate entry")) {
String[] split = message.split(" ");
String msg = split[2] + MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
} else {
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
}
8. AOP 切面
概念:把横切关注点(如日志、权限、事务)从业务中抽离。
使用场景:操作日志、权限校验、性能统计、事务增强等。
实现步骤:
- 定义自定义注解
/* * 自定义注解,用于标记和记录方法的执行信息 * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { //数据库操作类型:INSERT、UPDATE OperationType value(); }- 编写带
@Aspect的切面类,定义通知类型(@Before/@After/@Around)
@Aspect @Component @Slf4j public class AutoFillAspect { /** * 切入点方法 * 定义了需要进行自动填充的切入点 * 该切入点匹配使用了@AutoFill注解的Mapper接口中的所有方法 */ @Pointcut("@annotation(com.qi.annotation.AutoFill) && execution(* com.qi.mapper.*.*(..))") public void autoFillPointCut() { } /** * 在执行切入点方法之前进行自动填充 * * @param joinPoint 切入点对象,包含执行方法的信息 */ @Before("autoFillPointCut()") public void autoFill(JoinPoint joinPoint) { // 记录自动填充开始的日志 log.info("开始进行公共字段自动填充。。。。"); // 获取方法签名和@AutoFill注解 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]; // 获取当前时间和用户ID LocalDateTime now = LocalDateTime.now(); Long currentId = BaseContext.getCurrentId(); // 根据操作类型进行相应的字段填充 if (operationType == OperationType.INSERT) { try { // 对于插入操作,填充创建时间和用户以及更新时间和用户 Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class); Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); setCreateTime.invoke(entity, now); setCreateUser.invoke(entity, currentId); setUpdateTime.invoke(entity, now); setUpdateUser.invoke(entity, currentId); } catch (Exception e) { throw new RuntimeException(e); } } else if (operationType == OperationType.UPDATE) { 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) { throw new RuntimeException(e); } } } }- 在目标方法/类上加注解
@AutoFill(value = OperationType.INSERT) void insert(Dish dishDTO);
9. Redis
详细使用请移步存储中间件-Redis
概念:基于内存的 key-value 非关系型数据库。
场景:缓存、消息队列、排行榜、分布式锁等。
接入步骤:
- 引入依赖(spring-boot-starter-data-redis)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>- 配置 Redis 连接
定义与注入
RedisTemplate- 自定义序列化
@Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { // 记录创建RedisTemplate对象的日志 log.info("开始创建redis对象"); // 实例化RedisTemplate RedisTemplate redisTemplate = new RedisTemplate(); // 设置Redis连接工厂 redisTemplate.setConnectionFactory(redisConnectionFactory); // 设置键的序列化方式为StringRedisSerializer,确保键以字符串形式存储 redisTemplate.setKeySerializer(new StringRedisSerializer()); // 返回配置好的RedisTemplate实例 return redisTemplate; }- 注入
@Autowired private RedisTemplate redisTemplate; @Test public void testRedisTemplate(){ System.out.println(redisTemplate); //string数据操作 ValueOperations valueOperations = redisTemplate.opsForValue(); //hash类型的数据操作 HashOperations hashOperations = redisTemplate.opsForHash(); //list类型的数据操作 ListOperations listOperations = redisTemplate.opsForList(); //set类型数据操作 SetOperations setOperations = redisTemplate.opsForSet(); //zset类型数据操作 ZSetOperations zSetOperations = redisTemplate.opsForZSet(); }
10. Redis 序列化方式
JdkSerializationRedisSerializer:Java 默认(体积大)。StringRedisSerializer:字符串序列化。GenericToStringSerializer:泛型转字符串。Jackson2JsonRedisSerializer/GenericJackson2JsonRedisSerializer:JSON 序列化(常用,兼容性好)
11. Spring Cache
概念:通过注解简化缓存操作。
常用注解:
@EnableCaching(启动类)@Cacheable(读取并缓存)@CachePut(更新缓存)@CacheEvict(清理缓存)
流程:
- 引入缓存实现(如 Redis)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>- 开启缓存
/** * 1、开启基于注解的缓存 @EnableCaching */ @SpringBootApplication @EnableCaching //开启缓存 public class CacheApplication{ public static void main(String[] args) { SpringApplication.run(CacheApplication.class, args); } }- 标注方法
@Cacheable(value = "emp" ,key = "targetClass + methodName +#p0") public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee emp = employeeMapper.getEmpById(id); return emp; }
12. HttpClient
作用:发送 HTTP 请求并接收响应(服务间调用、第三方 API)
官方示例:Http Client 快速入门
流程:
- 创建
HttpClient;
// 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault();- 构造请求(GET/POST);
try{ URIBuilder builder = new URIBuilder(url); if(paramMap != null){ for (String key : paramMap.keySet()) { builder.addParameter(key,paramMap.get(key)); } } URI uri = builder.build(); //创建GET请求 HttpGet httpGet = new HttpGet(uri); //发送请求 response = httpClient.execute(httpGet);- 执行并处理响应,最后释放连接。
//判断响应状态 if (response.getStatusLine().getStatusCode() == 200) { result = EntityUtils.toString(response.getEntity(), "UTF-8"); } } catch (Exception e) { e.printStackTrace(); } finally { try { response.close(); httpClient.close(); } catch (IOException e) { e.printStackTrace(); } }- 创建
13. 微信小程序
内容较多,请移步小柒味来 - 微信登录与商品浏览功能开发查看流程
- 要点:使用微信官方开发文档(登录、会话、API 权限)。
- 登录文档:微信小程序登录流程(
wx.login、后端换取session_key)等。
14. Spring Task
场景:订单超时处理、定时推送等。
流程:
启动类启用注解
@EnableScheduling给需要定时的任务加上注解
@Scheduled(cron = "0/5 * * * * ?")
注意:长任务考虑异步或分布式调度,cron 表达式要谨慎测试
15. WebSocket(实时双向通信)
概念:基于 TCP 的全双工通信,适合实时推送(订单通知、催单)。
流程:
- 后端建立 WebSocket 服务器端点;
- 前端(小程序/网页)建立连接并处理消息。

16. 阿里云 OSS
用途:存储图片、视频、文件等静态资源。
步骤:
- 引入OSS SDK依赖
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> </dependency>- 在配置文件(YAML)写入
endpoint、accessKeyId、accessKeySecret、bucketName
alioss: endpoint: ${qi.alioss.endpoint} access-key-id: ${qi.alioss.access-key-id} access-key-secret: ${qi.alioss.access-key-secret} bucket-name: ${qi.alioss.bucket-name}- 编写上传工具类(参考快速使用 OSS SDK - 对象存储 OSS - 阿里云)并处理文件流
@Data @AllArgsConstructor @Slf4j public class AliOssUtil { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; /** * 文件上传 * * @param bytes * @param objectName * @return */ public String upload(byte[] bytes, String objectName) { // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); try { // 创建PutObject请求。 ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes)); } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message:" + ce.getMessage()); } finally { if (ossClient != null) { ossClient.shutdown(); } } //文件访问路径规则 https://BucketName.Endpoint/ObjectName StringBuilder stringBuilder = new StringBuilder("https://"); stringBuilder .append(bucketName) .append(".") .append(endpoint) .append("/") .append(objectName); log.info("文件上传到:{}", stringBuilder.toString()); return stringBuilder.toString(); } }详细请看小柒味来 - 菜品管理模块开发中的文件上传部分
17. 事务处理
- 概念:保持一组操作的原子性(ACID)。
- 注解:
@Transactional(常用属性:rollbackFor、propagation) - 传播行为:
REQUIRED(默认)、REQUIRES_NEW等,设计时注意嵌套调用的事务边界。
18. Apache ECharts
- 用途:前端数据可视化(图表、报表)
- 接入:前端引入 ECharts,通过接口拿数据并渲染图表。
- 快速上手
19. Apache POI
用途:生成/读取 Excel 报表
快速流程:
new XSSFWorkbook();- 导入模板文件并进行填充
setCellValue();写出并关闭流。
参考:POI 官方 QuickGuide
@Override public void exportBusinessData(HttpServletResponse httpServletResponse) { //查询数据库,获取营业数据--查询最近30天的运营数据 LocalDate dateBegin = LocalDate.now().minusDays(30); LocalDate dateEnd = LocalDate.now().minusDays(1); //查询概览数据 BusinessDataVO businessDataVO = workspaceService.getBusinessData(LocalDateTime.of(dateBegin, LocalTime.MIN), LocalDateTime.of(dateEnd, LocalTime.MAX)); //通过POI将数据写入到Excel中 InputStream in = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx"); try { //基于模板创建一个新的Excel文件 XSSFWorkbook excel = new XSSFWorkbook(in); //获取表格文件的Sheet页 XSSFSheet sheet1 = excel.getSheet("Sheet1"); //填充时间数据 sheet1.getRow(1).getCell(1).setCellValue("时间:" + dateBegin + "至" + dateEnd); //获取第四行 XSSFRow row = sheet1.getRow(3); row.getCell(2).setCellValue(businessDataVO.getTurnover()); row.getCell(4).setCellValue(businessDataVO.getOrderCompletionRate()); row.getCell(6).setCellValue(businessDataVO.getNewUsers()); //获得第五行VO row = sheet1.getRow(4); row.getCell(2).setCellValue(businessDataVO.getValidOrderCount()); row.getCell(4).setCellValue(businessDataVO.getUnitPrice() //填充明细数据 for (int i = 0; i < 30; i++) { LocalDate date = dateBegin.plusDays(i); //查询某一天的营业数据 BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(date, LocalTime.MIN), LocalDateTime.of(date, LocalTime.MAX)); //获得某一行 row = sheet1.getRow(7 + i); row.getCell(1).setCellValue(date.toString()); row.getCell(2).setCellValue(businessData.getTurnover()); row.getCell(3).setCellValue(businessData.getValidOrderCount()); row.getCell(4).setCellValue(businessData.getOrderCompletionRate()); row.getCell(5).setCellValue(businessData.getUnitPrice()); row.getCell(6).setCellValue(businessData.getNewUsers()); } //3. 通过输出流将Excel文件下载到客户端浏览器 ServletOutputStream out = httpServletResponse.getOutputStream(); excel.write(out); //关闭资源 out.close(); excel.close(); } catch (IOException e) { e.printStackTrace(); } }
20. 排除依赖
- 用途:避免传递依赖冲突或不需要的依赖引入。
<dependency>
<groupId>some</groupId>
<artifactId>lib</artifactId>
<exclusions>
<exclusion>
<groupId>bad</groupId>
<artifactId>conflict</artifactId>
</exclusion>
</exclusions>
</dependency>
21. @ConfigurationProperties
- 用途:把 YAML/Properties 的配置批量注入到 Bean。
- 优点:比
@Value更适合批量/数组配置,支持prefix映射。 - 使用:
@ConfigurationProperties(prefix="xxx")+@EnableConfigurationProperties或@Component
@Component
@ConfigurationProperties(prefix = "qi.alioss")
@Data
public class AliOssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
三、工作流程图
- 业务功能总览

四、具体业务流程图(按流程列出并留图位)
- 拦截器(请求进入 -> 拦截器校验 -> 放行/抛异常)

- 登录(前端登录 -> 后端校验 -> 返回 JWT)

- 公共字段填充(请求到达 -> AOP 填充公共字段如 createTime/createBy)

- 新增

- 店铺营业状态

- 微信小程序登录

- 用户端查看菜品 — Redis 缓存

- 用户查看套餐 — Spring Cache 注解缓存

- 添加菜品/套餐至购物车

- 用户下单

- 用户支付(第三方支付回调处理,幂等与订单状态更新)

- 订单状态定时修改 — Spring Task(超时未支付自动关闭)

- 来单/催单 — WebSocket

- 导出报表 — POI

项目中用到的大部分知识点就已经整理完了,在项目中遇到的小问题整理请看:小柒味来 - 零碎知识点学习记录
欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1701220998@qq.com