租房网
项目启动
拆分原则
1、单一职责
2、服务粒度适中
3、考虑团队结构
4、以业务模型切入
5、演进式拆分
6、避免环形依赖和双向依赖
服务发现的启动
项目开始之前前我们先要启动我们单机版的consul
$ consul agent -dev数据库的准备
在mysql中创建一个库
$ mysql -uroot -p#输入root密码#go2micro是库名Mysql>create database if not exists go2micro default charset utf8 collate utf8_general_ci; //创建1个库01 web端
REST
RESTful,是目前最为流行的一种互联网软件架构。因为它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用
什么是REST
REST(REpresentational State Transfer)这个概念,首次出现是在 2000年Roy Thomas Fielding(他是HTTP规范的主要编写者之一)的博士论文中,它指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful的。
要理解什么是REST,我们需要理解下面几个概念:
1 、资源(Resources)
REST是”表现层状态转化”,其实它省略了主语。“表现层”其实指的是”资源”的”表现层”。 那么什么是资源呢?就是我们平常上网访问的一张图片、一个文档、一个视频等。这些资源我们通过URI来定位,也就是一个URI表示一个资源。
2、表现层(Representation) 资源是做一个具体的实体信息,他可以有多种的展现方式。而把实体展现出来就是表现层,例如一个txt文本信息,他可以输出成html、json、xml等格式,一个图片他可以jpg、png等方式展现,这个就是表现层的意思。 URI确定一个资源,但是如何确定它的具体表现形式呢?应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对”表现层”的描述。
3、 状态转化(State Transfer) 访问一个网站,就代表了客户端(浏览器)和服务器的一个互动过程。在这个过程中,肯定涉及到数据和状态的变化。而HTTP协议是无状态的,那么这些状态肯定保存在服务器端,所以如果客户端想要通知服务器端改变数据和状态的变化,肯定要通过某种方式来通知它。
客户端能通知服务器端的手段,只能是HTTP协议。
具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:
查:GET用来获取资源
增:POST用来新建资源(也可以用于更新资源)
改:PUT用来更新资源
删:DELETE用来删除资源。
综合上面的解释,我们总结一下什么是RESTful架构: (1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现”资源 表现层 状态转化”。
RESTful的实现
Go没有为REST提供直接支持,但是因为RESTful是基于HTTP协议实现的,所以我们可以利用net/http包来自己实现,当然需要针对REST做一些改造,REST是根据不同的method来处理相应的资源,目前已经存在的很多自称是REST的应用,其实并没有真正的实现REST。
package main
import ( "fmt" "github.com/julienschmidt/httprouter" "net/http")
func getuser(w http.ResponseWriter, r *http.Request,ps httprouter.Params) { uid:=ps.ByName("uid") fmt.Fprintf(w, "获取用户名 %s", uid) mo:= r.Method fmt.Fprintf(w, "获取请求方式 %s", mo)
}
func main() {
router := httprouter.New() router.GET("/getuser/:uid", getuser)
http.Handle("/", router) http.ListenAndServe(":8088", nil)}上面的代码演示了如何编写一个REST的应用,我们通过get请求来访问对应的函数获取到了我传入的数据,这是离我们使用的是1个三方库
三方库安装
$ go get github.com/julienschmidt/httprouter总结
REST是一种架构风格,汲取了WWW的成功经验:无状态,以资源为中心,充分利用HTTP协议和URI协议,提供统一的接口定义,使得它作为一种设计Web服务的方法而变得流行。在某种意义上,通过强调URI和HTTP等早期Internet标准,REST是对大型应用程序服务器时代之前的Web方式的回归。目前Go对于REST的支持还是很简单的,通过实现自定义的路由规则,我们就可以通过不同的请求方法访问不同的函数。这样就实现了REST的架构。
创建命令
$ micro new --type "web" sss/IhomeWebweb服务的调整
修改main.go
package main
import ( "github.com/micro/go-log" "net/http" "github.com/micro/go-web" "sss/ihomeweb/handler" "github.com/julienschmidt/httprouter")
func main() { //创建web服务 service := web.NewService( web.Name("go.micro.web.ihomeweb"), web.Version("latest"), web.Address(":8999"), ) // 初始化服务 if err := service.Init(); err != nil { log.Fatal(err) } rou := httprouter.New() //映射静态页面 rou.NotFound = http.FileServer(http.Dir("html"))
//后续陆续添加服务所以这个文件的这个地方会一直添加内容
// 注册服务 service.Handle("/", rou)
// 运行服务 if err := service.Run(); err != nil { log.Fatal(err) }}创建工具函数文件夹
#创建工具函数文件夹$ mkdir utils# 进入文件夹创建文件$ cd utils# 配置文件读取函数文件$ vim config.go# 错误码文件$ vim error.go# 字符串拼接文件$ vim misc.go配置文件读取文件config.go
package utils
import ( "github.com/astaxie/beego" //使用了beego框架的配置文件读取模块 "github.com/astaxie/beego/config")
var ( G_server_name string //项目名称 G_server_addr string //服务器ip地址 G_server_port string //服务器端口 G_redis_addr string //redis ip地址 G_redis_port string //redis port端口 G_redis_dbnum string //redis db 编号 G_mysql_addr string //mysql ip 地址 G_mysql_port string //mysql 端口 G_mysql_dbname string //mysql db name G_fastdfs_port string //fastdfs 端口 G_fastdfs_addr string //fastdfs ip)
func InitConfig() { //从配置文件读取配置信息 appconf, err := config.NewConfig("ini", "./conf/app.conf") if err != nil { beego.Debug(err) return } G_server_name = appconf.String("appname") G_server_addr = appconf.String("httpaddr") G_server_port = appconf.String("httpport") G_redis_addr = appconf.String("redisaddr") G_redis_port = appconf.String("redisport") G_redis_dbnum = appconf.String("redisdbnum") G_mysql_addr = appconf.String("mysqladdr") G_mysql_port = appconf.String("mysqlport") G_mysql_dbname = appconf.String("mysqldbname") G_fastdfs_port = appconf.String("fastdfsport") G_fastdfs_addr = appconf.String("fastdfsaddr") return}
func init() { InitConfig()}错误码文件
package utils
const ( RECODE_OK = "0" RECODE_DBERR = "4001" RECODE_NODATA = "4002" RECODE_DATAEXIST = "4003" RECODE_DATAERR = "4004"
RECODE_SESSIONERR = "4101" RECODE_LOGINERR = "4102" RECODE_PARAMERR = "4103" RECODE_USERONERR = "4104" RECODE_ROLEERR = "4105" RECODE_PWDERR = "4106" RECODE_USERERR = "4107" RECODE_SMSERR = "4108" RECODE_MOBILEERR = "4109"
RECODE_REQERR = "4201" RECODE_IPERR = "4202" RECODE_THIRDERR = "4301" RECODE_IOERR = "4302" RECODE_SERVERERR = "4500" RECODE_UNKNOWERR = "4501")
var recodeText = map[string]string{ RECODE_OK: "成功", RECODE_DBERR: "数据库查询错误", RECODE_NODATA: "无数据", RECODE_DATAEXIST: "数据已存在", RECODE_DATAERR: "数据错误", RECODE_SESSIONERR: "用户未登录", RECODE_LOGINERR: "用户登录失败", RECODE_PARAMERR: "参数错误", RECODE_USERERR: "用户不存在或未激活", RECODE_USERONERR: "用户已经注册", RECODE_ROLEERR: "用户身份错误", RECODE_PWDERR: "密码错误", RECODE_REQERR: "非法请求或请求次数受限", RECODE_IPERR: "IP受限", RECODE_THIRDERR: "第三方系统错误", RECODE_IOERR: "文件读写错误", RECODE_SERVERERR: "内部错误", RECODE_UNKNOWERR: "未知错误", RECODE_SMSERR: "短信失败", RECODE_MOBILEERR: "手机号错误",}
func RecodeText(code string) string { str, ok := recodeText[code] if ok { return str } return recodeText[RECODE_UNKNOWERR]}字符串拼接文件
package utils
/* 将url加上 http://IP:PROT/ 前缀 *///http:// + 127.0.0.1 + :+ 8080 + 请求func AddDomain2Url(url string) (domain_url string) { domain_url = "http://" + G_fastdfs_addr + ":" + G_fastdfs_port + "/" + url
return domain_url}创建数据库文件
$ mkdir models#创建数据库文件$ vim models.gomodels.go文件基础内容
import ( "github.com/astaxie/beego/orm" _ "github.com/go-sql-driver/mysql")func init() {}models.go文件包的导入
import ( //beego的orm模块 "github.com/astaxie/beego/orm" //go操作数据库的模块 _ "github.com/go-sql-driver/mysql" //工具模块 "sss/ihomeweb/utils" //时间包 "time" //beego "github.com/astaxie/beego")models.go的表单与辅助工具
/* 用户 table_name = user */type User struct {Id int `json:"user_id"` //用户编号Name string `orm:"size(32)" json:"name"` //用户昵称Password_hash string `orm:"size(128)" json:"password"` //用户密码加密的Mobile string `orm:"size(11);unique" json:"mobile"` //手机号Real_name string `orm:"size(32)" json:"real_name"` //真实姓名 实名认证Id_card string `orm:"size(20)" json:"id_card"` //身份证号 实名认证Avatar_url string `orm:"size(256)" json:"avatar_url"` //用户头像路径 通过fastdfs进行图片存储Houses []*House `orm:"reverse(many)" json:"houses"` //用户发布的房屋信息 一个人多套房Orders []*OrderHouse `orm:"reverse(many)" json:"orders"` //用户下的订单 一个人多次订单}
/* 房屋信息 table_name = house */type House struct {Id int `json:"house_id"` //房屋编号User *User `orm:"rel(fk)" json:"user_id"` //房屋主人的用户编号 与用户进行关联Area *Area `orm:"rel(fk)" json:"area_id"` //归属地的区域编号 和地区表进行关联Title string `orm:"size(64)" json:"title"` //房屋标题Price int `orm:"default(0)" json:"price"` //单价,单位:分 每次的价格要乘以100Address string `orm:"size(512)" orm:"default("")" json:"address"` //地址Room_count int `orm:"default(1)" json:"room_count"` //房间数目Acreage int `orm:"default(0)" json:"acreage"` //房屋总面积Unit string `orm:"size(32)" orm:"default("")" json:"unit"` //房屋单元,如 几室几厅Capacity int `orm:"default(1)" json:"capacity"` //房屋容纳的总人数Beds string `orm:"size(64)" orm:"default("")" json:"beds"` //房屋床铺的配置Deposit int `orm:"default(0)" json:"deposit"` //押金Min_days int `orm:"default(1)" json:"min_days"` //最少入住的天数Max_days int `orm:"default(0)" json:"max_days"` //最多入住的天数 0表示不限制Order_count int `orm:"default(0)" json:"order_count"` //预定完成的该房屋的订单数Index_image_url string `orm:"size(256)" orm:"default("")" json:"index_image_url"` //房屋主图片路径Facilities []*Facility `orm:"reverse(many)" json:"facilities"` //房屋设施 与设施表进行关联Images []*HouseImage `orm:"reverse(many)" json:"img_urls"` //房屋的图片 除主要图片之外的其他图片地址Orders []*OrderHouse `orm:"reverse(many)" json:"orders"` //房屋的订单 与房屋表进行管理Ctime time.Time `orm:"auto_now_add;type(datetime)" json:"ctime"`}
//首页最高展示的房屋数量var HOME_PAGE_MAX_HOUSES int = 5
//房屋列表页面每页显示条目数var HOUSE_LIST_PAGE_CAPACITY int = 2//处理房子信息func (this *House) To_house_info() interface{} { house_info := map[string]interface{}{ "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"), }
return house_info}//处理1个房子的全部信息func (this *House) To_one_house_desc() interface{} { house_desc := map[string]interface{}{ "hid": this.Id, "user_id": this.User.Id, "user_name": this.User.Name, "user_avatar": utils.AddDomain2Url(this.User.Avatar_url), "title": this.Title, "price": this.Price, "address": this.Address, "room_count": this.Room_count, "acreage": this.Acreage, "unit": this.Unit, "capacity": this.Capacity, "beds": this.Beds, "deposit": this.Deposit, "min_days": this.Min_days, "max_days": this.Max_days, }
//房屋图片 img_urls := []string{} for _, img_url := range this.Images { img_urls = append(img_urls, utils.AddDomain2Url(img_url.Url)) } house_desc["img_urls"] = img_urls
//房屋设施 facilities := []int{} for _, facility := range this.Facilities { facilities = append(facilities, facility.Id) } house_desc["facilities"] = facilities
//评论信息
comments := []interface{}{} orders := []OrderHouse{} o := orm.NewOrm() order_num, err := o.QueryTable("order_house").Filter("house__id", this.Id).Filter("status", ORDER_STATUS_COMPLETE).OrderBy("-ctime").Limit(10).All(&orders) if err != nil { beego.Error("select orders comments error, err =", err, "house id = ", this.Id) } for i := 0; i < int(order_num); i++ { o.LoadRelated(&orders[i], "User") var username string if orders[i].User.Name == "" { username = "匿名用户" } else { username = orders[i].User.Name }
comment := map[string]string{ "comment": orders[i].Comment, "user_name": username, "ctime": orders[i].Ctime.Format("2006-01-02 15:04:05"), } comments = append(comments, comment) } house_desc["comments"] = comments
return house_desc}
/* 区域信息 table_name = area */ //区域信息是需要我们手动添加到数据库中的type Area struct {Id int `json:"aid"` //区域编号 1 2Name string `orm:"size(32)" json:"aname"` //区域名字 昌平 海淀Houses []*House `orm:"reverse(many)" json:"houses"` //区域所有的房屋 与房屋表进行关联}
/* 设施信息 table_name = "facility"*/ //设施信息 需要我们提前手动添加的type Facility struct {Id int `json:"fid"` //设施编号Name string `orm:"size(32)"` //设施名字Houses []*House `orm:"rel(m2m)"` //都有哪些房屋有此设施 与房屋表进行关联的}
/* 房屋图片 table_name = "house_image"*/type HouseImage struct {Id int `json:"house_image_id"` //图片idUrl string `orm:"size(256)" json:"url"` //图片url 存放我们房屋的图片House *House `orm:"rel(fk)" json:"house_id"` //图片所属房屋编号}
const (ORDER_STATUS_WAIT_ACCEPT = "WAIT_ACCEPT" //待接单ORDER_STATUS_WAIT_PAYMENT = "WAIT_PAYMENT" //待支付ORDER_STATUS_PAID = "PAID" //已支付ORDER_STATUS_WAIT_COMMENT = "WAIT_COMMENT" //待评价ORDER_STATUS_COMPLETE = "COMPLETE" //已完成ORDER_STATUS_CANCELED = "CONCELED" //已取消ORDER_STATUS_REJECTED = "REJECTED" //已拒单)
/* 订单 table_name = order */type OrderHouse struct {Id int `json:"order_id"` //订单编号User *User `orm:"rel(fk)" json:"user_id"` //下单的用户编号 //与用户表进行关联House *House `orm:"rel(fk)" json:"house_id"` //预定的房间编号 //与房屋信息进行关联Begin_date time.Time `orm:"type(datetime)"` //预定的起始时间End_date time.Time `orm:"type(datetime)"` //预定的结束时间Days int //预定总天数House_price int //房屋的单价Amount int //订单总金额Status string `orm:"default(WAIT_ACCEPT)"` //订单状态Comment string `orm:"size(512)"` //订单评论Ctime time.Time `orm:"auto_now;type(datetime)" json:"ctime"` //每次更新此表,都会更新这个字段Credit bool //表示个人征信情况 true表示良好}//处理订单信息func (this *OrderHouse) To_order_info() interface{} { order_info := map[string]interface{}{ "order_id": this.Id, "title": this.House.Title, "img_url": utils.AddDomain2Url(this.House.Index_image_url), "start_date": this.Begin_date.Format("2006-01-02 15:04:05"), "end_date": this.End_date.Format("2006-01-02 15:04:05"), "ctime": this.Ctime.Format("2006-01-02 15:04:05"), "days": this.Days, "amount": this.Amount, "status": this.Status, "comment": this.Comment, "credit": this.Credit, }
return order_info}models.go的函数
func init() { //注册mysql的驱动 orm.RegisterDriver("mysql", orm.DRMySQL)
// 设置默认数据库 orm.RegisterDataBase("default", "mysql", "root:1@tcp("+utils.G_mysql_addr+":"+utils.G_mysql_port+")/micropgc?charset=utf8", 30)
//注册model orm.RegisterModel(new(User), new(House), new(Area), new(Facility), new(HouseImage), new(OrderHouse))
// create table //第一个是别名 // 第二个是是否强制替换模块 如果表变更就将false 换成true 之后再换回来表就便更好来了 //第三个参数是如果没有则同步或创建 orm.RunSyncdb("default", false, true)
}运行服务并且创建表单
#创建conf文件夹用来存放配置文件$ mkdir conf#创建data.sql文件$ vim data.sqldatat.sql文件内容
INSERT INTO `area`(`name`) VALUES ('东城区'),('西城区'),('朝阳区'),('海淀区'),('昌平区'),('丰台区'),('房山区'),('通州区'),('顺义区'),('大兴区'),('怀柔区'),('平谷区'),('密云区'),('延庆区'),('石景山区');INSERT INTO `facility`(`name`) VALUES('无线网络'),('热水淋浴'),('空调'),('暖气'),('允许吸烟'),('饮水设备'),('牙具'),('香皂'),('拖鞋'),('手纸'),('毛巾'),('沐浴露、洗发露'),('冰箱'),('洗衣机'),('电梯'),('允许做饭'),('允许带宠物'),('允许聚会'),('门禁系统'),('停车位'),('有线网络'),('电视'),('浴缸'),('吃鸡'),('打台球');登入mysql进行数据导入
#登录mysql$ mysql -uroot -p#输入root密码Mysql> use go1micro#数据的导入mysql> source ./conf/data.sql#数据检查mysql> select * from area;mysql> select * from facility;创建1个app.conf文件
app.conf文件内容
#应用名称appname = ihome#地址httpaddr = 127.0.0.1#端口httpport = 8080#数据库地址mysqladdr = 192.168.110.20#数据库端口mysqlport = 3306导入前端页面
将html压缩到拷贝到项目将内容替换掉html文件夹中的内容$unzip html