文章目录
- 一、使用JWT进行跨域身份验证
- 1、传统用户身份验证
- 2、解决方案
- 二、JWT令牌
- 1、访问令牌的类型
- 2、JWT的组成
- 3、JWT的原则
- 4、JWT的用法
- 三、整合JWT令牌
- 1、在common_utils模块中添加jwt工具依赖
- 2、创建JWT工具类
- 四、阿里云短信服务
- 1、开通阿里云短信服务
- 2、添加签名管理与模板管理
- 五、新建短信微服务
- 1、在service模块下创建子模块service_sms
- 2、创建controller和service代码
- 3、配置application.properties
- 4、创建启动类
- 5、在service_sms的pom中引入依赖
- 6、编写工具类
- 7、编写controller,根据手机号发送短信
- 8、编写service
- 9、使用swagger进行测试
- 六、用户登录注册接口【后端】
- 1、在service模块下创建子模块service_ucenter
- 2、使用代码生成器生成代码
- 3、配置application.properties
- 4、创建启动类
- 5、创建Controller编写登录和注册方法
- 6、创建service接口和实现类
- 7、MD5工具类
- 8、使用swagger测试
- 七、用户登录注册【前端】
- 1、在Nuxt环境中安装插件
- 2、用户注册功能前端整合
- 1、在api文件夹中定义接口
- 2、在pages文件夹中创建注册页面,调用方法
- 3、修改nginx配置文件的端口
- 4、启动服务进行测试
- 3、用户登录功能前端整合
- 1、在api文件夹中创建登录的js文件,定义接口
- 2、在pages文件夹中创建登录页面,调用方法
- 3、在request.js添加拦截器,用于传递token信息
- 4、修改layouts中的default.vue页面
- 4、登录注册效果展示
一、使用JWT进行跨域身份验证
1、传统用户身份验证
Internet服务无法与用户身份验证分开。一般过程如下:
- 用户向服务器发送用户名和密码。
- 验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中。
- 服务器向用户返回session_id,session信息都会写入到用户的Cookie。
- 用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。
- 服务器收到session_id并对比之前保存的数据,确认用户的身份。
这种模式最大的问题是,没有分布式架构,无法支持横向扩展。
2、解决方案
- session广播
- 将透明令牌存入cookie,将用户身份信息存入redis
另外一种灵活的解决方案: 使用自包含令牌,通过客户端保存数据,而服务器不保存会话数据。 JWT是这种解决方案的代表。
二、JWT令牌
1、访问令牌的类型
2、JWT的组成
典型的,一个JWT看起来如下图:
该对象为一个很长的字符串,字符之间通过"."分隔符分为三个子串。
每一个子串表示了一个功能块,总共有以下三个部分:JWT头、有效载荷和签名
JWT头
JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。
{
"alg": "HS256",
"typ": "JWT"
}
在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
有效载荷
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段,如下例:
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。
JSON对象也使用Base64 URL算法转换为字符串保存。
签名哈希
签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。
Base64URL算法
如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。
作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/“和”=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"=“去掉,”+“用”-“替换,”/“用”_"替换,这就是Base64URL算法。
3、JWT的原则
JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下所示。
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名。
服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。
4、JWT的用法
客户端接收服务器返回的JWT,将其存储在Cookie
或localStorage
中。
此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域
,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。
三、整合JWT令牌
1、在common_utils模块中添加jwt工具依赖
<dependencies>
<!-- JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
2、创建JWT工具类
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");
}
}
四、阿里云短信服务
帮助文档:https://help.aliyun.com/product/44282.html?spm=5176.10629532.0.0.38311cbeYzBm73
1、开通阿里云短信服务
2、添加签名管理与模板管理
(1)添加签名管理
选择 国内消息 - 签名管理 - 添加签名
点击添加签名,进入添加页面,填入相关信息(签名要有实际意义)
点击提交,等待审核,审核通过后可以使用.
(2)添加模板管理
选择 国内消息 - 模板管理 - 添加模板
另外由于阿里云对个人用户限制较多,可以使用腾讯云短信服务代替.申请一个微信公众号便可进行申请使用.
五、新建短信微服务
1、在service模块下创建子模块service_sms
2、创建controller和service代码
3、配置application.properties
server: #服务端口
port: 8005
spring:
application: #服务名称
name: service-msm
jackson: #Json格式
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
redis: #redis配置
database: 0
host: 192.168.174.128
lettuce:
pool:
max-active: 20
max-idle: 5
max-wait: -1
min-idle: 0
port: 6379
timeout: 1800000
#nacos服务地址
cloud:
nacos:
discovery:
server-addr: 192.168.174.128:8848
#国阳云短信服务
gyyun:
sms:
appCode: xxx
smsSignId: xxx
templateId:
4、创建启动类
创建SmsApplication.java
@ComponentScan({"com.rg"})//扫描swagger配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
public class SmsApplication {
public static void main(String[] args) {
SpringApplication.run(SmsApplication.class, args);
}
}
5、在service_sms的pom中引入依赖
<dependencies>
<!--阿里云短信发送依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
<!--javaMail-->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.8.0</version>
</dependency>
</dependencies>
6、编写工具类
RandomUtil:随机数工具类
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;
}
}
MailUtils:发邮件工具类
/**
* 发邮件工具类
*/
public final class MailUtils {
private static final String USER = "2422737092@qq.com"; // 发件人称号,同邮箱地址
private static final String PASSWORD = "cfznjonpuhlndhfj"; // 如果是qq邮箱可以使户端授权码,或者登录密码
/**
*
* @param to 收件人邮箱
* @param text 邮件正文
* @param title 标题
*/
/* 发送验证信息的邮件 */
public static boolean sendMail(String to, String text, String title){
try {
final Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.host", "smtp.qq.com");
// 发件人的账号
props.put("mail.user", USER);
//发件人的密码
props.put("mail.password", PASSWORD);
// 构建授权信息,用于进行SMTP进行身份验证
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// 用户名、密码
String userName = props.getProperty("mail.user");
String password = props.getProperty("mail.password");
return new PasswordAuthentication(userName, password);
}
};
// 使用环境属性和授权信息,创建邮件会话
Session mailSession = Session.getInstance(props, authenticator);
// 创建邮件消息
MimeMessage message = new MimeMessage(mailSession);
// 设置发件人
String username = props.getProperty("mail.user");
InternetAddress form = new InternetAddress(username);
message.setFrom(form);
// 设置收件人
InternetAddress toAddress = new InternetAddress(to);
message.setRecipient(Message.RecipientType.TO, toAddress);
// 设置邮件标题
message.setSubject(title);
// 设置邮件的内容体
message.setContent(text, "text/html;charset=UTF-8");
// 发送邮件
Transport.send(message);
return true;
}catch (Exception e){
e.printStackTrace();
}
return false;
}
public static void main(String[] args) throws Exception { // 做测试用
MailUtils.sendMail("2422737092@qq.com","你好啊,李哥,我正在用代码给你发送邮件哈哈哈。","测试邮件");
System.out.println("发送成功");
}
}
7、编写controller,根据手机号发送短信
@RestController
@RequestMapping("/smsservice/sms")
@CrossOrigin
public class SmsApiController {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
private SmsService smsService;
//阿里云发送短信
@GetMapping("sendMsgByAly/{phone}")
public R sendMsgByAly(@PathVariable String phone){
//生成随机值,传递到阿里云进行发送
String code = RandomUtil.getSixBitRandom();
Map <String, Object> param = new HashMap <>();
param.put("code", code);
//调用service进行短信发送
boolean isSend = smsService.sendMsgAly(param, phone);
if(isSend){
return R.ok();
}else{
return R.error().message("短信发送失败");
}
}
//使用国阳云发送短信
@GetMapping("sendMsgByGyy/{phone}")
public R sendMsgByGyy(@PathVariable String phone){
//从redis中获取验证码,如果获取则直接返回..==> 根据现实情况,用户可以多次发送.
// String code = redisTemplate.opsForValue().get(phone);
// if(!StringUtils.isEmpty(code)){
// return R.ok();
// }
//2.如果redis获取不到,进行阿里云发送
//生成随机值,传递到阿里云进行发送
String code = RandomUtil.getSixBitRandom();
//调用service进行短信发送
boolean isSend = smsService.sendMsgGyy(code, phone);
if(isSend){
//发送成功,把发送成功验证码放入到redis中
//设置有效时间
redisTemplate.opsForValue().set(phone, code, 30, TimeUnit.MINUTES);
return R.ok();
}else{
return R.error().message("短信发送失败");
}
}
}
8、编写service
@Service
public class SmsServiceImpl implements SmsService {
//通过阿里云发送短信(由于未申请下来,所以就先写个方法.项目中用国阳云/邮箱进行代替)
@Override
public boolean sendMsgAly(Map <String, Object> param, String phone) {
if(StringUtils.isEmpty(phone)) return false;
DefaultProfile profile =
DefaultProfile.getProfile("default", "LTAIq6nIPY09VROj", "FQ7UcixT9wEqMv9F35nORPqKr8XkTF");
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
//request.setProtocol(ProtocolType.HTTPS);
request.setMethod(MethodType.POST);
request.setDomain("dysmsapi.aliyuncs.com");
request.setVersion("2017-05-25");
request.setAction("SendSms");
request.putQueryParameter("PhoneNumbers", phone);
request.putQueryParameter("SignName", "我的谷粒在线教育网站");
request.putQueryParameter("TemplateCode", "templateCode");
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));
try {
CommonResponse response = client.getCommonResponse(request);
System.out.println(response.getData());
return response.getHttpResponse().isSuccess();
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
//使用国阳云发送短信
@Override
public boolean sendMsgGyy(String code, String phone) {
try {
//真正的短信发送 用于上线
// SmsUtil.sendMessage(ConstantPropertiesUtil.APP_CODE,phone,code,"30",ConstantPropertiesUtil.SMS_SIGNID,ConstantPropertiesUtil.TEMPLATE_ID);
//太贵了,先用邮箱代替....
MailUtils.sendMail("2422737092@qq.com", "[谷粒学院] 验证码: \n"+code+"您正在进行注册,若非本人操作,请勿泄露.30分钟内有效.", "谷粒学院验证码");
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
}
9、使用swagger进行测试
http://localhost:8005/swagger-ui.html
国阳云短信如何使用,可参考 如何利用阿里云市场购买并使用短信服务
六、用户登录注册接口【后端】
1、在service模块下创建子模块service_ucenter
2、使用代码生成器生成代码
(1)创建ucenter_member表
(2)利用代码生成器生成代码
3、配置application.properties
# 服务端口
server.port=8006
# 服务名
spring.application.name=service-ucenter
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli_edu
spring.datasource.username=root
spring.datasource.password=186259
spring.redis.host=192.168.174.128
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
#最小空闲
#nacos服务地址
spring.cloud.nacos.discovery.server-addr=192.168.174.128:8848
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/rg/ucenterservice/mapper/xml/*.xml
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
4、创建启动类
创建UcenterMemberApplication.java
@SpringBootApplication
@ComponentScan("com.rg")
@MapperScan("com.rg.ucenterservice.mapper")
public class UcenterMemberApplication {
public static void main(String[] args) {
SpringApplication.run(UcenterMemberApplication.class, args);
}
}
5、创建Controller编写登录和注册方法
@RestController
@RequestMapping("/ucenterservice/member")
@CrossOrigin
public class UcenterMemberController {
@Autowired
private UcenterMemberService memberService;
//用户登录
@PostMapping("login")
public R login(@RequestBody LoginVo loginVo){
//返回token,使用jwt生成
String token = memberService.login(loginVo);
return R.ok().data("token",token);
}
//用户注册
@PostMapping("register")
public R register(@RequestBody RegisterVo registerVo){
R result = memberService.register(registerVo);
return result;
}
//根据token获取用户信息
@GetMapping("getMemberInfo")
public R getMemberInfo(HttpServletRequest request){
String memberId = JwtUtils.getMemberIdByJwtToken(request);
UcenterMember member = memberService.getById(memberId);
return R.ok().data("userInfo", member);
}
}
6、创建service接口和实现类
@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {
@Autowired
private RedisTemplate<String,String> redisTemplate;
//用户登录
@Override
public String login(LoginVo loginVo) {
String mobile = loginVo.getMobile();
String password = loginVo.getPassword();
//判断用户名密码,是否为空
if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)){
throw new GuLiException(20001, "用户名或密码不能为空!");
}
//去数据库查询
QueryWrapper <UcenterMember> wrapper = new QueryWrapper <>();
wrapper.eq("mobile",mobile);
UcenterMember ucenterMember = this.baseMapper.selectOne(wrapper);
//判断UCenterMember是否为空
if(ucenterMember==null){//没有这个手机号
throw new GuLiException(20001,"用户名或密码错误!");
}
//判断密码是否相等
if(!MD5Utils.md5(password).equals(ucenterMember.getPassword())){
throw new GuLiException(20001,"用户名或密码错误!");
}
//判断是否被禁用
if(ucenterMember.getIsDisabled()){
throw new GuLiException(20001, "您的账号暂时被冻结,请联系管理员!");
}
//登录成功
//生成token字符串,使用jwt工具类
String jwtToken = JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getMobile());
return jwtToken;
}
//用户注册
@Override
public R register(RegisterVo registerVo) {
String code = registerVo.getCode();
String mobile = registerVo.getMobile();
String nickname = registerVo.getNickname();
String password = registerVo.getPassword();
//判断是否为空
if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password) || StringUtils.isEmpty(code) || StringUtils.isEmpty(nickname)){
return R.error().message("数据格式不正确!!");
}
//判断验证码是否正确
String redisCode = redisTemplate.opsForValue().get(mobile);
if(!code.equals(redisCode)){
return R.error().message("验证码无效或已过期,请重新发送验证码!");
}
//判断手机号是否已经注册
QueryWrapper <UcenterMember> wrapper = new QueryWrapper <>();
wrapper.eq("mobile",mobile);
Integer count = this.baseMapper.selectCount(wrapper);
if(count > 0){
return R.error().message("手机号已被注册!");
}
//判断昵称是否重复
wrapper = new QueryWrapper <UcenterMember>();
wrapper.eq("nickname",nickname);
count = this.baseMapper.selectCount(wrapper);
if(count > 0){
return R.error().message("昵称 昵称已被使用,换一个吧");
}
//进行数据插入
UcenterMember member = new UcenterMember();
member.setMobile(mobile);
member.setNickname(nickname);
member.setPassword(MD5Utils.md5(password));
member.setIsDeleted(false);//用户不禁用
member.setAvatar("https://guli-photos.oss-cn-hangzhou.aliyuncs.com/FatDog.jpg");
this.baseMapper.insert(member);
return R.ok();
}
}
7、MD5工具类
public class MD5Utils {
/**
* 使用md5的算法进行加密
*/
public static String md5(String plainText) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(
plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有md5这个算法!");
}
String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字
// 如果生成数字未满32位,需要前面补0
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
public static void main(String[] args) {
System.out.println(md5("123456"));
}
}
8、使用swagger测试
http://localhost:8006/swagger-ui.html
七、用户登录注册【前端】
1、在Nuxt环境中安装插件
(1)安装element-ui 和 vue-qriously插件
npm install element-ui
npm install vue-qriously
(2)修改配置文件 nuxt-swiper-plugin.js,使用插件
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper/dist/ssr'
import VueQriously from 'vue-qriously'
import ElementUI from 'element-ui' //element-ui的全部组件
import 'element-ui/lib/theme-chalk/index.css'//element-ui的css
Vue.use(ElementUI) //使用elementUI
Vue.use(VueQriously)
Vue.use(VueAwesomeSwiper)
2、用户注册功能前端整合
1、在api文件夹中定义接口
register.js
import request from '@/utils/request'
export default {
//发送短信
sendCode(phone){
return request({
url:`/smsservice/sms/sendMsgByGyy/${phone}`,
method:`get`
})
},
//注册的方法
registerMember(formItem){
return request({
url:`/ucenterservice/member/register`,
method:`post`,
data:formItem
})
}
}
2、在pages文件夹中创建注册页面,调用方法
(1)在layouts创建布局页面
sign.vue
<template>
<div class="sign">
<!--标题-->
<div class="logo">
<img src="~/assets/img/logo.png" alt="logo">
</div>
<!--表单-->
<nuxt/>
</div>
</template>
(2)创建注册页面
修改layouts文件夹里面default.vue页面,修改登录和注册超链接地址
在pages文件夹下,创建注册和登录页面
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' }]">
<divstyle="width: 100%;display: block;float: left;position:">
<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: 45%;">
<a href="javascript:" type="button" @click="getCodeFun()" :value="codeTest"style="border: none;background-color:">
<el-button type="success" round size="medium" :disabled="!sending">{{codeTest}}</el-button>
</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('userForm')">
</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: {
//注册提交的方法
submitRegister(formName){
this.$refs[formName].validate(valid=>{
if(!valid){
this.$message({
type:'error',
message:'请填写正确的注册信息,再进行注册!'
})
return false;
}
registerApi.registerMember(this.params).then(response=>{
console.log(response);
//提示登录成功
this.$message({
type:'success',
message:'注册成功'
})
//跳转到登录页面
this.$router.push({path:'/login'})
}) //这里不用写catch了,因为外部进行了处理
})
},
getCodeFun(){
if(this.sending==false || this.params.mobile==""){
return;
}
registerApi.sendCode(this.params.mobile).then(response=>{
//禁止发送
this.sending = false
//倒计时
this.timeDown()
//提示验证码发送成功
this.$message({
type:'success',
message:'验证码已发送'
})
})
},
timeDown() {
let result = setInterval(() => {
--this.second;
this.codeTest = "重新发送"+this.second+"(s)"
if (this.second < 1) {
clearInterval(result);
this.sending = true;
//this.disabled = false;
this.second = 60;
this.codeTest = "获取验证码"
}
}, 1000);
},
checkPhone (rule, value, callback) {
//debugger
if (!(/^1[34578]\d{9}$/.test(value))) {
return callback(new Error('手机号码格式不正确'))
}
return callback()
}
}
}</script>
3、修改nginx配置文件的端口
4、启动服务进行测试
3、用户登录功能前端整合
1、在api文件夹中创建登录的js文件,定义接口
import request from '@/utils/request'
export default {
//登录的方法
submitLoginUser(userInfo){
return request({
url:`/ucenterservice/member/login`,
method:'post',
data:userInfo
})
},
//根据token获取用户信息
getLoginUserInfo(){
return request({
url:`/ucenterservice/member/getMemberInfo`,
method:'get'
})
}
}
2、在pages文件夹中创建登录页面,调用方法
(1)安装js-cookie插件
npm install
(2)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://localhost:8160/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 cookie from 'js-cookie'
import loginApi from '@/api/login'
export default {
layout: 'sign',
data () {
return {
user:{
mobile:'',
password:''
},
loginInfo:{}
}
},
methods: {
submitLogin(){
//第一步调用接口进行登录,返回token字符串
loginApi.submitLoginUser(this.user).then(response=>{
//第二步,获取token字符串存放到token中
cookie.set('guli_token',response.data.data.token,{domain:'localhost'})
//第四步 调用接口 根据token获取用户信息,为了首页面显示
loginApi.getLoginUserInfo().then(response=>{
this.loginInfo = response.data.data.userInfo
//获取用户信息,存放到cookie里面
cookie.set("guli_ucenter",JSON.stringify(this.loginInfo),{domain:'localhost'})
//跳转页面
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>
登录和登录成功后首页显示具体实现过程分析:
(1) 调用接口登录返回token字符串
(2) 把第一步返回token字符串放到cookie里面
(3) 创建拦截器:判断cookie里面是否有token字符串,如果有,把token字符串放到header(请求头中)
(4) 根据token值,调用接口,根据token获取用户信息,为了首页面显示。调用接口返回用户信息放到cookie里面
(5) 从首页面显示用户信息,从第四步cookie获取用户信息
3、在request.js添加拦截器,用于传递token信息
import axios from 'axios'
import cookie from 'js-cookie'
import { MessageBox, Message } from 'element-ui'
// 创建axios实例
const service = axios.create({
baseURL: 'http://localhost:9001', // api的base_url
timeout: 15000 // 请求超时时间
})
//第三步 创建拦截器 http request 拦截器
service.interceptors.request.use(
config => {
//debugger
if (cookie.get('guli_token')) {
config.headers['token'] = cookie.get('guli_token');
}
return config
},
err => {
return Promise.reject(err);
})
// http response 拦截器
service.interceptors.response.use(
response => {
//debugger
if (response.data.code == 28004) {
console.log("response.data.resultCode是28004")
// 返回 错误代码-1 清除ticket信息并跳转到登录页面
//debugger
window.location.href="/login"
return
}else{
if (response.data.code !== 20000) {
//25000:订单支付中,不做任何提示
if(response.data.code != 25000) {
Message({
message: response.data.message || 'error',
type: 'error',
duration: 5 * 1000
})
return Promise.reject('error')
}
} else {
return response;
}
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error.response) // 返回接口返回的错误信息
});
export default
4、修改layouts中的default.vue页面
(1)显示登录之后的用户信息
<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 {
token: '',
loginInfo: {
id: '',
age: '',
avatar: '',
mobile: '',
nickname: '',
sex: ''
}
}
},
created() {
//获取路径里面的token值
this.token = this.$route.query.token
if(this.token){//判断路径中是否有token值
this.wxLogin()
return;
}
this.showInfo()
},
methods: {
//从cookie中获取用户信息
showInfo(){
//从cookie获取用户信息 cookie中存放的都是str
let userStr = cookie.get('guli_ucenter')
console.log(userStr);
if(userStr){
this.loginInfo = JSON.parse(userStr)
}
},
//退出
logout(){
//清空cookie值
cookie.set('guli_token','',{domain:'localhost'})
cookie.set('guli_ucenter','',{domain:'localhost'})
//回到首页面
window.location.href = "/"
}
}
}
</script>
(2)default.vue页面显示登录之后的用户信息
<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:"> </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>
4、登录注册效果展示
- 用户填写注册信息,填写收到的短信验证码(目前先用邮箱代替),进行注册
- 注册表单校验功能
- 注册成功
- 用户登录