用户登陆业务介绍
单一服务器模式
早期单一服务器,用户认证。
缺点:单点性能压力,无法扩展。
SSO 模式(单点登陆)
分布式,SSO(single sign on)模式,也叫单点登陆模式。
优点:
- 用户身份信息独立管理,更好的分布式管理。
- 可以自己扩展安全策略
缺点:
- 认证服务器访问压力较大。
注:基于微服务开发,选择token的形式相对较多,因此我使用token作为用户认证的标准。
单点登陆的三种方式
集群部署与单点登陆:
单点登录的三种方式:
token是按照一定规则生成字符串,包含用户信息,规则是怎么样的不一定。一般采用通用的规则 —— JWT
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 算法。
整合 JWT
初始化
引入依赖
在 common_utils 模块的 pom.xml 文件中引入依赖:
<dependencies>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
工具类
在 common_utils 模块里面创建工具类:
public class JwtUtils {
// token 过期时间
public static final long EXPIRE = 1000 * 60 * 60 * 24;
// 用于生成签名哈希的密钥(下面的值是随便写的)
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
/*
根据 id 和 nickname 生成 token 字符串
*/
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
// 设置 JWT 头信息
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("auth-user")
// 设置过期时间
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
// 设置token主体部分,存储用户信息
.claim("id", id)
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
/*
判断token是否存在与有效,传入参数是字符串
*/
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是否存在与有效,传入参数是 request
*/
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
*/
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");
}
}
整合阿里云短信微服务
项目初始化
创建 Maven 项目
新建一个项目:service_msm
引入依赖
在 service_msm 模块的 pom.xml 文件中引入依赖:
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
</dependencies>
配置文件
# 服务端口
server.port=8005
# 服务名
spring.application.name=service-msm
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql 数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
# 返回 json 的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# Nacos 服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 配置 redis
spring.redis.host=127.0.0.1
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
# mybatis 日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
配置启动类
创建包:com.atguigu.msm,然后创建启动类:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan("com.atguigu")
@EnableDiscoveryClient // nacos注册
@EnableFeignClients // 服务调用
public class MsmApplication {
public static void main(String[] args) {
SpringApplication.run(MsmApplication.class, args);
}
}
开通阿里云短信服务
开通之后,进入控制台:
点击添加模版:
然后选择验证码,填写基本信息,点击常用模版库可以直接复制常用的模版,然后点击提交
之后等待审核通过即可:
然后申请签名管理,点击添加签名:
填写基本信息,然后提交:
然后等待审核通过即可:
代码实现
工具类
建立一个utils包,然后编写:
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个数据
*/
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;
}
}
controller 层
@RestController
@RequestMapping("/edumsm/msm")
@CrossOrigin
public class MsmController {
@Autowired
private MsmService msmService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
//发送短信的方法
@GetMapping("send/{phone}")
public R sendMsm(@PathVariable String phone) {
//1 从redis获取验证码,如果获取到直接返回
String code = stringRedisTemplate.opsForValue().get("gulixy:msm:"+phone);
if(!StringUtils.isEmpty(code)) {
return R.ok();
}
//2 如果redis获取不到,进行阿里云发送
//生成随机值,传递阿里云进行发送
code = RandomUtil.getFourBitRandom();
Map<String,Object> param = new HashMap<>();
param.put("code",code);
//调用service发送短信的方法
boolean isSend = msmService.send(param,phone);
if(isSend) {
//发送成功,把发送成功验证码放到redis里面
//设置有效时间
stringRedisTemplate.opsForValue().set("gulixy:msm:"+phone,code,5, TimeUnit.MINUTES);
return R.ok();
} else {
return R.error().message("短信发送失败");
}
}
}
service 层
public interface MsmService {
// 发送短信的方法
boolean send(Map<String, Object> param, String phone);
}
实现类:(我这里用的阿里的短信测试,应该是可以免费用一个月)
@Service
public class MsmServiceImpl implements MsmService {
// 发送短信的方法
@Override
public boolean send(Map<String, Object> param, String phone) {
// 判断手机号是否为空
if (!StringUtils.isEmpty(phone)) return false;
// 传入自己的阿里云ID和密钥
DefaultProfile profile =DefaultProfile.getProfile("default", "LTAI5t7BfQjZdxDLKWAjmZRf", "Lrmb49TR9x2wZb9BSmErVouOyRO9Lh");
IAcsClient client = new DefaultAcsClient(profile);
// 设置相关参数,固定的写法
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain("dysmsapi.aliyuncs.com");
request.setSysVersion("2017-05-25");
request.setSysAction("SendSms");
// 设置发送相关的参数
// TODO
request.putQueryParameter("PhoneNumbers", phone); //手机号
request.putQueryParameter("SignName", "阿里云短信测试"); //申请阿里云 签名名称
request.putQueryParameter("TemplateCode", "SMS_154950909"); //申请阿里云 模板code
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param)); //验证码数据,转换json数据传递
// 最终发送
try {
CommonResponse response = client.getCommonResponse(request);
boolean success = response.getHttpResponse().isSuccess();
return success;
} catch (ClientException e) {
e.printStackTrace();
return false;
}
}
}
登陆注册业务
项目初始化
新建 Maven 模块
service_ucenter
代码生成器
复制之前项目里面的代码生成器,改写表名和包名,自动生成如下:
public class CodeGenerator {
@Test
public void run() {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("D:\\java\\springcloud\\guli_xueyuan\\guli_parent\\service\\service_ucenter" + "/src/main/java");
gc.setAuthor("testjava");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
//UserServie
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.ID_WORKER_STR); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("educenter"); //模块名
//包 com.atguigu.eduservice
pc.setParent("com.atguigu");
//包 com.atguigu.eduservice.controller
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("ucenter_member");
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}
注意 UcenterMember 实体类,创建和更新时间的字段要手动添加下面的注解:
配置文件
# 服务端口
server.port=8006
# 服务名
spring.application.name=service-ucenter
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
# 返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# Nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 配置 redis
spring.redis.host=127.0.0.1
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
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:org/jyunkai/ucenter/mapper/xml/*.xml
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
启动类
@SpringBootApplication
@EnableDiscoveryClient // nacos注册
@EnableFeignClients // 服务调用
@ComponentScan(basePackages = {"com.atguigu"})
@MapperScan("com.atguigu.educenter.mapper")
public class UcenterApplication {
public static void main(String[] args) {
SpringApplication.run(UcenterApplication.class, args);
}
}
编写登陆和注册接口
工具类
下面是 MD5 加密的工具类,放在 common_utils 模块下面即可:
public final class MD5 {
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 static void main(String[] args) {
System.out.println(MD5.encrypt("111111"));
}
}
创建 vo 实体类
在 service_ucenter 模块的entity包下建立vo包,然后建立实体类:
**LoginVo **
@Data
@ApiModel(value = "登陆对象", description = "登陆对象")
public class LoginVo {
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "密码")
private String password;
}
RegisterVo
@Data
@ApiModel(value="注册对象", description="注册对象")
public class RegisterVo {
@ApiModelProperty(value = "昵称")
private String nickname;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "验证码")
private String code;
}
controller 层
注意 @RequestBody 只能用 POST 提交,否则会报错!
@RestController
@RequestMapping("/ucenter/member")
@CrossOrigin
public class MemberController {
@Autowired
private MemberService memberService;
/**
* 登陆方法
* loginVo 封装了手机号和密码
*/
@ApiOperation(value = "会员登陆")
@PostMapping("login")
public R loginUser(@RequestBody LoginVo loginVo) {
// 调用 service 方法实现登陆,返回 token 值(使用 jwt 生成)
String token = memberService.login(loginVo);
return R.ok().data("token", token);
}
/**
* 注册方法
* registerVo 封装了注册信息
*/
@ApiOperation(value = "注册会员")
@PostMapping("register")
public R register(@RequestBody RegisterVo registerVo) {
memberService.register(registerVo);
return R.ok();
}
/**
* 根据 token 获取用户信息,用于显示昵称和头像
*/
@ApiOperation(value = "根据 token 获取用户信息")
@GetMapping("getMemberInfo")
public R getMemberInfo(HttpServletRequest request) {
// 调用 jwt 工具类的方法,根据 request 对象获取头信息,返回用户 id
String memberId = JwtUtils.getMemberIdByJwtToken(request);
// 查询数据库,根据用户 id 查询用户信息
UcenterMember member = memberService.getById(memberId);
return R.ok().data("userInfo", member);
}
}
service 层
public interface MemberService extends IService<Member> {
//登陆
String login(LoginVo loginVo);
//注册
void register(RegisterVo registerVo);
}
实现类:
这里用到了 MD5 对密码进行加密,然后再去和数据库中的密码进行比较。
@Service
public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> implements MemberService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//登录的方法
@Override
public String login(UcenterMember member) {
//获取登录手机号和密码
String mobile = member.getMobile();
String password = member.getPassword();
//手机号和密码非空判断
if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
throw new GuliException(20001, "手机号和密码不能为空");
}
//判断手机号是否正确
QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
wrapper.eq("mobile", mobile);
UcenterMember mobileMember = getOne(wrapper);
//判断查询对象是否为空
if (mobileMember == null) {
throw new GuliException(20001, "该手机号不存在");
}
//判断密码
//把输入的密码进行MD5加密,再和数据库密码进行比较
if (!MD5.encrypt(password).equals(mobileMember.getPassword())) {
throw new GuliException(20001, "密码错误");
}
//判断用户是否禁用
if (mobileMember.getIsDisabled()) {
throw new GuliException(20001, "用户被禁用");
}
//登录成功 生成token字符串,使用jwt工具类
String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());
return jwtToken;
}
//注册的方法
@Override
public void 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)) {
throw new GuliException(20001, "请填写完整信息");
}
//判断验证码
//获取redis验证码
String redisCode = stringRedisTemplate.opsForValue().get("gulixy:msm:"+mobile);
if (!code.equals(redisCode)) {
throw new GuliException(20001, "验证码不正确");
}
//判断手机号是否重复,表里面存在相同手机号不进行添加
QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
wrapper.eq("mobile", mobile);
int count = count(wrapper);
if (count > 0) {
throw new GuliException(20001, "该手机号已存在");
}
//数据添加数据库中
UcenterMember member = new UcenterMember();
member.setMobile(mobile).setNickname(nickname)
.setPassword(MD5.encrypt(password)).setIsDisabled(false)
.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132");
save(member);
}
}
Swagger 测试
启动项目,然后访问:http://localhost:8006/swagger-ui.html
测试登陆方法:
测试注册方法:
输入验证码
然后测试:
数据库里面也添加上了:
用户登录注册【前端】
1、在 nuxt 环境中安装插件
1、安装插件
npm install element-ui vue-qriously js-cookie --registry=https://registry.npm.taobao.org
2、修改配置文件
plugins/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、配置 Nginx
改完以后需要重启 Nginx
3、整合登陆注册页面
整合注册页面
1、创建注册布局页面 sign.vue
在 layouts 目录下创建 sign.vue
<template>
<div class="sign">
<!--标题-->
<div class="logo">
<img src="~/assets/img/logo.png" alt="logo" />
</div>
<!--表单-->
<nuxt />
</div>
</template>
2、修改登陆和注册的超链接地址
在 layous/default.vue 中修改:
3、创建 register.js
在 api 目录下创建 register.js
import request from '@/utils/request'
export default{
// 根据手机号码发送短信
sendCode(phone) {
return request({
url: `/edumsm/msm/send/${phone}`,
method: 'get'
})
},
// 注册的方法
registerMember(formItem) {
return request({
url: '/ucenter/member/register',
method: 'post',
data: formItem
})
}
}
4、创建注册页面 register.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' }]">
<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: {
//注册提交的方法
submitRegister() {
registerApi.registerMember(this.params)
.then(response => {
//提示注册成功
this.$message({
type: 'success',
message: "注册成功"
})
//跳转登录页面
this.$router.push({ path: '/login' })
})
},
//倒计时
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.sendCode(this.params.mobile)
.then(response => {
this.sending = false
//调用倒计时的方法
this.timeDown()
})
},
checkPhone(rule, value, callback) {
//debugger
if (!(/^1[34578]\d{9}$/.test(value))) {
return callback(new Error('手机号码格式不正确'))
}
return callback()
}
}
}
</script>
整合登陆页面
1、创建 login.js
api 目录下创建 login.js
import request from '@/utils/request'
export default {
// 登陆的方法
submitLogin(userInfo) {
return request({
url: '/ucenter/member/login',
method: 'post',
data: userInfo
})
},
// 根据 token 值获取用户信息
getLoginUserInfo() {
return request({
url: '/ucenter/member/getMemberInfo',
method: 'get'
})
}
}
2、修改 request.js
修改 utils 目录下的 request.js 文件,添加拦截器:
import axios from 'axios'
import {MessageBox, Message} from 'element-ui'
import cookie from 'js-cookie'
// 创建axios实例
const service = axios.create({
baseURL: 'http://localhost:9001', // api的base_url
timeout: 20000 // 请求超时时间
})
// http request 拦截器
service.interceptors.request.use(
config => {
// 判断cookie中是否有名称叫 guli_token 的数据
if (cookie.get('guli_token')) {
// 把获取到的cookie值放到header中
config.headers['token'] = cookie.get('guli_token')
}
return config
},
err => {
return Promise.reject(err)
})
export default service
3、创建 login.vue
在 pages 目录下创建 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 :rules="[{required: true, message: '请输入手机号码', trigger: 'blur'}, { validator: checkPhone, trigger: 'blur' }]" class="input-prepend restyle" prop="mobile">
<div>
<el-input v-model="user.mobile" type="text" placeholder="手机号" />
<i class="iconfont icon-phone" />
</div>
</el-form-item>
<el-form-item :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]" class="input-prepend" prop="password">
<div>
<el-input v-model="user.password" type="password" placeholder="密码" />
<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 cookie from 'js-cookie'
import loginApi from '@/api/login'
export default {
layout: 'sign',
data() {
return {
user: {
// 封装用于登录的用户对象
mobile: '',
password: ''
},
// 用于获取接口传来的token中的对象
loginInfo: {}
}
},
methods: {
// 登陆的方法
submitLogin() {
// 第一步,调用接口进行登陆,返回 token 字符串
loginApi.submitLoginUser(this.user).then(response => {
// 第二步,将 token 放到 cookie 中
// 第1个是cookie名称,第2个是参数值,第3个是参数作用范围
cookie.set('guli_token', response.data.data.token, { domain: 'localhost' })
// 第三步,创建拦截器(代码在request.js中)
// 第四步,调用接口根据 token 获取用户信息,用于页面显示
loginApi.getLoginUserInfo().then(response => {
// 获取用户信息,放到 cookie 里面
this.loginInfo = JSON.stringify(response.data.data.userInfo)
cookie.set('guli_ucenter', 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>
4、修改 default.vue
修改 layouts 目录下的 default.vue 页面,用来显示登陆用户信息
上面:
<!-- 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 undis">
<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 undis">
<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>
</ul>
下面:
<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'
export default {
data() {
return {
token: '',
loginInfo: {
id: '',
age: '',
avatar: '',
mobile: '',
nickname: '',
sex: ''
}
}
},
created() {
this.showInfo()
},
methods: {
// 退出的方法
logout() {
// 清空cookie值
cookie.set('guli_token', '', { domain: 'localhost' })
cookie.set('guli_ucenter', '', { domain: 'localhost' })
// 回到首页面
window.location.href = '/'
},
// 创建方法从cookie中获取信息
showInfo() {
// 从cookie中获取信息
var userStr = cookie.get('guli_ucenter')
// 转字符串转换成json对象(js对象)
if (userStr) {
this.loginInfo = JSON.parse(userStr)
}
}
}
}
</script>