Web开发方面、专注于Web
2月2
编写干净、易于理解且可维护的代码是每位开发者必须掌握的技能。我们将探讨提高代码质量的最重要原则,并为每个原则提供代码示例。

如何命名变量(以及其他事物)

我们不用内存地址而使用名称的原因是:名称更容易记忆。更重要的是,它们能提供更多关于变量的信息,以便其他人理解其意义。

找到一个好的名字可能需要一些时间,但这将在未来为你和你的团队节省更多时间。而且我确信大多数读者都遇到过这种情况:几个月后再次访问自己的代码时,很难理解之前做了什么。


如何创建有意义的名字
不要用注释来解释变量的用途。如果一个名字需要注释,那么你应该花时间重命名那个变量,而不是写注释。

差:

var d; // elapsed time in days
我见过这种代码无数次。人们普遍认为应该用注释来隐藏代码的混乱,但这是一种常见的误解。除非有充分理由,否则不要使用 x、y、a 或 b 等字母作为变量名(循环变量除外)。

好:

var elapsedTimeInDays;
var daysSinceCreation;
var daysSinceModification;
这些名称要好得多。它们告诉你被测量的内容以及该测量的单位。



避免错误信息
注意那些有特定含义的词语。除非其类型确实是 List,否则不要将一组账户称为 accountList。这个词有特定的含义,可能会导致错误的结论。

即使类型是列表,accounts 也是一个更简单、更好的名称。

差:

var accountList = [];
好:

var accounts = [];


避免噪音词
噪声词是指那些不提供任何关于变量额外信息的词。它们是冗余的,应该被删除。

一些常见的噪声词有:

The (prefix)

Info

Data

Variable

Object

Manager

如果你的类名是 UserInfo,你只需去掉 Info,直接用 User。将类名用 BookData 代替 Book 是毋庸置疑的,因为类本来就是为了存储数据而存在的。


使用发音友好的名称
如果你不会读一个名字,讨论它时就会显得很愚蠢。

差:

const yyyymmdstr = moment().format("YYYY/MM/DD");
好:

const currentDate = moment().format("YYYY/MM/DD");


使用可搜索的名称
避免在代码中使用魔法数字。选择可搜索的、命名的常量。不要为常量使用单个字母的名称,因为它们可能出现在很多地方,因此不易搜索。

差:

if (student.classes.length < 7) {
   // Do something
}
好:

if (student.classes.length < MAX_CLASSES_PER_STUDENT) {
    // Do something
}
这样更好,因为 MAX_CLASSES_PER_STUDENT 可以在代码的许多地方使用。如果将来需要将其改为 6,我们只需更改常量即可。

糟糕的例子会在读者脑海中产生疑问,比如 7 的重要性是什么?

你也应该使用你语言的常量命名和声明约定,例如 Java 中的 private static final 或 JavaScript 中的 const。
12月20
在使用Vue3、TypeScript和ElementUIPlus构建前端时,遇到上传表单数据为[objectObject]且后端SpringBoot报错的情况。通过排查发现前端代码中的`file`对象需要将`raw`属性值作为上传内容,解决了`MissingServletRequestPartException`。

项目场景:
前端:Vue3 + ts + antdv
后端:springboot


打印的file:
发现file内部还有一个raw属性,我们要上传的是这个raw里面的内容

解决方案:
更改前端代码如下:

const file = uploadFileList.value[i];
    formData2.append('file', toRaw(file.raw));
    formData2.append('parentAdcode', formData.parentAdcode);
    importObjects(formData2).then(res=>{
      this.$message.success('导入成功')
    })
11月5

Restful API规范

10:18Web开发  From: 本站原创
一、简介
        RESTful API 是应用程序接口 (API) 的一种架构风格,它使用 HTTP 请求来访问和使用数据。该数据可用于 GET、PUT、POST 和 DELETE 数据类型,这些数据类型是指有关资源的操作的读取、更新、创建和删除。

        RESTful API(也称为 RESTful Web 服务或 REST API)基于表示状态传输 (REST),它是一种架构风格和经常用于 Web 服务开发的通信方法。

二、设计原则。
URL即资源,使用名词表示资源
接口应直观、简洁、风格命名保持一致
必须版本化,具有足够的灵活性来支持上层UI
三、RFC3986标准。
        RFC3986标准,简单讲就是规定了如下:除了 数字 + 字母 + -_.~ 不会被转义,其他字符都会被以百分号(%)后跟两位十六进制数 %{hex} 的方式进行转义。

         www 的 post form data 也就是 x-www-form-urlencode 的编码规则:除 -_.(没有 ~) 之外的所有 非字母、非数字 的字符都将被替换成 百分号(%)后跟两位十六进制数 %{hex},空格(注意)则编码为加号 +。

        RFC3986对 ~ 不做转码,x-www-form-urlencode 对 ~ 做转码 %7E。

        RFC3986对 空格 转为 %20,x-www-form-urlencode 对 空格 转为 +。

        JS的URL编码方式:encodeURIComponent。

// encodeURIComponent
console.log(encodeURIComponent("hello233 ~-_."))
hello233%20~-_.
5月16
我正在使用旧版Spring应用程序,并且想要迁移到Spring Boot。我的意图是使用spring-boot-starter-data- jpa。因此,我在pom.xml(管理所有spring-boot-dependencies)中添加了以下部分:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
</parent>
但这搞砸了我暂时需要保留的某些依赖项。我目前正在使用Selenium依赖项(版本2.53.0;从另一个项目中过渡添加),但是spring-
boot正在获取的依赖项3.9.1。

我想排除3.9.1依赖关系,但exclusion过滤器无法按预期工作。

不能乱<excludes>然后尝试弄清楚您需要再次包含什么(在弄清楚要排除的内容之后)。只需覆盖《Spring
Boot参考指南》中此处解释的版本即可。

假设您将spring-boot-starter- parent用作父项,则只需将a添加<selenium.version>到您的<properties>部分中即可指定所需的版本。

<properties>
  <selenium.version>2.53.0</selenium.version>
</properties>
这将使Spring Boot使用所需的版本。
1月2
在SpringBoot中使用单元测试时,出现以下报错,意思是创建名为‘serverEndpointExporter’的bean时出错。
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
[20240102 14:42:43][main][ERROR][org.springframework.boot.SpringApplication:854] Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverEndpointExporter' defined in class path resource [com/nine/rivers/bms/system/config/WebSocketConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953)
  at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
  at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:765)
  at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:445)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)
  at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:121)
  at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
  at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
  at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
  at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
  at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
  at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248)
  at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$6(ClassBasedTestDescriptor.java:350)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:355)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$7(ClassBasedTestDescriptor.java:350)
  at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
  at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
  at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
  at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
  at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
  at java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312)
  at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:743)
  at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:742)
  at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:349)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$4(ClassBasedTestDescriptor.java:270)
  at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:269)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:259)
  at java.util.Optional.orElseGet(Optional.java:267)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:258)
  at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
  at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:101)
  at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
  at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:100)
  at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:65)
  at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:111)
  at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
  at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
  at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
  at java.util.ArrayList.forEach(ArrayList.java:1259)

问题分析
通过报错提示可以看出,在创建命名为 ‘serverEndpointExporter’ 的bean时出错,而这个bean是webscoket中的,而webscoket是需要依赖tomcat等容器才能启动的,恰巧运行单元测试不需要依赖tomcat容器,两者互相矛盾这就是出现该问题的原因了。

问题解决
使用tomcat容器运行单元测试,在注解 @SpringbootTest 中追加以下内容即可:
@SpringbootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
12月25
前后端通过websocket通信,但是需要鉴权,可以利用websocket的子协议来传输token,代码如下:
var ws = new WebSocket("ws://localhost/ws",[token]);
在后面加上[这里是token内容]。这样请求头就会携带:
Sec-WebSocket-Protocol:token value
参数是一个数组。如果数组有多个值,后端也会接受到逗号隔开的值。
后端可以获取这个token进行鉴权。
重点来了!
如果传递了token参数,后端响应的时候,也必须带上这个token响应!否则前端接收不到数据!
我竟然忘记前端传过来的token是加密后的,首先解密后去校验,通过后再把原加密token放在响应头即可, 如果不一致也会导致前端接收不到消息。
如下是java的例子“:
HttpServletResponse response = (HttpServletResponse)servletResponse;
response.setHeader("Sec-WebSocket-Protocol", token);
12月19
咱话不多说,正如标题所见,实践开始。

首先打开cmd,win键+r打开运行,输入cmd打开控制台。

在控制台中输入msg / server:IP地址 * "要发送的内容。"   注:(这里的引号要加,且*号左右必须空一格)

例如:我的ipv4地址是192.168.90.1**    注:(用ipconfig命令可查)
7月17
Linux 系统中,Spring Boot 应用以 java -jar 命令启动时,会在操作系统的 /tmp 目录下生成一个 tomcat(或 undertow )临时目录,上传的文件先要转换成临时文件保存在这个文件夹下面。由于临时 /tmp 目录下的文件,在长时间(10天)没有使用的情况下,系统执行了 tmp 目录清理服务(systemd-tmpfiles-clean.service),导致 /tmp/undertow...8090 文件被清理,然而在上传的时候,undertow 服务器需要创建/tmp/undertow...8090/undertow...upload 临时文件,但是调用 Files.createFile(...) 的时候就会发现找不到父目录,才导致了以上的错误。

具体错误日志(参考)
undertow

java.nio.file.NoSuchFileException: /tmp/undertow.17753558642503713859.8085/undertow7370242804103803588upload

Tomcat

The temporary upload location [/tmp/tomcat.7957874575370093230.8088/work/Tomcat/localhost/ROOT] is not valid

重现方法
找到类 io.undertow.server.handlers.form.MultiPartParserDefinition
定位到如下代码

@Override
public void beginPart(final HeaderMap headers) {
    this.currentFileSize = 0;
    this.headers = headers;
    final String disposition = headers.getFirst(Headers.CONTENT_DISPOSITION);
    if (disposition != null) {
        if (disposition.startsWith("form-data")) {
            currentName = Headers.extractQuotedValueFromHeader(disposition, "name");
            fileName = Headers.extractQuotedValueFromHeaderWithEncoding(disposition, "filename");
            if (fileName != null && fileSizeThreshold == 0) {
                try {
                    if (tempFileLocation != null) {
                        file = Files.createTempFile(tempFileLocation, "undertow", "upload");
                    } else {
                        file = Files.createTempFile("undertow", "upload");
                    }
                    createdFiles.add(file);
                    fileChannel = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

在 createdFiles.add(file); 处打断点,复制file的 path 的值找到该文件并将其删除;放开断点,错误重现;
6月30
在做javaweb项目时,关于统一错误页面在开发的过程中就做过编码,并且一直都很有效,像500,404,403等常规错误码都能得到有效处理,但是400却不行,而且还暴露tomcat的版本信息,这是很严重的安全漏洞
解决方法百度和测试很多,总结如下:

问题产生:
根据rfc规范,url中不允许有 |,{,}等特殊字符,但在实际生产中还是有些url有可能携带有这些字符,特别是|还是较为常见的。在tomcat升级到7以后,对url字符的检查都变严格了,如果出现这类字符,服务器tomcat将直接返回400状态码。

方法一:降低tomcat的版本
开发人员增加一项设置,允许配置在url可以出现的特殊字符,但也仅限于|,{,}三种,见:http://tomcat.apache.org/tomcat-8.0-doc/config/systemprops.html#Other
该项设置在以下版本的tomcat中有效:

- 8.5.x for 8.5.12 onwards

- 8.0.x for 8.0.42 onwards

- 7.0.x for 7.0.76 onwards
- 这个只是允许出现一些特殊字符,并没有说是全部特殊字符。
好像是tomcat7.9以上的版本,都不支持请求链接上带有特殊字符.否则会报400错误,
tomcat请求中包含特殊字符 [] | {} 发送get请求失败:
原因:
这是因为Tomcat严格按照 RFC 3986规范进行访问解析,而 RFC 3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。传入的参数中有"[]"、""不在RFC3986中的保留字段中,所以会报这个错。

方法二:修改server.xml
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" relaxedQueryChars="[]|{}^&#x5c;&#x60;&quot;&lt;&gt;" redirectPort="8443" />
方法三:修改server.xml(推荐方法)
在Host里加入:
<Valve className="org.apache.catalina.valves.ErrorReportValve"  showReport="false" showServerInfo="false" />
方法三 修改后只会显示出HTTP 400 错误,不会打印堆栈和tomcat版本号信息
2月22

nginx目录穿越漏洞

10:59Web开发  From: 本站原创
漏洞原理
nginx在配置别名(Alias)的时候忘记加/,则可能实现目录穿越
配置中设置目录别名时/files配置为/home/的别名,那么当我们访问
/files…/时,nginx实际处理的路径时/home/…/,从而实现了穿越目录

location /file{
autoindex on;
alias /home/;
}

所以修复这个漏洞也就是将上面的/file/闭合成这样,就不支持../这些回上一层目录的操作了。
分页: 1/13 第一页 1 2 3 4 5 6 7 8 9 10 下页 最后页 [ 显示模式: 摘要 | 列表 ]