谷粒学院——十三章、登录与注册

用户登陆业务介绍

单一服务器模式

早期单一服务器,用户认证。
image.png
缺点:单点性能压力,无法扩展。
image.png

SSO 模式(单点登陆)

分布式,SSO(single sign on)模式,也叫单点登陆模式。
image.png
优点:

  • 用户身份信息独立管理,更好的分布式管理。
  • 可以自己扩展安全策略

缺点:

  • 认证服务器访问压力较大。

注:基于微服务开发,选择token的形式相对较多,因此我使用token作为用户认证的标准。

单点登陆的三种方式

集群部署与单点登陆:
image.png

单点登录的三种方式:
image.png
token是按照一定规则生成字符串,包含用户信息,规则是怎么样的不一定。一般采用通用的规则 —— JWT

JWT 介绍

JWT

image.png
该对象为一个很长的字符串,字符之间通过 **“.” **分隔符分为三个子串。
每一个子串表示了一个功能块,总共有以下三个部分:

  • 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);
    }
}

开通阿里云短信服务

image.png
开通之后,进入控制台:
image.png
点击添加模版:
image.png
然后选择验证码,填写基本信息,点击常用模版库可以直接复制常用的模版,然后点击提交
image.png
之后等待审核通过即可:
image.png
然后申请签名管理,点击添加签名:
image.png
填写基本信息,然后提交:
image.png
然后等待审核通过即可:
image.png

代码实现

工具类

建立一个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 实体类,创建和更新时间的字段要手动添加下面的注解:
image.png

配置文件

# 服务端口
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
测试登陆方法:
image.png
image.png

测试注册方法:
输入验证码
然后测试:
image.png
数据库里面也添加上了:
image.png

用户登录注册【前端】

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

image.png
改完以后需要重启 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 中修改:
image.png

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>

整合登陆页面

image.png

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">&nbsp;</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">&nbsp;</em>
    </a>
    <q class="red-point" style="display: none">&nbsp;</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>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肉丝不切片

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值