交友项目【手机号登录注册功能】实现
目录
1:用户登录
1.1:接口文档
1.2:API接口定义
1.3:Dubbo服务提供者
配置文件
启动引导类
数据访问层
API接口实现
1.4:Dubbo服务消费者
UserController
UserService
1.5:访问测试
1.6:可能存在的问题
序列化异常
连接超时
没有服务提供者可用
2:登录涉及到JWT生成Token
2.1:简介
2.2:格式
2.3:流程
2.4:示例
2.5:JWT工具类
1:用户登录
用户接收到验证码后,进行输入验证码,点击登录,前端系统将手机号以及验证码提交到服务端进行校验。
1.1:接口文档
YAPI接口地址:http://192.168.136.160:3000/project/19/interface/api/97
1.2:API接口定义
在接口模块(tanhua-dubbo-interface),将提供者模块(tanhua-dubbo-db)提供的接口抽取到一个专门的接口模块,便于统一管理,消费者模块只需要导入接口模块的依赖即可调用该接口。
/*** @Author 爱吃豆的土豆、* @Date 2023/3/30 11:05*/
public interface UserApi {public User findByMobile(String phone);public Long save(User user);
}
1.3:Dubbo服务提供者
-
在
tanhua-dubbo-db
项目中提供
UserApiImpl的实现 , 如下所示
配置文件
server:port: 18081
spring:application:name: tanhua-dubbo-dbdatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/tanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=falseusername: rootpassword: 1234cloud:nacos:discovery:server-addr: 192.168.136.160:8848
dubbo:protocol:name: dubboport: 20881registry:address: spring-cloud://192.168.136.160:8848scan:base-packages: com.czxy.tanhua.dubbo.api #dubbo中包扫描mybatis-plus:global-config:db-config:table-prefix: tb_ # 表名前缀id-type: auto # id策略为自增长
启动引导类
/*** @Author 爱吃豆的土豆、* @Date 2023/3/30 14:48*/
@SpringBootApplication
@EnableDubbo
public class DubboDBApplication {public static void main(String[] args) {SpringApplication.run(DubboDBApplication.class,args);}
}
数据访问层
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
API接口实现
@DubboService
public class UserApiImpl implements UserApi {@Autowiredprivate UserMapper userMapper;//根据手机号码查询用户public User findByMobile(String mobile) {QueryWrapper<User> qw = new QueryWrapper<>();qw.eq("mobile",mobile);return userMapper.selectOne(qw);}@Overridepublic Long save(User user) {userMapper.insert(user);return user.getId();}
}
1.4:Dubbo服务消费者
在tanhua-app-server
项目中接收APP请求, 调用Dubbo服务完成业务功能
UserController
/*** 校验登录*/
@PostMapping("/loginVerification")
public ResponseEntity loginVerification(@RequestBody Map map) {//1 调用map集合获取请求参数String phone = (String) map.get("phone");String code = (String) map.get("verificationCode");//2 调用userService完成用户登录Map retMap = userService.loginVerification(phone, code);//3 构造返回return ResponseEntity.ok(retMap);
}
UserService
package com.czxy.tanhua.service;import com.czxy.tanhua.autoconfig.template.SmsTemplate;
import com.czxy.tanhua.commons.utils.JwtUtils;
import com.czxy.tanhua.dubbo.api.UserApi;
import com.czxy.tanhua.entity.User;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;/*** @Author 爱吃豆的土豆、* @Date 2023/3/30 9:55*/
@Service
public class UserService {@Resourceprivate SmsTemplate smsTemplate;@Resourceprivate StringRedisTemplate stringRedisTemplate;@DubboReferenceprivate UserApi userApi;/*** 登录用户的验证码* @param phone*/public void loginUser(String phone) {//随机生成验证码String code = RandomStringUtils.randomNumeric(6);//调用短信工具类smsTemplate.sendSms(phone,"",code);String key = "CHECK_CODE"+phone;//存放到redis中stringRedisTemplate.opsForValue().set(key,code,Duration.ofMinutes(5));}/*** 登录验证码的校验&用户注册* @param map* @return*/public Map loginVerification(Map map) {String phone = (String) map.get("phone");String verificationCode = (String) map.get("verificationCode");//将验证码进行校验String s = stringRedisTemplate.opsForValue().get("CHECK_CODE" + phone);//取出之后删除验证码stringRedisTemplate.delete("CHECK_CODE"+phone);if (StringUtils.isEmpty(s) || !s.equals(verificationCode)){throw new RuntimeException("验证码校验失败!");}//判断用户是否已经注册Boolean isNew = false;//验证码校验成功,查询数据库是否存在该用户User user = userApi.findByMobile(phone);if (user == null){//新注册用户user = new User();user.setMobile(phone);user.setPassword(DigestUtils.md5Hex("123456"));//进行添加到数据库中Long id = userApi.save(user);user.setId(id);isNew = true;}//号码已经再数据库中存在生成token进行返回Map<String, Object> tokenMap = new HashMap<>();tokenMap.put("id",user.getId());tokenMap.put("mobile", user.getMobile());String token = JwtUtils.getToken(tokenMap);Map<String, Object> result = new HashMap<>();result.put("token",token);result.put("isNew",isNew);return result;}
}
1.5:访问测试
【第一步】查看ip地址黑窗口ipconfig
【第二步】修改服务配置:【自己电脑的IP地址】
【第三步】测试
1.6:可能存在的问题
序列化异常
连接超时
Nacos连接超时 ,Redis连接超时等
等待很长时间, 报如下类似的错误
找到连接超时的服务 :
-
查看服务是否启动
-
查看服务连接配置
IP
和端口
是否正确 -
查看开发机器和服务所在机器网络是否通畅(ping指令)
没有服务提供者可用
没有服务提供者可用
-
检查服务提供者工程是否开启成功
-
检查注册中心是否存在该服务
-
注册中心不存在 服务
-
检查dubbo扫描的包是否正确
-
检查@DubboService注解是否添加
-
-
注册中心存在服务
-
检查服务消费方的注册中心地址是否正确
-
检查提供方和消费方的接口定义是否相同
-
2:登录涉及到JWT生成Token
2.1:简介
-
JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全
2.2:格式
-
JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C
-
A由JWT头部信息header加密得到
-
B由JWT用到的身份验证信息json数据加密得到
-
C由A和B加密得到,是校验部分
2.3:流程
2.4:示例
导入依赖:
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency
编写测试用例:
@Test
public void testCreateToken() {//生成token//1 准备数据Map map = new HashMap();map.put("id",1);map.put("mobile","13612345678");//2 使用JWT的工具类生成tokenlong now = System.currentTimeMillis();String token = Jwts.builder().signWith(SignatureAlgorithm.HS512, "czxy") //指定加密算法.setClaims(map) //写入数据.setExpiration(new Date(now + 30000)) //失效时间.compact();System.out.println(token);
}//解析token/*** SignatureException : token不合法* ExpiredJwtException:token已过期*/
@Test
public void testParseToken() {String token = "eyJhbGciOiJIUzUxMiJ9.eyJtb2JpbGUiOiIxMzgwMDEzODAwMCIsImlkIjoxLCJleHAiOjE2MTgzOTcxOTV9.2lQiovogL5tJa0px4NC-DW7zwHFqZuwhnL0HPAZunieGphqnMPduMZ5TtH_mxDrgfiskyAP63d8wzfwAj-MIVw";try {Claims claims = Jwts.parser().setSigningKey("czxy").parseClaimsJws(token).getBody();Object id = claims.get("id");Object mobile = claims.get("mobile");System.out.println(id + "--" + mobile);}catch (ExpiredJwtException e) {System.out.println("token已过期");}catch (SignatureException e) {System.out.println("token不合法");}}
通过解析Token得知,如果抛出SignatureException异常表示token不合法,如果抛出ExpiredJwtException异常表示token已过期
2.5:JWT工具类
tanhua-commons
模块创建JWT工具类
public class JwtUtils {// TOKEN的有效期1小时(S)private static final int TOKEN_TIME_OUT = 3_600;// 加密KEYprivate static final String TOKEN_SECRET = "czxy";// 生成Tokenpublic static String getToken(Map params){long currentTime = System.currentTimeMillis();return Jwts.builder().signWith(SignatureAlgorithm.HS512, TOKEN_SECRET) //加密方式.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳.addClaims(params).compact();}/*** 获取Token中的claims信息*/private static Claims getClaims(String token) {return Jwts.parser().setSigningKey(TOKEN_SECRET).parseClaimsJws(token).getBody();}/*** 是否有效 true-有效,false-失效*/public static boolean verifyToken(String token) {if(StringUtils.isEmpty(token)) {return false;}try {Claims claims = Jwts.parser().setSigningKey(TOKEN_SECRET).parseClaimsJws(token).getBody();}catch (Exception e) {return false;}return true;}
}