---
# System prepended metadata

title: 知租網專案技術概觀

---


# 知租網專案技術概觀
主題為租借電商

## 方案共通

* 資料庫：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