golang实现的商城

小程序商城

  • 后台由golang实现
    • 技术栈
    • 先睹为快
    • 系统架构
    • 项目结构
    • 项目启动流程
  • 功能特点举例
    • 项目架构
    • websocket通知新订单
    • 邮件通知用户
    • 定时器自动取消订单

后台由golang实现

golang版本1.17

技术栈

mysql 5.7
redis
golang
negroni
jwt
gorilla mux
websocket
applet: 使用网上开源项目模板改造

先睹为快

后台:

golang实现的商城_第1张图片
golang实现的商城_第2张图片
golang实现的商城_第3张图片
applet:
golang实现的商城_第4张图片
golang实现的商城_第5张图片
golang实现的商城_第6张图片
golang实现的商城_第7张图片
golang实现的商城_第8张图片

系统架构

golang实现的商城_第9张图片

1.linux 安装nginx: nginx申请ssl证书,配置https,将服务打到上游的golang web server服务
2.后台管理功能使用golang html template技术
3.applet功能,前后端分离,使用json数据格式交互

项目结构

golang实现的商城_第10张图片

项目启动流程

golang实现的商城_第11张图片
启动入口代码如下:

func main() {
	// 初始化配置
	common.StartUp()
	// 初始化路由
	router := routers.InitRoutes()
	//n := negroni.Classic()
	//n.UseHandler(router)
	// 减少 negroni 日志的打印
	recovery := negroni.NewRecovery()
	recovery.Logger = common.Error
	n := negroni.New(recovery, negroni.Wrap(router))

	server := &http.Server{
		Addr:    common.AppConfig.Server,
		Handler: n,
	}

	common.Info.Println("Listening on:", server.Addr)
	server.ListenAndServe()
}

初始化配置部分如下:

func loadAppConfig() {

	file, err := os.Open(filepath.Join(CurDir, "common/config.json"))
	if err != nil {
		log.Fatalf("[loadConfig]:%s\n", err)
	}
	defer file.Close()

	AppConfig = configuration{}
	err = json.NewDecoder(file).Decode(&AppConfig)
	if err != nil {
		log.Fatalf("[loadAppConfig]:%s\n", err)
	}
	initDBConfig()

	initRedisConfig(AppConfig.RedisConfig)

	InitLog()
}

初始化路由部分如下:

func InitRoutes() *mux.Router {

	router := mux.NewRouter().StrictSlash(false)

	// 静态文件映射
	router.PathPrefix("/static/").Handler(http.StripPrefix("/static/",
		http.FileServer(http.Dir("views/static/"))))

	// applet 接口模块开始----------------------

	// 首页
	router = applet.SetIndexRoutes(router)
	// 分类
	router = applet.SetCatalogRoutes(router)
	// ... 其它模块

	// -----------------applet接口结束-------------------------
	// -----------------后台接口开始-------------------------
	// 首页
	router = SetIndexRoutes(router)
	// banner
	router = SetBannerRoutes(router)
	// ... 其它模块
	// -----------------后台接口结束-------------------------
	return router
}

// 后台banner 路由设置(完整)
func SetBannerRoutes(router *mux.Router) *mux.Router {

	bannerRouter := mux.NewRouter()

	subrouter := bannerRouter.PathPrefix("/commerce/api/v2/banner").Subrouter()

	subrouter.HandleFunc("/list", controllers.ListBanner).Methods("GET")
	subrouter.HandleFunc("/addUI", controllers.AddBannerUIHandler).Methods("GET")
	subrouter.HandleFunc("/updateUI", controllers.GetBannerHandler).Methods("GET")
	subrouter.HandleFunc("/addOrEdit", controllers.AddOrEditBannerHandler).Methods("POST")
	subrouter.HandleFunc("/updateStatus", controllers.UpdateBannerStatusHandler).Methods("POST")

	router.PathPrefix("/commerce/api/v2/banner").Handler(negroni.New(
		// 拦截器:校验授权
		negroni.HandlerFunc(common.Authorize),
		negroni.Wrap(bannerRouter),
	))
	return router
}

// bannerController
func init() {

	common.Templates["banner"] = template.Must(template.ParseFiles(filepath.Join(common.CurDir, "views/templates/banner.html")))
	common.Templates["banner_edit"] = template.Must(template.ParseFiles(filepath.Join(common.CurDir, "views/templates/banner_edit.html")))
}
// ...
func GetBannerHandler(w http.ResponseWriter, r *http.Request) {

	id, err := strconv.Atoi(r.FormValue("id"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	c, err := data.GetBannerById(id)
	if err != nil {
		common.Templates["5xx"].Execute(w, err)
		return
	}
	common.Templates["banner_edit"].Execute(w, c)
}

// applet indexController

// 人气推荐商品接口
func ListHotGoods(w http.ResponseWriter, r *http.Request) {

	var res vo.IndexHotGoodsVo

	skuList, err := data.ListHotGoodsSku()
	if err != nil {
		common.Error.Printf("ListHotGoods err:%v", err)
		j, _ := json.Marshal(vo.Err(err.Error()))
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		w.Write(j)
		return
	}
	var voList []vo.AppSku
	for _, k := range skuList {
		voList = append(voList, vo.AppSku{Id: k.Id, Name: k.Name, MainImg: k.MainImg, RetailPrice: k.RealPrice, Desc: k.Description})
	}
	res.HotGoodsList = voList

	j, _ := json.Marshal(vo.Ok(res))
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(j)
}

配置

{
    "Server" : ":9088",
    "SqlConfig" : {
        "DBName":"",
        "Addr":"<你的mysql地址>:3306",
        "User":"<用户名>",
        "Passwd":"<密码>",
        "Net": "tcp",
        "AllowNativePasswords": true
    },
    "ImgUploadPath": "views/imgs/", // 使用了oss后这行没有用,记得删除我(注释,不然会报错)
    "CookieDomain" : "localhost",
    "OssConfig" : {
        "Endpoint" : "<>",
        "AccessKeyId" : "<>",
        "AccessKeySecret" : "<>",
        "BucketName" : "<>"
    },
    "RedisConfig" : {
        "MaxIdle" : 10,
        "MaxActive": 0,
        "IdleTimeout": 10,
        "Addr" : "<>:6379",
        "Db" : 0,
        "Pwd" : "<>"
    },
    "LogConfig" : {
        "FileName" : "j.log",
        "MaxBytes" : 104857600,
        "BackUpCount": 2
    }
}

功能特点举例

项目架构

  1. 目前是单体应用,应付小规模的商家足够了,后续可能会考虑douyu-jupiter微服务或者dubbo-go服务
  2. 整个项目打包后只有10M大小,很轻量(不包括静态html, static文件)
  3. 静态文件static目录可以放在nginx相应目录,即动静分离

websocket通知新订单

  1. 管理后台页面和go server保持websocket链接(heartbeat)
  2. 当有新订单时,server端发送一个标识数据到管理后台浏览器端,html5页面播放一段提示音提醒商家来新订单啦~

邮件通知用户

用户注册、找回密码、下单成功 等业务场景时,需要发送邮件给用户,我这里使用golang sdk内置的go mail功能,减少依赖

定时器自动取消订单

用户下单后产生定时任务(golang自身定时器),15分钟内未支付时,自动取消订单并回滚相应的sku库存

源码

你可能感兴趣的:(golang,golang,后端,javascript,小程序,websocket)