乐优商城(11)--用户中心
乐优商城(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邮件服务
开启QQ
POP3/SMTP
服务按提示完成相关操作后会得到一份授权码,妥善保存
在配置文件中添加配置
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
将压缩包上传至
/usr/local/leyou
解压
cd /usr/local/leyou tar -xvf redis-6.0.15.tar.gz
编译安装
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
修改安装目录下的redis.conf文件
vim redis.conf
修改以下配置:
#bind 127.0.0.1 # 将这行代码注释,监听所有的ip地址,外网可以访问 port 8975 #改成8975 protected-mode no # 把yes改成no,允许外网访问 daemonize yes # 把no改成yes,后台运行
启动:
./src/redis-server redis.conf
修改端口为8975,不使用默认端口主要原因是会有黑客攻击阿里云服务器,植入挖矿程序,这里改变端口,安全性会提高一些,如果设置密码会更好
启用客户端连接,测试
注: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) | 被注释的元素必须符合指定的正则表达式 |
被注释的元素必须是电子邮箱地址 | |
@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)--用户中心相关推荐
- leyou商城项目搭建(1)-电商行业及乐优商城介绍
leyou商城项目搭建(1)-电商行业介绍 1.了解电商行业 1.1.项目分类 1.1.1.传统项目 1.1.2.互联网项目 1.2.电商行业的发展 1.2.1.钱景 1.2.2.数据 1.2.3.技 ...
- 乐优商城之项目搭建(四)
文章目录 (一)项目分类 (二)电商行业 (三)专业术语 (四)项目介绍 (五)技术选型 (六)开发环境 (七)搭建后台环境:父工程 (八)搭建后台环境:eureka (九)搭建后台环境:zuul ( ...
- 乐优商城(10)--数据同步
乐优商城(10)–数据同步 一.RabbitMQ 1.1.问题分析 目前已经完成了商品详情和搜索系统的开发.思考一下,是否存在问题? 商品的原始数据保存在数据库中,增删改查都在数据库中完成. 搜索服务 ...
- 乐优商城(十)用户注册
文章目录 1. 搭建用户微服务 1.1 用户微服务的结构 1.2 创建 leyou-user 1.3 创建 leyou-user-interface 1.4 创建 leyou-user-service ...
- 乐优商城源码/数据库及笔记总结
文章目录 1 源码 2 笔记 2.1 项目概述 2.2 微服务 3 项目优化 4 项目或学习过程中涉及到的设计模式 5 安全问题 6 高内聚低耦合的体现 7 项目中待优化的地方 1 源码 Github ...
- 【javaWeb微服务架构项目——乐优商城day15】——会调用订单系统接口,实现订单结算功能,实现微信支付功能
0.学习目标 会调用订单系统接口 实现订单结算功能 实现微信支付功能 源码笔记及资料: 链接:https://pan.baidu.com/s/1_opfL63P1pzH3rzLnbFiNw 提取码:v ...
- 乐优商城(四)商品规格管理
文章目录 1. 商品规格 1.1 SPU 和 SKU 1.2 分析商品规格的关系 1.3 数据库设计 1.3.1 商品规格组表 1.3.2 商品规格参数表 2. 商品规格组 2.1 商品规格组前端 2 ...
- 【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. ...
- 乐优商城之分类查询品牌查询(八)
文章目录 (一)编写分类查询 (二)跨域问题 (三)cors跨域原理 (四)解决跨域问题 (五)品牌查询页面分析 (六)品牌查询后台代码 (七)分页查询排序的原理 (八)axios (一)编写分类查询 ...
- 乐优商城(05)--商品管理
乐优商城(05)–商品管理 一.导入图片资源 现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,因此需要把图片导入到服务器中: 将images.zip文件上传至/leyou/static目录: ...
最新文章
- STM32单片机外部中断配置讲解
- 使用DFS求任意两点的所有路径
- c语言铁道,C语言程序设计(方少卿) 铁道C第8章(修订版).pdf
- java基础篇---网络编程(IP与URL)
- 信息掩码游戏地图掩码相关(msk)
- 汇编语言(三十四)之输出中文
- pandas 批量修改列名_pandas修改DataFrame列名的方法
- post请求改成body_如何使用BODY快速发送POST请求
- 机器视觉:平行光源在双远心系统中的应用
- 洛谷 P1098 字符串的展开
- Dll学习心得(2)
- 2017北理复试机试题
- 液压与气压传动(机电)_简要问答_复习笔记
- 免费的PSP下载应用
- 注册美国iTunes账号步骤(跳过绑定银行卡)
- latch:cbc等待
- [洛谷P4118][Ynoi2016]炸脖龙I([洛谷P3934]Nephren Ruq Insania)
- 天雁计算机TY-82MS-4说明书,怎样用天雁TY-82MS-4计算器做一次和二次函数,跪谢!...
- 数学常识--数学符号常识
- 关于substance painter 导出贴图到maya步骤
热门文章
- web性能压力测试工具
- echarts3D环形图(包含点击效果)
- 公司账号密码、通信录泄露屡见不鲜,肆意流淌的敏感信息:WEB安全基础入门—信息泄露漏洞
- Axure 9 Mac 版
- Oracle查看IP操作,Oracle VM VirtualBox虚拟机ip addr命令查看ip不显示以及静态IP设置
- 在计算机桌面中选择了隐藏如何显示不出来的,电脑桌面文件被隐藏了怎么办
- [C#]六十甲子纳音WM版 v2.1 By:LESLIE
- 自然语言处理NLP星空智能对话机器人系列:理解语言的 Transformer 模型-子词分词器
- 微软云加速器助edoc2入云腾飞
- python 2.7导入arcpy_导入 ArcPy