复盘工作-2024-08

复盘工作-2024-08-01

1.vue里:disabled绑定接收一个函数来动态决定其值,而不仅限于简单的表达式

2.js判数组是否含有某元素:.includes()

上述两点的练习代码:

<template>
    <f7-page>
        <!-- :disabled接收true、false值例子: -->
<!--              海岛名称:<input id="islandName" name="islandName" :disabled="isIslandNameDisabled"></input>-->
        <!-- :disabled接收简单表达式例子: -->
<!--              海岛名称:<input id="islandName" name="islandName" :disabled="cityName != 'YanTai'"></input>-->
        <!-- :disabled接收函数例子: -->
        海岛名称:<input id="islandName" name="islandName" :disabled="isNotCoastalCity()"></input>
    </f7-page>
</template>

<script>
    export default {
        data() {
            return{
                // isAppleTypeNameDisabled: false,
                cityName: 'YanTai',
                coastalCityArr: ['QingDao', 'DaLian', 'YanTai', 'WeiHai']
            }
        },
        methods: {
          /**
           * 判断是否:不是沿海城市(根据其是否在沿海城市这个js数组里),返回true(不是沿海城市)/false(是沿海城市),控制input框是否可编辑
           *
           * .includes() 判断数组是否包含某元素
           */
          isNotCoastalCity() {
            return !this.coastalCityArr.includes(this.cityName);
          }
        }
    }
</script>

复盘工作-2024-08-02

今天内容没有写具体代码验证练习。

1.复盘用户信息、session

1.1HttpSession是用于跟踪用户会话的一个对象。

1.2使HttpSession失效的方法:可以通过session.invalidate()方法。

1.3session基本概念:

1.3.1状态保持:http协议本身是无状态的,即服务器默认不知道两个请求是否来自同一个用户。session就是用来解决这个问题,通过它,服务器可以“记住”用户的状态。

1.3.2唯一标识符:每个session都有一个唯一的标识符(session id),这个id通常通过Cookie或者URL重写的方式发送给客户端,并在后续的请求中携带,以便服务器识别用户的session。

1.3.3存储位置:可以存储在服务器的内存,也可以存储在数据库、文件系统等持久化存储中。

1.4session的工作流程

1.4.1创建session:当用户首次访问网站时,服务器会创建一个新的session,并生成一个新的session id。

1.4.2发送session id:服务器通过cookie或URL重写的方式将session id发送给客户端。

1.4.3客户端存储session id:客户端(通常是浏览器)会存储这个session id,并在后续的请求中通过cookie或URL的方式发送给服务器。

1.4.4服务器识别session:服务器通过解析请求中的session id来识别用户的session,从而获取或更新session数据。

1.4.5session销毁:session可以通过配置自动销毁(如设置过期时间),也可以由服务器或客户端显示销毁(如用户登出)。

置过期时间),也可以由服务器或客户端显示销毁(如用户登出)。

复盘工作-2024-08-03

1.session和cookie的区别:

1.1存储位置:cookie存储在客户端,session存储在服务端

1.2安全性:cookie存储于客户端,因此存在被篡改的风险,而session存储于服务端,相对更安全

1.3数据大小:cookie存储在客户端,其大小收到浏览器限制(通常4KB左右),而session的大小则取决于服务器配置

1.4用途:cookie主要用于存储少量数据,如用户偏好设置;而session则用于存储用户状态信息,如登录状态、购物车内容等。

复盘工作-2024-08-04

1.cookie:

cookie技术允许网站在用户的浏览器上存储少量数据,以便在用户后续访问时能识别用户身份或追踪用户的行为。

2.cookie的基本定义:

2.1定义:cookie是一个小型文本文件,通常由网站服务器发送给用户的浏览器,并存储在用户的计算机硬盘或内存中。

2.2作用:帮助网站记住用户的信息,例如用户偏好设置、登录状态等,以便用户在下次访问时提供更加个性化的服务。

3.cookie工作原理:

3.1创建与发送:

3.1.1当用户首次访问网站时,服务器可能会根据需求创建一个cookie,并将其通过http响应发送给用户的浏览器。

3.1.2cookie中包含了加密的用户ID、访问时间等信息,这些信息对网站识别用户身份和追踪用户活动至关重要。

3.2存储与读取:

3.2.1浏览器接收到cookie后,会将其存储在用户的计算机上。存储位置与操作系统和浏览器相关,但通常位于特定的文件夹里。

3.2.2每当用户再次访问该网站时,浏览器会自动将cookie发送给服务器,服务器通过解析cookie中的信息来识别用户身份和追踪用户活动。

4.cookie的应用场景:

4.1用户认证:网站可以通过cookie记住用户的登录状态,避免用户每次访问时都需要重新输入用户名和密码。

4.2个性化推荐:cookie可以记录用户在网站上的浏览行为,如浏览的页面、点击的链接等,从而为用户提供个性化的广告和推荐内容。

4.3购物体验优化:在电子商务网站上,cookie可以记录用户添加到购物车中的商品,以便用户下次访问时仍然可以保留这些选项。

4.4网站分析和改进:网站可以通过cookie收集用户的访问数据,如浏览时间、访问频率等,用于分析和改进网站的使用效果。

复盘工作-2024-08-05

1.visio导出svg流程图用于系统,要做哪些修改:

1.1每个环节的名称要与系统里配置的环节名称完全一致;

1.2导出时,要先全选,然后再另存为svg,这样可以避免导出的svg有大量空白;

1.3导出时svg需命名为流程模板code.svg;

1.4最终将svg放到WebRoot/webpage/flowchart/文件夹下即可。

复盘工作-2024-08-06

1.两oracle数据库里相同字段定义都是varchar2(40)但A库导出的insert sql在B库执行报字段过长:排查原因为测试服务器A库字符集为ZHS16GBK,本地B库字符集为AL32UTF8,某些汉字在ZHS16GBK下所占字节比在AL32UTF8下更长,造成上述问题。解决方案:本地B库改为与测试环境字符集一致,用ZHS16GBK。

2.学习app检查新版本及更新逻辑:

2.1app前端请求原生插件获取本身的版本号;(原生插件通过PackageManager.getPackageInfo方法获取包信息,再从中获取版本号versionCode,int类型)

2.2app前端携带本身版本号请求原生插件的检查更新方法;

2.3原生插件的检查更新方法里,请求服务端接口,获取最新版本信息,若有新版本,则根据全量更新还是增量更新,将对应的apk文件或增量的差异文件,上传到ftp;然后将最新版本信息返回app前端;

2.4app前端接收到最新版本信息后,对是否强制更新、用户是否选择进行更新等判断后,若进行更新,则请求原生插件的更新方法;

2.5原生插件的更新方法里,再请求服务端的下载接口,从ftp上下载整个apk或差异包,并进行后续解压安装。

复盘工作-2024-08-07

1.::v-deep{} 使用::v-deep{}的目的是能实现样式穿透。用于在父组件中修改子组件的样式、或修改引入的第三方的样式

父页面BookList.vue:

<template>
  <f7-page>
    <book-detail></book-detail>
  </f7-page>
</template>

<script>
import BookDetail from "@/views/book/BookDetail";
export default {
  name: "BookList",
  components: {BookDetail}
}
</script>
<!-- ::v-deep{}
使用::v-deep{}的目的是能实现样式穿透。用于在父组件中修改子组件的样式、或覆盖第三方库的样式-->
<style scoped>
/* ::v-deep .book-name-span{} 将子组件BookDetail.vue里的book-name-span样式给修改掉。*/
/* 最终效果为红色。 */
::v-deep .book-name-span{
  color: red;
}
</style>

子组件BookDetail.vue:

<template>
  <span class="book-name-span">Vue入门</span>
</template>

<script>
export default {
  name: "BookDetail"
}
</script>

<style scoped>
.book-name-span{
  color: blue;
}
</style>

复盘工作-2024-08-08

1.::v-deep{}中定义的样式,会渗透到当前组件的所有子孙组件,无论其位于组件树的哪个层级,即无论这些子孙组件在组件树中的位置有多深。

父组件BookList.vue:

<template>
  <f7-page>
    <book-detail></book-detail>
  </f7-page>
</template>

<script>
import BookDetail from "@/views/book/BookDetail";
export default {
  name: "BookList",
  components: {BookDetail}
}
</script>

<!-- ::v-deep{}
使用::v-deep{}的目的是能实现样式穿透。用于在父组件中修改(所有)子孙组件的样式、或覆盖第三方库的样式。
会渗透到当前组件的所有子孙组件,无论其位于组件树的哪个层级,即无论这些子孙组件在组件树中的位置有多深。-->
<style scoped>
/* ::v-deep .book-name-span{} 将子组件BookDetail.vue里的book-name-span样式给修改掉。*/
/* 同时也将(孙)子组件BookDetailGrandSon.vue里的book-name-span样式给修改掉。*/
/* 最终效果全为红色。 */
::v-deep .book-name-span{
  color: red;
}
</style>

子组件BookDetail.vue:

<template>
  <f7-page>
    <span class="book-name-span">Vue入门</span>
    <book-detail-grand-son></book-detail-grand-son>
  </f7-page>
</template>

<script>
import BookDetailGrandSon from "./BookDetailGrandSon";
export default {
  name: "BookDetail",
  components: {BookDetailGrandSon}
}
</script>

<style scoped>
.book-name-span{
  color: blue;
}
</style>

(孙)子组件BookDetailGrandSon.vue:

<template>
  <span class="book-name-span">JS入门</span>
</template>

<script>
export default {
  name: "BookDetailGrandSon"
}
</script>

<style scoped>
.book-name-span{
  color: orange;
}
</style>

2.如下样式定义(.search-box f7-input, .search-box f7-button{}):是对search-box(具有这个样式的div)下的所有f7-input标签、f7-button标签,运用相同的样式:

/* 如下样式定义(.search-box f7-input, .search-box f7-button{}):是对search-box(具有这个样式的div)下的所有f7-input标签、f7-button标签,运用相同的样式 */
.search-box f7-input,
.search-box f7-button {
  height: 40px; /* 统一输入框和按钮的高度 */
  border-radius: 5px; /* 边框圆角 */
}

复盘工作-2024-08-09~2024-08-10

1.framework7实现一个列表上方的搜索框:补充对this.$refs.searchInput.备注markdown里需要拆开写否则写英文doller符号代码会变

.$el.querySelector('input')的解析;补充对dom元素获取value的分析;补充对display: flex; 弹性盒子布局的解析;

<template>
  <f7-page>
    <!-- 在f7-input和f7-button外层套个div,然后控制两元素显示在同一行 -->
    <div class="search-box">
      <!-- f7-input,framework7的组件,type属性可以取html中input的type属性 -->
      <!-- @input事件,每当输入框内容发生变化时触发 -->
      <f7-input
          ref="searchInput"
          type="text"
          placeholder="请输入姓名"
          class="long-input"
          @input="searchByRealname"/>
      <!-- <f7-button></f7-button>之间不写按钮文字的话,按钮是不会显示出来的 -->
      <f7-button @click="searchByRealname">查询</f7-button>
    </div>
  </f7-page>
</template>
<script>
export default {
  methods:{
    searchByRealname() {
      debugger;
      const _self = this;
      // 1.在vue中,$refs是一个对象,用于注册或访问在模版中带有ref属性的子组件或DOM元素
      // 当对元素或子组件设置ref属性后,vue会将这个元素或子组件的引用存储在$refs对象中
      // 可以通过$refs对象加上你定义的ref属性的值来访问这个元素或子组件
      // 2.html代码中定义了ref="searchInput",这里_self.$refs.searchInput就可以访问到这个f7组件的引用
      // 3.f7-input是一个vue组件,而不是原生的dom元素。因此,它可能有一个内部的dom结构,
      // 包含一个或多个input元素。如果想要访问这个内部的input元素(比如为了获取其值)
      // 你就需要穿透这个组件的作用域来访问它的dom。
      // 4.在vue组件中,通常每个组件的根元素都会暴露一个$el属性,这个属性是一个指向该组件根dom元素的引用。
      // 但是, 由于f7-input是一个组件,它可能封装了多个元素,而不仅仅是input。
      // 因此,你不能直接通过_self.$refs.searchInput.$el来访问内部的input元素,
      // 除非f7-input组件的根元素恰好是一个input元素,但这通常不是这种情况。

      // 所以const inputElement = _self.$refs.searchInput.$el.querySelector('input');的意图是:
      // 1.通过_self.$refs.searchInput访问到f7-input组件的引用
      // 2.使用.$el访问该组件的根dom元素
      // 3.使用querySelector('input')在这个根元素及其子元素中查找第一个input元素。

      // 这种方式假设f7-input组件的dom结构中确实包含了一个input元素,并且这个input元素是可以通过querySelector('input')找到的

      // 理解.$el:
      // 在vue.js中,$el是vue组件的实例的一个属性,它用于访问组件对应的实际dom元素。
      // 在vue组件中使用this.$el时,可以直接访问到该组件的根dom元素。
      // 这个属性在vue实例挂载到dom后变得可用,它允许开发者在需要时直接操作dom元素。
      let inputElement = _self.$refs.searchInput.$el.querySelector('input');
      if (inputElement) {
        // inputElement.value能获取input元素的值,是因为这是原生html input元素的一个标准属性。
        // 当input元素被用作文本输入(如type="text")时,其value属性会存储用户输入的文本。
        // 当通过querySelector('input')成功选择到了input元素,并将其引用存储在inputElement变量中时,
        // 你就可以像操作任何原生dom对象一样操作这个input对象。
        // inputElement.value就是访问这个input元素的value属性,从而获取到用户输入的文本值。
        let searchValue = inputElement.value;
        console.log(searchValue);
      }
    }
  }
}
</script>
<style scoped>
/* 关于flexbox布局:
     1.在css中,display: flex; 属性用于将一个元素的显示类型设置为弹性盒子(flexbox)布局
     弹性盒子布局是css3中引入的一种布局模式,旨在提供一种更有效的方式来布局、对齐和分配在容器中的项目(子元素),
     即使它们的大小未知或是动态变化的。
     2.当你对一个div元素设置display: flex;时,这个div就变成了一个弹性容器(flex container),而它的直接子元素
     (如f7-input和f7-button)则自动成为弹性项目(flex items)。弹性容器内的项目默认会沿着主轴(main axis)排列,
     这个主轴的默认方向是水平方向(即行内方向)
     3.因此,当设置div的属性为display: fixed;时,f7-input和f7-button(假设他们是div的直接子元素)就会沿着
     水平方向排列,从而显示在同一行上。这种排列方式不依赖于子元素的原始布局(如它们本来是块级元素block还是内联元素inline,
     因为一旦成为弹性项目,它们就会遵循弹性容器的布局规则。
     4.flexbox布局还提供了多种属性和值来调整项目的对齐、 大小、间距等。如justify-content用于控制项目在主轴上的对齐方式,
     align-items用于控制项目在交叉轴上的对齐方式 */
/* flex是“flexible box”的缩写,意为“弹性盒布局” */
/* axis /ˈæk.sɪs/ 轴 */
.search-box{
  /* 使用Flexbox布局 */
  display: flex;
  /* 使子元素垂直居中 */
  align-items: center;
  /* 使子元素之间有空隙,且分布在两端 */
  justify-content: space-between;
  /* 添加一些内边距 */
  padding: 10px;
  /* 添加背景色 */
  background-color: #f8f8f8;
  /* 圆角边框 */
  border-radius: 5px;
  /* 与上方保持一定的距离 */
  margin-top: 10px;
}

.search-box f7-input, .search-box f7-button{
  height: 40px;
  border-radius: 5px;
}

.search-box f7-button{
  background-color: #007AFF;
  color: white;
  border: none;
}

/* 这里long-input是f7-input的样式,其内部嵌了一个input */
/* 该元素在F12控制台里其实是<div data-v-18cb2726="" class="long-input input"><input type="text" class=""></div> */
/* 所以要想改变input框的placeholder的位置(由默认靠左,改为靠右),需要在long-input里再指明input */
.long-input{
  input{
    text-align: right;
  }
  /* width控制long-input的宽度,从而与右侧f7-button显示更美观 */
  width: 85%;
}
</style>

复盘工作-2024-08-11~2024-08-12

1.练习退出登录前后端请求接口调用:

后端接口定义:

@RestController
@RequestMapping("/api/login")
public class LoginApiController {
    /**
     * 对于一个请求,怎样判断其是get请求还是post请求?
     * 1.当@RequestMapping("requestUrl")这种请求,因为没有用method=xxx来明确指定只能处理特定类型的http请求,
     * 所以get/post请求都能接收
     * 2.可以通过方法签名来判断(非硬性的规则)。例如获取数据(如列表、详情)的方法更可能是get请求,
     * 而提交数据(如创建、更新)的方法更可能是post请求。
     *
     * @RequestMapping(value="logout")可以缩写为@RequestMapping("logout"),在SpringMVC中两者是完全等价的
     * 我们一般写做@RequestMapping("logout"),使代码更简洁,同时也符合SpringMVC框架的约定和最佳实践。
     *
     * value用于指定请求的路径
     */
    @RequestMapping(value = "practiceLogout", method = RequestMethod.POST)
    @ResponseBody
    public Result practiceLogout() {
        Result result = new Result();
        // 后端具体代码省略
        result.setSuccess(true);
        result.setMessage("退出登录成功");
        return result;
    }
}

前端调用代码:

const _self = this;
_self.request.post({
  url: 'login/practiceLogout'
}).then((res) => {
  if (res.data.success) {
    // 后续前端处理代码省略
    console.log('退出登录成功');
  }
})

复盘工作-2024-08-13

1.表单详情页面:加个“清空”按钮:

<template>
  <f7-page>
    <f7-button class="clear-button">清空</f7-button>
  </f7-page>
</template>

<style scoped lang="scss">
.clear-button{
  // 弹性盒子布局
  display: flex;
  // 使内容靠右对齐
  justify-content: flex-end;
  // 上下居中对齐
  align-items: center;
}
</style>

复盘工作-2024-08-14

1.oracle日期时间转换:to_date(string, format_mask)函数:

/* oracle to_date(string, format_mask)函数, */
/* 参数解析:*/
/* 1. string:想要转换的(某种格式的)日期时间字符串,其格式必须与f指定的ormat_mask格式掩码定义的格式完全一致
(包括分隔符,如短横线-、斜杠/、空格等),例如'2024-08-13 21:00:00' */
/* format_mask:格式掩码,日期时间格式模型,指定了string应该如何被解析成日期时间类型。例如'yyyy-mm-dd hh24:mi:ss' */
/* 备注:一定不能直接用createDate > '2024-08-13 21:00:00' 这种,直接把date类型和字符串比较,数据库会报错格式转换错误
必须要通过to_date(string, format_mask)函数,明确指定日期转换格式,并强制将字符串转换为日期时间类型。
这种方式不受数据库隐式转换规则的影响。*/
select * from sys_notice t where t.create_date > to_date('2024-08-13 21:00:00', 'yyyy-mm-dd hh24:mi:ss');

复盘工作-2024-08-15

1.通过@Transient注解标记实体类中不在数据库中的虚拟字段:

@Transient /ˈtræn.zi.ənt/ 注解是JPA(Java Persistence API)的一部分

hibernate是jpa的一个实现,因此@Transient对hibernate项目同样适用

@Transient属性用于标记在实体类(Entity)的字段的get/set方法上,以指示该字段不是数据库表的一部分,

即hibernate在持久化到数据库时,会忽略这个被@Transient注解标记的字段。

不加@Transient的话,功能里查询数据库映射到实体时会报错ORA-00904: "WFTODOTASK0_"."FIELDNOTINDB": 标识符无效。

import javax.persistence.*;

/**
 * @Entity注解表明该类是一个JPA(Java Persistence API)实体的一部分
 * @Table注解指定了实体类对应的数据库表名和模式
 */
@Entity
@Table(name = "wf_todo_tasks", schema = "")
@SuppressWarnings("serial")
public class WfTodoTasksEntity implements java.io.Serializable {

    // 数据库中该实体对应的库表中并没有的虚拟字段
    private String fieldNotInDB;

    /**
     * @Transient  /ˈtræn.zi.ənt/ 注解是JPA(Java Persistence API)的一部分
     * hibernate是jpa的一个实现,因此@Transient对hibernate项目同样适用
     * @Transient属性用于标记在实体类(Entity)的字段的get/set方法上,以指示该字段不是数据库表的一部分,
     * 即hibernate在持久化到数据库时,会忽略这个被@Transient注解标记的字段。
     * 不加@Transient的话,功能里查询数据库映射到实体时会报错ORA-00904: "WFTODOTASK0_"."FIELDNOTINDB": 标识符无效。
     */
    @Transient
    public String getFieldNotInDB() {
        return fieldNotInDB;
    }

    @Transient
    public void setFieldNotInDB(String fieldNotInDB) {
        this.fieldNotInDB = fieldNotInDB;
    }
}

2.之前对JSTL(javascript standard tag library,即javascript标准标签库)标签:<c:if>的理解有误。更正:在JSP页面中其后面必须接el(expression language即解释型语言)表达式,不允许直接跟变量,即使变量是布尔类型也不行。

后端接口中跳视图前,向request中设置属性:

boolean isShowAddBtn = true;
            // 向request中设置属性,该请求跳转到的JSP页面可以访问该属性
            request.setAttribute("isShowAddBtn", isShowAddBtn);

跳转到的JSP页面中,使用<c:if>标签,通过判断request中带的属性值来控制界面元素显示:

<%-- <c:if test后面必须是el(expression language解释型语言)表达式,
   el表达式在JSP页面中必须用${}括起来,而不允许直接跟变量(即使变量是布尔类型也不行)--%>
   <c:if test="${ isShowAddBtn}">
    <t:dgToolBar title="新增" icon="icon-add" funname="add"></t:dgToolBar>
   </c:if>

复盘工作-2024-08-16

1.复盘插件方法checkUpdate:

/**
     * 插件方法checkUpdate做的主要操作是:
     * 1.发起网络请求到服务端,查询是否需要更新
     * 2.接收服务端响应
     * 3.将更新信息存储起来,用于后续处理(如responseJson)
     */

复盘工作-2024-08-17

1.cordova插件开发步骤:

/**
 * cordova插件开发步骤:
 * 1.开发插件的js代码:放在app/src/main/assets/www/plugins/文件夹下
 * 2.开发插件的原生代码:放在app/src/main/java/路径下,需继承CordovaPlugin类
 * 3.在cordova_plugins.js文件里定义插件并导出,该文件路径:app/src/main/assets/www/cordova_plugins.js
 * 4.在config.xml文件里定义js代码与原生代码的映射,改文件路径:app/src/main/res/xml/config.xml
 */

复盘工作-2024-08-18

1.了解cordova插件的基本架构、如何定义插件:

app/src/main/assets/www/下,cordova_plugins.js里定义:

{
        // "id"属性,通常前半部分是插件的npm包名(或类似的唯一标识符),英文句号后面,后半部分是插件中定义的JS对象的名称
        "id": "sys-cordova-plugin-appupdate.SysAppUpdate",
        // "file"属性,定义插件路径,插件位于app/src/main/assets/www/plugins文件夹下,ysAppUpdate.js加扩看命jseiwwjek
        "file": "plugins/sys-cordova-plugin-appupdate/SysAppUpdate.js",
        // "pluginId"属性,插件的唯一标识符,通常与npm包名一致
        "pluginId": "sys-cordova-plugin-appupdate",
        // 我一开始写错属性名了,应该是"clobber"属性 /ˈklɒb.ər/,定义前端h5里js代码调用原生插件时的别名
        // 定义JS对象将如何覆盖全局对象(在这个例子中是window对象)上的同名属性。
        // 在这里,sysAppUpdate.js里定义的任何内容都将覆盖或添加到window.SysAppUpdate上
        // 定义成数组,更明确灵活,可以很容易地扩展到包含多个需要被覆盖或删除的对象。
        "clobber": [
            "window.SysAppUpdate"
        ]
    },

app/src/main/res/xml/下,config.xml里定义:

<!-- app/src/main/res/xml/config.xml文件里,
        定义cordova应用需要的一个特性(feature),它告诉cordova将使用名为SysAppUpdate的插件,
        且对于android平台,这个插件的Java类名是com.customer.plugins.appupdate.AppUpdatePlugin

        feature name定义为cordova_plugins.js里插件id的后半部分,这种匹配是一种约定俗成。-->
    <feature name="SysAppUpdate">
        <param name="android-package" value="com.customer.plugins.appupdate.AppUpdatePlugin"/>
    </feature>

复盘工作-2024-08-19~2024-08-20

1.定义插件SysAppUpdate2:

app/src/main/assets/www/plugins/下,新建sys-cordova-plugin-appupdate2文件夹,然后新建SysAppUpdate2.js文件:

require('cordova/exec');实际上是在加载cordova的exec函数,但其可能不是从某个具体的js文件里加载的,而是由cordova的构建系统或运行时环境动态提供的。

exec()是cordova的核心函数。调用方法为:exec(成功回调函数, 失败回调函数, '原生代码服务名', '原生代码方法名', 参数数组)。

/* cordova.define('模块ID', 函数);
使用cordova模块定义机制来创建一个名为“sys-cordova-plugin-appupdate2”的模块。
这个模块定义了一个SysAppUpdate2类,该类封装与原生应用更新功能相关的JS接口。

cordova.define('模块ID', 函数);
接收两个参数:模块ID,本例中是"sys-cordova-plugin-appupdate2.SysAppUpdate2",和一个函数。

函数function(require, exports, module){} 定义了模块的内容。
该函数接收三个参数:require、exports、module。它们分别用于引入其他模块、导出当前模块的内容,以及提供对插件的更多控制。

模块ID:"sys-cordova-plugin-appupdate2.SysAppUpdate2"是唯一的标识符,用于在cordova项目中引用这个模块。
这个标识符通常反映了插件的名称和类名。
*/
cordova.define("sys-cordova-plugin-appupdate2.SysAppUpdate2", function(require, exports, module) {
/* require() require函数 通过require函数引入cordova的exec函数,
require('cordova/exec');实际上是在加载cordova的exec函数,
但其不是从某个具体的js文件里加载的,而是由cordova的构建系统或运行时环境动态提供的。

cordova/exec是JS与原生(例如Android、iOS)代码交互的桥梁 */
var exec = require('cordova/exec');

/* SysAppUpdate2类:定义了一个名为SysAppUpdate2的构造函数
 初始化变量:this.onprocess用于前端下载进度展示 */
var SysAppUpdate2 = function() {
    this.onprogress = null;
}

/* SysAppUpdate2.prototype表明checkUpdate是SysAppUpdate2的原型方法,
即所有通过new SysAppUpdate2()创建的实例都将继承这个方法。
接收参数params(传递给原生的额外参数)、successCallback(成功时的回调函数)、errorCallback(失败时的回调函数),
然后使用exec函数调用原生代码中的checkUpdate方法,并传递相应的参数和回调函数。
params是前端传来的对象,包含版本号等信息。*/
SysAppUpdate2.prototype.checkUpdate = function(params, successCallback, errorCallback) {
    /* 因为exec(成功回调函数, 失败回调函数, '原生代码服务名', '原生代码方法名', 参数数组)第5个参数为参数数组,
     所以这里将params组装为数组 */
    var args = [params];

    /* 当存在成功回调函数successCallback时,将结果result传入successCallback */
    var win = function(result) {
        successCallback && successCallback(result);
    }

    /* 当存在失败回调函数errorCallback时,将结果result传入errorCallback */
    var fail = function(result) {
        errorCallback && errorCallback(result);
    }

    /* 调用cordova的exec方法,请求SysAppUpdate插件的checkUpdate方法,传入args参数
    exec()是cordova的核心函数。
    调用方法为:exec(成功回调函数, 失败回调函数, '原生代码服务名', '原生代码方法名', 参数数组) */
    exec(win, fail, 'SysAppUpdate', 'checkUpdate', args);
}

/* 将SysAppUpdate2类导出为当前模块的内容。
这样其他JS代码就可以通过require函数引入这个模块,并使用SysAppUpdate2类了。*/
module.exports = SysAppUpdate2;

/* cordova.define("模块ID", 函数);最后一定要加; */
});

复盘工作-2024-08-21

1.app前端设置请求超时时间:

1.1在要传给axios的options参数中,设置超时时间为5秒;

1.2在 catch(error => {}) 里通过验证error.code来判断是否连接超时。ECONNABORTED是一个在Socket编程和网络通信中常用的错误码,其含义为“Software caused Connection abort"即”软件引起的连接终止“。其中E是Error或Error code的缩写

// 在要传给axios的options参数中,设置超时时间为5秒,
      options.timeout = 5000;

// 在 catch(error => {}) 里通过验证error.code来判断是否连接超时
            // ECONNABORTED是一个在Socket编程和网络通信中常用的错误码,其含义为“Software caused Connection abort"即”软件引起的连接终止“。
            // 其中E是Error或Error code的缩写
            if (error && error.code === 'ECONNABORTED') {
                errorf7('请求超时,请稍后再试');
            }

复盘工作-2024-08-22

1.将泛型参数改为类列表(列表每个元素为一个类类型):

1.1原excel导入工具类。Class<?>泛型参数

1.2基于业务需求,改为不同POJO的sheet页导入工具类。List<Class> pojoClassList,定义 类列表,列表每个元素是一个类类型

1.3分析入参是定义为Class[]类数组好,还是List<Class>类列表好:

集合框架提供了丰富的API来进行操作,所以该API使用List<Class>作为入参更好。可以增强易用性。

1.4分析是用List<Class<?>>还是用List<Class><Class>:

Class本身就表示可以是任何类型。所以这里用List<Class>更好。

这样提高可读性,使用者可立刻知道List中元素是Class;并且避免了不必要的通配符使用,不必要的通配符使用可能会使代码难理解和维护。

1.5另一个要复盘的点(备注该函数后来我去掉了,逻辑里不需要):

当函数入参要定义sheet页索引时,是否从0开始(还是从1开始定义)的分析:

基于Apache POI库内部是从0索引开始访问sheet页,那么我在提供封装API时,也用从0开始的索引,可以减少使用过程中的混淆和错误。

/**
     * 原excel导入工具类。Class<?>泛型参数
     *
     * @param inputstream
     * @param pojoClass
     * @param params
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> List<T> importExcelPrac(InputStream inputstream, Class<?> pojoClass, ImportParams params) throws Exception {
        // 省略具体实现,假设return null
        return null;
    }

    /**
     * 基于业务需求,改为不同POJO的sheet页导入工具类。List<Class> pojoClassList,定义 类列表,列表每个元素是一个类类型
     *
     * 1.分析入参是定义为Class[]类数组好,还是List<Class>类列表好:
     * 集合框架提供了丰富的API来进行操作,所以该API使用List<Class>作为入参更好。可以增强易用性。
     *
     * 2.分析是用List<Class<?>>还是用List<Class>:
     * Class本身就表示可以是任何类型。所以这里用List<Class>更好。
     * 这样(1)提高可读性,使用者可立刻知道List中元素是Class;(2)避免了不必要的通配符使用,不必要的通配符使用可能会使代码难理解和维护。
     *
     * 3.另一个要复盘的点(备注该函数后来我去掉了,逻辑里不需要):
     * 当函数入参要定义sheet页索引时,是否从0开始(还是从1开始定义)的分析:
     * 基于Apache POI库内部是从0索引开始访问sheet页,那么我在提供封装API时,也用从0开始的索引,可以减少使用过程中的混淆和错误。
     *
     * @param inputstream
     * @param pojoClassList
     * @param params
     * @return
     * @throws Exception
     */
    public Map<String, Object> importDiffSheetPrac(InputStream inputstream, List<Class> pojoClassList, ImportParams params) throws Exception {
        // 省略具体实现,假设return null
        return null;
    }

复盘工作-2024-08-23

1.axios请求后捕获异常相关:

catch(error)后,

1.1error.response:

1.1.1含义:当请求已成功发出,并且服务器响应了状态码(无论这个状态码表示成功还是失败),error.response就会被设置。它包含了服务器响应的详细信息,如状态码(status)、响应头(headers)、响应体(data)等。

1.1.2用途:主要用于处理服务器返回的错误状态码,比如404(未找到)、401(授权)、500(服务器内部错误)等。

1.2error.request:

1.2.1含义:当请求已经发起,但由于某些原因(如网络原因、请求取消等)未能成功接收服务器的响应时,error.request会被设置。它包含了请求的配置信息和请求对象本身,但不包含服务器的响应。

1.2.2用途:主要用于处理请求过程中的非服务器错误,比如网络问题、请求超时等。

1.3分析:所以可以通过error.response为空,而error.request不为空来判断出现了断网或服务器IP错误不可达或服务崩溃等情况。

复盘工作-2024-08-25~2024-08-27

1.复盘Promise及基础请求:

Promise:一种异步编程解决方案。代表一个尚未未完成但预期将来会完成的操作的最终结果。 其有3种状态: pending:等待中,默认的初始状态,既不是成功状态,也不是失败状态 fulfilled:已成功 rejected:已失败

new Promise(executor)是创建一个新的Promise实例的构造函数。 其中executor是一个执行器函数,它接收两个函数作为参数:resolve和reject。 resolve、reject是js引擎提供的函数,用于改变Promise的状态

resolve(value):当异步操作成功完成时,调用resolve函数,并将操作的结果作为参数传递出去。 此时Promise的状态由pending变为fulfilled,所有注册的.then()方法回调会按注册顺序依次被调用, 并将resolve的参数作为回调函数的参数。

reject(value):当当异步操作失败时,调用rejecte函数,并将错误的原因作为参数传递出去。 此时Promise的状态由pending变为rejected,所有注册的.catch()方法回调会按注册顺序依次被调用, 并将reject的参数作为回调函数的参数。

.then(onFulfilled,onRejected)是Promise实例的方法,用于指定当Promise成功(fulfilled)或失败(rejected)时的回调函数。 它返回一个新的Promise对象,以便可以链式调用。 onFulfilled:成功回调函数,(当Promise成功时的回调函数),它接收Promise操作成功的结果作为参数 onRejected(可选):即失败回调函数。(当Promise失败时的回调函数),它接收Promise操作失败的原因作为参数。失败回调函数这个参数是可选的。

.catch(onRejected)是.then(null,失败回调函数)的语法糖。用于指定Promise失败时的回调函数。 onRejected:失败回调函数。

/**
     * 基础请求
     *
     * Promise:一种异步编程解决方案。代表一个尚未未完成但预期将来会完成的操作的最终结果。
     *      其有3种状态:
     *      pending:等待中,默认的初始状态,既不是成功状态,也不是失败状态
     *      fulfilled:已成功
     *      rejected:已失败
     *
     * new Promise(executor)是创建一个新的Promise实例的构造函数。
     *      其中executor是一个执行器函数,它接收两个函数作为参数:resolve和reject。
     *      resolve、reject是js引擎提供的函数,用于改变Promise的状态
     *
     * resolve(value):当异步操作成功完成时,调用resolve函数,并将操作的结果作为参数传递出去。
     *      此时Promise的状态由pending变为fulfilled,所有注册的.then()方法回调会按注册顺序依次被调用,
     *      并将resolve的参数作为回调函数的参数。
     *
     * reject(value):当当异步操作失败时,调用rejecte函数,并将错误的原因作为参数传递出去。
     *      此时Promise的状态由pending变为rejected,所有注册的.catch()方法回调会按注册顺序依次被调用,
     *      并将reject的参数作为回调函数的参数。
     *
     * .then(onFulfilled,onRejected)是Promise实例的方法,用于指定当Promise成功(fulfilled)或失败(rejected)时的回调函数。
     *      它返回一个新的Promise对象,以便可以链式调用。
     *      onFulfilled:成功回调函数,(当Promise成功时的回调函数),它接收Promise操作成功的结果作为参数
     *      onRejected(可选):即失败回调函数。(当Promise失败时的回调函数),它接收Promise操作失败的原因作为参数。失败回调函数这个参数是可选的。
     *
     * .catch(onRejected)是.then(null,失败回调函数)的语法糖。用于指定Promise失败时的回调函数。
     *      onRejected:失败回调函数。
     *
     * @param options
     */
    basePrac(options) {
      let msg = '网络异常,请稍后重试';
      // 一开始这里我写错了,要通过store.state.network.isOnline===false来判断没网,而不是!store.state.network.isOnline
      // store.state.network.isOnline的值是null(浏览器运行时无该值)、true、false
      if (store && store.state && store.state.network && store.state.network.isOnline === false) {
          showErrorTop(msg);
          // 一开始我这里漏了,需要直接返回
          // Promise.reject()是Promise的静态方法,用于快速返回一个已经处于rejected状态的Promise实例
          // Promis.reject()接收一个参数,通常是一个错误对象或错误信息
          return Promise.reject('未连接网络,不再继续发送请求');
      }
      window.preloader.show();
      if (!commonUtils.isStrIsNull(userInfo.getJwt())) {
          // 一开始我写的是错的:options.headers['token'] = userInfo.getJwt();
          // 错误原因是这时options.headers是null,直接对null设置token属性,会报错。
          // 需要写成如下:设置HTTP请求头(header):options.headers = {"token": userInfo.getJwt()};
          // 或者如下代码:
          // if (!options.headers) {
          //     options.headers = {};
          // }
          // options.headers['token'] = userInfo.getJwt();
          // 这样后端拦截器里才能从请求头中获取token:String token = request.getHeader("token");
          options.headers = {"token": userInfo.getJwt()};
      }
      if (commonUtils.isStrIsNull(options.timeout)) {
          options.timeout = 15000;
      }
      options.url = request.api_pre() + options.url + request.version(options.url);
      return new Promise((resolve, reject) => {
          axios(options).then(response => {
              debugger
              // 一开始我没写这步。需要获取后端响应里的token,存入localstorage
              // 后端通过response.setHeader("x-auth-token", token);向响应中设置的token
              userInfo.setJwt(response.headers['x-auth-token'])
              resolve(response);
          }).catch(error => {
              if (!error.response) {
                  if (error.request) {
                      showErrorTop(msg);
                      reject(msg);
                  }
                  return;
              }
              // 后续逻辑省略
              reject(error);
          }).finally(() => {
              window.preloader.hide();
          })
      })
    },

复盘工作-2024-08-28~2024-08-30

1.复盘app登录相关前后端传参:

后端:

@RestController
@RequestMapping("/api/login")
public class LoginPracApiController {
    /**
     * produces 常写在@RequestMapping后面,用来定义接收的请求体的格式
     * produces = "application/json;charset=UTF-8",说明接收的是JSON类型的请求体,UTF-8字符集
     *
     * "@RequestBody LoginInfo loginInfo"表示SpringMVC会自动从HTTP请求体中解析JSON并填充到LoginInfo对象中,
     * 即前端如下代码里的loginInfo:
     * this.request.post({
     *     url: '/api/login',
     *     data: this.loginInfo
     * })
     * data被序列化为JSON并发送
     *
     * "@ResponseBody"和@RestController一起工作,使得返回值(AjaxJson对象)自动被序列化未JSON格式,并写入HTTP响应体中。
     * springmvc的@RequestBody注解和底层的消息转换器(如JackSon)会自动将AjaxJson对象序列化为JSON格式的字符串,
     * 并将其放入响应体中
     *
     * 然后前端response.data获取的就是这里的AjaxJson对象,这是前端axios或浏览器自动实现的,
     * 自动解析响应体中的JSON字符串为Javascript对象。这个对象在前端通过response.data来访问,
     * 且它的结构和后端AjaxJson类的属性意义对象。我这里记住即可。
     *
     * @param loginInfo
     * @param response
     * @return
     */
    @RequestMapping(method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
    @ResponseBody
    public AjaxJson checkUser(@RequestBody LoginInfo loginInfo, HttpServletResponse response) {
        System.out.println("loginInfo: " + loginInfo);
        AjaxJson j = new AjaxJson();
        SysMessageInfoEntity infoEntity = new SysMessageInfoEntity();
        infoEntity.setId("practiceId");
        infoEntity.setMessageContent("测试消息内容");
        j.setObj(infoEntity);
        return j;
    }
}

前端:

signIn() {
              const _self = this;
              // 仅测试,不做加密处理
              let loginInfo = {
                'username': 'tom',
                'password': 'helloWorld'
              }
              _self.request.post({
                url: 'login',
                data: loginInfo
              }).then(response => {
                debugger;
                // 前端response.data获取的就是后端返回的AjaxJson对象,这是前端axios或浏览器自动实现的,
                // 自动解析响应体中的JSON字符串为Javascript对象
                if (response.data) {
                  if (response.data.success) {
                    let message = response.data.obj;
                    console.log(message);
                  }
                }
              })
          },

 

posted on 2024-08-02 12:28  平凡力量  阅读(5)  评论(0编辑  收藏  举报