• 【Spring Cloud】黑马头条 用户服务创建、登录功能实现


    点击去看上一篇

    一、创建用户 model

    1.创建用户数据库库 leadnews_user

    核心表 ap_user

    建库建表语句

    这里一定要使用 navicat,执行SQL 文件,以防止 cmd 中的编码问题

    先将 SQL 语句,保存在电脑中,再使用 navicat 打开

    1. CREATE DATABASE IF NOT EXISTS leadnews_user DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    2. USE leadnews_user;
    3. SET NAMES utf8;
    4. /*
    5. Navicat MySQL Data Transfer
    6. Source Server : localhost
    7. Source Server Version : 50721
    8. Source Host : localhost:3306
    9. Source Database : leadnews_user
    10. Target Server Type : MYSQL
    11. Target Server Version : 50721
    12. File Encoding : 65001
    13. Date: 2021-04-12 13:58:42
    14. */
    15. SET FOREIGN_KEY_CHECKS=0;
    16. -- ----------------------------
    17. -- Table structure for ap_user
    18. -- ----------------------------
    19. DROP TABLE IF EXISTS `ap_user`;
    20. CREATE TABLE `ap_user` (
    21. `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
    22. `salt` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码、通信等加密盐',
    23. `name` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',
    24. `password` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码,md5加密',
    25. `phone` varchar(11) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号',
    26. `image` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像',
    27. `sex` tinyint(1) unsigned DEFAULT NULL COMMENT '0 男\r\n 1 女\r\n 2 未知',
    28. `is_certification` tinyint(1) unsigned DEFAULT NULL COMMENT '0 未\r\n 1 是',
    29. `is_identity_authentication` tinyint(1) DEFAULT NULL COMMENT '是否身份认证',
    30. `status` tinyint(1) unsigned DEFAULT NULL COMMENT '0正常\r\n 1锁定',
    31. `flag` tinyint(1) unsigned DEFAULT NULL COMMENT '0 普通用户\r\n 1 自媒体人\r\n 2 大V',
    32. `created_time` datetime DEFAULT NULL COMMENT '注册时间',
    33. PRIMARY KEY (`id`) USING BTREE
    34. ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='APP用户信息表';
    35. -- ----------------------------
    36. -- Records of ap_user
    37. -- ----------------------------
    38. INSERT INTO `ap_user` VALUES ('1', 'abc', 'zhangsan', 'abc', '13511223453', null, '1', null, null, '1', '1', '2020-03-19 23:22:07');
    39. INSERT INTO `ap_user` VALUES ('2', 'abc', 'lisi', 'abc', '13511223454', '', '1', null, null, '1', '1', '2020-03-19 23:22:07');
    40. INSERT INTO `ap_user` VALUES ('3', 'sdsa', 'wangwu', 'wangwu', '13511223455', null, null, null, null, null, '1', null);
    41. INSERT INTO `ap_user` VALUES ('4', '123abc', 'admin', '81e158e10201b6d7aee6e35eaf744796', '13511223456', null, '1', null, null, '1', '1', '2020-03-30 16:36:32');
    42. INSERT INTO `ap_user` VALUES ('5', '123', 'suwukong', 'suwukong', '13511223458', null, '1', null, null, '1', '1', '2020-08-01 11:09:57');
    43. INSERT INTO `ap_user` VALUES ('6', null, null, null, null, null, null, null, null, null, null, null);
    44. -- ----------------------------
    45. -- Table structure for ap_user_fan
    46. -- ----------------------------
    47. DROP TABLE IF EXISTS `ap_user_fan`;
    48. CREATE TABLE `ap_user_fan` (
    49. `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
    50. `user_id` int(11) unsigned DEFAULT NULL COMMENT '用户ID',
    51. `fans_id` int(11) unsigned DEFAULT NULL COMMENT '粉丝ID',
    52. `fans_name` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '粉丝昵称',
    53. `level` tinyint(1) unsigned DEFAULT NULL COMMENT '粉丝忠实度\r\n 0 正常\r\n 1 潜力股\r\n 2 勇士\r\n 3 铁杆\r\n 4 老铁',
    54. `created_time` datetime DEFAULT NULL COMMENT '创建时间',
    55. `is_display` tinyint(1) unsigned DEFAULT NULL COMMENT '是否可见我动态',
    56. `is_shield_letter` tinyint(1) unsigned DEFAULT NULL COMMENT '是否屏蔽私信',
    57. `is_shield_comment` tinyint(1) unsigned DEFAULT NULL COMMENT '是否屏蔽评论',
    58. PRIMARY KEY (`id`) USING BTREE
    59. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='APP用户粉丝信息表';
    60. -- ----------------------------
    61. -- Records of ap_user_fan
    62. -- ----------------------------
    63. -- ----------------------------
    64. -- Table structure for ap_user_follow
    65. -- ----------------------------
    66. DROP TABLE IF EXISTS `ap_user_follow`;
    67. CREATE TABLE `ap_user_follow` (
    68. `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
    69. `user_id` int(11) unsigned DEFAULT NULL COMMENT '用户ID',
    70. `follow_id` int(11) unsigned DEFAULT NULL COMMENT '关注作者ID',
    71. `follow_name` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '粉丝昵称',
    72. `level` tinyint(1) unsigned DEFAULT NULL COMMENT '关注度\r\n 0 偶尔感兴趣\r\n 1 一般\r\n 2 经常\r\n 3 高度',
    73. `is_notice` tinyint(1) unsigned DEFAULT NULL COMMENT '是否动态通知',
    74. `created_time` datetime DEFAULT NULL COMMENT '创建时间',
    75. PRIMARY KEY (`id`) USING BTREE
    76. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='APP用户关注信息表';
    77. -- ----------------------------
    78. -- Records of ap_user_follow
    79. -- ----------------------------
    80. -- ----------------------------
    81. -- Table structure for ap_user_realname
    82. -- ----------------------------
    83. DROP TABLE IF EXISTS `ap_user_realname`;
    84. CREATE TABLE `ap_user_realname` (
    85. `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
    86. `user_id` int(11) unsigned DEFAULT NULL COMMENT '账号ID',
    87. `name` varchar(20) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '用户名称',
    88. `idno` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '资源名称',
    89. `font_image` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '正面照片',
    90. `back_image` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '背面照片',
    91. `hold_image` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手持照片',
    92. `live_image` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '活体照片',
    93. `status` tinyint(1) unsigned DEFAULT NULL COMMENT '状态\r\n 0 创建中\r\n 1 待审核\r\n 2 审核失败\r\n 9 审核通过',
    94. `reason` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '拒绝原因',
    95. `created_time` datetime DEFAULT NULL COMMENT '创建时间',
    96. `submited_time` datetime DEFAULT NULL COMMENT '提交时间',
    97. `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
    98. PRIMARY KEY (`id`) USING BTREE
    99. ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='APP实名认证信息表';
    100. -- ----------------------------
    101. -- Records of ap_user_realname
    102. -- ----------------------------
    103. INSERT INTO `ap_user_realname` VALUES ('1', '1', 'zhangsan', '512335455602781278', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbHSAQlqFAAXIZNzAq9E126.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbF6AR16RAAZB2e1EsOg460.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbDeAH2qoAAbD_WiUJfk745.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9ba9qANVEdAAS25KJlEVE291.jpg', '9', '', '2019-07-30 14:34:28', '2019-07-30 14:34:30', '2019-07-12 06:48:04');
    104. INSERT INTO `ap_user_realname` VALUES ('2', '2', 'lisi', '512335455602781279', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbHSAQlqFAAXIZNzAq9E126.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbF6AR16RAAZB2e1EsOg460.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbDeAH2qoAAbD_WiUJfk745.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9ba9qANVEdAAS25KJlEVE291.jpg', '1', '', '2019-07-11 17:21:18', '2019-07-11 17:21:20', '2019-07-12 06:48:04');
    105. INSERT INTO `ap_user_realname` VALUES ('3', '3', 'wangwu6666', '512335455602781276', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbHSAQlqFAAXIZNzAq9E126.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbF6AR16RAAZB2e1EsOg460.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbDeAH2qoAAbD_WiUJfk745.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9ba9qANVEdAAS25KJlEVE291.jpg', '9', '', '2019-07-11 17:21:18', '2019-07-11 17:21:20', '2019-07-12 06:48:04');
    106. INSERT INTO `ap_user_realname` VALUES ('5', '5', 'suwukong', '512335455602781279', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbHSAQlqFAAXIZNzAq9E126.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbF6AR16RAAZB2e1EsOg460.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9bbDeAH2qoAAbD_WiUJfk745.jpg', 'http://161.189.111.227/group1/M00/00/00/rBFwgF9ba9qANVEdAAS25KJlEVE291.jpg', '1', '', '2020-08-01 11:10:31', '2020-08-01 11:10:34', '2020-08-01 11:10:36');

    2.在 heima-leadnews-model 模块底下创建 com.heima.model.user 包

    3.在 com.heima.model.user 包底下创建 pojos 包

    dtos 包的创建暂时忽略,不过这里也会讲解 dtos 包的大概作用(存放 DTO 类型)

    POJO

    POJO 的全程有几种,如 pure old java object 、plain ordinary java object 等。但意思都差不多,可以理解为就是简单的 Java 对象,符合没有被继承类、没有实现接口、属性私有、拥有无参构造方法等规则的 Java 对象

    POJO 可划分为 VO、DTO、BO、DO 等

    POJO全称作用
    VOView Object前端会将 VO 内的属性渲染到页面上
    DTOData Transfer Object前后端通过 DTO 传输数据、后端的服务与服务之间也可以通过 DTO 传输数据
    BOBusiness ObjectBO 内会封装多个 DO,即业务直接处理 BO,间接处理 DO。如简历相关业务,简历就是 BO,专业技能、实习经历、项目经历、个人信息等为 DO
    DOData ObjectDO 中的属性与数据库表中的属性一一对应,即实体类

    这里黑马头条中的包划分可能不够严谨,也可能有其他考量,不是重点,暂时不关心

    在阿里开发规范中,规定类名中的 DO/BO/DTO/VO/AO/PO/UID 需要大写

    4.在 pojos 包中创建 ApUser 类

    1. package com.heima.model.user.pojos;
    2. import com.baomidou.mybatisplus.annotation.IdType;
    3. import com.baomidou.mybatisplus.annotation.TableField;
    4. import com.baomidou.mybatisplus.annotation.TableId;
    5. import com.baomidou.mybatisplus.annotation.TableName;
    6. import lombok.Data;
    7. import java.io.Serializable;
    8. import java.util.Date;
    9. /**
    10. *

    11. * APP用户信息表
    12. *

    13. *
    14. * @author itheima
    15. */
    16. @Data
    17. @TableName("ap_user")
    18. public class ApUser implements Serializable {
    19. private static final long serialVersionUID = 1L;
    20. /**
    21. * 主键
    22. */
    23. @TableId(value = "id", type = IdType.AUTO)
    24. private Integer id;
    25. /**
    26. * 密码、通信等加密盐
    27. */
    28. @TableField("salt")
    29. private String salt;
    30. /**
    31. * 用户名
    32. */
    33. @TableField("name")
    34. private String name;
    35. /**
    36. * 密码,md5加密
    37. */
    38. @TableField("password")
    39. private String password;
    40. /**
    41. * 手机号
    42. */
    43. @TableField("phone")
    44. private String phone;
    45. /**
    46. * 头像
    47. */
    48. @TableField("image")
    49. private String image;
    50. /**
    51. * 0 男
    52. 1 女
    53. 2 未知
    54. */
    55. @TableField("sex")
    56. private Boolean sex;
    57. /**
    58. * 0 未
    59. 1 是
    60. */
    61. @TableField("is_certification")
    62. private Boolean certification;
    63. /**
    64. * 是否身份认证
    65. */
    66. @TableField("is_identity_authentication")
    67. private Boolean identityAuthentication;
    68. /**
    69. * 0正常
    70. 1锁定
    71. */
    72. @TableField("status")
    73. private Boolean status;
    74. /**
    75. * 0 普通用户
    76. 1 自媒体人
    77. 2 大V
    78. */
    79. @TableField("flag")
    80. private Short flag;
    81. /**
    82. * 注册时间
    83. */
    84. @TableField("created_time")
    85. private Date createdTime;
    86. }
    @MyBatisPlus 注解
    1. import com.baomidou.mybatisplus.annotation.IdType;
    2. import com.baomidou.mybatisplus.annotation.TableField;
    3. import com.baomidou.mybatisplus.annotation.TableId;
    4. import com.baomidou.mybatisplus.annotation.TableName;

    ApUser 类使用了 MybatisPlus 中的 4 个注解

    注解作用
    @TableName

    让 MybatisPlus 识别当前类对应的表,ApUser > ap_user

    @TableId让 MybatisPlus 识别当前属性为表的主键,id > id
    @IdType让 MybatisPlus 识别当前主键的类型,IdType.AUTO > 自增类型
    @TableField让 MybatisPlus 识别当前属性对应的表字段,name > name

    添加注解是为了防止数据库字段或 Java 代码属性的命名不规范、不统一

    蛇形驼峰转化

    如果满足以下三个条件,即可开启蛇形驼峰转化,可以不用上面的 MybatisPlus 注解来进行识别:

    • Java 严格按照驼峰命名(名字中单词首字母大写,如属性名 helloWorld、类名 HelloWorld)
    • 数据库严格按照蛇形命名(名字中的单词之间用下划线分割,如 hello_world)
    • 名字都一一对应完全相同(名字都是 hello world 只是命名形式不同)

    在 yml 中开启蛇形驼峰转化:

        黑马提供的初始项目的 model 模块下并没有 resources 目录,需要自己创建。然后在 resources 目录下创建 application.yml 文件来进行配置(黑马并没有使用 MybatisPlus 的自动命名转化配置,为了不给后面埋雷,不建议大家做这个配置)

    1. mybatis-plus:
    2. configuration:
    3. map-underscore-to-camel-case: true

    MyBatisPlus POM 依赖

    在 heima-leadnews-model 模块的 pom.xml 中添加依赖

    1. <dependency>
    2. <groupId>mysqlgroupId>
    3. <artifactId>mysql-connector-javaartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.mybatisgroupId>
    7. <artifactId>mybatisartifactId>
    8. dependency>
    9. <dependency>
    10. <groupId>com.baomidougroupId>
    11. <artifactId>mybatis-plus-boot-starterartifactId>
    12. dependency>

    实际上 mybatis-plus-boot-starter 中包含了 mybatis,但是可能和导入的 mybatis 相比版本什么的有点不同,建议按照黑马的来

    @Lombok 注解
    import lombok.Data;
    注解作用
    @Data为当前类的所有属性添加 getter & setter 方法

    Lombok POM 依赖

    该依赖已经在父工程 pom.xml 中的 dependencies 标签下被引入,在该标签下被引入的依赖会在所有子模块中生效,因此无需再次引入

    1. <dependency>
    2. <groupId>org.projectlombokgroupId>
    3. <artifactId>lombokartifactId>
    4. <version>${lombok.version}version>
    5. <scope>providedscope>
    6. dependency>

    二、用户 service 微服务搭建

    微服务搭建口诀:建 module、改 pom、写 yml、主启动、业务类(不绝对,仅提供参考)

    1.在heima-leadnews-service 模块底下的 pom.xml 中添加所有微服务公用的依赖

    黑马提供的初始工程中已经添加好了,不必再次添加

    引入其他子模块
    1. <dependency>
    2. <groupId>com.heimagroupId>
    3. <artifactId>heima-leadnews-modelartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>com.heimagroupId>
    7. <artifactId>heima-leadnews-commonartifactId>
    8. dependency>
    9. <dependency>
    10. <groupId>com.heimagroupId>
    11. <artifactId>heima-leadnews-feign-apiartifactId>
    12. dependency>
    依赖作用
    heima-leadnews-model导入实体类等,如 ApUser
    heima-leadnews-common导入全局配置、全局功能等,如全局异常捕获
    heima-leadnews-feign-api导入该模块中统一管理的 feign 远程调用接口(一般微服务之间的调用,都通过 feign 来调用)
    引入开发和测试相关依赖
    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-webartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.springframework.bootgroupId>
    7. <artifactId>spring-boot-starter-testartifactId>
    8. <scope>testscope>
    9. dependency>
    依赖作用
    spring-boot-starter-web

    包含 @Controller、@Service、@RequestMapping 等 web 相关注解,实现相关 web 功能

    spring-boot-starter-test包含 @SpringBootTest、@Test 等测试相关注解,实现相关测试功能

    SpringBoot 把许多场景抽象为了启动器 starter。当前我们需要一个搭建 web 服务的应用场景,因此使用 starter-web 启动器,即可自动完成 web 服务的所有相关配置(使用默认值),如将使用了 @Bean 注解的类注册进 spring 容器、将内置的 tomcat 配置好等等。与 Spring 相比方便了许多,减少了大量配置文件、配置操作

    引入 nacos 服务注册、配置中心相关依赖
    1. <dependency>
    2. <groupId>com.alibaba.cloudgroupId>
    3. <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>com.alibaba.cloudgroupId>
    7. <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
    8. dependency>
    依赖作用
    spring-cloud-starter-alibaba-nacos-discovery

    实现 nacos 相关服务发现、服务注册等功能

    spring-cloud-starter-alibaba-nacos-config实现 nacos 相关配置拉取功能

    注册中心

    在微服务架构中,会有某些服务需要由许多机器/容器来提供,而每个机器/容器的 url 都是不一样的。因此我们需要通过服务注册中心将不同的 url 注册到同一个服务名底下,之后再调用该服务名的时候,会从注册中心将服务注册表(包含着 url 与服务名之间映射关系)拉取到本地,然后 nacos 内置的 Ribbon 会将服务名转化为具体的 url(默认轮询,这次是第一个 url,下次就轮到另一个 url),再进行服务调用

    配置中心

    由于在微服务架构下有许许多多的服务,因此也会产生许许多多的配置文件,每个配置文件也会有许许多多的配置项。当有需求变更时,修改起配置来十分头疼,管理起来也很混乱,很容易改错配置,开发环境直接成为修罗炼狱。而配置中心可以让项目中的配置全部统一管理,并且还可以实现动态修改、运行环境区分(开发、测试、生产等)、配置回滚等功能,很好的解决上述问题

    2.在 heima-leadnews-service 模块底下创建 heima-leadnews-user 子模块

    黑马提供初始项目中已经建好了的,不必再重建

    3.修改 heima-leadnews-user 底下的 pom.xml

    由于我们已经在 heima-leadnews-user 的父工程 heima-leadnews-service 中引入了必要的依赖,因此这里暂时不需要添加依赖

    1. "1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <parent>
    6. <artifactId>heima-leadnews-serviceartifactId>
    7. <groupId>com.heimagroupId>
    8. <version>1.0-SNAPSHOTversion>
    9. parent>
    10. <modelVersion>4.0.0modelVersion>
    11. <artifactId>heima-leadnews-userartifactId>
    12. <properties>
    13. <maven.compiler.source>8maven.compiler.source>
    14. <maven.compiler.target>8maven.compiler.target>
    15. properties>
    16. project>

    4.编写 heima-leadnews-user 的 yml 配置

    在 heima-leadnews-user 底下的 resources 目录中新建 bootstrap.yml 文件

    SpringBoot 核心配置文件有两种:bootstrap 和 application

    bootstrap

    bootstrap 是应用程序的父上下文,由父 ApplicationContext 加载,优先级更高。一般用来做系统层级的参数配置

    application

    application 是当前应用程序的上下文,优先级较低。一般用来做应用层级的参数配置

    为什么用 bootstrap

    由于 Spring 根据优先级会先加载 bootstrap 中 nacos 服务的 url(没有就先使用 nacos 默认的),并进行连接尝试,如果你的 nacos 不在默认的 url 上,那么就会报错。因此一定要在 bootstrap 上做好配置,避免报错,才能启动成功

    编写 bootstarp 文件的内容
    1. server:
    2. port: 51801
    3. spring:
    4. application:
    5. name: leadnews-user
    6. cloud:
    7. nacos:
    8. discovery:
    9. server-addr: localhost:8848
    10. config:
    11. server-addr: localhost:8848
    12. file-extension: yml
    13. profiles:
    14. active: dev

    注意使用自己的 nacos 服务地址

    nacos 下载 

    Releases · alibaba/nacos · GitHub

    通过 GitHub 下载,可以自行选择版本(我当时下载的是 1.1.4),点击版本下的 Assets 中的 nacos-server-1.1.4.zip(不论 tar.gz/zip 在 linux 和 windows 都能使用,应该)

    https://github.com/alibaba/nacos/releases/download/1.1.4/nacos-server-1.1.4.zip

    nacos 启动

    解压后双击 bin/startup.cmd 启动 nacos(默认端口为 8848)

    在 nocos 配置中心添加 heima-leadnews-user 的服务配置

    左边导航栏选择配置列表,点击列表右上方的加号,添加配置

    Data ID = spring.application.name (即 leadnews-user) + "-" + spring.profiles.active (即 dev) + "." + spring.cloud.nacos.config.file-extension (即 yml) = leadnews-user-dev.yml

    1. spring:
    2. datasource:
    3. driver-class-name: com.mysql.jdbc.Driver
    4. url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    5. username: root
    6. password: 159357
    7. # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
    8. mybatis-plus:
    9. mapper-locations: classpath*:mapper/*.xml
    10. # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
    11. type-aliases-package: com.heima.model.user.pojos

    5.完善 heima-leadnews-user 的主启动类 UserApplication

    创建 com.heima.user.UserApplication 主启动类(黑马提供的初始工程也建好了)

    1. package com.heima.user;
    2. import org.mybatis.spring.annotation.MapperScan;
    3. import org.springframework.boot.SpringApplication;
    4. import org.springframework.boot.autoconfigure.SpringBootApplication;
    5. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    6. @SpringBootApplication
    7. @EnableDiscoveryClient
    8. @MapperScan("com.heima.user.mapper")
    9. public class UserApplication {
    10. public static void main(String[] args) {
    11. SpringApplication.run(UserApplication.class,args);
    12. }
    13. }
    @SpringBootApplication 注解

    用来标记当前类为 SpringBoot 的主配置类,SpringBoot 应用由当前类的 main 方法启动,当 mian 方法执行下面这条代码的时候,

    SpringApplication.run(UserApplication.class,args);

    SpringBoot 将会把当前包底下的 @Bean 方法返回的对象以及 @Component 类的实例化对象自动存放入 Spring 容器中,并且将所有的配置项以默认值自动装配(约定大于配置)

    @EnableDiscoveryClient 注解

    该注解是由 SpringBoot 提供的,拥有服务注册与订阅的相关功能。使用该注解后,SpringBoot 应用会自动向配置的服务注册中心注册自己,并定时发送心跳包告知注册中心自己的服务状态

    @MapperScan 注解

    由 heima-leadnews-model 中的 MyBatis 依赖所提供的注解,可以自动扫描指定包下的类,将扫描到的接口作为 Mapper 并将其实现。有了这个注解可以不必再 XXXMapper 接口上再写 @Mapper 注解了。如果当前服务有许多的 Mapper 接口,那每一个接口上都要写 @Mapper 注解,就会有许多的 @Mapper 注解。如果使用 @MapperScan 注解,即可用一个 @MapperScan 注解代替大量 @Mapper 注解

    6.构建 heima-leadnews-user 的微服务目录

    config、controller、service、mapper

    config

    存放配置相关类

    controller

    为前端提供访问接口,不关心具体业务逻辑,只负责接收参数和返回结果

    service

    向 contrller 提供业务逻辑接口,serviceImpl 负责将接口中的方法实现

    mapper

    向 service 提供操作持久层数据的接口,mapper.xml 负责实现接口

    为什么这样设计

    这种设计方式体现了一种思想 MVC(即 model + view + controller)

    分层作用
    view从 controller 获取数据,并展示给用户
    controller接收 view 的请求,让 model 进行处理,并返回处理结果给 view
    model操作数据完成业务,将结果返回给 controller

    view 对应项目中的 前端,controller 对应项目中的 controller,model 对应项目中的 service + controller。controller 就如一个中介者,封装 view 和 model 之间的交互,松散耦合,集中控制交互。而 service 与 serviceIpml 分开,mapper 与 mapper.xml 分开,体现了依赖倒转,使调用方不依赖于实现,而是依赖于抽象接口

    7.开发 heima-leadnews-user 登录功能相关的 controller 层代码

    在 controller 下新建 v1 包,表示第一个版本

    在 v1 包底下新建 ApUserLoginController 类

    登录接口
    接口路径/api/v1/login/login_auth
    请求方式POST
    参数LoginDto
    响应结果ResponseResult

    LoginDto

    在 heima-leadnews-model 模块下的 com.heima.model.user.dtos 包下创建 LoginDto

    1. @Data
    2. public class LoginDto {
    3. /**
    4. * 手机号
    5. */
    6. private String phone;
    7. /**
    8. * 密码
    9. */
    10. private String password;
    11. }

    ResponseResult

    在 heima-leadnews-model 中的 com.heima.model.common.dtos 包底下创建 ResponseResult 类

    1. /**
    2. * 通用的结果返回类
    3. * @param
    4. */
    5. public class ResponseResult implements Serializable {
    6. private String host;
    7. private Integer code;
    8. private String errorMessage;
    9. private T data;
    10. public ResponseResult() {
    11. this.code = 200;
    12. }
    13. public ResponseResult(Integer code, T data) {
    14. this.code = code;
    15. this.data = data;
    16. }
    17. public ResponseResult(Integer code, String msg, T data) {
    18. this.code = code;
    19. this.errorMessage = msg;
    20. this.data = data;
    21. }
    22. public ResponseResult(Integer code, String msg) {
    23. this.code = code;
    24. this.errorMessage = msg;
    25. }
    26. public static ResponseResult errorResult(int code, String msg) {
    27. ResponseResult result = new ResponseResult();
    28. return result.error(code, msg);
    29. }
    30. public static ResponseResult okResult(int code, String msg) {
    31. ResponseResult result = new ResponseResult();
    32. return result.ok(code, null, msg);
    33. }
    34. public static ResponseResult okResult(Object data) {
    35. ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
    36. if(data!=null) {
    37. result.setData(data);
    38. }
    39. return result;
    40. }
    41. public static ResponseResult errorResult(AppHttpCodeEnum enums){
    42. return setAppHttpCodeEnum(enums,enums.getErrorMessage());
    43. }
    44. public static ResponseResult errorResult(AppHttpCodeEnum enums, String errorMessage){
    45. return setAppHttpCodeEnum(enums,errorMessage);
    46. }
    47. public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
    48. return okResult(enums.getCode(),enums.getErrorMessage());
    49. }
    50. private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
    51. return okResult(enums.getCode(),errorMessage);
    52. }
    53. public ResponseResult error(Integer code, String msg) {
    54. this.code = code;
    55. this.errorMessage = msg;
    56. return this;
    57. }
    58. public ResponseResult ok(Integer code, T data) {
    59. this.code = code;
    60. this.data = data;
    61. return this;
    62. }
    63. public ResponseResult ok(Integer code, T data, String msg) {
    64. this.code = code;
    65. this.data = data;
    66. this.errorMessage = msg;
    67. return this;
    68. }
    69. public ResponseResult ok(T data) {
    70. this.data = data;
    71. return this;
    72. }
    73. public Integer getCode() {
    74. return code;
    75. }
    76. public void setCode(Integer code) {
    77. this.code = code;
    78. }
    79. public String getErrorMessage() {
    80. return errorMessage;
    81. }
    82. public void setErrorMessage(String errorMessage) {
    83. this.errorMessage = errorMessage;
    84. }
    85. public T getData() {
    86. return data;
    87. }
    88. public void setData(T data) {
    89. this.data = data;
    90. }
    91. public String getHost() {
    92. return host;
    93. }
    94. public void setHost(String host) {
    95. this.host = host;
    96. }
    97. }

    AppHttpCodeEnum

    在 heima-leadnews-model 模块中的 com.heima.model.common.enums 包下新建 AppHttpCodeEnum 类

    1. public enum AppHttpCodeEnum {
    2. // 成功段固定为200
    3. SUCCESS(200,"操作成功"),
    4. // 登录段1~50
    5. NEED_LOGIN(1,"需要登录后操作"),
    6. LOGIN_PASSWORD_ERROR(2,"密码错误"),
    7. // TOKEN50~100
    8. TOKEN_INVALID(50,"无效的TOKEN"),
    9. TOKEN_EXPIRE(51,"TOKEN已过期"),
    10. TOKEN_REQUIRE(52,"TOKEN是必须的"),
    11. // SIGN验签 100~120
    12. SIGN_INVALID(100,"无效的SIGN"),
    13. SIG_TIMEOUT(101,"SIGN已过期"),
    14. // 参数错误 500~1000
    15. PARAM_REQUIRE(500,"缺少参数"),
    16. PARAM_INVALID(501,"无效参数"),
    17. PARAM_IMAGE_FORMAT_ERROR(502,"图片格式有误"),
    18. SERVER_ERROR(503,"服务器内部错误"),
    19. // 数据错误 1000~2000
    20. DATA_EXIST(1000,"数据已经存在"),
    21. AP_USER_DATA_NOT_EXIST(1001,"ApUser数据不存在"),
    22. DATA_NOT_EXIST(1002,"数据不存在"),
    23. // 数据错误 3000~3500
    24. NO_OPERATOR_AUTH(3000,"无权限操作"),
    25. NEED_ADMIND(3001,"需要管理员权限");
    26. int code;
    27. String errorMessage;
    28. AppHttpCodeEnum(int code, String errorMessage){
    29. this.code = code;
    30. this.errorMessage = errorMessage;
    31. }
    32. public int getCode() {
    33. return code;
    34. }
    35. public String getErrorMessage() {
    36. return errorMessage;
    37. }
    38. }
    ApUserLoginController 代码
    1. @RestController
    2. @RequestMapping("/api/v1/login")
    3. public class ApUserLoginController {
    4. @Autowired
    5. private ApUserService apUserService;
    6. @PostMapping("/login_auth")
    7. public ResponseResult login(@RequestBody LoginDto dto){
    8. return apUserService.login(dto);
    9. }
    10. }

    @RestController 注解

    1. @Target({ElementType.TYPE})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. @Controller
    5. @ResponseBody
    6. public @interface RestController {
    7. @AliasFor(
    8. annotation = Controller.class
    9. )
    10. String value() default "";
    11. }

    本质是个复合注解,即 @RestController = @Controller + @ResponseBody

    注解作用
    @Target

    规定注解的使用范围(

    ElementType.TYPE 类注解,

    ElementType.FIELD 属性注解,

    ElementType.METHOD 方法注解...

    @Retention

    规定注释的有效时期(

    RetentionPolicy.SOUTCE 代码注解,

    RetenionPolicy.CLASS 字节码注解,

    RetentionPolicy.RUNTIME 运行注解...

    @Documented

    将该注解写入 JAVADOC 中

    @Inherited使用该注解的类的子类,可以继承该注解
    注解作用
    @Controller包含 @Component 注解,可以将使用该注解的类的实例化对象注册到 Spring IOC 容器中
    @ResponseBody将方法的返回值直接写入 HTTP Response 的 body 中

    @RequestMapping 注解

    当请求路径与 @RequestMapping 的 value 值一致时,对请求进行路由,路由到当前类或方法

    @AutoWired 注解

    自动装载,将注册进容器中的 ApUserService 类的实例化对象取出并注入到当前的 ApUserController 类中。为 ApUserController 提供业务方法(ApUserService 还没建好,可以先不注入)

    @RequestBody 与 @RequestParam 的区别

    @RequestBody 发送

    @RequestBody 接收

    1. @RestController
    2. public class TestController {
    3. @RequestMapping(value = "/test")
    4. public void test(@RequestBody String body) {
    5. System.out.println(body);
    6. }
    7. }

    @RequestParam 发送

    第一种 query params

    第二种 body form-data

    @RequestParam 接收

    1. @RestController
    2. public class TestController {
    3. @RequestMapping(value = "/test")
    4. public void test(@RequestParam String param) {
    5. System.out.println(param);
    6. }
    7. }

    8.开发 heima-leadnews-user 登录功能相关的 service 层代码

    在 service 下新建 apUserService 接口

    1. public interface ApUserService extends IService {
    2. /**
    3. * app端登录功能
    4. * @param dto
    5. * @return
    6. */
    7. public ResponseResult login(LoginDto dto);
    8. }

    IService 是由 MyBatis Plus 提供的一个接口,MyBatis 也提供了对应的实现类 ServiceImpl

    ServiceImpl 类注入了 baseMapper 接口,而 MyBatis Plus 也实现了该接口

    因此我们可以使用 ServiceImpl 中的丰富的基础数据处理方法间接调用 mapper 来进行数据库操作

    在 service 下新建 impl 包并创建 ApUserServiceImpl 类实现 ApUserService 接口

    1. @Service
    2. @Transactional
    3. @Slf4j
    4. public class ApUserServiceImpl extends ServiceImpl implements ApUserService {
    5. /**
    6. * app端登录功能
    7. * @param dto
    8. * @return
    9. */
    10. @Override
    11. public ResponseResult login(LoginDto dto) {
    12. //1.正常登录 用户名和密码
    13. if(StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())){
    14. //1.1 根据手机号查询用户信息
    15. ApUser dbUser = getOne(Wrappers.lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
    16. if(dbUser == null){
    17. return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");
    18. }
    19. //1.2 比对密码
    20. String salt = dbUser.getSalt();
    21. String password = dto.getPassword();
    22. String pswd = DigestUtils.md5DigestAsHex((password + salt).getBytes());
    23. if(!pswd.equals(dbUser.getPassword())){
    24. return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
    25. }
    26. //1.3 返回数据 jwt user
    27. String token = AppJwtUtil.getToken(dbUser.getId().longValue());
    28. Map map = new HashMap<>();
    29. map.put("token",token);
    30. dbUser.setSalt("");
    31. dbUser.setPassword("");
    32. map.put("user",dbUser);
    33. return ResponseResult.okResult(map);
    34. }else {
    35. //2.游客登录
    36. Map map = new HashMap<>();
    37. map.put("token",AppJwtUtil.getToken(0L));
    38. return ResponseResult.okResult(map);
    39. }
    40. }
    41. }
    判断用户名和密码是否为空

    使用 StringUtils 的 isNotBlank 方法判断字符串是否为空、是否有长度、是否含有空白字符

    1. if(StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())) {
    2. // 1.正常登录
    3. } else {
    4. // 2.游客登录
    5. Map map = new HashMap<>();
    6. map.put("token",AppJwtUtil.getToken(0L));
    7. return ResponseResult.okResult(map);
    8. }

    游客登陆返回

    1. {
    2. "host": "",
    3. "code": 200, // AppHttpCodeEnum.SUCCESS
    4. "errorMessage": "操作成功",
    5. "data": {
    6. "token": // 使用 AppJwtUtil.getToken(0L) 方法生成游客 token,具体实现下面会讲
    7. }
    8. }
    判断该用户是否存在

    使用 ServiceImpl 中的 getOne 方法,用 Wrapper 构造查询条件( dbUser.getPhone() 等与 dto.getPhone)

    1. //1.1 根据手机号查询用户信息
    2. ApUser dbUser = getOne(Wrappers.lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
    3. if(dbUser == null){
    4. return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");
    5. }

    不存在返回

    1. {
    2. "host": "",
    3. "code": 1002, // AppHttpCodeEnum.DATA_NOT_EXIST
    4. "errorMessage": "用户信息不存在",
    5. "data": {}
    6. }
    判断密码是否正确

    数据库中的密码加密是通过先加盐再MD5的方式完成的

    MD5 为一种单向加密算法,只能加密不能解密。因此很适合用来加密密码,这样连数据库所有者都无法知道用户的真实密码,大大提高安全性。但是既然这么安全为什么还要加盐呢?因为MD5的广泛使用,黑客认为有利可图,于是使用暴力学习,将许多密码进行 MD5 加密,并将映射关系存起来,这样就能用映射表,将 MD5 加密后的密码转为真实的密码。所以,我们通过加盐,也就是给原有的密码加上其他的字符/字符串再进行 MD5 加密,使得暴力解密后的密码也不是真正的密码

    使用 org.springframework.util 中的 DigestUtils 类里的 md5DigestAsHex 方法对 用户输入的密码 + 数据库中获取的盐值 进行加密,然后将加密后的输入密码与数据库中的比较

    1. //1.2 比对密码
    2. String salt = dbUser.getSalt();
    3. String password = dto.getPassword();
    4. String pswd = DigestUtils.md5DigestAsHex((password + salt).getBytes());
    5. if(!pswd.equals(dbUser.getPassword())){
    6. return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
    7. }

    密码错误返回

    1. {
    2. "host": "",
    3. "code": 2, // AppHttpCodeEnum.LOGIN_PASSWORD_ERROR
    4. "errorMessage": "密码错误",
    5. "data": {
    6. "token": // 使用 AppJwtUtil.getToken(0L) 方法生成游客 token,具体实现下面会讲
    7. }
    8. }
    生成 JWT

    JWT,即 Json Web Token。它由三部分组成

    header

    JWT头

    一个 JSON 对象用来描述 JWT 的签名算法、令牌类型

    payload

    JWT载荷

    一个 JSON 对象用来传递数据

    signature

    JWT签名

    使用 base64 加密后的 header + base64 加密后的 payload + 服务器生成的密钥,通过指定算法生成签名

    形如 headerheaderheader.payloadpayloadpayload.signaturesignaturesignature

    即 header 与 payload 与 signature 之间使用 '.' 点号隔开

    eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQrDMAzA_uJzA_biuKa_cWnKUigEnMLG2N_nHnaTEPrAMRosUFhUmCmZUEmsImnN-kgZ90q2Z1EWmKDZgIVmRBRS1gn8WuP2t4963t099FnbaWF2bWHWe3B99f9Z-D5bNPz-AKKQJrqAAAAA.0ZNpu7dGgNtoF-UBGM3LvFZxEsltZFHazfByij_IW5KIv82oNxV4VremFE4zAx_lFAxE2XuOZ53LGb1u2mY7Lg

    由于 base64 可以解密,因此不要在 payload 中存放重要的隐私信息

    为什么需要 JWT

    JWT 用来存储客户端与服务器之间的身份识别信息,相当于是存会话信息。既然是会话,为什么不用 session 呢?因为 session 依赖于 cookie,session 中会存放用户信息,浏览器的 cookie 中会存 session 的 id,每次浏览器发送请求的时候就会携带 cookie,这样也能将 cookie 中的 sessionID 发送给服务器,这样服务器就能识别出这个请求时哪个用户发出的。可是有些客户端并没有 cookie,因此也无法存储 sessionID,于是我们就使用 JWT,客户端发送请求的时候带上 JWT,而 JWT 中又有用户的信息,这样服务器也能识别出请求的来源。抛弃掉 session cookie,实现前后端分离

    在 heima-leadnews-utils 模块下创建 com.heima.utils.common.AppJwtUtil 类

    1. public class AppJwtUtil {
    2. // TOKEN的有效期一天(S)
    3. private static final int TOKEN_TIME_OUT = 3_600;
    4. // 加密KEY
    5. private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
    6. // 最小刷新间隔(S)
    7. private static final int REFRESH_TIME = 300;
    8. // 生产ID
    9. public static String getToken(Long id){
    10. Map claimMaps = new HashMap<>();
    11. claimMaps.put("id",id);
    12. long currentTime = System.currentTimeMillis();
    13. return Jwts.builder()
    14. .setId(UUID.randomUUID().toString())
    15. .setIssuedAt(new Date(currentTime)) //签发时间
    16. .setSubject("system") //说明
    17. .setIssuer("heima") //签发者信息
    18. .setAudience("app") //接收用户
    19. .compressWith(CompressionCodecs.GZIP) //数据压缩方式
    20. .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
    21. .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
    22. .addClaims(claimMaps) //cla信息
    23. .compact();
    24. }
    25. /**
    26. * 获取token中的claims信息
    27. *
    28. * @param token
    29. * @return
    30. */
    31. private static Jws getJws(String token) {
    32. return Jwts.parser()
    33. .setSigningKey(generalKey())
    34. .parseClaimsJws(token);
    35. }
    36. /**
    37. * 获取payload body信息
    38. *
    39. * @param token
    40. * @return
    41. */
    42. public static Claims getClaimsBody(String token) {
    43. try {
    44. return getJws(token).getBody();
    45. }catch (ExpiredJwtException e){
    46. return null;
    47. }
    48. }
    49. /**
    50. * 获取hearder body信息
    51. *
    52. * @param token
    53. * @return
    54. */
    55. public static JwsHeader getHeaderBody(String token) {
    56. return getJws(token).getHeader();
    57. }
    58. /**
    59. * 是否过期
    60. *
    61. * @param claims
    62. * @return -1:有效,0:有效,1:过期,2:过期
    63. */
    64. public static int verifyToken(Claims claims) {
    65. if(claims==null){
    66. return 1;
    67. }
    68. try {
    69. claims.getExpiration()
    70. .before(new Date());
    71. // 需要自动刷新TOKEN
    72. if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
    73. return -1;
    74. }else {
    75. return 0;
    76. }
    77. } catch (ExpiredJwtException ex) {
    78. return 1;
    79. }catch (Exception e){
    80. return 2;
    81. }
    82. }
    83. /**
    84. * 由字符串生成加密key
    85. *
    86. * @return
    87. */
    88. public static SecretKey generalKey() {
    89. byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
    90. SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
    91. return key;
    92. }
    93. }

    生成过程

    1. //1.3 返回数据 jwt user
    2. String token = AppJwtUtil.getToken(dbUser.getId().longValue());
    3. Map map = new HashMap<>();
    4. map.put("token",token);
    5. dbUser.setSalt("");
    6. dbUser.setPassword("");
    7. map.put("user",dbUser);
    8. return ResponseResult.okResult(map);
    1. public static String getToken(Long id){
    2. Map claimMaps = new HashMap<>();
    3. claimMaps.put("id",id);
    4. long currentTime = System.currentTimeMillis();
    5. return Jwts.builder()
    6. .setId(UUID.randomUUID().toString())
    7. .setIssuedAt(new Date(currentTime)) //签发时间
    8. .setSubject("system") //说明
    9. .setIssuer("heima") //签发者信息
    10. .setAudience("app") //接收用户
    11. .compressWith(CompressionCodecs.GZIP) //数据压缩方式
    12. .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
    13. .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
    14. .addClaims(claimMaps) //cla信息
    15. .compact();
    16. }

    使用 jjwt 依赖提供的 jsonwebtoken 包中的 Jwts.builder 方法构建 jwt,并将用户 id 存放在 jwt 的 payload 中,从而让服务器能够识别请求的发送者

    三、测试接口

    1. {
    2. "host": null,
    3. "code": 200,
    4. "errorMessage": "操作成功",
    5. "data": {
    6. "user": {
    7. "id": 4,
    8. "salt": "",
    9. "name": "admin",
    10. "password": "",
    11. "phone": "13511223456",
    12. "image": null,
    13. "sex": true,
    14. "certification": null,
    15. "identityAuthentication": null,
    16. "status": true,
    17. "flag": 1,
    18. "createdTime": "2020-03-30T08:36:32.000+00:00"
    19. },
    20. "token": "eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWL0QrDIAwA_yXPFZLqau3fpBqZg4IQCxuj_770YW93HPeF12iwAfqEiWRxNefiAu-rYx-z48dMNZKUtSaYoPGAjSIizcHjMoGeu9360SHH3VVNn9IONuOzmHHvxvLu_zOm-2zWwvUD7al0KoAAAAA.5GEoiqO9MH7WCZeBWM95XAlAtlrkHvrbzUqZOO0HktuJjcCCJ20MVprJjXXa-DuNY9qdHIy0yt7Z1ziaflTHUw"
    21. }
    22. }

    基础用户登录功能完成

  • 相关阅读:
    【Hack The Box】Linux练习-- Forge
    【ES6】学习笔记:正则扩展
    mysql8.0数据库中explain查询及优化方案
    NameNode 和 SecondaryNameNode 工作机制
    四十四、模板层
    人类小脑内在组织背后的基因图谱
    每 日 练 习
    MES如何提升企业数字化能力?
    UDS - 深论Security Access Service
    【HDLBits 刷题 10】Circuits(6)Finite State Manchines 10-17
  • 原文地址:https://blog.csdn.net/m0_61213696/article/details/134199157