Сервер на Go для мобильной стратегии
armor5games.com
github.com/gocraft/web
github.com/gocraft/dbr
github.com/gocraft/health
github.com/penlook/daemon
github.com/pmylund/go-cache
bonus/dbr
github.com/gocraft/web
Routers
Context
Middleware
type Context struct {
ReadSession *dbr.Session
WriteSession *dbr.Session
HealthStream *health.Stream
Cache *cache.Cache
User *user.Params
}
router := web.New(context.Context{}).
Middleware((*context.Context).AddDbrSession).
Middleware((*context.Context).LogURL)
router.Get("/crossdomain.xml",CrossDomainHandler)
func (c *Context) AddDbrSession(rw web.ResponseWriter, req *web.Request,
next web.NextMiddlewareFunc) {
c.HealthStream = health.NewStream()
c.ReadSession = GetReadSession()
c.WriteSession = GetWriteSession()
next(rw, req)
}
func CrossDomainHandler(rw web.ResponseWriter, req *web.Request) {
rw.Header().Set("Content-type", "application/xml; charset=utf-8")
fmt.Fprintln(rw, `<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>`)
}
userRouter := router.Subrouter(context.Context{}, "/user")
userRouter.Middleware((*context.Context).UserRequired)
userRouter.Get("/", user.UserGet)
func (c *Context) UserRequired(rw web.ResponseWriter, req *web.Request,
next web.NextMiddlewareFunc) {
c.User := new(user.Params)
if err := c.User.LoadFromAccessToken(req); err == nil {
go userLastActive(c)
next(rw, req)
return
}
fmt.Fprintln(rw, “invalid access token”)
}
func UserGet(c *context.Context, rw web.ResponseWriter, req *web.Request) {
job := c.HealthStream.NewJob("user/get")
answer.AnswerJson(c.User, rw, req)
job.Complete(health.Success)
}
github.com/gocraft/dbr
MySql
Query Builder
Transactions
Interpolate
type UserParams struct {
ID int64 `db:"ID"`
Name string `db:"Name"`
AccessToken dbr.NullString `db:"AccessToken"`
}
func LoadFromAccessToken(req *web.Request) {
user_params := new(UserParams)
err := ReadSession.Select("*").From("users").
Where("AccessToken=?", req.GetAccessToken()).
LoadStruct(user_params)
return err
}
func userLastActive(c *Context) {
c.WriteSession.
Update("user_session").Set("Last", time.Now().Unix()).Where("User=?", c.
User.ID).
Exec()
}
type User_stats struct {
User int64 `db:"User"`
AdvWins int64 `db:"AdvWins"`
AdvLose int64 `db:"AdvLose"`
DailyWin int64 `db:"DailyWin"`
DailyLose int64 `db:"DailyLose"`
PvpaAttacks int64 `db:"PvpaAttacks"`
PvpaWins int64 `db:"PvpaWins"`
PvpaDefs int64 `db:"PvpaDefs"`
Sieges int64 `db:"Sieges"`
SiegesWins int64 `db:"SiegesWins"`
SiegesDefs int64 `db:"SiegesDefs"`
}
func CreateUserStatistics() error {
stats := new(User_stats)
tx, err := WriteSession.Begin()
defer tx.RollbackUnlessCommitted()
result, err := tx.InsertInto("user_stats").
Columns("User", "AdvWins", "AdvLose", "DailyWin", "DailyLose",
"PvpaAttacks", "PvpaWins", "PvpaDefs",
"Sieges", "SiegesWins", "SiegesDefs").
Record(stats).Exec()
tx.Commit()
}
github.com/gocraft/health
Jobs
Events
Timings
Errors
var stream = health.NewStream()
// jsonSink for healthTop
sink := health.NewJsonPollingSink(time.Minute, time.Minute * 5)
stream.AddSink(sink)
// stdout sink
stream.AddSink(&health.WriterSink{os.Stdout})
// https://bugsnag.com/ sink
stream.AddSink(bugsnag.NewSink(&bugsnag.Config{APIKey: "supersecretapikey"}))
func UserBuildingsUpgrade(c *context.Context, rw web.ResponseWriter,
req *web.Request) {
job := c.HealthStream.NewJob(“user/building/upgrade”)
type := req.GetBuildingType()
if isBuildingTypeValid(type) == false {
answer.answerErrorString(“invalid building type”, rw, req)
job.Event(“invalid building type”)
job.Complete(health.Junk)
return
}
if c.User.Money < getBuildingUpgradePrice(type) {
answer.answerErrorString(“no money”, rw, req)
job.EventKv(“cheater detected”,
map[string]string{“User” : c.User.ID, “cheater” : ”true”})
job.Complete(health.Junk)
return
}
if err := buildingUpgrade(type, c.User); err != nil {
answer.answerErrorString(“db error”, rw, req)
job.EventErr(“buildingUpgrade: ”, err)
job.Complete(health.Error)
return
}
if err := userMoneyMinus(c.User, price); err != nil {
answer.answerErrorString(“db error”, rw, req)
job.EventErr(“userMoneyMinus: ”, err)
job.Complete(health.Error)
return
}
job.Complete(health.Success)
}
github.com/penlook/daemon
Service
Signals
Daemon
...
go serv.Serve(listener)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)
for {
select {
case killSignal := <-interrupt:
stdlog.Println("Got signal:", killSignal)
stdlog.Println("Stoping listening on ", serv.Addr)
listener.Close()
if killSignal == os.Interrupt {
return "Daemon was interruped by system signal", nil
}
return "Daemon was killed", nil
}
}
github.com/pmylund/go-cache
Cache
Cache
Cache
Cache
package context
c.Cache = cache.New(time.Minute * 100, time.Minute * 1)
----------------------------------------------------------------------------
package static
import (
"strconv"
"github.com/gocraft/web"
"github.com/pmylund/go-cache"
)
const (
CACHE_STATIC_EQUIP string = "static/equip"
)
func StaticEquip(c *context.Context, rw web.ResponseWriter, req *web.Request) {
if data, ok := c.Cache.Get(CACHE_STATIC_EQUIP); ok {
answer.CachedAnswer(data, rw, req)
return
}
var equips []*Equip
_, err := c.ReadSession.Select("*").From("equipments").LoadStructs(&equips)
forCache := make(map[string][]*Equip)
forCache[“Equip”] = equips
answerJsonString := json.Marshal(&forCache)
c.Cache.Set(CACHE_STATIC_EQUIP, answerJsonString,
cache.DefaultExpiration)
answer.CachedAnswer(answerJsonString, rw, req)
}
Bonus
Эрик Реймонд: Искусство программирования в UNIX
Правило генерации:
Избегайте ручного набора кода; при любом удобном случае пишите
программы, которые бы писали программы.
CREATE TABLE Persons
(
PersonID int,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
);
type Persons struct {
PersonID int64 `db:"PersonID"`
LastName string `db:"LastName"`
FirstName string `db:"FirstName"`
Address dbr.NullString `db:"Address"`
City dbr.NullString `db:"City"`
}
"show full tables where Table_Type != 'VIEW'"
package databasemodels
import {{.DbrUsed}}"github.com/gocraft/dbr"
type {{.Table}} struct {
{{range .Fields}}{{.Name}} {{.Type}} {{.Tag}}
{{end}}
}
Виталий Симирнин
finalist736@gmail.com
https://golang-ru.slack.com/team/finalist
Armor5Games.com
armor5games@gmail.com

Cервер на Go для мобильной стратегии

  • 1.
    Сервер на Goдля мобильной стратегии armor5games.com
  • 3.
  • 4.
  • 5.
    type Context struct{ ReadSession *dbr.Session WriteSession *dbr.Session HealthStream *health.Stream Cache *cache.Cache User *user.Params } router := web.New(context.Context{}). Middleware((*context.Context).AddDbrSession). Middleware((*context.Context).LogURL) router.Get("/crossdomain.xml",CrossDomainHandler)
  • 6.
    func (c *Context)AddDbrSession(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) { c.HealthStream = health.NewStream() c.ReadSession = GetReadSession() c.WriteSession = GetWriteSession() next(rw, req) } func CrossDomainHandler(rw web.ResponseWriter, req *web.Request) { rw.Header().Set("Content-type", "application/xml; charset=utf-8") fmt.Fprintln(rw, `<?xml version="1.0"?> <cross-domain-policy> <allow-access-from domain="*"/> </cross-domain-policy>`) }
  • 7.
    userRouter := router.Subrouter(context.Context{},"/user") userRouter.Middleware((*context.Context).UserRequired) userRouter.Get("/", user.UserGet) func (c *Context) UserRequired(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) { c.User := new(user.Params) if err := c.User.LoadFromAccessToken(req); err == nil { go userLastActive(c) next(rw, req) return } fmt.Fprintln(rw, “invalid access token”) }
  • 8.
    func UserGet(c *context.Context,rw web.ResponseWriter, req *web.Request) { job := c.HealthStream.NewJob("user/get") answer.AnswerJson(c.User, rw, req) job.Complete(health.Success) }
  • 9.
  • 10.
    type UserParams struct{ ID int64 `db:"ID"` Name string `db:"Name"` AccessToken dbr.NullString `db:"AccessToken"` } func LoadFromAccessToken(req *web.Request) { user_params := new(UserParams) err := ReadSession.Select("*").From("users"). Where("AccessToken=?", req.GetAccessToken()). LoadStruct(user_params) return err }
  • 11.
    func userLastActive(c *Context){ c.WriteSession. Update("user_session").Set("Last", time.Now().Unix()).Where("User=?", c. User.ID). Exec() }
  • 12.
    type User_stats struct{ User int64 `db:"User"` AdvWins int64 `db:"AdvWins"` AdvLose int64 `db:"AdvLose"` DailyWin int64 `db:"DailyWin"` DailyLose int64 `db:"DailyLose"` PvpaAttacks int64 `db:"PvpaAttacks"` PvpaWins int64 `db:"PvpaWins"` PvpaDefs int64 `db:"PvpaDefs"` Sieges int64 `db:"Sieges"` SiegesWins int64 `db:"SiegesWins"` SiegesDefs int64 `db:"SiegesDefs"` }
  • 13.
    func CreateUserStatistics() error{ stats := new(User_stats) tx, err := WriteSession.Begin() defer tx.RollbackUnlessCommitted() result, err := tx.InsertInto("user_stats"). Columns("User", "AdvWins", "AdvLose", "DailyWin", "DailyLose", "PvpaAttacks", "PvpaWins", "PvpaDefs", "Sieges", "SiegesWins", "SiegesDefs"). Record(stats).Exec() tx.Commit() }
  • 14.
  • 15.
    var stream =health.NewStream() // jsonSink for healthTop sink := health.NewJsonPollingSink(time.Minute, time.Minute * 5) stream.AddSink(sink) // stdout sink stream.AddSink(&health.WriterSink{os.Stdout}) // https://bugsnag.com/ sink stream.AddSink(bugsnag.NewSink(&bugsnag.Config{APIKey: "supersecretapikey"}))
  • 16.
    func UserBuildingsUpgrade(c *context.Context,rw web.ResponseWriter, req *web.Request) { job := c.HealthStream.NewJob(“user/building/upgrade”) type := req.GetBuildingType() if isBuildingTypeValid(type) == false { answer.answerErrorString(“invalid building type”, rw, req) job.Event(“invalid building type”) job.Complete(health.Junk) return } if c.User.Money < getBuildingUpgradePrice(type) { answer.answerErrorString(“no money”, rw, req) job.EventKv(“cheater detected”, map[string]string{“User” : c.User.ID, “cheater” : ”true”}) job.Complete(health.Junk) return }
  • 17.
    if err :=buildingUpgrade(type, c.User); err != nil { answer.answerErrorString(“db error”, rw, req) job.EventErr(“buildingUpgrade: ”, err) job.Complete(health.Error) return } if err := userMoneyMinus(c.User, price); err != nil { answer.answerErrorString(“db error”, rw, req) job.EventErr(“userMoneyMinus: ”, err) job.Complete(health.Error) return } job.Complete(health.Success) }
  • 18.
  • 19.
    ... go serv.Serve(listener) interrupt :=make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM) for { select { case killSignal := <-interrupt: stdlog.Println("Got signal:", killSignal) stdlog.Println("Stoping listening on ", serv.Addr) listener.Close() if killSignal == os.Interrupt { return "Daemon was interruped by system signal", nil } return "Daemon was killed", nil } }
  • 20.
  • 21.
    package context c.Cache =cache.New(time.Minute * 100, time.Minute * 1) ---------------------------------------------------------------------------- package static import ( "strconv" "github.com/gocraft/web" "github.com/pmylund/go-cache" ) const ( CACHE_STATIC_EQUIP string = "static/equip" )
  • 22.
    func StaticEquip(c *context.Context,rw web.ResponseWriter, req *web.Request) { if data, ok := c.Cache.Get(CACHE_STATIC_EQUIP); ok { answer.CachedAnswer(data, rw, req) return } var equips []*Equip _, err := c.ReadSession.Select("*").From("equipments").LoadStructs(&equips) forCache := make(map[string][]*Equip) forCache[“Equip”] = equips answerJsonString := json.Marshal(&forCache) c.Cache.Set(CACHE_STATIC_EQUIP, answerJsonString, cache.DefaultExpiration) answer.CachedAnswer(answerJsonString, rw, req) }
  • 23.
    Bonus Эрик Реймонд: Искусствопрограммирования в UNIX Правило генерации: Избегайте ручного набора кода; при любом удобном случае пишите программы, которые бы писали программы.
  • 24.
    CREATE TABLE Persons ( PersonIDint, LastName varchar(255), FirstName varchar(255), Address varchar(255), City varchar(255) ); type Persons struct { PersonID int64 `db:"PersonID"` LastName string `db:"LastName"` FirstName string `db:"FirstName"` Address dbr.NullString `db:"Address"` City dbr.NullString `db:"City"` }
  • 25.
    "show full tableswhere Table_Type != 'VIEW'"
  • 26.
    package databasemodels import {{.DbrUsed}}"github.com/gocraft/dbr" type{{.Table}} struct { {{range .Fields}}{{.Name}} {{.Type}} {{.Tag}} {{end}} }
  • 27.