我覺(jué)得差不多該更新了,挖個(gè)坑明天寫(xiě),十一之前BUG都清完了,也沒(méi)什么事了。
這一章按理來(lái)說(shuō)應(yīng)該是“商城首頁(yè)信息和購(gòu)物車(chē)模塊”,但是入職新公司以后我看到了好多高端操作,所以順道重寫(xiě)一下之前亂七八糟的框架。
而且golang的web項(xiàng)目相對(duì)于那些重型的高封裝語(yǔ)言比如.net MVC項(xiàng)目來(lái)說(shuō)要好很多,用起來(lái)很自由。前后分離,只要支持REST就可以了,并不需要前端再去格式化model,雖然也有好處但總體感覺(jué)用起來(lái)比較笨重。
然后想起上一章好像還坑在session那呢,我晚上回去好好看一下。
今天來(lái)填坑
看一下我們之前慘的不行的main方法
func main() {
router := gin.Default()
RouterInit(router)
Helper.Dbinit()
router.Run(":8081")
}
雖然功能是實(shí)現(xiàn)了,但是及其簡(jiǎn)易。不光連接字符串是寫(xiě)死的,一些初始化函數(shù)也全都沒(méi)有封裝。一個(gè)靈活的網(wǎng)站需要很多配置文件,還要相對(duì)應(yīng)的log跟蹤及錯(cuò)誤處理,我們改一下main文件讓啟動(dòng)函數(shù)更豐富。
func main() {
//根據(jù)shell文件去啟動(dòng)編譯好的go文件
//shell命令:./ginmall -c web-config.toml
//定義flag參數(shù),返回一個(gè)相應(yīng)的指針
cfgFlag := flag.String("c", "", "配置文件路徑")
//調(diào)用flag.Parse()解析命令行參數(shù)到定義的flag
flag.Parse()
//解析配置文件
params := config.NewConfiguration()
if len(*cfgFlag) > 0 { //cfgFlag是*string型,取地址符
//
params.InitFromFile(*cfgFlag)
} else {
fmt.Println("配置文件錯(cuò)誤,請(qǐng)檢查內(nèi)容格式")
}
//設(shè)置time包時(shí)區(qū)(time包是個(gè)很神奇的東西,默認(rèn)采用UTC時(shí)區(qū),最好設(shè)置一下)
//Asia包里沒(méi)有北京時(shí)區(qū),可以寫(xiě)成Asia/Shanghai,小伙子們注意一下。
//具體的時(shí)區(qū)命名可以解壓$GOROOT/lib/time/zoneinfo.zip 這個(gè)文件打開(kāi)查看。
location, err := time.LoadLocation("Local")
if err != nil {
fmt.Println("本地時(shí)區(qū)初始化失敗! " + err.Error())
os.Exit(1)
}
fmt.Println("本地時(shí)區(qū)初始化成功!")
//初始化logger
logger := logger.NewLogger()
fmt.Println("日志初始化成功!")
//初始化服務(wù)
server := "server"
if server == nil {
logger.Error("服務(wù)初始化失敗")
os.Exit(1)
}
//啟動(dòng)服務(wù)
server.Start()
// router := gin.Default()
// RouterInit(router)
// Helper.Dbinit()
// router.Run(":8081")
}
用到的三個(gè)包,config,logger,server
//config.go
package config
import (
"fmt"
"github.com/BurntSushi/toml"
)
type Configuration struct {
Port string `toml:"port"` // 服務(wù)器監(jiān)聽(tīng)端口
// 數(shù)據(jù)庫(kù)相關(guān)參數(shù)
DbUrl string `toml:"database_url"` // 數(shù)據(jù)庫(kù)URL
DbPort string `toml:"database_port"` // 數(shù)據(jù)庫(kù)端口
DbName string `toml:"database_name"` // 數(shù)據(jù)庫(kù)名稱(chēng)
DbUser string `toml:"database_user"` // 數(shù)據(jù)庫(kù)用戶(hù)名
DbPasswd string `toml:"database_passwd"` // 數(shù)據(jù)庫(kù)密碼
// Redis數(shù)據(jù)庫(kù)相關(guān)參數(shù)
RedisUrl string `toml:"redis_url"` // 數(shù)據(jù)庫(kù)URL
RedisPort string `toml:"redis_port"` // 數(shù)據(jù)庫(kù)端口
RedisPasswd string `toml:"redis_passwd"` // 數(shù)據(jù)庫(kù)密碼
// 日志相關(guān)參數(shù)
LogLevel string `toml:"log_level"`
LogDest string `toml:"log_dest"`
LogDir string `toml:"log_dir"`
}
func NewConfiguration() *Configuration {
return &Configuration{ // 在此提供默認(rèn)值(為所有參數(shù)提供默認(rèn)值)
Port: "8081",
LogDir: "./log",
LogLevel: "debug",
LogDest: "file",
DbUrl: "localhost",
DbPort: "3306",
DbName: "sa",
DbUser: "root",
DbPasswd: "Your PassWord",
RedisUrl: "127.0.0.1",
RedisPort: "6379",
}
}
func (this *Configuration) InitFromFile(path string) { // 用于啟動(dòng)時(shí)加載配置文件
if _, err := toml.DecodeFile(path, this); err != nil {
panic(fmt.Sprintf("can't decode conf file: [%s]", path))
}
}
//logger.go
package logger
import (
"fmt"
"github.com/sirupsen/logrus"
)
type Logger struct {
logrus *logrus.Entry
}
//logger方法我沒(méi)具體寫(xiě),有興趣的朋友按需實(shí)現(xiàn)吧
func NewLogger() *Logger {
logger := &Logger{
logrus: logrus.WithFields(logrus.Fields{}),
}
return logger
}
func (logger *Logger) Info(str error) {
fmt.Print("Info:" + str.Error())
}
func (logger *Logger) Warn(str error) {
fmt.Print("Warn:" + str.Error())
}
func (logger *Logger) Error(str error) {
fmt.Print("Error:" + str.Error())
}
//service.go
package service
import (
"ginMall/logger"
"time"
"ginMall/config"
"ginMall/dao"
"ginMall/httpsvr"
)
type Service struct {
cfgFlag string // 配置文件路徑
config *config.Configuration //系統(tǒng)配置
logger *logger.Logger //日志
location *time.Location // 時(shí)區(qū)信息
httpsvr *httpsvr.HttpServer
daomgr *dao.DaoManager //引用Dao層
//redismgr *redis.RedisManager //Redis
}
func NewService(params *config.Configuration, logger *logger.Logger, location *time.Location, cfg string) *Service {
// 初始化數(shù)據(jù)庫(kù)連接
dsn := params.DbUser + ":" + params.DbPasswd + "@tcp" + "(" + params.DbUrl + ":" + params.DbPort + ")" + "/" + params.DbName + "?charset=utf8"
// 初始化Dao層
daomgr, err := dao.NewDaoManager(dsn)
if err != nil {
logger.Error(err)
}
//第一章寫(xiě)的router,挪到httpserver里
httpsvr := httpsvr.NewHttpServer()
service := &Service{
cfgFlag: cfg,
config: params,
logger: logger,
location: location,
daomgr: daomgr,
httpsvr: httpsvr,
}
return service
}
func (this *Service) Start() {
go this.httpsvr.Start()
}
展開(kāi)說(shuō)一下這個(gè)service層吧,看到我還注釋掉了Redis服務(wù)。其實(shí)你可以在service層里實(shí)例化多個(gè)DB連接,小項(xiàng)目也沒(méi)有什么必要非得寫(xiě)工廠類(lèi),需要用哪個(gè)就調(diào)用,我覺(jué)得這個(gè)是自己手搭框架最舒服的地方。
然后看一下Dao層,我只是在Dao層初步封裝了一下orm框架,在調(diào)用的時(shí)候可以直接通過(guò)GetEngine方法獲取orm的實(shí)例。而且gorm框架底層支持手寫(xiě)sql,我研究的不多,喜歡學(xué)習(xí)的朋友可以點(diǎn)進(jìn)去看一下源碼。功能比較全。
然后這個(gè)orm框架,我不知道別的公司或者國(guó)內(nèi)大趨勢(shì)是什么樣子的,用的多不多、好不好用我都不太清楚,我只是撿起手頭的東西拿來(lái)試試而已。不過(guò)這些都是小事情,代碼已經(jīng)貼出來(lái)了自己實(shí)例化一下就好了。
//dao.go
package dao
import (
"ginMall/Model"
"github.com/jinzhu/gorm"
)
type DaoManager struct {
DB *gorm.DB
//logger *logger.Logger
}
func NewDaoManager(constr string) (*DaoManager, error) {
db, err := gorm.Open("mysql", constr)
if err != nil {
return nil, err
}
//SetMaxOpenConns用于設(shè)置最大打開(kāi)的連接數(shù)
//SetMaxIdleConns用于設(shè)置閑置的連接數(shù)
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
// 啟用Logger,顯示詳細(xì)日志
db.LogMode(true)
// ORM自動(dòng)遷移模式
db.AutoMigrate(&Model.UserModel{},
&Model.UserDetailModel{},
&Model.UserAuthsModel{},
)
dao := &DaoManager{
DB: db,
//logger: logger,
}
return dao, nil
}
func (this *DaoManager) GetEngine() *gorm.DB {
return this.DB
}
然后service層里還寫(xiě)了一個(gè)httpserver,這個(gè)主要還是業(yè)務(wù)層(我架構(gòu)學(xué)的很爛,說(shuō)錯(cuò)了請(qǐng)指出)。實(shí)現(xiàn)系統(tǒng)的緩存及路由功能。
我在之前章節(jié)也講了一下session,當(dāng)時(shí)主要是用的插件,后來(lái)其實(shí)經(jīng)過(guò)一些項(xiàng)目的實(shí)戰(zhàn)以后(尤其是從.net脫坑以后)發(fā)現(xiàn)其實(shí)之前腦子很僵化。主要是.net的session就是直接在httpcontext里寫(xiě)好了了,用起來(lái)很無(wú)腦,而且還挺好使的,所以也沒(méi)有很在意。
等真的到golang的實(shí)戰(zhàn)時(shí)候,去問(wèn)同事“我怎么把一個(gè)常用表數(shù)據(jù)存到session里”的時(shí)候,同事告訴我你可以直接寫(xiě)全局變量。然后我瞬間就懵掉了,甚至不知道全局變量到底怎么寫(xiě)。翻了半天代碼,他們把所有用到的表變量,包括用戶(hù)信息什么的在初始化的時(shí)候就已經(jīng)寫(xiě)好了。
但是這樣也有局限性,也就是現(xiàn)在公司很小,只是單臺(tái)服務(wù)器,等需要搭建集群的時(shí)候就會(huì)出現(xiàn)內(nèi)存不同步的問(wèn)題,所以我推薦大家還是寫(xiě)到redis里。
等之后一兩個(gè)章節(jié)我會(huì)把這個(gè)代碼繼續(xù)完善,加一個(gè)redis的服務(wù)。我之前也寫(xiě)過(guò)一章redis存kv的簡(jiǎn)單思想,到十一在家沒(méi)事干的時(shí)候補(bǔ)一篇文章,寫(xiě)一些用戶(hù)權(quán)限及大量表關(guān)聯(lián)的這些解決方法和思想
//httpsvr.go
package httpsvr
import (
"ginMall/FPList"
"ginMall/session"
"net/http"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
type HttpServer struct {
gin *gin.Engine
session *session.Session
}
//HTTPserver包含幾大部分功能
//1:路由
//2:保存session,因?yàn)閟ession本質(zhì)上也是存儲(chǔ)在內(nèi)存之中,golang也并沒(méi)有原生支持session,所以可以直接將所有信息直接保存在系統(tǒng)里
// 也可以存儲(chǔ)靜態(tài)表,也可以存儲(chǔ)用戶(hù)信息
//3:保存service層及其他信息
func NewHttpServer() *HttpServer {
session := session.NewSession()
svr := &HttpServer{
gin: gin.Default(),
session: session,
}
return svr
}
func (this *HttpServer) Start() {
this.gin.Use(cors.Default())
this.gin.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "It works On 8081")
})
this.gin.GET("/Login", FPList.GetList)
this.gin.GET("/FPList", FPList.GetList)
}
上面這部分代碼其實(shí)也就是把之前寫(xiě)在router里的邏輯粘過(guò)來(lái)罷了。
session類(lèi)我沒(méi)有仔細(xì)寫(xiě),以后要被redis替換掉,只是簡(jiǎn)單寫(xiě)了get和set方法。
需要注意的一點(diǎn)就是用session的時(shí)候,變量接收interface需要斷言,用反射也好事先用之前寫(xiě)過(guò)的fmt.Sprintf("%T",)也好,都可以實(shí)現(xiàn)。
啊,晚上看了篇文章,然后發(fā)現(xiàn)自己理解錯(cuò)了,這東西不能叫session,只能叫全局變量,等過(guò)兩天仔細(xì)研究明白了再寫(xiě)這部分。
package session
type Session struct {
UserInfo map[string]string
SomeThingYouNeed map[string]interface{}
}
func NewSession() *Session {
session := &Session{
UserInfo: make(map[string]string),
SomeThingYouNeed: make(map[string]interface{}),
}
return session
}
func (this *Session) Set(key string, value interface{}) {
this.SomeThingYouNeed[key] = value
}
//Get方法返回interface是需要斷言的(我記得我在前面一兩章說(shuō)過(guò)用法)
//或者你也在Set里直接存Json然后用utils.GetJsonStruct(object)
//寫(xiě)法隨便你,按你的需求來(lái)定
func (this *Session) Get(key string) interface{} {
if _, ok := this.SomeThingYouNeed[key]; ok {
return this.SomeThingYouNeed[key]
} else {
return nil
}
}
我一個(gè)下午摸了半天魚(yú),也就寫(xiě)了這么多東西??纯疵魈煊袥](méi)有BUG,沒(méi)有的話就繼續(xù)補(bǔ)一下redis,然后補(bǔ)一下git和Jenkins的用法,因?yàn)橹蠊緝?nèi)部晉升答辯的時(shí)候會(huì)用到。
代碼已經(jīng)全都push到我的git上面了,有興趣的同學(xué)可以直接clone一份,如果我寫(xiě)的有什么含糊不清的地方,或者有什么問(wèn)題也歡迎指出。
git:https://github.com/nds15763/ginMall