365bet地区用365bet地区替换Node Express API

365bet地区了解如何用365bet地区替换Node Express中的RESTful API

s扎卡里·克莱因·塞尔吉奥·德尔阿莫

365bet地区版本 3.3.0

365bet地区培训

365bet地区培训由创建并积极维护365bet地区框架的人们开发和交付

介绍

我们将使用以下描述的React Node js应用程序马克·沃尔克曼在里面SEEN文章SETT的Web App分步发行本文为从头到尾开发现代Web应用程序奠定了详细的蓝图。本文中的示例项目具有一个由PostgresSQL数据库支持的React前端和一个Node express jsbackend。该项目包括WebSockets和HTTPS等多项高级功能。

365bet地区在本指南中,我们将演示如何使用365bet地区框架开发相同的Web应用程序。我们将进行的唯一明确的技术更改是在后端使用Node Express js上的365bet地区,否则我们将使用相同的堆栈,包括React和PostgresSQL但是,我们将看到365bet地区如何简化和加速开发过程,并使开发人员提高工作效率,而无需在需要的地方放弃更精细的粒度控制

本指南的重点不是学习太多有关Web应用程序如何工作的内在知识,而是开发人员生产力另外,我们将向读者介绍原始SETT文章,以获取有关React应用程序实现的详细信息,因为我们将使用几乎完全相同的代码,所有更改都将突出显示和说明。

本指南说明了如何从技术堆栈(例如,

所以ch的@gys ck

技术栈

要求

要完成本指南,您将需要以下内容

  • 花些时间在你手上

  • 体面的文本编辑器或IDE

  • 安装了JDK或更高版本JAVA首页适当配置

跟随

要开始,请执行以下操作

要么

365bet地区指南存储库包含两个文件夹

  • 初始初始项目通常是一个简单的365bet地区应用程序,其中包含一些其他代码,可以帮助您快速入门

  • 完成一个完整的示例它是按照指南中介绍的步骤进行操作并将这些更改应用于文档的结果。初始

要完成指南,请转到初始

  • 光盘进入grails指南grails vs nodejs初始

并按照下一节中的说明进行操作

您可以直接前往完成的例子如果你光盘进入grails指南grails vs nodejs完成

编写申请

可以在以下位置找到本文的完整示例项目。

反应概况

365bet地区每个365bet地区项目都始于一个创建应用命令为了遵循本指南的目的,您可以选择安装365bet地区通过官方网站或使用365bet地区sdkman365bet地区推荐但是,无需在计算机上安装框架来创建365bet地区应用,而是让我们浏览到http start grails org并使用365bet地区应用伪造.

365bet地区在撰写本文时,选择最新版本的365bet地区,然后选择反应轮廓

随着使用应用资料365bet地区365bet地区允许您构建现代的Web应用程序有一些配置文件可促进使用Javascript前端构建REST API或Web应用程序

启动客户端和服务器应用程序

下载完应用程序后,将其扩展到您选择的目录中光盘进入项目,并在两个单独的终端会话中运行以下两个命令

Windows用户在第二个终端会话中使用gradlew bat gradlew客户端bootRun

Gradlew命令启动Gradle包装器,该包装器由Gradle构建工具365bet地区自365bet地区以来,在所有365bet地区项目中都使用了该包装器。包装器是一个特殊的脚本,在运行命令之前,如果需要的话,可以实际下载并安装Gradle构建工具,然后Gradle将下载所有需要的依赖项(包括365bet地区)并将其安装在项目中,以将其缓存以备将来使用同样,这就是为什么如果您的项目包含Gradle包装程序,则无需在计算机上安装365bet地区,它将为您处理

您可以将Gradle大致视为npm的替代品,npm并不代表节点包管理器它没有提供npm提供的CLI,但是在依赖项管理和构建处理中实现了类似的目的。当运行Gradle命令或任务时,Gradle将首先下载项目s中列出的所有依赖项。建立gradle与运行类似的文件npm安装.

关于服务器客户这两个命令的一部分,因为我们正在使用反应365bet地区个人资料365bet地区实际上为我们创建了两个单独的应用程序,后端365bet地区应用程序和React应用程序是通过以下方式生成的创建反应应用Gradle将这两个应用视为具有上述名称的独立子项目,这称为a多项目构建.

从项目根目录运行Gradle任务后gradlew项目名称将匹配特定于该子项目的任务bootRun在两个项目中都配置了任务以启动相应的应用程序

在哪里bootRun365bet地区来自此Gradle任务是从365bet地区所基于的Spring Boot框架继承而来的创建反应应用默认情况下,项目没有这样的任务React配置文件提供了客户端启动作为npm纱的包裹物的任务开始脚本这使您可以使用高级Gradle功能,例如同时运行两者服务器客户用一个命令以并行模式运行给开发人员gradlew客户端bootRun与跑步相同asl开始要么纱线开始库存中创建反应应用项目,实际上您可以运行客户应用程序就是这样ASL/安装在您的机器上

一旦Gradlew命令已完成下载依赖项并启动它们各自的应用程序,您应该能够浏览到HTTP本地主机365bet地区查看365bet地区后端应用程序和HTTP本地主机查看React应用

365bet地区在继续执行我们的应用程序之前,请花点时间浏览我们现在拥有的应用程序。默认情况下,365bet地区应用程序以JSON格式提供一些有用的元数据,而React应用程序通过REST调用使用该数据,并通过应用程序的导航显示它们。菜单这是一个非常有用的应用程序,但您可以看到已经为您设置了许多样板

数据源

现在我们已经有了基本的应用程序结构,现在该建立数据库了。React Node Express应用365bet地区哪个使用了PostgreSQL,但是值得注意的是365bet地区已经以内存中的H数据库的形式为我们设置了基本数据源。每次运行应用程序时,该数据库都会被破坏并重新创建,甚至在运行时也会进行更新。表格列将在以后的内容中添加到域类中。对于许多应用程序,此默认数据库将非常适合应用程序开发的初始阶段,尤其是当您对数据模型进行许多迭代更改时。正在迁移的应用我们将用所需的PostgreSQL数据库替换此默认H数据源

让我们回顾一下在计算机上安装PostgresQL的步骤

要在Windows中安装PostgreSQL,请参见https www postgresql org下载Windows.

在macOS中安装PostgreSQL

按照以下说明安装Homebrewhttp brew sh输入以下brew install postgresql要启动数据库服务器,请输入pg ctl D usr本地var postgres开始.

要稍后停止数据库服务器,请输入pg ctl D usr本地var postgres快速停止.

网络应用
一步一步马克·沃尔克曼

一旦您安装了Postgres,或者已经安装了Postgres,请使用创建的

createdb冰淇淋db

如果您记得上一篇文章365bet地区从接下来的步骤开始执行上述安装步骤的地方是创建应用程序所需的数据库表。但是,在此项目中我们将不使用任何SQL,因为365bet地区提供了功能强大且对开发人员友好的替代方案。JVM的数据访问工具包

365bet地区GORM可与包括Postgres在内的任何兼容JDBC的数据库以及其他数据库一起使用。要开始在我们的新365bet地区应用中使用Postgres,我们需要完成一些步骤

在我们的系统中安装JDBC驱动程序服务器项目编辑服务器构建gradle并找到名为依存关系添加以下代码行运行时组织PostgreSQL PostgreSQL这将告诉Gradle下载该版本的组织postgresql postgresqlMaven Central存储库中的库,并将其安装在我们的应用中

服务器构建gradle
运行'组织postgresql postgresql'
你可以想到建立gradle填补了与包jsonNode js项目中的文件,它指定仓库, 依存关系定制任务类似于项目的npm脚本

配置GORM以使用我们的PostgreSQL数据库而不是默认的H数据库编辑服务器grails应用conf应用yml向下滚动到以数据源并替换为以下内容

服务器grails应用conf应用yml
数据源:
    dbCreate: 创建放置
    driverClassName: org postgresql驱动程序
    方言: 组织休眠方言PostgreSQLDialect
    用户名: Postgres的
    密码:
    网址: jdbc postgresql本地主机冰淇淋数据库

365bet地区现在我们的365bet地区应用已连接到我们的数据库,但是我们还没有创建任何表。有了365bet地区,就可以手动创建数据库模式了,尽管您当然可以这样做,但是我们可以通过编写代码来指定域模型域类.

365bet地区按照约定,365bet地区将加载位于以下位置的任何Groovy类。365bet地区应用程序域作为域类,这意味着GORM将这些类映射到数据库中的表,并将这些类的属性映射到相应表中的列(可选),GORM将为我们创建这些表,我们已经在我们的表中启用了这些表与文件dbCreate更新设置

这意味着从数据库中设置数据库架构实际上很简单。来源文章.

React Node Express使用了下一个数据库模式

创建 冰淇淋ID系列 味道文本
);

创建 用户名文本  密码文本 加密长度
);

创建 用户冰淇淋用户名文本 参考资料用户名冰淇淋ID整数 参考资料冰淇淋编号

365bet地区GORM是365bet地区默认使用的数据访问工具包。我们可以自定义生成的数据库架构的外观

365bet地区默认情况下,365bet地区中的域类指示使用合理的默认值将它们映射到数据库的方式。您可以使用ORM映射DSL进行自定义

对于我们应用程序中需要的每个表,我们将在365bet地区应用程序域目录

运行以下命令

grailsw创建域类演示IceCream grailsw创建域类演示User grailsw创建域类演示UserIceCream

这些命令将在下面生成三个Groovy类365bet地区应用程序域演示使用以下内容编辑这些文件以生成与上一个应用程序相同的数据库架构

服务器grails应用程序域演示用户
演示 用户 实施 可序列化 {
    用户名密码静态的映射表'使用者'密码类型: '文本'ID: '用户名', 发电机: '已分配'
    }
}
您可能已经注意到我们尚未加密我们的密码专栏不要担心,我们稍后会谈到
服务器grails应用程序域演示IceCream
演示 冰淇淋 实施 可序列化 {
    味道静态的映射表'冰淇淋'味道类型: '文本'
    }
}
服务器grails应用程序域演示UserIceCream
演示 UserIceCream 实施 可序列化用户用户IceCream iceCream静态的映射表'用户冰淇淋'ID综合: ['用户', '冰淇淋'用户: '用户名'冰淇淋: '冰淇淋编号'
    }
}

上一个域类代表我们的联接表用户冰淇淋

包命名

与Java项目中常见的做法一样,我们为域类创建了一个程序包程序包可帮助区分我们的类与以后可能使用的库或插件中的类。程序包反映在目录结构中,这两个文件也将在以下目录中创建365bet地区应用程序域演示.

为什么我们使用句点而不是破折号分隔符,如上一篇文章在Java中,通常认为在包名称中使用破折号连字符是违反约定的有关Java命名约定的详细信息,请参见此链接。.

365bet地区365bet地区控制台

365bet地区如果您现在要启动应用程序,365bet地区将连接到Postgres数据库,以创建持久化域对象所需的表和列。当然,最初数据库中将没有数据。我们将很快解决该问题,但现在我们可以运行365bet地区命令,它将作为交互式控制台提供,我们可以在其中创建更新删除并查询域对象

运行以下命令

grailsw控制台

365bet地区如果您的计算机上本地安装了365bet地区,则可以运行ils直接命令grails控制台365bet地区但是365bet地区项目包括365bet地区w365bet地区wrapper命令将为您安装正确版本的365bet地区

365bet地区现在您将看到365bet地区控制台。您可以从项目中导入自己的代码以及任何依赖项中的任何类,并运行所需的任何Groovy代码。

以下代码将显示如何完成上一个版本中的数据库操作网络应用文章使用GORM和Groovy语言

 演示

删除这些表中的所有行
用户和通过HQL进行的冰淇淋更新对于批处理操作有效IceCream executeUpdate"从IceCream中删除"用户executeUpdate"从用户删除")

插入对应于三种口味的三个新行
定义冰淇淋'香草', '巧克力', '草莓'收集味道冰淇淋味道保存风味齐平: )
}

获取新行的ID数组
定义ids iceCreams id println"插入了ID的记录${id加入',')}"

删除第一行香草
定义香草冰淇淋获得ID0香草删除将第二排巧克力的口味更改为巧克力片
定义巧克力冰激凌findByFlavor'巧克力'巧克力味'巧克力片'节省巧克力齐平: )

获取表中的所有行iceCreams IceCream清单输出其ID和风味ice奶油每个iceCream println"${iceCream ID}: ${冰淇淋味}"
}

365bet地区将上面的代码输入到使用上一个命令启动的365bet地区控制台中,然后单击“运行”按钮以执行脚本。如果您愿意,可以保存该脚本以供以后重用,请注意,此Groovy脚本仅在365bet地区控制台中有效,而不能通过普通脚本运行。 Groovy控制台或Groovy编译器groovyc365bet地区这是因为我们的域类需要由365bet地区加载才能使此代码正常工作

您可能会认为此方法可用于用一些初始数据填充我们的数据库,并且您将我们在控制台中进行的所有插入更新持久化到我们在我们配置的数据库中是正确的365bet地区文件,但是365bet地区提供了BootStrap常规下一部分将更适合该任务的文件

种子数据

编辑档案服务器grails应用程序初始化演示BootStrap groovy并添加以下代码

服务器grails应用程序初始化演示BootStrap groovy
演示 常规转换CompileStatic
 groovy util日志记录Slf j

自我
静态编译
 引导带UserService userService UserRoleService userRoleService RoleService roleService iceCreamService iceCreamService定义初始化servletContext日志信息"载入资料库"

        如果iceCreamService计数清单<long编号[]
            对于 (味道'香草', '巧克力', '草莓'IceCream iceCream iceCreamService saveIcream风味ID iceCream ID日志信息"插入了ID的记录${id加入',')}"
        }

        如果roleService计数角色saveRole roleService角色'角色用户'日志信息"角色扮演"用户用户userService createUser'夏洛克', '秘密'日志信息"插入的用户"userRoleService saveUserRole用户角色日志信息"关联用户和角色"
        }
    }
    定义破坏

365bet地区如您所见,随着协作者365bet地区鼓励将所有业务逻辑保留在服务层中,我们将在下一部分中使用更多有关服务的365bet地区服务。

服务器grails应用程序服务演示IceCreamService groovy
演示 grails编译器365bet地区CompileStatic
 365bet地区 Gorm DetachedCriteria
 grails gorm交易ReadOnly
 grails gorm Transactions事务性
 常规转换CompileStatic
 groovy util日志记录Slf j
 org springframework验证ObjectError

自我
静态编译
交易性
 IceCreamServiceIceCream saveIcream味道布尔值齐平冰淇淋冰淇淋冰淇淋味道味道如果iceCream保存齐平刷新日志错误'剃须膏失败'iceCream错误转换为String iceCream只读
    整型计数IceCream计数 整型
    }
服务器grails应用程序服务演示RoleService groovy
演示 grails gorm交易ReadOnly
 grails gorm Transactions事务性
 常规转换CompileStatic
 groovy util日志记录Slf j

自我
交易性
静态编译
 角色服务 {
    角色saveRole权威布尔值齐平) {
        角色[R 角色(权威权威如果保存齐平刷新日志错误'保存角色时失败'错误toString r只读
    整型计数角色计数 整型
    }
服务器grails应用服务演示UserService groovy
演示 365bet地区 Gorm DetachedCriteria
 grails gorm交易ReadOnly
 grails gorm Transactions事务性
 groovy util日志记录Slf j

自我
交易性
 用户服务用户createUser用户名密码布尔值齐平用户用户用户用户名用户名密码密码如果用户保存齐平刷新日志错误'无法保存用户'用户错误toString用户
服务器grails应用程序服务演示UserRoleService groovy
演示 grails gorm Transactions事务性
 常规转换CompileStatic
 groovy util日志记录Slf j

自我
交易性
静态编译
 UserRoleServiceUserRole saveUserRole用户用户角色角色布尔值齐平UserRole ur用户角色用户用户角色角色如果您保存齐平刷新日志错误'保存用户时失败'ur错误toString ur

认证方式

365bet地区因为365bet地区基于Spring Boot,所以它与Spring生态系统中的许多其他项目兼容。最受欢迎的此类项目之一是春季安全它为Java Web应用程序提供了强大的身份验证和访问控制,并支持从LDAP到OAuth的多种身份验证方法。365bet地区365bet地区插件轻松设置Spring Security

编辑服务器构建gradle再次添加以下两行服务器构建gradle

服务器构建gradle
编译'org grails插件春季安全核心M'编译"org grails插件spring security rest M"

我们已经在其中放置了Spring Security Core和REST插件配置

服务器grails应用conf应用yml
ils:
    插入:
        弹簧安全:
            userLookup:
                userDomainClassName演示用户AuthorityJoinClassName演示UserRole权威:
                班级名称演示角色休息:
                登录:
                    EndpointUrl: /登录
            使用安全事件监听器: 
            controllerAnnotations:
                staticRules:
                    -
                        图案: //**
                        访问allowAll图案: /注册/**
                        访问allowAllfilterChain:
                chainMap:
                    - #无状态链图案: 过滤器JOINED FILTERS异常TranslationFilter authenticationProcessingFilter securityContextPersistenceFilter RememberMeAuthenticationFilter

我们已经稍微修改了用户域类此外,我们还添加了两个其他域类来映射角色以及角色与用户之间的关系

服务器grails应用程序域演示用户groovy
演示 常规转换EqualsAndHashCode
 常规转换ToString
 grails编译器365bet地区CompileStatic

365bet地区CompileStatic
EqualsAndHashCode包括'用户名')
 用户 实施 可序列化 {

    用户名密码日期上次登录空值 (1)
    布尔值已启用
    布尔值accountExpired布尔值帐户被锁定布尔值密码已过期<角色getAuthorities UserRole findAllByUser这个)  清单角色 <角色>
    }

    静态的约束密码可为空: , 空白: , 密码: 用户名可为空: , 空白: , 独特: 上次登录可为空: 
    }

    静态的映射表'使用者'密码: '密码', 类型: '文本'ID: '用户名', 发电机: '已分配'
    }
}
1 我们将使用此属性来跟踪过期的会话
服务器grails应用程序域演示角色groovy
演示 常规转换EqualsAndHashCode
 常规转换ToString
 grails编译器365bet地区CompileStatic
 org springframework安全核心GrantedAuthority

365bet地区CompileStatic
EqualsAndHashCode包括'权威')
ToString包括'权威'includeNamesincludePackage)
 角色 实施 可序列化授予的权限私人的 静态的 最后 serialVersionUID1

    权威静态的约束权限可为空: , 空白: , 独特: 
    }

    静态的映射缓存
    }
}
服务器grails应用程序域演示UserRole groovy
演示 365bet地区 Gorm DetachedCriteria
 常规转换ToString

 org codehaus groovy util HashCodeHelper
 grails编译器365bet地区CompileStatic

365bet地区CompileStatic
ToString快取includeNamesincludePackage)
 用户角色 实施 可序列化 {

    私人的 静态的 最后 serialVersionUID1用户用户角色角色覆写
    布尔值等于其他如果其他实例UserRole其他userId用户用户名other roleId角色ID覆写
    整型hashCode整型hashCode HashCodeHelper initHash如果用户hashCode HashCodeHelper updateHash hashCode用户名如果角色hashCode HashCodeHelper updateHash hashCode角色ID hashCode静态的UserRole获取用户名roleId条件对于用户名roleId获取静态的 布尔值存在用户名roleId标准用于用户名roleId计数私人的 静态的DetachedCriteria标准那么roleId UserRole用户名用户名角色角色加载roleId静态的UserRole创建用户用户角色角色布尔值齐平) {
        定义实例用户角色用户用户角色角色实例保存齐平刷新实例静态的 布尔值删除用户角色[R如果空值[R空值UserRole用户u角色r deleteAll静态的 整型removeAll用户u u空值 ? 0UserRole用户u deleteAll 整型
    }

    静态的 整型移除所有角色r空值 ? 0UserRole,其中角色r deleteAll 整型
    }

    静态的约束用户可为空: 角色可为空: , 验证器: { 角色r UserRole ur如果您的用户ID如果UserRole存在您的用户名r id返回 ['userRole存在']
                }
            }
        }
    }

    静态的映射ID综合: ['用户', '角色'
    }
}

我们添加了一个监听器,该监听器将加密我们的密码一个新的领域用户被建造

服务器src主groovy演示UserPasswordEncoderListener groovy
演示 grails插件springsecurity SpringSecurityService
 组织grails数据存储区映射引擎事件AbstractPersistenceEvent
 组织grails数据存储区映射引擎事件EventType
 组织grails数据存储区映射引擎事件PreInsertEvent
 组织grails数据存储区映射引擎事件PreUpdateEvent
 组织休眠事件睡眠Predeletevent
 org springframework bean工厂注释自动装配
 grails事件注释gorm侦听器
 常规转换CompileStatic

静态编译
 UserPasswordEncoderListener {

    自动接线SpringSecurityService springSecurityService听众用户虚空onPreInsertEvent PreInsertEvent事件encodeUserPasswordForEvent事件听众用户虚空onPreUpdateEvent PreUpdateEvent事件encodeUserPasswordForEvent事件私人的 虚空encodeUserPasswordForEvent AbstractPersistenceEvent事件如果事件entityObject实例用户用户u事件entityObject用户如果u密码事件实例PreInsertEvent事件实例PreUpdateEvent u isDirty'密码'事件getEntityAccess setProperty"密码"encodePassword u密码私人的 encodePassword密码springSecurityService密码编码器springSecurityService encodingPassword密码密码

我们将此监听器注册为Bean资源槽

服务器grails应用conf春季资源groovy
 演示UserPasswordEncoderListener

将您的Spring DSL代码放在这里bean用户PasswordEncoder侦听器用户PasswordEncoder侦听器

REST服务

365bet地区现在我们已经配置好数据库并填充了数据,现在是时候建立服务层了。在此之前,了解365bet地区框架提供的两个主要数据处理工件非常重要。

  1. 控制器365bet地区365bet地区是一个MVC框架,其中C代表Controller。在MVC应用程序中,控制器定义Web应用程序的逻辑并管理模型与视图之间的通信控制器响应要求与模型中的数据进行交互,然后响应带有视图HTML页面或其他消耗性格式(例如JSON控制器)的Groovy类位于365bet地区应用程序控制器

  2. 服务365bet地区很多时候,我们需要对数据做更多的事情,而不只是简单地请求和返回响应。现实世界中的应用程序通常包含大量专用于业务逻辑的代码365bet地区支持的服务是可以完全访问模型但不受约束的类到请求的控制器以及应用程序的其他部分可以调用通过Spring依赖注入提供的服务,以获取他们需要响应其请求的数据。服务是位于365bet地区应用程序服务365bet地区并可以按名称注入其他365bet地区工件中

为了在我们的应用程序中实现我们的RESTful API,我们将使用控制器来响应API请求POST GET PUT DELETE和服务来处理我们的业务逻辑,这在我们的例子中非常简单,让我们从控制器开始

React应用消耗了多个端点

注册端点

注册

要注册到应用程序中,React应用程序会发送一个开机自检要求注册365bet地区端点,它包含一个包含用户名和密码的JSON有效负载我们将使用365bet地区进行映射

首先,通过将下一行添加到映射中,在URLMappings中注册端点

服务器grails应用程序控制器演示UrlMappings groovy
发布'注册'(控制者: '注册')

接下来,我们创建一个控制器,该控制器借助命令对象绑定JSON有效负载

服务器grails应用程序控制器演示SignupCommand groovy
演示 grails验证有效

 注册命令 实施有效期用户名密码静态的约束用户名可为空: , 空白: 密码可为空: , 空白: 
    }
}
服务器grails应用程序控制器演示SignupController groovy
演示 grails插件springsecurity注解安全
 grails插件springsecurity rest令牌AccessToken
 grails插件springsecurity rest令牌生成TokenGenerator
 grails插件springsecurity rest令牌渲染AccessTokenJsonRenderer
 365bet地区grails插件springsecurity userdetails 365bet地区User
 常规转换CompileStatic
 org springframework http HttpStatus
 org springframework安全核心GrantedAuthority
 org springframework安全核心用户详细信息UserDetails

(1)
静态编译
担保的(['被匿名认证'])
 注册控制器 {
    静态的responseFormats'json', 'XML文件'UserRoleService userRoleService UserService userService RoleService roleService TokenGenerator tokenGenerator AccessTokenJsonRenderer accessTokenJsonRenderer定义 注册SignupCommand cmd(2)
        如果userService existUserByUsername cmd用户名呈现状态HttpStatus无法处理的ENTITY值"重复键"
            返回
        }

        角色roleUser roleService findByRoleName'角色用户')
        如果roleUser渲染状态HttpStatus无法处理的ENTITY值"默认角色ROLE USER不存在"
            返回用户user userService createUser cmd用户名cmd密码userRoleService saveUserRole用户roleUser(3)UserDetails userDetails365bet地区365bet地区用户用户名用户名用户密码用户启用的用户帐户过期的用户密码过期的用户帐户锁定的用户权限 采集用户标识AccessToken令牌tokenGenerator generateAccessToken userDetails呈现状态HttpStatus OK值accessTokenJsonRenderer generateJson令牌
1 担保的批注指定此控制器的访问控制,允许匿名访问
2 检查重复的用户名
3 对新创建的用户进行身份验证并生成身份验证令牌。此步骤可节省React应用程序,使用户无需在注册

控制器使用多个服务作为协作者

服务器grails应用程序服务演示RoleService groovy
演示 grails gorm交易ReadOnly
 grails gorm Transactions事务性
 常规转换CompileStatic
 groovy util日志记录Slf j

自我
交易性
静态编译
 角色服务 {
    角色findByRoleName角色名角色权限roleName在哪里获得
服务器grails应用服务演示UserService groovy
演示 365bet地区 Gorm DetachedCriteria
 grails gorm交易ReadOnly
 grails gorm Transactions事务性
 groovy util日志记录Slf j

自我
交易性
 用户服务 {
    只读
    布尔值existUserByUsername用户名findQueryByUsername用户名计数DetachedCriteriafindQueryByUsername名称用户名用户名用户createUser用户名密码布尔值齐平用户用户用户用户名用户名密码密码如果用户保存齐平刷新日志错误'无法保存用户'用户错误toString用户
服务器grails应用程序服务演示UserRoleService groovy
演示 grails gorm Transactions事务性
 常规转换CompileStatic
 groovy util日志记录Slf j

自我
交易性
静态编译
 UserRoleServiceUserRole saveUserRole用户用户角色角色布尔值齐平UserRole ur用户角色用户用户角色角色如果您保存齐平刷新日志错误'保存用户时失败'ur错误toString ur

用户端点的口味

当登录到应用程序时或在注册应用程序后立即向他显示他最喜欢的口味的列表

为了获取这些口味,React应用程序发送了一个得到要求冰淇淋用户名365bet地区端点我们将用365bet地区映射它

首先,通过将下一行添加到映射中,在URLMappings中注册端点

服务器grails应用程序控制器演示UrlMappings groovy
得到"冰淇淋$用户名"(控制者: '冰淇淋', 行动: '指数')

接下来,我们创建一个控制器操作,该操作为所记录的用户名获取一个风味列表

服务器grails应用程序控制器演示IceCreamController groovy
演示 grails插件springsecurity SpringSecurityService
 grails插件springsecurity注解安全
 常规转换CompileDynamic
 常规转换CompileStatic

静态编译
担保的(['角色用户']) (1)
 IceCreamController {
    静态的responseFormats'json']

    静态的allowedMethods指数: '得到', : '开机自检', : '删除'IceCreamService iceCreamService UserIceCreamService userIceCreamService SpringSecurityService springSecurityService定义 指数(整数最大值用户名已记录如果用户名渲染状态: 404
            返回最大参数数学最小最大10, 100) (2)

        清单iceCreams userIceCreamService findAllIceCreamsBy用户名用户名冰淇淋冰淇淋(3)
    }
    动态编译
    受保护的 loggingUsername springSecurityService主体用户名 
    }
1 担保的批注指定此控制器身份验证的访问控制,需要ROLE USER

控制器动作将服务用作协作者

服务器grails应用程序服务演示UserIceCreamService groovy
演示 grails gorm交易ReadOnly
 grails gorm Transactions事务性
 常规转换CompileStatic
 groovy util日志记录Slf j
 org springframework验证ObjectError

自我
交易性
静态编译
 UserIceCreamService {
    只读
    清单findAllIceCreamsByUsername已登录的用户名用户IceCream,其中已登录的用户名用户名列表iceCream 清单
    }
}

我们借助JSON渲染JSON视图

服务器grails应用程序视图iceCream索引gson
 演示IceCream模型可迭代的iceCreams json tmpl冰淇淋iceCreams
服务器grails应用程序视图iceCream iceCream gson
未解决的指令  - 包括:://travis构建grails指南grails vs nodejs完整的服务器grails应用视图iceCream iceCream gson[]

保存用户收藏夹

用户可以从他的个人资料中添加口味

为此,React应用发送一个开机自检要求冰淇淋用户名终点

365bet地区我们将用365bet地区对此进行映射

首先,通过将下一行添加到映射中,在URLMappings中注册端点

服务器grails应用程序控制器演示UrlMappings groovy
发布"冰淇淋$用户名"(控制者: '冰淇淋', 行动: '')

接下来,我们创建一个控制器操作,为记录的用户名添加风味

服务器grails应用程序控制器演示IceCreamController groovy
演示 grails插件springsecurity SpringSecurityService
 grails插件springsecurity注解安全
 常规转换CompileDynamic
 常规转换CompileStatic

静态编译
担保的(['角色用户']) (1)
 IceCreamController {
    静态的responseFormats'json']

    静态的allowedMethods指数: '得到', : '开机自检', : '删除'IceCreamService iceCreamService UserIceCreamService userIceCreamService SpringSecurityService springSecurityService定义 (味道用户名已记录如果用户名渲染状态: 404
            返回
        }
        定义id iceCreamService addIceCreamToUser用户名风味ID呈现ID状态: 500]
    }
    动态编译
    受保护的 loggingUsername springSecurityService主体用户名 
    }
1 担保的批注指定此控制器身份验证的访问控制,需要ROLE USER

控制器动作将服务用作协作者

服务器grails应用程序服务演示IceCreamService groovy
演示 grails编译器365bet地区CompileStatic
 365bet地区 Gorm DetachedCriteria
 grails gorm交易ReadOnly
 grails gorm Transactions事务性
 常规转换CompileStatic
 groovy util日志记录Slf j
 org springframework验证ObjectError
自我
静态编译
交易性
 IceCreamService {
    如果在保存冰淇淋或icream与用户之间的关联时发生错误,则返回null
    365bet地区CompileStaticIceCream addIceCreamToUser用户名iceCreamFlavor布尔值齐平用户用户userService findByUsername用户名如果用户日志错误'用户不存在'用户名返回 空值IceCream iceCream findQueryByFlavor iceCreamFlavor get冰淇淋味道iceCreamFlavor如果iceCream保存齐平刷新iceCream错误allErrors每个ObjectError错误日志错误error toString返回 空值UserIceCream userIceCream userIceCreamService创建用户iceCream刷新如果返回 空值冰淇淋

删除用户的口味

用户可以从个人资料中删除口味

为此,React应用发送一个删除要求冰淇淋用户名ID终点

我们将用365bet地区对此进行映射

首先,通过将下一行添加到映射中,在URLMappings中注册端点

服务器grails应用程序控制器演示UrlMappings groovy
"冰淇淋$用户名/$ID"(控制者: '冰淇淋', 行动: '')

接下来,我们创建一个控制器操作,该操作将删除所记录用户名的特定样式

服务器grails应用程序控制器演示IceCreamController groovy
演示 grails插件springsecurity SpringSecurityService
 grails插件springsecurity注解安全
 常规转换CompileDynamic
 常规转换CompileStatic

静态编译
担保的(['角色用户']) (1)
 IceCreamController {
    静态的responseFormats'json']

    静态的allowedMethods指数: '得到', : '开机自检', : '删除'IceCreamService iceCreamService UserIceCreamService userIceCreamService SpringSecurityService springSecurityService定义 (longID用户名已记录如果用户名渲染状态: 404
            返回响应iceCreamService removeIceCreamFromUser用户名ID状态: 500]
    }
    动态编译
    受保护的 loggingUsername springSecurityService主体用户名 
    }
1 担保的批注指定此控制器身份验证的访问控制,需要ROLE USER

控制器动作将服务用作协作者

服务器grails应用程序服务演示IceCreamService groovy
演示 grails编译器365bet地区CompileStatic
 365bet地区 Gorm DetachedCriteria
 grails gorm交易ReadOnly
 grails gorm Transactions事务性
 常规转换CompileStatic
 groovy util日志记录Slf j
 org springframework验证ObjectError
自我
静态编译
交易性
 IceCreamService {
    布尔型removeIceCreamFromUserloggingUsernamelongid IceCream iceCreamEntity IceCream加载id用户loggingUser用户加载loggingUsername UserIceCream用户登录的用户iceCream iceCreamEntity deleteAll

网络套接字

365bet地区我们最终的服务器端功能是通过websocket连接将会话超时事件推送到客户端。我们将使用另一个365bet地区插件Spring Websocket插件支持此功能

通过添加另一行来安装插件建立gradle文件

服务器构建gradle
编译'org grails插件grails spring websocket'

现在,我们必须实现三个类,以使您的websocket会话超时工作

  1. 一个配置类来配置我们的websocket连接

  2. 一个侦听器类,用于跟踪何时创建新的身份验证令牌

  3. 调度程序类,用于定期检查过期的会话并通过websocket连接推送事件

365bet地区由于这些类不是特定于365bet地区的,因此我们将在Groovy类下将它们创建为服务器src主groovy.

这是这三个类的完整代码

服务器src主要groovy演示CustomWebSocketConfig groovy
演示 grails插件springwebsocket DefaultWebSocketConfig
 groovy util日志记录Slf j
 org springframework bean工厂注释值
 org springframework上下文注释配置
 org springframework网络套接字配置注释EnableWebSocketMessageBroker
 org springframework网络套接字配置注释StompEndpointRegistry

自我
组态
EnableWebSocketMessageBroker
 CustomWebSocketConfig 延伸DefaultWebSocketConfig('allowedOrigin')
    allowedOrigin(1)

    覆写
    虚空registerStompEndpoints StompEndpointRegistry stompEndpointRegistry(2)日志信息'使用allowedOrigin注册StompEndpoints'allowedOrigin stompEndpointRegistry addEndpoint""setAllowedOriginsallowedOrigin withSockJS
1 加载我们的allowedOrigin来自应用程序yml的config属性
2 配置websocket连接以接受来自客户端服务器的请求
服务器src主要groovy演示TokenCreationEventListener groovy
演示 org springframework上下文ApplicationListener
 grails插件springsecurity rest RestTokenCreationEvent

 TokenCreationEventListener 实施ApplicationListener {

    虚空onApplicationEvent RestTokenCreationEvent事件(1)具有交易的用户(2)用户用户用户名用户名事件主体用户名第一个用户lastLogin 日期用户保存齐平: )
        }
    }
}
1 我们正在扩展ApplicationListenerRestTokenCreationEvent您可以在Spring Security REST插件文档.
2 withTransaction这里需要此方法,因为默认情况下,与控制器和服务不同,我们的自定义类无法访问GORM。用户域类实际上并不重要,我们可以在此处使用任何域类withNewSession将启动GORM Hibernate事务,并允许我们查询数据库并保留更改。请参阅GORM文档以获取更多详细信息
服务器src主groovy演示SessionExpirationJobHolder groovy
演示 常规时间TimeCategory
 org springframework bean工厂注释自动装配
 org springframework bean工厂注释值
 组织springframework消息传递simp SimpMessagingTemplate
 org springframework调度注释Scheduled

 SessionExpirationJobHolder {

    自动接线SimpMessagingTemplate brokerMessagingTemplate(1)

    ('超时分钟') (2)
    整数暂停预定的克朗"0 * * * * *") (3)
    虚空findExpiredSessions日期timeoutDate使用TimeCategory(4)超时时间 日期超时分钟用户withTransaction清单expiredUsers用户所在的位置查询上次登录日期超出超时限制的登录用户上次登录空值lastLogin超时日期列表遍历过期的用户expiredUsers每个用户用户lastLogin空值 重设上次登录日期用户保存齐平: )

                (5)brokerMessagingTemplate convertAndSend"话题${用户名}""登出"
            }
        }
    }
}
1 该类由spring websocket插件提供,允许我们通过websocket通道推送事件
2 从应用程序yml加载超时分钟属性
3 每分钟运行一次方法
4 使用Groovy时间类别DSL用于时间操作
5 向每个已过期用户发送网络套接字消息到用户特定的频道,我们将其用户名用作每个频道的唯一密钥

有了这些类之后,我们需要通过将它们作为bean添加到我们的Spring上下文中来将它们插入资源槽文件编辑文件,如下所示

服务器grails应用conf春季资源groovy
 演示UserPasswordEncoderListener
 演示SessionExpirationJobHolder
 演示TokenCreationEventListener
 演示CustomWebSocketConfig

将您的Spring DSL代码放在这里bean用户PasswordEncoder侦听器用户PasswordEncoder侦听器(1)webSocketConfig CustomWebSocketConfig(2)tokenCreationEventListener TokenCreationEventListener(3)sessionExpirationJobHolder sessionExpirationJobHolder(4)
}
1 注册密码编码器监听器
2 Websocket的自定义设置
3 侦听新的访问令牌并设置loginDate
4 检查过期的会话

接下来为了我们的预定SessionExpirationJobHolder类实际上按计划触发,我们必须在我们的应用程序中启用计划,默认情况下未启用它,我们通过编辑应用程序来实现应用程序文件注释大写

服务器grails应用程序初始化演示应用程序常规
演示 grails启动365bet地区App
 grails启动配置365bet地区AutoConfiguration

启用排程
 应用 延伸365bet地区AutoConfiguration静态的 虚空主要[]args 365bet地区App运行应用程序args

最后您可能还记得我们在上面的类中引用了几个新的配置属性,让我们将它们添加到文件将以下行添加到文件末尾

服务器grails应用conf应用yml
allowedOrigin: https本地主机 Websocket连接接受的原始URL
暂停:
    分钟: 1 超时的配置设置
365bet地区365bet地区应用程序可以读取以各种方式设置的配置值,包括YAML文件,Groovy文件和系统属性。请参阅365bet地区365bet地区文档有关如何使用配置文件的更多信息

365bet地区对于365bet地区后端服务器,仅此而已,我们支持开箱即用的CORS websockets身份验证RESTful Web服务的计划方法和对PostgresSQL的持久性

反应

现在,我们准备转到应用程序的客户端部分。对于这一步,我们将使用Github存储库中的代码以前的Web App文章您可以在此处访问代码https github com mvolkmann冰淇淋应用程序树主src

下载或上方网址中的源文件,然后将其复制到客户端src目录覆盖任何现有文件

cd git clone https github com mvolkmann冰淇淋应用程序tmp cp rv tmp冰淇淋客户端

这将使客户子项目与的“冰淇淋”应用程序相同上一篇文章.

现在让我们删除不需要的文件

cd冰淇淋客户端rm射频数据库服务器css图像

这将使您在以下目录中拥有以下目录客户:

rw r r许可证rw r r自述文件md drwxr xr x生成rw r r生成gradle drwxr xr x节点模块rw r r软件包json drwxr xr x公共drwxr xr x src rw r r纱线锁

和以下文件客户端src:

rw rr App css rw rr App js rw rr App测试js rw rr配置js rw rr冰淇淋输入js rw rr冰淇淋列表js rw rr冰淇淋行js rw rr索引css rw rr索引js rw rr登录js rw rr主要js

我们只需要编辑这些src文件并更新我们的包json365bet地区为了将React应用程序与我们新的365bet地区后端连接起来

第一次编辑客户端软件包json如下所示

客户端软件包json
{
  "": "冰淇淋应用",
  "": "0.1.0",
  "私人的": ,
  "devDependencies": {
    "int": "^3.17.1",
    "eslint插件流程类型": "^2.30.3",
    "eslint插件反应": "^6.10.0",
    "反应脚本": "0.8.4"
  },
  "依存关系": {
    "反应": "^15.4.2",
    "反应dom": "^15.4.2",
    "sockjs客户端": "^1.1.4",
    "存根": "^2.3.3"
  },
  "剧本": {
    "建立": "反应脚本构建",
    "覆盖范围": "ASL测试覆盖率",
    "皮棉": "eslint src js服务器js",
    "开始": "反应脚本开始",
    "测试": "反应脚本测试环境jsdom"
  }
}

我们删除了Node Express服务器所需的几个软件包,并替换了给我插上与包装sockjs客户端存根我们之前配置的Spring Websockets库支持给我插上是为Node应用程序设计的,与Spring Websockets不兼容

我们还需要编辑配置js365bet地区服务器URL设置API基本URL的值,如下所示编辑文件

客户端src配置js
来自的pjson'包json';

出口const SERVER URL'HTTP本地主机';
出口const CLIENT VERSION pjson版本出口const REACT VERSION pjson依赖项有反应

现在我们必须更新冰淇淋应用程序中的两个React组件登录应用程式.

登录js如下图所示,文件从原始码标有注释

客户端src登录js
将组件PropTypes作为t来自'反应';
 '取什么';

功能 onChangePassword事件React setState密码事件目标值功能 onChangeUsername事件React setState用户名事件目标值登录延伸零件静态的范本密码t字符串是必需的restUrlt字符串是必需的用户名t字符串是必需的超时处理程序功能我们的timeoutHander函数的新propType
  };

  按下“登录”按钮时称为onLogin异步const密码restUrl用户名timeoutHandler这个道具常量网址`$restUrl登录`;

    尝试 {
      发送用户名和密码以登录REST服务const res等待获取网址方法: '开机自检',
        标头: {'内容类型': '应用程序json'},
        身体JSON字符串化用户名密码如果还可以成功登录常量文本等待res文本回报承诺

        新的Spring Security REST在响应主体中返回令牌,而不是Authorization标头const令牌`承载者$JSON解析文本访问令牌`反应setState已认证: ,
          错误: 空值, 清除先前的错误
          路线: '主要'令牌timeoutHandler用户名NEW连接到特定于用户的Websocket频道

      } 其他 { 新来自服务器的任何错误响应都表明登录失败const msg"用户名或密码无效"反应setState错误味精抓住e反应setState错误: `$网址$信息`});
    }
  }

  按下“注册”按钮时称为onSignup异步const密码restUrl用户名timeoutHandler这个道具常量网址`$restUrl注册`;

    尝试const res等待获取网址方法: '开机自检',
        标头: {'内容类型': '应用程序json'},
        身体JSON字符串化用户名密码如果还可以成功注册常量文本等待res文本回报承诺const令牌`承载者$JSON解析文本访问令牌`; 新见上文反应setState已认证: ,
          错误: 空值, 清除先前的错误
          路线: '主要'令牌timeoutHandler用户名新增连接到用户特定的Websocket频道

      } 其他 { 注册失败让文本等待res文本回报承诺
        如果 (/重复键/测试文字`用户$此用户名已存在`反应setState错误文本抓住e反应setState错误: `$网址$信息`渲染const密码用户名这个props const canSubmit用户名密码我们正在处理用户名和密码的发送
    到上面的REST服务,所以我们不想
    HTML表单为我们提交任何内容
    这就是调用preventDefault的原因
    返回 (
      <> 班级名称="登录表单"
            onSubmit={事件 =>事件preventDefault>
        <> 班级名称="">
          
          <> 类型="文本"
                 自动对焦
                 onChange={onChangeUsername}
                 ={用户名}
          />
        
<> 班级名称=""> 密码 <> 类型="密码" onChange={onChangePassword} ={密码} />
<> 班级名称="行提交">在任一输入中按Enter键将调用第一个按钮<> 残障人士={!堪苏贝} onClick={这个onLogin}>登录 <> 残障人士={!堪苏贝} onClick={这个onSignup}>注册
形成 ); } } 出口 默认登录

最后,我们可以编辑应用程式js文件来支持我们的新websocket频道以及我们的服务器URL像上面所有配置更改相对于原始码标有注释

客户端src App js
从反应组件'反应';
从登录'登录';
主要来自'主要';
 '取什么'; 用于REST调用
来自的服务器网址'配置'; REST调用的新基本URL
 '应用程式CSS';

这允许客户端监听sockJS事件
从服务器发出它用于主动
会话超时到期时终止会话
来自的SockJS'sockjs客户端'; 新的SockJS Stomp而不是socket io
踩到'存根';

应用程式延伸组件构造器();

    Redux是一个流行的库,用于在React应用程序中管理状态
    此应用程序较小,因此选择了更简单的方法
    最顶层的组件管理着所有状态
    将setState方法的绑定版本放置在React对象上
    允许其他组件调用它以修改状态
    每次调用都会导致UI重新呈现
    使用虚拟DOM来提高效率反应setState这个setState绑定这个);
  }

  新增删除顶级websocket配置,以支持登录时设置的用户特定频道

  这是应用程序的初始状态已认证: ,
    错误: '',
    味道: '',
    iceCreamMap: {},
    密码: '',
    restUrl服务器URLNEW使用我们的SERVER URL变量
    路线: '登录', 控制当前页面
    代币: '',
    用户名: ''
  };

  NEW清除令牌并重定向到登录页面不需要注销API调用,因为JWT令牌随着服务器状态更改而过期注销异步React setState已认证: ,
        路线: '登录',
        密码: '',
        用户名: ''
      });
  };

  新增功能该函数将作为道具传递给Login组件。它将从服务器获取SockJS连接,并为超时事件订阅主题用户名通道。如果收到该用户名,则用户将注销timeoutHandler用户名const套接字SockJS`$服务器URL踩踏`const client Stomp over socket client连接客户端订阅`/话题/$用户名`'您的会话超时');
        这个注销控制台错误'无法连接'渲染使用解构从状态对象中提取数据const验证错误的味道iceCreamMap密码restUrl路由令牌用户名这个返回 (
      <> 班级名称="应用程式">
        
<> 班级名称="标题图片" src="冰淇淋png" 一切="冰淇淋" />冰淇淋我们都尖叫着通过认证<> onClick={这次登出}>登出空值 <> 班级名称="应用程式主体">这是控制到页面的路由的一种替代方法,它比反应路由器路由登录之类的全面解决方案要简单得多<> 用户名={用户名} 密码={密码} restUrl={restUrl} 超时处理程序={这个timeoutHandler} // 通过 我们的 超时处理程序 一种 关闭 <登录> />主干<> 味道={味道} iceCreamMap={iceCreamMap} restUrl={restUrl} 代币={代币} 用户名={用户名} /> :
未知路线
} { 如果发生错误,请在任何页面的底部进行渲染错误<> 班级名称="错误">错误
: 空值 } </div/div出口 默认应用程式

运行应用程序

如下运行服务器应用程序

365bet地区gradlew服务器启动环境开发中运行在http localhost上的GraRun应用程序

运行启用了HTTPS的客户端应用程序,如下所示

export HTTPS true gradlew client bootRun启动开发服务器已成功编译应用程序正在https localhost运行

您应该能够浏览到https本地主机365bet地区创建新用户并登录尝试使用该应用程序添加删除您的用户帐户中的某些风味一分钟后,您的用户会话将结束,您将被注销,可以在365bet地区应用程序中更改此持续时间文件如果您打开多个浏览器并在不同时间以不同的用户身份登录,则每个用户仅在其会话期满时才分别注销。

365bet地区恭喜,您已经在365bet地区后端上运行了Ice Cream应用程序

部署方式

365bet地区部署本身是一个大话题,我们将不深入讨论。与许多Java Web应用程序一样,我们的365bet地区后端可以作为WAR文件部署到任何JEE容器中,这将需要一些额外的配置,或者作为一个自包含的组件而需要进行更简洁的配置。不需要单独的服务器即可运行的JAR文件

我们的React应用程序可以像任何其他应用程序一样构建创建反应应用项目使用npm运行构建要么并部署到标准Web服务器上显然,必须注意确保React客户端可以访问API后端,并且服务器URL变量可能需要相应地设置

365bet地区打包和部署365bet地区 React应用程序的一种有吸引力的方法是将这两个应用程序捆绑在一起,成为一个可执行的JAR文件。合并React Profile项目365bet地区365bet地区指南

摘要

365bet地区当然,该项目与众不同之处在于,我们正在设计一个使用一种技术堆栈设计的应用程序,然后在另一种技术堆栈中重新实现。但是希望本次练习向您展示了365bet地区框架的功能和灵活性,尤其是使用了最少的更改后针对新的365bet地区后端使用相同的React应用

365bet地区365bet地区提供了许多强大的Web开发功能,我们没有机会或时间在此应用程序中进行演示,包括JSON视图, HAL JSON支持, 自定义数据验证和约束, NoSQL支持, 多租户还有更多请查看365bet地区365bet地区指南网站365bet地区有关使用365bet地区构建有趣且功能强大的应用程序的更多教程,包括一些指南具有React).

帮助365bet地区

365bet地区服务:

免费咨询

OCI 365bet地区团队包括365bet地区联合创始人检查我们的并向发展和维护365bet地区的工程师学习

Grails OCI团队