Использование Golang для создания веб-проектов — проблема. Если вы используете Rails, вы, вероятно, не почувствуете этого. я здесьRefer MadnessИспользуется следующая архитектура.
-public/
-views/
-models/
-utils/
-controllers/
-web/
-main.go
我将打破这个体系,介绍各个目录的作用。在每个目录下,文件只能访问其同级或者上一级。 это означаетutils
доступ только к себе иmodels
,web
может получить доступ только к себе,controllers
,utils
,models
.models
может получить к нему доступ только сам. Я делаю это, чтобы сохранить ясность иерархии и предотвратить рекурсивные зависимости. Теперь давайте проанализируем роль каждого каталога и файла.
main.go
main.go
Это основной файл для создания зависимостей, получения переменных среды и запуска служб. Ниже приведен пример этого.
package main
import (
"github.com/larryprice/refermadness/utils"
"github.com/larryprice/refermadness/web"
"github.com/stretchr/graceful"
"os"
)
func main() {
isDevelopment := os.Getenv("ENVIRONMENT") == "development"
dbURL := os.Getenv("MONGOLAB_URI")
if isDevelopment {
dbURL = os.Getenv("DB_PORT_27017_TCP_ADDR")
}
dbAccessor := utils.NewDatabaseAccessor(dbURL, os.Getenv("DATABASE_NAME"), 0)
cuAccessor := utils.NewCurrentUserAccessor(1)
s := web.NewServer(*dbAccessor, *cuAccessor, os.Getenv("GOOGLE_OAUTH2_CLIENT_ID"),
os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET"), os.Getenv("SESSION_SECRET"),
isDevelopment, os.Getenv("GOOGLE_ANALYTICS_KEY"))
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
graceful.Run(":"+port, 0, s)
}
так какmain.go
действительно самый низкий уровень, поэтому он имеет доступ ко всем каталогам: в данном случае этоweb
иutils
. Получите все переменные среды здесь и введите их, где это необходимо. существуетmain.go
Создается сервер, внедряются зависимости, и сервер запускается на настроенном порту.
web
web
Каталог является основным сервисным кодом, но также включает промежуточный код. Нижеweb
Внутренняя структура каталога:
-web/
|-middleware/
|-server.go
server.go
Он содержит определение веб-сервера. Веб-сервер создает весь веб-контроллер и добавлен в промежуточное программное обеспечение Negroni. Вот его пример:
package web
import (
"github.com/codegangsta/negroni"
"github.com/goincremental/negroni-sessions"
"github.com/goincremental/negroni-sessions/cookiestore"
"github.com/gorilla/mux"
"github.com/larryprice/refermadness/controllers"
"github.com/larryprice/refermadness/utils"
"github.com/larryprice/refermadness/web/middleware"
"github.com/unrolled/secure"
"gopkg.in/unrolled/render.v1"
"html/template"
"net/http"
)
type Server struct {
*negroni.Negroni
}
func NewServer(dba utils.DatabaseAccessor, cua utils.CurrentUserAccessor, clientID, clientSecret,
sessionSecret string, isDevelopment bool, gaKey string) *Server {
s := Server{negroni.Classic()}
session := utils.NewSessionManager()
basePage := utils.NewBasePageCreator(cua, gaKey)
renderer := render.New()
router := mux.NewRouter()
// ...
accountController := controllers.NewAccountController(clientID, clientSecret, isDevelopment, session, dba, cua, basePage, renderer)
accountController.Register(router)
// ...
s.Use(sessions.Sessions("refermadness", cookiestore.New([]byte(sessionSecret))))
s.Use(middleware.NewAuthenticator(dba, session, cua).Middleware())
s.UseHandler(router)
return &s
}
Server
структура представляет собойnegroni.Negroni
веб-сервер, в этом файле естьutils
и другие сторонние ссылки на пакеты, создайте маршрутизатор, несколько контроллеров и зарегистрируйте контроллер на текущем маршрутизаторе. В то же время также внедряется необходимое промежуточное программное обеспечение. Говоря о промежуточном программном обеспечении, вот его код:
package middleware
import (
"github.com/codegangsta/negroni"
"github.com/larryprice/refermadness/utils"
"net/http"
)
type Database struct {
da utils.DatabaseAccessor
}
func NewDatabase(da utils.DatabaseAccessor) *Database {
return &Database{da}
}
func (d *Database) Middleware() negroni.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
reqSession := d.da.Clone()
defer reqSession.Close()
d.da.Set(r, reqSession)
next(rw, r)
}
}
Это промежуточное ПО аннотаций для доступа к сеансам базы данных через HTTP-маршрутизатор. Базовый файл взят изСообщение в блоге Брайана Гесиака о RESTful Go, изменил его в соответствии с моим файлом.
controllers/
Контроллер здесь очень похож на концепцию контроллера в Rails. Контроллер регистрирует собственный маршрутизатор и устанавливает собственные вызовы функций. Краткий пример выглядит следующим образом:
package controllers
import (
"encoding/json"
"errors"
"github.com/gorilla/mux"
"github.com/larryprice/refermadness/models"
"github.com/larryprice/refermadness/utils"
"gopkg.in/mgo.v2/bson"
"gopkg.in/unrolled/render.v1"
"html/template"
"net/http"
"strings"
)
type ServiceControllerImpl struct {
currentUser utils.CurrentUserAccessor
basePage utils.BasePageCreator
renderer *render.Render
database utils.DatabaseAccessor
}
func NewServiceController(currentUser utils.CurrentUserAccessor, basePage utils.BasePageCreator,
renderer *render.Render, database utils.DatabaseAccessor) *ServiceControllerImpl {
return &ServiceControllerImpl{
currentUser: currentUser,
basePage: basePage,
renderer: renderer,
database: database,
}
}
func (sc *ServiceControllerImpl) Register(router *mux.Router) {
router.HandleFunc("/service/{id}", sc.single)
// ...
}
// ...
type serviceResult struct {
*models.Service
RandomCode *models.ReferralCode
UserCode *models.ReferralCode
}
type servicePage struct {
utils.BasePage
ResultString string
}
func (sc *ServiceControllerImpl) single(w http.ResponseWriter, r *http.Request) {
data, err := sc.get(w, r)
if len(r.Header["Content-Type"]) == 1 && strings.Contains(r.Header["Content-Type"][0], "application/json") {
if err != nil {
sc.renderer.JSON(w, http.StatusBadRequest, map[string]string{
"error": err.Error(),
})
return
}
sc.renderer.JSON(w, http.StatusOK, data)
return
} else if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
resultString, _ := json.Marshal(data)
t, _ := template.ParseFiles("views/layout.html", "views/service.html")
t.Execute(w, servicePage{sc.basePage.Get(r), string(resultString)})
}
utils/
существуетutils
Файлы в каталоге предоставляют некоторые низкоуровневые функции для поддержки другого кода. В этом примере главное — определить структуру для доступа к контексту запроса, обработки сеанса и определения специальной страницы для наследования. Вот пример настройки пользователя через контекст доступа:
package utils
import (
"github.com/gorilla/context"
"github.com/larryprice/refermadness/models"
"net/http"
)
type CurrentUserAccessor struct {
key int
}
func NewCurrentUserAccessor(key int) *CurrentUserAccessor {
return &CurrentUserAccessor{key}
}
func (cua *CurrentUserAccessor) Set(r *http.Request, user *models.User) {
context.Set(r, cua.key, user)
}
func (cua *CurrentUserAccessor) Clear(r *http.Request) {
context.Delete(r, cua.key)
}
func (cua *CurrentUserAccessor) Get(r *http.Request) *models.User {
if rv := context.Get(r, cua.key); rv != nil {
return rv.(*models.User)
}
return nil
}
models/
модель — это структура данных, используемая для описания данных в базе данных. В этом примере модель используется для доступа к базе данных, создается пустая модель, а затем присваивается значение путем запроса к базе данных. Ниже приведен пример:
package models
import (
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"strings"
"time"
)
type Service struct {
// identification information
ID bson.ObjectId `bson:"_id"`
Name string `bson:"name"`
Description string `bson:"description"`
URL string `bson:"url"`
Search string `bson:"search"`
}
func NewService(name, description, url string, creatorID bson.ObjectId) *Service {
url = strings.TrimPrefix(strings.TrimPrefix(url, "http://"), "https://")
return &Service{
ID: bson.NewObjectId(),
Name: name,
URL: url,
Description: description,
Search: strings.ToLower(name) + ";" + strings.ToLower(description) + ";" + strings.ToLower(url),
}
}
func (s *Service) Save(db *mgo.Database) error {
_, err := s.coll(db).UpsertId(s.ID, s)
return err
}
func (s *Service) FindByID(id bson.ObjectId, db *mgo.Database) error {
return s.coll(db).FindId(id).One(s)
}
func (*Service) coll(db *mgo.Database) *mgo.Collection {
return db.C("service")
}
type Services []Service
func (s *Services) FindByIDs(ids []bson.ObjectId, db *mgo.Database) error {
return s.coll(db).Find(bson.M{"_id": bson.M{"$in": ids}}).Sort("name").All(s)
}
func (*Services) coll(db *mgo.Database) *mgo.Collection {
return db.C("service")
}
views/
Поместите файлы шаблонов Golang вviews
Под содержанием. Таким образом, независимо от того, какой механизм шаблонов можно разместить непосредственно вviews
Вниз.
public/
Как и прежде, этот файл является общедоступным, напримерcss
,img
,scripts
.
как бежать
Без сомнения, мой фаворитdocker, поэтому я буду использовать его для запуска приложения. Если вы не используете докер, вы можете поместить файл в$GOPATH/src/github.com/larryprice/refermadness
,бегатьgo get
Чтобы получить все зависимости, затем запуститьgo run main.go
илиgo build; ./refermadness
Запустите программу. Если вам также нравится использовать докер, вы можете напрямую передатьDockerfile
бежать.
FROM golang:1.4
RUN go get github.com/codegangsta/gin
ADD . /go/src/github.com/larryprice/refermadness
WORKDIR /go/src/github.com/larryprice/refermadness
RUN go get
Мне также нравитсяcompose, поэтому я также запускаю все приложения через файл компоновки. Я использовал АО, SASS и mongodb, так что вот мойdocker-compose.yml
документ.
main:
build: .
command: gin run
env_file: .env
volumes:
- ./:/go/src/github.com/larryprice/refermadness
working_dir: /go/src/github.com/larryprice/refermadness
ports:
- "3000:3000"
links:
- db
sass:
image: larryprice/sass
volumes:
- ./public/css:/src
jsx:
image: larryprice/jsx
volumes:
- ./public/scripts:/src
db:
image: mongo:3.0
command: mongod --smallfiles --quiet --logpath=/dev/null
volumes_from:
- dbvolume
dbvolume:
image: busybox:ubuntu-14.04
volumes:
- /data/db
Затем запуститеdocker-compose up
Запустить все контейнеры и запустить сервер.