开始时间: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";
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;
}
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();
}
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
#设置路由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=
根据要求完成各个部分代码的编写 具体查看码云后端代码的更新
配置邮箱服务
本来这一板块是用阿里云短信的 但是阿里云短信个人用户申请不了了 于是考虑使用邮箱服务
首先引入依赖
<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();
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) {
String code = redisTemplate.opsForValue().get(phone);
if (!StringUtils.isEmpty(code)) {
return Result.ok();
}
code = RandomUtil.getSixBitRandom();
System.out.println(phone + " " + code);
redisTemplate.opsForValue().set(phone, code, 2, TimeUnit.MINUTES);
boolean isSend = msmService.send(phone, code);
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();
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();
})
}
预约挂号页面调整 修改/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,点击科室测试 看看效果 单击任意具体科室 弹出登录提示 登录之后才能单击这些具体的科室
用户认证与网关整合
思路:
- 所有请求都会经过服务网关,服务网关对外暴露服务,在网关进行统一用户认证;
- 既然要在网关进行用户认证,网关得知道对哪些url进行认证,所以我们得对url制定规则
- 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);
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合法,否则不合法 取用户信息
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完整代码
@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);
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;
}
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));
}
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'
const service = axios.create({
baseURL: 'http://localhost',
timeout: 15000
})
service.interceptors.request.use(
config => {
if(cookie.get('token')) {
config.headers['token']=cookie.get('token')
}
return config
},
err => {
return Promise.reject(err)
})
service.interceptors.response.use(
response => {
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
|