后台由golang实现
golang版本1.17
技术栈
mysql 5.7
redis
golang
negroni
jwt
gorilla mux
websocket
applet: 使用网上开源项目模板改造
先睹为快
后台:
applet:
系统架构
1.linux 安装nginx: nginx申请ssl证书,配置https,将服务打到上游的golang web server服务
2.后台管理功能使用golang html template技术
3.applet功能,前后端分离,使用json数据格式交互
项目结构
项目启动流程
启动入口代码如下:
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":"<db name>",
"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
}
}
功能特点举例
项目架构
- 目前是单体应用,应付小规模的商家足够了,后续可能会考虑douyu-jupiter微服务或者dubbo-go服务
- 整个项目打包后只有10M大小,很轻量(不包括静态html, static文件)
- 静态文件static目录可以放在nginx相应目录,即动静分离
websocket通知新订单
- 管理后台页面和go server保持websocket链接(heartbeat)
- 当有新订单时,server端发送一个标识数据到管理后台浏览器端,html5页面播放一段提示音提醒商家来新订单啦~
邮件通知用户
用户注册、找回密码、下单成功 等业务场景时,需要发送邮件给用户,我这里使用golang sdk内置的go mail功能,减少依赖
定时器自动取消订单
用户下单后产生定时任务(golang自身定时器),15分钟内未支付时,自动取消订单并回滚相应的sku库存