Skip to content

Session功能实现

1.1业务流程图

111

1.2接口定义

json
#Request:
method: GET
url:api/v1.0/session
data:no input data
#Response
#返回成功:
{
    "errno": "0",
    "errmsg":"OK",
    "data": {"name" : "13313331333"}

}
#注册失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

1.3具体业务实现

session的处理,我们可以直接放在web端来实现,具体实现如下:

go
//定义返回数据容器
resp := make(map[string]interface{})
dataTmp := make(map[string]string)
//初始化session
s := sessions.Default(ctx)
userName := s.Get("userName")
//对获取的session校验
if userName == nil{
	//初始化返回值
	resp["errno"] = utils.RECODE_SESSIONERR
	resp["errmsg"] = utils.RecodeText(utils.RECODE_SESSIONERR)
}else{
	resp["errno"] = utils.RECODE_OK
	resp["errmsg"] = utils.RecodeText(utils.RECODE_OK)
	dataTmp["name"] = userName.(string)
	resp["data"] = dataTmp
}
ctx.JSON(200,resp)

2.用户信息展示

2.1业务流程图

im

2.2接口定义

json
#Request:
method: GET
url:api/v1.0/user
#data:
no input data
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "user_id": 1,
    "name": "itcast",
    "mobile": "110",
    "real_name": "传智",
    "id_card": "210112244556677",
    "avatar_url": "http://101.200.170.171:8888/group1/M00/00/00/Zciqq1n7It2ANn1dAADexS5wJKs808.png"
  }
}
#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

2.3服务端实现

首先我们根据接口定义来确定proto文件的参数,修改后的proto文件如下:

protobuf
syntax = "proto3";

package go.micro.srv.getUserInfo;

service GetUserInfo {
	rpc Call(Request) returns (Response) {}
}

message Request {
	string name = 1;
}

message Response {
	string errno = 1;
	string errmsg = 2;
	UserData data = 3;
}

message UserData{
	int32 user_id = 1;
	string name = 2;
	string mobile = 3;
	string real_name = 4;
	string id_card = 5;
	string avatar_url = 6;
}

然后修改main.go文件,再详细的实现我们的业务方法,具体代码如下:

go
	//根据用户名获取信息
	db ,err:= model.InitDb()
	user,err := model.GetUserInfo(db,req.Name)

	if err != nil {
		rsp.Errno = utils.RECODE_DBERR
		rsp.Errmsg = utils.RecodeText(utils.RECODE_DBERR)
		return err
	}

	var data getUserInfo.UserData

	data.Name = user.Name
	data.Mobile = user.Mobile
	data.IdCard = user.Id_card
	data.RealName = user.Real_name
	data.AvatarUrl = user.Avatar_url
	data.UserId  = int32(user.ID)

	rsp.Errno = utils.RECODE_OK
	rsp.Errmsg = utils.RecodeText(utils.RECODE_OK)
	rsp.Data  = &data


	return nil

注意函数的封装,封装的函数如下:

go
//根据用户名获取当前用户信息
func GetUserInfo(db*gorm.DB,name string)(User,error){
	var user User
	err := db.Where("name = ?",name).First(&user).Error
	return user,err
}

2.4web端实现

服务端实现之后,接着我们来实现web端,首先我们要匹配相应的路由,代码如下:

go
r1.GET("/user",controller.GetUserInfo)

然后实现具体的控制器函数,首先是从session中获取数据,代码如下:

go
//获取登录信息
s := sessions.Default(ctx)
userName := s.Get("userName")
errResp := make(map[string]interface{})
if userName == nil{
	errResp["errno"] = utils.RECODE_SESSIONERR
	errResp["errmsg"] = utils.RecodeText(utils.RECODE_SESSIONERR)
	ctx.JSON(200,errResp)
	return 
}

然后调用远程服务,把用户名传到远程,并获取返回值,代码如下:

go
//远程调用
grpcService := utils.GetGrpcService()
client := getUserInfo.NewGetUserInfoService("go.micro.srv.getUserInfo",grpcServiceClient())
resp,err := client.Call(context.TODO(),&getUserInfo.Request{Name:userName.(string)})

根据返回值确定返回给前端的数据,代码如下:

go
if err != nil {
	fmt.Println(err)
	resp.Errno = utils.RECODE_DBERR
	resp.Errmsg = utils.RecodeText(utils.RECODE_DBERR)
}
	
ctx.JSON(200,resp)

3.退出登陆

3.1业务流程图

mg

3.2接口定义

json
#Request:
method: DELETE
url:api/v1.0/session
#data:
no input data
#Response
#返回成功:
{
    "errno": "0",
    "errmsg":"OK",
}
#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

3.3业务实现

退出登陆实现比较简单,直接删除session即可,首先我们来匹配一下路由,代码如下:

go
r1.DELETE("/session",controller.DeleteSession)

然后在控制器实现删除session返回数据操作,代码如下:

go
func DeleteSession(ctx*gin.Context){
	resp := make(map[string]interface{})

	//删除session
	s := sessions.Default(ctx)
	s.Delete("userName")
	err := s.Save()
	if err != nil {
		resp["errno"] = utils.RECODE_SESSIONERR
		resp["errmsg"] = utils.RecodeText(utils.RECODE_SESSIONERR)
	}else {
		resp["errno"] = utils.RECODE_OK
		resp["errmsg"] = utils.RecodeText(utils.RECODE_OK)
	}

	ctx.JSON(200,resp)
}

注意。不管是设置session还是删除session最后都需要调用save方法。

4.登陆业务实现

4.1业务流程图

im

4.2接口定义

json
#Request:
method: POST
url:api/v1.0/sessions
#data:
{
    mobile: "133", //手机号
    password: "itcast"//密码
}
#Response
#返回成功:
{
    "errno": "0",
    "errmsg":"OK",
}
#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

4.3服务端实现

首先是创建服务,代码如下:

shell
$ micro new --type src project/login

然后根据接口定义,修改proto参数,修改后的proto内容如下:

protobuf
syntax = "proto3";

package go.micro.srv.login;

service Login {
	rpc Call(Request) returns (Response) {}
}

message Request {
	string mobile = 1;
	string password = 2;
}

message Response {
	string errno = 1;
	string errmsg = 2;
	string name = 3;
}

然后编译proto文件,生成对应接口,修改Main.go文件,再具体实现登陆业务,登陆业务实现如下:

go
	//初始化mysql连接    
	db,err := model.InitDb()
	if err != nil {
		rsp.Errno = utils.RECODE_DBERR
		rsp.Errmsg = utils.RecodeText(utils.RECODE_DBERR)
	}
	//对传入的密码进行md5加密,然后调用封装的函数进行登陆校验,注意后面需要把用户名存入session,所以要把name传出
	m5 := md5.New()
	m5.Write([]byte(req.Password))
	pwdHash := hex.EncodeToString(m5.Sum(nil))
	result,name := model.GetLoginInfo(db,req.Mobile,pwdHash)
	//根据结果返回数据
	if result{
		rsp.Errno = utils.RECODE_OK
		rsp.Errmsg = utils.RecodeText(utils.RECODE_OK)
		rsp.Name = name
	}else {
		rsp.Errno = utils.RECODE_DATAERR
		rsp.Errmsg = utils.RecodeText(utils.RECODE_DATAERR)
	}
	return nil

封装的函数如下:

go
//根据电话号和密码判断,登录是否成功
func GetLoginInfo(db *gorm.DB,mobile string,pwd string)(bool,string){
	var user User
	db.Where("mobile = ?",mobile).First(&user)
	return user.Password_hash == pwd,user.Name
}

4.4web端实现

处理完服务端接着我们来处理web端内容, 首先是做路由匹配,代码如下:

go
r1.POST("/sessions",controller.PostLogin)

然后在具体控制器函数中实现登陆业务,代码如下:

go
//登录处理
type UserLogin struct {
	Mobile string `json:"mobile"`
	Password string `json:"password"`
}
func PostLogin(ctx*gin.Context){
	//定义错误容器
	errResp := make(map[string]interface{})
	//获取数据
	var userLogin UserLogin
	err := ctx.Bind(&userLogin)
	if err != nil {
		errResp["errno"] = utils.RECODE_REQERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_REQERR)
		ctx.JSON(200,errResp)
		return
	}

	//远程服务调用
	grpcService := utils.GetGrpcService()
	client := login.NewLoginService("go.micro.srv.login",grpcService.Client())
	resp,err := client.Call(context.TODO(),&login.Request{Mobile:userLogin.Mobile,Password:userLogin.Password})
	if err != nil {
		fmt.Println(err)
		errResp["errno"] = utils.RECODE_LOGINERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_LOGINERR)
		ctx.JSON(200,errResp)
		return
	}
	//如果返回成功,存储session
	if resp.Errno == utils.RECODE_OK{
		s := sessions.Default(ctx)
		s.Set("userName",resp.Name)
		s.Save()
	}

	ctx.JSON(200,resp)
}

5.上传用户头像业务

5.1业务流程图

im

5.2接口定义

go
#Request:
method: POST
url:api/v1.0/user/avatar
#data:
图片的二进制数据
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "avatar_url": "http://101.200.170.171:8888/group1/M00/00/00/Zciqq1n6_L-AOB04AADexS5wJKs662.png" //图片地址需要进行拼接
  } 

}
#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

5.3服务端实现

首先创建服务。命令如下:

shell
$ micro new --type srv project/postAva

然后根据接口定义确定proto文件内容,proto文件内容如下:

protobuf
syntax = "proto3";

package go.micro.srv.postAvatar;

service PostAvatar {
	rpc Call(Request) returns (Response) {}
}


message Request {
	string name = 1;
	bytes file = 2;
	string fileExt = 3;
}

message Response {
	string errno = 1;
	string errmsg = 2;
	ImgPath data = 3;
}

message ImgPath{
	string avatar_url = 1;
}

这里注意,我们把文件存储的时候需要获取文件格式信息,所系需要把文件格式传递过来,头像信息上传之后需要和当前用户绑定,所以需要把用户名传递过来。

然后编译proto并修改main.go文件,再去handler中实现具体业务,业务代码如下:

go
	//解析出来文件
	client ,_:= fdfs_client.NewFdfsClient("/etc/fdfs/client.conf")

	uploadResp ,err := client.UploadByBuffer(req.File,req.FileExt)
	if err != nil {
		rsp.Errno = utils.RECODE_IOERR
		rsp.Errmsg = utils.RecodeText(utils.RECODE_IOERR)
		return err
	}
	//给返回值赋值
	rsp.Errno = utils.RECODE_OK
	rsp.Errmsg = utils.RecodeText(utils.RECODE_OK)
	var imgPath postAvatar.ImgPath
	imgPath.AvatarUrl = "http://192.168.137.130:8888/"+uploadResp.RemoteFileId

	rsp.Data = &imgPath
	return nil

5.4web端实现

具体实现,代码如下:

go
func PostAvatar(ctx*gin.Context){

	//定义错误返回容器
	errResp := make(map[string]interface{})

	//获取前段传递的文件
	fileHeader ,err:= ctx.FormFile("avatar")
	if err != nil {
		errResp["errno"] = utils.RECODE_REQERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_REQERR)
		ctx.JSON(200,errResp)
		return
	}

	//判断文件大小及格式是否正确
	if fileHeader.Size > 500000{
		errResp["errno"] = utils.RECODE_DATAERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_DATAERR)
		ctx.JSON(200,errResp)
		return
	}
	fileExt:= path.Ext(fileHeader.Filename)
	if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jpeg"{
		errResp["errno"] = utils.RECODE_DATAERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_DATAERR)
		ctx.JSON(200,errResp)
		return
	}

	//获取文件流,并写成字节格式
	file ,_ :=fileHeader.Open()
    buffer := make([]byte,fileHeader.Size)
	file.Read(buffer)

	//获取用户名
	s := sessions.Default(ctx)
	userName := s.Get("userName")

	//调用远程服务
	grpcService := utils.GetGrpcService()
	client := postAva.NewPostAvatarService("go.micro.srv.postAvatar",grpcService.Client())
	resp ,err:= client.Call(context.TODO(),&postAva.Request{
		FileExt:fileExt,
		File:buffer,
		Name:userName.(string),
	})

	if err != nil{
		errResp["errno"] = utils.RECODE_IOERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_IOERR)
		ctx.JSON(200,errResp)
		return
	}

	ctx.JSON(200,resp)
}

6.更新用户名

6.1业务流程图

im

6.2接口定义

json
#Request:
method: PUT
url:api/v1.0/user/name
#data:
{
 "name":"panda"
}
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "name": "Panda"
  }
}

#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

6.3服务端实现

6.4web端实现

7.检查用户实名认证

7.1业务流程图

im

7.2接口定义

go
#Request:
method: GET
url:api/v1.0/user/auth
#data:
no input data
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "user_id": 1,
    "name": "Panda",
    "password": "123123",
    "mobile": "110",
    "real_name": "熊猫",
    "id_card": "210112244556677",
    "avatar_url": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1n7It2ANn1dAADexS5wJKs808.png"
  }
}

#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

7.3服务端实现

7.4web端实现

8.更新用户实名认证

8.1业务流程图

im

8.2接口定义

json
#Request:
method: POST
url:api/v1.0/user/auth
#data:
{
    real_name: "熊猫", 
    id_card: "21011223344556677"
}
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功"
}
#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

8.3服务端实现

8.4web端实现

9 获取当前用户已发布房源信息

获取用户已发布房源信息服务(商品相关)

创建命令

shell
$ micro new --type "srv" sss/GetUserHouses

流程与接口

mg

json
#Request:
method: GET
url:api/v1.0/user/houses
#data:
no input data
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "houses": [
      {
        "address": "西三旗桥东",
        "area_name": "昌平区",
        "ctime": "2017-11-06 11:16:24",
        "house_id": 1,
        "img_url": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBJY-AL3m8AAS8K2x8TDE052.jpg",
        "order_count": 0,
        "price": 100,
        "room_count": 2,
        "title": "上奥世纪中心",
        "user_avatar": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBLFeALIEjAADexS5wJKs340.png"
      },
      {
        "address": "北清路郑上路",
        "area_name": "顺义区",
        "ctime": "2017-11-06 11:38:54",
        "house_id": 2,
        "img_url": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBKtmAC8y8AAZcKg5PznU817.jpg",
        "order_count": 0,
        "price": 1000,
        "room_count": 1,
        "title": "修正大厦302教室",
        "user_avatar": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBLFeALIEjAADexS5wJKs340.png"
      }
    ]
  }
}

#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

服务端实现

根据接口定义,生成对应的proto文件,proto文件内容如下:

protobuf
syntax = "proto3";

package go.micro.srv.getHouse;

service GetHouse {
	rpc Call(Request) returns (Response) {}
}


message Request {
	string name = 1;
}

message Response {
	string errno = 1;
	string errmsg = 2;
	houses data = 3;
}

message houses{
	repeated houseInfo houses = 1;
}

message houseInfo {
	string address = 1;
	string area_name = 2;
	string ctime = 3;
	int32 house_id = 4;
	string img_url = 5;
	int32 order_count = 6;
	int32 price = 7;
	int32 room_count = 8;
	string title = 9;
	string user_avatar = 10;
}

然后编译proto文件,接着去main.go文件中,删除不需要的部分,并添加consul服务发现,然后去hander里面处理具体的业务代码,具体代码内容如下:

go
func (e *GetHouse) Call(ctx context.Context, req *getHouse.Request, rsp *getHouse.Response) error {
	//查询数据库,根据用户名获取房源信息
	db,err :=model.InitDb()
	if err != nil{
		return err
	}
	//具体数据库操作,封装成函数,在下面展示
	houses,user,areas,err :=model.GetHouses(db,req.Name)


	if err != nil {
		fmt.Println(err)
		return err
	}

    //根据返回数据,构造返回值
	var houseInfos getHouse.Houses
	for k,v := range houses{
		var house getHouse.HouseInfo

		house.Address = v.Address
		house.HouseId = int32(v.ID)
		house.Ctime = v.CreatedAt.Format("2006-01-02 15:04:05")
		house.ImgUrl = v.Index_image_url
		house.OrderCount = int32(v.Order_count)
		house.Price = int32(v.Price)
		house.RoomCount = int32(v.Room_count)
		house.Title = v.Title
		house.UserAvatar = user.Avatar_url
		house.AreaName = areas[k].Name

		houseInfos.Houses = append(houseInfos.Houses,&house)
	}

	fmt.Println("houseInfos = ",houseInfos,"areas = ",areas)
	
    //返回数据
	rsp.Errno = utils.RECODE_OK
	rsp.Errmsg = utils.RecodeText(utils.RECODE_OK)
	rsp.Data = &houseInfos


	return nil
}

这里我们把数据库操作封装到函数里面,函数实现如下:

go
//根据用户名获取当前用户的房源信息
func GetHouses(db *gorm.DB, name string) ([]House, User, []Area, error) {
	//查询当前用户对象
	var user User
	err := db.Where("name = ?", name).First(&user).Error
	if err != nil {
		return nil, User{}, nil, err
	}

	//查询当前用户对应的房屋
	var houses []House
	err = db.Model(&user).Related(&houses).Error
	if err != nil {
		return nil, User{}, nil, err
	}

    //获取各个房源对应的地址
	var areas []Area
	for _, v := range houses {
		var area Area
		err = db.Model(&v).Related(&area).Error

		if err != nil {
			return nil, User{}, nil, err
		}

		areas = append(areas, area)
	}
	return houses, user, areas, nil
}

web端实现

根据设计文档,这里我们给对应请求添加路由,代码如下:

go
r1.GET("/user/houses",controller.GetUserHouses)

然后到controller中实现对应请求方法,代码如下:

go
//获取当前用户发布房源信息
func GetUserHouses(ctx*gin.Context){

	//获取用户名
	s := sessions.Default(ctx)
	userName := s.Get("userName")

	//调用远程服务
	grpcService := utils.GetGrpcService()
	client := getHouse.NewGetHouseService(utils.GetHouseName,grpcService.Client())
	resp,err := client.Call(context.TODO(),&getHouse.Request{Name:userName.(string)})
	fmt.Println("resp = ",err)

	if err != nil {
		resp.Errno = utils.RECODE_SERVERERR
		resp.Errmsg = utils.RecodeText(utils.RECODE_SERVERERR)
	}

	ctx.JSON(200,resp)
}

10 发布房源信息

发送(发布)房源信息服务(商品相关)

创建命令

shell
$ micro new --type "srv" sss/PostHouses

流程与接口

mg

json
#Request:
method: POST
url:api/v1.0/houses
#data:
{
"title":"上奥世纪中心",
"price":"666",
"area_id":"5",
"address":"西三旗桥东建材城1号",
"room_count":"2",
"acreage":"60",
"unit":"2室1厅",
"capacity":"3",
"beds":"双人床2张",
"deposit":"200",
"min_days":"3",
"max_days":"0",
"facility":["1","2","3","7","12","14","16","17","18","21","22"]
}
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功"
  "data" :{
        "house_id": "1"
  }
}
#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

服务端实现

创建服务,然后根据接口定义我们来修改服务的proto文件,proto内容如下:

go
syntax = "proto3";

package go.micro.srv.postHouse;

service PostHouse {
	rpc Call(Request) returns (Response) {}
}


message Request {
	string acreage = 1;
	string address = 2;
	string area_id = 3;
	string beds = 4;
	string capacity = 5;
	string deposit = 6;
	repeated string facility = 7;
	string min_days = 8;
	string max_days = 9;
	string price = 10;
	string room_count = 11;
	string title = 12;
	string unit = 13;

	string name = 14;
}

message Response {
	string errno = 1;
	string errmsg = 2;
	map<string,int32> data = 3;
}

修改main.go文件,然后到handler中实现具体服务内容,代码如下:

go
	//发布房源,插入操作
	db,err := model.InitDb()
	//向数据库中插入数据
	var house model.House
	acrea,_ :=strconv.Atoi(req.Acreage)
	house.Acreage = acrea
	house.Address = req.Address
	areaId ,_ :=strconv.Atoi(req.AreaId)
	house.AreaId = uint(areaId)
	house.Beds = req.Beds
	cap,_ :=strconv.Atoi(req.Capacity)
	house.Capacity = cap
	dep ,_ :=strconv.Atoi(req.Deposit)
	house.Deposit = dep
	maxDays ,_ :=strconv.Atoi(req.MaxDays)
	house.Max_days = maxDays
	minDays,_ :=strconv.Atoi(req.MinDays)
	house.Min_days = minDays
	price ,_:= strconv.Atoi(req.Price)
	house.Price = price
	house.Title = req.Title
	house.Unit = req.Unit

	roomCount,_ :=strconv.Atoi(req.RoomCount)
	house.Room_count = roomCount
	//在model中封装插入函数,具体实现请往下看
	houseId,err := model.InserHouse(db,req.Name,house,req.Facility)
	if err != nil{
		return err
	}

	//设置返回值
	rsp.Errno = utils.RECODE_OK
	rsp.Errmsg = utils.RecodeText(utils.RECODE_OK)
	rsp.Data = map[string]int32{"house_id":int32(houseId)}


	return nil

model中插入房源函数实现,代码如下:

go
//发布房源
func InserHouse(db*gorm.DB,name string,house House,fids []string)(uint,error){
	//根据用户名获取用户Id
	var user User
	user.Name = name
	err := db.Where("name = ?",name).Find(&user).Error
	if err != nil {
		return 0, err
	}
    //关联当前用户
	house.UserId = uint(user.ID)
	//查询所有家具放到house里面
	for _,v := range fids{
		id ,_:=strconv.Atoi(v)

		var fac Facility
		err := db.Where("id = ?",id).Find(&fac).Error
		if err != nil{
			return 0,err
		}

		house.Facilities = append(house.Facilities,&fac)
	}
	return house.ID, db.Create(&house).Error
}

web端实现

根据接口文档定义,给对应请求添加路由对应,代码如下:

go
r1.POST("/houses",controller.PostHouses)

然后到controller中实现具体操作,代码如下:

go
type HouseInfo struct {
	Title string `json:"title"`
	Acreage string `json:"acreage"`
	Address string 	`json:"address"`
	AreaId string  `json:"area_id"`
	Beds string    `json:"beds"`
	Capacity string `json:"capacity"`
	Deposit string `json:"deposit"`
	Facility []string `json:"facility"`
	MaxDays string `json:"max_days"`
	MinDays string `json:"min_days"`
	Price string   `json:"price"`
	RoomCount string `json:"room_count"`
	Unit string    `json:"unit"`
}

//发布房源
func PostHouses(ctx*gin.Context){
	//定义错误容器
	errResp := make(map[string]interface{})

    //获取前端传递过来的数据
	var houseInfo HouseInfo
	err := ctx.Bind(&houseInfo)
	if err != nil {
		errResp["errno"] = utils.RECODE_REQERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_REQERR)
		ctx.JSON(200,errResp)
		return
	}

	fmt.Println("houseInfo = ",houseInfo)
	//获取当前用户名
	s := sessions.Default(ctx)
	userName := s.Get("userName")

	//远程服务调用
	grpcService := utils.GetGrpcService()
	client := postHouse.NewPostHouseService(utils.PostHouseName,grpcService.Client())
	resp ,err:=client.Call(context.TODO(),&postHouse.Request{
		Name:userName.(string),
		Acreage:houseInfo.Acreage,
		Address:houseInfo.Address,
		AreaId:houseInfo.AreaId,
		Beds:houseInfo.Beds,
		Capacity:houseInfo.Capacity,
		Deposit:houseInfo.Deposit,
		Facility:houseInfo.Facility,
		MaxDays:houseInfo.MaxDays,
		MinDays:houseInfo.MinDays,
		Price:houseInfo.Price,
		RoomCount:houseInfo.RoomCount,
		Title:houseInfo.Title,
		Unit:houseInfo.Unit,
	})

    //返回数据
	if err != nil {
		fmt.Println(err)
		errResp["errno"] = utils.RECODE_SERVERERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_SERVERERR)
		ctx.JSON(200,errResp)
		return
	}

	ctx.JSON(200,resp)
}

11 上传房屋图片

发送(上传)房屋图片服务(商品相关)

创建命令

shell
$ micro new --type "srv" sss/PostHousesImage

流程与接口

im

json
#Request:
method: POST
#3表示房源id
url:api/v1.0/houses/3/images 
url:api/v1.0/houses/:id/images 

#data:
图片二进制数据
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "url": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBLmWAHlsrAAaInSze-cQ719.jpg"
  }
}
#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

服务端实现

创建服务,根据接口定义,修改proto文件,proto文件内容如下:

go
syntax = "proto3";

package go.micro.srv.uploadHouseImage;

service UploadHouseImage {
	rpc Call(Request) returns (Response) {}
}

message Request {
	int32 house_id = 1;
	bytes houseBuffer = 2;
	string fileExt = 3;
}

message Response {
	string errno = 1;
	string errmsg = 2;
	map<string,string>data = 3;
}

修改main.go文件,添加consul服务发现,然后实现hander中具体的业务,代码如下:

go
	//获取fastDFS操作对象
	fdfsClient,err:= fdfs_client.NewFdfsClient("/etc/fdfs/client.conf")
	if err != nil{
		return err
	}
	//通过字节切片上传文件到fastDFS
	fdfsResp,err := fdfsClient.UploadByBuffer(req.HouseBuffer,req.FileExt)
	if err != nil {
		return err
	}

	//把数据存储到数据库,这里把数据库操作封装成函数,具体代码往下看
	db,err := model.InitDb()
	err = model.UploadHouseImage(db,uint(req.HouseId),utils.NginxPath+fdfsResp.RemoteFileId)
	if err != nil {
		return err
	}

	//返回数据
	rsp.Errno = utils.RECODE_OK
	rsp.Errmsg = utils.RecodeText(utils.RECODE_OK)
	rsp.Data = map[string]string{"url":utils.NginxPath+fdfsResp.RemoteFileId}

	return nil

models中上传房屋图片的操作代码如下:

go
//上传房屋图片
func UploadHouseImage(db*gorm.DB,houseId uint,imgUrl string)error{
	var house House

	//查询对应 房屋信息
	err := db.First(&house,houseId).Error
	if err != nil {
		return err
	}

	if house.Index_image_url == ""{
		err = db.Model(&house).Update("index_image_url",imgUrl).Error
		if err != nil {
			return err
		}
	}else {
		var houseImage HouseImage
		houseImage.HouseId = houseId
		return db.Create(&houseImage).Error
	}
	return nil
}

web端实现

根据接口文档,给请求添加对应路由,代码如下:

go
r1.POST("/houses/:id/images",controller.PostHousesImage)

接着到controller中实现具体代码,代码如下:

go
func PostHousesImage(ctx*gin.Context){
	errResp := make(map[string]string)
    //获取数据
	houseId := ctx.Param("id")
	//这里我们发现,已经操作上传文件两次了,可以把上传文件操作封装成函数,代码在下面
	fileBuffer,fileExt := UploadFile(ctx,errResp,"house_image")
	if fileBuffer == nil {
		ctx.JSON(200,errResp)
		return
	}

	//调用远程服务
	id,_ := strconv.Atoi(houseId)
	grpcService := utils.GetGrpcService()
	client := uploadHouseImage.NewUploadHouseImageService(utils.UploadHouseImage,grpcService.Client())
	resp,err := client.Call(context.TODO(),&uploadHouseImage.Request{
		HouseId:int32(id),
		FileExt:fileExt,
		HouseBuffer:fileBuffer,
		})
	//返回数据
	if err != nil {
		fmt.Println(err)
		errResp["errno"] = utils.RECODE_SERVERERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_SERVERERR)
		ctx.JSON(200,errResp)
		return
	}

	ctx.JSON(200,resp)
}

上传文件的函数封装,代码如下:

go
func UploadFile(ctx*gin.Context,errResp map[string]string,fileName string)([]byte,string){
	//获取数据
	fileHeader,err := ctx.FormFile(fileName)
	//校验数据
	if err != nil {
		errResp["errno"] = utils.RECODE_DATAERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_DATAERR)
		return nil,""
	}
	//大小校验
	if fileHeader.Size > 500000{
		errResp["errno"] = utils.RECODE_MOREBIG
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_MOREBIG)
		return nil,""
	}

	//格式校验
	fileExt := path.Ext(fileHeader.Filename)
	if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jepg"{
		errResp["errno"] = utils.RECODE_FORMATERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_FORMATERR)
		return nil,""
	}

	file,_ := fileHeader.Open()
	buffer := make([]byte,fileHeader.Size)
	file.Read(buffer)
	return buffer,fileExt[1:]
}

12 获取房源详细信息

获取房屋详细信息服务(商品相关)

创建命令

shell
$ micro new --type "srv" sss/GetHouseInfo

流程与接口

im

json
#Request:
method: GET
#1表示房源id
url:api/v1.0/houses/1
url:api/v1.0/houses/:id
#data:
no input data
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "house": {
      "acreage": 80,
      "address": "西三旗桥东",
      "beds": "2双人床",
      "capacity": 3,
      "comments": [
        {
          "comment": "评论的内容",
          "ctime": "2017-11-12 12:30:30",
          "user_name": "评论人的姓名"
        },
        {
          "comment": "评论的内容",
          "ctime": "2017-11-12 12:30:30",
          "user_name": "评论人的姓名"
        },
        {
          "comment": "评论的内容",
          "ctime": "2017-11-12 12:30:30",
          "user_name": "评论人的姓名"
        }
      ],
      "deposit": 200,
      "facilities": [9,11,13,16,19,20,21,23],
      "hid": 1,
      "img_urls": [
        "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBJY-AL3m8AAS8K2x8TDE052.jpg",
        "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBJZmAYqGWAAaInSze-cQ230.jpg"
      ],
      "max_days": 30,
      "min_days": 1,
      "price": 100,
      "room_count": 2,
      "title": "上奥世纪中心",
      "unit": "3室3厅",
      "user_avatar": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBLFeALIEjAADexS5wJKs340.png",
      "user_id": 1,
      "user_name": "Panda"
    },
    "user_id": 1
  }
}

#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

服务端实现

创建服务,然后根据接口定义,修改proto文件,proto文件内容如下:

go
syntax = "proto3";

package go.micro.srv.getHouseDetail;

service GetHouseDetail {
	rpc Call(Request) returns (Response) {}
}


message Request {
	int32 houseId = 1;
}

message Response {
	string errno = 1;
	string errmsg = 2;
	data data = 3;
}

message data{
	houseInfo house = 1;
	int32 user_id = 2;
}

message houseInfo {
	int32 acreage = 1;
	string address = 2;
	string beds = 3;
	int32 capacity = 4;
	repeated commentInfo comments = 5;
	int32 deposit = 6;
	repeated int32 facilities = 7;
	int32 hid = 8;
	repeated string img_urls = 9;
	int32 max_days = 10;
	int32 min_days = 11;
	int32 price = 12;
	int32 room_count = 13;
	string title = 14;
	string unit = 15;
	string user_avatar = 16;
	int32 user_id = 17;
	string user_name = 18;
}

message commentInfo{
	string comment = 1;
	string ctime = 2;
	string user_name = 3;
}

修改main.go文件,然后去hander中实现具体业务,代码如下:

go
	//从redis数据库中获取数据
	var data getHouseDetail.Data
	redisConn := model.InitRedis().Get()
	houseBuffer, _ := redis.Bytes(redisConn.Do("get", "houseId_"+strconv.Itoa(int(req.HouseId))))
	if len(houseBuffer) == 0 {
		//如果查不到数据,就从数据库获取
		// 获取数据库实例
		db, err := model.InitDb()
		//把具体的操作封装成函数
		house, orders, orderUsers, user, facs, imgs, err := model.GetDetail(db, req.HouseId)
		if err != nil {
			return err
		}

		//获取评论数据
		var comments []*getHouseDetail.CommentInfo

		for k, v := range orders {
			var commment getHouseDetail.CommentInfo
			commment.Ctime = v.UpdatedAt.Format("2006:01:02 15:04:05")
			commment.UserName = orderUsers[k].Name
			commment.Comment = v.Comment

			comments = append(comments, &commment)
		}

		//获取家具数据
		var fids []int32
		for _, v := range facs {
			fids = append(fids, int32(v.ID))
		}

		//获取图片
		var imgPaths []string
		imgPaths = append(imgPaths, house.Index_image_url)
		for _, v := range imgs {
			imgPaths = append(imgPaths, v.Url)
		}

		//设置返回数据
		var houseInfo getHouseDetail.HouseInfo
		houseInfo.Acreage = int32(house.Acreage)
		houseInfo.Address = house.Address
		houseInfo.Beds = house.Beds
		houseInfo.Capacity = int32(house.Capacity)
		houseInfo.Comments = comments
		houseInfo.Deposit = int32(house.Deposit)
		houseInfo.Facilities = fids
		houseInfo.Hid = int32(house.ID)
		houseInfo.ImgUrls = imgPaths
		houseInfo.MinDays = int32(house.Min_days)
		houseInfo.MaxDays = int32(house.Max_days)
		houseInfo.Price = int32(house.Price)
		houseInfo.RoomCount = int32(house.Room_count)
		houseInfo.Title = house.Title
		houseInfo.Unit = house.Unit
		houseInfo.UserAvatar = user.Avatar_url
		houseInfo.UserId = int32(house.UserId)
		houseInfo.UserName = user.Name

		data.House = &houseInfo
		data.UserId = int32(user.ID)

		//把查询到的数据存储到redis中
		buffer,err := json.Marshal(data)
		if err != nil {
			return err
		}

		redisConn.Do("set","houseId_"+strconv.Itoa(int(house.ID)),buffer)
	}else {
		//如果查到数据,就直接解析数据
		err := json.Unmarshal(houseBuffer,data)
		if err != nil {
			return err
		}
	}

	//返回数据
	rsp.Errno = utils.RECODE_OK
	rsp.Errmsg = utils.RecodeText(utils.RECODE_OK)
	rsp.Data = &data
	return nil

model中封装的函数具体内容如下:

go
//根据房屋id获取房屋信息,获取评论,用户信息,家具信息,房屋图片
func GetDetail(db*gorm.DB,houseId int32)(House,[]OrderHouse,[]User,User,[]Facility,[]HouseImage,error){
	var house House
	var orders []OrderHouse
	var orderUsers []User
	var user User
	var facs []Facility
	var imgs []HouseImage


	house.ID = uint(houseId)
	err := db.Where(&house).First(&house).Error
	if err != nil {
		return house,orders,orderUsers,user,facs,imgs,err
	}

	//获取评论信息
	err = db.Model(&house).Related(&orders).Error
	if err != nil {
		return house,orders,orderUsers,user,facs,imgs,err
	}

	//获取评论用户信息
	for _,v := range orders{
		var orderUser User
		err := db.Model(&v).Related(&orderUser).Error
		if err != nil {
			return house,orders,orderUsers,user,facs,imgs,err
		}
		orderUsers = append(orderUsers,orderUser)
	}

	//获取用户信息
	err = db.Model(&house).Related(&user).Error
	if err != nil {
		return house,orders,orderUsers,user,facs,imgs,err
	}

	//获取家具信息
	err = db.Model(&house).Related(&facs,"Facilities").Error
	if err != nil {
		return house,orders,orderUsers,user,facs,imgs,err
	}

	//获取房屋图片
	err = db.Model(&house).Related(&imgs).Error
	if err != nil {
		return house,orders,orderUsers,user,facs,imgs,err
	}

	return house,orders,orderUsers,user,facs,imgs,nil
}

web端实现

根据接口定义,给请求设置对应路由,代码如下:

go
r1.GET("/houses/:id",controller.GetHouseInfo)

然后去controller中实现对应业务,代码如下:

go
//获取房屋详细信息
func GetHouseInfo(ctx*gin.Context){
    //获取数据
	errResp := make(map[string]interface{})
	houseId := ctx.Param("id")

    //校验数据
	id,err := strconv.Atoi(houseId)
	if err != nil {
		errResp["errno"] = utils.RECODE_REQERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_REQERR)
		ctx.JSON(200,errResp)
		return
	}

	//调用远程服务
	grpcService := utils.GetGrpcService()
	client := getDetail.NewGetHouseDetailService(utils.GetDetail,grpcService.Client())
	resp,err := client.Call(context.TODO(),&getDetail.Request{HouseId:int32(id)})
    
    //返回数据
	if err != nil {
		fmt.Println(err)
		errResp["errno"] = utils.RECODE_SERVERERR
		errResp["errmsg"] = utils.RecodeText(utils.RECODE_SERVERERR)
		ctx.JSON(200,errResp)
		return
	}

	ctx.JSON(200,resp)

}

13 获取首页动画图片

获取首页轮播图片服务(首页相关)

创建命令

shell
$ micro new --type "srv" sss/GetIndex

流程与接口

im

shell
#Request:
method: GET
url:api/v1.0/house/index
#data:
no input data
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
        "houses": [
      {
		"house_id":    this.Id,
		"title":       this.Title,
		"price":       this.Price,
		"area_name":   this.Area.Name,
		"img_url":     utils.AddDomain2Url(this.Index_image_url),
		"room_count":  this.Room_count,
		"order_count": this.Order_count,
		"address":     this.Address,
		"user_avatar": utils.AddDomain2Url(this.User.Avatar_url),
		"ctime":       this.Ctime.Format("2006-01-02 15:04:05"),
	},
      {
		"house_id":    this.Id,
		"title":       this.Title,
		"price":       this.Price,
		"area_name":   this.Area.Name,
		"img_url":     utils.AddDomain2Url(this.Index_image_url),
		"room_count":  this.Room_count,
		"order_count": this.Order_count,
		"address":     this.Address,
		"user_avatar": utils.AddDomain2Url(this.User.Avatar_url),
		"ctime":       this.Ctime.Format("2006-01-02 15:04:05"),
	}
    ],
   
  }
}


#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

14 搜索房源

获取(搜索)房源服务(商品相关)

创建命令

shell
$ micro new --type "srv" sss/GetHouses

流程与接口

im

json
#Request:
method: GET
#adi表示地区编号
#sd表示起始日期
#ed表示结束日期
#sk表示查询方式
#p表示页码
url:api/v1.0/houses?aid=5&sd=2017-11-12&ed=2017-11-30&sk=new
#data:
no input data
#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "current_page": 1,
    "houses": [
      {
        "address": "西三旗桥东",
        "area_name": "昌平区",
        "ctime": "2017-11-06 11:16:24",
        "house_id": 1,
        "img_url": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBJY-AL3m8AAS8K2x8TDE052.jpg",
        "order_count": 0,
        "price": 100,
        "room_count": 2,
        "title": "上奥世纪中心13号楼",
        "user_avatar": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBLFeALIEjAADexS5wJKs340.png"
      },
      {
        "address": "西三旗桥东",
        "area_name": "昌平区",
        "ctime": "2017-11-06 11:16:24",
        "house_id": 1,
        "img_url": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBJY-AL3m8AAS8K2x8TDE052.jpg",
        "order_count": 0,
        "price": 100,
        "room_count": 2,
        "title": "上奥世纪中心18号楼",
        "user_avatar": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBLFeALIEjAADexS5wJKs340.png"
        }
    ],
    "total_page": 1
  }
}

#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

15 发布订单

发送(发布)订单服务(订单相关)

创建命令

shell
$ micro new --type "srv" sss/PostOrders

流程与接口

im

json
#Request:
method: POST
url:api/v1.0/orders
#data:
{
  "house_id": "1",
  "start_date": "2017-11-11 21:23:49",
  "end_date": "2017-11-12 21:23:49",
}

#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "order_id":"1"
  }
}

#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

16 请求查看房东/租客订单信息

获取房东/租户订单信息服务(订单相关)

创建命令

shell
$ micro new --type "srv" sss/GetUserOrder

流程与接口

im

json
#Request:
method: GET
url:api/v1.0/user/orders?role=custom 备注:role=custom为租客查看订单信息
url:api/v1.0/user/orders?role=landlord 备注:role=landlord为房东查看被预定订单信息
#data:
no input data

#Response
#返回成功:
{
  "errno": "0",
  "errmsg": "成功",
  "data": {
    "orders": [
      {
        "amount": 200,
        "comment": "哈哈拒接",
        "ctime": "2017-11-11 21:23:49",
        "days": 2,
        "end_date": "2017-11-29 16:00:00",
        "img_url": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBJY-AL3m8AAS8K2x8TDE052.jpg",
        "order_id": 3,
        "start_date": "2017-11-28 16:00:00",
        "status": "REJECTED",//WAIT_ACCPET,WAIT_COMMENT,REJECTED,COMPLETE,CANCELED
        "title": "上奥世纪中心"
      },
      {
        "amount": 1500,
        "comment": "",
        "ctime": "2017-11-11 01:32:10",
        "days": 15,
        "end_date": "2017-11-24 16:00:00",
        "img_url": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBJY-AL3m8AAS8K2x8TDE052.jpg",
        "order_id": 2,
        "start_date": "2017-11-10 16:00:00",
        "status": "WAIT_COMMENT",
        "title": "上奥世纪中心"
      },
      {
        "amount": 300,
        "comment": "",
        "ctime": "2017-11-10 01:46:00",
        "days": 3,
        "end_date": "2017-11-11 16:00:00",
        "img_url": "http://101.200.170.171:9998/group1/M00/00/00/Zciqq1oBJY-AL3m8AAS8K2x8TDE052.jpg",
        "order_id": 1,
        "start_date": "2017-11-09 16:00:00",
        "status": "WAIT_COMMENT",
        "title": "上奥世纪中心"
      }
    ]
  }
}


#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

17 房东同意/拒绝订单

更新房东同意/拒绝订单(订单相关)

创建命令

shell
$ micro new --type "srv" sss/PutOrders

流程与接口

im

json
#Request:
method: PUT
#4表示订单id
url:api/v1.0/orders/4/status
url:api/v1.0/orders/:id/status
#data:
#"accept"表示接受
#"reject"表示拒绝
{action: "accept"}   

#Response
#返回成功:

{
  "errno": "0",
  "errmsg": "成功"
}

#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

18 用户评价订单信息

更新用户评价订单信息(订单相关)

创建命令

shell
$ micro new --type "srv" sss/PutComment

流程与接口

im

json
#Request:
method: PUT
#2表示订单id0
url:api/v1.0/orders/2/comment 
url:api/v1.0/orders/:id/comment 
#data:
{
    order_id: "2", 
    comment: "烂房子!"
}
#Response
#返回成功:

{
  "errno": "0",
  "errmsg": "成功"
}

#返回失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}