工信部备案网站查询,门店设计,如何做响应式的网站,网站开发的研究思路以下记录了使用go语言框架Beego#xff0c;Mysql数据库#xff0c;Redis数据库实现一个点菜/菜谱应用API的全过程。 技术方案 github地址
数据库设计
新建数据库#xff1a;
CREATE DATABASE menu;新建数据表#xff1a;
CREATE TABLE menu (
id int(10) unsigned NOT … 以下记录了使用go语言框架BeegoMysql数据库Redis数据库实现一个点菜/菜谱应用API的全过程。 技术方案 github地址
数据库设计
新建数据库
CREATE DATABASE menu;新建数据表
CREATE TABLE menu (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
title varchar(200) NOT NULL DEFAULT ,
content text NOT NULL,
pictures varchar(1000) NOT NULL DEFAULT ,
tags varchar(1000) NOT NULL DEFAULT ,
status tinyint(4) NOT NULL DEFAULT 0,
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINEInnoDB DEFAULT CHARSETutf8;
CREATE TABLE data (id int(10) unsigned NOT NULL AUTO_INCREMENT,menu_id int(10) unsigned NOT NULL DEFAULT 0,like_number int(10) NOT NULL DEFAULT 0,visit_number int(10) NOT NULL DEFAULT 0,order_number int(10) NOT NULL DEFAULT 0,create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id),UNIQUE KEY unique_menu (menu_id)
) ENGINEInnoDB DEFAULT CHARSETutf8
CREATE TABLE order_info (id int(10) unsigned NOT NULL AUTO_INCREMENT,menu_id_list varchar(200) NOT NULL DEFAULT ,status tinyint(4) NOT NULL DEFAULT 0,create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINEInnoDB DEFAULT CHARSETutf8;
生成models层
进入终端项目执行命令生成数据库表对应的models
bee generate appcode -tablesdata -drivermysql -connroot:123456tcp(127.0.0.1:3306)/menu -level1
bee generate appcode -tablesorder_info -drivermysql -connroot:123456tcp(127.0.0.1:3306)/menu -level1
bee generate appcode -tablesmenu -drivermysql -connroot:123456tcp(127.0.0.1:3306)/menu -level1 生成data、menu、order_info对应的model文件 我们数据库设置了创建时间和更新时间的自动更新
配置环境变量连接数据库
conf/app.conf中添加
# Mysql setting
mysqluser root
mysqlpass root
mysqlhost 127.0.0.1
mysqldb liuyan
mysqlport 3306新建core文件用于数据库连接的核心代码
package modelsimport (fmtgithub.com/beego/beego/v2/client/ormbeego github.com/beego/beego/v2/server/web_ github.com/go-sql-driver/mysql
)func Init() {username, err : beego.AppConfig.String(mysqlname)if err ! nil {fmt.Println(err:, err)}password, err : beego.AppConfig.String(mysqlpass)host, err : beego.AppConfig.String(mysqlhost)database, err : beego.AppConfig.String(mysqldb)port, err : beego.AppConfig.String(mysqlport)dsn : username : password tcp( host : port )/ database ?charsetutf8locLocalerr orm.RegisterDataBase(default, mysql, dsn)if err ! nil {fmt.Println(err::, err)}
}之后在main.go中初始化数据库连接
package mainimport (menu_api/models_ menu_api/routersbeego github.com/beego/beego/v2/server/web
)func init() {models.Init()
}func main() {if beego.BConfig.RunMode dev {beego.BConfig.WebConfig.DirectoryIndex truebeego.BConfig.WebConfig.StaticDir[/swagger] swagger}beego.Run()
}bee run . 没有打印错误则说明数据库连接成功
日志设置
新建logs/logs.go文件进行初始化日志
package logsimport (github.com/beego/beego/v2/core/logs
)func Init() {// 设置日志模式为文件输出的目录logs.SetLogger(file, {filename:logs/menu.log})
}将日志模式设置为文件格式输出的目录为logs/menu.log(无需手动创建) 在main.go中初始化日志 bee run . 后会发现新建了文件logs/menu.log并且可以在其中发现项目打印的日志关于日志可以参考官方文档Welcome to Beego | Beego
核心逻辑
菜单逻辑
注册路由
新增逻辑
beego.NSNamespace(/menu,beego.NSInclude(controllers.MenuController{},),), models层已经自动构建只需要实现controller即可
新建controller/menu.go
查询列表查询详情增加更新方法
package controllersimport (encoding/jsonerrorsgithub.com/beego/beego/v2/core/logsbeego github.com/beego/beego/v2/server/webmenu_api/modelsstrings
)type MenuController struct {beego.Controller
}// Title GetList
// Description get Menu List
// Param query query string false Filter. e.g. col1:v1,col2:v2 ...
// Param fields query string false Fields returned. e.g. col1,col2 ...
// Param sortby query string false Sorted-by fields. e.g. col1,col2 ...
// Param order query string false Order corresponding to each sortby field, if single value, apply to all sortby fields. e.g. desc,asc ...
// Param limit query string false Limit the size of result set. Must be an integer
// Param offset query string false Start position of result set. Must be an integer
// Success 200 {object} models.Menu
// router / [get]
func (m *MenuController) GetList() {var fields []stringvar sortby []stringvar order []stringvar query make(map[string]string)var limit int64 10var offset int64// fields: col1,col2,entity.col3if v : m.GetString(fields); v ! {fields strings.Split(v, ,)}// limit: 10 (default is 10)if v, err : m.GetInt64(limit); err nil {limit v}// offset: 0 (default is 0)if v, err : m.GetInt64(offset); err nil {offset v}// sortby: col1,col2if v : m.GetString(sortby); v ! {sortby strings.Split(v, ,)}// order: desc,ascif v : m.GetString(order); v ! {order strings.Split(v, ,)}// query: k:v,k:vif v : m.GetString(query); v ! {for _, cond : range strings.Split(v, ,) {kv : strings.SplitN(cond, :, 2)if len(kv) ! 2 {m.Data[json] errors.New(Error: invalid query key/value pair)m.ServeJSON()return}k, v : kv[0], kv[1]query[k] v}}menList, err : models.GetAllMenu(query, fields, sortby, order, offset, limit)if err ! nil {logs.Error(Get Database Menu List Error, ERR:, err)m.Data[json] errors.New(Error: Get Database Menu Error)}m.Data[json] menListm.ServeJSON()
}// Title Get
// Description get menu by id
// Param id path int true The key for staticblock
// Success 200 {object} models.Menu
// Failure 403 :id is empty
// router /:id [get]
func (m *MenuController) Get() {mid : 0if v, err : m.GetInt(:id); err nil {mid v}if mid ! 0 {menu, err : models.GetMenuById(mid)if err ! nil {logs.Error(Get Database Menu Error, ERR:, err)m.Data[json] err.Error()} else {m.Data[json] menu}}m.ServeJSON()
}// Title CreateMenu
// Description create menus
// Param body body models.Meau true body for user content
// Success 200 {int} models.Menu.Id
// Failure 403 body is empty
// router / [post]
func (m *MenuController) Post() {var menu models.Menuerr : json.Unmarshal(m.Ctx.Input.RequestBody, menu)if err ! nil || menu.Title || menu.Content || menu.Pictures {if err ! nil {logs.Error(Add Database Menu Unmarshal Error, ERR:, err)} else {err errors.New(title or content or picture is not empty)}m.Data[json] err.Error()m.ServeJSON()return}mid, err : models.AddMenu(menu)if err ! nil {logs.Error(Add Database Menu Error, ERR:, err)m.Data[json] err.Error()m.ServeJSON()return}m.Data[json] map[string]int64{mid:: mid}m.ServeJSON()
}// Title Update
// Description update the menu
// Param id path string true The menu_id you want to update
// Param body body models.Menu true body for user content
// Success 200 {object} models.Menu
// Failure 403 :id is not int
// router /:id [put]
func (m *MenuController) Put() {mid, err : m.GetInt(:id)var menu models.Menuerr json.Unmarshal(m.Ctx.Input.RequestBody, menu)if err ! nil {logs.Error(Add Database Menu Error, ERR:, err)m.Data[json] err.Error()m.ServeJSON()return}menu.Id midif mid ! 0 {err : models.UpdateMenuById(menu)if err ! nil {m.Data[json] err.Error()} else {m.Data[json] update success}}m.ServeJSON()
}订单逻辑
注册路由
新增逻辑 beego.NSNamespace(/order_info,beego.NSInclude(controllers.OrderInfoController{},),), 新建controller/order_info.go
查询列表查询详情增加更新方法
package controllersimport (encoding/jsonerrorsgithub.com/beego/beego/v2/core/logsbeego github.com/beego/beego/v2/server/webmenu_api/modelsstrings
)type OrderInfoController struct {beego.Controller
}// Title GetList
// Description get OrderInfo List
// Param query query string false Filter. e.g. col1:v1,col2:v2 ...
// Param fields query string false Fields returned. e.g. col1,col2 ...
// Param sortby query string false Sorted-by fields. e.g. col1,col2 ...
// Param order query string false Order corresponding to each sortby field, if single value, apply to all sortby fields. e.g. desc,asc ...
// Param limit query string false Limit the size of result set. Must be an integer
// Param offset query string false Start position of result set. Must be an integer
// Success 200 {object} models.OrderInfo
// router / [get]
func (o *OrderInfoController) GetList() {var fields []stringvar sortby []stringvar order []stringvar query make(map[string]string)var limit int64 10var offset int64// fields: col1,col2,entity.col3if v : o.GetString(fields); v ! {fields strings.Split(v, ,)}// limit: 10 (default is 10)if v, err : o.GetInt64(limit); err nil {limit v}// offset: 0 (default is 0)if v, err : o.GetInt64(offset); err nil {offset v}// sortby: col1,col2if v : o.GetString(sortby); v ! {sortby strings.Split(v, ,)}// order: desc,ascif v : o.GetString(order); v ! {order strings.Split(v, ,)}// query: k:v,k:vif v : o.GetString(query); v ! {for _, cond : range strings.Split(v, ,) {kv : strings.SplitN(cond, :, 2)if len(kv) ! 2 {o.Data[json] errors.New(Error: invalid query key/value pair)o.ServeJSON()return}k, v : kv[0], kv[1]query[k] v}}orderList, err : models.GetAllOrderInfo(query, fields, sortby, order, offset, limit)if err ! nil {logs.Error(Get Database OrderInfo List Error, ERR:, err)o.Data[json] errors.New(Error: Get Database OrderInfo Error)}o.Data[json] orderListo.ServeJSON()
}// Title Get
// Description get orderInfo by id
// Param id path int true The key for staticblock
// Success 200 {object} models.OrderInfo
// Failure 403 :id is empty
// router /:id [get]
func (o *OrderInfoController) Get() {oid : 0if v, err : o.GetInt(:id); err nil {oid v}if oid ! 0 {orderInfo, err : models.GetOrderInfoById(oid)if err ! nil {logs.Error(Get Database OrderInfo Error, ERR:, err)o.Data[json] err.Error()} else {o.Data[json] orderInfo}}o.ServeJSON()
}// Title CreateOrderInfo
// Description create OrderInfo
// Param body body models.OrderInfo true body for user content
// Success 200 {int} models.OrderInfo.Id
// Failure 403 body is empty
// router / [post]
func (o *OrderInfoController) Post() {var orderInfo models.OrderInfoerr : json.Unmarshal(o.Ctx.Input.RequestBody, orderInfo)if err ! nil || orderInfo.MenuIdList {if err ! nil {logs.Error(Add Database OrderInfo Unmarshal Error, ERR:, err)} else {err errors.New(title or content or picture is not empty)}o.Data[json] err.Error()o.ServeJSON()return}oid, err : models.AddOrderInfo(orderInfo)if err ! nil {logs.Error(Add Database OrderInfo Error, ERR:, err)o.Data[json] err.Error()o.ServeJSON()return}o.Data[json] map[string]int64{oid:: oid}o.ServeJSON()
}// Title Update
// Description update the OrderInfo
// Param id path string true The menu_id you want to update
// Param body body models.OrderInfo true body for user content
// Success 200 {object} models.OrderInfo
// Failure 403 :id is not int
// router /:id [put]
func (o *OrderInfoController) Put() {mid, err : o.GetInt(:id)var orderInfo models.OrderInfoerr json.Unmarshal(o.Ctx.Input.RequestBody, orderInfo)if err ! nil {logs.Error(Add Database OrderInfo Error, ERR:, err)o.Data[json] err.Error()o.ServeJSON()return}orderInfo.Id midif mid ! 0 {err : models.UpdateOrderInfoById(orderInfo)if err ! nil {o.Data[json] err.Error()} else {o.Data[json] update success}}o.ServeJSON()
} 数据更新
使用redis缓存定时任务的方式维护data数据库先存储在redis中再由定时脚本进行更新同步redis数据到mysql中。
1.安装redis包
go get github.com/gomodule/redigo/redis
2.配置环境变量
redis_host localhost redis_port 6379 redis_password redis_db 0
3.redis初始化连接 func RedisContent() redis.Conn {redis_host, err : beego.AppConfig.String(redis_host)redis_port, err : beego.AppConfig.String(redis_port)redis_password, err : beego.AppConfig.String(redis_password)redis_db, err : beego.AppConfig.String(redis_db)if err ! nil {logs.Error(database get appConfig err, err)}Redis_pool : redis.Pool{MaxIdle: 1, //最大空闲连接数MaxActive: 10, // 最大连接数IdleTimeout: 180 * time.Second, //空闲连接超时时间Wait: true, // 超过最大连接数的操作:等待Dial: func() (redis.Conn, error) {c, err : redis.Dial(tcp, fmt.Sprintf(%s:%s, redis_host, redis_port))if err ! nil {return nil, err}if redis_password ! {if _, err : c.Do(AUTH, redis_password); err ! nil {c.Close()return nil, err}}if redis_db ! {if _, err : c.Do(SELECT, redis_db); err ! nil {c.Close()return nil, err}}return c, nil},}return Redis_pool.Get()
}新建controller/common.go文件
逻辑redis的菜单ID的key加一并且添加到redis的菜单ID列表。
这个文件存放公共的function
package controllersimport (beego github.com/beego/beego/v2/server/webmenu_api/modelstime
)const (OptLikeNum like_numOptOrderNum order_numOptVisitNum visit_num
)func getRedisKey() (string, string, error) {// 获取key配置numKey, err : beego.AppConfig.String(redis_menu_num_key)if err ! nil {return , , err}updateKey, err : beego.AppConfig.String(redis_menu_update_key)if err ! nil {return , , err}return numKey, updateKey, nil
}func redisNumUpdate(mid string, operateType string) (error, interface{}) {numKey, updateKey, err : getRedisKey()if err ! nil {return err, nil}// like_numberconn, err : models.RedisContent()if err ! nil {return err, nil}defer conn.Close()newValue, err : conn.Do(HINCRBY, numKeymid, operateType, 1)if err ! nil {return err, nil}// TODO 确定是否为第一次1_, err conn.Do(SADD,updateKeytime.Now().Format(20060102), mid)if err ! nil {return err, nil}return nil, newValue
}点赞数量更新redis
注册路由
新增逻辑
beego.NSNamespace(/data,beego.NSInclude(controllers.DataController{},),), 新建controller/data.go文件
实现点赞数量存储
package controllersimport (github.com/beego/beego/v2/core/logsbeego github.com/beego/beego/v2/server/web_ menu_api/models
)type DataController struct {beego.Controller
}// Title Update
// Description update the menu
// Param menu_id path string true The menu_id you want to update
// Success 200 {int} 1
// Failure 403 :menu_id is not int
// router /:menu_id [put]
func (d *DataController) Put() {mid : d.GetString(:menu_id)// 更细rediserr, num : redisNumUpdate(mid, OptLikeNum)if err ! nil {logs.Error(Get AppConfig Error, ERR:, err)d.Data[json] err.Error()d.ServeJSON()return}d.Data[json] numd.ServeJSON()
}更新订单数量和浏览量
浏览量主要在菜单详情接口中点击一次1 err, _ : redisNumUpdate(strconv.Itoa(mid), OptBrowseNum)if err ! nil {logs.Error(Get AppConfig Error, ERR:, err)// 不影响主流程不return} 订单数量主要在订单添加接口中下单一次1
下单在更新接口中改订单状态为已下单:
// Title Update
// Description update the OrderInfo
// Param id path string true The menu_id you want to update
// Param body body models.OrderInfo true body for user content
// Success 200 {object} models.OrderInfo
// Failure 403 :id is not int
// router /:id [put]
func (o *OrderInfoController) Put() {oId, err : o.GetInt(:id)var orderInfo models.OrderInfoerr json.Unmarshal(o.Ctx.Input.RequestBody, orderInfo)if err ! nil {logs.Error(Add Database OrderInfo Error, ERR:, err)o.Data[json] err.Error()o.ServeJSON()return}orderInfo.Id oIdif oId ! 0 {err : models.UpdateOrderInfoById(orderInfo)if err ! nil {o.Data[json] err.Error()} else {o.Data[json] update success}}if orderInfo.Status 1 {// 遍历菜单列表对每个菜进行加一操作menuIdList : strings.Split(orderInfo.MenuIdList, ,)for _, menuId : range menuIdList {err, _ redisNumUpdate(menuId, OptOrderNum)if err ! nil {logs.Error(Get AppConfig Error, ERR:, err)// 不影响主流程不return}}}o.ServeJSON()
}同步redis数据到数据库
定时任务 func SyncDataFromRedisToMysql() error {numKey, updateKey, err : getRedisKey()if err ! nil {return err}// like_numberconn, err : models.RedisContent()if err ! nil {return err}defer conn.Close()// 获取集合数据menuIds, err : redis.Strings(conn.Do(SMEMBERS, updateKeytime.Now().Format(20060102)))if err ! nil {return err}for _, menuId : range menuIds {// 根据Id获取数据var fields []stringvar sortby []stringvar order []stringvar query map[string]string{MenuId: menuId,}var limit int64 10var offset int64ml, err : models.GetAllData(query, fields, sortby, order, offset, limit)if err ! nil {return err}// 根据menuId获取更新mysql数据values, err : redis.StringMap(conn.Do(HGETALL, numKeymenuId))if err ! nil {return err}// 先marshal再Unmarshalvar data models.Dataif len(ml) 0 ml ! nil {marshal, err : json.Marshal(ml[0])if err ! nil {return err}err json.Unmarshal((marshal), data)}likeNum, err : strconv.Atoi(values[OptLikeNum])orderNum, err : strconv.Atoi(values[OptOrderNum])visitNum, err : strconv.Atoi(values[OptVisitNum])data.LikeNumber likeNumdata.OrderNumber orderNumdata.VisitNumber visitNum// 如果找到则更新找不到则添加if len(ml) 1 || ml nil {mId, _ : strconv.Atoi(menuId)data.MenuId mId_, err models.AddData(data)} else {err models.UpdateDataById(data)}if err ! nil {return err}}return nil
}main.go中设置协程五分钟刷新一次
func init() {logs.Init()models.Init()controllers.SyncDataFromRedisToMysql()go func() {for {// 每隔五分钟执行一次同步方法time.Sleep(5 * time.Minute)err : controllers.SyncDataFromRedisToMysql()if err ! nil {beeLogs.Error(Data SyncDataFromRedisToMysql Error:, err)} else {beeLogs.Info(time.Now(), SyncDataFromRedisToMysql Success)}}}()
} 文件上传
需要上传图片到服务器
注册路由
新增逻辑
beego.NSNamespace(/common,beego.NSInclude(controllers.CommonController{},),),
controller/commpn.go中新增逻辑
// router /upload [post]
// Summary 上传图片
// Description 上传图片到服务器
// Accept multipart/form-data
// Param image formData file true 图片文件
// Success 200 {string} success 上传成功
// Failure 400 {string} error 上传失败
// router /upload [post]
func (c *CommonController) Post() {f, h, err : c.GetFile(image)if err ! nil {c.Ctx.WriteString(File upload failed: err.Error())return}ext : path.Ext(h.Filename)//验证后缀名是否符合要求var AllowExtMap map[string]bool map[string]bool{.jpg: true,.jpeg: true,.png: true,}if _, ok : AllowExtMap[ext]; !ok {c.Ctx.WriteString(后缀名不符合上传要求)return}//创建目录uploadDir : static/upload///构造文件名称rand.Seed(time.Now().UnixNano())randNum : fmt.Sprintf(%d, rand.Intn(9999)1000)hashName : md5.Sum([]byte(time.Now().Format(2006_01_02_15_04_05_) randNum))fileName : fmt.Sprintf(%x, hashName) ext//c.Ctx.WriteString( fileName )fpath : uploadDir fileNamedefer f.Close() //关闭上传的文件不然的话会出现临时文件不能清除的情况err c.SaveToFile(image, fpath)if err ! nil {c.Ctx.WriteString(fmt.Sprintf(%v, err))}}以上就是整体服务端的实现。
运行
生成路由
bee generate routers
生成swagger配置文件并运行
bee run -gendoctrue
访问swagger
http://127.0.0.1:8080/swagger/#/
接口如下