01-项目介绍以及实现登录功能

## 课程介绍 《探花交友》

  • 功能介绍
  • 项目介绍
  • 工程搭建
  • 短信验证码
  • 实现用户登录功能

1、功能介绍

探花交友是一个陌生人的在线交友平台,在该平台中可以搜索附近的人,查看好友动态,平台还会通过大数据计算进行智能推荐,通过智能推荐可以找到更加匹配的好友,这样才能增进用户对产品的喜爱度。探花平台还提供了在线即时通讯功能,可以实时的与好友进行沟通,让沟通随时随地的进行。

1.1、功能列表

功能 说明 备注
注册、登录 用户无需单独注册,直接通过手机号登录即可 首次登录成功后需要完善个人信息
交友 主要功能有:测灵魂、桃花传音、搜附近、探花等
圈子 类似微信朋友圈,用户可以发动态、查看好友动态等
消息 通知类消息 + 即时通讯消息
小视频 类似抖音,用户可以发小视频,评论等 显示小视频列表需要进行推荐算法计算后进行展现。
我的 我的动态、关注数、粉丝数、通用设置等

1.2、注册登录

业务说明:

用户通过手机验证码进行登录,如果是第一次登录则需要完善个人信息,在上传图片时,需要对上传的图片做人像的校验,防止用户上传非人像的图片作为头像。流程完成后,则登录成功。

1563681330146

1563681348382

1563681365123

1563681382592

1563681397466

1563681412881

1.3、交友

交友是探花项目的核心功能之一,用户可以查看好友,添加好友,搜索好友等操作。

1563698504256

1.3.1、首页

在首页中,主要功能有“今日佳人”、“推荐”、“最近访客”等

  • 今日佳人
    • 按照“缘分值”进行匹配,将“缘分值”最高的用户展现出来
  • 推荐
    • 按照“缘分值”进行推荐,由后台的推荐系统计算得出,展现出来
  • 最近访客
    • 显示最近来看“我”的用户

1.3.2、探花

1563714212568

说明:左划喜欢,右划不喜欢,每天限量不超过100个,开通会员可增加限额。双方互相喜欢则配对成功。

实现:数据来源推荐系统计算后的结果。

1.3.3、搜附近

1563714826374

根据用户当前所在的位置进行查询,并且在10km的范围内进行查询,可以通过筛选按钮进行条件筛选。

1.3.4、桃花传音

功能类似QQ中的漂流瓶,用户可以发送和接收语音消息,陌生人就会接收到消息。

1563718868686

1.3.5、测灵魂

  1. 测试题用于对用户进行分类,每次提交答案后更新用户属性

  2. 测试题在后台进行维护

  3. 测试题测试完后产生结果页可以进行分享

  4. 测试题为顺序回答,回答完初级题解锁下一级问题

  5. 点击锁定问题 显示提示 请先回答上一级问题

1563719052277

1563719092059

1563719100855

1.4、圈子

1、推荐频道为根据问卷及喜好推荐相似用户动态

2、显示内容为用户头像、用户昵称、用户性别、用户年龄、用户标签和用户发布动态

3、图片最多不超过6张或发布一个小视频

4、动态下方显示发布时间距离当时时间,例如10分钟前、3小时前、2天前,显示时间进行取整

5、动态下方显示距离为发布动态地与本地距离

6、显示用户浏览量

7、显示点赞数、评论数 转发数

1563719380223

1563719403171

1.5、消息

消息包含通知类的消息和好友消息。

1563719621939

1.6、小视频

用户可以上传小视频,也可以查看小视频列表,并且可以进行点赞操作。

1563719761162

1.7、我的

显示关注数、喜欢数、粉丝数、我的动态等信息。

1563719894481

1563719949700

2、项目介绍

2.1、项目背景

在线社交是互联网时代的产物,已成为互联网用户的基础需求之一。移动互联网自2003年起快速发展,促使在线社交逐渐从PC端转移至移动端。移动社交最初以熟人社交为主,以维系熟人关系、共享资源信息的形式存在。随着人们交友需求的延伸,移动社交开始向陌生人社交、兴趣社交等垂直方向发展,形式丰富多样。

2.2、市场分析

探花交友项目定位于 陌生人交友市场

  • 根据《2018社交领域投融资报告》中指出:虽然相比2017年,投融资事件减少29.5%,但是融资的总额却大幅增长,达到68%。
  • 这些迹象说明:社交领域的发展规模正在扩大,而很多没有特色的产品也会被淘汰。而随着那些尾部产品的倒下,对我们来说就是机会,及时抓住不同社交需求的机会。以社交为核心向不同的细分领域衍生正在逐渐走向成熟化。
  • 而我们按照娱乐形式和内容为主两个维度,将社交行业公司分类为:即时通信、内容社群、陌生人社交、泛娱乐社交以及兴趣社交几个领域。
  • 而在2018年社交的各个细分领域下,均有备受资本所关注的项目,根据烯牛数据2018年的报告中,也同样指出:内容社交及陌生人社交为资本重要关注领域,合计融资占比达73%。

img

根据市场现状以及融资事件来看:陌生人社交、内容社群、兴趣社交在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、技术架构

1563457093887

2.6、技术解决方案

  • 使用Elasticsearch geo实现附近的人的解决方案
  • 使用Spark + Mllib实现智能推荐的解决方案
  • 使用MongoDB进行海量数据的存储的解决方案
  • 使用采用分布式文件系统存储小视频数据的解决方案
  • 使用虹软开放平台进行人脸识别的解决方案
  • 使用阿里云进行短信验证码发送的解决方案

2.7、技术亮点

  • 采用Elasticsearch geo实现地理位置查询
  • 采用RocketMQ作为消息服务中间件
  • 采用MongoDB进行海量数据的存储
  • 采用Spark + Mllib实现智能推荐
  • 采用环信服务实现即时通讯
  • 采用分布式文件系统存储小视频数据
  • 采用Apache Dobbo作为微服务架构技术
  • 采用SpringBoot + Mybatis实现系统主架构
  • 采用Redis集群实现缓存的高可用

2.8、开发方式

探花交友项目采用前后端分离的方式开发,就是前端由前端团队负责开发,后端负责接口的开发,这种开发方式有2点好处:

  • 扬长避短,每个团队做自己擅长的事情
  • 前后端并行开发,需要事先约定好接口地址以及各种参数、响应数据结构等

什么是接口?接口就是一个http的请求地址,在定义接口的时候主要就是去定义:请求路径,请求方式,请求参数,响应结果数据等内容。

对于接口的定义我们采用YApi进行管理,YApi是一个开源的接口定义、管理、提供mock数据的管理平台。

地址:https://mock-java.itheima.net/

用户名:tanhua@itcast.cn

密码:123456

image-20201017100705286

接口定义:

image-20201017100848313

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

image-20201017101228582

image-20201017101252482

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

image-20201017103028622

image-20201017103045864

2.9、基础环境

探花交友项目的开发统一使用提供的Centos7环境,该环境中部署安装了项目所需要的各种服务,如:MySQL、MongoDB、Redis、RocketMQ等。

  • 虚拟机的root用户密码为:root123
  • 默认参数:CPU:2核,内存:4G,硬盘:60G
  • IP地址建议设置为192.168.31.81,否则有些服务将不可用,比如:Redis、RocketMQ等。

3、注册登录

业务说明:

用户通过手机验证码进行登录,如果是第一次登录则需要完善个人信息,在上传图片时,需要对上传的图片做人像的校验,防止用户上传非人像的图片作为头像。流程完成后,则登录成功。

流程:

image-20210202144610457

3.1、单点登录系统

为什么要使用单点登录系统?

以前实现的登录和注册是在同一个tomcat内部完成,我们现在的系统架构是每一个系统都是由一个团队进行维护,每个系统都是单独部署运行一个单独的tomcat,所以,不能将用户的登录信息保存到session中(多个tomcat的session是不能共享的),所以我们需要一个单独的系统来维护用户的登录信息。

image-20201016221250343

image-20201117235203408

由上图可以看出:

  • 客户端需要通过SSO系统才能获取到token;
  • 客户端在请求服务系统时,服务系统需要通过SSO系统进行对token进行校验;
  • SSO系统在整个系统架构中处于核心位置;

3.2、搭建工程

3.2.1、my-tanhua

创建maven工程

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

创建子模块/工程 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>

★★★安装 RockeMQ★★★

一、下载

1、 官网下载:http://rocketmq.apache.org/dowloading/releases/

2、百度网盘下载:https://pan.baidu.com/s/10Ktn1oDMTzDkZi1SvQpxEQ 提取码:0g5a

二、安装及启动

1、前提条件:已安装jdk1.8及maven
2、解压到想安装的目录

将下载下的安装文件解压到本地磁盘(任意,此处解压到D:盘)

3、配置[环境变量]

编辑上边的path变量

4、启动

(1)启动NAMESERVER

使用cmd打开命令窗口,进入到rocketmq的bin目录下,执行:start mqnamesrv.cmd,若成功会弹出如下提示框,请勿关闭此框。

(2)启动BROKER

同样在rocketmq的bin目录下,执行:start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true , 启动BROKER。成功后会弹出提示框,此框勿关闭。

如果弹出框提示‘错误: 找不到或无法加载主类 xxxxxx’。在bin下找到并打开runbroker.cmd,然后将‘%CLASSPATH%’加上英文双引号。保存并重新执行start语句。如下:

三、安装可视化插件

1、github下载地址:

https://codeload.github.com/apache/rocketmq-externals/zip/master 网盘下载:https://pan.baidu.com/s/16ZoOkYEBtNq1sGp0cuk3cA 提取码:ms2g

2、解压到安装目录

解压到d:\mq_rocket文件夹下(或者其他想放置的目录)

3、进入\rocketmq-console\src\main\resources文件夹

,用编辑器打开application.properties文件,配置如下:

4、 打包rocketmq-console

进入rocketmq-externals\rocketmq-console 文件夹,执行:mvn clean package -Dmaven.test.skip=true,编译生成,编译成功如下:

编译成功后,在rocketmq-externals\rocketmq-console下会生成target文件夹,此文件夹里面有jar包。

进入target文件夹后执行:java -jar rocketmq-console-ng-1.0.1.jar,这里是在启动jar工程。

5、进入控制台

启动完毕后,在浏览器输入:http://localhost:8085/ 进入控制台

3.2.3、网易模拟器

探花交友项目的前端采用Android APP的形式,所以我们需要使用模拟器或真机进行测试。

对于模拟器这里推荐使用网易模拟器,其兼容性好、功能完善而且还简洁,缺点是它不支持虚拟机中安装。

下载:https://mumu.163.com/

image-20201016214917500

安装 探花app后设置网络

3.3、数据库表

数据库使用的mysql:

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='用户表';

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.application.name = itcast-tanhua-sso

server.port = 18080

# 特别注意 : 后面不能跟空格

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.31.81:3306/mytanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root


# 枚举包扫描
mybatis-plus.type-enums-package=com.tanhua.sso.enums
# 表名前缀
mybatis-plus.global-config.db-config.table-prefix=tb_
# id策略为自增长
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
spring.redis.cluster.nodes = 192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381
spring.redis.cluster.max-redirects=5

# RocketMQ相关配置
rocketmq.name-server=192.168.31.81:9876
rocketmq.producer.group=tanhua

#itcast_tanhua
#盐 值
jwt.secret=76bd425b6f29f7fcc2e0bfc286043df1

#虹软相关配置
arcsoft.appid=*****
arcsoft.sdkKey=****
arcsoft.libPath=F:\\code\\WIN64

3.5、编写基础代码

3.5.1、Lombok

lombok 提供了简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 java 代码,尤其是针对pojo。

官网:https://projectlombok.org/

3.5.1.1、配置安装

导入依赖:

<!--简化代码的工具包-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

安装IDEA插件: 1542036205393

如果不安装插件,程序可以正常执行,但是看不到生成的一些代码,如: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注解

image-20201116113610810

是不是很神奇?!

测试二:使用@Slf4j注解

image-20201116113645107

测试:

image-20201116113812602

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

image-20201116114013519

测试四:@Builder

image-20201116114233416

测试结果:

image-20201116114254202

3.5.2、SexEnum

用户的性别用枚举进行表示。使用MybatisPlus配置枚举,必须实现 IEnum接口

package com.tanhua.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;
    }

    @Override
    public Integer getValue() {
        return this.value;
    }

    @Override
    public String toString() {
        return this.desc;
    }
}

3.5.3、User、UserInfo

package com.tanhua.sso.pojo;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

import java.util.Date;

/**
 * 基类,让其表相关DO都继承此类,限制其他类必须有这俩字段(方便实现MP的自动填充功能)
 */
@Data
public abstract class BasePojo {

    @TableField(fill = FieldFill.INSERT) //MP自动填充
    private Date created;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updated;
}

package com.tanhua.sso.pojo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User extends BasePojo {

    private Long id;
    private String mobile; //手机号

    @JsonIgnore
    private String password; //密码,json序列化时忽略

}

package com.tanhua.sso.pojo;

import com.tanhua.sso.enums.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo extends BasePojo {

    private Long id;
    private Long userId; //用户id
    private String nickName; //昵称
    private String logo; //用户头像
    private String tags; //用户标签:多个用逗号分隔
    private SexEnum sex; //性别  ps:注意此字段类型用的是继承MP的IEnum
    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 com.tanhua.sso.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        Object created = getFieldValByName("created", metaObject);
        if (null == created) {
            //字段为空,可以进行填充
            setFieldValByName("created", new Date(), metaObject);
        }

        Object updated = getFieldValByName("updated", metaObject);
        if (null == updated) {
            //字段为空,可以进行填充
            setFieldValByName("updated", new Date(), metaObject);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        //更新数据时,直接更新字段
        setFieldValByName("updated", new Date(), metaObject);
    }
}

3.5.5、UserMapper

创建mapper层包

package com.tanhua.sso.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tanhua.sso.pojo.User;

public 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 com.tanhua.sso;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("com.tanhua.sso.mapper") //设置mapper接口的扫描包
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

3.6、短信验证码

发送短信验证码的流程:

image-20201116090449776

流程说明:

  • 用户向SSO系统发送请求,在请求中传递手机号;
  • SSO系统接收到请求后,生成随机验证码以及短信内容,请求阿里云短信服务;
  • 阿里云短信服务接收到请求后,会进行一系列的验证,比如账号余额、短信模板是否正确等,最后向运营商发起请求;
  • 运营商接收到请求后,向该手机号下发短信,用户即可收到短信;

3.6.1、阿里云短信服务

3.6.1.1、申请签名与模板

https://dysms.console.aliyun.com/dysms.htm?spm=5176.12818093.0.ddysms.2a4316d0ql6PyD

image-20201016193819097

说明:申请签名时,个人用户只能申请一个并且签名的名称必须为“ABC商城”,否则审核不通过。

申请模板:

image-20201016193927924

审核时间需要12小时,请耐心等待

3.6.1.2、设置用户权限

在阿里云中,需要在RAM服务中创建用户以及权限,才能通过api进行访问接口。

image-20201016194331790

创建用户:

image-20201016194409024

image-20201016195011698

创建完成后要保存AccessKey Secret和AccessKey ID,AccessKey Secret只显示这一次,后面将不再显示。

添加权限:

image-20201016194636739

3.6.1.3、示例代码

文档:https://help.aliyun.com/document_detail/101414.html?spm=a2c4g.11186623.6.625.18705ffa8u4lwj:

package com.tanhua.sso.service;

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 SendSms {
    public static void main(String[] args) {
        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou",
                "LTAI4G7d2Q9CHc741gighjTF", "uKOOGdIKvmoGhHlej8cJY8H3nlU6Fj");
        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", "158****7944"); //目标手机号
        request.putQueryParameter("SignName", "ABC商城"); //签名名称
        request.putQueryParameter("TemplateCode", "SMS_204756062"); //短信模板code
        request.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-hangzhou
aliyun.sms.accessKeyId = LTAI4G7d2Q9CHc741gighjTF
aliyun.sms.accessKeySecret = uKOOGdIKvmoGhHlej8cJY8H3nlU6Fj
aliyun.sms.domain= dysmsapi.aliyuncs.com
aliyun.sms.signName= ABC商城
aliyun.sms.templateCode= SMS_204756062

需要注意中文编码问题:

image-20201016203847636

读取配置:

package com.tanhua.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")
@Data
public class AliyunSMSConfig {

    private String regionId;
    private String accessKeyId;
    private String accessKeySecret;
    private String domain;
    private String signName;
    private String templateCode;

}

代码实现:

//SmsService.java
/**
     * 发送短信验证码
     *
     * @param mobile
     * @return
     */
    public String sendSms(String mobile) {
        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()); //短信模板code
        request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");//模板中变量替换
        try {
            CommonResponse response = client.getCommonResponse(request);
            String data = response.getData();
            if (StringUtils.contains(data, "\"Message\":\"OK\"")) {
                return code;
            }
            log.info("发送短信验证码失败~ data = " + data);
        } catch (Exception e) {
            log.error("发送短信验证码失败~ mobile = " + mobile, e);
        }
        return null;
    }

3.6.2、SSO短信接口服务

3.6.2.1、mock接口

地址:https://mock-java.itheima.net/project/35/interface/api/581

1564535110994

3.6.2.2、编写接口服务

编写ErrorResult,ErrorResult对象是与前端约定好的结构,如果发生错误需要返回该对象,如果未发生错误响应200即可。

新建vo包

package com.tanhua.sso.vo;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class ErrorResult {

    private String errCode;
    private String errMessage;
}

SmsController:

新建controller包

package com.tanhua.sso.controller;

import com.tanhua.sso.service.SmsService;
import com.tanhua.sso.vo.ErrorResult;
import lombok.extern.slf4j.Slf4j;
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;

@RestController
@RequestMapping("user")
@Slf4j
public class SmsController {

    @Autowired
    private SmsService smsService;

    /**
     * 发送短信验证码接口
     *
     * @param param
     * @return
     */
    @PostMapping("login")
    public ResponseEntity<ErrorResult> sendCheckCode(@RequestBody Map<String, String> param) {
        ErrorResult errorResult = null;
        String phone = param.get("phone");
        try {
            errorResult = this.smsService.sendCheckCode(phone);
            if (null == errorResult) {
                return ResponseEntity.ok(null);
            }
        } catch (Exception e) {
            log.error("发送短信验证码失败~ phone = " + phone, e);
            errorResult = ErrorResult.builder().errCode("000002").errMessage("短信验证码发送失败!").build();
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult);
    }

}

SmsService:

package com.tanhua.sso.service;

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 com.tanhua.sso.config.AliyunSMSConfig;
import com.tanhua.sso.vo.ErrorResult;
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
@Slf4j
public class SmsService {

    @Autowired
    private AliyunSMSConfig aliyunSMSConfig;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    /**
     * 发送短信验证码
     *
     * @param mobile
     * @return
     */
    public String sendSms(String mobile) {
        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()); //短信模板code
        request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");//模板中变量替换
        try {
            CommonResponse response = client.getCommonResponse(request);
            String data = response.getData();
            if (StringUtils.contains(data, "\"Message\":\"OK\"")) {
                return code;
            }
            log.info("发送短信验证码失败~ data = " + data);
        } catch (Exception e) {
            log.error("发送短信验证码失败~ mobile = " + mobile, e);
        }
        return null;
    }

    /**
     * 发送短信验证码
     * 实现:发送完成短信验证码后,需要将验证码保存到redis中
     * @param phone
     * @return
     */
    public ErrorResult sendCheckCode(String phone) {

        String redisKey = "CHECK_CODE_" + phone;

        //先判断该手机号发送的验证码是否还未失效
        if(this.redisTemplate.hasKey(redisKey)){
            String msg = "上一次发送的验证码还未失效!";
            return ErrorResult.builder().errCode("000001").errMessage(msg).build();
        }

        String code = this.sendSms(phone);
        if(StringUtils.isEmpty(code)){
            String msg = "发送短信验证码失败!";
            return ErrorResult.builder().errCode("000000").errMessage(msg).build();
        }

        //短信发送成功,将验证码保存到redis中,有效期为5分钟
        this.redisTemplate.opsForValue().set(redisKey, code, Duration.ofMinutes(5));

        return 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加密得到

    • #默认的头信息
      {
        "alg": "HS256",
        "typ": "JWT"
      }
      
      #官网测试:https://jwt.io/
      #base64加密后的字符串为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
      
  • B是payload,存放有效信息的地方,这些信息包含三个部分:

    • 标准中注册的声明 (建议但不强制使用)

      • iss: jwt签发者
      • sub: jwt所面向的用户
      • aud: 接收jwt的一方
      • exp: jwt的过期时间,这个过期时间必须要大于签发时间
      • nbf: 定义在什么时间之前,该jwt都是不可用的.
      • iat: jwt的签发时间
      • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
    • 公共的声明

      • 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
    • 私有的声明

      • 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
    • #存放的数据:
      {
        "sub": "1234567890",
        "name": "John Doe",
        "iat": 1516239022
      }
      
      #base64后的字符串为:
      eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
      
  • C由A和B通过加密算法得到,用作对token进行校验,看是否有效

    • 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

    • #secret为:itcast
      #得到的加密字符串为:DwMTjJktoFFdClHqjJMRgYzICo6FJOUc3Jmev9EScBc
      
      #整体的token为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.DwMTjJktoFFdClHqjJMRgYzICo6FJOUc3Jmev9EScBc
      

3.7.3、流程

1563895628713

3.7.4、示例

导入依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

编写测试用例:

在test下java文件夹下创建包com.tanhua.sso.service

package com.tanhua.sso.service;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.Test;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class TestJWT {

    String secret = "itcast";

    @Test
    public void testCreateToken(){

        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", "1333333333");
        claims.put("id", "2");

        // 生成token
        String jwt = Jwts.builder()
                .setHeader(header)  //header,可省略
                .setClaims(claims) //payload,存放数据的位置,不能放置敏感数据,如:密码等
                .signWith(SignatureAlgorithm.HS256, secret) //设置加密方法和加密盐
                .setExpiration(new Date(System.currentTimeMillis() + 3000)) //设置过期时间,3秒后过期
                .compact();

        System.out.println(jwt);

    }

    @Test
    public void testDecodeToken(){
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtb2JpbGUiOiIxMzMzMzMzMzMzIiwiaWQiOiIyIiwiZXhwIjoxNjA1NTEzMDA2fQ.1eG3LpudD4XBycUG39UQDaKVBQHgaup-E1OLWo_m8m8";
        try {
            // 通过token解析数据
            Map<String, Object> body = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
            System.out.println(body); //{mobile=1333333333, id=2, exp=1605513392}
        } catch (ExpiredJwtException e) {
            System.out.println("token已经过期!");
        } catch (Exception e) {
            System.out.println("token不合法!");
        }
    }

}

3.8、用户登录

用户接收到验证码后,进行输入验证码,点击登录,前端系统将手机号以及验证码提交到SSO进行校验。

image-20201118231431191

3.7.1、mock接口

接口地址:https://mock-java.itheima.net/project/164/interface/api/12593

1564300265546

3.7.2、UserController

package com.tanhua.sso.controller;

import com.tanhua.sso.service.UserService;
import com.tanhua.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 {

    @Autowired
    private UserService userService;

    /**
     * 用户登录
     *
     * @param param
     * @return
     */
    @PostMapping("loginVerification")
    public ResponseEntity<Object> login(@RequestBody Map<String,String> param){
        try {
            String phone = param.get("phone");
            String code = param.get("verificationCode");

            String data = this.userService.login(phone, code);
            if(StringUtils.isNotEmpty(data)){
                //登录成功
                Map<String, Object> result = new HashMap<>(2);
                String[] ss = StringUtils.split(data, '|');

                result.put("token", ss[0]);
                result.put("isNew", Boolean.valueOf(ss[1]));
                return ResponseEntity.ok(result);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        ErrorResult errorResult = ErrorResult.builder().errCode("000002").errMessage("登录失败!").build();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult);
    }

}

3.7.3、UserService

package com.tanhua.sso.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.tanhua.sso.mapper.UserMapper;
import com.tanhua.sso.pojo.User;
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;

@Service
@Slf4j
public class UserService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;


    /**
     * 用户登录
     *
     * @param phone 手机号
     * @param code  验证码
     * @return
     */
    public String login(String phone, String code) {
        String redisKey = "CHECK_CODE_" + phone;
        boolean isNew = false;

        //校验验证码
        String redisData = this.redisTemplate.opsForValue().get(redisKey);
        if (!StringUtils.equals(code, redisData)) {
            return null; //验证码错误
        }

        //验证码在校验完成后,需要废弃
        this.redisTemplate.delete(redisKey);

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("mobile", phone);

        User user = this.userMapper.selectOne(queryWrapper);

        if (null == user) {
            //需要注册该用户
            user = new User();
            user.setMobile(phone);
            user.setPassword(DigestUtils.md5Hex("123456"));

            //注册新用户
            this.userMapper.insert(user);
            isNew = true;
        }

        //生成token
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("id", user.getId());

        // 生成token
        String token = Jwts.builder()
                .setClaims(claims) //payload,存放数据的位置,不能放置敏感数据,如:密码等
                .signWith(SignatureAlgorithm.HS256, secret) //设置加密方法和加密盐
                .setExpiration(new DateTime().plusHours(12).toDate()) //设置过期时间,12小时后过期
                .compact();

        try {
            //发送用户登录成功的消息
            Map<String,Object> msg = new HashMap<>();
            msg.put("id", user.getId());
            msg.put("date", System.currentTimeMillis());

            this.rocketMQTemplate.convertAndSend("tanhua-sso-login", msg);
        } catch (MessagingException e) {
            log.error("发送消息失败!", e);
        }

        return token + "|" + isNew;
    }
}

3.7.4、测试

1564300534797

posted @ 2021-12-02 20:27  起跑线小言  阅读(537)  评论(0编辑  收藏  举报