尚医通 (三十一) --------- 手机登录

news2025/7/7 21:29:18

目录

  • 一、登录需求
    • 1. 登录效果
    • 2. 登录需求
  • 二、登录
    • 1. 搭建 service-user 模块
    • 2. 添加用户基础类
    • 3. 登录 API 接口
    • 4. 生成 token
    • 5. 阿里云短信
    • 6. 登录前端
    • 7. 登录全局事件
    • 8. myheader.vue 完整代码
  • 三、用户认证与网关整合


一、登录需求

1. 登录效果

在这里插入图片描述

2. 登录需求

① 登录采取弹出层的形式

② 登录方式:

  • 手机号码+手机验证码
  • 微信扫描

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

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

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

二、登录

1. 搭建 service-user 模块

① 搭建 service-user 模块

搭建过程参考service-hosp模块

在这里插入图片描述

② 修改配置

A、修改 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>service</artifactId>
        <groupId>com.fancy.yygh</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>service-user</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.fancy.yygh</groupId>
            <artifactId>service-cmn-client</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
</project>

B、添加配置文件 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://192.168.38.130:3306/yygh_user?characterEncoding=utf-8&useSSL=false
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

#配置mapper xml文件的路径
# 服务端口
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://192.168.38.130:3306/yygh_user?characterEncoding=utf-8&useSSL=false
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

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

C、启动类

@SpringBootApplication
@ComponentScan(basePackages = "com.fancy.yygh")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.fancy.yygh")
public class ServiceUserApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceUserApplication.class, args);
    }
}

D、配置网关

#设置路由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/**

2. 添加用户基础类

① 添加 model

说明:由于实体对象没有逻辑,我们已经统一导入com.fancy.yygh.model.user.UserInfo

在这里插入图片描述

在这里插入图片描述

② 添加 Mapper

添加 com.fancy.yygh.user.UserInfoMapper

import com.fancy.yygh.model.user.UserInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface UserInfoMapper extends BaseMapper<UserInfo> {
}

在这里插入图片描述

添加 UserInfoMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fancy.yygh.mapper.UserInfoMapper">

</mapper>

③ 添加 service 接口及实现类

添加 com.fancy.yygh.user.service.UserInfoService 接口

public interface UserInfoService extends IService<UserInfo> {
}

添加 com.fancy.yygh.user.service.impl.UserInfoServiceImpl 接口实现

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

在这里插入图片描述

④ 添加 controller

添加 com.fancy.yygh.user.api.UserInfoApiController 类

@RestController
@RequestMapping("/api/user")
public class UserInfoApiController {

    @Autowired
    private UserInfoService userInfoService;
}

3. 登录 API 接口

① 添加 service 接口与实现

A、在 UserInfoService 类添加接口

//会员登录
Map<String, Object> login(LoginVo loginVo);

B、在 UserInfoServiceImpl 类实现接口

@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
    @Override
    public Map<String, Object> login(LoginVo loginVo) {
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();
        //校验参数
        if(StringUtils.isEmpty(phone) ||
                StringUtils.isEmpty(code)) {
            throw new YyghException(ResultCodeEnum.PARAM_ERROR);
        }

        //TODO 校验校验验证码

        //手机号已被使用
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("phone", phone);
        //获取会员
        UserInfo userInfo = baseMapper.selectOne(queryWrapper);
        if(null == userInfo) {
            userInfo = new UserInfo();
            userInfo.setName("");
            userInfo.setPhone(phone);
            userInfo.setStatus(1);
            this.save(userInfo);
        }
        //校验是否被禁用
        if(userInfo.getStatus() == 0) {
            throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }

        //TODO 记录登录

        //返回页面显示名称
        Map<String, Object> map = new HashMap<>();
        String name = userInfo.getName();
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getNickName();
        }
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getPhone();
        }
        map.put("name", name);
        map.put("token", "");
        return map;
    }
}

说明:

  • 验证码先注释,后续校验
  • 登录成功生成 token,后续讲解

② 添加 controller 接口

A、在 UserInfoApiController 类添加方法

@ApiOperation(value = "会员登录")
@PostMapping("login")
public Result login(@RequestBody LoginVo loginVo, HttpServletRequest request) {
    loginVo.setIp(IpUtil.getIpAddr(request));
    Map<String, Object> info = userInfoService.login(loginVo);
    return Result.ok(info);
}

B、添加 IpUtil 工具类

public class IpUtil {
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST = "127.0.0.1";
    private static final String SEPARATOR = ",";

    public static String getIpAddr(HttpServletRequest request) {
        System.out.println(request);
        String ipAddress;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (LOCALHOST.equals(ipAddress)) {
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            // "***.***.***.***".length()
            if (ipAddress != null && ipAddress.length() >15) {
                if (ipAddress.indexOf(SEPARATOR) >0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress = "";
        }
        return ipAddress;
    }
}

4. 生成 token

① JWT介绍

JWT 工具:

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

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

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

JWT的原理:

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

在这里插入图片描述
A、公共部分

主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。

Key=ATGUIGU

B、私有部分

用户自定义的内容,根据实际需要真正要封装的信息。

userInfo{用户的Id,用户的昵称nickName}

C、签名部分

SaltIP: 当前服务器的IP地址 !{linux 中配置代理服务器的ip}

主要用户对 JWT 生成字符串的时候,进行加密{盐值}
最终组成 key+salt+userInfo token!

Base64 编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以把 Base64 编码解成明文,所以不要在 JWT 中放入涉及私密的信息。

② 集成JWT

在 common-util 模块 pom.xml 添加依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>

版本已在 yygh-parent 父模块 pom.xml 添加

在 common-util 模块编写 JwtHelper 类

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, "55");
        System.out.println(token);
        System.out.println(JwtHelper.getUserId(token));
        System.out.println(JwtHelper.getUserName(token));
    }
}

说明:执行 main 方法测试

③ 完善登录 service 接口

修改 UserInfoServiceImpl 类登录方法

public Map<String, Object> loginUser(LoginVo loginVo) {
  	…………
    //jwt生成 token 字符串
    String token = JwtHelper.createToken(userInfo.getId(), name);
    map.put("token",token);
    return map;
}

④ 使用 Swagger 测试接口

测试多次,第一次注册,以后直接登录

请求参数:

在这里插入图片描述

5. 阿里云短信

① 阿里云短信介绍

在这里插入图片描述
A、开通阿里云短信服务

在这里插入图片描述
B、添加签名管理与模板管理

在这里插入图片描述
在这里插入图片描述

注:审批通过后方可使用

C、获取用户 AccessKey

在这里插入图片描述

② 搭建 service-msm 模块

A、搭建 service-msm 模块

搭建过程参考 service-hosp 模块

B、修改配置

修改 pom.xml

<dependencies>
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
    </dependency>
</dependencies>

添加配置文件 application.properties

# 服务端口
server.port=8204
# 服务名
spring.application.name=service-msm

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

spring.redis.host=192.168.44.165
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

# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
        
aliyun.sms.regionId=default
aliyun.sms.accessKeyId=LT6I0Y5633pX89qC
aliyun.sms.secret=jX8D04Dm12I3gGKj345FYSzu0fq8mT

启动类

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
@EnableDiscoveryClient
public class ServiceMsmApplication {
   public static void main(String[] args) {
      SpringApplication.run(ServiceMsmApplication.class, args);
   }
}

配置网关

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

③ 封装注册短信验证码接口

A、添加配置类

@Component
public class ConstantPropertiesUtils implements InitializingBean {

    @Value("${aliyun.sms.regionId}")
    private String regionId;

    @Value("${aliyun.sms.accessKeyId}")
    private String accessKeyId;

    @Value("${aliyun.sms.secret}")
    private String secret;

    public static String REGION_Id;
    public static String ACCESS_KEY_ID;
    public static String SECRECT;

    @Override
    public void afterPropertiesSet() throws Exception {
        REGION_Id=regionId;
        ACCESS_KEY_ID=accessKeyId;
        SECRECT=secret;
    }
}

B、封装 service 接口和实现类

public interface MsmService {
    //发送手机验证码
    boolean send(String phone, String code);
}

@Service
public class MsmServiceImpl implements MsmService {
    @Override
    public boolean send(String phone, String code) {
        //判断手机号是否为空
        if(StringUtils.isEmpty(phone)) {
            return false;
        }
        //整合阿里云短信服务
        //设置相关参数
        DefaultProfile profile = DefaultProfile.getProfile(ConstantPropertiesUtils.REGION_Id, ConstantPropertiesUtils.ACCESS_KEY_ID, ConstantPropertiesUtils.SECRECT);
        IAcsClient client = new DefaultAcsClient(profile);
        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");

        //手机号
        request.putQueryParameter("PhoneNumbers", phone);
        //签名名称
        request.putQueryParameter("SignName", "我的谷粒在线教育网站");
        //模板code
        request.putQueryParameter("TemplateCode", "SMS_180051135");
        //验证码  使用json格式   {"code":"123456"}
        Map<String,Object> param = new HashMap();
        param.put("code",code);
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));

        //调用方法进行短信发送
        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            return response.getHttpResponse().isSuccess();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return false;
    }
}

C、封装 controller 接口

@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();
        //调用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("发送短信失败");
        }
    }
}

D、工具类

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;
    }
}

E、完善登录 service 接口

修改 UserInfoServiceImpl 类登录方法,编写验证码校验

//校验校验验证码
String mobleCode = redisTemplate.opsForValue().get(phone);
if(!code.equals(mobleCode)) {
    throw new YyghException(ResultCodeEnum.CODE_ERROR);
}

6. 登录前端

① 封装 API 请求

创建 API 文件夹,创建 /api/userInfo.js

import request from '@/utils/request'

const api_name = `/api/user`

export default {
    login(userInfo) {
        return request({
            url: `${api_name}/login`,
            method: `post`,
            data: userInfo
        })
    }
}

创建 /api/msm.js

import request from '@/utils/request'

const api_name = `/api/msm`

export default {
    sendCode(mobile) {
        return request({
            url: `${api_name}/send/${mobile}`,
            method: `get`
        })
    }
}

② 安装 cookie

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

命令行执行:

npm install js-cookie

③ 添加登录组件

登录层是一个公共层,因此我们把它放在头部组件里面

修改 layouts/myheader.vue 文件

 <!-- 登录弹出层 -->
<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>

说明:登录成功用户信息记录cookie

<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'

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

  labelTips: '手机号码', // 输入框提示

  inputValue: '', // 输入框绑定对象
  placeholder: '请输入您的手机号', // 输入框placeholder
  maxlength: 11, // 输入框长度控制

  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()
  },
  methods: {
    // 绑定登录或获取验证码按钮
    btnClick() {
      // 判断是获取验证码还是登录
      if(this.dialogAtrr.loginBtn == '获取验证码') {
        this.userInfo.phone = this.dialogAtrr.inputValue
        // 获取验证码
        this.getCodeFun()
      } else {
        // 登录
        this.login()
      }
    },

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

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

    // 登录
    login() {
      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 != 6) {
        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 (!(/^1[34578]\d{9}$/.test(this.userInfo.phone))) {
        this.$message.error('手机号码不正确')
        return;
      }

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

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

      // 发送短信验证码
      this.timeDown();
      this.dialogAtrr.sending = false;
      smsApi.sendCode(this.userInfo.phone).then(response => {
        this.timeDown();
      }).catch(e => {
        this.$message.error('发送失败,重新发送')
        // 发送失败,回到重新获取验证码界面
        this.showLogin()
      })
    },

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

      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
      }
    },

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

    weixinLogin() {
      this.dialogAtrr.showLoginType = 'weixin'
    },

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

④ 头部显示登录状态

A、获取登录信息

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

B、登录页面控制

<!-- 右侧 -->
<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>

说明:<el-dropdown-item command="/user">实名认证</el-dropdown-item> command 的值为下拉列表的访问路径,即:实名认证的访问路径,后续会有对应的页面,目前我们将它处理好,方便后续使用

添加下拉列表事件处理:

loginMenu(command) {
  if('/logout' == command) {
    cookie.set('name', '', {domain: 'localhost'})
    cookie.set('token', '', {domain: 'localhost'})
    //跳转页面
    window.location.href = '/'
  } else {
    window.location.href = command
  }
},

7. 登录全局事件

目前登录层在 myheader 组件里面,登录按钮也在同一个组件里面,我们点击登录,调用 showLogin() 方法即可。

目前的问题是,我们在预约挂号页面,选择科室去挂号时我们需要判断当前是否登录,如果登录可以进入下一个页面;如果没有登录需要显示登录层,那么这个问题怎么解决呢,我们不能直接调用头部登录方法,我们目前的组件是包含在nuxt里面的问题总是能够解决的,其实很简单,我们可以注册一个全局登录事件,当需要登录层是,我们发送一个登录事件,头部监听登录事件,然后我们触发登录按钮的点击事件即可打开登录层,下面我们来试试。

① 头部注册和监听登录事件

修改 myheader.vue 组件

A、引入Vue

import Vue from 'vue'

B、注册与监听事件

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

② 预约挂号页面调整

修改 /pages/hospital/_hoscode.vue 组件

A、引入cookie

import cookie from 'js-cookie'

B、修改方法

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,点击科室测试

8. myheader.vue 完整代码

<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">尚医通 预约挂号统一平台</span>
            </div>
        <!-- 搜索框 -->
        <div class="search-wrapper">
            <div class="hospital-search animation-show">
                <el-autocomplete
                    class="search-input small"
                    prefix-icon="el-icon-search"
                    v-model="state"
                    :fetch-suggestions="querySearchAsync"
                    placeholder="点击输入医院名称"
                    @select="handleSelect"
                    >
                    <span slot="suffix" class="search-btn v-link highlight clickable selected">搜索 </span>
                </el-autocomplete>
            </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'

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

  labelTips: '手机号码', // 输入框提示

  inputValue: '', // 输入框绑定对象
  placeholder: '请输入您的手机号', // 输入框placeholder
  maxlength: 11, // 输入框长度控制

  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')
  },

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

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

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

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

    // 登录
    login() {
      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 != 6) {
        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 (!(/^1[34578]\d{9}$/.test(this.userInfo.phone))) {
        this.$message.error('手机号码不正确')
        return;
      }

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

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

      // 发送短信验证码
      this.timeDown();
      this.dialogAtrr.sending = false;
      smsApi.sendCode(this.userInfo.phone).then(response => {
        this.timeDown();
      }).catch(e => {
        this.$message.error('发送失败,重新发送')
        // 发送失败,回到重新获取验证码界面
        this.showLogin()
      })
    },

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

      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
      }
    },

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

    weixinLogin() {
      this.dialogAtrr.showLoginType = 'weixin'
    },

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

三、用户认证与网关整合

思路:

  • 所有请求都会经过服务网关,服务网关对外暴露服务,在网关进行统一用户认证

  • 既然要在网关进行用户认证,网关得知道对哪些 url 进行认证,所以我们得对ur制定规则

  • API 接口异步请求的,我们采取 url 规则匹配,如:/api/**/auth/**,如凡是满足该规则的都必须用户认证

① 调整 server-gateway 模块

A、在服务网关添加 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);
}

B、在服务网关中判断用户登录状态

在网关中如何获取用户信息:

我们统一从 header 头信息中获取

如何判断用户信息合法:

登录时我们返回用户 token,在服务网关中获取到 token 后,我在到 redis 中去查看用户 id,如何用户 id 存在,则 token 合法,否则不合法

C、取用户信息

/**
 * 获取当前登录用户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;
}

D、网关 filter 完整代码

/**
 * <p>
 * 全局Filter,统一处理会员登录与外部不允许访问的服务
 * </p>
 */
@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

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

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

相关文章

Python爬虫实战,requests+time模块,爬取某招聘网站数据并保存csv文件(附源码)

前言 今天给大家介绍的是Python爬取某招聘网站数据并保存本地&#xff0c;在这里给需要的小伙伴们代码&#xff0c;并且给出一点小心得。 首先是爬取之前应该尽可能伪装成浏览器而不被识别出来是爬虫&#xff0c;基本的是加请求头&#xff0c;但是这样的纯文 本数据爬取的人会…

ImmunoChemistry丨艾美捷抗体夹心ELISA开发试剂盒说明书

使用ImmunoChemistry艾美捷抗体夹心ELISA开发试剂盒评估检测可行性并优化ELISA性能参数。抗体夹心ELISA开发试剂盒提供了八种特殊配制的ELISA溶液和一个模板&#xff0c;用于抗体夹心ELISA测试的初始开发和优化。全面的ELISA开发手册提供了评估初始检测可行性和优化ELISA性能参…

关于 npm run buildprod 报错问题 :文件名、目录名或卷标语法不正确

引言 vue开发基本完成后进行打包时出现错误&#xff0c;这个错误以前没有遇到过&#xff0c;所以在这里激励 参考文章1 参考文章2 问题描述 在idea中运行npm run build:prod打包vue时出现报错 E:\Allworkspaces\idea-workspace\Project\vue-project\my-blog\vue-admin-te…

代码随想录第三天

专题&#xff1a;链表 题目&#xff1a;移除链表元素 题意&#xff1a;删除链表中等于给定值 val 的所有节点。 示例 &#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5] 解题思想&#xff1a; 我们为了统一操作&#xff0c;我们给链表…

身份证实名认证API接口有什么意义与作用?

身份证实名认证API接口有什么意义&#xff1f; 中国公民的身份证是一种对外证明自己身份的有效证件&#xff0c;随着国家对实名制要求的落实&#xff0c;工作与生活中许多场景都会需要进行身份验证。比如出门外出乘座火车与飞机时、注册互联网络平台时、网络游戏与购物时等等&a…

微信小程序怎么注册?【微信小程序制作】

即使在目前小程序盛行的时代&#xff0c;仍然有很多人在问微信小程序怎么注册、微信小程序怎么做的问题&#xff0c;也证明了我们对于微信小程序的需求依然巨大。那么微信小程序怎么注册呢&#xff1f;下面给大家简单介绍。 步骤1&#xff1a;注册小程序账号 我们在微信公众平…

启封化工行业管理方案—危化品的管理(1)

联合国危险品编码管理 什么是危化品&#xff1f; 危化品是指可能伤害人、其他生物体、财产或环境的固体、液体或气体。这些危险品在运输时始终受到规章制度的约束。运输这些危险品的团队是训练有素的专业人员。使用标签时&#xff0c;存储标签的容器或存储位置通常用菱形标牌标…

2022年疫情下的卡塔尔世界杯,你看了么,盘点一下爆冷的赛事

卡塔尔世界杯卡塔尔世界杯世界杯出现的爆冷比赛沙特阿拉伯 VS 阿根廷&#xff08;1:2&#xff09;德国 VS 日本 (1:2)比利时 VS 摩洛哥 (0:2)摩洛哥VS 加拿大 (2:1)日本 VS 西班牙 &#xff08;2:1&#xff09;摩洛哥 VS 西班牙(3:0)总结卡塔尔世界杯 卡塔尔世界杯有32个国家参…

数据结构与算法(Java版) | 几个经典的算法面试题(下)

上一讲&#xff0c;我给大家介绍了两个经典算法面试题&#xff0c;即字符串匹配问题和汉诺塔游戏&#xff0c;这一讲&#xff0c;我再来给大家介绍两个经典算法面试题&#xff0c;它们就是八皇后问题和马踏棋盘算法&#xff0c;注意&#xff0c;马踏棋盘算法也被称为骑士周游问…

springsecurity--Config层代码常用代码指令

前提搭建 使我们的类继承于WebSecurityConfigurerAdapter这个类 同时调用service还有新建一个bean方法 Bean public PasswordEncoder getPassword() {return new BCryptPasswordEncoder(); } 这个代码是可以自定义账户和密码 自定义登录账户和密码写在service类中 下方的红框…

SSM框架-SSM整合

目录 1 ContextLoaderListener 2 准备工作 3 配置web.xml 4 配置springmvc.xml 5 配置spring.xml 6 Spring整合MyBatis 7 配置log4j.xml 8 配置事务 9 测试功能 9.1 创建pojo类 9.2 员工列表功能 9.3 分页数据 9.4 分页相关超链接 1 ContextLoaderListener Spring提…

安卓玩机搞机技巧综合资源-----卸载内置软件 获取root权限 刷写第三方ROM【六】

接上篇 安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】 安卓玩机搞机技巧综合资源------开机英文提示解决dm-verity corruption your device is corrupt. 设备内部报错 AB分区等等【二】 安卓玩机搞机技巧综合资源------EROFS分区格式 小米红…

985、211毕业一年,面试八家大厂,四面拿美团offer(Java后端)

自我介绍 本人三年开发&#xff0c;985硕士&#xff0c;211本科&#xff0c;专业都是软件工程&#xff0c;一直投的是Java后台开发&#xff0c;只投过一次网易的测试&#xff0c;技术不是太牛&#xff0c;但是比较努力。实验室没有项目&#xff0c;so项目经验是0&#xff0c;在…

openssl自动批量生成证书

使用bash批处理程序自动生成证书信息。 1、在linux系统创建一个文件夹 $ mkdir my_cert 2、创建CA证书以及私钥 $ cd my_cert $ openssl genrsa -out CA_Private.key 2048 $ openssl req -x509 -new -nodes -key CA_Private.key -sha256 -days 365 -out CA_Certificate.p…

Node.js 动画:事件循环

我们都听说过 JavaScript 和 Node.js 是单线程的&#xff0c;但实际上这意味着什么&#xff1f; 这意味着 JavaScript 一次只能做一件事。例如&#xff0c;我们不能同时对数字进行乘法和求和。我们通常按顺序进行操作。我们先加然后乘&#xff0c;反之亦然。现代计算机速度很快…

常见的卷积神经网络模型,卷积神经网络数学建模

1、卷积神经网络cnn究竟是怎样一步一步工作的 用一个卷积核滑动图片来提取某种特征&#xff08;比如某个方向的边&#xff09;&#xff0c;然后激活函数用ReLU来压制梯度弥散。对得到的结果用另一个卷积核继续提取reLU&#xff0c;然后池化&#xff08;保留区域最大或者用区域…

【linux】centos7 普通用户授予 root 权限

目录前言sudo介绍sudo的使用前提 --- 配置文件/etc/sudoers结语前言 最近通知 linux 期末考试啦&#xff01;要上机实操并且要求不能使用 root 用户&#xff0c;因此有些操作需要涉及到权限问题&#xff0c;我们都知道在命令前加sudo就行了&#xff0c;但sudo的使用也是有前提…

Spring 创建和使用 · 存储 bean 对象 · 获取并使用 bean 对象 · 关于配置文件 id 的注意事项 · getBean 方法的更多用法

Spring 是一个包含了众多工具方法的 IoC 容器。 容器的最基本的功能&#xff1a;把对象存储到容器&#xff08;Spring&#xff09;中&#xff1b;从容器中将对象取出来。 在 Java 中对象也叫做 Bean&#xff0c;以后我们遇到对象就以 Bean 著称。 一、创建 Spring 项目1.1 创建…

吹爆,阿里专家手码“数据库优化速成小册”Github现已疯传

现在在很多设计、开发、测试人员的眼中会认为SQL优化只是DBA 的事情。他们不需要去关心。反映到具体工作中&#xff0c;他们缺乏相应的优化意识&#xff0c;只注重功能的实现而忽略了相应的执行成本。最终的结果往往就是代码质量不高&#xff0c;软件上线后问题多。 LZ当初就是…

现货黄金K线图的“温柔一刀”

学习现货黄金交易K线图如何看、如何分析的方法非常多&#xff0c;但是小编交易&#xff0c;徐学一到两种即可&#xff0c;学会小而精的分析方法&#xff0c;才是我们赚钱的秘诀。俗话说得好&#xff0c;一招鲜吃遍天&#xff0c;就是这个道理。如果技术太多&#xff0c;最后没办…