icon-cookie
The website uses cookies to optimize your user experience. Using this website grants us the permission to collect certain information essential to the provision of our services to you, but you may change the cookie settings within your browser any time you wish. Learn more
I agree
blank_error__heading
blank_error__body
Text direction?

电影MVC应用 · Go Web Iris中文网

MVC movie 应用程序

Iris有一个非常强大和极快的MVC支持,你可以从方法函数返回任何类型的任何值,它将按预期发送到客户端。

  • 如果是string ,那就是body。
  • 如果string是第二个输出参数,那么它就是内容类型。
  • 如果int是状态码。
  • 如果错误而不是nil,那么(任何类型)响应将被省略,错误的文本将呈现400个错误请求。
  • 如果(int, error)和error不是nil,那么响应结果将是错误的文本,状态码为int。
  • 如果定制struct或interface{}或slice或map,那么它将被呈现为json,除非后面是字符串内容类型。
  • 如果mvc。结果,然后它执行它的调度功能,所以好的设计模式可以用来分割模型的逻辑在需要的地方。

没有什么能阻止您使用自己喜欢的文件夹结构。 Iris是一个低级Web框架,它有MVC一流的支持,但它不限制你的文件夹结构,这是你的选择。

结构取决于您自己的需求。我们无法告诉您如何设计自己的应用程序,但您可以自由地仔细查看下面的一个用例示例;

folder

model层

让我们从我们的movie model 开始。

    // file: datamodels/movie.go
    package datamodels
    // Movie 是我们的一个简单的数据结构体
    // 请注意公共标签(适用于我们web网络应用)
    // 应保存在“web / viewmodels / movie.go”等其他文件中
    //可以通过嵌入datamodels.Movie或
    //声明新字段但我们将使用此数据模型
    //作为我们应用程序中唯一的一个Movie模型,
    //为了摇摇欲坠。
    type Movie struct {
        ID     int64  `json:"id"`
        Name   string `json:"name"`
        Year   int    `json:"year"`
        Genre  string `json:"genre"`
        Poster string `json:"poster"`
    }
数据源/数据存储层

之后,我们继续为我们的Movie创建一个简单的内存存储。

    // file: datasource/movies.go
    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()
            defer r.mu.Unlock()
        }
        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
    }
    func (r *movieMemoryRepository) Delete(query Query, limit int) bool {
        return r.Exec(query, func(m datamodels.Movie) bool {
            delete(r.source, m.ID)
            return true
        }, limit, ReadWriteMode)
    }
服务层 service

service可以访问“存储库”和“模型”(甚至是“数据模型”,如果是简单应用程序)的函数的层。它应该包含大部分域逻辑。

我们需要一个服务来与我们的存储库在“高级”和存储/检索电影中进行通信,这将在下面的Web控制器上使用。

    // file: services/movie_service.go
    package services
    import (
        "github.com/kataras/iris/_examples/mvc/overview/datamodels"
        "github.com/kataras/iris/_examples/mvc/overview/repositories"
    )
    // MovieService处理电影数据模型的一些CRUID操作。
    //这取决于影片库的动作。
    //这是将数据源与更高级别的组件分离。
    //因此,不同的存储库类型可以使用相同的逻辑,而无需任何更改。
    //它是一个界面,它在任何地方都被用作界面
    //因为我们可能需要在将来更改或尝试实验性的不同域逻辑。
    type MovieService interface {
        GetAll() []datamodels.Movie
        GetByID(id int64) (datamodels.Movie, bool)
        DeleteByID(id int64) bool
        UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error)
    }
    // NewMovieService返回默认 movie service.
    func NewMovieService(repo repositories.MovieRepository) MovieService {
        return &movieService{
            repo: repo,
        }
    }
    type movieService struct {
        repo repositories.MovieRepository
    }
    // GetAll 获取所有的movie.
    func (s *movieService) GetAll() []datamodels.Movie {
        return s.repo.SelectMany(func(_ datamodels.Movie) bool {
            return true
        }, -1)
    }
    // GetByID 根据其ID返回一行。
    func (s *movieService) GetByID(id int64) (datamodels.Movie, bool) {
        return s.repo.Select(func(m datamodels.Movie) bool {
            return m.ID == id
        })
    }
    // UpdatePosterAndGenreByID更新电影的海报和流派。
    func (s *movieService) UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error) {
        // update the movie and return it.
        return s.repo.InsertOrUpdate(datamodels.Movie{
            ID:     id,
            Poster: poster,
            Genre:  genre,
        })
    }
    // DeleteByID按ID删除电影。
    //
    //如果删除则返回true,否则返回false。
    func (s *movieService) DeleteByID(id int64) bool {
        return s.repo.Delete(func(m datamodels.Movie) bool {
            return m.ID == id
        }, 1)
    }
视图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
    }

Iris能够将任何自定义数据结构转换为HTTP响应调度程序,因此从理论上讲,如果确实需要,则允许使用以下内容:

    // 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”中, 注意“通常”你也可以使用其他设计模式,这取决于你。

    //file: web/controllers/movie_controller.go
    package controllers
    import (
        "errors"
        "github.com/kataras/iris/_examples/mvc/overview/datamodels"
        "github.com/kataras/iris/_examples/mvc/overview/services"
        "github.com/kataras/iris"
    )
    // MovieController
    type MovieController struct {
       //我们的MovieService,它是一个界面
        //从主应用程序绑定。
        Service services.MovieService
    }
    // 获取电影列表
    // curl -i http://localhost:8080/movies
    // 如果您有敏感数据,这是正确的方法:
    // func (c *MovieController) Get() (results []viewmodels.Movie) {
    //     data := c.Service.GetAll()
    //     for _, movie := range data {
    //         results = append(results, viewmodels.Movie{movie})
    //     }
    //     return
    // }
    // 否则只返回数据模型
    func (c *MovieController) Get() (results []datamodels.Movie) {
        return c.Service.GetAll()
    }
    //获取一部电影
    // Demo:
    // curl -i http://localhost:8080/movies/1
    func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
        return c.Service.GetByID(id) // it will throw 404 if not found.
    }
    // 用put请求更新一部电影
    // Demo:
    // curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
    func (c *MovieController) PutBy(ctx iris.Context, id int64) (datamodels.Movie, error) {
        // get the request data for poster and genre
        file, info, err := ctx.FormFile("poster")
        if err != nil {
            return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
        }
        // 不需要文件所以关闭他
        file.Close()
        //想象一下,这是上传文件的网址......
        poster := info.Filename
        genre := ctx.FormValue("genre")
        return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
    }
    // Delete请求删除一部电影
    // curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
    func (c *MovieController) DeleteBy(id int64) interface{} {
        wasDel := c.Service.DeleteByID(id)
        if wasDel {
            // 返回删除的id
            return iris.Map{"deleted": id}
        }
    //在这里我们可以看到方法函数可以返回这两种类型中的任何一种(map或int),
        //我们不必将返回类型指定为特定类型。
        return iris.StatusBadRequest
    }

“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",
        },
    })

最后我们的main.go.

    // 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应用程序
        app.Handle(new(controllers.MovieController))
    }
Measure
Measure
Related Notes
Get a free MyMarkup account to save this article and view it later on any device.
Create account

End User License Agreement

Summary | 5 Annotations
方法函数返回任何类型的任何值,它将按预期发送到客户端
2020/07/09 03:46
它应该包含大部分域逻辑。
2020/07/09 03:49
处理Web请求,在服务和客户端之间架起桥梁。
2020/07/09 03:50
我们通常将所有与http相关的东西存储在一个名为“web”的不同文件夹中
2020/07/09 03:50
没有什么能阻止您使用自己喜欢的文件夹结构。 Iris是一个低级Web框架,它有MVC一流的支持,但它不限制你的文件夹结构,这是你的选择。
2020/07/09 06:08