软件效果图

软件架构草图

效果解释:运行 winform 端后 使用 ctrl+c 先复制任何词语,然后ctrl+空格 就可以将翻译结果显示在 安卓,IOS,windows 甚至 mac 任意客户端
1:使用 VS2022 + net6 创建 MAUI 项目,创建不了的先安装

2:创建时使用 .NET MAUI 应用,应用名字自己定义,我这里用 MauiAppClient

3:创建后的项目图

4:添加项目引用
CommunityToolkit.Mvvm MAUI 的官方 MVVM 库,可以很方便的让C#像VUE那样简单的使用双向绑定
MQTTnet 这里我们 MAUI 客户端和服务端之间使用 MQTT 协议来通信
Newtonsoft.Json 这个不解释,不引用也可以直接使用官方的 System.Text.Json
5:打开 MainPage.xaml 将文件修改为一下内容,MainPage.xaml内容为APP软件主程序窗口页面内容
`<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MauiAppClient.MainPage" xmlns:viewmodel="clr-namespace:MauiAppClient.ViewModel" x:DataType="viewmodel:MainPageModel" BackgroundColor="Black" > <ScrollView> <StackLayout Margin="20,35,20,25"> <Label x:Name="LabelTips" Text="{Binding StrTips}" FontSize="12" TextColor="Red" Margin="15" /> <Label x:Name="LabelKey" Text="{Binding StrKey}" FontSize="32" TextColor="GreenYellow" Margin="15" /> <Label x:Name="LabelValue1" Text="{Binding StrValue1}" FontSize="26" TextColor="#08e589" Margin="15" /> <Label x:Name="LabelValue2" Text="{Binding StrValue2}" FontSize="26" TextColor="#08e589" Margin="15" /> </StackLayout> </ScrollView> </ContentPage> `
6:创建页面绑定视图模型 有不熟悉 CommunityToolkit.Mvvm 组件的同学建议先查阅下此组件的使用方式。加好模型后记得注入一下 builder.Services.AddSingleton();
public partial class MainPageModel : ObservableObject { [ObservableProperty] public string strTips; [ObservableProperty] public string strKey; [ObservableProperty] public string strValue1; [ObservableProperty] public string strValue2; }
7:MAUI 程序主代码
public static IMqttClient _mqttClient; public MainPageModel _vm; public MainPage(MainPageModel vm) { InitializeComponent(); _vm = vm; BindingContext = _vm; MqttInit(); } /// <summary> /// 初始化MQTT /// </summary> public void MqttInit() { string clientId = Guid.NewGuid().ToString(); var optionsBuilder = new MqttClientOptionsBuilder() .WithTcpServer("xxx.xxx.xxx.xxx", 1883) // 要访问的mqtt服务端的 ip 和 端口号 //.WithCredentials("admin", "123456") // 要访问的mqtt服务端的用户名和密码 .WithClientId(clientId) // 设置客户端id .WithCleanSession() .WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = false // 是否使用 tls加密 }); var clientOptions = optionsBuilder.Build(); _mqttClient = new MqttFactory().CreateMqttClient(); _mqttClient.ConnectedAsync += _mqttClient_ConnectedAsync; // 客户端连接成功事件 _mqttClient.DisconnectedAsync += _mqttClient_DisconnectedAsync; // 客户端连接关闭事件 _mqttClient.ApplicationMessageReceivedAsync += _mqttClient_ApplicationMessageReceivedAsync; // 收到消息事件 _mqttClient.ConnectAsync(clientOptions); } /// <summary> /// 客户端连接关闭事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private Task _mqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg) { _vm.StrTips = "已断开与服务端的连接"; int i = 0; Task.Factory.StartNew(() => { while (_mqttClient == null || _mqttClient.IsConnected == false) { i++; Thread.Sleep(5 * 1000); MqttInit(); _mqttClient.SubscribeAsync("pc-helper", MqttQualityOfServiceLevel.AtLeastOnce); _vm.StrTips = "尝试重新连..." + i; } }).ConfigureAwait(false); return Task.CompletedTask; } /// <summary> /// 客户端连接成功事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private Task _mqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg) { _vm.StrTips = "已连接服务端"; _mqttClient.SubscribeAsync("pc-helper", MqttQualityOfServiceLevel.AtLeastOnce); return Task.CompletedTask; } /// <summary> /// 收到消息事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private Task _mqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg) { string msg = Encoding.UTF8.GetString(arg.ApplicationMessage.Payload); var model = Newtonsoft.Json.JsonConvert.DeserializeObject<MsgModel>(msg); _vm.StrKey = "词语:" + model.query?.ToString(); string value1 = "翻译:"; if (model.translation != null) { foreach (var item in model.translation) { value1 += item; } } _vm.StrValue1 = value1; string value2 = "解释:"; if (model.basic != null && model.basic.explains != null) { foreach (var item in model.basic.explains) { value2 += item; } } _vm.StrValue2 = value2; return Task.CompletedTask; } public void Publish(string data) { var message = new MqttApplicationMessage { Topic = "pc-helper", Payload = Encoding.Default.GetBytes(data), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Retain = true // 服务端是否保留消息。true为保留,如果有新的订阅者连接,就会立马收到该消息。 }; _mqttClient.PublishAsync(message); }
MAUI 发布 APK 供手机下载安装使用 右键 MAUI 项目>使用终端 输入如下命令
keytool -genkey -v -keystore myapp.keystore -alias key -keyalg RSA -keysize 2048 -validity 10000
项目文件中增加如下配置
<PropertyGroup Condition="$(TargetFramework.Contains('-android')) and '$(Configuration)' == 'Release'"> <AndroidKeyStore>True</AndroidKeyStore> <AndroidSigningKeyStore>myapp.keystore</AndroidSigningKeyStore> <AndroidSigningKeyAlias>key</AndroidSigningKeyAlias> <AndroidSigningKeyPass></AndroidSigningKeyPass> <AndroidSigningStorePass></AndroidSigningStorePass> </PropertyGroup>
发布
dotnet publish -f:net6.0-android -c:Release /p:AndroidSigningKeyPass=youpwd /p:AndroidSigningStorePass=youpwd 替换命令中的 youpwd
参考官方 发布 文章 https://learn.microsoft.com/zh-cn/dotnet/maui/android/deployment/publish-cli
8:创建 winform 程序 使用 .net framework 4.7 主要使用全局快捷方式 需要用到 user32.dll 主程序代码
public partial class MainForm : Form { int crtlSpace;//定义快捷键 public static IMqttClient _mqttClient; public MainForm() { InitializeComponent(); crtlSpace = "CtrlSpace".GetHashCode(); //组合键模式 None = 0,Alt = 1,Ctrl = 2,Shift = 4,WindowsKey = 8 Win32Api.RegisterHotKey(this.Handle, crtlSpace, 2, (int)Keys.Space); } /// <summary> /// 热键 /// </summary> /// <param name="m"></param> protected override void WndProc(ref Message m) { const int WM_HOTKEY = 0x0312; int wParam = (int)m.WParam; switch (m.Msg) { case WM_HOTKEY: if (wParam == crtlSpace) { var text = Clipboard.GetText(); string result= YouDao.GetFanYiResult(text); Publish(result); AppentTextLog("触发:【" + text + "】->" + System.DateTime.Now); } break; } base.WndProc(ref m); } /// <summary> /// 隐藏窗体 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button_closed_Click(object sender, EventArgs e) { this.Visible = false; } private void MainForm_Load(object sender, EventArgs e) { MqttInit(); } /// <summary> /// 窗口拖拽 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MainForm_MouseDown(object sender, MouseEventArgs e) { Win32Api.ReleaseCapture(); Win32Api.SendMessage(this.Handle, Win32Api.WM_SYSCOMMAND, Win32Api.SC_MOVE + Win32Api.HTCAPTION, 0); } /// <summary> /// 双击右下角图标显示隐藏 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { this.Visible = !this.Visible; } } /// <summary> /// 右键右下角图标退出程序 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void 退出ToolStripMenuItem_Click(object sender, EventArgs e) { //卸载注册热键 Win32Api.UnregisterHotKey(this.Handle, this.crtlSpace); //关闭窗口 this.Close(); } /// <summary> /// 初始化MQTT /// </summary> public void MqttInit() { string clientId=Guid.NewGuid().ToString(); var optionsBuilder = new MqttClientOptionsBuilder() .WithTcpServer("xxx.xxx.xxx.xxx", 1883) // 要访问的mqtt服务端的 ip 和 端口号 //.WithCredentials("admin", "123456") // 要访问的mqtt服务端的用户名和密码 .WithClientId(clientId) // 设置客户端id .WithCleanSession() .WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = false // 是否使用 tls加密 }); var clientOptions = optionsBuilder.Build(); _mqttClient = new MqttFactory().CreateMqttClient(); _mqttClient.ConnectedAsync += _mqttClient_ConnectedAsync; // 客户端连接成功事件 _mqttClient.DisconnectedAsync += _mqttClient_DisconnectedAsync; // 客户端连接关闭事件 _mqttClient.ConnectAsync(clientOptions); } /// <summary> /// 客户端连接关闭事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private Task _mqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg) { AppentTextLog($"MQTT服务已关闭"); return Task.CompletedTask; } /// <summary> /// 客户端连接成功事件 /// </summary> /// <param name="arg"></param> /// <returns></returns> private Task _mqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg) { AppentTextLog($"MQTT服务已连接^v^"); _mqttClient.SubscribeAsync("pc-helper", MqttQualityOfServiceLevel.AtLeastOnce); return Task.CompletedTask; } /// <summary> /// 发送MQTT消息 /// </summary> /// <param name="data"></param> public void Publish(string data) { var message = new MqttApplicationMessage { Topic = "pc-helper", Payload = Encoding.UTF8.GetBytes(data), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Retain = true // 服务端是否保留消息。true为保留,如果有新的订阅者连接,就会立马收到该消息。 }; _mqttClient.PublishAsync(message); } /// <summary> /// 更新窗体文本 /// </summary> /// <param name="text"></param> void AppentTextLog(string text) { Action act = delegate () { textBox_log.AppendText(Environment.NewLine + text + Environment.NewLine); textBox_log.ScrollToCaret(); if (textBox_log.Text.Count() > 100000) { textBox_log.Clear(); } }; this.Invoke(act); } }
9:翻译API这里使用了“有道”
public static string GetFanYiResult(string text) { Dictionary<String, String> dic = new Dictionary<String, String>(); string url = "https://openapi.youdao.com/api"; string appKey = "xxx"; string appSecret = "xxx"; string salt = Guid.NewGuid().ToString(); dic.Add("from", "auto"); dic.Add("to", "zh-CHS"); dic.Add("signType", "v3"); TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)); long millis = (long)ts.TotalMilliseconds; string curtime = Convert.ToString(millis / 1000); dic.Add("curtime", curtime); string signStr = appKey + Truncate(text) + salt + curtime + appSecret; string sign = ComputeHash(signStr, new SHA256CryptoServiceProvider()); dic.Add("q", text); dic.Add("appKey", appKey); dic.Add("salt", salt); dic.Add("sign", sign); return Post(url, dic); }
10: 到这里两个端都有了,就差最关键的 MQTT 服务了。MQTTnet 组件本身是可以起身起一个服务供软件使用的(有兴趣的同学可以自己扩展),这里我们直接使用 docker 线上部署一个 mqtt 服务 (emqx,感兴趣的同学可以查阅此工具,自带web管理)
docker run -dit --restart=always -d --name emqx -e EMQX_HOST="127.0.0.1" -e EMQX_NAME="emqx" -p 4369:4369 -p 4370:4370 -p 5369:5369 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 0.0.0.0:1883:1883 -p 0.0.0.0:18083:18083 -p 0.0.0.0:9981:8081 emqx/emqx:latest;
