从零开始Gin Web+Vue商城的搭建(四)-- 重构用户模块和框架设计

我觉得差不多该更新了,挖个坑明天写,十一之前BUG都清完了,也没什么事了。

这一章按理来说应该是“商城首页信息和购物车模块”,但是入职新公司以后我看到了好多高端操作,所以顺道重写一下之前乱七八糟的框架。

而且golang的web项目相对于那些重型的高封装语言比如.net MVC项目来说要好很多,用起来很自由。前后分离,只要支持REST就可以了,并不需要前端再去格式化model,虽然也有好处但总体感觉用起来比较笨重。

然后想起上一章好像还坑在session那呢,我晚上回去好好看一下。


今天来填坑

看一下我们之前惨的不行的main方法

func main() {
    router := gin.Default()
    RouterInit(router)
    Helper.Dbinit()
    router.Run(":8081")
}

虽然功能是实现了,但是及其简易。不光连接字符串是写死的,一些初始化函数也全都没有封装。一个灵活的网站需要很多配置文件,还要相对应的log跟踪及错误处理,我们改一下main文件让启动函数更丰富。

func main() {
    //根据shell文件去启动编译好的go文件
    //shell命令:./ginmall -c web-config.toml

    //定义flag参数,返回一个相应的指针
    cfgFlag := flag.String("c", "", "配置文件路径")
    //调用flag.Parse()解析命令行参数到定义的flag
    flag.Parse()

    //解析配置文件
    params := config.NewConfiguration()

    if len(*cfgFlag) > 0 { //cfgFlag是*string型,取地址符
        //
        params.InitFromFile(*cfgFlag)
    } else {
        fmt.Println("配置文件错误,请检查内容格式")
    }

    //设置time包时区(time包是个很神奇的东西,默认采用UTC时区,最好设置一下)
    //Asia包里没有北京时区,可以写成Asia/Shanghai,小伙子们注意一下。
    //具体的时区命名可以解压$GOROOT/lib/time/zoneinfo.zip 这个文件打开查看。
    location, err := time.LoadLocation("Local")
    if err != nil {
        fmt.Println("本地时区初始化失败! " + err.Error())
        os.Exit(1)
    }
    fmt.Println("本地时区初始化成功!")

    //初始化logger
    logger := logger.NewLogger()
    fmt.Println("日志初始化成功!")

    //初始化服务
    server := "server"
    if server == nil {
        logger.Error("服务初始化失败")
        os.Exit(1)
    }

    //启动服务
    server.Start()

    // router := gin.Default()
    // RouterInit(router)
    // Helper.Dbinit()
    // router.Run(":8081")
}

用到的三个包,config,logger,server

//config.go
package config

import (
    "fmt"

    "github.com/BurntSushi/toml"
)

type Configuration struct {
    Port string `toml:"port"` // 服务器监听端口

    // 数据库相关参数
    DbUrl    string `toml:"database_url"`    // 数据库URL
    DbPort   string `toml:"database_port"`   // 数据库端口
    DbName   string `toml:"database_name"`   // 数据库名称
    DbUser   string `toml:"database_user"`   // 数据库用户名
    DbPasswd string `toml:"database_passwd"` // 数据库密码

    // Redis数据库相关参数
    RedisUrl    string `toml:"redis_url"`    // 数据库URL
    RedisPort   string `toml:"redis_port"`   // 数据库端口
    RedisPasswd string `toml:"redis_passwd"` // 数据库密码

    // 日志相关参数
    LogLevel string `toml:"log_level"`
    LogDest  string `toml:"log_dest"`
    LogDir   string `toml:"log_dir"`
}

func NewConfiguration() *Configuration {

    return &Configuration{ // 在此提供默认值(为所有参数提供默认值)
        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) { // 用于启动时加载配置文件

    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方法我没具体写,有兴趣的朋友按需实现吧
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 //系统配置
    logger   *logger.Logger        //日志
    location *time.Location        // 时区信息
    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 {

    // 初始化数据库连接
    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)
    }

    //第一章写的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()
}

展开说一下这个service层吧,看到我还注释掉了Redis服务。其实你可以在service层里实例化多个DB连接,小项目也没有什么必要非得写工厂类,需要用哪个就调用,我觉得这个是自己手搭框架最舒服的地方。

然后看一下Dao层,我只是在Dao层初步封装了一下orm框架,在调用的时候可以直接通过GetEngine方法获取orm的实例。而且gorm框架底层支持手写sql,我研究的不多,喜欢学习的朋友可以点进去看一下源码。功能比较全。

然后这个orm框架,我不知道别的公司或者国内大趋势是什么样子的,用的多不多、好不好用我都不太清楚,我只是捡起手头的东西拿来试试而已。不过这些都是小事情,代码已经贴出来了自己实例化一下就好了。

//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用于设置最大打开的连接数
    //SetMaxIdleConns用于设置闲置的连接数
    db.DB().SetMaxIdleConns(10)
    db.DB().SetMaxOpenConns(100)

    // 启用Logger,显示详细日志
    db.LogMode(true)

    // ORM自动迁移模式
    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层里还写了一个httpserver,这个主要还是业务层(我架构学的很烂,说错了请指出)。实现系统的缓存及路由功能。
我在之前章节也讲了一下session,当时主要是用的插件,后来其实经过一些项目的实战以后(尤其是从.net脱坑以后)发现其实之前脑子很僵化。主要是.net的session就是直接在httpcontext里写好了了,用起来很无脑,而且还挺好使的,所以也没有很在意。
等真的到golang的实战时候,去问同事“我怎么把一个常用表数据存到session里”的时候,同事告诉我你可以直接写全局变量。然后我瞬间就懵掉了,甚至不知道全局变量到底怎么写。翻了半天代码,他们把所有用到的表变量,包括用户信息什么的在初始化的时候就已经写好了。
但是这样也有局限性,也就是现在公司很小,只是单台服务器,等需要搭建集群的时候就会出现内存不同步的问题,所以我推荐大家还是写到redis里。
等之后一两个章节我会把这个代码继续完善,加一个redis的服务。我之前也写过一章redis存kv的简单思想,到十一在家没事干的时候补一篇文章,写一些用户权限及大量表关联的这些解决方法和思想

//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,因为session本质上也是存储在内存之中,golang也并没有原生支持session,所以可以直接将所有信息直接保存在系统里
//  也可以存储静态表,也可以存储用户信息
//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)
}

上面这部分代码其实也就是把之前写在router里的逻辑粘过来罢了。
session类我没有仔细写,以后要被redis替换掉,只是简单写了get和set方法。
需要注意的一点就是用session的时候,变量接收interface需要断言,用反射也好事先用之前写过的fmt.Sprintf("%T",)也好,都可以实现。

啊,晚上看了篇文章,然后发现自己理解错了,这东西不能叫session,只能叫全局变量,等过两天仔细研究明白了再写这部分。

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是需要断言的(我记得我在前面一两章说过用法)
//或者你也在Set里直接存Json然后用utils.GetJsonStruct(object)
//写法随便你,按你的需求来定
func (this *Session) Get(key string) interface{} {

    if _, ok := this.SomeThingYouNeed[key]; ok {

        return this.SomeThingYouNeed[key]

    } else {
        return nil
    }
}

我一个下午摸了半天鱼,也就写了这么多东西。看看明天有没有BUG,没有的话就继续补一下redis,然后补一下git和Jenkins的用法,因为之后公司内部晋升答辩的时候会用到。
代码已经全都push到我的git上面了,有兴趣的同学可以直接clone一份,如果我写的有什么含糊不清的地方,或者有什么问题也欢迎指出。
git:https://github.com/nds15763/ginMall

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343