要:在IT行业中,后端开发者发挥着至关重要的作用。尤其是在技术变革日新月异的时代,更需要后端开发者不断提升自我的技能,但是,无论技术迭代如何迅速,后端开发人员的“基本功”都不可以落下。本文展开讨论成为JavaScript堆栈后端开发人员,必须经历的那些事……
原文链接:
https://billypentester.medium.com/ultimate-10-stages-to-master-backend-development-f6f65f22a327
声明:本文为CSDN翻译,转载请注明来源。
译者 | 朱珂欣 责编 | 屠敏
出品 | CSDN(ID:CSDNnews)
随着时代的发展,越来越多编程语言和开发工具的出现,让开发者能够方便且快捷地开发和部署应用程序。其中,后端开发多是指服务器端编程,开发人员通过创建应用程序体系结构和业务逻辑,以此处理和存储数据并输出有用的信息。
毋庸置疑,成为后端开发人员必然有门槛,掌握Web和操作系统的基础知识、了解数据库、计算机网络协议、数据结构和算法、开发工具等,逐渐成为后端开发人员的入门技能。后端编程语言、框架和堆栈也很多,例如JavaScript、Python、PHP、Java、Ruby等。那么,成为JavaScript堆栈后端开发人员,需要面临那几个必须经历的阶段呢?
第1阶段:掌握Web基础知识
在开发人员的初始阶段,学习Web的基础知识是至关重要的。因为拥有Web概念可以帮助开发人员更好地了解互联网的工作原理,例如使用哪些协议、如何传递消息等。
首当其冲的是了解Web中的一些基本概念,例如HTTP的概念、HTTP的基本方面、HTTP 请求的类型及作用、客户端和服务器模型、OSI 模型、DNS 工作原理等。
第3阶段:学习后端框架和语言
第4阶段:使用服务器框架和API开发
第5阶段:使用模板引擎
第6阶段:了解数据库
后端应用程序可以通过使用MongoDB,MySQL等第三方npm包查询数据库来直接与数据库交互。如果开发人员不了解SQL或数据库语言,则需要学习任何ORM/ODM作为中间件,将对象方法转换为复杂的数据库查询,这些中间件为开发人员提供了一个名为模型的完整结构,用于在将数据输入数据库之前验证数据。此外,它在对象模型之上提供了一个额外的安全层。
第7阶段:关注安全性
安全性是企业级 Web 应用程序的一大关注点。单个漏洞会导致数据泄露或应用程序故障。Web 应用中应有适当的验证、身份验证和授权。
限制:使用中间件应用请求限制,防止DDOS攻击。
第8阶段:使用消息代理
消息代理成为帮助克服其中一些困难的解决方案之一。消息代理由 3 个组件组成:
制作人:生产者发送有关主题的消息。一个或多个发布者可以针对同一主题发布内容;
消费者:订阅者订阅主题,发布到该主题的所有消息都将由该主题的所有订阅者接收;
在实时服务器上部署Web应用程序之前,为了减少单个错误的输入或缺少数据导致正在运行的服务器出现故障,需要进行测试。
测试可以根据多个标准在多个级别上进行,开发人员可以检查Web应用是否返回了正确的状态代码、错误消息、数据等。
在部署Web应用之前,开发人员需要学习Git、GitHub的操作以及版本控制工具的使用,以使部署灵活且可扩展。
Git可以用于版本控制,通过创建多个分支,跟踪源代码中的更改,并使多个开发人员能够协同工作。GitHub是一个用于版本控制和协作的代码托管平台,可以允许开发人员和其他人共同处理项目。GitHub操作可帮助您自动执行软件开发工作流。单个操作是可重用的代码段,可用于在 GitHub上生成、测试、打包或部署项目。它还可用于自动执行工作流程的任何步骤。部署完整的Web应用非常复杂。开发者还需要一个专用服务器来响应HTTP请求,并使用在线数据库。
是3y,一年CRUD经验用十年的markdown程序员常年被誉为优质八股文选手
花了几天搭了个后台管理页面,今天分享下我的搭建过程,全文非技术向,就当跟大家吹吹水吧。
老读者可能知道我是上了大学以后,才了解什么是编程。在这之前,我对编程一无所知,甚至报考了计算机专业之后也未曾了解过它是做什么的。
在大一的第一个学期,我印象中只开了一门C++的编程课(其他的全是数学)。嗯,理所当然,我是听不懂的,也不知道用来干什么。
刚进大学的时候,我对一切充满了未知,在那时候顺其自然地就想要进几个社团玩玩。但在众多社团里都找不到我擅长的领域,等快到截止时间了。我又不想大学期间什么社团都没有参加,最后报了两个:乒乓球社团和计算机协会。
这个计算机协会绝大多数的人员都来自于计算机专业,再后来才发现这个协会的主要工作就是给人「重装系统」,不过这是后话啦。
当时加入计算机协会还需要满足一定的条件:师兄给了一个「网站」我们这群人,让我们上去学习,等到国庆回来后看下我们的学习进度再来决定是否有资格加入。
那个网站其实就是对HTML/CSS/JavaScript的入门教程,是一个国外的网站,具体的地址我肯定是忘了。不过那时候,我国庆闲着也没事干,于是就开始学起来了。我当时的进度应该是学到CSS,能简单的页面布局和展示图片啥的
刚开始的时候,觉得蛮有趣的:我改下这个代码,字体的颜色就变了,图片就能展示出来了。原来我平时上网的网站是这样弄出来的啊!(比什么C++有趣多了)
国庆后回来发现:考核啥的并不重要,只要报名了就都通过了。
有了基本的认知后,我对这个也并不太上心,没有持续地学下去。再后来,我实在是太无聊,就开始想以后毕业找工作的事了,自己也得在大学充实下自己,于是我开始在知乎搜各种答案「如何入门编程」。
在知乎搜了各种路线并浪费了大量时间以后,我终于开始看视频入门。我熬完了JavaSE基础之后,我记得我是看方立勋老师入门的JavaWeb,到前端的课程以后,我觉得前端HTML/CSS/JavaScript啥的都要补补,于是又去找资源学习(那时候信奉着技多不压身)。
印象中是看韩顺平老师的HTML/CSS/JavaScript,那时候还手打代码的阶段,把我看得一愣一愣的(IDE都不需要的)。随着学习,发现好像还得学AJAX/jQuery,于是我又去找资源了,不过我已经忘了看哪个老师的AJAX和jQuery课程。
在这个学习的过程中,我曾经用纯HTML/CSS/JavaScript跟着视频仿照过某某网站,在jQuery的学习时候做过各种的轮播图动画。还理解了margin和padding的区别。临近毕业的时候,也会点BootStrap来写个简单的页面(丑就完事了)
等我进公司了以后,技术架构前后端是分离的,虽然我拉了前端的代码,但我看不懂,期间我也没学。以至于我两年多是没碰过前端的,我对前端充满着敬畏(刚毕业那段时间,前端在飞速发展)
从我筹划要写austin项目的时候,我就知道我肯定要写一个「后台管理页面」,但我迟迟没下手。一方面是我认为「后端」才是我的赛道,另一方面我「前端」确实菜,不想动手。
我有想过要不找个小伙伴帮我写,但是很快就被我自己否定了:还得给小伙伴提需求,算了。
当我要面临前端的时,我第一时间就想到:肯定是有什么框架能够快速搭建出一个管理页面的。我自己不知道,但是,我的朋友圈肯定是有人知道的啊。于是,我果断求助:
我被安利了很多框架,简单列举下出场率比较高的。
注:大多数我只是粗略看了下,没有仔细研究。若有错误可以在评论区留言,轻喷
官网文档:https://www.renren.io/guide#getdoc
它这个框架是前后端分离的,后端还可以生成对应的CRUD代码,前端基于vue和element-ui开发。
当时其实我有点想选它的,但考虑到我要再部署个后端,还得学点vue,我就搁置了
官方文档:http://doc.ruoyi.vip/ruoyi/
RuoYi给我安利的也很多,这个貌似最近非常火?感觉我被推荐了以后,到处都能看到它的身影。
我简单刷了下文档,感觉他做的事比renren-fast要多,文档也很齐全,但是没找到我想要的东西:我打开一个文档,我希望能看到它的系统架构,系统之间的交互或者架构层面上的东西,但我没快速找到。
项目齐全和复杂对我来说或许并不是一件好事,很可能意味着我的学习成本可能会更大。于是,我也搁置着。
vue-element-admin
官方文档:https://panjiachen.github.io/vue-element-admin-site/zh/guide/
Vue Antd Admin
官方文档:https://iczer.gitee.io/vue-antd-admin-docs/start/use.html#%E5%87%86%E5%A4%87
Ant Design Pro
官方文档:https://pro.antdv.com/docs/getting-started
这几个项目被推荐率也是极高的,从第一行介绍我基本就知道需要去学Vue的语法,奈何我太懒了,搁置着。
有好几小伙伴们听说我会jQuery,于是给我推荐了layui。我以前印象中好像听过这个框架,但一直没了解过他。但是,当我搜到它的时候,它已经不维护了
GitHub地址:https://github.com/sentsin/layui
我简单浏览下文档,其实它也有对应的一套”语法“,需要一定的学习成本,但不高。
第一感觉有点类似我以前写过的BootStrap,我对这不太感冒,感觉如果要接入可能还是需要自己写比较多的代码。
还有些小伙伴推荐或者我看到的文章推荐:x-admin/D2admin/smartchart/JEECG-BOOT/Dcat-admin/iview-admin等等等,在这里面还有些依赖着PHP/Python
总的来说,我还是觉得这些框架有一定的学习成本(我真的是懒出天际了)。可能需要我去部署后端,也可能需要我学习前端的框架语法,也可能让我学Vue
看到这里,可能你们很好奇我最后选了什么作为austin的前端,都已经被我筛了这么多了。在公布之前,我想说的是:如果想要页面好看和灵活性高还是得学习Vue。从上面我被推荐的框架中,好多都是在Vue的基础上改动的,并且我敢肯定:还有很多基于Vue且好用的后台是我不知道的。
注:我这里指代跟我一样不懂前端的(如果本身就已经懂前端,你说啥都对)
我最后选择了amis作为austin的前端。这个框架在我朋友圈只有一个小伙伴推荐,我第一次打开文档的时候,确实惊艳到我了
文档地址:https://baidu.gitee.io/amis/zh-CN/docs/index
它是一个低代码前端框架:amis 的渲染过程是将 json 转成对应的 React 组件
我花了半天粗略地刷了下文档,大概知道了JSON的结构(说实话,他这个文档写得挺可以的),然后我去GitHub找了一份模板,就直接开始动手了,readme十分简短。
GitHub:https://github.com/aisuda/amis-admin
这个前端低代码工具还有个好处就是可以通过可视化编辑器拖拉生成JSON代码,将生成好的代码直接往自己本地一贴,就完事了,确实挺方便的。
可视化编辑器的地址:https://aisuda.github.io/amis-editor-demo/
其实没什么好讲的,无非就是在页面上拖拉得到一个页面,然后调用API的时候看下文档的姿势。
在这个过程中我也去看了下这个框架的评价,发现百度内部很多系统就用的这个框架来搭建页面的,也看到Bigo也有在线上使用这个框架来搭建后台。有一线/二线公司都在线上使用该框架了,我就认为问题不大了。
总的来说,我这次搭建austin后台实际编码时间没多少,都在改JSON配置和查文档。我周六下午2点到的图书馆,新建了GitHub仓库,在6点闭馆前就已经搭出个大概页面了,然后在周日空闲时间里再完善了几下,感觉可以用了
austin-amis仓库地址:https://github.com/ZhongFuCheng3y/austin-admin
在搭建的过程中,amis低代码框架还是有地方可吐槽的,就是它的灵活性太低。我们的接口返回值需要迎合它的主体结构,当我们如果有嵌套JSON这种就变得异常难处理,表单无法用表达式进行回显等等。
它并不完美,很可能需要我用些奇怪的姿势妥协,不要问我接口返回的时候为啥转了一层Map
不管怎么说,这不妨碍我花了极短的时间就能搭出一个能看的后台管理页面(CRUD已齐全)
目前搭好的前端能用,也只能用一点点,后面会逐渐完善它的配置和功能的。我后面有链路追踪的功能,肯定要在后台这把清洗后的数据提供给后台进行查询,但也不会花比较长的篇幅再来聊前端这事了。
我一直定位是在后端的代码上,至于前端我能学,但我又不想学。怎么说呢,利益最大化吧。我把学前端的时间花在学后端上,或许可能对我有更大的受益。现在基本前后端分离了,在公司我也没什么机会写前端。
下一篇很有可能是聊分布式定时任务框架上,我发现我的进度可以的,这个季度拿个4.0应该问题不大了。
都看到这里了,点个赞一点都不过分吧?我是3y,下期见。
关注我的微信公众号【Java3y】除了技术我还会聊点日常,有些话只能悄悄说~ 【对线面试官+从零编写Java项目】 持续高强度更新中!求star!!原创不易!!求三连!!
austin项目源码Gitee链接:gitee.com/austin
austin项目源码GitHub链接:github.com/austin
来源:https://www.cnblogs.com/Java3y/p/16020226.html
读
本文主要总结了在ICBU的核心沟通场景下服务端在此次性能优化过程中做的工作,供大家参考讨论。
一、背景与效果
ICBU的核心沟通场景有了10年的“积累”,核心场景的界面响应耗时被拉的越来越长,也让性能优化工作提上了日程,先说结论,经过这一波前后端齐心协力的优化努力,两个核心界面90分位的数据,FCP平均由2.6s下降到1.9s,LCP平均由2.8s下降到2s。本文主要着眼于服务端在此次性能优化过程中做的工作,供大家参考讨论。
二、措施一:流式分块传输(核心)
2.1. HTTP分块传输介绍
分块传输编码(Chunked Transfer Encoding)是一种HTTP/1.1协议中的数据传输机制,它允许服务器在不知道整个内容大小的情况下,就开始传输动态生成的内容。这种机制特别适用于生成大量数据或者由于某种原因数据大小未知的情况。
在分块传输编码中,数据被分为一系列的“块”(chunk)。每一个块都包括一个长度标识(以十六进制格式表示)和紧随其后的数据本身,然后是一个CRLF(即"\r\n",代表回车和换行)来结束这个块。块的长度标识会告诉接收方这个块的数据部分有多长,使得接收方可以知道何时结束这一块并准备好读取下一块。
当所有数据都发送完毕时,服务器会发送一个长度为零的块,表明数据已经全部发送完毕。零长度块后面可能会跟随一些附加的头部信息(尾部头部),然后再用一个CRLF来结束整个消息体。
我们可以借助分块传输协议完成对切分好的vm进行分块推送,从而达到整体HTML界面流式渲染的效果,在实现时,只需要对HTTP的header进行改造即可:
public void chunked(HttpServletRequest request, HttpServletResponse response) {
try (PrintWriter writer = response.getWriter()) {
// 设置响应类型和编码
oriResponse.setContentType(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8");
oriResponse.setHeader("Transfer-Encoding", "chunked");
oriResponse.addHeader("X-Accel-Buffering", "no");
// 第一段
Context modelMain = getmessengerMainContext(request, response, aliId);
flushVm("/velocity/layout/Main.vm", modelMain, writer);
// 第二段
Context modelSec = getmessengerSecondContext(request, response, aliId, user);
flushVm("/velocity/layout/Second.vm", modelSec, writer);
// 第三段
Context modelThird = getmessengerThirdContext(request, response, user);
flushVm("/velocity/layout/Third.vm", modelThird, writer);
} catch (Exception e) {
// logger
}
}
private void flushVm(String templateName, Context model, PrintWriter writer) throws Exception {
StringWriter tmpWri = new StringWriter();
// vm渲染
engine.mergeTemplate(templateName, "UTF-8", model, tmpWri);
// 数据写出
writer.write(tmpWri.toString());
writer.flush();
}
2.2. 页面流式分块传输优化方案
我们现在的大部分应用都是springmvc架构,浏览器发起请求,后端服务器进行数据准备与vm渲染,之后返回html给浏览器。
从请求到达服务端开始计算,一次HTML请求到页面加载完全要经过网络请求、网络传输与前端资源渲染三个阶段:
HTML流式输出,思路是对HTML界面进行拆分,之后由服务器分批进行推送,这样做有两个好处:
这个思路对需要加载资源较多的页面有很明显的效果,在我们此次的界面优化中,页面的FCP与LCP均有300ms-400ms的性能提升,在进行vm界面的数据拆分时,有以下几个技巧:
2.3. 注意事项
此次优化的应用与界面本身历史包袱很重,在进行流式改造的过程中,我们遇到了不少的阻力与挑战,在解决问题的过程也学到了很多东西,这部分主要对遇到的问题进行整理。
/**
* 防止filter或者其他代理包装了response并开启缓存
* 这里获取到真实的response
*
* @param response
* @return
*/
private static HttpServletResponse getResponse(HttpServletResponse response) {
ServletResponse resp = response;
while (resp instanceof ServletResponseWrapper) {
ServletResponseWrapper responseWrapper = (ServletResponseWrapper) resp;
resp = responseWrapper.getResponse();
}
return (HttpServletResponse) resp;
}
为了确保cookie能正常写入,需要指定cookie的SameSite=None。
我们的项目中使用的模板引擎为VelocityEngine,在流式分块传输时,需要手动渲染vm:
private void flushVm(String templateName, Context model, PrintWriter writer) throws Exception {
StringWriter tmpWri = new StringWriter();
// vm渲染
engine.mergeTemplate(templateName, "UTF-8", model, tmpWri);
// 数据写出
writer.write(tmpWri.toString());
writer.flush();
}
需要注意的是VelocityEngine模板引擎支持自定义tool,在vm文件中是如下的形式,当vm引擎渲染到对应位置时,会调用配置好的方法进行解析:
<title>$tool.do("xx", "$!{arg}")</title>
如果用注解的形式进行vm渲染,框架本身会帮我们自动做tools的初始化。但如果我们想手动渲染vm,那么需要将这些tools初始化到context中:
/**
* 初始化 toolbox.xml 中的工具
*/
private Context initContext(HttpServletRequest request, HttpServletResponse response) {
ViewToolContext viewToolContext = null;
try {
ServletContext servletContext = request.getServletContext();
viewToolContext = new ViewToolContext(engine, request, response, servletContext);
VelocityToolsRepository velocityToolsRepository = VelocityToolsRepository.get(servletContext);
if (velocityToolsRepository != null) {
viewToolContext.putAll(velocityToolsRepository.getTools());
}
} catch (Exception e) {
LOGGER.error("createVelocityContext error", e);
return null;
}
}
对于比较古老的应用,VelocityToolsRepository需要将二方包版本进行升级,而且需要注意,velocity-spring-boot-starter升级后可能存在tool.xml文件失效的问题,建议可以采用注解的形式实现tool,并且注意tool对应java类的路径。
@DefaultKey("assetsVersion")
public class AssertsVersionTool extends SafeConfig {
public String get(String key) {
return AssetsVersionUtil.get(key);
}
}
server {
location ~ ^/chunked {
add_header X-Accel-Buffering no;
proxy_http_version 1.1;
proxy_cache off; # 关闭缓存
proxy_buffering off; # 关闭代理缓冲
chunked_transfer_encoding on; # 开启分块传输编码
proxy_pass http://backends;
}
}
SC_Enabled on;
SC_AppName gangesweb;
SC_OldDomains //b.alicdn.com;
SC_NewDomains //b.alicdn.com;
SC_OldDomains //bg.alicdn.com;
SC_NewDomains //bg.alicdn.com;
SC_FilterCntType text/html;
SC_AsyncVariableNames asyncResource;
SC_MaxUrlLen 1024;
详见:https://github.com/dinic/styleCombine3
proxy_buffers 128 32k;
proxy_buffer_size 64k;
proxy_busy_buffers_size 128k;
client_header_buffer_size 32k;
large_client_header_buffers 4 16k;
如果页面在浏览器上有问题时,可以通过curl命令在服务器上直接访问,排查是否为ngnix的问题:
curl --trace - 'http://127.0.0.1:7001/chunked' \
-H 'cookie: xxx'
在开始,我们使用StreamingResponseBody来实现的分块传输:
@GetMapping("/chunked")
public ResponseEntity<StreamingResponseBody> streamChunkedData() {
StreamingResponseBody stream = outputStream -> {
// 第一段
Context modelMain = getmessengerMainContext(request, response, aliId);
flushVm("/velocity/layout/Main.vm", modelMain, writer);
// 第二段
Context modelSec = getmessengerSecondContext(request, response, aliId, user);
flushVm("/velocity/layout/Second.vm", modelSec, writer);
// 第三段
Context modelThird = getmessengerThirdContext(request, response, user);
flushVm("/velocity/layout/Third.vm", modelThird, writer);
}
};
return ResponseEntity.ok()
.contentType(MediaType.TEXT_HTML)
.body(stream);
}
}
但是我们在运行时发现vm的部分变量会渲染失败,卡点了不少时间,后面在排查过程中发现应用在处理http请求时会在ThreadLocal中进行用户数据、request数据与部分上下文的存储,而后续vm数据准备时,有一部分数据是直接从中读取或者间接依赖的,而StreamingResponseBody本身是异步的(可以看如下的代码注释),这就导致新开辟的线程读不到原线程ThreadLocal的数据,进而渲染错误:
/**
* A controller method return value type for asynchronous request processing
* where the application can write directly to the response {@code OutputStream}
* without holding up the Servlet container thread.
*
* <p><strong>Note:</strong> when using this option it is highly recommended to
* configure explicitly the TaskExecutor used in Spring MVC for executing
* asynchronous requests. Both the MVC Java config and the MVC namespaces provide
* options to configure asynchronous handling. If not using those, an application
* can set the {@code taskExecutor} property of
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
* RequestMappingHandlerAdapter}.
*
* @author Rossen Stoyanchev
* @since 4.2
*/
@FunctionalInterface
public interface StreamingResponseBody {
/**
* A callback for writing to the response body.
* @param outputStream the stream for the response body
* @throws IOException an exception while writing
*/
void writeTo(OutputStream outputStream) throws IOException;
}
三、措施二:非流量中间件优化
在性能优化过程中,我们发现在流量高峰期,某个服务接口的平均耗时会显著升高,结合arths分析发现,是由于在流量高峰期,对于配置中心的调用被限流了。原因是配置中心的使用不规范,每次都是调用getConfig方法从配置中心服务端拉取的数据。
在读取配置中心的配置时,更标准的使用方法是由配置中心主动推送变更,客户端监听配置信息缓存到本地,这样,每次读取配置其实读取的是机器的本地缓存,可以参考如下的方式:
public static void registerDynamicConfig(final String dataIdKey, final String groupName) {
IOException initError = null;
try {
String e = Diamond.getConfig(dataIdKey, groupName, DEFAULT_TIME_OUT);
if(e != null) {
getGroup(groupName).put(dataIdKey, e);
}
logger.info("Diamond config init: dataId=" + dataIdKey + ", groupName=" + groupName + "; initValue=" + e);
} catch (IOException e) {
logger.error("Diamond config init error: dataId=" + dataIdKey, e);
initError = e;
}
Diamond.addListener(dataIdKey, groupName, new ManagerListener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String s) {
String oldValue = (String)DynamicConfig.getGroup(groupName).get(dataIdKey);
DynamicConfig.getGroup(groupName).put(dataIdKey, s);
DynamicConfig.logger.warn(
"Receive config update: dataId=" + dataIdKey + ", newValue=" + s + ", oldValue=" + oldValue);
}
});
if(initError != null) {
throw new RuntimeException("Diamond config init error: dataId=" + dataIdKey, initError);
}
}
四、措施三:数据直出
数据直出有利有弊,对于页面的加载性能有正向影响的同时,也会同时导致HTTP的response增大以及服务端RT的升高。数据直出与流式分块传输相结合的效果可能会更好,当服务端分块响应HTTP请求时,本身的response就被切割成多块,单次大小得到了控制,流式分块传输下,服务端分批执行数据准备的策略也能很好的缓冲RT增长的问题。
五、措施四:本地缓存
以我们遇到的一个问题为例,我们的云盘文件列表需要在后端准备好文件所属人的昵称,这是在后端服务器由用户id调用会员的rpc接口实时查询的。分析这个场景,我们不难发现,同一时间,IM场景下的文件所属人往往是其中归属在聊天的几个人名下的,因此,可以利用HashMap作为缓存rpc查询到的会员昵称,避免重复的查询与调用。
六、措施五:下线历史债务
针对有历史包袱的应用,历史债务导致的额外耗时往往很大,这些历史代码可能包括以下几类:
作者:树塔
来源-微信公众号:阿里云开发者
出处:https://mp.weixin.qq.com/s/06eND-fUGQ7Y6gwJxmvwQQ
*请认真填写需求信息,我们会在24小时内与您取得联系。