效果解釋:執行 winform 端後 使用 ctrl+c 先複製任何詞語,然後ctrl+空格 就可以將翻譯結果顯示在 安卓,IOS,windows 甚至 mac 任意使用者端
CommunityToolkit.Mvvm MAUI 的官方 MVVM 庫,可以很方便的讓C#像VUE那樣簡單的使用雙向繫結
MQTTnet 這裡我們 MAUI 使用者端和伺服器端之間使用 MQTT 協定來通訊
Newtonsoft.Json 這個不解釋,不參照也可以直接使用官方的 System.Text.Json
`<?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>
`
public partial class MainPageModel : ObservableObject
{
[ObservableProperty]
public string strTips;
[ObservableProperty]
public string strKey;
[ObservableProperty]
public string strValue1;
[ObservableProperty]
public string strValue2;
}
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);
}
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
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);
}
}
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);
}
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;