当前位置: 首页 > news >正文

使用QQ登录(头条项目-09)

一 QQ登录开发文档

QQ登录:即我们所说的 第三⽅登录,是指⽤户可以不在本项⽬中输⼊密码,⽽直接 通过第三⽅的验证,成功登录本项⽬。

1.1 QQ互联开发者申请步骤

若想实现QQ登录,需要成为 QQ互联的开发者,审核通过 才可实现。

qq开发文档链接

1.2 QQ互联应⽤申请步骤

成为QQ互联开发者后,还需 创建应⽤,即 获取本项⽬对应与QQ互联的应⽤ID

创建应用

1.3 ⽹站接⼊QQ登录功能实现

QQ互联提供有 开发⽂档,帮助开发者实现QQ登录。

开发文档准备工作

1.4 QQ登录流程分析

1.5 知识要点

当我们在对接第三⽅平台的接⼝时,⼀定要认真阅读 第三⽅平台提供的⽂档。⽂档 中⼀定会有接⼝的使⽤说明,⽅便我们开发。

二 定义QQ登录模型类

QQ登录成功后,我们需要 将QQ⽤户和芒果头条⽤户 关联 到⼀起,⽅便下次QQ登录 时使⽤,所以我们选择 使⽤MySQL数据库进⾏存储

2.1 定义模型类基类

为了给项⽬中模型类补充 数据创建时间和更新时间两个字段,我们需要 定义模型类 基类。 在mgproject.utils/models.py⽂件中创建模型类基类。

from django.db import modelsclass BaseModel(models.Model):"""为模型类补充字段"""create_time = models.DateTimeField(auto_now_add=True,verbose_name="创建时间")update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")class Meta:# 说明是抽象模型类, ⽤于继承使⽤,数据库迁移时不会 创建BaseModel的表abstract = True

2.2 定义QQ登录模型类

创建⼀个新的应⽤oauth,⽤来实现QQ第三⽅认证登录。

# oauth
re_path(r'^oauth/', include('oauth.urls')),

在oauth/models.py中 定义QQ身份(openid)与⽤户模型类Users的关联关系。

from django.db import models
from mgproject.utils.models import BaseModel# Create your models here
class OAuthQQUser(BaseModel):"""QQ登录⽤户数据"""user = models.ForeignKey('userapp.Users',on_delete=models.CASCADE, verbose_name='⽤户')openid = models.CharField(max_length=64, verbose_name='openid',db_index=True)class Meta:db_table = 'tb_oauth_qq'verbose_name = 'QQ登录⽤户数据'verbose_name_plural = verbose_name

2.3 迁移QQ登录模型类

$ python manage.py makemigrations
$ python manage.py migrate

三 QQ登录工具AgentLogin

3.1 AgentLogin介绍

⽬前 只⽀持 腾讯QQ,微信,微博 的第三⽅登录

该⼯具封装了QQ登录 时对接QQ互联接⼝的 请求操作。可⽤于快速实现QQ登录功能。

3.2 AgentLogin安装

pip install AgentLogin

3.3 AgentLogin 使⽤说明

from AgentLogin import AgentLogin# 获取扫码⻚⾯地址
qq_url = AgentLogin.qq_url(client_id, redirect_uri)
# <a href="{{ qq_url }}">QQ登录<a>
# client_id:QQ互联上应⽤的APPID
# redirect_uri: QQ互联上应⽤的⽹站回调域# 获取⽤户名和openid
AgentLogin.qq(client_id, client_secret, url, code)
# 获取⽤户所有信息
AgentLogin.all_qq(client_id, client_secret, url, code)
# client_id: QQ互联上应⽤的 APPID
# client_secret: QQ互联上应⽤的APP Key
# url: QQ互联上应⽤的⽹站回调域
# code: 从QQ服务器得到code
# 注意此code会在10分钟内过期AgentLogin.all_qq(settings.QQ_CLIENT_ID, settings.QQ_APP_KEY, settings.QQ_REDIRECT_URI, code)

四 通过OAuth2.0认证 获取openid

  • 提取code 请求参数
  • 使⽤code向QQ服务器请求 access_token
  • 使⽤access_token向QQ服务器请求 openid
  • 使⽤openid查询 该QQ⽤户是否芒果头条中 绑定过⽤户
  • 如果openid已绑定 芒果头条⽤户,直接⽣成JWT token,并返回
  • 如果openid没绑定 芒果头条⽤户,创建⽤户并绑定到openid

4.1 获取QQ登录扫码⻚⾯

4.1.1 请求⽅式
选项⽅案
请求⽅法GET
请求地址/qq/login/
4.1.2 请求参数:⽆
4.1.3 响应结果:JSON
字段说明
code状态码
errmsg错误信息
login_urlQQ登录扫码⻚⾯链接
4.1.4 后端逻辑实现
class QQLoginURLView(View):"""提供QQ登录⻚⾯⽹址"""def get(self, request):# 获取QQ登录⻚⾯⽹址qq_login_url = AgentLogin.qq_url(settings.QQ_CLIENT_ID, settings.QQ_REDIRECT_URI)return http.JsonResponse({'code': 200, 'errmsg': 'OK','qq_login_url': qq_login_url})
4.1.5 QQ登录参数
# QQ登录的配置参数
QQ_CLIENT_ID = '101917966'
QQ_REDIRECT_URI = 'http://www.nagle.cn:8083/about'
QQ_APP_KEY = '20fcc768255829c08fa4efbe8acf0001'

4.2 接收Authorization Code

提示:

  • ⽤户在QQ登录成功后,QQ会 将⽤户重定向到我们配置的回调⽹址
  • 在QQ 重定向到回调⽹址时会传给我们⼀个Authorization Code
  • 我们需要拿到Authorization Code并 完成OAuth2.0认证获取openid
  • 在本项⽬中,我们申请QQ登录开发资质时配置的 回调⽹址 为:
    • http://www.nagle.cn:8083/about
  • QQ互联重定向的 完整⽹址 为:
    • http://www.nagle.cn/about/? code=991088ECBF489B38CFBDF1BB4B093EC9
class QQLoginUserView(View):"""⽤户扫码登录的回调处理"""def get(self, request):"""Oauth2.0认证"""# 接收Authorization Codecode = request.GET.get('code')if not code:raise Forbbiden('缺少code')pass
re_path(r'^about/$', views.QQLoginUserView.as_view()),

4.3 OAuth2.0认证 获取openid

import logging
from django import httplogger = logging.getLogger('django')class QQLoginUserView(View):"""⽤户扫码登录的回调处理"""def get(self, request):"""Oauth2.0认证"""# 接收Authorization Codecode = request.GET.get('code')if not code:return http.HttpResponseForbidden('缺少code')try:nickname, openid = AgentLogin.qq(settings.QQ_CLIENT_ID, settings.QQ_APP_KEY, settings.QQ_REDIRECT_URI, code)except Exception as e:logger.error(e)return http.HttpResponseServerError('OAuth2.0认证失败')pass

4.4 本机绑定www.nagle.cn域名

1.ubuntu系统或者Mac系统

sudo vi /etc/hosts
127.0.0.1 www.nagle.cn

4.5 修改 dev.py 配置⽂件

ALLOWED_HOSTS = ['www.nagle.cn','127.0.0.1']

4.6 修改服务器端⼝号

4.7 配置回调地址路由

# oauth/urls.py
re_path('^about/$',views.QQAuthUserView.as_view()),

4.8 创建类视图

class QQAuthUserView(View):def get(self, request):"""获取openid:param request::return:"""# 获取code参数code = request.GET.get('code', '')# 校验参数if not code:return http.HttpResponseForbidden('缺少code参数值')# 调⽤接⼝⽅法获取openidnickname, openid = AgentLogin.qq(settings.QQ_CLIENT_ID, settings.QQ_APP_KEY,settings.QQ_REDIRECT_URI, code)return HttpResponse(openid)

五 QQ用户 是否绑定项目用户 的处理

5.1 判断 openid是否绑定过⽤户

使⽤openid 查询该QQ⽤户是否在芒果头条中绑定过⽤户

try:oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:# 如果openid没绑定芒果头条⽤户pass
else:# 如果openid已绑定芒果头条⽤户pass

5.2 openid 已绑定⽤户的处理

如果openid已绑定芒果头条⽤户,直接⽣成状态保持信息,登录成功,并 重定向到⾸⻚

class QQOauthUser(View):def get(self, request):# 获取code参数code = request.GET.get('code', '')if code == '':return http.HttpResponseForbidden('缺少参数')# 获取QQ⽤户名和openidnickname, openid = AgentLogin.qq(settings.QQ_CLIENT_ID, settings.QQ_APP_KEY, settings.QQ_REDIRECT_URI, code)try:# 查询当前QQ⽤户是否绑定芒果头条⽤户qq_user = QQAuthUser.objects.get(openid=openid)except QQAuthUser.DoesNotExist:# 没绑定芒果头条⽤户passelse:# 已绑定芒果头条⽤户# 实现状态保持mg_user = qq_user.userlogin(request, mg_user)# 响应结果return redirect(reverse('newsapp:index'))

5.3 openid 未绑定⽤户的处理

  • 为了能够在后续的绑定⽤户操作前端可以使⽤openid,在这⾥将openid签名后响应给前端
  • openid属于⽤户的隐私信息,所以需要将openid签名处理,避免暴露。
class QQOauthUser(View):def get(self, request):# 获取code参数code = request.GET.get('code', '')if code == '':return http.HttpResponseForbidden('缺少参数')# 获取QQ⽤户名和openidnickname, openid = AgentLogin.qq(settings.QQ_CLIENT_ID, settings.QQ_APP_KEY,settings.QQ_REDIRECT_URI, code)try:# 查询当前QQ⽤户是否绑定芒果头条⽤户qq_user = QQAuthUser.objects.get(openid=openid)except QQAuthUser.DoesNotExist:# 没绑定芒果头条⽤户# 加密openid数据sec_openid = generate_secret_openid(openid)return render(request, 'oauth/oauth_user.html',{'sec_openid': sec_openid})else:# 已绑定芒果头条⽤户# 实现状态保持mg_user = qq_user.userlogin(request, mg_user)# 响应结果return redirect(reverse('newsapp:index'))
# oauth/oauth_user.html 
<input type="hidden" name="sec_openid" value="{{ sec_openid }}">

5.4 itsdangerous的使⽤

  • itsdangerous模块的 参考资料链接
  • 安装:pip install itsdangerous
  • TimedJSONWebSignatureSerializer的使⽤ 使⽤TimedJSONWebSignatureSerializer可以 ⽣成带有有效期的token
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from django.conf import settings# serializer = Serializer(秘钥, 有效期秒)
serializer = Serializer(settings.SECRET_KEY, 300)
# serializer.dumps(数据), 返回bytes类型
token = serializer.dumps({'uname': 'zhangsan'})
token = token.decode()
# 检验token
# 验证失败,会抛出itsdangerous.BadData异常
serializer = Serializer(settings.SECRET_KEY, 300)
try:data = serializer.loads(token)
except BadData:return None
  • openid签名处理 (对openid进⾏ 加密)
# oauth/utils.py
def generate_secret_openid(openid):"""签名openid:param openid: ⽤户的openid:return: access_token"""# 创建序列化器对象给数据加密serializer = Serializer(settings.SECRET_KEY, expires_in=600)data = {'openid': openid}token = serializer.dumps(data)return token.decode()

六 QQ用户绑定项目 用户实现

类似于⽤户注册的业务逻辑

  • 当⽤户输⼊的⼿机号对应的 ⽤户已存在
    • 直接 将该已存在⽤户跟openid绑定
  • 当⽤户输⼊的⼿机号对应的 ⽤户不存在
    • 新建⼀个⽤户,并跟openid绑定
# /qq/login/ POST
# oauth/views.py
class QQLoginUserView(View):"""⽤户扫码登录的回调处理"""def get(self, request):"""Oauth2.0认证"""......def post(self, request):"""QQ⽤户登录成功后绑定芒果头条⽤户"""# 接收参数phone = request.POST.get('phone')pwd = request.POST.get('password')sms_code_client = request.POST.get('msgcode')sec_openid = request.POST.get('sec_openid')# 校验参数# 判断参数是否⻬全if not all([phone, pwd, sms_code_client, sec_openid]):return http.HttpResponseForbidden('缺少必传参数')# 判断⼿机号是否合法if not re.match(r'^1[35789]\d{9}$', phone):return http.HttpResponseForbidden('请输⼊正确的⼿机号码')# 判断密码是否合格if not re.match(r'^[0-9A-Za-z]{3,8}$', pwd):return http.HttpResponseForbidden('请输⼊3,8位的密码')# 判断短信验证码是否⼀致redis_conn = get_redis_connection('verify_code')sms_code_server = redis_conn.get('sms_%s' % phone)if sms_code_server is None:return render(request, 'oauth/oauth_user.html',{'sms_code_errmsg': '⽆效的短信验证码'})if sms_code_client != sms_code_server.decode():return render(request, 'oauth/oauth_user.html',{'sms_code_errmsg': '输⼊短信验证码有误'})# 判断openid是否有效:错误提示放在sms_code_errmsg位置openid = check_secret_openid(sec_openid)if not openid:return render(request, 'oauth/oauth_user.html',{'openid_errmsg': '⽆效的openid'})# 保存注册数据try:user = Users.objects.get(phone=phone)except Users.DoesNotExist:# ⽤户不存在,新建⽤户user = Users.objects.create_user(username=phone,password=pwd, phone=phone)else:# 如果⽤户存在,检查⽤户密码if not user.check_password(pwd):return render(request, 'oauth/oauth_user.html',{'account_errmsg': '⽤户名或密码错误'})# 将⽤户绑定openidtry:QQAuthUser.objects.create(openid=openid, user=user)except DatabaseError:return render(request, 'oauth/oauth_user.html',{'qq_login_errmsg': 'QQ登录失败'})# 实现状态保持login(request, user)# 响应绑定结果return redirect(reverse('newsapp:index'))

 

七 添加邮箱前端逻辑

7.1 前端逻辑处理

1.user_center.js

methods: {// 获取指定名称的cookie值getCookie: function(name) {var value = '; ' + document.cookie;var parts = value.split('; ' + name + '=');if (parts.length === 2) {// 返回cookie值return parts.pop().split(';').shift();}// 如果没有找到对应的cookie,返回nullreturn null;},// 保存邮箱save_email: function() {// 使用axios发送post请求axios.post('/emails/', {'email': this.email,'userid': this.userid}, {headers: {// 从cookie中获取csrftoken并设置到请求头中,解决跨域问题'X-CSRFToken': this.getCookie('csrftoken')}}).then(response => {// 请求成功后的处理if (response.data.code == '200') {// 如果返回码为200,表示成功,刷新页面location.reload();this.error_email = false;} else {// 否则标记邮箱保存出错this.error_email = true;}}).catch(error => {// 请求失败后的处理,打印错误信息console.log(error.response);});}
}

2.user_center.html

<table border="0" cellpadding="0" cellspacing="0"><tr><td width="30%">⽤户名</td><td>{{ user.username }}</td></tr><tr><td>⼿机号</td><td>{{ user.phone }}</td></tr><tr><td>邮箱</td><td>{% if user.email %}{{ user.email}}{% else %}<input type="text" v-model="email" style="width: 290px;" placeholder="输⼊邮箱地址"><button @click="save_email">保存</button><span class="error-tip" v-show="error_email">${error_email_msg}</span>{% endif %}</td></tr>
</table>
<script>// 别忘了加""号let userid = "{{ user.id }}";
</script>
<script type="text/javascript" src="{% static 'js/userapp/user_center.js' %}"></script>

7.2 添加邮箱后端逻辑

7.2.1 添加邮箱后端接⼝设计
选项⽅案
请求⽅法POST
请求地址/emails/
7.2.2 请求参数:json参数
参数名类型是否必传说明
emailstring邮箱地址
useridstring当前登录⽤户id
7.2.3 响应结果:JSON
字段说明
code状态码
errmsg错误信息

7.3 添加邮箱后端实现

7.3.1 配置路由
# userapp/urls.py
re_path('^emails/$',views.EmailView.as_view()),
7.3.2 创建视图
# userapp/views.py
class EmailView(View):def post(self, request):params_str = request.body.decode()if params_str:p_dict = json.loads(params_str)count = Users.objects.filter(id=p_dict['userid']).update(email=p_dict['email'])if count:return JsonResponse({'code': 200, 'errormsg': 'OK'})return JsonResponse({'code': 500, 'errormsg': '保存邮箱地址失败!'})

相关文章:

使用QQ登录(头条项目-09)

一 QQ登录开发文档 QQ登录&#xff1a;即我们所说的 第三⽅登录&#xff0c;是指⽤户可以不在本项⽬中输⼊密码&#xff0c;⽽直接 通过第三⽅的验证&#xff0c;成功登录本项⽬。 1.1 QQ互联开发者申请步骤 若想实现QQ登录&#xff0c;需要成为 QQ互联的开发者&#xff0c;…...

iOS页面设计:UIScrollView布局问题与应对策略

在iOS开发中&#xff0c;UIScrollView是一个极其重要且常用的控件&#xff0c;它允许用户通过手势滑动查看大量内容。然而&#xff0c;在利用UIScrollView进行页面布局时&#xff0c;开发者往往会遇到一些挑战。本文将深入探讨UIScrollView布局中常见的问题&#xff0c;并提供相…...

Linux提权-02 sudo提权

文章目录 1. sudo 提权原理1.1 原理1.2 sudo文件配置 2. 提权利用方式2.1 sudo权限分配不当2.2 sudo脚本篡改2.3 sudo脚本参数利用2.4 sudo绕过路径执行2.5 sudo LD_PRELOAD环境变量2.6 sudo caching2.7 sudo令牌进程注入 3. 参考 1. sudo 提权原理 1.1 原理 sudo是一个用于在…...

vscode 设置

一、如何在vscode中设置放大缩小代码 1.1.文件—首选项——设置 1.2.在搜索框里输入“Font Ligatures”&#xff0c;然后点击"在settings.json中编辑" 1.3.在setting中&#xff08;"editor.fontLigatures":前&#xff09;添加如下代码 "editor.mous…...

学习threejs,使用FlyControls相机控制器

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.FlyControls 相机控制…...

在 C++ 中实现调试日志输出

在 C 编程中&#xff0c;调试日志对于定位问题和优化代码至关重要。有效的调试日志不仅能帮助我们快速定位错误&#xff0c;还能提供有关程序运行状态的有价值的信息。本文将介绍几种常用的调试日志输出方法&#xff0c;并教你如何在日志中添加时间戳。 1. 使用 #ifdef _DEBUG…...

从零搭建一套远程手机的桌面操控和文件传输的小工具

从零搭建一套远程手机的桌面操控和文件传输的小工具 --ADB连接专题 一、前言 前面的篇章中&#xff0c;我们确定了通过基于TCP连接的ADB控制远程手机的操作思路。本篇中我们将进行实际的ADB桥接的具体链路搭建工作&#xff0c;从原理和实际部署和操作层面上&#xff0c;从零…...

Python中的静态方法

目录 什么是静态方法&#xff1f;静态方法的特点 定义和调用静态方法示例&#xff1a;定义一个简单的静态方法 静态方法 vs 类方法 vs 实例方法示例对比 静态方法的应用场景1. &#x1f527; 工具函数2. &#x1f3ed; 工厂方法3. ✅ 数据验证 静态方法的限制总结 静态方法是 P…...

【C++】面试题整理(未完待续)

【C】面试题整理 文章目录 一、概述二、C基础2.1 - 指针在 32 位和 64 位系统中的长度2.2 - 数组和指针2.3 - 结构体对齐补齐2.4 - 头文件包含2.5 - 堆和栈的区别2.6 - 宏函数比较两个数值的大小2.7 - 冒泡排序2.8 - 菱形继承的内存布局2.9 - 继承重写2.10 - 如何禁止类在栈上分…...

每日一题 403. 青蛙过河

403. 青蛙过河 动态规划&#xff0c;状态转移 和 上一步步长 和 当前位置点 有关系 class Solution { public:bool canCross(vector<int>& stones) {int n stones.size();unordered_map<int,unordered_set<int>> dp;unordered_map<int,int> mp;…...

Spring Boot 集成 MongoDB:启动即注入的便捷实践

引言 在现代后端开发中&#xff0c;Spring Boot 凭借其快速开发、自动配置等特性深受开发者喜爱&#xff0c;而 MongoDB 以其灵活的文档存储结构和出色的扩展性&#xff0c;成为处理非结构化数据的首选数据库之一。将两者结合&#xff0c;利用 Spring Boot 的自动配置功能&…...

【电视盒子】HI3798MV300刷机教程笔记/备份遥控码修复遥控器/ADB/线刷卡刷/电视盒子安装第三方应用软件

心血来潮&#xff0c;看到电视机顶盒满天飞的广告&#xff0c;想改造一下家里的电视盒子&#xff0c;学一下网上的人刷机&#xff0c;但是一切都不知道怎么开始&#xff0c;虽然折腾了一天&#xff0c;以失败告终&#xff0c;还是做点刷机笔记。 0.我的机器 年少不会甄别&…...

R语言的文件操作

R语言的文件操作 引言 在数据科学和分析的过程中&#xff0c;文件操作是不可或缺的一部分。R语言作为一种强大的统计计算和图形作图的编程语言&#xff0c;提供了丰富的文件操作函数&#xff0c;使得用户能够方便地读取和保存数据。本文将详细介绍R语言中的文件操作&#xff…...

锐捷路由器网关RG-NBR6135-E和锐捷交换机 Ruijie Reyee RG-ES224GC 电脑登录web方法

2025年1月17日22:29:35 最近淘了点东西&#xff0c;准备在家里搞一套深度学习的服务器&#xff0c;先把网关和交换机搞到了 锐捷路由器网关RG-NBR6135-E 电脑登录web方法 在拿到机器的时候&#xff0c;如果不是全新建议拿根牙签&#xff0c;差入reset 5-10秒,灯光会全部闪几下…...

论文速读|NoteLLM: A Retrievable Large Language Model for Note Recommendation.WWW24

论文地址&#xff1a;https://arxiv.org/abs/2403.01744 bib引用&#xff1a; misc{zhang2024notellmretrievablelargelanguage,title{NoteLLM: A Retrievable Large Language Model for Note Recommendation}, author{Chao Zhang and Shiwei Wu and Haoxin Zhang and Tong Xu…...

在线图片转为excel工具

在线图片转为excel工具&#xff0c;无需登录&#xff0c;无需成本&#xff0c;用完就走。 包括中文和英文版本。 官网地址&#xff1a; https://img2excel.openai2025.com 效果&#xff1a;...

深度学习篇---数据集分类

文章目录 前言第一部分&#xff1a;VOC数据集标签、COCO数据集格式1.VOC数据集标签的特点及优缺点特点优点缺点 2.COCO数据集标签的特点及优缺点特点优点缺点 3.YOLO数据集标签的特点及优缺点特点优点缺点 第二部分&#xff1a;VOC格式和YOLO格式1.VOC格式3.YOLO格式3.区别(1)文…...

Unity3D仿星露谷物语开发23之拿起道具的动画

1、目标 当点击库存栏上可以carry的道具时&#xff0c;首先arms替换为carry状态&#xff0c;同时手上拿着被点击的道具。当再次点击同一个道具时&#xff0c;ams替换为idle状态&#xff0c;手上放下之前的道具。 这个最主要的是要学会使用AnimatorOverrideController类。 2、…...

素描风格渲染

素描风格渲染&#xff08;Hatching Style Rendering&#xff09;&#xff0c;是一种非真实感渲染&#xff08;NPR&#xff09;&#xff0c;主要目的是使3D模型看起来像 手绘素描的视觉效果。这种风格的渲染常用于游戏、动画和电影中&#xff0c;用来创造一种独特的艺术风格 1、…...

STM32使用DSP库 Keil方式添加

文章目录 前言一、添加DSP库二、使能FPU及配置1. 使能FPU2. 增加编译的宏3.增加头文件的检索路径三. 验证1. 源码中添加2.代码测试前言 添加DSP有两种方案,本文采用的是是Keil 中添加。 一、添加DSP库 在创建好的工程中添加DSP库:步骤如下: 步骤1:选择运行环境管理; 步…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

快速排序算法改进:随机快排-荷兰国旗划分详解

随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...

GAN模式奔溃的探讨论文综述(一)

简介 简介:今天带来一篇关于GAN的,对于模式奔溃的一个探讨的一个问题,帮助大家更好的解决训练中遇到的一个难题。 论文题目:An in-depth review and analysis of mode collapse in GAN 期刊:Machine Learning 链接:...

Netty自定义协议解析

目录 自定义协议设计 实现消息解码器 实现消息编码器 自定义消息对象 配置ChannelPipeline Netty提供了强大的编解码器抽象基类,这些基类能够帮助开发者快速实现自定义协议的解析。 自定义协议设计 在实现自定义协议解析之前,需要明确协议的具体格式。例如,一个简单的…...

break 语句和 continue 语句

break语句和continue语句都具有跳转作用&#xff0c;可以让代码不按既有的顺序执行 break break语句用于跳出代码块或循环 1 2 3 4 5 6 for (var i 0; i < 5; i) { if (i 3){ break; } console.log(i); } continue continue语句用于立即终…...