票务小程序源码_使用AdonisJs构建支持票务应用程序-第1部分

票务小程序源码

AdonisJs is a MVC Framework for Node that is greatly inspired by the Laravel framework and borrows some of it concepts. AdonisJs follows the software paradigm of conventions over configuration which allows you focus on building your application rather than waste time on configurations. AdonisJs uses ES2015 Generators which removes the unnecessary callbacks from your code.

AdonisJs是Node的MVC框架,受Laravel框架的启发,并借鉴了其中的一些概念。 AdonisJs遵循约定之上的配置软件范例,使您可以专注于构建应用程序,而不是在配置上浪费时间。 AdonisJs使用ES2015 Generators ,可从代码中删除不必要的回调

To explore more about AdonisJs, checkout this tutorial by the creator of Adonis: Meet AdonisJs! A Laravel-style MVC Framework for Node.js.

要了解有关AdonisJs的更多信息,请查看Adonis的创建者提供的本教程: Meet AdonisJs! 适用于Node.js的Laravel样式MVC框架

In this tutorial, we'll to be building a support ticket application. Some times ago, I did a tutorial on how to build a support ticket with Laravel. We're basically going to rebuild that application, but this time we'll build it with AdonisJs.

在本教程中,我们将构建一个支持票证应用程序。 之前,我做了一个教程, 介绍如何使用Laravel构建支持凭单 。 我们基本上将重建该应用程序,但是这次我们将使用AdonisJs进行构建。

A support ticket application provides a medium for customers to lodge issues they face using a particular organization's service/product by opening a support ticket with the organization's help desk.

支持票证应用程序为客户提供了一种媒介,可以通过使用组织的服务台打开支持票证来使用特定组织的服务/产品来解决他们所面对的问题。

What we will be building will be minimal, but it will have the major features of support ticket application.

我们将要构建的内容很少,但是它将具有支持票证应用程序的主要功能。

需求与流程 ( Requirements And Flow )

  • Only authenticated user can open support tickets.

    只有经过身份验证的用户才能打开支持凭单。
  • Upon opening the tickets, an email will be sent to the user along with the details of the support ticket opened.

    打开票证后,将会向用户发送电子邮件以及所打开的支持票证的详细信息。
  • Subsequently, mails will be sent to the user as the customer support staff or admin response to the ticket.

    随后,邮件将作为客户支持人员或管理员对票证的响应发送给用户。
  • The user can also respond to the ticket he/she opened by commenting on the ticket.

    用户还可以通过评论票证来响应他/她打开的票证。
  • The admin or the customer support staff can also mark a ticket as resolved.

    管理员或客户支持人员也可以将故障单标记为已解决。
  • Once a ticket is marked as closed, the user who opened the ticket will be notified by email on the status of the ticket.

    一旦将票证标记为已关闭,打开票证的用户将收到有关票证状态的电子邮件通知。

Below is an image of the final output of what we'll be building.

下面是我们将要构建的最终输出的图像。

让我们开始吧 ( Let's Get Started )

We'll start by installing the Adonis-CLI which is a command line tool to install stable and dev releases of AdonisJs with all required dependencies.

我们将从安装Adonis-CLI开始,这是一个命令行工具,用于安装具有所有必需依赖项的AdonisJs的稳定版本和dev版本。

npm install -g adonis-cli

Note: you might need to use sudo with the above command.

注意:您可能需要在上述命令中使用sudo

With the adonis-cli installed, let's create a new project:

安装了adonis-cli ,我们创建一个新项目:

adonis new adonis-support-ticket

This will create a new project called adonis-support-ticket and will also install all the required dependencies from NPM.

这将创建一个名为adonis-support-ticket的新项目,还将安装NPM所需的所有依赖项。

Note: If you are using Node.JS < 6.0, make sure to make following changes. Replace the scripts inside package.json file with following:

注意:如果使用的Node.JS <6.0 ,请确保进行以下更改。 用以下内容替换package.json文件中的脚本:

// package.json"scripts": {
  "serve:dev": "nodemon --watch app --watch bootstrap --watch config --watch .env -x \"node --harmony_proxies\" server.js",
  "serve": "node --harmony_proxies server.js"
}

Lastly replace the first line of the ace file with following .ace:

最后,用以下.ace替换ace文件的第一行:

// .ace#!/usr/bin/env node --harmony_proxies

You can test to make sure the installation was successful by running:

您可以通过运行以下命令进行测试,以确保安装成功:

cd adonis-support-ticket
npm run serve:dev

Now open http://localhost:3333 to see the welcome page.

现在打开http:// localhost:3333以查看欢迎页面。

The rest of this tutorial assumes you are already in the project directory which is adonis-support-ticket.

本教程的其余部分假定您已经在项目目录中,即adonis-support-ticket

数据库设置 ( Database Setup )

We'll be using MySQL for our database. First, we need to install a Node.JS driver for MySQL:

我们将对数据库使用MySQL。 首先,我们需要为MySQL安装Node.JS驱动程序:

npm install mysql --save

Now we need a database to hold our data. Open the .env file and fill it with your database details.

现在我们需要一个数据库来保存我们的数据。 打开.env文件,并用数据库详细信息填充它。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_USER=root
DB_PASSWORD=
DB_DATABASE=adonis-support

Update the details above with your own database details.

使用您自己的数据库详细信息更新上述详细信息。

用户认证 ( User Authentication )

Only registered users can make use of our Support Ticket Application, so we need an authentication system which will allow users to register and login to the application. AdonisJs can get us up to speed with that. With AdonisJs Authentication Provider, we can generate required migrations using an ace command:

只有注册用户才能使用我们的支持票务应用程序,因此我们需要一个身份验证系统,该系统将允许用户注册和登录该应用程序。 AdonisJs可以帮助我们加快步伐。 借助AdonisJs身份验证提供程序,我们可以使用ace命令生成所需的迁移:

./ace auth:setup

This will create the required migrations and models for authentication. AdonisJs by default, makes use of the session authenticator (which we'll stick to in this tutorial) to authenticate requests. Since we'll be using the session based login system, we are concerned with the User model and users table migration. But before we run the migrations, let's modify the users migration to suit our application. Update the up() of users table migration with:

这将创建所需的迁移和身份验证模型。 默认情况下,AdonisJs使用session身份验证器(我们将在本教程中继续介绍)来对请求进行身份验证。 由于我们将使用基于会话的登录系统,因此我们关注User模型和users表的迁移。 但是在运行迁移之前,让我们修改users迁移以适合我们的应用程序。 使用以下命令更新users表迁移的up()

// database/migrations/...create_users_table.js

// users table migration showing only the up() schemas with our modifications
this.create('users', table => {
    table.increments()
    table.string('username', 80).notNullable().unique()
    table.string('email', 254).notNullable().unique()
    table.string('password', 60).notNullable()
    table.integer('is_admin').unsigned().default(0);
    table.timestamps()
})

We add an is_admin column which will help us track if a particular user is an admin or not. Go ahead and run the migrations:

我们添加了一个is_admin列,该列将帮助我们跟踪特定用户是否为管理员。 继续并运行迁移:

./ace migration:run

Now we can start building the authentication system. Let's start by creating a HTTP request controller called AuthController which will handle all the authentication logic:

现在我们可以开始构建身份验证系统。 首先创建一个名为AuthController的HTTP请求控制器,该控制器将处理所有身份验证逻辑:

./ace make:controller Auth

This will create AuthController.js within the app/Http/Controllers directory.

这将在app/Http/Controllers目录中创建AuthController.js

Before we start writing the authentication logic, let's setup Validator which we'll use for form validation since we'll need to validate user submitted data.

在开始编写身份验证逻辑之前,让我们设置Validator,将其用于表单验证,因为我们需要验证用户提交的数据。

Validator is not the part of the base installation and hence we are going to install and register it manually:

验证程序不是基本安装的一部分,因此我们将手动安装并注册它:

npm install adonis-validation-provider --save

Once installed, we need to add it to the providers array in bootstrap/app.js

安装后,我们需要将其添加到bootstrap/app.jsproviders数组中

// bootstrap/app.js

const providers = [
    // ...
    'adonis-validation-provider/providers/ValidatorProvider'
    // ...
]

Also, we need to add it to the aliases object in bootstrap/app.js

另外,我们需要将其添加到bootstrap/app.jsaliases对象中

// bootstrap/app.js

const aliases = {
    // ...
    Validator: 'Adonis/Addons/Validator'
    // ...
}

Now we can start using the Validator. Let's define some rules for our users registration. Open app/Model/User.js and add the code below to it:

现在我们可以开始使用验证器了。 让我们为用户注册定义一些规则。 打开app/Model/User.js并添加以下代码:

// app/Model/User.js

/**
 * Define validation rules
 */
static get rules() {
    return {
        username: 'required|unique:users',
        email: 'required|email|unique:users',
        password: 'required|confirmed|min:6'
    }
}

Yeah, just some basic vaildation rules. We'll be using these rules shortly.

是的,只是一些基本的验证规则。 我们将很快使用这些规则。

Now open app/Http/Controllers/AuthController.js and add the following to it:

现在打开app/Http/Controllers/AuthController.js并添加以下内容:

// app/Http/Controllers/AuthController.js

// remember to add these to the top of file after 'use strict'
const User = use('App/Model/User')
const Validator = use('Validator')

/**
 * Show register page
 */
* showRegisterPage(request, response) {
    yield response.sendView('auth.register')
}

/**
 * Handle user registration
 */
* register(request, response) {
    // validate form input
    const validation = yield Validator.validateAll(request.all(), User.rules)

    // show error messages upon validation fail
    if (validation.fails()) {
        yield request
            .withAll()
            .andWith({ errors: validation.messages() })
            .flash()

        return response.redirect('back')
    }

    // persist to database
    const user = yield User.create({
        username: request.input('username'),
        email: request.input('email'),
        password: request.input('password')
    })

    // login the user
    yield request.auth.login(user)

    // redirect to homepage
    response.redirect('/')
}

The showRegisterPage() will simply render the user registration page. The register() will handle the user registration. It first validates the user submitted form inputs against the rules we defined above. If the validation fails, we return back to the registration page with the form request and display the appropriate error message(s) as flash message. If the validation passes, we persist user data to the database using Lucid, which is AdonisJs ORM to make secure SQL queries. Next, we log the registered user in and lastly redirect them to the homepage.

showRegisterPage()将仅呈现用户注册页面。 register()将处理用户注册。 它首先根据我们上面定义的规则验证用户提交的表单输入。 如果验证失败,我们将返回带有表单请求的注册页面,并将适当的错误消息显示为Flash消息。 如果通过验证,我们将使用Lucid将用户数据持久保存到数据库中, Lucid是AdonisJs ORM,用于进行安全SQL查询。 接下来,我们登录注册用户,最后将他们重定向到主页。

用户注册途径 (User Registrarion Routes)

Next, we need to define our user registration routes. Open app/Http/routes.js and paste the code below into it:

接下来,我们需要定义我们的用户注册路线。 打开app/Http/routes.js并将以下代码粘贴到其中:

// app/Http/routes.js

Route.get('register', 'AuthController.showRegisterPage')
Route.post('register', 'AuthController.register')

We defined two register routes that will handle GET and POST request respectively. The first route will trigger the showRegisterPage() on AuthController while the second will trigger the register() on AuthController.

我们定义了两个register路由,分别处理GETPOST请求。 第一条路线将触发showRegisterPage()AuthController而第二将触发register()AuthController

用户注册视图 (User Registration View)

AdonisJs view engine is built on top of nunjucks. We'll start by creating a master layout which we would use across the entire application. Within the views directory, there is a master.njk view file, we are going to move this file to a new directory called layouts:

AdonisJ的视图引擎建立在nunjucks之上 。 我们将从创建一个主布局开始,该布局将在整个应用程序中使用。 在views目录中,有一个master.njk视图文件,我们将把这个文件移动到一个名为layouts的新目录中:

cd resources/views
mkdir layouts
mv master.njk layouts

With master.njk in its new directory, let's open it up and replace the code in it with the following:

master.njk放在新目录中,让我们打开它,并用以下代码替换其中的代码:

<!-- resources/views/layouts/master.njk -->

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

    <title>Adonis Support - {% block title %}{% endblock %}</title>

    <link rel="icon" href="/assets/favicon.png" type="image/x-icon">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
    <nav class="navbar navbar-default">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
            <span class="sr-only">Menu</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="/">Adonis Support</a>
        </div>

        <div class="collapse navbar-collapse" id="navbar-collapse">
          <ul class="nav navbar-nav navbar-right">
            {% ifAsync currentUser %}
              <li><a href="/new_ticket">Open Ticket</a></li>
              <li class="dropdown">
                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ currentUser.username }} <span class="caret"></span></a>
                <ul class="dropdown-menu">
                  <li><a href="/my_tickets">My Tickets</a></li>
                  <li role="separator" class="divider"></li>
                  <li><a href="/logout">Logout</a></li>
                </ul>
              </li>

            {% else %}
              <li><a href="/login">Login</a></li>
              <li><a href="/register">Register</a></li>
            {% endif %}
          </ul>
        </div>
      </div>
    </nav>

    <div class="container">
      {% block content %}{% endblock %}
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>

A basic Bootstrap layout, the navbar shows links for users to either login or register if they are not already logged in. Otherwise it shows a link to open a new ticket and a dropdown menu with a link to display all tickets created my the logged in user and lastly, a logout link. The main page of the layout contains a content block which will be replaced by the actual content of the page extending this master layout.

基本的Bootstrap布局,导航栏显示供用户登录或注册(如果他们尚未登录)的链接。否则,它显示一个用于打开新票证的链接和一个下拉菜单,其中包含用于显示通过登录名创建的所有票证的链接。用户,最后是注销链接。 布局的主页包含一个内容块,该内容块将由扩展此主布局的页面的实际内容替换。

With the master layout structured, let's move on to creating the user registration view. Create a new directory called auth within the resources/views directory, this will house all the application's authentication view files. Within the auth directory, create a new file named register.njk and paste the code below to it:

通过构建主布局,让我们继续创建用户注册视图。 在resources/views目录中创建一个名为auth的新目录,该目录将容纳所有应用程序的身份验证视图文件。 在auth目录中,创建一个名为register.njk的新文件,并将以下代码粘贴到该文件:

<!-- resources/views/auth/register.njk -->

{% extends 'layouts.master' %}

{% block title %}
    Register
{% endblock %}

{% block content %}
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Register</div>
                    <div class="panel-body">

                        {% include 'includes.errors' %}

                        {{ form.open({ url: '/register', method: 'POST', class: 'form-horizontal' }) }}
                            {{ csrfField }}

                            <div class="form-group">
                                {{ form.label('username', 'Username', { class: 'col-md-4 control-label' }) }}

                                <div class="col-md-6">
                                    {{ form.text('username', old('username'), { class: 'form-control' }) }}
                                </div>
                            </div>

                            <div class="form-group">
                                {{ form.label('email', 'Email', { class: 'col-md-4 control-label' }) }}

                                <div class="col-md-6">
                                    {{ form.email('email', old('email'), { class: 'form-control' }) }}
                                </div>
                            </div>

                            <div class="form-group">
                                {{ form.label('password', 'Password', { class: 'col-md-4 control-label' }) }}

                                <div class="col-md-6">
                                    {{ form.password('password', null, { class: 'form-control' }) }}
                                </div>
                            </div>

                            <div class="form-group">
                                {{ form.label('password_confirmation', 'Confirm Password', { class: 'col-md-4 control-label' }) }}

                                <div class="col-md-6">
                                    {{ form.password('password_confirmation', null, { class: 'form-control' }) }}
                                </div>
                            </div>

                            <div class="form-group">
                                <div class="col-md-6 col-md-offset-4">
                                    {{ form.button('Register', null, { class: 'btn btn-primary' }) }}
                                </div>
                            </div>
                        {{ form.close() }}
                    </div>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

As you can see, the register.njk is extending the master we created above. Noticed we included a errors partial (which we'll create shortly), this partial will display error messages encountered during form validation we discussed earlier on. We are using AdonisJs form builder which is pretty straightforward. Because AdonisJs protects us from CSRF, we need to add CSRF token to our form. Once the form is submitted, a POST request will be made to the register route.

如您所见, register.njk扩展了我们上面创建的master 。 注意,我们包含了一个errors部分(稍后将创建),该部分将显示我们在前面讨论的表单验证期间遇到的错误消息。 我们正在使用非常简单的AdonisJs表单生成器。 由于AdonisJs保护我们免受CSRF的侵害,因此我们需要在表单中添加CSRF令牌。 提交表单后,将向register路由发出POST请求。

Now create the errors partial we talked about. Create a new directory called includes within the resources/views directory. Within the includes directory, create a new file named errors.njk and paste the code below into it:

现在创建我们所讨论的部分errors 。 在resources/views目录中创建一个名为includes的新目录。 在includes目录中,创建一个名为errors.njk的新文件,并将下面的代码粘贴到其中:

<!-- resources/views/includes/errors.njk -->

{% if old('errors') %}
    <div class="alert alert-danger">
        <ul>
            {% for error in old('errors') %}
                <li>{{ error.message }}</li>
            {% endfor %}
        </ul>
    </div>
{% endif %}

If there are errors in the flash messages, we loop through each of them and display them upon failed form validation.

如果Flash消息中有错误,我们将遍历每个错误并在表单验证失败时显示它们。

When we visit http://localhost:3333/register in our browser, we should get a screen like below:

当我们在浏览器中访问http:// localhost:3333 / register时,我们将得到如下屏幕:

Go on and register. Now that we have registered, we need a way to login into the application. Okay, let's tackle that. Open app/Http/Controllers/AuthController.js and add the following code to it:

继续注册。 现在我们已经注册,我们需要一种登录到应用程序的方法。 好吧,让我们解决这个问题。 打开app/Http/Controllers/AuthController.js并向其中添加以下代码:

// app/Http/Controllers/AuthController.js

/**
 * Show login page
 */
* showLoginPage(request, response) {
    yield response.sendView('auth.login')
}

/**
 * Handle user authentication
 */
* login(request, response) {
    const email = request.input('email')
    const password = request.input('password')

    try {
        yield request.auth.attempt(email, password)

        // redirect to homepage
        response.redirect('/')
    } catch (e) {
        yield request.with({ error: 'Invalid credentials' }).flash()

        // redirect back with error
        response.redirect('back')
    }
}

The showLoginPage() will render the login page, while login() will handle the actual user authentication. We attempt to log the user in with the credentials (email and password) submitted and redirect them to the homepage. If either of the credential they submitted does not correspond with what we have in our database, we simple return them to the login page with the error message "Invalid credentials".

showLoginPage()将呈现登录页面,而login()将处理实际的用户身份验证。 我们尝试使用提交的凭据(电子邮件和密码)登录用户,并将其重定向到首页。 如果他们提交的任何一个凭证与我们数据库中的凭证都不匹配,我们将简单地将其返回到登录页面,并显示错误消息“ Invalid certificate”。

用户登录路线 (User Login Routes)

Next, we need to define the login route. Open app/Http/routes.js and paste the code below into it:

接下来,我们需要定义登录路径。 打开app/Http/routes.js并将以下代码粘贴到其中:

// app/Http/routes.js

Route.get('login', 'AuthController.showLoginPage')
Route.post('login', 'AuthController.login')

We defined two login routes that will handle GET and POST request respectively. The first route will trigger the showLoginPage() on AuthController while the second will trigger the login() on AuthController.

我们定义了两个login路由,分别处理GETPOST请求。 第一条路线将触发showLoginPage()AuthController而第二将触发login()AuthController

用户登录视图 (User Login View)

Create a new file named login.njk within the resources/views/auth directory and paste the code below to it:

resources/views/auth目录中创建一个名为login.njk的新文件,并将以下代码粘贴到其中:

<!-- resources/views/auth/login.njk -->

{% extends 'layouts.master' %}

{% block title %}
    Login
{% endblock %}

{% block content %}
  <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Login</div>
                    <div class="panel-body">

                        {% if old('error') %}
                            <div class="alert alert-danger">
                                {{ old('error') }}
                            </div>
                        {% endif %}

                        {{ form.open({ url: '/login', method: 'POST', class: 'form-horizontal' }) }}
                            {{ csrfField }}

                            <div class="form-group">
                                {{ form.label('email', 'Email', { class: 'col-md-4 control-label' }) }}

                                <div class="col-md-6">
                                    {{ form.email('email', null, { class: 'form-control' }) }}
                                </div>
                            </div>

                            <div class="form-group">
                                {{ form.label('password', 'Password', { class: 'col-md-4 control-label' }) }}

                                <div class="col-md-6">
                                    {{ form.password('password', null, { class: 'form-control' }) }}
                                </div>
                            </div>

                            <div class="form-group">
                                <div class="col-md-6 col-md-offset-4">
                                    {{ form.button('Login', null, { class: 'btn btn-primary' }) }}
                                </div>
                            </div>
                        {{ form.close() }}
                    </div>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

Again, a simple login form with email and password fields. There is also a section that will display login failure error. Just as with the register form, we add CSRF token to the form. Once the form is submitted, a POST request will be made to the login route.

同样,使用电子邮件和密码字段的简单登录表单。 还有一个部分将显示登录失败错误。 与注册表单一样,我们将CSRF令牌添加到表单中。 提交表单后,将向login路由发出POST请求。

When we visit http://localhost:3333/login in our browser, we should get a screen like below:

当我们在浏览器中访问http:// localhost:3333 / login时,我们将获得如下屏幕:

We should be able to login now.

我们应该可以立即登录。

用户注销 (User Logout)

Let's allow logged in user to be able to logout of the application. Open app/Http/Controllers/AuthController.js and add the following code to it:

让我们允许已登录的用户能够注销该应用程序。 打开app/Http/Controllers/AuthController.js并向其中添加以下代码:

// app/Http/Controllers/AuthController.js

/**
 * Logout authenticated user
 */
* logout(request, response) {
    // logouts the user
    yield request.auth.logout()

    // redirect to login page
    response.redirect('/login')
}

The logout() simply logs the authenticated user out and redirect to the login page.

logout()只是注销已认证的用户,然后重定向到登录页面。

用户登出路线 (User Logout Route)

// app/Http/routes.js

Route.get('logout', 'AuthController.logout')

票证和类别模型及其迁移 ( Ticket And Category Model With Their Migrations )

Having taken care of users authentication, we move on to allow users create new tickets. First we need a tickets table to hold the tickets that users will be creating and also a Ticket model for them.

完成用户身份验证后,我们继续允许用户创建新票证。 首先,我们需要一个tickets表来保存用户将要创建的票证,以及用于他们的Ticket模型。

./ace make:model Ticket --migration

The above command will create a Ticket model and tickets table migration. Open the tickets table migration file and update it with:

上面的命令将创建一个Ticket模型和tickets表迁移。 打开tickets表迁移文件,并使用以下命令进行更新:

// database/migrations/...create_ticket_table.js

// tickets table migration showing only the up() schemas with our modifications
this.create('tickets', (table) => {
    table.increments()
    table.integer('user_id').unsigned()
    table.integer('category_id').unsigned()
    table.string('ticket_id').unique()
    table.string('title')
    table.string('priority')
    table.text('message')
    table.string('status')
    table.timestamps()
})

The migration is straightforward, we have a user_id column that will hold the ID of the user that created the ticket, a category_id column to indicate the category the ticket belongs, ticket_id column which hold a unique random string that will be used to refer to the ticket, a title column, a priority column, a message column and a status column which indicate whether a ticket is open or closed.

迁移非常简单,我们有一个user_id列,该列将保存创建票证的用户的ID ;一个category_id列,以指示票证所属的类别; ticket_id列,其包含一个唯一的随机字符串,该字符串将用于引用该票证。票证, title列, priority列, message列和status列,它们指示票证是打开还是关闭。

We also need a categories table to hold our various categories:

我们还需要一个categories表来保存我们的各种类别:

./ace make:model Category --migration

As above, this will create a Category model and categories table migration. Open the categories table migration file and update it with:

如上所述,这将创建Category模型和categories表迁移。 打开categories表迁移文件,并使用以下命令进行更新:

// database/migrations/...create_category_table.js

// categories table migration showing only the up() schemas with our modifications
this.create('categories', (table) => {
    table.increments()
    table.string('name')
    table.timestamps()
})

We can now run the migrations:

现在,我们可以运行迁移:

./ace migration:run

票证与类别的关系 ( Ticket To Category Relationship )

A ticket can belong to a category, while a category can have many tickets. This is a one to many relationship and we'll use Lucid to setup the relationship.

一个票证可以属于一个category ,而一个类别可以有许多tickets 。 这是一对多的关系,我们将使用Lucid来建立关系。

Open the Ticket model and add the following code to it:

打开Ticket模型并向其中添加以下代码:

// app/Model/Ticket.js

/**
 * A ticket belongs to a category
 */
category() {
    return this.belongsTo('App/Model/Category')
}

Next, we need to create the inverse relationship, open the Category model and add the following code to it:

接下来,我们需要创建逆关系,打开Category模型并向其中添加以下代码:

// app/Model/Category.js

/**
 * A category can have many tickets
 */
tickets () {
    return this.hasMany('App/Model/Ticket')
}

添加类别 ( Adding Categories )

For now, let's manually populate the categories table with some data. AdonisJs provides Seeds and Factories which we can use to generate fake data. For the purpose of this tutorial, we won't be using Factories, we'll only use Seeds and Query Builder. Open app/database/seeds/Database.js and update the run():

现在,让我们用一些数据手动填充categories表。 AdonisJs提供了“种子”和“工厂”,可用于生成伪造数据。 出于本教程的目的,我们将不使用工厂,仅使用种子和查询生成器。 打开app/database/seeds/Database.js并更新run()

// app/database/seeds/Database.js

// remember to add these to the top of file
const Database = use('Database')

* run () {
    yield Database.table('categories').insert([
        {
            name: 'Technical',
            created_at: '2017-03-07 00:00:00',
            updated_at: '2017-03-07 00:00:00'
        },
        {
            name: 'Sales',
            created_at: '2017-03-07 00:00:00',
            updated_at: '2017-03-07 00:00:00'
        }
])

Using AdnonisJs query builder, we perform database insertion which insert two categories in the categories table of our database. To run the database seeder, we make use of an ace command:

使用AdnonisJs查询构建器,我们执行数据库插入,该插入将两个类别插入数据库的categories表中。 要运行数据库播种器,我们使用ace命令:

./ace db:seed

Now we should have two categories in our categories table.

现在,在categories表中应该有两个类别。

票务总监 ( Tickets Controller )

We need a controller that will contain the logic for opening a ticket. Run the command below to create an HTTP request controller:

我们需要一个控制器,其中包含打开票证的逻辑。 运行以下命令以创建HTTP请求控制器:

./ace make:controller Tickets

This will create a TicketsController. Open app/Http/Controllers/TicketsController.js and add the code below to it:

这将创建一个TicketsController 。 打开app/Http/Controllers/TicketsController.js并添加以下代码:

// app/Http/Controllers/TicketsController.js

// remember to add these to the top of file
const Category = use('App/Model/Category')

/**
 * Show the form for opening a new ticket.
 */
* create(request, response) {
    const categories = yield Category.pair('id', 'name')

    yield response.sendView('tickets.create', {categories: categories})
}

The create() will get all the categories created and pass them along to a view file. Since we'll be displaying our categories in a select dropdown on the ticket opening form, we use the pair() which will return a flat object with a key/value pair of LHS and RHS key. Before we move further, let's create the routes that will handle opening a new ticket.

create()将获取所有创建的类别,并将它们传递到视图文件。 由于我们将在开票表单上的选择下拉列表中显示类别,因此我们使用pair()将返回带有LHSRHS键的键/值对的平面对象。 在继续之前,让我们创建将处理打开新票证的路线。

// app/Http/routes.js

Route.get('new_ticket', 'TicketsController.create')
Route.post('new_ticket', 'TicketsController.store')

The first route new_ticket will show the form to open a new ticket while the second route will call the store() on TicketsController which will do the actual storing of the ticket in the database.

第一条路线new_ticket将显示打开新票证的表单,而第二条路线将在TicketsController上调用store() ,这将在数据库中实际存储票证。

The create() from above will render a view file tickets.create which we are yet to create. Let's go on and create the file.

上面的create()将呈现一个视图文件tickets.create ,我们尚未创建。 让我们继续创建文件。

Create a new folder named tickets in the resources/views directory and inside the tickets directory, create a new file named create.njk and add the following code to:

创建一个新的文件夹命名ticketsresources/views目录和里面tickets目录下创建一个新的文件名为create.njk和下面的代码添加到:

<!-- resources/views/tickets/create.njk -->

{% extends 'layouts.master' %}

{% block title %} Open Ticket {% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div class="panel panel-default">
                <div class="panel-heading">Open New Ticket</div>

                <div class="panel-body">
                    {% include 'includes.status' %}
                    {% include 'includes.errors' %}

                    {{ form.open({ url: '/new_ticket', method: 'POST', class: 'form-horizontal' }) }}
                        {{ csrfField }}

                        <div class="form-group">
                            {{ form.label('title', 'Title', { class: 'col-md-4 control-label' }) }}

                            <div class="col-md-6">
                                {{ form.text('title', old('title'), { class: 'form-control' }) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form.label('category', 'Category', { class: 'col-md-4 control-label' }) }}

                            <div class="col-md-6">
                                {{ form.select('category', categories, null, 'Select Category', { class: 'form-control' }) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form.label('priority', 'Priority', { class: 'col-md-4 control-label' }) }}

                            <div class="col-md-6">
                                {{ form.select('priority', { low: 'Low', medium: 'Medium', high: 'High' }, null, 'Select Priority', { class: 'form-control' }) }}
                            </div>
                        </div>

                        <div class="form-group">
                            {{ form.label('message', 'Message', { class: 'col-md-4 control-label' }) }}

                            <div class="col-md-6">
                                {{ form.textarea('message', null, { class: 'form-control', rows: 10 }) }}
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                {{ form.button('Open Ticket', null, { class: 'btn btn-primary' }) }}
                            </div>
                        </div>
                    {{ form.close() }}
                </div>
            </div>
        </div>
    </div>
{% endblock %}

Again we are using the AdonisJs form builder. Notice we included a status and errors partials. The status partial will display a flash message upon successful opening of ticket. The errors partial is the same we created earlier.

再次,我们使用AdonisJs表单生成器。 注意,我们包括了statuserrors部分。 成功打开票证后, status部分将显示一条闪烁消息。 部分errors与我们之前创建的相同。

状态Flash消息 ( Status Flash Message )

Create a file named status.njk in the resource/views/includes folder inside the views folder. Paste the code snippets below into it:

在views文件夹内的resource/views/includes文件夹中创建一个名为status.njk的文件。 将以下代码片段粘贴到其中:

<!-- resources/views/includes/status.njk -->

{% if old('status') %}
    <div class="alert alert-success">
        {{ old('status') }}
    </div>
{% endif %}

When we visit http://localhost:3333/new_ticket in our browser, we should get a screen like below:

当我们在浏览器中访问http:// localhost:3333 / new_ticket时,我们应该得到如下屏幕:

To handle the actual saving of the ticket to the database, open TicketsController and add the store() to it.

要处理票证的实际保存,请打开TicketsController并将store()添加到其中。

// app/Http/Controllers/TicketsController.js

// remember to add these to the top of file
const Mail = use('Mail')
const Validator = use('Validator')
const Ticket = use('App/Model/Ticket')
const RandomString = use('randomstring')
const Category = use('App/Model/Category')

/**
 * Store a newly created ticket in database.
 */
* store(request, response) {
    const user = request.currentUser

    // validate form input
    const validation = yield Validator.validateAll(request.all(), {
        title: 'required',
        category: 'required',
        priority: 'required',
        message: 'required'
    })

    // show error messages upon validation fail
    if (validation.fails()) {
        yield request
            .withAll()
            .andWith({ errors: validation.messages() })
            .flash()

        return response.redirect('back')
    }

    // persist ticket to database
    const ticket = yield Ticket.create({
        title: request.input('title'),
        user_id: user.id,
        ticket_id: RandomString.generate({ length: 10, capitalization: 'uppercase' }),
        category_id: request.input('category'),
        priority: request.input('priority'),
        message: request.input('message'),
        status: "Open",
    })

    // send mail notification
    yield Mail.send('emails.ticket_info', { user, ticket }, (message) => {
        message.to(user.email, user.username)
        message.from('support@adonissupport.dev')
        message.subject(`[Ticket ID: ${ticket.ticket_id}] ${ticket.title}`)
    })        

    yield request.with({ status: `A ticket with ID: #${ticket.ticket_id} has been opened.` }).flash()
    response.redirect('back')
}

We'll use an NPM package called randomstring to generate random string for our ticket IDs. So let's install the package:

我们将使用一个名为randomstringNPM包为票证ID生成随机字符串。 因此,让我们安装软件包:

npm install randomstring --save

Note: the randomstring package is require at the top of TicketsController.

注意: randomstring包在randomstring的顶部是TicketsController

Within the store() we get the currently authenticated user. Next, we set some form validation rules that must be met before moving forward. If the validation fail, we redirect the user back to the ticket opening page with appropriate error messages. Otherwise, a new the ticket is created and an email containing the ticket details is sent to the user (more on this below) and finally the user is redirected back with a success message.

store()我们获取当前已认证的用户。 接下来,我们设置一些表单验证规则,然后继续前进。 如果验证失败,我们将通过适当的错误消息将用户重定向到票务开放页面。 否则,将创建一个新的票证,并将包含票证详细信息的电子邮件发送给用户(有关更多信息,请参见下文),最后将用户重定向到成功消息。

发送票务信息邮件 ( Sending Ticket Information Mail )

AdonisJs official Mail Provider makes it so easy and intuitive to send emails using one of the available drivers. Mail provider is not part of the base installation, and we have to pull the package from NPM and register the provider:

AdonisJs的官方邮件提供者使使用一种可用的驱动程序发送电子邮件变得如此容易和直观。 邮件提供程序不是基本安装的一部分,我们必须从NPM提取软件包并注册提供程序:

npm install adonis-mail-provider --save

Next, we need to register the provider in bootstrap/app.js:

接下来,我们需要在bootstrap/app.js注册提供者:

// bootstrap/app.js

const providers = [
    ...,
    'adonis-mail-provider/providers/MailProvider'
]

and setup an alias:

并设置一个别名:

// bootstrap/app.js

const aliases = {
    Mail: 'Adonis/Addons/Mail'
}

Next, we need a configuration file that tell AdonisJs which Mail driver we are using along with the driver's details. Create a mail.js file within the config directory and paste the following code into it:

接下来,我们需要一个配置文件,该文件告诉AdonisJs我们正在使用哪个Mail驱动程序以及该驱动程序的详细信息。 在config目录中创建一个mail.js文件,并将以下代码粘贴到其中:

// config/mail.js
'use strict'

const Helpers = use('Helpers')
const Env = use('Env')

module.exports = {
  /*
  |--------------------------------------------------------------------------
  | Driver
  |--------------------------------------------------------------------------
  |
  | driver defines the default driver to be used for sending emails. Adonis
  | has support for 'mandrill', 'mailgun', 'smtp', 'ses' and 'log' driver.
  |
  */
  driver: Env.get('MAIL_DRIVER', 'smtp'),

  /*
  |--------------------------------------------------------------------------
  | SMTP
  |--------------------------------------------------------------------------
  |
  | Here we define configuration for sending emails via SMTP.
  |
  */
  smtp: {
    pool: true,
    port: 2525,
    host: '',
    secure: false,
    auth: {
      user: Env.get('MAIL_USERNAME'),
      pass: Env.get('MAIL_PASSWORD')
    },
    maxConnections: 5,
    maxMessages: 100,
    rateLimit: 10
  },

  /*
  |--------------------------------------------------------------------------
  | Mandrill
  |--------------------------------------------------------------------------
  |
  | Here we define api options for mandrill. Mail provider makes use of
  | mandrill raw api, which means you cannot set email body specific
  | options like template, tracking_domain etc.
  |
  */
  mandrill: {
    apiKey: Env.get('MANDRILL_APIKEY'),
    async: false,
    ip_pool: 'Main Pool'
  },

  /*
  |--------------------------------------------------------------------------
  | Amazon SES
  |--------------------------------------------------------------------------
  |
  | Here we define api credentials for Amazon SES account. Make sure you have
  | verified your domain and email address, before you can send emails.
  |
  */
  ses: {
    accessKeyId: Env.get('SES_KEY'),
    secretAccessKey: Env.get('SES_SECRET'),
    region: 'us-east-1',
    rateLimit: 10
  },

  /*
  |--------------------------------------------------------------------------
  | MailGun
  |--------------------------------------------------------------------------
  |
  | Here we define api credentials for Amazon SES account. Make sure you have
  | verified your domain and email address, before you can send emails.
  |
  */
  mailgun: {
    domain: Env.get('MAILGUN_DOMAIN'),
    apiKey: Env.get('MAILGUN_APIKEY')
  },

  /*
  |--------------------------------------------------------------------------
  | Log
  |--------------------------------------------------------------------------
  |
  | Log driver is mainly for testing your emails expectations. Emails are
  | written inside a log file, which can be used for inspection.
  |
  */
  log: {
    toPath: Helpers.storagePath('logs/mail.eml')
  }
}

Let's take a closer look at the mail sending snippet from the save() above.

让我们仔细看看上面的save()中的邮件发送代码段。

// app/Http/Controllers/TicketsController.js

// send mail notification
yield Mail.send('emails.ticket_info', { user, ticket }, (message) => {
    message.to(user.email, user.username)
    message.from('support@adonissupport.dev')
    message.subject(`[Ticket ID: ${ticket.ticket_id}] ${ticket.title}`)
})

The Mail's send() accepts a path to a view file email/ticket_info.njk, an object of data (user and ticket) we want to pass to the view file and lastly a callback. The view file will be inside the emails directory (which does not exist yet) and will be used to compose the mail we want to send. The message argument passed to the callback is an instance of message builder which is used to build the mail by specifying who we want to send the mail to, who is sending the mail and the subject of the mail.

邮件的send()接受视图文件email/ticket_info.njk的路径,该文件是我们要传递给视图文件的数据对象( userticket ),最后是回调。 该查看文件将位于emails目录(尚不存在)内,并将用于撰写我们要发送的邮件。 传递给回调的message参数是message builder的一个实例,该实例用于通过指定我们要将邮件发送给谁,向谁发送邮件以及邮件的主题来构建邮件。

Now let's create the email view file. Create a new ticket_info.njk file within the resources/views/emails directory and add:

现在,让我们创建电子邮件视图文件。 在resources/views/emails目录中创建一个新的ticket_info.njk文件,并添加:

<!-- resources/views/emails/ticket_info.njk -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Suppor Ticket Information</title>
</head>
<body>
    <p>
        Thank you {{ user.username }} for contacting our support team. A support ticket has been opened for you. You will be notified when a response is made by email. The details of your ticket are shown below:
    </p>

    <p>Title: {{ ticket.title }}</p>
    <p>Priority: {{ ticket.priority }}</p>
    <p>Status: {{ ticket.status }}</p>

    <p>
        You can view the ticket at any time at http://localhost:3333/tickets/{{ ticket.ticket_id }}
    </p>

</body>
</html>

This is the email that will be sent to the user once a ticket is created.

这是在创建票证后将发送给用户的电子邮件。

Before testing this out in the browser, remember to configure your Mail settings. For the purpose of this tutorial, we won't be sending actual emails. Instead we will just log them. So open .env file and update it as below:

在浏览器中进行测试之前,请记住配置您的邮件设置。 就本教程而言,我们将不会发送实际的电子邮件。 相反,我们将只记录它们。 因此,打开.env文件并进行如下更新:

MAIL_DRIVER=log

Now hit the browser and try opening a new ticket, you should see the mail content logged in the storage/logs/mail.eml file once the ticket is created.

现在,单击浏览器并尝试打开新票证,创建票证后,您应该会在storage/logs/mail.eml文件中看到记录的邮件内容。

结论 ( Conclusion )

So far, we have been able to setup our application and created our first ticket. You can see how simple and straightforward it is to develop an app with AdonisJs. We have been able to utilize AdonisJs Query Builder to define our table structures without writing a single SQL command, layout our views with its built in View Engine without doing extra setup as with other Node.JS frameworks (Express, Hapi.js etc). We also saw Lucid ORM in action which makes it seamless to interact with our database irrelevant of the databse type. Lastly, we saw Ace, a powerful interactive shell which we used to scaffold and perform some operations from within a terminal.

到目前为止,我们已经能够设置我们的应用程序并创建了第一张票证。 您会看到使用AdonisJs开发应用程序是多么简单和直接。 我们已经能够利用AdonisJs Query Builder定义表结构,而无需编写单个SQL命令,使用其内置的View Engine布局视图,而无需像其他Node.JS框架(Express,Hapi.js等)那样进行额外的设置。 我们还看到了Lucid ORM的实际应用,它使与databse类型无关的数据库无缝交互成为可能。 最后,我们看到了Ace,它是一个功能强大的交互式外壳,用于从终端内部进行支架和执行一些操作。

In the next post we'll cover commenting on ticket and marking ticket as closed. If you have any questions about the tutorial, let me know in the comments below.

在下一篇文章中,我们将介绍对票证进行评论并将票证标记为已关闭。 如果您对本教程有任何疑问,请在下面的评论中告诉我。

翻译自: https://scotch.io/tutorials/build-a-support-ticket-application-with-adonisjs

票务小程序源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值