[三国第三章] :

目录


一、进入游戏服务

//- 1创建game server
//- 2在controller层完成路由初始化和业务逻辑
// - 2-1加载基础、地图资源、地图单元格、城池设施、武将、技能配置到gameConfig包的结构体里
// - 2-2添加角色路由和做进入的游戏的逻辑
// - 2-2-1查询用户及角色资源\初始化玩家属性\城池
// - 2-2-2查询角色拥有的属性(资源、城池、建筑、部队、武将)
// - 2-3添加地图路由和做地图相关的逻辑
// - 2-4做初始化城池逻辑:根据城市id,从城市-设施表查找对应项,然后将配置文件中的城市设施填充到"城市-设施"结构体,再写回数据库
// - 2-5做标记逻辑:存储role_attribute 的pos_tag 数据,[{“x”:74,“y”:81,“name”:“土地Lv.2”}]
//- 3启动服务

1、

func main() {
	host := config.File.MustValue("game_server", "host", "127.0.0.1")
	port := config.File.MustValue("game_server", "port", "8001")
	s := net.NewServer(host + ":" + port) //- 1创建game server
	s.NeedSecret(false)
	game.Init()           //- 2在controller层完成路由初始化和业务逻辑
	s.Router(game.Router) //将初始化的路由赋给server
	s.Start()             //- 3启动服务
}
func Init() {
	db.TestDB()
	// - 2-1加载基础和地图资源配置
	//加载基础配置
	gameConfig.Base.Load()
	//加载地图的资源配置
	gameConfig.MapBuildConf.Load()
	initRouter()
}

// - 2-2添加角色路由和做进入的游戏的逻辑
func (r *RoleController) Router(router *net.Router) {
	g := router.Group("role")
	g.AddRouter("enterServer", r.enterServer)
	g.AddRouter("myProperty", r.myProperty)
}

// - 2-2-1查询用户及角色资源\初始化玩家属性\城池
func (r *RoleController) enterServer(req *net.WsMsgReq, rsp *net.WsMsgRsp) {
	//Session 需要验证是否合法 合法的情况下 可以取出登录的用户id
	//根据用户id 去查询对应的游戏角色,如果有 就继续 没有 提示无角色
	//根据角色id 查询角色拥有的资源 roleRes,如果资源有 返回,没有 初始化资源
	reqObj := &model.EnterServerReq{}
	rspObj := &model.EnterServerRsp{}
	err := mapstructure.Decode(req.Body.Msg, reqObj)
	rsp.Body.Seq = req.Body.Seq
	rsp.Body.Name = req.Body.Name
	if err != nil {
		rsp.Body.Code = constant.InvalidParam
		return
	}
	session := reqObj.Session //校验session合法性即解析token
	_, claim, err := utils.ParseToken(session)
	if err != nil {
		rsp.Body.Code = constant.SessionInvalid
		return
	}
	uid := claim.Uid

	err = logic.RoleService.EnterServer(uid, rspObj, req.Conn)
	if err != nil {
		rsp.Body.Code = err.(*common.MyError).Code()
		return
	}
	rsp.Body.Code = constant.OK
	rsp.Body.Msg = rspObj
}

// - 2-2-2查询角色拥有的属性(资源、城池、建筑、部队、武将)
func (r *RoleController) myProperty(req *net.WsMsgReq, rsp *net.WsMsgRsp) {
	//分别根据角色id 去查询 军队 资源 建筑 城池 武将
	role, err := req.Conn.GetProperty("role")
	if err != nil {
		rsp.Body.Code = constant.SessionInvalid
		return
	}
	rsp.Body.Seq = req.Body.Seq
	rsp.Body.Name = req.Body.Name
	rid := role.(*data.Role).RId
	rspObj := &model.MyRolePropertyRsp{}
	//资源
	rspObj.RoleRes, err = logic.RoleService.GetRoleRes(rid)
	if err != nil {
		rsp.Body.Code = err.(*common.MyError).Code()
		return
	}
	//城池
	rspObj.Citys, err = logic.RoleCityService.GetRoleCitys(rid)
	if err != nil {
		rsp.Body.Code = err.(*common.MyError).Code()
		return
	}
	//建筑
	rspObj.MRBuilds, err = logic.RoleBuildService.GetBuilds(rid)
	if err != nil {
		rsp.Body.Code = err.(*common.MyError).Code()
		return
	}
	//军队
	rspObj.Armys, err = logic.ArmyService.GetArmys(rid)
	if err != nil {
		rsp.Body.Code = err.(*common.MyError).Code()
		return
	}
	//武将
	rspObj.Generals, err = logic.GeneralService.GetGenerals(rid)
	if err != nil {
		rsp.Body.Code = err.(*common.MyError).Code()
		return
	}
	rsp.Body.Code = constant.OK
	rsp.Body.Msg = rspObj
}

2-5 做标记逻辑

数据库就是存储role_attribute 的pos_tag 数据,[{“x”:74,“y”:81,“name”:“土地Lv.2”}]
具体实现:controller层先从conn中获取角色,然后service层根据角色id查询角色属性(roleAttribute)

2-6 我的武将

路由:general.myGenerals
首先加载武将信息,然偶后controller层根据角色id查询武将,如果没有武将,则随机生成三个武将

2-7 我的军队

路由:army.myList
军队不用初始化,刚进来没有军队,军队跟城市绑定,根据城市id查询军队

2-8 我的战报

涉及war_report
controller处理战报请求,并从conn连接中取出角色,service层根据角色id查询角色所有战报,涵盖攻防所有战报,最后将data层查询出的数据转成客户端需要的数据返回即可.

2-9 我的技能

设计skill.list表,包括技能id,归属武将等
跟我的战报差不多,就把战报换成技能接口

二、一些优化和功能

1、事务

用于保证多个业务逻辑过程同时成功,只要有一个出错就不会写入数据库.

session := db.Engine.NewSession()//新建事务
defer session.close()
session.Begin()//开启事务
//将db.Engine替换成session由session接管增删改查操作.如:
_,err := session.Table(roleRes).Insert(roleRes)
//如果有逻辑出错使用Session.Rollback()然后return,其实不Rollback也可以,因为出错后return了,没有执行session.Commit(),就不会固化到数据库
session.Commit()

为了保证事务在不同服务相同链路间传递,可以在请求结构体中添加一个上下文context,在service层将session放到ctx中,就可以使用事务控制不同服务业务逻辑一致性

2、中间件

go语言中,中间件的应用非常广,“中间件”通常意思是“包装原始应用并添加一些额外的功能的应用的一部分”。比如日志,权限,认证等

比如:在我们的应用中,很多接口都要用到角色的信息,我们需要从conn中进行获取,并进行判断其是否存在,我们可以将这个判断用于是否拥有角色的业务逻辑抽取出去,进行统一验证。

实现过程:

  • 1.定义中间件结构体,在路由组结构体里添加中间件属性
  • 2.定义新增"路由和组中间件"的函数
  • 3.定义具体中间件,并使用2的函数,将中间件添加到结构体保存
  • 4.在执行路由请求对应函数前,执行中间件逻辑
// - 1.定义中间件结构体
type HandlerFunc func(req *WsMsgReq, rsp *WsMsgRsp)
type MiddlewareFunc func(handlerFunc HandlerFunc) HandlerFunc

// - 2.在路由组结构体里添加中间件
type group struct {
	mutex         sync.RWMutex
	prefix        string
	handlerMap    map[string]HandlerFunc
	middlewareMap map[string][]MiddlewareFunc //某个路由需要执行的对应的中间件
	middlewares   []MiddlewareFunc            //整个组里需要执行的中间件
}

// 	- 3.定义添加路由中间件
func (g *group) AddRouter(name string, handlerFunc HandlerFunc, middlewares ...MiddlewareFunc) {
	g.mutex.Lock()
	defer g.mutex.Unlock()
	g.handlerMap[name] = handlerFunc
	g.middlewareMap[name] = middlewares
}
// - 4.定义添加组中间件
func (g *group) Use(middlewares ...MiddlewareFunc) {
	g.middlewares = append(g.middlewares, middlewares...)
}

// - 5.定义添加检查角色中间件
func CheckRole() net.MiddlewareFunc  {
	return func(next net.HandlerFunc) net.HandlerFunc {
		return func(req *net.WsMsgReq, rsp *net.WsMsgRsp) {
			log.Println("进入到角色检测....")
			_ , err := req.Conn.GetProperty("role")
			if err != nil {
				rsp.Body.Code = constant.SessionInvalid
				return
			}
			next(req,rsp)//检查完毕执行请求函数,中间件定义的时候,定义为请求函数类型,执行完中间件逻辑后,继续执行请求函数即可
			
		}
	}
}

// - 6.定义添加日志打印中间件
func Log() net.MiddlewareFunc  {
	return func(next net.HandlerFunc) net.HandlerFunc {
		return func(req *net.WsMsgReq, rsp *net.WsMsgRsp) {
			log.Println("请求路由",req.Body.Name)
			log.Println("请求参数",fmt.Sprintf("%v",req.Body.Msg))
			next(req,rsp)
		}
	}
}

// - 7.添加组和路由中间件
func (r *RoleController) Router(router *net.Router) {
	g := router.Group("role")
	g.Use(middleware.Log())
	g.AddRouter("create", r.create)
	g.AddRouter("enterServer", r.enterServer)
	g.AddRouter("myProperty", r.myProperty, middleware.CheckRole())
	g.AddRouter("posTagList", r.posTagList, middleware.CheckRole())
}

// - 8.执行中间件
func (g *group) exec(name string, req *WsMsgReq, rsp *WsMsgRsp) {
	h, ok := g.handlerMap[name] //如果name有处理函数,则执行中间件和请求函数
	if !ok {
		h, ok = g.handlerMap["*"] //复用ok,如果有*,即过滤任何路由,则也执行中请求函数
		if !ok {
			log.Println("路由未定义")
		}
	}
	if ok {
		//- 8.在执行路由之前,需要执行中间件代码
		for i := 0; i < len(g.middlewares); i++ {
			h = g.middlewares[i](h) //使用i中间件处理请求函数h
		}
		mm, ok := g.middlewareMap[name]
		if ok {
			for i := 0; i < len(mm); i++ {
				h = mm[i](h)
			}
		}
		h(req, rsp) //继续处理请求函数
	}
}

3、扫描地图资源请求

鼠标移动时,需要扫描对应位置地图资源

路由: nationMap.scanBlock
业务逻辑实现流程:在controller层分别调用service层的"扫描角色建筑"、“扫描角色城池”、"扫描角色军队"三个服务,获取结果填充到rsp返回即可.
以查询角色建筑为例:首先,在初始化时从配置文件里load系统和玩家建筑,保存到service层的角色建筑结构体里。然后controller层调用角色建筑sevice层ScanBlock方法,将x,y传入获取该点角色建筑.
角色建筑因为存在占领,需要写数据库,而角色城池,只需要查询显示不同玩家城池.
“扫描玩家军队”:军队信息存储在一个map中,map的key是坐标点,value还是一个map,此map的key是armyID。该功能会遍历查找某块地图半径范围内所有军队(传入的是点和length,和角色id,据此进行查询),然后判断军队是否在角色视野范围内或者是盟友,如果是就加入结果,最后返回结果即可.

4、创建角色

路由:role.create

参数:

type CreateRoleReq struct {
	UId			int		`json:"uid"`
	NickName 	string	`json:"nickName"`
	Sex			int8	`json:"sex"`
	SId			int		`json:"sid"`
	HeadId		int16	`json:"headId"`
}

根据前端传递请求往数据库插入角色,并返回角色信息即可

三、

1. 查询下次征收时间

路由:interior.openCollect
根据角色id查询role attribute表,根据collect_times征收次数和last_collect_time最后征收时间进行计算
过程:在role attribute service层先将信息查询缓存到本地map.然后从map中获取rid,根据rid获取征收次数和征收间隔,如果今天征收次数用完,则间隔24小时再次征收,如果次数没用完,下次征收就是最后征收时间+间隔

2.征收资源

路由:interior.collect
1.查询角色属性 获取征收的相关信息(征收次数,最后一次征收时间)
2.查询角色资源 得到当前的金币(Gold)
3.查询获取当前的产量(yield) 和 征收的金币是多少
4.将当前金币和征收金币相加获取最新金币,向dao层的channel发送一个更新消息,角色资源dao层在init方法开启一个go协程监听channel消息,一旦监听到消息,就更新角色资源表.
5.然后计算产量,也向角色属性dao层发送一个消息,也用channel固化数据库.
6.最后将金币和征收结果返回给前端即可

3.怎么控制资源刷新?或怎么定期获取资源?

在角色资源里load加载资源的时候,起一个go协程每隔一段时间获取资源和容量,在未超容量前提下,不断增加资源即可
武将回复体力类似
在武将load加载资源完毕,起一个go协程每隔一段时间获取体力和体力上线,在未超容量前提下,不断增加体力。

4. 征兵

路由:army.conscript
//征兵
type ConscriptReq struct {
ArmyId int json:"armyId" //队伍id
Cnts []int json:"cnts" //征兵人数 [20,20,0]
}
type ConscriptRsp struct {
Army Army json:"army"
RoleRes RoleRes json:"role_res"
}
请求参数是军队id和各武将需征兵人数的数组,返回军队和征兵完剩余资源
征兵实现过程:首先查询角色和军队,然后根据武将和募兵所等级计算征兵上限,确保武将请求的征兵数满足上限。最后计算征兵消耗资源,更新每个武将对应的士兵人数(每个城池最多3个武将,同时军队也对应3个征兵槽)。

4.1 查看某一个部队详情

路由:army.myOne
请求参数“城市id”和“部队id”,返回“Army军队”
实现过程:根据“城市id”和“部队id”查询“Army军队”返回即可

派遣军队

路由:army.assign
请求参数"军队id"、“命令(如攻击、驻军)”、“移动位置坐标”,返回"Army详情"

  • 1初始化时开启两个协程监听军队到达和到达后处理
    check协程:不断检测军队是否到达,一直遍历一个map,key是到达时间,value是到达军队,若当前时间>到达时间,就将到达军队作为信号发给running协程处理
    running协程:监听到达军队channel,一旦有军队到达,就开启一场战争

  • 1.1城池攻打处理
    查询城池是否有驻守军队(驻守军队也是map,key是positionId,value是军队)
    如果没有军队驻守,则直接攻打城池耐久
    如果有军队,就处理战斗,对于每个敌军都会产生战斗、生成战报、升级将领,如果战斗成功,则从map里删除驻军,否则就更新战报就可
    具体战斗过程为(随机出手,根据攻击和防御、兵种的克制扣减士兵;结束的条件 主将 士兵为0 或者到达最大回合数)

  • 1.2 建筑攻打处理
    与上面不同的是:没有玩家军队驻守的情况下,随机生成NPC军队进行战斗,其他相同。

  • 2查询军队,执行命令

  • 2.1占领
    首先确保军队能出战、土地非山地和自己或盟友城池、体力够用
    然后扣减体力,将到达时间和到达军队加入map

3.联盟

3.1 联盟列表

路由:union.list
实现逻辑:在init方法中load角色属性到map中保存
(key是角色id,值是角色属性,包括联盟id),并将联盟id设置进联盟的每个成员list方法就是遍历这个联盟map

type Union struct {
Id int json:"id" //联盟id
Name string json:"name" //联盟名字
Cnt int json:"cnt" //联盟人数
Notice string json:"notice" //公告
Major []Major json:"major" //联盟主要人物,盟主副盟主
}
涉及到的表:

CREATE TABLE `coalition`  (
  `id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '联盟名字',
  `members` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '成员',
  `create_id` int(0) UNSIGNED NOT NULL COMMENT '创建者id',
  `chairman` int(0) UNSIGNED NOT NULL COMMENT '盟主',
  `vice_chairman` int(0) UNSIGNED NOT NULL COMMENT '副盟主',
  `notice` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '公告',
  `state` tinyint(0) UNSIGNED NOT NULL DEFAULT 1 COMMENT '0解散,1运行中',
  `ctime` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '联盟' ROW_FORMAT = Dynamic;
CREATE TABLE `coalition_apply`  (
  `id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'id',
  `union_id` int(0) UNSIGNED NOT NULL COMMENT '联盟id',
  `rid` int(0) UNSIGNED NOT NULL COMMENT '申请者的rid',
  `state` tinyint(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请状态,0未处理,1拒绝,2通过',
  `ctime` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '联盟申请表' ROW_FORMAT = Dynamic;
CREATE TABLE `coalition_log`  (
  `id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'id',
  `union_id` int(0) UNSIGNED NOT NULL COMMENT '联盟id',
  `op_rid` int(0) UNSIGNED NOT NULL COMMENT '操作者id',
  `target_id` int(0) UNSIGNED NULL DEFAULT NULL COMMENT '被操作的对象',
  `des` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '描述',
  `state` tinyint(0) UNSIGNED NOT NULL COMMENT '0:创建,1:解散,2:加入,3:退出,4:踢出,5:任命,6:禅让,7:修改公告',
  `ctime` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '发生时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '联盟日志表' ROW_FORMAT = Dynamic;

3.2 联盟详情

路由:union.info
从前端接收联盟id,返回联盟详情即可

3.3 申请列表

路由:union.applyList
根据联盟id,查找联盟,然后比较当前角色是否是联盟主席,如果是才返回申请人相关信息,不是则返回空

4.武将

4.1 武将抽卡

路由:general.drawGeneral
参数:抽卡次数
返回值:武将切片
实现逻辑:

  1. 计算抽卡花费的金钱
  2. 判断金钱是否足够
  3. 判断角色卡池是否足够 (待抽卡的次数 + 已有的武将<限制 )
  4. 根据次数随机生成武将
  5. 扣除金币 返回即可

4.2 给城市配置武将

路由:army.dispose
配置武将就是将某个武将配置到某个城市的哪一队的哪一个位置(如:将"张良"配置到"长城"的"第1队"的"位置1")

确保主城存在且属于该角色
确保将领存在且属于该角色
确保"第几大队"小于主城校场等级(3级校场允许有1\2\3队)
查询当前主城的军队 没有就创建,然后确保该军队在主城内,即军队坐标和主城坐标重合,然后进行上下阵操作
如果请求位置为-1则是下阵请求:确保武将空闲或不在征兵状态,然后把将领状态设为"未上阵"状态,即"将军队的将领状态设为"下阵",将领的主城id设为0(没有主城)
否则就是上阵请求:要确保将领空闲或不在征兵状态,同时确保新军队和将领之前状态为"未上阵",然后确保所有将领cost之和满足条件(cost小于一定值,该值随主城等级增加而增加,用于控制将领个数),最后用新军队将领代替旧军队将领,并设置新将领所属城市即可

5. 城池设施

1.查询

路由:city.facilities

根据城市id返回城池设施列表:先提前从数据库中查询所有城市设施,然后存储到service层的map中,key是城市id,value是城市设施的切片.

2.升级设施

路由:city.upFacility

  1. 需要根据城池id 查询城池
  2. 根据城池id和角色id查询城池的设施
  3. 升级设施,需要当前剩余升级时间upTime==0, 而且资源否符合
  4. 升级完成 设施更新和资源减少的内容固话到数据库
  5. 资源查询出来 返回前端

6.交易

路由:interior.transform
请求结构体:
type TransformReq struct {
From []int json:"from" //0 Wood 1 Iron 2 Stone 3 Grain
To []int json:"to" //0 Wood 1 Iron 2 Stone 3 Grain
}
我们要做的是把From数组内容转到To数组
首先,做交易的时候在主城做交易,需要根据角色id查找城市id,再根据城市id查找"集市"level的城市设施存在
然后,根据角色id查找角色拥有的资源,再把角色资源减去"From数组资源"、“加上To数组资源”,最后将资源通过channel写回数据库即可

四 、

1、各层数据结构你是怎么放的?

数据库对应的结构体 放到 data目录
客户端需要的数据 放到 model目录

2、

3、

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值