资讯动态

MD5工具类实战:SpringBoot密码加密与接口签名最佳实践

发布时间:2026/10/18 12:37:19 来源:尧图企业网站定制
这次我们不用先讲概念直接看代码。在基于 SpringBoot Vue Uniapp LangChain4j SpringAI 的智能视频推荐小程序项目里注册登录、接口签名、防重复提交、AI 会话鉴权这些环节几乎都绕不开一个基础工具类MD5 工具类。这一讲的核心任务是把 MD5 工具类写好、写稳、写对。不是简单贴一个MessageDigest的封装而是从实际业务出发把字符串加密、文件摘要、密码加盐、签名校验、Uniapp 端调用、SpringBoot 接口联动全部串起来。同时我们也要说清楚 MD5 的安全边界什么场景能用什么场景不能用为什么推荐进一步用 BCrypt。如果你正在做 SSM、SpringBoot 单体项目、小程序后端或者想给自己的 AI 应用加一层简单的请求签名这篇文章可以直接收藏。后面每一段代码都能直接复制到项目里运行附带单元测试和接口联调示例。1. 核心能力速览能力项说明项目背景SpringBoot Vue Uniapp 智能视频推荐小程序后端技术栈Java 8 / Java 17、Maven、Spring Boot工具类功能字符串 MD5、文件 MD5、加盐 MD5、签名校验实现方式JDK 原生 MessageDigest、Spring DigestUtils、Hutool SecureUtil核心应用用户密码加密存储、接口签名防篡改、AI 会话参数校验前端配合Uniapp 端密码传输建议使用 HTTPS RSA 或后端 MD5 盐值加密安全边界MD5 存在碰撞风险密码存储建议使用 BCryptMD5盐仅用于学习与签名场景是否支持批量支持批量文件摘要与批量参数签名适合场景单体项目教程、接口签名、文件完整性校验、学习案例注意本讲不讨论 SHA-256 与 MD5 的算法细节优劣重点放在工程落地与代码组织上。项目中的密码加密演示主要用于理解登录流程生产环境请参考第 9 节使用 BCrypt 替代。2. 适用场景与使用边界2.1 MD5 工具类在项目中的定位在一个智能视频推荐小程序中MD5 工具类主要出现在这些位置用户注册时对用户明文密码做不可逆摘要再落库。用户登录时将前端传入的密码摘要与数据库中的摘要做比对。接口请求中对关键参数拼接后计算签名防止请求参数被篡改。视频素材上传后计算文件 MD5用于素材去重与完整性校验。AI 推荐接口调用前后对会话标识做摘要避免非法遍历。这些场景都有一个共同点我们需要的是一个稳定的、可复用的摘要工具而不是每次在不同 Service 里重复写MessageDigest的 try-catch。这就是工具类存在的意义。2.2 使用边界与合规提醒MD5 是摘要算法不是加密算法。它生成的固定长度摘要不可逆理论上无法还原原始数据但存在碰撞攻击风险。密码存储场景生产环境不建议直接使用 MD5。即使加盐MD5 的迭代速度太快容易被暴力破解。更稳妥的选择是 BCrypt、Argon2 或 scrypt。接口签名场景MD5 可以用于内部系统间低安全等级的数据完整性校验。如果是对外开放 API建议升级为 HMAC-SHA256 或 RSA 签名。视频推荐小程序涉及用户行为数据、观看记录、AI 推荐结果。所有摘要、加密、签名逻辑只能用于合法业务不得用于绕过权限控制、伪造请求、窃取数据或规避平台规则。如果项目中涉及用户上传的视频、人脸信息、语音内容必须获得用户明确授权并按照最小必要原则存储与使用。3. 环境准备与前置条件3.1 开发环境检查清单以当前项目的后端工程为例本讲代码可以在以下环境直接运行JDKJava 8 或 Java 17Spring Boot 2.x 建议 Java 8Spring Boot 3.x 建议 Java 17构建工具Maven 3.6 或 Gradle 7.x框架版本Spring Boot 2.7.x 或 3.1.x前端开发工具HBuilderX 或 VSCodeUniapp 项目数据库MySQL 5.7 / 8.0本讲不涉及复杂表结构Redis可选用于接口防重与签名缓存如果没有项目环境也可以单独创建一个 Spring Boot 工程验证本讲内容。只需要spring-boot-starter-web和lombok即可。3.2 pom.xml 最小依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- Hutool 工具库可选如果不想引入可以直接用 JDK 实现 -- dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.25/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependenciesHutool 版本号建议以 Maven 仓库最新稳定版为准。如果你不想引入额外依赖可以使用 JDK 自带的MessageDigest后者在本文第 4 节有完整实现。3.3 目录规划本讲新增代码统一放在以下包结构中com.example.videorecommend ├── common │ └── util │ ├── Md5Util.java │ └── SignUtil.java ├── controller │ └── AuthController.java ├── service │ └── UserService.java └── entity └── User.java如果项目是多模块结构建议把Md5Util放在公共模块的common-util子模块中方便视频推荐服务、AI 会话服务、用户服务共同引用。4. MD5 工具类实现与代码解析4.1 基于 JDK 原生实现JDK 自带的MessageDigest是实现 MD5 最基础的方式。优点是零依赖缺点是代码需要处理字节数组与十六进制字符串的转换。package com.example.videorecommend.common.util; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * MD5 工具类 * 基于 JDK 原生 MessageDigest 实现 */ public class Md5Util { private static final char[] HEX_DIGITS {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f}; private Md5Util() { } /** * 计算字符串的 MD5 摘要 * * param text 原始字符串不允许为空 * return 32 位小写十六进制字符串 */ public static String md5(String text) { if (text null) { throw new IllegalArgumentException(text 不能为 null); } try { MessageDigest digest MessageDigest.getInstance(MD5); byte[] bytes digest.digest(text.getBytes(StandardCharsets.UTF_8)); return toHex(bytes); } catch (NoSuchAlgorithmException e) { // 实际不会发生MD5 在 JDK 中始终可用 throw new IllegalStateException(当前 JDK 不支持 MD5 算法, e); } } /** * 计算文件的 MD5 摘要适合视频素材去重与完整性校验 * * param file 文件对象 * return 32 位小写十六进制字符串 */ public static String md5File(File file) { if (file null || !file.exists()) { throw new IllegalArgumentException(文件不存在); } try (InputStream in new FileInputStream(file)) { MessageDigest digest MessageDigest.getInstance(MD5); byte[] buffer new byte[8192]; int len; while ((len in.read(buffer)) ! -1) { digest.update(buffer, 0, len); } return toHex(digest.digest()); } catch (Exception e) { throw new RuntimeException(文件 MD5 计算失败, e); } } private static String toHex(byte[] bytes) { char[] chars new char[bytes.length * 2]; int index 0; for (byte b : bytes) { chars[index] HEX_DIGITS[(b 4) 0x0F]; chars[index] HEX_DIGITS[b 0x0F]; } return new String(chars); } }这段代码有四个关键点字符串统一使用UTF-8编码避免不同系统默认编码导致摘要不一致。toHex方法把字节数组转换为 32 位小写字符串这是最常用的格式。md5File使用缓冲流逐块读取视频文件较大时不会一次性把所有字节加载到内存。工具类构造方法私有化不允许外部实例化。4.2 基于 Spring 的 DigestUtils 实现Spring 框架自带的org.springframework.util.DigestUtils是对MessageDigest的封装Spring Boot 项目可以直接使用不需要额外引入依赖package com.example.videorecommend.common.util; import org.springframework.util.DigestUtils; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; public class SpringMd5Util { private SpringMd5Util() { } /** * 字符串 MD5 */ public static String md5(String text) { return DigestUtils.md5DigestAsHex(text.getBytes(StandardCharsets.UTF_8)); } /** * 文件 MD5 */ public static String md5File(File file) { try (InputStream in new FileInputStream(file)) { return DigestUtils.md5DigestAsHex(in); } catch (Exception e) { throw new RuntimeException(文件 MD5 计算失败, e); } } }如果你用的是 Spring Boot 3.xDigestUtils仍然存在于spring-core中上面的代码可以直接使用。唯一需要注意的是Spring 官方文档曾提示DigestUtils.md5DigestAsHex仅用于非安全场景因为 MD5 已经不适合密码存储。4.3 基于 Hutool 的 SecureUtil如果项目中已经引入 Hutool那么 MD5 工具类可以写得非常简洁package com.example.videorecommend.common.util; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.DigestUtil; import java.io.File; public class HutoolMd5Util { private HutoolMd5Util() { } public static String md5(String text) { return SecureUtil.md5(text); } public static String md5File(File file) { return DigestUtil.md5File(file); } /** * 加盐 MD5盐值直接拼接到原文后面 */ public static String md5WithSalt(String text, String salt) { return SecureUtil.md5(text salt); } }在纯工具类层面我更推荐直接使用 Spring 自带的DigestUtils或 Hutool。不过在教程项目中自己实现Md5Util的好处是可以让新手看到底层原理后续切换到 SHA-256、BCrypt 时也更容易理解。4.4 加盐增强版考虑到项目中有用户密码存库的需求这里给出一个加盐 MD5 的完整实现。盐值的处理方式是在原密码后面拼接一段随机字符串然后再做摘要package com.example.videorecommend.common.util; import java.security.MessageDigest; import java.security.SecureRandom; import java.nio.charset.StandardCharsets; public class PasswordUtil { private static final int SALT_LENGTH 16; private PasswordUtil() { } /** * 生成随机盐值 */ public static String generateSalt() { byte[] bytes new byte[SALT_LENGTH]; new SecureRandom().nextBytes(bytes); StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(String.format(%02x, b)); } return sb.toString(); } /** * 带盐 MD5 摘要 */ public static String md5WithSalt(String password, String salt) { return Md5Util.md5(salt password salt); } /** * 密码校验 */ public static boolean matches(String rawPassword, String salt, String encodedPassword) { String target md5WithSalt(rawPassword, salt); return target.equalsIgnoreCase(encodedPassword); } }需要说明的是加盐 MD5 比裸 MD5 安全很多但仍然不是最优方案。因为在现代 GPU 环境下MD5 的计算速度非常快攻击者可以对“盐密码”的组合进行大规模字典攻击。第 9 节会给出 BCrypt 的推荐写法。5. 在注册与登录业务中集成 MD5 工具类5.1 用户实体类package com.example.videorecommend.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; Data TableName(t_user) public class User { TableId(type IdType.AUTO) private Long id; private String username; /** 实际存储的是 MD5(盐 密码 盐) */ private String password; /** 随机盐值 */ private String salt; private String nickname; private String avatar; private LocalDateTime createTime; }5.2 注册逻辑注册时后端拿到 Uniapp 传来的用户名和密码先生成盐值再用PasswordUtil.md5WithSalt计算摘要最后落库package com.example.videorecommend.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.videorecommend.common.util.PasswordUtil; import com.example.videorecommend.entity.User; import com.example.videorecommend.mapper.UserMapper; import com.example.videorecommend.service.UserService; import org.springframework.stereotype.Service; Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { Override public void register(String username, String rawPassword) { // 检查用户名是否已存在 long count lambdaQuery() .eq(User::getUsername, username) .count(); if (count 0) { throw new RuntimeException(用户名已存在); } User user new User(); user.setUsername(username); user.setSalt(PasswordUtil.generateSalt()); user.setPassword(PasswordUtil.md5WithSalt(rawPassword, user.getSalt())); user.setCreateTime(LocalDateTime.now()); save(user); } Override public String login(String username, String rawPassword) { User user lambdaQuery() .eq(User::getUsername, username) .one(); if (user null) { throw new RuntimeException(用户不存在); } boolean matched PasswordUtil.matches(rawPassword, user.getSalt(), user.getPassword()); if (!matched) { throw new RuntimeException(密码错误); } // 实际项目中这里会生成 JWT Token本讲不做展开 return mock-token- user.getId(); } }注册流程的时序是前端提交密码 - 后端生成盐值 - 拼接并计算 MD5 - 存盐值和摘要。登录流程的时序是前端提交密码 - 后端从数据库查出盐值和摘要 - 重新拼接计算 MD5 - 比对。5.3 认证接口 Controllerpackage com.example.videorecommend.controller; import com.example.videorecommend.service.UserService; import org.springframework.web.bind.annotation.*; import java.util.Map; RestController RequestMapping(/api/auth) public class AuthController { private final UserService userService; public AuthController(UserService userService) { this.userService userService; } PostMapping(/register) public MapString, Object register(RequestBody RegisterRequest request) { userService.register(request.getUsername(), request.getPassword()); return Map.of(code, 200, message, 注册成功); } PostMapping(/login) public MapString, Object login(RequestBody RegisterRequest request) { String token userService.login(request.getUsername(), request.getPassword()); return Map.of(code, 200, message, 登录成功, data, Map.of(token, token)); } public static class RegisterRequest { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username username; } public String getPassword() { return password; } public void setPassword(String password) { this.password password; } } }5.4 Uniapp 前端调用示例在 Uniapp 中注册和登录页面只需要把用户输入的密码通过 HTTPS 提交到后端不在前端做 MD5 处理。原因是 MD5 一旦在前端执行摘要结果就变成了事实上的“密码”如果被抓包逆向攻击者可以直接用摘要请求登录接口。// pages/login/login.vue 中 uniapp 请求示例 uni.request({ url: https://api.example.com/api/auth/login, method: POST, data: { username: this.username, password: this.password }, header: { Content-Type: application/json }, success: (res) { if (res.data.code 200) { uni.setStorageSync(token, res.data.data.token); uni.switchTab({ url: /pages/index/index }); } else { uni.showToast({ title: res.data.message, icon: none }); } } });这里必须强调一个工程原则注册登录链路中MD5 是后端用来“处理密码摘要与落库”不是用来“加密传输”。网络传输安全靠 HTTPS 保证。如果项目要求更高安全性前端可以使用 RSA 公钥加密密码再传输后端用私钥解密后再做 MD5 或 BCrypt 摘要。6. 在 AI 推荐接口与视频推荐场景中使用签名6.1 为什么需要接口签名智能视频推荐小程序中后端会暴露两类接口普通业务接口和 AI 推荐接口。普通业务接口有登录态控制使用 JWT 即可。AI 推荐接口可能出现这样的问题AI 推荐参数被恶意篡改例如把用户 ID 换成另一个用户导致越权访问。外部脚本反复调用推荐接口消耗服务器资源。推荐结果回传时被中间人替换导致前端展示异常内容。针对这些问题可以在关键接口上增加 MD5 签名校验。签名规则是将请求参数按字典序排序拼接字符串再附加一个双方约定的密钥最后计算 MD5 作为签名字段。6.2 签名工具类实现package com.example.videorecommend.common.util; import java.util.Map; import java.util.TreeMap; /** * 接口签名工具类 * 规则参数按 key 字典序排序拼接 keyvalue...keyvaluekeysecret * 最终计算 MD5 */ public class SignUtil { private static final String SECRET your-ai-secret-key; private SignUtil() { } public static String generateSign(MapString, String params) { // TreeMap 会自动按 key 排序 MapString, String sortedParams new TreeMap(params); StringBuilder sb new StringBuilder(); for (Map.EntryString, String entry : sortedParams.entrySet()) { if (entry.getValue() null || entry.getValue().isEmpty()) { continue; } sb.append(entry.getKey()) .append() .append(entry.getValue()) .append(); } sb.append(key).append(SECRET); return Md5Util.md5(sb.toString()); } public static boolean verifySign(MapString, String params, String sign) { String expected generateSign(params); return expected.equalsIgnoreCase(sign); } }6.3 在视频推荐接口中接入package com.example.videorecommend.controller; import com.example.videorecommend.common.util.SignUtil; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; RestController RequestMapping(/api/recommend) public class RecommendController { PostMapping(/ai-videos) public MapString, Object aiVideos(RequestBody AiRecommendRequest request) { // 参数校验与签名校验可以放在拦截器里这里演示手动校验 MapString, String params new HashMap(); params.put(userId, request.getUserId()); params.put(category, request.getCategory()); params.put(timestamp, request.getTimestamp()); if (!SignUtil.verifySign(params, request.getSign())) { return Map.of(code, 401, message, 签名校验失败); } // 这里调用 LangChain4j / SpringAI 相关服务生成推荐结果 // 本讲不做展开 return Map.of(code, 200, message, 推荐成功); } public static class AiRecommendRequest { private String userId; private String category; private String timestamp; private String sign; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId userId; } public String getCategory() { return category; } public void setCategory(String category) { this.category category; } public String getTimestamp() { return timestamp; } public void setTimestamp(String timestamp) { this.timestamp timestamp; } public String getSign() { return sign; } public void setSign(String sign) { this.sign sign; } } }6.4 前端签名计算示例如果签名在 Uniapp 端计算需要和后端保持完全相同的排序与拼接规则。由于 API 的请求体比较复杂推荐使用 Hash 模式把userId、category、timestamp三个字段传给签名工具同时后端排除sign字段。// utils/sign.js function md5(str) { // 使用 js-md5 库 const md5 require(js-md5); return md5(str); } function generateSign(params, secret) { const keys Object.keys(params).sort(); let str ; for (let key of keys) { if (params[key] ! params[key] ! null) { str ${key}${params[key]}; } } str key${secret}; return md5(str); } module.exports { generateSign };这里有一个实际项目经常踩的坑签名参数必须排除sign本身否则拼接结果会出现循环依赖。另一个坑是时间戳校验建议后端把timestamp与当前时间的差值限制在 5 分钟以内超过直接拒绝。7. 单元测试与接口联调验证7.1 工具类单元测试package com.example.videorecommend.common.util; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class Md5UtilTest { Test void md5_knownInput_shouldReturnExpectedValue() { // 已知 md5(123456) e10adc3949ba59abbe56e057f20f883e String md5 Md5Util.md5(123456); Assertions.assertEquals(e10adc3949ba59abbe56e057f20f883e, md5); } Test void md5_sameInput_shouldReturnSameValue() { String first Md5Util.md5(video-recommend); String second Md5Util.md5(video-recommend); Assertions.assertEquals(first, second); } Test void md5_diffInput_shouldReturnDifferentValue() { String first Md5Util.md5(video-recommend); String second Md5Util.md5(video-recommend2); Assertions.assertNotEquals(first, second); } Test void md5WithSalt_samePasswordDifferentSalt_shouldReturnDifferentValue() { String pwd1 PasswordUtil.md5WithSalt(123456, salt-1); String pwd2 PasswordUtil.md5WithSalt(123456, salt-2); Assertions.assertNotEquals(pwd1, pwd2); } Test void matches_correctPassword_shouldReturnTrue() { String salt PasswordUtil.generateSalt(); String encoded PasswordUtil.md5WithSalt(123456, salt); Assertions.assertTrue(PasswordUtil.matches(123456, salt, encoded)); Assertions.assertFalse(PasswordUtil.matches(wrong, salt, encoded)); } }7.2 注册登录接口联调流程启动 SpringBoot 应用后按下面步骤验证启动项目默认端口 8080。注册用户curl -X POST http://127.0.0.1:8080/api/auth/register \ -H Content-Type: application/json \ -d {username:test01,password:123456}查看数据库t_user表确认salt字段有随机值password字段是 32 位十六进制字符串。登录测试curl -X POST http://127.0.0.1:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:test01,password:123456}错误密码测试curl -X POST http://127.0.0.1:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:test01,password:654321}预期行为正确密码返回code: 200错误密码返回code: 500或自定义错误码。7.3 API 签名接口的 curl 验证先手动生成签名。假设参数是userId1001、categorycomedy、timestamp1700000000密钥是your-ai-secret-key拼接待签名字符串categorycomedytimestamp1700000000userId1001keyyour-ai-secret-key然后请求curl -X POST http://127.0.0.1:8080/api/recommend/ai-videos \ -H Content-Type: application/json \ -d { userId: 1001, category: comedy, timestamp: 1700000000, sign: 上面计算出的签名 }如果签名正确返回推荐成功如果随意修改userId或category字段签名校验会失败返回 401。8. 常见问题与排查方法问题现象可能原因排查方式解决方案前后端 MD5 结果不一致编码方式不同字符串包含不可见字符统一使用 UTF-8输出格式统一为小写十六进制后端打印拼接字符串前端逐字符比对登录时密码校验失败盐值未正确存储或读取密码摘要拼接顺序不一致检查数据库中的 salt 字段检查 PasswordUtil 的拼接顺序保证注册和登录使用同一个 PasswordUtil文件 MD5 计算超时视频文件过大单线程读取慢打印耗时检查文件流是否关闭加大缓冲异步处理考虑只抽取前 N 字节做初步校验接口签名校验失败参数排序不一致包含空值sign 字段被拼接在后端临时打印签名字符串使用 TreeMap 排序过滤空值排除 sign 字段数据库直接存 MD5数据库被盗后密码泄露裸 MD5 易被破解检查 password 字段是否带盐已上线项目可增加 salt 字段并做摘要迁移新项目直接使用 BCrypt注册时有相同用户名同时提交并发注册存在竞态条件查看数据库唯一索引为 username 添加唯一索引捕获重复键异常Uniapp 请求提示 415 或 400Content-Type 未设置或参数格式错误查看浏览器 Network 面板设置Content-Type: application/json时间戳签名过期客户端与服务器时区不一致打印双方当前时间统一使用时间戳秒放宽时间窗口到 5 分钟校准服务器时间9. 最佳实践与使用建议9.1 密码存储升级从 MD5 加盐到 BCrypt本讲为了教学演示使用了 MD5 加盐方案。在真实项目中更推荐直接使用 Spring Security 自带的BCryptPasswordEncoder或者引入 jBCrypt。BCrypt 自带盐值每次加密结果都不同而且计算速度远慢于 MD5能有效对抗暴力破解。import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class BCryptExample { private static final BCryptPasswordEncoder ENCODER new BCryptPasswordEncoder(); public static void main(String[] args) { String rawPassword 123456; String encoded1 ENCODER.encode(rawPassword); String encoded2 ENCODER.encode(rawPassword); System.out.println(encoded1); System.out.println(encoded2); // 每次输出都不一样但 matches 结果都为 true System.out.println(ENCODER.matches(rawPassword, encoded1)); System.out.println(ENCODER.matches(rawPassword, encoded2)); } }如果已经上线了 MD5 密码的用户可以在登录逻辑里判断密码摘要前缀命中旧格式就自动升级为 BCrypt 并更新数据库实现平滑迁移。9.2 接口签名的最佳实践签名密钥不要硬编码在 front-end 代码中。Uniapp 端如果必须计算签名密钥需要做混淆并定期更换更稳妥的方案是登录后由后端下发临时密钥或者把计算签名移到后端专用接口。签名必须带时间戳并加一个 5 分钟的时间窗口。重要接口加请求频率限制例如同一个用户每 10 秒最多调用一次 AI 推荐接口。Auditor 不要只在 Controller 手动校验建议抽成拦截器或注解例如SignCheck。9.3 工具类的工程规范工具类构造方法私有化方法全部静态。输入参数做非空校验避免空指针。文件流使用 try-with-resources 自动关闭。不要在工具类里混入业务参数例如把SECRET单独抽到一个配置类。工具类的日志使用LoggerFactory.getLogger不要打印签名密钥。9.4 小程序端的合规建议用户注册协议必须写明密码摘要存储方式和数据用途。视频推荐系统会采集观看记录、点击行为、搜索关键词需要在隐私政策中明确说明并获得用户授权。AI 推荐功能如果用到了用户的历史行为数据只能在授权范围内使用不能超范围收集。如果涉及未成年人必须依法取得监护人同意并做数据最小化处理。10. 总结与下一步这一讲重点落在 MD5 工具类的实现与业务接入表面上只是写一个Md5Util实际上解决了三类问题用户注册登录时的密码摘要与校验流程AI 推荐接口的签名防篡改视频素材文件完整性校验的通用方案。从代码层面看最值得先把Md5Util和PasswordUtil两个类跑通再用 curl 或 Uniapp 调一次注册登录接口最后把签名工具集成到推荐接口的拦截器中。最容易踩的坑不是 MD5 算法本身而是编码不一致、盐值存储不统一、签名拼接顺序不一致。这三类问题只要按第 8 节的排查思路处理基本都能快速定位。下一步建议优先做这几件事给t_user表增加唯一索引防止并发注册问题把密码存储升级为 BCrypt把签名校验从 Controller 抽到拦截器最后配合 LangChain4j SpringAI 的推荐逻辑完成 AI 视频推荐接口的联调。这样整个小程序后端的认证与接口安全体系就能稳定跑起来后续再接入 Apache ShardingSphere、Redis 缓存、消息队列时也不会因为基础工具类出问题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑