{{ range $i,$value:=.contents }}
{{ if $value.WORD }}
{{ $value.WORD }}
{{end}}
{{ end }}
Tell The World What You Want To Say
这是我第一次接触和使用Gin框架和MySQL数据库,也算是第一次尝试
项目名为SayHello,模仿李辉大佬的Flask书籍中的第一个实战项目,不用Flask是因为正在入门Gin
项目的功能是一个留言板,但我做的是一个阉割版(因为最近上网课作业是真多,唉,写不完〒▽〒,时间又太少了,又想尝试一下完整的过程)
目录
1、前端设计
2、后端实现
3、编写Dockerfile文件
4、部署到阿里云服务器
项目目录:
SayHello
步骤如下:
前端内容比较简单,代码及效果如下
/static/SayHello.css
html{
background-color: aliceblue;
}
body{
background-color: aliceblue;
width: 1000px;
height: auto;
margin: auto;
}
header{
height: 100px;
font-weight: bold;
text-shadow: lightgray;
vertical-align: middle;
color: #ff7b23;
font-size: 80px;
text-align: center;
line-height: 100px;
margin: 20px auto;
background-color: papayawhip;
border-radius: 5px;
box-shadow: 3px 3px 3px lightgrey;
}
.font{
color: black;
font-weight: bold;
font-size: 20px;
display: inline;
}
#main{
background-color: papayawhip;
height: auto;
border-radius: 5px;
box-shadow: 3px 3px 3px lightgray ;
}
#head{
width: 1000px;
height: 120px;
margin: 0 auto;
}
#head-font{
font-size: 30px;
color: gray;
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
font-weight: bold;
text-align: center;
margin-top: 30px;
margin-bottom: 10px;
}
.c-checkbox {
display: none;
}
.c-checkbox:checked + .c-formContainer .c-form {
width: 30em;
}
.c-checkbox:checked + .c-formContainer .c-form__toggle {
visibility: hidden;
opacity: 0;
transform: scale(0.7);
}
.c-checkbox:checked + .c-formContainer .c-form__input,
.c-checkbox:checked + .c-formContainer .c-form__buttonLabel {
transition: 0.2s 0.1s;
visibility: visible;
opacity: 1;
transform: scale(1);
}
.c-checkbox:not(:checked) + .c-formContainer .c-form__input:required:valid ~ .c-form__toggle::before, .c-checkbox:checked + .c-formContainer .c-form__input:required:valid ~ .c-form__toggle::before {
content: 'Thank You! \1F60A';
}
.c-checkbox:not(:checked) + .c-formContainer .c-form__input:required:valid ~ .c-form__toggle {
pointer-events: none;
cursor: default;
}
.c-formContainer,
.c-form,
.c-form__toggle {
width: 10em;
height: 4em;
}
.c-formContainer {
font-weight: 700;
}
@keyframes light{
0%{
box-shadow: 0 5px 20px rgba(255, 63, 25, 0.5);
}
50%{
box-shadow: 0 5px 20px rgba(255, 240, 200, 0.5);
}
100%{
box-shadow: 0 5px 20px rgba(255, 63, 25, 0.5);
}
}
.c-form__toggle {
position: absolute;
border-radius: 6.25em;
background-color: #ffcccc;
box-shadow: 0 5px 20px rgba(255, 63, 25, 0.5);
transition: 0.2s;
}
.c-form__toggle:hover{
position: absolute;
border-radius: 6.25em;
background-color: #ffcccc;
box-shadow: 0 5px 20px rgba(255, 63, 25, 0.5);
transition: 0.2s;
animation: light 1s linear infinite;
}
.c-form{
position: absolute;
border-radius: 6.25em;
background-color:#ffffff;
transition: 0.2s;
}
.c-form {
left: 50%;
transform: translateX(-50%);
padding: 0.625em;
box-sizing: border-box;
display: flex;
justify-content: center;
}
.c-form__toggle {
color: #ff7b73;
top: 0;
cursor: pointer;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
.c-form__toggle::before {
font-size: 1.25em;
content: attr(data-title);
}
.c-form__input,
.c-form__button {
font: inherit;
border: 0;
outline: 0;
border-radius: 5em;
box-sizing: border-box;
}
.c-form__input,
.c-form__buttonLabel {
font-size: 1.2em;
opacity: 0;
visibility: hidden;
transform: scale(0.7);
transition: 0s;
}
.c-form__input {
color: #ffcccc;
}
.c-form__input::placeholder {
color: currentColor;
}
.c-form__input:required:valid {
color: #ff7b73;
}
.c-form__input:required:valid + .c-form__buttonLabel {
color: #ffffff;
}
.c-form__input:required:valid + .c-form__buttonLabel::before {
pointer-events: initial;
}
.c-form__buttonLabel {
color: #ffaea9;
height: 100%;
width: auto;
}
.c-form__buttonLabel::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
cursor: pointer;
}
.c-form__button {
color: white;
font-weight: bolder;
line-height: 100%;
padding: 0;
height: 100%;
width: 5em;
background-color: #ff7b73;
}
#input{
margin: 0 100px;
}
input{
width: 16em;
height: 100%;
border-radius: 50%;
text-align: center;
margin: 0 2em;
}
hr{
width: 800px;
color: grey;
}
#content{
margin: 0 100px;
}
li{
display: inline-block;
min-width: 500px;
height: 50px;
border: 1px solid lightgray;
border-radius: 5px;
list-style-type: none;
line-height: 50px;
margin: 5px auto;
}
.li-font{
font-size: 25px;
color: gray;
}
.button{
width: 50px;
height: 50px;
border-radius: 50%;
}
.sure{
background-color: lawngreen;
font-size: 25px;
text-align: center;
line-height: 50px;
}
.cancel{
background-color: #ff7b73;
font-size: 25px;
text-align: center;
line-height: 50px;
}
/templates/SayHello.tmpl
SayHello
Say Hello to the World
Tell The World What You Want To Say
{{ range $i,$value:=.contents }}
{{ if $value.WORD }}
{{ $value.WORD }}
{{end}}
{{ end }}
在这里我还没有下载MySQL,所以直接使用Docker push了MySQL镜像
具体操作:
C:\Users\admin>docker push mysql
The push refers to repository [docker.io/library/mysql]
797622a2c5eb: Layer already exists 750760613cac: Layer already exists 46138c79bcf0: Layer already exists 7d021d828f72: Layer already exists b0019e07d5a5: Layer already exists ace74cb61ec0: Layer already exists d84f8cf1dc23: Layer already exists 24bd91e7be37: Layer already exists 49baacc63c3b: Layer already exists 8d3b3830445d: Layer already exists 49003fe88142: Layer already exists c2adabaecedb: Layer already exists
启动一个MySQL容器
//这是Windows操作系统,数据卷挂载时挂载到D:\DockerVolume\mysql\data目录,保证目录存在
//可以用docker logs mysql 查看mysql容器启动日志
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=YourPassWord -v /d/DockerVolume/mysql/data:/var/lib/mysql mysql
//启动一个MySQL client进行交互 -u是user -p 是password
docker run -it --network host --rm mysql mysql -h127.0.0.1 -p3306 --default-character-set=utf8mb4 -uroot -p
//因为Gorm是无法帮你创建数据库的,所以需要手动创建数据库
CREATE DATABASE SayHello
use database SayHello;
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"strings"
//导入驱动文件
_ "github.com/jinzhu/gorm/dialects/mysql"
"net/http"
"strconv"
)
var db *gorm.DB
var err error
var num int
type Content struct {
ID int
WORD string `gorm:"type:varchar(20)"`
}
//定义处理函数
func index(c *gin.Context) {
var contents []Content
err=db.Find(&contents).Error
if err!=nil {
c.AbortWithStatus(404)
fmt.Println(err)
}else {
c.HTML(http.StatusOK, "SayHello", gin.H{
"contents": contents,
})
}
}
func input(c *gin.Context){
content:=c.PostForm("content")
v:=Content{num,content}
db.Create(&v)
num++
c.Redirect(http.StatusMovedPermanently,"/")
}
func del(c *gin.Context){
var v Content
arg:= c.Param("i")
i, _ :=strconv.Atoi(arg[1:])
err=db.Where("ID=?",i).Delete(&v).Error
c.Redirect(http.StatusMovedPermanently,"/")
}
//添加中间件
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method //请求方法
origin := c.Request.Header.Get("Origin") //请求头部
var headerKeys []string // 声明请求头keys
for k, _ := range c.Request.Header {
headerKeys = append(headerKeys, k)
}
headerStr := strings.Join(headerKeys, ", ")
if headerStr != "" {
headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)
} else {
headerStr = "access-control-allow-origin, access-control-allow-headers"
}
if origin != "" {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Origin", "*") // 这是允许访问所有域
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") //服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
// header的类型
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
// 允许跨域设置 可以返回其他子段
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域关键设置 让浏览器可以解析
c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒
c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true
c.Set("content-type", "application/json") // 设置返回格式是json
}
//放行所有OPTIONS方法
if method == "OPTIONS" {
c.JSON(http.StatusOK, "Options Request!")
}
// 处理请求
c.Next() // 处理请求
}
}
func main(){
//gin初始化
gin.SetMode(gin.ReleaseMode)
router:=gin.Default()
router.Use(Cors())
router.StaticFS("/static", http.Dir("./static"))
router.StaticFile("/favicon.ico", "./static/SayHello.ico")
router.LoadHTMLGlob("./templates/*")
//数据库初始化
//注意:这里的IP地址是Docker MySQL容器中的地址,docker inspect mysql查看IP
db,err=gorm.Open("mysql","root:root@tcp(172.18.0.2:3306)/SayHello?charset=utf8")
if err!=nil{
panic(err)
}
defer db.Close()
//创建表,将结构体与表单映射
db.AutoMigrate(&Content{})
//绑定路由
router.GET("/",index)
router.POST("/",input)
router.POST("/del/*i", del)
_ =router.Run(":9000")
}
这里最难受的就是Golang的环境配置了,也是因为我对GOROOT和GOPATH等没理解清楚,就是搞了好久,最后好不容易解决了,才知道go mod还是舒服啊。
FROM golang
LABEL maintainer=rong<[email protected]>
COPY . /$GOPATH/src/SayHello/
WORKDIR /$GOPATH/src/SayHello/
#设置环境变量,开启go module和设置下载代理
RUN go env -w GO111MODULE=on
RUN go env -w GOPROXY=https://goproxy.cn,direct
#会在当前目录生成一个go.mod文件用于包管理
RUN go mod init
#增加缺失的包,移除没用的包
RUN go mod tidy
RUN go build app.go
EXPOSE 9000:9000
CMD ["go","run","app.go"]
在当前文件夹下输入docker 命令就可以生成镜像
docker build -t sayhello .
首先得有一个阿里云账号和云服务器
阿里云注册地址
注册成功后,搜索“容器镜像服务”在这里可以创建自己的镜像仓库,具体操作步骤见阿里云,这里就不多叙述
我之前购买了阿里云高校学生“在家实践”计划的免费服务器,只要是学生并且答对题就可以免费领了,真的非常感谢阿里爸爸,相关内容可以百度
阿里云高校学生在家实践计划
之后使用Xshell6连接云服务器,进行下述操作:
至此,这个最简单的项目就算完成了,然后在浏览器输入云服务器IP地址+端口号就可以访问了
所有的源码均已粘贴到文章中,可以直接复制并尝试搭建。