如何自动配置项目版本?只需一个简单的服务

作为一个纯前端的程序员,第一次开发后端服务,内心还是有点小激动的,但是为了一劳永逸
还是决定挑战一下。准备试试最近接触的Go语言,因为服务不是很复杂,不采用任何Go的框架
第一个服务器项目,先造势,这样显得专业一些,哈哈哈。。。

思维导图:

思维导图

项目需求:

  1. 需要固定接口获取最新的项目地址
  2. 自动更新,配置文件
  3. 可视化后台界面
  4. 可以满足产品、测试人员操作/修改

立项:

预计开发一个后端服务,提供上传,更新配置,提供最新链接

选型:

使用Go语言,支持IO,压缩、编码,较完善的HTTP封装,新人友好

开发

  • 先开发一个测试接口,实现最基础的HTTP行为
  • 实现上传接口,
    1、包含上传文件格式校验,
    2、限制上传文件体积
  • 更新配置文件 调用io功能模块,动态维护服务器配置文件
  • 读取配置文件内容,拼接成合适的链接,提供给客户端
  • 可视化后台界面(这里加入公司的统一后台界面了,本篇文章没有赘述,可以根据自己喜好到第三方网站上找自己喜欢的后台界面:下面推荐几个好用的模板)
    饿了么组件模板
    Layui
    阿里中后台设计系统解决方案

部署

  • 配置服务器nginx代理,监听需要的端口号;
  • 配置域名(允许外部访问)
  • 设置允许上传的包体大小 (这一步很容易忽略,表现是会报跨域的错,是个大坑!)

在nginx.conf文件的http{}标签中加入
client_max_body_size 1024m

可能会遇到的问题:

接触一个新的领域肯定会遇到很多的问题(踩各种坑):
这里分享一下我遇到的坑: (仅供借鉴)

  • 校验上传格式 ——可能会遇到获取文件名不正确的清空,和对文件名的修改问题
  • 配置文件格式(不同格式的文件读取和写入方式不同)
  • 跨域警告(这个前端常遇到的问题,终于在写服务的时候完美提前考虑到了)
  • nginx配置(主要是设置包体大小!这是个很容易忽略的坑。且没有直接报错,总是报跨域的错,一头雾水,后来终于找到原因,才得以解决。太坑了qaq)
  • 服务器文件资源维护(涉及到服务器硬盘容量)

项目提取出的代码展示:

/*
* @Author: Hifun
* @Date: 2020/8/27 19:27
 */
package main

import (
    "archive/zip"
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "path/filepath"
    "strings"
)

const (
    configName = "config.json"
    currentPath = "./path.txt"
)

var targetPath  = ""
func sayHello(w http.ResponseWriter, r *http.Request)  {
    w.Write([]byte("hello world!" + targetPath))
    return
}
func initCurrentPath()  {
    targetPath = loadConfig(currentPath)
}
func main()  {
    // 先初始化当前的路径
    initCurrentPath()
    fmt.Printf("server start success!\n Listening...\nCurrentPath:  "+targetPath)
    // 测试接口
    http.HandleFunc("/test",sayHello)
    //  注册上传压缩包的接口
    http.HandleFunc("/upload", uploadHandler)
    // 注册更新文件内容的接口
    http.HandleFunc("/updateContext",UpdateConfig)
    // 注册读取文件内容的接口
    http.HandleFunc("/getContext",getConfigContext)
    // 创建一个监听 使用默认的handler监听 8090端口
    err := http.ListenAndServe(":8090", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}
func uploadHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Printf("\n enter func uploadHandler\n")
    w.Header().Set("Access-Control-Allow-Origin", "*")             //允许访问所有域
    w.Header().Add("Access-Control-Allow-Headers", "Content-Type") //header的类型
    w.Header().Set("content-type", "application/json")             //返回数据格式是json
    // 限制客户端上传文件的大小
    r.Body = http.MaxBytesReader(w, r.Body, 20*1024*1024)
    err := r.ParseMultipartForm(20 * 1024 * 1024)
    if err != nil {
       http.Error(w, err.Error(), http.StatusInternalServerError)
       return
    }
    // 获取上传的文件
    file, fileHeader, err := r.FormFile("uploadFile")
    // 检查文件类型
    ret := strings.HasSuffix(fileHeader.Filename, ".zip")
    if ret == false {
       http.Error(w, err.Error(), http.StatusInternalServerError)
    }
    //r.ParseForm()
    appName := r.Form["appName"][0]
    targetPath := "./" + appName
    isExist,err := PathExists(targetPath)
    if err != nil {
        fmt.Printf("get dir error![%v]\n", err)
        return
    }
    if !isExist {
        // 创建文件夹
        err := os.Mkdir(targetPath, os.ModePerm)
        if err != nil {
            fmt.Printf("mkdir failed![%v]\n", err)
        } else {
            fmt.Printf("mkdir success!\n")
        }
    }
    // 写入文件
    dst, err := os.Create(targetPath + "/" + fileHeader.Filename)
    defer dst.Close()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer file.Close()
    if _, err := io.Copy(dst, file); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    result := NewBaseJsonBean()
    result.Code = 1000
    result.Message = "hifun upload success!"
    json, _ := json.Marshal(result)
    w.Write(json)
    return
}
func UpdateConfig(w http.ResponseWriter,r *http.Request)  {
    // 获取客户端POST方式传递的参数
    r.ParseForm()
    appName := r.Form["appName"]
    version := r.Form["version"]
    fmt.Println("hifun: getVersion:  ",version)
    updateConfig(appName[0],version[0])
    w.Header().Set("Access-Control-Allow-Origin", "*")
    result := NewBaseJsonBean()
    result.Code = 1000
    result.Message = "updateConfig success!"
    //result.Data = data
    w.Header().Set("Content-Type", "application/json")
    json, _ := json.Marshal(result)
    w.Write(json)
    return
}
func updateConfig(appName,version string)  {
    targetPath := "./" + appName + "/"
    // 解压缩文件
    zipFil,tarDir := targetPath + version + ".zip",targetPath
    Unzip(zipFil,tarDir)
    strTest := appName + "/" + version
    var strByte = []byte(strTest)
    err := ioutil.WriteFile(targetPath + configName, strByte, 0666)
    if err != nil {
        fmt.Println("write fail")
    }
    fmt.Println("write success")
}
func getConfigContext(w http.ResponseWriter,r *http.Request)  {
    w.Header().Set("Access-Control-Allow-Origin", "*")
    // 获取客户端POST方式传递的参数
    r.ParseForm()
    appName := r.Form["appName"][0]
    targetConfig := "./" + appName + "/" + configName
    config := loadConfig(targetConfig)
    // 向客户端返回JSON数据
    data := make(map[string]interface{})
    data["url"] = targetPath + config+"/index.html"
    data["appName"] = appName
    temLen := len(config)
    appVersion := config[temLen-5: temLen]
    data["appVersion"] =appVersion
    result := NewBaseJsonBean()
    result.Code = 1000
    result.Message = "获取成功"
    result.Data = data
    w.Header().Set("Content-Type", "application/json")
    json, _ := json.Marshal(result)
    w.Write(json)
    return
}
//读取到file中,再利用ioutil将file直接读取到[]byte中, 这是最优
func loadConfig(targetConfig string) string {
    f, err := os.Open(targetConfig)
    if err != nil {
        fmt.Println("read file fail", err)
        return ""
    }
    defer f.Close()

    fd, err := ioutil.ReadAll(f)
    if err != nil {
        fmt.Println("read to fd fail", err)
        return ""
    }

    return string(fd)
}

// 判断文件夹是否存在
func PathExists(path string) (bool, error) {
    _, err := os.Stat(path)
    if err == nil {
        return true, nil
    }
    if os.IsNotExist(err) {
        return false, nil
    }
    return false, err
}

通用的返回代码模块:

/*NewBaseJsonBean用于创建一个struct对象:*/
type BaseJsonBean struct {

    Code    int         `json:"code"`

    Data    interface{} `json:"data"`

    Message string      `json:"message"`

}

func NewBaseJsonBean() *BaseJsonBean {
    return &BaseJsonBean{}
}

压缩文件通用方法:

// srcFile could be a single file or a directory
func Zip(srcFile string, destZip string) error {
    zipfile, err := os.Create(destZip)
    if err != nil {
        return err
    }
    defer zipfile.Close()

    archive := zip.NewWriter(zipfile)
    defer archive.Close()

    filepath.Walk(srcFile, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        header, err := zip.FileInfoHeader(info)
        if err != nil {
            return err
        }


        header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile) + "/")
        // header.Name = path
        if info.IsDir() {
            header.Name += "/"
        } else {
            header.Method = zip.Deflate
        }

        writer, err := archive.CreateHeader(header)
        if err != nil {
            return err
        }

        if ! info.IsDir() {
            file, err := os.Open(path)
            if err != nil {
                return err
            }
            defer file.Close()
            _, err = io.Copy(writer, file)
        }
        return err
    })

    return err
}

解压缩文件通用方法:

// unzip a zipFile to a directory
func Unzip(zipFile string, destDir string) error {
    zipReader, err := zip.OpenReader(zipFile)
    if err != nil {
        return err
    }
    defer zipReader.Close()

    for _, f := range zipReader.File {
        fpath := filepath.Join(destDir, f.Name)
        if f.FileInfo().IsDir() {
            os.MkdirAll(fpath, os.ModePerm)
        } else {
            if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
                return err
            }

            inFile, err := f.Open()
            if err != nil {
                return err
            }
            defer inFile.Close()

            outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
            if err != nil {
                return err
            }
            defer outFile.Close()

            _, err = io.Copy(outFile, inFile)
            if err != nil {
                return err
            }
        }
    }
    return nil
}

其他通用方法都在main模块中,可以选择性取舍。

总结:

做服务器项目(不论功能复杂与否),完全是和前端采用不一样的思维来开发的,这种经历值得仔细回味。看到自己的服务运行在服务器上,得到其他人的认可,这种感觉真的很棒。前端开发也可以上手后台服务,技术无界限,大家加油!

————————————————————————————————————————

如果本篇文章对你有启发和帮助,希望可以给我点个赞,嘻嘻嘻嘻。欢迎随时交流
笔者QQ:840658308
笔者微信:HifunAmos
(扫描他,带走我!)
在这里插入图片描述
github项目链接

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值