• Day112.尚医通:手机验证码登录功能


    目录

    一、手机验证码接口实现

    1、在service下创建service_user模块

    2、实现登录注册接口

    一、手机验证码登录生成token

    1、单点登录方案:

    2、JWT工具:

    3、实现JWP整合

    二、申请、集成阿里云短信服,搭建接口

    三、完成登录接口

    四、前端页面实现


    一、手机验证码接口实现

    1、在service下创建service_user模块

    详见课件

    1. # 服务端口
    2. server.port=8203
    3. # 服务名
    4. spring.application.name=service-user
    5. # 环境设置:dev、test、prod
    6. spring.profiles.active=dev
    7. # mysql数据库连接
    8. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    9. spring.datasource.url=jdbc:mysql://localhost:3306/yygh_user?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
    10. spring.datasource.username=root
    11. spring.datasource.password=********
    12. #返回json的全局时间格式
    13. spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    14. spring.jackson.time-zone=GMT+8
    15. #mybatis日志
    16. mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    17. #mongo 配置
    18. #spring.data.mongodb.uri=mongodb://192.168.86.86:27017/test
    19. # nacos服务地址
    20. spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    21. #配置mapper xml文件的路径
    22. mybatis-plus.mapper-locations=classpath:com/atguigu/yygh/user/mapper/xml/*.xml

    2、实现登录注册接口

    1. 分析接口

    *参数:LoginVo,请求 

    *返回值:map(登录标识、用户名)

    2. controller

    1. @Api(tags = "用户接口")
    2. @RestController
    3. @RequestMapping("/api/user")
    4. public class UserInfoApiController {
    5. @Autowired
    6. private UserInfoService userInfoService;
    7. @ApiOperation(value = "会员登录")
    8. @PostMapping("login")
    9. public R login(@RequestBody LoginVo loginVo, HttpServletRequest request) {
    10. loginVo.setIp(IpUtils.getIpAddr(request));
    11. Map map = userInfoService.login(loginVo);
    12. return R.ok().data(map);
    13. }
    14. }

    3. service

    1. @Service
    2. public class UserInfoServiceImpl extends
    3. ServiceImpl implements UserInfoService {
    4. //会员登录
    5. @Override
    6. public Map login(LoginVo loginVo) {
    7. //1.数据校验
    8. String phone = loginVo.getPhone();
    9. String code = loginVo.getCode();
    10. if(StringUtils.isEmpty(phone)||StringUtils.isEmpty(code)){
    11. throw new YyghException(20001,"注册信息有误");
    12. }
    13. //2. TODO 校验验证码
    14. //3.根据手机号查询用户信息
    15. QueryWrapper wrapper = new QueryWrapper<>();
    16. wrapper.eq("phone",phone);
    17. UserInfo userInfo = baseMapper.selectOne(wrapper);
    18. //4.用户信息为空,走注册功能
    19. if(userInfo==null){
    20. userInfo = new UserInfo();
    21. userInfo.setPhone(phone);
    22. userInfo.setStatus(1); //0锁定 1正常
    23. baseMapper.insert(userInfo);
    24. }
    25. //5.判断用户是否被锁定
    26. if(userInfo.getStatus()==0){
    27. throw new YyghException(20001,"用户已被锁定");
    28. }
    29. //6.补全用户信息
    30. //返回页面显示名称
    31. Map map = new HashMap<>();
    32. String name = userInfo.getName();
    33. if(StringUtils.isEmpty(name)) {
    34. name = userInfo.getNickName();
    35. }
    36. if(StringUtils.isEmpty(name)) {
    37. name = userInfo.getPhone();
    38. }
    39. //7. TODO 用户登录
    40. map.put("token", "");
    41. map.put("name", name);
    42. return map;
    43. }
    44. }

    一、手机验证码登录生成token

    1、单点登录方案:

    1.Session广播(广播风暴、浪费带宽)

    2.Redis+cookie

    3. token+cookie

    2、JWT工具:

    JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。

    JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上

    JWT最重要的作用就是对 token信息的防伪作用。

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

    3、实现JWP整合

    1. common_utils模块添加依赖

    1. <dependency>
    2. <groupId>io.jsonwebtokengroupId>
    3. <artifactId>jjwtartifactId>
    4. dependency>

    2. 添加JWT工具类

    1. public class JwtHelper {
    2. private static long tokenExpiration = 24*60*60*1000;
    3. private static String tokenSignKey = "123456";
    4. public static String createToken(Long userId, String userName) {
    5. String token = Jwts.builder()
    6. .setSubject("YYGH-USER")
    7. .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
    8. .claim("userId", userId)
    9. .claim("userName", userName)
    10. .signWith(SignatureAlgorithm.HS512, tokenSignKey)
    11. .compressWith(CompressionCodecs.GZIP)
    12. .compact();
    13. return token;
    14. }
    15. public static Long getUserId(String token) {
    16. if(StringUtils.isEmpty(token)) return null;
    17. Jws claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
    18. Claims claims = claimsJws.getBody();
    19. Integer userId = (Integer)claims.get("userId");
    20. return userId.longValue();
    21. }
    22. public static String getUserName(String token) {
    23. if(StringUtils.isEmpty(token)) return "";
    24. Jws claimsJws
    25. = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
    26. Claims claims = claimsJws.getBody();
    27. return (String)claims.get("userName");
    28. }
    29. public static void main(String[] args) {
    30. String token = JwtHelper.createToken(1L, "55");
    31. System.out.println(token);
    32. System.out.println(JwtHelper.getUserId(token));
    33. System.out.println(JwtHelper.getUserName(token));
    34. }
    35. }

    3. 完善service方法,实现接口功能、

    1. //会员登录
    2. @Override
    3. public Map login(LoginVo loginVo) {
    4. ...
    5. //7. TODO 用户登录
    6. String token = JwtHelper.createToken(userInfo.getId(), userInfo.getName());
    7. map.put("token", token);
    8. map.put("name", name);
    9. return map;

    二、申请、集成阿里云短信服,搭建接口

    官方文档:Java SDK - 短信服务 - 阿里云

    1. 开通鼎信短信服务 鼎信 (参考课件)

    需要申请签名、模板

    2. 在service模块下创建子模块service_msm

    导入依赖:

    1. <dependencies>
    2. <dependency>
    3. <groupId>com.alibabagroupId>
    4. <artifactId>fastjsonartifactId>
    5. dependency>
    6. <dependency>
    7. <groupId>com.aliyungroupId>
    8. <artifactId>aliyun-java-sdk-coreartifactId>
    9. dependency>
    10. dependencies>

    application.properties

    1. # 服务端口
    2. server.port=8204
    3. # 服务名
    4. spring.application.name=service-msm
    5. spring.redis.host=192.168.86.86
    6. spring.redis.port=6379
    7. spring.redis.database= 0
    8. spring.redis.timeout=1800000
    9. spring.redis.lettuce.pool.max-active=20
    10. spring.redis.lettuce.pool.max-wait=-1
    11. #最大阻塞等待时间(负数表示没限制)
    12. spring.redis.lettuce.pool.max-idle=5
    13. spring.redis.lettuce.pool.min-idle=0
    14. #最小空闲
    15. #返回json的全局时间格式
    16. spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    17. spring.jackson.time-zone=GMT+8
    18. # nacos服务地址
    19. spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

     添加网关配置

    1. #设置路由id
    2. spring.cloud.gateway.routes[3].id=service-msm
    3. #设置路由的uri
    4. spring.cloud.gateway.routes[3].uri=lb://service-msm
    5. #设置路由断言,代理servicerId为auth-service的/auth/路径
    6. spring.cloud.gateway.routes[3].predicates= Path=/*/msm/**

    3. 实现controller

    1. @Api(description = "发送验证码")
    2. @RestController
    3. @RequestMapping("/api/msm")
    4. public class MsmController {
    5. @Autowired
    6. private MsmService msmService;
    7. @Autowired
    8. private RedisTemplate redisTemplate;
    9. @ApiOperation(value = "发送验证码短信")
    10. @GetMapping(value = "/send/{phone}")
    11. public R send(@PathVariable String phone) {
    12. //1.根据手机号查询redis,获取验证码进行校验
    13. String redisCode = redisTemplate.opsForValue().get(phone);
    14. if(redisCode!=null){
    15. return R.ok();
    16. }
    17. //2。获取新的验证码,封装验证码
    18. String code = RandomUtil.getFourBitRandom();
    19. Map paramMap = new HashMap<>();
    20. paramMap.put("code",code);
    21. //3.调用接口发送短信
    22. boolean isSend = msmService.send(phone,paramMap);
    23. //4.将验证码存入Redis,时效五分钟
    24. if(isSend){
    25. redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);
    26. return R.ok();
    27. }else {
    28. return R.error().message("发送短信失败");
    29. }
    30. }
    31. }

    4. service

    1. @Service
    2. public class MsmServiceImpl implements MsmService {
    3. //发送验证码短信
    4. @Override
    5. public boolean send(String phone, Map paramMap) {
    6. //1.手机号验空
    7. if(StringUtils.isEmpty(phone)){
    8. return false;
    9. }
    10. //3.创建请求对象存入参数 AccessKey id、秘钥
    11. DefaultProfile profile =
    12. DefaultProfile.getProfile("default", "LTAI5tMGtK....", "baAdNzxAjnIMKU3....");
    13. IAcsClient client = new DefaultAcsClient(profile);
    14. CommonRequest request = new CommonRequest();
    15. //request.setProtocol(ProtocolType.HTTPS);
    16. request.setMethod(MethodType.POST);
    17. request.setDomain("dysmsapi.aliyuncs.com");
    18. request.setVersion("2017-05-25");
    19. request.setAction("SendSms");
    20. request.putQueryParameter("PhoneNumbers", phone);
    21. //要与申请的签名相同
    22. request.putQueryParameter("SignName", "我的谷粒在线教育网站");
    23. request.putQueryParameter("TemplateCode", "SMS_183195440");
    24. request.putQueryParameter("TemplateParam", JSONObject.toJSONString(paramMap));
    25. try {
    26. //4.使用客户端对象方法发送请求,获取响应
    27. CommonResponse response = client.getCommonResponse(request);
    28. //5.从响应中获取最终结果
    29. System.out.println(response.getData());
    30. return response.getHttpResponse().isSuccess();
    31. } catch (ClientException e) {
    32. e.printStackTrace();
    33. throw new YyghException(20001,"短信发送失败");
    34. }
    35. }
    36. }

    三、完成登录接口

    1. user模块导入redis配置

    1. #redis
    2. spring.redis.host=192.168.86.86
    3. spring.redis.port=6379
    4. spring.redis.database= 0
    5. spring.redis.timeout=1800000
    6. spring.redis.lettuce.pool.max-active=20
    7. spring.redis.lettuce.pool.max-wait=-1
    8. #最大阻塞等待时间(负数表示没限制)
    9. spring.redis.lettuce.pool.max-idle=5
    10. spring.redis.lettuce.pool.min-idle=0

    2. 修改service方法

    1. @Autowired
    2. RedisTemplate redisTemplate;
    3. //会员登录
    4. @Override
    5. public Map login(LoginVo loginVo) {
    6. //1.数据校验
    7. String phone = loginVo.getPhone();
    8. String code = loginVo.getCode();
    9. if(StringUtils.isEmpty(phone)||StringUtils.isEmpty(code)){
    10. throw new YyghException(20001,"注册信息有误");
    11. }
    12. //2. TODO 校验验证码
    13. //2.1 根据手机号从redis取出验证码
    14. String rediscode = redisTemplate.opsForValue().get(phone);
    15. //2.2 对比验证码
    16. //2.2 对比验证码,注意字符串空指针
    17. if(!code.equals(rediscode)){
    18. throw new YyghException(20001,"验证码有误");
    19. }
    20. //3.根据手机号查询用户信息
    21. QueryWrapper wrapper = new QueryWrapper<>();
    22. wrapper.eq("phone",phone);
    23. UserInfo userInfo = baseMapper.selectOne(wrapper);
    24. //4.用户信息为空,走注册功能
    25. if(userInfo==null){
    26. userInfo = new UserInfo();
    27. userInfo.setPhone(phone);
    28. userInfo.setStatus(1); //0锁定 1正常
    29. baseMapper.insert(userInfo);
    30. }
    31. //5.判断用户是否被锁定
    32. if(userInfo.getStatus()==0){
    33. throw new YyghException(20001,"用户已被锁定");
    34. }
    35. //6.补全用户信息
    36. //返回页面显示名称
    37. Map map = new HashMap<>();
    38. String name = userInfo.getName();
    39. if(StringUtils.isEmpty(name)) {
    40. name = userInfo.getNickName();
    41. }
    42. if(StringUtils.isEmpty(name)) {
    43. name = userInfo.getPhone();
    44. }
    45. //7. TODO 用户登录
    46. String token = JwtHelper.createToken(userInfo.getId(), userInfo.getName());
    47. map.put("token", token);
    48. map.put("name", name);
    49. return map;
    50. }

    四、前端页面实现

    1.创建API接口方法

    userinfo.js

    1. import request from '@/utils/request'
    2. const api_name = `/api/user`
    3. export default {
    4. //登录验证
    5. login(userInfo) {
    6. return request({
    7. url: `${api_name}/login`,
    8. method: `post`,
    9. data: userInfo
    10. })
    11. }
    12. }

    msm.js

    1. import request from '@/utils/request'
    2. const api_name = `/api/msm`
    3. export default {
    4. //发送验证短信
    5. sendCode(mobile) {
    6. return request({
    7. url: `${api_name}/send/${mobile}`,
    8. method: `get`
    9. })
    10. }
    11. }

    2. 修改layouts/myheader.vue

    1. <template>
    2. <div class="header-container">
    3. <div class="wrapper">
    4. <div class="left-wrapper v-link selected">
    5. <img style="width: 50px" width="50" height="50" src="~assets/images/logo.png">
    6. <span class="text">尚医通 预约挂号统一平台span>
    7. div>
    8. <div class="search-wrapper">
    9. <div class="hospital-search animation-show">
    10. <el-autocomplete class="search-input small" prefix-icon="el-icon-search" v-model="state"
    11. :fetch-suggestions="querySearchAsync" placeholder="点击输入医院名称" @select="handleSelect">
    12. <span slot="suffix" class="search-btn v-link highlight clickable selected">搜索 span>
    13. el-autocomplete>
    14. div>
    15. div>
    16. <div class="right-wrapper">
    17. <span class="v-link clickable">帮助中心span>
    18. <span v-if="name == ''" class="v-link clickable" @click="showLogin()" id="loginDialog">登录/注册span>
    19. <el-dropdown v-if="name != ''" @command="loginMenu">
    20. <span class="el-dropdown-link">
    21. {{ name }}<i class="el-icon-arrow-down el-icon--right">i>
    22. span>
    23. <el-dropdown-menu class="user-name-wrapper" slot="dropdown">
    24. <el-dropdown-item command="/user">实名认证el-dropdown-item>
    25. <el-dropdown-item command="/order">挂号订单el-dropdown-item>
    26. <el-dropdown-item command="/patient">就诊人管理el-dropdown-item>
    27. <el-dropdown-item command="/logout" divided>退出登录el-dropdown-item>
    28. el-dropdown-menu>
    29. el-dropdown>
    30. div>
    31. div>
    32. <el-dialog :visible.sync="dialogUserFormVisible" style="text-align: left;" top="50px" :append-to-body="true"
    33. width="960px" @close="closeDialog()">
    34. <div class="container">
    35. <div class="operate-view" v-if="dialogAtrr.showLoginType === 'phone'">
    36. <div class="wrapper" style="width: 100%">
    37. <div class="mobile-wrapper" style="position: static;width: 70%">
    38. <span class="title">{{ dialogAtrr.labelTips }}span>
    39. <el-form>
    40. <el-form-item>
    41. <el-input v-model="dialogAtrr.inputValue" :placeholder="dialogAtrr.placeholder"
    42. :maxlength="dialogAtrr.maxlength" class="input v-input">
    43. <span slot="suffix" class="sendText v-link" v-if="dialogAtrr.second > 0">{{
    44. dialogAtrr.second }}s span>
    45. <span slot="suffix" class="sendText v-link highlight clickable selected"
    46. v-if="dialogAtrr.second == 0" @click="getCodeFun()">重新发送 span>
    47. el-input>
    48. el-form-item>
    49. el-form>
    50. <div class="send-button v-button" @click="btnClick()"> {{ dialogAtrr.loginBtn }}div>
    51. div>
    52. <div class="bottom">
    53. <div class="wechat-wrapper" @click="weixinLogin()"><span class="iconfont icon">span>
    54. div>
    55. <span class="third-text"> 第三方账号登录 span>
    56. div>
    57. div>
    58. div>
    59. <div class="operate-view" v-if="dialogAtrr.showLoginType === 'weixin'">
    60. <div class="wrapper wechat" style="height: 400px">
    61. <div>
    62. <div id="weixinLogin">div>
    63. div>
    64. <div class="bottom wechat" style="margin-top: -80px;">
    65. <div class="phone-container">
    66. <div class="phone-wrapper" @click="phoneLogin()"><span class="iconfont icon">span>
    67. div>
    68. <span class="third-text"> 手机短信验证码登录 span>
    69. div>
    70. div>
    71. div>
    72. div>
    73. <div class="info-wrapper">
    74. <div class="code-wrapper">
    75. <div><img src="//img.114yygh.com/static/web/code_login_wechat.png" class="code-img">
    76. <div class="code-text"><span class="iconfont icon">span>微信扫一扫关注
    77. div>
    78. <div class="code-text"> “快速预约挂号”div>
    79. div>
    80. <div class="wechat-code-wrapper"><img src="//img.114yygh.com/static/web/code_app.png"
    81. class="code-img">
    82. <span class="iconfont icon">span><div class="code-text"> 扫一扫下载div>
    83. <div class="code-text"> “预约挂号”APPdiv>
    84. div>
    85. div>
    86. <div class="slogan">
    87. <div>xxxxxx官方指定平台div>
    88. <div>快速挂号 安全放心div>
    89. div>
    90. div>
    91. div>
    92. el-dialog>
    93. div>
    94. template>

    3. JS实现

    4. 测试

     

  • 相关阅读:
    jpa 连接sqlserver 发布tomcat报错 SunJSSE
    Flutter-无限循环滚动标签
    OceanBase 分布式数据库【信创/国产化】- OceanBase Demo 环境搭建
    关于操作系统中对进程管理的认识
    自动驾驶行业观察之2023上海车展-----车企发展趋势(2)
    Linux CentOS7 lrzsz工具
    HTTP协议
    RustDay04------Exercise[11-20]
    【git】超详细使用指令
    深度学习 神经网络(5)逻辑回归二分类-Pytorch实现乳腺癌预测
  • 原文地址:https://blog.csdn.net/a111042555/article/details/126007649