第九次作业个人报告
一:负责的工作:
1:编写后端如下API:
”/“
-get API root遵照要求放回API列表
“/picture”
-get: 图片
/picture/upload
-post: 上传图片到(./picture)
"/commodities/
/commodities/{commodityName}
-get:商品详细信息
/commodities/{commodityName}/comments
-get 获取该商品所有评论
-post (表单:model.Comment) 发布
-patch (表单:model.Comment) 修改
-delete (表单:model.Comment) 删除
"/commodities
-get ;所有商品
-post : 上传商品信息
/users/{user}/cart
-在访问该路径时,需要先进行token验证:
-get 用户的购物车
-post (model.cart) 更新购物车
二:API详细介绍:
整个后端的实现分为三个层次,
- 负责数据库MongoDB操作的db.go、
- 负责路由的app.go
- 以及搭建服务器和链接数据库的server.go;
- model.go是数据json模型;
- 所有的API实现都依照这一模式实现:
1:"/"
http.HandlerFunc : writeApiRoot
返回API服务列表:通过一个map[string]string结构体记录服务列表,然后写入response返回
//Api信息
apiStr := make(map[string]string)
apiStr["all_commodities_url"] = "http://localhost:8080/commodities"
apiStr["post_commoditie_url:http"] = "//localhost:8080/commodities"
apiStr["get_commoditie_info_url"] = "http://localhost:8080/commodities/{commodity}"
apiStr["get_comment_url"] = "http://localhost:8080/commodities/{commodity}/comments"
apiStr["post_comment_url"] = "http://localhost:8080/commodities"
apiStr["delete_comment_url"] = "http://localhost:8080/commodities"
apiStr["get_alluser_url"] = "http://localhost:8080/users"
apiStr["user_register_url"] = "http://localhost:8080/users/register"
apiStr["get_a_user_url"] = "http://localhost:8080/users/{user}"
apiStr["get_user_cart"] = "http://localhost:8080/users/{user}/cart"
apiStr["update_comment_url"] = "http://localhost:8080/users/{user}/cart"
apiStr["get_picture"] = "http://localhost:8080/picture/{picture}"
apiStr["post_picture"] = "http://localhost:8080/picture/upload"
//发送到根root
err := json.NewEncoder(w).Encode(apiStr)
2: “/picture” get图片
此API是需要实现一个静态文件服务器,通过 如下代码从url映射文件夹:http.StripPrefix用来将/picture/从url中过滤出去,将文件映射到/picture/文件夹下
app.handlers["/picture/"] = http.StripPrefix("/picture/", http.FileServer(http.Dir("./picture"))).ServeHTTP
3:"/picture/upload" 上传图片:
文件标签为image
HandlerFunc: recieveImage
关键代码:
//获取图片文件
f, h, err := r.FormFile("image")
fmt.Println("recieve a img")
//在路径./picture/下创建新文件
filename := h.Filename
defer f.Close()
t, err := os.Create("./picture/" + filename)
//将图片复制到同名新文件
io.Copy(t, f)
4:"/commodities"
HandlerFunc: GetCommodities
该路径下有两个API:
- get方法: 从数据库中获取所有的商品信息并返回
- 其它方法: 根据商品名字更新某商品的信息,如果不存在该商品则添加,如果存在则更新该商品信息。
// GetCommodities if r.Method is GET it will get all commodities, if r.Method is POST will add or update a commodity
func (a *App) GetCommodities(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Println("get all commodities")
//获取所有商品信息
if r.Method == "GET" {
//从数据库取所有商品信息的数据
commodities, err := a.d.GetAllCommodity()
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
return
}
//将信息写入response
err = json.NewEncoder(w).Encode(commodities)
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
}
} else { //为商店添加新商品
var commodity model.Commodity
r.ParseMultipartForm(512)
fmt.Println("Add a new commodity")
for k, v := range r.MultipartForm.Value {
fmt.Println("value,k,v = ", k, ",", v)
}
//将信息写入数据库
commodity.Introduction = r.MultipartForm.Value["introduction"][0]
commodity.Name = r.MultipartForm.Value["name"][0]
commodity.Picture = r.MultipartForm.Value["picture"][0]
//string转float64
commodity.Price, _ = strconv.ParseFloat(r.MultipartForm.Value["price"][0], 64)
a.d.PostCommodity(&commodity)
}
}
其中操作数据库的方法 :a.d.GetAllCommodity()、a.d.PostCommidity() ; a是结构体App的指针,a的成员d是接口DB, 该接口定义了对数据库的所有操作函数。他们的定义如下:
//App define a app
type App struct {
d db.DB
handlers map[string]http.HandlerFunc
}
//DB 对数据库的操作接口
type DB interface {
//get获取所有商品|post:上传商品
GetAllCommodity() ([]*model.Commodity, error)
//
GetOneCommodity(name string) (*model.Commodity, error)
GetCommentsForCM(com string) ([]*model.Comment, error)
WriteComment(comment *model.Comment)
DeleteComment(comment *model.Comment)
UpdateComment(comment *model.Comment)
//
GetUsersInfo() ([]*model.User, error)
GetAUserInfo(string) ([]*model.User, error)
UserRegister(string, string, float64) (*model.User, error)
GetCart(username string) (*model.Cart, error)
WriteCart(cart *model.Cart)
PostCommodity(commodity *model.Commodity)
AddToken(token *model.TokenKey)
GetAToken(user string) (*model.TokenKey, error)
}
被调用的方法PostCommidity()就是用来更新数据库信息的,实现如下:
根据商品名选择要更新的商品,UpdateOpts为更新选项,为存在则更新,不存在则添加;
MongoDB是一个结构体,有一个唯一的成员database代表着服务器使用的数据库。
//PostCommodity update or add a commodity to the app
func (m MongoDB) PostCommodity(commodity *model.Commodity) {
selector := bson.M{"name": commodity.Name}
updateOpts := options.Update().SetUpsert(true)
//data := bson.M{"$set": bson.M{"comment": comment.Comment}}
data := bson.M{"$set": commodity}
updateResult, err := m.database.Collection(commodityCollection).UpdateOne(context.Background(), selector, data, updateOpts)
if err != nil {
log.Fatal(err)
}
fmt.Println("Updateresult: ", updateResult)
}
5:"/commodities/"
该路径下有很多API,主要有两类
获取单一商品信息的操作:
-/commodities/{commoditiesname}
对该商品评论的操作:
-/commodities/{commoditiesname}/comments
需要通过正则表达式来判断是哪种具体情况:
//isComment return true if name is a comment or false is not a comment
func isComment(name string) bool {
commentMode := regexp.MustCompile(`[A-Za-z0-9%]+/comments`)
return commentMode.MatchString(name)
}
具体实现
// GetCommodity define all the api in the form as /commodities/
func (a *App) GetCommodity(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
//获取url“commodities/”后面的字段
urlStr := r.URL.String()
fmt.Println("get a commodity")
commodityname := urlStr[len("/commodities")+1:]
//判断是否是操作商品(/commodities/{}/comments评论的url
if isComment(urlStr) {
fmt.Println("Operate a commodity's comment")
a.OperateCommentsForCM(w, r)
} else { //请求单个商品的详细信息
//将url编码解编码
cName, _ := url.QueryUnescape(commodityname)
println(cName)
//从数据库获取信息
commodity, err := a.d.GetOneCommodity(cName)
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
return
}
//写信息
err = json.NewEncoder(w).Encode(commodity)
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
}
}
}
当情况为对评论的操作,HandlerFunc : OperateCommentsForCM
该情况下实现了四种API:
分别是 GET获取 POST发布 DELETE删除 PATCH(通过用户名和商品名定位更新):Get方法是通过解析url定位信息,其余三种方法都是通过表单提取信息
// OperateCommentsForCM get post patch delete the coments of a commodity
func (a *App) OperateCommentsForCM(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" { //获取某商品的评论
w.Header().Set("Content-Type", "application/json")
//获取商品名
urlStr := r.URL.String()
println(urlStr)
commodityname := urlStr[len("/commodities")+1 : len(urlStr)-len("/comments")]
cName, _ := url.QueryUnescape(commodityname)
fmt.Println("get comments for a commodity", cName)
//从数据库取数据
commemts, err := a.d.GetCommentsForCM(cName)
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
return
}
//写数据
err = json.NewEncoder(w).Encode(commemts)
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
}
} else if r.Method == "POST" { //发布新评论
var comment model.Comment
r.ParseMultipartForm(128)
for k, v := range r.MultipartForm.Value {
fmt.Println("value,k,v = ", k, ",", v)
}
comment.Username = r.MultipartForm.Value["username"][0]
comment.Comment = r.MultipartForm.Value["comment"][0]
comment.Commodity = r.MultipartForm.Value["commodity"][0]
a.d.WriteComment(&comment)
fmt.Println(comment)
} else if r.Method == "DELETE" { //删除评论
fmt.Println("Delete a commet")
var comment model.Comment
r.ParseMultipartForm(128)
for k, v := range r.MultipartForm.Value {
fmt.Println("value,k,v = ", k, ",", v)
}
comment.Username = r.MultipartForm.Value["username"][0]
comment.Comment = r.MultipartForm.Value["comment"][0]
comment.Commodity = r.MultipartForm.Value["commodity"][0]
a.d.DeleteComment(&comment)
} else if r.Method == "PATCH" { //修改某评论
fmt.Println("Update a comment")
var comment model.Comment
r.ParseMultipartForm(128)
comment.Username = r.MultipartForm.Value["username"][0]
comment.Comment = r.MultipartForm.Value["comment"][0]
comment.Commodity = r.MultipartForm.Value["commodity"][0]
a.d.UpdateComment(&comment)
}
}
每个API依旧是通过接口DB对数据实施操作:a.d.{}
具体代码看db.go
6:/users/{username}/cart
这里的基本实现与上面的/commodities/{commoditiesname}/comments实现大体一致,主要有获取信息的get和更新信息的post方法:
具体实现看HandlerFunc : GetAUserFunc
3:使用postman进行简单的测试:
3.1注册测试:
- 注册得到token信息:
- 查看数据库数据是否正确写入:
- token:记录用户和密钥
- user :记录用户信息
- 注册成功
3.3 root API测试:
3.4 “/commodities” API测试:
3.5 "/commodities/{cname}测试:
3.6 "/commodities/{commodityname}/comments"测试:
1: get:
2:post: 发布:
3:patch:修改
4:delete:删除:
3.7: "/picture/"测试:
3.8 “/picture/upload”:
文件key要设置为image: