单元测试实战(五)普通类的测试
为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。
本文中的测试均基于JUnit5。
单元测试实战(一)Controller 的测试
单元测试实战(二)Service 的测试
单元测试实战(三)JPA 的测试
单元测试实战(四)MyBatis-Plus 的测试
单元测试实战(五)普通类的测试
单元测试实战(六)其它
概述
普通类或曰POJO的测试,是最简单的一种情况,大多数情况下只使用JUnit即可。万一有不易实例化的外部依赖,也可以用Mockito的@Mock来模拟。这类测试一般应脱离Spring上下文来进行。
需要的话,在每个测试之前应清理/重置测试数据,一般为方法参数或待测类实例;断言应主要检查待测类的行为是否符合预期。
依赖
大多数普通类测试只依赖JUnit,但作为一般实践,我们通常也带上Spring Boot自己的测试工具集。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><scope>test</scope>
</dependency>
示例
以下是一个BigDecimal的包装类,实现了一些BigDecimal的最佳实践;该类是我们的待测试类:
package com.aaa.sdk.utils;import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Objects;/*** BigDecimal的包装类,封装了以下实践:* <li>不允许null值(使用工厂方法创建实例时会直接报错)。</li>* <li>避免double构造传参造成精度丢失。</li>* <li>封装equals,强制使用compareTo来比较。</li>* <li>封装格式化输出(补零对齐,默认两位)。</li>* <li>封装加减乘除,尤其除法,强制使用BigDecimal的三参方法避免无限小数报错。</li>* <li>直观的判断正数、负数、零的方法。</li>* <br/>** 本类设计为不可变对象,非读方法(加减乘除、取反、四舍五入等)都会产生新对象。** @author ioriogami**/
public class DecimalWrapper implements Comparable<DecimalWrapper> {/*** 默认精度,保留两位。*/public static final int DEFAULT_SCALE = 2;private final BigDecimal decimal;private DecimalWrapper(BigDecimal decimal) {Objects.requireNonNull(decimal);this.decimal = decimal;}// 以下工厂方法/*** 通过一个BigDecimal对象构造DecimalWrapper实例。* @param decimal 本wrapper代表的BigDecimal对象*/public static DecimalWrapper of(BigDecimal decimal) {return new DecimalWrapper(decimal);}/*** 通过一个String实例构造DecimalWrapper实例。* @param s 字符串数值*/public static DecimalWrapper of(String s) {return of(new BigDecimal(s));}/*** 通过一个double实例构造DecimalWrapper实例。* @param d double数值*/public static DecimalWrapper of(double d) {return of(new BigDecimal(String.valueOf(d)));}/*** 通过一个float实例构造。* @param f float数值*/public static DecimalWrapper of(float f) {return of(new BigDecimal(String.valueOf(f)));}// 以下一般接口/*** 获取底层的BigDecimal对象*/public BigDecimal toBigDecimal() {return decimal;}/*** 获取double值。*/public Double toDouble() {return decimal.doubleValue();}@Overridepublic String toString() {return decimal.toPlainString();}@Overridepublic int hashCode() {return decimal.hashCode();}@Overridepublic boolean equals(Object obj) {if (obj instanceof DecimalWrapper) {DecimalWrapper other = (DecimalWrapper) obj;return decimal.compareTo(other.decimal) == 0;}return false;}@Overridepublic int compareTo(DecimalWrapper that) {if (that == null) return 1;return this.decimal.compareTo(that.decimal);}// 以下格式化输出/*** 四舍五入保留两位。* @return 一个新的DecimalWrapper对象*/public DecimalWrapper round() {return round(DEFAULT_SCALE, RoundingMode.HALF_UP);}/*** 四舍五入保留i位。* @param scale 精度,即i,保留几位。* @return 一个新的DecimalWrapper对象*/public DecimalWrapper round(int scale) {return round(scale, RoundingMode.HALF_UP);}/*** m舍n入保留i位。** @param scale 精度,即i,保留几位。* @param mode 舍入策略(m和n)。* @return 一个新的DecimalWrapper对象*/public DecimalWrapper round(int scale, RoundingMode mode) {return of(decimal.setScale(scale, mode));}/*** 获得四舍五入保留两位(强制补0)后的字符串。*/public String format() {return new DecimalFormat("0.00").format(decimal.setScale(DEFAULT_SCALE, RoundingMode.HALF_UP));}/*** 获得四舍五入保留i位(强制补0)后的字符串。* @param scale 精度,即i,保留几位。*/public String format(int scale) {return format(scale, RoundingMode.HALF_UP);}/*** 获得m舍n入保留scale位(强制补0)后的字符串。不会影响本身的值。* @param scale 精度,即i,保留几位。* @param mode 舍入策略(m和n),若为null则默认四舍五入。* @return 格式化后的字符串。*/public String format(int scale, RoundingMode mode) {if (scale <= 0) throw new IllegalArgumentException("精度必须大于0");if (mode == null) mode = RoundingMode.HALF_UP;StringBuilder buff = new StringBuilder("0.");for (int i = 0; i < scale; i++) {buff.append("0");}return new DecimalFormat(buff.toString()).format(decimal.setScale(scale, mode));}// 以下加减乘除、取反/*** 加法。*/public DecimalWrapper add(DecimalWrapper other) {if (other == null) throw new IllegalArgumentException("操作数为null,无法进行加法运算。");return of(decimal.add(other.decimal));}/*** 减法。*/public DecimalWrapper subtract(DecimalWrapper other) {if (other == null) throw new IllegalArgumentException("操作数为null,无法进行减法运算。");return of(decimal.subtract(other.decimal));}/*** 乘法。*/public DecimalWrapper multiply(DecimalWrapper other) {if (other == null) throw new IllegalArgumentException("操作数为null,无法进行乘法运算。");return of(decimal.multiply(other.decimal));}/*** 除法。*/public DecimalWrapper divide(DecimalWrapper other) { // 使用三参除法,避免结果为无限小数时报错。return divide(other, DEFAULT_SCALE, RoundingMode.HALF_UP);}/*** 除法,指定精度和舍入策略。*/public DecimalWrapper divide(DecimalWrapper other, int scale, RoundingMode mode) {if (other == null) throw new IllegalArgumentException("操作数为null,无法进行除法运算。");if (scale <= 0) throw new IllegalArgumentException("精度必须大于0");if (mode == null) mode = RoundingMode.HALF_UP;return of(decimal.divide(other.decimal, scale, mode));}/*** 取反。*/public DecimalWrapper negate() {return of(decimal.negate());}/*** 判断是否零值,不管到底是0.0、0.00还是0.0000..*/public boolean isZero() {return decimal.signum() == 0;}/*** 判断是否正数。*/public boolean isPositive() {return decimal.signum() == 1;}/*** 判断是否正数。*/public boolean isNegative() {return decimal.signum() == -1;}
}
以下是对DecimalWrapper进行测试的测试类:
package com.aaa.sdk.utils;import java.math.BigDecimal;
import java.math.RoundingMode;import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.*;class DecimalWrapperTest {@Testvoid testConstructBigDecimal() {DecimalWrapper d = DecimalWrapper.of(new BigDecimal("1.11"));assertEquals("1.11", d.format());}@Testvoid testConstructDouble() {DecimalWrapper d = DecimalWrapper.of(1.11);assertEquals("1.11", d.format());}@Testvoid testConstructString() {DecimalWrapper d = DecimalWrapper.of("1.11");assertEquals("1.11", d.format());}@Testvoid testConstructFloat() {DecimalWrapper d = DecimalWrapper.of(1.1f);assertEquals("1.10", d.format());}@Testvoid testConstructNullParam() {try {DecimalWrapper.of((String) null);fail("should not get here!");} catch (NullPointerException npe) {}try {DecimalWrapper.of((Double) null);fail("Should not get here!");} catch (NullPointerException npe) {}try {DecimalWrapper.of((Float) null);fail("Should not get here!");} catch (NullPointerException npe) {}try {DecimalWrapper.of((BigDecimal) null);fail("Should not get here!");} catch (NullPointerException npe) {}}@Testvoid testComparison() {DecimalWrapper d1 = DecimalWrapper.of(1.1);DecimalWrapper d2 = DecimalWrapper.of(1.2);DecimalWrapper d3 = DecimalWrapper.of(1.0);assertTrue(d1.compareTo(d2) < 0);assertTrue(d2.compareTo(d1) > 0);assertTrue(d3.compareTo(d1) < 0);assertTrue(d3.compareTo(d2) < 0);DecimalWrapper d4 = DecimalWrapper.of("1.00");assertTrue(d3.compareTo(d4) == 0);DecimalWrapper d5 = null;assertTrue(d3.compareTo(d5) > 0);}@Testvoid testToDecimal() {DecimalWrapper d1 = DecimalWrapper.of(1.11);assertEquals(d1.toBigDecimal(), new BigDecimal("1.11"));}@Testvoid testToDouble() {DecimalWrapper d1 = DecimalWrapper.of(1.11);assertEquals(d1.toDouble(), 1.11);}@Testvoid testEquals() {DecimalWrapper d1 = DecimalWrapper.of(1.12345);DecimalWrapper d2 = DecimalWrapper.of(1.12345f);DecimalWrapper d3 = DecimalWrapper.of("1.12345");DecimalWrapper d4 = DecimalWrapper.of(new BigDecimal("1.12345"));assertEquals(d1, d2);assertEquals(d2, d3);assertEquals(d3, d4);}@Testvoid testRoundDefault() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);DecimalWrapper d2 = DecimalWrapper.of(1.12385f);DecimalWrapper d3 = DecimalWrapper.of("1.12385");DecimalWrapper d4 = DecimalWrapper.of(new BigDecimal("1.12385"));assertEquals(d1.round(), DecimalWrapper.of("1.12"));assertEquals(d2.round(), DecimalWrapper.of("1.12"));assertEquals(d3.round(), DecimalWrapper.of("1.12"));assertEquals(d4.round(), DecimalWrapper.of("1.12"));}@Testvoid testRound() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);DecimalWrapper d2 = DecimalWrapper.of(1.12385f);DecimalWrapper d3 = DecimalWrapper.of("1.12385");DecimalWrapper d4 = DecimalWrapper.of(new BigDecimal("1.12385"));assertEquals(d1.round(3), DecimalWrapper.of("1.124"));assertEquals(d2.round(3), DecimalWrapper.of("1.124"));assertEquals(d3.round(3), DecimalWrapper.of("1.124"));assertEquals(d4.round(3), DecimalWrapper.of("1.124"));assertEquals(d4.round(3, RoundingMode.DOWN), DecimalWrapper.of("1.123"));}@Testvoid testFormat() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);assertEquals(d1.format(), "1.12");assertEquals(d1.format(3), "1.124");assertEquals(d1.format(3, RoundingMode.DOWN), "1.123");}@Testvoid testAdd() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);DecimalWrapper d2 = DecimalWrapper.of(1.12385);assertEquals(DecimalWrapper.of("2.24770"), d1.add(d2));DecimalWrapper d3 = DecimalWrapper.of(0);assertEquals(DecimalWrapper.of("1.12385"), d3.add(d2));assertTrue(d3.add(d3).isZero());}@Testvoid testSubtract() {DecimalWrapper d1 = DecimalWrapper.of(2.24770);DecimalWrapper d2 = DecimalWrapper.of(1.12385);assertEquals(DecimalWrapper.of("1.12385"), d1.subtract(d2));DecimalWrapper d3 = DecimalWrapper.of(0); // change to: 0.0assertEquals(DecimalWrapper.of("1.12385"), d2.subtract(d3));assertTrue(d2.subtract(d2).isZero());assertTrue(d3.subtract(d3).isZero());}@Testvoid testMultiply() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);DecimalWrapper d2 = DecimalWrapper.of(1.12385);assertEquals(DecimalWrapper.of("1.2630388225"), d1.multiply(d2));DecimalWrapper d3 = DecimalWrapper.of(0.00);assertTrue(d2.multiply(d3).isZero());DecimalWrapper d4 = DecimalWrapper.of(-1);assertEquals(DecimalWrapper.of("-1.12385"), d1.multiply(d4));}@Testvoid testDivide() {DecimalWrapper d1 = DecimalWrapper.of(10.0);DecimalWrapper d2 = DecimalWrapper.of(3);assertEquals(DecimalWrapper.of(3.33), d1.divide(d2));DecimalWrapper d3 = DecimalWrapper.of(0);assertTrue(d3.multiply(d1).isZero());try {d1.divide(d3);fail("divide by zero, should not get here!");} catch (Exception e){}}@Testvoid testNegate() {DecimalWrapper d1 = DecimalWrapper.of(-1.12385);assertTrue(d1.isNegative());assertFalse(d1.isZero());assertFalse(d1.isPositive());DecimalWrapper d2 = d1.negate();assertTrue(d2.isPositive());assertFalse(d2.isZero());assertFalse(d2.isNegative());assertTrue(d1.add(d2).isZero());}}
测试类说明:
测试类的代码和被测类的代码都比较直观,不需做过多说明。
在该类中,我们为被测类的每个公共方法都创建了测试方法,且只用到了JUnit的@Test和Assertions;如果需要事前准备和事后清理工作,还可以加上@BeforeEach、@AfterEach、@BeforeAll、@AfterAll等方法。
第37行的testConstructNullParam是负面测试,测试当传入null值是不允许的,会抛出NullPointerException。
第101行的testRoundDefault、第113行的testRound,是对同一个方法的不同情况(精度)的测试。
私有方法的测试
私有方法不需要测试。对私有方法的测试,应通过调用它的公有方法的测试来进行。直接测试私有方法往往是一种代码“坏味道”。
虽然但是,有时候确实想给私有方法写一个测试,也不是做不到:
package com.aaa.api.auth.filter;import com.aaa.sdk.utils.Utils;
import org.junit.jupiter.api.Test;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;import static org.junit.jupiter.api.Assertions.*;
class AuthNFilterTest {@Testvoid testExtractUsernameFromToken() throws InvocationTargetException, IllegalAccessException {String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...";AuthNFilter filter = new AuthNFilter();Method m = Utils.doGetMethod(AuthNFilter.class, "extractUsernameFromToken", String.class);//long start = System.currentTimeMillis();String user = (String) m.invoke(filter, token);//System.out.println("User is " + user + ", used " + (System.currentTimeMillis() - start) + " ms");assertEquals("ioriogami", user);}
}
第15行,我们new了一个待测试对象。
第16行,我们用反射获取了AuthNFilter类的extractUsernameFromToken私有方法(该方法接受一个String类型的参数),然后在第18行对其进行调用。
如需对构造方法、私有方法、静态方法、final类和方法进行mock,可以使用powermock提供的增强版Mockito:PowerMockito。
总结
普通类的测试以JUnit简单测试为主,一般不需要Spring上下文。
每一个public方法都有至少一个测试;对不同的情况/分支,建议有多个测试;最好也有负面测试。当然,一个完美的测试类应该测试每个方法的正面行为、负面行为、边缘情况;并应尽可能覆盖所有分支。但在有限的时间内,我们应识别最重要的方面进行测试。
单元测试针对单个类,原则上测试类与被测类一对一。当然也可以针对一组紧密相关的类/接口编写单元测试,比如pagewindow是一个实现无界分页的小模块,由几个接口和类组成,它的单元测试就是针对这一组类的。
Mock的原理是采用字节码生成的方式对要mock的类进行sub-class,然后生成这个子类的对象,以此达到对其行为进行订制的目的。显然,这种方式会受到Java继承机制的限制:静态类/方法、final类/方法、构造器、私有方法等都不能继承,因此就难以订制。
Mockito在2.1.0中加入了对final类/方法的支持。见 https://www.cnblogs.com/yuluoxingkong/p/14813558.html。
powermock提供的增强版Mockito:PowerMockito 则对这些方面提供了全面的支持。
相关文章:
单元测试实战(五)普通类的测试
为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。 本文中的测试均基于JUnit5。 单元测试实战(一)Controller 的测试 单元测试实战(二)Service 的测试 单元测试实战&am…...
js 迭代器iterator 和 生成器Generator 10
✌ 文章目录 一、迭代器 iterator二、使用步骤1.引入库2.读入数据 总结 一、迭代器 iterator 迭代器是帮助我们对某个数据结构进行遍历的对象 迭代器:是一个对象,帮助我们对某个数据结构进行遍历 迭代器要符合迭代器协议,必须要有一个特定的n…...

100套Axure RP大数据可视化大屏模板及通用组件库
106套Axure RP大数据可视化大屏模板包括了多种实用美观的可视化组件库及行业模板库,行业模板涵盖:金融、教育、医疗、政府、交通、制造等多个行业提供设计参考。 随着大数据的发展,可视化大屏在各行各业得到越来越广泛的应用。可视化大屏不再…...
【OpenGauss源码学习 —— 执行算子(Append算子)】
执行算子(Append算子) Append 算子ExecInitAppend 函数exec_append_initialize_next 函数ExecAppend 函数ExecEndAppend 函数ExecReScanAppend 函数 声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和…...

Java(一)(引用类型的参数在传递,方法重载,面向对象编程基础)
基本类型和引用类型的参数在传递的时候有什么不同? 基本类型的值传递:参数传输存储的数据值 引用类型的值传递:参数传输存储的地址值 传递数组名字的时候,传递的是数组的地址,change方法可以通过地址直接访问我们在堆内存中开辟的数组,然后改变数组,数组中的元素发生变化 方…...
Vue第1天:特性概览
文章目录 Vue.js 简介 Vue的特性 如何使用Vue 安装Vue 通过CDN引入 使用npm 创建Vue实例 结语 Vue.js 简介 Vue.js(通常简称为Vue)是一款流行的JavaScript框架,专注于构建用户界面。它的设计灵感来自于现代的JavaScript框架…...
C++语法基础知识面经汇总
背景:汇总了网上C常考的基础知识,方便复习 1,static关键字 static可以用于成员变量,或者成员函数。存储空间在静态存储区(编译器会将其初始化为0,对应的存储空间直到程序执行结束才会释放)&…...
AM@幂级数性质@幂级数和函数求解
文章目录 幂级数性质四则运算性质分析性质求解和函数例例 幂级数性质 和多项式有相似的性质本文介绍用幂级数的性质求解幂级数和函数的两个例子 四则运算性质 若幂级数 ∑ n 0 ∞ a n x n \sum_{n0}^{\infin}a_{n}x^{n} ∑n0∞anxn(1)的收敛半径为 R 1 R_1 R1,和函数为…...
PHP低版本安全问题
目录 1、PHP弱类型问题 1.1 MD5、 SHA1 弱比较问题 1.2 数组 0 1)函数无法处理数组,返回0 2)strcmp 2、特殊字符串导致的问题 2.1 "ffifdyop" 与 md5(string,raw) 2.2 ereg函数漏洞:00 截断 3、正则匹配问…...
结构体——C语言初阶
一.结构体的声明: (1)结构的基础知识: 结构体是一种构造数据类型把不同类型的数据组合成一个整体结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量需要注意的是,结构体是一种…...

基于django电影推荐系统
基于django电影推荐系统 摘要 该Django电影推荐系统是一个简单而基础的框架,旨在展示系统的基本组件。系统包括两个主要模型,即Movie和Rating,用于存储电影信息和用户评分。视图层包括展示电影列表和电影详情的功能,使用模板进行页…...

【问题处理】WPS提示不能启动此对象的源应用程序如何处理?
哈喽,大家好,我是雷工! 最近在用WPS打开word文件中,插入的Excel附件时,无法打开,提示:“不能启动此对象的源应用程序”。 经过上网查找处理办法,尝试解决,现将解决过程记…...

UE 程序化网格 计算横截面
首先在构造函数内加上程序化网格,然后复制网格体到程序化网格组件上,将Static Mesh(类型StaticMeshActor)的静态网格体组件给到程序化网格体上 然后把StaticMesh(类型为StaticMeshActor)Instance暴漏出去 …...

【Spring】IoC容器的一些总结与补充
文章目录 1. 创建容器的两种方式相对路径导入绝对路径导入 2. 获取Bean的三种方式getBean后强转类型getBean内写明类别根据类别获取bean 3. 容器层次结构4. BeanFactory5. bean的总结6. 注入的总结 1. 创建容器的两种方式 相对路径导入 ApplicationContext ctx new ClassPat…...

Java GUI实现五子棋游戏
五子棋是一种双人对弈的棋类游戏,通常在棋盘上进行。棋盘为 1515 的方格,黑白双方各执棋子,轮流在棋盘的格点上落子,先在横、竖、斜线上形成五个相连的同色棋子者获胜。五子棋规则简单,易学难精,兼具攻防和…...

Python 集成 Nacos 配置中心
Python 集成 Nacos 配置中心 下载 Nacos 官方 pyhton 库 pip install nacos-sdk-python # 指定国内阿里云镜像源 pip3 install nacos-sdk-python -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com配置 Nacos 相关信息 Global:nacos:port: 8848…...
Debian 11 更新 Node.js 版本
发布于 2023-07-14 在 https://chenhaotian.top/debian/d-upd-nodejs/ 步骤 从 NodeSource 服务下载需要的 Node.js 安装脚本。注意更换版本号。当前的 LTS 版本是 18.x curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -现在可以直接从 apt 安装࿰…...

python 对图像进行聚类分析
import cv2 import numpy as np from sklearn.cluster import KMeans import time# 中文路径读取 def cv_imread(filePath, cv2_falgcv2.COLOR_BGR2RGB): cv_img cv2.imdecode(np.fromfile(filePath, dtypenp.uint8), cv2_falg) return cv_img# 自定义装饰器计算时间 def…...

程序员导航站
探路者 hello.alluniverse.vip 开发者导航 - Pro Developer网站导航 探路者是一款极简导航工具,致力于收录的每个站点都有其独特的作用。同时支持自定义导航,让用户快速实现个性化的导航站点。 特性概述 免费ChatGPT 装机必备 开发工具 Git精选项目 …...

BIO、NIO、AIO三者的区别及其应用场景(结合生活例子,简单易懂)
再解释三者之前我们需要先了解几个概念: 阻塞、非阻塞:是相较于线程来说的,如果是阻塞则线程无法往下执行,不阻塞,则线程可以继续往下 执行。同步、异步:是相较于IO来说的,同步需要等待IO操作完…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...