REST Assured接口自动化测试分层实践:从环境搭建到工程维护

发布时间:2026/9/7 23:12:27

REST Assured接口自动化测试分层实践:从环境搭建到工程维护
先说个碰了无数次的小事标题里的REST-assure其实是手误正确拼写是REST Assured。这个拼写问题在技术社区里特别常见但不影响它是Java生态里做接口自动化测试最顺手的库。我这次拿它写了个分层的小练习正好把从环境搭建、代码结构到日常维护的完整思路捋一遍适合刚接触接口自动化测试、想从脚本能跑过渡到工程能维护的朋友参考。接口自动化测试说起来不复杂对被测系统的HTTP接口发请求校验返回的状态码、响应体、响应时间跑在CI里每天盯回归。但真正写好它的人不多因为大多数人把它当脚本写而不是当代码工程写。这篇博文就围绕REST Assured Java TestNG这套组合重点讲清楚分层这件事——POJO层、接口封装层、数据层、用例层各管什么代码怎么写坑怎么避。1. 为什么是REST Assured先搞清楚工具选型的底层逻辑1.1 它到底解决了什么痛点在没有REST Assured之前用Java写接口测试最痛苦的事是你要用HttpClient拼URL、拼Header、拼请求体再把响应的JSON字符串手动解析成对象然后一个个字段去比较。哪怕只测一个最简单的登录接口光样板代码就得写三四十行而且每多一个接口这段代码就要复制粘贴一份改个参数要全局搜索替换。REST Assured做的事情是把发HTTP请求、解析响应、写断言这三件事压缩成一段接近自然语言的链式调用。它本身是一个基于Groovy的DSL但在Java里用起来完全没有违和感底层帮你处理了HTTP连接、JSON序列化、JSONPath提取这些脏活。你只需要关心接口的业务逻辑长什么样。另外它还解决了断言难写的问题。原生的JUnit断言只能比较两个对象是否相等但接口测试需要的断言往往是状态码是不是201返回的列表里有没有name等于Tom的元素响应时间是不是小于2秒这类结构化校验。REST Assured内置了Hamcrest Matcher可以直接对JSON结构做链式断言这个体验非常接近Postman里的断言但比Postman更适合写进代码仓库做版本管理。1.2 与HttpClient、OkHttp、Postman的对比很多人会问Java里做HTTP请求明明有HttpClient和OkHttp为什么要再学一个REST Assured这个问题的答案其实很简单那些库是给业务代码调用第三方接口用的而REST Assured是给测试代码验证接口正确性用的。我把几个方案的差异整理成了一张表方便大家根据自己的场景选方案适合场景优势劣势REST Assured接口自动化测试链式DSL流畅、断言体系完整、JSONPath提取方便、社区教程多依赖Groovy底层出问题要懂一点Groovy和HTTP原理HttpClient业务代码调用HTTP灵活、底层可控、性能好写测试用例样板代码太多断言要自己造轮子OkHttpAndroid/高性能场景连接池、拦截器、性能优秀偏底层纯测试场景效率不高Postman Newman手工调试、快速冒烟上手快、图形化界面友好脚本表达能力弱、不好做复杂数据驱动、代码仓库协作不方便一句话总结如果你们团队是Java技术栈又要长期维护一套接口自动化测试REST Assured基本是首选。如果你只是想临时验证一个接口通不通用Postman没必要上代码。1.3 工程依赖与版本确认先说一个容易踩的坑REST Assured在3.0以前groupId是com.jayway.restassured3.0之后改成了io.rest-assured。现在新项目统一用后者网上很多老教程还写着旧的依赖坐标复制进去会导致依赖下载失败。我这次练习用的版本组合是JDK 8 REST Assured 5.4.0 TestNG 7.10.2 Jackson 2.17.1都是当前比较稳定的版本。Maven依赖配置如下properties rest-assured.version5.4.0/rest-assured.version testng.version7.10.2/testng.version jackson.version2.17.1/jackson.version /properties dependencies !-- REST Assured 核心库 -- dependency groupIdio.rest-assured/groupId artifactIdrest-assured/artifactId version${rest-assured.version}/version scopetest/scope /dependency !-- 测试框架用 TestNG 而不是 JUnit后面解释为什么 -- dependency groupIdorg.testng/groupId artifactIdtestng/artifactId version${testng.version}/version scopetest/scope /dependency !-- JSON 序列化与反序列化 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version${jackson.version}/version scopetest/scope /dependency /dependencies为什么用TestNG而不用JUnit两个框架都能跑测试但TestNG天然支持DataProvider数据驱动、groups分组、dependsOnMethods依赖测试这些特性非常适合接口自动化测试的场景。JUnit 5虽然也能做参数化测试但整体设计还是偏单元测试分工没那么清晰。当然这个选择不是绝对的关键是团队统一。2. 分层设计让测试代码从一次性脚本变成可维护工程2.1 不分层会怎样我在不少团队里见过所谓的接口自动化测试代码打开就是一个几万行的测试类一个方法对应一个接口请求参数写死在方法里断言直接对着JSON字符串做contains判断。这种脚本在接口数量少于20个的时候还能勉强维护一旦接口涨到上百个就会出现几个非常要命的问题第一个接口地址散落在一堆测试方法里后端改个URL前缀你要全局搜索替换改漏一个就产生一条虚假的失败用例第二个请求体构造和接口调用逻辑混在断言里测试代码读起来极其痛苦根本分不清哪些是前置准备、哪些是真正要验证的东西第三个接口返回的数据结构一变所有涉及这个接口的用例都要跟着改但同一个接口可能被十几个用例调用改起来就是一场灾难。分层想解决的问题本质上是让变化的成本可控。接口地址变了你只改一处配置请求体数据结构变了你只改POJO和封装方法测试数据变了你只改数据文件。测试用例本身应该尽量稳定因为它描述的是业务规则而不是HTTP协议细节。2.2 四层结构职责划分我做这个练习时把工程分成了四层加一个全局配置对应关系如下层级包名职责典型内容测试用例层cases描述业务场景、编排操作步骤、写断言新增用户成功、查询用户列表接口封装层api把HTTP请求封装成可复用的方法UserApiClient类方法对应增删改查数据准备层utils / testdata构造测试数据隔离用例与数据细节DataProvider、JSON数据文件实体层pojo定义请求/响应的Java对象结构User、LoginRequest、LoginResponse全局配置config管理BaseURL、超时、公共HeaderTestConfig类依赖方向严格自上而下cases依赖api、utils、pojoapi依赖pojo和configutils依赖pojo和config。禁止跨层调用比如cases直接去拼HTTP请求或者api层里写断言逻辑这些都是分层腐烂的征兆。2.3 包结构与依赖方向实际的包结构长这样src/test/java ├── config/ │ └── TestConfig.java ├── pojo/ │ ├── User.java │ ├── LoginRequest.java │ └── LoginResponse.java ├── api/ │ └── UserApiClient.java ├── utils/ │ ├── DataProviderUtils.java │ └── LogUtils.java └── cases/ ├── UserApiTest.java └── LoginTest.java src/test/resources/ ├── testdata/ │ ├── users.json │ └── login.json └── testng.xml画一下依赖方向会更容易理解cases指向api、utils、pojoapi指向pojo和configutils指向pojo和config。config是整个结构的地基任何一层都可以读取全局配置但只有config允许直接操作REST Assured的全局静态变量。这种设计带来的直观好处是你拿到一个陌生项目从类名和包路径就能判断出某个东西该放哪里、该找谁要数据不需要把几十个文件全部翻一遍。3. 落地实操手把手把每一层代码写出来3.1 准备一个可复现的测试环境写测试代码之前你需要一个被测接口。这里我推荐用json-server在本地快速起一个REST API它把一个JSON文件变成完整的增删改查接口用来练习接口自动化测试非常合适而且完全可控。先安装并准备数据文件npm install -g json-server在项目根目录新建db.json{ users: [ { id: 1, name: Tom, email: tomexample.com, job: Engineer }, { id: 2, name: Jerry, email: jerryexample.com, job: QA } ] }启动服务json-server --watch db.json --port 8080启动完成后http://localhost:8080/api/users就是一个标准的REST接口支持GET、POST、PUT、DELETE。当然你也可以用reqres.in这类公开测试接口练手但本地服务不依赖外网跑CI的时候更稳定。3.2 POJO层用对象代替Map让代码可读且可校验POJO层做的事情很简单把接口请求和响应里的JSON结构映射成Java对象。为什么要这么做你可以用Map乱装但Map的key拼错只有在运行时才会暴露而POJO有编译期类型检查字段名也能获得IDE的自动补全提示。比如用户对象package com.example.pojo; public class User { private Integer id; private String name; private String email; private String job; public User() { } public User(String name, String job) { this.name name; this.job job; } public Integer getId() { return id; } public void setId(Integer id) { this.id id; } public String getName() { return name; } public void setName(String name) { this.name name; } public String getEmail() { return email; } public void setEmail(String email) { this.email email; } public String getJob() { return job; } public void setJob(String job) { this.job job; } }注意POJO一定要保留无参构造方法因为Jackson在反序列化时默认会调用无参构造器来创建对象如果你只写了带参构造方法反序列化会直接报错。这个问题我在后面常见问题里还会详细说。3.3 接口封装层把HTTP细节关进黑盒接口封装层是整个分层的核心。它把向哪个URL发什么方法、带什么Header、传什么参数这些HTTP细节全部封装起来向上层暴露的只有语义化的方法名比如createUser、getUserList。一个典型的接口封装类长这样package com.example.api; import com.example.pojo.User; import io.restassured.response.Response; import static io.restassured.RestAssured.given; public class UserApiClient { private static final String USER_PATH /users; public Response createUser(User user) { return given() .log().ifValidationFails() .contentType(application/json) .body(user) .when() .post(USER_PATH); } public Response getUserList(int page, int perPage) { return given() .queryParam(page, page) .queryParam(per_page, perPage) .when() .get(USER_PATH); } public Response getUserById(int id) { return given() .pathParam(id, id) .when() .get(USER_PATH /{id}); } public Response updateUser(int id, User user) { return given() .contentType(application/json) .pathParam(id, id) .body(user) .when() .put(USER_PATH /{id}); } public Response deleteUser(int id) { return given() .pathParam(id, id) .when() .delete(USER_PATH /{id}); } }这里我统一返回Response对象不在封装层做断言。原因是同一个接口在不同场景下可能有不同的期望结果比如创建用户要断言201权限不足时要断言403断言应该交给用例层按场景去写封装层只负责把请求发出去、把响应带回来。.log().ifValidationFails()这行建议保留它的意思是只有断言失败时才打印完整的请求和响应日志。平时测试通过时日志干净排查问题时又能拿到完整信息很实用。3.4 数据准备层数据驱动让一条用例覆盖多组入参接口测试里最常见的场景是同一套流程换不同的入参验证不同的结果。比如创建用户你想测正常姓名、超长姓名、空姓名、特殊字符姓名如果每种情况写一个测试方法代码会变得非常冗余。TestNG的DataProvider就是专门解决这个问题的。数据准备层我拆成了两个部分一部分是DataProviderUtils类负责向用例层提供参数化数据另一部分是testdata目录下的JSON文件负责存放测试数据。先看JSON数据文件users.json{ users: [ { name: Tom, job: Engineer }, { name: Jerry, job: QA } ] }再来看读取这个文件的DataProviderpackage com.example.utils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.testng.annotations.DataProvider; import java.io.File; import java.io.IOException; public class DataProviderUtils { private static final ObjectMapper MAPPER new ObjectMapper(); DataProvider(name userData) public static Object[][] userData() throws IOException { JsonNode root MAPPER.readTree(new File(src/test/resources/testdata/users.json)); JsonNode users root.get(users); Object[][] result new Object[users.size()][2]; for (int i 0; i users.size(); i) { result[i][0] users.get(i).get(name).asText(); result[i][1] users.get(i).get(job).asText(); } return result; } }这个做法的好处是测试数据跟测试代码完全分离。以后想加一组测试数据只需要改JSON文件不用重新编译代码。数据量大了之后还可以把JSON文件替换成Excel、YAML或者数据库对用例层完全透明。3.5 测试用例层只关心业务场景不关心HTTP细节用例层是离业务最近的一层也是代码量最少的一层。因为前面的分层已经把脏活都干完了用例层要做的事情就是准备数据、调用接口、写断言。package com.example.cases; import com.example.api.UserApiClient; import com.example.config.TestConfig; import com.example.pojo.User; import com.example.utils.DataProviderUtils; import io.restassured.response.Response; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static org.hamcrest.Matchers.*; public class UserApiTest { private UserApiClient userApiClient; BeforeClass public void setUp() { TestConfig.init(); userApiClient new UserApiClient(); } Test(dataProvider userData, dataProviderClass DataProviderUtils.class) public void testCreateUser(String name, String job) { User user new User(name, job); Response response userApiClient.createUser(user); response.then() .statusCode(201) .body(name, equalTo(name)) .body(job, equalTo(job)) .body(id, notNullValue()) .body(createdAt, notNullValue()); } Test public void testGetUserList() { Response response userApiClient.getUserList(1, 2); response.then() .statusCode(200) .body(page, equalTo(1)) .body(per_page, equalTo(2)) .body(data, hasSize(2)) .body(data[0].name, notNullValue()); } Test public void testGetUserById() { Response response userApiClient.getUserById(1); response.then() .statusCode(200) .body(data.id, equalTo(1)) .body(data.email, containsString()); } Test public void testDeleteUser() { // 先创建一个用户拿到新生成的id再删除 User temp new User(Temp, TempJob); Response createResponse userApiClient.createUser(temp); int userId createResponse.jsonPath().getInt(id); Response deleteResponse userApiClient.deleteUser(userId); deleteResponse.then().statusCode(200); Response getResponse userApiClient.getUserById(userId); getResponse.then().statusCode(404); } }这里有几个细节值得说一下。dataProviderClass DataProviderUtils.class是因为DataProvider方法定义在另一个类里需要显式指定类名。data[0].name这种写法是JSONPath的数组下标访问方式REST Assured会直接用JSONPath解析非常强大。testDeleteUser演示了用例间的数据依赖处理方式先调用接口创建数据拿到返回的id再基于这个id做后续操作。这是接口自动化测试里非常典型的模式数据不是预先准备好的固定数据而是运行时动态生成的。3.6 全局配置BaseURL和超时统一管理最后是全局配置类。它的作用是集中管理REST Assured的公共设置避免在每个测试方法里重复写BaseURL、超时时间、公共Header之类的代码。package com.example.config; import io.restassured.RestAssured; import io.restassured.config.DecoderConfig; import io.restassured.config.HttpClientConfig; import io.restassured.config.RestAssuredConfig; public class TestConfig { private TestConfig() { } public static final String BASE_URL http://localhost:8080/api; public static void init() { RestAssured.baseURI BASE_URL; // 连接和读取超时设置单位毫秒 RestAssured.config new RestAssuredConfig() .httpClient(new HttpClientConfig() .setParam(http.connection.timeout, 5000) .setParam(http.socket.timeout, 8000)) .decoderConfig(new DecoderConfig() .defaultContentCharset(UTF-8)); } }切换环境的时候只需要改这一个文件里的BASE_URL全工程的用例都会跟着切换。这就是分层的价值把易变的、公共的东西从具体业务逻辑里剥离出来。4. 生成报告与日常维护测试跑完不是结束4.1 用TestNG组织用例和报告TestNG除了数据驱动还有一个很实用的能力是支持testng.xml文件管理用例。你可以按模块分组、指定执行顺序、设置多线程并发。在src/test/resources/testng.xml里!DOCTYPE suite SYSTEM https://testng.org/testng-1.0.dtd suite name接口自动化测试套件 verbose1 parallelmethods thread-count4 test name用户模块 classes class namecom.example.cases.UserApiTest/ class namecom.example.cases.LoginTest/ /classes /test /suite配置了parallelmethods之后TestNG会把测试方法放到4个线程里并发执行跑完一套用例的时间能明显缩短。但要注意并发执行要求用例之间没有数据依赖如果你的用例共享了同一个测试账号或者互相依赖创建的数据就不要轻易开并发否则会出现数据竞争导致的随机失败。运行结束之后测试报告默认生成在target/surefire-reports目录下。其中emailable-report.html是一个可以直接用浏览器打开的HTML报告包含每个用例的执行状态、耗时和失败堆栈发给团队看很方便。为了让Maven正确运行TestNG还需要在pom.xml里加上surefire插件配置build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-surefire-plugin/artifactId version3.2.5/version configuration suiteXmlFiles suiteXmlFilesrc/test/resources/testng.xml/suiteXmlFile /suiteXmlFiles /configuration /plugin /plugins /build这样直接在项目根目录执行mvn test就能跑完整套接口测试CI接入也就是多一条命令的事。4.2 日志输出别全量打印按需打印接口自动化测试排障的大部分时间花在看请求和响应上。REST Assured提供了log().all()方法可以在控制台完整打出请求头、请求体、响应头、响应体非常直观。但我不建议在所有用例上无脑加log().all()因为一套用例可能几千个请求全量打印会让日志文件迅速膨胀反而淹没有用的信息。我的习惯是分层控制在接口封装层统一加log().ifValidationFails()这样只有在断言失败的时候才会打印完整的HTTP交互记录。如果某个用例你要调试临时改成log().all()调试完再改回去。如果你觉得控制台日志不够用可以自己写个简单的LogUtils把响应body写入本地文件package com.example.utils; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class LogUtils { private LogUtils() { } public static void saveResponseToFile(String fileName, String content) { try { Path path Paths.get(target/responses/ fileName); Files.createDirectories(path.getParent()); Files.write(path, content.getBytes()); } catch (IOException e) { e.printStackTrace(); } } }失败的时候把响应体存成文件用IDE打开格式化一下对比字段差异会比在控制台里翻一堆日志舒服得多。4.3 分层之后日常改代码的动作会变成什么样分层的直接回报体现在日常维护中。我说几个真实场景大家感受一下后端接口地址从/api/users改成了/api/v2/users你只需要去UserApiClient里改一个常量或者去TestConfig里改BaseURL不用碰任何用例。接口响应里新增了一个字段比如用户对象加了个phone字段。这时只需要在User这个POJO类里加一个字段和对应的getter/setter原来所有用例的断言都不受影响。需要新增一个查询用户订单的接口测试流程是在POJO包下新建Order类在API包下新建OrderApiClient类在数据目录加订单测试数据在cases包下新建OrderTest类。每一层都是独立的增删改不会牵连其他模块。如果当初不分层这些改动里每一个都可能演变成一次全局搜索替换加提心吊胆的回归。5. 高频问题排查实录你大概率会踩的坑5.1 依赖导入后报错NoClassDefFoundError: groovy/lang/GroovyObject这个错误我见过太多次了尤其是在已有的Java项目里引入REST Assured时。REST Assured底层依赖Groovy但它在Maven里通常不会主动拉取Groovy核心库如果你的项目里恰好有旧版本的Groovy依赖或者IDE缓存的classpath不完整就会在运行时抛出这个异常。排查思路是先用Maven命令查看依赖树mvn dependency:tree -Dincludesorg.codehaus.groovy如果发现Groovy版本冲突在pom.xml里排除掉旧的Groovy依赖或者在dependencyManagement中统一Groovy版本。最省事的办法是执行一次mvn clean install再看有时只是IDE的索引缓存出了问题。5.2 Jackson反序列化LocalDateTime失败JSON里常见的createTime字段如果你在POJO里对应的是LocalDateTime类型直接用REST Assured的response.jsonPath().getObject(json, YourClass.class)时会报错因为Jackson默认不处理Java 8的时间类型。解决办法是引入jackson-datatype-jsr310依赖并注册JavaTimeModuledependency groupIdcom.fasterxml.jackson.datatype/groupId artifactIdjackson-datatype-jsr310/artifactId version${jackson.version}/version scopetest/scope /dependencyObjectMapper mapper new ObjectMapper(); mapper.registerModule(new JavaTimeModule());如果不关心时间字段的反序列化另一个偷懒的做法是把POJO里的时间字段类型改成String这样不需要任何额外配置。接口测试的主要目标是验证接口行为不是去做全字段的对象映射时间字段用字符串断言反而更直观。5.3 控制台打印响应中文乱码REST Assured从5.x开始默认使用UTF-8解码但如果你接的是老系统响应头里Content-Type没有带charset或者接口本身返回的是GBK编码控制台就会输出乱码。解决办法是在全局配置里指定解码字符集RestAssured.config RestAssured.config() .decoderConfig(new DecoderConfig() .defaultContentCharset(UTF-8));还有一种隐蔽情况接口返回的数据经过了gzip压缩而客户端没有正确处理Content-Encoding: gzip导致拿到的是压缩字节流然后按字符串打印看起来也像乱码。这种情况需要检查响应头里是否有Content-Encoding: gzip有的话确认REST Assured是否自动解压必要时手动配置given() .config(RestAssured.config() .decoderConfig(new DecoderConfig() .contentDecoders(ContentDecoder.DEFLATE, ContentDecoder.GZIP))) .when() .get(...);5.4 测试环境是自签名HTTPS证书请求一直报SSLHandshakeException这个问题在对接公司内部测试环境时非常常见测试环境为了省钱用自签名证书REST Assured默认会校验SSL证书然后直接拒绝连接。如果你明确知道目标环境是测试环境可以在请求里放开HTTPS校验given() .relaxedHTTPSValidation() .when() .get(https://test.example.com/api/users);.relaxedHTTPSValidation()是REST Assured专门为测试提供的API它相当于告诉你这个环境的证书我不管了放心发请求。但必须强调这只应该用在测试环境绝不允许在生产环境或者任何涉及真实用户数据的场景里使用否则等于把安全校验的底裤给脱了。5.5 断言失败时不知道接口到底返回了什么新手最容易遇到的情况是断言写好了测试挂了但只看到一个Expected: 201, Actual: 500完全不知道服务器为什么返回500。这是因为你只做了状态码断言但没有把响应内容打出来。如果你在接口封装层按我前面说的统一加了.log().ifValidationFails()那断言失败时REST Assured会自动把请求和响应完整打印出来这个问题基本不会出现。另外我还会把复杂的断言拆成多个独立步骤// 不推荐一次性断言太多挂了不好定位 response.then() .statusCode(200) .body(code, equalTo(0)) .body(data.size(), greaterThan(0)) .body(data[0].name, equalTo(Tom)); // 推荐先验证关键信息再逐层验证字段 response.then().statusCode(200); response.then().body(code, equalTo(0)); response.then().body(data, hasSize(3)); response.then().body(data[0].name, equalTo(Tom));这样每一步失败时报错信息就能精确告诉你是哪一层出了问题而不是一条长链条里猜。5.6 高频问题速查表现象可能原因解决方案NoClassDefFoundError: groovy/lang/GroovyObjectGroovy依赖缺失或版本冲突检查依赖树排除旧版Groovyclean后重建LocalDateTime反序列化失败Jackson缺少jsr310模块引入jackson-datatype-jsr310并注册JavaTimeModule中文乱码字符集不匹配配置decoderConfig指定UTF-8检查Content-EncodingSSLHandshakeException测试环境自签名证书测试环境使用relaxedHTTPSValidation()断言失败但看不到响应内容缺少日志配置统一加log().ifValidationFails()JSON数据量大时断言很难定位断言链条太长拆分断言步骤逐层验证用例并发执行随机失败测试数据互相污染关闭并发或用独立数据隔离6. 写在最后的个人建议我做接口自动化测试从最早的HttpClient手写请求到后来全面切到REST Assured最大的感受是写测试代码跟写业务代码一样架构决定了你后三个月的幸福感。分层看起来多写了好几个类但等接口从十几个涨到上百个你就明白这些工夫到底值不值。最后分享一个小习惯每次跑完测试我会顺手把失败接口的响应body存成文件用IDE打开对照字段比翻控制台日志直观得多。另外REST Assured的官方文档和源码注释写得相当好遇到用法问题先查这两处解决问题的速度往往比搜搜索引擎快。这套Java分层的练习框架我已经在多个项目里复用过了核心代码的量级基本没变变的只是POJO和ApiClient的数量这大概就是分层设计最好的证明。

相关新闻

PFC2D颗粒离散元数值模拟试验合集:从参数标定到工程应用

PFC2D颗粒离散元数值模拟试验合集:从参数标定到工程应用

2026/9/7 23:02:26

做岩土数值仿真这些年,PFC2D一直是我工具箱里很特别的一个存在。它不像有限元软件那样把材料当成连续介质去“切网格”,而是从颗粒尺度出发,用一个个圆盘单元去逼近真实材料的力学行为,这也是“颗粒离散元”这四个字最核心的内涵。…

Vim编辑器入门指南:安装、配置与基础操作

Vim编辑器入门指南:安装、配置与基础操作

2026/9/7 23:02:26

1. Vim编辑器入门:从安装到基础操作全指南 作为Linux系统中最经典的文本编辑器之一,Vim以其高效的操作方式和强大的扩展能力闻名。我第一次接触Vim是在大学实验室,当时看着学长在黑色终端里飞快地编辑代码,完全不用鼠标&#xff0…

基于MATLAB蚁群算法的配网重构与故障恢复实现

基于MATLAB蚁群算法的配网重构与故障恢复实现

2026/9/7 23:02:26

提到配网重构和故障恢复,很多做电力系统研究的朋友第一反应就是启发式算法、智能优化算法那一套。而蚁群算法在其中算是个常客,尤其适合处理配电网这种“开关状态组合优化”问题。这篇博文我就围绕“用MATLAB蚁群算法实现配网重构与故障恢复,…

你熟悉的GoPro正在悄然改变

你熟悉的GoPro正在悄然改变

2026/9/8 0:02:30

在过去二十多年里,GoPro让无数人把摄像头绑在冲浪板、山地车、滑雪头盔或赛车上。它几乎凭一己之力开创了运动相机这一品类,把普通的探险爱好者变成了影像创作者,并打造出消费电子领域最具辨识度的品牌之一。但在经历多年销售放缓和竞争加剧后…

Compose Hot Reload新增MCP服务器助AI智能体调试

Compose Hot Reload新增MCP服务器助AI智能体调试

2026/9/8 0:02:30

JetBrains发布了Compose Multiplatform 1.12.0版本,这是用于跨平台共享Kotlin UI代码的声明式框架更新。此次新版本的亮点包括:为Compose Hot Reload中的AI智能体提供模型上下文协议(MCP)服务器、为Web应用提供自动字体回退功能&a…

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

2026/9/8 0:02:30

有人可能在网上开着皮卡拍视频,声称中国电动车不仅性能不如美国大排量车型,安全性也堪忧。然而事实恰恰相反,GAC、吉利和零跑最新推出的电动车型在极为严苛的欧盟新车安全评鉴(Euro NCAP)测试中全部斩获满分。就在特斯…

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

2026/9/8 0:02:30

ValueError: sampler option is mutually exclusive with shuffle,这个报错我在 PyTorch 的 DataLoader 上至少见过几十次了,而且很有意思的是,它经常不是新手专属——很多写了好几年模型的老手,在从单机改成自定义采样器&#xf…

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

2026/9/8 0:02:30

芯片这个行业有个不太被人摆到台面上、但几乎每天都在发生的场景:客户拿着一条良率曲线截图问你,这批货的良率怎么掉了三个点,是不是工艺出问题了,产生的不良会不会流到他们产线上去。你解释了半天,客户似懂非懂&#…

Android高校教室预约管理平台全流程开发:从需求设计到答辩准备

Android高校教室预约管理平台全流程开发:从需求设计到答辩准备

2026/9/7 23:52:29

最近几年我带过的计算机类毕设项目里,Android方向的选题来来回回就那几类:电商、新闻、点餐、备忘录……真正有区分度、能拿得出手去答辩的,反而不多。高校教室预约管理平台算是一个经典且经久不衰的方向——业务场景真实、用户角色清晰、功能…

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

2026/9/7 20:21:46

本文首发于“生态学者”!从“湿地面积”到“土壤碳密度”:为什么需要重新认识潮汐湿地蓝碳变化?潮汐湿地位于陆地与海洋的交汇地带,包括红树林、盐沼和潮滩,是全球重要的蓝碳生态系统。其土壤能够长期储存大量有机碳&a…

adb抓包

adb抓包

2026/9/7 3:44:24

前言 本文介绍如何通过 tcpdump 在 Android 手机上抓取网络数据包,并在电脑端使用 Wireshark 进行分析。适用于需要排查 App 网络请求、分析接口调用或调试网络问题的开发与测试场景。1. 手机要有 root 权限2. 下载 tcpdump3. adb push C:\Users\zhangkuixun\Downlo…

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

2026/9/7 8:03:37

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战 在云原生基础设施中,容器镜像体积直接决定了服务的部署速度与弹性扩容敏捷度。对于传统的 Go / Java 微服务,镜像体积通常被严格控制在 50MB 到 200MB 以内,拉取镜像只…

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

2026/9/8 0:02:30

芯片这个行业有个不太被人摆到台面上、但几乎每天都在发生的场景:客户拿着一条良率曲线截图问你,这批货的良率怎么掉了三个点,是不是工艺出问题了,产生的不良会不会流到他们产线上去。你解释了半天,客户似懂非懂&#…

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

2026/9/8 0:02:30

ValueError: sampler option is mutually exclusive with shuffle,这个报错我在 PyTorch 的 DataLoader 上至少见过几十次了,而且很有意思的是,它经常不是新手专属——很多写了好几年模型的老手,在从单机改成自定义采样器&#xf…

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

2026/9/8 0:02:30

有人可能在网上开着皮卡拍视频,声称中国电动车不仅性能不如美国大排量车型,安全性也堪忧。然而事实恰恰相反,GAC、吉利和零跑最新推出的电动车型在极为严苛的欧盟新车安全评鉴(Euro NCAP)测试中全部斩获满分。就在特斯…

远程协作的工作台整理

远程协作的工作台整理

2026/9/7 3:38:07

远程协作的工作台整理远程协作的核心不是再加一个工具,而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/4 7:42:10

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/6 23:21:51

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…