通用方法模块的设计
- 通用的方法可以定义在一个模块里,以方便调用,无需重复造轮子
- 一般可以设计一个顶层的 models 包来承载公共方法
models 包
package models
import (
"crypto/md5"
"encoding/hex"
"math/rand"
"time"
"fmt"
"io"
)
// 通用时间模板定义
var goTime = "2006-01-02 15:04:05"
var shortGoTime = "2006-01-02"
var md5Salt = GenerateSalt(16) // 加密时用的盐
// 时间戳转换成日期
func UnixToTime(timestamp int) string {
t := time.Unix(int64(timestamp), 0)
return t.Format(goTime)
}
// 日期转换成时间戳
func DateToUnix(str string) int64 {
t, err := time.ParseInLocation(goTime, str, time.Local)
if err != nil {
return 0
}
return t.Unix()
}
// 获取时间戳
func GetUnix() int64 {
return time.Now().Unix()
}
// 获取当前的日期
func GetDate() string {
return time.Now().Format(goTime)
}
// 获取年月日
func GetDateShort() string {
return time.Now().Format(shortGoTime)
}
// GenerateSalt 生成一个指定长度的随机盐
func GenerateSalt(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}
// MD5Hash 计算并返回给定字符串的 MD5 哈希值
func Md5Hash(text string) string {
hasher := md5.New()
hasher.Write([]byte(text + md5Salt))
md5Bytes := hasher.Sum(nil)
return hex.EncodeToString(md5Bytes)
}
// 第二种加密方式,和上述结果一致
func Md5Hash2(text string) string {
md5str := fmt.Sprintf("%x", md5.Sum([]byte(text + md5Salt)))
return md5str
}
// 第三种加密方法
func Md5Hash3(text string) string {
hasher := md5.New()
io.WriteString(hasher, text + md5Salt)
md5str := fmt.Sprintf("%x", hasher.Sum(nil))
return md5str
}
// VerifyMD5Hash 验证给定的明文密码是否与存储的哈希值匹配
func VerifyMD5Hash(plainPassword, storedHash string) bool {
return storedHash == Md5Hash(plainPassword)
}
- 上述的方法,可以写在程序里,通过models来调用,也可配置全局,在模板里调用
main.go 主程序
package main
import (
"gin-demo/routers" // gin-demo 是 go mod init 初始化的工程,下同
"github.com/gin-gonic/gin"
"html/template"
"gin-demo/models"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 自定义模板函数 注意要把这个函数放在加载模板前 全局配置
r.SetFuncMap(template.FuncMap{
"UnixToTime": models.UnixToTime,
"DateToUnix": models.DateToUnix,
"GetUnix": models.GetUnix,
"GetDate": models.GetDate,
"GetDateShort": models.GetDateShort,
})
// 加载模板 放在配置路由前面
r.LoadHTMLGlob("tpls/**/*")
routers.WebRoutersInit(r)
routers.ApiRoutersInit(r)
routers.AdminRoutersInit(r)
r.Run()
}
web首页控制器
package web
import (
"gin-demo/models"
"net/http"
"github.com/gin-gonic/gin"
)
type WebCtrl struct{}
func (con WebCtrl) Index(c *gin.Context) {
// 验证加密和验证
originStr := "dsdssdsdsfdsdsfss" // 原字符
md5HashStr := models.Md5Hash(originStr) // 加密后的字符串
checkMd5Result := models.VerifyMD5Hash(originStr, md5HashStr) // 加密后的验证
c.HTML(http.StatusOK, "web/index.html", gin.H{
"msg": "我是一个msg",
"t": 1760025600,
"d": "2025-10-10 00:00:00",
"originStr": originStr, // 原字符串
"md5HashStr": md5HashStr, // md5 加密后的字符串
"checkMd5Result": checkMd5Result, // 验证是否匹配
"testMd5_2": models.Md5Hash2(originStr), // 第二种加密结果
"testMd5_3": models.Md5Hash3(originStr), // 第三种加密结果
})
}
web首页模板 tpls/web/index.html
{{ define "web/index.html" }}
<h3>web index 页面</h3>
{{.msg}}
<h3> UnixToTime 时间处理</h3>
{{ UnixToTime .t }} <br />
{{ .t | UnixToTime }}
<h3> DateToUnix 时间处理</h3>
{{ .d | DateToUnix }} <br />
{{ DateToUnix .d }}
<h3> GetUnix 时间处理</h3>
{{ GetUnix }} <br />
<h3> GetDate 时间处理</h3>
{{ GetDate }}
<h3> GetUnix 时间处理</h3>
{{ GetDateShort }}
<h3> md5 原字符串 </h3>
{{ .originStr }}
<h3> md5 加密后的hash </h3>
{{ .md5HashStr }}
<h3> md5 验证是否相同 </h3>
{{ .checkMd5Result }}
<h3> md5 第二种md5加密方式得到的字符串 </h3>
{{ .testMd5_2 }}
<h3> md5 第三种md5加密方式得到的字符串 </h3>
{{ .testMd5_3 }}
{{ end }}
效果图
- 如果我们的应用非常简单的话,我们可以在 Controller 里面处理常见的业务逻辑
- 但是如果我们有一个功能想在多个控制器、或者多个模板里面复用的话
- 那么我们就可以把公共的功能单独抽取出来作为一个模块(Models)
- Model 是逐步抽象的过程,一般我们会在 Model
- 里面封装一些公共的方法让不同 Controller 使用
- 也可以在 Model 中实现和数据库打交道
- 以上是这些应用场景,和其他项目设计都是一致的思路
上传功能
这里上传分多种场景:上传单张图片,上传多张图片
在上传多张图片中又分为,同一文件字段和不同文件字段的处理
1 ) 设计路由
package routers
import (
"gin-demo/controllers/admin"
"gin-demo/middlewares" // 引入
"github.com/gin-gonic/gin"
)
func AdminRoutersInit(r *gin.Engine) {
adminRouters := r.Group("/admin", middlewares.InitMiddleware) // 注意这里
{
adminRouters.GET("/", admin.IndexCtrl{}.Index)
adminRouters.GET("/user", admin.UserCtrl{}.Index)
adminRouters.GET("/user/success", admin.UserCtrl{}.Success)
adminRouters.GET("/user/error", admin.UserCtrl{}.Error)
adminRouters.GET("/user/upload", admin.UserCtrl{}.Upload) // 这是单个上传页面
adminRouters.GET("/user/uploads", admin.UserCtrl{}.Uploads) // 这个是多文件上传页面,在这个页面中有两个上传入口
adminRouters.POST("/user/doUpload", admin.UserCtrl{}.DoUpload) // 处理单个上传
adminRouters.POST("/user/doUploads", admin.UserCtrl{}.DoUploads) // 处理多个上传(同一字段)
adminRouters.POST("/user/doUploads2", admin.UserCtrl{}.DoUploads2) // 处理多个上传(不同字段)
}
}
2 ) main 文件中对上传路径和模板的处理
package main
import (
"gin-demo/routers" // gin-demo 是 go mod init 初始化的工程,下同
"github.com/gin-gonic/gin"
"html/template"
"gin-demo/models"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 自定义模板函数 注意要把这个函数放在加载模板前 全局配置
r.SetFuncMap(template.FuncMap{
"UnixToTime": models.UnixToTime,
"DateToUnix": models.DateToUnix,
"GetUnix": models.GetUnix,
"GetDate": models.GetDate,
"GetDateShort": models.GetDateShort,
})
// 加载模板 放在配置路由前面
r.LoadHTMLGlob("tpls/**/**/*")
// 配置静态web目录 第一个参数表示路由, 第二个参数表示映射的目录
r.Static("/static", "./static")
routers.WebRoutersInit(r)
routers.ApiRoutersInit(r)
routers.AdminRoutersInit(r)
r.Run()
}
注意了,这个之前的项目中,首页模板修改成 tpls/web/home/index.html 就不会有问题了 (这个可忽略,因为不涉及)
主要是这两个
// 加载模板 放在配置路由前面
r.LoadHTMLGlob("tpls/**/**/*")
// 配置静态web目录 第一个参数表示路由, 第二个参数表示映射的目录
r.Static("/static", "./static")
3 )HTML 模板文件处理
3.1 单个上传
{{ define "admin/user/upload.html" }}
<h2>演示单一文件上传</h2>
<form action="/admin/user/doUpload" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" placeholder="用户名" />
<br>
<br>
头 像<input type="file" name="face" />
<br> <br>
<input type="submit" value="提交">
</form>
{{ end }}
3.2 多个上传
{{ define "admin/user/uploads.html" }}
<h2>演示多文件上传:相同文件字段</h2>
<form action="/admin/user/doUploads" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" placeholder="用户名" />
<br>
<br>
头 像1:<input type="file" name="face[]" />
<br> <br>
头 像2:<input type="file" name="face[]" />
<br> <br>
头 像3:<input type="file" name="face[]" />
<br> <br>
<input type="submit" value="提交">
</form>
<br />
<hr />
<br />
<h2>演示多文件上传:不同的文件字段</h2>
<form action="/admin/user/doUploads2" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" placeholder="用户名" />
<br>
<br>
头 像1:<input type="file" name="face1" />
<br> <br>
头 像2:<input type="file" name="face2" />
<br> <br>
<input type="submit" value="提交">
</form>
{{ end }}
4 ) 上传控制器
package admin
import (
"gin-demo/models"
"mime/multipart"
"net/http"
"os"
"path"
"strconv"
"time"
"math/rand"
"github.com/gin-gonic/gin"
)
type UserCtrl struct {
BaseCtrl
}
// 获取 单一上传 页面
func (con UserCtrl) Upload(c *gin.Context) {
c.HTML(http.StatusOK, "admin/user/upload.html", gin.H{})
}
// 获取 多上传 页面
func (con UserCtrl) Uploads(c *gin.Context) {
c.HTML(http.StatusOK, "admin/user/uploads.html", gin.H{})
}
// 允许的后缀判断
func checkAllowedExt(extName string, c *gin.Context) bool {
allowExtMap := map[string]bool{ ".jpg": true, ".png": true, ".gif": true, ".jpeg": true, }
if _, ok := allowExtMap[extName]; !ok {
return false
}
return true
}
// 获取文件路径
func saveFile(file *multipart.FileHeader, c *gin.Context) string {
fileName := file.Filename
extName := path.Ext(fileName) // 获取后缀
// 判断文件后缀名是否正确 .jpg .png .gif .jpeg
if (!checkAllowedExt(extName, c)) {
c.String(200, "文件类型不合法")
return ""
}
// 处理文件存储路径
day := models.GetDateShort()
dir := "./static/upload/" + day
// 0777用来测试 生产环境应使用 0666 让其不可执行,保证安全性
if err := os.MkdirAll(dir, 0777); err != nil {
// 处理错误
return ""
}
source := rand.NewSource(time.Now().UnixNano()) // 初始化随机数种子
rnd := rand.New(source) // 使用新的随机数源创建一个随机数生成器
random10DigitNumber := rnd.Intn(9000000000) + 1000000000 // 生成一个10位的随机整数
// 没有错误 生成文件名称 144325235235_xxxxxxxxxx.png
fileUnixName := strconv.FormatInt(models.GetUnix(), 10) + "-" + strconv.Itoa(random10DigitNumber)
//static/upload/20200623/144325235235.png
savedDir := path.Join(dir, fileUnixName + extName)
// 存储
c.SaveUploadedFile(file, savedDir)
return savedDir
}
// 处理 单一上传 提交
func (con UserCtrl) DoUpload(c *gin.Context) {
// 获取表单
username := c.PostForm("username") // 获取字段
file, err := c.FormFile("face") // 获取文件
if err != nil {
// 处理错误
c.String(http.StatusOK, "文件错误:%v", err)
return
}
// 保存文件位置
dst := saveFile(file, c)
if dst == "" {
return
}
// 响应json
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
"dst": dst,
})
}
// 处理 多上传 提交:相同字段
func (con UserCtrl) DoUploads(c *gin.Context) {
// 获取表单
username := c.PostForm("username")
form, _ := c.MultipartForm()
// 处理文件
files := form.File["face[]"]
// 没有文件的校验
if (len(files) == 0) {
// 做一些处理
c.String(http.StatusOK, "没上传一张图片!")
return
}
var dsts []string
// 处理文件
for _, file := range files {
// 进入存储流程
dst := saveFile(file, c)
if (dst == "") {
continue
}
dsts = append(dsts, dst)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
"dst": dsts,
})
}
// 处理 多上传 提交:不同字段
func (con UserCtrl) DoUploads2(c *gin.Context) {
// 表单处理
username := c.PostForm("username")
file1, err1 := c.FormFile("face1")
file2, err2 := c.FormFile("face2")
// 错误处理:上传文件到指定的目录
if !(err1 == nil || err2 == nil) {
c.String(200, "没有上传文件")
return;
}
// 保存文件
var dsts []string
// 处理文件
if file1 != nil {
dst1 := saveFile(file1, c)
if (dst1 != "") {
dsts = append(dsts, dst1)
}
}
if file2 != nil {
dst2 := saveFile(file2, c)
if (dst2 != "") {
dsts = append(dsts, dst2)
}
}
// 响应
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
"dst": dsts,
})
}
5 ) 聊聊注意事项
- 上传文件注意 html 模板中设置
enctype="multipart/form-data"这个是关键 - 上传时,如果有文件服务器,需要上传到文件服务器中,这里只演示本服务器上传基础功能
- 上传图片时的路径处理,包括按照时间来创建目录和文件
- 这里仅是一个示例程序,用于学习和交流,可以基于此来改造
1606



被折叠的 条评论
为什么被折叠?



