Jmeter二次开发全解析:脚本、函数与插件三档实战指南

发布时间:2026/10/11 11:49:30
Jmeter二次开发全解析:脚本、函数与插件三档实战指南 1. 二次开发到底改的是什么先给三种路线定个性网上聊Jmeter二次开发很容易被带偏到改源码这条路上去。我刚入行时也犯过这个错下了源码工程配了半天的Ant环境最后只是把某个输出日志的中文改成了英文纯属浪费了一个下午。后来做过的项目多了才明白Jmeter的二次开发其实应该按需求和难度拆成三档脚本级BeanShell/JSR223、函数级Java扩展函数、插件级自定义Sampler/ConfigElement等GUI插件。三者的代码量、可维护性、复杂度完全不是一个量级。先说结论后续章节再逐条展开。脚本级开发不碰Jmeter本身的编译环境和GUI代码只在测试计划里写BeanShell或JSR223脚本完成断言、参数加工、数据生成。门槛最低适合90%的接口测试和性能测试场景。函数级开发写一个Java类继承AbstractFunction打成jar包丢进lib/ext就能在Jmeter里当内置函数一样用${__myFunc(...)}。适合需要被大量脚本复用、但Jmeter默认函数覆盖不到的工具能力。插件级开发继承Sampler、ConfigElement、PreProcessor等抽象类做配套的GUI类在Jmeter里新增一个可视化的测试元件。这是真正意义的深度改造适合把公司的加解密算法、RPC调用协议、私有报文协议封装成拖拽即用的元件。这三档在社区里经常被混着讲搜索jmeter beanshell断言出来的帖子大多是脚本级别搜索二次开发出来的又全是写Java类改源码的教程。你如果只是想在压测时加个签名参数不需要也不可能从改源码开始。但如果你想在公司内封装一个公司自研协议的Sampler那不仅仅是写Java类的问题还要搞懂Jmeter的实例化机制和GUI组件生命周期。这篇内容我按脚本级 → 函数级 → 插件级 → 踩坑记录的顺序来拆每一部分都会给完整可跑的方案并且会把那些文档里不会写、但实际做必然会撞上的坑提前说出来。2. 脚本级开发BeanShell与JSR223的实战边界2.1 先别急着写Java类BeanShell能覆盖大部分日常需求很多人一听到二次开发四个字就默认必须打开IDEA写Java代码这其实是认知偏差。Jmeter的BeanShell组件断言、后置处理器、前置处理器本质上是让Jmeter在运行时直接执行一段Java风格脚本你可以在脚本里访问vars、prev、props、log这些内置对象不必重新编译工程。举个例子假设压测接口的入参需要一个MD5加密后的sign正常做法可能是用__digest函数。但如果业务规则复杂一点比如sign md5(时间戳 固定salt 请求体里的两个字段)需要先取上一个请求的响应值再拼接那用内置函数就比较麻烦。这时候一个BeanShell前置处理器加六行脚本就解决了。import java.security.MessageDigest; String timestamp String.valueOf(System.currentTimeMillis()); String rawStr stamp timestamp saltmysaltorderId vars.get(orderId); MessageDigest md MessageDigest.getInstance(MD5); byte[] bytes md.digest(rawStr.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(Integer.toHexString((b 0xFF) | 0x100).substring(1, 3)); } vars.put(sign, sb.toString()); vars.put(timestamp, timestamp);这段代码直接写在前置处理器 → BeanShell PreProcessor里脚本执行时vars对象就是Jmeter运行时变量的句柄vars.put往全局变量池里塞数据后续HTTP请求里直接${sign}引用。这里有一个非常关键的性能前提BeanShell脚本引擎本身解释执行较慢单脚本几百行或压测线程数一多会拖累压测自身的TPS上限。Java开发常用的优化方案是高频逻辑务必迁移到JSR223 Groovy引擎。JSR223是Java标准脚本接口java.script包Groovy的运行时性能比BeanShell高一个数量级代码风格几乎完全兼容切过来成本很低。2.2 断言场景里JSR223 Groovy才是主流选择我在真实压测项目中已经把BeanShell断言全部迁到JSR223了。原因是BeanShell断言在1万并发下会出现脚本执行耗时增加、甚至GC压力变大的问题。JSR223 Groovy的编译缓存机制能显著提升重复执行场景的稳定性。一个常见需求响应是JSON数组要求数组里每个元素的status字段都不能是failed同时每个元素的耗时不能超过800毫秒。很多人第一反应是引入JSON库做解析实际上Groovy可以直接调用Jmeter脚本区里的JsonSlurper工具类。import groovy.json.JsonSlurper def response prev.getResponseDataAsString() def list new JsonSlurper().parseText(response) assert list.size() 0 : 返回列表为空 list.each { item - assert item.status ! failed : 存在失败状态: item assert item.durationTime 800 : 响应超时: item.durationTime }注意这段脚本里的prev对象它是SampleResult的实例prev.getResponseDataAsString()拿到的就是当前取样器返回的报文体。脚本执行失败断言失败时Jmeter会把失败信息挂到当前取样器的断言结果里压测报告的失败率统计用的就是这个结果。用JSR223除了性能还有一个隐藏优势你可以直接引入项目组自研的jar包里的构造类。比如公司的加解密库放在公司私服你在lib/ext下丢一个封装jar再在JSR223脚本里import com.company.xx.EncryptUtil就能复用公司的算法逻辑。这比在BeanShell里手动复制粘贴200行AES代码靠谱得多。2.3 脚本级开发的内容组织问题复用逻辑别散落到每个线程组当脚本量增多只依赖每个元件里贴一份脚本的方式会变得很难维护。我之前遇到过一个项目四个线程组需要相同的签名逻辑我在四个地方各粘贴了一份。等算法加了一个version字段后修改的地方有四处而且有一处漏改了压测到一半才发现签名的version有旧值。后来我把公共逻辑抽成单独的Groovy脚本文件放在bin/scripts/目录下每个JSR223元件只保留调用代码def helper new SignHelper(vars, props, log) helper.applySign(orderList)SignHelper是一个Groovy类文件放在Jmeter的bin/scripts目录中通过Groovy的源码路径机制被加载。这样签名逻辑只有一份改一次全测试计划生效。这是从会写脚本到脚本工程化的转折点。再往后如果公共逻辑还需要被多个Jmeter测试计划共用我更建议直接把这类逻辑下沉到函数级或自定义Java jar里。3. 函数级开发写一个真正意义的自定义JMeter函数3.1 为什么需要自定义函数Jmeter内置函数虽然多但覆盖面有限。比如我需要一个读取指定数据库表当前最大orderId并自增1的函数用于压测时生成唯一业务号。类似逻辑也可以放JSR223里做但问题在于脚本里连接数据库必须每次执行都初始化连接逻辑重复且效率低。更合理的做法是写一个Java函数元件名称叫__nextOrderId在测试计划里可以直接${__nextOrderId()}引用内部做长连接池复用。函数级开发就是这条路。整条链路清晰简单新建Maven工程引入org.apache.jmeter:ApacheJMeter_core依赖写一个类继承org.apache.jmeter.functions.AbstractFunction实现execute、setParameters、getReferenceKey、getArgumentDesc四个方法打成jar包放到lib/ext目录重启Jmeter在函数助手对话框里能看到新增的函数测试计划里也能直接引用3.2 代码骨架与核心方法说明package com.example.jmeter.functions; import org.apache.jmeter.engine.util.CompoundVariable; import org.apache.jmeter.functions.AbstractFunction; import org.apache.jmeter.functions.InvalidVariableException; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.samplers.Sampler; import java.util.List; public class NextOrderId extends AbstractFunction { private static final String KEY __nextOrderId; private static final ListString DESC java.util.Arrays.asList(prefix(optional)); private String prefix ; Override public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException { long current System.currentTimeMillis(); // 这里实际业务可以去查库或走连接池配合自增序列确保唯一 return prefix current; } Override public void setParameters(ListCompoundVariable parameters) throws InvalidVariableException { if (parameters.size() 1) { prefix parameters.get(0).execute(); } } Override public String getReferenceKey() { return KEY; } Override public ListString getArgumentDesc() { return DESC; } }几个核心方法的含义execute是函数真正执行的逻辑previousResult是当前取样器的前一个结果对象currentSampler是当前取样器实例。这里只是演示你可以在execute里读取previousResult.getResponseDataAsString()作输入也能用JMeterContextService.getContext()获取全局变量用法非常灵活。setParameters负责解析调用方传入的参数。Jmeter会把${__nextOrderId(abc)}中的abc封装成CompoundVariable对象这里可以做一个或多个参数的解析。getReferenceKey返回的函数名必须与__开头一致这也是函数助手对话框里显示的名字。getArgumentDesc用来描述参数含义辅助用户在函数助手对话框里填写参数。3.3 部署验证的完整流程在pom.xml里加入核心依赖dependency groupIdorg.apache.jmeter/groupId artifactIdApacheJMeter_core/artifactId version5.5/version scopeprovided/scope /dependencymvn clean package打包将target下的jar放到$JMETER_HOME/lib/ext/目录。注意这里有一个坑我后面会在踩坑部分专门讲。重启Jmeter。点击选项 → 函数助手对话框左侧下拉框如果多出了__nextOrderId说明函数加载成功。测试计划任意参数位置引用${__nextOrderId(ORD)}跑一次请求查看发送报文中是否带上了ORD 时间戳拼接的结果。一个补充经验execute里如果只依赖系统时间和随机数压测线程并发时会存在极小概率生成重复值。真实业务号建议在函数内部维护一个线程安全的AtomicLong计数器用前缀 日期 AtomicLong自增值拼业务号这样才能保证压测采集的数据不互相干扰。3.4 函数级开发与脚本级开发的取舍函数级开发的成本是在Java工程里改动后需要重新打包重启Jmeter周期长。但只要这个函数是通用能力、多处调用这个代价是值得的。我的判断标准是如果一段逻辑在三个以上JSR223脚本里重复出现而它的实现还涉及连接池、缓存这类状态管理就先抽函数。如果只是单一场景的一次性逻辑留在脚本里更灵活。4. 插件级开发做一个带GUI的自定义Sampler4.1 理解Jmeter的组件模型如果函数级开发是穿针引线插件级开发就是重新打造一个工具。具体到自定义请求类插件核心要搞懂Jmeter元件的双类结构一个类负责业务逻辑继承AbstractSampler一个类负责GUI展示继承AbstractSamplerGui。这两个类通过TestBean或setProperty机制进行字段交互。很多初学二次开发的人搞不明白为什么GUI类的字段修改后Sampler运行时能拿到——因为Jmeter的存储机制很特殊测试计划保存的其实是JavaBean的property属性不是GUI对象的字段。当你点击一个Sampler时GUI类从TestElement里加载属性值渲染到界面上点应用时GUI类把界面值写回TestElement真正跑请求时采样器类从TestElement拿属性去执行。这个模型如果不理解写插件时最典型的问题就是界面填的参数值永远传不进请求逻辑。4.2 从零写一个发送TCP协议的Sampler假设公司内部有很多设备走私有TCP长连接协议单位置监听端口。用Jmeter压测这种服务默认的TCP Sampler不够灵活所以需要一个自定义TCP Sampler来支持加密逻辑。先写业务逻辑类PrivateTcpSampler extends AbstractSampler。AbstractSampler要求实现sample()方法返回SampleResult。package com.example.jmeter.sampler; import org.apache.jmeter.samplers.AbstractSampler; import org.apache.jmeter.samplers.Entry; import org.apache.jmeter.samplers.SampleResult; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class PrivateTcpSampler extends AbstractSampler { public static final String HOST privateTcp.host; public static final String PORT privateTcp.port; public static final String PAYLOAD privateTcp.payload; Override public SampleResult sample(Entry entry) { SampleResult result new SampleResult(); result.setSampleLabel(getName()); result.sampleStart(); String host getPropertyAsString(HOST); int port getPropertyAsInt(PORT); String payload getPropertyAsString(PAYLOAD); try (Socket socket new Socket(host, port)) { socket.setSoTimeout(5000); OutputStream os socket.getOutputStream(); InputStream is socket.getInputStream(); // 企业私有协议封装这里可以根据公司加解密算法改造字节流 byte[] requestBytes encode(payload); result.setSentBytes(requestBytes.length); os.write(requestBytes); os.flush(); // 读取响应的简化处理第一轮4字节为报文长度 byte[] lenBuf new byte[4]; is.read(lenBuf); int bodyLen ((lenBuf[0] 0xFF) 24) | ((lenBuf[1] 0xFF) 16) | ((lenBuf[2] 0xFF) 8) | (lenBuf[3] 0xFF); ByteArrayOutputStream bos new ByteArrayOutputStream(); byte[] buf new byte[1024]; int readTotal 0; while (readTotal bodyLen) { int n is.read(buf, 0, Math.min(buf.length, bodyLen - readTotal)); if (n -1) break; bos.write(buf, 0, n); readTotal n; } byte[] responseBytes bos.toByteArray(); result.setResponseData(responseBytes); result.setResponseCodeOK(); result.setResponseMessage(OK); result.setSuccessful(true); } catch (Exception e) { result.setResponseCode(500); result.setResponseMessage(e.toString()); result.setSuccessful(false); } finally { result.sampleEnd(); } return result; } private byte[] encode(String payload) { // 示例可在此接入公司AES加密、长度字段拼装等逻辑 byte[] body payload.getBytes(java.nio.charset.StandardCharsets.UTF_8); int length body.length; byte[] header new byte[4]; header[0] (byte) ((length 24) 0xFF); header[1] (byte) ((length 16) 0xFF); header[2] (byte) ((length 8) 0xFF); header[3] (byte) (length 0xFF); java.io.ByteArrayOutputStream bos new java.io.ByteArrayOutputStream(); bos.write(header, 0, 4); bos.write(body, 0, body.length); return bos.toByteArray(); } }这里面有几个细节值得说sampleStart()和sampleEnd()之间包裹的是真正的耗时Jmeter报告和聚合图里的时间统计都依赖这两个时间戳。getPropertyAsString系列是从TestElement读取配置存储值来自GUI类写回的属性。变量持有关的要注意不要用静态变量存连接。压测是并发模型每个线程都有自己独立的Sampler实例调用sample()但如果你把Socket缓存到静态Map里就必须自己管理线程安全和连接失效问题否则压测一半就报连接超时。SampleResult的setResponseCodeOK()和setSuccessful(true)配合使用会让这个请求在聚合报告里计入成功如果异常setSuccessful(false)则计入失败。4.3 配套GUI类与插件注册机制有了业务类还差一半接下来写GUI类PrivateTcpSamplerGui extends AbstractSamplerGui。package com.example.jmeter.sampler; import org.apache.jmeter.samplers.gui.AbstractSamplerGui; import org.apache.jmeter.testelement.TestElement; import javax.swing.*; import java.awt.*; public class PrivateTcpSamplerGui extends AbstractSamplerGui { private JTextField hostField; private JTextField portField; private JTextArea payloadArea; public PrivateTcpSamplerGui() { init(); } private void init() { setLayout(new BorderLayout()); JPanel mainPanel new JPanel(new GridBagLayout()); GridBagConstraints gbc new GridBagConstraints(); gbc.insets new Insets(5, 5, 5, 5); gbc.fill GridBagConstraints.HORIZONTAL; hostField new JTextField(20); portField new JTextField(10); payloadArea new JTextArea(6, 30); gbc.gridx 0; gbc.gridy 0; mainPanel.add(new JLabel(Host:), gbc); gbc.gridx 1; gbc.gridy 0; mainPanel.add(hostField, gbc); gbc.gridx 0; gbc.gridy 1; mainPanel.add(new JLabel(Port:), gbc); gbc.gridx 1; gbc.gridy 1; mainPanel.add(portField, gbc); gbc.gridx 0; gbc.gridy 2; mainPanel.add(new JLabel(Payload:), gbc); gbc.gridx 1; gbc.gridy 2; mainPanel.add(new JScrollPane(payloadArea), gbc); add(mainPanel, BorderLayout.CENTER); } Override public String getStaticLabel() { return Private TCP Sampler; } Override public String getLabelResource() { return getClass().getSimpleName(); } Override public TestElement createTestElement() { PrivateTcpSampler sampler new PrivateTcpSampler(); modifyTestElement(sampler); return sampler; } Override public void modifyTestElement(TestElement element) { super.configureTestElement(element); if (element instanceof PrivateTcpSampler) { PrivateTcpSampler sampler (PrivateTcpSampler) element; sampler.setProperty(PrivateTcpSampler.HOST, hostField.getText()); sampler.setProperty(PrivateTcpSampler.PORT, portField.getText()); sampler.setProperty(PrivateTcpSampler.PAYLOAD, payloadArea.getText()); } } Override public void configure(TestElement element) { super.configure(element); PrivateTcpSampler sampler (PrivateTcpSampler) element; hostField.setText(sampler.getPropertyAsString(PrivateTcpSampler.HOST)); portField.setText(String.valueOf(sampler.getPropertyAsInt(PrivateTcpSampler.PORT))); payloadArea.setText(sampler.getPropertyAsString(PrivateTcpSampler.PAYLOAD)); } }GUI类里的关键方法createTestElement()当用户在测试计划里手动新增这个元件时调用负责生成一个默认的Sampler实例。modifyTestElement用户点击面板上的Apply按钮时把界面值写入TestElement。注意回调函数顺序是test plan的监控器修改 → GUI修改 → 运行态取值。configure打开一个既有Sampler时Jmeter会把已存属性回填到界面控件。如果configure不实现或字段不对应你会发现新建的Sampler没问题但是重新打开保存好的测试计划时界面上的Host、Port值全部丢失。getStaticLabel()返回组件在测试计划树和添加元件菜单里显示的名称。打包方式类似函数级开发mvn clean package生成jar放到lib/ext目录重启Jmeter后右键线程组 → 添加 → Sampler就能看到Private TCP Sampler选项。4.4 附加面板如果Sampler还依赖ConfigElement配置很多私有协议不是单纯的Host/Port/Payload三个字段而是一整套参数密钥版本、加密算法类型、报文超时重传次数等。把这些塞进PrivateTcpSamplerGui会让面板非常拥挤更优雅的做法是做一个配套的ConfigElement配置元件集中管理这些参数Sampler运行时通过getPropertyAsString从同名TestElement的属性里读取。ConfigElement的最小实现也是继承AbstractConfigElement配套一个GUI类。配置元件的GUI类布局和Sampler GUI几乎一致唯一的区别在createTestElement返回值是配置元件实例。在测试计划中使用时同一个线程组下放置配置元件和SamplerSampler便可在运行时通过getContext().getCurrentSampler()或线程上下文的配置片段拿到这些属性。实际开发里我就是把加密算法、私钥索引、重试次数全放在配置元件里Sampler GUI只保留基础连接参数这样测试人员只需要关注连接和报文体不需要理解加密内部细节。5. 热加载、调试和版本兼容二次开发中的关键工程化细节5.1 关于每次重启Jmeter才能生效这件事函数级和插件级开发最讨厌的就是每次改完代码都要杀掉Jmeter进程再重启。加载路径一旦装机确认后常见的协作流程是把jar包放到lib/ext然后开发机调整jmeter.properties里的plugin.not.ignored等属性。但我的实际建议是写一个专门的调试启动脚本直接调用JMeter的启动类和CLI参数。这样每次改完代码执行一次脚本十几秒就能重启进入命令行模式通过-t指定测试计划、-n指定非GUI模式、-j输出日志文件再配合-e生成报告即可快速回归。GUI模式因为要等整个Swing界面加载通常要慢得多。但这里有个比重启本身更大的坑Jmeter进程启动时扫描类同一个类有多个版本时会加载哪个5.2 版本矩阵JDK、Maven依赖版本与Jmeter的对应关系我把几个稳定组合列出来这套是我在不同项目里实跑过的不是网上抄来的Jmeter版本推荐JDKApacheJMeter_core依赖版本特别注意Jmeter 5.4.xJDK 8 / 115.4.1最稳妥的老牌组合脚本API兼容性好Jmeter 5.5.xJDK 8 / 115.5函数签名无变化插件开发可复用Jmeter 5.6.2JDK 11 / 175.6.2包结构有调整老插件需重新编译有次我在Jmeter 5.6.2环境里直接复用5.4时代打好的自定义Sampler jar启动时报NoSuchMethodError。排查后原因是5.6版本把某些内部API改了包路径老jar引用的是旧包名。这类问题不会在编译期暴露只会在运行期爆出来所以生产环境里换Jmeter版本一定要重新编译一遍所有自定义jar包。5.3 jar依赖冲突这是插件开发里最磨人的问题lib/ext下放了自定义jar里面带了第三方依赖包。如果这个第三方包版本和Jmeter主程序自带的冲突就会出现两个比较典型的症状Jmeter启动卡在Downloading或扫描插件阶段日志里反复刷ClassNotFoundException某个Sampler执行时报NoClassDefFoundError或ClassCastException我踩过最狠的一次是给自定义函数引入了公司内部的httpclient版本4.5.x而Jmeter自带的是4.2.x。函数一调用就抛方法不存在异常查了一个多小时才定位到是jar里面的旧版本覆盖了Jmeter类路径里的新版本。解决办法就一条纪律自定义jar内部使用到的依赖优先选用provided scope或shade重命名避免往Jmeter亲类加载器里塞冲突类。如果你必须携带一个高版本依赖建议用Maven shade插件把依赖包relocation迁移到另一个包名下这样既不会被Jmeter的类稀释也不会干扰其他插件。5.4 UI插件刷不出图标的调试技巧某些自定义Sampler在测试计划树里正常显示但图标是空白方块。原因是Jmeter GUI组件有独立接口getIcon如果你没实现Jmeter会尝试从类路径下查找和getStaticLabel同名的png资源。两个调试方向检查Maven打包时是否把src/main/resources里的图标文件带进了jar。默认Java打包插件如果不改配置不会单独把resources目录打进classpath根路径。如果组件实现了getIcon接口确认返回的ImageIcon是独立新建实例不要用共享静态引用。Jmeter在多个GUI界面切换时会重新拉取图标资源静态共享容易导致区域绘图异常。5.5 中文乱码问题插件面板和日志里出现乱码多数是文件编码问题。开发工程在pom.xml里强制指定UTF-8同时源码文件本身必须是UTF-8编码。很多IDE默认GBK编码一旦中文注释或中文字符串写入源码打包后的class文件里就是乱码Jmeter GUI里显示全成问号。properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties另外Jmeter本身的bin/jmeter启动参数里有可能被默认添加了-Dfile.encoding。如果出现压测时发送的中文报文体乱码而函数插件里显示正常就要看是不是Jmeter配置文件的编码推导覆盖了你jar里的设置。解决方案是在user.properties里显式设置file.encodingUTF-8保证JVM进程层面的统一。6. 从二次开发回到测试业务我的最后三点经验做过几个完整的Jmeter二次开发项目之后我发现决定二次开发成功与否的往往不是代码能力而是边界的把握和前期沟通。第一先搞清楚要封装的是业务逻辑、协议逻辑还是纯粹的测试工具逻辑。如果业务规则频繁变化比如签名算法每三个月换一次算法那么做成插件级组件会让每次改动都变成一次版本发布反而拖慢使用节奏。这种更适合JSR223脚本加配置文件的形式。只有当规则稳定且多团队共用时才值得下沉成Sampler级插件。第二压测工具的开发有一个和业务开发完全不同的思维差异性能测试工具自身不能成为性能瓶颈。脚本引擎选Groovy而非BeanShell、连接池要复用、内存不能随意缓存过多响应数据、图表标签不能过度刷新UI线程这些都是在做插件设计阶段就要考虑的问题。一个执行一次需要50毫秒的Sampler压测200并发时自身CPU占用就可能率先打满目标系统的瓶颈数据就完全失真了。第三也是最实际的一点二次开发产出的东西一定要让一线测试同学能用起来。我见过有人做了很完整的自定义Sampler但没配使用文档一线同事在JMeter UI里根本不知道怎么填报文最后只能回到老办法。后来我给插件GUI加了示例按钮用于一键填充测试报文并配套了一个demo.jmx文件放到bin/scripts/examples新同学第一次上手五分钟就能跑通。这个投入比写几百行代码的价值还要大。Jmeter的二次开发说到底不是炫技是让工具更贴合你的业务场景。把脚本熟练用起来解决日常70%的问题再用函数级开发解决复用问题等确确实实需要可视化组件时再进入插件级开发这条路走下来不会太痛苦也不会走太多弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询