首页 文章 精选 留言 我的

精选列表

搜索[webapi],共317篇文章
优秀的个人博客,低调大师

C# webapi相关问题记录

C# Swagger问题记录: 1、使用nginx代理时无法访问Swagger 修改文件Web.config;在configuration节点下添加节点(暂未明白该配置项的作用) <add key="aspnet:UseHostHeaderForRequestUrl" value="true"/> 2、使用nginx代理后出现 Can't read from server. It may not have the appropriate access-control-origin settings修改文件Web.config;在system.webServer节点下添加节点 <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="*"/> <add name="Access-Control-Allow-Headers" value="Content-Type"/> <add name="Access-Control-Allow-Methods" value="POST, GET, PUT, DELETE, OPTIONS"/> </customHeaders> </httpProtocol>

优秀的个人博客,低调大师

.netcore 3.1高性能微服务架构:webapi规范

1.1 定义1、基础接口:单一职责原则,每个接口只负责各自的业务,下接db,通用性强。 2、聚合接口:根据调用方需求聚合基础接口数据,业务性强。 1.2 协议 客户端在通过 API 与后端服务通信的过程中, 应该使用 HTTPS(生产环境) 协议 服务端响应的数据格式统一为JSON 1.3域名hostprd环境:https://xxx-xxx-api.example.com/ uat环境:https://xxx-xxx-api-uat.example.com/ test环境:https://xxx-xxx-api-test.example.com/ dev环境:https://xxx-xxx-api-dev.example.com/ 将api放到子域名里,这种做法可以保持某些规模化上的灵活性。 1.4路径pathpath命名应该是以资源为导向的命名,对资源的操作是由HttpMethod(get、post、put、delete)来决定。所以一般来说url上的单词都应该是名词,一定不要是动词。一般遵循以下约定: (1)URL 的命名必须全部小写;(2) URL 必须 是易读的 URL;(3)一定不可 暴露服务器架构 (4)出现复合词汇使用下划线分隔,例如:animal_types 举几个正面例子: 新增用户:http://localhost/user post方法提交; 修改用户:http://localhost/users put方法提交; 删除文章:http://localhost/articles?author=1&category=2 delete方法提交; 查询用户:http://localhost/users get方法提交; 查询文章:http://localhost/articles?author=1&category=2get方法提交; 错误的例子如下: http://localhost/get_user https://api.example.com/getUserInfo?userid=1 https://api.example.com/getusers https://api.example.com/sv/u https://api.example.com/cgi-bin/users/get_user.php?userid=1 1.5动词RESTful 的核心思想就是,客户端发出的数据操作指令都是"动词 + 宾语"的结构,动词通常就是四种 HTTP 方法,对应 CRUD 操作: GET(SELECT):从服务器取出资源(一项或多项)。 POST(CREATE):在服务器新建一个资源。 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。 PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。 DELETE(DELETE):从服务器删除资源。 其中 (1)删除资源 必须 用 DELETE 方法 (2)创建新的资源 必须 使用 POST 方法 (3)更新资源 应该 使用 PUT 方法 (4)获取资源信息 必须 使用 GET 方法 针对每一个路径来说,下面列出所有可行的 HTTP 动词和端点的组合 请求方 URL 描述 法 GET /zoos 列出所有的动物园(ID和名称,不要太详细) POST /zoos 新增一个新的动物园 GET /zoos/{zoo} 获取指定动物园详情 PUT /zoos/{zoo} 更新指定动物园(整个对象) PATCH /zoos/{zoo} 更新动物园(部分对象) DELETE /zoos/{zoo} 删除指定动物园 GET /zoos/{zoo}/animals 检索指定动物园下的动物列表(ID和名称,不要太详 细) GET /animals 列出所有动物(ID和名称)。 POST /animals 新增新的动物 GET /animals/{animal} 获取指定的动物详情 PUT /animals/{animal} 更新指定的动物(整个对象) PATCH /animals/{animal} 更新指定的动物(部分对象) GET /animal_types 获取所有动物类型(ID和名称,不要太详细) GET /animal_types/{type} 获取指定的动物类型详情 GET /employees 检索整个雇员列表 GET /employees/{employee} 检索指定特定的员工 GET /zoos/{zoo}/employees 检索在这个动物园工作的雇员的名单(身份证和姓名) POST /employees 新增指定新员工 POST /zoos/{zoo}/employees 在特定的动物园雇佣一名员工 DELETE /zoos/{zoo}/employees/{employee} 从某个动物园解雇一名员工 1.6入参 1、如果记录数量很多,服务器不可能都将它们返回给用户。API 应该 提供参数,过滤返回结果。下面是一些常见的参数。 ?limit=10:指定返回记录的数量?offset=10:指定返回记录的开始位置。?page=2&per_page=100:指定第几页,以及每页的记录数。?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。?animal_type_id=1:指定筛选条件 所有URL参数 必须是全小写,必须使用下划线类型的参数形式。 分页参数 必须 固定为 page 、 per_page 经常使用的、复杂的查询 应该 标签化,降低维护成本,如 GET /trades?status=closed&sort=sortby=name&order=asc 可为其定制快捷方式 GET /trades/recently_closed 2、入参可分为业务参数和公共参数;公共参数有: 参数 名称 说明 timestamp 时间戳 clientid 调用方appid 统一管理应用,否则不放行 token 令牌 幂等情况可用 version 版本号 1.7响应1、出参(返回值):必须的字段有: 字段 类型 描述 code 数值 状态码 msg 字符串 信息描述 data 结果集 返回结果集 2、如果请求处理完全正确,则状态码为0 ; 3、状态码暂定8位数数字,前4位为某一个应用(服务)拟的一个数字,后4位为具体的状态值。状态码分为2种---公共和自定义,公共码以0打头+3位数。比如: 99990400 --客户端错误,比如请求语法格式错误、无效的请求、无效的签名等。 99991001 -----用户Id不能为空 响应的公共码如下: 编码 描述 说明 001 注解使用错误 002 微服务不在线,或网络超时 003 TOKEN解析失败 004 TOKEN无效或没有对应的用户 400 客户端错误,比如请求语法格式错误、无效的请求、无效的签名等。 服务器 应该 放弃该请求 401 需要身份认证,比如access_token 无效/过期 客户端在收到 401 响应后,都 应该 提示用户进行下一步的登录操作 403 没有权限访问该请求 服务器收到请求但拒绝提供服务。如当普通用户请求操作管理员用户时,必须 返回该状态码 404 用户请求的资源不存在 如获取不存在的用户信息 410 请求的资源不存在,并且未来也不会存在 在收到 410 状态码后,客户端 应该 停止再次请求该资源。 429 请求次数超过允许范围 500 未知异常 应该 提供完整的错误信息支持,也方便跟踪调试 1.8项目结构 1、采用经典DDD领域取到模型:(默认一个解决方案有5个项目) 5个项目分别为: Web层为最外层接口定义; Service为具体的应用服务处理; Infrastructure基础设施层,处理具体的业务逻辑和数据DB的处理; Domain领域层为模型和仓库接口interface; Common为通用的一些Helper类; 2、一个解决方案创建5个项目(如上图),并且里包含常用的基础组件:Log4net日志,听云监听;dockerfile,skywalking,全局异常捕捉,接口请求开始和结束的日志记录,swagger,service层的依赖注入,Mapping等。 3、代码全部采用依赖注入写法,尽量少些静态类; 4、HttpClient的写法:使用采用.netcore官方提供的方法,采用工厂类+依赖注入方式:实例代码如下: 复制代码1、SartUp类里添加代码-- httpclient初始化: services.AddHttpClient("MsgApi", c => { c.BaseAddress = new Uri(Configuration["OuterApi:MsgApi:url"]); c.Timeout = TimeSpan.FromSeconds(30); }); //2 构造函注入private IDbContext _dbContext;private IUnitOfWork _unitOfWork;private IordersRepository _ordersRepository;private IordercourseRepository _ordercourseRepository;private ILogger _logger;privatereadonly IConfiguration _config;privatereadonly IHttpClientFactory _clientFactory; public ordersService(IDbContext dbContext, ILogger logger, IConfiguration config, IHttpClientFactory clientFactory) { _dbContext = dbContext; _unitOfWork = new UnitOfWork(_dbContext); _ordersRepository = new ordersRepository(_dbContext); _ordercourseRepository = new ordercourseRepository(_dbContext); _mapper = mapper; _config = config; _logger = logger; _clientFactory = clientFactory; } //3使用 //////判断此时该校区是否可以下单/////////publicasync Task> CheckDept(CheckSchoolDeptReq req) { Result<string> sendRet = new Result<string>(); try { HttpClient client = _clientFactory.CreateClient("ContractApi"); MyHttpClientHelper myHttpClientHelper = new MyHttpClientHelper(); MarketToUPCCheckReq checkreq = new MarketToUPCCheckReq(); sendRet = await myHttpClientHelper.GetData<Result<string>>(client, "MarketToUPCCheck", checkreq); } catch (Exception ex) { sendRet.state = false; sendRet.error_code = ErrorCode.SysExceptionError; sendRet.error_msg = "调用《是否可以下订单接口》报错了。请重试或者联系管理员!"; _logger.LogError(ex, ErrorCode.SysExceptionError +"调用《是否可以下订单》接口报错了:" + ex.Message); } return sendRet; } 复制代码 1.9日志 1、接口开始前和结束后都已在LogstashFilter里记录,接口里就不需要再次记录; LogstashFilter里的代码如下: 复制代码 /// /// 记录日志用过滤器 /// </summary> public class LogstashFilter : IActionFilter, IResultFilter { private string ActionArguments { get; set; } /// <summary> /// 请求体中的所有值 /// </summary> private string RequestBody { get; set; } private Stopwatch Stopwatch { get; set; } private ILogger _logger; public LogstashFilter(ILogger<LogstashFilter> logger ) { _logger = logger; } /// <summary> /// Action 调用前执行 /// </summary> /// <param name="context"></param> public void OnActionExecuting(ActionExecutingContext context) { long contentLen = context.HttpContext.Request.ContentLength == null ? 0 : context.HttpContext.Request.ContentLength.Value; if (contentLen > 0) { // 读取请求体中所有内容 System.IO.Stream stream = context.HttpContext.Request.Body; if (context.HttpContext.Request.Method == "POST") { stream.Position = 0; } byte[] buffer = new byte[contentLen]; stream.Read(buffer, 0, buffer.Length); RequestBody = System.Text.Encoding.UTF8.GetString(buffer);// 转化为字符串 } ActionArguments = JsonConvert.SerializeObject(context.ActionArguments); Stopwatch = new Stopwatch(); Stopwatch.Start(); string url = context.HttpContext.Request.Host + context.HttpContext.Request.Path + context.HttpContext.Request.QueryString; string method = context.HttpContext.Request.Method; _logger.LogInformation($"地址:{url} \n " + $"方式:{method} \n " + $"请求体:{RequestBody} \n " + $"完整参数:{ActionArguments}\n " ); } /// <summary> /// Action 方法调用后,Result 方法调用前执行 /// </summary> /// <param name="context"></param> public void OnActionExecuted(ActionExecutedContext context) { // do nothing } /// <summary> /// Result 方法调用前(View 呈现前)执行 /// </summary> /// <param name="context"></param> public void OnResultExecuting(ResultExecutingContext context) { // do nothing } /// <summary> /// Result 方法调用后执行 /// </summary> /// <param name="context"></param> public void OnResultExecuted(ResultExecutedContext context) { Stopwatch.Stop(); string url = context.HttpContext.Request.Host + context.HttpContext.Request.Path + context.HttpContext.Request.QueryString; string method = context.HttpContext.Request.Method; string qs = ActionArguments; string res = "在返回结果前发生了异常"; if (context.Result is ObjectResult) { dynamic result = context.Result.GetType().Name == "EmptyResult" ? new { Value = "无返回结果" } : context.Result as dynamic; if (result != null) { res = JsonConvert.SerializeObject(result.Value); } } _logger.LogInformation($"地址:{url} \n " + $"方式:{method} \n " + $"请求体:{RequestBody} \n " + $"参数:{qs}\n " + $"结果:{res}\n " + $"耗时:{Stopwatch.Elapsed.TotalMilliseconds} 毫秒"); } } 复制代码 2、try Catch日志必须要添加LogError日志,并且要将堆栈信息记录,代码如下: catch (Exception ex) { _logger.LogError(ex, ErrorCode500 + ex.Message); }

优秀的个人博客,低调大师

吃透 Dapper 异步操作,WebAPI 性能直接翻倍

![](https://developer.qcloudimg.com/http-save/yehe-2415476/1c7e88efc9ca9811b351d0ad0ba247db.jpg) 同步 Dapper 写起来很顺手,但一上 Web API 就得换异步。这篇聊为什么以及怎么用。 --- ## **为什么不用异步会出事** 线程池的大小是有限的。同步调数据库的时候,线程卡在那里等 IO,什么也干不了。100 个并发请求过来,线程池里如果只有 100 个线程,全部堵在数据库等待上——你猜后面进来的请求怎么办?排队,超时,503。 异步的本质是:发起数据库查询后,线程立刻回到池子里接下一个请求;等数据库返回数据了,系统再分配一个线程继续执行。所以同样的线程数,异步能扛的并发量远高于同步。 ![](https://developer.qcloudimg.com/http-save/yehe-2415476/399d0224c294c6c8594620a68c682ee6.jpg) 但异步也有一个雷区,我踩过一次印象非常深。 有个支付回调接口,之前同事写的同步代码,我接手后改成异步。改完上线,第二天中午支付高峰期接口直接卡死,订单全超时。回滚代码一看——我在 Controller 里写了 `.Result`: ``` // 就这行,高并发下直接死锁 var order = _orderService .GetAsync(id) .Result; ``` 原因不展开了,简单说是 `async/await` 的上下文捕获跟 `.Result` 的阻塞等待冲突,线程互相等就死锁了。 **所以第一条铁律:异步就要到底。Controller → Service → Repository 全是 `async Task`,任何一层都不许用 `.Result`、`.Wait()`、`WaitAll()`。** --- ## **异步连接和查询** 连接用 `OpenAsync()`,跟同步就这一个区别: ``` using (IDbConnection db = new SqlConnection(connectionString)) { await db.OpenAsync(); // 后面全异步 } ``` 查询: ``` // 查列表 var data = await db.QueryAsync("SELECT * FROM Users WHERE Age > @Age", new { Age = }); // 查单条(最常用) var user = await db.QueryFirstOrDefaultAsync("SELECT * FROM Users WHERE Id = @Id", new { Id = }); // 多条件 var result = await db.QueryAsync( "SELECT * FROM Users WHERE Name = @Name AND Email = @Email", new { Name = name, Email = email }); ``` 跟同步版规则一样:`QueryFirstOrDefaultAsync` 查不到返回 null,`QueryFirstAsync` 查不到抛异常,`QuerySingleOrDefaultAsync` 查到多条抛异常。 增删改用 `ExecuteAsync`: ``` await db.ExecuteAsync("INSERT INTO Users (Name, Email, Age) VALUES (@Name, @Email, @Age)", model); await db.ExecuteAsync("UPDATE Users SET Name = @NewName WHERE Id = @Id", new { NewName = "Robert", Id = }); await db.ExecuteAsync("DELETE FROM Users WHERE Id = @Id", new { Id = }); ``` 联表查询异步版也一样,加个 `Async` 后缀,`splitOn` 用法不变。存储过程也是多一行 `commandType` 参数,跟同步完全一个套路。这两个就不展开贴完整方法签名了,照第一章的同步版加 `Async` + `await` 就是: ``` // 联表 var result = await db.QueryAsync(sql, (user, addr) => { user.Address = addr; return user; }, splitOn: "City"); // 存储过程 var user = await db.QueryFirstOrDefaultAsync( "GetUserById", new { Id = id }, commandType: CommandType.StoredProcedure); ``` --- ## **异步事务** 这是生产环境最重要的场景。比如用户下单,要同时扣库存、生成订单、写流水——三条 SQL 必须同生共死。 流程很直:`OpenAsync → BeginTransactionAsync → ExecuteAsync(都带同一个 tran)→ CommitAsync`,异常就 `RollbackAsync`。 ![](https://developer.qcloudimg.com/http-save/yehe-2415476/136b3693c9512f66cdaf2b4c3a2e81ba.jpg) ``` public async Task BatchSaveAsync() { using IDbConnection db = new SqlConnection(connStr); await db.OpenAsync(); using IDbTransaction tran = await db.BeginTransactionAsync(); try { await db.ExecuteAsync("INSERT INTO Users(Name) VALUES (@Name)", new { Name = "用户A" }, transaction: tran); await db.ExecuteAsync("UPDATE Logs SET Status='Done' WHERE Id=@Id", new { Id = }, transaction: tran); await tran.CommitAsync(); } catch (Exception ex) { await tran.RollbackAsync(); throw new Exception("事务失败已回滚", ex); } } ``` 还是要提醒一句:事务里每条 SQL 都得传 `transaction: tran`,漏一条就白搞。还有就是事务里不能新建连接——同一个 `tran` 绑定的是当前这个 `db`,换连接就管不到了。 --- ## **异步开发的几条死线** 1. 不用 `.Result`、`.Wait()`、`WaitAll()`——死锁的根 1. 异步全套:`OpenAsync → ExecuteAsync → CommitAsync → RollbackAsync`,别跟同步混 1. 事务内所有操作同一个连接,不能新建 1. `async Task` 从 Controller 一路传到 Repository,中间不许断 1. 异步连接也得 `using`,一样的释放逻辑 --- 异步 Dapper 其实很好记:同步方法名后面加 `Async`,前面加 `await`。难的不是写法,是整条调用链上都保持异步,不留阻塞点。

优秀的个人博客,低调大师

🔥 把 Java WebApi 快速转为 Mcp-Server(使用 Solon AI MCP)

solon-ai-mcp,提供了各种 mcp 相关能力,支持 java8, java11, java17, java21, java24 。是 solon-ai 项目的重要组成部分,也可以嵌入到 springboot2, springboot3, jfinal, vert.x 等项目中使用。 依赖包: <dependency> <groupId>org.noear</groupId> <artifactId>solon-ai-mcp</artifactId> <version>最新版</version> </dependency> 1、看一个简单的 web 控制器 此控制器有两个方法:get_weather,获取天气;get_rainfall,获取降雨量。后面基于它进行快速改造演示。 @Mapping("/web/api") @Controller public class McpServerTool { @Mapping("get_weather") public String get_weather(String location) { return "晴,14度"; } @Mapping("get_rainfall") public String get_rainfall(String location) { return "555毫米"; } } 2、改造方案1:复用 mvc 注解,并添加@McpServerEndpoint注解 在 mvc 注解基础上,给方法和参数添加描述信息description信息(只适合 solon-web 项目,它的注解带有description属性)。再添加@McpServerEndpoint注解 @McpServerEndpoint(sseEndpoint = "/mcp/sse") @Mapping("/web/api") @Controller public class McpServerTool { @Mapping(path="get_weather", description = "查询天气预报") public String get_weather(@Param(description = "城市位置") String location) { return "晴,14度"; } @Mapping(path="get_rainfall", description = "查询城市降雨量") public String get_rainfall(@Param(description = "城市位置") String location) { return "555毫米"; } } 这个方案的提醒说明: @Mapping(通用注解)相当于 ai 的@ToolMapping(专用注解) @Param(通用注解)相当于 ai 的@ToolParam(专用注解) 必须申明description属性(否则会异常提示) 3、改造方案2:在控制器上,添加完整的 Mcp 相关注解 这个方案也适合 springboot2-web、sprngboot3-web、spring-mvc 或 jfinal 等项目,原控制器的代码不用动(原来有没有注解,不影响)。就像添加 swagger 注解那样,添加 mcp 相关注解即可。 注解简单说明(?表示可选属性): 注解 描述 @ToolMapping(name?, description, returnDirect?, resultConverter?) 工具映射 @ToolParam(name?, description, required?) 工具的参数申明 改造后的代码: @McpServerEndpoint(sseEndpoint = "/mcp/sse") @Mapping("/web/api") @RestController public class McpServerTool { @ToolMapping(description = "查询天气预报") @GetMapping("get_weather") public String get_weather(@ToolParam(description = "城市位置") String location) { return "晴,14度"; } @ToolMapping(description = "查询城市降雨量") @GetMapping("get_rainfall") public String get_rainfall(@ToolParam(description = "城市位置") String location) { return "555毫米"; } } 4、也可以转为本地的大模型工具提供者使用(ToolProvider) 上两个方案去掉@McpServerEndpoint注解,就不会发布为 MCP 服务端点。使用 MethodToolProvider 加载它,就能成为本地的工具提供者。 MethodToolProvider toolProvider = new MethodToolProvider(new McpServerTool()); var chatModel = ChatModel.of(...) .defaultToolsAdd(toolProvider) //添加默认工具 .build(); chatModel.prompt("杭州今天的天气怎么样?") .call(); 5、附代码仓库 solon-ai(源码) https://gitee.com/opensolon/solon-ai https://gitcode.com/opensolon/solon-ai https://github.com/opensolon/solon-ai solon-ai-mcp-embedded-examples(嵌入示例) https://gitee.com/opensolon/solon-ai-mcp-embedded-examples https://gitcode.com/opensolon/solon-ai-mcp-embedded-examples https://github.com/opensolon/solon-ai-mcp-embedded-examples

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册