一个关于面经的网站开发全程记录--3
总览:搞定登录注册、整合短信接口实现手机号验证码注册(用的是简单的身份验证appcode)、单点登录(采用Token的方式实现,也有其他方法流入session广播,redis+cookie)
前提:准备相应的工具类
JWTUtils(网上大把写好的,拿过来直接用即可)
public class JwtUtils {
//常量
public static final long EXPIRE = 1000 * 60 * 60 * 24; //token过期时间
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //秘钥
//生成token字符串的方法
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("guli-user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id) //设置token主体部分 ,存储用户信息
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token字符串获取用户id
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
MD5Utils进行密码加密,数据库中存储密文,也是网上找的
public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}
随机生成验证码,自己写也可以
public class RandomUtil {
private static final Random random = new Random();
private static final DecimalFormat fourdf = new DecimalFormat("0000");
private static final DecimalFormat sixdf = new DecimalFormat("000000");
public static String getFourBitRandom() {
return fourdf.format(random.nextInt(10000));
}
public static String getSixBitRandom() {
return sixdf.format(random.nextInt(1000000));
}
/**
* 给定数组,抽取n个数据
* @param list
* @param n
* @return
*/
public static ArrayList getRandom(List list, int n) {
Random random = new Random();
HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
// 生成随机数字并存入HashMap
for (int i = 0; i < list.size(); i++) {
int number = random.nextInt(100) + 1;
hashMap.put(number, i);
}
// 从HashMap导入数组
Object[] robjs = hashMap.values().toArray();
ArrayList r = new ArrayList();
// 遍历数组并打印数据
for (int i = 0; i < n; i++) {
r.add(list.get((int) robjs[i]));
System.out.print(list.get((int) robjs[i]) + "\t");
}
System.out.print("\n");
return r;
}
}
RedisTemplate,搞一个redis配置类,写一个自己的redisTemplate,可以网上找
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
正题:
controller接口:(具体干啥看注解)
@ApiOperation(value = "实现单点登录")
@PostMapping(value = "/login")
public R login(@RequestBody LoginVo loginVo){
//LoginVo 是封装的vo类,用于接收前端传递过来的数据,用于登录
String token = userService.login(loginVo);
//返回一个token字符串即可,token字符串中包含了用户的登录信息,前端验证token即可实现
return R.ok().data("token",token);
}
@ApiOperation(value ="用户注册")
@PostMapping(value = "register")
public R register(@RequestBody RegisterVo registerVo){
//RegisterVo封装的注册vo类,包换注册时的输入信息
userService.register(registerVo);
return R.ok();
}
@ApiOperation(value = "根据token获取登录的部分用户信息")
@GetMapping(value = "user/getUserInfo")
public R getUserInfo(HttpServletRequest request){
try {
//用jwt工具类中的方法,解析前端的请求,从token中获取到当前用户id
String userId = JwtUtils.getMemberIdByJwtToken(request);
//根据用户id查询数据库得到用户的详细信息 这边是返回了一个LoginVo,放回一个User其实更好
LoginVo loginVo = userService.getUserInfo(userId);
return R.ok().data("info",loginVo);
} catch (Exception e) {
e.printStackTrace();
throw new InterviewException(20001,"error");
}
}
@ApiOperation(value = "发送短信接口")
@PostMapping(value = "send/{phone}")
public R sendSmsCode(@PathVariable String phone){
//如果在redis中可以找到对应的key 返回其value值,进行比较判断 相等的话返回ok
String code = redisTemplate.opsForValue().get(phone);
if(!StringUtils.isEmpty(code)) return R.ok();
//如果在redis中找不到,随机生成验证码
code = RandomUtil.getFourBitRandom();
//发送验证码
boolean isSend = userService.send(phone,code);
if(isSend){
//设置存入redis中并设置过期时间
redisTemplate.opsForValue().set(phone,code,1, TimeUnit.MINUTES);
return R.ok();
} else{
return R.error().message("发送短信新失败");
}
}
@ApiOperation(value = "根据用户id修改用户信息")
@PostMapping(value = "updateUserInfo")
public R updateUserInfo(@RequestBody User user){
boolean res = userService.updateById(user);
if(res){
return R.ok();
}else{
return R.error();
}
}
service层
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Override
public String login(LoginVo loginVo) {
//从登录的vo类中获取属性
String phone = loginVo.getPhone();
String password = loginVo.getPassword();
//判断是否为空
if(phone.isEmpty() || password.isEmpty()) {
throw new InterviewException(20001,"error");
}
//获取会员
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("phone",phone);
User user = baseMapper.selectOne(wrapper);
if(user == null){
throw new InterviewException(20001,"error");
}
//校验密码 加密之后判断
if(!MD5.encrypt(password).equals(user.getPassword())){
throw new InterviewException(20001,"error");
}
//前边都成功之后使用JWT生成字符串 将用户id和用户名称放入到token中
String token = JwtUtils.getJwtToken(user.getId(),user.getUsername());
return token;
}
//返回类型为LoginVo,其实感觉返回User更好
@Override
public LoginVo getUserInfo(String userId) {
//根据用户id查询用户信息
User user = baseMapper.selectById(userId);
LoginVo loginVo = new LoginVo();
//拷贝 源 -> 目标
BeanUtils.copyProperties(user,loginVo);
return loginVo;
}
@Override
public void register(RegisterVo registerVo) {
//获取前端传过来的注册信息
String username = registerVo.getUsername();
String phone = registerVo.getPhone();
String password = registerVo.getPassword();
String code = registerVo.getCode();
//avatar注册时候有一个默认头像TODO默认头像存储在 阿里OSS服务中
//校验参数
if(username.isEmpty() || phone.isEmpty()
|| password.isEmpty() || code.isEmpty()){
throw new InterviewException(20001,"error");
}
//校验验证码
String smscode = redisTemplate.opsForValue().get(phone);
if(!code.equals(smscode)){
throw new InterviewException(20001,"error");
}
//查询数据库中是否存在相同手机号
Integer count = baseMapper.selectCount(new QueryWrapper<User>().eq("phone", phone));
if(count.intValue() > 0) {
throw new InterviewException(20001,"error");
}
//拆分微服务时候利用雪花算法生成全局唯一用户ID
//目前使用MP注解 @TableId(value = "id", type = IdType.ID_WORKER_STR) 自动填充ID
//利用MD5进行密码加密,数据库存储密文
password = MD5.encrypt(password);
//添加注册到数据库
User user = new User();
BeanUtils.copyProperties(registerVo,user);
baseMapper.insert(user);
}
@Override
public boolean send(String phone, String code) {
//设置自己短信服务的 我直接用云市场的现成的 直接用即可
String host = SmsConstProperty.APP_HOST;
String path = SmsConstProperty.APP_PATH;
String method = SmsConstProperty.APP_METHOD;
String appcode = SmsConstProperty.APP_CODE;
//下边的代码都是商家弄好的模板
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
Map<String, String> querys = new HashMap<String, String>();
//注册的手机号
querys.put("mobile", phone);
//短信模板需要自己设置 用自己的模板
querys.put("templateId", "M72CB42894");
//验证码这边我测试用的是固定的数字,前边有随机生成验证码的类,直接使用
querys.put("value", "1234");
Map<String, String> bodys = new HashMap<String, String>();
try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
*/
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if(statusCode == 200){
return true;
}
} catch (Exception e) {
e.printStackTrace();
throw new InterviewException(20001,"error");
}
return false;
}
进行测试即可
以上就是单点登陆,短信验证注册等功能接口的实现
今天就到这
欢迎指正