领域模型应用 API接口以及页面实现

领域模型应用 API接口以及页面实现

一、概述

本章核心内容是将领域模型从API接口模型模板数据查询到前端页面输出展示。项目主要由服务模块、路由模块以及对应的测试代码组成。服务模块负责处理业务逻辑和数据获取,路由模块负责将客户端的请求准确分发到相应的处理逻辑,而测试代码则用于确保系统的各个接口能够正常工作,保证系统的稳定性和可靠性。

二、服务端API接口实现

2.1 服务端核心文件

app/service/project.js

此文件为服务模块,其主要职责是封装与项目数据相关的业务逻辑,为上层的路由模块和其他业务模块提供统一的数据获取接口。

js 复制代码
//service/project.js
module.exports = (app) => {
  const BaseService = require("./base")(app);
  const modelList = require("../../model/index.js")(app);

  return class ProjectService extends BaseService {
    // 获取所有模型与项目的结构化数据
    async getModelList() {
      return modelList;
    }
  };
};
代码解释
  • 模块导出 :使用 module.exports 导出一个函数,该函数接收 app 作为参数。app 通常是 Koa 应用实例,它包含了整个应用的上下文信息,通过传入 app,可以让服务模块与应用的其他部分进行交互。

  • 依赖引入

    • BaseService:从 ./base 模块引入,并传入 app 进行初始化。BaseService 可能是一个基础服务类,包含了一些通用的服务方法和属性,ProjectService 继承自它可以复用这些通用功能,提高代码的复用性。
    • modelList:从 ../../model/index.js 模块引入,同样传入 app 进行初始化。modelList 代表了所有领域模型与项目的结构化数据。
  • 类定义与方法 :定义了 ProjectService 类,它继承自 BaseServicegetModelList 方法是一个异步方法,用于获取 modelList 领域模型与项目的结构化数据。


/app/router/project.js

该文件为路由模块,其核心功能是定义路由规则,将客户端的请求路径映射到相应的控制器方法,实现请求的分发和处理。

js 复制代码
//router/project.js
module.exports = (app, router) => {
  const { project: projectController } = app.controller;
  router.get(
    "/api/project/model_list",
    projectController.getModelList.bind(projectController)
  );
};
代码解释
  • 模块导出 :使用 module.exports 导出一个函数,该函数接收 approuter 作为参数。app 是 Koa 应用实例,router 是 koa-router 路由实例,用于定义和管理路由规则。
  • 控制器提取 :通过对象解构赋值从 app.controller 中提取 project 控制器,并将其赋值给 projectController。这样做的好处是可以更方便地使用 project 控制器中的方法。
  • 路由定义 :使用 router.get 方法定义一个处理 GET 请求的路由。当客户端发送一个 GET 请求到 /api/project/model_list 路径时,会调用 projectControllergetModeList 方法,并使用 bind 方法将 getModeList 方法的 this 上下文绑定到 projectController 对象上,确保在 getModeList 方法内部使用 this 时,它指向的是 projectController 对象。

2.2 服务端模块的调用关系

路由模块(app/router/project.js)负责接收客户端的请求,并将请求分发到相应的控制器方法。在这个过程中,控制器方法会调用服务模块(app/service/project.js)中的方法来处理业务逻辑和获取数据。

具体调用流程如下:

  1. 客户端发送一个 GET 请求到 /api/project/model_list 路径。
  2. 路由模块接收到请求后,根据路由规则调用 projectControllergetModeList 方法。
  3. getModeList 方法在控制器中被调用,它会进一步调用服务模块中 ProjectService 类的 getModelList 方法。
  4. getModelList 方法从 ../../model/index.js 模块中获取 modelList 数据,并将其返回给控制器。
  5. 控制器将获取到的数据返回给客户端。

2.3 Mocha 测试文件结构

Mocha测试用例文件 /test/controller/project.test.js
Mocha 是一个功能强大的 JavaScript 测试框架,主要用于 Node.js 和浏览器环境中的单元测试和集成测试,对项目相关的接口进行测试,确保接口的功能正确性和稳定性。

js 复制代码
// test/controller/project.test.js
const assert = require("assert");
const supertest = require("supertest");
const md5 = require("md5");
const eplisCore = require("../../elpis-core");

const signKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCg";
const st = Date.now();

describe("测试 project 相关接口", function () {
  this.timeout(60000);

  let request;

  it("启动测试服务", async () => {
    const app = await eplisCore.start();
    request = supertest(app.listen());
  });

  it("GET /api/project/model_list", async () => {
    let tmpRes = request.get("/api/project/model_list");
    tmpRes = tmpRes.set("s_t", st);
    tmpRes = tmpRes.set("s_sign", md5(`${signKey}_${st}`));
    const res = await tmpRes;
    assert(res.body.success === true);

    const resData = res.body.data;
    assert(resData.length > 0);
    for (let i = 0; i < resData.length; i++) {
      const item = resData[i];
      assert(item.model);
      assert(item.model.key);
      assert(item.model.name);

      assert(item.project);
      for (const projKey in item.project) {
        assert(item.project[projKey].name);
        assert(item.project[projKey].key);
      }
    }
  });
});
配置启动脚本 (package.json)
json 复制代码
"scripts": {
    "test": "set _ENV='local' && mocha 'test/**/*.js'",
}
说明:
  1. set _ENV='local'
    设置环境变量 _ENV'local',通常用于指定测试运行的环境(如本地开发环境)。
  2. mocha 'test/**/*.js'
    使用 Mocha 执行所有位于 test 目录下的 .js 文件中的测试用例。
  3. 运行 npm test 后,Mocha 会自动找到并执行该测试文件。
测试用例代码解释
  • 依赖引入

    • assert:用于进行断言操作,验证测试结果是否符合预期。
    • supertest:用于模拟 HTTP 请求,方便对接口进行测试。
    • md5:用于生成签名,模拟请求时的签名验证。
    • eplisCore:用于启动测试服务,获取 Koa 应用实例。
  • 测试用例

    • 启动测试服务:调用 eplisCore.start() 方法启动测试服务,并使用 supertest 创建一个请求对象,用于后续的接口测试。
    • GET /api/project/model_list:模拟发送一个 GET 请求到 /api/project/model_list 接口,设置请求头 s_ts_sign,并对响应结果进行断言。确保响应数据的结构和内容符合预期,如 success 字段为 true,数据列表不为空,每个数据项包含 modelproject 字段,且这些字段包含必要的属性。
  • 具体调用流程如下

    • 测试代码调用 eplisCore.start() 方法启动测试服务,获取 Koa 应用实例。
    • 使用 supertest 创建一个请求对象,模拟客户端发送请求到 /api/project/model_list 接口。
    • 路由模块接收到模拟请求后,按照正常的请求处理流程,调用控制器方法,控制器方法再调用服务模块的 getModelList 方法获取数据。
    • 服务模块返回数据给控制器,控制器将数据返回给测试代码。
    • 测试代码使用 assert 进行断言,验证响应数据是否符合预期。

三、客户端页面实现

3.1 客户端文件及功能

  1. entry.project-list.js 项目入口文件
js 复制代码
// entry.project-list.js
import boot from "$pages/boot.js";
import projectList from "./project-list.vue";

boot(projectList);
代码解释
  • 导入模块

    • boot:从 $pages/boot.js 导入,用于初始化和启动组件。
    • projectList:从 ./project-list.vue 导入项目列表组件。
  • 启动组件 :调用 boot 函数并传入 projectList 组件,完成项目列表页面的初始化和启动。

  1. project-list.vue 项目列表组件
模板部分
js 复制代码
// project-list.vue
<template>
  <header-container title="项目列表">
    <template #main-content>
      <div v-loading="loading">
        <div v-for="item in modelList" :key="item.model?.key">
          <!-- 展示 model -->
          <div class="model-panel">
            <el-row type="flex" align="middle">
              <div class="title">
                {{ item.model?.name }}
              </div>
            </el-row>
            <div class="divider" />
          </div>
          <!-- 展示 project -->
          <el-row flex class="project-list">
            <el-card
              v-for="projectItem in item.project"
              :key="projectItem.key"
              class="project-card"
            >
              <!-- project 头部 -->
              <template #header>
                <div class="title">
                  {{ projectItem.name }}
                </div>
              </template>
              <!-- project 主体 -->
              <div class="content">
                {{ projectItem.desc ?? "----" }}
              </div>
              <!-- project 底部 -->
              <template #footer>
                <el-row justify="end">
                  <el-button link type="primary" @click="onEnter(projectItem)">
                    进入
                  </el-button>
                </el-row>
              </template>
            </el-card>
          </el-row>
        </div>
      </div>
    </template>
  </header-container>
</template>
代码解释
  • 使用 header-container 组件:作为页面的整体布局,设置标题为"项目列表"。
  • main-content 插槽:用于展示项目列表的具体内容。
  • 加载状态 :使用 v-loading 指令根据 loading 状态显示加载动画。
  • 循环渲染 :使用 v-for 指令遍历 modelList 数组,展示项目模型和具体项目。
脚本部分
js 复制代码
// project-list.vue
<script setup>
import { ref, onMounted } from "vue";
import $curl from "$common/curl.js";
import headerContainer from "$widgets/header-container/header-container.vue";

const loading = ref(false);
const modelList = ref([]);
const getModelList = async () => {
  loading.value = true;
  const res = await $curl({
    method: "GET",
    url: "/api/project/model_list",
    errorMessage: "获取项目列表失败",
  });
  loading.value = false;
  if (!res || !res.data || !res.success) {
    return;
  }

  modelList.value = res.data;
};

const onEnter = (projectItem) => {
  console.log("on enter project", projectItem);
};

onMounted(() => {
  getModelList();
});
</script>
代码解释
  • 导入模块

    • refonMounted:从 vue 导入,用于响应式数据和生命周期钩子。
    • $curl:从 $common/curl.js 导入,用于发送 HTTP 请求。
    • headerContainer:从 $widgets/header-container/header-container.vue 导入头部容器组件。
  • 响应式数据

    • loading:用于控制加载状态。
    • modelList:用于存储项目模型列表。
  • 获取项目列表getModelList 函数通过 $curl 发送 GET 请求获取项目列表,并更新 modelList

  • 进入项目事件onEnter 函数处理进入项目的点击事件。

  • 生命周期钩子onMounted 钩子在组件挂载后调用 getModelList 函数。

样式部分
css 复制代码
/* project-list.vue */
<style lang="less" scoped>
.model-panel {
  margin: 20px 50px;
  min-width: 500px;
  .title {
    font-size: 25px;
    font-weight: bold;
    color: #6d6c6c;
  }
  .divider {
    margin-top: 10px;
    border-bottom: 1px solid #d7d7d7;
    width: 200px;
  }
}

.project-list {
  margin: 0 50px;
  .project-card {
    margin-right: 30px;
    margin-bottom: 20px;
    width: 300px;
    .title {
      font-weight: bold;
      font-size: 17px;
      color: #47a2ff;
    }
    .content {
      height: 70px;
      color: darkgrey;
      font-size: 15px;
      overflow: auto;
    }
  }
}
</style>
代码解释
  • 使用 Less 预处理器:提高样式代码的可维护性。
  • scoped 属性:确保样式只作用于当前组件。
  • 样式定义:定义了项目模型面板和项目卡片的样式。
  1. header-container.vue 页面头部组件
模板部分
js 复制代码
// header-container.vue
<template>
  <el-container class="header-container">
    <el-header class="header">
      <el-row type="flex" align="middle" class="header-row">
        <!-- 左侧 上方 标题区域 -->
        <el-row type="flex" align="middle" class="title-panel">
          <img src="./asserts//icon.png" class="logo" />
          <el-row class="text">
            {{ title }}
          </el-row>
        </el-row>
        <!-- 插槽: 菜单区域 -->
        <slot name="menu-container" />
        <!-- 右侧: 上方 设置| 用户区域 -->
        <el-row type="flex" align="middle" justify="end" class="setting-panel">
          <!-- 插槽:设置区域  -->
          <slot name="setting-container" />
          <img src="./asserts//avatar.png" class="avatar" />
          <el-dropdown @command="hanleUserCommand">
            <span class="user-name">
              {{ userName }}
              <i class="el-icon-arrow-down el-icon--right" />
            </span>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item>我的消息</el-dropdown-item>
                <el-dropdown-item command="logout"> 退出登录 </el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </el-row>
      </el-row>
    </el-header>
    <el-main class="main-container">
      <!-- 核心内容: 外部拓展填充区域 -->
      <slot name="main-content" />
    </el-main>
  </el-container>
</template>
代码解释
  • 使用 ElementPlus 组件el-containerel-headerel-row 等,构建头部容器的布局。
  • 插槽 :提供 menu-containersetting-containermain-content 三个插槽,用于外部拓展内容。
  • 用户信息:展示用户头像和用户名,并提供下拉菜单,包含"我的消息"和"退出登录"选项。
脚本部分
js 复制代码
// header-container.vue
<script setup>
import { ref } from "vue";
defineProps({
  title: String,
});

const userName = ref("admin");

const hanleUserCommand = (event) => {
  console.log("handle user command");
};
</script>
代码解释
  • 导入模块refvue 导入,用于响应式数据。
  • 定义属性 :使用 defineProps 定义 title 属性,用于设置页面标题。
  • 响应式数据userName 用于存储用户名,初始值为"admin"。
  • 用户命令处理hanleUserCommand 函数处理用户下拉菜单的命令。
样式部分
css 复制代码
/* header-container.vue */
<style lang="less" scoped>
.header-container {
  height: 100%;
  // min-width: 1000px;
  overflow: hidden;
  .header {
    max-height: 120px;
    border-bottom: 1px solid #e8e8e8;

    .header-row {
      height: 60px;
      padding: 0 20px;
      .title-panel {
        width: 180px;
        min-width: 180px;
        .logo {
          margin-right: 10px;
          width: 25px;
          width: 25px;
          border-radius: 50%;
        }
        .text {
          font-size: 15px;
          font-weight: 500;
        }
      }
      .setting-panel {
        margin-right: auto;
        min-width: 180px;
        flex: 1;
        .avatar {
          margin-right: 12px;
          width: 25px;
          height: 25px;
          border-radius: 50%;
        }
        .user-name {
          font-size: 16px;
          font-weight: 500;
          cursor: pointer;
          height: 60px;
          line-height: 60px;
          outline: none;
        }
      }
    }
  }
  .main-container {
  }
}

:deep(.el-header) {
  padding: 0;
}
</style>
代码解释
  • 使用 Less 预处理器:提高样式代码的可维护性。
  • scoped 属性:确保样式只作用于当前组件。
  • 样式定义:定义了头部容器、标题区域、设置区域和用户信息区域的样式。

五、总结

服务端通过 app/service/project.js 封装业务逻辑,app/router/project.js 定义路由规则,将客户端请求分发到相应处理逻辑,同时使用 Mocha 进行接口测试以确保系统稳定性;客户端以 entry.project-list.js 为入口,project-list.vue 展示项目列表,header-container.vue 构建页面头部,利用 Vue 的响应式特性和组件化开发,结合 ElementPlus 组件库和 Less 样式预处理器,实现页面的渲染和交互。围绕领域模型应用展开,核心思想是实现从 API 接口模型模板数据查询到前端页面输出展示的完整流程。

相关推荐
vvilkim4 分钟前
Vue.js 中的计算属性、监听器与方法:区别与使用场景
前端·javascript·vue.js
乘风!6 分钟前
SpringBoot前后端不分离,前端如何解析后端返回html所携带的参数
前端·spring boot·后端
Anlici1 小时前
跨域解决方案还有优劣!?
前端·面试
庸俗今天不摸鱼1 小时前
【万字总结】构建现代Web应用的全方位性能优化体系学习指南(二)
前端·性能优化·webp
追寻光2 小时前
Java 绘制图形验证码
java·前端
前端snow2 小时前
爬取数据利用node也行,你知道吗?
前端·javascript·后端
村头一颗草2 小时前
高德爬取瓦片和vue2使用
前端·javascript·vue.js
远山无期2 小时前
vue3+vite项目接入qiankun微前端关键点
前端·vue.js
陈随易2 小时前
告别Node.js:2025年,我为何全面拥抱Bun
前端·后端·程序员
还是鼠鼠2 小时前
Node.js--exports 对象详解:用法、示例与最佳实践
前端·javascript·vscode·node.js·web