using Microsoft.Owin.Hosting;
using Newtonsoft.Json;
using Owin;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Cors;
using System.Windows.Forms;
using static WindowsFormsApp1.Form1;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private IDisposable _webApp;
private int _localPort = 8082;
private string _localIp;
private string _baseUrl;
// 关键修正:手动配置对方 IP,不再自动生成
private string _remoteIp = "192.168.250.1"; // 运行时手动输入对方 IP
private string _apiBaseUrl => $"http://{_remoteIp}:{_localPort}"; // 指向对方的服务地址
private static readonly HttpClient _httpClient = new HttpClient();
public Form1()
{
InitializeComponent();
this.FormClosing += Form1_FormClosing;
// 1. 获取本机 IP
_localIp = GetLocalIPAddress();
_baseUrl = $"http://{_localIp}:{_localPort}";
// 2. 添加「对方IP输入框」和「保存配置按钮」(需要在窗体上拖入两个控件)
// 窗体控件建议:
// - Label:Text = "对方IP地址:"
// - TextBox:Name = txtRemoteIp
// - Button:Name = btnSaveConfig, Text = "保存配置"
txtLog.AppendText($"本机 IP:{_localIp} | 服务地址:{_baseUrl}\r\n");
txtLog.AppendText($"请输入对方 IP 后点击【保存配置】\r\n");
// 绑定保存配置按钮事件
// btnSaveConfig.Click += btnSaveConfig_Click;
_remoteIp = "192.168.250.1";// txtRemoteIp.Text.Trim();
// 重新初始化 HttpClient 指向对方地址
InitHttpClient();
txtLog.AppendText($"已配置对方 IP:{_remoteIp} | 调用地址:{_apiBaseUrl}\r\n");
}
#region 关键修正:手动保存对方 IP 配置
private void btnSaveConfig_Click(object sender, EventArgs e)
{
//if (string.IsNullOrWhiteSpace(txtRemoteIp.Text))
//{
// MessageBox.Show("请输入对方电脑的局域网 IP!");
// return;
//}
_remoteIp = "192.168.250.1";// txtRemoteIp.Text.Trim();
// 重新初始化 HttpClient 指向对方地址
InitHttpClient();
txtLog.AppendText($"已配置对方 IP:{_remoteIp} | 调用地址:{_apiBaseUrl}\r\n");
}
#endregion
#region API 服务端(无需修改,保持不变)
private void btnStartApi_Click(object sender, EventArgs e)
{
try
{
if (IsPortInUse(_localPort))
{
txtLog.AppendText($"错误:{_localPort} 端口被占用!\r\n");
return;
}
_webApp = WebApp.Start<Startup>(url: _baseUrl);
txtLog.AppendText($"API 服务启动成功!地址:{_baseUrl}\r\n");
btnStartApi.Enabled = false;
btnStopApi.Enabled = true;
}
catch (Exception ex)
{
txtLog.AppendText($"启动失败:{ex.Message}\r\n");
}
}
private void btnStopApi_Click(object sender, EventArgs e)
{
_webApp?.Dispose();
txtLog.AppendText("API 服务已停止\r\n");
btnStartApi.Enabled = true;
btnStopApi.Enabled = false;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_webApp?.Dispose();
}
private bool IsPortInUse(int port)
{
try
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Bind(new IPEndPoint(IPAddress.Parse(_localIp), port));
return false;
}
}
catch { return true; }
}
private string GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork) return ip.ToString();
}
return "127.0.0.1";
}
#endregion
#region API 客户端(修正:指向对方地址)
private void InitHttpClient()
{
_httpClient.BaseAddress = new Uri(_apiBaseUrl);
_httpClient.Timeout = TimeSpan.FromSeconds(10);
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
// GET 调用:异步执行,避免 UI 卡死
private async void btnSendGet_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(_remoteIp))
{
MessageBox.Show("请先配置对方 IP!");
return;
}
btnSendGet.Enabled = false;
try
{
var result = await CallGetHelloApi("张三");
txtLog.AppendText($"【跨机 GET 响应】:{result}\r\n");
}
catch (Exception ex)
{
txtLog.AppendText($"GET 调用失败:{ex.Message}\r\n");
}
finally { btnSendGet.Enabled = true; }
}
// POST 调用:异步执行
private async void btnSendPost_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(_remoteIp))
{
MessageBox.Show("请先配置对方 IP!");
return;
}
btnSendPost.Enabled = false;
try
{
var result = await CallPostUserInfoApi();
if (result.Code == 200)
{
txtLog1.Text = $"【跨机 POST 成功】处理时间:{result.Data.HandleTime} | 消息:{result.Message}";
}
else
{
txtLog1.Text = $"【跨机 POST 失败】:{result.Message}";
}
}
catch (Exception ex)
{
txtLog1.Text = $"POST 调用异常:{ex.Message}";
}
finally { btnSendPost.Enabled = true; }
}
private async Task<string> CallGetHelloApi(string name)
{
var url = $"/api/hello?name={Uri.EscapeDataString(name)}";
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
// 替换原来的 CallPostUserInfoApi 方法
private async Task<ApiResponse<UserInfoResponse>> CallPostUserInfoApi()
{
var defaultRes = new ApiResponse<UserInfoResponse>
{
Code = 500,
Message = "调用失败(默认值)"
};
// 先校验对方IP是否配置
if (string.IsNullOrWhiteSpace(_remoteIp))
{
defaultRes.Message = "未配置对方IP,请先保存配置";
return defaultRes;
}
try
{
// 1. 构造请求参数(和对方的 UserInfoRequest 完全一致)
var reqModel = new UserInfoRequest
{
UserId = new Random().Next(1000, 2000), // 随机ID,方便区分不同请求
UserName = $"测试用户李四_{DateTime.Now:HHmmss}",
Age = 28
};
// 2. 序列化JSON(关键:确保无循环引用、空值处理)
string jsonBody = JsonConvert.SerializeObject(reqModel, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
StringEscapeHandling = StringEscapeHandling.EscapeNonAscii
});
// 3. 构造请求内容(核心修正:指定UTF-8编码)
var httpContent = new StringContent(jsonBody, Encoding.UTF8, "application/json");
// 4. 拼接完整的请求地址(避免路由匹配问题)
string fullUrl = $"{_apiBaseUrl}/api/userinfo"; // 完整地址:http://对方IP:8082/api/userinfo
Application.OpenForms["Form1"]?.Invoke(new Action(() =>
{
var form = (Form1)Application.OpenForms["Form1"];
form.txtLog.AppendText($"【准备发送Post请求】地址:{fullUrl}\r\n参数:{jsonBody}\r\n");
}));
// 5. 发送Post请求(禁用缓存,确保每次都是新请求)
var response = await _httpClient.PostAsync(fullUrl, httpContent).ConfigureAwait(false);
// 6. 打印响应状态码(关键:调试是否到达对方)
Application.OpenForms["Form1"]?.Invoke(new Action(() =>
{
var form = (Form1)Application.OpenForms["Form1"];
form.txtLog.AppendText($"【响应状态码】:{response.StatusCode} ({(int)response.StatusCode})\r\n");
}));
// 7. 读取完整响应(哪怕状态码不是200,也要读出来)
string responseStr = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Application.OpenForms["Form1"]?.Invoke(new Action(() =>
{
var form = (Form1)Application.OpenForms["Form1"];
form.txtLog.AppendText($"【原始响应内容】:{responseStr}\r\n");
}));
// 8. 处理响应
if (response.IsSuccessStatusCode)
{
// 反序列化成功响应
var result = JsonConvert.DeserializeObject<ApiResponse<UserInfoResponse>>(responseStr);
return result ?? defaultRes;
}
else
{
// 非200状态码:返回错误信息
defaultRes.Code = (int)response.StatusCode;
defaultRes.Message = $"对方返回错误:{response.StatusCode} | {responseStr}";
return defaultRes;
}
}
catch (HttpRequestException ex)
{
defaultRes.Message = $"网络调用异常:{ex.Message}";
Application.OpenForms["Form1"]?.Invoke(new Action(() =>
{
var form = (Form1)Application.OpenForms["Form1"];
form.txtLog.AppendText($"【调用异常】:{ex.Message}\r\n");
}));
return defaultRes;
}
catch (Exception ex)
{
defaultRes.Message = $"序列化/解析异常:{ex.Message}";
Application.OpenForms["Form1"]?.Invoke(new Action(() =>
{
var form = (Form1)Application.OpenForms["Form1"];
form.txtLog.AppendText($"【其他异常】:{ex.Message}\r\n");
}));
return defaultRes;
}
}
#endregion
#region 数据模型(保持不变)
public class UserInfoRequest
{
Required(ErrorMessage = "用户ID不能为空")
Range(1, int.MaxValue)
public int UserId { get; set; }
Required(ErrorMessage = "用户名不能为空")
StringLength(20)
public string UserName { get; set; } = string.Empty;
Range(0, 150)
public int Age { get; set; }
}
public class ApiResponse<T>
{
public int Code { get; set; }
public string Message { get; set; } = string.Empty;
public T Data { get; set; } = default;
}
public class UserInfoResponse
{
public int UserId { get; set; }
public string UserName { get; set; } = string.Empty;
public int Age { get; set; }
public string HandleTime { get; set; } = string.Empty;
}
#endregion
}
#region OWIN 启动 + 控制器(保持不变)
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });
app.UseWebApi(config);
}
}
public class ExternalApiController : ApiController
{
HttpGet
Route("api/hello")
public IHttpActionResult GetHello(string name = "访客")
{
return Json(new ApiResponse<string>
{
Code = 200,
Message = "成功",
Data = $"你好 {name}!我是 {GetLocalIPAddress()} 的服务",
});
}
HttpPost
Route("api/userinfo")
public IHttpActionResult PostUserInfo([FromBody] UserInfoRequest request)
{
// 关键:添加日志,确认方法被调用(会输出到对方的txtLog控件)
string logMsg = $"【PostUserInfo被调用】时间:{DateTime.Now:HH:mm:ss.fff} | ";
if (request == null)
{
logMsg += "请求参数为NULL";
}
else
{
logMsg += $"UserId={request.UserId}, UserName={request.UserName}, Age={request.Age}";
}
// 跨线程调用窗体控件(必须用Invoke,否则会报错)
Application.OpenForms["Form1"]?.Invoke(new Action(() =>
{
var form = (Form1)Application.OpenForms["Form1"];
form.txtLog.AppendText(logMsg + "\r\n");
}));
// 原有的参数校验逻辑
if (request == null)
{
return Json(new ApiResponse<object>
{
Code = 400,
Message = "参数不能为空"
});
}
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(request, new ValidationContext(request), validationResults, true);
if (!isValid)
{
return Json(new ApiResponse<object>
{
Code = 400,
Message = "参数校验失败",
Data = validationResults.Select(v => v.ErrorMessage)
});
}
try
{
return Json(new ApiResponse<UserInfoResponse>
{
Code = 200,
Message = $"接收用户 {request.UserName} 的信息成功(来自 {GetLocalIPAddress()})",
Data = new UserInfoResponse
{
UserId = request.UserId,
UserName = request.UserName,
Age = request.Age,
HandleTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
}
});
}
catch (Exception ex)
{
return Json(new ApiResponse<object>
{
Code = 500,
Message = "服务器内部错误",
Data = ex.Message
});
}
}
private static string GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
if (ip.AddressFamily == AddressFamily.InterNetwork) return ip.ToString();
return "127.0.0.1";
}
}
#endregion
}