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 开始。

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

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

  1. // file: datasource/movies.go
  2. package datasource
  3. import "github.com/kataras/iris/_examples/mvc/overview/datamodels"
  4. // Movies is our imaginary data source.
  5. var Movies = map[int64]datamodels.Movie{
  6. 1: {
  7. ID: 1,
  8. Name: "Casablanca",
  9. Year: 1942,
  10. Genre: "Romance",
  11. Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
  12. },
  13. 2: {
  14. ID: 2,
  15. Name: "Gone with the Wind",
  16. Year: 1939,
  17. Genre: "Romance",
  18. Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
  19. },
  20. 3: {
  21. ID: 3,
  22. Name: "Citizen Kane",
  23. Year: 1941,
  24. Genre: "Mystery",
  25. Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
  26. },
  27. 4: {
  28. ID: 4,
  29. Name: "The Wizard of Oz",
  30. Year: 1939,
  31. Genre: "Fantasy",
  32. Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
  33. },
  34. 5: {
  35. ID: 5,
  36. Name: "North by Northwest",
  37. Year: 1959,
  38. Genre: "Thriller",
  39. Poster: "https://iris-go.com/images/examples/mvc-movies/5.jpg",
  40. },
  41. }
Repositories

可以直接访问“数据源”并可以直接操作数据的层。

可选(因为您也可以在服务中使用它),但是该示例需要,我们创建一个存储库,一个存储库处理“低级”,直接访问Movies数据源。保留“存储库”,它是一个接口,因为它可能不同,它取决于您的应用程序开发的状态,即在生产中将使用一些真正的SQL查询或您用于查询数据的任何其他内容。

  1. package repositories
  2. import (
  3. "errors"
  4. "sync"
  5. "github.com/kataras/iris/_examples/mvc/overview/datamodels"
  6. )
  7. // Query represents the visitor and action queries.
  8. type Query func(datamodels.Movie) bool
  9. // MovieRepository handles the basic operations of a movie entity/model.
  10. // It's an interface in order to be testable, i.e a memory movie repository or
  11. // a connected to an sql database.
  12. type MovieRepository interface {
  13. Exec(query Query, action Query, limit int, mode int) (ok bool)
  14. Select(query Query) (movie datamodels.Movie, found bool)
  15. SelectMany(query Query, limit int) (results []datamodels.Movie)
  16. InsertOrUpdate(movie datamodels.Movie) (updatedMovie datamodels.Movie, err error)
  17. Delete(query Query, limit int) (deleted bool)
  18. }
  19. // NewMovieRepository returns a new movie memory-based repository,
  20. // the one and only repository type in our example.
  21. func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository {
  22. return &movieMemoryRepository{source: source}
  23. }
  24. // movieMemoryRepository is a "MovieRepository"
  25. // which manages the movies using the memory data source (map).
  26. type movieMemoryRepository struct {
  27. source map[int64]datamodels.Movie
  28. mu sync.RWMutex
  29. }
  30. const (
  31. // ReadOnlyMode will RLock(read) the data .
  32. ReadOnlyMode = iota
  33. // ReadWriteMode will Lock(read/write) the data.
  34. ReadWriteMode
  35. )
  36. func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
  37. loops := 0
  38. if mode == ReadOnlyMode {
  39. r.mu.RLock()
  40. defer r.mu.RUnlock()
  41. } else {
  42. r.mu.Lock()
  43. defer r.mu.Unlock()
  44. }
  45. for _, movie := range r.source {
  46. ok = query(movie)
  47. if ok {
  48. if action(movie) {
  49. loops++
  50. if actionLimit >= loops {
  51. break // break
  52. }
  53. }
  54. }
  55. }
  56. return
  57. }
  58. //选择接收查询功能
  59. //为内部的每个电影模型触发
  60. //我们想象中的数据源
  61. //当该函数返回true时,它会停止迭代。
  62. //它返回查询返回的最后一个已知“找到”值
  63. //和最后一个已知的电影模型
  64. //帮助呼叫者减少LOC。
  65. //它实际上是一个简单但非常聪明的原型函数
  66. //自从我第一次想到它以来,我一直在使用它,
  67. //希望你会发现它也很有用。
  68. func (r *movieMemoryRepository) Select(query Query) (movie datamodels.Movie, found bool) {
  69. found = r.Exec(query, func(m datamodels.Movie) bool {
  70. movie = m
  71. return true
  72. }, 1, ReadOnlyMode)
  73. //如果根本找不到的话,设置一个空的datamodels.Movie,
  74. if !found {
  75. movie = datamodels.Movie{}
  76. }
  77. return
  78. }
  79. // SelectMany与Select相同但返回一个或多个datamodels.Movie作为切片。
  80. //如果limit <= 0则返回所有内容
  81. func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Movie) {
  82. r.Exec(query, func(m datamodels.Movie) bool {
  83. results = append(results, m)
  84. return true
  85. }, limit, ReadOnlyMode)
  86. return
  87. }
  88. // InsertOrUpdate将影片添加或更新到(内存)存储。
  89. // 返回新电影,如果有则返回错误。
  90. func (r *movieMemoryRepository) InsertOrUpdate(movie datamodels.Movie) (datamodels.Movie, error) {
  91. id := movie.ID
  92. if id == 0 { // Create new action
  93. var lastID int64
  94. //找到最大的ID,以便不重复
  95. //在制作应用中,您可以使用第三方
  96. //库以生成UUID作为字符串。
  97. r.mu.RLock()
  98. for _, item := range r.source {
  99. if item.ID > lastID {
  100. lastID = item.ID
  101. }
  102. }
  103. r.mu.RUnlock()
  104. id = lastID + 1
  105. movie.ID = id
  106. // map-specific thing
  107. r.mu.Lock()
  108. r.source[id] = movie
  109. r.mu.Unlock()
  110. return movie, nil
  111. }
  112. //基于movie.ID更新动作,
  113. //这里我们将允许更新海报和流派,如果不是空的话。
  114. //或者我们可以做替换:
  115. // r.source [id] =电影
  116. //并评论下面的代码;
  117. current, exists := r.Select(func(m datamodels.Movie) bool {
  118. return m.ID == id
  119. })
  120. if !exists { //ID不是真实的,返回错误。
  121. return datamodels.Movie{}, errors.New("failed to update a nonexistent movie")
  122. }
  123. // 或者注释这些和r.source [id] = m进行纯替换
  124. if movie.Poster != "" {
  125. current.Poster = movie.Poster
  126. }
  127. if movie.Genre != "" {
  128. current.Genre = movie.Genre
  129. }
  130. // map-specific thing
  131. r.mu.Lock()
  132. r.source[id] = current
  133. r.mu.Unlock()
  134. return movie, nil
  135. }
  136. func (r *movieMemoryRepository) Delete(query Query, limit int) bool {
  137. return r.Exec(query, func(m datamodels.Movie) bool {
  138. delete(r.source, m.ID)
  139. return true
  140. }, limit, ReadWriteMode)
  141. }
服务层 service

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

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

  1. // file: services/movie_service.go
  2. package services
  3. import (
  4. "github.com/kataras/iris/_examples/mvc/overview/datamodels"
  5. "github.com/kataras/iris/_examples/mvc/overview/repositories"
  6. )
  7. // MovieService处理电影数据模型的一些CRUID操作。
  8. //这取决于影片库的动作。
  9. //这是将数据源与更高级别的组件分离。
  10. //因此,不同的存储库类型可以使用相同的逻辑,而无需任何更改。
  11. //它是一个界面,它在任何地方都被用作界面
  12. //因为我们可能需要在将来更改或尝试实验性的不同域逻辑。
  13. type MovieService interface {
  14. GetAll() []datamodels.Movie
  15. GetByID(id int64) (datamodels.Movie, bool)
  16. DeleteByID(id int64) bool
  17. UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error)
  18. }
  19. // NewMovieService返回默认 movie service.
  20. func NewMovieService(repo repositories.MovieRepository) MovieService {
  21. return &movieService{
  22. repo: repo,
  23. }
  24. }
  25. type movieService struct {
  26. repo repositories.MovieRepository
  27. }
  28. // GetAll 获取所有的movie.
  29. func (s *movieService) GetAll() []datamodels.Movie {
  30. return s.repo.SelectMany(func(_ datamodels.Movie) bool {
  31. return true
  32. }, -1)
  33. }
  34. // GetByID 根据其ID返回一行。
  35. func (s *movieService) GetByID(id int64) (datamodels.Movie, bool) {
  36. return s.repo.Select(func(m datamodels.Movie) bool {
  37. return m.ID == id
  38. })
  39. }
  40. // UpdatePosterAndGenreByID更新电影的海报和流派。
  41. func (s *movieService) UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error) {
  42. // update the movie and return it.
  43. return s.repo.InsertOrUpdate(datamodels.Movie{
  44. ID: id,
  45. Poster: poster,
  46. Genre: genre,
  47. })
  48. }
  49. // DeleteByID按ID删除电影。
  50. //
  51. //如果删除则返回true,否则返回false。
  52. func (s *movieService) DeleteByID(id int64) bool {
  53. return s.repo.Delete(func(m datamodels.Movie) bool {
  54. return m.ID == id
  55. }, 1)
  56. }
视图View Models

应该有视图模型,客户端将能够看到的结构。例:

  1. import (
  2. "github.com/kataras/iris/_examples/mvc/overview/datamodels"
  3. "github.com/kataras/iris/context"
  4. )
  5. type Movie struct {
  6. datamodels.Movie
  7. }
  8. func (m Movie) IsValid() bool {
  9. /* 做一些检查,如果有效则返回true。.. */
  10. return m.ID > 0
  11. }

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

  1. // Dispatch完成`kataras / iris / mvc#Result`界面。
  2. //将“电影”作为受控的http响应发送。
  3. //如果其ID为零或更小,则返回404未找到错误
  4. //否则返回其json表示,
  5. //(就像控制器的函数默认为自定义类型一样)。
  6. //
  7. //不要过度,应用程序的逻辑不应该在这里。
  8. //在响应之前,这只是验证的又一步,
  9. //可以在这里添加简单的检查。
  10. //
  11. //这只是一个展示
  12. //想象一下设计更大的应用程序时此功能给出的潜力。
  13. //
  14. //调用控制器方法返回值的函数
  15. //是“电影”的类型。
  16. //例如`controllers / movie_controller.go#GetBy`.
  17. func (m Movie) Dispatch(ctx context.Context) {
  18. if !m.IsValid() {
  19. ctx.NotFound()
  20. return
  21. }
  22. ctx.JSON(m, context.JSON{Indent: " "})
  23. }

但是,我们将使用“datamodels”作为唯一的模型包,因为Movie结构不包含任何敏感数据,客户端能够查看其所有字段,并且我们不需要任何额外的功能或验证。

控制器 Controllers

处理Web请求,在服务和客户端之间架起桥梁。

而最重要的是,Iris来自哪里,是与MovieService进行通信的Controller。我们通常将所有与http相关的东西存储在一个名为“web”的不同文件夹中,这样所有控制器都可以在“web / controllers”中,注意“通常”你也可以使用其他设计模式,这取决于你。

  1. //file: web/controllers/movie_controller.go
  2. package controllers
  3. import (
  4. "errors"
  5. "github.com/kataras/iris/_examples/mvc/overview/datamodels"
  6. "github.com/kataras/iris/_examples/mvc/overview/services"
  7. "github.com/kataras/iris"
  8. )
  9. // MovieController
  10. type MovieController struct {
  11. //我们的MovieService,它是一个界面
  12. //从主应用程序绑定。
  13. Service services.MovieService
  14. }
  15. // 获取电影列表
  16. // curl -i http://localhost:8080/movies
  17. // 如果您有敏感数据,这是正确的方法:
  18. // func (c *MovieController) Get() (results []viewmodels.Movie) {
  19. // data := c.Service.GetAll()
  20. // for _, movie := range data {
  21. // results = append(results, viewmodels.Movie{movie})
  22. // }
  23. // return
  24. // }
  25. // 否则只返回数据模型
  26. func (c *MovieController) Get() (results []datamodels.Movie) {
  27. return c.Service.GetAll()
  28. }
  29. //获取一部电影
  30. // Demo:
  31. // curl -i http://localhost:8080/movies/1
  32. func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
  33. return c.Service.GetByID(id) // it will throw 404 if not found.
  34. }
  35. // 用put请求更新一部电影
  36. // Demo:
  37. // curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
  38. func (c *MovieController) PutBy(ctx iris.Context, id int64) (datamodels.Movie, error) {
  39. // get the request data for poster and genre
  40. file, info, err := ctx.FormFile("poster")
  41. if err != nil {
  42. return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
  43. }
  44. // 不需要文件所以关闭他
  45. file.Close()
  46. //想象一下,这是上传文件的网址......
  47. poster := info.Filename
  48. genre := ctx.FormValue("genre")
  49. return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
  50. }
  51. // Delete请求删除一部电影
  52. // curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
  53. func (c *MovieController) DeleteBy(id int64) interface{} {
  54. wasDel := c.Service.DeleteByID(id)
  55. if wasDel {
  56. // 返回删除的id
  57. return iris.Map{"deleted": id}
  58. }
  59. //在这里我们可以看到方法函数可以返回这两种类型中的任何一种(map或int),
  60. //我们不必将返回类型指定为特定类型。
  61. return iris.StatusBadRequest
  62. }

“web / middleware”中的一个中间件,用于动画示例。

  1. // file: web/middleware/basicauth.go
  2. package middleware
  3. import "github.com/kataras/iris/middleware/basicauth"
  4. // 简单的授权验证
  5. var BasicAuth = basicauth.New(basicauth.Config{
  6. Users: map[string]string{
  7. "admin": "password",
  8. },
  9. })

最后我们的main.go.

  1. // file: main.go
  2. package main
  3. import (
  4. "github.com/kataras/iris/_examples/mvc/overview/datasource"
  5. "github.com/kataras/iris/_examples/mvc/overview/repositories"
  6. "github.com/kataras/iris/_examples/mvc/overview/services"
  7. "github.com/kataras/iris/_examples/mvc/overview/web/controllers"
  8. "github.com/kataras/iris/_examples/mvc/overview/web/middleware"
  9. "github.com/kataras/iris"
  10. "github.com/kataras/iris/mvc"
  11. )
  12. func main() {
  13. app := iris.New()
  14. app.Logger().SetLevel("debug")
  15. //加载模板文件
  16. app.RegisterView(iris.HTML("./web/views", ".html"))
  17. // 注册控制器
  18. // mvc.New(app.Party("/movies")).Handle(new(controllers.MovieController))
  19. //您还可以拆分您编写的代码以配置mvc.Application
  20. //使用`mvc.Configure`方法,如下所示。
  21. mvc.Configure(app.Party("/movies"), movies)
  22. // http://localhost:8080/movies
  23. // http://localhost:8080/movies/1
  24. app.Run(
  25. //开启web服务
  26. iris.Addr("localhost:8080"),
  27. // 禁用更新
  28. iris.WithoutVersionChecker,
  29. // 按下CTRL / CMD + C时跳过错误的服务器:
  30. iris.WithoutServerError(iris.ErrServerClosed),
  31. //实现更快的json序列化和更多优化:
  32. iris.WithOptimizations,
  33. )
  34. }
  35. //注意mvc.Application,它不是iris.Application。
  36. func movies(app *mvc.Application) {
  37. //添加基本身份验证(admin:password)中间件
  38. //用于基于/电影的请求。
  39. app.Router.Use(middleware.BasicAuth)
  40. // 使用数据源中的一些(内存)数据创建我们的电影资源库。
  41. repo := repositories.NewMovieRepository(datasource.Movies)
  42. // 创建我们的电影服务,我们将它绑定到电影应用程序的依赖项。
  43. movieService := services.NewMovieService(repo)
  44. app.Register(movieService)
  45. //为我们的电影控制器服务
  46. //请注意,您可以为多个控制器提供服务
  47. //你也可以使用`movies.Party(relativePath)`或`movies.Clone(app.Party(...))创建子mvc应用程序
  48. app.Handle(new(controllers.MovieController))
  49. }