JianghuJS-复杂逻辑开发

12002

1、业务组件封装

业务组件封装是前端开发中的一种最佳实践,它指的是将特定业务功能或模块的代码、样式和逻辑封装成独立的组件,以便在整个项目中复用和维护。业务组件封装的主要目的是提高代码的可维护性、可复用性,降低耦合度,促进团队协作和加速开发过程。

  • 为什么要封装业务组件

    为了实现页面的复杂功能和交互。将一个页面拆分成多个组件,每个组件负责自己的部分,可以使代码更加模块化、易于维护和扩展。

    例如,在一个电商网站中,一个产品页面可能包含产品图片、产品名称、产品描述、价格、评价等多个部分,每个部分都可以使用一个组件来实现。这样,每个组件负责自己的功能和状态,组件之间相互独立,可以单独进行开发、测试和维护,也可以在不同的页面中重复使用。此外,组件之间可以通过组件间通信(props、events)来实现交互和数据共享,使页面的功能更加丰富和灵活。

    因此,使用多个组件有以下优点:

    1. 可维护性:
      业务组件封装使得相关功能的代码集中到一个组件中,便于维护和修改。如果业务逻辑发生变化或者需要优化,只需修改组件内部的代码而不影响整个应用。

    2. 可复用性:
      通过封装业务组件,可以轻松地在项目的不同部分或者不同项目中复用相同的功能。这降低了代码冗余,提高了开发效率。

    3. 降低耦合度:
      封装业务组件可以将业务逻辑与其他组件解耦,减少组件之间的依赖性。这使得修改一个组件不会影响到其他组件,提高了代码的灵活性。

    4. 提高团队协作:
      通过业务组件的封装,团队成员可以更容易地理解和使用彼此编写的组件。这有助于提高团队协作效率,特别是在大型项目中。

    5. 加速开发过程:
      封装业务组件可以提高开发效率,因为在新功能的开发中,开发者可以直接使用已经封装好的组件,而不是从头开始编写相似的代码。

  • 如何封装业务组件

    1. 抽象业务逻辑:
      将业务组件内的业务逻辑抽象成可复用的函数或方法。确保组件关注单一职责,不涉及过多的业务逻辑。

    2. 提取可配置项:
      将组件中的可配置项(例如样式、参数、回调函数)提取为组件的属性或配置项,以便在使用组件时进行定制。

    3. 良好的接口设计:
      设计清晰、简单、易于使用的接口,确保组件的使用方式直观,减少使用者的学习成本。

    4. 考虑状态管理:
      如果组件内部需要维护一些状态,考虑使用状态管理工具(如Redux、Vuex等)来管理状态,以确保状态的一致性和可追踪性。

    5. 文档和示例:
      为业务组件提供详细的文档和示例,包括如何使用、可配置项、事件触发等信息,以便其他开发者能够轻松地使用该组件。

    6. 测试:
      编写单元测试以确保组件的功能和逻辑的正确性。测试是确保组件稳定性的重要手段。

    7. 版本管理:
      使用版本控制工具(如Git)对业务组件进行版本管理,确保在不同项目或团队中的使用一致性。

示例代码(Vue.js 组件):

  1. <template>
  2. <div>
  3. <button @click="handleClick">点击我</button>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. methods: {
  9. handleClick() {
  10. // 处理点击事件的业务逻辑
  11. console.log('按钮被点击了!');
  12. }
  13. }
  14. }
  15. </script>
  16. <style scoped>
  17. /* 组件样式 */
  18. </style>

上述示例是一个简单的Vue.js组件,通过封装按钮点击的业务逻辑,实现了组件的复用。在实际应用中,可以根据业务的复杂性进行更灵活和丰富的封装。

2、父子组件传参

  • 不同的传参方式

    在 Vue.js 中,父组件向子组件传递数据可以使用不同的方式,包括以下几种:

    1. Props
      Props(属性)传递: 是最常见的一种方式,当父组件需要将数据传递给子组件时,可以使用属性传递。父组件通过在子组件上声明 props,使用v-bind或简写:,将数据以属性的形式传递给子组件。子组件接收 props 数据并在内部使用。

    2. $emit
      $emit 事件传递: 当子组件需要将数据传递给父组件时,可以使用事件传递。子组件可以通过$emit触发自定义事件并携带数据来通知父组件。父组件通过在子组件上使用@eventName监听该事件,并在相应的事件处理程序中获取传递的数据。

    3. $refs
      $refs 属性: 当父组件需要主动调用子组件的方法或访问子组件的属性时,可以使用 $refs。在子组件上使用 ref 属性为其命名,然后在父组件中通过 this.$refs.childRef 来访问子组件实例。可以直接调用子组件的方法或访问其属性,而无需通过props或事件进行通信。

  • 用例 & 逻辑说明

下面是一个简单的 jianghusjs 父子组件传递参数的示例:

  1. <!-- 父page页面 -->
  2. {% extends 'template/jhTemplateV4.html'%}
  3. {% block vueTemplate %}
  4. <script type="text/html" id="app-template">
  5. <div>
  6. <h1>{{ title }}</h1>
  7. <child-component :message="message" @update="updateMessage"></child-component>
  8. </div>
  9. </script>
  10. <div id="app"></div>
  11. {% endblock %}
  12. {% block vueScript %}
  13. <script>
  14. {% include 'component/childComponent.html' %}
  15. new Vue({
  16. el: '#app',
  17. template: '#app-template',
  18. vuetify: new Vuetify(),
  19. data() {
  20. return {
  21. title: 'Parent Component',
  22. message: 'Hello, Child Component!'
  23. }
  24. },
  25. methods: {
  26. updateMessage(newMessage) {
  27. this.message = newMessage;
  28. }
  29. }
  30. });
  31. </script>
  32. {% endblock %}
  1. <!-- 子组件 component/childComponent.html -->
  2. <template id="child-component">
  3. <div>
  4. <p>{{ message }}</p>
  5. <button @click="changeMessage">Change Message</button>
  6. </div>
  7. </template>
  8. <script>
  9. Vue.component("ChildComponent", {
  10. template: "#child-component",
  11. vuetify: new Vuetify(),
  12. props: {
  13. message: String
  14. },
  15. methods: {
  16. changeMessage() {
  17. this.$emit('update', 'New Message');
  18. }
  19. }
  20. });
  21. </script>

在这个例子中,父组件包含一个标题和一个子组件<child-component>

  • 属性传递给子组件:
    通过:message="message"将父组件中的数据message作为属性传递给子组件。

  • 子组件接收属性:
    子组件中通过props选项声明接收这个属性。

  • 子组件变更事件:
    子组件中的<p>标签显示message属性的值,<button>标签点击后会触发changeMessage()方法,通过this.$emit('update', 'New Message')语句向父组件发送update事件,并传递一个新的消息'New Message'。父组件中的@update="updateMessage"监听到update事件,并执行updateMessage(newMessage)方法,将新消息保存到message**属性中。

通过props选项实现父子组件之间的数据传递,可以使组件之间解耦、独立、易于维护和扩展。同时,通过事件机制实现组件之间的交互,可以使组件之间更加灵活和动态。

3、jianghuKnex

  • jianghuKnex 与 knex 的关系

    knex 是一个 Node.js 的 SQL 查询构建器,可简化开发者与 PostgreSQL、MySQL、SQLite 和 MSSQL 等常见数据库交互。

    jianghuKnex是在 Knex 的基础上进行的封装,添加了数据历史特性。包括在数据库操作中保留历史数据、追踪数据变更历史等功能。

    1. 数据历史记录:
      包括记录每次数据变更的历史,包括插入、更新和删除操作。这对于跟踪数据变更并进行审计非常有用。

    2. 版本化数据:
      在 Knex 查询的基础上,提供了一些额外的语法或方法,以便查询特定版本的数据,或者查询某个时间点的数据状态。

    3. 时间戳:
      为每个表格自动添加时间戳,记录每次数据的创建和修改时间,用于更方便地追踪数据的时间线。

    4. 数据快照:
      提供生成数据的快照或历史版本,以便在需要时进行比较或还原。

  • jianghuKnex 常用方法

在你的代码中使用jianghuKnex时,你可以按照以下的基本格式:

  1. // 选择全部数据记录
  2. await this.app.jianghuKnex('${表的名字}').select();
  3. // 增加一条数据记录
  4. await this.app.jianghuKnex('${表的名字}').jhInsert({id: 1, data: 'abc'});
  5. // 更新id为1的数据记录
  6. await this.app.jianghuKnex('${表的名字}').where({id: 1}).jhUpdate({data: '123'});

接下来,我们以一个学生管理系统为例,示范各种SQL数据操作的写法:

  • 示例数据表名:'student_basic'
  • 数据表部分数据字段:
    1. - id:记录序号
    2. - studentId:学生编号
    3. - name:学生姓名
    4. - gender:性别
    5. - dateOfBirth:出生日期
    6. - classId:班级编号
    7. - level:年级

读取数据

  • 读全部数据
  1. await this.app.jianghuKnex('student_basic').select();
  • 读取第一条数据
  1. await this.app.jianghuKnex('student_basic').first();
  • 仅读取某些字段 【需要试验】
  1. // 读取第一条数据
  2. await this.app.jianghuKnex('student_basic').column('studentId', 'name', 'classId').first();
  3. // 读取全部数据
  4. await this.app.jianghuKnex('student_basic').column('studentId', 'name', 'classId').select();

设置各种Where条件

在jianghuKnex中,where条件并非必要,根据查询的需要增加即可。

通用查询条件:where

  1. const { jianghuKnex } = this.app
  2. // 查询名叫“张三丰”、班级编号是“2021-01级-02班”的学生
  3. await jianghuKnex('student_basic').where({ name: ‘张三丰’, classId: 2021-01级-02班’}).select();
  4. // 模糊查询名为“张”开头的学生
  5. await jianghuKnex('student_basic').where("name", "like", "张%").select();

和与或查询:andWhere、orWhere

  1. const { jianghuKnex } = this.app
  2. // 查询名叫“张三丰”并且身高大于180
  3. await jianghuKnex("student_basic")
  4. .where({"name": "张三丰"})
  5. .andWhere("bodyHeight", ">", 180).select()
  6. // 查询名叫“张三丰”或身高大于180
  7. await jianghuKnex("student_basic")
  8. .where({"name": "张三丰"})
  9. .orWhere("bodyHeight", ">", 180).select()

在查询条件中规定多个值:whereIn

  1. // 查找所有名字是“张三丰”或者“张无忌”的学生
  2. await this.app.jianghuKnex('student_basic').whereIn("name", ["张三丰", "张无忌"]).select();

自定义sql查询(不建议使用,容易sql注入)

  1. await this.app.jianghuKnex('student_basic').whereRaw('id = ? or bodyHeight > ?', [187, 180]).select();

设置Order By、Limit等条件

在jianghuKnex中,我们还可以对查询设置起始位置、条数以及排序规则。这些规则不是必须的,根据需要选填即可。

设置查询起始位置 offset

  • 例如,从第100位开始查询:
  1. await this.app.jianghuKnex('student_basic').offset(100).select();

设置查询条数 limit

  • 例如,查询前10条数据:
  1. await this.app.jianghuKnex('student_basic').limit(10).select();

设置排序规则

  • 例如,按照年龄倒序排列:
  1. await this.app.jianghuKnex('student_basic').orderBy('age', 'desc').select();
  • 也可以设置多个排序规则。例如,按照班级正序排列,再按年龄倒序排列:
  1. await this.app.jianghuKnex('student_basic').orderBy([
  2. 'classId', { column: 'age', order: 'desc' }
  3. ]).select();

插入数据 Insert

  • 插入一条数据:
  1. await this.app.jianghuKnex('student_basic', ctx).jhInsert({
  2. studentId: 'G00100',
  3. name: '小虾米',
  4. gender: '男',
  5. dateOfBirth'2010-01-01',
  6. classId'2022-01级-01班'
  7. level1
  8. });
  • 插入多条数据,需要使用数组:
  1. await this.app.jianghuKnex('student_basic', ctx).jhInsert([{
  2. studentId: 'G00003',
  3. name: '小虾米',
  4. gender: 'male',
  5. dateOfBirth'2010-01-01',
  6. classId'2022-01级-02班'
  7. level1
  8. }],[{
  9. studentId: 'G00101',
  10. name: '大虾米',
  11. gender: 'male',
  12. dateOfBirth'2010-01-02',
  13. classId'2022-01级-02班'
  14. level1
  15. }]);

修改数据 Update

  • 修改学生编号为“G00101”的学生的姓名:
  1. await this.app.jianghuKnex('student_basic',ctx).where({studentId: 'G00101'}).jhUpdate({
  2. name: '张大虾'
  3. });

可以通过设置where条件来修改多条数据。

删除数据 Delete

  • 删除学生编号为“G00101”的数据:
  1. await this.app.jianghuKnex('student_basic',ctx).where({studentId: 'G00101'}).jhDelete();

可以通过设置where条件来删除多条数据。

直接执行 sql 语句

jianghuKnex也支持直接执行sql语句。使用raw命令可以执行合法的sql语句。
注意!!我们极其不建议开发者拼接sql语句,这样很容易引起sql注入!!

  • 用法示例:查询学生编号为“G00101”的数据:
  1. const studentId = 'G00101';
  2. await this.app.jianghuKnex.raw(`SELECT * FROM student_basic WHERE studentId = '${studentId}'`);

注意事项:使用jhInsert/Update/Delete要传入ctx

jianghuKnex在对数据进行插入、修改或删除时,可以将数据的历史记录,存入_record_history表中。但需要注意的是,你需要把环境变量ctx作为一个参数传入jianghuKnex()函数中,这样才能记录是哪个用户执行了这个操作。

  • 在service中,一个完整的函数的写法是:
  1. // app/service/demo.js
  2. class DemoService extends Service {
  3. async demoSelectFunction() {
  4. const { ctx, app } = this;
  5. const { jianghuKnex } = app;
  6. const result = await jianghuKnex('sample-table').select();
  7. return result;
  8. }
  9. async demoInsertFunction() {
  10. const { ctx, app } = this;
  11. const { jianghuKnex } = app;
  12. const result = await jianghuKnex('sample-table', ctx).insert({id: 100, name: '张三'});
  13. return result;
  14. }
  15. }
  16. module.exports = DemoService;
  • jianghuKnex 事务处理

MySQL事务是一组SQL语句的执行,全部成功或全部失败。在MySQL中,使用BEGIN、COMMIT和ROLLBACK三个关键字来实现事务。如果所有SQL语句都执行成功,使用COMMIT提交事务,否则使用ROLLBACK回滚事务。

  1. await jianghuKnex.transaction(async trx => {
  2. await trx('student').insert({ name: 'xxx1' });
  3. await trx('student').insert({ name: 'xxx2' });
  4. });

4、beforeHook

"beforeHook" 通常指的是在进行某个操作或执行某个函数之前执行的钩子(hook)。这种机制常见于软件开发中,尤其是在使用框架或库的情况下,以允许在某个操作之前执行一些自定义的逻辑。

  • 为什么要有 beforeHook

    1. 预处理逻辑:
      在执行某个操作之前,可能需要进行一些预处理逻辑,例如验证输入、准备数据、检查权限等。"beforeHook" 提供了一个机会在实际操作之前执行这些逻辑。

    2. 代码组织:
      使用 "beforeHook" 可以将相关的预处理逻辑集中到一个地方,使代码更加清晰和易于维护。这有助于提高代码的可读性和可维护性。

    3. 可扩展性:
      "beforeHook" 允许开发者轻松地添加、移除或修改预处理逻辑,从而增强了代码的可扩展性。这对于应对未来需求的变化非常重要。

  • beforeHook 用法

jianghujs中的 beforehook 是通过在 app/service 中调用指定函数来实现的。配置信息存储在 "_resource" 表的 "resourcehook" 字段中,其格式如下:

  1. {
  2. "after": [],
  3. "before": [
  4. {
  5. "service": "servicename",
  6. "serviceFunction": "function"
  7. }
  8. ]
  9. }

5、afterHook

  • 为什么要有 afterHook

    1. 后处理逻辑:
      在执行某个操作之后,可能需要进行一些后处理逻辑,例如记录日志、触发其他异步任务、更新缓存等。afterHook 提供了一个机会在实际操作之后执行这些逻辑。

    2. 代码组织:
      类似于 beforeHook,使用 afterHook 可以将后处理逻辑集中到一个地方,使代码更加清晰和易于维护。这有助于提高代码的可读性和可维护性。

    3. 异常处理:
      在操作成功完成后,可能需要处理一些额外的异常情况或边缘情况。afterHook 可以用于执行这样的异常处理逻辑。

    4. 触发其他操作:
      在某个操作完成后,可能需要触发其他相关的操作。afterHook 提供了一个适当的时机来执行这些操作。

  • afterHook 用法

jianghujs中的 afterHook 是通过在 app/service 中调用指定函数来实现的。配置信息存储在 "_resource" 表的 "resourcehook" 字段中,其格式如下:

  1. {
  2. "after": [
  3. {
  4. "service": "servicename",
  5. "serviceFunction": "function"
  6. }
  7. ],
  8. "before": []
  9. }