开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

springboot单文件上传和多文件上传

发表于 2021-07-24

1、设置文件上传大小的限制:

yml配置文件设置:

1
2
3
4
5
yaml复制代码spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB

2、前端页面:

3、control层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
less复制代码package com.jf3q.study.control;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@Controller
@Slf4j
public class RegControl {

@GetMapping("/toreg")
public String toreg(){
return "register";
}


/**
*
* 平时遇到自己解决不了的bug问题可以来编程问答平台悬赏问答,
* 解决不了不要钱,地址:https://www.jf3q.com/
* @param username
* @param password
* @param faceImg
* @param lifeimg
* @return
*/
@PostMapping("/reg")
public String reg( @RequestParam("username") String username,
@RequestParam("username") String password,
@RequestParam("faceImg") MultipartFile faceImg,
@RequestParam("lifeimg") MultipartFile[] lifeimg ) throws IOException {

log.info("username:{},password:{},faceImg:{},lifeimg:{}",username,password,faceImg.getSize(),lifeimg.length);

if(!faceImg.isEmpty()){
String originalFilename = faceImg.getOriginalFilename();//获取上传文件的名字
faceImg.transferTo(new File("E:\\upload\\"+originalFilename));
}
if(lifeimg.length > 0){
for(MultipartFile img :lifeimg){
if(!img.isEmpty()){
String originalFilename = img.getOriginalFilename();//获取上传文件的名字
img.transferTo(new File("E:\\upload\\"+originalFilename));
}

}
}


return "login";
}
}

4、如果有拦截器的话,拦截器设置下放行

1
2
3
4
5
6
typescript复制代码@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //所有请求都被拦截,包括静态资源
.excludePathPatterns("/","/login","/toreg","/reg","/img/**","/js/**","/css/**","/favicon.ico");//设置不拦截的路径
}

5、效果如下:

有什么不懂得可以q我1913284695

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【规则引擎】多种规则引擎(处理复杂业务逻辑)整理

发表于 2021-07-24

1 规则引擎:(低耦合)

2 全面介绍所有的规则引擎:

www.jianshu.com/p/41ea7a430… 包含了 业界常用的4-5种

www.ibm.com/support/kno… IBM参考

www.jboss.org/drools/docu… drools 官方文档

blog.csdn.net/lifetragedy… 教程简介

3 其他参考

google的表达式引擎aviator

loveshisong.cn/%E7%BC%96%E…

源码:github.com/killme2008/…

4 阿里巴巴业务规则动态脚本引擎解析工具:QLExpress

blog.csdn.net/YuYunTan/ar…

源码:github.com/tanqiwei/QL…

5 JUEL:表达式引擎

juel.sourceforge.net/guide/start… 流程引擎中常用来,各种rpc http等接口的调用

6 drools:

www.ibm.com/developerwo… 基于rete算法的规则引擎

www.ibm.com/developerwo… IBM06年的使用

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot技术实践-SpringRetry重试框架

发表于 2021-07-24

一、环境搭建

  1. 加入SpringRetry依赖,SpringRetry使用AOP实现,所以也需要加入AOP包
1
2
3
4
5
6
7
8
9
xml复制代码<!-- SpringRetry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
  1. 官方文档
    • www.baeldung.com/spring-retr…

二、RetryTemplate

2.1 RetryTemplate

  1. RetryTemplate封装了Retry基本操作
    • org.springframework.retry.support.RetryTemplate
  2. RetryTemplate中可以指定监听、回退策略、重试策略等
  3. 只需要正常new RetryTemplate()即可使用

2.2 RetryListener

  1. RetryListener指定了当执行过程中出现错误时的回调
    • org.springframework.retry.RetryListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码package org.springframework.retry;

public interface RetryListener {

/**
* 任务开始执行时调用,只调用一次
*/
<T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback);

/**
* 任务执行结束时(包含重试)调用,只调用一次
*/
<T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);

/**
* 出现错误时回调
*/
<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
}
  1. 配置之后在RetryTemplate中指定

2.3 回退策略

2.3.1 FixedBackOffPolicy

  1. 当出现错误时延迟多少时间继续调用
1
2
3
java复制代码FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(1000L);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
  1. 配置之后在RetryTemplate中指定

2.3.2 ExponentialBackOffPolicy

  1. 当出现错误时第一次按照指定延迟时间延迟后按照指数进行延迟
1
2
3
4
5
java复制代码// 指数回退(秒),第一次回退1s,第二次回退2s,第三次4秒,第四次8秒
ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(1000L);
exponentialBackOffPolicy.setMultiplier(2);
retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);
  1. 配置之后在RetryTemplate中指定

2.4 重试策略

  1. 重试策略主要指定出现错误时重试次数
1
2
3
4
java复制代码// 重试策略
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(5);
retryTemplate.setRetryPolicy(retryPolicy);
  1. 配置之后在RetryTemplate中指定

2.5 RetryCallback

  1. RetryCallback为retryTemplate.execute时执行的回调
    • public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E

image.png

2.6 核心使用

  1. 可以使用RetryTemplate完成简单使用
  2. 配置retryTemplate
    • 指定回退策略为ExponentialBackOffPolicy
    • 指定重试策略为SimpleRetryPolicy
    • 指定监听器RetryListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
java复制代码import com.codecoord.util.PrintUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

@Configuration
public class RetryTemplateConfig {

/**
* 注入retryTemplate
*/
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();

/// 回退固定时间(秒)
/* FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(1000L);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);*/

// 指数回退(秒),第一次回退1s,第二次回退2s
ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(1000L);
exponentialBackOffPolicy.setMultiplier(2);
retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);

// 重试策略
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(5);
retryTemplate.setRetryPolicy(retryPolicy);

// 设置监听器,open和close分别在启动和结束时执行一次
RetryListener[] listeners = {
new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
PrintUtil.print("open");
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
PrintUtil.print("close");
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
PrintUtil.print("onError");
}
}
};
retryTemplate.setListeners(listeners);

return retryTemplate;
}
}
  1. 在controller中注入RetryTemplate使用,也可以是在service中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码@RestController
public class SpringRetryController {
@Resource
private RetryTemplate retryTemplate;
private static int count = 0;

@RequestMapping("/retry")
public Object retry() {
try {
count = 0;
retryTemplate.execute((RetryCallback<Void, RuntimeException>) context -> {
// 业务代码
// ....
// 模拟抛出异常
++count;
throw new RuntimeException("抛出异常");
});
} catch (RuntimeException e) {
System.out.println("Exception");
}

return "retry = " + count;
}
}
  1. 访问retry接口,然后观察日志输出
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码18:27:20.648 - http-nio-8888-exec-1 - open
18:27:20.649 - http-nio-8888-exec-1 - retryTemplate.execute执行
18:27:20.649 - http-nio-8888-exec-1 - onError
18:27:21.658 - http-nio-8888-exec-1 - retryTemplate.execute执行
18:27:21.658 - http-nio-8888-exec-1 - onError
18:27:23.670 - http-nio-8888-exec-1 - retryTemplate.execute执行
18:27:23.670 - http-nio-8888-exec-1 - onError
18:27:27.679 - http-nio-8888-exec-1 - retryTemplate.execute执行
18:27:27.679 - http-nio-8888-exec-1 - onError
18:27:35.681 - http-nio-8888-exec-1 - retryTemplate.execute执行
18:27:35.681 - http-nio-8888-exec-1 - onError
18:27:35.681 - http-nio-8888-exec-1 - close

三、EnableRetry

  1. @EnableRetry开启重试,在类上指定的时候方法将默认执行,重试三次
  2. 定义service,开启@EnableRetry注解和指定@Retryable,重试可以参考后面一节
1
2
3
4
5
6
7
8
9
10
java复制代码import org.springframework.retry.annotation.Retryable;

public interface RetryService {

/**
* 重试方法调用
*/
@Retryable
void retryServiceCall();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码import org.springframework.retry.annotation.EnableRetry;
import org.springframework.stereotype.Service;

@EnableRetry
@Service
public class RetryServiceImpl implements RetryService {

@Override
public void retryServiceCall() {
PrintUtil.print("方法调用..");
throw new RuntimeException("手工异常");
}
}
  1. controller中注入service
1
2
3
4
5
java复制代码@RequestMapping("/retryAnnotation")
public Object retryAnnotation() {
retryService.retryServiceCall();
return "retryAnnotation";
}
  1. 将会默认重试
1
2
3
4
java复制代码18:46:48.721 - http-nio-8888-exec-1 - 方法调用..
18:46:49.724 - http-nio-8888-exec-1 - 方法调用..
18:46:50.730 - http-nio-8888-exec-1 - 方法调用..
java.lang.RuntimeException: 手工异常

四、Retryable

  1. 用于需要重试的方法上的注解
  2. 有以下几个属性
    • Retryable注解参数
      • value:指定发生的异常进行重试
      • include:和value一样,默认空,当exclude也为空时,所有异常都重试
      • exclude:指定异常不重试,默认空,当include也为空时,所有异常都重试
      • maxAttemps:重试次数,默认3
      • backoff:重试补偿机制,默认没有
    • @Backoff 注解 重试补偿策略
      • 不设置参数时,默认使用FixedBackOffPolicy(指定等待时间),重试等待1000ms
      • 设置delay,使用FixedBackOffPolicy(指定等待设置delay和maxDealy时,重试等待在这两个值之间均态分布)
      • 设置delay、maxDealy、multiplier,使用 ExponentialBackOffPolicy(指数级重试间隔的实现),multiplier即指定延迟倍数,比如delay=5000L,multiplier=2,则第一次重试为5秒,第二次为10秒,第三次为20秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
java复制代码@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

/**
* Retry interceptor bean name to be applied for retryable method. Is mutually
* exclusive with other attributes.
* @return the retry interceptor bean name
*/
String interceptor() default "";

/**
* Exception types that are retryable. Synonym for includes(). Defaults to empty (and
* if excludes is also empty all exceptions are retried).
* @return exception types to retry
*/
Class<? extends Throwable>[] value() default {};

/**
* Exception types that are retryable. Defaults to empty (and if excludes is also
* empty all exceptions are retried).
* @return exception types to retry
*/
Class<? extends Throwable>[] include() default {};

/**
* Exception types that are not retryable. Defaults to empty (and if includes is also
* empty all exceptions are retried).
* If includes is empty but excludes is not, all not excluded exceptions are retried
* @return exception types not to retry
*/
Class<? extends Throwable>[] exclude() default {};

/**
* A unique label for statistics reporting. If not provided the caller may choose to
* ignore it, or provide a default.
*
* @return the label for the statistics
*/
String label() default "";

/**
* Flag to say that the retry is stateful: i.e. exceptions are re-thrown, but the
* retry policy is applied with the same policy to subsequent invocations with the
* same arguments. If false then retryable exceptions are not re-thrown.
* @return true if retry is stateful, default false
*/
boolean stateful() default false;

/**
* @return the maximum number of attempts (including the first failure), defaults to 3
*/
int maxAttempts() default 3;

/**
* @return an expression evaluated to the maximum number of attempts (including the first failure), defaults to 3
* Overrides {@link #maxAttempts()}.
* @date 1.2
*/
String maxAttemptsExpression() default "";

/**
* Specify the backoff properties for retrying this operation. The default is a
* simple {@link Backoff} specification with no properties - see it's documentation
* for defaults.
* @return a backoff specification
*/
Backoff backoff() default @Backoff();

/**
* Specify an expression to be evaluated after the {@code SimpleRetryPolicy.canRetry()}
* returns true - can be used to conditionally suppress the retry. Only invoked after
* an exception is thrown. The root object for the evaluation is the last {@code Throwable}.
* Other beans in the context can be referenced.
* For example:
* <pre class=code>
* {@code "message.contains('you can retry this')"}.
* </pre>
* and
* <pre class=code>
* {@code "@someBean.shouldRetry(#root)"}.
* </pre>
* @return the expression.
* @date 1.2
*/
String exceptionExpression() default "";

/**
* Bean names of retry listeners to use instead of default ones defined in Spring context
* @return retry listeners bean names
*/
String[] listeners() default {};

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
java复制代码@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Backoff {

/**
* Synonym for {@link #delay()}.
*
* @return the delay in milliseconds (default 1000)
*/
long value() default 1000;

/**
* A canonical backoff period. Used as an initial value in the exponential case, and
* as a minimum value in the uniform case.
* @return the initial or canonical backoff period in milliseconds (default 1000)
*/
long delay() default 0;

/**
* The maximimum wait (in milliseconds) between retries. If less than the
* {@link #delay()} then the default of
* {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
* is applied.
*
* @return the maximum delay between retries (default 0 = ignored)
*/
long maxDelay() default 0;

/**
* If positive, then used as a multiplier for generating the next delay for backoff.
*
* @return a multiplier to use to calculate the next backoff delay (default 0 =
* ignored)
*/
double multiplier() default 0;

/**
* An expression evaluating to the canonical backoff period. Used as an initial value
* in the exponential case, and as a minimum value in the uniform case. Overrides
* {@link #delay()}.
* @return the initial or canonical backoff period in milliseconds.
* @date 1.2
*/
String delayExpression() default "";

/**
* An expression evaluating to the maximimum wait (in milliseconds) between retries.
* If less than the {@link #delay()} then the default of
* {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
* is applied. Overrides {@link #maxDelay()}
*
* @return the maximum delay between retries (default 0 = ignored)
* @date 1.2
*/
String maxDelayExpression() default "";

/**
* Evaluates to a vaule used as a multiplier for generating the next delay for
* backoff. Overrides {@link #multiplier()}.
*
* @return a multiplier expression to use to calculate the next backoff delay (default
* 0 = ignored)
* @date 1.2
*/
String multiplierExpression() default "";

/**
* In the exponential case ({@link #multiplier()} &gt; 0) set this to true to have the
* backoff delays randomized, so that the maximum delay is multiplier times the
* previous delay and the distribution is uniform between the two values.
*
* @return the flag to signal randomization is required (default false)
*/
boolean random() default false;

}
  1. 在需要重试的方法上配置对应的重试次数、重试异常的异常类型、设置回退延迟时间、重试策略、方法监听名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@Component
public class PlatformClassService {
@Retryable(
// 重试异常的异常类型
value = {Exception.class},
// 最大重试次数
maxAttempts = 5,
// 设置回退延迟时间
backoff = @Backoff(delay = 500),
// 配置回调方法名称
listeners = "retryListener"
)
public void call() {
System.out.println("call...");
throw new RuntimeException("手工异常");
}
}
1
2
java复制代码// 初始延迟2秒,然后之后验收1.5倍延迟重试,总重试次数4
@Retryable(value = {Exception.class}, maxAttempts = 4, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
  1. 监听方法,在配置类中进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码/**
* 注解调用
*/
@Bean
public RetryListener retryListener() {
return new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
System.out.println("open context = " + context + ", callback = " + callback);
// 返回true继续执行后续调用
return true;
}

@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("close context = " + context + ", callback = " + callback);
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("onError context = " + context + ", callback = " + callback);
}
};
}
  1. 调用服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@RestController
public class SpringRetryController {
@Resource
private PlatformClassService platformClassService;

@RequestMapping("/retryPlatformCall")
public Object retryPlatformCall() {
try {
platformClassService.call();
} catch (Exception e) {
return "尝试调用失败";
}
return "retryPlatformCall";
}
}
  1. 调用结果

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MongoDB 导入导出和数据迁移

发表于 2021-07-23

迁移需求

现有测试服务器A 和 测试服务器 B,需要实现从测试服务器A向测试服务器B进行mongoDB 数据库的迁移。
可以使用 mongoDB 的导出工具 mongoexport 和导入工具 mongoimport 实现。
官方英文文档链接 mongoDB mongoexport ,mongoDB mongoimport

数据导出

mongoexport 参数项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
sql复制代码general options:
--help print usage
--version print the tool version and exit

verbosity options:
-v, --verbose=<level> more detailed log output (include multiple
times for more verbosity, e.g. -vvvvv, or
specify a numeric value, e.g. --verbose=N)
--quiet hide all log output

connection options:
-h, --host=<hostname> mongodb host to connect to (setname/host1,host2
for replica sets)
--port=<port> server port (can also use --host hostname:port)

authentication options:
-u, --username=<username> username for authentication
-p, --password=<password> password for authentication
--authenticationDatabase=<database-name> database that holds the user's credentials
--authenticationMechanism=<mechanism> authentication mechanism to use

namespace options:
-d, --db=<database-name> database to use
-c, --collection=<collection-name> collection to use

output options:
-f, --fields=<field>[,<field>]* comma separated list of field names (required
for exporting CSV) e.g. -f "name,age"
--fieldFile=<filename> file with field names - 1 per line
--type=<type> the output format, either json or csv (defaults
to 'json')
-o, --out=<filename> output file; if not specified, stdout is used
--jsonArray output to a JSON array rather than one object
per line
--pretty output JSON formatted to be human-readable

querying options:
-q, --query=<json> query filter, as a JSON string, e.g.,
'{x:{$gt:1}}'
--queryFile=<filename> path to a file containing a query filter (JSON)
-k, --slaveOk allow secondary reads if available (default
true)
--readPreference=<string>|<json> specify either a preference name or a
preference json object
--forceTableScan force a table scan (do not use $snapshot)
--skip=<count> number of documents to skip
--limit=<count> limit the number of documents to export
--sort=<json> sort order, as a JSON string, e.g. '{x:1}'
--assertExists if specified, export fails if the collection
does not exist (false)

导出集合为 .csv 文件

1
css复制代码mongoexport --db users --collection contacts --csv --fieldFile fields.txt --out /opt/backups/contacts.csv

导出集合为 .json 文件

1
css复制代码mongoexport --db sales --collection contacts --out contacts.json --journal

mongoimport 参数选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
sql复制代码general options:
--help print usage
--version print the tool version and exit

verbosity options:
-v, --verbose=<level> more detailed log output (include multiple
times for more verbosity, e.g. -vvvvv, or
specify a numeric value, e.g. --verbose=N)
--quiet hide all log output

connection options:
-h, --host=<hostname> mongodb host to connect to (setname/host1,host2
for replica sets)
--port=<port> server port (can also use --host hostname:port)

authentication options:
-u, --username=<username> username for authentication
-p, --password=<password> password for authentication
--authenticationDatabase=<database-name> database that holds the user's credentials
--authenticationMechanism=<mechanism> authentication mechanism to use

namespace options:
-d, --db=<database-name> database to use
-c, --collection=<collection-name> collection to use

input options:
-f, --fields=<field>[,<field>]* comma separated list of field names, e.g. -f
name,age
--fieldFile=<filename> file with field names - 1 per line
--file=<filename> file to import from; if not specified, stdin is
used
--headerline use first line in input source as the field
list (CSV and TSV only)
--jsonArray treat input source as a JSON array
--type=<type> input format to import: json, csv, or tsv
(defaults to 'json')

ingest options:
--drop drop collection before inserting documents
--ignoreBlanks ignore fields with empty values in CSV and TSV
--maintainInsertionOrder insert documents in the order of their
appearance in the input source
-j, --numInsertionWorkers=<number> number of insert operations to run concurrently
(defaults to 1)
--stopOnError stop importing at first insert/upsert error
--upsert insert or update objects that already exist
--upsertFields=<field>[,<field>]* comma-separated fields for the query part of
the upsert
--writeConcern=<write-concern-specifier> write concern options e.g. --writeConcern
majority, --writeConcern '{w: 3, wtimeout: 500,
fsync: true, j: true}' (defaults to 'majority')
--bypassDocumentValidation bypass document validation

mongoimport 导入 .csv 文件

1
css复制代码mongoimport --db users --collection contacts --type csv --file /opt/backups/contacts.csv

mongoimport 导入 .json 文件

1
css复制代码mongoimport --collection contacts --file contacts.json --journal

服务器实例

进入找到服务器A的 mongodb 安装目录下
例如:cd /usr/local/mongodb/bin

数据导出:
1
bash复制代码./mongoexport -d DataBaseName -c CollectionName -o bak.dat

其中,DataBaseName 为数据库名称,CollectionName 为集合名称,bak.dat 为导出后的名称

导出后的bak.dat将在 mongoexport所在的目录下。
例如:

1
bash复制代码./mongoexport -d user -c guset -o guset.dat

将数据库 user 下的集合 guset 导出到 mongoexport 所在的目录下,并将其命名为 guset.dat

导入数据

移动导出的数据文件到另外一台服务器的mongo 目录下

1
bash复制代码sudo mv /tmp/bak.dat /db/mongo/bin

注:bak.dat 问原来服务器导出的数据文件。
进入 mongoDB 安装目录。

1
bash复制代码cd /db/mongo/bin

使用

1
bash复制代码./mongoimport -h 127.0.0.1:port -u xxx -p xxx-d DataBaseName -c CollectionName bak.dat

其中,DataBaseName 为数据库名称,CollectionName 为集合名称,bak.dat 为导入的集合

实例操作

1
sql复制代码./mongoimport -h 127.0.0.1:27017 -u user -p user -d guset -c guset bak.dat

操作结束。

注意 ⚠️

其中有写到

Do not use mongoimport and mongoexport for full instance, production backups because they will not reliably capture data type information. Use mongodump and mongorestore as described in “Backup Strategies for MongoDB Systems” for this kind of functionality.

即不要将 mongoimport 和 mongoexport 用于完整实例生产备份,因为它们无法可靠地捕获数据类型信息。使用 mongodump 和 mongorestore ,如“MongoDB系统的备份策略”中所述,以实现此类功能。
mongoDB mongodump 文档

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

springboot整合redis使用scan代替keys方

发表于 2021-07-23

​

一、为什么使用scan代替keys?

·因为redis是单线程的,使用keys命令,如果redis中的key非常庞大,那么这条命令执行时间非常长,这个时候就会阻塞到其他命令的执行,所以要redis也提供给我们另一个scan命令来解决这种常见的场景,

二、scan有什么优势呢?

  1. scan命令的时间复杂度虽然也是O(N),但它是分次进行的,不会阻塞线程。
  2. scan命令提供了limit参数,可以控制每次返回结果的最大条数。

这两个优势就帮助我们解决了上面的难题,不过scan命令也并不是完美的,它返回的结果有可能重复,因此需要客户端去重。但是这个问题很好解决,我们可以用Set集合巧妙的处理。

Redis Scan 命令用于迭代数据库中的数据库键。

SCAN 命令是一个基于游标的迭代器,每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。

SCAN 返回一个包含两个元素的数组, 第一个元素是用于进行下一次迭代的新游标, 而第二个元素则是一个数组, 这个数组中包含了所有被迭代的元素。如果新游标返回 0 表示迭代已结束。

如下代码表示,从redis服务器中,扫描1000个有指定前缀的key,放入到set集合中,count是每次扫描的key个数,并不是结果集个数

1
2
3
4
5
6
7
8
vbnet复制代码 Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<String> keysTmp = new HashSet<>();
            Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(REDIS_KEY_NAME + "*").count(1000).build());
            while (cursor.hasNext()) {
                keysTmp.add(new String(cursor.next()));
            }
            return keysTmp;
        });

​

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

字节提前批后端开发一、二、三面面经,已意向书!

发表于 2021-07-23

概述

分享一波在读者群,应届生大佬的校招面试经历,目前手上已经拿到了 字节意向书,蚂蚁转正offer ,大佬是我在蚂蚁认识的一位师弟,个人能力很强,职业规划很清晰,源码钻研也很深入,给大家分享一波他的字节SP面经。

楼主是周天下午4点到7点连续三面,第二天早上发的意向书,效率很快

感觉全部是怼着简历问的,八股文问的比较少,全部是基于项目 实习的基础上去问的!感觉参考价值不是特别高,还是发出来给大家看看!

一面

  • 1、因为项目做了基于netty的rpc框架,针对这个进行展开提问
  • 2、Reactor线程模型
  • 3、netty怎么实现实现reactor线程模型的
  • 4、rpc调用的时候调用远程方法像调用本地方法一样是用了什么(这里我回答了网络连接的底层,结果面试官问的代码层面的动态代理)
  • 5、动态代理怎么实现的?有哪两种动态代理(JDK、cglib)?有什么区别?
  • 6、IO多路复用
  • 7、select、poll、epoll
  • 8、由于自己简历上写了看过rocketmq源码,接下来对mq展开提问
  • 9、rocketmq和市面上常见的mq有什么区别,都有什么优缺点
  • 10、rocketmq事务消息底层
  • 11、一个数组,从输入中找一个数看看在不在这里面(开放题,任何你想到的都能说):我回答了排序二分、遍历、用set、用hashmap、hashcode、用布隆过滤器。比较开放题
  • 12、自增id有什么好处(我回答了和uuid相比,节省磁盘空间,作为聚簇索引提升查询效率)
  • 13、select * from user where id >= 多少 order by phone 这个sql有什么问题可以优化的
  • 14、算法题:选定一个链表,返回环的入口节点,没有则返回空节点

二面

  • 1、怼项目(支付宝实习项目)
  • 2、rocketmq延时消息底层实现,应用场景
  • 3、epoll 水平触发和边缘触发
  • 4、常见的json序列化工具有哪些?
  • 5、看到你写netty ,知道protobuf吗?和json比有什么好处呢?
  • 6、那你能说说dubbo是怎么实现的吗?
  • 7、dubbo的序列化方式是什么呢?
  • 8、微服务zookeeper、eureka、consul、nacos对比
  • 9、zookeeper讲讲?CP还是AP?eruka呢? 服务调用需要ap还是cp?分析一下场景?
  • 10、为什么mysql单表最多不放超过2000w行数据呢?
  • 11、算法题:两个字符串找最长公共子串

三面

  • 1、怼项目(商汤实习项目、数学建模项目)
  • 2、认证、授权、熔断、限流都是怎么实现的?
  • 3、常见的限流算法?(令牌桶等)
  • 4、常见的限流方式?(nginx、网关)
  • 5、JWT了解吗?
  • 6、进程通信方式?哪种通信方式最快?
  • 7、开发中怎么解决线程安全问题?
  • 8、如果你在浏览器上输入一个网址返回error怎么排查?(ping对应的ip)
  • 9、如果你ping出来的ip是128.0.0.1怎么办?(肯定是对应的浏览器缓存映射、或者本级host被修改,面试官说就是这个)
  • 10、你前面两面还有没被问到的吗?(不知道没有,别问了)
  • 11、算法题:两个有序数组找中位数

点关注,不迷路

好了各位,以上就是这篇文章的全部内容了,我后面会每周都更新几篇高质量的大厂面试和常用技术栈相关的文章。感谢大伙能看到这里,如果这个文章写得还不错, 求三连!!! 感谢各位的支持和认可,我们下篇文章见!

我是 九灵 ,有需要交流的童鞋可以 加我wx,Jayce-K,关注公众号:Java 补习课,掌握第一手资料! 如果本篇博客有任何错误,请批评指教,不胜感激 !

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

HTTP3/QUIC 性能测试与配套组件

发表于 2021-07-23

背景

最近一年很多关于QUIC的文章层出,但是发现一个问题,这些文章都是在介绍QUIC或HTTP3是怎样的一个东西,以及它的优点和机制,将它夸的近乎上天了。然而有心的人估计会亲手做一些测试,就会发现这个被捧上天的东西性能居然还不如HTTP1.1,这是怎么回事呢?

最近我一直在做QUIC或者说HTTP3的相关工作,就一直在憋着写这样一篇文章,给和我当初有同样疑问的人一种变相的解答。

测试

测试很简单,分为两台机器,均在同一局域网内。服务器使用Nginx的QUIC分支版本,即nginx-quic。客户端使用h2load(支持HTTP3版本的)做基准测试工具。在服务端使用netem模块对网络状况进行操控,模拟不同的网络环境。请求无请求体,响应体为Nginx默认612字节首页文件,那么简单来看下测试结果吧:

h2load的参数如下:-t 10 -c 100 -n 1000 -m 100,即10线程、100个连接、1000个请求,每个连接可以同时处理100个请求。

HTTP版本

延迟

丢包率

重复率

包损毁率

结果

HTTP1.1

-

-

-

-

总耗时406.49ms, 24601.15 req/s QPS,21.30MB/s 每秒传输

HTTP3

-

-

-

-

总耗时611.90ms, 16342.59 req/s QPS,12.98MB/s 每秒传输

HTTP1.1

100ms+-10

-

-

-

总耗时1.90s, 5275.52 req/s QPS,4.57MB/s 每秒传输

HTTP3

100ms+-10

-

-

-

总耗时3.65ms, 2740.22 req/s QPS,2.18MB/s 每秒传输

HTTP1.1

-

30%

-

-

总耗时33.64s, 297.28 req/s QPS,263.60KB/s 每秒传输

HTTP3

-

30%

-

-

总耗时19.82s, 504.45 req/s QPS,447.31KB/s 每秒传输

HTTP1.1

-

-

70%

-

总耗时443.55ms, 23065.39 req/s QPS,19.97MB/s 每秒传输

HTTP3

-

-

70%

-

总耗时419.98ms, 23810.43 req/s QPS,18.92MB/s 每秒传输

HTTP1.1

-

-

-

20%

总耗时14.46s, 691.61 req/s QPS,613.27KB/s 每秒传输

HTTP3

-

-

-

20%

总耗时4.12s, 2424.55 req/s QPS,1.93MB/s 每秒传输

HTTP1.1

100ms+-10

30%

-

-

总耗时30.64s, 326.42req/s QPS,289.44KB/s 每秒传输

HTTP3

100ms+-10

30%

-

-

总耗时17.16s, 582.89 req/s QPS,474.19KB/s 每秒传输

HTTP1.1

-

30%

70%

-

总耗时2.03s, 4914.90 req/s QPS,4.26MB/s 每秒传输

HTTP3

-

30%

70%

-

总耗时3.06s, 3264.89 req/s QPS,2.59MB/s 每秒传输

HTTP1.1

-

30%

-

20%

慢到没结果…

HTTP3

-

30%

-

20%

总耗时15.09s, 662.75req/s QPS,539.16KB/s 每秒传输

在这份测试结果中我给出的都是典型值,当然我也对这些值都做过大小调整看结果。从这份结果我们可以看出如下结论:

  1. 单独提高延迟并不会出现HTTP3性能优于HTTP1.1的情况。
  2. 丢包率的增加会使得HTTP3对HTTP1.1的性能优势明显增加。
  3. 单独提升包重复率HTTP3对HTTP1.1的性能仅有微弱的优势。
  4. 单独提升包损毁率会明显提升HTTP3对HTTP1.1的性能优势。
  5. 延迟与其他因素同时出现不会对整体结果造成更大的影响。
  6. 包重复率与损毁率(或丢包率)是一组对立项,丢包或损毁导致可用包减少,但重复率又填补了这一损失,导致结果倾向HTTP1.1更优。

从上述结论中我们可以看到,并非任何时候HTTP3都优于HTTP1.1,但对弱网高丢包率、包损的情况下,QUIC或者说HTTP3的优势就非常明显了,这得益于其FEC机制和连接复用机制。然而生活中,弱网的环境其实非常多,例如地铁换站、电梯、部分楼宇内、以及一些网络覆盖不全面的城镇等等。因此QUIC更多的是一个对整体网络的兜底策略。

因此,如果使用nginx-quic的默认配置将所有的请求都升为HTTP3版本是不明智的,因为常规情况下HTTP1.1的性能优势是不可视而不见的。这也就引入了下一小节的组件。

组件

由上面的结论,我们发现并非对每一个请求都升级是一个明智之举,因此就有了这个Nginx模块对版本升级进行自动化控制——ngx_http_autoquic_module

这个模块会根据TCP的网络状况来决定是否需要将HTTP版本升至HTTP3版本。而根据QUIC的统计情况来判断是否需要降级至HTTP1.1版本。由于降级并不像升级那样平滑,因此对降级增加了开关,让使用者可以选择是否允许降级。

目前对于浏览器,可以很好地支持升降级。但对于众多客户端而言,升降级与否还要看客户端所使用的HTTP库的处理逻辑。

感谢阅读,期待诸位的评论。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

为什么你需要一台NAS(网盘云盘vs路由器硬盘vsNAS)

发表于 2021-07-23

前言

本文并不是要安利你买一台NAS,而是从各个方面分析对比,让你能够根据自己的实际情况选择合适的私人数据存储方案。

另外,本文不打算介绍什么是NAS,我相信能够点开看这篇文章的同学多多少少对NAS这个概念都有一定的了解。

如果你正想为自己移动硬盘上的小哥哥小姐姐找个家,那么你就可以继续看下去了。。。

为什么需要NAS等数据存储方案

相信上了年纪的我们硬盘上多多少少都有一些或者很多重要的资料,比如私照,视频,未美颜的照片(纯属调侃),工作资料,私人数据等等。那么这些数据睡在移动硬盘中不是很好么,为什么还要什么存储方案尼?接下来让我给你闹闹。

如果某一天你的硬盘罢工了尼(前段时间就有个在医院上班的朋友找到我,说他们医院的一台电脑硬盘坏了,上面有他们医院10几年的资料,甚至很多Level级别很高的资料,问我能不能修复,然后就没有然后了),此时如果你的硬盘能够正常修复还好(此时花多少钱你应该都不会觉得心疼),如果彻底罢工了,你会不会心都凉了半截,会不会寝食难安,哈哈哈,貌似有点夸张了。

就算你的硬盘不罢工,但是睡在硬盘上的资料,特别是照片(我们这个年纪娃娃的照片应该特别多,不过我见过很多朋友用通过发朋友圈的方式将照片存在微信和QQ上,但是却不能放太多私照,你懂得),你有没有觉得使用起来很不方便,在现在这个互联网这么发达的时代,不应该是人在哪里,资料就在哪里么。所以你可以把硬盘一直带上身上,然后再背一个电脑。。。。嘿嘿嘿。。开个玩笑。

所以我们都想我们的数据存在这么一个地方:

1、随时随地都能够读取到(PC或者手机);

2、资料能够抵抗一定的风险,不会因为一块硬盘罢工之后数据就丢失了(专业点说:叫具有容灾能力)

3、且资料具有绝对的安全性和私密性,不存在泄露的风险。

那么目前能够做到这些且比较常用的方案有三个:网盘(云盘)、路由器硬盘、NAS。接下来我们分别看看这三个方案各有什么特点。

本文不会介绍路由器硬盘和NAS的具体搭建方式。

本文不安利选择什么方式,仅仅为大家做一些比较分析。

三种解决方案

网盘(云盘)

网盘也叫云盘,由云服务商提供服务。购买容量和速度都需要买会员,你懂得。在买了会员的情况下,还是比较方便的。PC和手机都有相应的应用能够随时随地的读取。且云盘本身是具有容灾的(但是貌似也不是绝对的,你可以去百度下,就会发现很多关于某度盘上数据丢失无法恢复的情况,具体原因就不知道了。但作为一个程序员来看我觉得很正常,因为if else必定是有bug的)。使用云盘的优缺点如下。

优点:

1、很容易入手,交钱就OK

2、不存在任何维护费用,也无需自己维护折腾

缺点:

1、读写速度(上传下载速度)应该只有20M/s(没有具体研究过,买不起会员)。

2、不建议存在私照(云盘都声称不看你的内容,你信不信我不知道,反正我是不信)

3、存在数据丢失的可能(可能性应该不会很大,但是万一尼,人家可是不陪得哦)

4、存在云盘关闭的可能(前两年关闭了很多云盘,如360,金山等)

5、播放视频不方便(比如通过电视播放、内网分享等),只能手机、PC在线播放

路由器硬盘

这种方案比较便宜(后面会对比三种方案的价格),需要自己买有个能够挂硬盘的路由器,然后再买一个硬盘(比如10T的)。这样就可以组装成一个数据存储方案了,具体组装方式大家可以百度。组装完成之后通过相应的App能够做到手机端访问。

优点:

1、比较便宜(便宜的几百就可以搞定)

2、读写速度快,内网应该可以达到千兆数据(100M/s上下)。外网则依赖你的网络带宽。

3、视频播放方便,电视可以直接连接

缺点:

1、数据没有容灾,单硬盘存在硬盘罢工的风险(没有用过,但是我觉得数据应该是可以备份到对应的云盘的,只是需要购买空间)

2、功能比较简单,只能够提供基本的视频播放、照片查看备份、文件共享等

3、外网访问应该比较麻烦,且数据可能需要经过第三方服务器,存在安全风险(人家肯定声称不看的,还是那句话,不管你信不信反正我是不信的)。

4、搭建、维护和配置需要一定的动手能力。

由于没有使用过路由器硬盘,所以功能不是很了解。大部分内容都是百度看的,可能存在分析不够全面或者有误的地方,请大家批评指正。

NAS

简单的理解就是私人云盘,即自己在家搭建属于自己的云盘。如何搭建就涉及到两种情况了,即大家说的黑NAS或者白NAS(黑群or白裙)。黑NAS:即自己购买各种硬件(主机,硬盘架等),然后通过安装盗版或者开源的NAS系统,来构建NAS。白NAS:即直接购买官方正版NAS服务器(就一个小黑盒子),然后直接使用即可。

优点:(从白NAS的角度考虑)

1、安装简单、维护简单

2、使用简单,功能丰富(有各种App支持各种功能),大部分功能都能够在手机上操作,照片管理、视频管理、文件管理等。

3、具有数据容灾(可以做磁盘阵列,即raid0, raid1, raid5, raid10),比如:raid1的情况下,一个硬盘挂了,另一个硬盘上还有数据。

4、具有完整的操作系统,是一台小型的服务器。能够支撑一个小型公司的基本办公(邮件系统,ERP系统等,内部网站,甚至是对外网站)和数据存储需求。

5、支持Docker(不是所有NAS都有,当然有钱就有,哈哈哈),能够搭建服务器,所以你可以为所欲为。

6、数据非常安全,可以存放大量的小哥哥小姐姐。所有数据都存在自己的硬盘中,且不会经过第三方服务器。

7、速度快,能够跑满千兆网络(内网),外网则依赖你的网络带宽。

8、具有完整的权限管理策略,可以新建各种成员和人群划分。

9、能够做视频监控存储。

缺点:

1、搭建、维护和配置需要一定的动手能力。

2、成本比较高

三种方案对比

​

我该用哪种方案

网盘(云盘)适合的人群:

  1. 不想动手;
  2. 只需要普通的照片管理和文件存储;
  3. 不关心局域网文件共享;
  4. 不需要内部视频共享;
  5. 能够接受数据被后台查看的可能性;
  6. 能够接受数据丢失或者网盘关闭的极小可能性;

路由器硬盘适合的人群:

  1. 具有一定的动手能力;
  2. 对网盘(云盘)存在怀疑;
  3. 能够接受硬盘挂了带来的风险;
  4. 只需要普通的照片管理、局域网视频共享和文件共享。
  5. 不关心外网数据存取。

NAS适合的人群:

  1. 具有一定的动手能力,喜欢折腾;
  2. 能够接受上千的费用;(其他都是扯淡,这个才是重点)
  3. 十分重视数据安全和隐私泄露;
  4. 对功能易用性有要求较高;
  5. 对外网读取要求高。
  6. 喜欢折腾服务器或者Docker;
  7. 想要折腾家庭影院系统,通俗说:就是手机、PC、电视、投影等可以极为方便的共享视频和照片内容;
  8. 喜欢玩BT(一如BT深似海,施主请慎重);
  9. 对监控视频存储有需求;
  10. 等等

PS(有安利大家的嫌疑):其实有了NAS,手机存储只需要32G就够了。几年下来,一家人几部手机还是可以省很多钱。

总结

所有分析都是扯淡,只要不care钱钱,通通安利上NAS,嘿嘿嘿……我X,前面好像不是这么说的尼….哈哈哈

最后

如果你对本文有任何疑问或者高见,请添加公众号咱们一起探讨。偷偷告诉你,添加公众号可以获得”Java高级架构“上10G的视频和图文资料哦。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何编写高质量的代码 为什么要提高代码的可读性

发表于 2021-07-23

刚来我现在这个公司的时候,还有再上一家公司去优化外包写的代码的时候。总会遇到一些问题,相信大家也跟我一样会遇到过这些问题:

  1. 接手的项目,文档缺失,代码一点注释没有,交接维护的人已经离职,所以基本上只能靠自己猜来梳理代码逻辑
  2. 代码风格过于抽象(命名缩写且意思不明确,直接用字母或者加上数字命名,比如 Student s, int num1,int num2,重名方法等),看不懂,也不敢轻易修改。
  3. 运行的代码,不打印日志,info日志没有,error日志没有。sql不打印。或者分布式情况下,没有logid做全局追踪
  4. 代码写的过于low,if else嵌套了n层,或者一个方法,写了一百多行,甚至几百行。扩展性差,重构优化费时,费力。一般不敢轻易优化
  5. 业务代码和逻辑代码不分家,全写一起,service层基本无作用,全再controller层做操作

我上面的一列,你有没有感触,发没发现就是你遇到过的所有的问题。其实,根本原因就是代码可读性差,没能很好的串联起代码内在的逻辑。可读性差的代码不仅代码难以理解,维护起来也是相当的头疼,最终导致交付效率变差。

今天就来讲一下如何快速提高代码的可读性

为什么要提高代码的可读性

提升源代码的可读性主要有以下四大好处。

第一,更易于维护。代码写好后,需要调试、运行与修复 Bug,设计文档、需求文档和口头交流只能表达部分业务逻辑的意图,而代码则能反映出编程实现业务逻辑时的全部真实意图。可读性高的代码,能让阅读者在阅读时快速理解编写者的意图,即便逻辑复杂,也能在修改时准确地分析和理解,大大节省维护和修改代码的时间。

第二,更易于重构。现在很多项目之所以难以重构,就是因为代码的可读性太差。当你无法理解一段代码时,你会跳过它,而整个系统都难以理解的话,你可能就会选择重写而不是重构,因为重构必然会修改原有代码,这会引入一定的风险,一旦因为重构而导致故障,那么维护的人就要担责。所以说,可读性的高低在某种程度上决定了你重构意愿的大小。(这里我相信大部分人其实都碰到过入职接手别人的代码,可能因为那个人在你入职之前就离职了,也可能因为他跟你交接的时候并没有交接完整就匆匆的离职了,导致你对现有的代码并不熟悉,公司的业务发展,提了新需求要求你去改原本的代码,但是你这时候看不懂,所以你只能自己去重写一个方法,之后去调用你重写之后的接口,我相信大部分人都这么做过,我也这么做过)

第三,更易于测试。代码在修改时需要反复调试,如果代码的可读性很差,那么很多时候都需要写一些额外的 Mock 或测试接口来对原有的代码进行测试,不仅浪费时间,还容易造成误读。可读性高的代码,参数与输出都更清晰,在测试时能更精准地找到对应逻辑和问题点。

第四,更易于应用设计模式。设计模式除了在设计之初被使用外,其实更多时候都是在代码重构过程中被使用。在工作中,你会发现有的代码虽然写了很多嵌套的if-else,但命名和注释都写得很好,逻辑也很易读,在重构时就能通过设计模式很好地去优化。而有的代码虽然看上去很简洁,但使用了很多高级技巧或缩写命名,理解起来非常费时、费力,对于维护人员来说,自然不愿意考虑使用设计模式。

虽说编写文档能够表达软件开发意图,但事实上,你可能很讨厌写文档,这是因为大部分文档都与代码没有直接关系,并且随着代码的不断调试与修改,文档会变得越来越难以与最新的真实情况同步。

另外,你可能也没有太多时间阅读文档,需求上线、Bug 修复、多项目并发是现在程序员的日常现状。因为时间紧、任务重,你可能只能边改代码边学习,这时一份逻辑清晰的代码才是你真正需要的。

可以换个角度想想,假如你是代码使用者,你希望看到什么样的代码?很明显,没有人想要看到这样的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
java复制代码cName = InpList.get(0).replace(",", ".");

cCode = InpList.get(1).replace(",", ".");

cAlpha2 = InpList.get(2).replace(",", ".");

cAbreviation = InpList.get(3).replace(",", ".");

dYear = InpList.get(4).replace(",", ".");

dPoliticalCompatibility = InpList.get(5).replace(",", ".");

dRankPoliticalCompatibility = InpList.get(6).replace(",", ".");

dEconomicCompatibility = InpList.get(7).replace(",", ".");

dRankEconomicCompatibility = InpList.get(8).replace(",", ".");

dMilitaryCompatibility = InpList.get(9).replace(",", ".");

dRankMilitaryCompatibility = InpList.get(10).replace(",", ".");

dDemoScore = InpList.get(11).replace(",", ".");

dRankDemoScore = InpList.get(12).replace(",", ".");

dEnvironmentalCompatibility = InpList.get(13).replace(",", ".");

dRankEnvironmentalCompatibility = InpList.get(14).replace(",", ".");

dSumCompatibility = InpList.get(15).replace(",", ".");

dRankCompatibility = InpList.get(16).replace(",", ".");

dPoliticalUtility = InpList.get(17).replace(",", ".");

dRankPoliticalUtility = InpList.get(18).replace(",", ".");

dEconomicUtility = InpList.get(19).replace(",", ".");

dRankEconomicUtility = InpList.get(20).replace(",", ".");

dMilitaryUtility = InpList.get(21).replace(",", ".");

dRankMilitaryUtility = InpList.get(22).replace(",", ".");

dEnvironmentalUtility = InpList.get(23).replace(",", ".");

dRankEnvironmentalUtility = InpList.get(24).replace(",", ".");

dSumUtility = InpList.get(25).replace(",", ".");

dRankUtility = InpList.get(26).replace(",", ".");

dPoliticalScore = InpList.get(27).replace(",", ".");

dRankPoliticalScore = InpList.get(28).replace(",", ".");

dEconomicScore = InpList.get(29).replace(",", ".");

dRankEconomicScore = InpList.get(30).replace(",", ".");

dMilitaryScore = InpList.get(31).replace(",", ".");

dRankMilitaryScore = InpList.get(32).replace(",", ".");

dEnvironmentalScore = InpList.get(33).replace(",", ".");

dRankEnvironmentalScore = InpList.get(34).replace(",", ".");

dAggregate = InpList.get(35).replace(",", ".");

dRankAggregate = InpList.get(36).replace(",", ".");

而是,希望看到这样的代码(HttpClient 的某个代码片段):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
java复制代码/**

* {@inheritDoc}

*/

@Override

public CloseableHttpResponse execute(

final HttpUriRequest request,

final HttpContext context) throws IOException, ClientProtocolException {

Args.notNull(request, "HTTP request");

return doExecute(determineTarget(request), request, context);

}

private static HttpHost determineTarget(final HttpUriRequest request) throws ClientProtocolException {

// A null target may be acceptable if there is a default target.

// Otherwise, the null target is detected in the director.

HttpHost target = null;

final URI requestURI = request.getURI();

if (requestURI.isAbsolute()) {

target = URIUtils.extractHost(requestURI);

if (target == null) {

throw new ClientProtocolException("URI does not specify a valid host name: "

+ requestURI);

}

}

return target;

}

所以说,在开发代码时,应该更注重代码表达的意图是否清晰,考虑使用一些方法和技巧,虽然会耗费一点时间,但是从整体来看,你会节省很多沟通与解释的时间,做到在真正的提升编码效率。

如何写出有“逻辑线索”的源代码

要想写出可读性高的代码,你可以从三个方面来入手。

  • 代码表现形式:在命名(变量名、方法名、类名)、代码格式、注释等方面的改进。
  • 控制流和逻辑:尽量分离控制流和逻辑,让代码变得更容易理解。
  • 惯性思维:找出常犯的一些惯性思考方式并逐一改进。

下面我就来具体解释下。

1. 优化代码表现形式

命名在编程中至关重要,无论是变量名、类名还是方法名,好的名字能快速准确地传达要表达的含义,而缩写、自定义名称会让代码变得难以理解。我们先来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public class T {

private Set<String> pns = new HashSet();

private int s = 0;

private Boolean f(String n) {return pns.contains(n);}

int getS() {return s;}

int s(List<T> ts, String n) {

for (T t :ts)

if (t.f(n))

return t.getS();

return 0;

}

}

这段代码到底实现了什么功能?估计没有人能回答出来。如果编写者不是我,我肯定也无法理解这段代码。光凭看代码,几乎是无法理解这段代码的真实含义到底是什么的。

实际上,这个类是获取球队比赛得分的,除了通过球队直接获得比赛得分外,还可以通过球队里的某个球员来查找对应得分,具体修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
java复制代码/**

* 获取球队比赛得分

**/

public class Team {

private Set<String> playerNames = new HashSet(); //保证名字不重复

private int score = 0; //默认为零



/**

* 判断是否包含球员

* @param playerName

* @return

*/

private Boolean containsPlayer(String playerName) {

return playerNames.contains(playerName);

}



/**

* 知道队伍,直接获取分数

* @return

*/

public int getScore() {

return score;

}



/**

* 通过队员名字查找所属队伍分数

* @param teams 支持多个队伍

* @param playerName

* @return 兜底为0分,不出现负分

*/

public int getTeamScoreForPlayer(List<Team> teams, String playerName) {

for (Team team :teams) {

if (team.containsPlayer(playerName)) {

return team.getScore();

}

}

return 0;

}

}

从优化后的代码中,你就能直观地看到, “命名的优化加上注释的说明”一下子就让源代码的逻辑变得清晰起来,即便你没有学过编程,也能大致了解这段代码的逻辑和作用。

2. 改进控制流和逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
java复制代码public List<User> getUsers(int id) {

List<User> result = new ArrayList<>();

User user = getUserById(id);

if (null != user) {

Manager manager = user.getManager();

if (null != manager) {

List<User> users = manager.getUsers();

if (null != users && users.size() > 0) {

for (User user1 : users) {

if (user1.getAge() >= 35 && "MALE".equals(user1.getSex())) {

result.add(user1);

}

}

} else {

System.out.println("获取员工列表失败");

}

} else {

System.out.println("获取领导信息失败");

}

} else {

System.out.println("获取员工信息失败");

}

return result;

}

这段代码的含义是:想要通过 id 来查询员工的信息,如果 id找不到,就查询员工的领导,然后通过他领导下的员工信息来寻找,这时还需要判断员工年龄大于 35 岁且为男性。

这是我们最常使用的逻辑实现方式,俗称箭头型代码,但是随着判断条件逐渐增多,嵌套就会增多。代码逻辑越多,你就越容易搞不清楚逻辑是什么,因为看到最内层的代码时,你已经忘记前面每一层的条件判断是什么了。

那么,我们该如何去优化呢?其实很简单,就是改变控制流,先判断会出现失败的条件,一旦出现优先推出。优化后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
java复制代码public List<User> getStudents(int uid) {

List<User> result = new ArrayList<>();

User user = getUserByUid(uid);

if (null == user) {

System.out.println("获取员工信息失败");

return result;

}

Manager manager = user.getManager();

if (null == manager) {

System.out.println("获取领导信息失败");

return result;

}

List<User> users = manager.getUsers();

if (null == users || users.size() == 0) {

System.out.println("获取员工列表失败");

return result;

}

for (User user1 : users) {

if (user1.getAge() > 35 && "MALE".equals(user1.getSex())) {

result.add(user1);

}

}

return result;

}

现在,代码逻辑是不是很清晰了?虽然这个快速失败方法很简单,但是非常有效。实际上,快速失败就是 KISS 原则一个很好的实践,这样能保证条件判断的逻辑简单清晰。只要 if 的嵌套超过三层,你就可以应用这个原则来改进控制流,让逻辑更清晰易懂。

3. 避免惯性思维

除了改进表层和逻辑外,我们更应该尽量避免设计代码时的一些惯性思维,这里我总结出了“五个避免”,下面我们就来具体分析一下。

第一,要避免一次性代码。一次性编码最大的坏处在于,一旦需要修改,多处就得跟着修改,而多次修改又可能会出现遗漏的风险。一次性代码在越来越多的软件代码中出现,一个本质的原因就是多人协作开发的情况越来越多。由于编程是一件非标准化的事情,不同程序员可能对同一个逻辑的理解完全不同,而一旦每个人都只从自己的角度出发写一次性代码,那么同一个系统里的代码很快就会变得冗余与混乱。

第二,要避免复制粘贴代码。一方面,不同的人编码风格可能会有所不同,这会给阅读者在理解上造成一定的认知负担(需要来回切换判断标准)。另一方面,还会带来未知 Bug的风险。复制过来的代码,更多是关注输入和输出,一旦代码正常运行后,很少会去关注代码的内部逻辑,但是等出现问题后,再想去梳理逻辑反而变得更加困难(因为不知道详细的实现逻辑)

第三,避免写超长代码。超长代码带来的最大问题是:在阅读代码时,函数方法之间的跳转过多,思维很容易发生混乱,尤其对于一些命名相同但参数不同的方法,很容易出现修改错误的情况。从编写者的角度来看,你写超长代码,可能是觉得在一个文件里维护代码比较方便;但对于阅读者来说,他可能并不知道你是如何对代码进行职责划分的,更多时候他都会以为一个类里都是一个职责,但实际上一旦出现多个职责,加上逻辑跳转很多,阅读者基本上是会放弃阅读的。

第四,避免过度简化命名和表达式。在开发任务重的时候,我们通常会选用一些简化命名的方法,比如,num1、num2、num3 这类变量命名形式。虽然在写代码的时候,我们可能记得这些变量的含义,但是过一段时间后,如果没有注释或说明,几乎是不可能直接通过名字知道它们的作用的,还得借助上下文代码,这样不仅费时,而且还可能会出现理解错误的情况。

第五,避免写“是什么”的注释。代码的命名和结构如果能直接反映出来“是什么”的话,我们就不应该用注释去表达,因为看代码一眼就能明白,比如,获取用户信息的方法名——get 和 getFromUserInfo。

我们应该多写“为什么”的注释,比如,为什么要多加一个适配的方法,原因可能是线上 xxx 问题引起,或临时修复的Bug,后续可能随 xxx 接口调整而废弃,等等。在很多优秀的开源框架中,我们都能看到作者会在 interface 接口上写很多“为什么”的说明,就是为了帮助我们快速抓住代码的逻辑线索。

另外,写“为什么”的注释还有一个好处:尤其在早期快速迭代过程中,能给后来的维护者提供一个优化的切入点,而不至于交接代码后让维护代码的人看不懂、不敢动。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

十分钟!教你玩转SprintBoot定时任务

发表于 2021-07-23

常用的定时任务有两种:

  1. 基于注解
  2. 基于接口

基于注解@Scheduled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
java复制代码
@Service
public class Scheduling1Service {

//每2秒执行一次(若上次任务执行时间超过2秒,则立即执行,否则从上一个任务开始时算起2秒后执行本次任务)
@Scheduled(fixedRate = 2000)
public void test1() throws InterruptedException {
Thread.sleep(1000L);//模拟定时任务执行耗费了1s
Thread.sleep(3000L);//模拟定时任务执行耗费了3s
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(format.format(date)+"==>SchedulingService.test1 is called");
}

//上一个任务执行完2秒后,再执行本次任务
@Scheduled(fixedDelay = 2000)
public void test2() throws InterruptedException {
Thread.sleep(3000L);//模拟定时任务执行耗费了3s
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(format.format(date)+"==>SchedulingService.test2 is called");
}

//支持corn表达式
@Scheduled(cron = "0 0 1 * * ?")//每天凌晨1点执行
public void test3() throws InterruptedException {
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(format.format(date)+"==>SchedulingService.test3 is called");
}
}

注:不会写corn表达式的小伙伴,可以使用这个哦:https://cron.qqe2.com 会帮你自动生成corn表达式,且能检测你的表达式是否合法。非常好用!

以上三种是使用频次比较多的。因为不接受参数,主要用户定时同步第三方基础数据的业务场景。

使用@Scheduled需在pom中引用springboot的相关依赖,并在Application主入口函数中增加@EnableScheduling的注解。

基于接口形式的定时任务

基于注解的方式的任务配置起来很简单也很好用,但是由于不能传递参数,使用场景有限。那么就需要使用基于接口形式的定时任务了。

添加依赖:
1
2
3
4
yaml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@Service
public class Scheduling3Service implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.addTriggerTask(
new Runnable() {
@Override
public void run() {
System.out.println("cccccccc");
}
},
triggerContext -> {
return new CronTrigger("0/1 * * * * ? ").nextExecutionTime(triggerContext);
}
);
}
}

以上就是两种常用的定时任务,小伙伴们,你,学废了吗?

更多java原创阅读:javawu.com

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…595596597…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%