YYGH-5-邮箱登录

邮箱登录

需求

完成这样一个案例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UiodrrXJ-1649086667296)(C:\Users\86157\AppData\Local\Temp\1649081195035.png)]

1,登录采取弹出层的形式

2,登录方式:

(1)手机号码+手机验证码

(2)微信扫描

3,无注册界面,第一次登录根据手机号判断系统是否存在,如果不存在则自动注册

4,微信扫描登录成功必须绑定手机号码,即:第一次扫描成功后绑定手机号,以后登录扫描直接登录成功

5,网关统一判断登录状态,如何需要登录,页面弹出登录层

登录完成

思路

1、前端发送登录信息登录邮箱

2、service-msm模块发送邮箱验证码,同时将验证码存放到redis中,方便也会登录的时候用到

3、前端得知验证码已经发送成功、发送登录的信息,包含邮箱和验证码

4、service-user获取登录传来的信息,校验验证码

5、如果验证码通过返回用户名和token,方便判断是否登录

service-user模块

创建service-user的maven的模块

尤其要注意的是要配置网关,和启动类

配置网关

#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/**

启动类开启mubatis的注解

@EnableDiscoveryClient
@SpringBootApplication
@ComponentScan(basePackages = "com.example") //扫描文件
@EnableFeignClients(basePackages = "com.example")
public class ServiceUserApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceUserApplication.class,args);
    }
}

Controller

//用手机号登录接口
@PostMapping("login")
public Result login(@RequestBody LoginVo loginVo){
    Map<String,Object> info = userInfoService.login(loginVo);
    return Result.ok(info);
}

Model

@Data
@ApiModel(description="登录对象")
public class LoginVo {

    @ApiModelProperty(value = "openid")
    private String openid;

    @ApiModelProperty(value = "手机号")
    private String phone;

    @ApiModelProperty(value = "密码")
    private String code;

    @ApiModelProperty(value = "IP")
    private String ip;
}

Service

@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    @Autowired
    public JavaMailSenderImpl javaMailSenderImpl;

    @Autowired
    public RedisTemplate<String,String> redisTemplate;


    //手机登录接口
    @Override
    public Map<String, Object> login(LoginVo loginVo) {
        //从loginVo获取输入的手机号和验证码
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();
        //判断手机号和验证码是否未空
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
            throw new YyghException(ResultCodeEnum.PARAM_ERROR);
        }
        //判断手机验证码和输入的验证码是否一致
        //手机验证码,这里我们用邮箱代替
        String s = redisTemplate.opsForValue().get(phone);
        System.out.println("======"+s);
        if(!code.equals(s)){
            throw new YyghException(ResultCodeEnum.CODE_ERROR);
        }
        System.out.println(phone);
        //判断是否是第一次登录
        QueryWrapper<UserInfo> qw = new QueryWrapper<>();
        qw.eq("phone",phone);
        //是第一次登录
        UserInfo userInfo = baseMapper.selectOne(qw);
        if (userInfo == null) {
            //说明是第一次使用这个手机号登录
            userInfo = new UserInfo();
            userInfo.setName("");
            userInfo.setPhone(phone);
            userInfo.setStatus(1);
            baseMapper.insert(userInfo);
        }
        //如果该用户被禁用就返回这个异常
        if(userInfo.getStatus() == 0){
            throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }
        //不是第一次登录直接登录
        Map<String, Object> map = new HashMap<>();
        //返回登录信息
        //返回登录用户名
        //返回token信息
        String name = userInfo.getName();
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getNickName();
        }
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getPhone();
        }
        map.put("name", name);
        //token生成,JWT工具
        String token = JwtHelper.createToken(userInfo.getId(), name);
        map.put("token", token);
        return map;

    }
}

Repository类

@Repository
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {

}
service-msm模块

这是一个发送验证码给邮箱的模块,如果要了解更多springboot发送邮箱关注我之前写过的一篇博客,任务安全。

导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
</dependencies>

Controlle

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

    @Autowired
    private MsmService msmService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    //发送手机号
    @GetMapping("send/{phone}")
    public Result sendCode(@PathVariable(value = "phone") String phone){
        log.info(phone);
        //从redis获取验证码,如果获取到返回ok
        /*String code = redisTemplate.opsForValue().get(phone);
        if (!StringUtils.isEmpty(code)){
            return Result.ok();
        }*/
        //如果从redis取不到
        //生成验证码,通过整合短信服务进行发送
        String code = RandomUtil.getFourBitRandom();

        msmService.send(phone,code);

        redisTemplate.opsForValue().set(phone, code,2, TimeUnit.MINUTES);
        return Result.ok();
    }
}

Service类

@Service
@Slf4j
public class MsmServiceImpl implements MsmService {

    @Autowired
    public JavaMailSenderImpl javaMailSenderImpl;

    //邮箱发送
    @Override
    @Async
    public void send(String phone, String code) {
        log.info(phone);
        //判断邮箱是否为空
        if (StringUtils.isEmpty(phone)) {
            return;
        }
        //1.创建一个简单的的消息邮件
        System.out.println(code);
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setSubject("正在登录YYGH系统");
        simpleMailMessage.setText("验证码:"+code);
        simpleMailMessage.setTo(phone);
        simpleMailMessage.setFrom("2590416618@qq.com");
        javaMailSenderImpl.send(simpleMailMessage);
    }
}

Util类

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个数据
     *
     * @param list
     * @param n
     * @return
     */
    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;
    }
}

值得注意的是我们的启动类需要排除DataSource这一部分,因为我们的配置类里面没有关于mysql的东西

@EnableDiscoveryClient//注册服务
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan(basePackages = "com.example") //swagger扫描文件
@EnableFeignClients(basePackages = "com.example")
@EnableAsync
public class ServiceMsmApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceMsmApplication.class,args);
    }
}

测试一下我们的接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9piGKN6s-1649086667297)(C:\Users\86157\AppData\Local\Temp\1648957047630.png)]

成功运行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ONyM9Q2J-1649086667298)(C:\Users\86157\AppData\Local\Temp\1648957058275.png)]

前端

登录成功,我们要把用户信息记录在cookie里面

命令行执行:

npm install js-cookie

<template>
  <div class="header-container">
    <div class="wrapper">
      <!-- logo -->
      <div class="left-wrapper v-link selected">
        <img style="width: 50px" width="50" height="50" src="~assets/images/logo.png">
        <span class="text">YYGH 预约挂号统一平台</span>
      </div>
      <!-- 搜索框 -->
      <div class="search-wrapper">
        <div class="hospital-search animation-show">
          <div id="search" style="display: block;">
            <el-autocomplete
              class="search-input"
              prefix-icon="el-icon-search"
              v-model="hosname"
              :fetch-suggestions="querySearchAsync"
              :trigger-on-focus="false"
              @select="handleSelect"
              placeholder="点击输入医院名称"
            >
              <span slot="suffix" class="search-btn v-link highlight clickable selected">搜索 </span>
            </el-autocomplete>
          </div>
        </div>
      </div>
      <!-- 右侧 -->
      <div class="right-wrapper">
        <span class="v-link clickable">帮助中心</span>

        <span v-if="name == ''" class="v-link clickable" @click="showLogin()" id="loginDialog">登录/注册</span>

        <el-dropdown v-if="name != ''" @command="loginMenu">
              <span class="el-dropdown-link">
                {{ name }}<i class="el-icon-arrow-down el-icon--right"></i>
              </span>
          <el-dropdown-menu class="user-name-wrapper" slot="dropdown">
            <el-dropdown-item command="/user">实名认证</el-dropdown-item>
            <el-dropdown-item command="/order">挂号订单</el-dropdown-item>
            <el-dropdown-item command="/patient">就诊人管理</el-dropdown-item>
            <el-dropdown-item command="/logout" divided>退出登录</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </div>
    </div>

    <!-- 登录弹出层 -->
    <el-dialog :visible.sync="dialogUserFormVisible" style="text-align: left;" top="50px" :append-to-body="true"
               width="960px" @close="closeDialog()">
      <div class="container">

        <!-- 手机登录 #start -->
        <div class="operate-view" v-if="dialogAtrr.showLoginType === 'phone'">
          <div class="wrapper" style="width: 100%">
            <div class="mobile-wrapper" style="position: static;width: 70%">
              <span class="title">{{ dialogAtrr.labelTips }}</span>
              <el-form>
                <el-form-item>
                  <el-input v-model="dialogAtrr.inputValue" :placeholder="dialogAtrr.placeholder"
                            :maxlength="dialogAtrr.maxlength" class="input v-input">
                    <span slot="suffix" class="sendText v-link" v-if="dialogAtrr.second > 0">{{
                        dialogAtrr.second
                      }}s </span>
                    <span slot="suffix" class="sendText v-link highlight clickable selected"
                          v-if="dialogAtrr.second == 0" @click="getCodeFun()">重新发送 </span>
                  </el-input>
                </el-form-item>
              </el-form>
              <div class="send-button v-button" @click="btnClick()"> {{ dialogAtrr.loginBtn }}</div>
            </div>
            <div class="bottom">
              <div class="wechat-wrapper" @click="weixinLogin()"><span
                class="iconfont icon"></span></div>
              <span class="third-text"> 第三方账号登录 </span></div>
          </div>
        </div>
        <!-- 手机登录 #end -->

        <!-- 微信登录 #start -->
        <div class="operate-view" v-if="dialogAtrr.showLoginType === 'weixin'">
          <div class="wrapper wechat" style="height: 400px">
            <div>
              <div id="weixinLogin"></div>
            </div>
            <div class="bottom wechat" style="margin-top: -80px;">
              <div class="phone-container">
                <div class="phone-wrapper" @click="phoneLogin()"><span
                  class="iconfont icon"></span></div>
                <span class="third-text"> 手机短信验证码登录 </span></div>
            </div>
          </div>
        </div>
        <!-- 微信登录 #end -->

        <div class="info-wrapper">
          <div class="code-wrapper">
            <div><img src="//img.114yygh.com/static/web/code_login_wechat.png" class="code-img">
              <div class="code-text"><span class="iconfont icon"></span>微信扫一扫关注
              </div>
              <div class="code-text"> “快速预约挂号”</div>
            </div>
            <div class="wechat-code-wrapper"><img
              src="//img.114yygh.com/static/web/code_app.png"
              class="code-img">
              <div class="code-text"> 扫一扫下载</div>
              <div class="code-text"> “预约挂号”APP</div>
            </div>
          </div>
          <div class="slogan">
            <div>xxxxxx官方指定平台</div>
            <div>快速挂号 安全放心</div>
          </div>
        </div>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import cookie from 'js-cookie'
import Vue from 'vue'

import userInfoApi from '@/api/userInfo'
import smsApi from '@/api/msm'
// import hospitalApi from '@/api/hosp/hospital'
// import weixinApi from '@/api/user/weixin'

const defaultDialogAtrr = {
  showLoginType: 'phone', // 控制手机登录与微信登录切换

  labelTips: '电子邮箱', // 输入框提示

  inputValue: '', // 输入框绑定对象
  placeholder: '请输入您的邮箱', // 输入框placeholder
  maxlength: 20, // 输入框长度控制

  loginBtn: '获取验证码', // 登录按钮或获取验证码按钮文本

  sending: true,      // 是否可以发送验证码
  second: -1,        // 倒计时间  second>0 : 显示倒计时 second=0 :重新发送 second=-1 :什么都不显示
  clearSmsTime: null  // 倒计时定时任务引用 关闭登录层清除定时任务
}
export default {
  data() {
    return {
      userInfo: {
        phone: '',
        code: '',
        openid: ''
      },

      dialogUserFormVisible: false,
      // 弹出层相关属性
      dialogAtrr: defaultDialogAtrr,

      name: '' // 用户登录显示的名称
    }
  },

  created() {
    this.showInfo()
  },

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

    //初始化微信js
    const script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
    document.body.appendChild(script)

    // 微信登录回调处理
    let self = this;
    window["loginCallback"] = (name, token, openid) => {
      debugger
      self.loginCallback(name, token, openid);
    }
  },

  methods: {
    schedule(depcode) {
      // 登录判断
      let token = cookie.get('token')
      if (!token) {
        loginEvent.$emit('loginDialogEvent')
        return
      }
      window.location.href = '/hospital/schedule?hoscode=' + this.hospital.hoscode + "&depcode="+ depcode
    },



    loginCallback(name, token, openid) {
      // 打开手机登录层,绑定手机号,改逻辑与手机登录一致
      if (openid != '') {
        this.userInfo.openid = openid

        this.showLogin()
      } else {
        this.setCookies(name, token)
      }
    },

    // 绑定登录或获取验证码按钮
    btnClick() {
      // 判断是获取验证码还是登录
      if (this.dialogAtrr.loginBtn == '获取验证码') {
        this.userInfo.phone = this.dialogAtrr.inputValue

        // 获取验证码
        this.getCodeFun()
      } else {
        // 登录
        this.login()
      }
    },

    // 绑定登录,点击显示登录层
    showLogin() {
      this.dialogUserFormVisible = true

      // 初始化登录层相关参数
      this.dialogAtrr = {...defaultDialogAtrr}
    },

    // 登录
    login() {
      debugger
      this.userInfo.code = this.dialogAtrr.inputValue

      if (this.dialogAtrr.loginBtn == '正在提交...') {
        this.$message.error('重复提交')
        return;
      }

      if (this.userInfo.code == '') {
        this.$message.error('验证码必须输入')
        return;
      }
      if (this.userInfo.code.length != 4) {
        this.$message.error('验证码格式不正确')
        return;
      }

      this.dialogAtrr.loginBtn = '正在提交...'
      userInfoApi.login(this.userInfo).then(response => {
        console.log(response.data)
        // 登录成功 设置cookie
        this.setCookies(response.data.name, response.data.token)
      }).catch(e => {
        this.dialogAtrr.loginBtn = '马上登录'
      })
    },

    setCookies(name, token) {
      cookie.set('token', token, {domain: 'localhost'})
      cookie.set('name', name, {domain: 'localhost'})
      window.location.reload()
    },

    // 获取验证码
    getCodeFun() {
      if (!(/^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(this.userInfo.phone))) {
        this.$message.error('邮箱格式不正确')
        return;
      }

      // 初始化验证码相关属性
      this.dialogAtrr.inputValue = ''
      this.dialogAtrr.placeholder = '请输入验证码'
      this.dialogAtrr.maxlength = 4
      this.dialogAtrr.loginBtn = '马上登录'

      // 控制重复发送
      if (!this.dialogAtrr.sending) return;

      // 发送短信验证码
      this.timeDown();
      this.dialogAtrr.sending = false;
      smsApi.sendCode(this.userInfo.phone).then(response => {
        this.timeDown();
      })
    },

    // 倒计时
    timeDown() {
      if (this.clearSmsTime) {
        clearInterval(this.clearSmsTime);
      }
      this.dialogAtrr.second = 90;

      this.dialogAtrr.labelTips = '验证码已发送至' + this.userInfo.phone
      this.clearSmsTime = setInterval(() => {
        --this.dialogAtrr.second;
        if (this.dialogAtrr.second < 1) {
          clearInterval(this.clearSmsTime);
          this.dialogAtrr.sending = true;
          this.dialogAtrr.second = 0;
        }
      }, 1000);
    },

    // 关闭登录层
    closeDialog() {
      if (this.clearSmsTime) {
        clearInterval(this.clearSmsTime);
      }
    },

    showInfo() {
      let token = cookie.get('token')
      if (token) {
        this.name = cookie.get('name')
        console.log(this.name)
      }
    },

    loginMenu(command) {
      if ('/logout' == command) {
        cookie.set('name', '', {domain: 'localhost'})
        cookie.set('token', '', {domain: 'localhost'})

        //跳转页面
        window.location.href = '/'
      } else {
        window.location.href = command
      }
    },

    /*    // 搜索
        querySearchAsync(queryString, cb) {
          if(queryString == '') return
          hospitalApi.getByHosname(queryString).then(response => {
            for (let i = 0, len = response.data.length; i < len; i++) {
              response.data[i].value = response.data[i].hosname
            }
            cb(response.data)
          })
        },*/

    handleSelect(item) {
      window.location.href = '/hospital/' + item.hoscode
    },

    /*weixinLogin() {
      this.dialogAtrr.showLoginType = 'weixin'

      weixinApi.getLoginParam().then(response => {
        var obj = new WxLogin({
          self_redirect:true,
          id: 'weixinLogin', // 需要显示的容器id
          appid: response.data.appid, // 公众号appid wx*******
          scope: response.data.scope, // 网页默认即可
          redirect_uri: response.data.redirectUri, // 授权成功后回调的url
          state: response.data.state, // 可设置为简单的随机数加session用来校验
          style: 'black', // 提供"black"、"white"可选。二维码的样式
          href: '' // 外部css文件url,需要https
        })
      })
    },*/

    phoneLogin() {
      this.dialogAtrr.showLoginType = 'phone'
      this.showLogin()
    }
  }
}
</script>

这样就完成了我们前端的整合

服务网关

我们现在有这个样一个需求点击挂号,如果没有登录就弹出登录页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hOPkxqlb-1649086667298)(C:\Users\86157\AppData\Local\Temp\1649086269240.png)]

类似于这样

com.example.yygh.gateway.Filter.AuthGlobalFilter

@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/**/department/**", 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;
    }
}

之前我们在service-user的login方法中封装了token现在就有用了获取登录状态,有token就是登录,没有就是没登录

这样做之后就可以做到这样的效果

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值