尚医通-手机验证码登录与gateway拦截实现

news2025/8/11 11:03:38

需求分析

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

2,登录方式:

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

(2)微信扫描

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

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

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

搭建环境

创建模块 service-user

配置文件

# 服务端口
server.port=8150
# 服务名
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://localhost:3306/yygh_user?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=sugon666


spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000

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

# nacos服务地址
spring.cloud.nacos.discovery.server-addr=localhost:8848

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

配置网关

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

主要创建下 UserInfo 相关的 类,这里不赘述

手机登录基本实现

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

这块比较常规,用的 token + jwt

// 手机号登录
    public Map<String, Object> loginUser(LoginVo loginVo) {
        // 从 loginVo 获取到 输入的手机号和验证码
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();
        // 非空判断
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
            throw new HospitalException(ResultCodeEnum.PARAM_ERROR);
        }

        // TODO 判断手机验证码和输入的验证码是否一致
        //String redisCode = redisTemplate.opsForValue().get(phone);
        //if (!code.equals(redisCode)) {
         //   throw new HospitalException(ResultCodeEnum.CODE_ERROR);
        //}

        // 判断是否第一次登录:查询数据库
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("phone", phone);
        UserInfo userInfo = baseMapper.selectOne(wrapper);
        if (userInfo == null) {
            // 添加信息到数据库
            userInfo = new UserInfo();
            userInfo.setName("");
            userInfo.setPhone(phone);
            userInfo.setStatus(1);
            baseMapper.insert(userInfo);
        }

        // 不是第一次,直接登录
        //校验是否被禁用
        if (userInfo.getStatus() == 0) {
            throw new HospitalException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }
        // 返回登录信息
        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);

      // TODO 生成 jwt 的 token
        // 返回 token 信息
        map.put("token", JwtHelper.createToken(userInfo.getId(), name));

        return map;
    }

jwt 集成

依赖

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

工具类

public class JwtHelper {
    private static long tokenExpiration = 24 * 60 * 60 * 1000; //token1过期时间设置 单位:毫秒
    private static String tokenSignKey = "123456";  //签名秘钥

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

    //根据token字符串得到用户id
    public static Long getUserId(String token) {
        if (StringUtils.isEmpty(token)) return null;
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer) claims.get("userId");
        return userId.longValue();
    }

    //根据token字符串得到用户名称
    public static String getUserName(String token) {
        if (StringUtils.isEmpty(token)) return "";
        Jws<Claims> claimsJws
                = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("userName");
    }

    public static void main(String[] args) {
        String token = JwtHelper.createToken(1L, "lucy");
        System.out.println(token);
        System.out.println(JwtHelper.getUserId(token));
        System.out.println(JwtHelper.getUserName(token));
    }

}

调用

map.put("token", JwtHelper.createToken(userInfo.getId(), name));

Swagger 测试,输入一个不存在的

image-20221122135929349

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZHpTvhv-1669282098210)(https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20221122140725584.png)]

看到已经写入 user_info 表了

整合 阿里云短信服务

首先创建个msm的模块

image-20221122141338003

我用的是这个测试,点进来

<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>alibabacloud-dysmsapi20170525</artifactId>
  <version>2.0.22</version>
</dependency>

我用的最新版本的异步的sdk

aliyun.sms.regionId=default
aliyun.sms.accessKeyId=xxxx
aliyun.sms.secret=xxx

把配置信息读取到 配置类

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

}

controller 发送验证码接口实现

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

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


@Override
public boolean send(String phone, String code) {
    DefaultProfile profile = DefaultProfile.getProfile(ConstantPropertiesUtils.REGION_Id,
            ConstantPropertiesUtils.ACCESS_KEY_ID, ConstantPropertiesUtils.SECRECT);

    IAcsClient client = new DefaultAcsClient(profile);


    SendSmsRequest request = new SendSmsRequest();
    request.setSignName("阿里云短信测试");
    request.setTemplateCode("SMS_154950909");
    request.setPhoneNumbers(phone);

    Map<String, String> param = new HashMap<>();
    param.put("code", code);

    request.setTemplateParam(JSONObject.toJSONString(param));

    try {
        SendSmsResponse response = client.getAcsResponse(request);
        System.out.println(response.getCode());
        System.out.println(new Gson().toJson(response));
        return response.getCode().equals("OK");
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

这里给出我的实现,注意点

  • 设置两分钟验证码的过期时间,如果已经发送过直接返回 true,否则把验证码存储到 redis ,并且发送验证码

我们放开前面登录的注释

String redisCode = redisTemplate.opsForValue().get(phone);
if (!code.equals(redisCode)) {
    throw new HospitalException(ResultCodeEnum.CODE_ERROR);
}

前端整合

前端两个api 文件

import request from '@/utils/request'

const api_name = `/api/user`

export default {
    //手机登录接口
    login(userInfo) {
        return request({
            url: `${api_name}/login`,
            method: `post`,
            data: userInfo
        })
    }
}

import request from '@/utils/request'

const api_name = `/api/msm`

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

myHeader

<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
      v-if="dialogUserFormVisible"
      :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";
import weixinApi from "@/api/weixin";

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

    //初始化微信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) => {
      self.loginCallback(name, token, openid);
    };
  },

  methods: {
    //微信回调方法
    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() {
      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");
      }
    },

    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";
      //初始化微信相关参数
      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>

Cookie 记录登录成功的用户信息

npm install js-cookie

这块参考上面前端代码即可

用户登录网关整合

网关层处理登录部门校验

添加 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);
        }


        //api接口,异步请求,校验用户必须登录
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            Long userId = this.getUserId(request);
            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;
    }
}

前端 request.js 统一处理

  • 请求拦截器:处理发出请求不带 token
  • 响应拦截器:处理返回值 208 (gateway 返回),跳转登录页
import axios from "axios";
import { MessageBox, Message } from "element-ui";
import cookie from "js-cookie"

// 创建axios实例
const service = axios.create({
  baseURL: "http://localhost:81",
  timeout: 15000, // 请求超时时间
});
// http request 拦截器
service.interceptors.request.use(
  (config) => {
    //判断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/33827.html

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

相关文章

怎么裁剪视频时长?手把手教你裁剪

现在的网络非常方便&#xff0c;我们可以很轻松的在网上找到各种视频进行网课的学习。不过有些网课重点的内容可能不多而且又分散&#xff0c;我们很难做到高效的学习。其实我们可以通过视频裁剪&#xff0c;将需要的视频内容裁剪下来&#xff0c;这样子就方便我们学习啦。那你…

自动驾驶感知算法实战专栏总结:如何打造“高可靠、多冗余、可量化、数据驱动的感知系统”

自动驾驶感知算法实战专栏:https://blog.csdn.net/charmve/category_12097938.html目录 「超融合」感知方案高可靠:对障碍物、红绿灯的识别精度有保证多冗余:各个模块相互支撑、非串行可量化:PRT、仿真场景测试、Profiling数据驱动(全流程闭环)「超融合」感知方案 专注在…

vmware 桥接模式设置桥接到无线网卡

vmware共有三种网络&#xff0c;仅主机Host、NAT和桥接模式。 仅主机Host用于和主机通信的网络。NAT用于网络地址转发上网。桥接模式用于搭建与主机之外的网络的网桥。 在添加桥接模式的网卡后&#xff0c;如果使用有线连接&#xff0c;这个时候&#xff0c;桥接网卡会桥接到的…

消能减震神器之“黏滞阻尼器”的力学原理与应用

作者 | 建源之光&#xff0c;仿真秀专栏作者 一、写在文前 消能阻尼器的基本力学原理主要体现在恢复力模型上&#xff0c;恢复力模型的建立对整体结构模型的动力分析起了便捷作用&#xff0c;便于指导工程实际应用。对于消能阻尼器通常选择以下本构进行模拟&#xff1a; 软钢…

如何利用Airtest做一些简单的装包小任务

1. 前言 很多同学对于Airtest都有一些刻板的印象&#xff0c;觉得Airtest只能截图&#xff0c;然后进行一些简单的点点点操作。 但实际上&#xff0c;抛开Airtest的图像识别点击功能&#xff0c;Airtest还能帮助我们连接设备&#xff0c;然后完成一些别的小任务&#xff0c;比…

CAD .NET 14.1.X DWG/ DXF, PLT 自由转换- CNC

CAD .NET CAD .NET是一个用于在 .NET 环境中开发解决方案的库。它支持 AutoCAD DWG/ DXF、PLT和其他 CAD 格式。 该库可用于广泛的领域&#xff1a; 在所有项目阶段使用工业图纸监控和远程控制程序数控加工数据导出为 CAD 格式使用数据库文件管理系统使用图纸的高度专业化产品…

构建镜像开源工具 buildah

构建镜像开源工具 buildah 文章目录构建镜像开源工具 buildah1. 简介2. 特点3. Buildah 和 Podman4. 安装4.1 CentOS4.2 Ubuntu4.3 RHEL74.4 Fedora5. 命令6. 示例6.1 命令行构建一个 httpd 镜像6.2 Dockerfile 构建6.3 构建镜像脚本&#xff08;代替 Dockerfile&#xff09;1.…

基于JAVA的新闻发布管理系统开发参考【数据库设计、源码、开题报告】

数据库脚本下载地址&#xff1a; https://download.csdn.net/download/itrjxxs_com/86427655 目的 本系统的目的是实现新闻发布系统的基本功能。新闻发布系统提供了不同类型新闻&#xff08;如社会新闻、娱乐新闻和技术前沿新闻等&#xff09; 满足不同用户需求&#xff1b;系…

基于Amos优化器思想推导出来的一些“炼丹策略”

©PaperWeekly 原创 作者 | 苏剑林单位 | 追一科技研究方向 | NLP、神经网络如果将训练模型比喻为“炼丹”&#xff0c;那么“炼丹炉”显然就是优化器了。据传 AdamW 优化器是当前训练神经网络最快的方案&#xff0c;这一点笔者也没有一一对比过&#xff0c;具体情况如何不…

RationalDMIS2022校验测头

一.为什么要校验测头 校验测头的目的有2个 第一是得到测针的实际直径&#xff0c;后续用于探头半径补偿&#xff1b; 第二是得到各角度下测针的偏置&#xff0c;这样不同角度的探针测量出来的数据可以统一到一起&#xff1b; 测头校验包括2部分&#xff1a;定位标准球和测头…

C#程序采用AOT发布,真的可以避免被反编译?

上次跟大家分享过&#xff0c;C#程序反编译与篡改代码的教程《C#程序发布时&#xff0c;一定要好好的保护&#xff0c;不然你会后悔的&#xff01;》&#xff0c;根据这个教程&#xff0c;我们都知道C#程序&#xff0c;发布后必须进行加密混淆&#xff0c;不然就是相当于源码直…

Java 网络编程之 BIO、NIO、AIO

目录I/O 模型BIO基本介绍工作机制编程实例同步阻塞模型&#xff08;一个客户端对应一个服务端&#xff09;BIO模式下一个服务端接收多个客户端伪异步I/O编程&#xff0c;使用线程池基于BIO形式下的文件上传NIOBuffer缓冲区Buffer 类及其子类缓冲区的基本属性Buffer常见方法缓冲…

iNFTnews|Web3走进FIFA世界杯

中心化交易所FTX暴雷留下的阴影还未消退&#xff0c;另一个交易所http://Crypto.com的标志出现在了2022卡塔尔世界杯的赛场上。 据FIFA公告&#xff0c;http://Crypto.com的品牌将于今年11月开始&#xff0c;出现在卡塔尔世界杯体育馆中&#xff0c;且作为赞助商的一部分&#…

Redis 各种用法总结

前言 Redis绝不部分使用场景就是用来做缓存&#xff1b;但是&#xff0c;由于Redis 支持比较丰富的数据结构&#xff0c;因此他能实现的功能并不仅限于缓存&#xff0c;而是可以运用到各种业务场景中&#xff0c;开发出既简洁、又高效的系统; 下面整理了几种 Redis 的妙用场景…

免费录屏软件有哪些?录屏软件下载,认准这3款软件

​在网上活动越来越活跃的今天&#xff0c;人们对于录屏的需求也越来越多了起来。在我们日常生活或者工作生活中经常会使用到录屏功能&#xff0c;录屏的场景和需求变得多样化起来。那么有没有一些好用的免费录屏软件呢&#xff1f;别着急&#xff0c;下面小编带来了3款十分好用…

LabVIEW使用Desktop Execution Trace工具包

LabVIEW使用Desktop Execution Trace工具包 可以使用桌面执行跟踪工具包来调试和优化大型LabVIEW应用程序&#xff0c;包括具有多个循环的应用程序、客户端-服务器架构、动态加载VI等。该工具包从本地或远程计算机桌面上运行的应用程序捕获执行事件&#xff0c;并在表窗格中显…

EPICS -- asynRecord记录使用示例

这个示例演示了如何使用asynRecord记录 1、硬件准备工作 在这里准备了一个型号为NPort 5650-8-DT的Moxa串口服务器&#xff0c;用于一根交叉DB9双母头线缆连接设备上端口2和端口3&#xff0c;使之可以相互通信。 串口服务器配置如下&#xff1a; IP地址&#xff1a;192.168…

xilinx PL测 DP 点屏 /接收(三)--TX

环境&#xff1a; a)硬件&#xff1a;官方ZCU106开发板 , tb-fmch-vfmc-dp子卡。 b)软件&#xff1a;vivado2021.1&#xff0c;vitis2021.1&#xff0c;裸机程序。 1、例程&#xff1a; 1、DP TX ip速率&#xff1a; 2、框架&#xff1a; 3、重要寄存器&#xff1a; 4、 5、时钟…

Java 内存溢出(二)使用 MAT 分析 .hprof 内存映像文件

目录一、内存溢出时自动导出 .hprof 文件二、下载安装 MAT三、启动 MAT四、MAT 分析 hprof 文件1.Overview 概览2.Leak Suspects 溢出原因猜测3.Histogram 对象实例数量排序4.Dominator Tree 支配树.hprof 文件&#xff1a; 是 java 项目的 Heap Dump 文件&#xff0c;也叫内存…

手把手教你搭建属于自己的PyQt5-YOLOv5目标检测平台(保姆级教程)

>>>深度学习Tricks,第一时间送达<<< 🚀🚀🚀NEW!!!PyQt5-YOLOv5目标检测平台来啦 ~ 💡💡近期,小海带尝试用Pycharm做可视化界面相关设计,并搭载之前实验训练较好的YOLOv5算法模型,以此成功搭建了属于自己的PyQt5-YOLOv5目标检测平台,平台界面…