- 课程介绍 《探花交友》
- 默认的头信息
- https://jwt.io/">官网测试:https://jwt.io/
- base64加密后的字符串为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- secret为:itcast
- 得到的加密字符串为:DwMTjJktoFFdClHqjJMRgYzICo6FJOUc3Jmev9EScBc
- 整体的token为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.DwMTjJktoFFdClHqjJMRgYzICo6FJOUc3Jmev9EScBc
- 4、总结
课程介绍 《探花交友》
- 功能介绍
- 项目介绍
- 工程搭建
- 短信验证码
- 实现用户登录功能
1、功能介绍
探花交友是一个陌生人的在线交友平台,在该平台中可以搜索附近的人,查看好友动态,平台还会通过大数据计算进行智能推荐,通过智能推荐可以找到更加匹配的好友,这样才能增进用户对产品的喜爱度。探花平台还提供了在线即时通讯功能,可以实时的与好友进行沟通,让沟通随时随地的进行。
1.1、功能列表
| 功能 | 说明 | 备注 |
|---|---|---|
| 注册、登录 | 用户无需单独注册,直接通过手机号登录即可 | 首次登录成功后需要完善个人信息 |
| 交友 | 主要功能有:测灵魂、桃花传音、搜附近、探花等 | |
| 圈子 | 类似微信朋友圈,用户可以发动态、查看好友动态等 | |
| 消息 | 通知类消息 + 即时通讯消息 | |
| 小视频 | 类似抖音,用户可以发小视频,评论等 | 显示小视频列表需要进行推荐算法计算后进行展现。 |
| 我的 | 我的动态、关注数、粉丝数、通用设置等 |
1.2、注册登录
业务说明:
用户通过手机验证码进行登录,如果是第一次登录则需要完善个人信息,在上传图片时,需要对上传的图片做人像的校验,防止用户上传非人像的图片作为头像。流程完成后,则登录成功。





1.3、交友
交友是探花项目的核心功能之一,用户可以查看好友,添加好友,搜索好友等操作。
1.3.1、首页
在首页中,主要功能有“今日佳人”、“推荐”、“最近访客”等
- 今日佳人
- 按照“缘分值”进行匹配,将“缘分值”最高的用户展现出来
- 推荐
- 按照“缘分值”进行推荐,由后台的推荐系统计算得出,展现出来
- 最近访客
- 显示最近来看“我”的用户
1.3.2、探花

说明:左划喜欢,右划不喜欢,每天限量不超过100个,开通会员可增加限额。双方互相喜欢则配对成功。 实现:数据来源推荐系统计算后的结果。
1.3.3、搜附近

根据用户当前所在的位置进行查询,并且在10km的范围内进行查询,可以通过筛选按钮进行条件筛选。
1.3.4、桃花传音
功能类似QQ中的漂流瓶,用户可以发送和接收语音消息,陌生人就会接收到消息。
1.3.5、测灵魂
- 测试题用于对用户进行分类,每次提交答案后更新用户属性
- 测试题在后台进行维护
- 测试题测试完后产生结果页可以进行分享
- 测试题为顺序回答,回答完初级题解锁下一级问题
- 点击锁定问题 显示提示 请先回答上一级问题



1.4、圈子
1、推荐频道为根据问卷及喜好推荐相似用户动态
2、显示内容为用户头像、用户昵称、用户性别、用户年龄、用户标签和用户发布动态
3、图片最多不超过6张或发布一个小视频
4、动态下方显示发布时间距离当时时间,例如10分钟前、3小时前、2天前,显示时间进行取整
5、动态下方显示距离为发布动态地与本地距离
6、显示用户浏览量
7、显示点赞数、评论数 转发数

1.5、消息
消息包含通知类的消息和好友消息。
1.6、小视频
用户可以上传小视频,也可以查看小视频列表,并且可以进行点赞操作。
1.7、我的
显示关注数、喜欢数、粉丝数、我的动态等信息。

2、项目介绍
2.1、项目背景
在线社交是互联网时代的产物,已成为互联网用户的基础需求之一。移动互联网自2003年起快速发展,促使在线社交逐渐从PC端转移至移动端。移动社交最初以熟人社交为主,以维系熟人关系、共享资源信息的形式存在。随着人们交友需求的延伸,移动社交开始向陌生人社交、兴趣社交等垂直方向发展,形式丰富多样。
2.2、市场分析
探花交友项目定位于 陌生人交友市场。
- 根据《2018社交领域投融资报告》中指出:虽然相比2017年,投融资事件减少29.5%,但是融资的总额却大幅增长,达到68%。
- 这些迹象说明:社交领域的发展规模正在扩大,而很多没有特色的产品也会被淘汰。而随着那些尾部产品的倒下,对我们来说就是机会,及时抓住不同社交需求的机会。以社交为核心向不同的细分领域衍生正在逐渐走向成熟化。
- 而我们按照娱乐形式和内容为主两个维度,将社交行业公司分类为:即时通信、内容社群、陌生人社交、泛娱乐社交以及兴趣社交几个领域。
- 而在2018年社交的各个细分领域下,均有备受资本所关注的项目,根据烯牛数据2018年的报告中,也同样指出:内容社交及陌生人社交为资本重要关注领域,合计融资占比达73%。

根据市场现状以及融资事件来看:陌生人社交、内容社群、兴趣社交在2019年仍然保持强劲的动力,占到近70%的比例,它们仍然是资本市场主要关注领域。从增长率来看陌生人社交的增长速度远远大于其他几类,因此我们要从这个方向入手。
2.3、目标用户群体
从整体年龄段来看:目前目标用户群体主要以30岁以下为主,其中以18-25岁年龄群体为主要受众人群。
- 上班群体:热衷于通过分享内容或表达“个人情绪”在陌生人面前建立特殊的人设,并借此提升自我价值扩大自己的交际圈;
- 学生群体:追求个性选择,更倾向找到有共同话题的陌生人对象并建立长期的关系,乐于展现自我;
- 文艺群体:拥有自己独特的爱好且拥有特别的个人追求,追求文艺圈子内的交流,希望通过分享结交更多好友;
- 沟通弱势群体:对现有长期保持线上对社交模式表现无力且无效,渴望有更加有效且安全的社交方式出现,解决目前单调乏味的沟通方式;
2.4、技术方案
前端:
- flutter + android + 环信SDK + redux + shared_preferences + connectivity + iconfont + webview + sqflite
后端:
- Spring Boot + SpringMVC + Mybatis + MybatisPlus + Dubbo
- Elasticsearch geo 实现地理位置查询
- MongoDB 实现海量数据的存储
- Redis 数据的缓存
- Spark + MLlib 实现智能推荐
- 第三方服务 环信即时通讯
- 第三方服务 阿里云 OSS 、 短信服务
- 第三方服务 虹软开放平台
2.5、技术架构

2.6、技术解决方案
- 使用Elasticsearch geo实现附近的人的解决方案
- 使用Spark + Mllib实现智能推荐的解决方案
- 使用MongoDB进行海量数据的存储的解决方案
- 使用采用分布式文件系统存储小视频数据的解决方案
- 使用虹软开放平台进行人脸识别的解决方案
2.7、技术亮点
- 采用Elasticsearch geo实现地理位置查询
- 采用RocketMQ作为消息服务中间件
- 采用MongoDB进行海量数据的存储
- 采用Spark + Mllib实现智能推荐
- 采用环信服务实现即时通讯
- 采用分布式文件系统存储小视频数据
- 采用Apache Dobbo作为微服务架构技术
- 采用SpringBoot + Mybatis实现系统主架构
- 采用Redis集群实现缓存的高可用
2.8、开发方式
探花交友项目采用前后端分离的方式开发,就是前端由前端团队负责开发,后端负责接口的开发,这种开发方式有2点好处:
- 扬长避短,每个团队做自己擅长的事情
- 前后端并行开发,需要事先约定好接口地址以及各种参数、响应数据结构等
对于接口的定义我们采用YApi进行管理,YApi是一个开源的接口定义、管理、提供mock数据的管理平台。
地址:https://mock-java.itheima.net/ 用户名:tanhua@itcast.cn 密码:123456

接口定义:
mock数据,YApi提供了mock功能,就是模拟服务端返回测试数据:

还可以运行http请求(需要在Chrome中安装支持跨域扩展 https://juejin.im/post/6844904057707085832):

2.9、基础环境
探花交友项目的开发统一使用提供的Centos7环境,该环境中部署安装了项目所需要的各种服务,如:MySQL、MongoDB、Redis、RocketMQ等。
- 虚拟机的root用户密码为:root123
- 默认参数:CPU:2核,内存:4G,硬盘:60G
- IP地址建议设置为192.168.31.81,否则有些服务将不可用,比如:Redis、RocketMQ等。
3、注册登录
业务说明:
用户通过手机验证码进行登录,如果是第一次登录则需要完善个人信息,在上传图片时,需要对上传的图片做人像的校验,防止用户上传非人像的图片作为头像。流程完成后,则登录成功。
流程:
3.1、单点登录系统
为什么要使用单点登录系统?
以前实现的登录和注册是在同一个tomcat内部完成,我们现在的系统架构是每一个系统都是由一个团队进行维护,每个系统都是单独部署运行一个单独的tomcat,所以,不能将用户的登录信息保存到session中(多个tomcat的session是不能共享的),所以我们需要一个单独的系统来维护用户的登录信息。

由上图可以看出:
- 客户端需要通过SSO系统才能获取到token;
- 客户端在请求服务系统时,服务系统需要通过SSO系统进行对token进行校验;
- SSO系统在整个系统架构中处于核心位置;
3.2、搭建工程
3.2.1、my-tanhua
itcast-tanhua是父工程,集中定义了依赖的版本以及所需要的依赖信息。
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.0.RELEASE</version></parent><groupId>cn.itcast.tanhua</groupId><artifactId>my-tanhua</artifactId><version>1.0-SNAPSHOT</version><!-- 集中定义依赖版本号 --><properties><mysql.version>5.1.47</mysql.version><jackson.version>2.9.9</jackson.version><druid.version>1.0.9</druid.version><servlet-api.version>2.5</servlet-api.version><jsp-api.version>2.0</jsp-api.version><joda-time.version>2.9.9</joda-time.version><commons-lang3.version>3.7</commons-lang3.version><commons-io.version>1.3.2</commons-io.version><mybatis.version>3.2.8</mybatis.version><mybatis.mybatis-plus>3.1.1</mybatis.mybatis-plus><lombok.version>1.18.4</lombok.version></properties><!--通用依赖--><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency></dependencies><dependencyManagement><dependencies><!-- mybatis-plus插件依赖 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId><version>${mybatis.mybatis-plus}</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis.mybatis-plus}</version></dependency><!-- MySql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><dependency><groupId>org.mongodb</groupId><artifactId>mongodb-driver-sync</artifactId><version>3.9.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional><version>${lombok.version}</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>${commons-lang3.version}</version></dependency><!--RocketMQ相关--><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.0.3</version></dependency><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-client</artifactId><version>4.6.0</version></dependency><!-- Jackson Json处理工具包 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>${druid.version}</version></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.11</version></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>${joda-time.version}</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.32.Final</version></dependency><!--zk依赖--><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.13</version></dependency><dependency><groupId>com.github.sgroschupf</groupId><artifactId>zkclient</artifactId><version>0.1</version></dependency><!--dubbo的springboot支持--><dependency><groupId>com.alibaba.boot</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>0.2.0</version></dependency><!--dubbo框架--><dependency><groupId>com.alibaba</groupId><artifactId>dubbo</artifactId><version>2.6.4</version></dependency><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>2.8.3</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.3</version></dependency><dependency><groupId>com.github.tobato</groupId><artifactId>fastdfs-client</artifactId><version>1.26.7</version><exclusions><exclusion><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId></exclusion></exclusions></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency></dependencies></dependencyManagement><build><plugins><!-- java编译插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.2</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build></project>
3.2.2、my-tanhua-sso
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>my-tanhua</artifactId><groupId>cn.itcast.tanhua</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>my-tanhua-sso</artifactId><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId></dependency><!--RocketMQ相关--><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId></dependency><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-client</artifactId></dependency><!--简化代码的工具包--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId></dependency></dependencies></project>
3.2.3、网易模拟器
探花交友项目的前端采用Android APP的形式,所以我们需要使用模拟器或真机进行测试。
对于模拟器这里推荐使用网易模拟器,其兼容性好、功能完善而且还简洁,缺点是它不支持虚拟机中安装。
3.3、数据库表
数据库使用的mysql:
#user表CREATE TABLE `tb_user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`mobile` varchar(11) DEFAULT NULL COMMENT '手机号',`password` varchar(32) DEFAULT NULL COMMENT '密码,需要加密',`created` datetime DEFAULT NULL,`updated` datetime DEFAULT NULL,PRIMARY KEY (`id`),KEY `mobile` (`mobile`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表';#user_info表CREATE TABLE `tb_user_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`user_id` bigint(20) NOT NULL COMMENT '用户id',`nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',`logo` varchar(100) DEFAULT NULL COMMENT '用户头像',`tags` varchar(50) DEFAULT NULL COMMENT '用户标签:多个用逗号分隔',`sex` int(1) DEFAULT '3' COMMENT '性别,1-男,2-女,3-未知',`age` int(11) DEFAULT NULL COMMENT '用户年龄',`edu` varchar(20) DEFAULT NULL COMMENT '学历',`city` varchar(20) DEFAULT NULL COMMENT '居住城市',`birthday` varchar(20) DEFAULT NULL COMMENT '生日',`cover_pic` varchar(50) DEFAULT NULL COMMENT '封面图片',`industry` varchar(20) DEFAULT NULL COMMENT '行业',`income` varchar(20) DEFAULT NULL COMMENT '收入',`marriage` varchar(20) DEFAULT NULL COMMENT '婚姻状态',`created` datetime DEFAULT NULL,`updated` datetime DEFAULT NULL,PRIMARY KEY (`id`),KEY `user_id` (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';
3.4、编写配置
application.properties 文件:
#相关spring核心配置文件的命名spring.application.name=xiaoha-tanhua-sso#服务端口server.port=18080#数据源及数据库的连接配置spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://192.168.31.81:3306/mytanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=falsespring.datasource.username=rootspring.datasource.password=root#枚举的包扫描(封装性别字段的枚举类)应用到的技术为mybatis-plusmybatis-plus.type-enums-package=cn.xiaoha.sso.enums#设定(通配)表名的前缀(应用到的技术为mybatis-plus)mybatis-plus.global-config.db-config.table-prefix=tb_#设置id策略为自增长(应用到的技术为mybatis-plus)mybatis-plus.global-config.db-config.id-type=auto#redis相关的配置#设置最长等待时间spring.redis.jedis.pool.max-wait=5000ms#设置最大活动连接数spring.redis.jedis.pool.max-idle=100#设置最小活动连接数spring.redis.jedis.pool.min-idle=10#设置连接超时时间spring.redis.timeout=10s#设置redis集群节点的ip地址和端口号spring.redis.cluster.nodes=192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381spring.redis.cluster.max-redirects=5#设置rocketMQ相关的配置#配置rocketmq服务器的地址端口号rocketmq.name-server=192.168.31.81:9876#配置rocketmq的分组名rocketmq.producer.group=tanhua#xiaoha_tanhua#盐 值jwt.secret=76bd425b6f29f7fcc2e0bfc286043df1#虹软相关配置arcsoft.appid=*****arcsoft.sdkKey=****arcsoft.libPath=F:\\code\\WIN64
3.5、编写基础代码
3.5.1、Lombok
lombok 提供了简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 java 代码,尤其是针对pojo。
3.5.1.1、配置安装
导入依赖:
<!--简化代码的工具包--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
安装IDEA插件:
如果不安装插件,程序可以正常执行,但是看不到生成的一些代码,如:get、set方法。
3.5.1.2、常用注解
- @Data:注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法
- @Setter:注解在属性上;为属性提供 setting 方法
- @Getter:注解在属性上;为属性提供 getting 方法
- @Slf4j:注解在类上;为类提供一个 属性名为log 的 slf4j日志对象
- @NoArgsConstructor:注解在类上;为类提供一个无参的构造方法
- @AllArgsConstructor:注解在类上;为类提供一个全参的构造方法
- @Builder:使用Builder模式构建对象
测试一:使用@Data注解
是不是很神奇?!
测试二:使用@Slf4j注解

测试:
测试三:@AllArgsConstructor、@NoArgsConstructor注解的使用

测试四:@Builder

测试结果:
3.5.2、创建编写SexEnum类
用户的性别用枚举进行表示。
package cn.xiaoha.sso.enums;import com.baomidou.mybatisplus.core.enums.IEnum;public enum SexEnum implements IEnum<Integer> {/*** 封装性别的枚举*/MAN(1,"男"),WOMAN(2,"女"),UNKNOWN(3,"未知");private int value;private String desc;SexEnum(int value,String desc){this.value = value;this.desc = desc;}@Overridepublic Integer getValue() {return this.value;}@Overridepublic String toString(){return this.desc;}}
3.5.3、创建编写User、UserInfo、BasePOJO实体类
package cn.xiaoha.sso.pojo;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.TableField;import java.util.Date;@Datapublic class BasePOJO {/*** 该类为封装user表和userInfo表共同的拥有的字段* created和updated;*///添加数据时的日期@TableField(fill = FieldFill.INSERT) //配置数据自动填充private Date created;//修改数据时的日期@TableField(fill = FieldFill.INSERT_UPDATE) //配置数据自动填充private Date updated;}
package cn.xiaoha.sso.pojo;import com.fasterxml.jackson.annotation.JsonIgnore;import lombok.Data;import java.util.Date;@Datapublic class User extends BasePOJO {/*** 该类为映射tb_user表的实体类(数据封装类)*/private Long id;private String mobile; //手机号@JsonIgnoreprivate String password; //密码 json序列化时(将对象转为json格式字符串时忽略)//private Date created;//private Date updated;}
package cn.xiaoha.sso.pojo;import cn.xiaoha.sso.enums.SexEnum;import lombok.Data;@Datapublic class UserInfo extends BasePOJO{/*** 该类为映射tb_user_info表的数据封装类*/private Long id;private Long userId; //用户idprivate String nickName; //昵称private String logo; //用户头像private String tags; //用户标签:多个用逗号分隔private SexEnum sex; //性别private Integer age; //年龄private String edu; //学历private String city; //城市private String birthday; //生日private String coverPic; // 封面图片private String industry; //行业private String income; //收入private String marriage; //婚姻状态}
3.5.4、创建编写MyMetaObjectHandler
对自动填充字段的处理:
package cn.xiaoha.sso.hanlder;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import org.apache.ibatis.reflection.MetaObject;import java.util.Date;public class MyMetaObjectHandler implements MetaObjectHandler {/** 该类是对自动填充的字段做进一步的手动处理*//*** 此方法是对新添加数据的created和updated做一个非空的判断和设置* @param metaObject*/@Overridepublic void insertFill(MetaObject metaObject) {//获取created对象Object created = getFieldValByName("created", metaObject);//做非空判断if(created==null){//为空,就添加 参数一为指定的字段名,// 参数二为需要添加的新日期对象setFieldValByName("created",new Date(),metaObject);}//获取updated对象Object updated = getFieldValByName("updated", metaObject);//做非空判断if(updated==null){setFieldValByName("updated",new Date(),metaObject);}}/*** 此方法是对修改数据时对updated的设置* @param metaObject*/@Overridepublic void updateFill(MetaObject metaObject) {//修改时不需要做非空判断,因为修改的数据肯定是不为空的//而且只要处理updated的字段//Object updated = getFieldValByName("updated", metaObject);setFieldValByName("updated",new Date(),metaObject);}}
3.5.5、创建编写UserMapper(技术实现mybatis-plus)
package cn.xiaoha.sso.mapper;import cn.xiaoha.sso.pojo.User;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import org.apache.ibatis.annotations.Mapper;import org.springframework.stereotype.Repository;//@Mapper@Repositorypublic interface UserMapper extends BaseMapper<User> {}
package com.tanhua.sso.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.tanhua.sso.pojo.UserInfo;public interface UserInfoMapper extends BaseMapper<UserInfo> {}
3.5.6、MyApplication
SpringBoot的启动类。
package cn.xiaoha.sso;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@MapperScan("cn.xiaoha.sso.mapper")@SpringBootApplicationpublic class MySpringApplication {public static void main(String[] args) {SpringApplication.run(MySpringApplication.class,args);}}
3.6、短信验证码
发送短信验证码的流程:
流程说明:
- 用户向SSO系统发送请求,在请求中传递手机号;
- SSO系统接收到请求后,生成随机验证码以及短信内容,请求阿里云短信服务;
- 阿里云短信服务接收到请求后,会进行一系列的验证,比如账号余额、短信模板是否正确等,最后向运营商发起请求;
- 运营商接收到请求后,向该手机号下发短信,用户即可收到短信;
3.6.1、阿里云短信服务
3.6.1.1、申请签名与模板
https://dysms.console.aliyun.com/dysms.htm?spm=5176.12818093.0.ddysms.2a4316d0ql6PyD
说明:申请签名时,个人用户只能申请一个并且签名的名称必须为“ABC商城”,否则审核不通过。
申请模板:
审核时间需要1
3.6.1.2、设置用户权限
在阿里云中,需要在RAM服务中创建用户以及权限,才能通过api进行访问接口。
创建用户:


创建完成后要保存AccessKey Secret和AccessKey ID,AccessKey Secret只显示这一次,后面将不再显示。
添加权限:
3.6.1.3、编写发送短信的测试类SendSms
文档:https://help.aliyun.com/document_detail/101414.html?spm=a2c4g.11186623.6.625.18705ffa8u4lwj:
package com.xiaoha.sso.test;import com.aliyuncs.CommonRequest;import com.aliyuncs.CommonResponse;import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.IAcsClient;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.exceptions.ServerException;import com.aliyuncs.http.MethodType;import com.aliyuncs.profile.DefaultProfile;/*pom.xml<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.3</version></dependency>*/public class MageTest {public static void main(String[] args) {DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou","LTAI4GAcwoRi7xR3GUUrayCL", "6hWIMDC7a8kLLRxXN9nzI0x3wQ2biS");IAcsClient client = new DefaultAcsClient(profile);CommonRequest request = new CommonRequest();request.setSysMethod(MethodType.POST);request.setSysDomain("dysmsapi.aliyuncs.com");request.setSysVersion("2017-05-25");request.setSysAction("SendSms");request.putQueryParameter("RegionId", "cn-hangzhou");//request.putQueryParameter("PhoneNumbers", "15874204087"); //目标手机号request.putQueryParameter("SignName", "青橙"); //签名名称request.putQueryParameter("TemplateCode", "SMS_205136201"); //短信模板coderequest.putQueryParameter("TemplateParam", "{\"code\":\"123456\"}");//模板中变量替换try {CommonResponse response = client.getCommonResponse(request);//{"Message":"OK","RequestId":"EC2D4C9A-0EAC-4213-BE45-CE6176E1DF23","BizId":"110903802851113360^0","Code":"OK"}System.out.println(response.getData());} catch (ServerException e) {e.printStackTrace();} catch (ClientException e) {e.printStackTrace();}}}
3.6.1.4、实现发送短信方法(功能)
配置文件:aliyun.properties 文件
aliyun.sms.regionId = cn-hangzhoualiyun.sms.accessKeyId = LTAI4GAcwoRi7xR3GUUrayCLaliyun.sms.accessKeySecret = 6hWIMDC7a8kLLRxXN9nzI0x3wQ2biSaliyun.sms.domain= dysmsapi.aliyuncs.comaliyun.sms.signName=青橙aliyun.sms.templateCode= SMS_205136201
需要注意中文编码问题:
创建读取配置的配置类:
package cn.xiaoha.sso.config;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.PropertySource;@Configuration@PropertySource("classpath:aliyun.properties") //指定配置文件的路径@ConfigurationProperties(prefix = "aliyun.sms")//设置前缀@Datapublic class AliyunSMSConfig {private String regionId;private String accessKeyId;private String accessKeySecret;private String domain;private String signName;private String templateCode;}
service层的代码实现:
package cn.xiaoha.sso.service;import cn.xiaoha.sso.config.AliyunSMSConfig;import cn.xiaoha.sso.vo.ErrorResult;import com.aliyuncs.CommonRequest;import com.aliyuncs.CommonResponse;import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.IAcsClient;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.exceptions.ServerException;import com.aliyuncs.http.MethodType;import com.aliyuncs.profile.DefaultProfile;import io.netty.util.internal.StringUtil;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.RandomUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;import java.time.Duration;@Service@Slf4jpublic class SendSms {//注入redisTemplate的工具类//泛型一定要写@Autowiredprivate RedisTemplate<String,String> redisTemplate;//将封装有参数的配置配置类注入过来@Autowiredprivate AliyunSMSConfig aliyunSMSConfig;/*** 发送验证码的方法* @param mobile 方法参数为接收验证码的手机号* @return 返回结果为验证码*/public String getSendSMS(String mobile){String code = "123456";DefaultProfile profile = DefaultProfile.getProfile(this.aliyunSMSConfig.getRegionId(),this.aliyunSMSConfig.getAccessKeyId(), this.aliyunSMSConfig.getAccessKeySecret());IAcsClient client = new DefaultAcsClient(profile);//随机生成的六位数验证码//String code = RandomUtils.nextInt(100000,999999)+"";//向目标手机发送验证码CommonRequest request = new CommonRequest();request.setSysMethod(MethodType.POST);request.setSysDomain(this.aliyunSMSConfig.getDomain());request.setSysVersion("2017-05-25");request.setSysAction("SendSms");request.putQueryParameter("RegionId", this.aliyunSMSConfig.getRegionId());request.putQueryParameter("PhoneNumbers", mobile); //目标手机号(接收验证码的手机号)request.putQueryParameter("SignName", this.aliyunSMSConfig.getSignName()); //签名名称request.putQueryParameter("TemplateCode", this.aliyunSMSConfig.getTemplateCode()); //短信模板coderequest.putQueryParameter("TemplateParam", "{\"code\":"+code+"}");//模板中变量替换try {CommonResponse response = client.getCommonResponse(request);String data = response.getData();//String data = code;//判断结果,一般结果为:{"Message":"OK","RequestId":"EC2D4C9A-0EAC-4213-BE45-CE6176E1DF23","BizId":"110903802851113360^0","Code":"OK"}if (StringUtils.contains(data,"\"Message\":\"OK\"")){//如果数据中(结果)存在"Message":"OK",说明成功的发送了验证码//返回验证码return code;}log.info("发送成功 , data=",data);//System.out.println(response.getData());} catch (Exception e) {//e.printStackTrace();log.error("发送失败 ,mobile=",mobile,e);}//报错则没有成功的发送验证码,其实也不会走return null//只是骗骗编译器return null;}public ErrorResult sendCheckCode(String phone) {//先判断redis中的验证码是否失效了String redisKey = "CHECK_CODE_"+phone;if (this.redisTemplate.hasKey(redisKey)) {//如果redis中有该键则不用再次发//返货发送失败String mes = "验证码还没有失效";ErrorResult errorResult = ErrorResult.builder().errCode("000000").errMessage(mes).build();return errorResult;}//调用getSendSms方法发送验证码String code = this.getSendSMS(phone);//判断验证码是否为空if(StringUtils.isEmpty(code)){//为空则发送验证码失败String mes = "发送验证码失败";ErrorResult errorResult = ErrorResult.builder().errCode("000002").errMessage(mes).build();return errorResult;}//如果成功的发送验证码,就把验证码存入到redis中this.redisTemplate.opsForValue().set(redisKey,code, Duration.ofMinutes(10));//没有错误就返回nullreturn null;}}
3.6.2、SSO短信接口服务
3.6.2.1、mock接口
地址:https://mock-java.itheima.net/project/35/interface/api/581
3.6.2.2、编写接口服务
编写ErrorResult,ErrorResult对象是与前端约定好的结构,如果发生错误需要返回该对象,如果未发生错误响应200即可。
package cn.xiaoha.sso.vo;import lombok.Builder;import lombok.Data;@Data@Builderpublic class ErrorResult {/*** 该类为封装结果的类* 发生错误返回该类的对象* 未发生错误直接返回200,* 即可*///错误的代码private String errCode;//错误的提示信息private String errMessage;}
SmsController:
package cn.xiaoha.sso.controller;import cn.xiaoha.sso.service.SendSms;import cn.xiaoha.sso.vo.ErrorResult;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Map;@Slf4j@RestController@RequestMapping("user")public class SmsController {//注入service层@Autowiredprivate SendSms sendSms;@PostMapping("login")public ResponseEntity<ErrorResult> sendCheckCode(@RequestBody Map<String,String>param){//获取请求参数的值String phone = param.get("phone");ErrorResult errorResult = null;try {//调用service层发送检测验证码的方法//返回值为ErrorResult对象,参数为手机号errorResult=this.sendSms.sendCheckCode(phone);//判断errorResult结果,为空表示没有出错,则发送验证码正常if(errorResult==null){//返回200状态码即可return ResponseEntity.ok(null);}} catch (Exception e) {//e.printStackTrace();log.info("发送短信失败,phone=",phone,e);//出错了就新制造一个错String mes = "验证码发送失败";errorResult = ErrorResult.builder().errCode("000001").errMessage(mes).build();}return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult);}}
SmsService:
package cn.xiaoha.sso.service;import cn.xiaoha.sso.config.AliyunSMSConfig;import cn.xiaoha.sso.vo.ErrorResult;import com.aliyuncs.CommonRequest;import com.aliyuncs.CommonResponse;import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.IAcsClient;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.exceptions.ServerException;import com.aliyuncs.http.MethodType;import com.aliyuncs.profile.DefaultProfile;import io.netty.util.internal.StringUtil;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.RandomUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;import java.time.Duration;@Service@Slf4jpublic class SendSms {//注入redisTemplate的工具类//泛型一定要写@Autowiredprivate RedisTemplate<String,String> redisTemplate;//将封装有参数的配置配置类注入过来@Autowiredprivate AliyunSMSConfig aliyunSMSConfig;/*** 发送验证码的方法* @param mobile 方法参数为接收验证码的手机号* @return 返回结果为验证码*/public String getSendSMS(String mobile){String code = "123456";DefaultProfile profile = DefaultProfile.getProfile(this.aliyunSMSConfig.getRegionId(),this.aliyunSMSConfig.getAccessKeyId(), this.aliyunSMSConfig.getAccessKeySecret());IAcsClient client = new DefaultAcsClient(profile);//随机生成的六位数验证码//String code = RandomUtils.nextInt(100000,999999)+"";//向目标手机发送验证码CommonRequest request = new CommonRequest();request.setSysMethod(MethodType.POST);request.setSysDomain(this.aliyunSMSConfig.getDomain());request.setSysVersion("2017-05-25");request.setSysAction("SendSms");request.putQueryParameter("RegionId", this.aliyunSMSConfig.getRegionId());request.putQueryParameter("PhoneNumbers", mobile); //目标手机号(接收验证码的手机号)request.putQueryParameter("SignName", this.aliyunSMSConfig.getSignName()); //签名名称request.putQueryParameter("TemplateCode", this.aliyunSMSConfig.getTemplateCode()); //短信模板coderequest.putQueryParameter("TemplateParam", "{\"code\":"+code+"}");//模板中变量替换try {CommonResponse response = client.getCommonResponse(request);String data = response.getData();//String data = code;//判断结果,一般结果为:{"Message":"OK","RequestId":"EC2D4C9A-0EAC-4213-BE45-CE6176E1DF23","BizId":"110903802851113360^0","Code":"OK"}if (StringUtils.contains(data,"\"Message\":\"OK\"")){//如果数据中(结果)存在"Message":"OK",说明成功的发送了验证码//返回验证码return code;}log.info("发送成功 , data=",data);//System.out.println(response.getData());} catch (Exception e) {//e.printStackTrace();log.error("发送失败 ,mobile=",mobile,e);}//报错则没有成功的发送验证码,其实也不会走return null//只是骗骗编译器return null;}public ErrorResult sendCheckCode(String phone) {//先判断redis中的验证码是否失效了String redisKey = "CHECK_CODE_"+phone;if (this.redisTemplate.hasKey(redisKey)) {//如果redis中有该键则不用再次发//返货发送失败String mes = "验证码还没有失效";ErrorResult errorResult = ErrorResult.builder().errCode("000000").errMessage(mes).build();return errorResult;}//调用getSendSms方法发送验证码String code = this.getSendSMS(phone);//判断验证码是否为空if(StringUtils.isEmpty(code)){//为空则发送验证码失败String mes = "发送验证码失败";ErrorResult errorResult = ErrorResult.builder().errCode("000002").errMessage(mes).build();return errorResult;}//如果成功的发送验证码,就把验证码存入到redis中this.redisTemplate.opsForValue().set(redisKey,code, Duration.ofMinutes(10));//没有错误就返回nullreturn null;}}
3.7、JWT
3.7.1、简介
JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全。
在身份验证过程中, 当用户使用其凭据成功登录时, 将返回 JSON Web token, 并且必须在本地保存 (通常在本地存储中)。
每当用户要访问受保护的路由或资源 (端点) 时, 用户代理(user agent)必须连同请求一起发送 JWT, 通常在授权标头中使用Bearer schema。后端服务器接收到带有 JWT 的请求时, 首先要做的是验证token。
3.7.2、格式
- JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C
- A由JWT头部信息header经过base64加密得到
官网测试:https://jwt.io/
base64加密后的字符串为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- B是payload,存放有效信息的地方,这些信息包含三个部分:- 标准中注册的声明 (建议但不强制使用)- iss: jwt签发者- sub: jwt所面向的用户- aud: 接收jwt的一方- exp: jwt的过期时间,这个过期时间必须要大于签发时间- nbf: 定义在什么时间之前,该jwt都是不可用的.- iat: jwt的签发时间- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。- 公共的声明- 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.- 私有的声明- 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。- ```json#存放的数据:{"sub": "1234567890","name": "John Doe","iat": 1516239022}#base64后的字符串为:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
- C由A和B通过加密算法得到,用作对token进行校验,看是否有效
整体的token为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.DwMTjJktoFFdClHqjJMRgYzICo6FJOUc3Jmev9EScBc
<a name="f4440c9a"></a>#### 3.7.3、流程<a name="aa0c0b0b"></a>#### 3.7.4、示例导入依赖:```xml<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
编写测试用例:
package com.xiaoha.sso.test;import io.jsonwebtoken.*;import org.junit.Test;import java.util.Date;import java.util.HashMap;import java.util.Map;public class TestJWT {//定义一个解密的秘钥String secret = "xiaoha";@Testpublic void createJIT(){//数据的准备//头信息Map<String,Object> header = new HashMap<String,Object>();header.put(JwsHeader.TYPE,JwsHeader.JWT_TYPE);header.put(JwsHeader.ALGORITHM,"HS256");//中间部分Map<String,Object> claims = new HashMap<String,Object>();claims.put("mobile","12345678910");claims.put("id","2");//生成token核心代码String jwt = Jwts.builder().setHeader(header)//header ,可以省略.setClaims(claims) //payload,存储数据的位置,不能放置敏感的数据,如:密码等.signWith(SignatureAlgorithm.HS256,secret)//设置加密的方法和加密盐.setExpiration(new Date(System.currentTimeMillis()+300000))//设置过期时间,当前时间加指定过期的时间.compact();System.out.println(jwt);}//测试解析token、@Testpublic void decodeToken(){//准备要解析的tokenString token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtb2JpbGUiOiIxMjM0NTY3ODkxMCIsImlkIjoiMiIsImV4cCI6MTYxMTgzMzAzMn0.5qvBoxzfNYlTOmPlV93zJ-k-DMtClZSZbJAJv5nya9s";try{Map<String,Object> body = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();System.out.println(body);}catch(ExpiredJwtException e){System.out.println("token已经过期了");}catch(Exception e){System.out.println("无效的token");}}}
3.8、用户登录
用户接收到验证码后,进行输入验证码,点击登录,前端系统将手机号以及验证码提交到SSO进行校验。
3.7.1、mock接口
接口地址:https://mock-java.itheima.net/project/164/interface/api/12593
3.7.2、创建编写UserController类
package cn.xiaoha.sso.controller;import cn.xiaoha.sso.service.UserService;import cn.xiaoha.sso.vo.ErrorResult;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;import java.util.Map;@RestController@RequestMapping("user")public class UserController {//注入UserService层@Autowiredprivate UserService userService;/*** 用户登录验证方法* @param param 封装了请求参数:手机号和验证码* @return 返回值包括isNew和token*/@PostMapping("loginVerification")public ResponseEntity<Object> loginVerification(@RequestBody Map<String,String> param){ErrorResult error = null;try {//获取请求参数String phone = param.get("phone");String code = param.get("verificationCode");//调用service层String data = this.userService.login(phone,code);//判断data是否不为空if(StringUtils.isNotEmpty(data)){//不为空则表示登录成功String[] split = StringUtils.split(data,"|");Map<String,String> result = new HashMap<String,String>();result.put("token",split[0]);result.put("isNew",split[1]);return ResponseEntity.ok(result);}} catch (Exception e) {e.printStackTrace();//如果出错了//就抛出一个异常error = ErrorResult.builder().errCode("000003").errMessage("登录失败,请重试").build();}return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);}}
3.7.3、UserService
package cn.xiaoha.sso.service;import cn.xiaoha.sso.mapper.UserMapper;import cn.xiaoha.sso.pojo.User;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import lombok.extern.slf4j.Slf4j;import org.apache.commons.codec.digest.DigestUtils;import org.apache.commons.lang3.StringUtils;import org.apache.rocketmq.spring.core.RocketMQTemplate;import org.joda.time.DateTime;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.messaging.MessagingException;import org.springframework.stereotype.Service;import java.util.HashMap;import java.util.Map;@Slf4j@Servicepublic class UserService {//从配置文件中获取secret@Value("${jwt.secret}")private String secret;//注入redis@Autowiredprivate RedisTemplate<String,String> redisTemplate;//注入userMapper@Autowiredprivate UserMapper userMapper;//注入rocketMQTemplate@Autowiredprivate RocketMQTemplate rocketMQTemplate;/*** 登录验证* @param phone 手机号* @param code 验证码* @return*/public String login(String phone, String code) {//先去redis中核对验证码String redisKey = "CHECK_CODE_"+phone;//定义一个标记isNew,该标记为是否为新用户boolean isNew = false ;//获取redis中的验证码String redisValue = this.redisTemplate.opsForValue().get(redisKey);//核对验证码if(!StringUtils.equals(code,redisValue)){//验证码和redis中的不相同则登录失败return null;}//如果验证码通过则把redis中的数据删除//redisTemplate.delete(redisKey);//之后调用mapper查询数据库,看是不是新用户QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.eq("mobile",phone);User user = this.userMapper.selectOne(wrapper);//判断if(user==null){//如果查询结果为空表示为新用户,就把用户信息注册到数据库user = new User();user.setMobile(phone);user.setPassword(DigestUtils.md5Hex("123456"));//设置初始密码时加密//将数据添加到数据库this.userMapper.insert(user);//是新用户就把标记改为trueisNew = true;}//不管是不是新用户都要生成token//将数据表中的id生成tokenMap<String,Object> claims = new HashMap<String,Object>();claims.put("id",user.getId());String jwt = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, secret).setExpiration(new DateTime().plusHours(12).toDate()).compact();try {//通过rocketMQ将用户已经登录的消息发送给各个服务器//封装消息数据Map<String,Object>mes = new HashMap<String,Object>();mes.put("id",user.getId());mes.put("date",System.currentTimeMillis());this.rocketMQTemplate.convertAndSend("tanhua-sso-login",mes);} catch (MessagingException e) {//e.printStackTrace();log.error("发送消息失败",e);}return jwt+"|"+isNew;}}
3.7.4、测试

4、总结
4.1 、用到的技术——sso(单点登录技术)
(1)与数据库交互的mapper层用到了mybatis-plus,加强了mybatis的开发效率
(2)service层用到了阿里云的短信服务技术,实现登录验证码的发送,并且结合redis,将验证码临时的进行存储。
还用到了rocketMQ技术,。用户登录验证时用到了jwt技术,实现了对用户的基础信息进行盐加密生成token,而token实现了用户每次访问不同的功能模块时,不需要再次登录。
4.2 、整个单点登录的流程总结
(1)用户注册:用户通过手机号进行注册账号,我们拿到手机号先查询redis中有没有发给该手机号的验证码,如果有则直接返回(错误)提示信息,说验证码已经发过,而且还没有失效,如果redis中没有就调用发送验证码的方法进行发送验证码。然后判断是否成功的发送了验证码,如果返回的验证码为空,则直接返回(错误)信息,说发送验证码失败,请重试,如果成功的发送了验证码,则将验证码存入redis中设置有效时间为n分钟,最后返回null,因为没有错误。
(2)用户登录 :用户拿到验证码后,第一次根据手机号和验证码进行登录,我们拿到用户的手机号和验证码后,先拿验证那去redis中进行核对,如果redis中没有或和用户传过来的验证码不一致,则返回空,为空的话controller层直接返回错误信息给前端,如果redis中有而且跟用户传来的验证码一致,则判断是不是新用户,是新用户则将用户信息存入数据库,不是则不存,然后生成token,再然后通过rocketMQ将消息发送给各个服务器,最后返回token和isNew



