# 知租網專案技術概觀 主題為租借電商 ## 方案共通 * 資料庫:Azure線上資料庫 + SQL Server + SSMS管理工具 * 圖床:cloudinary * 快取:Redis ## 前台: 購物流程功能:逛商城(篩選查詢排序)、加入購物車、下單、付款 會員相關功能:登入登出、貨物狀態通知、編輯個資、取消訂單、對網站評價 ### (一)專案範本:ASP.NET MVC5 (.Net Framework 4.7.2) ### (二)架構 * #### 分層架構:service、repository `DbContext -- GenericRepository -- DM -- service -- VM -- action -- View` ### (三)後端技術: 1. #### 共通: * Route系統以網址傳引數給後端參數 * Linq to Entity:EF6 * WebAPI 2. #### 身分驗證: * Form Authentication * 第三方登入:google(OAuth)、FB、Line 3. #### 付款: * 綠界API 4. #### 通知: * signalR ### (四)前端技術 #### 第三方套件: | 名稱 | 目的 | | -------- | -------- | | swipper | Carousel視覺效果 | | funcybox | 特寫圖片並有Carousel特性 | | flatpickr | 用原生JS做的日期選擇器套件 | | SweetAlert | 美化的訊息彈窗 | | google map | 顯示店址位置 | #### 其他: * 前端POST:form表單 / AJAX * 前端驗證:jQuery + DataAnnotation + 正規表達式 * 圖片lazyloading --- ## 後台: 銷售圖表、產品編輯上下架、訂單商品改變貨物狀態、Blog發文/修文 ### (一)專案範本:ASP.NET Core5 MVC (.Net 5.0) ### (二)架構 * #### 分層架構:Service `DbContext -- DM -- service -- VM -- action -- View` 因泛型repository的功能都可透過DbContext的內建語法做到,故沒再分層。 * #### 採前後端分離 ### (三)後端技術: 1. #### 共通: * Route的Annotation * Linq to Entity:EF Core * WebAPI + Swagger + JWT驗證(不完整) 1. #### 富文本 發布、編輯 Blog文章 ### (四)前端技術 #### 第三方套件: | 名稱 | 目的 | | -------- | -------- | | SB Admin 2(後臺樣板) | 加速開發。配合Vue.js的相依性(bootstrap4.3) | Vue.js | 前端動態渲染 | | BootstrapVue | 節省網頁元件開發時間 | | Chart.js | 圖表 | | SweetAlert | 美化的訊息彈窗 | #### 其他: * AJAX發送request * 前端驗證 --- # 一些技術的導入步驟 ## (一)Swagger管理API 可讓專案中所有的API以圖形化介面的網頁頁面呈現,便於概覽、測試。 且 .Net Core 的WebAPI專案範本,內建swagger機制。 ### 簡介在 ASP.Net Core MVC專案範本導入Swagger #### 1. 安裝Nuget套件`nswag.AspNetCore` #### 2. StartUp.cs 中註冊、啟用 ```csharp= public void ConfigureServices(IServiceCollection services) { ...... //註冊Swagger服務 services.AddSwaggerDocument(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ...... app.UseHttpsRedirection(); //Swagger app.UseStaticFiles();//使用靜態檔案,允許程式讀取wwwroot的檔案 //Swagger ...... //Swagger //啟用,兩者順序可掉換 app.UseOpenApi(); app.UseSwaggerUi3(); //Swagger app.UseRouting(); } ``` #### 3. 加入一些API 若還沒有任何API,則加入一個陽春的API Controller、Action做測試 ```csharp= [Route("api/[controller]/[action]")] [ApiController] public class MyApiController : ControllerBase { [HttpGet]//此annotation可省略 public IActionResult GetProduct() { return Ok("此API供應的資料"); } } ``` #### 4. 啟用專案XML輸出 `專案屬性>建置>輸出 >打勾 XML文件檔案,並改成相對路徑` 會爆出不少的警告,將警告類型的代碼背起來,到 `專案屬性>建置>錯誤和警告>隱藏警告` 中輸入,避免一直被打擾 #### 5. 試運行Swagger頁面 運行專案,於網址根目錄下加Swagger/index.html ## (二)Json Web Token驗證 Json Web Token,是一種token的驗證機制。 通常在登入時核發token,要request那些限制權限的資料時,將token夾帶在http header中,以通行。 特別適合SPA,不跳頁使得token可以只存在記憶體中,較安全 JWT有三個區段 header:通常放演算法、類型 payload:發行者、有效時間 signature ### 簡介在 .Net Core MVC專案範本導入JWT #### 1. 安裝Nuget套件 `Microsoft.AspNetCore.Authentication.JwtBearer` #### 2. 建一個class,名稱例如為JwtHelper,實作GenerateToken方法 ```csharp= public string GenerateToken(string username) { var issuer = "issuer"; //發行者 var signKey = "1Zl4h9703IzROidasgfegkK3@f4po1jkd"; //上為加密金鑰,可隨意打,但長度似乎要16位以上 //設一些要寫在payload中的資訊 var claims = new List<Claim>(); claims.Add(new Claim(JwtRegisteredClaimNames.Sub, username)); claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); // payload中的資訊 var userClaimsIdentity = new ClaimsIdentity(claims); var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey)); var signingCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256Signature);//某加密算法 var tokenDescriptor = new SecurityTokenDescriptor { Expires = DateTime.UtcNow.AddHours(1),//過期時間 Issuer = issuer,//發行者 SigningCredentials = signingCredentials,//加密設定 Subject = userClaimsIdentity }; var tokenHandler = new JwtSecurityTokenHandler(); var securityToken = tokenHandler.CreateToken(tokenDescriptor); var token = tokenHandler.WriteToken(securityToken); //tokenHandler是負責產生token的類別 return token; } ``` #### 3. StartUp.cs 中註冊、啟用 ```csharp= //註冊驗證: services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(option => { { option.IncludeErrorDetails = true; option.TokenValidationParameters = new TokenValidationParameters { //Marvin:加這行才取得到identity.name NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifiers", ValidateIssuer = true, //發行者 ValidIssuer = "issuer", //得和發證時的發行者一樣 ValidateAudience = false, ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1Zl4h9703IzROidasgfegkK3@f4po1jkd"))//這串字串得和金鑰一樣 }; }); --------------------- //啟用驗證,必須在授權前 app.UseAuthentication(); app.UseAuthorization(); ``` #### 4. 模擬核發token 做一個陽春的login action,取得GenerateToken方法索回傳的token「字串」 ```csharp= public IActionResult Login (string username) { return Ok(_jwtHelper.GenerateToken(username)); } ``` #### 5. 讓Swagger支援JWT ```csharp= //修改Swagger的註冊 services.AddSwaggerDocument(config => { var apiScheme = new OpenApiSecurityScheme() { Type = OpenApiSecuritySchemeType.ApiKey, Name = "Authorization", In = OpenApiSecurityApiKeyLocation.Header, Description = "請將Token填入 : Bearer {token}" }; config.AddSecurity("JWT Token", Enumerable.Empty<string>(), apiScheme); config.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT Token")); }); ``` #### 6. 模擬驗證 找一個Api的Action上加`[Authorize]` ##### swagger測試: 發現API上多出一鎖頭,點擊進去可輸入token字串,即可完成授權 ##### postman測試: Authorization頁籤 --> Type選Bearer Token,右邊Token貼上三段密文 --> Send ##### 從前端以AJAX: 視使用的是fetch/xhr/axios/jquery,於request的header中夾帶token字串。 ## (三)Redis快取 對於高頻率取用的資料,通常希望加快存取效能,可考慮使用記憶體快取。 ### 註冊 到Redis官網註冊,免費方案有30MB的空間 ### 創訂閱Subscription、資料庫Database 點擊New Subscription 1. 選cloud vendor,沒想法就選google cloud platform 2. Region選近的,比如台灣可選 Asia Pacific(Tokyo) 3. 選 30MB Free方案 4. 輸入名稱 5. 點擊Create Subscription,完成 在此Subscription底下,點擊New Database 1. 輸入名稱,其他都照著預設設定 2. 右上角按下activate 點進此Database,待會會用到[Public endpoint]字串、下方Security中的[Default user password]字串 ### 簡介在 .Net Core5 MVC專案範本導入Redis #### 1. appsettings.json 準備好組態 `"MyRedis": "[Public endpoint],password=[Default user password]"` #### 2. 安裝Nuget套件`Microsoft.Extensions.Caching.StackExchangeRedis` #### 3. StartUp.cs 中註冊 ```csharp= //註冊redis資料庫 services.AddStackExchangeRedisCache(options => options.Configuration = Configuration["MyRedis"]); ``` #### 4. RedisRepository類別中建立[存、取、刪]三個泛型方法 ```csharp= using Microsoft.Extensions.Caching.Distributed; //IDistributedCache欄位+類別建構式注入其DI public class RedisRepository:IRedisRepository { readonly IDistributedCache _iDistributedCache; public RedisRepository(IDistributedCache distributedCache) { _iDistributedCache = distributedCache; } public void Set<T>(string key, T value) where T : class { _iDistributedCache.Set(key, ObjectToByteArray(value), new DistributedCacheEntryOptions() { //設定過期時間(絕對值) AbsoluteExpiration = DateTimeOffset.Now.Addhours(1) }); } public T Get<T>(string key) where T : class { return ByteArrayToObject<T>(_iDistributedCache.Get(key)); } public void Remove(string key) { _iDistributedCache.Remove(key); } //泛型物件 與 binary陣列互相轉化的兩個方法 //object序列化成byte陣列 private byte[] ObjectToByteArray(object obj) { //using System.Text.Json; return JsonSerializer.SerializeToUtf8Bytes(obj); } //byte陣列反序列化成泛型物件 private T ByteArrayToObject<T>(byte[] bytes) where T : class { return bytes is null ? null : JsonSerializer.Deserialize<T>(bytes); } } ``` #### 5. DI註冊 對RedisRepository執行快速動作中的擷取介面 再到StartUp.cs中註冊相依性 `services.AddSingleton<IRedisRepository , RedisRepository>` #### 6. 呼叫端使用 例如在某service呼叫資料庫資料 ```csharp= private readonly IRedisRepository public MyService(IRedisRepository iRedisRepository) { //注入相依性 _iRedisRepository = iRedisRepository; } //可到此ViewModel類別上方補[Serializable] public List<MyViewModel> GetMyData() { //如果存在快取,就回傳之 var result = _iRedisRepository.Get<List<MyViewModel>>("MyDataKey"); if (result != null) return result; //如果不存在快取,就重撈,存入快取再回傳 result = ...一些EFCore的查詢語法... _iRedisRepository.Set("MyDataKey", result); return result; ``` 然後再從某個controller下的action呼叫此service。 #### 7. 下載、使用管理工具 ##### 裝軟體 以Another Redis Desktop Manager這套工具為例 先到git下載,安裝: https://github.com/qishibo/AnotherRedisDesktopManager/releases ##### 建立連線 打開軟體,按視窗左上角的New Connection建立新連線,需填host、port、password填入 三資料: 先前備好的[Public endpoint]、[Default user password],將[Public endpoint],以其中的冒號為分界,切割成`[host]:[port]`的規則填入;將[Default user password]填入password。 ##### 測試 運行自己做好的程式,讓資料存入快取;再將管理工具中的連線刷新,可看見出現了幾個key名稱,點擊key,可以看見儲存的資料及TTL(剩餘的有效秒數) 可到網頁的開發者工具 > network頁籤中,查看資源的存取時間,是否往後運行程式有因為快取而變快。 ### 簡介在 ASP.NET MVC5 專案範本導入Redis #### 1. Web.config 準備好組態 ```csharp= <appSettings> 略.. <add key="MyRedis" value="[Public endpoint],password=[Default user password]"/> </appSettings> ``` #### 2. 安裝Nuget套件`StackExchange.Redis` #### 3. 建立RedisConnectionFactory類別 ```csharp= using StackExchange.Redis; namespace 略 { public static class RedisConnectionFactory { private static readonly Lazy<ConnectionMultiplexer> Connection; static RedisConnectionFactory() { var connStr = System.Configuration.ConfigurationManager.AppSettings["MyRedis"]; var options = ConfigurationOptions.Parse(connStr); Connection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(options)); } public static ConnectionMultiplexer GetConnection() => Connection.Value; } } ``` #### 4. RedisRepository類別中建立[存、取、刪]三個泛型方法 ```csharp= using StackExchange.Redis; using XXnamespace.RedisConnectionFactory; public class RedisRepository : IRedisRepository { private ConnectionMultiplexer _conn; private IDatabase _db; public RedisRepository() { _conn = RedisConnectionFactory.GetConnection(); _db = _conn.GetDatabase(); } public T Get<T>(string key) where T : class { var data = _db.StringGet(key); return ByteArrayToObject<T>(data); } public void Set<T>(string key, T value) where T : class { TimeSpan cacheItemPolicy = new TimeSpan(0, 0, 30, 0); _db.StringSet(key, ObjectToByteArray(value), cacheItemPolicy); } public void Remove(string key) { _db.KeyDelete(key); } //ObjectToByteArray方法和ByteArrayToObject方法,同 .net Core中的介紹一模一樣 } ``` #### 5. DI註冊 .net framework沒有內建DI機制,需先導入 ##### 安裝Nuget套件`Unity.Mvc5` 安裝完不要把自動開啟的readme.txt關掉,須按照其中指示補上設定: Global.asax.cs檔中補一行code ```csharp= protected void Application_Start() { //加入這行 UnityConfig.RegisterComponents(); } ``` 對RedisRepository執行快速動作中的擷取介面 再到App_Start資料夾下的UnityConfig.cs中註冊相依性 ```csharp= container.RegisterType<IRedisRepository, RedisRepository>(); ``` #### 6. 呼叫端使用 同 .net Core #### 7. 下載、使用管理工具 同 .net Core ## chart、swipper、flatpickr