阿里云三件套实战:短信登录、内容审核与支付回调的Spring Boot接入指南
简介这份毕业设计源码面向高校计算机相关专业学生与Java Web初学者围绕阿里云服务落地三个典型业务场景短信验证码登录、用户上传内容审核与支付宝沙箱支付。项目以Java后端为核心配合HTML、CSS与JavaScript完成前端页面与交互并借助Maven与Git管理构建和版本适合作为毕设参考或云服务集成练手项目。压缩包共126个文件、约4.54MB其中Java源文件60个承载核心业务逻辑HTML页面23个与CSS样式13个构成前端界面另有8个JavaScript脚本、8张图片资源及XML、YAML等配置与字体文件目录划分清晰便于按模块阅读与二次开发。目前已有286人学习关注。读者可从中获得一套可运行的完整赛题方案理解验证码登录的短信下发与校验流程、内容审核服务的接入方式以及支付宝沙箱支付接口的对接思路同时参考其分层结构与配置组织快速搭建自己的云服务集成项目。1. 从毕设到上线阿里云三件套怎么把登录、审核、支付串成一条线很多同学的毕设做到最后一周才发现前端页面都画好了后端接口也跑通了唯独三样东西卡着交不了差手机号验证码登录发不出去短信、用户上传的图片没人管、支付回调永远验签失败。这三个功能恰好对应阿里云的三条产品线——短信服务、内容安全、支付能力。把它们串起来一个能演示、能答辩、甚至能小范围上线的完整闭环就成了。这篇笔记面向正在做 Java/Spring Boot 毕设的开发者也适合想把老项目接上云服务的一线同学。我会按「先讲清选型理由再给可抄的代码和参数最后把踩过的坑摊开」的顺序推进。核心词就三个验证码登录、内容审核、支付功能全部围绕阿里云服务的接入方式展开。读完你应该能自己把这三块接进任意一个 Spring Boot 工程里而不是只会复制一段跑不通的示例。2. 验证码登录从短信 API 到 Session 落地的完整链路2.1 为什么选阿里云短信而不是自建短信网关毕设阶段最常见的错误是试图自己搭一个短信发送服务。自建网关需要对接运营商、申请通道、处理签名报备个人开发者根本走不通。阿里云短信服务Dysmsapi提供的是已经报备好的通道你只需要申请签名和模板审核通过后就能调用 API 发送。常见做法是在阿里云控制台创建签名比如「XX 毕设系统」和模板比如「您的验证码为 ${code}5 分钟内有效」拿到 AccessKey ID 和 Secret 后写进配置文件。这里有个容易被忽略的点短信模板的变量名必须和控制台里定义的一模一样${code}不能写成${verifyCode}否则 API 返回成功但用户收不到正确内容。另外个人认证的阿里云账号每天短信条数有上限毕设演示够用但压测时要注意别把额度跑光。2.2 用 Spring Boot 封装短信发送服务下面这段代码是我一般会用的最小实现基于阿里云 SDK 的dysmsapi20170525版本。先加依赖!-- pom.xml 中引入阿里云短信 SDK -- dependency groupIdcom.aliyun/groupId artifactIddysmsapi20170525/artifactId version3.0.0/version /dependency然后是发送逻辑注意 AccessKey 不要硬编码在代码里放到application.yml中通过Value注入Service public class SmsService { Value(${aliyun.sms.access-key-id}) private String accessKeyId; Value(${aliyun.sms.access-key-secret}) private String accessKeySecret; Value(${aliyun.sms.sign-name}) private String signName; Value(${aliyun.sms.template-code}) private String templateCode; public boolean sendVerifyCode(String phone, String code) throws Exception { // 初始化客户端Endpoint 固定为 dysmsapi.aliyuncs.com com.aliyun.dysmsapi20170525.Client client new com.aliyun.dysmsapi20170525.Client( new com.aliyun.teaopenapi.models.Config() .setAccessKeyId(accessKeyId) .setAccessKeySecret(accessKeySecret) .setEndpoint(dysmsapi.aliyuncs.com)); // 模板参数必须是 JSON 字符串key 对应模板里的变量名 SendSmsRequest request new SendSmsRequest() .setPhoneNumbers(phone) .setSignName(signName) .setTemplateCode(templateCode) .setTemplateParam({\code\:\ code \}); SendSmsResponse response client.sendSms(request); // Code 为 OK 才表示提交成功不代表用户一定收到 return OK.equals(response.getBody().getCode()); } }逻辑说明Config里必须同时设置 AccessKey 和 Endpoint缺一个就会报MissingEndpoint或InvalidAccessKeyId。TemplateParam是 JSON 字符串拼接时注意转义。返回的Code为OK只代表阿里云侧接收了请求实际到达率受运营商影响不要拿这个当「用户已收到」的凭证。参数说明signName是控制台审核通过的签名全称不是简称templateCode形如SMS_123456789在模板列表里复制。验证码建议 6 位数字有效期 5 分钟存 Redis 时 key 用sms:code:{phone}value 存验证码设置 TTL 300 秒。2.3 验证码校验与登录态保持发送之后登录接口要做三件事校验验证码、比对 Redis、生成登录态。我一般会这样写PostMapping(/login) public Result login(RequestBody LoginDTO dto) { String cacheCode redisTemplate.opsForValue() .get(sms:code: dto.getPhone()); if (cacheCode null) { return Result.fail(验证码已过期); } if (!cacheCode.equals(dto.getCode())) { return Result.fail(验证码错误); } // 校验通过后立即删除防止重放 redisTemplate.delete(sms:code: dto.getPhone()); User user userService.getOrCreateByPhone(dto.getPhone()); String token JwtUtil.generate(user.getId()); return Result.ok(token); }逻辑说明先查 Redis 再比对比对成功后立刻删除 key这是防重放的关键。如果先删再比对一旦比对失败用户就得重新发短信体验很差。JWT 里放用户 ID 和过期时间前端存 localStorage后续请求带在 Header 里。参数说明Redis TTL 建议 300 秒和短信里写的有效期一致。JWT 过期时间毕设场景设 7 天足够生产环境建议 2 小时加刷新令牌。手机号要做格式校验国内号码用^1[3-9]\\d{9}$正则过滤避免无效请求打到短信 API 浪费额度。3. 内容审核图片和文本怎么在入库前拦住3.1 阿里云内容安全的产品形态与选型内容审核这块阿里云提供的是「内容安全」Green服务支持图片、文本、视频等多种形态。毕设里最常用的是图片审核和文本审核两个接口。图片审核走ImageModeration文本走TextModeration都是同步接口返回风险等级和具体标签。选它的理由很简单自己训练一个审核模型不现实调用第三方又涉及数据出境问题阿里云的内容安全在国内合规性和接入成本上比较平衡。常见做法是用户上传图片后先存 OSS拿到 URL 后调审核接口通过才写业务表不通过就删除 OSS 文件并返回提示。3.2 图片审核的接入代码与风险等级判断先加依赖dependency groupIdcom.aliyun/groupId artifactIdgreen20220302/artifactId version2.2.0/version /dependency审核逻辑Service public class ContentAuditService { Value(${aliyun.green.access-key-id}) private String accessKeyId; Value(${aliyun.green.access-key-secret}) private String accessKeySecret; public boolean auditImage(String imageUrl) throws Exception { com.aliyun.green20220302.Client client new com.aliyun.green20220302.Client( new com.aliyun.teaopenapi.models.Config() .setAccessKeyId(accessKeyId) .setAccessKeySecret(accessKeySecret) .setEndpoint(green-cip.cn-shanghai.aliyuncs.com)); ImageModerationRequest request new ImageModerationRequest(); // service 参数固定为 baselineCheck表示通用基线检测 request.setService(baselineCheck); request.setServiceParameters({\imageUrl\:\ imageUrl \}); ImageModerationResponse response client.imageModeration(request); // 返回结果里 riskLevel 为 none 表示通过 String riskLevel response.getBody().getData().getRiskLevel(); return none.equals(riskLevel); } }逻辑说明service参数用baselineCheck是通用场景如果做的是社交类毕设可以换成socialCheck。serviceParameters是 JSON 字符串图片传 URL 或 Base64 都行URL 方式更省带宽。返回的riskLevel有none、low、medium、high四档毕设场景建议low及以上都拦截生产环境可以按业务容忍度调整。参数说明Endpoint 根据地域不同有差异上海是green-cip.cn-shanghai.aliyuncs.com北京是green-cip.cn-beijing.aliyuncs.com写错会报连接超时。审核接口有 QPS 限制默认 50毕设够用批量审核时记得加限流。3.3 文本审核与审核结果的业务处理文本审核的调用方式和图片几乎一样只是请求类换成TextModerationRequestservice用comment_detectionpublic boolean auditText(String content) throws Exception { com.aliyun.green20220302.Client client buildClient(); TextModerationRequest request new TextModerationRequest(); request.setService(comment_detection); request.setServiceParameters({\content\:\ content \}); TextModerationResponse response client.textModeration(request); String riskLevel response.getBody().getData().getRiskLevel(); return none.equals(riskLevel); }逻辑说明文本审核对长度有限制单次最多 10000 字符超长要分段。审核结果里除了riskLevel还有labels字段标明具体违规类型如广告、辱骂、涉政毕设里可以把这个字段存下来做审核日志答辩时是个加分项。业务处理上我一般会在上传接口里做「先审后存」图片先传 OSS 临时目录审核通过后移动到正式目录并写业务表审核不通过直接删除临时文件返回「内容不合规」。这样即使审核接口挂了也不会让违规内容进入正式存储。4. 支付功能从下单到回调验签的避坑指南4.1 毕设场景下支付方案的选择毕设里做支付最容易翻车的地方是选了需要企业资质的方案。微信支付和支付宝的正式商户号都需要营业执照个人开发者拿不到。常见做法有两种一是用支付宝的沙箱环境二是用微信支付的「Native 支付」配合测试账号。沙箱环境能跑通完整流程答辩演示足够但要注意沙箱的网关地址和正式环境不同。我一般会推荐支付宝沙箱因为它的文档对个人开发者更友好回调验签的调试工具也更完善。下面以支付宝沙箱为例讲接入。4.2 支付宝沙箱下单与回调验签代码先加依赖dependency groupIdcom.alipay.sdk/groupId artifactIdalipay-sdk-java/artifactId version4.38.0.ALL/version /dependency下单接口Service public class PayService { Value(${alipay.app-id}) private String appId; Value(${alipay.private-key}) private String privateKey; Value(${alipay.alipay-public-key}) private String alipayPublicKey; Value(${alipay.notify-url}) private String notifyUrl; public String createOrder(String orderNo, BigDecimal amount) throws Exception { AlipayClient client new DefaultAlipayClient( https://openapi-sandbox.dl.alipaydev.com/gateway.do, appId, privateKey, json, UTF-8, alipayPublicKey, RSA2); AlipayTradePagePayRequest request new AlipayTradePagePayRequest(); request.setNotifyUrl(notifyUrl); request.setReturnUrl(http://localhost:8080/pay/return); JSONObject bizContent new JSONObject(); bizContent.put(out_trade_no, orderNo); bizContent.put(total_amount, amount.toString()); bizContent.put(subject, 毕设订单- orderNo); bizContent.put(product_code, FAST_INSTANT_TRADE_PAY); request.setBizContent(bizContent.toString()); // 返回的是一段 HTML 表单前端直接渲染即可跳转 return client.pageExecute(request).getBody(); } }逻辑说明沙箱网关地址是openapi-sandbox.dl.alipaydev.com正式环境是openapi.alipay.com写错会报Invalid AppId。privateKey是自己的应用私钥alipayPublicKey是支付宝的公钥两个不能搞混。pageExecute返回的是自动提交的 HTML 表单前端拿到后直接document.write就能跳转到收银台。回调验签PostMapping(/pay/notify) public String notify(HttpServletRequest request) throws Exception { MapString, String params new HashMap(); request.getParameterMap().forEach((k, v) - params.put(k, v[0])); // 验签必须用支付宝公钥不是应用公钥 boolean signVerified AlipaySignature.rsaCheckV1( params, alipayPublicKey, UTF-8, RSA2); if (!signVerified) { return fail; } if (TRADE_SUCCESS.equals(params.get(trade_status))) { String orderNo params.get(out_trade_no); orderService.markPaid(orderNo); } return success; }逻辑说明验签用AlipaySignature.rsaCheckV1公钥必须是支付宝公钥。返回success字符串告诉支付宝不要再重发通知返回其他内容支付宝会按策略重试。trade_status为TRADE_SUCCESS才处理业务WAIT_BUYER_PAY等状态忽略。参数说明notifyUrl必须是公网可访问的地址本地开发用内网穿透工具映射否则支付宝回调不到。验签失败最常见的原因是公钥用错或编码不是 UTF-8。4.3 支付状态一致性与订单幂等处理支付回调可能重复发送所以markPaid必须幂等。我一般会在订单表加一个status字段更新时带上条件UPDATE orders SET status PAID, pay_time NOW() WHERE order_no #{orderNo} AND status UNPAID;逻辑说明只有status为UNPAID时才更新影响行数为 0 说明已经处理过直接返回成功。这样即使支付宝发了三次回调订单也只会被标记一次。参数说明订单号建议用「日期 随机数」生成保证唯一且可读。金额用BigDecimal不要用double否则会出现0.1 0.2 ! 0.3的经典问题。5. 避坑与排查那些让我熬夜的报错和修复5.1 短信 API 返回 OK 但用户收不到现象调用sendSms返回Code为OK但手机迟迟没收到短信。原因模板变量名和控制台定义不一致或者签名未审核通过。解决登录阿里云短信控制台检查模板的变量名是否和代码里TemplateParam的 key 完全一致签名状态是否为「已通过」。另外测试号码要加到控制台的测试号码列表里否则会被运营商拦截。5.2 内容审核接口报 MissingEndpoint现象调用imageModeration时报MissingEndpoint或Connection timeout。原因Config里没有设置 Endpoint或者 Endpoint 地域写错。解决根据控制台所在地域填写上海用green-cip.cn-shanghai.aliyuncs.com北京用green-cip.cn-beijing.aliyuncs.com。如果还是超时检查服务器是否能访问外网。5.3 支付回调验签一直失败现象支付宝回调接口返回fail日志里验签结果为false。原因公钥用成了应用公钥或者参数编码不是 UTF-8。解决确认alipayPublicKey是从支付宝开放平台下载的「支付宝公钥」不是自己生成的应用公钥。验签时编码参数传UTF-8signType传RSA2。如果还不行把回调参数打印出来和支付宝的验签工具比对。5.4 AccessKey 泄露导致被盗刷现象阿里云账号收到异常消费告警短信或审核接口被大量调用。原因AccessKey 硬编码在前端代码或提交到了公开仓库。解决立即在控制台禁用泄露的 AccessKey重新生成一对。代码里用环境变量或配置中心注入.gitignore里加上application-local.yml。毕设项目建议用 RAM 子账号只授予短信和内容安全的最小权限。5.5 Redis 验证码被暴力破解现象日志里同一个手机号短时间内大量登录请求验证码被逐个尝试。原因登录接口没有做频率限制。解决发送验证码时限制同一手机号 60 秒内只能发一次登录接口限制同一 IP 每分钟最多尝试 10 次。用 Redis 的INCR加EXPIRE实现计数器超过阈值直接返回「操作过于频繁」。6. 进阶技巧把三块能力做成可复用的 Starter6.1 用自定义 Starter 收敛云服务配置三个功能接完后你会发现每个模块都在重复写 AccessKey 注入、客户端初始化、异常处理。我一般会把它们抽成一个cloud-service-spring-boot-starter用ConfigurationProperties统一管理配置用ConditionalOnProperty控制开关。这样换项目时只需要引入一个依赖改几行配置就能复用。ConfigurationProperties(prefix cloud.service) public class CloudProperties { private Sms sms new Sms(); private Green green new Green(); private Alipay alipay new Alipay(); // getter/setter 省略 }逻辑说明ConfigurationProperties会把application.yml里cloud.service开头的配置自动绑定到对象上。每个子模块一个内部类结构清晰。配合EnableConfigurationProperties在自动配置类里注册Spring Boot 启动时就会加载。参数说明ConditionalOnProperty的havingValue设为true时只有配置里显式开启才生效避免本地开发时误调云服务产生费用。6.2 用统一异常处理兜住云服务报错云服务调用失败时抛出的异常五花八门直接抛给前端体验很差。我一般会写一个RestControllerAdvice把TeaException、AlipayApiException统一转成业务错误码RestControllerAdvice public class CloudExceptionHandler { ExceptionHandler(TeaException.class) public Result handleTeaException(TeaException e) { // 云服务返回的 code 和 message 记日志前端只看到友好提示 log.error(云服务调用失败: code{}, message{}, e.getCode(), e.getMessage()); return Result.fail(服务暂时不可用请稍后重试); } }逻辑说明TeaException是阿里云 SDK 的通用异常getCode()能拿到具体错误码比如isv.AMOUNT_NOT_ENOUGH。日志里记详细错误前端只返回通用提示避免暴露内部信息。6.3 验证清单上线前必须确认的 5 件事检查项确认内容常见问题AccessKey 权限使用 RAM 子账号最小权限主账号 AK 泄露风险高短信模板变量名与代码一致签名已通过模板未审核导致发送失败审核开关生产环境必须开启测试环境可关闭关闭后违规内容直接入库支付回调notifyUrl 公网可访问验签用支付宝公钥内网地址导致回调丢失订单幂等更新带 status 条件影响行数为 0 直接返回重复回调导致重复发货这张表是我每次上线前都会过一遍的尤其是 AccessKey 权限和支付回调地址这两个出问题基本就是通宵排查的节奏。内容审核的开关也容易忘测试时关了上线时没打开等于裸奔。6.4 一个我踩过的坑沙箱环境切正式环境沙箱跑通后切正式环境最容易忘的是网关地址和 AppId。沙箱的网关是openapi-sandbox.dl.alipaydev.com正式是openapi.alipay.comAppId 也要换成正式应用的。我有一次只改了 AppId 没改网关结果一直报Invalid AppId排查了两个小时才发现是网关没换。后来我养成了一个习惯把沙箱和正式的配置写成两个 profile切换时用spring.profiles.active控制再也不用手动改代码。希望帮到你。本文还有配套的精品资源点击获取