Go Web学习笔记(Gin和Gorm)
本人在学长的推荐下,之后就要用Go开发后端啦!之前已经把go的基础语法过了一遍,现在学习Gin和Gorm框架,特此记录一下,也希望对你们有帮助,当然因为本人是Go新手,所以有写的不对的地方尽情指教,谢谢!
这里顺便再提供一下Go基础语法的中文官方文档
注:本篇文章大部分是基于https://www.topgoer.com/gin%E6%A1%86%E6%9E%B6这个网址写的学习笔记,其他一些则是去网上找的资料和视频,感谢大佬提供的观点和代码!!
Gin框架
用go原生的https包写hello world
package mainimport ("fmt""net/http" //引用http包
)func sayHello(w http.ResponseWriter, r *http.Request) {_, _ = fmt.Fprintf(w, "<h1>Hello Golang!</h1>")// b, _ := ioutil.ReadFile("./hello.txt") //ioutil这个函数能够读去文本文件// _, _ = fmt.Fprintf(w, string(b))
}func main() {http.HandleFunc("/hello", sayHello) //访问 http://localhost:8080/hello 这个urlerr := http.ListenAndServe(":8080",nil) //监听8080端口,url中可以不用写(因为默认就是8080)if err != nil{fmt.Printf("ERROR:%v\n", err)return}
}
小记: 下载Gin框架 命令行输入:go get -u github.com/gin-gonic/gin
GET请求
package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default() // 返回默认的路由引擎//指定用户使用GET请求访问/hello时,会执行sayhello这个函数r.GET("/hello", func(c *gin.Context) { //一定要是*gin.Context类型的参数c.JSON(200,gin.H{"message":"Hello golang!",})})//启动服务r.Run(":9090") //改变默认端口号,注意要加 ":" !!!//r.Run()
}
小记:Resful风格的请求:GET:获取、 POST:创建、 DELETE:删除、 PUT:更新
POSTMan安装
这个网上有很多教程,这里就不多提了,提几个重要的点吧:
1.首先这个软件是要用梯子的,包括下载和登陆注册
2.建议下载软件版
3.下载好后新建一个work space之后就可以开始操作了
小记:template框架因为要读取一个html文件,没有做到前后端分离,我个人认为比较落后,这里就先不学它了
获取参数、文件的方式
API获取
package mainimport ("github.com/gin-gonic/gin""net/http""strings"
)func main() {r := gin.Default()r.GET("/user/:name/*action", func(c *gin.Context) { //通过匿名函数的方式实现的对url的抓取name := c.Param("name") //获得url中的nameaction := c.Param("action")//截取/action = strings.Trim(action, "/") //获得url中的最后一个参数c.String(http.StatusOK, name+" is "+action)})//默认为监听8080端口r.Run(":8000")
}
URL中获取参数(Get方式)
用Query()来获得参数
用DefaultQuery()来设置默认参数
package mainimport ("fmt""net/http""github.com/gin-gonic/gin"
)func main() {r := gin.Default()r.GET("/user", func(c *gin.Context) {//指定默认值//http://localhost:8080/user 才会打印出来默认的值name := c.DefaultQuery("name", "小恐龙")c.String(http.StatusOK, fmt.Sprintf("hello %s", name))})r.Run()
}
当name没有传进get参数时,返回为默认的小恐龙:
当然也可以传进参数:
表单获取参数(一般是Post方式)
表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded或from-data格式的参数
(这里也同样没有前后端分离,不推荐)
html文件:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body><form action="http://localhost:8080/form" method="post" action="application/x-www-form-urlencoded">用户名:<input type="text" name="username" placeholder="请输入你的用户名"> <br>密 码:<input type="password" name="userpassword" placeholder="请输入你的密码"> <br><input type="submit" value="提交"></form>
</body>
</html>
go后台文件
package main//
import ("fmt""net/http""github.com/gin-gonic/gin"
)func main() {r := gin.Default()r.POST("/form", func(c *gin.Context) {types := c.DefaultPostForm("type", "post")username := c.PostForm("username")password := c.PostForm("userpassword")c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types))})r.Run()
}
上传文件(单个或多个)
html文件:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body>
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">上传文件:<input type="file" name="file" ><input type="submit" value="提交">
</form>
</body>
</html>
用FormFile()函数来获得上传的文件
SaveUploadedFile用于保存
go文件:
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()// 限制表单上传大小 8MB,默认为32MBr.MaxMultipartMemory = 8 << 20r.POST("/upload", func(c *gin.Context) {file, err := c.FormFile("file")if err != nil {c.String(500, "上传图片出错")}//c.JSON(200, gin.H{"message": file.Header.Context})c.SaveUploadedFile(file, file.Filename)c.String(http.StatusOK, file.Filename)})r.Run()
}
gin好像暂时没有限制文件大小的函数,那么我们就自己写一个,也不是很难。
就是一个要注意的点是要把原来的c.FormFile变成c.Request.FormFile。
package mainimport ("log""net/http""github.com/gin-gonic/gin"
)func main() {r := gin.Default()//上传单个文件r.POST("/upload", func(c *gin.Context) {_, headers, err := c.Request.FormFile("file")if err != nil {log.Printf("Error when try to get file: %v", err)}//headers.Size 获取文件大小if headers.Size > 1024*1024*2 { //限制2MB的大小c.String(200,"文件太大了")return//获取文件类型}else if headers.Header.Get("Content-Type") != "image/png" {c.String(200,"只允许上传png图片")return}else{//Go返回json数据的方法之一;利用map(还有一种方法是用结构体)my_json := map[string]interface{}{ "1":"成功","2":true,}c.JSON(200,my_json)}//headers.Header.Get("Content-Type")获取上传文件的类型c.SaveUploadedFile(headers, "./video/"+headers.Filename)c.String(http.StatusOK, headers.Filename)})//上传多个文件//r.POST("/upload", func(c *gin.Context) {// form, err := c.MultipartForm()// if err != nil {// c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))// }// // 获取所有图片// files := form.File["files"]// // 遍历所有图片// for _, file := range files {// // 逐个存// if err := c.SaveUploadedFile(file, file.Filename); err != nil {// c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))// return// }// }// c.String(200, fmt.Sprintf("upload ok %d files", len(files)))//})r.Run()
}
路由和路由组
路由:就是一个通过一个路径去用一种方法去请求数据
路由中不同的请求方法以及后台处理
r.any()可以对任何请求做出处理
r.NoRoute()可以自定义404页面
示例:
package mainimport ("github.com/gin-gonic/gin"
)
func main() {// 默认使用了2个中间件Logger(), Recovery()r := gin.Default()r.NoRoute(func(c *gin.Context) {c.JSON(404, gin.H{"msg": "12138"})})r.Run()
}
路由组
package mainimport ("github.com/gin-gonic/gin""fmt"
)// gin的helloWorldfunc main() {// 1.创建路由// 默认使用了2个中间件Logger(), Recovery()r := gin.Default()// 路由组1 ,处理GET请求v1 := r.Group("/v1")// {} 是书写规范{v1.GET("/login", login)v1.GET("submit", submit)//路由组的可以嵌套的//xx := v1.shopGroup("xx")//xx.GET("/oo",my_func)}v2 := r.Group("/v2"){v2.POST("/login", login)v2.POST("/submit", submit)}r.Run(":8000")
}func login(c *gin.Context) {name := c.DefaultQuery("name", "jack")c.String(200, fmt.Sprintf("hello %s\n", name))
}func submit(c *gin.Context) {name := c.DefaultQuery("name", "lily")c.String(200, fmt.Sprintf("hello %s\n", name))
}
效果图:
小记: 不同文件夹下要导入函数的话,导出的函数首字母需要大写
JSON数据的解析和绑定:
小记:gin.h{...}和map[string]interface{}{...}的效果是一样的
package mainimport ("github.com/gin-gonic/gin""net/http"
)// 定义接收数据的结构体
type Login struct {// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}func main() {// 1.创建路由// 默认使用了2个中间件Logger(), Recovery()r := gin.Default()// JSON绑定r.POST("loginJSON", func(c *gin.Context) {// 声明接收的变量var json Login //声明json为Login类型的结构体// 将request的body中的数据,自动按照json格式解析到结构体if err := c.ShouldBindJSON(&json); err != nil {//c.ShouldBindUri(...)是用于解析url中的参数的//c.Bind(...)是用于解析form中的参数的// 返回错误信息// gin.H封装了生成json数据的工具c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 判断用户名密码是否正确if json.User != "root" || json.Pssword != "admin" {c.JSON(http.StatusBadRequest, gin.H{"status": "304"})return}c.JSON(http.StatusOK, gin.H{"status": "200"})})r.Run(":8000")
}
注意: 上面结构体定义时使用了结构体tag,因为go的限制,所以结构体内名字只能返回大写开头,于是我们就可以用tag去定制结构体在不同方法请求时所传入参数名称的不同
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"
比如这句话,用form请求时就应该是username
binding:"required"就说明是必要参数,若接收为空值,则报错,是必须字段。
重定向
package mainimport ("net/http""github.com/gin-gonic/gin"
)func main() {r := gin.Default()r.GET("/index", func(c *gin.Context) {c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")})r.Run()
}
异步执行和同步执行
这里用到了go程的概念,没有了解的小伙伴可以去康康文档
package mainimport ("log""time""github.com/gin-gonic/gin"
)func main() {// 1.创建路由// 默认使用了2个中间件Logger(), Recovery()r := gin.Default()// 1.异步r.GET("/long_async", func(c *gin.Context) {// 需要搞一个副本copyContext := c.Copy()// 异步处理go func() {time.Sleep(3 * time.Second)log.Println("异步执行:" + copyContext.Request.URL.Path)}()})// 2.同步r.GET("/long_sync", func(c *gin.Context) {time.Sleep(3 * time.Second)log.Println("同步执行:" + c.Request.URL.Path)})r.Run(":8000")
}
效果:
中间件
局部中间件
用于在浏览器和服务器中间处理的函数,一般是所有网站都需要使用的函数,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
原理图类似下图:
package mainimport ("fmt""github.com/gin-gonic/gin""time"
)func hello(c *gin.Context){c.JSON(200,gin.H{"msg":"index",})
}func m1(c *gin.Context){fmt.Println("进入验证...")start := time.Now()c.Next() // 调用后续处理函数(hello) !!!important//c.Abort() //阻止调用后续的处理函数cost := time.Since(start)fmt.Printf("cost:%v\n", cost)
}func main() {r := gin.Default()r.GET("/index", m1, hello)r.Run()
}
像上面的m1就是一个简单的中间件函数,
它在hello函数之前执行,可以决定hello函数执行的逻辑。
效果图:
全局中间件
用r.use(…)
package mainimport ("fmt""github.com/gin-gonic/gin""time"
)func hello(c *gin.Context){c.JSON(200,gin.H{"msg":"index",})
}func m1(c *gin.Context){fmt.Println("进入验证...")start := time.Now()c.Next() // 调用后续处理函数(hello) !!!important//c.Abort() //阻止调用后续的处理函数cost := time.Since(start)fmt.Printf("cost:%v\n", cost)
}func main() {r := gin.Default()//只要请求就要通过m1这个中间件(其实就是一个函数)r.Use(m1)r.GET("/index", hello)r.Run()
}
好用的中间件推荐
网址
会话控制:(cookie和session)
搜索、设置、删除cookie:
package mainimport ("fmt""github.com/gin-gonic/gin"
)func main() {// 1.创建路由// 默认使用了2个中间件Logger(), Recovery()r := gin.Default()// 服务端要给客户端cookier.GET("cookie", func(c *gin.Context) {// 获取客户端是否携带cookiecookie, err := c.Cookie("key_cookie")if err != nil {cookie = "NotSet"// 给客户端设置cookie// maxAge int, 单位为秒// path,cookie所在目录// domain string,域名// secure 是否智能通过https访问// httpOnly bool 是否允许别人通过js获取自己的cookiec.SetCookie("key_cookie", "value_cookie", 60, "/","localhost", false, true)//删除cookie的话就可以直接设置时长为0或者为空//c.SetCookie("key_cookie", "", 60, "/","localhost", false, true)//c.SetCookie("key_cookie", "value_cookie", 0, "/","localhost", false, true)}fmt.Printf("cookie的值是: %s\n", cookie)})r.Run(":8000")
}
请求:http://localhost:8000/cookie 后查看cookie
小记:Cookie的缺点:1.不安全,明文、2.增加带宽消耗、3.可以被禁用、4.cookie有上限
session的设置、查找和删除(值设为nil)
package mainimport ("github.com/gin-contrib/sessions""github.com/gin-contrib/sessions/cookie""github.com/gin-gonic/gin"
)func main() {// 初始化一个cookie存储对象// something-very-secret应该是一个你自己的密匙,只要不被别人知道就行var store = cookie.NewStore([]byte("something-very-secret"))r := gin.Default()//使用中间件,store是之前创建的存储引擎,可以替换成其他引擎//mysession是之后会存储到浏览器上cookie中的名字,服务器通过这个名字去找到对应的sessionr.Use(sessions.Sessions("mysession", store))r.GET("/save", func(c *gin.Context) {session := sessions.Default(c)v := session.Get("count")var count intif v == nil {count = 0}else {count = v.(int)count++}session.Set("count",count)session.Save()c.JSON(200, gin.H{"now the count":count})})r.GET("/get", func(c *gin.Context) {session := sessions.Default(c)v := session.Get("count")c.JSON(200, gin.H{"the count":v})})err := r.Run()if err != nil {return}
}
效果图:
gin中是没有session包的,所以需要我们自己去导入其他的session包
参数验证
结构体验证:
在定义结构体的对象的后面写上要求
package mainimport ("fmt""time""github.com/gin-gonic/gin"
)//Person ..
type Person struct {//不能为空并且大于10Age int `form:"age" binding:"required,gt=10"`Name string `form:"name" binding:"required"`Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}func main() {r := gin.Default()r.GET("/5lmh", func(c *gin.Context) {var person Personif err := c.ShouldBind(&person); err != nil {c.String(500, fmt.Sprint(err))return}c.String(200, fmt.Sprintf("%#v", person))})r.Run()
}
自定义函数验证
package mainimport ("fmt""github.com/go-playground/validator/v10"
)type Users struct {Name string `form:"name" json:"name" validate:"required,CustomValidationErrors"`//包含自定义函数Age uint8 `form:"age" json:"age" validate:"required,gt=18"`Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`Code string `form:"code" json:"code" validate:"required,len=6"`
}func main() {// 测试传入的数据users := &Users{Name: "admin",Age: 121,Passwd: "126783",Code: "123456",}validate := validator.New()//注册自定义函数_=validate.RegisterValidation("CustomValidationErrors", CustomValidationErrors)err := validate.Struct(users)if err != nil {for _, err := range err.(validator.ValidationErrors) {fmt.Println(err)//Key: 'Users.Name' Error:Field validation for 'Name' failed on the 'CustomValidationErrors' tagreturn}}return
}func CustomValidationErrors(fl validator.FieldLevel) bool {return fl.Field().String() != "admin"
}
CustomValidationErrors是自己定义的函数,可以自定义过滤参数
注:一定要看清楚结构体后面的tag加的是validate而不是binding,这个是因为validator版本的问题导致的,这个问题搞了我半天,,一定要看清楚validator的版本是v10的,v9、v8都可能出问题
这里再分享一个大佬对这个包使用方法发总结:网址
其他常用功能
记录日志
package mainimport ("io""os""github.com/gin-gonic/gin"
)func main() {gin.DisableConsoleColor()// Logging to a file.f, _ := os.Create("gin.log")gin.DefaultWriter = io.MultiWriter(f)// 如果需要同时将日志写入文件和控制台,请使用以下代码。// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)r := gin.Default()r.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})r.Run()
}
效果图:
验证码
///正在更新ing
Gorm框架
小记:英文Go Object(对象) Relational(关系) Mapping(映射)
Gorm的安装
命令行输入:
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
基本操作
这里以mysql为例
package mainimport ("fmt""github.com/jinzhu/gorm"_ "github.com/jinzhu/gorm/dialects/mysql"
)type Gorm_test struct {ID int //注意:如果定义了ID,则会被默认为主键Name stringGender stringHobby string
}func main() {mysql_conn := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", "root", "1234", "127.0.0.1", 3306, "mydatabase")//赋值给全局变量 Db 没有 := 就是赋值db, err := gorm.Open("mysql", mysql_conn)if err != nil {panic(err)}fmt.Println("初始化数据库成功......")//创建表,把结构体和数据表关联起来db.AutoMigrate(&Gorm_test{})//创建数据行u1 := Gorm_test{1, "小恐龙", "男", "钢琴"}db.Create(&u1) //这边建议加上&fmt.Println("添加数据成功")//查询var u Gorm_test//用First函数查询第一个db.First(&u) //查询第一个,把它赋值给u 注意,要修改结构体一定要传指针!fmt.Printf("u:%#v\n", u)//更新数据db.Model(&u).Update("hobby", "打击乐")fmt.Println("更改数据成功")//删除db.Delete(&u)fmt.Println("删除数据成功")}
效果图:
小记:gorm在我们关联结构体时会创建一个数据库,名字就算把我们结构体的名字小写并且后面加上s
查询plus
package mainimport ("fmt""github.com/jinzhu/gorm"_ "github.com/jinzhu/gorm/dialects/mysql"
)type Gorm_test struct {ID int //注意:如果定义了ID,则会被默认为主键Name stringGender stringHobby string
}func main() {mysql_conn := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", "root", "1234", "127.0.0.1", 3306, "mydatabase")//赋值给全局变量 Db 没有 := 就是赋值db, err := gorm.Open("mysql", mysql_conn)if err != nil {panic(err)}fmt.Println("初始化数据库成功......")//创建表,把结构体和数据表关联起来db.AutoMigrate(&Gorm_test{})var test Gorm_testdb.First(&test, 4) //根据主键(当前为ID),如果为空则返回:{0 } 只能查出符号条件的第一条fmt.Println(test)var test1 []Gorm_testdb.Where("name = ?", "zlz").Find(&test1) //查询所有符合的记录并保存在test1数组中 海曙注意一定要加&!!!fmt.Println(test1)
}
小记:当查询不出来数据时,可以在结构体后面指定字段名,类似: ID int `gorm:"column:ID"
其他Go的知识
实时加载工具Air
这里我直接引用大佬的文章吧:网址
Go包管理工具Go Mod
具体操作可以看看这个:网址
注意的点:
需要主文件夹下面有main.go
每次创建新项目后,可以命令行输入:
go mod init 你的文件夹名
go mod tidy
之后就能自动帮你下载包啦
云服务器liunx宝塔面板配置Go环境
分享大佬的网址:网址
Go的项目结构与导包
一般我们是利用Go mod来管理包
一个项目一般只有一个go.mod
比如说我的项目叫sisipai,那么首先建一个大文件夹,下面建立两个小文件夹叫article和quku,还有一个main.go作为项目的主要文件
在article和quku中也分别有一个main.go的文件
里面第一行分别是
//注意要首字母大写
package Article //表明是Article的这个包
//和
package Quku //表明是Quku的这个包
注意:在article和quku中不能由.idea或者go.mod、go.sum这种东西出现(一个项目一般只有一个go.mod)
再切回sisipai这个主文件夹,写
package main
//这时候就可以import了
import ("sisipai/Article""sisipai/Qupu"
)
如果出现报错,不要慌,在sisipai文件夹命令行下输入:
go mod init sispai
go mod tidy
就可以自己帮你找包啦!
成功运行
Go项目放在服务端运行
如果服务端是Linux,想直接放文件上去运行的话可以在本地先编译好Linux所可以运行的文件,再直接放到服务器上去跑。
方法:依次在命令行输入:
set GOARCH=amd64
set GOOS=linux
go build .
接着就会生成一个和你项目名称相同的没有后缀名的文件,这个就算linux可以执行的文件。
把它拖到你的服务器上,然后给个运行权限:
chmod +x [你的项目名称]
就可以加上"./"运行啦!
端口问题
很多新手小白(比如说我)在一开始并不知道如何让自己的go文件在服务端跑起来之后访问它,那么今天我们来解决一下这个问题。
首先你端口要是这样的:(如果你要在8080端口下运行的话)
_ = r.Run("0.0.0.0:8080“)
然后别忘了把防火墙给开启了,放行8080端口
接下去就好啦!你可以试试用curl 127.0.0.1:8080端口测试,也可以通过你服务器的公网IP访问试试。
Go语言设置跨域访问
网址
通过使用cors的中间件改变header来实现跨域,一般要给域名加上https
等待完善ing…
Go Web学习笔记(Gin和Gorm)相关推荐
- 2019年Java Web学习笔记目录
Java Web学习笔记目录 1.Java Web学习笔记01:动态网站初体验 2.Java Web学习笔记02:在Intellij里创建Web项目 3.Java Web学习笔记03:JSP元素 4. ...
- web学习笔记-html-html新增
CCS学习系列笔记 web学习笔记–css(1) web学习笔记–css(2) web学习笔记–css(3) web学习笔记-html-html新增 1.html基本发展 2.h5新增的功能 3.新增 ...
- 【慕课网】Web学习笔记———CSS3 (一)
[Web学习笔记]CSS3 (一) CSS3代码语法 CSS注释代码 CSS样式 内联式css样式 嵌入式css样式 外部式css样式 权值 CSS3选择器 标签选择器 类选择器 ID选择器 类与ID ...
- java web学习笔记(持续更新)
java web学习笔记 一.Java Web简介 二.认识Servlet 1.什么是Servlet? 2.请求路径 3.tomcat 4.Servlet的使用 三.Servlet简单应用 1.创建S ...
- [原创]java WEB学习笔记02:javaWeb开发的目录结构
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- [原创]java WEB学习笔记48:其他的Servlet 监听器:域对象中属性的变更的事件监听器 (3 个),感知 Session 绑定的事件监听器(2个)...
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- [原创]java WEB学习笔记58:Struts2学习之路---Result 详解 type属性,通配符映射
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- [原创]java WEB学习笔记35:java WEB 中关于绝对路径 和相对路径问题
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- 移动web——学习笔记整理
目录 Day1 00.流式布局 00.1目标 00.2目录 01.移动端浏览器 02.视口(layout viewport) 03.meta视口标签(单标签) 04.物理像素&物理像素比 05 ...
最新文章
- (原)调用jpeglib对图像进行压缩
- java图片上传下载_java web 文件上传与下载
- 代码管理 ,git 命令整理
- 64位的Mac OS X也有Windows.Forms了
- 山寨威武 仿冒Xoom先于行货获得Android 4.0升级
- 事业单位计算机知识c语言,事业单位考试计算机基础知识C语言程序设计
- unity3d android hdr,Unity3d 中的 HDR_BLOOM
- 想要定位其中的iframe并切进去的定位方法
- Hexo博文加密思路总结
- 数组中大于等于左侧所有数,小于等于右侧所有数的数
- a 机械硬盘损坏,如何恢复数据
- 简单的 Nodejs jade 实现Hello world
- 使用Robotframework-ride,导入Selenium2Library库后缺少“Open Browser”关键字
- 刘卫国python语言程序设计实验题答案_Python语言程序设计-中国大学mooc-试题题目及答案...
- php学好要多久,零基础php自学要多久
- 全网最全之接口测试【加密解密攻防完整版】实战教程详解
- 飞腾CPU 麒麟系统 安装docker
- 魔兽争霸3在win10中调节亮度的办法
- R语言 数据正态化+标准化
- 从excel中读取信号,首先计算信号的vmd分解,得到imf分量