乐优商城(11)–用户中心

一、创建用户中心

用户搜索到自己心仪的商品,接下来就要去购买,但是购买必须先登录。所以要创建用户中心,实现用户的登录和注册功能。

用户中心的提供的服务:

  • 用户的注册
  • 用户登录
  • 用户个人信息管理
  • 用户地址管理
  • 用户收藏管理
  • 我的订单
  • 优惠券管理

这里暂时先实现基本的功能:登录和注册。

因为用户中心的服务其它微服务也会调用,因此这里需要做聚合。

leyou-user:父工程,包含2个子工程:

  • leyou-user-interface:实体及接口

  • leyou-user-service:业务和服务

1.1、创建父module

1.2、创建leyou-user-interface

在leyou-user下,创建module:

1.3、创建leyou-user-service

pom文件:

<?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>leyou-user</artifactId><groupId>com.leyou.user</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>leyou-user-service</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- mybatis启动器 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!-- 通用Mapper启动器 --><dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId></dependency><!-- mysql驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.leyou.user</groupId><artifactId>leyou-user-interface</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies></project>

启动类:

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.leyou.user.mapper")
public class LeyouUserApplication {public static void main(String[] args) {SpringApplication.run(LeyouUserApplication.class,args);}
}

配置文件

server:port: 8085
spring:application:name: user-servicedatasource:url: jdbc:mysql://127.0.0.1:3306/leyouusername: rootpassword: 123456hikari:max-lifetime: 28830000 # 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL wait_timeout参数(show variables like '%timeout%';)maximum-pool-size: 9 # 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count)driver-class-name: com.mysql.jdbc.Drivercloud:nacos:discovery:server-addr: ip地址:8848username: nacospassword: nacos
mybatis:type-aliases-package: com.leyou.user.pojo

1.4、添加网关路由

修改leyou-gateway,添加路由规则,对leyou-user-service进行路由:

二、后台功能准备

2.1、数据库表结构

CREATE TABLE `tb_user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(32) NOT NULL COMMENT '密码,加密存储',`phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',`created` datetime NOT NULL COMMENT '创建时间',`salt` varchar(32) NOT NULL COMMENT '密码加密的salt值',PRIMARY KEY (`id`),UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8 COMMENT='用户表';

数据结构比较简单,因为根据用户名查询的频率较高,所以给用户名创建了索引

2.2、基本代码

2.2.1、实体类

@Table(name = "tb_user")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;// 用户名@JsonIgnoreprivate String password;// 密码private String phone;// 电话private Date created;// 创建时间@JsonIgnoreprivate String salt;// 密码的盐值
}

注意:出于安全考虑。这里对password和salt添加了注解@JsonIgnore,这样在json序列化时,就不会把password和salt返回。

2.2.2、mapper

/*** User 的通用 mapper*/
public interface UserMapper extends Mapper<User> {}

2.2.3、Service

public interface UserService {}

实现类:

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;
}

2.2.4、controller

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;}

三、数据验证功能

3.1、接口说明

实现用户数据的校验,主要包括对:电子邮箱、用户名的唯一性校验。

接口路径:

GET /check/{data}/{type}

参数说明:

参数 说明 是否必须 数据类型 默认值
data 要校验的数据 String
type 要校验的数据类型:1,用户名;2,电子邮箱; Integer 1

返回结果:

返回布尔类型结果:

  • true:可用
  • false:不可用

状态码:

  • 200:校验成功
  • 400:参数有误
  • 500:服务器内部异常

3.2、controller

因为有了接口,可以不关心页面,所有需要的东西都一清二楚:

  • 请求方式:GET
  • 请求路径:/check/{param}/{type}
  • 请求参数:param,type
  • 返回结果:true或false
/*** 用户注册时电子邮箱、用户名的唯一性校验* @param data* @param type* @return*/
@GetMapping("/check/{data}/{type}")
public ResponseEntity<Boolean> checkUserData(@PathVariable("data") String data,@PathVariable("type") Integer type){Boolean bool = this.userService.checkUserData(data,type);if (null == bool) return ResponseEntity.badRequest().build();return ResponseEntity.ok(bool);
}

3.3、Service

/*** 对电子邮箱、用户名的唯一性校验* @param data* @param type* @return*/
Boolean checkUserData(String data, Integer type);

实现类:

/*** 对电子邮箱、用户名的唯一性校验** @param data* @param type* @return*/
@Override
public Boolean checkUserData(String data, Integer type) {User user = new User();if (type == 1){user.setUsername(data);}else if (type == 2){user.setPhone(data);}else {return null;}//为0说明数据库中不存在该数据,可以注册return this.userMapper.selectCount(user) == 0;
}

3.4、测试

在数据库插入一条数据:

然后在浏览器调用接口,测试:

四、邮件服务

4.1、创建邮件微服务

因为系统中不止注册一个地方需要邮件发送,因此将邮件发送抽取为微服务:leyou-sms-service,凡是需要的地方都可以使用。

另外,因为邮件发送API调用时长的不确定性,为了提高程序的响应速度,短信发送将采用异步发送方式,即:

  • 邮件服务监听MQ消息,收到消息后发送短信。
  • 其它服务要发送短信时,通过MQ通知短信微服务。

注:阿里云短信服务个人不可用,申请需要提供相关证明资料,这里使用邮件服务替代

4.1.1、开启QQ邮件服务

  1. 开启QQPOP3/SMTP服务

  2. 按提示完成相关操作后会得到一份授权码,妥善保存

  3. 在配置文件中添加配置

4.1.2、创建module

4.1.3、pom文件

<?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>myLeyou</artifactId><groupId>com.leyou.parent</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><groupId>com.leyou.sms</groupId><artifactId>leyou-sms-service</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency></dependencies></project>

4.1.4、编写启动类

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

4.1.5、编写application.yml

server:port: 8086
spring:application:name: sms-servicerabbitmq:host: 192.168.56.101username: leyoupassword: leyouvirtual-host: /leyoumail:host: smtp.qq.comusername: xxxxxxxxx@qq.com #自己的邮箱地址password: xxxxxxxxxxxxx #邮箱授权码default-encoding: UTF-8protocol: smtpport: 25

4.2、编写邮件工具类

package com.leyou.sms.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;@Component
public class SmsUtils {@Autowiredprivate JavaMailSender javaMailSender;/*** 给用户的邮箱发送验证码* @param email* @param code* @throws MessagingException*/public void sendMailMessage(String email,String code) throws MessagingException {MimeMessage mimeMessage = this.javaMailSender.createMimeMessage();MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);helper.setSubject("【乐优商城】验证");helper.setText("<p>您好 [" + email + "]</p><p>您的验证码为 "+code+"。</p>" +"<p>验证码5分钟内有效!</p>" +"<p>如非本人操作,请忽略本邮件</p>" +"<p>[本邮件由系统自动发送,请勿直接回复]</p>",true);helper.setFrom("xxxxxxxxx@qq.com"); //这里填写开启邮件服务的QQ号码helper.setTo(email);this.javaMailSender.send(mimeMessage);}
}

4.3、编写消息监听器

接下来,编写消息监听器,当接收到消息后,发送短信。

@Component
public class SmsListener {@Autowiredprivate SmsUtils smsUtils;@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "leyou.sms.queue",durable = "true"),exchange = @Exchange(value = "leyou.sms.exchange",ignoreDeclarationExceptions = "true"),key = {"sms.verify.code"}))public void listenSms(Map<String,String> msg) throws MessagingException {//如果msg为空 不处理if (msg == null || msg.size() <= 0) return;String email = msg.get("email");String code = msg.get("code");//email 或者 验证码为空  不处理if (StringUtils.isEmpty(email) || StringUtils.isEmpty(code)) return;//发送邮箱验证码this.smsUtils.sendMailMessage(email,code);}
}

测试

启动项目,然后查看RabbitMQ控制台,发现交换机已经创建:

五、发送邮件功能

5.1、生成邮件验证码

功能说明

根据用户输入的电子邮箱,生成随机验证码,长度为6位,纯数字。并且调用邮件服务,发送验证码到用户的电子邮箱。

接口路径

POST /code

参数说明:

参数 说明 是否必须 数据类型 默认值
phone 用户的手机号码 String

**返回结果:**无

状态码:

  • 204:请求已接收
  • 400:参数有误
  • 500:服务器内部异常

业务逻辑:

  • 1)接收页面发送来的手机号码
  • 2)生成一个随机验证码
  • 3)将验证码保存在服务端
  • 4)发送短信,将验证码发送到用户手机

那么问题来了:验证码保存在哪里呢?

验证码有一定有效期,一般是5分钟,可以利用Redis的过期机制来保存。

5.2、Redis

5.2.1、下载和安装

官网:https://redis.io/download

  1. 将压缩包上传至/usr/local/leyou

  2. 解压

    cd /usr/local/leyou
    tar -xvf redis-6.0.15.tar.gz
    
  3. 编译安装

    mv redis-6.0.15 redis
    cd redis# 编译安装需要gcc5.3以上,可以用gcc -v 命令查看当前版本号,使用下面的命令升级到gcc9.1:
    yum -y install centos-release-scl
    yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
    #scl命令启用只是临时的,新开的会话默认还是原gcc版本。
    scl enable devtoolset-9 bash#如果编译出错之后再编译可以先执行命令删除之前的编译文件
    make distcleanmake && make install#编译完了可以执行命令测试 需要一定的等待时间
    make test
    
  4. 修改安装目录下的redis.conf文件

    vim redis.conf
    

    修改以下配置:

    #bind 127.0.0.1 # 将这行代码注释,监听所有的ip地址,外网可以访问
    port 8975 #改成8975
    protected-mode no # 把yes改成no,允许外网访问
    daemonize yes # 把no改成yes,后台运行
    
  5. 启动:./src/redis-server redis.conf

  6. 修改端口为8975,不使用默认端口主要原因是会有黑客攻击阿里云服务器,植入挖矿程序,这里改变端口,安全性会提高一些,如果设置密码会更好

  7. 启用客户端连接,测试

注:redis6之后支持多线程

5.3、Spring Data Redis

官网:http://projects.spring.io/spring-data-redis/

Spring Data Redis,是Spring Data 家族的一部分。 对Jedis客户端进行了封装,与spring进行了整合。可以非常方便的来实现对redis的配置和操作。

5.3.1、RedisTemplate基本操作

Spring Data Redis 提供了一个工具类:RedisTemplate。里面封装了对于Redis的五种数据结构的各种操作,包括:

  • redisTemplate.opsForValue() :操作字符串
  • redisTemplate.opsForHash() :操作hash
  • redisTemplate.opsForList():操作list
  • redisTemplate.opsForSet():操作set
  • redisTemplate.opsForZSet():操作zset

其它一些通用命令,如expire,可以通过redisTemplate.xx()来直接调用

5种结构:

  • String:等同于java中的,Map<String,String>
  • list:等同于java中的Map<String,List<String>>
  • set:等同于java中的Map<String,Set<String>>
  • sort_set:可排序的set
  • hash:等同于java中的:`Map<String,Map<String,String>>

5.3.2、StringRedisTemplate

RedisTemplate在创建时,可以指定其泛型类型:

  • K:代表key 的数据类型
  • V: 代表value的数据类型

注意:这里的类型不是Redis中存储的数据类型,而是Java中的数据类型,RedisTemplate会自动将Java类型转为Redis支持的数据类型:字符串、字节、二进制等等。

不过RedisTemplate默认会采用JDK自带的序列化(Serialize)来对对象进行转换。生成的数据十分庞大,因此通常都会指定key和value为String类型,这样就可以把对象序列化为json字符串来存储。

Spring默认提供了这样一个实现: StringRedisTemplate,它是继承RedisTemplate<String,String>

5.4、项目实现

需要三个步骤:

  • 生成随机验证码
  • 将验证码保存到Redis中,用来在注册的时候验证
  • 发送验证码到leyou-sms-service服务,发送短信

引入依赖:

<!--redis依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

添加RabbitMQ和Redis配置:

spring:redis:host: 192.168.56.101port: 8975rabbitmq:virtual-host: /leyouusername: leyoupassword: leyouhost: 192.168.56.101

另外还要用到工具类,生成6位随机码,这个封装到了leyou-common中,因此需要引入依赖:

<dependency><groupId>com.leyou.common</groupId><artifactId>leyou-common</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>

NumberUtils中有生成随机码的工具方法:

/*** 生成指定位数的随机数字* @param len 随机数的位数* @return 生成的随机数*/
public static String generateCode(int len){len = Math.min(len, 8);int min = Double.valueOf(Math.pow(10, len - 1)).intValue();int num = new Random().nextInt(Double.valueOf(Math.pow(10, len + 1)).intValue() - 1) + min;return String.valueOf(num).substring(0,len);
}

UserController

在leyou-user-service工程中的UserController添加方法:

/*** 发送邮箱验证码* @param email* @return*/
@PostMapping("/code")
public ResponseEntity<Void> sendVerifyCode(@RequestParam("phone") String email){Boolean bool = this.userService.sendVerifyCode(email);if (null == bool || !bool){return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();}return ResponseEntity.status(HttpStatus.CREATED).build();
}

UserService

/*** 发送邮箱验证码* @param email* @return*/
Boolean sendVerifyCode(String email);

实现类:

@Autowired
private AmqpTemplate amqpTemplate;@Autowired
private StringRedisTemplate redisTemplate;private final static String KEY_PREFIX = "user:code:email:";private final static Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
/*** 发送邮箱验证码** @param email* @return*/
@Override
public Boolean sendVerifyCode(String email) {//校验email是否为空if(StringUtils.isBlank(email)) return false;//生成邮箱验证码String code = NumberUtils.generateCode(6);try {//将消息发送给mq,发送邮箱验证码Map<String, String> msg = new HashMap<>();msg.put("email",email);msg.put("code",code);this.amqpTemplate.convertAndSend("leyou.sms.exchange","sms.verify.code",msg);//存入redis 并指定过期时间为5分钟this.redisTemplate.opsForValue().set(KEY_PREFIX+email,code,5, TimeUnit.MINUTES);return true;} catch (AmqpException e) {LOGGER.error("发送邮件失败。email:{}, code:{}", email, code);return false;}
}

测试

通过ApiPost发送请求测试:

查看redis中的数据:

查看邮件:

六、注册功能

6.1、接口说明

功能说明

实现用户注册功能,需要对用户密码进行加密存储,使用MD5加密,加密过程中使用随机码作为salt加盐。另外还需要对用户输入的验证码进行校验。

接口路径

POST /register

参数说明:

form表单格式

参数 说明 是否必须 数据类型 默认值
username 用户名,格式为4~30位字母、数字、下划线 String
password 用户密码,格式为4~30位字母、数字、下划线 String
phone 电子邮箱 String
code 邮箱验证码 String

**返回结果:**无返回值。

状态码:

  • 201:注册成功
  • 400:参数有误,注册失败
  • 500:服务器内部异常,注册失败

业务逻辑:

  • 1)校验邮箱验证码
  • 2)生成盐
  • 3)对密码加密
  • 4)写入数据库
  • 5)删除Redis中的验证码

6.2、UserController

/*** 用户注册* @param user* @param code* @return*/
@PostMapping("/register")
public ResponseEntity<Void> register(User user, @RequestParam("code") String code){Boolean bool = this.userService.register(user,code);if (null == bool || !bool){return ResponseEntity.badRequest().build();}return ResponseEntity.status(HttpStatus.CREATED).build();
}

6.3、UserService

/*** 用户注册* @param user* @param code* @return*/
Boolean register(User user, String code);

实现类:

/*** 用户注册** @param user* @param code* @return*/
@Override
public Boolean register(User user, String code) {//从redis获取验证码String cacheCode = this.redisTemplate.opsForValue().get(KEY_PREFIX + user.getPhone());//校验验证码if (!StringUtils.equals(cacheCode,code)) return false;//生成盐String salt = CodeUtils.generateSalt();user.setSlat(salt);//对密码加密String password = CodeUtils.md5Hex(user.getPassword(), salt);//添加用户user.setId(null);user.setCreated(new Date());user.setPassword(password);Boolean bool = this.userMapper.insertSelective(user) == 1;if (bool){//注册成功后,删除该验证码this.redisTemplate.delete(KEY_PREFIX+user.getPhone());}return bool;
}

这里使用了工具类CodeUtils

public class CodeUtils {public static String md5Hex(String data,String salt) {if (StringUtils.isBlank(salt)) {salt = data.hashCode() + "";}return DigestUtils.md5Hex(salt + DigestUtils.md5Hex(data));}public static String shaHex(String data, String salt) {if (StringUtils.isBlank(salt)) {salt = data.hashCode() + "";}return DigestUtils.sha512Hex(salt + DigestUtils.sha512Hex(data));}public static String generateSalt(){return StringUtils.replace(UUID.randomUUID().toString(), "-", "");}
}

6.4、测试

通过ApiPost发送请求测试:

查看数据库:

6.5、服务端数据校验

6.5.1、hibernate-validate

Hibernate Validator是Hibernate提供的一个开源框架,使用注解方式非常方便的实现服务端的数据校验。

官网:http://hibernate.org/validator/

SpringBoot的web启动器中已经集成了相关依赖。

hibernate Validator 是 Bean Validation 的参考实现 。

Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint。

在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。

6.5.2、Bean校验的注解

常用注解如下:

Constraint 详细信息
@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内
@NotBlank 被注释的字符串的必须非空
@URL(protocol=,host=, port=,regexp=, flags=) 被注释的字符串必须是一个有效的url
@CreditCardNumber 被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性

6.5.3、给User添加校验

leyou-user-interface中添加Hibernate-Validator依赖:

<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId>
</dependency>

在User对象的部分属性上添加注解:

@Table(name = "tb_user")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;        //用户id@Length(min = 4,max = 30,message = "用户名字符长度只能在4~30位之间")private String username;//用户名@JsonIgnore         //数据以json格式传输时 该字段不传输@Length(min = 6,max = 30,message = "密码长度只能在6~30位之间")private String password;//密码@Email(message = "邮箱格式不正确")     //由于阿里云短信不可用   将手机服务全部改成邮箱服务  但字段名不变private String phone;   //注册手机号码private Date created;   //创建时间@JsonIgnore         //数据以json格式传输时 该字段不传输private String salt;    //密码加密的盐值
}

在controller上进行控制:

可以进行相关测试…

七、查询用户

7.1、接口说明

功能说明

查询功能,根据参数中的用户名和密码查询指定用户

接口路径

GET /query

参数说明:

form表单格式

参数 说明 是否必须 数据类型 默认值
username 用户名,格式为4~30位字母、数字、下划线 String
password 用户密码,格式为4~30位字母、数字、下划线 String

**返回结果:**用户的json格式数据

{"id": 6572312,"username":"test","phone":"13688886666","created": 1342432424
}

状态码:

  • 200:注册成功
  • 400:用户名或密码错误
  • 500:服务器内部异常,注册失败

7.2、Controller

/*** 根据用户名查询用户,校验密码* @param username* @param password* @return*/
@PostMapping("/query")
public ResponseEntity<User> queryUser(@RequestParam("username") String username,@RequestParam("password") String password){User user = this.userService.queryUser(username,password);if (null == user){return ResponseEntity.badRequest().build();}return ResponseEntity.ok(user);
}

7.3、Service

/*** 根据用户名查询用户,校验密码* @param username* @param password* @return*/
User queryUser(String username, String password);

实现类:

@Override
public User queryUser(String username, String password) {User record = new User();record.setUsername(username);User user = this.userMapper.selectOne(record);//校验查询的用户if (null == user) return null;//将用户传入的密码用盐进行加密再与数据库数据进行比较//对用户输入的密码加盐加密password = CodeUtils.md5Hex(password, user.getSalt());//判断用户输入的密码是否正确if (!StringUtils.equals(password, user.getPassword())) {return null;//抛出异常}return user;
}

7.4、 测试

7.5、优化

经常要查询用户信息,将用户信息放入到redis中,直接从redis中查询。

改造queryUser,让其查询用户信息的时候不是直接从数据库中获取,先从redis中查询,查询不到的话再去数据库中查询,查询成功后再将数据放入到redis缓存中

private final static String USER_PREFIX = "user:verify:";@Override
public User queryUser(String username, String password) {/*** 逻辑改变,先去缓存中查询用户数据,查到的话直接返回,查不到再去数据库中查询,然后放入到缓存当中*///先去缓存中查询BoundHashOperations<String, Object, Object> boundHashOps = this.redisTemplate.boundHashOps(USER_PREFIX + username);User user = (User) boundHashOps.get(username);//如果缓存中没有查找到,则去数据库查询if (user == null){User recode = new User();recode.setUsername(username);user = this.userMapper.selectOne(recode);//查询后放入缓存中boundHashOps.put(user.getUsername(),user);}//校验查询的用户if (null == user) return null;//将用户传入的密码用盐进行加密再与数据库数据进行比较//对用户输入的密码加盐加密password = CodeUtils.md5Hex(password, user.getSalt());//判断用户输入的密码是否正确if (!StringUtils.equals(password, user.getPassword())) {return null;//抛出异常}return user;
}

存在的问题

数据不一致问题!!

比如说用户修改密码,那么在后端修改数据库时会产生一些问题。是先删除缓存,再更新数据库呢?还是先更新数据库再删除缓存?

  • 先删除缓存,再更新数据库

试想,两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去。

  • 先更新数据库,再删除缓存。这是常规做法,缓存基于数据库,取自数据库

@Override
public Boolean updatePassword(String username, String password, String newPassword) {/*** 这里面涉及到对缓存的操作:* 先把数据存到数据库中,成功后,再让缓存失效。*///获取用户的信息User user = query(username, password);if (user == null) return false;//修改用户的密码User record = new User();record.setId(user.getId());//将新密码加密newPassword = CodecUtils.md5Hex(newPassword,user.getSalt());record.setPassword(newPassword);this.userMapper.updateByPrimaryKeySelective(record);//处理缓存中的数据,先更新数据库中的数据,再删除缓存中的数据BoundHashOperations<String, Object, Object> boundHashOps = this.redisTemplate.boundHashOps(KEY_PREFIX2 + username);boundHashOps.delete(username);return true;
}

注:上面的方法还是存在问题的,更好的解决办法是延时双删,当然还有其他办法有兴趣可自行了解

乐优商城(11)--用户中心相关推荐

  1. leyou商城项目搭建(1)-电商行业及乐优商城介绍

    leyou商城项目搭建(1)-电商行业介绍 1.了解电商行业 1.1.项目分类 1.1.1.传统项目 1.1.2.互联网项目 1.2.电商行业的发展 1.2.1.钱景 1.2.2.数据 1.2.3.技 ...

  2. 乐优商城之项目搭建(四)

    文章目录 (一)项目分类 (二)电商行业 (三)专业术语 (四)项目介绍 (五)技术选型 (六)开发环境 (七)搭建后台环境:父工程 (八)搭建后台环境:eureka (九)搭建后台环境:zuul ( ...

  3. 乐优商城(10)--数据同步

    乐优商城(10)–数据同步 一.RabbitMQ 1.1.问题分析 目前已经完成了商品详情和搜索系统的开发.思考一下,是否存在问题? 商品的原始数据保存在数据库中,增删改查都在数据库中完成. 搜索服务 ...

  4. 乐优商城(十)用户注册

    文章目录 1. 搭建用户微服务 1.1 用户微服务的结构 1.2 创建 leyou-user 1.3 创建 leyou-user-interface 1.4 创建 leyou-user-service ...

  5. 乐优商城源码/数据库及笔记总结

    文章目录 1 源码 2 笔记 2.1 项目概述 2.2 微服务 3 项目优化 4 项目或学习过程中涉及到的设计模式 5 安全问题 6 高内聚低耦合的体现 7 项目中待优化的地方 1 源码 Github ...

  6. 【javaWeb微服务架构项目——乐优商城day15】——会调用订单系统接口,实现订单结算功能,实现微信支付功能

    0.学习目标 会调用订单系统接口 实现订单结算功能 实现微信支付功能 源码笔记及资料: 链接:https://pan.baidu.com/s/1_opfL63P1pzH3rzLnbFiNw 提取码:v ...

  7. 乐优商城(四)商品规格管理

    文章目录 1. 商品规格 1.1 SPU 和 SKU 1.2 分析商品规格的关系 1.3 数据库设计 1.3.1 商品规格组表 1.3.2 商品规格参数表 2. 商品规格组 2.1 商品规格组前端 2 ...

  8. 【javaWeb微服务架构项目——乐优商城day05】——商品规格参数管理(增、删、改,查已完成),SPU和SKU数据结构,商品查询

    乐优商城day05 0.学习目标 1.商品规格数据结构 1.1.SPU和SKU 1.2.数据库设计分析 1.2.1.思考并发现问题 1.2.2.分析规格参数 1.2.3.SKU的特有属性 1.2.4. ...

  9. 乐优商城之分类查询品牌查询(八)

    文章目录 (一)编写分类查询 (二)跨域问题 (三)cors跨域原理 (四)解决跨域问题 (五)品牌查询页面分析 (六)品牌查询后台代码 (七)分页查询排序的原理 (八)axios (一)编写分类查询 ...

  10. 乐优商城(05)--商品管理

    乐优商城(05)–商品管理 一.导入图片资源 现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,因此需要把图片导入到服务器中: 将images.zip文件上传至/leyou/static目录: ...

最新文章

  1. STM32单片机外部中断配置讲解
  2. 使用DFS求任意两点的所有路径
  3. c语言铁道,C语言程序设计(方少卿) 铁道C第8章(修订版).pdf
  4. java基础篇---网络编程(IP与URL)
  5. 信息掩码游戏地图掩码相关(msk)
  6. 汇编语言(三十四)之输出中文
  7. pandas 批量修改列名_pandas修改DataFrame列名的方法
  8. post请求改成body_如何使用BODY快速发送POST请求
  9. 机器视觉:平行光源在双远心系统中的应用
  10. 洛谷 P1098 字符串的展开
  11. Dll学习心得(2)
  12. 2017北理复试机试题
  13. 液压与气压传动(机电)_简要问答_复习笔记
  14. 免费的PSP下载应用
  15. 注册美国iTunes账号步骤(跳过绑定银行卡)
  16. latch:cbc等待
  17. [洛谷P4118][Ynoi2016]炸脖龙I([洛谷P3934]Nephren Ruq Insania)
  18. 天雁计算机TY-82MS-4说明书,怎样用天雁TY-82MS-4计算器做一次和二次函数,跪谢!...
  19. 数学常识--数学符号常识
  20. 关于substance painter 导出贴图到maya步骤

热门文章

  1. web性能压力测试工具
  2. echarts3D环形图(包含点击效果)
  3. 公司账号密码、通信录泄露屡见不鲜,肆意流淌的敏感信息:WEB安全基础入门—信息泄露漏洞
  4. Axure 9 Mac 版
  5. Oracle查看IP操作,Oracle VM VirtualBox虚拟机ip addr命令查看ip不显示以及静态IP设置
  6. 在计算机桌面中选择了隐藏如何显示不出来的,电脑桌面文件被隐藏了怎么办
  7. [C#]六十甲子纳音WM版 v2.1 By:LESLIE
  8. 自然语言处理NLP星空智能对话机器人系列:理解语言的 Transformer 模型-子词分词器
  9. 微软云加速器助edoc2入云腾飞
  10. python 2.7导入arcpy_导入 ArcPy