【Go】十四、图形验证码、短信验证码、注册接口与redis的简单使用

图形验证码

如何嵌入图形验证码工作:

这里选择使用captcha 开源库进行验证码设计:

选用下面的地址进行验证码开发工作

https://zh.mojotv.cn/go/refactor-base64-captcha

基础功能构建

在 api 目录下创建 captcha.go 用来编写验证码操作

package apiimport ("github.com/gin-gonic/gin""github.com/mojocn/base64Captcha""go.uber.org/zap""net/http"
)// 使用内存方式存储验证码信息
var store = base64Captcha.DefaultMemStorefunc GetCaptcha(ctx *gin.Context) {// 配置验证码参数,数字、高:80、宽:240,长度:5、倾斜程度:0.7、背景圆圈数量:80driver := base64Captcha.NewDriverDigit(80, 240, 5, 0.7, 80)// 结合参数与生成方式,创建验证码生成器cp := base64Captcha.NewCaptcha(driver, store)id, b64s, _, err := cp.Generate()if err != nil {zap.S().Errorf("生成验证码错误:%s", err.Error())ctx.JSON(http.StatusInternalServerError, gin.H{"msg": "验证码生成错误",})}// 若没有发生错误:将图片id 和 图片的 base64 编码作为参数传递ctx.JSON(http.StatusOK, gin.H{"captchaId": id,"picPath": b64s,})
}

功能嵌入路由

将验证码放入路由:

在 router 目录下创建文件:base.go:

package apiimport ("github.com/gin-gonic/gin""github.com/mojocn/base64Captcha""go.uber.org/zap""net/http"
)// 使用内存方式存储验证码信息
var store = base64Captcha.DefaultMemStorefunc GetCaptcha(ctx *gin.Context) {// 配置验证码参数,数字、高:240、宽:80,长度:5、倾斜程度:0.7、背景圆圈数量:80driver := base64Captcha.NewDriverDigit(80, 240, 5, 0.7, 80)// 结合参数与生成方式,创建验证码生成器cp := base64Captcha.NewCaptcha(driver, store)id, b64s, _, err := cp.Generate()if err != nil {zap.S().Errorf("生成验证码错误:%s", err.Error())ctx.JSON(http.StatusInternalServerError, gin.H{"msg": "验证码生成错误",})}// 若没有发生错误:将图片id 和 图片的 base64 编码作为参数传递ctx.JSON(http.StatusOK, gin.H{"captchaId": id,"picPath":   b64s,})
}

将base 的router 配置到初始化的 Initialize 目录中的 router 初始化中:

func Routers() *gin.Engine {Router := gin.Default()// 配置跨域的拦截器Router.Use(middlewares.Cors())ApiGroup := Router.Group("/u/v1")router2.InitUserRouter(ApiGroup)router2.InitBaseRouter(ApiGroup)return Router
}

我们可以在前端直接做一个展示:

<img src="data:image/png;base64,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" alt="">

将验证码验证逻辑添加到登录逻辑中:

修改 form/user.go 添加验证码逻辑

package forms// 这里要注意 binding 内部的参数不可以加空格
// 请求中需要有 手机号、密码、验证码信息type PassWordLoginForm struct {Mobile     string `form:"mobile" json:"mobile" binding:"required,mobile"`PassWord   string `form:"password" json:"password" binding:"required,min=3,max=10"`Captcha    string `form:"captcha" json:"captcha" binding:"required,min=5,max=5"`Captcha_id string `form:"captcha_id" json:"captcha_id" binding:"required"`
}

再对 api/user.go 中的登录方法进行改造:

	// 绑定请求参数passwordLoginForm := forms.PassWordLoginForm{}if err := c.ShouldBind(&passwordLoginForm); err != nil {HandleValidatorError(c, err)}// 下面是新增部分,再请求参数绑定后 进行验证码验证逻辑// 直接使用 store 进行验证码验证,由于store 是在同一个 package 下的,所以该变量也可以直接使用// 若验证不正确则报错if !store.Verify(passwordLoginForm.CaptchaId, passwordLoginForm.Captcha, true) {c.JSON(http.StatusBadRequest, gin.H{"captcha": "验证码错误",})return}

之后POST访问登录:

BODY:
{"mobile": "13001350015","password": "admin123","captchaId": "y2uaAcBujzdUXz0DgX6b","captcha": "56570"
}

短信验证码

阿里云短信接口

稍后在 api 目录下进行开发,构建 api/sms.go 用来处理短信验证码的核心逻辑:

package apiimport ("fmt""github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests""github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi""github.com/gin-gonic/gin"
)func SendSms(ctx *gin.Context) {client, err := dysmsapi.NewClientWithAccessKey("cn-beijing", 你的acessKey, 你的acessSecret)if err != nil {panic(err)}request := requests.NewCommonRequest()request.Method = "POST"request.Scheme = "https"request.Domain = "dysmsapi.aliyuncs.com"request.Version = "2017-05-25"	// 必须写这个日期request.ApiName = "SendSms"// 直接写,无需在阿里云上配置request.QueryParams["RegionId"] = "cn-beijing"request.QueryParams["PhoneNumbers"] = 发送的手机号// 这里要写 签名的名字,注意是名字request.QueryParams["SignName"] = 你的签名名称// 这里要写模版的 id,注意是 idrequest.QueryParams["TemplateCode"] = 你的模版id// 参照你的模版发送请求,注意中间的验证码是可以自定义生成的request.QueryParams["TemplateParam"] = "{\"code\":" + "77777" + "}"response, err := client.ProcessCommonRequest(request)fmt.Println(client.DoAction(request, response))if err != nil {fmt.Println(err.Error())}fmt.Printf("response is %#v\n", response)
}

添加随机生成验证码的逻辑:
api/sms.go:

package apiimport ("fmt""github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests""github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi""github.com/gin-gonic/gin""math/rand""strings""time"
)// 随机验证码的生成
func GenerateSmsCode(width int) string {numeric := [10]byte{0,1,2,3,4,5,6,7,8,9}r := len(numeric)// 随机数种子rand.Seed(time.Now().Unix())var sb strings.Builderfor i := 0; i < width; i++ {fmt.Fprintf(&sb, "%d", numeric[rand.Intn(r)])}return sb.String()
}func SendSms(ctx *gin.Context) {client, err := dysmsapi.NewClientWithAccessKey("cn-beijing", "LTAI5tHRZ7GqMXx271nxuSgd", "tJErGmml9ArkhJmMoo0QK1BaJnlCWM")if err != nil {panic(err)}request := requests.NewCommonRequest()request.Method = "POST"request.Scheme = "https"request.Domain = "dysmsapi.aliyuncs.com"request.Version = "2017-05-25"request.ApiName = "SendSms"// 直接写,无需在阿里云上配置request.QueryParams["RegionId"] = "cn-beijing"request.QueryParams["PhoneNumbers"] = "13001350015"// 这里要写 签名的名字,注意是名字request.QueryParams["SignName"] = "清河个人博客网站"// 这里要写模版的 id,注意是 idrequest.QueryParams["TemplateCode"] = "SMS_461975751"// 参照你的模版发送请求,注意中间的验证码是可以自定义生成的request.QueryParams["TemplateParam"] = "{\"code\":" + GenerateSmsCode(6) + "}"response, err := client.ProcessCommonRequest(request)fmt.Println(client.DoAction(request, response))if err != nil {fmt.Println(err.Error())}fmt.Printf("response is %#v\n", response)// TODO 将验证码保存到 redis 以手机号为 key, 以验证码为 value}

验证码redis存储

虚拟机拉取 redis:

docker run -d --name redis -p 6379:6379 \
-v /mydata/redis:/usr/local/etc/redis \
redis

redis 默认数据库有 16 个 (0 - 15)

默认连接会连接到第一个 0 的 redis 数据库

将短信中的信息修改为从配置文件中读取:

在 config/config,go 中进行添加需要的配置信息:

package configtype UserSrvConfig struct {Host string `mapstructure:"host"`Port int32  `mapstructure:"port"`
}type ServerConfig struct {Name        string        `mapstructure:"name"`Port        int32         `mapstructure:"port"`UserSrvInfo UserSrvConfig `mapstructure:"user_srv"`JWTInfo     JWTConfig     `mapstructure:"jwt"`AliSmsInfo  AliSmsConfig  `mapstructure:"sms"`RedisInfo   RedisConfig   `mapstructure:"redis"`
}type JWTConfig struct {SigningKey string `mapstructure:"key"`
}// redis 配置
type RedisConfig struct {Host   string `mapstructure:"host"`Port   uint   `mapstructure:"port"`Expire int    `mapstructure:"expire"`
}// 阿里云信息配置
type AliSmsConfig struct {ApiKey    string `mapstructure:"key"`ApiSecret string `mapstructure:"secret"`
}

配置文件的修改不再显示,这里显示修改之后的核心代码:

package apiimport ("context""fmt""math/rand""mxshop-api/user-web/forms""net/http""strings""time""github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests""github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi""github.com/gin-gonic/gin""github.com/go-redis/redis/v8""mxshop-api/user-web/global"
)// 随机验证码的生成
func GenerateSmsCode(width int) string {numeric := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}r := len(numeric)// 随机数种子rand.Seed(time.Now().Unix())var sb strings.Builderfor i := 0; i < width; i++ {fmt.Fprintf(&sb, "%d", numeric[rand.Intn(r)])}return sb.String()
}func SendSms(ctx *gin.Context) {// 表单验证 要求必须传入手机号和 验证码类型sendSmsForm := forms.SendSmsForm{}if err := ctx.ShouldBind(&sendSmsForm); err != nil {HandleValidatorError(ctx, err)return}client, err := dysmsapi.NewClientWithAccessKey("cn-beijing", global.ServerConfig.AliSmsInfo.ApiKey, global.ServerConfig.AliSmsInfo.ApiSecret)smsCode := GenerateSmsCode(6)if err != nil {panic(err)}request := requests.NewCommonRequest()request.Method = "POST"request.Scheme = "https"request.Domain = "dysmsapi.aliyuncs.com"request.Version = "2017-05-25"request.ApiName = "SendSms"// 直接写,无需在阿里云上配置request.QueryParams["RegionId"] = "cn-beijing"request.QueryParams["PhoneNumbers"] = sendSmsForm.Mobile// 这里要写 签名的名字,注意是名字request.QueryParams["SignName"] = "清河个人博客网站"// 这里要写模版的 id,注意是 idrequest.QueryParams["TemplateCode"] = "SMS_461975751"// 参照你的模版发送请求,注意中间的验证码是可以自定义生成的request.QueryParams["TemplateParam"] = "{\"code\":" + smsCode + "}"response, err := client.ProcessCommonRequest(request)fmt.Println(client.DoAction(request, response))if err != nil {fmt.Println(err.Error())}fmt.Printf("response is %#v\n", response)// 将验证码保存到 redis 以手机号为 key, 以验证码为 valuerdb := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port),})rdb.Set(context.Background(), sendSmsForm.Mobile, smsCode, time.Duration(global.ServerConfig.RedisInfo.Expire)*time.Second)ctx.JSON(http.StatusOK, gin.H{"msg": "验证码发送成功",})
}

将验证码存储进 redis

拉取 redis 的 go 语言库:go-redis:

package apiimport ("context""fmt""math/rand""strings""time""github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests""github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi""github.com/gin-gonic/gin""github.com/go-redis/redis/v8""mxshop-api/user-web/global"
)// 随机验证码的生成
func GenerateSmsCode(width int) string {numeric := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}r := len(numeric)// 随机数种子rand.Seed(time.Now().Unix())var sb strings.Builderfor i := 0; i < width; i++ {fmt.Fprintf(&sb, "%d", numeric[rand.Intn(r)])}return sb.String()
}func SendSms(ctx *gin.Context) {client, err := dysmsapi.NewClientWithAccessKey("cn-beijing", global.ServerConfig.AliSmsInfo.ApiKey, global.ServerConfig.AliSmsInfo.ApiSecret)mobile := "13001350015"smsCode := GenerateSmsCode(6)if err != nil {panic(err)}request := requests.NewCommonRequest()request.Method = "POST"request.Scheme = "https"request.Domain = "dysmsapi.aliyuncs.com"request.Version = "2017-05-25"request.ApiName = "SendSms"// 直接写,无需在阿里云上配置request.QueryParams["RegionId"] = "cn-beijing"request.QueryParams["PhoneNumbers"] = mobile// 这里要写 签名的名字,注意是名字request.QueryParams["SignName"] = "清河个人博客网站"// 这里要写模版的 id,注意是 idrequest.QueryParams["TemplateCode"] = "SMS_461975751"// 参照你的模版发送请求,注意中间的验证码是可以自定义生成的request.QueryParams["TemplateParam"] = "{\"code\":" + smsCode + "}"response, err := client.ProcessCommonRequest(request)fmt.Println(client.DoAction(request, response))if err != nil {fmt.Println(err.Error())}fmt.Printf("response is %#v\n", response)// 将验证码保存到 redis 以手机号为 key, 以验证码为 valuerdb := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port),})rdb.Set(context.Background(), mobile, smsCode, 30*time.Second)ctx.JSON(http.StatusOK, gin.H{"msg": "验证码发送成功",})
}

另外,该接口还涉及到 form 表单问题,要求手机号必须填写:

在 form 目录下新建一个 go 文件:form/sms.go

package forms// 这里要注意 binding 内部的参数不可以加空格
// 请求中需要有 手机号、密码、验证码输入信息、验证码图片信息
type SendSmsForm struct {Mobile string `form:"mobile" json:"mobile" binding:"required,mobile"`// 这里为了区分开登录和注册的逻辑,包括具体场景下会出现的 找回密码等逻辑,需要添加一个 Type 表单Type uint `form:"type" json:"type" binding:"required,oneof=1 2"`
}

之后在 核心逻辑中添加表单验证的代码:

func SendSms(ctx *gin.Context) {// 表单验证 要求必须传入手机号和 验证码类型sendSmsForm := forms.SendSmsForm{}if err := ctx.ShouldBind(&sendSmsForm); err != nil {HandleValidatorError(ctx, err)return}client, err := dysmsapi.NewClientWithAccessKey("cn-beijing", global.ServerConfig.AliSmsInfo.ApiKey, global.ServerConfig.AliSmsInfo.ApiSecret)smsCode := GenerateSmsCode(6)......

在router/base.go 中添加发送短信验证码的路由:

package routerimport ("github.com/gin-gonic/gin""mxshop-api/user-web/api"
)func InitBaseRouter(Router *gin.RouterGroup) {BaseRouter := Router.Group("base"){BaseRouter.GET("captcha", api.GetCaptcha)// TODOBaseRouter.POST("send_sms", api.SendSms)}
}

之后,手机会收到验证码,验证码也会被存储在 redis 中

注册接口

创建核心逻辑的方法:

api/user.go

func Register(ctx *gin.Context) {// 配置传入的表单
}

在 forms / user.go 中添加表单验证信息:

type RegisterForm struct {Mobile   string `form:"mobile" json:"mobile" binding:"required,mobile"`Password string `form:"password" json:"password" binding:"required,min=3,max=10"`Code     string `form:"code" json:"code" binding:"required,min=6,max=6"`
}

之后继续编写注册的核心逻辑

api/user.go

func Register(ctx *gin.Context) {registerForm := forms.RegisterForm{}// 配置传入的表单,进行表单验证if err := ctx.ShouldBind(&registerForm); err != nil {HandleValidatorError(ctx, err)return}// 进行短信验证码校验rdb := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port),})if value, err := rdb.Get(context.Background(), registerForm.Mobile).Result(); err == redis.Nil {zap.S().Debug("key 不存在")ctx.JSON(http.StatusBadRequest, gin.H{"code": "验证码过期或未发送验证码",})return} else if value != registerForm.Code {ctx.JSON(http.StatusBadRequest, gin.H{"code": "验证码错误",})return}// 用户拨号连接 grpc 服务userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", global.ServerConfig.UserSrvInfo.Host, global.ServerConfig.UserSrvInfo.Port), grpc.WithInsecure())if err != nil {zap.S().Errorw("[Register] 连接用户服务器失败", "msg", err.Error())}// 生成用户模块的 grpc 接口并调用userSrvClient := proto.NewUserClient(userConn)user, err := userSrvClient.CreateUser(context.Background(), &proto.CreateUserInfo{Nickname: registerForm.Mobile,Password: registerForm.Password,Mobile:   registerForm.Mobile,})if err != nil {zap.S().Errorf("[Register] 创建用户 失败")HandleGrpcErrorToHttp(err, ctx)return}// 注册成功,自动登录,生成登录的ToKEN// 登录成功,生成TOKENj := middlewares.NewJWT() // 获得签名// 构建传递的信息以及签名信息claims := models.CustomClaims{ID:          uint(user.Id),NickName:    user.NickName,AuthorityID: uint(user.Role),StandardClaims: jwt.StandardClaims{ // 签名相关信息NotBefore: time.Now().Unix(),               // 签名生效时间:现在ExpiresAt: time.Now().Unix() + 60*60*24*30, // 签名过期时间,从现在开始一个月Issuer:    "BaiLu",                         // 签名 对象 (公司)},}// 真正创建 TOKEN:token, err := j.CreateToken(claims)if err != nil {ctx.JSON(http.StatusInternalServerError, gin.H{"msg": "TOKEN生成失败",})return}ctx.JSON(http.StatusOK, gin.H{"id":         user.Id,"nick_name":  user.NickName,"token":      token,"expired_at": (time.Now().Unix() + 60*60*24*30) * 1000,})}

将 Register 添加到 Router 中

router/user.go

func InitUserRouter(Router *gin.RouterGroup) {// 这样就需要 /user/list 才可以进行访问了UserRouter := Router.Group("user"){// 在这里添加拦截器的作用响应位置UserRouter.GET("list", middlewares.JWTAuth(), middlewares.IsAdminAuth(), api.GetUserList)//UserRouter.GET("list", api.GetUserList)UserRouter.POST("pwd_login", api.PassWordLogin)UserRouter.POST("register", api.Register)}
}

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

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

相关文章

车辆轨迹预测系列 (二):常见数据集介绍

车辆轨迹预测系列 (二)&#xff1a;常见数据集介绍 文章目录 车辆轨迹预测系列 (二)&#xff1a;常见数据集介绍1、NuScenes (2020)&#xff1a;1、下载2、说明 2、Waymo Open Dataset (2020)&#xff1a;1、介绍2、概述3、下载4、教程5、参考 3、Lyft Level 5 (2020)&#xff…

VOC数据集

VOC&#xff08;Visual Object Classes&#xff09;格式的数据集是一种用于计算机视觉任务的标准数据集格式&#xff0c;它最初是由Pascal VOC&#xff08;PASCAL Visual Object Classes&#xff09;数据集引入的。VOC数据集格式定义了一套标准化的数据集结构&#xff0c;包括X…

SD-WAN为什么适合小企业

SD-WAN&#xff08;软件定义广域网&#xff09;是一种革新性的网络技术&#xff0c;通过软件智能管理&#xff0c;实现灵活和高效的网络连接。在数字化转型浪潮中&#xff0c;企业对网络稳定性和性能的要求不断提升&#xff0c;SD-WAN因此受到了广泛关注。对于资源有限的小型企…

JAVA NIO(二) Buffer和Channel

一&#xff0c;基本使用 1&#xff0c; 一个Socket连接使用一个Channel来表示&#xff0c;以前直接操作Socket文件描述符来对读写缓冲区操作&#xff0c;比如读数据到用户空间的一个byte数组&#xff0c;NIO中Channel对这个过程作了封装&#xff0c;其中用户空间的byte数组就类…

macbook rust项目编译跨平台windows,linux

创建项目 cargo new test11, 编译windows exe包 rustup target add x86_64-pc-windows-gnu brew install mingw-w64 cargo build --target i686-pc-windows-gnu2. 编译ubuntu linux包 brew install FiloSottile/musl-cross/musl-cross rustup target add x86_64-unknown-lin…

Elk安装及使用

es安装及使用 单机版安装 集群安装 132 node-01 133 node-02 135 node-03 日志用户权限有问题 看日志 解决方案&#xff1a; 出现错误后&#xff0c;再次重启前&#xff0c;需要删除三个节点/data/下的内容 9300-http 9300-tcp logstasha安装及使用 Ssh错误 Yum安装默认路…

2024-06-21力扣每日一题

链接&#xff1a; LCP 61. 气温变化趋势 题意 A、B两个数组&#xff0c;数组内相邻两个数字有大于、等于、小于三种变化情况&#xff0c;求最长的一段&#xff0c;使两个数组的这一段变化情况相同&#xff0c;并且不要求这一段只能有一种变化 解&#xff1a; 因为数组内只…

UBUNTU安装KVM并加速安卓模拟器

在 Linux 上安装安卓模拟器&#xff08;如 Genymotion 或 Android Studio 的内置模拟器&#xff09;通常需要硬件 虚拟化支持&#xff0c;这可以通过 KVM 完成。本人亲测在ubuntu24.04上可用&#xff0c;但是是不是真的有加速效果不知&#xff0c;毕竟该卡的时候还是卡。 1.安…

逻辑回归(Logistic Regression)及其在机器学习中的应用

&#x1f680;时空传送门 &#x1f50d;逻辑回归原理&#x1f4d5;Sigmoid函数&#x1f388;逻辑回归模型 &#x1f4d5;损失函数与优化&#x1f388;损失函数&#x1f680;优化算法 &#x1f50d;逻辑回归的应用场景&#x1f340;使用逻辑回归预测客户流失使用scikit-learn库实…

Golang学习笔记02

封装 将结构体&#xff0c;字段的属性都小写&#xff0c;类似于private 给结构体提供一个工厂模式的函数&#xff0c;首字母大写 提供一个首字母大写的set方法&#xff0c;对属性赋值 提供一个首字母大写的get方法&#xff0c;对属性访问 package mainimport "fmt"ty…

全外显子测序分析流程1 - Fastq质控与去接头、低质量和引物序列

全外显子测序分析流程1 - Fastq质控与去接头、低质量和引物序列 1. 运行实例 # -d 样本根目录 # -s 样本名称 python trim_fastq.py -d /result/WES/sample -s sample2. fastqc质控报告与去接头、低质量序列主程序 对raw fastq和clean fastq生成质控QC报告trim_galore去接头、…

Avalonia:一个.NET跨平台UI框架

概述 Avalonia是一个强大的框架&#xff0c;使开发人员能够使用. NET创建跨平台应用程序。它使用自己的渲染引擎来绘制UI控件&#xff0c;确保在各种平台上保持一致的外观和行为&#xff0c;包括Windows&#xff0c;macOS&#xff0c;Linux&#xff0c;Android&#xff0c;iOS…

Linux命令详解

Linux关机重启 命令解释shutdown -h now或poweroff立刻关机shutdown -h 11分钟后关机&#xff0c;不带参数默认1分钟shutdown -r now立刻重启halt立刻关机reboot立刻重启sync把内存的数据同步到磁盘&#xff0c;关机和重启前应该先做这一步&#xff0c;目前shutdown、reboot、…

深入理解RunLoop

RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念&#xff0c;这篇文章将从 CFRunLoop 的源码入手&#xff0c;介绍 RunLoop 的概念以及底层实现原理。之后会介绍一下在 iOS 中&#xff0c;苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的。 一…

MySQL Online DDL 原理解读

在线工具站 推荐一个程序员在线工具站&#xff1a;程序员常用工具&#xff08;http://cxytools.com&#xff09;&#xff0c;有时间戳、JSON格式化、文本对比、HASH生成、UUID生成等常用工具&#xff0c;效率加倍嘎嘎好用。 程序员资料站 推荐一个程序员编程资料站&#xff1a;…

Vector 例题

例题一&#xff1a; 下面这个代码输出的是( ) &#xfeff;#include <iostream> #include <vector> using namespace std; int main(void) { vector<int>array; array.push_back(100); array.push_back(300); array.push_back(300); array.push_back(300); a…

html做一个雷达图的软件

要实现一个在线输入数据并生成雷达图的功能&#xff0c;可以使用HTML表单和JavaScript来处理用户输入的数据。以下是一个示例代码&#xff0c;演示了如何实现这个功能&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"…

行列视(RCV)在报表设计中的创新应用

行列视(RCV)在报表设计中的创新应用 报表设计一直是数据处理和分析领域的重要一环&#xff0c;其质量和效率直接影响到企业决策的准确性和及时性。近年来&#xff0c;行列视(RCV)作为一种先进的数据处理和展示工具&#xff0c;在报表设计中的应用越来越广泛&#xff0c;带来了…

【LeetCode热题 100】三数之和

leetcode原地址&#xff1a;https://leetcode.cn/problems/3sum/description 描述 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和…

U盘文件夹损坏0字节:现象解析、恢复方法与预防措施

在日常工作和生活中&#xff0c;U盘因其便携性和大容量成为我们存储和传输数据的重要工具。然而&#xff0c;当U盘中的文件夹突然损坏并显示为0字节时&#xff0c;我们可能会感到困惑和焦虑。本文将对U盘文件夹损坏0字节的现象进行详细描述&#xff0c;分析其可能的原因&#x…