基于Django的博客系统之增加手机验证码登录(九)

需求文档

概述

实现基于Redis和第三方短信服务商的短信验证码登录功能。用户可以通过手机号码获取验证码,并使用验证码进行登录。

需求细节
  1. 用户请求验证码
    • 用户在登录页面输入手机号码并请求获取验证码。
    • 系统生成验证码并将其存储在Redis中,同时通过第三方短信服务商发送验证码到用户手机。
  2. 用户提交验证码
    • 用户在登录页面输入手机号码和验证码。
    • 系统验证手机号码和验证码的匹配情况,如果匹配成功,用户登录成功。
功能模块
  1. 短信验证码生成与存储
    • 生成一个随机的6位数字验证码。
    • 验证码和手机号码绑定存储在Redis中,设置验证码有效期(例如5分钟)。
  2. 验证码发送
    • 集成第三方短信服务商API,发送验证码到用户手机。
  3. 验证码验证
    • 校验用户提交的手机号码和验证码是否匹配。
    • 如果匹配成功,允许用户登录。
  4. 用户登录
    • 生成用户会话或JWT令牌,返回给前端。
安全考虑
  • 对于频繁请求验证码的行为进行限制(如一个手机号每分钟只能请求一次,每小时不超过5次)。
  • 验证码存储在Redis中设置合理的过期时间。
  • 确保与第三方短信服务商的API通信使用HTTPS协议。
流程图
  1. 用户请求验证码
    • 用户提交手机号 -> 系统生成验证码 -> 存储到Redis -> 发送验证码到用户手机
  2. 用户提交验证码
    • 用户提交手机号和验证码 -> 系统验证验证码 -> 如果成功,生成会话或JWT令牌 -> 返回登录成功信息

第三方短信服务商

基于aliyun的第三方短信服务商提供5次免费试用功能,开通后配置后台页面如下:

在这里插入图片描述

API地址

调用方式

import urllib, urllib2, sys
import ssl


host = 'https://zwp.market.alicloudapi.com'
path = '/sms/sendv2'
method = 'GET'
appcode = '你自己的AppCode'
querys = 'mobile=1343994XXXX&content=%E3%80%90%E6%99%BA%E8%83%BD%E4%BA%91%E3%80%91%E6%82%A8%E7%9A%84%E9%AA%8C%E8%AF%81%E7%A0%81%E6%98%AF568126%E3%80%82%E5%A6%82%E9%9D%9E%E6%9C%AC%E4%BA%BA%E6%93%8D%E4%BD%9C%EF%BC%8C%E8%AF%B7%E5%BF%BD%E7%95%A5%E6%9C%AC%E7%9F%AD%E4%BF%A1'
bodys = {}
url = host + path + '?' + querys

request = urllib2.Request(url)
request.add_header('Authorization', 'APPCODE ' + appcode)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
response = urllib2.urlopen(request, context=ctx)
content = response.read()
if (content):
    print(content)

开启服务

云市场API商品的认证方式主要以下两种方式

  • 简单身份认证Appcode
  • 签名认证

目前先采用简单身份认证,购买4元套餐启动认证,否则请求调用返回403鉴权错误。

在这里插入图片描述

技术实现

技术栈

  • HTML5
  • CSS3
  • JavaScript (使用Vue.js)
  • Axios (用于HTTP请求)

架构概述

  1. 前端部分:使用 Vue.js 编写手机验证码登录页面。
  2. 后端部分:使用 Django 编写 API,处理手机号码和验证码的验证逻辑,并与 Redis 集成存储验证码。

Django + Vue

Django 和 Vue.js 可以很好的集成在一起。Django 处理后端逻辑和 API,而 Vue.js 可以处理前端交互和视图。通过 Django 提供的 API 接口,与 Vue.js 前端进行数据交互。

Vue
  1. 在项目目录下创建 Vue.js 项目
npm install -g @vue/cli
vue create frontend
cd frontend
  1. 创建登录组件

src/components/LoginWithSMS.vue 中:

<template>
    <div class="login-container">
        <form
        @submit.prevent="submitLogin">
        <div class="input-group">
            <select id="country_code" v-model="countryCode">

                <!-- 添加其他国家的区号选项 -->
                <option v-for="country in countryCodes" :key="country.code" :value="country.code">{{ country.name }} ({{
                    country.code }})
                </option>

            </select>
            <input type="text" id="phone_number" v-model="phoneNumber" placeholder="请输入手机号" required>
        </div>
        <div class="input-group">
            <label for="verification_code" style="float: top;">验证码</label>
            <input type="text" class="verification_code" id="verification_code"  v-model="verificationCode"
                   style="width: calc(100%); float: top;" required>
                <button type="button" class="verification_code_button"
                @click="requestVerificationCode" :disabled="isSendingCode" style="float: bottom;">
                {{ buttonText }}
            </button>
        </div>
        <button type="submit">登录</button>
    </form>
    <div v-if="message" class="message">{{ message }}</div>
</div>
        </template>

<script>
import axios from 'axios';
import { countryCodes } from '../assets/countryCodes'; // 导入国家代码数据

export default {
  data() {
    return {
        countryCodes: countryCodes, // 使用导入的国家代码数据
      countryCode: '+86',
      phoneNumber: '',
      verificationCode: '',
      isSendingCode: false,
      countdown: 60,
       message: '', // 添加 message 状态
    };
  },
  computed: {
    buttonText() {
      return this.isSendingCode ? `${this.countdown} 秒后重新获取` :'获取验证码'  ;
    }
  },
  methods: {
    async requestVerificationCode() {
      if (!this.phoneNumber) {
       this.message = '请填写手机号';
        return;
      }
      this.isSendingCode = true;
      try {
        const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
        const response = await axios.post('/api/request_verification_code/', {
          country_code: this.countryCode,
          phone_number: this.phoneNumber,
        }, {
      headers: {
      'Content-Type': 'application/json', // 指定请求的数据格式为 JSON
        'X-CSRFToken': csrftoken
      }
    });
        if (response.data.success) {
          this.isSendingCode = true;
          this.message = '验证码已发送';
          // 开始倒计时
          this.startCountdown();
        } else {
           this.message = '发送验证码失败';
           this.isSendingCode = false;
        }
      } catch (error) {
      console.error(error);
         this.message = '发送验证码失败';
         this.isSendingCode = false;
      }
    },
    async submitLogin() {
      if (!this.phoneNumber || !this.verificationCode) {
        this.message = '请填写完整信息';
        this.isSendingCode = false;
        return;
      }
      try {
      const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
        const response = await axios.post('http://localhost:8000/api/login_with_verification_code/', {
          country_code: this.countryCode,
          phone_number: this.phoneNumber,
          verification_code: this.verificationCode,
        }, {
      headers: {
      'Content-Type': 'application/json', // 指定请求的数据格式为 JSON
        'X-CSRFToken': csrftoken
      }
    }
        );
        if (response.data.success) {
           this.message = '登录成功';
          // 可以根据需要进行重定向或其他登录成功操作
        } else {
          this.message = '验证码错误或登录失败';
          this.isSendingCode = false;
        }
      } catch (error) {
        console.error(error);
         this.message = '登录失败';
         this.isSendingCode = false;
      }
    },
    startCountdown() {
      const countdownInterval = setInterval(() => {
        if (this.countdown > 0) {
          this.countdown--;
        } else {
          clearInterval(countdownInterval);
          this.countdownTimer = null;
          this.isSendingCode = false;
          this.countdown = 60; // 重置倒计时时间
        }
      }, 1000);
    },
  },
};
</script>

<style scoped>
.login-container {
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  width: 200px;
  text-align: center;
}

.input-group {
  margin-bottom: 15px;
}


label {
  display: block;
  margin-bottom: 5px;
}

input[type="text"], select {
  padding: 8px;
  margin-right: 5px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

button {
  padding: 8px 15px;
  border: none;
  border-radius: 4px;
  background-color: #007bff;
  color: white;
  cursor: pointer;
}

button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}
.message {
  margin-top: 15px;
  color: red; /* 可以根据需要更改消息的样式 */
}
</style>


配置setting如下:

import { createApp } from 'vue'
import App from './App.vue'
import LoginWithSMS from './components/LoginWithSMS.vue';

createApp(App)
.component('LoginWithSMS', LoginWithSMS)
.mount('#app');

Django

在 Django 中设置 API 来处理手机号码和验证码的验证逻辑,并与 Redis 集成存储验证码。

  1. 创建 Django API 端点

myblog 应用中,创建 API 端点以处理验证码请求和登录验证。

from django.urls import path
from .views import request_verification_code, login_with_verification_code

urlpatterns = [
    path('api/request_verification_code/', request_verification_code, name='request_verification_code'),
    path('api/login_with_verification_code/', login_with_verification_code, name='login_with_verification_code'),
]

  1. 创建视图函数

blog/views.py 中:

import random
import redis
from django.conf import settings
from django.http import JsonResponse
from django.contrib.auth.models import User
from django.contrib.auth import login
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
import json

# 连接Redis
redis_client = redis.StrictRedis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0)

@require_POST
def request_verification_code(request):
    data = json.loads(request.body)
    phone_number = data.get('phone_number')
    if not phone_number:
        return JsonResponse({'success': False, 'message': '手机号不能为空'}, status=400)
    
    code = str(random.randint(100000, 999999))
    redis_key = f"verification_code:{phone_number}"
    redis_client.set(redis_key, code, ex=300)  # 5分钟有效期

    # 这里调用第三方短信服务商API发送验证码
    # send_verification_code(phone_number, code)

    return JsonResponse({'success': True, 'message': '验证码已发送'})

@require_POST
def login_with_verification_code(request):
    data = json.loads(request.body)
    phone_number = data.get('phone_number')
    verification_code = data.get('verification_code')
    
    if not phone_number or not verification_code:
        return JsonResponse({'success': False, 'message': '手机号和验证码不能为空'}, status=400)
    
    redis_key = f"verification_code:{phone_number}"
    stored_code = redis_client.get(redis_key)
    
    if stored_code and stored_code.decode('utf-8') == verification_code:
        redis_client.delete(redis_key)
        user, created = User.objects.get_or_create(username=phone_number)
        if created:
            user.set_unusable_password()
            user.save()
        login(request, user)
        return JsonResponse({'success': True, 'message': '登录成功'})
    return JsonResponse({'success': False, 'message': '验证码错误'}, status=400)

  1. 在 Django 模板中引入 Vue.js 应用

在 Django 的模板文件中login.html,引入 Vue.js 组件:

{% load static %}
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
    <link rel="stylesheet" href="{% static 'css/login.css' %}">

    <!-- 引入 Vue 3 的静态文件 -->
    <script src="{% static 'js/app.85a93ec8.js' %}" defer></script>
    <script src="{% static 'js/chunk-vendors.6b7a5a13.js' %}" defer></script>
    <link rel="stylesheet" type="text/css" href="{% static 'css/app.438959e3.css' %}">

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        // 当点击验证码图片时,刷新验证码
        $('.captcha').click(function () {
                $.getJSON('/captcha/refresh/',function (result) {
                    $('.captcha').attr('src',result['image_url']);
                    $('#id_captcha_0').val(result['key']);
                });
            });
    </script>
    <style>
        .focusable {
            padding: 10px;
            margin: 10px;
            border: 1px solid #ccc;
            outline: none;
        }
        .focusable:focus {
            border-color: #007BFF;
            background-color: #E9F7FF;
        }
    </style>
</head>
<body>
<div id="main-container">
    <div class="main">
        <div class="auth-content">
            <div class="auth-form">
                <div class="tabs">

                    <input type="radio" id="tab1" name="tab-group" checked>
                    <label for="tab1">邮箱登录</label>
                    <div class="tab-content">

                        {% if error_message %}
                        <p>{{ error_message }}</p>
                        {% endif %}
                        <form method="post">
                            {% csrf_token %}
                            {{ form.as_p }}
                        </form>
                    </div>

                    <input type="radio" id="tab2" name="tab-group">
                    <label for="tab2">手机登录</label>
                    <div class="tab-content" id="app">
                        <login-with-sms></login-with-sms>
                    </div>

                    <input type="radio" id="tab3" name="tab-group">
                    <label for="tab3">扫码登录</label>
                    <div class="tab-content">
                        <h2>Content 3</h2>
                        <p>This is the content of tab 3.</p>
                    </div>


                    <div class="clearfix shortcut-action">
                        <span class="login"><button type="submit">登录</button></span>
                        <span class="forgot"><a href="{% url 'password_reset_request' %}">忘记密码</a></span>
                    </div>

                </div>
            </div>
        </div>
    </div>
</div>
</body>

</html>

效果如下:

在这里插入图片描述

运行 Django 和 Vue.js 项目
  1. 运行 Django 项目

确保你在虚拟环境中安装了 DjangoRedis

pip install django djangorestframework redis
python manage.py runserver
  1. 运行 Vue.js 项目
npm run serve

集成第三方调用短信API

集成上面的API调用,采用AppCode方式简单鉴权。

def send_verification_code(phone_number, code):
    host = 'http://zwp.market.alicloudapi.com'
    path = '/sms/sendv2'
    method = 'GET'
    appcode = settings.SEND_TEXT_APP_CODE
    content = f"【智能云】您的验证码是{code}。如非本人操作,请忽略本短信"
    querys = f'mobile={phone_number}&content={content}'
    print(f'querys, {querys}')
    bodys = {}
    api_url = host + path + '?' + querys
    print(f'api_url, {api_url}')

    headers = {
        'Authorization': 'APPCODE ' + appcode,
        'Content-Type': 'application/json',
    }
    print(f'headers, {headers}')

    try:
        response = requests.get(api_url, headers=headers, verify=True)
        if response.status_code == 200:
            print('短信发送成功')
            return True
        else:
            print(f'短信发送失败,错误代码: {response.status_code}, {response.text}')
            return False
    except requests.RequestException as e:
        print(f'短信发送失败: {str(e)}')
        return False

运行

确保 Vue.js 应用编译和打包正确

  • 确保你已经正确编译和打包了 Vue.js 应用。你可以通过以下命令进行打包:
npm run build

这将生成一个 dist 目录,其中包含所有静态文件。

将编译后的文件放到 Django 的静态文件目录

  • 确保将编译后的静态文件(通常在 dist 目录中)放置在 Django 项目的静态文件目录中。你可以将这些文件复制到 static 目录中:
cp -r frontend/dist/* path/to/django/static/

启动Django服务。效果如下:

输入手机号

在这里插入图片描述

点击获取验证码,启动1分钟倒计时禁止重复请求验证码功能。

在这里插入图片描述

同时,成功发送验证码到用户手机。

在这里插入图片描述

查询redis服务器能够看到对应的值。

在这里插入图片描述

输入验证码,点击登录。报错AxiosError: Network Error at u.onerror (http://127.0.0.1:8000/static/vue/js/chunk-vendors.6b7a5a13.js:18:56732) at nn.request (http://127.0.0.1:8000/static/vue/js/chunk-vendors.6b7a5a13.js:18:64167) at async Proxy.requestVerificationCode (http://127.0.0.1:8000/static/vue/js/app.8100d9be.js:1:2450)

Axios错误解决方法:

AxiosError: Network Error 表示 Axios 在尝试进行网络请求时遇到了问题。以下是一些可能的原因和解决方法:

1. 确保 Django 服务器正在运行

确保你的 Django 服务器正在运行,并且你可以通过浏览器访问 http://127.0.0.1:8000

python manage.py runserver

2. 检查 URL 和端口

确保在 Vue.js 中 Axios 请求的 URL 和端口是正确的。

const response = await axios.post('http://127.0.0.1:8000/api/request_verification_code/', {
  // 请求数据
});

3. 确保 CORS 配置正确

如果前端和后端在不同的端口上运行,请确保你已经正确配置了 CORS。

安装 django-cors-headers

如果还没有安装 django-cors-headers,请先安装它:

pip install django-cors-headers
配置 django-cors-headers

在你的 Django 项目的 settings.py 文件中进行以下配置:

INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

CORS_ALLOWED_ORIGINS = [
    "http://localhost:8080",
    "http://127.0.0.1:8080",
    # 其他允许的源
]

4. 检查网络和代理设置

确保你的网络连接正常,并且没有任何代理服务器阻止网络请求。

5. 确保 CSRF Token 正确传递

在你的 Django 模板中添加 CSRF token:

<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="{% csrf_token %}">

在 Vue.js 中读取并传递 CSRF token:

async requestVerificationCode() {
  if (!this.phoneNumber) {
    this.message = '请填写手机号';
    return;
  }
  this.isSendingCode = true;
  try {
    const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
    const response = await axios.post('http://127.0.0.1:8000/api/request_verification_code/', {
      country_code: this.countryCode,
      phone_number: this.phoneNumber,
    }, {
      headers: {
        'Content-Type': 'application/json',
        'X-CSRFToken': csrftoken
      }
    });
    if (response.data.success) {
      this.message = '验证码已发送';
      this.startCountdown();
    } else {
      this.message = '发送验证码失败';
      this.isSendingCode = false;
    }
  } catch (error) {
    console.error(error);
    this.message = '发送验证码失败';
    this.isSendingCode = false;
  }
}

6. 检查浏览器控制台和网络请求日志

使用浏览器的开发者工具(通常按 F12 打开),查看 Network 面板,检查网络请求的详细信息。

7. 确保前后端运行在正确的端口

确保你的前端(Vue.js)和后端(Django)都在正确的端口上运行。

示例 Vue.js 代码

export default {
  data() {
    return {
      countryCodes: countryCodes, // 使用导入的国家代码数据
      countryCode: '+86',
      phoneNumber: '',
      verificationCode: '',
      isSendingCode: false,
      countdown: 0,
      countdownSeconds: 60,
      message: '',
    };
  },
  computed: {
    buttonText() {
      return this.isSendingCode ? `${this.countdown} 秒后重新获取` : '获取验证码';
    }
  },
  methods: {
    async requestVerificationCode() {
      if (!this.phoneNumber) {
        this.message = '请填写手机号';
        return;
      }
      this.isSendingCode = true;
      try {
        const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
        const response = await axios.post('http://127.0.0.1:8000/api/request_verification_code/', {
          country_code: this.countryCode,
          phone_number: this.phoneNumber,
        }, {
          headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': csrftoken
          }
        });
        if (response.data.success) {
          this.message = '验证码已发送';
          this.startCountdown();
        } else {
          this.message = '发送验证码失败';
          this.isSendingCode = false;
        }
      } catch (error) {
        console.error(error);
        this.message = '发送验证码失败';
        this.isSendingCode = false;
      }
    },
    async submitLogin() {
      if (!this.phoneNumber || !this.verificationCode) {
        this.message = '请填写完整信息';
        return;
      }
      try {
        const response = await axios.post('http://127.0.0.1:8000/api/login_with_verification_code/', {
          phone_number: this.countryCode + this.phoneNumber,
          verification_code: this.verificationCode,
        });
        if (response.data.success) {
          this.message = '登录成功';
          // 可以根据需要进行重定向或其他登录成功操作
        } else {
          this.message = '验证码错误或登录失败';
        }
      } catch (error) {
        console.error(error);
        this.message = '登录失败';
      }
    },
    startCountdown() {
      const countdownInterval = setInterval(() => {
        if (this.countdownSeconds > 0) {
          this.countdownSeconds--;
        } else {
          clearInterval(countdownInterval);
          this.countdownTimer = null;
          this.isSendingCode = false;
          this.countdownSeconds = 60; // 重置倒计时时间
        }
      }, 1000);
    },
  },
};

通过以上步骤,你应该能够解决 AxiosError: Network Error 问题。如果问题仍然存在,请提供更多详细信息以便进一步帮助。

再次点击登录,报错网络请求报错 302 found,分析原因在login_with_verification_code中调用了redirect('/post_list')

Redirect错误解决

在 Vue.js 中成功登录后,使用浏览器的原生 JavaScript 方法进行页面重定向。

submitLogin 方法中,当登录成功时,使用 window.location.hrefwindow.location.replace() 方法来实现页面的重定向。这些方法直接操作浏览器的地址栏,可以导航到任何 URL,包括 Django 中定义的页面 URL。

示例:

在django中处理rediect逻辑如下:

        if stored_code and stored_code.decode('utf-8') == verification_code:
            redis_client.delete(redis_key)
            user, created = CustomUser.objects.get_or_create(username=phone_number)
            print(f'user, {user}')
            if created:
                user.set_password(phone_number)
                user.email = f'{phone_number}@qq.com'
                user.save()
            login(request, user)
            redirect_url = '/post_list'  # 修改为实际的 post_list 页面 URL
            return JsonResponse({'success': True, 'redirect_url': redirect_url})
        return JsonResponse({'success': False, 'message': '验证码错误'}, status=400)

在urls.py中添加路由如下:

    path('post_list', views.post_list, name='post_list'),

在vue里面添加重定向路径:

 if (response.data.success) {
           this.message = '登录成功';
           if (response.data.redirect_url) {
 window.location.href = response.data.redirect_url;   // 页面重定向到 Django 中的 post_list 页面
        } else {
          this.message = '验证码错误或登录失败';
          this.isSendingCode = false;
        }

效果

再次点击登录,成功登录跳转
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设备保养计划不再是纸上谈兵,智能系统让执行更到位!

在物业管理的日常工作中&#xff0c;我们常常听到“设备保养台账”“设备保养计划”“设备保养记录”等等这些词&#xff0c;但你是否真正了解它们的含义&#xff1f;是否知道一个完善的设备保养计划、记录、台账对于物业运营的重要性&#xff1f;今天&#xff0c;我们就来深入…

一个开源的快速准确地将 PDF 转换为 markdown工具

大家好&#xff0c;今天给大家分享的是一个开源的快速准确地将 PDF 转换为 markdown工具。 Marker是一款功能强大的PDF转换工具&#xff0c;它能够将PDF文件快速、准确地转换为Markdown格式。这款工具特别适合处理书籍和科学论文&#xff0c;支持所有语言的转换&#xff0c;并…

【AI绘画】文心一格

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

Spring Cloud全家桶(上)【Nacos、OpenFeign、LoadBalancer、GateWay、金丝雀灰色发布】

0.零基础入门微服务实战课 1.微服务和 Spring Cloud1.1 什么是微服务&#xff1f;1.2 什么是 Spring Cloud&#xff1f;1.3 微服务 VS Spring Cloud 2.为什么要学微服务&#xff1f;3.Spring Cloud 组件介绍1.什么是 Nacos?1.1 Nacos 功能1.1.1 配置中心1.1.2 注册中心 1.2 Na…

openlayers 使用WMTS和XYZ加载天地图切片服务

openlayers 使用WMTS和XYZ加载天地图切片服务 本篇介绍一下使用openlayers加载天地图切片&#xff0c;两种方法&#xff1a; 使用WMTS使用XYZ 1 需求 openlayers加载天地图 2 分析 主要是不同类型source的使用 WMTS&#xff08;Web Map Tile Service&#xff09; 是 OGC…

《地下城与勇士》新手攻略,开荒必备!云手机多开教程!

《地下城与勇士》&#xff08;DNF&#xff09;是一款广受欢迎的多人在线动作角色扮演游戏。玩家将在游戏中扮演不同职业的角色&#xff0c;通过打怪、做任务、PK等方式不断提升自己&#xff0c;探索广阔的阿拉德大陆。游戏中设有丰富的副本、装备、技能系统&#xff0c;玩家可以…

ESP32-S3芯片的Strapping管脚功能描述

文章目录 一、Strapping管脚是什么&#xff1f;二、ESP32-S3芯片的Strapping管脚总体描述三、ESP32-S3芯片的Strapping管脚具体功能描述1、芯片启动模式控制2、VDD_SPI 电压控制3、ROM 日志打印控制4、JTAG 信号源控制 一、Strapping管脚是什么&#xff1f; 芯片每次上电或复位…

销售如何提高回复客户消息的速度?

在如今竞争激烈的商业环境中&#xff0c;能够快速回复客户消息是维护客户关系和提升用户体验的重要一环。尤其是对于很多企业或是销售客服人员来说&#xff0c;及时回复客户的咨询和反馈&#xff0c;能够有效增强客户的粘性和满意度。 那么怎样才能快速回复客户消息呢&#xf…

sklearn 基础教程

scikit-learn&#xff08;简称sklearn&#xff09;是一个开源的机器学习库&#xff0c;它提供了简单和有效的数据分析和数据挖掘工具。sklearn是Python语言中最重要的机器学习库之一&#xff0c;广泛用于统计学习和数据分析。 以下是scikit-learn的基础教程&#xff0c;帮助您开…

洗地机怎么选?洗地机哪个品牌比较好?四款实力超牛的单品推荐

随着生活节奏的加快&#xff0c;家庭清洁已经成为许多人面临的一大挑战。传统的扫地和拖地方式不仅耗时耗力&#xff0c;还难以彻底清洁每一个角落。家用洗地机的出现&#xff0c;为人们的家庭提供了一个全新的清洁解决方案。然而&#xff0c;在选择合适的洗地机时&#xff0c;…

示例:WPF中DataGrid简单设置合并列头

一、目的&#xff1a;应用DataGridTemplateColumn列模板&#xff0c;去拆分列头和单元格布局的方式设置列头合并样式 二、实现 效果如下 三、环境 VS2022 四、示例 应用DataGridTemplateColumn自定义列头信息和单元格信息 <DataGrid AutoGenerateColumns"False"…

一分钱不花!本地部署Google最强开源AI大模型Gemma教程

谷歌发布了轻量级开源系列模型Gemma&#xff0c;其性能强大&#xff0c;可与主流开源模型竞争。通过Ollama可轻松部署Gemma模型&#xff0c;并使用JANAI美化UI界面。显卡在AIGC应用中至关重要&#xff0c;推荐选择性能强、显存大的NVIDIA系列显卡。 半个月前&#xff0c;谷歌搞…

echarts引入百度地图vue3(大屏项目中缩放点偏移到左上角,解决代码在最后)

实际开发中的问题&#xff0c;遇到了大屏做了自适应&#xff0c;为非标准文档流之后&#xff0c;在缩放时不是以鼠标当前位置缩放的&#xff0c;而是偏移到左上角。 向百度地图提了工单也没解决&#xff0c;同一套适应方案用cesium地图时缩放没问题&#xff1a; 先看看效果&am…

数字人全拆解:如何构建一个基于大模型的实时对话3D数字人?

简单地说&#xff0c;数字人就是在数字世界的“人”。当前语境下我们谈到的数字人通常指的是借助AI技术驱动的虚拟世界人物&#xff0c;具备与真实人类相似甚至接近的外形、感知、交互与行为能力。 AI技术在智能数字人的应用中举足轻重&#xff0c;特别是随着大模型能力的涌现…

死锁预防之银行家算法

死锁预防之银行家算法 第一章 概述 Dijkstra提出了一种能够避免死锁的调度算法,称为银行家算法。 它的模型基于一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,每个客户都有一个贷款额度,银行家知道不可能所有客户同时都需要最大贷款额,所以他只保留一定单位…

工业上常见的智能测量设备

工业智能测量仪包括测径仪、测宽仪、测厚仪和直线度测量仪等&#xff0c;主要用于自动化生产线上的高精度尺寸测量。这些设备通常采用光电、激光、工业相机等技术进行非接触式测量&#xff0c;以确保高效率和准确性。测径仪用于测量圆形物体的直径&#xff0c;测宽仪用于测量板…

基于springboot+vue的供应商管理系统

一、系统架构 前端&#xff1a;vue2 | element-ui 后端&#xff1a;springboot | mybatis 环境&#xff1a;jdk1.8 | mysql | maven | node 二、代码及数据库 三、功能介绍 01. 员工注册 02. 登录 03. 管理员-首页 04. 管理员-个人中心-修改密码 05. …

Windows电脑部署Jellyfin服务端并进行远程访问配置详细教程

文章目录 前言1. Jellyfin服务网站搭建1.1 Jellyfin下载和安装1.2 Jellyfin网页测试 2.本地网页发布2.1 cpolar的安装和注册2.2 Cpolar云端设置2.3 Cpolar本地设置 3.公网访问测试4. 结语 前言 本文主要分享如何使用Windows电脑本地部署Jellyfin影音服务并结合cpolar内网穿透工…

ECharts词云图(案例一)+配置项详解

ECharts词云图&#xff08;案例一&#xff09;配置项详解 ECharts 是一款由百度团队开发的基于 JavaScript 的开源可视化图表库&#xff0c;它提供了丰富的图表类型&#xff0c;包括常见的折线图、柱状图、饼图等&#xff0c;以及一些较为特殊的图表&#xff0c;如词云图。从版…

14.编写自动化测试(上)

标题 一、如何编写测试1.1 一些概念1.2 测试函数剖析1.3 使用assert!宏检查结果1.4 使用assert_eq!和assert_ne!宏来测试相等1&#xff09; assert_eq!2&#xff09; assert_ne! 1.5 使用 should_panic 检查 panic 二、将 Result<T, E> 用于测试 一、如何编写测试 1.1 一…