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

news2024/12/26 11:32:22

用户登陆业务介绍

单一服务器模式

早期单一服务器,用户认证。
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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140372.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++高阶数据结构】图

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

代码随想录拓展day7 649. Dota2 参议院;1221. 分割平衡字符串;5.最长回文子串;132. 分割回文串 II;673.最长递增子序列的个数

代码随想录拓展day7 649. Dota2 参议院&#xff1b;1221. 分割平衡字符串&#xff1b;5.最长回文子串&#xff1b;132. 分割回文串 II&#xff1b;673.最长递增子序列的个数 贪心和动态规划的题目。因为贪心其实没什么规律&#xff0c;所以就简要记录了。 649. Dota2 参议院 …

数据可视化系列-04数据大屏基础知识

文章目录5.销售数据看板5.1 了解数据大屏基础知识1.数据大屏简介&#xff1a;2.数据大屏使用场景3.数据大屏分类5.2 数据大屏的设计&#xff1a;1.大屏前端设计流程2.数据大屏设计尺寸解析3.可视化视觉设计5.3 大屏开发工具DataV&#xff1a;1.DataV数据可视化简介2.优势3.Data…

植物大战僵尸:遍历向日葵的生产速度

找到向日葵的吐出阳光的速度&#xff0c;向日葵生产阳光是一个周期性的,所以其内部是有一个计时器控制的,我们只要找到这个计时器并改写它的定时时间即可完成无限吐阳光的作用 向日葵的遍历技巧: 首先种下一个向日葵 -> 然后CE马上搜索->未知初始化回到游戏->然后在…

关于单相智能多用户远程预付费控系统的优化设计研究

摘要&#xff1a;由于现有系统仅对电表数据进行读取操作&#xff0c;存在成本较高和耗时较长的问题&#xff0c;为此对单相智能多用户远程预付费控系统优化设计进行研究。选择电能表子系统作为优化对象&#xff0c;选取78KO527A微控制器作为电能表子系统的控制核心&#xff0c;…

Java 并发编程 (三)Phaser

#Phaser 功能介绍 CyclicBarrier 解决了 CountDownLatch的缺点&#xff0c;但是其本身也仍然具备一定的缺陷&#xff0c;比如不可以动态添加parties 调用一次await 仅占用1个parties public class MyPhaser {private Phaser phaser new Phaser(3);public void testA(){System…

微信过期文件怎么找回?2个方法,轻松解决

现在很多小伙伴都喜欢使用微信发送文字、图片、语音、照片、文件等内容给对方。可是在其中&#xff0c;文件内容比较特殊。如果没有及时进行查看&#xff0c;过了一段时间就会变成过期文件&#xff0c;无法再进行查看了。微信过期文件怎么找回&#xff1f;今天小编分享2个简单好…

微型消防站一体化指挥作战解决方案

一、政策要求 近年政府对社区微型消防站提出了新的建设要求&#xff1a; 1、 大力发展多种形式消防队伍&#xff0c;逐步加强现役消防力量建设&#xff0c;发展政府专职消防队&#xff0c;企业事业单位专职消防队和志愿消防队。 2、 明确城市消防安全布局和公共消防设施的消…

vue 可视化表单设计器 自定义组件

有一款基于vue的可视化表单编辑器&#xff0c;直接双击或者拖拽组件&#xff0c;便可以创建一个表单&#xff0c;非常方便。今天为大家介绍这款编辑器&#xff1a; 官方地址&#xff1a;Variant Form - 可视化低代码表单 github地址&#xff1a;https://github.com/vform666/…

Redshift渲染器为什么这么受欢迎?

Redshift是用于高质量制作工作的渲染器 。它是很快的渲染引擎&#xff0c;并且可以很好地与许多第三方插件配合使用。在本文中&#xff0c;让我们深入了解Redshift的更多细节。什么是 Redshift 及其工作原理&#xff1f;Redshift 是一种有偏见的 GPU 渲染引擎&#xff0c;它支持…

【回答问题】ChatGPT上线了!给我推荐20个比较流行的OCR模型

给我推荐20个比较流行的OCR模型 好的&#xff0c;以下是我为你推荐的 20 个比较流行的 OCR 模型。 Tesseract CuneiForm Ocrad GOCR OCRopus OCR-ICR Ocropy Ocrad.js Ocracoke Ocralysis Ocrinum Ocrmypdf Ocrsaurus Ocrdroid Ocr-textscan Ocrfeeder Ocrmypicture Ocrus Oc…

RHCE-chrony服务部署

目录 要求&#xff1a; 思路&#xff1a; 配置过程&#xff1a; a: 1.开启两台服务器&#xff0c;开启chrony服务。 2.部署chrony服务 查看chrony服务状态 b: 区别&#xff1a; 配置过程: 修改完配置后&#xff0c;重启chrony服务 查看chrony状态&#xff1a; 小结一…

element-ui 多选框和级联选择的部分bug以及解决方法

前言 最近在开发一款使用了 element-ui 的低代码设计器&#xff0c;在开发的过程当中碰到了一些关于 element-ui 组件本身不合理的地方&#xff0c;并且在百度的基础上自己去阅读了一下 element-ui 的源码&#xff0c;也找出了这些问题的一个解决方案&#xff0c;下面就来看一…

steam搬砖是什么?怎么做呀?

steam平台是什么&#xff1f;它是国外一个集全球大部分网游于一体的游戏平台&#xff0c;玩过绝地求生端游&#xff08;吃鸡&#xff09;&#xff0c;csgo的朋友&#xff0c;对它都不陌生&#xff0c;就像国内的Wegame一样&#xff0c;现在玩英雄联盟的&#xff0c;都是通过Weg…

排序算法之冒泡算法

目录 排序算法介绍 冒泡排序 算法流程 算法实现 python C 排序算法介绍 《Hello算法》是GitHub上一个开源书籍&#xff0c;对新手友好&#xff0c;有大量的动态图&#xff0c;很适合算法初学者自主学习入门。而我则是正式学习算法&#xff0c;以这本书为参考&#xff0c…

返回数组所有元素中或每行(列)中,最小值的位置(位置号从0开始):argmin()函数

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 返回数组所有元素中或每行(列)中 最小值的位置&#xff08;位置号从0开始&#xff09; argmin()函数 选择题 下列说法错误的是? import numpy as np a np.array([[10,20,30,40],[15,20,25…

Electron 企业级应用开发实战(二)

这一讲会重点介绍如何集成 Node.js、使用 preload 脚本、进程间双向通信、上下文隔离等&#xff0c;为大家揭开 Electron 更强大的能力。 集成 Node.js 企业级桌面应用的资源都是本地化的&#xff0c;离线也能使用&#xff0c;所以需要把 html、js、css 这些资源都打包进去&a…

独立光伏-电池-柴油发电机组的能源管理系统的主干网研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

ESPI3接收机

18320918653 ESPI3 ESPI3|R&S ESPI3|二手EMI接收机|EMI预认证测试接收机|罗德与施瓦茨|EMC接收机|9KHz至3GHz品 牌&#xff1a;德国罗德与施瓦茨 | R&S | Rohde&Schwarz处于预认证级别的 R&S ESPI测试接收机有两种型号, 集成了罗德与施瓦茨公司认证级EMI测试…

springboot:除了OpenOffice还可以用它轻松实现文档在线预览功能【附带源码】

0. 引言 我们在项目中常常需要实现文档在线预览的功能&#xff0c;而文档种类繁多&#xff0c;除了pdf&#xff0c;还有word、text、excel、甚至还有mp3,mp4等多媒体文件。常用的手段是通过OpenOffice来将文档转换为pdf实现预览&#xff0c;本期我们就来看如何通过kkFileView实…