留子厨房开发日志

news2024/12/23 10:50:35

以下记录了使用go语言框架Beego,Mysql数据库,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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

生成models层

进入终端项目,执行命令生成数据库表对应的models:

bee generate appcode -tables="data" -driver=mysql -conn="root:123456@tcp(127.0.0.1:3306)/menu" -level=1
bee generate appcode -tables="order_info" -driver=mysql -conn="root:123456@tcp(127.0.0.1:3306)/menu" -level=1
bee generate appcode -tables="menu" -driver=mysql -conn="root:123456@tcp(127.0.0.1:3306)/menu" -level=1

 生成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 models

import (
	"fmt"
	"github.com/beego/beego/v2/client/orm"
	beego "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 + "?charset=utf8&loc=Local"

	err = orm.RegisterDataBase("default", "mysql", dsn)
	if err != nil {
		fmt.Println("err::", err)
	}
}

之后在main.go中初始化数据库连接:

package main

import (
	"menu_api/models"
	_ "menu_api/routers"

	beego "github.com/beego/beego/v2/server/web"
)

func init() {
	models.Init()
}

func main() {
	if beego.BConfig.RunMode == "dev" {
		beego.BConfig.WebConfig.DirectoryIndex = true
		beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
	}
	beego.Run()
}

bee run .

没有打印错误,则说明数据库连接成功 

日志设置

新建logs/logs.go文件进行初始化日志:

package logs

import (
	"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 controllers

import (
	"encoding/json"
	"errors"
	"github.com/beego/beego/v2/core/logs"
	beego "github.com/beego/beego/v2/server/web"
	"menu_api/models"
	"strings"
)

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 []string
	var sortby []string
	var order []string
	var query = make(map[string]string)
	var limit int64 = 10
	var offset int64

	// fields: col1,col2,entity.col3
	if 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,col2
	if v := m.GetString("sortby"); v != "" {
		sortby = strings.Split(v, ",")
	}
	// order: desc,asc
	if v := m.GetString("order"); v != "" {
		order = strings.Split(v, ",")
	}
	// query: k:v,k:v
	if 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"] = menList
	m.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 := 0
	if 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.Menu
	err := 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.Menu
	err = 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 = mid
	if 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 controllers

import (
	"encoding/json"
	"errors"
	"github.com/beego/beego/v2/core/logs"
	beego "github.com/beego/beego/v2/server/web"
	"menu_api/models"
	"strings"
)

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 []string
	var sortby []string
	var order []string
	var query = make(map[string]string)
	var limit int64 = 10
	var offset int64

	// fields: col1,col2,entity.col3
	if 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,col2
	if v := o.GetString("sortby"); v != "" {
		sortby = strings.Split(v, ",")
	}
	// order: desc,asc
	if v := o.GetString("order"); v != "" {
		order = strings.Split(v, ",")
	}
	// query: k:v,k:v
	if 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"] = orderList
	o.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 := 0
	if 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.OrderInfo
	err := 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.OrderInfo
	err = 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 = mid
	if 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 controllers

import (
	beego "github.com/beego/beego/v2/server/web"
	"menu_api/models"
	"time"
)

const (
	OptLikeNum  = "like_num"
	OptOrderNum = "order_num"
	OptVisitNum = "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_number++
	conn, err := models.RedisContent()
	if err != nil {
		return err, nil
	}
	defer conn.Close()
	newValue, err := conn.Do("HINCRBY", numKey+mid, operateType, 1)
	if err != nil {
		return err, nil
	}
	// TODO 确定是否为第一次+1
	_, err = conn.Do("SADD",
		updateKey+time.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 controllers

import (
	"github.com/beego/beego/v2/core/logs"
	beego "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")
	// 更细redis
	err, 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"] = num
	d.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.OrderInfo
	err = 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 = oId
	if 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_number++
	conn, err := models.RedisContent()
	if err != nil {
		return err
	}
	defer conn.Close()
	// 获取集合数据
	menuIds, err := redis.Strings(conn.Do("SMEMBERS", updateKey+time.Now().Format("20060102")))
	if err != nil {
		return err
	}
	for _, menuId := range menuIds {
		// 根据Id获取数据
		var fields []string
		var sortby []string
		var order []string
		var query = map[string]string{
			"MenuId": menuId,
		}
		var limit int64 = 10
		var offset int64
		ml, err := models.GetAllData(query, fields, sortby, order, offset, limit)
		if err != nil {
			return err
		}

		// 根据menuId获取更新mysql数据
		values, err := redis.StringMap(conn.Do("HGETALL", numKey+menuId))
		if err != nil {
			return err
		}
		// 先marshal,再Unmarshal
		var data models.Data
		if 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 = likeNum
		data.OrderNumber = orderNum
		data.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 + fileName
	defer f.Close() //关闭上传的文件,不然的话会出现临时文件不能清除的情况
	err = c.SaveToFile("image", fpath)
	if err != nil {
		c.Ctx.WriteString(fmt.Sprintf("%v", err))
	}

}

以上就是整体服务端的实现。

运行

生成路由 

bee generate routers

生成swagger配置文件,并运行

bee run -gendoc=true

访问swagger

http://127.0.0.1:8080/swagger/#/

接口如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1468861.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker 第十九章 : 阿里云个人镜像仓使用

Docker 第十九章 : 阿里云个人镜像仓使用 本章知识点: 如何创建镜像库,如何设置密码,如何登录与退出个人镜像仓,如何本地打镜像,如何将本地镜像推送到个人镜像库。 背景 在项目YapiDocker部署中,因读取mongo:latest 版本不一致,导致后续执行步骤的异常。遇到此场景…

OpenCV Mat实例详解 六

本文将接着OpenCV Mat实例详解继续介绍OpenCV Mat类的操作符及公有成员函数。 Mat & operator Mat & operator (const Mat &m) 将一个Mat对象赋值个另一个Mat对象。 Mat & operator (const MatExpr &expr) 将一个Mat表达式值赋值给Mat对象 Mat & op…

【高德地图】Android高德地图绘制标记点Marker

&#x1f4d6;第4章 Android高德地图绘制标记点Marker ✅绘制默认 Marker✅绘制多个Marker✅绘制自定义 Marker✅Marker点击事件✅Marker动画效果✅Marker拖拽事件✅绘制默认 Infowindow&#x1f6a9;隐藏InfoWindow 弹框 ✅绘制自定义 InfoWindow&#x1f6a9;实现 InfoWindow…

Covalent Network(CQT)与 Movement Labs 达成合作,重新定义 M2 系统区块链数据可用性与性能

Covalent Network&#xff08;CQT&#xff09;是行业领先的多链索引器&#xff0c;正在与 Movement Labs 的 M2 展开具有突破性意义的合作。M2 是以太坊上的首个 Move-EVM&#xff08;MEVM&#xff09;ZK rollup 。这一战略合作标志着先进的实时数据索引和部署工具&#xff0c;…

Sora - 探索AI视频模型的无限可能

Sora - 探索AI视频模型的无限可能 随着人工智能技术的飞速发展,AI视频模型已成为科技领域的新热点。而在这个浪潮中,OpenAI推出的首个AI视频模型Sora,以其卓越的性能和前瞻性的技术,引领着AI视频领域的创新发展。让我们将一起探讨Sora的技术特点、应用场景以及对未来创作方…

高级RAG:使用RAGAs + LlamaIndex进行RAG评估,包括原理、图和代码

原文地址&#xff1a;Using RAGAs LlamaIndex for RAG evaluation 2024 年 2 月 5 日 如果您已经为实际的业务系统开发了检索增强生成&#xff08;Retrieval Augmented Generation, RAG&#xff09;应用程序&#xff0c;那么您可能会关心它的有效性。换句话说&#xff0c;您…

【大数据】Flink 内存管理(三):TaskManager 内存分配(理论篇)

Flink 内存管理&#xff08;三&#xff09;&#xff1a;TaskManager 内存分配 1.配置 Total Memory2.配置 Heap and Managed Memory2.1 Task (Operator) Heap Memory2.2 Managed Memory 3.配置 Off-Heap Memory&#xff08;Direct or Native&#xff09;4.详细内存模型5.Framew…

现在学Oracle是49年入国军么?

今天周末&#xff0c;不聊技术&#xff0c;聊聊大家说的最多的一个话题 先说明一下&#xff0c;防止挨喷&#x1f606; 本人并不是职业dba&#xff0c;对数据库就是爱好&#xff0c;偶尔兼职&#xff0c;以下仅个人观点分析&#xff0c;如有不同观点请轻喷&#xff0c;哈哈&…

分享一个我爱工具网源码优化版

应用介绍 本文来自&#xff1a;分享一个我爱工具网源码优化版 - 源码1688 前几天在网上看到了一个不错的工具网源码&#xff0c;但是源码存在一些问题&#xff0c;遂进行了修改优化。 主要修改内容有&#xff1a; 1、后台改为账号密码登录&#xff0c;上传即用&#xff0c;不…

算法分析-面试1-字符串

文章目录 前言一、分类&#xff1a;看看就行了二、字符串API&#xff1a;创建和初始化&#xff1a;查询操作&#xff1a;比较操作&#xff1a;修改操作&#xff1a;截取操作&#xff1a;分割操作&#xff1a;格式化操作&#xff1a;连接操作&#xff08;Java 8 及以后&#xff…

pclpy KD-Tree半径最近邻搜索

pclpy 半径最近邻搜索 一、算法原理1.KD-Tree 介绍2.原理 二、代码三、结果1.原点云2.半径最近邻搜索的点云 四、相关数据 一、算法原理 1.KD-Tree 介绍 kd 树或 k 维树是计算机科学中使用的一种数据结构&#xff0c;用于在具有 k 维的空间中组织一定数量的点。它是一个二叉搜…

Windows下VTK 源码编译(For Qt PCL)

虽然我们在windows下安装PCL的时候就已经安装了VTK&#xff0c;由于跟着PCL安装的VTK是没有和QT联合编译的&#xff0c;所以在使用PCL和QT做点云可视化界面的时候是无法使用QT的插件QVTKWidget。 VTK 源码下载 Tags VTK / VTK GitLab 我这里的环境是Win10 Visual Studio&…

Qt 设置隐式加载dll路径

在c++中DLL的加载方式有两种,显式加载和隐式加载。 隐式加载 在程序从开始运行时,就会按照系统中一定的搜索路径,寻找动态库,找到就自动加载它,才能成功运行程序,这些步骤,是系统自动完成的。 显示加载 我们对动态库的调用,是在代码中直接使用LoadLibrary,或其他加载函…

ContainerHelpers之二分查找算法详解

目录 前言一、JAVA移位运算符1.1 >> 带符号右移位运算符1.2 >>> 无符号右移位运算符1.3 << 左移位运算符1.4 Java 中没有 <<<1.5 ~取反操作 二、ContainerHelpers二分查找算法总结 前言 安卓SparseArray中多次用到了ContainerHelpers的binarySe…

Android java中包的使用

一.包的使用 为了更好的实现项目中类的管理&#xff0c;提供包的概念。 package语句作为Java源文件的第一条语句&#xff0c;指明该文件中定义的类所在的包。(若缺省该语句&#xff0c;则指定为无名包)。 它的格式为&#xff1a;package 顶层包名.子包名 ; 二.java中主要的包…

Leetcode3039. 进行操作使字符串为空

Every day a Leetcode 题目来源&#xff1a;3039. 进行操作使字符串为空 解法1&#xff1a;哈希 排序 操作的定义&#xff1a;每次操作依次遍历 ‘a’ 到 ‘z’&#xff0c;如果当前字符出现在 s 中&#xff0c;那么删除出现位置最早的该字符&#xff08;如果存在的话&…

【ArcGIS】利用DEM进行水文分析:流向/流量等

利用DEM进行水文分析 ArcGIS实例参考 水文分析通过建立地表水文模型&#xff0c;研究与地表水流相关的各种自然现象&#xff0c;在城市和区域规划、农业及森林、交通道路等许多领域具有广泛的应用。 ArcGIS实例 某流域30m分辨率DEM如下&#xff1a; &#xff08;1&#xff09…

机器学习模型的过拟合与欠拟合

机器学习模型的训练过程中&#xff0c;可能会出现3种情况&#xff1a;模型欠拟合、模型正常拟合与模型过拟合。其中模型欠拟合与模型过拟合都是不好的情况。下面将会从不同的角度介绍如何判断模型属于哪种拟合情况。 &#xff08;1&#xff09;欠拟合与过拟合表现方式 欠拟合…

适配器模式:转换接口,无缝对接不同系统

文章目录 **一、技术背景与应用场景****为什么使用适配器模式&#xff1f;****典型应用场景包括但不限于&#xff1a;** **二、适配器模式定义与结构****三、使用步骤举例****四、优缺点分析****总结** 一、技术背景与应用场景 适配器模式在软件设计中扮演着桥梁角色&#xff…

十一、Qt数据库操作

一、Sql介绍 Qt Sql模块包含多个类&#xff0c;实现数据库的连接&#xff0c;Sql语句的执行&#xff0c;数据获取与界面显示&#xff0c;数据与界面直接使用Model/View结构。1、使用Sql模块 &#xff08;1&#xff09;工程加入 QT sql&#xff08;2&#xff09;添加头文件 …