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对象的属性值无法注入,为什么?

    • 如果不写@RequestBodySpringMVC默认发送过来的请求参数为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 Projectpro14-spring-session-apro14-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方法大致流程

    • 接收包装前的HttpServletRequestHttpServletResponse

    • HttpServletRequestHttpServletResponse包装为我们自定义的SessionRepositoryRequestWrapperSessionRepositoryResponseWrapper

    • 将我们包装后的RequestResponse传入filterChain中,在之后的流程中,程序便会使用我们自定义的SessionRepositoryRequestWrapperSessionRepositoryResponseWrapper,执行我们重写之后的方法

      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方法:使用Redissession保存维护起来

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【前台】用户登录注册相关推荐

  1. vue+node---使用element框架实现的前后台用户登录注册功能

    为了更进一步清晰地了解前台数据向后台提交的过程,更好地加强巩固前端开发学习,整理了基础的[前后台用户登录注册功能]实现代码.后台通过node.js开发,数据存储在sqlite中,前台vue+eleme ...

  2. [golang gin框架] 29.Gin 商城项目-用户登录,注册操作

    一.用户登录,注册界面展示说明 先看登录,注册界面以及相关流程,再根据流程写代码,一般网站的登录,注册功能都会在一个页面进行操作,还有的是在几个页面进行操作,这里讲解在几个页面进行注册的操作,步骤如下 ...

  3. java实现用户登录注册功能(用集合框架来实现)

    需求:实现用户登录注册功能(用集合框架来实现) 分析: A:需求的类和接口 1.用户类 UserBean 2.用户操作方法接口和实现类 UserDao UserDaoImpl 3.测试类 UserTe ...

  4. java实现登录注册案例_Java基于IO版实现用户登录注册的案例

    下面小编就为大家带来一篇基于IO版的用户登录注册实例(Java).小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 今天学的是用户登录注册功能. 4个包: itcast.c ...

  5. 基于Servlet+JSP+JavaBean开发模式的用户登录注册

    基于Servlet+JSP+JavaBean开发模式的用户登录注册 一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复 ...

  6. Codeigniter 用户登录注册模块

    Codeigniter 用户登录注册模块 以下皆是基于Codeigniter + MySQL 一.要实现用户登录注册功能,首先就要和MySQL数据库连接,操作流程如下: CI中贯彻MVC模型,即Mod ...

  7. javaweb学习总结(二十二)——基于Servlet+JSP+JavaBean开发模式的用户登录注册

    一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...

  8. javaweb学习总结(二十二):基于Servlet+JSP+JavaBean开发模式的用户登录注册

    一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...

  9. javaweb学习总结——基于Servlet+JSP+JavaBean开发模式的用户登录注册

    一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...

最新文章

  1. python 零基础学习之路-01 计算机硬件
  2. 【Bash百宝箱】shell内建命令之echo、printf
  3. MySQL—创建数据表
  4. uibot和按键精灵区别_uibot和按键精灵哪个强大
  5. java string类型时间段 转换 date类型
  6. 001@多用派发队列,少用同步锁
  7. dotNET Core:编码规范
  8. 前端学习(3204):复习类相关知识2
  9. 天气预报HTML代码
  10. 数据结构-树3-红黑树
  11. 面试时会谈薪的人一开口就赢了:让你薪资翻倍的谈薪技巧
  12. js/jquery判断浏览器的方法小结
  13. 中国替代运动器材市场趋势报告、技术动态创新及市场预测
  14. oracle 获取awk报告,Oracle 使用 ass.awk 工具查看 system state dump 说明
  15. (5) IFC 总体架构 (Industry Foundation Class)
  16. 转发:大学课本答案大全!爱死你了…
  17. Unity插件——Odin 学习笔记(二)
  18. 微信小程序 学科竞赛比赛报名管理系统 Android hbuilderx App毕业设计
  19. 手把手教你搭建织女星开发板RISC-V开发环境
  20. 如何计算游戏客户端与服务器之间的时间延迟?

热门文章

  1. 超声乳化设备行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  2. linux 6.9切换中文,Linux Centos6.9 中文乱码问题处理
  3. Python菜鸟入门:day11文件操作
  4. Web3 是去中心化的“骗局”?
  5. 想快速体验谷歌 Fuchsia OS?FImage 项目来了!
  6. 腾讯云首次披露云原生智能数据湖全景图,数据湖之争再起波澜
  7. 马化腾首次回应反垄断;乔布斯 1973 年求职申请表被拍出22.24万美元;英特尔新CEO表示很乐意为苹果代工 | 极客头条...
  8. 一个快速排序写了快 10000 字?
  9. 分析数万条国庆旅游评论数据后,我发现了“坑爹”景点背后的秘密
  10. 程序员删库被判 6 年,公司损失近亿,云原生时代如何打造安全防线?