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创建一个简单的内存存储。

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

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

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

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

    “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. })
    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应用程序