day14【前台】用户登录注册
day14【前台】用户登录注册
1、会员登录框架
2、发送短信(测试)
2.1、去哪儿找API
- 上阿里云:直接搜索【短信接口】
- 随便找一个就行,往下翻有
API
接口的使用方法
2.2、测试API
2.2.1、main方法
- 将
API
文档中的示例代码直接拿过来
public class ShortMessageTest {public static void main(String[] args) {// 短信调用接接口的URL地址String host = "https://fesms.market.alicloudapi.com";// 具体发送短信功能的地址String path = "/sms/";// 请求方式String method = "GET";// 登录到阿里云,进入控制台,找到自己的短信接口AppCodeString appcode = "你自己的AppCode";// 最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105Map<String, String> headers = new HashMap<String, String>();headers.put("Authorization", "APPCODE " + appcode);// 封装其他参数Map<String, String> querys = new HashMap<String, String>();querys.put("code", "123456"); // 要发送的验证码querys.put("phone", "13262792031"); // 收短信人的手机号querys.put("sign", "1"); // 签名编号querys.put("skin", "1"); // 模板编号// JDK 1.8示例代码请在这里下载: http://code.fegine.com/Tools.ziptry {/*** * https:/ /github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java* 或者直接下载: http://code.fegine.com/HttpUtils.zip 下载** 相应的依赖请参照* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml* 相关jar包(非pom)直接下载: http://code.fegine.com/aliyun-jar.zip*/HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);// System.out.println(response.toString());如不输出json, 请打开这行代码,打印调试头部状态码。// 状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误// 获取response的bodySystem.out.println(EntityUtils.toString(response.getEntity()));} catch (Exception e) {e.printStackTrace();}}}
2.2.2、你自己的AppCode
- 阿里云控制台获取自己的
AppCode
2.2.3、创建HttpUtils类
- 创建
HttpUtils
类
public class HttpUtils {/*** get* * @param host* @param path* @param method* @param headers* @param querys* @return* @throws Exception*/public static HttpResponse doGet(String host, String path, String method, Map<String, String> headers,Map<String, String> querys) throws Exception {HttpClient httpClient = wrapClient(host);HttpGet request = new HttpGet(buildUrl(host, path, querys));for (Map.Entry<String, String> e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}return httpClient.execute(request);}/*** post form* * @param host* @param path* @param method* @param headers* @param querys* @param bodys* @return* @throws Exception*/public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers,Map<String, String> querys, Map<String, String> bodys) throws Exception {HttpClient httpClient = wrapClient(host);HttpPost request = new HttpPost(buildUrl(host, path, querys));for (Map.Entry<String, String> e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (bodys != null) {List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();for (String key : bodys.keySet()) {nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));}UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");request.setEntity(formEntity);}return httpClient.execute(request);}/*** Post String* * @param host* @param path* @param method* @param headers* @param querys* @param body* @return* @throws Exception*/public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers,Map<String, String> querys, String body) throws Exception {HttpClient httpClient = wrapClient(host);HttpPost request = new HttpPost(buildUrl(host, path, querys));for (Map.Entry<String, String> e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (StringUtils.isNotBlank(body)) {request.setEntity(new StringEntity(body, "utf-8"));}return httpClient.execute(request);}/*** Post stream* * @param host* @param path* @param method* @param headers* @param querys* @param body* @return* @throws Exception*/public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers,Map<String, String> querys, byte[] body) throws Exception {HttpClient httpClient = wrapClient(host);HttpPost request = new HttpPost(buildUrl(host, path, querys));for (Map.Entry<String, String> e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (body != null) {request.setEntity(new ByteArrayEntity(body));}return httpClient.execute(request);}/*** Put String* * @param host* @param path* @param method* @param headers* @param querys* @param body* @return* @throws Exception*/public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers,Map<String, String> querys, String body) throws Exception {HttpClient httpClient = wrapClient(host);HttpPut request = new HttpPut(buildUrl(host, path, querys));for (Map.Entry<String, String> e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (StringUtils.isNotBlank(body)) {request.setEntity(new StringEntity(body, "utf-8"));}return httpClient.execute(request);}/*** Put stream* * @param host* @param path* @param method* @param headers* @param querys* @param body* @return* @throws Exception*/public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers,Map<String, String> querys, byte[] body) throws Exception {HttpClient httpClient = wrapClient(host);HttpPut request = new HttpPut(buildUrl(host, path, querys));for (Map.Entry<String, String> e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (body != null) {request.setEntity(new ByteArrayEntity(body));}return httpClient.execute(request);}/*** Delete* * @param host* @param path* @param method* @param headers* @param querys* @return* @throws Exception*/public static HttpResponse doDelete(String host, String path, String method, Map<String, String> headers,Map<String, String> querys) throws Exception {HttpClient httpClient = wrapClient(host);HttpDelete request = new HttpDelete(buildUrl(host, path, querys));for (Map.Entry<String, String> e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}return httpClient.execute(request);}private static String buildUrl(String host, String path, Map<String, String> querys)throws UnsupportedEncodingException {StringBuilder sbUrl = new StringBuilder();sbUrl.append(host);if (!StringUtils.isBlank(path)) {sbUrl.append(path);}if (null != querys) {StringBuilder sbQuery = new StringBuilder();for (Map.Entry<String, String> query : querys.entrySet()) {if (0 < sbQuery.length()) {sbQuery.append("&");}if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {sbQuery.append(query.getValue());}if (!StringUtils.isBlank(query.getKey())) {sbQuery.append(query.getKey());if (!StringUtils.isBlank(query.getValue())) {sbQuery.append("=");sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));}}}if (0 < sbQuery.length()) {sbUrl.append("?").append(sbQuery);}}return sbUrl.toString();}private static HttpClient wrapClient(String host) {HttpClient httpClient = new DefaultHttpClient();if (host.startsWith("https://")) {sslClient(httpClient);}return httpClient;}private static void sslClient(HttpClient httpClient) {try {SSLContext ctx = SSLContext.getInstance("TLS");X509TrustManager tm = new X509TrustManager() {public X509Certificate[] getAcceptedIssuers() {return null;}public void checkClientTrusted(X509Certificate[] xcs, String str) {}public void checkServerTrusted(X509Certificate[] xcs, String str) {}};ctx.init(null, new TrustManager[] { tm }, null);SSLSocketFactory ssf = new SSLSocketFactory(ctx);ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);ClientConnectionManager ccm = httpClient.getConnectionManager();SchemeRegistry registry = ccm.getSchemeRegistry();registry.register(new Scheme("https", 443, ssf));} catch (KeyManagementException ex) {throw new RuntimeException(ex);} catch (NoSuchAlgorithmException ex) {throw new RuntimeException(ex);}}
}
2.2.4、引入其他依赖
- 引入依赖后,不再报错
<dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.15</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.2.1</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId><version>4.2.1</version></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-util</artifactId><version>9.3.7.v20160115</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.5</version><scope>test</scope></dependency>
</dependencies>
2.3、测试
- 使用阿里云自带的调试工具和
Java
程序的运行结果如下
3、发送短信(项目)
3.1、引入依赖
- 由于工具类需要放在
util
工程中,所以我们需要在util
工程下添加短信API
所需的依赖;注意依赖的版本号需要与auth
工程中的版本号相对应(因为父工程中已经有了短信API
所需的依赖,但是因为util
工程并不依赖于父工程,所以只好单独写版本号咯)
<!-- 以下是发送短信时调用第三方API所需依赖 -->
<!-- JSON工具 -->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.15</version>
</dependency><!-- httpclient依赖了httpcore,所以不需要再额外添加httpcore的依赖 -->
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.9</version>
</dependency><dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-util</artifactId><version>9.4.19.v20190610</version>
</dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version>
</dependency>
3.2、创建HttpUtils类
- 在
util
工程下创建HttpUtils
类,代码和上一节一样
3.3、测试短信API
3.3.1、引入依赖
- 引入
SpringBoot
测试的依赖
<!-- SpringBoot Test -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
3.3.2、创建测试类
- 测试一波
API
接口
@RunWith(SpringRunner.class)
@SpringBootTest
public class CrowdTest {private Logger logger = LoggerFactory.getLogger(CrowdTest.class);@Testpublic void testSendMessage() {// 短信调用接接口的URL地址String host = "https://fesms.market.alicloudapi.com";// 具体发送短信功能的地址String path = "/sms/";// 请求方式String method = "GET";// 登录到阿里云,进入控制台,找到自己的短信接口AppCodeString appcode = "9844f3f479cf41ea92ccbea03c70db58";Map<String, String> headers = new HashMap<String, String>();// 最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105headers.put("Authorization", "APPCODE " + appcode);// 封装其他参数Map<String, String> querys = new HashMap<String, String>();// 要发送的验证码,也就是模板中会变化的部分querys.put("param", "123456");// 收短信的手机号querys.put("phone", "13262792031");// 签名编号querys.put("sign", "1");// 模板编号querys.put("skin", "1");// JDK 1.8示例代码请在这里下载: http://code.fegine.com/Tools.ziptry {/*** 重要提示如下: HttpUtils请从* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java* 或者直接下载: http://code.fegine.com/HttpUtils.zip 下载** 相应的依赖请参照* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml* 相关jar包(非pom)直接下载: http://code.fegine.com/aliyun-jar.zip*/HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);StatusLine statusLine = response.getStatusLine();// 状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误int statusCode = statusLine.getStatusCode();logger.info("code=" + statusCode);String reasonPhrase = statusLine.getReasonPhrase();logger.info("reason=" + reasonPhrase);// System.out.println(response.toString());如不输出json, 请打开这行代码,打印调试头部状态码。// 获取response的bodylogger.info(EntityUtils.toString(response.getEntity()));} catch (Exception e) {e.printStackTrace();}}}
3.4、创建工具类
- 在
util
工程下,向工具类中添加如下方法
/*** 给远程第三方短信接口发送请求把验证码发送到用户手机上* @param host 短信接口调用的URL地址* @param path 具体发送短信功能的地址* @param method 请求方式* @param phoneNum 接收短信的手机号* @param appCode 用来调用第三方短信API的AppCode* @param sign 签名编号* @param skin 模板编号* @return 返回调用结果是否成功* 成功:返回验证码* 失败:返回失败消息* 状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误*/
public static ResultEntity<String> sendCodeByShortMessage(String host,String path,String method,String phoneNum, String appCode, String sign, String skin) {Map<String, String> headers = new HashMap<String, String>();// 最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105headers.put("Authorization", "APPCODE " + appCode);// 封装其他参数Map<String, String> querys = new HashMap<String, String>();// 生成验证码StringBuilder builder = new StringBuilder();for(int i = 0; i < 4; i++) {int random = (int) (Math.random() * 10);builder.append(random);}String code = builder.toString();// 要发送的验证码,也就是模板中会变化的部分querys.put("code", code);// 收短信的手机号querys.put("phone", phoneNum);// 签名编号querys.put("sign", sign);// 模板编号querys.put("skin", skin);// JDK 1.8示例代码请在这里下载: http://code.fegine.com/Tools.ziptry {HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);StatusLine statusLine = response.getStatusLine();// 状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误int statusCode = statusLine.getStatusCode();String reasonPhrase = statusLine.getReasonPhrase();if(statusCode == 200) {// 操作成功,把生成的验证码返回return ResultEntity.successWithData(code);}return ResultEntity.failed(reasonPhrase);} catch (Exception e) {e.printStackTrace();return ResultEntity.failed(e.getMessage());}}
3.5、测试工具类
- 创建测试方法
@Test
public void testSendMessageUtil() {// 短信调用接接口的URL地址String host = "https://fesms.market.alicloudapi.com";// 具体发送短信功能的地址String path = "/sms/";// 请求方式String method = "GET";// 收件人电话号码String phoneNum = "13262792031";// 登录到阿里云,进入控制台,找到自己的短信接口AppCodeString appCode = "9844f3f479cf41ea92ccbea03c70db58";// 签名编号String sign = "1";// 模板编号String skin = "1";// 发送短信,获取响应信息ResultEntity<String> resultEntity = CrowdUtil.sendCodeByShortMessage(host, path, method, phoneNum, appCode, sign, skin);logger.info(resultEntity.toString());
}
- 测试结果:生成的验证码为
data=3906
2020-06-27 15:13:41.178 INFO 5548 --- [ main] com.atguigu.crowd.test.CrowdTest : ResultEntity [result=SUCCESS, message=null, data=3906]
4、常量定义
public class CrowdConstant {public static final String MESSAGE_LOGIN_FAILED = "抱歉!账号密码错误!请重新输入!";public static final String MESSAGE_LOGIN_ACCT_ALREADY_IN_USE = "抱歉!这个账号已经被使用了!";public static final String MESSAGE_ACCESS_FORBIDEN = "请登录以后再访问!";public static final String MESSAGE_STRING_INVALIDATE = "字符串不合法!请不要传入空字符串!";public static final String MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE = "系统错误:登录账号不唯一!";public static final String MESSAGE_ACCESS_DENIED = "抱歉!您不能访问这个资源!";public static final String MESSAGE_CODE_NOT_EXISTS = "验证码已过期!请检查手机号是否正确或重新发送!";public static final String MESSAGE_CODE_INVALID = "验证码不正确!";public static final String ATTR_NAME_EXCEPTION = "exception";public static final String ATTR_NAME_LOGIN_ADMIN = "loginAdmin";public static final String ATTR_NAME_LOGIN_MEMBER = "loginMember";public static final String ATTR_NAME_PAGE_INFO = "pageInfo";public static final String ATTR_NAME_MESSAGE = "message";public static final String REDIS_CODE_PREFIX = "REDIS_CODE_PREFIX_";}
5、会员注册
5.1、添加view-controller
- 点击【注册】按钮直接转发至注册页面,添加
view-controller
即可,无需编写Handler
方法
@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {// 添加view-controller// 浏览器访问的地址 // 目标视图的名称,将来拼接“prefix: classpath:/templates/”、“suffix: .html”前后缀registry.addViewController("/auth/member/to/reg/page").setViewName("member-reg");}}
5.2、修改注册超链接
- 修改首页中【注册】按钮的超链接
<li><a href="reg.html" th:href="@{/auth/member/to/reg/page}">注册</a></li>
5.3、添加layer弹框组件
- 引入
layer
弹框组件的资源
5.4、新建注册页面
- 导入
Thymeleaf
名称空间 - 添加
base
标签 - 导入
layer
弹框组件
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="keys" content="">
<meta name="author" content="">
<base th:href="@{/}"/>
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="css/font-awesome.min.css">
<link rel="stylesheet" href="css/login.css">
<script type="text/javascript" src="jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="layer/layer.js"></script>
5.5、发送短信
5.5.1、绑定单击响应函数
- 为【发送验证码】按钮绑定单击响应函数
$(function(){$("#sendBtn").click(function(){// 1.获取接收短信的手机号var phoneNum = $.trim($("[name=phoneNum]").val());// 2.发送请求$.ajax({"url":"auth/member/send/short/message.json","type":"post","data":{"phoneNum":phoneNum},"dataType":"json","success":function(response){var result = response.result;if(result == "SUCCESS") {layer.msg("发送成功!");}if(result == "FAILED") {layer.msg("发送失败!请再试一次!");}},"error":function(response){layer.msg(response.status + " " + response.statusText);}});});
});
5.5.2、创建短信的配置类
- 创建配置类
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
@ConfigurationProperties(prefix = "short.message")
public class ShortMessageProperties {private String host;private String path;private String method;private String appCode;private String sign;private String skin;}
- 遇到这个问题,
fix
即可,这样编写yml
时才会有代码提示
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional>
</dependency>
5.5.3、编写短信相关配置
- 在
yml
配置文件中编写配置即可
short:message:app-code: 9844f3f479cf41ea92ccbea03c70db58host: https://fesms.market.alicloudapi.commethod: GETpath: /sms/sign: 1skin: 1
5.5.4、启用feign-client
auth
工程需要调用各个provider
工程,provider
工程作为Feign
服务器端,则auth
工程Feign
客户端,在auth
工程主启动类上添加@EnableFeignClients
注解:启用Feign
客户端功能
//启用Feign客户端功能
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class CrowdMainClass {public static void main(String[] args) {SpringApplication.run(CrowdMainClass.class, args);}}
5.5.5、auth-consumer Handler
- 接受
Ajax
请求 - 调用短信
API
接口 - 返回消息给浏览器
// CrowdConstant类中的常量定义
public static final String REDIS_CODE_PREFIX = "REDIS_CODE_PREFIX_";@Controller
public class MemberHandler {@Autowiredprivate ShortMessageProperties shortMessageProperties;@Autowiredprivate RedisRemoteService redisRemoteService;@ResponseBody@RequestMapping("/auth/member/send/short/message.json")public ResultEntity<String> sendMessage(@RequestParam("phoneNum") String phoneNum) {// 1.发送验证码到phoneNum手机ResultEntity<String> sendMessageResultEntity = CrowdUtil.sendCodeByShortMessage(shortMessageProperties.getHost(), shortMessageProperties.getPath(), shortMessageProperties.getMethod(), phoneNum, shortMessageProperties.getAppCode(), shortMessageProperties.getSign(), shortMessageProperties.getSkin());// 2.判断短信发送结果if(ResultEntity.SUCCESS.equals(sendMessageResultEntity.getResult())) {// 3.如果发送成功,则将验证码存入Redis// ①从上一步操作的结果中获取随机生成的验证码String code = sendMessageResultEntity.getData();// ②拼接一个用于在Redis中存储数据的keyString key = CrowdConstant.REDIS_CODE_PREFIX + phoneNum;// ③调用远程接口存入RedisResultEntity<String> saveCodeResultEntity = redisRemoteService.setRedisKeyValueRemoteWithTimeout(key, code, 15, TimeUnit.MINUTES);// ④判断结果if(ResultEntity.SUCCESS.equals(saveCodeResultEntity.getResult())) {return ResultEntity.successWithoutData();}else {return saveCodeResultEntity;}} else {return sendMessageResultEntity;}}}
5.5.6、发送短信测试
- 反正我收到了验证码
5.6、mysql-provider
5.6.1、loginacct唯一约束
t_member
表中,loginacct
列添加唯一约束
ALTER TABLE `project_crowd`.`t_member` ADD UNIQUE INDEX (`loginacct`);
5.6.2、Handler代码
- 保存成功:返回成功消息
- 保存失败:返回失败消息
- 注意:
@RequestBody
是必须的,因为auth-consumer
调用mysql-provider
时,请求参数是通过RequestBody
发送过来滴
Logger logger = LoggerFactory.getLogger(MemberProviderHandler.class);@RequestMapping("/save/member/remote")
public ResultEntity<String> saveMember(@RequestBody MemberPO memberPO) {logger.info(memberPO.toString());try {memberService.saveMember(memberPO);return ResultEntity.successWithoutData();}catch(Exception e) {if(e instanceof DuplicateKeyException) {return ResultEntity.failed(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE);}return ResultEntity.failed(e.getMessage());}}
5.6.3、Service代码
- 事务传播行为:开启新事务
- 遇到
Exception
类型的异常便回滚 - 该方法为写操作
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class, readOnly = false)
@Override
public void saveMember(MemberPO memberPO) {memberPOMapper.insertSelective(memberPO);}
5.6.4、声明api远程调用接口
- 在
api
工程中,声明远程调用接口
@RequestMapping("/save/member/remote")
public ResultEntity<String> saveMember(@RequestBody MemberPO memberPO);
5.6.5、测试
使用
Postman
进行测试- 请求方式:
POST
- 请求地址:
http://localhost:2000/save/member/remote
- 请求头:
Content-Type:application/json;charset=UTF-8
- 请求体
{"loginacct":"Heygo","userpswd":123123 }
- 请求方式:
- 保存成功
5.7、auth-consumer
5.7.1、调整表单
- 调整注页面的表单
- 表单提交地址
- 表单项的
name
属性值 - 错误信息回显
<div class="container"><form action="/auth/do/member/register" method="post" class="form-signin" role="form"><h2 class="form-signin-heading"><i class="glyphicon glyphicon-log-in"></i> 用户注册</h2><p th:text="${message}">这里显示从请求域取出的提示消息</p><div class="form-group has-success has-feedback"><input type="text" name="loginacct" class="form-control" id="inputSuccess4"placeholder="请输入登录账号" autofocus> <spanclass="glyphicon glyphicon-user form-control-feedback"></span></div><div class="form-group has-success has-feedback"><input type="text" name="userpswd" class="form-control" id="inputSuccess4"placeholder="请输入登录密码" style="margin-top: 10px;"> <spanclass="glyphicon glyphicon-lock form-control-feedback"></span></div><div class="form-group has-success has-feedback"><input type="text" name="username" class="form-control" id="inputSuccess4"placeholder="请输入用户昵称" style="margin-top: 10px;"> <spanclass="glyphicon glyphicon-lock form-control-feedback"></span></div><div class="form-group has-success has-feedback"><input type="text" name="email" class="form-control" id="inputSuccess4"placeholder="请输入邮箱地址" style="margin-top: 10px;"> <spanclass="glyphicon glyphicon glyphicon-envelope form-control-feedback"></span></div><div class="form-group has-success has-feedback"><input type="text" name="phoneNum" class="form-control" id="inputSuccess4"placeholder="请输入手机号" style="margin-top: 10px;"> <spanclass="glyphicon glyphicon glyphicon-earphone form-control-feedback"></span></div><div class="form-group has-success has-feedback"><input type="text" name="code" class="form-control" id="inputSuccess4"placeholder="请输入验证码" style="margin-top: 10px;"> <spanclass="glyphicon glyphicon glyphicon-comment form-control-feedback"></span></div><button type="button" id="sendBtn" class="btn btn-lg btn-success btn-block">获取验证码</button><button type="submit" class="btn btn-lg btn-success btn-block">注册</button></form>
</div>
5.7.2、创建MemberVO
MemberVO
对应着浏览器表单数据
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberVO {private String loginacct;private String userpswd;private String username;private String email;private String phoneNum;private String code;}
5.7.3、Handler代码
- 通过用户手机号(
key
)去Redis
里面查询对应的value
- 如果验证码一致,则将用户密码进行加密,执行保存操作
@Autowired
private ShortMessageProperties shortMessageProperties;@Autowired
private RedisRemoteService redisRemoteService;@Autowired
private MySQLRemoteService mySQLRemoteService;@RequestMapping("/auth/do/member/register")
public String register(MemberVO memberVO, ModelMap modelMap) {// 1.获取用户输入的手机号String phoneNum = memberVO.getPhoneNum();// 2.拼Redis中存储验证码的KeyString key = CrowdConstant.REDIS_CODE_PREFIX + phoneNum;// 3.从Redis读取Key对应的valueResultEntity<String> resultEntity = redisRemoteService.getRedisStringValueByKeyRemote(key);// 4.检查查询操作是否有效String result = resultEntity.getResult();if(ResultEntity.FAILED.equals(result)) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, resultEntity.getMessage());return "member-reg";}String redisCode = resultEntity.getData();if(redisCode == null) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_CODE_NOT_EXISTS);return "member-reg";}// 5.如果从Redis能够查询到value则比较表单验证码和Redis验证码String formCode = memberVO.getCode();if(!Objects.equals(formCode, redisCode)) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_CODE_INVALID);return "member-reg";}// 6.如果验证码一致,则从Redis删除redisRemoteService.removeRedisKeyRemote(key);// 7.执行密码加密BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String userpswdBeforeEncode = memberVO.getUserpswd();String userpswdAfterEncode = passwordEncoder.encode(userpswdBeforeEncode);memberVO.setUserpswd(userpswdAfterEncode);// 8.执行保存// ①创建空的MemberPO对象MemberPO memberPO = new MemberPO();// ②复制属性BeanUtils.copyProperties(memberVO, memberPO);// ③调用远程方法ResultEntity<String> saveMemberResultEntity = mySQLRemoteService.saveMember(memberPO);if(ResultEntity.FAILED.equals(saveMemberResultEntity.getResult())) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, saveMemberResultEntity.getMessage());return "member-reg";}// 使用重定向避免刷新浏览器导致重新执行注册流程return "redirect:/auth/member/to/login/page";
}
5.7.4、ribbon超时时间
- 由于在第一次请求中需要建立缓存、建立连接,操作较多,所以比较耗时。
- 如果按照默认的
ribbon
超时时间来工作,第一次请求会超过这个时间导致超时报错。我就说有次我第一次报错504
(这个好像是Redis
超时错误),调了半天程序也没问题。。。 - 为了避免这个问题,把
ribbon
的超时时间延长,在yml
配置文件中添加如下配置
ribbon:ReadTimeout: 10000ConnectTimeout: 10000
- 奇怪,说属性找不到,这个配置会生效吗?
5.7.5、测试
- 成功发送短信
- 账号已经被使用
- 验证码错误
- 成功注册
问题:表单并没有回显。。。
解决办法:出错将转发至注册页面,
解决办法一:我们只需将信息放到
Request
域中,然后回显即可解决办法二:通过
Ajax
请求的返回值将要回显的数据返回给浏览器端
6、会员登录
6.1、创建会员登录页面
- 老规矩:
- 引入
Thymeleaf
名称空间 - 修改字符编码
- 添加
base
标签 - 调整登录表单
- 添加登录错误信息的回显
- 引入
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="keys" content="">
<meta name="author" content="">
<base th:href="@{/}"/>
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="css/font-awesome.min.css">
<link rel="stylesheet" href="css/login.css">
<script src="jquery/jquery-2.1.1.min.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
<style>
</style>
</head>
<body><nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"><div class="container"><div class="navbar-header"><div><a class="navbar-brand" href="index.html" style="font-size: 32px;">尚筹网-创意产品众筹平台</a></div></div></div></nav><div class="container"><form action="auth/member/do/login" method="post" class="form-signin" role="form"><h2 class="form-signin-heading"><i class="glyphicon glyphicon-log-in"></i> 用户登录</h2><p th:text="${message}">这里显示登录失败的提示消息</p><div class="form-group has-success has-feedback"><input type="text" name="loginacct" class="form-control" id="inputSuccess4"placeholder="请输入登录账号" autofocus> <spanclass="glyphicon glyphicon-user form-control-feedback"></span></div><div class="form-group has-success has-feedback"><input type="text" name="userpswd" class="form-control" id="inputSuccess4"placeholder="请输入登录密码" style="margin-top: 10px;"> <spanclass="glyphicon glyphicon-lock form-control-feedback"></span></div><div class="checkbox" style="text-align: right;"><a href="reg.html" th:href="@{/auth/member/to/reg/page}">我要注册</a></div><button class="btn btn-lg btn-success btn-block" type="submit">登录</button></form></div>
</body>
</html>
6.2、添加view-controller
- 工程路径下的
/auth/member/to/login/page
请求,对应着templates/member-login.html
页面
@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {// 添加view-controller// 浏览器访问的地址 // 目标视图的名称,将来拼接“prefix: classpath:/templates/”、“suffix: .html”前后缀registry.addViewController("/auth/member/to/reg/page").setViewName("member-reg");registry.addViewController("/auth/member/to/login/page").setViewName("member-login");}}
6.3、跳转至登录页面
- 修改首页中【登陆】按钮的超链接
<li><a href="login.html" th:href="@{/auth/member/to/login/page}">登录</a></li>
6.4、创建MemberLoginVO类
- 用户登陆成功之后,将
MemberLoginVO
类的对象,存入session
域中
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberLoginVO {private Integer id;private String username;private String email;}
6.5、Handler代码
- 编写用户登录的逻辑
- 调用远程
mysql-provider
,根据loginacct
查询用户 - 如果用户不存在,或者用户密码错误则返回相应的提示消息
- 如果用户存在且密码正确,则将用户信息封装为
MemberLoginVO
对象,存入session
域中
- 调用远程
@RequestMapping("/auth/member/do/login")
public String login(@RequestParam("loginacct") String loginacct, @RequestParam("userpswd") String userpswd,ModelMap modelMap,HttpSession session) {// 1.调用远程接口根据登录账号查询MemberPO对象ResultEntity<MemberPO> resultEntity = mySQLRemoteService.getMemberPOByLoginAcctRemote(loginacct);if(ResultEntity.FAILED.equals(resultEntity.getResult())) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, resultEntity.getMessage());return "member-login";}MemberPO memberPO = resultEntity.getData();if(memberPO == null) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_LOGIN_FAILED);return "member-login";}// 2.比较密码String userpswdDataBase = memberPO.getUserpswd();BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();boolean matcheResult = passwordEncoder.matches(userpswd, userpswdDataBase);if(!matcheResult) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_LOGIN_FAILED);return "member-login";}// 3.创建MemberLoginVO对象存入Session域MemberLoginVO memberLoginVO = new MemberLoginVO(memberPO.getId(), memberPO.getUsername(), memberPO.getEmail());session.setAttribute(CrowdConstant.ATTR_NAME_LOGIN_MEMBER, memberLoginVO);return "redirect:/auth/member/to/center/page";
}
6.6、创建会员中心页面
- 老规矩:
- 引入
Thymeleaf
名称空间 - 修改字符编码
- 添加
base
标签 - 从
session
域中取出用户昵称,在页面上显示
- 引入
<!-- 上方导航栏显示用户昵称 -->
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="glyphicon glyphicon-user"></i> [[${session.loginMember.username}]]<span class="caret"></span></a><!-- 左侧导航栏显示用户昵称 -->
<div class="caption" style="text-align:center;"><h3>[[${session.loginMember.username}]]</h3><span class="label label-danger" style="cursor:pointer;" onclick="window.location.href='accttype.html'">未实名认证</span>
</div>
6.7、添加view-controller
- 工程路径下的
/auth/member/to/center/page
请求,对应着templates/member-center.html
页面
@Configuration
public class CrowdWebMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {// 添加view-controller// 浏览器访问的地址 // 目标视图的名称,将来拼接“prefix: classpath:/templates/”、“suffix: .html”前后缀registry.addViewController("/auth/member/to/reg/page").setViewName("member-reg");registry.addViewController("/auth/member/to/login/page").setViewName("member-login");registry.addViewController("/auth/member/to/center/page").setViewName("member-center");}}
6.8、测试
- 用户名或用户密码不正确
- 成功登陆
- 我佛了,通过
zuul
访问为啥就是500
错误?
- 问题:仍然是没有回显表单
- 解决办法一:存入
request
域 - 解决办法二:通过
Ajax
带回浏览器
- 解决办法一:存入
7、会员退出登录
7.1、修改超链接
- 修改【退出】按钮的超链接
<li><a href="index.html" th:href="@{/auth/member/logout}"><i class="glyphicon glyphicon-off"></i> 退出系统</a></li>
7.2、Handler代码
- 清除
session
域
@RequestMapping("/auth/member/logout")
public String logout(HttpSession session) { session.invalidate(); return "redirect:/";
}
8、@RequestBody
8.1、去掉@RequestBody
- 去掉
api
远程调用接口和mysql-provider
工程中的@RequestBody
注解,会发生什么?
- 日志打印
MemberPO
对象的属性值均为null
,即MemberPO
对象的属性值无法注入
8.2、分析原因
MemberPO
对象的属性值无法注入,为什么?- 如果不写
@RequestBody
,SpringMVC
默认发送过来的请求参数为Query Strings
的形式,即key1=value1&key2=value2
auth-consumer
调用远程mysql-provider
工程,传参数时,会将参数序列化为json
字符串进行传输- 如果
mysql-provider
中的Handler
方法不写@RequestBody
注解,那么SpringMVC
按照Query Strings
的形式去解析json
字符串,这样当然无法解析,所以无法为MemberPO
对象的属性注入值 - 所以必须加上
@RequestBody
注解,让mysql-provider
中的Handler
方法以json
字符串的形式去解析请求参数,这样才能为MemberPO
对象成功注入值
- 如果不写
9、session共享
9.1、提出问题
- 各个
consumer
之间的session
域并不互通,auth-consumer
微服务的session
和其他consumer
微服务的session
并不是同一个session
9.2、SpringSession的使用
9.2.1、创建工程
- 创建两个
Maven Project
:pro14-spring-session-a
和pro14-spring-session-b
9.2.2、引入依赖
- 两个工程的依赖一样
<!-- SpringBoot父工程 -->
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.6.RELEASE</version>
</parent><dependencies><!-- 引入SpringMVC --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 引入springboot&redis整合场景 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 引入springboot&springsession整合场景 --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>
</dependencies>
9.2.3、创建配置文件
pro14-spring-session-a
工程
# Redis Host
spring.redis.host=192.168.152.129# SpringSession use Redis
spring.session.store-type=redis
pro14-spring-session-b
工程
# Redis Host
spring.redis.host=192.168.152.129# SpringSession use Redis
spring.session.store-type=redis# Server Port
server.port=8181
9.2.4、创建主启动类
- 两个工程主启动类一样
@SpringBootApplication
public class MainClass {public static void main(String[] args) {SpringApplication.run(MainClass.class, args);}}
9.2.5、创建Handler
pro14-spring-session-a
工程
@RestController
public class HelloHandler {@RequestMapping("/test/spring/session/save")public String testSession(HttpSession session) {session.setAttribute("king", "hello-king");return "数据存入Session域!";}}
pro14-spring-session-b
工程
@RestController
public class HelloHandler {@RequestMapping("/test/spring/session/retrieve")public String testSession(HttpSession session) {String value = (String) session.getAttribute("king");return value;}}
9.2.6、测试
- 存入
session
- 取出
session
Session
名称不是原来的JSESSIONID
,变成了SESSION
SpringSession
是真他妈的牛逼,原有代码都不需要改动,这种非侵入式的框架,牛批!
10、SpringSession原理
10.1、SpringSession配置类
- 熟悉的
XxxProperties
配置类
@ConfigurationProperties(prefix = "spring.session")
public class SessionProperties {/*** Session store type.*/private StoreType storeType;/*** Session timeout. If a duration suffix is not specified, seconds will be used.*/@DurationUnit(ChronoUnit.SECONDS)private Duration timeout;private Servlet servlet = new Servlet();private final ServerProperties serverProperties;
熟悉的
XxxAutoConfiguration
自动配置类@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
@Configuration
@ConditionalOnClass(Session.class)
@ConditionalOnWebApplication
@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class,JdbcTemplateAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class,RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class })
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
public class SessionAutoConfiguration {
10.2、SpringSession接管Session原理
10.3、SessionRepositoryFilter
SessionRepositoryFilter
类中doFilterInternal
方法大致流程接收包装前的
HttpServletRequest
,HttpServletResponse
将
HttpServletRequest
,HttpServletResponse
包装为我们自定义的SessionRepositoryRequestWrapper
,SessionRepositoryResponseWrapper
将我们包装后的
Request
和Response
传入filterChain
中,在之后的流程中,程序便会使用我们自定义的SessionRepositoryRequestWrapper
,SessionRepositoryResponseWrapper
,执行我们重写之后的方法filterChain.doFilter(wrappedRequest, wrappedResponse);
@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response, this.servletContext);SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);try {filterChain.doFilter(wrappedRequest, wrappedResponse);}finally {wrappedRequest.commitSession();}
}
10.4、SessionRepositoryRequestWrapper
- 在
SessionRepositoryRequestWrapper
类中重写了有关Session
部分的方法
private S getRequestedSession() {if (!this.requestedSessionCached) {List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this);for (String sessionId : sessionIds) {if (this.requestedSessionId == null) {this.requestedSessionId = sessionId;}S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId);if (session != null) {this.requestedSession = session;this.requestedSessionId = sessionId;break;}}this.requestedSessionCached = true;}return this.requestedSession;
}
S
是啥?S
代表Session
的子类
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
10.5、HttpServletRequestWrapper
SessionRepositoryRequestWrapper
继承于HttpServletRequestWrapper
类,- 为什么
SessionRepositoryRequestWrapper
类只需要重写几个方法就能完成对Session
掌控?那得归功于HttpServletRequestWrapper
类 HttpServletRequestWrapper
类中有默认的方法实现,如无重写需求,可直接调用,所以只需要重写我们需要重写的方法即可
public class HttpServletRequestWrapper extends ServletRequestWrapper implementsHttpServletRequest {/*** Constructs a request object wrapping the given request.** @param request The request to wrap** @throws java.lang.IllegalArgumentException* if the request is null*/public HttpServletRequestWrapper(HttpServletRequest request) {super(request);}private HttpServletRequest _getHttpServletRequest() {return (HttpServletRequest) super.getRequest();}/*** The default behavior of this method is to return getAuthType() on the* wrapped request object.*/@Overridepublic String getAuthType() {return this._getHttpServletRequest().getAuthType();}/*** The default behavior of this method is to return getCookies() on the* wrapped request object.*/@Overridepublic Cookie[] getCookies() {return this._getHttpServletRequest().getCookies();}/*** The default behavior of this method is to return getDateHeader(String* name) on the wrapped request object.*/@Overridepublic long getDateHeader(String name) {return this._getHttpServletRequest().getDateHeader(name);}// ...
10.6、RedisOperationsSessionRepository
- 继承树
- 顶级接口
SessionRepository
public interface SessionRepository<S extends Session> {S createSession();void save(S session);S findById(String id);void deleteById(String id);
}
FindByIndexNameSessionRepository
接口继承于SessionRepository
接口
public interface FindByIndexNameSessionRepository<S extends Session>extends SessionRepository<S> {String PRINCIPAL_NAME_INDEX_NAME = FindByIndexNameSessionRepository.class.getName().concat(".PRINCIPAL_NAME_INDEX_NAME");Map<String, S> findByIndexNameAndIndexValue(String indexName, String indexValue);default Map<String, S> findByPrincipalName(String principalName) {return findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);}}
RedisOperationsSessionRepository
类实现了FindByIndexNameSessionRepository
接口
public class RedisOperationsSessionRepository implementsFindByIndexNameSessionRepository<RedisOperationsSessionRepository.RedisSession>,MessageListener {
RedisOperationsSessionRepository
方法:使用Redis
将session
保存维护起来
public RedisOperationsSessionRepository(RedisOperations<Object, Object> sessionRedisOperations) {Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null");this.sessionRedisOperations = sessionRedisOperations;this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations,this::getExpirationsKey, this::getSessionKey);configureSessionChannels();
}@Override
public void save(RedisSession session) {session.save();if (session.isNew()) {String sessionCreatedKey = getSessionCreatedChannel(session.getId());this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);session.setNew(false);}
}public void cleanupExpiredSessions() {this.expirationPolicy.cleanExpiredSessions();
}
11、星图
12、登录状态检查
12.1、思路
12.2、Session共享
12.2.1、引入依赖
zuul
工程和auth-consumer
工程的pom
文件中引入SpringSession
整合Redi
的依赖
<!-- 引入springboot&redis整合场景 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 引入springboot&springsession整合场景 -->
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency>
12.2.2、修改配置文件
zuul
工程:配置Redis
主机地址,配置SpringSession
使用Redis
作为Session
缓存
server:port: 80
spring:application:name: atguigu-crowd-zuulredis:host: 192.168.152.129session:store-type: redis
auth-consumer
工程:配置Redis
主机地址,配置SpringSession
使用Redis
作为Session
缓存
server:port: 4000
spring:application:name: atguigu-crowd-auththymeleaf:prefix: classpath:/templates/suffix: .htmlredis:host: 192.168.152.129session:store-type: redis
12.3、需要放行的资源
- 创建常量类,存储放行资源的地址(无需登录就能访问):
- 访问特定请求地址无需登录
- 访问静态资源无需登录
- 静态方法
judgeCurrentServletPathWetherStaticResource
:检查当前请求是否为需要放行的资源
public class AccessPassResources {public static final Set<String> PASS_RES_SET = new HashSet<>();static {PASS_RES_SET.add("/");PASS_RES_SET.add("/auth/member/to/reg/page");PASS_RES_SET.add("/auth/member/to/login/page");PASS_RES_SET.add("/auth/member/logout");PASS_RES_SET.add("/auth/member/do/login");PASS_RES_SET.add("/auth/do/member/register");PASS_RES_SET.add("/auth/member/send/short/message.json");}public static final Set<String> STATIC_RES_SET = new HashSet<>();static {STATIC_RES_SET.add("bootstrap");STATIC_RES_SET.add("css");STATIC_RES_SET.add("fonts");STATIC_RES_SET.add("img");STATIC_RES_SET.add("jquery");STATIC_RES_SET.add("layer");STATIC_RES_SET.add("script");STATIC_RES_SET.add("ztree");}/*** 用于判断某个ServletPath值是否对应一个静态资源* @param servletPath* @return* true:是静态资源* false:不是静态资源*/public static boolean judgeCurrentServletPathWetherStaticResource(String servletPath) {// 1.排除字符串无效的情况if(servletPath == null || servletPath.length() == 0) {throw new RuntimeException(CrowdConstant.MESSAGE_STRING_INVALIDATE);}// 2.根据“/”拆分ServletPath字符串String[] split = servletPath.split("/");// 3.考虑到第一个斜杠左边经过拆分后得到一个空字符串是数组的第一个元素,所以需要使用下标1取第二个元素String firstLevelPath = split[1];// 4.判断是否在集合中return STATIC_RES_SET.contains(firstLevelPath);}}
12.4、登录检查
12.4.1、引入依赖
- 在
Zuul
工程中引入util
工具类工程的依赖
<!-- 使用工具类 -->
<dependency><groupId>com.atguigu.crowd</groupId><artifactId>atcrowdfunding05-common-util</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>
12.4.2、创建Filter
shouldFilter
方法:判断当前请求路径是否为无需登录就可以访问的资源- 是:直接
return false
,放行资源 - 否:
return true
,执行run
方法,检查用户是否登录
- 是:直接
run
方法:从共享的session
域中,检查用户是否已经登录- 已经登录:放行资源
- 未登录:
- 将错误信息
CrowdConstant.MESSAGE_ACCESS_FORBIDEN
放入共享session
域中 - 重定向至登录页面
- 将错误信息
filterType
方法:pre
表示在目标微服务前执行过滤
@Component
public class CrowdAccessFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {// 1.获取RequestContext对象RequestContext requestContext = RequestContext.getCurrentContext();// 2.通过RequestContext对象获取当前请求对象(框架底层是借助ThreadLocal从当前线程上获取事先绑定的Request对象)HttpServletRequest request = requestContext.getRequest();// 3.获取servletPath值String servletPath = request.getServletPath();// 4.根据servletPath判断当前请求是否对应可以直接放行的特定功能boolean containsResult = AccessPassResources.PASS_RES_SET.contains(servletPath);if (containsResult) {// 5.如果当前请求是可以直接放行的特定功能请求则返回false放行return false;}// 5.判断当前请求是否为静态资源// 工具方法返回true:说明当前请求是静态资源请求,取反为false表示放行不做登录检查// 工具方法返回false:说明当前请求不是可以放行的特定请求也不是静态资源,取反为true表示需要做登录检查return !AccessPassResources.judgeCurrentServletPathWetherStaticResource(servletPath);}@Overridepublic Object run() throws ZuulException {// 1.获取当前请求对象RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();// 2.获取当前Session对象HttpSession session = request.getSession();// 3.尝试从Session对象中获取已登录的用户Object loginMember = session.getAttribute(CrowdConstant.ATTR_NAME_LOGIN_MEMBER);// 4.判断loginMember是否为空if(loginMember == null) {// 5.从requestContext对象中获取Response对象HttpServletResponse response = requestContext.getResponse();// 6.将提示消息存入Session域session.setAttribute(CrowdConstant.ATTR_NAME_MESSAGE, CrowdConstant.MESSAGE_ACCESS_FORBIDEN);// 7.重定向到auth-consumer工程中的登录页面try {response.sendRedirect("/auth/member/to/login/page");} catch (IOException e) {e.printStackTrace();}}return null;}@Overridepublic String filterType() {// 这里返回“pre”意思是在目标微服务前执行过滤return "pre";}@Overridepublic int filterOrder() {return 0;}}
12.4.3、Zuul的特殊配置
sensitive-headers: "*"
:在Zuul
向其他微服务重定向时保持原本头信息(请求头、响应头)
zuul:ignored-services: "*"sensitive-headers: "*" # 在Zuul向其他微服务重定向时保持原本头信息(请求头、响应头)routes:crowd-portal:service-id: atguigu-crowd-authpath: /** # 这里一定要使用两个“*”号,不然“/”路径后面的多层路径将无法访问
12.4.4、页面信息回显
- 添加错误信息回显
<p th:text="${session.message}">检查登录状态拦截后的提示消息</p>
12.5、测试
- 静态资源无需登录就能访问
- 强行访问受保护的地址
- 靠,我为啥就不能回显
session
中的数据呢??? - 但编写测试方法测试,共享
session
确实是存在的啊。。。 - 详细解决办法见
day15
- 通过
zuul
网关访问,走的是localhost:80
这条线 - 直接通过
auth-consumer
访问,走的是localhost:4000
这条线 - 域名不同,
cookie
不同,进而导致session
不同
- 通过
- 靠,我为啥就不能回显
- 解决分布式
session
问题之后,拦截成功,返回登录页面并显示提示消息
day14【前台】用户登录注册相关推荐
- vue+node---使用element框架实现的前后台用户登录注册功能
为了更进一步清晰地了解前台数据向后台提交的过程,更好地加强巩固前端开发学习,整理了基础的[前后台用户登录注册功能]实现代码.后台通过node.js开发,数据存储在sqlite中,前台vue+eleme ...
- [golang gin框架] 29.Gin 商城项目-用户登录,注册操作
一.用户登录,注册界面展示说明 先看登录,注册界面以及相关流程,再根据流程写代码,一般网站的登录,注册功能都会在一个页面进行操作,还有的是在几个页面进行操作,这里讲解在几个页面进行注册的操作,步骤如下 ...
- java实现用户登录注册功能(用集合框架来实现)
需求:实现用户登录注册功能(用集合框架来实现) 分析: A:需求的类和接口 1.用户类 UserBean 2.用户操作方法接口和实现类 UserDao UserDaoImpl 3.测试类 UserTe ...
- java实现登录注册案例_Java基于IO版实现用户登录注册的案例
下面小编就为大家带来一篇基于IO版的用户登录注册实例(Java).小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 今天学的是用户登录注册功能. 4个包: itcast.c ...
- 基于Servlet+JSP+JavaBean开发模式的用户登录注册
基于Servlet+JSP+JavaBean开发模式的用户登录注册 一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复 ...
- Codeigniter 用户登录注册模块
Codeigniter 用户登录注册模块 以下皆是基于Codeigniter + MySQL 一.要实现用户登录注册功能,首先就要和MySQL数据库连接,操作流程如下: CI中贯彻MVC模型,即Mod ...
- javaweb学习总结(二十二)——基于Servlet+JSP+JavaBean开发模式的用户登录注册
一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...
- javaweb学习总结(二十二):基于Servlet+JSP+JavaBean开发模式的用户登录注册
一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...
- javaweb学习总结——基于Servlet+JSP+JavaBean开发模式的用户登录注册
一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...
最新文章
- python 零基础学习之路-01 计算机硬件
- 【Bash百宝箱】shell内建命令之echo、printf
- MySQL—创建数据表
- uibot和按键精灵区别_uibot和按键精灵哪个强大
- java string类型时间段 转换 date类型
- 001@多用派发队列,少用同步锁
- dotNET Core:编码规范
- 前端学习(3204):复习类相关知识2
- 天气预报HTML代码
- 数据结构-树3-红黑树
- 面试时会谈薪的人一开口就赢了:让你薪资翻倍的谈薪技巧
- js/jquery判断浏览器的方法小结
- 中国替代运动器材市场趋势报告、技术动态创新及市场预测
- oracle 获取awk报告,Oracle 使用 ass.awk 工具查看 system state dump 说明
- (5) IFC 总体架构 (Industry Foundation Class)
- 转发:大学课本答案大全!爱死你了…
- Unity插件——Odin 学习笔记(二)
- 微信小程序 学科竞赛比赛报名管理系统 Android hbuilderx App毕业设计
- 手把手教你搭建织女星开发板RISC-V开发环境
- 如何计算游戏客户端与服务器之间的时间延迟?
热门文章
- 超声乳化设备行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
- linux 6.9切换中文,Linux Centos6.9 中文乱码问题处理
- Python菜鸟入门:day11文件操作
- Web3 是去中心化的“骗局”?
- 想快速体验谷歌 Fuchsia OS?FImage 项目来了!
- 腾讯云首次披露云原生智能数据湖全景图,数据湖之争再起波澜
- 马化腾首次回应反垄断;乔布斯 1973 年求职申请表被拍出22.24万美元;英特尔新CEO表示很乐意为苹果代工 | 极客头条...
- 一个快速排序写了快 10000 字?
- 分析数万条国庆旅游评论数据后,我发现了“坑爹”景点背后的秘密
- 程序员删库被判 6 年,公司损失近亿,云原生时代如何打造安全防线?