本文使用 go 版本是: go version go1.13 linux/amd64

阿里云oss go sdk版本:v2.0.4  官网可以下载

linux版本是:Linux version 3.10.0-957.21.3.el7.x86_64

各系统版本可能操作细节不一致,读者注意

目录结构如下:

项目根目录是 gooss
 
gooss的目录结构
├── conf                           # 配置文件统一存放目录
│   ├── config.yaml          # 配置文件
├── config                        # 专门用来处理配置和配置文件的Go package
│   └── config.go                 
├── handler                      # 控制输出到浏览器
│   ├── handler.go
├── logs                           # 日志记录
├── model                        # 操作模型
│   ├── aliyunoss.go         # 阿里云oss操作模型
├── pkg                            # 引用的包
│   ├── errno                    # 错误码存放位置
│   │   ├── code.go
│   │   └── errno.go
├── router                         # 路由相关处理
│   ├── middleware           # API服务器用的是Gin Web框架,Gin中间件存放位置
│   │   ├── header.go        # Gin中间件
│   └── router.go               # 路由
├── service                       # 实际业务处理函数存放位置
│   └── hotupdate.go        # 热重启服务
│   └── service.go             # 业务处理函数
├── template                     # 模板目录
│   ├── formdata               # formdata模板目录
│   │   └── form.gtpl          # form模板
├── uploads                      # 上传文件本地存储目录
├── main.go                      # Go程序唯一入口

下面,我们根据目录结构,从上往下建立文件夹和文件

建立文件夹和文件 gooss/conf/config.yaml ,config.yaml 内容如下:

common:#https://help.aliyun.com/document_detail/87712.html?spm=a2c4g.11186623.6.882.36c55837XaJBzg 阿里云oss接口aliyunoss:accessid: XXX                # 阿里云的accessidaccesskey: XXX               # 阿里云的accesskeyendpoint: XXX                # 阿里云的endpointbucket: XXX                  # 阿里云的 bucket名称uploaddir: test              # 所有上传文件都存在这个目录下面 server: #服务器配置runmode: debug               # 开发模式, debug, release, testaddr: :6668                  # HTTP绑定端口name: ossserver              # API Server的名字url: :6668   # pingServer函数请求的API服务器的ip:portmax_ping_count: 10           # pingServer函数尝试的次数

建立文件夹和文件 gooss/config/config.go,config.go 内容如下:

package configimport ("github.com/spf13/viper""time""os""log"
)// LogInfo 初始化日志配置
func LogInfo() {file := "./logs/" + time.Now().Format("2006-01-02") + ".log"logFile, _ := os.OpenFile(file,os.O_RDWR| os.O_CREATE| os.O_APPEND, 0755)log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)log.SetOutput(logFile)
}// Init 读取初始化配置文件
func Init() error {//初始化配置if err := Config();err != nil{return err}//初始化日志LogInfo()return nil
}// Config viper解析配置文件
func Config() error{viper.AddConfigPath("conf")viper.SetConfigName("config")if err := viper.ReadInConfig();err != nil{return err}return nil
}

建立文件夹和文件 gooss/handle/handle.go,handle.go 内容如下:

package handlerimport ("bytes""net/http""io/ioutil""github.com/gin-gonic/gin""gooss/pkg/errno"
)type Response struct {Code    int         `json:"code"`Message string      `json:"message"`Data    interface{} `json:"data"`
}//返回json 格式
func SendResponse(c *gin.Context,err error,data interface{}){code,message := errno.DecodeErr(err)//总是返回http状态okc.JSON(http.StatusOK,Response{Code: code,Message:message,Data: data,})}//返回html 格式
func SendResponseHtml(c *gin.Context,err error,data string){c.Header("Content-Type", "text/html; charset=utf-8")//总是返回http状态okc.String(http.StatusOK,data)
}//http请求
func HttpRequest(api string,json string,method string) (string, error) {jsonStr := []byte(json)req, err := http.NewRequest(method, api, bytes.NewBuffer(jsonStr))req.Header.Set("Content-Type", "application/json") //使用json格式传参client := &http.Client{}resp, err := client.Do(req)if err != nil {return "", errno.ApiServerError}defer resp.Body.Close()body, _ := ioutil.ReadAll(resp.Body)if !(resp.StatusCode == 200) {return "",  errno.ApiServerError}return string(body), nil
}

建立文件夹 gooss/logs 权限为777

建立文件夹和文件 gooss/model/aliyunoss.go,aliyunoss.go 内容如下:

package modelimport ("fmt""log""strings""github.com/spf13/viper""github.com/aliyun/aliyun-oss-go-sdk/oss"
)
//sdk 版本
func Aliossversion()(version string){return oss.Version
}//初始化oss服务
func Initserver()(client *oss.Client,err error){// Endpoint以杭州为例,其它Region请按实际情况填写。endpoint := viper.GetString("common.aliyunoss.endpoint")// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。accessKeyId := viper.GetString("common.aliyunoss.accessid")accessKeySecret := viper.GetString("common.aliyunoss.accesskey")// 创建OSSClient实例。client, err = oss.New(endpoint, accessKeyId, accessKeySecret)if err != nil{return}return
}//获取文件列表
func GetFilelist()(list []string,err error){list = make([]string,100)client,err := Initserver()// 获取存储空间。bucketName := viper.GetString("common.aliyunoss.bucket")bucket, err := client.Bucket(bucketName)if err != nil {return list,err}// 列举文件。marker := ""for {lsRes, err := bucket.ListObjects(oss.Marker(marker))if err != nil {return list,err}// 打印列举文件,默认情况下一次返回100条记录。for _, object := range lsRes.Objects {log.Printf("object.Key:%v\n",object.Key)list = append(list,object.Key)}if lsRes.IsTruncated {marker = lsRes.NextMarker} else {break}}return list,err
}//上传文件
func UploadFile(localfile string,uploadfile string)(resultfile string,err error){resultfile = ""// 创建OSSClient实例。client,err := Initserver()bucketName := viper.GetString("common.aliyunoss.bucket")// <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。uploaddir := viper.GetString("common.aliyunoss.uploaddir")uploadfile = strings.Trim(uploadfile,"/")objectName := fmt.Sprintf("%s/%s",uploaddir,uploadfile) //完整的oss路径// <yourLocalFileName>由本地文件路径加文件名包括后缀组成,例如/users/local/myfile.txt。localFileName := localfile// 获取存储空间。bucket, err := client.Bucket(bucketName)if err != nil {return}// 上传文件。err = bucket.PutObjectFromFile(objectName, localFileName)if err != nil {return}resultfile = objectNamereturn
}

建立文件夹和文件 gooss/pkg/errno/code.go,code.go 内容如下:

package errnovar (// Common errorsOK                  = &Errno{Code: 0, Message: "OK"}VALUEERROR        = &Errno{Code: -1, Message: "输入错误"}InternalServerError = &Errno{Code: 10001, Message: "服务器错误"}ApiServerError = &Errno{Code: 20001, Message: "接口服务器错误"}ModelError = &Errno{Code: 30001, Message: "阿里云oss连接错误"}
)

建立文件夹和文件 gooss/pkg/errno/errno.go,errno.go 内容如下:

package errnoimport "fmt"type Errno struct {Code intMessage string
}//返回错误信息
func (err Errno) Error() string{return err.Message
}//设置 Err 结构体
type Err struct {Code intMessage stringErr error
}//声明构造体
func New(errno *Errno,err error) *Err{return &Err{Code:errno.Code,Message:errno.Message,Err:err}
}//添加错误信息
func (err *Err) Add(message string) error{err.Message += " " + messagereturn err
}//添加指定格式的错误信息
func (err * Err) Addf(format string,args...interface{}) error{err.Message += " " + fmt.Sprintf(format,args...)return err
}//拼接错误信息字符串
func (err *Err) Error() string{return fmt.Sprintf("Err - code: %d, message: %s, error: %s",err.Code,err.Message,err.Err)
}// 解析 错误信息, 返回字符串
func DecodeErr(err error) (int,string){if err == nil{return OK.Code,OK.Message}switch typed := err.(type) {case *Err:return typed.Code,typed.Messagecase *Errno:return typed.Code,typed.Messagedefault:}return InternalServerError.Code,err.Error()
}

建立文件夹和文件 gooss/router/middleware/header.go,header.go 内容如下:

package middlewareimport ("net/http""time""github.com/gin-gonic/gin"
)//无缓存头部中间件 ,
//要来防止客户端获取已经缓存的响应信息
func NoCache(c *gin.Context){c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value")c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat))c.Next()
}//选项中间件
//要来给预请求 终止并退出中间件 ,链接并结束请求
func Options(c *gin.Context){if c.Request.Method != "OPTIONS"{c.Next()}else{c.Header("Access-Control-Allow-Origin","*")c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")c.Header("Content-Type", "application/json")c.AbortWithStatus(200)}
}//安全中间件
//要来保障数据安全的头部
func Secure(c *gin.Context){c.Header("Access-Control-Allow-Origin", "*")c.Header("X-Frame-Options", "DENY")c.Header("X-Content-Type-Options", "nosniff")c.Header("X-XSS-Protection", "1; mode=block")if c.Request.TLS != nil {c.Header("Strict-Transport-Security", "max-age=31536000")}//也可以考虑添加一个安全代理的头部//c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com")
}

建立文件夹和文件 gooss/router/router.go,router.go 内容如下:

package routerimport ("net/http""github.com/gin-gonic/gin""gooss/service""gooss/router/middleware"
)//初始化路由
func InitRouter(g *gin.Engine){middlewares := []gin.HandlerFunc{}//中间件g.Use(gin.Recovery())g.Use(middleware.NoCache)g.Use(middleware.Options)g.Use(middleware.Secure)g.Use(middlewares...)//404处理g.NoRoute(func(c *gin.Context){c.String(http.StatusNotFound,"该路径不存在")})g.LoadHTMLGlob("template/**/*") //加载模板路径//健康检查中间件g.GET("/",service.Index)//主页g.GET("/gooss",service.Gooss)//oss信息g.GET("/ossupload",service.OssUpload)//上传ossg.POST("/ossupload",service.OssUpload)//上传oss
}

建立文件夹和文件 gooss/service/hotupdate.go,hotupdate.go 内容如下:

package serviceimport ("context""errors""flag""log""net""net/http""os""os/exec""os/signal""syscall""time"
)/************************** 热重启 ***************************/var (listener net.Listener = nilgraceful =  flag.Bool("graceful", false, "listen on fd open 3 (internal use only)")
)//监听服务器
func Listenserver(server *http.Server){var err error//解析参数flag.Parse()//设置监听的对象(新建或已存在的socket描述符)if *graceful {//子进程监听父进程传递的 socket描述符log.Println("listening on the existing file descriptor 3")//子进程的 0 1 2 是预留给 标准输入 标准输出 错误输出//因此传递的socket 描述符应该放在子进程的 3f := os.NewFile(3,"")listener,err = net.FileListener(f)log.Printf( "graceful-reborn  %v %v  %#v \n", f.Fd(), f.Name(), listener)}else{//启动守护进程daemonProcce(1,1);//父进程监听新建的 socket 描述符log.Println("listening on a new file descriptor")listener,err = net.Listen("tcp",server.Addr)log.Printf("Actual pid is %d\n", syscall.Getpid())}if err != nil{log.Fatalf("listener error: %v\n",err)}go func(){err = server.Serve(listener) //http//err = server.ServeTLS(listener,pemPath,keyPath) //httpslog.Printf("server.Serve err: %v\n",err)tcp,_ := listener.(*net.TCPListener)fd,_ := tcp.File()log.Printf( "first-boot  %v %v %#v \n ", fd.Fd(),fd.Name(), listener)}()//监听信号handleSignal(server)log.Println("signal end")
}//处理信号
func handleSignal(server *http.Server){//把信号 赋值给 通道ch := make(chan os.Signal, 1)//监听信号signal.Notify(ch, syscall.SIGINT,syscall.SIGTERM,syscall.SIGUSR2)//阻塞主进程, 不停的监听系统信号for{//通道 赋值给 sigsig := <-chlog.Printf("signal receive: %v\n", sig)ctx,_ := context.WithTimeout(context.Background(),20*time.Second)switch sig{case syscall.SIGINT,syscall.SIGTERM:  //终止进程执行log.Println("shutdown")signal.Stop(ch)       //停止通道server.Shutdown(ctx)  //关闭服务器窗口log.Println("graceful shutdown")returncase syscall.SIGUSR2:  //进程热重启log.Println("reload")err := reload()  //执行热重启if err != nil{log.Fatalf("listener error: %v\n",err)}//server.Shutdown(ctx)log.Println("graceful reload")return}}
}//热重启
func reload() error{tl, ok := listener.(*net.TCPListener)if !ok {return errors.New("listener is not tcp listener")}//获取socket描述符currentFD, err := tl.File()if err != nil {return err}//设置传递给子进程的参数(包含 socket描述符)args := []string{"-graceful"}//args = append(args, "-continue")cmd := exec.Command(os.Args[0],args...)cmd.Stdout = os.Stdout  //标准输出cmd.Stderr = os.Stderr  //错误输出cmd.ExtraFiles = []*os.File{currentFD} //文件描述符err = cmd.Start()log.Printf("forked new pid %v: \n",cmd.Process.Pid)if err != nil{return err}return nil
}
/*
我们在父进程执行 cmd.ExtraFiles = []*os.File{f} 来传递 socket 描述符给子进程,子进程通过执行 f := os.NewFile(3, "") 来获取该描述符。值得注意的是,子进程的 0 、1 和 2 分别预留给标准输入、标准输出和错误输出,所以父进程传递的 socket 描述符在子进程的顺序是从 3 开始。
*///nochdir 是 程序初始路径 1是当前路径,0是系统根目录
//noclose 是 错误信息输出 1是输出当前, 0是不显示错误信息
func daemonProcce(nochdir, noclose int) (int,error){// already a daemonlog.Printf("syscall.Getppid() %+v\n",syscall.Getppid())//如果是守护进程 syscall.Getppid() = 1if syscall.Getppid() == 1 {/* Change the file mode mask */syscall.Umask(0)if nochdir == 0 {os.Chdir("/")}return 0, nil}files := make([]*os.File, 3, 6)if noclose == 0 {nullDev, err := os.OpenFile("/dev/null", 0, 0)if err != nil {return 1, err}files[0], files[1], files[2] = nullDev, nullDev, nullDev} else {files[0], files[1], files[2] = os.Stdin, os.Stdout, os.Stderr}dir, _ := os.Getwd()sysattrs := syscall.SysProcAttr{Setsid: true}attrs := os.ProcAttr{Dir: dir, Env: os.Environ(), Files: files, Sys: &sysattrs}proc, err := os.StartProcess(os.Args[0], os.Args, &attrs)if err != nil {return -1, err}proc.Release()os.Exit(0)return 0, nil
}

建立文件夹和文件 gooss/service/service.go,service.go 内容如下:

package serviceimport ("io""log""fmt""time""strconv""net/http""crypto/md5""github.com/gin-gonic/gin". "gooss/handler""gooss/model"
)//首页
func Index(c *gin.Context){html := `
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>hello world</title>
</head>
<body>hello world
</body>
</html>
`SendResponseHtml(c,nil,html)
}//oss信息
func Gooss(c *gin.Context){var results stringresults = model.Aliossversion()log.Printf("results: %s\n",results)var list []stringlist,err := model.GetFilelist()if err != nil{log.Printf("model.Getlist err: %v\n",err)}log.Printf("文件列表: %v\n",list)SendResponse(c,nil,results)
}func OssUpload(c *gin.Context){log.Printf("method: ",c.Request.Method) //请求方法if c.Request.Method == "GET"{curtime := time.Now().Unix()h := md5.New()io.WriteString(h,strconv.FormatInt(curtime,10))token := fmt.Sprintf("%x",h.Sum(nil)) //生成token//设置cookieexpiration := 60//SetCookie/*第一个参数为 cookie 名;第二个参数为 cookie 值;第三个参数为 cookie 有效时长/秒,当 cookie 存在的时间超过设定时间时,cookie 就会失效,它就不再是我们有效的 cookie;第四个参数为 cookie 所在的目录;第五个为所在域,表示我们的 cookie 作用范围;第六个表示是否只能通过 https 访问;第七个表示 cookie 是否可以通过 js代码进行操作。*/c.SetCookie("go-cookie","go-cookies", expiration, "/", "", false, true)c.HTML(http.StatusOK, "form.gtpl", gin.H{"token":token,})}else{//读取cookiecookie, _ := c.Cookie("go-cookie")SendResponse(c,nil,cookie)//防止多次重复提交表单//解决方案是在表单中添加一个带有唯一值的隐藏字段。// 在验证表单时,先检查带有该唯一值的表单是否已经递交过了。// 如果是,拒绝再次递交;如果不是,则处理表单进行逻辑处理。res1 := verifyToken(c)if !res1 {return}//上传文件header, err := c.FormFile("files")if err != nil {//ignoreSendResponse(c,nil,"上传失败")return}localfile := "./uploads/"+header.Filename //本地文件路径// gin 简单做了封装,拷贝了文件流if err := c.SaveUploadedFile(header, localfile); err != nil {// ignoreSendResponse(c,nil,"本地上传失败")return}SendResponse(c,nil,"本地上传成功")//上传到阿里云ossyunfiletmp := "uploads/"+header.Filenameyunfile,err := model.UploadFile(localfile,yunfiletmp)if err != nil{// ignoreSendResponse(c,nil,"阿里云上传失败")return}SendResponse(c,nil,"阿里云上传成功")SendResponse(c,nil,"阿里云路径"+yunfile)}
}//防止多次重复提交表单
func verifyToken(c *gin.Context) bool{token := c.PostForm("token")SendResponse(c,nil,token)if token != ""{// 验证 token 的合法性if len(token) <10{SendResponse(c,nil,"token验证失败")return false}}else{//不存在token 报错SendResponse(c,nil,"token验证失败")return false}SendResponse(c,nil,"token验证通过")return true
}

建立文件夹和文件 gooss/template/formdata/form.gtpl,form.gtpl 内容如下:

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>oss上传文件</title>
</head>
<body>
<form action="/ossupload" method="post" enctype="multipart/form-data">文件:<input type="file" name="files"><input type="hidden" name="token" value="{{ .token }}"><input type="submit" value="提交">
</form>
</body>
</html>

建立文件夹 gooss/uploads 权限为777

建立文件 gooss/main.go,main.go 内容如下:

package mainimport ("log""net/http""github.com/gin-gonic/gin""github.com/spf13/viper""gooss/config""gooss/router""gooss/service"
)func main() {if err := config.Init();err != nil{panic(err)}//设置gin模式gin.SetMode(viper.GetString("common.server.runmode"))//创建一个gin引擎g := gin.New()router.InitRouter(g)log.Printf("开始监听服务器地址: %s\n", viper.GetString("common.server.url"))//使用热重启// kill -USR2 pid 重启// kill -INT pid 关闭add := viper.GetString("common.server.addr")srv := &http.Server{Addr:    add,Handler: g,}log.Printf( "srv.Addr  %v  \n", srv.Addr)service.Listenserver(srv)}

初始化模块

[root@izj6c4jirdug8kh3uo6rdez gooss]# go mod init gooss
go: creating new go.mod: module gooss

打包并运行服务

# 打包到本地
[root@izj6c4jirdug8kh3uo6rdez gooss]# go build# 运行
[root@izj6c4jirdug8kh3uo6rdez gooss]# ./gooss
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.- using env:  export GIN_MODE=release- using code:   gin.SetMode(gin.ReleaseMode)[GIN-debug] Loaded HTML Templates (2): - - form.gtpl[GIN-debug] GET    /                         --> gooss/service.Index (5 handlers)
[GIN-debug] GET    /gooss                    --> gooss/service.Gooss (5 handlers)
[GIN-debug] GET    /ossupload                --> gooss/service.OssUpload (5 handlers)
[GIN-debug] POST   /ossupload                --> gooss/service.OssUpload (5 handlers)
[root@izj6c4jirdug8kh3uo6rdez gooss]# [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.- using env:   export GIN_MODE=release- using code:   gin.SetMode(gin.ReleaseMode)[GIN-debug] Loaded HTML Templates (2): - - form.gtpl[GIN-debug] GET    /                         --> gooss/service.Index (5 handlers)
[GIN-debug] GET    /gooss                    --> gooss/service.Gooss (5 handlers)
[GIN-debug] GET    /ossupload                --> gooss/service.OssUpload (5 handlers)
[GIN-debug] POST   /ossupload                --> gooss/service.OssUpload (5 handlers)

浏览器访问如下:

首页 http://daily886.com:6668/

阿里云oss版本页 http://daily886.com:6668/gooss

上传文件页 http://daily886.com:6668/ossupload

上传成功

访问 oss 文件 http://oss.daily886.com/test/uploads/test.gif

参考阿里云go sdk:https://help.aliyun.com/document_detail/32145.html?spm=a2c4g.11186623.6.883.68695837mcpha0

参考gin模板:https://blog.csdn.net/mayongze321/article/details/79348641

go 使用 gin 上传文件到 阿里云oss存储相关推荐

  1. 微信小程序直接上传文件到阿里云OSS组件封装

    微信小程序直接上传文件到OSS 1. 封装公共方法 在根目录utils目录新建一个upload文件夹: // utils/upload/base64.jsvar base64EncodeChars = ...

  2. java上传文件至阿里云oss工具类

    第一步:引入oss maven坐标 <dependency><groupId>com.aliyun.oss</groupId><artifactId>a ...

  3. go walk 开发window界面,上传文件到阿里云oss -- 服务器端

    前面我们完成了一个网页端的上传oss程序:https://blog.csdn.net/daily886/article/details/103366145 现在我们把前后端分离 前端使用walk开发, ...

  4. 上传文件到阿里云OSS对象存储,查询访问地址,删除文件

    一:pom添加以来jar <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun ...

  5. 【微信小程序】上传文件到阿里云OSS

    小程序上传文件到OSS也是利用OSS提供的PostObject接口来实现表单文件上传到OSS 步骤1:配置Bucket跨域访问 客户端进行表单直传到OSS时,会从浏览器向OSS发送带有Origin的请 ...

  6. 小程序上传文件到阿里云oss

    本文仅举例上传图片和上传视频! 准备工作: 需要引入的js文件:https://download.csdn.net/download/impossible1994727/12328614 参考文档:h ...

  7. Erlang上传文件至阿里云OSS

    忙了快一个月忘记写博客这茬了,正好今天阿里云工作人员又给我打电话我才想起我还买过一个OSS资源包,今天写一个如何调用Erlang代码来将文件资源上传至OSS对象存储中. 前言 OSS对象存储的服务不必 ...

  8. vue 上传文件至阿里云oss

    先让后端在阿里云里面处理跨域问题 参考这篇文章   vue直传图片到阿里云OSS(单张直接上传)__小郑有点困了的博客-CSDN博客_vue 阿里云背景:近期项目使用到多图片上传功能,常规的调用后端接 ...

  9. 上传文件到阿里云OSS

    最近项目中有文件上传的功能,才发现阿里云oss真是个好东西. 在其中做了好多的权限设置,角色.子账户.bucket等等. web端进行文件上传有多种方式 一.无需临时授权(安全性较低) (一).拿到权 ...

最新文章

  1. 如何轻松阅读 GitHub 上的项目源码?
  2. 关于fastlane自动化打包
  3. Javascript生成全局唯一标识符(GUID,UUID)的方法
  4. python能做软件开发吗-python代码能做成软件吗
  5. c++ h cpp文件如何关联_C++核心准则SF.5: .cpp文件必须包含定义它接口的.h文件
  6. app canvas渲染后图片黑色_H5 基于 canvas 实现电子签名并生成PDF文档
  7. SQL Server 插入数据报IDENTITY_INSERT设置为off
  8. .NET的RedisProvider
  9. Windows 环境下adb.exe无法启动的解决办法之一
  10. PDF Expert 坚果云 强强联合 优惠来袭
  11. 傻瓜看完都可以简单使用Git
  12. 413.等差数列划分(力扣leetcode) 博主可答疑该问题
  13. win10 计算机显示英文,电脑win10系统改了中文之后为何显示还是英文?
  14. 海思3518E开发笔记6.1——RTSP实时图传源码分析
  15. idea项目工具窗口
  16. 机房收费系统可行性研究报告
  17. 关键字const有什么含意?
  18. 龙芯Fedora21平台制作feodra21-loongson-app docker镜像
  19. Ubuntu一次更改用户组后,qv2ray不能运行的修复
  20. PEiD0.95 - 经典查壳工具

热门文章

  1. unity录音获取真实音频大小并获取字节流保存录音文件
  2. 传统经典CV算法_高斯核函数介绍
  3. aerospike入门
  4. telegram不小心泄露号码
  5. p77 Python 开发-批量 FofaSRC 提取POC 验证
  6. 搜索引擎中输入检索词到返回十条结果,发生了哪些事情
  7. 交互与前端14 Tabulator 表格实践2
  8. maven中央仓库,其他公共库
  9. 财务管理系统|基于Springboot开发实现公司财务管理系统
  10. 解决:jssip中接通后 PC没有声音但是话机有声音