chore: 首次簽入 Thinkyu ASP.NET 專案

- 加入 Visual Studio / ASP.NET .gitignore
- 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
2026-09-10 09:42:37 +08:00
commit 577060bc78
2496 changed files with 501389 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
<%@ WebService Language="C#" CodeBehind="BPSNService.asmx.cs" Class="報到系統.BPSNService" %>
+68
View File
@@ -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
};
}
}
}
}
+104
View File
@@ -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; }
}
}
}
+287
View File
@@ -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; }
}
}
}
+1
View File
@@ -0,0 +1 @@
<%@ WebService Language="C#" CodeBehind="LoginService.asmx.cs" Class="報到系統.LoginService" %>
+192
View File
@@ -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;
}
}
}
}
+309
View File
@@ -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 };
}
}
}
}
+44
View File
@@ -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
};
}
}
}
}
+250
View File
@@ -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
{
// 忽略日誌記錄的錯誤
}
}
}
}
+1
View File
@@ -0,0 +1 @@
<%@ WebService Language="C#" CodeBehind="SignUpService.asmx.cs" Class="報到系統.SignUpService" %>
File diff suppressed because it is too large Load Diff
+58
View File
@@ -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>