dotnet core配置swagger文档报错

在给一个新的dotnet core项目配置swagger文档的时候,编译没问题访问接口文档 /swagger 出现错误 Failed to load API definition.使用的是Swashbuckle.AspNetCore包。以前一直没出现过这个错误。好在F12查看到swagger的结果报500错误了,才找到原因。

访问/swagger/v1/swagger.json 报错信息如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
System.NotSupportedException: Ambiguous HTTP method for action - DEMO.Controllers.HomeController.Index (TianGui.Web.WebApi). Actions require an explicit HttpMethod binding for Swagger/OpenAPI 3.0
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: application/json,*/*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Cookie: JSESSIONID=6a8d2980-37a5-459d-ad2c-d804425e567d; ASP.NET_SessionId=x11uh1hk1z3yy3erjji12i0i
Host: localhost:52764
Pragma: no-cache
Referer: http://localhost:52764/swagger/index.html
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
DNT: 1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty

看报错得知 action HomeController.Index 未明确标记 HttpMethod,这个action可以访问是因为全局路由所以可以访问。看到这个报错直接在报错对应的action上增加HttpMethod即可。

1
2
3
4
5
6
7
8
9
10
[ApiController]
[Route("/")]
public class HomeController : Controller
{
[HttpGet]
public ResponseVm Index()
{
return ResponseVm.Success();
}
}

Jmeter对于web api进行压力测试

前言

最近在项目中遇到一个记录用户播放记录的场景。简单来说就是用户在看视频的时候,每间隔几秒钟就提交一次用户观看视频的进度,如果用户中途关闭视频播放页面,下次再打开的时候视频从上次关闭时的播放进度继续播放。例如用户在视频的第300秒关闭了页面,下次再打开视频的时候,从第300秒开始播放,当然可能会再提前几秒。

从场景来看,当越多用户看视频的时候,接口并发量就会响应的增加。所以很有必要对这个接口一轮压力测试。下面记录试用JMeter进行压力测试的过程。

创建测试

  1. 创建线程组并设置线程数,线程数可以理解为用户数或者并发量。
  2. 在新建的线程组上右键选择添加-取样器-http请求,设置目标api的信息。
  3. 你可能注意到了目标接口接受json作为payload,所以需要设置http请求头Content-Type属性。在新建的http请求上右键,选择添加-配置元件-http信息头管理器。
  4. 请求参数可能需要一些随机内容。JMeter提供了随机变量。在http请求上右键添加-配置元件-Random Variable。设置好变量名称,在你需要的地方引用。引用方式:${var}。配置随机发生器用于确定随机数的区间。图中rnduser和rndlessonId均为随机变量,只是随机区间不同。
  5. 对于接口返回的json内容可以增加判断,JMeter提供了json断言判断接口返回内容是否正确。在http请求上右键添加-断言-json断言。使用json path选择到你要判断的json字段,填写接口正确返回内容,这样在接口报错的时候JMeter才能感知到。本例中正确的返回结果为: {“message”:”ok”,”code”:0}。设置如图
  6. 最后添加察看结果树和聚合报告即可。

提示信息

1
2
3
4
5
6
7
8
9
10
11
================================================================================

Don't use GUI mode for load testing !, only for Test creation and Test debugging
.
For load testing, use CLI Mode (was NON GUI):
jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]
& increase Java Heap to meet your test requirements:
Modify current env variable HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m" in
the jmeter batch file
Check : https://jmeter.apache.org/usermanual/best-practices.html
================================================================================

意思是:不要使用GUI运行压力测试,GUI仅用于压力测试的创建和调试;执行压力测试请不要使用GUI。使用下面的命令来执行测试

1
jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]

修改JMeter批处理文件的环境变量

1
HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"

这是一个批处理的例子

1
2
set "HEAP=-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"
bin/jmeter.bat -n -t ./HTTP请求.jmx -l ,/HTTP请求 -e -o ./report/

asp.net core 统一模型验证拦截实现

在使用webapi对外提供服务的时候,我们希望提供一个统一的返回值例如 {“code”:1,”message”:”ok”,”data”:””}。为了解耦会对未处理的异常和模型验证进行统一处理。本文介绍了asp.net core 3.1版本中实现统一模型验证的实现方法。

在asp.net core中的过滤器和framework中的过滤器使用上并没有太大差异。新建过滤器ModelVerificationFilter.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ModelVerificationFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}

public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
{
return;
}

var errorMessage = context.ModelState
?.FirstOrDefault(m => m.Value.ValidationState == ModelValidationState.Invalid).Value
?.Errors
?.FirstOrDefault()
?.ErrorMessage;

context.HttpContext.Response.StatusCode = (int) HttpStatusCode.BadRequest;
context.Result = new ObjectResult(new ResponseVm(errorMessage, 400));
}
}

这里在发现模型验证失败的时候,返回第一个模型验证错误信息,并把http的状态码设置为BadRequest。代码中ResponseVm类是前面提到的统一返回值类型。

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
public class ResponseVm
{
public ResponseVm(string message, int code)
{
Message = message;
Code = code;
}

public ResponseVm()
{
Message = "ok";
Code = 0;
}

/// <summary>
/// 错误信息
/// </summary>
public string Message { get; set; }

/// <summary>
/// 错误码
/// </summary>
public int Code { get; set; }

public static ResponseVm Success(string msg = "ok")
{
return new ResponseVm(msg, 0);
}

public static ResponseVm Failed(string msg = "error", int code = -1)
{
return new ResponseVm(msg, code);
}
}

设置好过滤器之后发现并没有输出我们要求的结果,而是产生了一个系统默认的模型验证错误的返回值。其中Source是我们要验证的字段。

1
2
3
4
5
6
7
8
9
10
11
12
{
"errors": {
"Source": [
"来源 必须在 1 至 2 之间"
]
...
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|61e0ca29-42ac2f842289c956."
}

参考官方文档Web API应用-概述-自动 HTTP 400响应一节。

使用 2.1 的兼容性版本时,HTTP 400 响应的默认响应类型为 SerializableError。 下述请求正文是序列化类型的示例:
使用 2.2 或更高版本的兼容性版本时,HTTP 400 响应的默认响应类型为 ValidationProblemDetails。
若要禁用自动 400 行为,请将 SuppressModelStateInvalidFilter 属性设置为 true。 将以下突出显示的代码添加到 Startup.ConfigureServices:

1
2
3
4
5
6
7
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
...
options.SuppressModelStateInvalidFilter = true;
...
});

按文档修改后,统一模型验证拦截成功。两种方法配置

1
2
3
4
services.Configure<ApiBehaviorOptions>(o =>
{
o.SuppressModelStateInvalidFilter = true;
});
1
2
services.AddControllers()
.ConfigureApiBehaviorOptions(opt => opt.SuppressModelStateInvalidFilter = true)

参考:

  1. https://docs.microsoft.com/zh-cn/aspnet/core/web-api/?view=aspnetcore-3.1#automatic-http-400-responses

登录 Kubernetes 仪表盘

默认情况下Kubernetes仪表盘有两种登录方式,kubeconfig和Token.下面记录一下基于Token的登录方法

按照搜索的结果操作下来登录的账号权限比较低,估计是版本原因吧.secrets的位置发生了改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# kubectl -n kubernetes-dashboard get secrets 
NAME TYPE DATA AGE
admin-user-token-vmb25 kubernetes.io/service-account-token 3 59d
....

# kubectl -n kubernetes-dashboard describe secrets admin-user-token-vmb25
Name: admin-user-token-vmb25
Namespace: kubernetes-dashboard
Labels: <none>
Annotations: kubernetes.io/service-account.name: admin-user
kubernetes.io/service-account.uid: 21815f5d-7ac0-4a73-81e3-103e572326e4

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1017 bytes
namespace: 20 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IlB3MTA5eVZqTEJGUDYtcVAzQXpRV2sxWWdVU1JrTHcta2ZOdWsxa3F4MWMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLXZtYjI1Iiwia3ViZXJuZXRlcy5pby9....

结果里隐去了部分不必要的信息.指的注意的是命名空间kubernetes-dashboard,如果你用命名空间kube-system,也会查到一些secrets.使用命名空间kube-system下的token登录的话,大概率会发现权限不足,无法查看全部信息

react 项目 添加 less支持 和 antd 按需加载

配置方法

在项目中yarn eject暴露出webpack文件进行改造

  1. 安装 less-loader less babel-plugin-import
    1
    yarn add less-loader less babel-plugin-import --dev
  2. 在webpack.config.js文件中增加配置,复制sass的配置修改即可
    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
    //配置less的变量
    const lessRegex = /\.less$/;
    const lessModuleRegex = /\.module\.less$/;

    {
    test: lessRegex,
    exclude: sassModuleRegex,
    use: getStyleLoaders(
    {
    importLoaders: 2,
    sourceMap: isEnvProduction && shouldUseSourceMap,
    },
    'less-loader'
    ),
    sideEffects: true,
    },
    {
    test: lessModuleRegex,
    use: getStyleLoaders(
    {
    importLoaders: 2,
    sourceMap: isEnvProduction && shouldUseSourceMap,
    modules: true,
    getLocalIdent: getCSSModuleLocalIdent,
    },
    'less-loader'
    ),
    },
  3. 修改package.json文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    "babel": {
    "presets": [
    "react-app"
    ],
    // 增加 plugins 数组
    "plugins": [
    [
    "import",{"libraryName":"antd","style":"css"}
    ]
    ]
    },

依赖版本

1
2
3
4
5
"babel-plugin-import": "^1.13.0",
"less": "^3.10.3",
"less-loader": "^5.0.0"
"antd": "^3.25.3",
"react": "^16.12.0",

最终效果

最终效果

错误处理

  • 执行yarn eject之后报错 Cannot find module ‘@babel/plugin-transform-react-jsx’

删除 node_modules 文件夹 重新 yarn install

  • 配置less-load之后报错 bezierEasingMixin
1
2
3
// https://github.com/ant-design/ant-motion/issues/44
.bezierEasingMixin();
^

在webpack.config.js文件中有一个getStyleLoaders方法.修改后的方法如下

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
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {},
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
postcssNormalize(),
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
].filter(Boolean);
if (preProcessor) {
loaders.push(
{
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
{
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
javascriptEnabled: true // 增加这句
},
}
);
}
return loaders;
};
  • 添加less-loader后还会报缺少less模块. yarn安装一下