05 . Go+Vue開發一個線上外賣應用(Session整合及修改使用者頭像到Fastdfs)

men發表於2020-11-03

使用者頭像上傳

功能介紹

在使用者中心中,允許使用者更換自己的頭像。因此,我們開發上傳一張圖片到伺服器,並儲存成為使用者的頭像。

介面解析

在使用者模組的控制器MemberController中,解析頭像上傳的介面,解析如下:

func (mc *MemberController) Router(engine *gin.Engine) {
    ...
    //使用者頭像上傳
	engine.POST("/api/upload/avator",mc.uploadAvator)
}

在檔案上傳過程中,後臺伺服器需要確認該頭像檔案是哪位使用者上傳的。前端在上傳檔案時,一併將使用者id上傳至伺服器。伺服器需要確認該使用者是否已經登入,只有登入的使用者才有許可權上傳。最通常的做法是通過session來獲取使用者是否已經登入,並進行許可權判斷。

Session功能整合

安裝session庫

go語言和gin框架都沒有實現session庫,可以通過第三方實現的session庫來整合session功能。安裝如下session庫:

go get github.com/gin-contrib/sessions

等待安裝完成,可以在$GOPATH/src/github.com/gin-contrib目錄下看到sessions庫。

初始化session

在專案中,整合session功能,首先要進行初始化。我們選擇將session資料持久化儲存到redis中,因此需要與redis相結合。

新建SessionStore.go檔案,並定義session初始化函式如下:

func InitSession() gin.HandlerFunc {
	config := GetConfig().RedistConfig
	SessionStore, _ := redis.NewStore(10, "tcp", config.Addr+":"+config.Port, config.Password, []byte("secret"))
	return sessions.Sessions("onlinerestaurant", SessionStore)
}

通過redis.NewStore例項化sessionStore結構體物件,通過sessions.Sessions方法設定例項化後的sessionStore結構體物件。

封裝Session操作方法

session功能初始化完成以後就可以使用了,session的使用主要有兩個操作:set和get。在sessions庫中,有對應的session.Set(key, value)和session.Get(key)方法來實現set和get操作。

為了方便session的set和get操作,在初始化完session後,另外封裝session的set和get函式,具體實現如下:

//設定session
func SetSess(context *gin.Context, key interface{}, value interface{}) error {
	session := sessions.Default(context)
	if session == nil {
		return nil
	}
	session.Set(key, value)
	return session.Save()
}
//獲取session
func GetSess(context *gin.Context, key interface{}) interface{} {
	session := sessions.Default(context)
	if session == nil {
		return nil
	}
	return session.Get(key)
}
使用者登入新增session

當使用者進行登入,並登入成功後,選擇將使用者的資訊儲存在session中。在專案的需要登入才能使用的地方,可以進行許可權的判斷和攔截。

因此,在之前已經完成的登入功能方法中,新增登入操作成功後,將使用者資料儲存到sesion的操作。在MemberController中的nameLogin和smsLogin方法中,新增如下設定session的程式碼操作,具體呼叫如下所示:

...
//設定session
sess, _ := json.Marshal(member)
err = tool.SetSess(context, "user_"+string(member.Id), sess)
if err != nil {
	tool.Failed(context, "登入失敗")
	return
}
...
整合session操作

在專案的入口main.go檔案的main函式中,通過中介軟體呼叫開啟session整合。main函式修改如下:

...
//整合session
app.Use(tool.InitSession())
...
檔案上傳Contoller實現

在MemberController中,建立uploadAvator方法,用於實現使用者頭像上傳的業務流程控制。該方法其實主要有幾個步驟:第一步是獲取到使用者端上傳的檔案,接下來將檔案儲存到對應的目錄中,因為要知道該檔案對應的是哪位使用者的資料,因此需要將檔案路徑更新到使用者資料庫中的對應記錄中:

//使用者頭像檔案上傳
func (mc *MemberController) uploadAvator(context *gin.Context) {
	//1、獲取上傳的檔案
	userId := context.Request.PostFormValue("user_id") //使用者id
	file, header, err := context.Request.FormFile("avator")
	if err != nil {
		toolbox.Failed(context, "引數解析失敗")
		return
	}

	//從session中獲取使用者資訊
	sess := sessions.Default(context)
	user := sess.Get(userId).(model.Member)
	if user.Id == 0 {
		toolbox.Failed(context, "引數不合法")
		return
	}

	//2、將檔案儲存到本地
	fileFullPath := "./uploadfile/" + header.Filename
	out, err := os.Create(fileFullPath)
	if err != nil {
		toolbox.Error(err.Error())
		return
	}
	defer out.Close()
	_, err = io.Copy(out, file)
	if err != nil {
		toolbox.Error(err.Error())
		return
	}
	
	//3、將檔案對應路徑更新到資料庫中
	memberService := impl.NewMemberService()
	path := memberService.UploadAvator(user.Id, fileFullPath[1:])
	if path != "" {
		toolbox.Success(context, path)
		return
	}
	toolbox.Failed(context, "上傳失敗")
Service層實現

在MemberService層實現UploadAvator方法,直接運算元據庫方法,完成資料記錄修改:

func (msi *MemberServiceImpl) UploadAvator(userid int64, path string) string {

	dao := impl.NewMemberDao()
	result := dao.UpdateMemberAvatar(userid, path)
	if result == 0 {
		return ""
	}
	return path
}

在service層,實現更新資料庫記錄的操作

資料庫操作層

在dao層,主要就是實現對資料庫表的操作:

func (mdi *MemberDaoImpl) UpdateMemberAvatar(userid int64, path string) int64 {
	var member model.Member
	result, err := mdi.Where(" id = ? ", userid).Update(&member,"avatar");
	if err != nil {
		toolbox.Error(err.Error())
	}
	return result
}

功能及背景介紹

上面完成了個人中心模組,用d戶可以上傳圖片,修改並儲存使用者頭像的功能。除此之外,在正常的開發中,也經常會有檔案上傳的功能和需要。

在實際的開發中,涉及到檔案上傳的功能,往往單獨搭建一個檔案伺服器用於檔案的儲存。因此我們接下來就搭建一個分散式的檔案系統,並將已完成的檔案上傳功能進行優化,將檔案儲存到分散式檔案系統中。

在本API專案中,我們通過搭建fastDFS檔案系統來實現檔案上傳和儲存的功能。

FastDFS介紹原理

FastDFS介紹,原理,分散式儲存介紹請看我寫的Fastdfs專篇

https://www.cnblogs.com/you-men/p/12863555.html

FastDFS 單節點部署(5.09)

環境
[Fastdfs-Server]
	主機名 = fastdfs-1
	系統 = CentOS7.6.1810
	地址 = 192.168.242.128
	軟體 = libfastcommon-master
    nginx-1.8.0.tar.gz
    fastdfs_v5.05.tar.gz
    fastdfs-nginx-module_v1.16.tar.gz
節點名 IP 軟體版本 硬體 網路 說明
fastdfs 192.168.242.128 list 裡面都有 2C4G Nat,內網 測試環境
安裝相關工具和依賴
yum install git gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vim  libevent -y
解壓編譯安裝
wget https://github.com/happyfish100/libfastcommon/archive/master.zip
unzip master.zip 
cd libfastcommon-master/ 
./make.sh && ./make.sh install
下載安裝FastDFS
 wget https://github.com/happyfish100/fastdfs/archive/V5.09.tar.gz
tar xf V5.09.tar.gz 
cd fastdfs-5.09/
./make.sh && ./make.sh install
cp conf/http.conf /etc/fdfs/
cp conf/mime.types /etc/fdfs/
tracker配置
mkdir /home/fastdfs
cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
vim /etc/fdfs/tracker.conf

#需要修改的內容如下
port=22122  # tracker伺服器埠(預設22122,一般不修改)
base_path=/home/fastdfs  # 儲存日誌和資料的根目錄
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart
storage配置
cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
vim /etc/fdfs/storage.conf
#需要修改的內容如下
port=23000  # storage服務埠(預設23000,一般不修改)
base_path=/home/fastdfs  # 資料和日誌檔案儲存根目錄
store_path0=/home/fastdfs  # 第一個儲存目錄
tracker_server=192.168.242.128:22122  # tracker伺服器IP和埠

/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart
安裝fastdfs-nginx-module
tar xf fastdfs-nginx-module_v1.16.tar.gz -C /usr/local
cd /usr/local/fastdfs-nginx-module/src/
cp mod_fastdfs.conf /etc/fdfs/

vim /etc/fdfs/mod_fastdfs.conf
base_path=/home/fastdfs
tracker_server=192.168.242.128:22122 
url_have_group_name=true        #url中包含group名稱
store_path0=/home/fdfs_storage  #指定檔案儲存路徑(上面配置的store路徑)

cp /usr/lib64/libfdfsclient.so /usr/lib/
配置nginx訪問
tar xv nginx-1.12.0.tar.gz 
tar xf fastdfs-nginx-module_v1.16.tar.gz  -C /usr/local
mkdir /usr/local/nginx
cd nginx-1.12.0/
./configure --prefix=/usr/local/nginx --add-module=/usr/local/fastdfs-nginx-module/src
make && make install 
cp /usr/local/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs/


make && make install

mkdir /usr/local/nginx/logs # 建立logs目錄
cd /usr/local/nginx/conf/
vim nginx.conf
user root;
pid        /usr/local/nginx/logs/nginx.pid;
        server_name  192.168.242.128;
        location /group1/M00/ {
                root /home/fstdfs/data;
                ngx_fastdfs_module;
        }

此處可能會編譯報錯ngninx在gmake時可能出現找不到fdfs_define.h問題

錯誤資訊

root/fastdfs-nginx-module/src//common.c:21:25: fatal error:
fdfs_define.h: No such file or directory
 #include "fdfs_define.h"

新增如下配置

# 把/usr/lib64/libfdfsclient.so庫拷貝到/usr/lib/目錄下:
# sudo cp /usr/lib64/libfdfsclient.so /usr/lib/

配置/usr/local/fastdfs-nginx-module/src/目錄下的config檔案, 把CORE_INCS和CORE_LIBS的所有路徑都修改為/usr/include和/usr/lib:

vim /usr/local/src/fastdfs-nginx-module/src/config
CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"
CORE_LIBS="$CORE_LIBS -L/usr/lib -lfastcommon -lfdfsclient"
啟動tracker與storage
fdfs_storaged /etc/fdfs/storage.conf start
fdfs_trackerd /etc/fdfs/tracker.conf start
/usr/local/nginx/sbin/nginx
配置client上傳檔案測試
vim /etc/fdfs/client.conf

#需要修改的內容如下
base_path=/home/fastdfs
tracker_server=192.168.242.128:22122  
[root@tracker1 sbin]# fdfs_test /etc/fdfs/client.conf upload /root/1.jpg 
This is FastDFS client test program v5.05

Copyright (C) 2008, Happy Fish / YuQing

FastDFS may be copied only under the terms of the GNU General
Public License V3, which may be found in the FastDFS source kit.
Please visit the FastDFS Home Page http://www.csource.org/ 
for more detail.

[2020-07-17 00:00:50] DEBUG - base_path=/home/fastdfs, connect_timeout=30, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0

tracker_query_storage_store_list_without_group: 
	server 1. group_name=, ip_addr=192.168.242.128, port=23000

group_name=group1, ip_addr=192.168.242.128, port=23000
storage_upload_by_filename
group_name=group1, remote_filename=M00/00/00/wKjygF8QebKAWJCPAADMPhWBDxw409.jpg
source ip address: 192.168.242.128
file timestamp=2020-07-17 00:00:50
file size=52286
file crc32=360779548
example file url: http://192.168.242.128/group1/M00/00/00/wKjygF8QebKAWJCPAADMPhWBDxw409.jpg
storage_upload_slave_by_filename
group_name=group1, remote_filename=M00/00/00/wKjygF8QebKAWJCPAADMPhWBDxw409_big.jpg
source ip address: 192.168.242.128
file timestamp=2020-07-17 00:00:50
file size=52286
file crc32=360779548
example file url: http://192.168.242.128/group1/M00/00/00/wKjygF8QebKAWJCPAADMPhWBDxw409_big.jpg

Go實現檔案上傳FastDFS

安裝fastdfs的golang庫

在專案中進行檔案上傳程式設計實現,需要安裝一個go語言庫,該庫名稱為fdfs_client,通過如下命令進行安裝。

go get github.com/tedcy/fdfs_client
編寫fdfs.conf配置檔案

在fdfs_client庫中,提供對檔案的上傳和下載方法,其中檔案上傳支援兩種方式。

要使用檔案上傳功能方法,首先需要構造一個fdfsClient例項。如同我們前文講的fastDFS的元件構成一樣,client需要連線tracker伺服器,然後進行上傳。

在構造fdfsClient例項時,首先需要編寫配置檔案fdfs.conf,在fdfs.conf檔案中進行配置選項的設定:

tracker_server=114.246.98.91:22122
http_port=http://114.246.98.91:80
maxConns=100

/*
 tracker_server:跟蹤伺服器的ip和跟蹤服務的埠
 http_port:配置了nginx伺服器後的http訪問地址和埠
 maxConns:最大連線數為100,預設值即可
*/
實現檔案上傳

將檔案上傳功能作為全域性的一個工具函式進行定義,實現檔案上傳功能,並返回儲存後的檔案的id。程式設計實現如下:

func UploadFile(fileName string) string {
	client, err := fdfs_client.NewClientWithConfig("./config/fastdfs.conf")
	defer client.Destory()

	if err != nil {
		fmt.Println(err.Error())
	}

	fileId, err := client.UploadByFilename(fileName)
	if err != nil {
		fmt.Println(err.Error())
	}
	return fileId
}

在自定義UploadFile函式中,通過fdfs_client.NewClientWithConfig例項化client物件,並呼叫UploadByFilename方法實現檔案上傳,該方法接收一個檔名稱,該檔名稱包含檔案的路徑,最後返回上傳的fileId。

修改Controller檔案上傳方法

現在,已經接入了fastDFS檔案系統,因此,對MemberController的uploadAvator方法進行修改。

修改思路:將客戶端上傳的檔案,先儲存在伺服器目錄下的uploadfile目錄中,然後將檔案的路徑和名稱作為引數傳遞到UploadFile函式中,進行上傳。上傳成功後,將儲存到本地的uploadfile檔案刪除,並把儲存到fastDFS系統的fileId更新到對應使用者記錄的資料庫。最後拼接檔案訪問的全路徑,返回給客戶端。

依照上述思路,修改後的uploadAvator方法邏輯實現如下所示:

func (mc *MemberController) uploadAvator(context *gin.Context) {

	//1、獲取上傳的檔案
	userId := context.PostForm("user_id") //使用者id
	fmt.Println(userId)
	file, err := context.FormFile("avatar")
	if err != nil {
		tool.Failed(context, "引數解析失敗")
		return
	}

	//從session中獲取使用者資訊
	var member model.Member
	sess := tool.GetSess(context, "user_"+userId)
	if sess == nil {
		tool.Failed(context, "引數不合法")
		return
	}

	json.Unmarshal(sess.([]byte), &member)

	//2、將檔案儲存到本地
	fileName := "./uploadfile/" + strconv.FormatInt(time.Now().Unix(), 10) + file.Filename
	err = context.SaveUploadedFile(file, fileName)
	fmt.Println(err)
	if err != nil {
		tool.Failed(context, "頭像更新失敗")
		return
	}

	//3、將檔案上傳到分散式檔案系統
	fileId := tool.UploadFile(fileName)

	if fileId != "" {
		//刪除本地檔案
		os.Remove(fileName)

		//4、將檔案對應路徑更新到資料庫中
		memberService := impl.NewMemberService()
		path := memberService.UploadAvator(3, fileId)
		fullPath := tool.FileServerAddr() + "/" + path
		tool.Success(context, fullPath)
		return
	}
	tool.Failed(context, "上傳失敗")
}

在最後返回客戶端資料前,需要對上傳檔案的訪問全路徑進行拼接。因此,提供FileServerAddr函式,使用者解析fdfs.conf檔案,並提取其中的http_port選項。

FileServerAddr函式的實現如下所示:

func FileServerAddr() string {
	f, err := os.Open("./config/fastdfs.conf")
	if err != nil {
		return ""
	}
	reader := bufio.NewReader(f)
	for {
		line, err := reader.ReadString('\n')
		line = strings.TrimSuffix(line, "\n")
		str := strings.SplitN(line, "=", 2)
		switch str[0] {
		case "http_port":
			return str[1]
		}
		if err != nil {
			return ""
		}
	}
}

相關文章