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),})
}

接下来进行验证即可

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

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

相关文章

Spark RDD、DataFrame和DataSet的区别

Spark RDD、DataFrame和DataSet的区别 在比较这三者的区别之前&#xff0c;先看看他们各自的定义是什么。 Spark RDD RDD是一种弹性分布式数据集&#xff0c;是一种只读分区数据。它是spark的基础数据结构&#xff0c;具有内存计算能力、数据容错性以及数据不可修改特性。 S…

[CISCN2019 华东北赛区]Web2

[CISCN2019 华东北赛区]Web2 随便注册一个登录&#xff0c;发现 还有反馈页面&#xff0c;一看就知道大概率是xss&#xff0c;应该是为了得到管理员cookie扫描了一下&#xff0c;果然有admin.php后台登录 buu可以连接访问外网了&#xff0c;所以内部的xss平台关闭了&#xff0…

静态住宅IP好用吗?怎么选择?

在进行海外 IP 代理时&#xff0c;了解动态住宅 IP 和静态住宅 IP 的区别以及如何选择合适的类型非常重要。本文将介绍精态住宅 IP 特点和&#xff0c;并提供选择建议&#xff0c;帮助您根据需求做出明智的决策。 静态住宅 IP 的特点 静态住宅 IP 是指 IP 地址在一段时间内保…

深度理解文件操作

目录 文件 文件名&#xff1a; 标准流 文件指针 文件的打开和关闭 文件的顺序读写&#xff1a; 使用部分 文件的打开和关闭 文件 文件分两种&#xff0c;第一种是程序文件&#xff0c;后一种是数据文件。 程序文件&#xff1a;包括源程序文件&#xff08;后缀为.c&…

如何使用WordPress插件保护网站的安全

前段时间我们的网站受到了黑客的攻击&#xff0c;网站丢失了一些重要的数据&#xff0c;为了防止这种情况的再次发生&#xff0c;我们准备将网站全部迁移到高防服务器&#xff0c;经过一番对比后&#xff0c;我们选择了Hostease提供的高防服务器。它可以有效地抵御各种类型的网…

银行单元化架构体系介绍

1.背景 自2018年以来&#xff0c;受“华为、中兴事件”影响&#xff0c;我国科技受制于人的现状对国家稳定和经济发展都提出了严峻考验。目前我国IT架构体系严重依赖国外产品&#xff0c;金融行业尤其明显。大部分传统银行的关键账务系统都架设在IBM的大型机、小型机之上&…

【jenkins+cmake+svn管理c++项目】创建一个项目

工作台点击"新建item",进入下图&#xff0c;选择Freestyle project,并输入项目名称&#xff0c; 点击确定之后进入项目配置页面&#xff0c;填写描述&#xff0c;然后在下边源码管理部分选择svn, 填写代码的url 上图的Credentials处填写svn的有效登录名和密码&#x…

VMware虚拟机中的Ubuntu Samba映射Windows10文件夹

sudo apt-get install samba安装Samba mkdir share创建共享文件夹 sudo vim /etc/samba/smb.conf编辑配置文件 [share]comment VMware Ubuntu Sharepath /home/zhu/share browseable yeswritable yescreate mask 777comment 是简介 path 是共享文件夹的路径&#xff0c…

【MATLAB源码-第15期】基于matlab的MSK的理论误码率与实际误码率BER对比仿真,采用差分编码和IQ调制解调。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 在数字调制中&#xff0c;最小频移键控&#xff08;Minimum-Shift Keying&#xff0c;缩写&#xff1a;MSK&#xff09;是一种连续相位调制的频移键控方式&#xff0c;在1950年代末和1960年代产生。[1] 与偏移四相相移键控&a…

“数字化”持续走热,VR全景助力制造业上“云”

制造业要升级&#xff0c;数字化改造是重要途径。 早年间&#xff0c;由于对数字化的认识不足&#xff0c;一些企业明明有数字化改造需求&#xff0c;却不敢、不愿、不会上“云”。直到此次两会期间&#xff0c;2024年政府工作报告再次提出推动制造业数字化转型&#xff0c;越…

网络——套接字编程TCP

目录 服务端 创建套接字&#xff08;socket&#xff09; 服务端绑定&#xff08;bind&#xff09; 服务端监听&#xff08;listen&#xff09; 服务器接收&#xff08;accept&#xff09; 服务端处理&#xff08;read & write&#xff09; 客户端 创建套接字&#…

CVE-2022-33891 Apache Spark shell 命令注入漏洞分析

漏洞简介 Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行框架 Spark&#xff0c;拥有Hadoop MapReduce所具有的优点&#xff1b;但不同于MapReduce的…

鸿蒙OS开发实例:【demo选择列表限定数量】

效果图&#xff1a; 示例代码 // 使用 DevEco Studio 3.1.1 Release 及以上版本&#xff0c;API 版本为 api 9 及以上。 // 主要功能及注意事项&#xff1a; // 该组件展示了一个乘客选择列表。列表中的每个项目包含一个复选框和对应的乘客姓名&#xff0c; // 用户点击任意一…

MATLAB 自定义生成圆柱点云(49)

MATLAB 自定义生成圆柱点云(49) 一、算法介绍二、具体实现1.代码2.效果一、算法介绍 按照一些提前指定的圆柱参数,自定义生成圆柱点云,可添加噪声,用于后续的实验测试 二、具体实现 1.代码 代码如下(示例): % 指定圆柱的参数 radius = 5; % 圆柱半径 height = 20…

【Spring源码】Bean采用什么数据结构进行存储

一、前瞻 经过上篇源码阅读博客的实践&#xff0c;发现按模块阅读也能获得不少收获&#xff0c;而且能更加系统地阅读源码。 今天的阅读方式还是按模块阅读的方式&#xff0c;以下是Spring各个模块的组成。 那今天就挑Beans这个模块来阅读&#xff0c;先思考下本次阅读的阅读…

Jmeter脚本优化——随机函数

线程组下有 2 个请求的参数中均使用到相同的参数&#xff0c;在进行参数化时&#xff0c;想 要每个请求使用不同的取值。 &#xff08; 1 &#xff09; 线程组设置如下 &#xff08; 2 &#xff09; 线程组下添加加购物车请求&#xff0c;请求传参包含商品 id &#xff08;…

前端日期组件layui使用,月模式

初学前端&#xff0c;实战总结 概要 有一个日期组件&#xff0c;我的谷歌浏览器选完日期后&#xff0c;偶尔获取不到最新数据&#xff0c;有一个客户&#xff0c;是经常出不来数据。 日期组件是Wdate&#xff1a;调用的方法是WdatePicker onpicking&#xff0c;代码片段如下…

基于AT89C51单片机的智能交通灯设计

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/89035863?spm1001.2014.3001.5503 1绪 论 1.1课题研究背景 交通是城市经济活动的命脉&#xff0c;对城市经济发展、人民生活水平的提高起着十分重要的作用。城市交…

干货分享DS5L1伺服电机通过倍讯科技485转 Profinet 网关与西门子PLC进行通信的配置方法

倍讯科技485转 ProfinetDS5L1 伺服电机与 Profinet 网关进行通信需要了解 Profinet 协议和伺服电机的具体通信要求。以下是您可以如何解决此问题的总体概述&#xff1a; 了解 Profinet&#xff1a;Profinet 是自动化工业以太网标准。您需要了解 Profinet 的工作原理、其寻址方案…

纳斯达克大屏:媒体尺寸及投放费用详解

纳斯达克大屏媒体尺寸及投放费用详解 纳斯达克图片要求 像素 纳斯达克大屏媒体图片的像素要求为2336 H x 1832 W (pixels)。确保你的图片符合这一尺寸要求&#xff0c;以确保在大屏上的显示效果最佳。 分辨率 分辨率要求为(1.0) px 72 dpi。这意味着每个像素显示为一个实…