尚医通项目101-123:前台用户系统、登录注册、邮箱登录

开始时间:2022-05-30
课程链接:课程链接:【尚医通】

前端页面
在这里插入图片描述
需要完成医院等级、地区查询、医院列表、医院名称模糊查询等功能
按等级查询
请添加图片描述
按地区查询
请添加图片描述
模糊查询
并能跳转到具体详情页面
跳转到详情页
请添加图片描述

预约挂号

在这里插入图片描述

在这里插入图片描述
预约须知
在这里插入图片描述
代码我就不写了,可以看gitee上后端的第九次提交
以及前端的第九次提交

注意调bug的时候一定要前后端端口名一致
这一部分没有太多新知识点,主要还是在写之前类似的
还要看前端有没有配置好依赖
调出命令框,再切换找到对应位置,
输入命令

F:\编程学习\尚医通项目\前端代码\shangyitong-front-end-code\vue-admin-template-test>cd vue-admin-template-master

F:\编程学习\尚医通项目\前端代码\shangyitong-front-end-code\vue-admin-template-test\vue-admin-template-master>npm install

npm install

如果maven导包导不进去,去找到maven配置的位置,把下载的包先删除掉
再重新reload
在这里插入图片描述

nacos报错
在这里插入图片描述

nacos报错,我就重新开了一个nacos文件,重新解压,再启动

又有报错了

我将gitee上的代码拷贝到自己电脑上
跑后端代码都没问题,用swagger测试过,
但是前端死活拿不到数据
之前出现这个问题,是因为端口不匹配或者是参数不匹配,只要改好前后端对接的地方一致就行
这个错我也按照这个思路,发现不行
在这里插入图片描述
然后我测试了所有接口,在前端都没能响应
我又远程登录我的台式电脑,一一比对代码,发现也没问题
于是我测试了一下我的本机nacos服务
发现我自己启动的两个微服务都注册上去了
那哪里出问题了呢
我再去看前端,拷贝了之前的代码,新开了一个窗口,还是不行

但是我注意到,我本机电脑和工位电脑的idea显示其实略有不同
在这里插入图片描述
上面是本机

下面是工位
在这里插入图片描述
我当时估计可能这就是一个显示问题,也没放在心上

我现在来看正常启动下的F12里面有什么东西

在这里插入图片描述
在这里插入图片描述
然后看报错的preview里面
在这里插入图片描述
同学帮我分析,可能是网关配置问题
我就看了我网关配置的代码,也都是一模一样
maven包不知道导了多少次了还是不能解决
但我发现了我本机电脑的gateway配置文件是灰色的
个人电脑上的application.properties只有这个gateway是灰的,两个微服务都是叶子型
在这里插入图片描述
然后他让我看看网关,我说我看过啊,两个微服务都在
但是此时发现我的gateway服务是没有注册的,我之前没太在意这一点
我再看了看工位电脑,发现nacos里面是有gateway的
在这里插入图片描述
问题肯定就在这里,那么是为什么会注册不上去呢
我又想到跟刚刚叶子灰了有没有关系
我定睛一看,发现我叶子灰了的resources文件没有mark as resources
于是我mark了一下
果然,问题解决了,此时gateway通了
在这里插入图片描述

登录设置

1,登录采取弹出层的形式
2,登录方式:
(1)手机号码+手机验证码
(2)微信扫描
3,无注册界面,第一次登录根据手机号判断系统是否存在,如果不存在则自动注册
4,微信扫描登录成功必须绑定手机号码,即:第一次扫描成功后绑定手机号,以后登录扫描直接登录成功
5,网关统一判断登录状态,如何需要登录,页面弹出登录层

JWT

JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上,
JWT最重要的作用就是对 token信息的防伪作用。

一个JWT由三个部分组成:公共部分、私有部分、签名部分。最后由这三者组合进行base64编码得到JWT。

运行时报错
JwtHelper测试

package com.bupt.yygh.common.helper;

import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;

import java.util.Date;

public class JwtHelper {

    //过期时间
    private static long tokenExpiration = 24*60*60*1000;
    //签名秘钥
    private static String tokenSignKey = "123456";

    //根据参数生成token
    public static String createToken(Long userId, String userName) {
        String token = Jwts.builder()
                .setSubject("YYGH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .claim("userId", userId)
                .claim("userName", userName)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }

    //根据token字符串得到用户id
    public static Long getUserId(String token) {
        if(StringUtils.isEmpty(token)) return null;

        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer)claims.get("userId");
        return userId.longValue();
    }

    //根据token字符串得到用户名称
    public static String getUserName(String token) {
        if(StringUtils.isEmpty(token)) return "";

        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("userName");
    }

    public static void main(String[] args) {
        String token = JwtHelper.createToken(1L, "lucy");
        System.out.println(token);
        System.out.println(JwtHelper.getUserId(token));
        System.out.println(JwtHelper.getUserName(token));
    }
}
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter

参考博客
添加依赖

<dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

输出

eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWKi5NUrJSiox099ANDXYNUtJRSq0oULIyNDM1sTA1NrM00FEqLU4t8kwBikGYfom5qUAtOaXJlUq1AHd1oZJBAAAA.GlrhPyM_P7o3jrVa9q5RpQgGbXpblwmglB3Vu8E5QlSDlzacbhrkYkfaIQRQuTNUXrck_3ycXZIyYQR9yvxMPA
1
lucy

搭建 service-user模块

在这里插入图片描述
如图搭建模块,先完成第一个需求
手机登录需求的demo

  • 修改pom.xml
  • 添加配置文件application.properties
# 服务端口
server.port=8203
# 服务名
spring.application.name=service-user

# 环境设置:dev、test、prod
spring.profiles.active=dev

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://47.94.15.66:3306/yygh_user?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root123

#返回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

#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/atguigu/yygh/user/mapper/xml/*.xml

  • 启动类
  • 配置网关
#设置路由id
spring.cloud.gateway.routes[2].id=service-user
#设置路由的uri
spring.cloud.gateway.routes[2].uri=lb://service-user
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[2].predicates= Path=/*/user/**

根据要求完成各个部分代码的编写
具体查看码云后端代码的更新

配置邮箱服务

本来这一板块是用阿里云短信的
但是阿里云短信个人用户申请不了了
于是考虑使用邮箱服务

首先引入依赖

     <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-email</artifactId>
            <version>1.5</version>
        </dependency>

然后写一个主方法测试一下

package com.bupt.yygh.hosp.sendEmail;

import org.apache.commons.mail.HtmlEmail;

public class sendEmailTest {
    public static void main(String[] args) {
        try {
            HtmlEmail email = new HtmlEmail();
            //用哪家服务器的就.什么就行,这里用的QQ邮箱
            email.setHostName("smtp.qq.com");
            //编码方式
            email.setCharset("utf-8");
            //发送的目的地
            email.addTo("XXXX@bupt.edu.cn");
            //发件人
            email.setFrom("XXXX@qq.com", "锦到黑");
            //发件人,以及其对应的授权码
            email.setAuthentication("XXXX@qq.com", "XXXXXX");
            //发送主体
            email.setSubject("测试");
            //发送内容
            email.setMsg("1234");
            email.send();
        } catch (Exception e) {
        }
    }
}

QQ邮箱的授权码,在邮箱-设置-账户-POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务-把上面俩服务开启一下
选择生成授权码就行了
在这里插入图片描述

我是参考的博客,向作者表示感谢
亲测有效果,再看如何整合进我们的服务里面
首先写一个Controller
由于原来的计划是用短信,所以很多命名是按短信来的,自己改为邮箱就行

package com.bupt.yygh.msm.controller;


import com.bupt.yygh.common.result.Result;
import com.bupt.yygh.msm.service.MsmService;
import com.bupt.yygh.msm.utils.RandomUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/api/msm")
public class MsmApiController {

    @Autowired
    private MsmService msmService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    //发送手机验证码
    @GetMapping("send/{phone}")
    public Result sendCode(@PathVariable String phone) {
        //从redis获取验证码,如果获取获取到,返回ok
        // key 手机号  value 验证码
        String code = redisTemplate.opsForValue().get(phone);
        if (!StringUtils.isEmpty(code)) {
            return Result.ok();
        }
        //如果从redis获取不到,
        // 生成六位验证码
        code = RandomUtil.getSixBitRandom();
        System.out.println(phone + " " + code);
        redisTemplate.opsForValue().set(phone, code, 2, TimeUnit.MINUTES);

        //调用service方法,通过整合邮箱服务进行发送
        boolean isSend = msmService.send(phone, code);
        //生成验证码放到redis里面,设置有效时间
        if (isSend) {
            //两分钟之内有效
            redisTemplate.opsForValue().set(phone, code, 2, TimeUnit.MINUTES);
            return Result.ok();
        } else {
            return Result.fail().message("发送邮件失败");
        }
    }
}

Service的实现类

package com.bupt.yygh.msm.service.impl;

import com.bupt.yygh.msm.service.MsmService;
import org.apache.commons.mail.HtmlEmail;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;


@Service
public class MsmServiceImpl implements MsmService {
    @Override
    public boolean send(String phone, String code) {
        //判断邮箱号是否为空
        if (StringUtils.isEmpty(phone)) {
            return false;
        }
        try {
            HtmlEmail email = new HtmlEmail();
            //用哪家服务器的就.什么就行,这里用的QQ邮箱
            email.setHostName("smtp.qq.com");
            //编码方式
            email.setCharset("utf-8");
            //发送的目的地
            email.addTo(phone);
            //发件人
            email.setFrom("XXX@qq.com", "锦到黑");
            //发件人,以及其对应的授权码
            email.setAuthentication("XXX@qq.com", "XXXbgji");
            //发送主体
            email.setSubject("邮箱验证码");
            //发送内容
            email.setMsg(code);
            email.send();
            return true;
        } catch (Exception e) {
        }
 }

测试一下
控制台输出看看我们这把的验证码生成的是什么,以及我要发送邮件的收件方地址
在这里插入图片描述
在Redis里面,可以找到这条数据
在这里插入图片描述
收到邮件
在这里插入图片描述
到时候的过程就是这样
用户先申请获取验证码
然后我这边生成验证码,验证码存到redis里面,再发邮件告诉用户
用户在前端输入验证码,我把前端验证码的值拿过来,再判断这个值是否和redis的值一致,一致就通过,不一致就不通过

前端界面补充

登录前端我们要与后端连接起来,直接可以看gitee上上传的代码
因为我们要存放登录信息
所以需要用上cookie,这也就需要我们安装cookie插件
终端里面使用

npm install js-cookie

我在尝试连接数据库时居然报错
Too many connections,发现问题出在我没有设置过自动断开连接,连接数累积太多了
参考博客
所以需要设置一个过期策略,让其过期
那么如何设置呢?
首先要找到mysql安装的位置
我是使用的docker容器
那docker容器默认安装mysql的位置是在

Docker安装好后默认路径为  /var/lib/docker ,其下的containers文件夹为容器文件夹,image为镜像文件夹

要修改文件,我们必须要找到配置文件,参考博客但是docker自身没有vim,所以要先安装vim,参考博客

根据以上三个博客的内容,应该可以将这个连接的问题解决

因为我们设置的时候是用email字段代替phone字段,那么我们就要把后端代码所有实体类,SQL语句以及表示为phone的地方全部要修改,包括在数据库中也要修改对应的字段名称。

把登录效果就做出来了
在这里插入图片描述

在这里插入图片描述

全局登录判断

当我们想从导航页进入具体的医院详情时,进入后要先判断是否处于登录状态,如果不处于则要先登录

头部注册和监听全局事件

注册一个全局登录事件,当需要登录层是,我们发送一个登录事件,头部监听登录事件,然后我们触发登录按钮的点击事件即可打开登录层
修改myheader.vue组件
1、引入Vue


import Vue from 'vue'

2、注册与监听事件

mounted() {
// 注册全局登录事件对象
window.loginEvent = new Vue();
// 监听登录事件
loginEvent.$on('loginDialogEvent', function () {
document.getElementById("loginDialog").click();
  })
// 触发事件,显示登录层:loginEvent.$emit('loginDialogEvent')
}

预约挂号页面调整
修改/pages/hospital/_hoscode.vue组件
1、引入cookie

import cookie from 'js-cookie'
2、修改方法
schedule(depcode) {
  // 登录判断
  let token = cookie.get('token')
  if (!token) {
    loginEvent.$emit('loginDialogEvent')
    return
  }
  window.location.href = '/hospital/schedule?hoscode=' + this.hospital.hoscode + "&depcode="+ depcode
},

说明:清除cookie,点击科室测试
看看效果
在这里插入图片描述
单击任意具体科室
弹出登录提示
在这里插入图片描述
登录之后才能单击这些具体的科室
在这里插入图片描述

用户认证与网关整合

思路:

  1. 所有请求都会经过服务网关,服务网关对外暴露服务,在网关进行统一用户认证;
  2. 既然要在网关进行用户认证,网关得知道对哪些url进行认证,所以我们得对url制定规则
  3. Api接口异步请求的,我们采取url规则匹配,如:/api//auth/,如凡是满足该规则的都必须用户认证

在服务网关添加fillter


@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    String path = request.getURI().getPath();
    System.out.println("==="+path);

    //内部服务接口,不允许外部访问
    if(antPathMatcher.match("/**/inner/**", path)) {
        ServerHttpResponse response = exchange.getResponse();
        return out(response, ResultCodeEnum.PERMISSION);
    }

    Long userId = this.getUserId(request);
    //api接口,异步请求,校验用户必须登录
    if(antPathMatcher.match("/api/**/auth/**", path)) {
        if(StringUtils.isEmpty(userId)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response, ResultCodeEnum.LOGIN_AUTH);
        }
    }
    return chain.filter(exchange);
}

在服务网关中判断用户登录状态
在网关中如何获取用户信息:
1,我们统一从header头信息中获取
如何判断用户信息合法:
登录时我们返回用户token,在服务网关中获取到token后,我在到redis中去查看用户id,如何用户id存在,则token合法,否则不合法
取用户信息

/**
 * 获取当前登录用户id
 * @param request
 * @return
 */
private Long getUserId(ServerHttpRequest request) {
    String token = "";
    List<String> tokenList = request.getHeaders().get("token");
    if(null  != tokenList) {
        token = tokenList.get(0);
    }
    if(!StringUtils.isEmpty(token)) {
        return JwtHelper.getUserId(token);
    }
    return null;
}

网关filter完整代码

/**
 * <p>
 * 全局Filter,统一处理会员登录与外部不允许访问的服务
 * </p>
 *
 * @author qy
 * @since 2019-11-21
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("==="+path);

        //内部服务接口,不允许外部访问
        if(antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response, ResultCodeEnum.PERMISSION);
        }

        Long userId = this.getUserId(request);
        //api接口,异步请求,校验用户必须登录
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            if(StringUtils.isEmpty(userId)) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response, ResultCodeEnum.LOGIN_AUTH);
            }
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * api接口鉴权失败返回数据
     * @param response
     * @return
     */
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        Result result = Result.build(null, resultCodeEnum);
        byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 获取当前登录用户id
     * @param request
     * @return
     */
    private Long getUserId(ServerHttpRequest request) {
        String token = "";
        List<String> tokenList = request.getHeaders().get("token");
        if(null  != tokenList) {
            token = tokenList.get(0);
        }
        if(!StringUtils.isEmpty(token)) {
            return JwtHelper.getUserId(token);
        }
        return null;
    }
}

调整前端yygh-site

请求服务器端接口时我们默认带上token,需要登录的接口如果token没有或者token过期,服务器端会返回208状态,然后发送登录事件打开登录弹出层登录
修改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',
    timeout: 15000 // 请求超时时间
})
// http request 拦截器
service.interceptors.request.use(
    config => {
    // token 先不处理,后续使用时在完善
    //判断cookie是否有token值
    if(cookie.get('token')) {
        //token值放到cookie里面
        config.headers['token']=cookie.get('token')
    }
    return config
},
  err => {
    return Promise.reject(err)
})
// http response 拦截器
service.interceptors.response.use(
    response => {
        //状态码是208
        if(response.data.code === 208) {
            //弹出登录输入框
            loginEvent.$emit('loginDialogEvent')
            return
        } else {
            if (response.data.code !== 200) {
                Message({
                    message: response.data.message,
                    type: 'error',
                    duration: 5 * 1000
                })
                return Promise.reject(response.data)
            } else {
                return response.data
            }
        }
    },
    error => {
        return Promise.reject(error.response)
})
export default service

结束时间:2022-06-17

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值