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))
    }

results matching ""

    No results matching ""