目录
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-db
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/tanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
username: root
password: 1234
cloud:
nacos:
discovery:
server-addr: 192.168.136.160:8848
dubbo:
protocol:
name: dubbo
port: 20881
registry:
address: spring-cloud://192.168.136.160:8848
scan:
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 {
@Autowired
private UserMapper userMapper;
//根据手机号码查询用户
public User findByMobile(String mobile) {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("mobile",mobile);
return userMapper.selectOne(qw);
}
@Override
public 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 {
@Resource
private SmsTemplate smsTemplate;
@Resource
private StringRedisTemplate stringRedisTemplate;
@DubboReference
private 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的工具类生成token
long 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;
// 加密KEY
private static final String TOKEN_SECRET = "czxy";
// 生成Token
public 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;
}
}