using Ds;
using Ds.Gv;
using iText.Kernel.Geom;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NPOI.SS.Formula.Functions;
using NPOI.SS.Util;
using Su;
using System.Linq.Expressions;
namespace CallCampaign.Api
{
/// <summary>
/// 行銷活動
/// </summary>
[Route("api/call-campaign")]
[ApiController]
[SetAuthorizationFilter(Sh.AuthCode.不設限)]
public class ReserveCampaignController : Controller
{
/// <summary>
/// 取得行銷活動列表
/// </summary>
/// <param name="reserveCampaignName"></param>
/// <param name="currentPage"></param>
/// <param name="pageSize"></param>
/// <param name="orderByName"></param>
/// <param name="sort"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
[HttpGet("")]
public async Task<object> ListAsync([FromQuery] string reserveCampaignName = "", [FromQuery] int? currentPage = 1, [FromQuery] int? pageSize = 20, [FromQuery] string orderByName = "OrderNo", [FromQuery] string sort = "asc")
{
if (pageSize > 500)
{
pageSize = 500;
}
if (!(sort == "asc" || sort == "desc"))
{
throw new CustomException(System.Net.HttpStatusCode.BadRequest, "sort只能是asc或desc");
}
var temp = new V_ReserveCampaign().GetType().GetProperty(orderByName);
if (temp == null)
{
throw new CustomException(System.Net.HttpStatusCode.BadRequest, "不存在欄位");
}
Expression<Func<V_ReserveCampaign, bool>> q = p => p.Is_Deleted == "N"
&& (string.IsNullOrEmpty(reserveCampaignName) || (p.ReserveCampaignName != null && p.ReserveCampaignName.Contains(reserveCampaignName)))
;
if (orderByName.ToLower().Trim() != "id")
{
orderByName += " " + sort + ", id desc";
}
else
{
orderByName += " " + sort;
}
var ct = NewContext.GvContext;
var list = await ct.GetPageListAsync(q, columns: "Id, ReserveCampaignName, OrderNo, StartAt, EndAt, ModifierName, ModifyDate, CreatorName, CreateDate", page: currentPage ?? 1, pageSize: pageSize ?? 20, orderByName);
//var list = await ct.GetPageListAsync(q, page: currentPage ?? 1, pageSize: pageSize ?? 20, orderByName + " " + sort);
return list;
}
/// <summary>
/// 取得行銷活動
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
[HttpGet("get")]
public async Task<dynamic> GetAsync([FromQuery] int Id)
{
var res = await Ds.NewContext.GvContext.ReserveCampaigns.Where(r => r.Id == Id)
.FirstOrDefaultAsync();
if (res == null)
{
throw new CustomException(System.Net.HttpStatusCode.BadRequest, "查無資料 " + Id.ToString());
}
return res;
}
/// <summary>
/// 建立行銷活動
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
/// <exception cref="CustomException"></exception>
[HttpPost("")]
public async Task<object> CreateAsync(Dtos.CreateReserveCampaign dto)
{
var ct = NewContext.GvContext;
var res = await Models.ReserveCampaignHelper.CreateReserveCampaignAsync(ct, dto);
return res;
}
/// <summary>
/// 編輯行銷活動
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
/// <exception cref="CustomException"></exception>
[HttpPatch("")]
public async Task<object> UpdateAsync(Dtos.UpdateReserveCampaign dto)
{
var ct = NewContext.GvContext;
var res = await Models.ReserveCampaignHelper.UpdateReserveCampaignAsync(ct, dto);
return res;
}
/// <summary>
/// 刪除行銷活動
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
/// <exception cref="CustomException"></exception>
[HttpDelete("")]
public async Task<object> DeleteAsync([FromQuery] int id)
{
var res = await Ds.NewContext.GvContext.MarkDeleteAsync<Ds.Gv.ReserveCampaign>(id, Sh.ModifyInfo);
return res;
}
}
}
/// <summary>
/// 取得列表
/// </summary>
/// <param name="name"></param>
/// <param name="currentPage"></param>
/// <param name="pageSize"></param>
/// <param name="orderByName"></param>
/// <param name="sort"></param>
/// <returns></returns>
[HttpGet("")]
public object List([FromQuery] string name = "", [FromQuery] int? currentPage = 1, [FromQuery] int? pageSize = 20, [FromQuery] string orderByName = "OrderNo", [FromQuery] string sort = "asc")
{
return "";
}
/// <summary>
/// 取得明細資料
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
[HttpGet("get")]
public object Get([FromQuery] int id)
{
return "";
}
/// <summary>
/// 建立
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
/// <exception cref="CustomException"></exception>
[HttpPost("")]
public object Create(Dtos.PhysicalCheckUpType dto)
{
return "";
}
/// <summary>
/// 編輯
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
/// <exception cref="CustomException"></exception>
[HttpPatch("")]
public object Update(Dtos.PhysicalCheckUpType dto)
{
return "";
}
/// <summary>
/// 刪除
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
/// <exception cref="CustomException"></exception>
[HttpDelete("")]
public object Delete([FromQuery] int id)
{
return 1;
}
static List<Brush> CaptchaBrushes = null;
public static FileStreamResult CreateCaptcha(string captcha)
{
if (CaptchaBrushes == null)
{
CaptchaBrushes = new List<Brush>();
CaptchaBrushes.Add(Brushes.White);
CaptchaBrushes.Add(Brushes.Gold);
CaptchaBrushes.Add(Brushes.LightSkyBlue);
CaptchaBrushes.Add(Brushes.LimeGreen);
CaptchaBrushes.Add(Brushes.AliceBlue);
CaptchaBrushes.Add(Brushes.AntiqueWhite);
CaptchaBrushes.Add(Brushes.BurlyWood);
CaptchaBrushes.Add(Brushes.Silver);
}
int width = 90;
int height = 45;
//https://stackoverflow.com/questions/61365732/cannot-access-a-closed-stream-when-returning-filestreamresult-from-c-sharp-netc
//Using statements close and unload the variable from memory set in the using statement which is why you are getting an error trying to access a closed memory stream.You don't need to use a using statement if you are just going to return the result at the end.
//這個 memory stream 不用關閉或 dispose
var ms = new MemoryStream();
// 釋放所有在 GDI+ 所佔用的記憶體空間 ( 非常重要!! )
using (Bitmap _bmp = new Bitmap(width, height))
using (Graphics _graphics = Graphics.FromImage(_bmp))
using (Font _font = new Font("Courier New", 24, FontStyle.Bold)) // _font 設定要出現在圖片上的文字字型、大小與樣式
{
// (封裝 GDI+ 繪圖介面) 所有繪圖作業都需透過 Graphics 物件進行操作
_graphics.Clear(Color.Black);
// 如果想啟用「反鋸齒」功能,可以將以下這行取消註解
//_graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
// 將亂碼字串「繪製」到之前產生的 Graphics 「繪圖板」上
var x = 10;
for(var i = 0; i < captcha.Length; i++)
{
_graphics.DrawString(captcha.Substring(i, 1), _font, CaptchaBrushes[Su.MathUtil.GetRandomInt(CaptchaBrushes.Count)], x, Su.MathUtil.GetRandomInt(15));
x += 10 + Su.MathUtil.GetRandomInt(10);
}
// 畫線
_graphics.DrawLine(new Pen(CaptchaBrushes[Su.MathUtil.GetRandomInt(CaptchaBrushes.Count)], 1),
Su.MathUtil.GetRandomInt(0, Convert.ToInt32((width * 0.9 / 2))), 0, Su.MathUtil.GetRandomInt(Convert.ToInt32(width / 2), Convert.ToInt32(width * 1.9 / 2)), height);
_graphics.DrawLine(new Pen(CaptchaBrushes[Su.MathUtil.GetRandomInt(CaptchaBrushes.Count)], 1),
Su.MathUtil.GetRandomInt(Convert.ToInt32(width / 2), Convert.ToInt32(width * 1.9 / 2)), 0, Su.MathUtil.GetRandomInt(0, Convert.ToInt32((width * 0.9 / 2))), height);
_graphics.DrawLine(new Pen(CaptchaBrushes[Su.MathUtil.GetRandomInt(CaptchaBrushes.Count)], 1),
0,
Su.MathUtil.GetRandomInt(height / 2),
width,
height / 2 + Su.MathUtil.GetRandomInt(height / 2)
);
_graphics.DrawLine(new Pen(CaptchaBrushes[Su.MathUtil.GetRandomInt(CaptchaBrushes.Count)], 1),
0,
height / 2 + Su.MathUtil.GetRandomInt(height / 2),
width,
Su.MathUtil.GetRandomInt(height / 2)
);
_bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
}
ms.Seek(0, SeekOrigin.Begin);
// Controller 的型別為 FileResult
return new FileStreamResult(ms, "image/jpeg")
{ FileDownloadName = $"{DateTime.Now.Ymdhmsf()}.jpg" };
}
namespace Web.Controllers
{
public class CaptchaController : Controller
{
[Route("captcha")]
public async Task<FileStreamResult> Index()
{
//產生 Captcha 並存入 Session 之中。目前是四位數字
string captcha = (await Ah.ReGetAsync<object>("api/kol/create-captcha-code")).ToString();
//產生圖檔並回傳 FileStreamResult
return Su.Wu.CreateCaptcha(captcha);
}
}
}
int tries = 0;
while (tries < Su.PgSql.maxTransactionAborted)
{
try
{
using (var scop = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var context = ShopBandContext.Context;
updateCount = await context.UpdateAsync<MarketDiscount>(dto, Sc.ModifyInfo,
onlyColumns: "StartAt,EndAt,NewPrice,FrontEndMemo,BackEndMemo");
scop.Complete();
break; //這個 Break 很重要
}
}
catch (Exception ex)
{
// 我發現因為是非同步模式,所以要檢查很多層的 InnerException
if (tries < Su.PgSql.maxTransactionAborted
&& ((ex.InnerException != null && ex.InnerException is Npgsql.PostgresException && ((Npgsql.PostgresException)ex.InnerException).SqlState == "40001")
|| (ex.InnerException.InnerException != null && ex.InnerException.InnerException is Npgsql.PostgresException && ((Npgsql.PostgresException)ex.InnerException.InnerException).SqlState == "40001")))
{
//隨機休息 10 ~ 20 ms, 等另一個 job 完工
System.Threading.Thread.Sleep(Su.MathUtil.Random.Next(10, 20));
tries++;
}
else
{
throw;
}
}
}
public class UploadController : ApiController
{
public async Task<object> PostFormData()
{
var provider = new MultipartMemoryStreamProvider();
if (! Request.Content.IsMimeMultipartContent())
{
return "no file";
}
//要注意這裡的 await
await Request.Content.ReadAsMultipartAsync(provider);
foreach (var content in provider.Contents)
{
if (content.Headers.ContentDisposition.FileName != null)
{
string localFilename = content.Headers.ContentDisposition.FileName.Replace("\"", "");
System.IO.Directory.CreateDirectory(HttpContext.Current.Server.MapPath(@"~/App_Data/Temp/"));
string filename = HttpContext.Current.Server.MapPath(@"~/App_Data/Temp/" + localFilename);
if (System.IO.File.Exists(filename))
{
System.IO.File.Delete(filename);
}
using (var fileStream = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
var contentStream = await content.ReadAsStreamAsync();
await contentStream.CopyToAsync(fileStream);
Trace.WriteLine("Save To" + filename);
}
}
}
return "OK";
}
}
/// <summary>
/// 檔案檢查
/// </summary>
/// <param name="actionContext"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
Trace.WriteLine("ApiCheckFile OnActionExecutingAsync");
var request = actionContext.Request;
if (!request.Content.IsMimeMultipartContent())
{
return;
}
var provider = new MultipartMemoryStreamProvider();
await request.Content.ReadAsMultipartAsync(provider);
//把 provider 存入 System.Web.HttpContext.Current.Items 之中,以便在 controller 中再度使用
System.Web.HttpContext.Current.Items["MimeMultipartContentProvider"] = provider;
foreach (var content in provider.Contents)
{
if (content.Headers.ContentDisposition.FileName != null)
{
var filename = content.Headers.ContentDisposition.FileName.Replace("\"", "");
Trace.WriteLine(filename);
var ext = System.IO.Path.GetExtension(filename);
if (!".jpg,.jpeg,.png".Contains(ext.ToLower()))
{
throw new Exception("file format error.");
}
}
}
return;
}
public async Task<object> PostFormData()
{
//改由 HttpContext.Current.Items 中,讀取資料。
MultipartMemoryStreamProvider provider = (MultipartMemoryStreamProvider)System.Web.HttpContext.Current.Items["MimeMultipartContentProvider"];
//如果沒有經過 filter,provider會是 null, 這時就要直接由 Request.Content 讀入 provider
if (provider == null)
{
provider = new MultipartMemoryStreamProvider();
Request.Content.ReadAsMultipartAsync(provider);
}
foreach (var content in provider.Contents)
{
if (content.Headers.ContentDisposition.FileName != null)
{
string localFilename = content.Headers.ContentDisposition.FileName.Replace("\"", "");
Trace.WriteLine("FileName: " + localFilename);
Trace.WriteLine("FileName: " + @"~/App_Data/Temp/" + localFilename);
System.IO.Directory.CreateDirectory(HttpContext.Current.Server.MapPath(@"~/App_Data/Temp/"));
string filename = HttpContext.Current.Server.MapPath(@"~/App_Data/Temp/" + localFilename);
if (System.IO.File.Exists(filename))
{
System.IO.File.Delete(filename);
}
using (var fileStream = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
var contentStream = await content.ReadAsStreamAsync();
await contentStream.CopyToAsync(fileStream);
Trace.WriteLine("Save To" + filename);
}
}
else
{
var contentStream = await content.ReadAsStreamAsync();
var reader = new System.IO.StreamReader(contentStream);
var data = reader.ReadToEnd();
Trace.WriteLine("data: " + data);
}
}
return "OK";
}
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
Debug.WriteLine("MvcCheckFileFilter OnActionExecuting");
if (actionContext.HttpContext.Request.Files.Count > 0)
{
for (int i = 0; i < actionContext.HttpContext.Request.Files.Count; i++)
{
System.Web.HttpPostedFileBase file = actionContext.HttpContext.Request.Files[i];
if (System.IO.Path.GetExtension(file.FileName) != ".jpg")
{
throw new Exception("file format error.");
}
Debug.WriteLine(i + "MvcCheckFileFilter OnActionExecuting File type: " + file.FileName.ToString());
}
}
//以下寫法會發生錯誤: 無法將類型 'System.String' 的物件轉換為類型 'System.Web.HttpPostedFileBase'。
//foreach (HttpPostedFileBase file in actionContext.HttpContext.Request.Files)
//{
// if (System.IO.Path.GetExtension(file.FileName) != ".jpg")
// {
// throw new Exception("file format error.");
// }
//}
}
/// <summary>
/// 取得授權的項目
/// </summary>
static string[] Scopes = { GmailService.Scope.GmailSend };
// 和登入 google 的帳號無關
// 任意值,若未來有使用者認証,可使用使用者編號或登入帳號。
string Username = "ABC";
/// <summary>
/// 存放 client_secret 和 credential 的地方
/// </summary>
string SecretPath = @"D:\project\GmailTest\Data\Secrets";
/// <summary>
/// 認証完成後回傳的網址, 必需和 OAuth 2.0 Client Id 中填寫的 "已授權的重新導向 URI" 相同。
/// </summary>
string RedirectUri = $"https://localhost:44340/Home/AuthReturn";
/// <summary>
/// 取得認証用的網址
/// </summary>
/// <returns></returns>
public async Task<string> GetAuthUrl()
{
using (var stream = new FileStream(Path.Combine(SecretPath, "client_secret.json"), FileMode.Open, FileAccess.Read))
{
FileDataStore dataStore = null;
var credentialRoot = Path.Combine(SecretPath, "Credentials");
if (!Directory.Exists(credentialRoot))
{
Directory.CreateDirectory(credentialRoot);
}
//存放 credential 的地方,每個 username 會建立一個目錄。
string filePath = Path.Combine(credentialRoot, Username);
dataStore = new FileDataStore(filePath);
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = GoogleClientSecrets.Load(stream).Secrets,
Scopes = Scopes,
DataStore = dataStore
});
var authResult = await new AuthorizationCodeWebApp(flow, RedirectUri, Username)
.AuthorizeAsync(Username, CancellationToken.None);
return authResult.RedirectUri;
}
}
public async Task<string> AuthReturn(AuthorizationCodeResponseUrl authorizationCode)
{
string[] scopes = new[] { GmailService.Scope.GmailSend };
using (var stream = new FileStream(Path.Combine(SecretPath, "client_secret.json"), FileMode.Open, FileAccess.Read))
{
//確認 credential 的目錄已建立.
var credentialRoot = Path.Combine(SecretPath, "Credentials");
if (!Directory.Exists(credentialRoot))
{
Directory.CreateDirectory(credentialRoot);
}
//暫存憑証用目錄
string tempPath = Path.Combine(credentialRoot, authorizationCode.State);
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = GoogleClientSecrets.Load(stream).Secrets,
Scopes = scopes,
DataStore = new FileDataStore(tempPath)
});
//這個動作應該是要把 code 換成 token
await flow.ExchangeCodeForTokenAsync(Username, authorizationCode.Code, RedirectUri, CancellationToken.None).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(authorizationCode.State))
{
string newPath = Path.Combine(credentialRoot, Username);
if (tempPath.ToLower() != newPath.ToLower())
{
if (Directory.Exists(newPath))
Directory.Delete(newPath, true);
Directory.Move(tempPath, newPath);
}
}
return "OK";
}
}
public async Task<bool> SendTestMail()
{
var service = await GetGmailService();
GmailMessage message = new GmailMessage();
message.Subject = "標題";
message.Body = $"<h1>內容</h1>";
message.FromAddress = "bikehsu@gmail.com";
message.IsHtml = true;
message.ToRecipients = "bikehsu@gmail.com";
message.Attachments = new List<Attachment>();
string filePath = @"C:\Users\bike\Pictures\Vegetable_pumpkin.jpg"; //要附加的檔案
Attachment attachment1 = new Attachment(filePath);
message.Attachments.Add(attachment1);
SendEmail(message, service);
Console.WriteLine("OK");
return true;
}
async Task<GmailService> GetGmailService()
{
UserCredential credential = null;
var credentialRoot = Path.Combine(SecretPath, "Credentials");
if (!Directory.Exists(credentialRoot))
{
Directory.CreateDirectory(credentialRoot);
}
string filePath = Path.Combine(credentialRoot, Username);
using (var stream = new FileStream(Path.Combine(SecretPath, "client_secret.json"), FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
Username,
CancellationToken.None,
new FileDataStore(filePath));
}
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Send Mail",
});
return service;
}
public class GmailMessage
{
public string FromAddress { get; set; }
public string ToRecipients { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public bool IsHtml { get; set; }
public List<System.Net.Mail.Attachment> Attachments { get; set; }
}
public static void SendEmail(GmailMessage email, GmailService service)
{
var mailMessage = new System.Net.Mail.MailMessage();
mailMessage.From = new System.Net.Mail.MailAddress(email.FromAddress);
mailMessage.To.Add(email.ToRecipients);
mailMessage.ReplyToList.Add(email.FromAddress);
mailMessage.Subject = email.Subject;
mailMessage.Body = email.Body;
mailMessage.IsBodyHtml = email.IsHtml;
if (email.Attachments != null)
{
foreach (System.Net.Mail.Attachment attachment in email.Attachments)
{
mailMessage.Attachments.Add(attachment);
}
}
var mimeMessage = MimeKit.MimeMessage.CreateFromMailMessage(mailMessage);
var gmailMessage = new Google.Apis.Gmail.v1.Data.Message
{
Raw = Encode(mimeMessage)
};
Google.Apis.Gmail.v1.UsersResource.MessagesResource.SendRequest request = service.Users.Messages.Send(gmailMessage, "me");
request.Execute();
}
public static string Encode(MimeMessage mimeMessage)
{
using (MemoryStream ms = new MemoryStream())
{
mimeMessage.WriteTo(ms);
return Convert.ToBase64String(ms.GetBuffer())
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
}
<security>
<authentication>
<anonymousAuthentication enabled="false" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
<section name="anonymousAuthentication" overrideModeDefault="Deny" />
<section name="windowsAuthentication" overrideModeDefault="Deny" />
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://localhost:5002"
}
}
},
"AllowedHosts": "*"
}
upstream portal2 {
# localhost:5000 改成 ASP.NET Core 所監聽的 Port
server localhost:5002;
}
server {
# 只要是透過這些 Domain 連 HTTP 80 Port,都會轉送封包到 ASP.NET Core
listen 80;
# 可透過空白區分,綁定多個 Domain
server_name coretest2.bike.idv.tw;
location / {
proxy_pass http://portal2/;
include /etc/nginx/conf.d/default_proxy_settings;
}
}
# 用 HTTPS 必須要有 SSL 憑證,如果沒有要綁定 SSL 可以把下面整段移除
server {
# 只要是透過這些 Domain 連 HTTPS 443 Port,都會轉送封包到 ASP.NET Core
listen 443 ssl;
server_name coretest2.bike.idv.tw;
ssl_certificate /etc/nginx/ssl/coretest2.bike.idv.tw.crt;
ssl_certificate_key /etc/nginx/ssl/coretest2.bike.idv.tw.key;
location / {
proxy_pass http://portal2/;
include /etc/nginx/conf.d/default_proxy_settings;
}
}
#!/bin/bash
main() {
sudo yum -y install epel-release
sudo yum -y update
install_nginx
install_dotnet
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload
}
install_nginx() {
echo "###################################"
echo "########## Install Nginx ##########"
echo "###################################"
sudo yum -y install httpd-tools nginx
sudo setsebool -P httpd_can_network_connect on
sudo sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
sudo setenforce 0
sudo systemctl enable nginx
sudo systemctl restart nginx
}
install_dotnet() {
echo "###########################################"
echo "########## Install .NET Core 2.2 ##########"
echo "###########################################"
sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
sudo yum -y install aspnetcore-runtime-2.2
}
main "$@"
sudo sh setup-aspnet-core.sh
[Unit]
# Description=<此服務的摘要說明>
Description=MyWebsite
[Service]
# WorkingDirectory=<ASP.NET Core 專案目錄>
WorkingDirectory=/usr/share/my-website
# ExecStart=/bin/dotnet <ASP.NET Core 起始 dll>
ExecStart=/bin/dotnet MyWebsite.dll
# 啟動若失敗,就重啟到成功為止
Restart=always
# 重啟的間隔秒數
RestartSec=10
# 設定環境變數,注入給 ASP.NET Core 用
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
# 開啟,開機自動啟動服務
systemctl enable my-website.service
# 關閉,開機自動啟動服務
systemctl disable my-website.service
# 啟動服務
systemctl start my-website.service
# 重啟服務
systemctl restart my-website.service
# 停止服務
systemctl stop my-website.service
# 查看服務狀態
systemctl status my-website.service
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
upstream portal {
# localhost:5000 改成 ASP.NET Core 所監聽的 Port
server localhost:5000;
}
server {
# 只要是透過這些 Domain 連 HTTP 80 Port,都會轉送封包到 ASP.NET Core
listen 80;
# 可透過空白區分,綁定多個 Domain
server_name demo.johnwu.cc example.johnwu.cc;
location / {
proxy_pass http://portal/;
include /etc/nginx/conf.d/default_proxy_settings;
}
}
# 用 HTTPS 必須要有 SSL 憑證,如果沒有要綁定 SSL 可以把下面整段移除
server {
# 只要是透過這些 Domain 連 HTTPS 443 Port,都會轉送封包到 ASP.NET Core
listen 443 ssl;
server_name demo.johnwu.cc;
ssl_certificate /etc/nginx/ssl/demo.johnwu.cc_bundle.crt;
ssl_certificate_key /etc/nginx/ssl/demo.johnwu.cc.key;
location / {
proxy_pass http://portal/;
include /etc/nginx/conf.d/default_proxy_settings;
}
}
# 檢查 Nginx 的設定是否有誤
nginx -t
# 若沒有錯誤,即可套用
nginx -s reload