复盘工作-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); } } }) },