ruoyi-cloud登录接口实现滑块验证码

news2024/10/7 20:28:32

一、前言

ruoyi项目默认的验证码是这样的
在这里插入图片描述

今天来尝试增加滑块验证码,我们用到的是tianai-captcha
文档地址:http://doc.captcha.tianai.cloud/
源码地址:https://gitee.com/tianai/tianai-captcha

下面来看具体的步骤。

二、后端

gateway中引入依赖

<dependency>
    <groupId>cloud.tianai.captcha</groupId>
    <artifactId>tianai-captcha-springboot-starter</artifactId>
    <version>1.4.1</version>
</dependency>

并增加相应的配置

# 客户端验证码
captcha:
  cache:
    enabled: true
    cache-size: 20
  # 二次验证
  secondary:
    enabled: false
  # 是否初始化默认资源
  init-default-resource: false

gateway中新增加一个SliderCaptchaHandler处理类

import cloud.tianai.captcha.spring.application.ImageCaptchaApplication;
import cloud.tianai.captcha.spring.vo.CaptchaResponse;
import cloud.tianai.captcha.spring.vo.ImageCaptchaVO;
import com.iinplus.common.core.exception.CaptchaException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;

/**
 * 验证码获取
 */
@Component
public class SliderCaptchaHandler implements HandlerFunction<ServerResponse> {
    @Resource
    private ImageCaptchaApplication sca;

    @Override
    public Mono<ServerResponse> handle(ServerRequest serverRequest) {
        CaptchaResponse<ImageCaptchaVO> res;
        try {
            // 1.生成滑块验证码(该数据返回给前端用于展示验证码数据)
            res = sca.generateCaptcha();
        } catch (CaptchaException e) {
            return Mono.error(e);
        }
        return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(res));
    }
}

RouterFunctionConfiguration中新增一个路由

@Resource
private SliderCaptchaHandler sliderCaptchaHandler;

@Bean
public RouterFunction routerFunc() {
    return RouterFunctions.route(
         RequestPredicates.GET("/captcha")
         .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),            
         sliderCaptchaHandler);
}

新增一个filter类,用来验证图形验证码。

import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.spring.application.ImageCaptchaApplication;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.iinplus.common.core.exception.CaptchaException;
import com.iinplus.common.core.utils.ServletUtils;
import com.iinplus.common.core.utils.StringUtils;
import com.iinplus.gateway.config.properties.CaptchaProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;

import javax.annotation.Resource;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 验证码过滤器
 */
@Component
public class ValidateCaptchaFilter extends AbstractGatewayFilterFactory<Object> {
    private final static String[] VALIDATE_URL = new String[]{"/system/login"};
    @Resource
    private ImageCaptchaApplication sca;
    @Resource
    private CaptchaProperties captchaProperties;

    private static final String TRACK = "sliderCaptchaTrack";
    private static final String UUID = "id";

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            // 非登录/注册请求或验证码关闭,不处理
            List<String> list = Arrays.asList(VALIDATE_URL);
            // 请求地址
            String url = request.getURI().getPath();
            if (!StringUtils.matches(url, list) || !captchaProperties.getEnabled()) {
                return chain.filter(exchange);
            }

            try {
                String rspStr = resolveBodyFromRequest(request);
                if (StringUtils.isEmpty(rspStr)) {
                    throw new CaptchaException("验证码不能为空");
                }
                JSONObject obj = JSON.parseObject(rspStr);
                if (!obj.containsKey(UUID) || !obj.containsKey(TRACK)) {
                    throw new CaptchaException("验证码不能为空");
                }
                String id = obj.getString(UUID);
                ImageCaptchaTrack sliderCaptchaTrack = obj.getObject(TRACK, ImageCaptchaTrack.class);
                ApiResponse<?> match = sca.matching(id, sliderCaptchaTrack);
                if (!match.isSuccess()) {
                    throw new CaptchaException(match.getMsg());
                }
            } catch (Exception e) {
                e.printStackTrace();
                return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
            }
            return chain.filter(exchange);
        };
    }

    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        // 获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        return bodyRef.get();
    }
}

注意:其中/system/login为登录验证的路径,需要在网关中配置,并且需要加入白名单。

如果需要修改图形验证码默认的背景图

import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
import cloud.tianai.captcha.resource.impl.DefaultResourceStore;
import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider;
import org.springframework.stereotype.Component;

import static cloud.tianai.captcha.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH;

/**
 * 自定义背景图片
 */
@Component
public class ResourceStore extends DefaultResourceStore {
    public ResourceStore() {
        // 滑块验证码 模板 (系统内置)
        ResourceMap template1 = new ResourceMap("default", 4);
        template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png")));
        template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png")));
        ResourceMap template2 = new ResourceMap("default", 4);
        template2.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/active.png")));
        template2.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/fixed.png")));

        // 1. 添加一些模板
        addTemplate(CaptchaTypeConstant.SLIDER, template1);
        addTemplate(CaptchaTypeConstant.SLIDER, template2);

        // 2. 添加自定义背景图片
        addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bg/1.png", "default"));
        addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bg/2.png", "default"));
        addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bg/3.png", "default"));
        addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bg/4.png", "default"));
        addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bg/5.png", "default"));
        addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bg/6.png", "default"));
    }
}

图片的路径如下
在这里插入图片描述

网关配置增加filters,把ValidateCaptchaFilter加上才生效

spring:
  cloud:
    gateway:
      discovery:
        ... ...
      routes:
        # 系统模块
        - id: system
          uri: lb://system
          predicates:
            - Path=/system/**
          filters:
            # 验证码处理
            - ValidateCaptchaFilter
            - StripPrefix=1

system模块的登录验证

/**
 * 系统用户登录
 */
@RestController
public class TokenController {
    @Autowired
    private SysLoginService sysLoginService;

    @PostMapping("login")
    public RpcResult<?> login(@RequestBody @Validated LoginBody form) {   
        LoginUser userInfo = sysLoginService.login(form);
        ... ...
        return RpcResult.success(rspMap);
    }
}
import lombok.Data;
import javax.validation.constraints.NotBlank;

@Data
public class LoginBody {
    @NotBlank(message = "用户名不能为空")
    private String username;
    
    @NotBlank(message = "密码不能为空")
    private String password;
}

三、前端

components下增加一个组件SliderCaptcha

<template>
  <div class="slider rotate">
    <div class="content">
      <div class="bg-img-div">
        <img :src="captcha.backgroundImage" id="imgId" alt/>
        <canvas id="canvasId" ref="canvas"></canvas>
      </div>
      <div class="rotate-img-div" :style="rotateImgDiv">
        <img :src="captcha.templateImage" alt/>
      </div>
    </div>
    <div class="slider-move">
      <div class="slider-move-track">
        拖动滑块到正确位置
      </div>
      <div class="slider-move-btn" :style="sliderMoveBtn" @mousedown="down" @touchstart="down"></div>
    </div>
    <div class="bottom">
      <div class="close-btn" @click="close()"></div>
      <div class="refresh-btn" @click="refreshCaptcha"></div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Slider",
  props: {
    captcha: {
      type: Object
    },
  },
  data() {
    return {
      currentCaptchaConfig: {},
      sliderMoveBtn: "",
      rotateImgDiv: "",
      checkParam: {}
    }
  },
  mounted() {
    this.initCaptcha();
  },
  methods: {
    refreshCaptcha() {
      this.$emit("init");
      this.initCaptcha();
    },
    initCaptcha() {
      this.sliderMoveBtn = "background-position: -5px 11.79625%; transform: translate(0, 0)";
      this.rotateImgDiv = "transform: translate(0, 0)";

      this.currentCaptchaConfig = {};

      let bgImageWidth = this.$refs.canvas.offsetWidth;
      let bgImageHeight = this.$refs.canvas.offsetHeight;

      this.checkParam = {
        bgImageWidth: bgImageWidth,
        bgImageHeight: bgImageHeight,
        startSlidingTime: new Date(),
        endSlidingTime: undefined,
        trackList: [],
      }
    },
    down(event) {
      let targetTouches = event.originalEvent ? event.originalEvent.targetTouches : event.targetTouches;
      let startX = event.pageX;
      let startY = event.pageY;
      if (startX === undefined) {
        startX = Math.round(targetTouches[0].pageX);
        startY = Math.round(targetTouches[0].pageY);
      }
      this.currentCaptchaConfig.startX = startX;
      this.currentCaptchaConfig.startY = startY;

      const pageX = this.currentCaptchaConfig.startX;
      const pageY = this.currentCaptchaConfig.startY;
      const startSlidingTime = this.checkParam.startSlidingTime;
      const trackList = this.checkParam.trackList;
      trackList.push({
        x: pageX - startX,
        y: pageY - startY,
        type: "down",
        t: (new Date().getTime() - startSlidingTime.getTime())
      });

      // pc
      window.addEventListener("mousemove", this.move);
      window.addEventListener("mouseup", this.up);

      // 手机端
      window.addEventListener("touchmove", this.move, false);
      window.addEventListener("touchend", this.up, false);

      this.sliderMoveBtn = `background-position:-5px 31.0092%`;
    },
    move(event) {
      if (event instanceof TouchEvent) {
        event = event.touches[0];
      }
      let pageX = Math.round(event.pageX);
      let pageY = Math.round(event.pageY);
      const startX = this.currentCaptchaConfig.startX;
      const startY = this.currentCaptchaConfig.startY;
      const startSlidingTime = this.checkParam.startSlidingTime;
      const end = 305;
      const trackList = this.checkParam.trackList;
      let moveX = pageX - startX;
      const track = {
        x: pageX - startX,
        y: pageY - startY,
        type: "move",
        t: (new Date().getTime() - startSlidingTime.getTime())
      };
      trackList.push(track);
      if (moveX < 0) {
        moveX = 0;
      } else if (moveX > end + 5) {
        moveX = end;
      }

      this.sliderMoveBtn = `transform:translate(${moveX}px, 0)`;
      this.rotateImgDiv = `transform:translate(${moveX}px, 0);`;
    },
    up(event) {
      window.removeEventListener("mousemove", this.move);
      window.removeEventListener("mouseup", this.up);

      // 手机端
      window.removeEventListener("touchmove", this.move);
      window.removeEventListener("touchend", this.up);
      if (event instanceof TouchEvent) {
        event = event.changedTouches[0];
      }
      let pageX = Math.round(event.pageX);
      let pageY = Math.round(event.pageY);
      const startX = this.currentCaptchaConfig.startX;
      const startY = this.currentCaptchaConfig.startY;
      const startSlidingTime = this.checkParam.startSlidingTime;
      const trackList = this.checkParam.trackList;

      const track = {
        x: pageX - startX,
        y: pageY - startY,
        type: "up",
        t: (new Date().getTime() - startSlidingTime.getTime())
      }
      trackList.push(track);

      this.checkParam.endSlidingTime = new Date();
      // 校验
      this.checkCaptcha()
    },
    close() {
      this.$emit("close");
    },
    checkCaptcha() {
      //this.checkParam = {};
      this.$emit("checkParam", this.checkParam)
      this.$emit("login");
    }
  },
}
</script>
<style scoped>
.slider {
  background-color: #fff;
  width: 380px;
  height: 340px;
  z-index: 999;
  box-sizing: border-box;
  padding: 9px;
  border-radius: 6px;
  box-shadow: 0 0 11px 0 #999999;
}

.slider .content {
  width: 100%;
  height: 160px;
  position: relative;
}

.bg-img-div {
  width: 100%;
  height: 100%;
  position: absolute;
  transform: translate(0px, 0px);
}

.bg-img-div img {
  width: 100%;
}

.bg-img-div canvas {
  width: 100%;
  position: absolute;
  left: 0;
  top: 0;
}

.slider .slider-move {
  height: 60px;
  width: 100%;
  margin: 0;
  position: relative;
  top: 80px
}

.slider .bottom {
  height: 25px;
  width: 100%;
  margin: 65px 10px 10px 0;
}

.refresh-btn, .close-btn, .slider-move-btn {
  background: url(../../assets/images/sprite.1.2.4.png) no-repeat;
}

.refresh-btn, .close-btn {
  display: inline-block;
}

.slider-move .slider-move-track {
  line-height: 38px;
  font-size: 14px;
  text-align: center;
  white-space: nowrap;
  color: #88949d;
  -moz-user-select: none;
  -webkit-user-select: none;
  user-select: none;

  border-radius: 50px;
  background: #dfe1e2;
  width: 100%;
}

.slider {
  user-select: none;
}

.slider-move .slider-move-btn {
  transform: translate(0, 0);
  background-position: -5px 11.79625%;
  position: absolute;
  top: -12px;
  left: 0;
  width: 100%;
  height: 100%;
}

.slider-move-btn:hover, .close-btn:hover, .refresh-btn:hover {
  cursor: pointer
}

.bottom .close-btn {
  width: 25px;
  height: 25px;
  background-position: 0 44.86874%;
  margin: 10px 10px 10px 5px;
  float: left;
}

.bottom .refresh-btn {
  width: 25px;
  height: 25px;
  background-position: 0 81.38425%;
  margin: 7px 10px 10px 2px;
  float: left;
}

.rotate-img-div {
  height: 140%;
  position: absolute;
  transform: translate(0, 0);
}

.rotate-img-div img {
  height: 100%;
}
</style>

修改登录和获取验证码的方法

// 登录方法
export function login(data) {
  return request({
    url: '/system/login',
    headers: {
      isToken: false
    },
    method: 'post',
    data: data
  })
}
// 获取验证码
export function captcha() {
  return request({
    url: '/captcha',
    headers: {
      isToken: false
    },
    method: 'get',
    timeout: 20000
  })
}

修改login.vue页面

<template>
  <div class="login">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">xxxx管理系统</h3>
      <el-form-item prop="username">
        <el-input
          v-model="loginForm.username"
          type="text"
          auto-complete="off"
          placeholder="账号"
        >
          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input
          v-model="loginForm.password"
          type="password"
          auto-complete="off"
          placeholder="密码"
        >
          <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
        </el-input>
      </el-form-item>
      <!--注释掉原来的验证码-->
	  <!--
	  <el-form-item prop="code" v-if="captchaEnabled" style="margin: 10px 0">
        <el-input
          v-model="loginForm.code"
          auto-complete="off"
          placeholder="验证码"
          style="width: 68%"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
        </div>
      </el-form-item>
      -->
      <el-form-item style="width:100%; margin: 10px 0">
        <el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
      </el-form-item>
      <el-form-item style="width:100%;margin-bottom: 10px">
        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="initCaptcha"
        >
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>
      </el-form-item>
    </el-form>
    <!-- 滑块验证码,通过show来控制显示遮蔽层-->
    <div v-if="show" class="mask">
      <SliderCaptcha
          v-if="captchaVisible"
          ref="sliderCaptcha"
          :captcha="captcha"
          @init="initCaptcha"
          @close="hideCaptcha"
          @login="handleLogin"
      />
    </div>
    <!--  底部  -->
    <div class="el-login-footer">
      <span>Copyright © xxx All Rights Reserved.</span>
    </div>
  </div>
</template>

<script>
import {captcha} from '@/api/login'
import Cookies from "js-cookie";
import {decrypt, encrypt} from '@/utils/jsencrypt'
import SliderCaptcha from '@/components/SliderCaptcha'

export default {
  name: "Login",
  components: {
    SliderCaptcha
  },
  data() {
    return {
      // codeUrl: "",
      show: false,
      captcha: {},
      captchaVisible: false,
      loginForm: {
        username: undefined,
        password: undefined,
        rememberMe: false,
        //code: "",
        //uuid: "",
        // 增加下面两个属性
        ImageCaptchaTrack: {},
        id: ''
      },
      loginRules: {
        username: [
          { required: true, trigger: "blur", message: "请输入您的账号" }
        ],
        password: [
          { required: true, trigger: "blur", message: "请输入您的密码" }
        ],
        // 不再需要这个验证
        // code: [{ required: true, trigger: "change", message: "请输入验证码" }]
      },
      loading: false,
      // 验证码开关
      captchaEnabled: true,
      redirect: undefined
    };
  },
  watch: {
    $route: {
      handler: function(route) {
        this.redirect = route.query && route.query.redirect;
      },
      immediate: true
    }
  },
  created() {
    //this.getCode();
    this.getCookie();
  },
  methods: {
    //注释原先的获取验证码方法
    /*getCode() {
      getCodeImg().then(res => {
        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
        if (this.captchaEnabled) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;
        }
      });
    },*/
    getCookie() {
      const username = Cookies.get("username");
      const password = Cookies.get("password");
      const rememberMe = Cookies.get('rememberMe')
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password: password === undefined ? this.loginForm.password : decrypt(password),
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
      };
    },
    hideCaptcha() {
      this.captchaVisible = false
      this.show = false;
    },
    // 获取图形验证码
    initCaptcha() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          captcha().then(res => {
            if (res) {
              this.captcha = res["captcha"];
              this.loginForm.id = res["id"];
              this.captchaVisible = true
              this.show = true;
            }
          })
        }
      })
    },
    // 登录方法
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true;
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove('rememberMe');
          }
		  // 从子组件获取值
          this.loginForm.sliderCaptchaTrack = this.$refs.sliderCaptcha.checkParam
          this.$store.dispatch("Login", this.loginForm).then(() => {
            // console.info("this.redirect", this.redirect)
            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
          }).catch(() => {
            this.loading = false;
            //调用子组件的刷新图形验证码的方法
            this.$refs.sliderCaptcha.refreshCaptcha()
          });
        }
      });
    }
  }
};
</script>

<style rel="stylesheet/scss" lang="scss">
<!--新增遮蔽层,其他省略-->
.mask {
  box-sizing: border-box;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  z-index: 1001;
  background: rgba(0, 0, 0, 0.3);
  transition: all 0.5s;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
</style>

最后this.$store.dispatch("Login", this.loginForm)调用的Login也需要修改一下,在user.js里面。
在这里插入图片描述

最终效果
在这里插入图片描述
点击【登录】按钮
在这里插入图片描述

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

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

相关文章

JL-33 手持式气象站/便携式气象站 小型气象站厂家 微型气象站

产品概述 手持式气象站是一款携带方便&#xff0c;操作简单&#xff0c;集多项气象要素于一体的可移动式气象观测仪器。产品采用传感器及芯片&#xff0c;能同时对空气温度、空气湿度、风速、风向、光照、大气压力、颗粒物、噪声等要素进行准确测量、记录并存储。仪器带有机械…

游泳哪个牌子好?6大游泳耳机选购技巧总结分享

游泳耳机作为水上运动爱好者和游泳专业人士的必备装备&#xff0c;不仅要能够抵御水的侵入&#xff0c;还要提供清晰的音质和舒适的佩戴体验。在市面上&#xff0c;不同品牌的游泳耳机琳琅满目&#xff0c;选择起来可能会令人头疼。本文旨在为您提供一份详尽的游泳耳机选购指南…

详细解释下flutter初始示例的代码

详细解释下flutter初始示例的代码 main 首句导入需要的包 类似于其他语言的import main函数为入口函数 包裹MyApp类 MyApp 这个类继承自无状态类 可见myapp不管理任何状态 build方法是所有widget内必须实现的方法 此处返回一个 ChangeNotferiProvider 可以看到它用于管理应…

2024年低碳发展与地球科学国际会议 (LCDES 2024)

2024年低碳发展与地球科学国际会议 (LCDES 2024) 2024 International Conference on Low Carbon Development and Earth Science 【重要信息】 大会地点&#xff1a;长沙 大会官网&#xff1a;http://www.iclcdes.com 投稿邮箱&#xff1a;iclcdessub-conf.com 【注意&#xf…

一个opencv实现检测程序

引言 图像处理是计算机视觉中的一个重要领域&#xff0c;它在许多应用中扮演着关键角色&#xff0c;如自动驾驶、医疗图像分析和人脸识别等。边缘检测是图像处理中的基本任务之一&#xff0c;它用于识别图像中的显著边界。本文将通过一个基于 Python 和 OpenCV 的示例程序&…

《昇思25天学习打卡营第6天|网络构建》

文章目录 前言&#xff1a;今日所学&#xff1a;1. 定义模型类2. 模型层3. 模型参数 前言&#xff1a; 在第六节中我们学习了网络构建&#xff0c;了解了神经网络模型是由神经网络层和Tensor操作构成&#xff0c;我们使用的mindspore.nn中提供了常见的升级网络层的实现&#x…

c++边界处理机制

1.vector std::vector&#xff1a;std::vector 是动态数组&#xff0c;它会在运行时动态地调整存储空间大小&#xff0c;因此当访问超出边界时&#xff0c;会触发运行时异常 std::out_of_range。可以通过try-catch块来捕获这种异常来处理越界访问。 #include <iostream>…

十五、【源码】给代理对象设置属性

源码地址&#xff1a;https://github.com/spring-projects/spring-framework 仓库地址&#xff1a;https://gitcode.net/qq_42665745/spring/-/tree/15-proxy-set-property 给代理对象设置属性 之前的代码是创建Bean进行判断&#xff0c;要不要进行代理&#xff0c;如果代理…

console 报错 之 Uncaught (in promise) RangeError: Maximum call stack size exceeded

1. 背景 demo 环境报错。。。 2. 报错问题 3. 问题原因 vue 报错: “RangeError: Maximum call stack size exceeded” 报错通常是由于无限的递归 导致的。当使用 Vue 路由时&#xff0c;如果设置不当&#xff0c;会导致无限的递归&#xff0c;最终导致栈溢出&#xff0c;即…

【TypeScript】TS入门到实战(详解:高级类型)

目录 第三章、TypeScript的数据类型 3.1 TypeScript的高级类型 3.1.1 class 3.1.1.1 熟悉class类 3.1.1.2 class类继承的两种方式 3.1.1.3 class类的5种修饰符 3.1.2 类型兼容 3.1.3 交叉类型 3.1.4 泛型 3.1.4.1 创建泛型函数 3.1.4.2 泛型函数的调用 3.1.4.3 泛型…

【操作与配置】Linux的CPU深度学习环境

Conda安装 可浏览官网&#xff1a;Miniconda — Anaconda 文档 这四条命令会快速而且悄悄地安装最新的64位安装程序&#xff0c;然后清理安装过程中产生的文件。如果需要安装 Linux 版本的其他版本或架构的 Miniconda&#xff0c;只需要在命令中更改安装程序的名称。 mkdir …

【C++】const详解

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文作为 JohnKi &#xff0c;引用了部分大佬的案例 &#x1f4e2;未来很长&#xff0c;…

【Kali-linux for WSL】图形化界面安装

文章目录 前言图形化界面安装 前言 之前在WSL中安装了Kali 启动之后发现什么都没有&#xff01;&#xff01;&#xff01; 那我还怎么学习渗透技术&#xff1f;&#xff1f;&#xff1f; 看来&#xff0c;得改进下我的kali-linux for wsl&#xff0c;安装个图形化界面 图形化…

JCR一区级 | Matlab实现BO-Transformer-LSTM多变量回归预测

JCR一区级 | Matlab实现BO-Transformer-LSTM多变量回归预测 目录 JCR一区级 | Matlab实现BO-Transformer-LSTM多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现BO-Transformer-LSTM多变量回归预测&#xff0c;贝叶斯优化Transformer结合LSTM长…

【Python】Python中的常量与变量

常量与变量 导读一、新建项目二、常量2.1 字面常量2.2 特殊常量 三、变量3.1 变量的定义3.2 变量的命名3.2.1 关键字 结语 导读 大家好&#xff0c;很高兴又和大家见面啦&#xff01;&#xff01;&#xff01; 在上一篇内容中我们详细介绍了Python环境的搭建过程&#xff0c;…

一键转换,高效管理:引领文件批量改后缀名与TXT转DOCX格式新潮流

在这个数字化时代&#xff0c;文件管理和格式转换成为了我们日常工作中不可或缺的一部分。然而&#xff0c;手动更改文件后缀名以及将TXT文件转换为DOCX格式&#xff0c;不仅耗时耗力&#xff0c;还容易出错。幸运的是&#xff0c;我们有了文件批量改名高手这款强大的工具&…

【JAVA多线程】JDK中的各种锁,看这一篇就够了

目录 1.概论 1.1.实现锁的要素 1.2.阻塞队列 1.3.Lock接口和Sync类 2.各种锁 2.1.互斥锁 2.1.1.概论 2.1.2.源码 1.lock() 2.unlock() 2.2.读写锁 2.3.Condition 2.3.1.概论 2.3.2.底层实现 1.概论 1.1.实现锁的要素 JAVA中的锁都是可重入的锁&#xff0c;因为…

固定网国内数据传送业务经营许可证

一、国内固定网数据传送业务是什么&#xff1f; 固定网国内数据传送业务是指互联网数据传送业务以外的&#xff0c;在固定网中以有线方式提供的国内端到端数据传送业务。主要包括基于IP承载网、ATM网、X.25分组交换网、DDN网、帧中继网络的数据传送业务等。该业务属于A2类基础…

数字源表表征及测试纳米材料高温原位方案

01/纳米材料电学性能的表征和分析/ 与传统的材料相比&#xff0c;纳米材料具有原子级厚度、表面平整无悬空键、载流子迁移率好等优点&#xff0c;其导电性能很大程度依赖于材料本身的带隙、掺杂浓度和载流子迁移率。同样的掺杂浓度下&#xff0c;迁移率越大&#xff0c;电阻率…

计算机网络--网络层

一、网络层的服务和功能 网络层主要为应用层提供端对端的数据传输服务 网络层接受运输层的报文段&#xff0c;添加自己的首部&#xff0c;形成网络层分组。分组是网络层的传输单元。网络层分组在各个站点的网络层之间传输&#xff0c;最终到达接收方的网络层。接收方网络层将运…