chore: 首次簽入 Thinkyu ASP.NET 專案
- 加入 Visual Studio / ASP.NET .gitignore - 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="BPSNService.asmx.cs" Class="報到系統.BPSNService" %>
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Web.Services;
|
||||
using System.Web.Script.Services;
|
||||
using WebLib;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
[WebService(Namespace = "http://tempuri.org/")]
|
||||
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
|
||||
[System.ComponentModel.ToolboxItem(false)]
|
||||
[ScriptService]
|
||||
public class BPSNService : System.Web.Services.WebService
|
||||
{
|
||||
/// <summary>
|
||||
/// 下載員工資料
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse Download()
|
||||
{
|
||||
try
|
||||
{
|
||||
DAC_BPSN dac = new DAC_BPSN();
|
||||
BPSNList result = dac.SelectForClient();
|
||||
|
||||
// 因為決定客戶端離線模式不驗證密碼,所以不把密碼雜湊值傳給客戶端了
|
||||
//foreach (var item in result)
|
||||
//{
|
||||
// string decryptedPassword = EncDec.KeyDec(item.BPAWD);
|
||||
// string combined = item.BPENO + decryptedPassword;
|
||||
|
||||
// using (SHA1 sha1 = SHA1.Create())
|
||||
// {
|
||||
// byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(combined));
|
||||
// item.BPAWD = Convert.ToBase64String(hash);
|
||||
// }
|
||||
//}
|
||||
|
||||
if (result == null || result.Count == 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "沒有員工資料",
|
||||
Data = new BPSNList()
|
||||
};
|
||||
}
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "下載成功",
|
||||
Data = result
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "下載員工資料發生錯誤:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
public class BaseSMSProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 驗證手機號碼格式
|
||||
/// </summary>
|
||||
protected static bool IsValidMobileNumber(string mobile)
|
||||
{
|
||||
if (string.IsNullOrEmpty(mobile))
|
||||
return false;
|
||||
|
||||
mobile = mobile.Trim();
|
||||
return System.Text.RegularExpressions.Regex.IsMatch(mobile, @"^09\d{8}$");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化安全設定
|
||||
/// </summary>
|
||||
protected static void InitializeSecuritySettings()
|
||||
{
|
||||
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
|
||||
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 發送 JSON POST 請求
|
||||
/// </summary>
|
||||
//protected static string PostJson(string url, object jsonBody, Dictionary<string, string> headers = null, int timeoutSeconds = 60)
|
||||
//{
|
||||
// var json = JsonConvert.SerializeObject(jsonBody);
|
||||
// var data = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
// HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||
// request.Method = "POST";
|
||||
// request.ContentType = "application/json";
|
||||
// request.ContentLength = data.Length;
|
||||
|
||||
// // 設定 timeout(毫秒)
|
||||
// request.Timeout = timeoutSeconds * 1000;
|
||||
// // 可選:設定讀取/寫入 timeout
|
||||
// request.ReadWriteTimeout = timeoutSeconds * 1000;
|
||||
|
||||
// // 加入 headers
|
||||
// if (headers != null)
|
||||
// {
|
||||
// foreach (var header in headers)
|
||||
// {
|
||||
// request.Headers.Add(header.Key, header.Value);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 寫入 request body
|
||||
// using (var stream = request.GetRequestStream())
|
||||
// {
|
||||
// stream.Write(data, 0, data.Length);
|
||||
// }
|
||||
|
||||
// // 取得 response
|
||||
// using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
|
||||
// using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
|
||||
// {
|
||||
// return reader.ReadToEnd();
|
||||
// }
|
||||
//}
|
||||
|
||||
protected static string PostJson(string url, object jsonBody, Dictionary<string, string> headers = null, int timeoutSeconds = 60)
|
||||
{
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
client.Encoding = Encoding.UTF8;
|
||||
client.Headers.Add("Content-Type", "application/json");
|
||||
if (headers != null)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
{
|
||||
client.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
}
|
||||
return client.UploadString(url, "POST", JsonConvert.SerializeObject(jsonBody));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 發送表單 POST 請求
|
||||
/// </summary>
|
||||
protected static string PostForm(string url, NameValueCollection formData)
|
||||
{
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
client.Encoding = Encoding.UTF8;
|
||||
return Encoding.UTF8.GetString(client.UploadValues(url, "POST", formData));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="ClassesService.asmx.cs" Class="報到系統.ClassesService" %>
|
||||
@@ -0,0 +1,676 @@
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.Util;
|
||||
using NPOI.XSSF.UserModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Script.Services;
|
||||
using System.Web.Services;
|
||||
using WebLib;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
[WebService(Namespace = "http://tempuri.org/")]
|
||||
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
|
||||
[System.ComponentModel.ToolboxItem(false)]
|
||||
[ScriptService]
|
||||
public class ClassesService : System.Web.Services.WebService
|
||||
{
|
||||
/// <summary>
|
||||
/// 搜尋活動
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse SearchClasses(int pageIndex, int pageSize, string courseCode, string courseName, DateTime? courseDate1, DateTime? courseDate2, string courseLocation, bool? closed, string projectID)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 計算起始索引 (pageIndex 從 1 開始)
|
||||
int startRowIndex = (pageIndex - 1) * pageSize;
|
||||
|
||||
// 判斷是否所有條件都為空
|
||||
bool noInput = string.IsNullOrEmpty(courseCode) &&
|
||||
string.IsNullOrEmpty(courseName) &&
|
||||
!courseDate1.HasValue &&
|
||||
!courseDate2.HasValue &&
|
||||
string.IsNullOrEmpty(courseLocation);
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
DAC_Classes daoClasses = new DAC_Classes(conn);
|
||||
//DAC_SignUp daoSignUp = new DAC_SignUp(conn);
|
||||
DAC_BPAA daoBpaa = new DAC_BPAA(conn);
|
||||
|
||||
// 查詢資料
|
||||
ClassesList result = daoClasses.SelectPage(
|
||||
noInputReturnAll: true,
|
||||
startRowIndex: startRowIndex,
|
||||
maximumRows: pageSize,
|
||||
CourseCode: courseCode,
|
||||
CourseName: courseName,
|
||||
CourseDate_1: courseDate1,
|
||||
CourseDate_2: courseDate2,
|
||||
CourseLocation: courseLocation,
|
||||
Closed: closed,
|
||||
ProjectID: projectID,
|
||||
orderBy: "CourseDate DESC, CourseCode ASC"
|
||||
);
|
||||
|
||||
// 取得總筆數
|
||||
int totalCount = daoClasses.SelectCount(
|
||||
noInputReturnAll: true,
|
||||
CourseCode: courseCode,
|
||||
CourseName: courseName,
|
||||
CourseDate_1: courseDate1,
|
||||
CourseDate_2: courseDate2,
|
||||
CourseLocation: courseLocation,
|
||||
Closed: closed,
|
||||
ProjectID: projectID,
|
||||
orderBy: ""
|
||||
);
|
||||
|
||||
foreach (var item in result)
|
||||
{
|
||||
// 填入關聯欄位與統計欄位
|
||||
item.ProjectName = daoBpaa.SelectProjectName(item.ProjectID);
|
||||
// TODO: 這些等用戶提出再加上
|
||||
//var thisCourdeSignUps = daoSignUp.Select(false, item.ID, null, null, null, null);
|
||||
//item.Actual = thisCourdeSignUps.Count(x => x.IsPresent);
|
||||
//item.Expected = thisCourdeSignUps.Count(x => !x.IsOnSite);
|
||||
//item.Absent = thisCourdeSignUps.Count(x => !x.IsOnSite && x.IsPresent);
|
||||
//item.Exception = thisCourdeSignUps.Count(x => x.IsException);
|
||||
//item.OnSite = thisCourdeSignUps.Count(x => x.IsOnSite);
|
||||
}
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "查詢成功",
|
||||
Data = result?.ToDict() ?? new ClassesList().ToDict(),
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "查詢活動發生錯誤:" + ex.Message,
|
||||
Data = new ClassesList(),
|
||||
TotalCount = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 儲存活動 (新增或更正)
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse SaveClasses(bool isNew, ClassesItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "參數不完整"
|
||||
};
|
||||
}
|
||||
|
||||
// 驗證必填欄位
|
||||
if (string.IsNullOrEmpty(item.CourseName))
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "課程名稱為必填欄位"
|
||||
};
|
||||
}
|
||||
|
||||
if (item.CourseName.Length > 100)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "課程名稱最長 100 個字元"
|
||||
};
|
||||
}
|
||||
|
||||
if (item.CourseDate == DateTime.MinValue)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "課程日期為必填欄位"
|
||||
};
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(item.NotificationTitle) && item.NotificationTitle.Length > 100)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "通知標題最長 100 個字元"
|
||||
};
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(item.NotificationTemplate) && item.NotificationTemplate.Length > 500)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "通知模板最長 500 個字元"
|
||||
};
|
||||
}
|
||||
|
||||
DAC_Classes dac = new DAC_Classes();
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
// 新增
|
||||
ClassesItem newItem = dac.InsertOne(item);
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "新增成功",
|
||||
Data = newItem
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 更正
|
||||
ClassesItem updatedItem = dac.UpdateOne(item);
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "更新成功",
|
||||
Data = updatedItem
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "儲存活動發生錯誤:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刪除活動
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse DeleteClasses(int id, int dbAppNo)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (id <= 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "參數不完整"
|
||||
};
|
||||
}
|
||||
|
||||
DAC_Classes dac = new DAC_Classes();
|
||||
int rowsAffected = dac.DeleteOne(id, dbAppNo);
|
||||
|
||||
if (rowsAffected > 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "刪除成功"
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "找不到要刪除的資料或資料已被修改"
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "刪除活動發生錯誤:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜尋專案 - 用於自動完成
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse SearchProjects(string keyword)
|
||||
{
|
||||
try
|
||||
{
|
||||
DAC_BPAA dac = new DAC_BPAA();
|
||||
var result = dac.Select(keyword);
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "搜尋成功",
|
||||
Data = result ?? new NameValueList()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "搜尋專案發生錯誤:" + ex.Message,
|
||||
Data = new NameValueList()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 匯出學員簽到表 - 使用 NPOI 生成 Excel 檔案並回傳 Base64
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse ExportClassesReport(int classesID, string timeFormat = "datetime")
|
||||
{
|
||||
try
|
||||
{
|
||||
if (classesID <= 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "課程編號為必填欄位"
|
||||
};
|
||||
}
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
DAC_Classes dacClasses = new DAC_Classes(conn);
|
||||
DAC_SignUp dacSignUp = new DAC_SignUp(conn);
|
||||
DAC_BPAA dacBpaa = new DAC_BPAA(conn);
|
||||
|
||||
// 查詢課程資訊
|
||||
ClassesList classes = dacClasses.SelectOne(classesID);
|
||||
if (classes.Count == 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "找不到課程資訊"
|
||||
};
|
||||
}
|
||||
|
||||
ClassesItem classItem = classes[0];
|
||||
classItem.ProjectName = dacBpaa.SelectProjectName(classItem.ProjectID);
|
||||
|
||||
// 查詢該課程的參加人員
|
||||
SignUpList signups = dacSignUp.Select(
|
||||
noInputReturnAll: true,
|
||||
CourseID: classesID,
|
||||
SeqNo: null,
|
||||
Name: null,
|
||||
Mobile: null,
|
||||
QRCode: null,
|
||||
orderBy: "ID ASC"
|
||||
);
|
||||
|
||||
var 實到人數 = signups.Count(x => x.IsPresent);
|
||||
var 應到人數 = signups.Count(x => !x.IsOnSite );
|
||||
var 缺席人數 = signups.Count(x => !x.IsOnSite && !x.IsPresent);
|
||||
var 異常人數 = signups.Count(x => x.IsException);
|
||||
var 現場報名 = signups.Count(x => x.IsOnSite);
|
||||
|
||||
// 使用 NPOI 建立 Excel 檔案
|
||||
Excel excel = new Excel();
|
||||
IWorkbook workbook = new XSSFWorkbook();
|
||||
ISheet sheet = workbook.CreateSheet("學員簽到表");
|
||||
excel.WorkBook = workbook;
|
||||
excel.Sheet = sheet;
|
||||
|
||||
// 建立字體和樣式
|
||||
IFont titleFont = excel.NewFont(null, 12, true);
|
||||
IFont headerFont = excel.NewFont(null, 10, true);
|
||||
IFont bodyFont = excel.NewFont(null, 10, false);
|
||||
|
||||
ICellStyle titleStyle = excel.NewCellStyle(titleFont, HorizontalAlignment.Center, VerticalAlignment.Center, true);
|
||||
ICellStyle headerStyleCenter = excel.NewCellStyle(headerFont, HorizontalAlignment.Center, VerticalAlignment.Center, true);
|
||||
ICellStyle headerStyleLeft = excel.NewCellStyle(headerFont, HorizontalAlignment.Left, VerticalAlignment.Center, true);
|
||||
ICellStyle dataStyle = excel.NewCellStyle(bodyFont, HorizontalAlignment.Left, VerticalAlignment.Center, true);
|
||||
ICellStyle dataStyleCenter = excel.NewCellStyle(bodyFont, HorizontalAlignment.Center, VerticalAlignment.Center, true);
|
||||
ICellStyle dataStyleRight = excel.NewCellStyle(bodyFont, HorizontalAlignment.Right, VerticalAlignment.Center, true);
|
||||
|
||||
int rowIndex = 0;
|
||||
|
||||
// 表頭
|
||||
excel.MergeCell(rowIndex, 0, rowIndex, 10);
|
||||
excel.SetCellValue(rowIndex, 0, classItem.ProjectName, titleStyle);
|
||||
excel.SetRowHeightInPoints(rowIndex, 15);
|
||||
rowIndex++;
|
||||
|
||||
excel.MergeCell(rowIndex, 0, rowIndex, 10);
|
||||
excel.SetCellValue(rowIndex, 0, "學員簽到表", headerStyleCenter);
|
||||
excel.SetRowHeightInPoints(rowIndex, 15);
|
||||
rowIndex++;
|
||||
|
||||
// 課程時間
|
||||
excel.MergeCell(rowIndex, 0, rowIndex, 1);
|
||||
excel.SetCellValue(rowIndex, 0, "課程時間", headerStyleCenter);
|
||||
excel.MergeCell(rowIndex, 2, rowIndex, 10);
|
||||
excel.SetCellValue(rowIndex, 2, string.Format(PublicVariable.TaiwanCulture, "{0:yyyy年MM月dd日} {1}-{2}", classItem.CourseDate, classItem.StartTime, classItem.EndTime), headerStyleLeft);
|
||||
rowIndex++;
|
||||
|
||||
// 授課講師
|
||||
excel.MergeCell(rowIndex, 0, rowIndex, 1);
|
||||
excel.SetCellValue(rowIndex, 0, "授課講師", headerStyleCenter);
|
||||
excel.MergeCell(rowIndex, 2, rowIndex, 10);
|
||||
excel.SetCellValue(rowIndex, 2, classItem.Teacher, headerStyleLeft);
|
||||
rowIndex++;
|
||||
|
||||
// 課程地點
|
||||
excel.MergeCell(rowIndex, 0, rowIndex, 1);
|
||||
excel.SetCellValue(rowIndex, 0, "課程地點", headerStyleCenter);
|
||||
excel.MergeCell(rowIndex, 2, rowIndex, 10);
|
||||
excel.SetCellValue(rowIndex, 2, classItem.CourseLocation, headerStyleLeft);
|
||||
rowIndex++;
|
||||
|
||||
// 課程名稱
|
||||
excel.MergeCell(rowIndex, 0, rowIndex, 1);
|
||||
excel.SetCellValue(rowIndex, 0, "課程名稱", headerStyleCenter);
|
||||
excel.MergeCell(rowIndex, 2, rowIndex, 10);
|
||||
excel.SetCellValue(rowIndex, 2, classItem.CourseName, headerStyleLeft);
|
||||
rowIndex++;
|
||||
|
||||
// 到課人數
|
||||
excel.MergeCell(rowIndex, 0, rowIndex, 1);
|
||||
excel.SetCellValue(rowIndex, 0, "到課人數", headerStyleCenter);
|
||||
excel.MergeCell(rowIndex, 2, rowIndex, 10);
|
||||
excel.SetCellValue(rowIndex, 2,
|
||||
string.Format("實到人數:{0} 人 = 應到人數:{1} 人 - 缺席人數:{2} 人 + 現場報名:{3} 人 / (異常人數:{4} 人)",
|
||||
實到人數, 應到人數, 缺席人數, 現場報名, 異常人數),
|
||||
headerStyleLeft);
|
||||
rowIndex++;
|
||||
|
||||
// 參加人員表頭
|
||||
excel.SetCellValue(rowIndex, 0, "序號", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 1, "學員姓名", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 2, "手機號碼", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 3, "身分證字號", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 4, "性別", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 5, "認定次數", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 6, "用餐別", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 7, "報名方式", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 8, "簽到時間", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 9, "簽退時間", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 10, "出席狀況", headerStyleCenter);
|
||||
rowIndex++;
|
||||
|
||||
// 參加人員資料
|
||||
foreach (var signup in signups)
|
||||
{
|
||||
excel.SetCellValue(rowIndex, 0, signup.SeqNo, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 1, signup.Name, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 2, signup.Mobile, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 3, string.IsNullOrEmpty(signup.IDNumber) ? "-未填寫-" : signup.IDNumber, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 4, string.IsNullOrEmpty(signup.Gender) ? "-未填寫-" : signup.Gender, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 5, signup.LossJobTimes.HasValue ? signup.LossJobTimes.ToString() : "-未填寫-", dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 6, string.IsNullOrEmpty(signup.MealType) ? "-未填寫-" : signup.MealType, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 7, string.IsNullOrEmpty(signup.SignUpType) ? "預約" : signup.SignUpType, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 8, Utils.FormatTimeValue(signup.CheckInTime, timeFormat), dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 9, Utils.FormatTimeValue(signup.CheckOutTime, timeFormat), dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 10, signup.IsException ? "異常" : (signup.IsPresent ? "" : "缺席"), dataStyleCenter);
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
// 設定欄寬
|
||||
excel.SetColumnWidthInChars(0, 6);
|
||||
excel.SetColumnWidthInChars(1, 15);
|
||||
excel.SetColumnWidthInChars(2, 15);
|
||||
excel.SetColumnWidthInChars(3, 15);
|
||||
excel.SetColumnWidthInChars(4, 8);
|
||||
excel.SetColumnWidthInChars(5, 8);
|
||||
excel.SetColumnWidthInChars(6, 8);
|
||||
excel.SetColumnWidthInChars(7, 10);
|
||||
excel.SetColumnWidthInChars(8, timeFormat == "datetime" ? 20 : 10);
|
||||
excel.SetColumnWidthInChars(9, timeFormat == "datetime" ? 20 : 10);
|
||||
excel.SetColumnWidthInChars(10, 8);
|
||||
|
||||
// 儲存到記憶體流並轉換為 Base64
|
||||
MemoryStream ms = new MemoryStream();
|
||||
workbook.Write(ms);
|
||||
|
||||
byte[] fileBytes = ms.ToArray();
|
||||
string fileBase64 = Convert.ToBase64String(fileBytes);
|
||||
string fileName = $"{classItem.CourseName}_{DateTime.Now:yyyyMMdd}.xlsx";
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "匯出成功",
|
||||
Data = new
|
||||
{
|
||||
FileName = fileName,
|
||||
FileBase64 = fileBase64,
|
||||
FileSize = fileBytes.Length,
|
||||
DataCount = signups.Count
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "匯出課程報表發生錯誤:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 匯出課程清單 - 使用 NPOI 生成 Excel 檔案並回傳 Base64
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse ExportClassesList(DateTime? courseDate1, DateTime? courseDate2, string projectID)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 判斷是否所有條件都為空
|
||||
bool noInput = !courseDate1.HasValue &&
|
||||
!courseDate2.HasValue &&
|
||||
string.IsNullOrEmpty(projectID);
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
DAC_Classes dac = new DAC_Classes(conn);
|
||||
DAC_BPAA dacBpaa = new DAC_BPAA(conn);
|
||||
DAC_SignUp dacSignUp = new DAC_SignUp(conn);
|
||||
|
||||
var projectName = !string.IsNullOrEmpty(projectID) ? dacBpaa.SelectProjectName(projectID) : "全部";
|
||||
|
||||
// 查詢課程資料
|
||||
ClassesList result = dac.SelectPage(
|
||||
noInputReturnAll: true,
|
||||
startRowIndex: 0,
|
||||
maximumRows: Int32.MaxValue,
|
||||
CourseCode: null,
|
||||
CourseName: null,
|
||||
CourseDate_1: courseDate1,
|
||||
CourseDate_2: courseDate2,
|
||||
CourseLocation: null,
|
||||
Closed: null,
|
||||
ProjectID: projectID,
|
||||
orderBy: "ProjectID ASC, CourseDate DESC"
|
||||
);
|
||||
|
||||
if (result.Count == 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "沒有符合條件的資料",
|
||||
Data = null
|
||||
};
|
||||
}
|
||||
|
||||
var reportData = new List<ClassesReportItem>();
|
||||
foreach (var item in result)
|
||||
{
|
||||
// 查詢課程的參加人員
|
||||
SignUpList signups = dacSignUp.Select(
|
||||
noInputReturnAll: true,
|
||||
CourseID: item.ID,
|
||||
SeqNo: null,
|
||||
Name: null,
|
||||
Mobile: null,
|
||||
QRCode: null
|
||||
);
|
||||
|
||||
reportData.Add(new ClassesReportItem
|
||||
{
|
||||
CourseName = item.CourseName,
|
||||
CourseLocation = item.CourseLocation,
|
||||
Teacher = item.Teacher,
|
||||
實到人數 = signups.Count(x => x.IsPresent),
|
||||
應到人數 = signups.Count(x => x.SignUpType != "現場"),
|
||||
缺席人數 = signups.Count(x => x.SignUpType != "現場" && !x.IsPresent),
|
||||
現場報名 = signups.Count(x => x.SignUpType == "現場")
|
||||
});
|
||||
}
|
||||
|
||||
// 使用 NPOI 建立 Excel 檔案
|
||||
Excel excel = new Excel();
|
||||
IWorkbook workbook = new XSSFWorkbook();
|
||||
ISheet sheet = workbook.CreateSheet("課程匯出");
|
||||
excel.WorkBook = workbook;
|
||||
excel.Sheet = sheet;
|
||||
|
||||
// 建立字體和樣式
|
||||
IFont titleFont = excel.NewFont(null, 12, true);
|
||||
IFont headerFont = excel.NewFont(null, 10, true);
|
||||
IFont bodyFont = excel.NewFont(null, 10, false);
|
||||
|
||||
ICellStyle titleStyle = excel.NewCellStyle(titleFont, HorizontalAlignment.Center, VerticalAlignment.Center, true);
|
||||
ICellStyle headerStyleCenter = excel.NewCellStyle(headerFont, HorizontalAlignment.Center, VerticalAlignment.Center, true);
|
||||
ICellStyle headerStyleLeft = excel.NewCellStyle(headerFont, HorizontalAlignment.Left, VerticalAlignment.Center, true);
|
||||
ICellStyle dataStyle = excel.NewCellStyle(bodyFont, HorizontalAlignment.Left, VerticalAlignment.Center, true);
|
||||
ICellStyle dataStyleCenter = excel.NewCellStyle(bodyFont, HorizontalAlignment.Center, VerticalAlignment.Center, true);
|
||||
ICellStyle dataStyleRight = excel.NewCellStyle(bodyFont, HorizontalAlignment.Right, VerticalAlignment.Center, true);
|
||||
|
||||
int rowIndex = 0;
|
||||
|
||||
// 表頭
|
||||
excel.MergeCell(rowIndex, 0, rowIndex, 6);
|
||||
excel.SetCellValue(rowIndex, 0, "課程匯出", headerStyleCenter);
|
||||
excel.SetRowHeightInPoints(rowIndex, 15);
|
||||
rowIndex++;
|
||||
|
||||
// 計畫
|
||||
excel.SetCellValue(rowIndex, 0, "計畫", headerStyleCenter);
|
||||
excel.MergeCell(rowIndex, 1, rowIndex, 6);
|
||||
excel.SetCellValue(rowIndex, 1, projectName, headerStyleLeft);
|
||||
rowIndex++;
|
||||
|
||||
// 課程日期
|
||||
excel.SetCellValue(rowIndex, 0, "課程日期", headerStyleCenter);
|
||||
excel.MergeCell(rowIndex, 1, rowIndex, 6);
|
||||
excel.SetCellValue(rowIndex, 1, string.Format("{0:yyyy-MM-dd} - {1:yyyy-MM-dd}", courseDate1, courseDate2), headerStyleLeft);
|
||||
rowIndex++;
|
||||
|
||||
// 參加人員表頭
|
||||
excel.SetCellValue(rowIndex, 0, "課程名稱", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 1, "課程地點", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 2, "授課講師", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 3, "實到人數", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 4, "應到人數", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 5, "缺席人數", headerStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 6, "現場報名", headerStyleCenter);
|
||||
rowIndex++;
|
||||
|
||||
// 參加人員資料
|
||||
foreach (var reportItem in reportData)
|
||||
{
|
||||
excel.SetCellValue(rowIndex, 0, reportItem.CourseName, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 1, reportItem.CourseLocation, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 2, reportItem.Teacher, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 3, reportItem.實到人數, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 4, reportItem.應到人數, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 5, reportItem.缺席人數, dataStyleCenter);
|
||||
excel.SetCellValue(rowIndex, 6, reportItem.現場報名, dataStyleCenter);
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
// 設定欄寬
|
||||
excel.SetColumnWidthInChars(0, 40);
|
||||
excel.SetColumnWidthInChars(1, 40);
|
||||
excel.SetColumnWidthInChars(2, 20);
|
||||
excel.SetColumnWidthInChars(3, 12);
|
||||
excel.SetColumnWidthInChars(4, 12);
|
||||
excel.SetColumnWidthInChars(5, 12);
|
||||
excel.SetColumnWidthInChars(6, 12);
|
||||
|
||||
// 儲存到記憶體流並轉換為 Base64
|
||||
MemoryStream ms = new MemoryStream();
|
||||
workbook.Write(ms);
|
||||
|
||||
byte[] fileBytes = ms.ToArray();
|
||||
string fileBase64 = Convert.ToBase64String(fileBytes);
|
||||
string fileName = $"課程匯出_{DateTime.Now:yyyyMMdd}.xlsx";
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "匯出成功",
|
||||
Data = new
|
||||
{
|
||||
FileName = fileName,
|
||||
FileBase64 = fileBase64,
|
||||
FileSize = fileBytes.Length
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "匯出課程報表發生錯誤:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 課程報表項目
|
||||
/// </summary>
|
||||
private class ClassesReportItem
|
||||
{
|
||||
public string CourseName { get; set; }
|
||||
public string CourseLocation { get; set; }
|
||||
public string Teacher { get; set; }
|
||||
public int 實到人數 { get; set; }
|
||||
public int 應到人數 { get; set; }
|
||||
public int 缺席人數 { get; set; }
|
||||
public int 現場報名 { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
/// <summary>
|
||||
/// EVERY8D 簡訊服務提供者
|
||||
/// </summary>
|
||||
public class EVERY8DSMSProvider : BaseSMSProvider
|
||||
{
|
||||
// EVERY8D API 相關常數
|
||||
private static string BASE_URL = "https://new.e8d.tw/API21/HTTP";
|
||||
private static string API_URL_SEND = string.Format("{0}/SendParam.ashx", BASE_URL);
|
||||
private static string API_URL_CHECK_BALANCE = string.Format("{0}/GetCredit.ashx", BASE_URL);
|
||||
|
||||
/// <summary>
|
||||
/// 發送簡訊到指定手機號碼
|
||||
/// </summary>
|
||||
public static bool SendMMS(MessageQueueItem message, out string failMessage)
|
||||
{
|
||||
failMessage = "";
|
||||
|
||||
try
|
||||
{
|
||||
InitializeSecuritySettings();
|
||||
|
||||
if (string.IsNullOrEmpty(message.Mobile))
|
||||
{
|
||||
failMessage = "手機號碼不能為空";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(message.Content))
|
||||
{
|
||||
failMessage = "簡訊內容不能為空";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidMobileNumber(message.Mobile))
|
||||
{
|
||||
failMessage = "手機號碼格式不正確";
|
||||
return false;
|
||||
}
|
||||
|
||||
var requestBody = new EVERY8DParamMMSBody
|
||||
{
|
||||
SB = message.Subject,
|
||||
UID = message.MMSAccount,
|
||||
PWD = message.MMSPassword,
|
||||
ATTACHMENT = message.QRCode
|
||||
};
|
||||
|
||||
requestBody.RecipientDataList.Add(new EVERY8DRecipient
|
||||
{
|
||||
Mobile = message.Mobile,
|
||||
Param = message.Content,
|
||||
});
|
||||
|
||||
string response = PostJson(API_URL_SEND, requestBody);
|
||||
|
||||
return ParseMMSResponse(response, out failMessage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failMessage = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 EVERY8D API 回應
|
||||
/// </summary>
|
||||
private static bool ParseMMSResponse(string response, out string failMessage)
|
||||
{
|
||||
failMessage = "";
|
||||
|
||||
try
|
||||
{
|
||||
EVERY8DMMSResponse responseObj = JsonConvert.DeserializeObject<EVERY8DMMSResponse>(response.Trim());
|
||||
|
||||
if (responseObj.Result)
|
||||
return true;
|
||||
|
||||
failMessage = GetErrorMessage(responseObj.Status);
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
failMessage = string.Format("無法解析 API 回應:{0}", response);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得錯誤訊息
|
||||
/// </summary>
|
||||
private static string GetErrorMessage(string status)
|
||||
{
|
||||
Dictionary<string, string> errorMessages = new Dictionary<string, string>
|
||||
{
|
||||
{ "-1", "參數錯誤,傳送失敗" },
|
||||
{ "-2", "帳號或密碼錯誤,傳送失敗" },
|
||||
{ "-3", "接收方屬於黑名單,傳送失敗" },
|
||||
{ "-4", "預計發送時間超過24小時,傳送失敗" },
|
||||
{ "-5", "內容長度超出限制,傳送失敗" },
|
||||
{ "-8", "接收方手機號碼格式不符,傳送失敗" },
|
||||
{ "-10", "接收方不支援MMS,傳送失敗" },
|
||||
{ "-300", "帳號密碼不得為空" },
|
||||
{ "101", "電信端回覆因收訊方手機關機、訊號不良、簡訊容量不足等異常原因,收訊失敗" },
|
||||
{ "102", "電信端回覆因網路系統、設備出現異常,收訊失敗" },
|
||||
{ "103", "電信端回覆因收訊方手機門號錯誤、空號或停用中,收訊失敗" },
|
||||
{ "104", "因本手機門號為電信端之黑名單,故傳送失敗" },
|
||||
{ "105", "電信端回覆經判斷因訊息內文含有敏感關鍵字進行阻擋,故傳送失敗" },
|
||||
{ "106", "系統顯示經判斷因訊息內文含有敏感關鍵字進行阻擋,故傳送失敗" },
|
||||
{ "301", "無額度(或額度不足)無法發送" },
|
||||
{ "500", "表該門號為國際門號,請至帳號設定開啟國際簡訊發送功能" }
|
||||
};
|
||||
|
||||
return errorMessages.ContainsKey(status) ? errorMessages[status] : string.Format("未知錯誤代碼:{0}", status);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 驗證帳號額度
|
||||
/// </summary>
|
||||
public static bool CheckBalance(out int balance, out string errorMessage)
|
||||
{
|
||||
balance = 0;
|
||||
errorMessage = "";
|
||||
|
||||
try
|
||||
{
|
||||
InitializeSecuritySettings();
|
||||
|
||||
var requestBody = new EVERY8DRequestBody();
|
||||
string response = PostForm(API_URL_CHECK_BALANCE, requestBody.ToFormData());
|
||||
|
||||
if (decimal.TryParse(response, out decimal b))
|
||||
{
|
||||
balance = (int)b;
|
||||
return true;
|
||||
}
|
||||
errorMessage = string.Format("餘額查詢失敗: {0}", response);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EVERY8D 簡訊接收者
|
||||
/// </summary>
|
||||
public class EVERY8DRecipient
|
||||
{
|
||||
[JsonProperty("Name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("Mobile")]
|
||||
public string Mobile { get; set; }
|
||||
|
||||
[JsonProperty("Email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[JsonProperty("SendTime")]
|
||||
public string SendTime { get; set; }
|
||||
|
||||
[JsonProperty("Param")]
|
||||
public string Param { get; set; }
|
||||
|
||||
public EVERY8DRecipient()
|
||||
{
|
||||
Name = string.Empty;
|
||||
Mobile = string.Empty;
|
||||
Email = string.Empty;
|
||||
SendTime = string.Empty;
|
||||
Param = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EVERY8D 請求基礎類
|
||||
/// </summary>
|
||||
public class EVERY8DRequestBody
|
||||
{
|
||||
[JsonProperty("UID")]
|
||||
public string UID { get; set; }
|
||||
|
||||
[JsonProperty("PWD")]
|
||||
public string PWD { get; set; }
|
||||
|
||||
public EVERY8DRequestBody()
|
||||
{
|
||||
using (var dac = new DAC_MMSPlatform())
|
||||
{
|
||||
var platform = dac.SelectByCode(DAC_ProjectMMSAccount.EVERY8DPlatformCode);
|
||||
if (platform != null)
|
||||
{
|
||||
UID = platform.Account;
|
||||
PWD = platform.Password;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NameValueCollection ToFormData()
|
||||
{
|
||||
var result = new NameValueCollection();
|
||||
var properties = this.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var jsonPropertyAttr = property.GetCustomAttributes(typeof(JsonPropertyAttribute), false).FirstOrDefault() as JsonPropertyAttribute;
|
||||
if (jsonPropertyAttr != null)
|
||||
{
|
||||
var value = property.GetValue(this, null);
|
||||
result.Add(jsonPropertyAttr.PropertyName, value?.ToString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EVERY8D MMS 請求
|
||||
/// </summary>
|
||||
public class EVERY8DParamMMSBody : EVERY8DRequestBody
|
||||
{
|
||||
[JsonProperty("SB")]
|
||||
public string SB { get; set; }
|
||||
|
||||
[JsonProperty("ATTACHMENT")]
|
||||
public string ATTACHMENT { get; set; }
|
||||
|
||||
[JsonProperty("TYPE")]
|
||||
public string TYPE { get; set; }
|
||||
|
||||
[JsonProperty("RecipientDataList")]
|
||||
public List<EVERY8DRecipient> RecipientDataList { get; }
|
||||
|
||||
public EVERY8DParamMMSBody()
|
||||
{
|
||||
SB = "活動通知";
|
||||
ATTACHMENT = string.Empty;
|
||||
TYPE = "png";
|
||||
RecipientDataList = new List<EVERY8DRecipient>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EVERY8D MMS 回應
|
||||
/// </summary>
|
||||
public class EVERY8DMMSResponse
|
||||
{
|
||||
[JsonProperty("Result")]
|
||||
public bool Result { get; set; }
|
||||
|
||||
[JsonProperty("Status")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[JsonProperty("Msg")]
|
||||
public string Msg { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EVERY8D Credit 回應
|
||||
/// </summary>
|
||||
public class EVERY8DCreditResponse
|
||||
{
|
||||
[JsonProperty("Credit")]
|
||||
public decimal Credit { get; set; }
|
||||
|
||||
[JsonProperty("Status")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[JsonProperty("Msg")]
|
||||
public string Msg { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebHandler Language="C#" CodeBehind="FileUploadHandler.ashx.cs" Class="報到系統.FileUploadHandler" %>
|
||||
@@ -0,0 +1,385 @@
|
||||
using NPOI.SS.UserModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
public class FileUploadHandler : IHttpHandler
|
||||
{
|
||||
public void ProcessRequest(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
string action = context.Request.QueryString["action"];
|
||||
|
||||
if (action == "ProcessImportFile")
|
||||
{
|
||||
ProcessImportFile(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendJsonResponse(context, new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "無效的操作"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SendJsonResponse(context, new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "處理請求發生錯誤:" + ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理匯入檔案 - 讀取 Excel 檔案並進行驗證,使用 NPOI
|
||||
/// </summary>
|
||||
private void ProcessImportFile(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
string courseIDStr = context.Request.QueryString["courseID"];
|
||||
if (!int.TryParse(courseIDStr, out int courseID) || courseID <= 0)
|
||||
{
|
||||
SendJsonResponse(context, new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "活動編號為必填欄位"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 檢查是否有上傳檔案
|
||||
if (context.Request.Files.Count == 0)
|
||||
{
|
||||
SendJsonResponse(context, new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "沒有上傳檔案"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
HttpPostedFile file = context.Request.Files[0];
|
||||
if (file.ContentLength == 0)
|
||||
{
|
||||
SendJsonResponse(context, new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "檔案為空"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 驗證檔案格式
|
||||
string fileName = Path.GetFileName(file.FileName);
|
||||
if (!fileName.ToLower().EndsWith(".xlsx") && !fileName.ToLower().EndsWith(".xls"))
|
||||
{
|
||||
SendJsonResponse(context, new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "只支援 .xlsx 或 .xls 格式的 Excel 檔案"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用 NPOI 讀取 Excel 檔案
|
||||
IWorkbook workbook = WorkbookFactory.Create(file.InputStream);
|
||||
ISheet sheet = workbook.GetSheetAt(0);
|
||||
|
||||
List<Dictionary<string, object>> previewData = new List<Dictionary<string, object>>();
|
||||
List<string> validationErrors = new List<string>();
|
||||
int headerRowIndex = -1;
|
||||
|
||||
// 偵測表頭並讀取資料
|
||||
for (int rowIndex = 0; rowIndex <= sheet.LastRowNum; rowIndex++)
|
||||
{
|
||||
IRow row = sheet.GetRow(rowIndex);
|
||||
if (row == null) continue;
|
||||
|
||||
// 欄位順序: 序號(0), 學員姓名(1), 手機號(2), 身分別(5), 身分證字號(6), 餐點(7), 電子郵件(8), 失業給付認定次數(9), 性別(10)
|
||||
string indexValue = GetCellValue(row.GetCell(0))?.Trim();
|
||||
string nameValue = GetCellValue(row.GetCell(1))?.Trim();
|
||||
string mobileValue = GetCellValue(row.GetCell(2))?.Trim();
|
||||
string idTypeValue = GetCellValue(row.GetCell(5))?.Trim();
|
||||
string idNumberValue = GetCellValue(row.GetCell(6))?.Trim();
|
||||
string mealTypeValue = GetCellValue(row.GetCell(7))?.Trim();
|
||||
string emailValue = GetCellValue(row.GetCell(8))?.Trim();
|
||||
string lossJobTimesValue = GetCellValue(row.GetCell(9))?.Trim();
|
||||
string genderValue = GetCellValue(row.GetCell(10))?.Trim();
|
||||
|
||||
// 偵測表頭 - 檢查第1欄和第2欄
|
||||
if (headerRowIndex < 0 && IsHeaderRow(indexValue, nameValue))
|
||||
{
|
||||
headerRowIndex = rowIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 跳過表頭之前的行
|
||||
if (headerRowIndex < 0) continue;
|
||||
|
||||
// 跳過空行
|
||||
if (string.IsNullOrEmpty(nameValue) && string.IsNullOrEmpty(mobileValue))
|
||||
continue;
|
||||
|
||||
// 驗證資料
|
||||
List<string> rowErrors = new List<string>();
|
||||
int dataRowIndex = rowIndex - headerRowIndex;
|
||||
|
||||
if (string.IsNullOrEmpty(nameValue?.Trim()))
|
||||
{
|
||||
rowErrors.Add($"第 {dataRowIndex} 列:姓名為必填欄位");
|
||||
validationErrors.Add(rowErrors[rowErrors.Count - 1]);
|
||||
}
|
||||
else if (nameValue.Length > 100)
|
||||
{
|
||||
rowErrors.Add($"第 {dataRowIndex} 列:姓名最長 100 個字元");
|
||||
validationErrors.Add(rowErrors[rowErrors.Count - 1]);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(mobileValue?.Trim()))
|
||||
{
|
||||
rowErrors.Add($"第 {dataRowIndex} 列:手機號為必填欄位");
|
||||
validationErrors.Add(rowErrors[rowErrors.Count - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 清洗資料,只保留數字
|
||||
mobileValue = Regex.Replace(mobileValue, @"\D", "");
|
||||
|
||||
if (!Regex.IsMatch(mobileValue.Trim(), @"^09\d{8}$"))
|
||||
{
|
||||
rowErrors.Add($"第 {dataRowIndex} 列:手機號格式必須為 09 開頭的 10 位數字");
|
||||
validationErrors.Add(rowErrors[rowErrors.Count - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// 驗證身分證字號(如果有填寫)
|
||||
if (!string.IsNullOrEmpty(idNumberValue?.Trim()))
|
||||
{
|
||||
if (!ValidateIDNumber(idNumberValue.ToUpper().Trim()))
|
||||
{
|
||||
rowErrors.Add($"第 {dataRowIndex} 列:身分證字號無效(格式應為:大寫英文字母 + 9位數字,且檢查碼需正確)");
|
||||
validationErrors.Add(rowErrors[rowErrors.Count - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// 驗證電子郵件(如果有填寫)
|
||||
if (!string.IsNullOrEmpty(emailValue?.Trim()))
|
||||
{
|
||||
if (!ValidateEmail(emailValue.Trim()))
|
||||
{
|
||||
rowErrors.Add($"第 {dataRowIndex} 列:電子郵件格式不正確(例如:user@example.com)");
|
||||
validationErrors.Add(rowErrors[rowErrors.Count - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// 驗證失業給付認定次數(如果有填寫)
|
||||
if (!string.IsNullOrEmpty(lossJobTimesValue?.Trim()))
|
||||
{
|
||||
if (!int.TryParse(lossJobTimesValue, out int i))
|
||||
{
|
||||
rowErrors.Add($"第 {dataRowIndex} 列:失業給付認定次數必須為數字");
|
||||
validationErrors.Add(rowErrors[rowErrors.Count - 1]);
|
||||
}
|
||||
|
||||
if (i < 0)
|
||||
{
|
||||
rowErrors.Add($"第 {dataRowIndex} 列:失業給付認定次數必須大於或等於 0");
|
||||
validationErrors.Add(rowErrors[rowErrors.Count - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
var rowData = new Dictionary<string, object>();
|
||||
rowData["Name"] = nameValue;
|
||||
rowData["Mobile"] = mobileValue;
|
||||
rowData["IDType"] = idTypeValue;
|
||||
rowData["IDNumber"] = idNumberValue?.ToUpper();
|
||||
rowData["MealType"] = mealTypeValue;
|
||||
rowData["Email"] = emailValue;
|
||||
rowData["LossJobTimes"] = lossJobTimesValue;
|
||||
rowData["Gender"] = genderValue;
|
||||
rowData["SignUpType"] = "預約";
|
||||
rowData["Errors"] = rowErrors;
|
||||
rowData["IsValid"] = rowErrors.Count == 0;
|
||||
previewData.Add(rowData);
|
||||
}
|
||||
|
||||
workbook.Close();
|
||||
|
||||
int validRowCount = 0;
|
||||
foreach (var item in previewData)
|
||||
{
|
||||
if ((bool)item["IsValid"])
|
||||
{
|
||||
validRowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
SendJsonResponse(context, new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "檔案讀取成功",
|
||||
Data = new
|
||||
{
|
||||
PreviewData = previewData,
|
||||
ValidationErrors = validationErrors,
|
||||
ValidRowCount = validRowCount,
|
||||
TotalRowCount = previewData.Count
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SendJsonResponse(context, new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "處理匯入檔案發生錯誤:" + ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 驗證中華民國身分證字號
|
||||
/// 規則:第一碼為英文字母(A-Z),後接九位數字
|
||||
/// 使用檢查碼算法驗證
|
||||
/// </summary>
|
||||
private bool ValidateIDNumber(string idNumber)
|
||||
{
|
||||
if (string.IsNullOrEmpty(idNumber))
|
||||
return false;
|
||||
|
||||
idNumber = idNumber.Trim().ToUpper();
|
||||
|
||||
// 檢查長度
|
||||
if (idNumber.Length != 10)
|
||||
return false;
|
||||
|
||||
// 第一碼必須是英文字母
|
||||
if (idNumber[0] < 'A' || idNumber[0] > 'Z')
|
||||
return false;
|
||||
|
||||
// 後九碼必須是數字
|
||||
for (int i = 1; i < 10; i++)
|
||||
{
|
||||
if (idNumber[i] < '0' || idNumber[i] > '9')
|
||||
return false;
|
||||
}
|
||||
|
||||
// 驗證檢查碼
|
||||
// 字母對應的數字
|
||||
int[] letterValues = {
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 34, 18, 19, 20, 21,
|
||||
22, 35, 23, 24, 25, 26, 27, 28, 29, 32, 30, 31, 33
|
||||
};
|
||||
|
||||
int code = letterValues[idNumber[0] - 'A'];
|
||||
|
||||
int sum = code / 10 + (code % 10) * 9;
|
||||
|
||||
int[] weights = { 8, 7, 6, 5, 4, 3, 2, 1 };
|
||||
|
||||
for (int i = 1; i <= 8; i++)
|
||||
{
|
||||
sum += (idNumber[i] - '0') * weights[i - 1];
|
||||
}
|
||||
|
||||
sum += idNumber[9] - '0';
|
||||
|
||||
return sum % 10 == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 驗證電子郵件格式
|
||||
/// </summary>
|
||||
private bool ValidateEmail(string email)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email))
|
||||
return true;
|
||||
|
||||
return System.Text.RegularExpressions.Regex.IsMatch(
|
||||
email.Trim(),
|
||||
@"^[^\s@]+@[^\s@]+\.[^\s@]+$");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得單元格的值(文字)
|
||||
/// </summary>
|
||||
private string GetCellValue(ICell cell)
|
||||
{
|
||||
if (cell == null) return "";
|
||||
|
||||
try
|
||||
{
|
||||
switch (cell.CellType)
|
||||
{
|
||||
case CellType.String:
|
||||
return cell.StringCellValue;
|
||||
case CellType.Numeric:
|
||||
return cell.NumericCellValue.ToString();
|
||||
case CellType.Boolean:
|
||||
return cell.BooleanCellValue.ToString();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判斷是否為表頭行
|
||||
/// </summary>
|
||||
private bool IsHeaderRow(string col1, string col2)
|
||||
{
|
||||
// col1 是序號, col2 是學員姓名
|
||||
string[] col1Keywords = { "序號", "編號", "index", "Index", "#" };
|
||||
string[] col2Keywords = { "姓名", "名字", "name", "Name", "學員" };
|
||||
|
||||
bool isCol1Header = col1Keywords.Any(keyword =>
|
||||
(col1 ?? "").ToLower().Contains(keyword.ToLower()));
|
||||
bool isCol2Header = col2Keywords.Any(keyword =>
|
||||
(col2 ?? "").ToLower().Contains(keyword.ToLower()));
|
||||
|
||||
return isCol1Header || isCol2Header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 發送 JSON 響應
|
||||
/// </summary>
|
||||
private void SendJsonResponse(HttpContext context, object data)
|
||||
{
|
||||
context.Response.ContentType = "application/json; charset=utf-8";
|
||||
try
|
||||
{
|
||||
// 使用 JavaScriptSerializer 序列化為 JSON
|
||||
System.Web.Script.Serialization.JavaScriptSerializer serializer =
|
||||
new System.Web.Script.Serialization.JavaScriptSerializer();
|
||||
// 設定遞迴深度以支援複雜物件
|
||||
serializer.MaxJsonLength = int.MaxValue;
|
||||
string json = serializer.Serialize(data);
|
||||
context.Response.Write(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 備用的簡單 JSON 序列化
|
||||
context.Response.Write("{\"Success\":false,\"Message\":\"序列化錯誤:" + ex.Message.Replace("\"", "\\\"") + "\"}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReusable
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="LoginService.asmx.cs" Class="報到系統.LoginService" %>
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Script.Services;
|
||||
using System.Web.Security;
|
||||
using System.Web.Services;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
[WebService(Namespace = "http://tempuri.org/")]
|
||||
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
|
||||
[System.ComponentModel.ToolboxItem(false)]
|
||||
[ScriptService]
|
||||
public class LoginService : System.Web.Services.WebService
|
||||
{
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse Login(string username, string password, bool rememberMe)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 驗證輸入
|
||||
if (string.IsNullOrEmpty(username))
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "請輸入帳號和密碼"
|
||||
};
|
||||
}
|
||||
|
||||
// 驗證使用者
|
||||
if (ValidateUser(username, password))
|
||||
{
|
||||
// 設定 Session (ValidateUser 已設定使用者相關資訊)
|
||||
Session["LoginTime"] = DateTime.Now;
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "登入成功",
|
||||
Data = new {
|
||||
UserID = username,
|
||||
UserName = Session[PublicVariable.UserName] != null ? Session[PublicVariable.UserName].ToString() : username
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "帳號或密碼錯誤"
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "登入發生錯誤:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse Logout()
|
||||
{
|
||||
try
|
||||
{
|
||||
FormsAuthentication.SignOut();
|
||||
Session.Clear();
|
||||
Session.Abandon();
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "登出成功"
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "登出發生錯誤:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse CheckSession()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Session[PublicVariable.UserName] != null)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "",
|
||||
Data = new
|
||||
{
|
||||
UserID = Session[PublicVariable.UserId].ToString(),
|
||||
UserName = Session[PublicVariable.UserName].ToString(),
|
||||
LoginTime = Session["LoginTime"] != null ? (DateTime)Session["LoginTime"] : DateTime.MinValue
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "尚未登入"
|
||||
};
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "尚未登入"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse Ping()
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
private bool ValidateUser(string username, string password)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoginBPSN loginBPSN = new LoginBPSN();
|
||||
loginBPSN.UserId = username;
|
||||
loginBPSN.Password = password;
|
||||
|
||||
if (loginBPSN.GetResult())
|
||||
{
|
||||
// 建立 Form 驗證票但不重定向(適用於 Web Service)
|
||||
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
|
||||
1,
|
||||
loginBPSN.UserId,
|
||||
DateTime.Now,
|
||||
DateTime.Now.AddHours(24),
|
||||
false,
|
||||
string.Empty,
|
||||
FormsAuthentication.FormsCookiePath
|
||||
);
|
||||
|
||||
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
|
||||
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
|
||||
// ✨ 關鍵修正: 明確設定 Cookie 屬性
|
||||
authCookie.HttpOnly = true;
|
||||
authCookie.Path = FormsAuthentication.FormsCookiePath;
|
||||
authCookie.Expires = DateTime.Now.AddHours(24);
|
||||
HttpContext.Current.Response.Cookies.Add(authCookie);
|
||||
|
||||
// 將使用者資訊儲存到 Session
|
||||
Session[PublicVariable.UserId] = loginBPSN.UserId;
|
||||
Session[PublicVariable.UserName] = loginBPSN.UserName;
|
||||
Session[PublicVariable.IsSystemManager] = true || loginBPSN.IsSystemManager;
|
||||
Session[PublicVariable.IsCustomManager] = true || loginBPSN.IsCustomManager;
|
||||
Session[PublicVariable.IsFieldStaffWorker] = true || loginBPSN.IsFieldStaffWorker;
|
||||
Session[PublicVariable.IsQueryWorker] = true || loginBPSN.IsQueryWorker;
|
||||
|
||||
// ✨ 強制儲存 Session
|
||||
Session["LoginTime"] = DateTime.Now;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Text;
|
||||
using WebLib;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
/// <summary>
|
||||
/// 三竹(MITAKE)簡訊服務提供者
|
||||
/// </summary>
|
||||
public class MITAKESMSProvider : BaseSMSProvider
|
||||
{
|
||||
private const string ApiBaseUrl = "https://message.mitake.com.tw";
|
||||
|
||||
/// <summary>
|
||||
/// 驗證帳號密碼是否正確,回傳 token(如果驗證成功)或錯誤訊息(如果驗證失敗)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static bool Authenticate(out string token)
|
||||
{
|
||||
token = string.Empty;
|
||||
|
||||
var responseStr = PostJson(ApiBaseUrl + "/AuthorizationService/Token",
|
||||
new MITAKEAuthRequest()
|
||||
);
|
||||
var responseObj = JsonConvert.DeserializeObject<MITAKEAuthResponse>(responseStr);
|
||||
if (responseObj != null && !string.IsNullOrEmpty(responseObj.AccessToken))
|
||||
{
|
||||
token = responseObj.AccessToken;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 發送簡訊到指定手機號碼
|
||||
/// </summary>
|
||||
public static bool SendMMS(MessageQueueItem message, out string failMessage)
|
||||
{
|
||||
failMessage = "";
|
||||
|
||||
try
|
||||
{
|
||||
InitializeSecuritySettings();
|
||||
|
||||
if (string.IsNullOrEmpty(message.Mobile))
|
||||
{
|
||||
failMessage = "手機號碼不能為空";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(message.Content))
|
||||
{
|
||||
failMessage = "簡訊內容不能為空";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Authenticate(out string token))
|
||||
{
|
||||
failMessage = "驗證失敗,無法取得存取權杖";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 組裝簡訊發送請求
|
||||
var smsRequest = new MITAKESMSRequest
|
||||
{
|
||||
Subject = message.Subject,
|
||||
Content = message.Content,
|
||||
};
|
||||
smsRequest.Destinations.Add(new MITAKEDestination { To = message.Mobile });
|
||||
smsRequest.Attachments.Add(new MITAKEAttachment
|
||||
{
|
||||
AttachType = "png",
|
||||
AttachName = string.Format("attach_{0}.png", message.ID),
|
||||
AttachContent = message.QRCode
|
||||
});
|
||||
|
||||
// 組裝 HTTP 標頭
|
||||
var smsHeaders = new Dictionary<string, string>
|
||||
{
|
||||
{ "authorization", "bearer " + token },
|
||||
{ "content-type", "application/json" },
|
||||
{ "clientID", message.MMSAccount },
|
||||
{ "actionType", "push" }
|
||||
};
|
||||
|
||||
// 發送簡訊請求
|
||||
var smsResponseStr = PostJson(ApiBaseUrl + "/msg/mms/json", smsRequest, smsHeaders, 120);
|
||||
var smsResponseObj = JsonConvert.DeserializeObject<MITAKESMSResponse>(smsResponseStr);
|
||||
|
||||
// 檢查回應結果
|
||||
if (smsResponseObj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (smsResponseObj.Messages == null || smsResponseObj.Messages.Count == 0)
|
||||
{
|
||||
string responsePreview = smsResponseStr.Length > 400 ? smsResponseStr.Substring(0, 400) : smsResponseStr;
|
||||
failMessage = $"簡訊發送失敗,未收到有效回應:({responsePreview})";
|
||||
return false;
|
||||
}
|
||||
|
||||
var firstMessage = smsResponseObj.Messages[0];
|
||||
if (firstMessage.Status != "0" && firstMessage.Status != "1" && firstMessage.Status != "2")
|
||||
{
|
||||
failMessage = $"簡訊發送失敗,狀態碼:{firstMessage.Status},說明:{firstMessage.StatusDesc}";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failMessage = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查詢帳號餘額
|
||||
/// </summary>
|
||||
//public static bool CheckBalance(out int balance, out string errorMessage)
|
||||
//{
|
||||
// balance = 0;
|
||||
// errorMessage = "";
|
||||
|
||||
// try
|
||||
// {
|
||||
// InitializeSecuritySettings();
|
||||
|
||||
// // TODO: 實作三竹查詢餘額邏輯
|
||||
// // GET {ApiBaseUrl}/SmQueryGet.asp
|
||||
// // ?username={account}
|
||||
// // &password={password}
|
||||
// // &CharsetURL=UTF-8
|
||||
// //
|
||||
// // 回傳格式:AccountPoint=100
|
||||
|
||||
// throw new NotImplementedException("三竹查詢餘額尚未實作");
|
||||
// }
|
||||
// catch (NotImplementedException)
|
||||
// {
|
||||
// errorMessage = "三竹查詢餘額尚未實作";
|
||||
// return false;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// errorMessage = ex.Message;
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三竹簡訊授權請求
|
||||
/// </summary>
|
||||
public class MITAKEAuthRequest
|
||||
{
|
||||
[JsonProperty("clientID")]
|
||||
public string ClientID { get; set; }
|
||||
|
||||
[JsonProperty("clientSecret")]
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
public MITAKEAuthRequest()
|
||||
{
|
||||
ClientID = string.Empty;
|
||||
ClientSecret = string.Empty;
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
var dac = new DAC_MMSPlatform(conn);
|
||||
MMSPlatformItem item = dac.SelectByCode(PublicVariable.CurrentMMSPlatform);
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
ClientID = item.Account;
|
||||
ClientSecret = item.Password;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三竹簡訊授權回應
|
||||
/// </summary>
|
||||
public class MITAKEAuthResponse
|
||||
{
|
||||
[JsonProperty("accessToken")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
[JsonProperty("expiresIn")]
|
||||
public int ExpiresIn { get; set; }
|
||||
|
||||
[JsonProperty("tokenType")]
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三竹簡訊接收者
|
||||
/// </summary>
|
||||
public class MITAKEDestination
|
||||
{
|
||||
[JsonProperty("to")]
|
||||
public string To { get; set; }
|
||||
|
||||
[JsonProperty("cID")]
|
||||
public string CID { get; set; }
|
||||
|
||||
public MITAKEDestination()
|
||||
{
|
||||
CID = Guid.NewGuid().ToString().Replace("-", "");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三竹簡訊附件
|
||||
/// </summary>
|
||||
public class MITAKEAttachment
|
||||
{
|
||||
[JsonProperty("attachContent")]
|
||||
public string AttachContent { get; set; }
|
||||
|
||||
[JsonProperty("attachType")]
|
||||
public string AttachType { get; set; }
|
||||
|
||||
[JsonProperty("attachName")]
|
||||
public string AttachName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三竹簡訊發送請求
|
||||
/// </summary>
|
||||
public class MITAKESMSRequest
|
||||
{
|
||||
[JsonProperty("subject")]
|
||||
public string Subject { get; set; }
|
||||
|
||||
[JsonProperty("content")]
|
||||
public string Content { get; set; }
|
||||
|
||||
[JsonProperty("orderTime")]
|
||||
public string OrderTime { get; set; }
|
||||
|
||||
[JsonProperty("expireTime")]
|
||||
public string ExpireTime { get; set; }
|
||||
|
||||
[JsonProperty("destinations")]
|
||||
public List<MITAKEDestination> Destinations { get; set; }
|
||||
|
||||
[JsonProperty("attachments")]
|
||||
public List<MITAKEAttachment> Attachments { get; set; }
|
||||
|
||||
|
||||
public MITAKESMSRequest()
|
||||
{
|
||||
Subject = string.Empty;
|
||||
Content = string.Empty;
|
||||
OrderTime = PublicVariable.TaiwanNow.ToString("yyyyMMddHHmmss");
|
||||
ExpireTime = PublicVariable.TaiwanNow.AddDays(1).ToString("yyyyMMddHHmmss");
|
||||
Destinations = new List<MITAKEDestination>();
|
||||
Attachments = new List<MITAKEAttachment>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三竹簡訊發送回應項目
|
||||
/// </summary>
|
||||
public class MITAKESMSResponseItem
|
||||
{
|
||||
[JsonProperty("msgID")]
|
||||
public string MsgID { get; set; }
|
||||
|
||||
[JsonProperty("cID")]
|
||||
public string CID { get; set; }
|
||||
|
||||
[JsonProperty("status")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[JsonProperty("statusDesc")]
|
||||
public string StatusDesc { get; set; }
|
||||
|
||||
[JsonProperty("statusTime")]
|
||||
public string StatusTime { get; set; }
|
||||
|
||||
[JsonProperty("point")]
|
||||
public string Point { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三竹簡訊發送回應
|
||||
/// </summary>
|
||||
public class MITAKESMSResponse
|
||||
{
|
||||
[JsonProperty("batchID")]
|
||||
public string BatchID { get; set; }
|
||||
|
||||
[JsonProperty("messages")]
|
||||
public List<MITAKESMSResponseItem> Messages { get; set; }
|
||||
|
||||
[JsonProperty("costPoints")]
|
||||
public string CostPoints { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="MessageAccountManage.asmx.cs" Class="報到系統.MessageAccountManage" %>
|
||||
@@ -0,0 +1,418 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Services;
|
||||
using System.Web.Script.Services;
|
||||
using WebLib;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
[WebService(Namespace = "http://tempuri.org/")]
|
||||
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
|
||||
[System.ComponentModel.ToolboxItem(false)]
|
||||
[ScriptService]
|
||||
public class MessageAccountManage : System.Web.Services.WebService
|
||||
{
|
||||
/// <summary>
|
||||
/// 查詢簡訊帳號密碼
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse Search(int pageNo, int pageSize, string projectID, string platformCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 計算起始索引 (pageNo 從 1 開始)
|
||||
int startRowIndex = (pageNo - 1) * pageSize;
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
DAC_ProjectMMSAccount dac = new DAC_ProjectMMSAccount(conn);
|
||||
DAC_BPAA dacBpaa = new DAC_BPAA(conn);
|
||||
|
||||
// 查詢資料
|
||||
ProjectMMSAccountList result = dac.SelectPage(
|
||||
noInputReturnAll: true,
|
||||
startRowIndex: startRowIndex,
|
||||
maximumRows: pageSize,
|
||||
ProjectID: projectID,
|
||||
orderBy: "ProjectID ASC",
|
||||
PlatformCode: platformCode
|
||||
);
|
||||
|
||||
// 取得總筆數
|
||||
int totalCount = dac.SelectCount(
|
||||
noInputReturnAll: true,
|
||||
ProjectID: projectID,
|
||||
orderBy: "",
|
||||
PlatformCode: platformCode
|
||||
);
|
||||
|
||||
// 補充計畫名稱
|
||||
foreach (var item in result)
|
||||
{
|
||||
item.ProjectName = dacBpaa.SelectProjectName(item.ProjectID);
|
||||
}
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "查詢成功",
|
||||
Data = result ?? new ProjectMMSAccountList(),
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "查詢簡訊帳號發生錯誤:" + ex.Message,
|
||||
Data = new ProjectMMSAccountList(),
|
||||
TotalCount = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 儲存簡訊帳號密碼 (新增或修改)
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse Save(bool isNew, ProjectMMSAccountItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "參數不完整"
|
||||
};
|
||||
}
|
||||
|
||||
// 驗證必填欄位
|
||||
if (string.IsNullOrEmpty(item.ProjectID))
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "計畫代號為必填欄位"
|
||||
};
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(item.MMSAccount))
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "簡訊帳號為必填欄位"
|
||||
};
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(item.MMSPassword))
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "簡訊密碼為必填欄位"
|
||||
};
|
||||
}
|
||||
|
||||
// PlatformCode 長度驗證(可為空,表示使用系統預設)
|
||||
if (!string.IsNullOrEmpty(item.PlatformCode) && item.PlatformCode.Length > 20)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "平台代碼最長 20 個字元"
|
||||
};
|
||||
}
|
||||
|
||||
// 驗證欄位長度
|
||||
if (item.ProjectID.Length > 20)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "計畫代號最長 20 個字元"
|
||||
};
|
||||
}
|
||||
|
||||
if (item.MMSAccount.Length > 50)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "簡訊帳號最長 50 個字元"
|
||||
};
|
||||
}
|
||||
|
||||
if (item.MMSPassword.Length > 50)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "簡訊密碼最長 50 個字元"
|
||||
};
|
||||
}
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
DAC_ProjectMMSAccount dac = new DAC_ProjectMMSAccount(conn);
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
// 檢查計畫代號是否已存在
|
||||
ProjectMMSAccountList existingList = dac.Select(noInputReturnAll: false, ProjectID: item.ProjectID);
|
||||
if (existingList != null && existingList.Count > 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "該計畫已存在簡訊帳號設定,請使用修改功能"
|
||||
};
|
||||
}
|
||||
|
||||
// 新增
|
||||
ProjectMMSAccountItem newItem = dac.InsertOne(item);
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "新增成功",
|
||||
Data = newItem
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 修改:檢查計畫代號是否重複(新的計畫代號)
|
||||
if (item.ID > 0)
|
||||
{
|
||||
ProjectMMSAccountList existingList = dac.Select(noInputReturnAll: false, ProjectID: item.ProjectID);
|
||||
if (existingList != null && existingList.Count > 0 && existingList[0].ID != item.ID)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "該計畫已被其他記錄使用,無法修改"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 更新
|
||||
ProjectMMSAccountItem updatedItem = dac.UpdateOne(item);
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "修改成功",
|
||||
Data = updatedItem
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "儲存簡訊帳號發生錯誤:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刪除簡訊帳號密碼
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse Delete(int id, int dbAppNo)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (id <= 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "參數不完整"
|
||||
};
|
||||
}
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
DAC_ProjectMMSAccount dac = new DAC_ProjectMMSAccount(conn);
|
||||
int rowsAffected = dac.DeleteOne(id, dbAppNo);
|
||||
|
||||
if (rowsAffected > 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "刪除成功"
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "找不到要刪除的資料或資料已被修改"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "刪除簡訊帳號發生錯誤:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜尋計畫 - 用於自動完成
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse SearchProjects(string keyword)
|
||||
{
|
||||
try
|
||||
{
|
||||
DAC_BPAA dac = new DAC_BPAA();
|
||||
var result = dac.Select(keyword);
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "搜尋成功",
|
||||
Data = result ?? new NameValueList()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "搜尋計畫發生錯誤:" + ex.Message,
|
||||
Data = new NameValueList()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得所有簡訊平台清單
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse GetPlatforms()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
var dac = new DAC_MMSPlatform(conn);
|
||||
MMSPlatformList list = dac.SelectAll();
|
||||
return new BaseResponse { Success = true, Message = "查詢成功", Data = list };
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse { Success = false, Message = "查詢平台發生錯誤:" + ex.Message };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得目前系統預設平台
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse GetCurrentPlatform()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
var dac = new DAC_MMSPlatform(conn);
|
||||
MMSPlatformItem item = dac.SelectByCode(PublicVariable.CurrentMMSPlatform);
|
||||
return new BaseResponse { Success = true, Message = "查詢成功", Data = item };
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse { Success = false, Message = "查詢目前平台發生錯誤:" + ex.Message };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 儲存平台設定(帳號 / 密碼)
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse SavePlatform(MMSPlatformItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (item == null || string.IsNullOrEmpty(item.PlatformCode))
|
||||
return new BaseResponse { Success = false, Message = "參數不完整" };
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
var dac = new DAC_MMSPlatform(conn);
|
||||
MMSPlatformItem existing = dac.SelectByCode(item.PlatformCode);
|
||||
if (existing == null)
|
||||
return new BaseResponse { Success = false, Message = "找不到指定平台:" + item.PlatformCode };
|
||||
|
||||
existing.Account = item.Account;
|
||||
existing.Password = item.Password;
|
||||
existing.Remark = item.Remark;
|
||||
dac.UpdateOne(existing);
|
||||
|
||||
return new BaseResponse { Success = true, Message = "儲存成功" };
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse { Success = false, Message = "儲存平台設定發生錯誤:" + ex.Message };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 設定目前使用的簡訊平台(更新 BWEX 並同步 PublicVariable)
|
||||
/// </summary>
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse SetCurrentPlatform(string platformCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(platformCode))
|
||||
return new BaseResponse { Success = false, Message = "平台代碼不能為空" };
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
// 確認平台代碼存在
|
||||
var dacPlatform = new DAC_MMSPlatform(conn);
|
||||
if (dacPlatform.SelectByCode(platformCode) == null)
|
||||
return new BaseResponse { Success = false, Message = "找不到指定平台:" + platformCode };
|
||||
|
||||
// 更新 BWEX
|
||||
var dacBwex = new DAC_BWEX(conn);
|
||||
dacBwex.SetKeyValue("CurrentMMSPlatform", platformCode);
|
||||
}
|
||||
|
||||
// 同步更新記憶體中的變數
|
||||
PublicVariable.CurrentMMSPlatform = platformCode;
|
||||
|
||||
return new BaseResponse { Success = true, Message = "已切換至平台:" + platformCode };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse { Success = false, Message = "設定平台發生錯誤:" + ex.Message };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
/// <summary>
|
||||
/// 回應的基底類別
|
||||
/// </summary>
|
||||
public class BaseResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否成功
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 訊息
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 資料
|
||||
/// </summary>
|
||||
public object Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 資料筆數
|
||||
/// </summary>
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
public class LoginSuccessResponse: BaseResponse
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
}
|
||||
|
||||
public class CheckSessionResponse: LoginSuccessResponse
|
||||
{
|
||||
public bool IsLoggedIn { get; set; }
|
||||
public DateTime LoginTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="RoleManageService.asmx.cs" Class="報到系統.RoleManageService" %>
|
||||
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Services;
|
||||
using System.Web.Script.Services;
|
||||
using WebLib;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
[WebService(Namespace = "http://tempuri.org/")]
|
||||
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
|
||||
[System.ComponentModel.ToolboxItem(false)]
|
||||
[ScriptService]
|
||||
public class RoleManageService : System.Web.Services.WebService
|
||||
{
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse SearchEmployees(string keyword)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "請輸入搜尋關鍵字",
|
||||
Data = new NameValueList()
|
||||
};
|
||||
}
|
||||
|
||||
DAC_BPSN dac = new DAC_BPSN();
|
||||
var result = dac.Select(keyword);
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "搜尋成功",
|
||||
Data = result ?? new NameValueList(),
|
||||
TotalCount = result != null ? result.Count : 0
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "搜尋員工失敗:" + ex.Message,
|
||||
Data = new NameValueList()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse GetGroupMembers(string groupID)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupID))
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "請選擇群組",
|
||||
Data = new NameValueList()
|
||||
};
|
||||
}
|
||||
|
||||
DAC_BWEH dac = new DAC_BWEH();
|
||||
var result = dac.SelectByGroupId(groupID);
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "取得成功",
|
||||
Data = result ?? new NameValueList(),
|
||||
TotalCount = result != null ? result.Count : 0
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "取得群組成員失敗:" + ex.Message,
|
||||
Data = new NameValueList()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse AddMemberToGroup(string groupID, string[] employeeIDs)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupID) || employeeIDs == null || employeeIDs.Length == 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "參數不正確"
|
||||
};
|
||||
}
|
||||
|
||||
DAC_BWEH dac = new DAC_BWEH();
|
||||
int successCount = 0;
|
||||
int totalCount = employeeIDs.Length;
|
||||
|
||||
foreach (string employeeID in employeeIDs)
|
||||
{
|
||||
if (dac.AddMemberToGroup(groupID, employeeID))
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = successCount > 0,
|
||||
Message = successCount == totalCount
|
||||
? $"成功加入 {successCount} 位成員"
|
||||
: $"成功加入 {successCount} 位成員,失敗 {totalCount - successCount} 位",
|
||||
TotalCount = successCount
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "加入群組失敗:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public BaseResponse RemoveMemberFromGroup(string groupID, string[] employeeIDs)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupID) || employeeIDs == null || employeeIDs.Length == 0)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "參數不正確"
|
||||
};
|
||||
}
|
||||
|
||||
DAC_BWEH dac = new DAC_BWEH();
|
||||
int successCount = 0;
|
||||
int totalCount = employeeIDs.Length;
|
||||
|
||||
foreach (string employeeID in employeeIDs)
|
||||
{
|
||||
if (dac.RemoveMemberFromGroup(groupID, employeeID))
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = successCount > 0,
|
||||
Message = successCount == totalCount
|
||||
? $"成功移除 {successCount} 位成員"
|
||||
: $"成功移除 {successCount} 位成員,失敗 {totalCount - successCount} 位",
|
||||
TotalCount = successCount
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "移除群組失敗:" + ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Transactions;
|
||||
using WebLib;
|
||||
|
||||
namespace 報到系統
|
||||
{
|
||||
/// <summary>
|
||||
/// 簡訊發送服務
|
||||
/// 單例類,用於定時發送待發送簡訊
|
||||
/// </summary>
|
||||
public sealed class SMSSendingService
|
||||
{
|
||||
private static SMSSendingService _instance;
|
||||
private static readonly object _lockObject = new object();
|
||||
private bool _busy = false;
|
||||
|
||||
/// <summary>
|
||||
/// 取得單一實例
|
||||
/// </summary>
|
||||
public static SMSSendingService Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new SMSSendingService();
|
||||
}
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private SMSSendingService()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在忙碌中
|
||||
/// </summary>
|
||||
public bool IsBusy
|
||||
{
|
||||
get { return _busy; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 啟動發送服務,發送所有待發送的簡訊
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
// 若已在忙碌中,則不重複啟動
|
||||
if (_busy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_busy = true;
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
// 查詢待發送簡訊(Status 為 PENDING 或 SENDING 的)
|
||||
var dacQueue = new DAC_MessageQueue(conn);
|
||||
var messagesToSend = dacQueue.SelectAllPendingOrSending();
|
||||
|
||||
// 如果沒有待發送簡訊,直接返回
|
||||
if (messagesToSend.Count == 0)
|
||||
{
|
||||
_busy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 查詢簡訊發送帳號密碼併填入 messagesToSend 的項目裡
|
||||
var dacClasses = new DAC_Classes(conn);
|
||||
var classes = dacClasses.SelectAll();
|
||||
|
||||
// 查詢簡訊平台的主帳號密碼
|
||||
var dacMessagePlatforms = new DAC_MMSPlatform(conn);
|
||||
var currentMMSPlatformAccount = dacMessagePlatforms.SelectByCode(PublicVariable.CurrentMMSPlatform);
|
||||
|
||||
// 查詢專案的簡訊帳號密碼
|
||||
var dacMessageAccount = new DAC_ProjectMMSAccount(conn);
|
||||
var messageAccounts = dacMessageAccount.SelectAll();
|
||||
|
||||
foreach (var message in messagesToSend)
|
||||
{
|
||||
var cls = classes.FirstOrDefault(p => p.ID == message.CourseID);
|
||||
// 沒有設定計畫的,使用預設平台的主要帳號密碼
|
||||
if (cls == null || string.IsNullOrEmpty(cls.ProjectID))
|
||||
{
|
||||
message.PlatformCode = PublicVariable.CurrentMMSPlatform;
|
||||
message.MMSAccount = currentMMSPlatformAccount?.Account;
|
||||
message.MMSPassword = currentMMSPlatformAccount?.Password;
|
||||
continue;
|
||||
}
|
||||
|
||||
var acc = messageAccounts.FirstOrDefault(p => p.ProjectID == cls.ProjectID && p.PlatformCode == PublicVariable.CurrentMMSPlatform);
|
||||
// 沒有設定計畫簡訊帳號密碼的,使用預設平台的主要帳號密碼
|
||||
if (acc == null || string.IsNullOrEmpty(acc.MMSAccount) || string.IsNullOrEmpty(acc.MMSPassword))
|
||||
{
|
||||
message.PlatformCode = PublicVariable.CurrentMMSPlatform;
|
||||
message.MMSAccount = currentMMSPlatformAccount?.Account;
|
||||
message.MMSPassword = currentMMSPlatformAccount?.Password;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 使用計畫指定的帳號密碼
|
||||
message.MMSAccount = acc.MMSAccount;
|
||||
message.MMSPassword = acc.MMSPassword;
|
||||
message.PlatformCode = PublicVariable.CurrentMMSPlatform;
|
||||
}
|
||||
|
||||
// 發送
|
||||
var dacLog = new DAC_MessageLog(conn);
|
||||
foreach (var message in messagesToSend)
|
||||
{
|
||||
SendMessage(message, dacQueue, dacLog);
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 記錄錯誤日誌
|
||||
LogError("SMSSendingService.Start", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_busy = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 發送單條簡訊
|
||||
/// </summary>
|
||||
private void SendMessage(MessageQueueItem message, DAC_MessageQueue dacQueue, DAC_MessageLog dacLog)
|
||||
{
|
||||
using (TransactionScope ts = DAC.NewTransactionScope())
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 更新簡訊狀態為 SENDING
|
||||
message.Status = "SENDING";
|
||||
dacQueue.UpdateOne(message);
|
||||
message.DB_APPNO++;
|
||||
|
||||
// 2. 呼叫簡訊服務商 API 發送
|
||||
bool sendSuccess = false;
|
||||
string failMessage = "";
|
||||
|
||||
try
|
||||
{
|
||||
switch (message.PlatformCode)
|
||||
{
|
||||
case "EVERY8D":
|
||||
sendSuccess = EVERY8DSMSProvider.SendMMS(message, out failMessage);
|
||||
break;
|
||||
case "MITAKE":
|
||||
default:
|
||||
sendSuccess = MITAKESMSProvider.SendMMS(message, out failMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sendSuccess = false;
|
||||
failMessage = ex.Message;
|
||||
}
|
||||
|
||||
// 3. 記錄發送結果到 MessageLog
|
||||
MessageLogItem logItem = new MessageLogItem
|
||||
{
|
||||
CourseID = message.CourseID,
|
||||
Status = sendSuccess ? "SENT" : "FAILED",
|
||||
Name = message.Name,
|
||||
Mobile = message.Mobile,
|
||||
Subject = message.Subject,
|
||||
Content = message.Content,
|
||||
DB_APPNO = 1,
|
||||
DB_CRDAT = DateTime.Now,
|
||||
DB_CRUSR = "SYSTEM",
|
||||
DB_TRDAT = DateTime.MinValue,
|
||||
DB_TRUSR = ""
|
||||
};
|
||||
|
||||
// 如果失敗,記錄失敗訊息
|
||||
if (!sendSuccess && !string.IsNullOrEmpty(failMessage))
|
||||
{
|
||||
logItem.FailMessage = failMessage;
|
||||
}
|
||||
|
||||
dacLog.InsertOne(logItem);
|
||||
|
||||
// 4. 刪除 MessageQueue 紀錄
|
||||
dacQueue.DeleteOne(message);
|
||||
|
||||
ts.Complete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("SendMessage", ex, message.ID.ToString());
|
||||
ts.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 記錄錯誤日誌
|
||||
/// </summary>
|
||||
private void LogError(string source, Exception ex, string details = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
string logPath = System.Web.HttpContext.Current?.Server.MapPath("~/Log") ?? "~/Log";
|
||||
if (!System.IO.Directory.Exists(logPath))
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(logPath);
|
||||
}
|
||||
|
||||
string fileName = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
|
||||
string message = string.Format(
|
||||
"[{0}] {1}\r\nSource: {2}\r\nException: {3}\r\nDetails: {4}\r\nStackTrace: {5}\r\n",
|
||||
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
|
||||
"SMS Sending Error",
|
||||
source,
|
||||
ex.Message,
|
||||
details,
|
||||
ex.StackTrace
|
||||
);
|
||||
|
||||
System.IO.File.AppendAllText(
|
||||
System.IO.Path.Combine(logPath, string.Format("SMSError_{0}.txt", DateTime.Now.ToString("yyyyMMdd"))),
|
||||
message
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略日誌記錄的錯誤
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="SignUpService.asmx.cs" Class="報到系統.SignUpService" %>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.web>
|
||||
<authorization>
|
||||
<!-- 預設:所有人都可訪問 -->
|
||||
<allow users="*" />
|
||||
</authorization>
|
||||
</system.web>
|
||||
|
||||
<!-- 允許所有人訪問 LoginService(明確設定) -->
|
||||
<location path="LoginService.asmx">
|
||||
<system.web>
|
||||
<authorization>
|
||||
<allow users="*" />
|
||||
</authorization>
|
||||
</system.web>
|
||||
</location>
|
||||
|
||||
<!-- ClassesService 需要認證 -->
|
||||
<location path="ClassesService.asmx">
|
||||
<system.web>
|
||||
<authorization>
|
||||
<deny users="?" />
|
||||
<allow users="*" />
|
||||
</authorization>
|
||||
</system.web>
|
||||
</location>
|
||||
|
||||
<!-- RoleManageService 需要認證 -->
|
||||
<location path="RoleManageService.asmx">
|
||||
<system.web>
|
||||
<authorization>
|
||||
<deny users="?" />
|
||||
<allow users="*" />
|
||||
</authorization>
|
||||
</system.web>
|
||||
</location>
|
||||
|
||||
<!-- SignUpService 需要認證 -->
|
||||
<location path="SignUpService.asmx">
|
||||
<system.web>
|
||||
<authorization>
|
||||
<deny users="?" />
|
||||
<allow users="*" />
|
||||
</authorization>
|
||||
</system.web>
|
||||
</location>
|
||||
|
||||
<!-- BPSNService 需要認證 -->
|
||||
<location path="BPSNService.asmx">
|
||||
<system.web>
|
||||
<authorization>
|
||||
<deny users="?" />
|
||||
<allow users="*" />
|
||||
</authorization>
|
||||
</system.web>
|
||||
</location>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user