用戶頭像上傳
功能介紹
在用戶中心中,允許用戶更換自己的頭像。因此,我們開發上傳一張圖片到服務器,並保存成為用戶的頭像。
接口解析
在用戶模塊的控制器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 ""
}
}
}