# 知租網專案技術概觀
主題為租借電商
## 方案共通
* 資料庫: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