shopping-web项目逻辑分析

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
historical-voting-web/
├── user-service/ # 用户服务模块
│ ├── src/main/java/
│ │ └── com/historical/voting/user/
│ │ ├── annotation/ # 自定义注解
│ │ ├── config/ # 配置类
│ │ ├── controller/ # 控制器
│ │ ├── entity/ # 实体类
│ │ ├── exception/ # 异常处理
│ │ ├── Factory/ # 工厂类
│ │ ├── interceptor/ # 拦截器
│ │ ├── mapper/ # MyBatis映射
│ │ ├── repository/ # JPA仓库
│ │ ├── service/ # 服务层
│ │ ├── strategy/ # 策略模式实现
│ │ └── util/ # 工具类
│ └── resources/
│ └── application.yml # 配置文件

核心功能模块

4.1 用户认证模块

4.1.1 JWT认证流程

  1. 用户登录流程:

    1
    用户登录 -> 验证凭据 -> 生成JWT令牌(访问令牌+刷新令牌)-> 返回令牌

流程:

在serviceimpl中实现login方法:

从数据库中select有没有输入的username,用户注册状态是不是0,用户是否是被封锁,密码是不是对。

然后生成access token 和refreash token

使用redis,opsforvalue.set设置key-vaule和过期时间

把他们放入map集合里面,然后返回token


JWT加密:

采用HS512,然后创建payload,里面存入username。

1
2
3
4
5
6
7
8
return Jwts.builder()
.setClaims(claims) // 设置自定义声明
.setSubject(username) // 设置主题,一般也是用户标识
.setIssuedAt(new Date()) // 签发时间
.setExpiration(new Date(System.currentTimeMillis() + expiration)) // 过期时间
.signWith(key) // 使用给定密钥签名(默认 HMAC-SHA256)
.compact(); // 构建并返回字符串

  1. 请求认证流程:

    1
    请求 -> JwtAuthenticationFilter拦截 -> 验证Token -> 提取用户信息 -> 放行请求

JwtAuthenticationFilter:

尝试直接使用access token,从requset的头里提取header.startsWith(“Bearer “)开头的,然后跳过前面的前缀,获取到token

access token刷新,claims必须要合法,然后从claims里获取username

然后检查token是不是在redis的黑名单中,然后看redis是不是存了这个令牌,get出来,然后requset.setAttribute加到请求上,然后返回response

然后refresh token刷新,

先是从request看到刷新令牌,然后先验证是不是和redis中的相同

相同的话,再生成一个新的access token令牌,然后更新redis中的令牌

把他加到resonse的响应头里,然后去看放不放行请求。


注册的话,就是使用mailSender去发送验证码,只有验证码对才可以进行注册操作,创建新用户,写入数据库。

4.1.2 OAuth2认证流程

GitHub OAuth2登录流程:

1
用户点击GitHub登录 -> 重定向到GitHub -> 用户授权 -> 回调接口 -> 创建/更新用户信息 -> 生成JWT

4.2 用户管理模块

主要接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
@RequestMapping("/api/users")
public class UserController {

// 发送验证码
@PostMapping("/register/send-code")

// 用户注册
@PostMapping("/register")

// 用户登录
@PostMapping("/login")

// 用户登出
@PostMapping("/logout")

// 获取用户信息
@GetMapping("/profile")
}

4.3 文件存储+处理模块

  • 支持图片、视频上传
  • 支持生成缩略图
  • 文件存储位置可配置(本地/腾讯云COS)

📌 文件合法性校验(FileUtils)

在用户上传视频文件时,系统会通过 FileUtils 工具类对文件进行校验,主要包括以下几项逻辑:

  • 单个文件大小限制:系统设定上传视频的最大允许体积(如 100MB),超过则直接拒绝上传。
  • 总上传大小控制:可根据用户标识或 IP,设置每日或每小时内上传视频的总大小上限,用于防止恶意刷带宽。
  • 文件类型判断:仅允许特定的视频类型(如 MP4、AVI 等)通过,防止伪装文件上传。

该工具类在上传接口中被调用,作为服务端对用户输入的第一道校验防线。


🧩 策略模式设计:TranscodingStrategy

系统中使用了策略模式来支持不同的转码协议(如 HLS、DASH),定义了统一的策略接口 TranscodingStrategy,它约定了所有转码实现类都必须提供统一的转码方法。

该接口屏蔽了具体的命令细节,为后续新增新格式(如 WebM、AV1)提供了扩展点。


🏗️ 抽象策略实现类:AbstractTranscodingStrategy

系统设计了一个抽象类 AbstractTranscodingStrategy 来统一封装转码过程的通用步骤。主要逻辑包括:

  • 组装转码命令:根据输入路径、输出路径、分辨率等参数拼接出 FFmpeg 的命令行。
  • 调用系统进程:通过 ProcessBuilder.start() 执行命令,完成实际的视频转码操作。
  • 异常捕获与输出校验:判断进程返回值是否为 0,确认转码是否成功,并统一抛出异常或写入日志。

这样,具体策略类只需关注自己的格式命令,不用重复通用逻辑。


🔀 具体策略类:HLS 与 DASH 实现

系统实现了两个具体的转码策略:

  • HLSTranscodingStrategy:使用 .m3u8 + 分片方式转码,适用于直播或渐进式加载。
  • DASHTranscodingStrategy:使用 .mpd 格式与多码率轨道,适用于 ABR(自适应码率)播放。

每个策略类内部根据自己的输出格式构建专属的 FFmpeg 命令,并通过抽象类封装的方法执行转码。

策略类使用 @Component 注册为 Spring Bean,支持通过名称注入选择。


🏭 策略工厂类:TranscodingStrategyFactory

为了按需获取具体的策略实现,系统设计了一个工厂类:

  • 构造时收集策略 Bean:在 Spring 初始化过程中,将所有实现了 TranscodingStrategy 接口的 Bean 收集进 Map,key 为策略名(如 “hls”、”dash”)。
  • 根据名称获取策略:对外提供 get(String name) 方法,根据输入的策略名称,返回对应的实现类。
  • 支持动态扩展:后续新增策略只需实现接口并加 @Component 即可被自动注册到工厂中。

该工厂类被视频服务调用,用于根据配置或用户选择的协议动态选择转码策略。


VideoServiceImpl 服务实现逻辑说明:

  1. 视频上传与转码处理
  • 视频上传路径配置:通过配置注入 videoUploadPaththumbnailPath,用于存储上传的视频文件和封面缩略图。
  • 视频转码处理 (processVideo):
    • 通过 ffmpeg 命令行对上传视频进行转码,统一编码格式(H.264)和音频编码(AAC),并启用快速启动优化。
    • 转码完成后调用封面提取方法。
    • 若转码或封面提取失败,抛出自定义异常 FileProcessException,确保上传流程异常可控。
  • 封面缩略图提取 (extractThumbnail):
    • 利用 ffmpeg 截取视频的第一帧作为缩略图,存放到指定目录。
    • 确保缩略图文件命名与视频文件对应,方便管理和访问。

  1. ABR(自适应码率)转码任务提交
  • 多分辨率转码 (submitTranscoding):
    • 针对预设的多个分辨率(如 720p、480p、360p),先通过 ffmpeg 对视频进行缩放处理,生成不同清晰度的视频片段。
    • 使用线程池异步执行各分辨率转码任务,提高并发性能,避免阻塞主流程。
    • 转码后调用策略工厂,获取指定格式(如 HLS)的转码策略,执行格式封装转码。
    • 任何转码失败都会被捕获并记录日志,保证服务稳定。

  1. 播放记录与访问限流
  • 播放记录统计 (recordView):
    • 使用 Redis 以用户 IP 作为唯一标识限制重复计数,避免刷播放量。
    • 通过 setIfAbsent 设置一分钟的缓存有效期,实现短时间内对同一视频同一 IP 只计数一次。
    • 播放量计数存在 Redis,减少数据库压力。
  • 定时同步播放量 (syncViewCounts):
    • 使用定时任务每 5 分钟将 Redis 中缓存的播放量批量同步到数据库。
    • 同步后清理缓存,保证数据一致性和高效。

  1. 点赞与点踩功能
  • 点赞与点踩操作
    • 采用 Redis Set 结构存储点赞和点踩的用户 ID,保证用户只能点赞或点踩其中一个。
    • 点赞时自动移除对应的点踩记录,点踩亦然,确保数据互斥。
    • 读取点赞和点踩数量时,直接返回 Redis Set 的大小,实时且性能高。

  1. 分享统计
  • 分享次数统计
    • 通过 Redis 字符串计数,记录视频被分享的总次数。
    • 读写均通过 Redis 完成,避免频繁访问数据库。

  1. 标签分页查询
  • 视频标签分页查询 (pageByTags):
    • 支持根据标签关键字模糊查询视频列表。
    • 使用 MyBatis-Plus 的分页功能,按发布时间倒序排列,方便前端分页展示。
    • 如果标签为空,则返回全部视频列表。

4.3 优惠券模块

4.3.1 优惠券类型

  • 满减券:满足指定金额后减免固定金额
  • 折扣券:按比例折扣
  • 无门槛券:直接减免固定金额
  • 限时券:在指定时间段内有效

4.3.2 主要接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@RequestMapping("/api/coupons")
public class CouponController {
// 创建优惠券
@PostMapping("/create")

// 发放优惠券
@PostMapping("/distribute")

// 查询用户优惠券
@GetMapping("/user/{userId}")

// 使用优惠券
@PostMapping("/use")

// 查询优惠券详情
@GetMapping("/{couponId}")
}

4.3.3 优惠券业务流程

  1. 优惠券创建流程:

    1
    管理员创建优惠券 -> 设置优惠券规则 -> 设置发放策略 -> 保存优惠券信息
  2. 优惠券发放流程:

    1
    触发发放条件 -> 检查发放规则 -> 创建用户优惠券记录 -> 发送优惠券到账通知
  3. 优惠券使用流程:

    1
    用户选择优惠券 -> 验证使用条件 -> 计算优惠金额 -> 标记优惠券已使用 -> 应用优惠

4.4 限流AOP注释

限流注解:RateLimit

定义了这个注解,

1
2
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)

这个注解只能用在方法上,注解信息会保留到运行时,这允许通过反射读取注解内容,用于 AOP(切面)处理。可以通过@Around来拦截

里面定义了标识,每秒最大的访问次数,默认五次,然后


RateLimitUtils:工具类

基于内存桶(Bucket4j)限流实现的核心方法,使用 ConcurrentHashMap 存储每个唯一 key 对应的令牌桶。

然后创键一个限流规则,容量为permitsPerSecond,然后Refill每秒

然后返回这个使用这个限流规则的Bucket4j作为value


Aspect

获取注解参数:key(限流维度)、permitsPerSecond(限流速率)、message(失败提示)

获取客户端 IP + 方法名 作为限流键(若未自定义)

使用 Bucket4j 获取或创建对应的桶对象

使用 tryConsume(1) 判断是否允许访问,调用限流器看是不能是访问,失败就返回msg

@Around规定切入点

还加上了全局处理异常,如果类型是限流的这个异常,给出429,说明当前是限流了。


4.5 AOP实现业务和登录的跟踪