乐优商城(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.pojo1.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
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郵件服務
開啟QQPOP3/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: 254.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校驗的注解
常用注解如下:
| @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; }存在的問題
數據不一致問題!!
比如說用戶修改密碼,那么在后端修改數據庫時會產生一些問題。是先刪除緩存,再更新數據庫呢?還是先更新數據庫再刪除緩存?
- 先刪除緩存,再更新數據庫
試想,兩個并發操作,一個是更新操作,另一個是查詢操作,更新操作刪除緩存后,查詢操作沒有命中緩存,先把老數據讀出來后放到緩存中,然后更新操作更新了數據庫。于是,在緩存中的數據還是老的數據,導致緩存中的數據是臟的,而且還一直這樣臟下去。
- 先更新數據庫,再刪除緩存。這是常規做法,緩存基于數據庫,取自數據庫
注:上面的方法還是存在問題的,更好的解決辦法是延時雙刪,當然還有其他辦法有興趣可自行了解
總結
以上是生活随笔為你收集整理的乐优商城(11)--用户中心的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dalvik虚拟机与Java区别_05
- 下一篇: 61-1 认识webpack