一。环境准备
咱们先搭个基础环境,新建一个 SpringBoot 项目,再把需要的依赖加进去。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.12</version>
<relativePath/>
</parent>
<groupId>com.ljw.mall</groupId>
<artifactId>mall-auth-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mall-auth-server</name>
<description>认证服务</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
</properties>
<dependencies>
<!-- 公共依赖 -->
<dependency>
<groupId>com.ljw.mall</groupId>
<artifactId>mall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
父模块管理子模块,确保依赖版本统一。
二。注册中心与配置中心
接下来要把认证服务注册到 Nacos 里,这样其他模块才能找到它。添加对应的依赖后,完成配置并启动测试,确认服务能正常上线。
三。登录注册页面资源整理
界面部分也不能少,先把登录和注册的模板文件拷进项目。进入 Nginx 静态资源文件夹下,建立登陆和注册文件夹,然后把对应的静态文件拷贝过去。上传成功后,我们需要在 host 文件中添加配置,修改 Nginx 的反向代理设置,重启服务。
同时调整网关服务的路由规则,把登录和注册页面中所有静态资源文件的路径也改好。这里有个坑要注意:spring.resources.static-location 参数默认指向 classpath:/static 等路径,不包含 /templates。所以需要在 application.yml 里显式指定:
spring:
resources:
static-locations: classpath:/templates
配置完成后再次启动项目,访问登录和注册页面应该就能看到了。
四。视图解析器配置
Controller 层对于一些访问过于麻烦的请求接口,通过视图解析器来帮我们提升效率。直接映射 URL 到视图名即可:
@Configuration
public class MyWebViewConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login.html").setViewName("/login");
registry.addViewController("/reg.html").setViewName("/reg");
}
}
五。页面跳转逻辑
主商品界面、检索模块、商品详情页都需要支持跳转到登录或注册页,注册页也要能跳回登录页。这部分主要是在前端 HTML 中配置好链接地址,配合 Gateway 路由转发即可。
六。注册功能实现
1. 发送验证码前端逻辑
前端通过 jQuery 异步提交来发送短信请求。给按钮绑定点击事件,设置倒计时计时器,防止用户短时间内重复点击。
2. 阿里云短信接口接入
找到三方短信接口,通过阿里云的短信服务来实现。购买相应的套餐后,进入管理控制台查看 AccessKey 等信息。供应商通常会提供 HttpUtils 工具类,我们基于此封装发送验证码的接口。
package com.ljw.mall.third.utils;
import lombok.Data;
import org.apache.http.HttpResponse;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 短信组件
*/
@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Data
@Component
public class SmsComponent {
private String host;
private String path;
private String method = "POST";
private String appCode;
/**
* 发送短信验证码
* @param phone 发送的手机号
* @param code 发送的短信验证码
*/
public void sendSmsCode(String phone, String code) {
Map<String, String> headers = new HashMap<>();
// Authorization 格式为 APPCODE + 空格 + 密钥
headers.put("Authorization", "APPCODE " + appCode);
headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
Map<String, String> querys = new HashMap<>();
Map<String, String> bodys = new HashMap<>();
bodys.put("content", "code:" + code);
bodys.put("phone_number", phone);
bodys.put("template_id", "TPL_0000");
try {
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
在 mall-third-party 模块导入相关依赖,配置属性信息后测试发送,手机成功接收到消息才算搞定。
七。短信功能架构
整体流程是:第三方服务提供对外接口调用短信 API -> 认证服务通过 Feign 调用第三方接口 -> 客户端发起请求。
1. Mall-Third-Party 服务
要在第三方服务中提供对外的接口服务,负责调用短信服务商提供的短信 API 发送短信。
2. Mall-Auth-Server 认证服务
在认证服务中加入 loadbalancer 依赖,主启动类添加 @EnableFeignClients 注解。声明 ThirdPartFeginService 接口,调用我们的 mall-third 模块中短信服务的接口。定义 LoginController 暴露给客户端访问。
3. Redis 验证码存储
当验证码通过短信发送给客户手机后,我们需要把手机号和对应的验证码存起来。考虑到后续可能集群部署,存在本地内存不安全,所以选择 Redis。
在 application.yml 配置 Redis 参数,并在 LoginController 中通过 Redis 存储短信验证码数据。同时在 mall-commons 模块下配置与短信相关的常量。
4. 频率限制
为了防止恶意刷短信,需要限制 60 秒内不能重复发送。我们在保存到 Redis 的数据值里加上发送时间,读取时判断当前时间与上次发送时间的差值。如果间隔不足 60 秒,前端提示错误信息,后端也不执行发送逻辑。
至此,认证服务的核心功能——注册、登录、短信验证已基本闭环。


