是否有可能在短短几个小时内构建一个时间跟踪应用程序?是的,在本文中,我将向您展示如何操作!
我是一名高级后端 Java 开发人员,在构建 Web 应用程序方面拥有 8 年的经验。我将向您展示节省大量时间来构建我的下一个是多么令人满意和革命性。我使用的方法如下:我想创建一个与 ClickUp API 集成的时间跟踪应用程序(我称之为 Timelog)。它提供了一个简单的功能,在这里非常有用:远程创建时间条目。
为了节省时间,我将使用 Openkoda 平台提供的一些开箱即用的功能。这些功能在设计时充分考虑了开发人员的需求。使用它们,我可以跳过构建每个 Web 应用程序中使用的标准功能(一遍又一遍)。相反,我可以专注于核心业务逻辑。
我将使用以下预构建功能来满足我的应用程序需求:
登录名/密码身份验证
用户和组织管理
不同的用户角色和权限
电子邮件发件人
日志概述
服务器端代码编辑器
Web 终结点创建者
CRUDs 发电机
让我们开始吧!
Timelog 应用程序概述
我们的示例内部应用程序创建了一个小型复杂系统,然后可以很容易地按模型扩展,并使用额外的业务逻辑或自定义视图进行扩展。
该应用程序的主要重点是:
存储与 ClickUp API 通信所需的数据。
将用户分配到其票证。
将新的时间条目发布到外部 API。
为了加快构建应用程序的过程,我们依赖于上面提到的一些开箱即用的功能。在此阶段,我们使用了以下方法:
数据模型生成器 (Form) - 允许我们定义数据结构,而无需重新编译应用程序,并能够动态调整数据架构
现成的管理功能 - 有了这个功能,我们可以忘记开发身份验证、安全性和标准仪表板视图等内容。
服务器端代码编辑器 - 用于开发负责 ClickUp API 集成的专用服务,它在 Openkoda UI 中以 JavaScript 编码。
WebEndpoint 构建器 - 允许我们创建一个自定义表单处理程序,该处理程序使用服务器端代码服务将时间跟踪条目数据发布到 ClickUp 服务器,而不是将其存储在我们的内部数据库中
步骤 1:设置体系结构
为了实现上述功能并存储所需的数据,我们设计了一个简单的数据模型,由以下五个实体组成。
ClickUpConfig、 、 和 旨在存储发送到 ClickUp API 的连接和消息所需的密钥和 ID。最后一个是 ,旨在利用现成的 HTML 表单(Thymeleaf 片段),从而节省大量开发时间。ClickUpUserTicketAssignmentTimeEntry
下面显示了用于 Timelog ClickUp 集成的预准备数据模型的详细结构。
ClickUpConfig
apiKey- ClickUp API 密钥
teamId- ClickUp 中用于创建时间条目的空间 ID
ClickUpUser
userId- 用户的内部 ID
clickUpUserId- 在 ClickUp 中分配给工作区的用户的 ID
Ticket
name- 工单的内部名称
clickUpTicketid- ClickUp 中用于创建时间条目的工单的 ID
Assignment
userId- 用户的内部 ID
ticketId- 工单的内部 ID
TimeEntry
userId- 用户的内部 ID
ticketId- 工单的内部 ID
date- 时间条目的日期
durationHours- 以小时为单位提供的时间输入持续时间
durationMinutes- 以分钟为单位提供时间输入持续时间
description- 创建时间条目的简短说明
我们希望在仪表板上最终显示五个数据磁贴:
第 2 步:与 ClickUp API 集成
我们将应用程序与 ClickUp API 集成,专门使用其端点在 ClickUp 中创建时间条目。
要将 Timelog 应用程序与我们的 ClickUp 工作区连接,需要提供 API 密钥。这可以使用个人 API 令牌或通过在 ClickUp 仪表板中创建应用程序生成的令牌来完成。有关如何检索其中之一的信息,请参阅官方 ClickUp 文档。
为了使我们的应用程序能够在 ClickUp 工作区中创建时间条目,我们需要提供一些 ClickUp ID:
teamId:这是访问工作区后 URL 中的第一个 ID 值。
userId:
要检查用户的 ClickUp ID(成员 ID),请转到 Workspace -> 管理用户。
在“用户”列表中,选择用户的“设置”,然后选择“复制成员 ID”。
taskId:
任务 ID 可在仪表板上的三个位置访问:URL、任务模式和任务列表视图。
有关详细说明,请参阅 ClickUp 帮助中心。
您可以识别以符号为前缀的任务 ID - 我们使用不带前缀的 ID。#
第 3 步:使用 Openkoda 进行数据模型魔术
Openkoda 使用 Byte Buddy 库在 Spring Boot 应用程序的运行时为动态注册的实体动态构建实体和存储库类。
以下是 Openkoda 中实体类生成的简短片段(整个服务类可在他们的 GitHub 上找到)。
1 dynamicType = new ByteBuddy() 2 .with(SKIP_DEFAULTS) 3 .subclass(OpenkodaEntity.class) 4 .name(PACKAGE + name) 5 .annotateType(entity) 6 .annotateType(tableAnnotation) 7 .defineConstructor(PUBLIC) 8 .intercept(MethodCall 9 .invoke(OpenkodaEntity.class.getDeclaredConstructor(Long.class)) 10 .with((Object) null));
Openkoda 提供了定义实体结构的自定义表单生成器语法。然后,此结构用于生成实体类和存储库类,以及 CRUD 视图的 HTML 表示形式,例如包含所有记录的分页表、设置表单和简单的只读视图。
前面描述的数据模型中的所有五个实体都以相同的方式注册,只是使用表单生成器语法。
下面显示了该实体的表单生成器代码段。Ticket
JavaScript的
1 a => a 2 .text("name") 3 .text("clickUpTaskId")
上面的定义导致实体使用一组默认字段 for 和两个名为 和 的自定义字段命名。TicketOpenkodaEntity“name”“clickUpTaskId”
动态生成实体的数据库表结构如下:Ticket
降价
1 Table "public.dynamic_ticket" 2 Column | Type | Collation | Nullable | Default 3 ------------------+--------------------------+-----------+----------+----------------------- 4 id | bigint | | not null | 5 created_by | character varying(255) | | | 6 created_by_id | bigint | | | 7 created_on | timestamp with time zone | | | CURRENT_TIMESTAMP 8 index_string | character varying(16300) | | | ''::character varying 9 modified_by | character varying(255) | | | 10 modified_by_id | bigint | | | 11 organization_id | bigint | | | 12 updated_on | timestamp with time zone | | | CURRENT_TIMESTAMP 13 click_up_task_id | character varying(255) | | | 14 name | character varying(255) | | | 15
成功注册实体的最后一步是刷新 Spring 上下文,以便它识别新的存储库 bean,并让 Hibernate 确认实体。这可以通过从管理面板(“监视”部分)重新启动应用程序来完成。
我们的最终结果是为实体自动生成的完整 CRUD。Ticket
自动生成的工单设置视图:
自动生成的所有工单列表视图:
步骤 4:设置服务器端代码即服务
我们使用 Openkoda 服务器端代码实现了 ClickUp API 集成,将 API 调用逻辑作为一项服务分开。可以在自定义表单视图请求处理程序的逻辑中进一步使用导出的 JS 函数。
然后,我们创建了一个 JavaScript 服务,该服务提供负责 ClickUp API 通信的函数。Openkoda 使用 GraalVM 在后端服务器上完全运行任何 JS 代码。
我们的 ClickupAPI 服务器端代码服务只有一个函数 (),需要它来满足我们的时间日志应用程序要求。postCreateTimeEntry
JavaScript的
1 export function postCreateTimeEntry(apiKey, teamId, duration, description, date, assignee, taskId) { 2 let url = `https://api.clickup.com/api/v2/team/${teamId}/time_entries`; 3 let timeEntryReq = { 4 duration: duration, 5 description: '[Openkoda Timelog] ' + description, 6 billable: true, 7 start: date, 8 assignee: assignee, 9 tid: taskId, 10 }; 11 let headers = {Authorization: apiKey}; 12 return context.services.integrations.restPost(url, timeEntryReq, headers); 13 } 14
要稍后在 WebEndpoints 中使用此类服务,很容易遵循标准 JS 导入表达式。import * as clickupAPI from 'clickupAPI';
第 5 步:使用自定义 GET/POST 处理程序构建时间输入表单
在这里,我们为演示应用程序准备了基本屏幕:将数据发布到 ClickUp API 的时间输入表单。所有这些都是在 Openkoda 用户界面中通过提供简单的 HTML 内容和一些 JS 代码片段来完成的。
景观
HTML 片段就像下面发布的片段一样简单。我们使用了一个现成的 Thymeleaf 片段(参见标签),代码的其余部分是 Thymeleaf 模板的标准结构。form
[HTML全
1 <!--DEFAULT CONTENT--> 2 <!DOCTYPE html> 3 <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" lang="en" layout:decorate="~{${defaultLayout}}"> 4 <body> 5 <div class="container"> 6 <h1 layout:fragment="title"/> 7 <div layout:fragment="content"> 8 <form th:replace="~{generic-forms::generic-form(${TimeEntry}, 'TimeEntry', '', '', '', 'Time Entry', #{template.save}, true)}"></form> 9 </div> 10 </div> 11 </body> 12 </html>
HTTP 处理程序
一旦有了视图的简单HTML代码,我们就需要提供通用表单片段所需的实际表单对象。(${TimeEntry})
作为第一步,我们在 GET 端点中执行此操作,然后设置当前记录的用户 ID,以便在进入时间条目视图时选择默认值。
JavaScript的
1 flow 2 .thenSet("TimeEntry", a => a.services.data.getForm("TimeEntry")) 3 .then(a => a.model.get("TimeEntry").dto.set("userId", a.model.
get("userEntityId")))
最后,注册 POST 端点以处理从表单视图发送的实际 POST 请求(上面介绍的 HTML 代码)。它实现了用户输入时间输入表单,提供数据,然后将数据发送到 ClickUp 服务器的方案。
以下 POST 端点 JS 代码:
接收表单数据。
从内部数据库读取其他配置(如 API 密钥、团队 ID 或 ClickUp 用户 ID)。
准备要发送的数据。
触发服务与远程 API 通信。clickupAPI
JavaScript的
1 import * as clickupAPI from 'clickupAPI'; 2 3 flow 4 .thenSet("clickUpConfig", a => a.services.data.getRepository("clickupConfig").search( (root, query, cb) => { 5 let orgId = a.model.get("organizationEntityId") != null ? a.model.get("organizationEntityId") : -1; 6 return cb.or(cb.isNull(root.get("organizationId")), cb.equal(root.get("organizationId"), orgId)); 7 }).get(0) 8 ) 9 .thenSet("clickUpUser", a => a.services.data.getRepository("clickupUser").search( (root, query, cb) => { 10 let userId = a.model.get("userEntityId") != null ? a.model.get("userEntityId") : -1; 11 return cb.equal(root.get("userId"), userId); 12 }) 13 ) 14 .thenSet("ticket", a => a.form.dto.get("ticketId") != null ? a.services.data.getRepository("ticket").findOne(a.form.dto.get("ticketId")) : null) 15 .then(a => { 16 let durationMs = (a.form.dto.get("durationHours") != null ? a.form.dto.get("durationHours") * 3600000 : 0) 17 + (a.form.dto.get("durationMinutes") != null ? a.form.dto.get("durationMinutes") * 60000 : 0); 18 return clickupAPI.postCreateTimeEntry( 19 a.model.get("clickUpConfig").apiKey, 20 a.model.get("clickUpConfig").teamId, 21 durationMs, 22 a.form.dto.get("description"), 23 a.form.dto.get("date") != null ? (new Date(a.services.util.toString(a.form.dto.get("date")))).getTime() : Date.now().getTime(), 24 a.model.get("clickUpUser").length ? a.model.get("clickUpUser").get(0).clickUpUserId : -1, 25 a.model.get("ticket") != null ? a.model.get("ticket").clickUpTaskId : '') 26 }) 27
第 6 步:我们的应用程序已准备就绪!
就是这样!
我构建了一个复杂的应用程序,它能够存储用户的数据、他们的票证分配以及 ClickUp API 连接所需的任何属性。它提供了一个时间条目表单,涵盖单个时间条目的票证选择、日期、持续时间和描述输入,并将数据从表单直接发送到集成 API。
不要忘记 Openkoda 中提供的所有预构建功能,例如身份验证、用户帐户管理、日志概述等。因此,创建 Timelog 应用程序的总时间只有几个小时。
我所构建的只是一个具有一个主要功能的简单应用程序。但是有很多方法可以扩展它,例如,通过向数据模型添加新结构,通过开发更多的 ClickUp API 集成,或者通过创建更复杂的屏幕,如下面的日历视图。
如果您遵循与我在此案例中介绍的几乎完全相同的场景,您将能够构建任何其他简单(或不简单)的业务应用程序,从而节省重复和无聊功能的时间,并专注于核心业务需求。
我能想到几个可以用同样方式构建的应用程序,例如法律文档管理系统、房地产应用程序、旅行社系统,仅举几例。
作为一名经验丰富的软件工程师,我总是喜欢实施新想法并快速看到结果。在这种情况下,这就是我所做的一切。我花了最少的时间创建一个根据我的需求量身定制的全功能应用程序,并跳过了单调的工作。