MVC movie 应用程序
- 如果是string ,那就是body。
- 如果string是第二个输出参数,那么它就是内容类型。
- 如果int是状态码。
- 如果错误而不是nil,那么(任何类型)响应将被省略,错误的文本将呈现400个错误请求。
- 如果(int, error)和error不是nil,那么响应结果将是错误的文本,状态码为int。
- 如果定制struct或interface{}或slice或map,那么它将被呈现为json,除非后面是字符串内容类型。
- 如果mvc。结果,然后它执行它的调度功能,所以好的设计模式可以用来分割模型的逻辑在需要的地方。没有什么能阻止您使用自己喜欢的文件夹结构。 Iris是一个低级Web框架,它有MVC一流的支持,但它不限制你的文件夹结构,这是你的选择。
结构取决于您自己的需求。我们无法告诉您如何设计自己的应用程序,但您可以自由地仔细查看下面的一个用例示例;
model层
让我们从我们的movie model 开始。
数据源/数据存储层
之后,我们继续为我们的Movie创建一个简单的内存存储。
package datasource
import "github.com/kataras/iris/_examples/mvc/overview/datamodels"
// Movies is our imaginary data source.
var Movies = map[int64]datamodels.Movie{
1: {
ID: 1,
Name: "Casablanca",
Year: 1942,
Genre: "Romance",
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
},
2: {
ID: 2,
Name: "Gone with the Wind",
Year: 1939,
Genre: "Romance",
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
},
3: {
ID: 3,
Name: "Citizen Kane",
Year: 1941,
Genre: "Mystery",
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
},
4: {
ID: 4,
Name: "The Wizard of Oz",
Year: 1939,
Genre: "Fantasy",
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
},
5: {
ID: 5,
Name: "North by Northwest",
Year: 1959,
Genre: "Thriller",
Poster: "https://iris-go.com/images/examples/mvc-movies/5.jpg",
},
}
Repositories
可选(因为您也可以在服务中使用它),但是该示例需要,我们创建一个存储库,一个存储库处理“低级”,直接访问Movies数据源。保留“存储库”,它是一个接口,因为它可能不同,它取决于您的应用程序开发的状态,即在生产中将使用一些真正的SQL查询或您用于查询数据的任何其他内容。
package repositories
import (
"errors"
"sync"
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
)
// Query represents the visitor and action queries.
type Query func(datamodels.Movie) bool
// MovieRepository handles the basic operations of a movie entity/model.
// It's an interface in order to be testable, i.e a memory movie repository or
// a connected to an sql database.
type MovieRepository interface {
Exec(query Query, action Query, limit int, mode int) (ok bool)
Select(query Query) (movie datamodels.Movie, found bool)
SelectMany(query Query, limit int) (results []datamodels.Movie)
InsertOrUpdate(movie datamodels.Movie) (updatedMovie datamodels.Movie, err error)
Delete(query Query, limit int) (deleted bool)
}
// NewMovieRepository returns a new movie memory-based repository,
// the one and only repository type in our example.
func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository {
return &movieMemoryRepository{source: source}
}
// movieMemoryRepository is a "MovieRepository"
// which manages the movies using the memory data source (map).
type movieMemoryRepository struct {
source map[int64]datamodels.Movie
mu sync.RWMutex
}
const (
// ReadOnlyMode will RLock(read) the data .
ReadOnlyMode = iota
// ReadWriteMode will Lock(read/write) the data.
ReadWriteMode
)
func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
loops := 0
if mode == ReadOnlyMode {
r.mu.RLock()
defer r.mu.RUnlock()
} else {
r.mu.Lock()
}
for _, movie := range r.source {
ok = query(movie)
if ok {
if action(movie) {
loops++
if actionLimit >= loops {
break // break
}
}
}
return
}
//选择接收查询功能
//为内部的每个电影模型触发
//我们想象中的数据源
//当该函数返回true时,它会停止迭代。
//它返回查询返回的最后一个已知“找到”值
//和最后一个已知的电影模型
//帮助呼叫者减少LOC。
//它实际上是一个简单但非常聪明的原型函数
//自从我第一次想到它以来,我一直在使用它,
//希望你会发现它也很有用。
func (r *movieMemoryRepository) Select(query Query) (movie datamodels.Movie, found bool) {
found = r.Exec(query, func(m datamodels.Movie) bool {
movie = m
return true
}, 1, ReadOnlyMode)
//如果根本找不到的话,设置一个空的datamodels.Movie,
if !found {
movie = datamodels.Movie{}
}
return
}
// SelectMany与Select相同但返回一个或多个datamodels.Movie作为切片。
//如果limit <= 0则返回所有内容
func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Movie) {
r.Exec(query, func(m datamodels.Movie) bool {
results = append(results, m)
return true
}, limit, ReadOnlyMode)
return
}
// InsertOrUpdate将影片添加或更新到(内存)存储。
// 返回新电影,如果有则返回错误。
func (r *movieMemoryRepository) InsertOrUpdate(movie datamodels.Movie) (datamodels.Movie, error) {
id := movie.ID
if id == 0 { // Create new action
var lastID int64
//找到最大的ID,以便不重复
//在制作应用中,您可以使用第三方
//库以生成UUID作为字符串。
r.mu.RLock()
for _, item := range r.source {
if item.ID > lastID {
lastID = item.ID
}
}
r.mu.RUnlock()
id = lastID + 1
movie.ID = id
// map-specific thing
r.mu.Lock()
r.source[id] = movie
r.mu.Unlock()
return movie, nil
}
//基于movie.ID更新动作,
//这里我们将允许更新海报和流派,如果不是空的话。
//或者我们可以做替换:
// r.source [id] =电影
//并评论下面的代码;
current, exists := r.Select(func(m datamodels.Movie) bool {
return m.ID == id
})
if !exists { //ID不是真实的,返回错误。
return datamodels.Movie{}, errors.New("failed to update a nonexistent movie")
}
// 或者注释这些和r.source [id] = m进行纯替换
if movie.Poster != "" {
current.Poster = movie.Poster
}
if movie.Genre != "" {
current.Genre = movie.Genre
}
// map-specific thing
r.mu.Lock()
r.source[id] = current
r.mu.Unlock()
return movie, nil
}
return r.Exec(query, func(m datamodels.Movie) bool {
delete(r.source, m.ID)
return true
}
服务层 service
service可以访问“存储库”和“模型”(甚至是“数据模型”,如果是简单应用程序)的函数的层。它应该包含大部分域逻辑。
我们需要一个服务来与我们的存储库在“高级”和存储/检索电影中进行通信,这将在下面的Web控制器上使用。
视图View Models
应该有视图模型,客户端将能够看到的结构。例:
import (
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/context"
)
type Movie struct {
datamodels.Movie
}
func (m Movie) IsValid() bool {
/* 做一些检查,如果有效则返回true。.. */
return m.ID > 0
}
// Dispatch完成`kataras / iris / mvc#Result`界面。
//将“电影”作为受控的http响应发送。
//如果其ID为零或更小,则返回404未找到错误
//否则返回其json表示,
//(就像控制器的函数默认为自定义类型一样)。
//
//不要过度,应用程序的逻辑不应该在这里。
//在响应之前,这只是验证的又一步,
//可以在这里添加简单的检查。
//
//这只是一个展示
//想象一下设计更大的应用程序时此功能给出的潜力。
//
//调用控制器方法返回值的函数
//是“电影”的类型。
//例如`controllers / movie_controller.go#GetBy`.
func (m Movie) Dispatch(ctx context.Context) {
if !m.IsValid() {
ctx.NotFound()
return
}
ctx.JSON(m, context.JSON{Indent: " "})
}
但是,我们将使用“datamodels”作为唯一的模型包,因为Movie结构不包含任何敏感数据,客户端能够查看其所有字段,并且我们不需要任何额外的功能或验证。
控制器 Controllers
处理Web请求,在服务和客户端之间架起桥梁。
而最重要的是,Iris来自哪里,是与MovieService进行通信的Controller。我们通常将所有与http相关的东西存储在一个名为“web”的不同文件夹中,这样所有控制器都可以在“web / controllers”中,注意“通常”你也可以使用其他设计模式,这取决于你。
“web / middleware”中的一个中间件,用于动画示例。
// file: web/middleware/basicauth.go
package middleware
import "github.com/kataras/iris/middleware/basicauth"
// 简单的授权验证
var BasicAuth = basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": "password",
},
})
// file: main.go
package main
import (
"github.com/kataras/iris/_examples/mvc/overview/datasource"
"github.com/kataras/iris/_examples/mvc/overview/repositories"
"github.com/kataras/iris/_examples/mvc/overview/services"
"github.com/kataras/iris/_examples/mvc/overview/web/controllers"
"github.com/kataras/iris/_examples/mvc/overview/web/middleware"
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
)
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
//加载模板文件
app.RegisterView(iris.HTML("./web/views", ".html"))
// 注册控制器
// mvc.New(app.Party("/movies")).Handle(new(controllers.MovieController))
//您还可以拆分您编写的代码以配置mvc.Application
//使用`mvc.Configure`方法,如下所示。
mvc.Configure(app.Party("/movies"), movies)
// http://localhost:8080/movies
// http://localhost:8080/movies/1
app.Run(
//开启web服务
iris.Addr("localhost:8080"),
// 禁用更新
iris.WithoutVersionChecker,
// 按下CTRL / CMD + C时跳过错误的服务器:
iris.WithoutServerError(iris.ErrServerClosed),
//实现更快的json序列化和更多优化:
iris.WithOptimizations,
)
}
//注意mvc.Application,它不是iris.Application。
func movies(app *mvc.Application) {
//添加基本身份验证(admin:password)中间件
//用于基于/电影的请求。
app.Router.Use(middleware.BasicAuth)
// 使用数据源中的一些(内存)数据创建我们的电影资源库。
repo := repositories.NewMovieRepository(datasource.Movies)
// 创建我们的电影服务,我们将它绑定到电影应用程序的依赖项。
movieService := services.NewMovieService(repo)
app.Register(movieService)
//为我们的电影控制器服务
//请注意,您可以为多个控制器提供服务
//你也可以使用`movies.Party(relativePath)`或`movies.Clone(app.Party(...))创建子mvc应用程序