Day2谷粒学院登录和注册、redis存短信和首页
1.登录功能
简单说说思路。谷粒学院这个地方的登录和其它地方的登录有点不一样。其它通常就是验证一下账号和密码,然后把用户信息放入session就完成了。但是这个地方还需要注意我们启动了多个服务,服务之间是需要交流的,而且每个服务有自己的session他们之间是不能够共享的。
解决方案
①session共享,也就是把session复制到所有的服务上去,问题是消耗大量的资源
②redis+cookie的组合。把session存入redis。然后cookie保存这个session存入的名称,用于去到下一个服务时候获取。
③token。也就是把用户信息变成一串编码后的字符串,然后通过地址进行传输比如 http://localhost:1111/xxx?token=xxxx这样子的。
这里的解决方案就是采用token。简单说说用在项目的步骤。
①后端接口,根据传输的电话号和密码来获取对应的用户信息。需要注意判断电话号是否存在,然后才查询对应用户信息。然后就是把用户信息通过JWT来编码成为token。
JWT其实是一种解决方案,也就是形成token的一种规则和框架。
②前端相对来说比较简单,就是获取token。然后跳转页面(通过路由)。还要把获取到的token存入cookie,但是存入cookie之间一定要变成JSON字符串。cookie域是localhost所以可以在几个本地服务之间调用。
③然后就是跳转到首页之后显示用户信息。
(1)获取对应cookie的token。
(2)调用后端接口完成对token的解析,并且返回对应用户信息,存入cookie。
(3)显示用户信息在页面。可以通过是否存在用户id来判断是否显示。
代码
default.vue
**
<template>
<div class="in-wrap">
<!-- 公共头引入 -->
<header id="header">
<section class="container">
<h1 id="logo">
<a href="#" title="阿昌之谷粒学院">
<img
src="~/assets/img/logo.png"
width="100%"
alt="阿昌之谷粒学院"
/>
</a>
</h1>
<div class="h-r-nsl">
<ul class="nav">
<router-link to="/" tag="li" active-class="current" exact>
<a>首页</a>
</router-link>
<router-link to="/course" tag="li" active-class="current">
<a>课程</a>
</router-link>
<router-link to="/teacher" tag="li" active-class="current">
<a>名师</a>
</router-link>
<router-link to="/article" tag="li" active-class="current">
<a>文章</a>
</router-link>
<router-link to="/qa" tag="li" active-class="current">
<a>问答</a>
</router-link>
</ul>
<!-- / nav -->
<ul class="h-r-login">
<li v-if="!loginInfo.id" id="no-login">
<a href="/login" title="登录">
<em class="icon18 login-icon"> </em>
<span class="vam ml5">登录</span>
</a>
<a href="/register" title="注册">
<span class="vam ml5">注册</span>
</a>
</li>
<li v-if="loginInfo.id" id="is-login-one" class="mr10">
<a id="headerMsgCountId" href="#" title="消息">
<em class="icon18 news-icon"> </em>
</a>
<q class="red-point" style="display: none"> </q>
</li>
<li v-if="loginInfo.id" id="is-login-two" class="h-r-user">
<a href="/ucenter" title>
<img
:src="loginInfo.avatar"
width="30"
height="30"
class="vam picImg"
alt
/>
<span id="userName" class="vam disIb">{{
loginInfo.nickname
}}</span>
</a>
<a
href="javascript:void(0);"
title="退出"
@click="logout()"
class="ml5"
>退 出</a
>
</li>
<!-- /未登录显示第1 li;登录后显示第2,3 li -->
</ul>
<aside class="h-r-search">
<form action="#" method="post">
<label class="h-r-s-box">
<input
type="text"
placeholder="输入你想学的课程"
name="queryCourse.courseName"
value
/>
<button type="submit" class="s-btn">
<em class="icon18"> </em>
</button>
</label>
</form>
</aside>
</div>
<aside class="mw-nav-btn">
<div class="mw-nav-icon"></div>
</aside>
<div class="clear"></div>
</section>
</header>
<!-- /公共头引入 -->
<nuxt />
<!-- 公共底引入 -->
<footer id="footer">
<section class="container">
<div class>
<h4 class="hLh30">
<span class="fsize18 f-fM c-999">友情链接</span>
</h4>
<ul class="of flink-list">
<li>
<a href="http://www.atguigu.com/" title="阿昌" target="_blank"
>阿昌</a
>
</li>
</ul>
<div class="clear"></div>
</div>
<div class="b-foot">
<section class="fl col-7">
<section class="mr20">
<section class="b-f-link">
<a href="#" title="关于我们" target="_blank">关于我们</a>|
<a href="#" title="联系我们" target="_blank">联系我们</a>|
<a href="#" title="帮助中心" target="_blank">帮助中心</a>|
<a href="#" title="资源下载" target="_blank">资源下载</a>|
<span>服务热线:18757750375(温州) 17376597290(杭州)</span>
<span>Email:info@atguigu.com</span>
</section>
<section class="b-f-link mt10">
<span>©2021阿昌之谷粒学院</span>
</section>
</section>
</section>
<aside class="fl col-3 tac mt15">
<section class="gf-tx">
<span>
<img src="~/assets/img/wx-icon.png" alt />
</span>
</section>
<section class="gf-tx">
<span>
<img src="~/assets/img/wb-icon.png" alt />
</span>
</section>
</aside>
<div class="clear"></div>
</div>
</section>
</footer>
<!-- /公共底引入 -->
</div>
</template>
<script>
import "~/assets/css/reset.css";
import "~/assets/css/theme.css";
import "~/assets/css/global.css";
import "~/assets/css/web.css";
import cookie from "js-cookie";
import loginApi from "@/api/login"
export default {
data() {
return {
loginInfo: {
id: "",
age: "",
avatar: "",
mobile: "",
nickname: "",
sex: "",
},
token: "",
};
},
created() {
//1.获取token
this.token=this.$route.query.token;
//2.判断token是否存在,存在就开启微信登录
if(this.token){
this.wxLogin();
}
this.getUserInfo();
},
methods: {
//微信登录
wxLogin(){
//3.把token放入cookie里面
cookie.set("guli_token",this.token,{domain:"localhost"});
//4.调用后端接口解析token
loginApi.getUserInfo()
.then(response=>{
//1.返回用户信息
this.loginInfo=response.data.data.userInfo;
alert(loginInfo.avatar)
var userStr=JSON.stringify(loginInfo);
//2.设置到cookie
cookie.set("guli_ucenter",userStr,{domain:"localhost"});
})
}
,
//退出功能
logout(){
cookie.set("guli_token","",{domain:"localhost"})
cookie.set("guli_ucenter","",{domain:"localhost"})
window.location.href = "/";
}
,
//获取cookie并显示
getUserInfo() {
if(!cookie.get("guli_ucenter")){
return ;
}
var userStr = cookie.get("guli_ucenter");
console.log("展示")
console.log(cookie.get("guli_ucenter"));
//字符串转换成JSON
if (userStr) {
this.loginInfo = JSON.parse(userStr);
}
// this.token=cookie.get("guli_token");
},
},
};
</script>
login.vue
.<template>
<div class="main">
<div class="title">
<a class="active" href="/login">登录</a>
<span>·</span>
<a href="/register">注册</a>
</div>
<div class="sign-up-container">
<el-form ref="userForm" :model="user">
<el-form-item
class="input-prepend restyle"
prop="mobile"
:rules="[
{
required: true,
message: '请输入手机号码',
trigger: 'blur',
},
{ validator: checkPhone, trigger: 'blur' },
]"
>
<div>
<el-input type="text" placeholder="手机号" v-model="user.mobile" />
<i class="iconfont icon-phone" />
</div>
</el-form-item>
<el-form-item
class="input-prepend"
prop="password"
:rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"
>
<div>
<el-input
type="password"
placeholder="密码"
v-model="user.password"
/>
<i class="iconfont icon-password" />
</div>
</el-form-item>
<div class="btn">
<input
type="button"
class="sign-in-button"
value="登录"
@click="submitLogin()"
/>
</div>
</el-form>
<!-- 更多登录方式 -->
<div class="more-sign">
<h6>社交帐号登录</h6>
<ul>
<li>
<a
id="weixin"
class="weixin"
target="_blank"
href="http://qy.free.idcfengye.com/api/ucenter/weixinLogin/login"
><i class="iconfont icon-weixin"
/></a>
</li>
<li>
<a id="qq" class="qq" target="_blank" href="#"
><i class="iconfont icon-qq"
/></a>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import "~/assets/css/sign.css";
import "~/assets/css/iconfont.css";
import loginApi from "@/api/login"
import cookie from "js-cookie";
export default {
layout: "sign",
data() {
return {
user: {
//封装用于登录的用户对象
mobile: "",
password: "",
},
//用于获取接口传来的token中的对象
loginInfo: {},
};
},
methods: {
submitLogin(){
loginApi.login(this.user)
.then(response=>{
//1.提示
this.$message({
type:"success",
message:"登录成功"
})
//第一步获取令牌
response.data.data.token;
//第二步把令牌赋值给cookie
cookie.set("guli_token",response.data.data.token,{domain:"localhost"})
//第四步就是调用获取userInfo,其实就是解析tokenw
loginApi.getUserInfo()
.then(response=>{
// alert("我执行了")
//获取info
this.loginInfo=response.data.data.userInfo;
//放进cookie
cookie.set("guli_ucenter",JSON.stringify(this.loginInfo),{domain:"localhost"})
var userStr=cookie.get("guli_ucenter")
console.log(JSON.parse(userStr));
//跳转到首页
window.location.href = "/";
})
})
}
,
checkPhone(rule, value, callback) {
//debugger
if (!/^1[34578]\d{9}$/.test(value)) {
return callback(new Error("手机号码格式不正确"));
}
return callback();
},
},
};
</script>
<style>
.el-form-item__error {
z-index: 9999999;
}
</style>
ucenterController
@RestController
@RequestMapping("/educenter/member")
@CrossOrigin
@Slf4j
public class UcenterMemberController {
@Autowired
UcenterMemberService ucenterMemberService;
//1.登录
@PostMapping("login")
public R login(@RequestBody UcenterMember member){
//2.返回对应的token信息
String token=ucenterMemberService.login(member);
return R.ok().data("token",token);
}
//3.getToken解析成用户信息
@GetMapping("getUserInfo")
public R getUserInfo(HttpServletRequest request){
String memberId = JwtUtils.getMemberIdByJwtToken(request);
UcenterMember member = ucenterMemberService.getById(memberId);
return R.ok().data("userInfo",member);
}
//2.注册
@PostMapping("register")
public R register(@RequestBody RegisterVo registerVo){
log.info("我注册了");
ucenterMemberService.register(registerVo);
return R.ok();
}
}
2.注册功能(上面代码也涉及到controller)
注册功能包括了短信发送,倒计时。注册添加用户功能。相对来说比较简单。简单讲讲思路
①后端写发送短信验证码,验证码自己生成,然后调用短信服务api进行发送比较简单
②然后就是后端还要接收注册对象,完成新用户添加。可以用VO对象来存储这个注册对象,然后通过utils来转移属性到EduUcenter上去。
③接着就是前端写好调用的api接口,也就是js文件。然后就是页面写好调用发送验证码,注册的方法调用。(js文件就不提供。)
register.vue
<template>
<div class="main">
<div class="title">
<a href="/login">登录</a>
<span>·</span>
<a class="active" href="/register">注册</a>
</div>
<div class="sign-up-container">
<el-form ref="userForm" :model="params">
<el-form-item
class="input-prepend restyle"
prop="nickname"
:rules="[
{
required: true,
message: '请输入你的昵称',
trigger: 'blur',
},
]"
>
<div>
<el-input
type="text"
placeholder="你的昵称"
v-model="params.nickname"
/>
<i class="iconfont icon-user" />
</div>
</el-form-item>
<el-form-item
class="input-prepend restyle no-radius"
prop="mobile"
:rules="[
{ required: true, message: '请输入手机号码', trigger: 'blur' },
{ validator: checkPhone, trigger: 'blur' },
]"
>
<div>
<el-input
type="text"
placeholder="手机号"
v-model="params.mobile"
/>
<i class="iconfont icon-phone" />
</div>
</el-form-item>
<el-form-item
class="input-prepend restyle no-radius"
prop="code"
:rules="[
{ required: true, message: '请输入验证码', trigger: 'blur' },
]"
>
<div
style="width: 100%; display: block; float: left; position: relative"
>
<el-input type="text" placeholder="验证码" v-model="params.code" />
<i class="iconfont icon-phone" />
</div>
<div
class="btn"
style="position: absolute; right: 0; top: 6px; width: 40%"
>
<a
href="javascript:"
type="button"
@click="getCodeFun()"
:value="codeTest"
style="border: none; background-color: none"
>{{codeTest}}</a
>
</div>
</el-form-item>
<el-form-item
class="input-prepend"
prop="password"
:rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"
>
<div>
<el-input
type="password"
placeholder="设置密码"
v-model="params.password"
/>
<i class="iconfont icon-password" />
</div>
</el-form-item>
<div class="btn">
<input
type="button"
class="sign-up-button"
value="注册"
@click="submitRegister()"
/>
</div>
<p class="sign-up-msg">
点击 “注册” 即表示您同意并愿意遵守简书
<br />
<a target="_blank" href="http://www.jianshu.com/p/c44d171298ce"
>用户协 议</a
>
和
<a target="_blank" href="http://www.jianshu.com/p/2ov8x3">隐私政策</a>
。
</p>
</el-form>
<!-- 更多注册方式 -->
<div class="more-sign">
<h6>社交帐号直接注册</h6>
<ul>
<li>
<a
id="weixin"
class="weixin"
target="_blank"
href="http://huaan.free.idcfengye.com/api/ucenter/wx/login"
><i class="iconfont icon-weixin"
/></a>
</li>
<li>
<a id="qq" class="qq" target="_blank" href="#"
><i class="iconfont icon-qq"
/></a>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import "~/assets/css/sign.css";
import "~/assets/css/iconfont.css";
import registerApi from "@/api/register"
export default {
layout: "sign",
data() {
return {
params: {
mobile: "",
code: "", //验证码
nickname: "",
password: "",
},
sending: true, //是否发送验证码
second: 60, //倒计时间
codeTest: "获取验证码",
};
},
methods: {
//倒计时
timeDown() {
let result = setInterval(() => {
--this.second;
this.codeTest = this.second;
if (this.second < 1) {
clearInterval(result);
this.sending = true;
//this.disabled = false;
this.second = 60;
this.codeTest = "获取验证码";
}
}, 1000);
},
getCodeFun(){
registerApi.sendMsm(this.params.mobile)
.then(response=>{
//1.提示
this.$message({
type:"success",
message:"发送成功"
})
//2.不能够点击验证码
this.sending=false
this.timeDown();
})
}
,
//注册
submitRegister(){
registerApi.register(this.params)
.then(response=>{
//1.提示
this.$message({
type:"success",
message:"注册成功"
})
//2.跳转
this.$router.push({path:"/login"})
})
}
,
checkPhone(rule, value, callback) {
//debugger
if (!/^1[34578]\d{9}$/.test(value)) {
return callback(new Error("手机号码格式不正确"));
}
return callback();
},
},
};
</script>
短信发送
service(注释那段,前面那段是因为我没有注册成功,我用的是别的短信服务)
@Service
public class MsmServiceImpl implements MsmService {
@Override
public boolean sendMsm(Map<String, Object> params, String phone) {
if (StringUtils.isEmpty(phone))return false;
System.out.println("我要发送了");
return true;
//参数还没有修改
//参数1:地域节点
//参数2:AccessKey ID
//参数3:AccessKey Secret
// DefaultProfile profile = DefaultProfile.getProfile("default", "id", "secret");
// DefaultAcsClient client = new DefaultAcsClient(profile);
//
// //设置相关固定参数
// CommonRequest request = new CommonRequest();
// //request.setProtocol(ProtocolType.HTTPS);
// request.setSysMethod(MethodType.POST); //提交方式
// request.setSysDomain("dysmsapi.aliyuncs.com");//请求阿里云哪里,默认不能改
// request.setSysVersion("2017-05-25");//版本号
// request.setSysAction("SendSms");//请求哪个方法
//
// //设置发送相关参数
// request.putQueryParameter("PhoneNumbers",phone);//设置要发送的【手机号】
// request.putQueryParameter("SignName","我的谷粒学院学习网站");//申请阿里云短信服务的【签名名称】
// request.putQueryParameter("TemplateCode","xxxx");//申请阿里云短信服务的【模版中的 模版CODE】
//
// //要求传递的code验证码为jason格式,可以使用JSONObject.toJSONString()将map转为json格式
// request.putQueryParameter("TemplateParam", JSONObject.toJSONString(params));
//
// //最终发送
// try {
// CommonResponse response = client.getCommonResponse(request);
// return response.getHttpResponse().isSuccess();
// } catch (ClientException e) {
// e.printStackTrace();
// return false;
// }
}
}
controller
@RestController
@RequestMapping("/edumsm/msm")
@CrossOrigin
@Api(description = "短信发送服务")
@Slf4j
public class MsmController {
@Autowired
MsmService msmService;
@Autowired
RedisTemplate<String,String> redisTemplate;
@GetMapping("sentMes/{phone}")
public R sendMessage(@PathVariable("phone")String phone){
log.info("发送短信");
//0.先从redis上取验证码
String code = redisTemplate.opsForValue().get(phone);
//2.如果不为空就直接返回
if(!StringUtils.isEmpty(code)){
return R.ok();
}
//1.获取code
code = RandomUtil.getFourBitRandom();
//2.创建map
Map<String,Object> params=new HashMap<>();
params.put("code",code);
//3.调用service方法
boolean isSend= msmService.sendMsm(params,phone);
if(isSend){
//存入redis,5分钟之后失效
redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);
return R.ok();
}else{
return R.error();
}
}
}
3.redis存储短信和首页
存储短信其实就是,发送的时候顺便redisTemplate存储验证码。首页呢就更简单了,只需要给需要存储的数据上面的方法加上@Cacheable就能够存储了。通常是放在service上面。但是别忘了需要把redis的组件加入到spring,并且开启cache服务。
config
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@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;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
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);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
首页数据service(举个例子)
@Cacheable(value = "course",key = "'selectCourseList'")
@Override
public List<EduCourse> getAllCourseInfo() {
QueryWrapper<EduCourse> courseQueryWrapper=new QueryWrapper<>();
courseQueryWrapper.orderByDesc("id");
courseQueryWrapper.last("limit 8");
List<EduCourse> courseList = this.list(courseQueryWrapper);
return courseList;
}
总结:
①存储用户的时候还要加密,对比的时候先用MD5加密之后再对比。
②优化网站访问速率可以用redis存入高频数据,利用注解@Cacheable(原理了解还不够深入)
③登录的功能步骤要么token要么redis,需要比较熟练。