You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

480 lines
18 KiB
C#

9 months ago
using JwKdsV.Core;
using JwKdsV.Core.Utils;
using JwKdsV.Entity.Common;
using JwKdsV.Entity.Tenant;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace JwKdsV
{
static class Program
{
private const int SW_HIDE = 0;
private const int SW_NORMAL = 1;
private const int SW_MAXIMIZE = 3;
private const int SW_SHOWNOACTIVATE = 4;
private const int SW_SHOW = 5;
private const int SW_MINIMIZE = 6;
private const int SW_RESTORE = 9;
private const int SW_SHOWDEFAULT = 10;
/// 该函数设置由不同线程产生的窗口的显示状态
/// </summary>
/// <param name="hWnd">窗口句柄</param>
/// <param name="cmdShow">指定窗口如何显示。查看允许值列表请查阅ShowWlndow函数的说明部分</param>
/// <returns>如果函数原来可见,返回值为非零;如果函数原来被隐藏,返回值为零</returns>
[DllImport("User32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow);
/// <summary>
/// 该函数将创建指定窗口的线程设置到前台,并且激活该窗口。键盘输入转向该窗口,并为用户改各种可视的记号。
/// 系统给创建前台窗口的线程分配的权限稍高于其他线程。
/// </summary>
/// <param name="hWnd">将被激活并被调入前台的窗口句柄</param>
/// <returns>如果窗口设入了前台,返回值为非零;如果窗口未被设入前台,返回值为零</returns>
[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
//全局异常捕捉
Application.ThreadException += ApplicationThreadException; //UI线程异常
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
AppDomain.CurrentDomain.UnhandledException += CurrentDomainUnhandledException; //多线程异常
bool createdNew = true;
using (Mutex mutex = new Mutex(true, Application.ProductName, out createdNew))
{
if (createdNew)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SplashScreen.ShowSplashScreen();
//初始化NLog
NLogUtils.InitLogger();
//数据库去密码
try
{
ChangePwd("");
NLog.LogManager.GetCurrentClassLogger().Info("sqliteNoP success");
}
catch (Exception)
{
NLog.LogManager.GetCurrentClassLogger().Info("sqliteNoP error, already success!");
}
AutoUpdateNew();
SplashScreen.SetStatus("检测本机网络连接...");
var openApiTask = Task.Factory.StartNew<bool>(() => { return HttpClientUtils.IsAvailable(ApiType.Business); });
Task.WaitAll(openApiTask);
//网络可用:a)网卡工作;b)可以联网;c)开放平台正常
bool isConnected = openApiTask.Result;
SplashScreen.SetStatus(string.Format("本机网络连接{0}...", isConnected ? "正常" : "不正常"));
//网络连接压入全局参数
Global.Instance.Online = isConnected;
bool isGo = true;
SplashScreen.SetStatus("检测数据库版本...");
bool isException = AutoUpdateDatabase();
if (isException)
{
isGo = false;
SplashScreen.SetStatus("数据更新失败,即将退出...");
}
else
{
SplashScreen.SetStatus("数据更新成功...");
//重新加载设置,避免升级脚本更新设置,导致第一次不生效
Global.Instance.ReloadConfig();
}
SplashScreen.CloseForm();
if (isGo)
{
if (!CheckSameDevice())
{
//设备不一致,需要重新进行注册
var initForm = new TenantInitForm();
initForm.BringToFront();
if (initForm.ShowDialog() != DialogResult.OK)
{
//自杀
System.Diagnostics.Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();
currentProcess.Kill();
}
}
//登录
var loginForm = new LoginForm();
loginForm.BringToFront();
if (loginForm.ShowDialog() == DialogResult.OK)
{
//下载处理信息
var download = new DownloadingForm();
download.BringToFront();
if(download.ShowDialog() == DialogResult.OK)
{
//启动websocket
WebSocketEnter.Instance.Init();
//启动定时任务
JobUtils.Startup();
//展示主界面
var mainForm = new MainForm();
mainForm.BringToFront();
Application.Run(mainForm);
}
}
//关闭计划任务
JobUtils.Stop();
NLog.LogManager.GetCurrentClassLogger().Info("关闭计划任务");
}
}
else
{
Process instance = Instance();
ShowWindowAsync(instance.MainWindowHandle, SW_MAXIMIZE);//显示
SetForegroundWindow(instance.MainWindowHandle);//当到最前端
}
}
}
/// <summary>
/// 验证pos设备信息是否变更
/// </summary>
/// <returns></returns>
private static bool CheckSameDevice()
{
Authc authc = null;
using(var db = Global.Instance.OpenDataBase)
{
authc = db.FirstOrDefault<Authc>("where 1 = 1");
}
if(authc == null)
{
return false;
}
else
{
var device = new DeviceInfo();
device.Cpu = authc.CpuSerialNumber;
device.Disk = authc.DiskSerialNumber;
device.Mac = authc.MacAddress;
device.ComputerName = authc.CompterName;
return device.Equals(Global.Instance.DeviceInfo);
}
}
/// <summary>
/// 只运行一个实例
/// </summary>
/// <returns></returns>
private static Process Instance()
{
Process current = Process.GetCurrentProcess();
Process[] processes = Process.GetProcessesByName(current.ProcessName);
foreach (Process process in processes)
{
//如果实例已经存在则忽略当前进程
if (process.Id != current.Id)
{
//保证要打开的进程同已经存在的进程来自同一文件路径
if (Assembly.GetExecutingAssembly().Location.Replace("/", "\\") == current.MainModule.FileName)
{
//返回已经存在的进程
return process;
}
}
}
return null;
}
//UI线程异常
static void ApplicationThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
NLog.LogManager.GetCurrentClassLogger().Error(e.Exception, "ApplicationThreadException");
Global.Instance.BugReport(e.Exception);
}
//多线程异常
static void CurrentDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
NLog.LogManager.GetCurrentClassLogger().Error(e.ExceptionObject as Exception, "ApplicationThreadException");
Global.Instance.BugReport(e.ExceptionObject as Exception);
}
/// <summary>
/// 主程序更新 AutoUpdater.exe
/// </summary>
static void AutoUpdateNew()
{
string auto_update = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"AutoUpdater.exe");
string auto_update_new = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Constant.AUTO_UPDATER_NEW);
string auto_update_backup = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"data\backup\AutoUpdater.exe");
if (File.Exists(auto_update_new))
{
if (File.Exists(auto_update))
{
if (File.Exists(auto_update_backup))
{
File.Delete(auto_update_backup);
}
File.Move(auto_update, auto_update_backup);
}
FileInfo fi = new FileInfo(auto_update_new);
fi.MoveTo(auto_update);
}
}
static bool AutoUpdateDatabase()
{
bool isException = false;
//当前程序嵌入的数据库版本
int _version = 1;
//数据库的版本
int _dbVersion = 1;
ApplicationVersion entity = null;
try
{
var _array = Application.ProductVersion.Split('.');
int major = 0;
int.TryParse(_array[0], out major);
int minor = 0;
int.TryParse(_array[1], out minor);
int build = 2;
int.TryParse(_array[2], out build);
//当前程序嵌入的数据库版本
_version = major * 100 + minor * 10 + build;
//应用的版本
var _appVersion = Application.ProductVersion;
//判断数据库中记录的版本
using (var db = Global.Instance.OpenDataBase)
{
entity = db.Query<ApplicationVersion>().OrderByDescending(x => x.ModifyDate).FirstOrDefault();
}
if (entity == null)
{
entity = new ApplicationVersion();
entity.Id = IdWorkerUtils.Instance.NextId();
entity.AppVersion = _appVersion;
entity.DbVersion = _dbVersion.ToString();
}
int.TryParse(entity.DbVersion, out _dbVersion);
//升级前备份数据文件
string sourceFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Data\kdsDatav1.s3db");
string destFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format(@"Data\backup\kdsDatav1.s3db", ""));
//数据库需要升级
if (_version > _dbVersion)
{
SplashScreen.SetStatus(string.Format("备份历史数据<{0}>...", _dbVersion));
System.IO.File.Copy(sourceFileName, destFileName, true);
for (int i = _dbVersion + 1; i <= _version; i++)
{
var _name = string.Format("Data.db_{0}.txt", i);
//获得正在运行类所在的名称空间
Type type = MethodBase.GetCurrentMethod().DeclaringType;
string _namespace = type.Namespace;
//获得当前运行的Assembly
Assembly _assembly = Assembly.GetExecutingAssembly();
//根据名称空间和文件名生成资源名称
string resourceName = _namespace + "." + _name;
Tuple<bool, ConcurrentQueue<string>> migrate = null;
using (Stream stream = _assembly.GetManifestResourceStream(resourceName))
{
if (stream == null)
{
continue;
}
NLog.LogManager.GetCurrentClassLogger().Info("发布数据库更新<{0}>", _name);
using (StreamReader reader = new StreamReader(stream))
{
string json = reader.ReadToEnd();
migrate = ParseDbMigrate(json);
}
}
if (migrate != null && migrate.Item1)
{
var sqls = migrate.Item2;
SplashScreen.SetStatus(string.Format("更新数据,请稍候..."));
foreach (var sql in migrate.Item2)
{
try
{
SQLiteUtils.Execute(sql);
}
catch (Exception ex)
{
NLog.LogManager.GetCurrentClassLogger().Info(sql);
if (ex.Message.Contains("duplicate column name"))
{
NLog.LogManager.GetCurrentClassLogger().Error(ex, "列名升级失败,已存在");
}
else
{
NLog.LogManager.GetCurrentClassLogger().Error(ex, "数据库升级异常");
}
}
}
}
}
}
}
catch (Exception ex)
{
isException = true;
NLog.LogManager.GetCurrentClassLogger().Error(ex, "数据库升级异常");
}
finally
{
if (!isException)
{
entity.AppVersion = Application.ProductVersion;
entity.DbVersion = _version.ToString();
using (var db = Global.Instance.OpenDataBase)
{
using (var trans = db.GetTransaction())
{
db.Save(entity);
trans.Complete();
}
}
}
//刷新Http请求的AppKey避免调用错误
OpenApiUtils.Instance.Refresh();
}
return isException;
}
static Tuple<bool, ConcurrentQueue<string>> ParseDbMigrate(string json)
{
Tuple<bool, ConcurrentQueue<string>> result = null;
try
{
var list = new ConcurrentQueue<string>();
var migrate = DynamicJson.Parse(json);
if (migrate != null)
{
//处理全局更新
if (migrate.IsDefined("global"))
{
var global = migrate["global"];
if (global.IsDefined("all"))
{
var arrayJson = global["all"];
foreach (var item in arrayJson)
{
list.Enqueue(item);
}
}
}
}
result = new Tuple<bool, ConcurrentQueue<string>>(true, list);
}
catch (Exception ex)
{
NLog.LogManager.GetCurrentClassLogger().Error(ex, "数据库升级文件格式错误");
result = new Tuple<bool, ConcurrentQueue<string>>(false, null);
}
return result;
}
public static void ChangePwd(string pwd)
{
SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder();
builder.DataSource = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Data\kdsDatav1.s3db");
builder.Password = "passwd";
builder.Version = 3;
builder.CacheSize = 10000;
builder.DefaultTimeout = 5000;
builder.PageSize = 4096;
builder.FailIfMissing = true;
builder.Pooling = true;
builder.DefaultIsolationLevel = System.Data.IsolationLevel.ReadCommitted;
builder.SyncMode = SynchronizationModes.Off;
builder.JournalMode = SQLiteJournalModeEnum.Truncate;
builder.UseUTF16Encoding = false;
using (SQLiteConnection connection = new SQLiteConnection(builder.ToString()))
{
try
{
connection.Open();
connection.ChangePassword(pwd);
}
catch (SQLiteException ex)
{
throw new Exception(ex.Message);
}
finally
{
connection.Close();
}
}
}
}
}