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

Go-Gin-Example 第八部分 优化配置接口+图片上传功能

文章目录

    • 前情提要
    • 本节目标
  • 优化配置结构
    • 讲解
    • 落实
      • 修改配置文件
      • 优化配置读取及设置初始化顺序
        • 第一步
      • 验证
    • 抽离file
  • 实现上传图片接口
    • 图片名加密
    • 封装image的处理逻辑
    • 编写上传图片的业务逻辑
    • 增加图片上传的路由
  • 验证
  • 实现前端访问 http.FileServer
    • r.StaticFS
    • 修改文章接口
      • 新增、更新文章接口

前情提要

学习项目github地址

上一部分学习笔记

本节目标

  • 优化配置结构(因为配置项越来越多)
  • 抽离 原 loggingFile 便于公用(logging、upload 各保有一份并不合适)
  • 实现上传图片接口(需限制文件格式、大小)
  • 修改文章接口(需支持封面地址参数)
  • 增加 blog_article (文章)的数据库字段
  • 实现 http.FileServer

优化配置结构

讲解

在先前章节中,我们通过读取KEY的方式读取配置项(建立setting模块)
本次需求中,需要增加图片的配置项,总体就有些冗余了

我们采用以下解决方法:

  • 映射结构体:使用 MapTo 来设置配置参数
  • 配置统管:所有的配置项统管到 setting

落实

修改配置文件

修改 conf/app.ini

增加了 5 个配置项用于上传图片的功能,4文件日志方面的配置项

[app]
PageSize = 10
JwtSecret = 233RuntimeRootPath = runtime/ImagePrefixUrl = http://127.0.0.1:8000
ImageSavePath = upload/images/
# MB
ImageMaxSize = 5
ImageAllowExts = .jpg,.jpeg,.pngLogSavePath = logs/
LogSaveName = log
LogFileExt = log
TimeFormat = 20060102[server]
#debug or release
RunMode = debug
HttpPort = 8000
ReadTimeout = 60
WriteTimeout = 60[database]
Type = mysql
User = root
Password = rootroot
Host = 127.0.0.1:3306
Name = blog
TablePrefix = blog_

优化配置读取及设置初始化顺序

第一步

将散落在其他文件里的配置都删掉,统一在 setting 中处理以及修改 init 函数为 Setup 方法

  1. 打开 pkg/setting/setting.go 文件,修改如下:
package modelsimport ("fmt""log""time""github.com/jinzhu/gorm"_ "github.com/jinzhu/gorm/dialects/mysql""github.com/kingsill/gin-example/pkg/setting"
)// 定义一个全局的数据库连接变量
var db *gorm.DB// Model 设定常用结构体,可以作为匿名结构体嵌入到别的表格对应的结构体
type Model struct {ID         int `gorm:"primary_key" json:"id"`CreatedOn  int `json:"created_on"`ModifiedOn int `json:"modified_on"`DeletedOn  int `json:"deleted_on"`
}func Setup() {//配置文件加载Cfg, err := ini.Load("conf/app.ini")if err != nil {log.Fatalf("Fail to parse 'conf/app.ini': %v", err)}//将app section 部分映射到AppSetting结构体上err = Cfg.Section("app").MapTo(AppSetting)if err != nil {log.Fatalf("Cfg.MapTo AppSetting err: %v", err)}//将图片最大大小设置从5字节Byte转换为5兆字节MBAppSetting.ImageMaxSize = AppSetting.ImageMaxSize * 1024 * 1024err = Cfg.Section("server").MapTo(ServerSetting)if err != nil {log.Fatalf("Cfg.MapTo ServerSetting err: %v", err)}//将读取时自动转换的类型转换为时间间隔了,只不过是最小单位纳秒ServerSetting.ReadTimeout = ServerSetting.ReadTimeout * time.SecondServerSetting.WriteTimeout = ServerSetting.WriteTimeout * time.Seconderr = Cfg.Section("database").MapTo(DatabaseSetting)if err != nil {log.Fatalf("Cfg.MapTo DatabaseSetting err: %v", err)}
}

在这里,我们做了如下几件事:

  • 编写与配置项保持一致的结构体(App、Server、Database
  • 使用 MapTo 将配置项映射到结构体上
  • 对一些需特殊设置的配置项进行再赋值
  1. 修改models.go
    init函数改为Setup方法,将独立读取的DB配置项删除,改为统一读取setting
package modelsimport (
...
)// 定义一个全局的数据库连接变量
var db *gorm.DB// Model 设定常用结构体,可以作为匿名结构体嵌入到别的表格对应的结构体
type Model struct {ID         int `gorm:"primary_key" json:"id"`CreatedOn  int `json:"created_on"`ModifiedOn int `json:"modified_on"`DeletedOn  int `json:"deleted_on"`
}func Setup() {var err error//使用gorm框架初始化数据库连接db, err = gorm.Open(setting.DatabaseSetting.Type, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",setting.DatabaseSetting.User,setting.DatabaseSetting.Password,setting.DatabaseSetting.Host,setting.DatabaseSetting.Name))if err != nil {log.Println(err)}//自定义默认表的表名,使用匿名函数,在原默认表名的前面加上配置文件中定义的前缀gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {return setting.DatabaseSetting.TablePrefix + defaultTableName}//gorm默认使用复数映射,当前设置后即进行严格匹配db.SingularTable(true)//log记录打开db.LogMode(true)//进行连接池设置db.DB().SetMaxIdleConns(10)db.DB().SetMaxOpenConns(100)//替换Create和Update回调函数db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)//添加删除的回调CallBacksdb.Callback().Delete().Replace("gorm:delete", deleteCallback)
}// CloseDB 与数据库断开连接函数
func CloseDB() {defer db.Close()
}// updateTimeStampForCreateCallback 在创建记录时设置 `CreatedOn`, `ModifiedOn`
func updateTimeStampForCreateCallback(scope *gorm.Scope) {...
}// updateTimeStampForUpdateCallback 在更新记录时设置 `ModifyOn`
func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
...
}// 设定delete操作的callback逻辑
func deleteCallback(scope *gorm.Scope) {...
}// 判断是否为空来进行空格插入,防止sql注入,保证安全性
func addExtraSpaceIfExist(str string) string {...
}
  1. 修改log.go
    init函数改为Setup方法
func Setup() {//获取log文件目录filePath := getLogFileFullPath()//得到log文件句柄F = openLogFile(filePath)//创建一个新的日志记录器logger = log.New(F, DefaultPrefix, log.LstdFlags)
}
  1. 修改pkg/logging/file.go

    独立的 LOG 配置项删除,改为统一读取 setting,修改这两个函数即可

// 返回log文件的前缀路径,算是一个具有仪式感的函数
func getLogFilePath() string {return fmt.Sprintf("%s", setting.AppSetting.LogSavePath)
}// 获得log文件的整体路径,以当前日期作为.log文件的名字
func getLogFileFullPath() string {prefixPath := getLogFilePath()suffixPath := fmt.Sprintf("%s%s.%s", setting.AppSetting.LogSaveName, time.Now().Format(setting.AppSetting.TimeFormat), setting.AppSetting.LogFileExt)return fmt.Sprintf("%s%s", prefixPath, suffixPath)
}
  1. 其他漏下的未改为统一读取setting的根据报错进行修改即可

验证

在这里为止,针对本需求的配置优化就完毕了,你需要执行 go run main.go 验证一下你的功能是否正常哦

抽离file

  1. pkg目录下新建file/file.go
package fileimport ("io""mime/multipart""os""path"
)// GetSize multipart.file用于处理HTTP请求中文件上传到类型   os.file则主要是本地文件的操作
func GetSize(f multipart.File) (int, error) {content, err := io.ReadAll(f)return len(content), err
}// GetExt 获取文件扩展名
func GetExt(filename string) string {return path.Ext(filename)
}// CheckExist 检查文件是否存在
func CheckExist(src string) bool {//os.stat用于获取文件的相关信息_, err := os.Stat(src)return os.IsNotExist(err)
}// CheckPermission 检查访问文件的权限
func CheckPermission(src string) bool {_, err := os.Stat(src)//检查是否有访问文件的权限return os.IsPermission(err)
}// IsNotExistMkDir 检查是否存在目录,不存在则创建目录
func IsNotExistMkDir(src string) error {if notExist := CheckExist(src); notExist == true {if err := MkDir(src); err != nil {return err}}return nil
}// MkDir 创建目录
func MkDir(src string) error {err := os.MkdirAll(src, os.ModePerm) //权限0777,权限拉满if err != nil {return err}return nil
}// Open 算是简单包装os.openfile
func Open(name string, flag int, perm os.FileMode) (*os.File, error) {f, err := os.OpenFile(name, flag, perm)if err != nil {return nil, err}return f, nil
}

在这里我们用到了 mime/multipart 包,它主要实现了 MIMEmultipart解析,主要适用于 HTTP 和常见浏览器生成的 multipart 主体

  1. 修改原logging包的方法
  • 修改pkg/logging/file.go
package loggingimport ("fmt""github.com/kingsill/gin-example/pkg/file""github.com/kingsill/gin-example/pkg/setting""os""time"
)// 返回log文件的前缀路径,算是一个具有仪式感的函数
func getLogFilePath() string {return fmt.Sprintf("%s", setting.AppSetting.LogSavePath)
}// 获得log文件的整体路径,以当前日期作为.log文件的名字 runtime/log20010212.log
func getLogFileFullPath() string {prefixPath := getLogFilePath()suffixPath := fmt.Sprintf("%s%s.%s",setting.AppSetting.LogSaveName,time.Now().Format(setting.AppSetting.TimeFormat),setting.AppSetting.LogFileExt,)return fmt.Sprintf("%s%s", prefixPath, suffixPath)
}// 打开日志文件,返回写入的句柄handle
func openLogFile() (*os.File, error) {//获取文件整体路径fileName := getLogFileFullPath()//创建目录mkDir()//如果.log文件不存在,这里会创建一个handle, err := file.Open(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)if err != nil {return nil, fmt.Errorf("fail to open:%s\n", fileName)}return handle, nil
}// 创建log目录
func mkDir() {//获得当前目录 dir: /home/wang2/gin-exampledir, _ := os.Getwd()//检查目录访问权限perm := file.CheckPermission(getLogFilePath())if perm == true {panic("Permission denied")}//如果目录不存在,创建目录err := file.IsNotExistMkDir(dir + "/" + getLogFilePath())if err != nil {panic(err)}
}
  • 修改pkg/logging/log.go
    由于原方法传参有变,这里也进行相关调整
...// Setup 自定义logger的初始化
func Setup() {var err error//得到log文件句柄F, err = openLogFile()if err != nil {log.Fatalln(err)}//创建一个新的日志记录器logger = log.New(F, DefaultPrefix, log.LstdFlags)
}
...

实现上传图片接口

首先需要在 blog_article 中增加字段 cover_image_url,格式为 varchar(255) DEFAULT '' COMMENT '封面图片地址'

alter table blog_article add cover_image_url varchar(255) DEFAULT '' COMMENT '封面图片地址';

图片名加密

我们通过 MD5 对图片进行加密,防止图片名暴露
util目录下新建md5.go,写入文件内容

package utilimport ("crypto/md5""encoding/hex"
)// EncodeMD5 计算给定字符的MD5哈希值,返回其十六进制表示
func EncodeMD5(value string) string {//创建一个新的MD5计算器实例m := md5.New()//将value写入到MD5计算器中m.Write([]byte(value))//nil表示计算完哈希值后不添加后缀return hex.EncodeToString(m.Sum(nil))
}

封装image的处理逻辑

pkg 目录下新建upload/image.go文件,写入文件内容

这里基本是对底层代码的二次封装,为了更灵活的处理一些图片特有的逻辑,并且方便修改,不直接对外暴露下层

package uploadimport (
...
)func GetImageFullUrl(name string) string {return setting.AppSetting.ImagePrefixUrl + "/" + GetImagePath() + name
}// GetImageName 计算MD5加密之后的图片名
func GetImageName(name string) string {//将图片的名字剥离扩展名ext := path.Ext(name)fileName := strings.TrimSuffix(name, ext)//对单纯的图片名进行MD5加密fileName = util.EncodeMD5(fileName)//将MD5加密后的图片名和后缀返回return fileName + ext
}// GetImagePath 包装文件路径 upload/images/
func GetImagePath() string {return setting.AppSetting.ImageSavePath
}// GetImageFullPath 拼凑完整路径 runtime/+upload/images/
func GetImageFullPath() string {return setting.AppSetting.RuntimeRootPath + GetImagePath()
}// CheckImageExt 检查图片格式是否正确
func CheckImageExt(fileName string) bool {ext := file.GetExt(fileName)for _, allowExt := range setting.AppSetting.ImageAllowExts {//都大写进行对比if strings.ToUpper(allowExt) == strings.ToUpper(ext) {return true}}return false
}// CheckImageSize 检查图片的大小是否小于规定的最大值 5M
func CheckImageSize(f multipart.File) bool {size, err := file.GetSize(f)if err != nil {log.Println(err)logging.Warn(err)return false}return size <= setting.AppSetting.ImageMaxSize
}func CheckImage(src string) error {dir, err := os.Getwd()if err != nil {return fmt.Errorf("os.Getwd err: %v", err)}//检查图片目录err = file.IsNotExistMkDir(dir + "/" + src)if err != nil {return fmt.Errorf("file.IsNotExistMkDir err: %v", err)}//检查访问权限perm := file.CheckPermission(src)if perm == true {return fmt.Errorf("file.CheckPermission Permission denied src: %s", src)}return nil
}

编写上传图片的业务逻辑

routers/api 目录下新建 upload.go 文件,写入内容

package apiimport (
...
)func UploadImage(c *gin.Context) {code := e.SUCCESSdata := make(map[string]string)file, image, err := c.Request.FormFile("image")if err != nil {logging.Warn(err)code = e.ERRORc.JSON(http.StatusOK, gin.H{"code": code,"msg":  e.GetMsg(code),"data": data,})}if image == nil {code = e.INVALID_PARAMS} else {imageName := upload.GetImageName(image.Filename) //获取图片名fullPath := upload.GetImageFullPath()            //图片完整路径savePath := upload.GetImagePath()                //仓库内保存路径//图片路径+名字src := fullPath + imageName//检查图片格式和大小if !upload.CheckImageExt(imageName) || !upload.CheckImageSize(file) {code = e.ERROR_UPLOAD_CHECK_IMAGE_FORMAT} else {//检查图片目录、访问权限err := upload.CheckImage(fullPath)if err != nil {logging.Warn(err)code = e.ERROR_UPLOAD_CHECK_IMAGE_FAIL} else if err := c.SaveUploadedFile(image, src); err != nil { //图片保存到指定位置logging.Warn(err)code = e.ERROR_UPLOAD_SAVE_IMAGE_FAIL} else {//data["image_url"] = upload.GetImageFullUrl(imageName)data["image_save_url"] = savePath + imageName}}}c.JSON(http.StatusOK, gin.H{"code": code,"msg":  e.GetMsg(code),"data": data,})
}

在这一大段的业务逻辑中,我们做了如下事情:

  • c.Request.FormFile:获取上传的图片(返回提供的表单键的第一个文件)
  • CheckImageExt、CheckImageSize 检查图片大小,检查图片后缀
  • CheckImage:检查上传图片所需(权限、文件夹)
  • SaveUploadedFile:保存图片
    总的来说,就是 入参 -> 检查 -》 保存 的应用流程

增加图片上传的路由

打开 routers/router.go 文件,增加路由 r.POST("/upload", api.UploadImage)

func InitRouter() *gin.Engine {r := gin.New()...r.GET("/auth", api.GetAuth)r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))r.POST("/upload", api.UploadImage)apiv1 := r.Group("/api/v1")apiv1.Use(jwt.JWT()){...}return r
}

验证

使用 postman,测试图片上传功能
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
看到runtime/upload/images下存在我们上传的文件
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现前端访问 http.FileServer

在完成了上一小节后,我们还需要让前端能够访问到图片,一般是如下:

  • CDN
  • http.FileSystem

在公司的话,CDN 或自建分布式文件系统居多,也不需要过多关注。而在实践里的话肯定是本地搭建了,Go 本身对此就有很好的支持,而 Gin 更是再封装了一层,只需要在路由增加一行代码即可

r.StaticFS

打开 routers/router.go 文件,增加路由 r.StaticFS("/upload/images", http.Dir(upload.GetImageFullPath()))

func InitRouter() *gin.Engine {...//网页 请求我们指定目录内的内容r.StaticFS("/upload/images", http.Dir(upload.GetImageFullPath()))r.GET("/auth", api.GetAuth)r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))r.POST("/upload", api.UploadImage)...
}

http.dir 创建了文件系统,将 /upload/image 路径映射到我们指定的文件目录中,这里为 runtime/upload/images/ ,即我们放置图片的文件夹下

更多内容可以查看源码进行学习,到这里可以自行进行验证,访问 127.0.0.1:8000/upload/images/图片名

修改文章接口

新增、更新文章接口

支持入参 cover_image_url、增加对cover_image_url的非空、最长长度的检验

  1. 修改 models/article.go
...// Article 建立对应article表的struct结构体,方便进行信息读写
type Article struct {
...CoverImageUrl string `json:"cover_image_url"`
}// AddArticle 添加文章
func AddArticle(data map[string]interface{}) bool {db.Create(&Article{
...CoverImageUrl: data["cover_image_url"].(string),})return true
}
...
  1. 修改 routers/api/v1/article.go
    AddArticleEditArticle 方法在原来的基础上进行修改,首先将之前为了方便验证写的使用 查询参数 ,改为 表单参数, 更安全
// @Summary	新增文章
// @Produce	json
// @Param		tagId		body		int		true	"tagId"
// @Param		title		body        string	true	"title"
// @Param		desc		body		string	true	"desc"
// @Param		content		body		string	true	"content"
// @Param		createdBy	body		string	true	"createdBy"
// @Param		state		body		int		true	"state"
// @Success	200			{string}	json	"{"code":200,"data":{},"msg":"ok"}"
// @Router		/api/v1/tags [post]
func AddArticle(c *gin.Context) {tagId := com.StrTo(c.PostForm("tag_id")).MustInt()title := c.PostForm("title")desc := c.PostForm("desc")content := c.PostForm("content")createdBy := c.PostForm("created_by")coverImageUrl := c.PostForm("cover_image_url")//**********state := com.StrTo(c.DefaultQuery("state", "0")).MustInt()valid := validation.Validation{}valid.Min(tagId, 1, "tag_id").Message("标签ID必须大于0")valid.Required(title, "title").Message("标题不能为空")valid.Required(desc, "desc").Message("简述不能为空")valid.Required(content, "content").Message("内容不能为空")valid.Required(createdBy, "created_by").Message("创建人不能为空")valid.Range(state, 0, 1, "state").Message("状态只允许0或1")valid.Required(coverImageUrl, "cover_image_url").Message("封面地址不能为空")//***********code := e.INVALID_PARAMSif !valid.HasErrors() {if models.ExistTagByID(tagId) {data := make(map[string]interface{})data["tag_id"] = tagIddata["title"] = titledata["desc"] = descdata["content"] = contentdata["created_by"] = createdBydata["state"] = statedata["cover_image_url"] = coverImageUrl//****************models.AddArticle(data)code = e.SUCCESS} else {code = e.ERROR_NOT_EXIST_TAG}} else {for _, err := range valid.Errors {logging.Info("err.key: %s, err.message: %s", err.Key, err.Message)}}c.JSON(http.StatusOK, gin.H{"code": code,"msg":  e.GetMsg(code),"data": make(map[string]interface{}),})
}// @Summary	修改文章
// @Produce	json
// @Param		id			path		int		true	"id"
// @Param		tagId		body		int		true	"tagId"
// @Param		title		body		string	true	"title"
// @Param		desc		body		string	true	"desc"
// @Param		content		body		string	true	"content"
// @Param		modifiedBy	body		string	true	"modifiedBy"
// @Param		state		body		int		false	"state"
// @Success	200			{string}	json	"{"code":200,"data":{},"msg":"ok"}"
// @Router		/api/v1/tags [post]
func EditArticle(c *gin.Context) {valid := validation.Validation{}id := com.StrTo(c.Param("id")).MustInt()tagId := com.StrTo(c.PostForm("tag_id")).MustInt()title := c.PostForm("title")desc := c.PostForm("desc")content := c.PostForm("content")coverImageUrl := c.PostForm("cover_image_url")//******modifiedBy := c.PostForm("modified_by")var state int = -1if arg := c.Query("state"); arg != "" {state = com.StrTo(arg).MustInt()valid.Range(state, 0, 1, "state").Message("状态只允许0或1")}valid.Min(id, 1, "id").Message("ID必须大于0")valid.Min(tagId, 1, "tag_id").Message("标签ID必须大于0")valid.MaxSize(title, 100, "title").Message("标题最长为100字符")valid.Required(title, "title").Message("标题不能为空")valid.MaxSize(desc, 255, "desc").Message("简述最长为255字符")valid.Required(desc, "desc").Message("简述不能为空")valid.MaxSize(content, 65535, "content").Message("内容最长为65535字符")valid.Required(modifiedBy, "modified_by").Message("修改人不能为空")valid.Required(coverImageUrl, "cover_image_url").Message("封面地址不能为空")//****************valid.MaxSize(coverImageUrl, 255, "cover_image_url").Message("封面地址最长为255字符")//*************valid.MaxSize(modifiedBy, 100, "modified_by").Message("修改人最长为100字符")code := e.INVALID_PARAMSif !valid.HasErrors() {if models.ExistArticleByID(id) {if models.ExistTagByID(tagId) {data := make(map[string]interface{})data["tag_id"] = tagIddata["title"] = titledata["desc"] = descdata["content"] = contentdata["modified_by"] = modifiedBymodels.EditArticle(id, data)code = e.SUCCESS} else {code = e.ERROR_NOT_EXIST_TAG}} else {code = e.ERROR_NOT_EXIST_ARTICLE}} else {for _, err := range valid.Errors {logging.Info("err.key: %s, err.message: %s", err.Key, err.Message)}}c.JSON(http.StatusOK, gin.H{"code": code,"msg":  e.GetMsg(code),"data": make(map[string]string),})
}

接下来进行验证即可

相关文章:

Go-Gin-Example 第八部分 优化配置接口+图片上传功能

文章目录 前情提要本节目标 优化配置结构讲解落实修改配置文件优化配置读取及设置初始化顺序第一步 验证 抽离file 实现上传图片接口图片名加密封装image的处理逻辑编写上传图片的业务逻辑增加图片上传的路由 验证实现前端访问 http.FileServerr.StaticFS修改文章接口新增、更新…...

阿里云国际DDoS高防的定制场景策略

DDoS高防的定制场景策略允许您在特定的业务突增时段&#xff08;例如新业务上线、双11大促销等&#xff09;选择应用独立于通用防护策略的定制防护策略模板&#xff0c;保证适应业务需求的防护效果。您可以根据需要设置定制场景策略。 背景信息 定制场景策略提供基于业务场景…...

v4l2采集视频

Video4Linux2&#xff08;v4l2&#xff09;是用于Linux系统的视频设备驱动框架&#xff0c;它允许用户空间应用程序直接与视频设备&#xff08;如摄像头、视频采集卡等&#xff09;进行交互。 linux系统下一切皆文件&#xff0c;对视频设备的操作就像对文件的操作一样&#xff…...

Spring Cloud 八:微服务架构中的数据管理

Spring Cloud 一&#xff1a;Spring Cloud 简介 Spring Cloud 二&#xff1a;核心组件解析 Spring Cloud 三&#xff1a;API网关深入探索与实战应用 Spring Cloud 四&#xff1a;微服务治理与安全 Spring Cloud 五&#xff1a;Spring Cloud与持续集成/持续部署&#xff08;CI/C…...

Chrome/Edge 使用 Markdown Viewer 查看 Markdown 格式文件

Chrome/Edge 使用 Markdown Viewer 查看 Markdown 格式文件 0. 引言1. 安装 Markdown Viewer 插件2. 使用 Markdown Viewer 阅读 Markdown 格式文件 0. 引言 大部分程序员都喜欢 Markdown 格式的文件&#xff0c;这时给一些没有在电脑上安装 Markdown 编辑器的同事分享资料时&…...

flutter 弹窗之系列一

自定义不受Navigator影响的弹窗 class MyHomePage extends StatefulWidget {const MyHomePage({super.key, required this.title});final String title;overrideState<MyHomePage> createState() > _MyHomePageState(); }class _MyHomePageState extends State<MyH…...

【Flink实战】Flink hint更灵活、更细粒度的设置Flink sql行为与简化hive连接器参数设置

文章目录 一. create table hints1. 语法2. 示例3. 注意 二. 实战&#xff1a;简化hive连接器参数设置三. select hints(ing) SQL 提示&#xff08;SQL Hints&#xff09;是和 SQL 语句一起使用来改变执行计划的。本章介绍如何使用 SQL 提示来实现各种干预。 SQL 提示一般可以…...

【python从入门到精通】-- 第二战:注释和有关量的解释

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…...

【手写AI代码目录】准备发布的教程

文章目录 1. tensorboard2. F.cross_entropy(input_tensor, target) F.log_softmax() F.nll_loss() 1. tensorboard from torch.utils.tensorboard import SummaryWriter# TensorBoard writer SummaryWriter(runs/mnist_experiment_1) ...if i % 100 99: # 每 100 个 b…...

2024.3.9|第十五届蓝桥杯模拟赛(第三期)

2024.3.9|十五届蓝桥杯模拟赛&#xff08;第三期&#xff09; 第一题 第二题 第三题 第四题 第五题 第六题 第七题 第八题 第九题 第十题 心有猛虎&#xff0c;细嗅蔷薇。你好朋友&#xff0c;这里是锅巴的C\C学习笔记&#xff0c;常言道&#xff0c;不积跬步无以至千里&…...

搭建PHP本地开发环境:看这一篇就够了

什么是PHP本地开发环境 PHP本地开发环境是指在个人计算机上模拟的服务器环境&#xff0c;这使得开发者能够在没有网络连接的情况下也能开发、测试和调试PHP应用程序。就像在你的电脑里装个小“服务器”&#xff0c;即使没网也能搞定PHP程序的开发和修修补补。这就是PHP本地开发…...

[蓝桥杯 2015]机器人数目

机器人数目 题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 少年宫新近邮购了小机器人配件&#xff0c;共有3类。 A 类含有&#xff1a;8个轮子&#xff0c;1个传感器&#xff1b; B 类含有: 6个轮子&#xff0…...

Codeforces Round 935 (Div. 3)

A. Setting up Camp&#xff08;模拟&#xff09; #include<iostream> #include<algorithm> using namespace std; const int N 2e5 10;int main(){int t, n;scanf("%d", &t);int a, b, c;while(t--){scanf("%d%d%d", &a, &b, …...

自然语言处理下载nltk模块库

nltk安装 目录 nltk安装 1.官方下载 2.离线下载 2.1 下载nltk资料包 2.2 解压下载的资料包重命名 2.2.1 将解压后的packages文件夹重命名为nltk_data 2.2.2 查看将重命名的文件夹放在那个位置 2.2.3 将上述nltk_data 文件夹放在 2.2.2 打印的位置处 3.验证是否下载成…...

题解:CF1937B(Binary Path)

题解&#xff1a;CF1937B&#xff08;Binary Path&#xff09; 一、 理解题意 1. 题目链接 CodeForces&#xff1b; 洛谷。 2. 题目翻译 给定一个 由 0 0 0 和 1 1 1 组成的 2 2 2 行 n n n 列的网格上寻找一条路径&#xff0c;使得这条路径上所有的数串联起来形成的0…...

JS——9大陷阱

一、警惕A>X>B写法 3>2>1 返回值为false&#xff08;原因&#xff1a;3>2为true&#xff0c;会默认转成数字1&#xff0c;1>1为false&#xff09; 1<4<3 返回值为true&#xff08;原因&#xff1a;1<4为true&#xff0c;会默认转成数字1&#xff…...

USB - 通过configfs配置Linux USB Gadget

Linux USB gadget configured through configfs Overview USB Linux 小工具是一种具有 UDC&#xff08;USB 设备控制器&#xff09;的设备&#xff0c;可连接到 USB 主机&#xff0c;以扩展其附加功能&#xff0c;如串行端口或大容量存储功能。 A USB Linux Gadget is a device…...

迷宫与陷阱(蓝桥杯)

文章目录 迷宫与陷阱问题描述bfs解题思路代码 迷宫与陷阱 问题描述 小明在玩一款迷宫游戏&#xff0c;在游戏中他要控制自己的角色离开一间由 N x N 个格子组成的2D迷宫。 小明的起始位置在左上角&#xff0c;他需要到达右下角的格子才能离开迷宫&#xff0c;每一步&#xf…...

Temple of Doom靶场nodejs获取shellss-manager漏洞tcpdump提权

下载链接&#xff1a; Temple of Doom: 1 ~ VulnHub 下载完成后直接在vxbox中导入即可&#xff0c;网络链接模式根据自身情况而定&#xff08;我采用的桥接模式&#xff09; 正文&#xff1a; 先用nmap进行扫描靶机ip nmap -sn 192.168.1.1/24 对192.168.1.5进行端口探测&a…...

day03_mysql_课后练习 - 参考答案

文章目录 day03_mysql_课后练习mysql练习题第1题第2题第3题第4题第5题 day03_mysql_课后练习 mysql练习题 第1题 案例&#xff1a; 1、创建一个数据库&#xff1a;day03_test01_school 2、创建如下表格 表1 Department表的定义 字段名字段描述数据类型主键外键非空唯一D…...

creator-webview与Android交互

title: creator-webview与Android交互 categories: Cocos2dx tags: [cocos2dx, creator, webview, 交互] date: 2024-03-23 13:17:20 comments: false mathjax: true toc: true creator-webview与Android交互 前篇 Android&#xff1a;你要的WebView与 JS 交互方式 都在这里了…...

22.WEB渗透测试-BurpSuite(一)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;21.WEB渗透测试-HTTP协议&#xff08;下&#xff09;-CSDN博客 工具的使用需要先搭建靶场…...

前端性能优化:防抖与节流

一、防抖和节流主要是干什么的 防抖和节流主要用于控制函数执行的频率&#xff0c;通过限制函数的触发次数&#xff0c;避免函数被过度调用而引发的性能问题或产生不必要的副作用。 二、防抖 防抖是什么: 1、对于在事件被触发 n 秒后再执行的回调 --> 延迟执行 2、如果…...

Copilot 编程助手的介绍及使用

介绍 Copilot 是2021年由 GitHub 与 OpenAI 合作研发的一款编程助手&#xff0c;同时也是全球首款使用OpenAI Codex模型&#xff08;GPT-3后代&#xff09;打造的大规模生成式AI开发工具。 Copilot 底层模型目前经过了数十亿行公开代码的训练&#xff0c;与大多数代码辅助工具…...

数据库专题(oracle基础和进阶)

前言 本专题主要记录自己最近学的数据库&#xff0c;有兴趣一起补习的可以一起看看&#xff0c;有补充和不足之处请多多指出。希望专题可以给自己还有读者带去一点点提高。 数据库基本概念 本模块有参考&#xff1a;数据库基本概念-CSDN博客 数据库管理系统是一个由互相关联的…...

web蓝桥杯2022省赛真题:水果拼盘

代码及注释&#xff1a; /* TODO&#xff1a;待补充代码 */ #pond {display: flex; //flex布局flex-direction: column; //主轴方向从上到下flex-wrap: wrap; //子元素换行 } 知识点&#xff1a; flex弹性布局 父元素&#xff1a;diasplay: flex; flex-d…...

Web核心

目录 Web核心HTTP概念&#xff1a;协议特点&#xff1a;请求数据格式响应数据格式 Tomcat简介基本使用配置部署项目IDEA中创建 Maven Web 项目 IDEA使用Tomcat Servlet简介快速入门执行流程生命周期体系结构Servlet urlPattern配置一个Servlet&#xff0c;可以配置多个 urlPatt…...

iOS应用审核问题解决方案及优化方法 ✨

摘要 本文将针对iOS应用提交审核时可能遇到的问题&#xff0c;如“你必须在Xcode中添加com.apple.developer.game-center密钥”&#xff0c;以及突然间提交送审报错情况进行探讨。通过大量查询资料和尝试&#xff0c;结合案例分析&#xff0c;提供了解决方案和优化方法&#xf…...

java post、get请求第三方https接口

java post、get请求第三方https接口 前段时间做项目新加功能由于要对接其它系统&#xff0c;请求系统接口传输数据。写完后发现我写的这个方法和网上现有的例子有点不太一样&#xff0c;可能是因为我做的项目是政务网的原因&#xff0c;但我想正常的即便是互联网的系统请求方式…...

【C语言】鸡兔同笼,鸡和兔共 100 只,共 284 只脚,求鸡和兔的个数。

鸡兔同笼&#xff0c;鸡和兔共 100 只&#xff0c;共 284 只脚&#xff0c;求鸡和兔的个数。 int main() {for (int i 0; ; i){if (2 * i 4 * (100 - i) 284){printf("鸡的数量&#xff1a;%d,兔子的数量&#xff1a;%d", i, 100 - i);break;} } }这里直接算出题…...