前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何最快的完成一个Web产品的开发?

如何最快的完成一个Web产品的开发?

作者头像
用户2936994
发布2022-07-21 14:13:18
5930
发布2022-07-21 14:13:18
举报
文章被收录于专栏:祝威廉祝威廉

2020年春节回东北的路上凌晨四点多诞生了一个想法,然后就开始吭哧坑次设计开发,基于我以前开发的 ServiceFramework 框架,一个春节假期就迭代开发出了一套名为 web-platform 框架。 该框架包含三个部分:

  1. 提供了一套以 Form/Table 为核心交互元素的通用 Web UI界面
  2. API 层面完全抛弃了Rest的那套语义
  3. 提供了一套辅助命令行工具(基于Python) sfcli, 用来创建模板项目,动态编译,启动服务等。

我们分别简要介绍下1,2 两个层面的东西。

对于1, 我们的理论基础是,Web交互的基础形态就两个: Form 和 Table。 Form 可以让人告诉机器干什么, Table 则是机器告诉人他执行的结果。 Web上其他丰富多彩的交互形态,本质上都是这两者的变形或者组合。通过组合多个 Form 形成 Form 向导,我们可以帮助用户完成一个非常复杂的交互需求。

比如,对于一个插件商店,我们有两个核心流程:查看下载和上传。

入口:

查看检索插件,并获得下载地址:

上传自定义插件:

和 ToC 用户不同的是,对于 ToB 用户,帮其完成工作是核心。所以基础的 Form/Table 打造的足够完善,其实已经完全可以满足用户诉求。

以 Form/Table 为核心交互元素的通用 Web UI 界面带来的价值在于,你无需开发繁琐的前端, 仅需要开发 API 即可。在前面的示例图中,我们无需开发一行前端代码。

对于2, 我认为 Web 开发者过于陶醉于 Rest 带来的丰富语义(比如 POST/GET/PUT/DELETE 等对资源的语义),然而这套语义除了给我们感官上的享受,并没有带来额外价值。同理,现在流行的 Json进Json出似乎带来了统一,但我也认为是程序员的自hign, 他并没有到来额外的效率价值。 整个 Rest 抽象的语义,99%的需求,应该就是一个普通的函数,这个函数的输入是一个 Map[String,String], 输出是一个 String,用户根据输入完成对应的方法逻辑即可。同样的,对于复杂痛苦的数据库操作,我们可以借助 Scala 等语言的编译时能力,对case class进行操作,系统在编译的时候自动将这些操作转化为原生的SQL,实现运行时 0 开销。

下面是我开发的一个Action, 它遵循了我上面提到的规范,整个类很简单,开发者不用学习任何Rest相关的只是,只是实现一个 _run 函数而已。而且每个Action也可以互相调用。

另外值得注意的事,我们扩展了参数的定义能力,他不仅仅可以定义成参数名字,还能做前端形态的定义,比如 pageNum 是个输入框,pluginType 是个选择框,前端会根据这些信息,绘制合理的 Form。

基本理念讲完,然我们开始使用 web-platform的开发之旅吧。

环境准备

  1. python >= 3.6
  2. JDK 8
  3. Intellij Idea (Scala)

在此基础上,安装一个命令行工具 sfcli(以及手动安装相关依赖):

代码语言:javascript
复制
pip install watchdog requests click uuid sfcli

项目初始化配置

在开发代码之前,我们初始化项目,增加依赖,修改数据库连接配置,以及启动应用进行基本的校验。

初始化项目

通过下面的命令可以创建一个模板项目:

代码语言:javascript
复制
sfcli create --name byzer-extension-market

增加依赖 (添加 APIs Master支持)

打开项目根目录下的的pom文件,添加一个依赖:

代码语言:javascript
复制
<dependency>
            <groupId>tech.mlsql.serviceframework.baseweb</groupId>
            <artifactId>ar_runtime_web_console-lib_${scala.binary.version}</artifactId>
            <version>1.0.3</version>
        </dependency>

修改数据库配置

修改数据库连接文件(位于项目根目录下的 config/application.yml):

代码语言:javascript
复制
development:
  datasources:
    mysql:
      host: 127.0.0.1
      port: 3306
      database: basic_app_runtime
      username: xxxx
      password: xxxxx
      initialSize: 8
      disable: false
      removeAbandoned: true
      testWhileIdle: true
      removeAbandonedTimeout: 30
      maxWait: 100
      filters: stat,log4j
    mongodb:
      disable: true
    redis:
      disable: true

给启动类增加插件

找到启动类 tech.mlsql.app_runtime.plugin.App ,新增三个三个插件,他们分别对应数据库支持,用户系统支持,以及 API master 支持。

代码语言:javascript
复制
new DBPluginDesc,
      new UserPluginDesc,
      new ConsolePluginDesc,

修改后的结果如下:

启动运行

通过 IDE 启动该项目,默认端口为 9007, 访问地址 http://127.0.0.1:9007/web/ 可以得到如下界面:

点击 API LIST, 找打一个叫 hello_world 的 API

点击进去,随意输入:

现在整个项目已经Run起来了,我们可以着手开发我们要的功能了。

API 设计

使用 web-platform 框架做开发,我们要关注【面向用户的 API 设计】 而不是面向 【 应用的API设计 】,两者的区别在于,面向用户的API设计,首先是考虑用户使用你的API希望能否走通核心流程,而面向应用的API设计一般都是为了方便程序调用的。

APIs Master 提供了 Form 作为基本的人机交互手段,提供了 Table 作为结果展示,提供了 Nav 向导组织多个 API 完成一个具体的任务。我们要充分考虑如何适配这种能力。

我认为Byzer Extension Market 核心流程有三个:

  1. 注册,登录,上传扩展
  2. 无登录状态下浏览或者检索插件
  3. 无登录下载插件

其中,注册登已经被实现过了。所以可以抽象出三个核心 API:

  1. 上传用户自己开发的 Byzer 扩展
  2. 浏览/检索扩展
  3. 点击下载地址,可以下载扩展文件

我们主要会议上传扩展为例子,介绍主要开发流程。

上传扩展

上传扩展分成两部分:

  1. 上传文件
  2. 填写扩展信息,比如名称,版本号,扩展类型等

很多人第一感觉是在一个API里实现这两个功能,我个人偏向于使用两个 API,一个Form 来完成。

首先新建一个 UploadExtensionAction 类,继承 ActionRequireLogin,

代码语言:javascript
复制
class UploadExtensionAction extends ActionRequireLogin {

  override def _run(params: Map[String, String]): String = {   
  }

  override def _help(): String = {    
  }
}

接着,我们定义一个描述Form表单元素的类:

代码语言:javascript
复制
object UploadExtensionAction {

  object Params {
    val USER_NAME = Input("userName", "")
    val PLUGIN_NAME = Input("pluginName", "")
    val PLUGIN_TYPE = Select("pluginType", values = List(), valueProvider = Option(() => {
      List(
        KV(Option("byzer_app"), Option("byzer_app")),
        KV(Option("byzer_et"), Option("byzer_et")),
        KV(Option("byzer_ds"), Option("byzer_ds"))
      )
    }))
    val PLUGIN_VERSION = Input("version", "")
    val FILE = Upload("file", valueProviderName = UploadExtensionFields_file.action)
  }

  def action = "uploadExtension"

  def plugin = PluginItem(UploadExtensionAction.action,
    classOf[UploadExtensionAction].getName, PluginType.action, None)
}

这里最显眼的是嵌套 object Params, 他定义了五个参数,定义每个参数的Form元素,其中第五个元数 FILE 比较特殊,他有一个 valueProviderName, 表示该元素用户操作后,会和Action UploadExtensionFields_file.action 进行交互。这意味着用户拖拽文件到上传框后,UploadExtensionFields_file.action 会提供服务端上传能力,上传后返回的结果会作为 file 参数和其他四个参数一起发送给 UploadExtensionAction

信息收集的一个简单实现如下:

代码语言:javascript
复制
override def _run(params: Map[String, String]): String = {
    import UploadExtensionAction._
    val userName = getUserName(params)
    val pluginName = params(Params.PLUGIN_NAME.name)
    val versionOpt = params.get(Params.PLUGIN_VERSION.name)
    val pluginType = ByzerPluginType.from(params.get(Params.PLUGIN_TYPE.name).getOrElse("byzer_app"))
    val jarPath = params(Params.FILE.name)
    val saveSuccess = ExtensionService.saveUploadInfo(userName, pluginName, jarPath, versionOpt.get, pluginType, JSONTool.toJsonStr(
      params - ActionRequireLogin.Params.ADMIN_TOKEN.name - UserService.Config.LOGIN_TOKEN
    ))
    if (saveSuccess) {
      JSONTool.toJsonStr(Map("msg" -> "Save extension success"))
    } else {
      JSONTool.toJsonStr(Map("msg" -> "Extension have the same version exists"))
    }

  }

接着我们来实现下 UploadExtensionFields_file,整个代码也很简单:

代码语言:javascript
复制
class UploadExtensionFields_file extends ActionRequireLogin {
  override def _run(params: Map[String, String]): String = {
    val actionContext = ActionContext.context()
    val items = actionContext.others(ActionContext.Config.formItems).asInstanceOf[java.util.List[FileItem]]
    if (items.size() != 1) {
      render(400, JSONTool.toJsonStr(List(Map("msg" -> "Only support one file to upload"))))
    }
    val paths = ArrayBuffer[Map[String, String]]()
    try {
      val repoLocation = UploadExtensionFields_file.repo
      items.asScala.filterNot(f => f.isFormField).map {
        item =>

          val tempFilePath = PathFun(repoLocation).add("files").add(getUserName(params)).add(item.getFieldName).toPath


          if (new File(tempFilePath).exists()) {
            render(400, JSONTool.toJsonStr(Map("msg" -> s"${item.getFieldName} is already exits")))
          }

          val relativePath = "files" + PathFun.pathSeparator + getUserName(params) + PathFun.pathSeparator + item.getFieldName

          val fileContent = item.getInputStream()
          val targetPath = new File(tempFilePath)
          FileUtils.copyInputStreamToFile(fileContent, targetPath)
          fileContent.close()
          paths += Map("path" -> relativePath)
      }
    } catch {
      case e: Exception =>
        throw e
    }
    JSONTool.toJsonStr(paths)
  }

  override def _help(): String = JSONTool.toJsonStr(
    FormParams.toForm(TestAction.Params).toList.reverse)
}

这里引入了 ActionContext 对象,该对象可以提供 上传的文件句柄给你,然后你进行一些文件相关的操作就好,所以可以看到,非常简单,根本无需和 Rest中的比如 httpServlete打交道。

上面两个 Action开发好以后,就可以注册在描述类中进行配置:

现在,重启应用,就可以在 API List 页面中查看到我们开发的 API 了:

用户可以点击 View 后进入界面进行测试功能(是不是比辅助的API调试工具如 PostMan更好用?)。

添加向导

API 开发完成后,我们希望用户能够更加方便的使用我们的 API,而不是需要自己去找到这个 API,此时向导就起到作用了。

向导创建是在 APIs Master里手工完成的。创建完成后,就可以得到这样的入口:

然后用户比如点击进 上传扩展 向导后,就可以进行相关 API 操作了, 而不用到 API列表中去寻找。

最后

web-platform 使用 embedded jetty, 所以可以使用主类直接启动,部署上会很方便。如果用户想要开发直接的 Web 界面,既可以独立开发,也可以使用 sfcli 创建项目的时候带上参数 --include-ui 然后系统会自动创建一个 reactjs 的前端项目,也是很方便的。

web-platform 也可以用于帮助用户迅速的构建可使用的 MVP 产品。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 环境准备
  • 项目初始化配置
    • 初始化项目
      • 增加依赖 (添加 APIs Master支持)
        • 修改数据库配置
          • 给启动类增加插件
            • 启动运行
            • API 设计
            • 上传扩展
            • 添加向导
            • 最后
            相关产品与服务
            数据库
            云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档