標籤: 包裝設計

  • lin-cms-dotnetcore功能模塊的設計

    lin-cms-dotnetcore功能模塊的設計

    lin-cms-dotnetcore功能模塊的設計

    先來回答以下問題。可拉到最下面查看預覽圖。

    1.什麼是cms?

    Content Management System,內容管理系統。

    2.dotnetcore是什麼?

    .NET Core,是由Microsoft開發,目前在.NET Foundation(一個非營利的開源組織)下進行管理,採用寬鬆的MIT協議,可構建各種軟件,包括Web應用程序、移動應用程序、桌面應用程序、雲服務、微服務、API、遊戲和物聯網應用程序。

    3.lin-cms 是什麼?

    Lin-CMS 是林間有風團隊經過大量項目實踐所提煉出的一套內容管理系統框架。Lin-CMS 可以有效的幫助開發者提高 CMS 的開發效率,

    Lin的定位在於實現一套 CMS的解決方案,管理系統的基礎框架,提供了不同的後端,不同的前端實現,後端也支持不同的數據庫,是一套前後端完整的解決方案

    目前官方團隊維護 lin-cms-vue,lin-cms-spring-boot,lin-cms-koa,lin-cms-flask 社區維護了 lin-cms-tp5,lin-cms-react,lin-cms-dotnetcore,即已支持vue,react二種前端框架,java,nodejs,python,php,c#等五種後端語言。

    lin-cms-vue(官方)

    • https://github.com/TaleLin/lin-cms-vue
    • Vue+ElementUI構建的CMS開發框架,
    • 林間有風團隊經過大量項目實踐所提煉出的一套內容管理系統框架
    • 內置了 CMS 中最為常見的需求:用戶管理、權限管理、日誌系統等

    lin-cms-koa(官方)

    • python
    • https://github.com/TaleLin/lin-cms-koa
    • 使用Node.JS KOA構建的CMS開發框架

    lin-cms-flask(官方)

    • node.js
    • https://github.com/TaleLin/lin-cms-flask
    • A simple and practical CMS implememted by flask

    lin-cms-spring-boot(官方)

    • java
    • https://github.com/TaleLin/lin-cms-spring-boot
    • 基於SpringBoot的CMS/DMS/管理系統開發框架

    lin-cms-tp5(社區)

    • php 被官方fork。
    • https://github.com/TaleLin/lin-cms-tp5
    • A simple and practical CMS implememted by ThinkPHP 5.1

    lin-cms-react(社區)

    • https://github.com/Bongkai/lin-cms-react
    • React+Antd構建的CMS開發框架

    lin-cms-dotnetcore(社區)

    • C#
    • A simple and practical CMS implemented by .NET Core 3.1 一個簡單實用、基於.NET Core
    • https://github.com/luoyunchong/lin-cms-dotnetcore
    • .NET Core 3.1實現的CMS;前後端分離、Docker部署、OAtuh2授權登錄、自動化部署DevOps、GitHub Action同步至Gitee

    4.lin-cms-dotnetcore有哪些特點?

    基於.NET Core3.1實現的LIN-CMS-VUE後端API,並增加了博客模塊。目前實現簡約的權限管理系統、基礎字典項管理、隨筆專欄,評論點贊、關注用戶、技術頻道(標籤分類)、消息通知,標籤等仿掘金模塊。

    功能模塊的設計

    基礎權限模塊

    • 用戶信息:郵件、用戶名(唯一)、昵稱、頭像、分組、是否激活、手機號、是否是Admin、個性簽名
      • [x] 註冊/登錄
      • [x] 上傳頭像
      • [x] 修改個人密碼
      • [x] 用戶基本信息修改
      • [x] 用戶增刪改,配置分組
    • 綁定第三方賬號
      • [x] GitHub登錄
      • [x] QQ 登錄
      • [ ] Gitee登錄
    • 分組信息:是否靜態分組(無法刪除,無法修改分組編碼)、名稱可以修改
      • [x] 分組增刪改
      • [x] 分組配置權限
    • 文件管理
      • [x] 本地文件上傳
      • [x] 七牛雲存儲
      • [x] 文件去重,秒傳
    • 系統日誌:請求方法、路徑、http返回碼、時間、用戶昵稱、用戶id、訪問哪個權限、 日誌信息
      • [x] 記錄系統請求的日誌
      • [ ] 異常日誌
    • 設置管理:name(鍵),value(值),provider_name(提供名),provider_key(提供者值)
      • [x] 設置新增修改刪除
      • [x] 所有設置

    比如存某用戶選擇的是markdown還是富文本。

    name="Article.Editor",
    value="markdown" 或 "富文本",
    provider_name為"User",
    provider_key為用戶Id
    

    或存儲七牛雲的某一個配置

    name="Qiniu.AK",
    value="asfadsfadf23rft66S4XM2GIK7FxfqefauYkcAyNGDAc" ,
    provider_name為"Qiniu"或自己定義的字符串
    provider_key為空
    

    cms 管理員維護模塊

    • [x] 標籤管理:名稱、圖片,是否啟用/禁用,排序、文章數量、用戶關注數量。
      • [x] 標籤增刪改
      • [x] 標籤列表,禁用
      • [x] 校正文章數量
    • [x] 技術頻道:封面圖、名稱、是否啟用/禁用、排序、編碼、備註描述、下屬標籤.一個技術頻道對應多個標籤
      • [x] 技術頻道增刪改
      • [x] 列表、禁用
    • [x] 隨筆管理:
      • [x] 審核隨筆/拉黑
      • [x] 管理員刪除隨筆
    • [x] 評論管理
      • [x] 後台審核通過/拉黑
      • [x] 管理員刪除評論
    • [x] 字典類別管理:編碼,名稱,排序
      • [x] 增刪改查
    • [x] 字典管理::編碼,名稱,排序,類別:如隨筆類型(原創、轉載、翻譯)
      • [x] 增刪改查

    cms 用戶端模塊

    • 技術頻道
      • [x] 首頁展示技術頻道
      • [x] 選擇技術頻道后,可再根據標籤查詢文章
    • 分類專欄管理:發布隨筆時可選擇單個分類。
      • [x] 分類增刪改(隨筆數量、圖片、名稱、排序)
      • [x] 分類列表,僅查看、編輯自己創建的分類專欄
    • 標籤:統計每個標籤下多少個文章、多少人關注
      • [x] 標籤列表
      • [x] 無限加載
      • [x] 最新/最熱 根據標籤名稱模糊查詢
      • [x] 已關注的標籤
      • [x] 熱門標籤
    • 隨筆
      • [x] 支持markdown,增刪改(僅自己的隨筆),修正分類專欄中的隨筆數量
      • [x] 支持富文本編輯隨筆
      • [x] 列表無限加載,按標籤查詢隨筆
      • [x] 點贊隨筆
      • 隨筆詳情頁
        • [x] 支持目錄導航(滾動時,固定至頂部位置),展示字數統計、預計閱讀時長;
        • [x] 作者介紹:頭像,昵稱,簽名,隨筆數;
        • [x] 展示文章類型:原創、轉載、翻譯
        • [ ] 相關文章
        • [ ] 推薦文章
    • 評論
      • [ ] 用戶關閉評論時,無法對隨筆進行評論
      • [ ] 評論隨筆(內容支持超鏈接、emoji)
      • [x] 刪除自己的評論
      • [x] 點贊評論
      • [x] 回複評論
    • 關注
      • [x] 關注/取消關注用戶
      • [x] 關注/取消關註標簽
      • [x] 我關注的用戶發隨筆
    • 個人主頁
      • 隨筆
        • [x] 用戶專欄分類展示
        • [x] 最新發布的隨筆
      • 關注
        • [x] 關注的用戶
        • [x] 粉絲
        • [x] 關注的標籤
    • 設置
      • 個人主頁設置
        • [x] 個人資料更新
      • 安全設置
        • [x] 密碼修改:快速登錄的賬號,初次設置時可留空
      • 博客設置
        • [x] 編輯器設置,(可切換markdown/富文本)
        • [x] 代碼風格配置(tango、native、monokai、github、solarized-light、vs)
    • 消息
      • [x] 評論:點贊評論、評論隨筆、回複評論
      • [x] 喜歡和贊:點贊隨筆、點贊評論
      • [x] 關注,誰誰關注了你

    腦圖分享

    http://naotu.baidu.com/file/6532431a2e1f0c37c93c5ffd1dd5b49c?token=87690a9bc64fbae1

    分組

    分為三種

    id  name        info
    1	Admin	    系統管理員
    2	CmsAdmin	內容管理員
    3	User	    普通用戶
    

    審計日誌

    大多數表存在如下8個字段,用於記錄行的變化狀態,is_deleted為軟刪除,執行刪除操作時,將其狀態置為true,默認實體類繼承 FullAduitEntity 即可擁有以下8個字段。該設計參考ABP中的實現。FullAduitEntity為泛型,默認id為long類型,FullAduitEntity<Guid>,即可改變主鍵類型,默認LinUser表主鍵long,保持create_user_id,delete_user_id,update_user_id都與LinUser的主鍵相同

    
    id	                bigint
    create_user_id  	bigint
    create_time	        datetime
    is_deleted	        bit
    delete_user_id  	bigint
    delete_time	        datetime
    update_user_id	    bigint
    update_time	        datetime
    
    
    

    相關技術

    • 數據庫相關:ORM:FreeSql+DataBase:MySQL5.6
    • ASP.NET Core3.1+WebAPI+RESTful
    • 簡化對象映射:AutoMapper
    • 身份認證框架:IdentityServer4
    • Json Web令牌:JWT
    • 文檔API:Swagger(Swashbuckle.AspNetCore)
    • 序列化:Newtonsoft.Json
    • 測試框架:Xunit
    • 日誌 Serilog
    • 依賴注入服務AutoFac
    • 通用擴展方法 Z.ExtensionMethods
    • 雲存儲:七牛雲 MQiniu.Core
    • 分佈式事務、EventBus:DotNeteCore.CAP
    • GitHub第三方授權登錄AspNet.Security.OAuth.GitHub
    • QQ第三方授權登錄AspNet.Security.OAuth.QQ
    • Docker
    • Azure DevOps
    • 健康檢查AspNetCore.HealthChecks.UI.Client
    • GitHub Action同步至Gitee

    分層結構(Layers)

    • framework
      • IGeekfan.CAP.MySql:為CAP實現了配合FreeSql的事務一致性擴展
    • identityserver4
      • LinCms.IdentityServer4:使用id4授權登錄
    • src
      • LinCms.Web:接口API(ASP.NET Core)
      • LinCms.Application:應用服務
      • LinCms.Application.Contracts:DTO,數據傳輸對象,應用服務接口
      • LinCms.Infrastructure:基礎設施,數據庫持久性的操作
      • LinCms.Core:該應用的核心,實體類,通用操作類,AOP擴展,分頁對象,基礎依賴對象接口,時間擴展方法,當前用戶信息,異常類,值對象
      • LinCms.Plugins 使用單項目實現某個業務的擴展,不需要主要項目結構,可暫時忽略。
    • test
      • LinCms.Test:對倉儲,應用服務或工具類進行測試

    功能特性

    • [x] Azure Devops CI/CD構建
    • [x] GitHub Action實現 GitHub Gitee代碼同步
    • [x] .Net Core結合AspNetCoreRateLimit實現限流
    • [x] 方法級別權限控制
    • 社交賬號管理:支持多種第三社交賬號登錄,不干涉原用戶數據,實現第三方賬號管理
    • 多語言
    • [x] 全局敏感詞處理
    • 日誌記錄,方便線上排查錯誤
    • [ ] 支持多種數據庫,並測試,
      • [x] Mysql
      • [ ] Postgresql
      • [ ] Sql Server
      • [ ] SQlite

    產品設計-評論模塊的設計

    下面我們來設計一個評論模塊,需要注意的是,一個評論模塊也有不同的方式。從展示形式,排序規則,按鈕功能設計,操作等方式都有詳細的分析設計,大家可以參考who shi pm 中的文章http://www.woshipm.com/pd/3548139.html,這裏主要講解下展式方式中的主題式的應用。

    1.主題式

    相信很多人都刷過抖音,他的評論主題式的強化版。

    特點為前三個是熱門評論(喜歡最多的評論),將評論分為二級,第一級採用時間倒序,第二級按照時間正序,有助於理解上下文關係。

    可以總結為如下功能點

    用戶操作:

    • [x] 評論隨筆(內容支持超鏈接、emoji)
    • [x] 點贊評論/取消點贊
    • [x] 回複評論
    • [x] 刪除自己的評論

    運營操作:

    • [x] 審核通過/拉黑評論
    • [x] 刪除任何評論
    • [x] 拉黑后的显示邏輯。(保留當前區塊、显示內容為:該評論因違規被拉黑)
    • 刪除:(如果是二級評論,直接軟刪除,如果是一級評論,軟刪除子評論和當前評論-需要提前提醒用戶)

    交互設計

    • 評論的字數長度(500)、emoji。
    • 點贊交互-動畫、消息通知/推送
    • 評論區域元素,需要有明確可點擊的區域,會跳轉到哪個地方。

    優化

    • 精選或者叫置頂評論
    • 該文章是否開放評論功能。
    • 熱門評論(點贊最多的評論)
    • 標註哪些評論是作者,標準哪些評論被用戶點贊。
    • @邏輯,emoji,舉報等

    2 平鋪式

    特點是不區分子父節點關係,比如現在博客園(大多數的主題),微信朋友圈,github。
    不過博客園,github,回復時,可選擇引用回復,方便用戶理解上下文關係。

    3.蓋樓式

    這種方式有點這種感覺 》>-,即.net core中間件請求方式。當回復越來越多時,显示的效果就越誇張。一層套一層的显示上下文的關係。下圖是網易新聞的評論效果。

    排行榜見解

    排行榜從心理學上分析,主要從四個方面影響着您:尋找權威 、參与比較 、關注主流 、自我確認。

    如何設計一個簡單的排行榜呢。。

    在一個博客隨筆中,我們設計一個3天、七天(周榜)、30天(月榜)、全部的榜單。以瀏覽量(權重1)、點贊量(20)、評論量(30)。權重可自己定義。

    1.默認取最新的隨筆

    前台傳create_time時,使用如下sql

    select * from `blog_article` order by create_time desc;
    

    2.傳排序方式為最近n天的熱榜時。

    參數:THREE_DAYS_HOTTEST(三天)、WEEKLY_HOTTEST(七天)、MONTHLY_HOTTEST(一個月)、HOTTEST(全部)

    mysql 查詢當前日期時間前三天數據

    select date_sub(now() ,interval 3 day);
    

    根據權重查詢

    select * from `blog_article` a 
    where a.`create_time`>(select date_sub(now() ,interval 3 day))
    order by (a.`view_hits` + a.`likes_quantity` * 20 + a.`comment_quantity` * 30) DESC, a.`create_time` DESC
    
    

    更多參考

    • 評論區如何設計?
    • 萬字長文深度分析:產品排行榜的設計和玩法
    • 想知道誰是你的最佳用戶?基於Redis實現排行榜周期榜與最近N期榜

    lin-cms 開源地址分享

    • 後端接口 https://github.com/luoyunchong/lin-cms-dotnetcore
    • 管理後台UI https://github.com/luoyunchong/lin-cms-vue
    • 前端UIhttps://github.com/luoyunchong/lin-cms-vvlog

    Demo

    • 用戶端 lin-cms-vvlog https://vvlog.baimocore.cn

      • 普通用戶:710277267@qq.com
      • 密碼:123qwe
    • 管理員 lin-cms-vue https://cms.baimocore.cn/

      • 管理員: admin
      • 密碼:123qwe

    預覽圖

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • Flutter學習筆記(36)–常用內置動畫,Flutter學習筆記(36)–常用內置動畫

    Flutter學習筆記(36)–常用內置動畫,Flutter學習筆記(36)–常用內置動畫

    如需轉載,請註明出處:Flutter學習筆記(36)–常用內置動畫

    Flutter給我們提供了很多而且很好用的內置動畫,這些動畫僅僅需要簡單的幾行代碼就可以實現一些不錯的效果,Flutter的動畫分為補間動畫和基於物理的動畫,基於物理的動畫我們先不說。

    補間動畫很簡單,Android裏面也有補間動畫,就是給UI設置初始的狀態和結束狀態,經過我們定義的一段時間,系統去幫助我們實現開始到結束的過渡變化,這就是補間動畫。

    今天我們要看的Flutter的內置動畫就是補間動畫,根據Flutter提供的動畫組件,我們去設置初始、結尾的狀態,並且定義一下這個變化過程所需要的時間,再經過系統的處理(其實就是setState())來達到動畫的效果。

    接下來我們會寫一下常用的內置動畫組件,並且提供一下動畫效果的gif,方便大家更直觀的去理解。

    • AnimatedContainer

    看到Container我們就會知道這是一個帶有動畫屬性的容器組件,這個組件可以定義大小、顏色等屬性,那麼我們是不是就可以給這個組件設置初始和結束的大小及顏色的屬性值,然後通過系統來幫助我們來補足中間過程的動畫呢?

    答案是可以的,下面看一下demo和動畫效果:

    class _MyHomePageState extends State<MyHomePage> {
      double _width = 100.0;
      double _height = 100.0;
      Color _color = Colors.red;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: AnimatedContainer(
            width: _width,
            height: _height,
            duration: Duration(seconds: 2),
            color: _color,
            curve: Curves.bounceInOut,
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                _width = 300.0;
                _height = 300.0;
                _color = Colors.green;
              });
            },
            tooltip: 'Increment',
            child: Icon(Icons.adjust),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }

     

    demo很簡單,就是先定義好組件初始的大小和顏色,點擊按鈕,在按鈕事件裏面去更改大小和顏色的屬性值。這裏唯一需要特別說一下就是curve這個屬性。

    curve指的是動畫曲線?我開始的時候不理解這個動畫曲線是什麼意思,後來看了一組圖之後,豁然開朗。demo裏面curve我們用的是Curves.bounceInOut。如下:

     

    它其實就是一個非線性的動畫的變化形式(變化過程)也可以理解為就是一種函數,也不知道這麼說大家能不能理解。

    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4}

     

    這裡是每一種curve曲線的表現形式,大家可以看看,也可以在demo裏面多嘗試,或者可以看另一篇博客,有動畫曲線Curves 效果。

    • AnimatedCrossFade

    Flutter中文網:一個widget,在兩個孩子之間交叉淡入,並同時調整他們的尺寸。

    個人說明:CrossFade,故名思意,淡入淡出,AnimatedCrossFade組件包含兩個子widget,一個firstChild一個secondChild,這兩個組件根據狀態(我們自己定義的一個標識)改變狀態,

    一個淡入,一個淡出,同時改變大小或顏色等屬性。

    class _MyHomePageState extends State<MyHomePage> {
      bool _showFirst = true;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: AnimatedCrossFade(
            firstChild: Container(
              width: 100,
              height: 100,
              color: Colors.red,
              alignment: Alignment.center,
              child: Text('firstChild'),
            ),
            secondChild: Container(
              width: 200,
              height: 200,
              color: Colors.green,
              alignment: Alignment.center,
              child: Text('secondChild'),
            ),
            duration: Duration(seconds: 3),
            crossFadeState:
                _showFirst ? CrossFadeState.showFirst : CrossFadeState.showSecond,
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                _showFirst = false;
              });
            },
            tooltip: 'Increment',
            child: Icon(Icons.adjust),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }

     

    • Hero

    Hero常用於頁面跳轉的過長動畫,比如電商App有一個商品列表,列表的每個item都有一張縮略圖,點擊會跳轉到詳情頁面,在Flutter中將圖片從一個路由飛到另一個路由稱為hero動畫,儘管相同的動作有時也稱為 共享元素轉換

    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Hero(
            tag: 'heroTag',
            child: ClipOval(
              child: Image.asset(
                'images/banner.png',
                width: 60,
                height: 60,
                fit: BoxFit.cover,
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                Navigator.push(context, MaterialPageRoute(builder: (_) {
                  return new HeroPage();
                }));
              });
            },
            tooltip: 'Increment',
            child: Icon(Icons.adjust),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }

     

    詳情頁面:

    import 'package:flutter/material.dart';
    
    class HeroPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'HeroPage',
          home: Scaffold(
            appBar: AppBar(
              title: Text('HeroPage'),
            ),
            body: Center(
              child: GestureDetector(
                child: Hero(
                  tag: 'heroTag',
                  child: ClipOval(
                    child: Image.asset(
                      'images/banner.png',
                      width: 300,
                      height: 300,
                      fit: BoxFit.cover,
                    ),
                  ),
                ),
                onTap: () {
                  Navigator.pop(context);
                },
              ),
            ),
          ),
        );
      }
    }

     

    注:特彆強調一下,為了將兩個頁面的元素關聯起來,hero有個tag標識,前後兩個頁面的tag標識必須一樣,不然的話元素是關聯不起來的,也就意味着不會產生hero動畫。

    1.同級tag不允許相同。

    2.前後頁面想要有hero動畫,tag必須相同。

    3.前後關聯起來的hero組件,其各自內部的child組件不是必須一樣的,就是說前面的hero的子組件可以是image,後面的hero的子組件可以是image以外的其他組件。

    • AnimatedBuilder

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 3), vsync: this);
        _animation =
            new Tween(begin: 0.0, end: 200.0).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: AnimatedBuilder(
              animation: _animation,
              builder: (BuildContext context, Widget child) {
                return Center(
                  child: Container(
                    color: Colors.red,
                    width: _animation.value,
                    height: _animation.value,
                    child: child,
                  ),
                );
              },
            ));
      }
    }

    AnimationController:動畫控制器(定義動畫過程時長)。

    Animation:動畫變化區間值(也可以說是開始和結束的關鍵幀值),demo里定義的值為初始0,結束200。

    _animation.value:關鍵幀值是0和200,_animation.value的值為0–200之間連續變化的值(0-1-2-3-…-198-199-200)。

    • DecoratedBoxTransition

    Decortated可以給容器添加各種外觀裝飾,比如增加圓角、陰影等裝飾。DecoratedBox的動畫版本,可以給它的Decoration不同屬性使用動畫

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<Decoration> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 3), vsync: this);
        _animation = DecorationTween(
                begin: BoxDecoration(
                    borderRadius: BorderRadius.all(Radius.circular(0.0)),
                    color: Colors.red),
                end: BoxDecoration(
                    borderRadius: BorderRadius.all(Radius.circular(30.0)),
                    color: Colors.green))
            .animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: DecoratedBoxTransition(
              decoration: _animation,
              child: Container(
                width: 100,
                height: 100,
              ),
            ),
          ),
        );
      }
    }

    • FadeTransition

    透明度變化動畫,因為透明度也是在0-1之間變化的,所以animation就還繼續用double類型的就可以了。

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 1.0, end: 0.0).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: FadeTransition(
              opacity: _animation,
              child: Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: Colors.red,
                ),
              ),
            ),
          ),
        );
      }
    }

    • RotationTransition

    旋轉動畫,對widget使用旋轉動畫 1~360°(Tween(begin: 0.0, end: 1.0))這裏的0-1指的是0°-360°

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: RotationTransition(
              turns: _animation,
              child: Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: Colors.red,
                ),
                child: Center(child: Text('data')),
              ),
            ),
          ),
        );
      }
    }

    • ScaleTransition

    縮放動畫,Tween(begin: 1.0, end: 0.2)指的是原大小的倍數,demo里是由原大小縮小到原來的0.2倍。

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 1.0, end: 0.2).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: ScaleTransition(
              scale: _animation,
              child: Container(
                width: 200,
                height: 200,
                decoration: BoxDecoration(
                  color: Colors.red,
                ),
                child: Center(child: Text('data')),
              ),
            ),
          ),
        );
      }
    }

    • SizeTransition

    僅一個方向進行縮放

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 1.0, end: 0.2).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: SizeTransition(
              axis: Axis.horizontal,
              sizeFactor: _animation,
              child: Center(
                child: Container(
                  width: 200,
                  height: 200,
                  decoration: BoxDecoration(
                    color: Colors.red,
                  ),
                  child: Center(child: Text('data')),
                ),
              ),
            ),
          ),
        );
      }
    }

     

    以上!有任何疑問歡迎留言!

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 卡爾大火極具破壞性 加州史上第9大

    摘錄自2018年7月31日中央社加州報導

    美國加州北部野火「卡爾大火」(Carr Fire)已奪走至少六條人命,在今夏極度乾燥的美西地區數十起火災中災情最嚴重,森林防火廳官員表示,據信這是加州史上第九大破壞性大火。

    美聯社報導,加州森林防火廳(CalFire)發言人麥克林(Scott Mclean)表示,這場大火目前據信是加州史上第九大破壞性大火,燒毀至少818棟房屋和311棟附屬建築物,並造成165棟房屋受損。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 地球2017年體檢報告出爐 溫室氣體突破80萬年記錄

    摘錄自2018年8月5日東森新聞報導

    美國國家海洋暨大氣總署(NOAA)1日發布了2017年的氣候狀況報告,顯示地球的溫室氣體突破80萬年來的紀錄,這份報告由超過500名全球科學家共同研究,測量2017年的溫度、降水和天氣現象。美國氣象學會公報主編羅森菲爾德(Jeff Rosenfeld)說,這個報告就像地球的身體檢查一樣。

    「每年都會檢查同樣的項目,這些數據可以讓我們了解長時間來看,什麼是正常狀況,什麼是異常」,羅森菲爾德說。根據這份長達300頁的報告,2017年地球表面的全球平均二氧化碳濃度是405ppm,比2016年高出2.2ppm,是近代大氣測量記錄和80萬年的冰芯紀錄中最高的。

    報告指出,自1960年代以來,二氧化碳濃度已經成長近4倍,海平面高度也創下新高,平均每年上升1.2英吋(3.05公分),現在的海平面高度已經比1993年高約3英吋(7.62公分)。此外,2017年是有記錄以來最熱的非聖嬰年,巴基斯坦甚至在5月份時達到目前世界記錄的5月最高溫攝氏53.5度。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 三菱汽車計畫到2020年 擴增環保車款比重至20%

    三菱汽車計畫到2020年 擴增環保車款比重至20%

    據日經新聞11日報導,因看好今後日本國內快速充電器可望日益普及,汽車大廠三菱汽車(Mitsubishi Motors)計畫擴增插電式油電混合車(PHV)的車種數量;並計畫投資約50億日圓改良主力生產據點「名古屋製作所」的產線,目標為在2015年5月底前將PHV年產能倍增至6萬台的水準、並計劃於2015年開始出口至北美進行販售。

    目前PHV等環保車款佔三菱汽車整體產量比重僅2%,但三菱汽車計劃於2020年度將其比重提高至20%的水準。三菱汽車已於2013年1月開賣PHV車種「Outlander PHEV」,目前日本國內累計銷售量為1萬台、歐洲市場累銷也約1萬台。而除了Outlander車種之外,三菱汽車也計畫推出「Pajero」及「RVR」車款的PHV。

    Outlander PHEV

    日經新聞曾於2013年7月報導指出,豐田汽車(Toyota)、日產汽車(Nissan)、本田汽車(Honda)及三菱汽車(Mitsubishi Motors)等日本4大車廠計畫攜手合作,計畫將日本國內使用於電動車等車種的快速充電器設置數量擴增至4,000座以上,將較現行提高1倍。以期望藉由擴充充電設備,擴大電動車及插電式油電混合車(PHV)市場。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 傳Tesla擬在中國建免費電動車充電站

    據國外媒體報導,美國電動汽車生產商特斯拉(Tesla)正準備在中國建立一批免費充電站,從而支援公司汽車的長距離行駛,例如從北京開到上海。特斯拉方面已開始同物業主及電力服務提供商進行交流,但尚未透露這些充電站何時可以投入使用。
     
    特斯拉在美國有一套類似的充電站網絡,靠電池行駛的特斯拉「Model S」型轎車能夠通過免費充電站的支援,橫穿美國。該公司目前還在為歐洲建設類似的網絡。
     
    中國政府一直在推廣電動汽車銷售,作為解決當地汽車尾氣污染的措施之一。但這一舉措並不怎么成功,主要是因為充電設施建設的難度太大。

    特斯拉目前在中國北京有一處展廳,以及一個服務與銷售網點。該公司打算將上海作為下一個目標,並且大膽拓展業務。目前中國購車者可以訂購特斯拉「Model S」與即將發布的「Model X」,預付款為25萬元人民幣,約合4.1萬美元。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 英最大單電動車合約易主 比亞迪遺憾出局

    據《英國每日電訊》昨(17)日報導,比亞迪推出倫敦首支全電動出租車隊的計劃受挫,倫敦第二大出租車服務商綠番茄出租車公司(Greentomatocars)表示,決定不再和比亞迪進行合作,而改為準備測試現代汽車的一款燃料電池汽車。

    這是英國史上最大一筆電動車交易。去年曾有傳言稱,倫敦充電樁設施不全,導致這批出租車無法按時上路。

    比亞迪已向綠番茄公司交付了20輛E6汽車,這批車輛隨后將由另一家運營商Thriev接盤。Thriev已在倫敦建造兩個快速充電站,可為E6汽車在兩個小時內完全完電。據比亞迪介紹,E6在充電完畢之后能續航186英里,比倫敦市場上日產聆風的124英里要高出不少。

    Thriev公司發言人還表示,公司將在18-24個月內打造一支由1000輛電動汽車所組成的電動車隊。Thriev還與英國天然氣集團進行了接洽,商談如何在倫敦建立多個電動汽車充電站事宜。

    倫敦市長辦公室曾表示,出租車貢獻了倫敦所有尾氣排放的逾三分之一,推廣零排放出租車是政府將英國打造成重要電動汽車市場舉措的一部分。倫敦市長鮑里斯•約翰遜也設定了全市出租車必須在2018年前實現零排放的目標。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 日企推水陸兩用電動汽車FOMM Concept One

    日企推水陸兩用電動汽車FOMM Concept One

    目前限制全球電動汽車發展的主要瓶頸,在於電池成本和續航力問題。日前有日本廠商推出一款名為FOMM Concept One的超小型電動車,雖說續航力僅有100km,但是卻有另外一點吸引人的地方,那就是如果碰到水災還能變成小艇,且價格非常親民。據說不到日幣100萬圓(約合新台幣29.7萬元)。

    這款小型電動汽車,雖然不是專門的水陸兩用車,但遇到洪水等緊急情況,可以在水面漂浮24小時,也能在水面上以時速3.8公里左右的速度移動。車內裝有可拆卸的電池,充滿電後最多可在陸地上行駛100公里。這款4人座汽車全長約2.5公尺,重量僅460公斤,計劃從明年10月開始先在水災較多的泰國銷售。

    FOMM公司由日本大同工業(DAIDO Kogyo)、日本特殊陶業(NGK)所共同建立,FOMM Concept One為幾家公司合作下的第一個產物。根據FOMM的規劃, Concept One的開發成本不低,但2014年4-6月將有一比龐大資金注入,倘若一切都順利預計2015年9月將可在泰國進行量產,至於第一年銷售量目標是5000輛。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 完美解決asp.net core 3.1 兩個AuthenticationScheme(cookie,jwt)共存在一個項目中,基於領域驅動設計(DDD)超輕量級快速開發架構

    完美解決asp.net core 3.1 兩個AuthenticationScheme(cookie,jwt)共存在一個項目中,基於領域驅動設計(DDD)超輕量級快速開發架構

    內容

    在我的項目中有mvc controller(view 和 razor Page)同時也有webapi,那麼就需要網站同時支持2種認證方式,web頁面的需要傳統的cookie認證,webapi則需要使用jwt認證方式,兩種默認情況下不能共存,一旦開啟了jwt認證,cookie的登錄界面都無法使用,原因是jwt是驗證http head “Authorization” 這屬性.所以連login頁面都無法打開.

    解決方案

    實現web通過login頁面登錄,webapi 使用jwt方式獲取認證,支持refreshtoken更新過期token,本質上背後都使用cookie認證的方式,所以這樣的結果是直接導致token沒用,認證不是通過token唯一的作用就剩下refreshtoken了

    通過nuget 安裝組件包

    Microsoft.AspNetCore.Authentication.JwtBearer

    下面是具體配置文件內容

    //Jwt Authentication
          services.AddAuthentication(opts =>
          {
            //opts.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            //opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
          })
          //這裡是關鍵,添加一個Policy來根據http head屬性或是/api來確認使用cookie還是jwt chema
            .AddPolicyScheme(settings.App, "Bearer or Jwt", options =>
            {
              options.ForwardDefaultSelector = context =>
              {
                var bearerAuth = context.Request.Headers["Authorization"].FirstOrDefault()?.StartsWith("Bearer ") ?? false;
                // You could also check for the actual path here if that's your requirement:
                // eg: if (context.HttpContext.Request.Path.StartsWithSegments("/api", StringComparison.InvariantCulture))
                if (bearerAuth)
                  return JwtBearerDefaults.AuthenticationScheme;
                else
                  return CookieAuthenticationDefaults.AuthenticationScheme;
              };
            })
    //這裏和傳統的cookie認證一致       .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
           {
             options.LoginPath = "/Identity/Account/Login";
             options.LogoutPath = "/Identity/Account/Logout";
             options.AccessDeniedPath = "/Identity/Account/AccessDenied";
             options.Cookie.Name = "CustomerPortal.Identity";
             options.SlidingExpiration = true;
             options.ExpireTimeSpan = TimeSpan.FromSeconds(10); //Account.Login overrides this default value
           })
            .AddJwtBearer(x =>
          {
            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            {
              ValidateIssuerSigningKey = true,
              IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Jwt:Key"])),
              ValidateIssuer = true,
              ValidateAudience = true,
              ValidateLifetime = true,
              ValidIssuer = Configuration["Jwt:Issuer"],
              ValidAudience = Configuration["Jwt:Issuer"],
            };
          });
    
     //這裏需要對cookie做一個配置
          services.ConfigureApplicationCookie(options =>
          {
            // Cookie settings
            options.Cookie.Name = settings.App;
            options.Cookie.HttpOnly = true;
            options.ExpireTimeSpan = TimeSpan.FromSeconds(10);
            options.LoginPath = "/Identity/Account/Login";
            options.LogoutPath = "/Identity/Account/Logout";
            options.Events = new CookieAuthenticationEvents()
            {
              OnRedirectToLogin = context =>
              {
               //這裏區分當訪問/api 如果cookie過期那麼 不重定向到login登錄界面
                if (context.Request.Path.Value.StartsWith("/api"))
                {
                  context.Response.Clear();
                  context.Response.StatusCode = 401;
                  return Task.FromResult(0);
                }
                context.Response.Redirect(context.RedirectUri);
                return Task.FromResult(0);
              }
            };
            //options.AccessDeniedPath = "/Identity/Account/AccessDenied";
          });        

    startup.cs

    下面userscontroller 認證方式

    重點:我簡化了refreshtoken的實現方式,原本規範的做法是通過第一次登錄返回一個token和一個唯一的隨機生成的refreshtoken,下次token過期后需要重新發送過期的token和唯一的refreshtoken,同時後台還要比對這個refreshtoken是否正確,也就是說,第一次生成的refreshtoken必須保存到數據庫里,這裏我省去了這個步驟,這樣做是不嚴謹的的.

    [ApiController]
      [Route("api/users")]
      public class UsersEndpoint : ControllerBase
      {
        private readonly ILogger<UsersEndpoint> _logger;
        private readonly ApplicationDbContext _context;
        private readonly UserManager<ApplicationUser> _manager;
        private readonly SignInManager<ApplicationUser> _signInManager;
        private readonly SmartSettings _settings;
        private readonly IConfiguration _config;
    
        public UsersEndpoint(ApplicationDbContext context,
          UserManager<ApplicationUser> manager,
          SignInManager<ApplicationUser> signInManager,
          ILogger<UsersEndpoint> logger,
          IConfiguration config,
          SmartSettings settings)
        {
          _context = context;
          _manager = manager;
          _settings = settings;
          _signInManager = signInManager;
          _logger = logger;
          _config = config;
        }
        [Route("authenticate")]
        [AllowAnonymous]
        [HttpPost]
        public async Task<IActionResult> Authenticate([FromBody] AuthenticateRequest model)
        {
          try
          {
            //Sign user in with username and password from parameters. This code assumes that the emailaddress is being used as the username. 
            var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, true, true);
    
            if (result.Succeeded)
            {
              //Retrieve authenticated user's details
              var user = await _manager.FindByNameAsync(model.UserName);
    
              //Generate unique token with user's details
              var accessToken = await GenerateJSONWebToken(user);
              var refreshToken = GenerateRefreshToken();
              //Return Ok with token string as content
              _logger.LogInformation($"{model.UserName}:JWT登錄成功");
              return Ok(new { accessToken = accessToken, refreshToken = refreshToken });
            }
            return Unauthorized();
          }
          catch (Exception e)
          {
            return StatusCode(500, e.Message);
          }
        }
        [Route("refreshtoken")]
        [AllowAnonymous]
        [HttpPost]
        public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest model)
        {
          var principal = GetPrincipalFromExpiredToken(model.AccessToken);
          var nameId = principal.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
          var user = await _manager.FindByNameAsync(nameId);
          await _signInManager.RefreshSignInAsync(user);
    
            //Retrieve authenticated user's details
            //Generate unique token with user's details
            var accessToken = await GenerateJSONWebToken(user);
            var refreshToken = GenerateRefreshToken();
            //Return Ok with token string as content
            _logger.LogInformation($"{user.UserName}:RefreshToken");
            return Ok(new { accessToken = accessToken, refreshToken = refreshToken });
    
    
        }
    
        private async Task<string> GenerateJSONWebToken(ApplicationUser user)
        {
          //Hash Security Key Object from the JWT Key
          var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
          var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
    
          //Generate list of claims with general and universally recommended claims
          var claims = new List<Claim>  {
               new Claim(ClaimTypes.NameIdentifier, user.UserName),
               new Claim(ClaimTypes.Name, user.UserName),
                    new Claim(JwtRegisteredClaimNames.Sub, user.Email),
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                    new Claim(ClaimTypes.NameIdentifier, user.Id),
                    //添加自定義claim
                    new Claim(ClaimTypes.GivenName, string.IsNullOrEmpty(user.GivenName) ? "" : user.GivenName),
                    new Claim(ClaimTypes.Email, user.Email),
                    new Claim("http://schemas.microsoft.com/identity/claims/tenantid", user.TenantId.ToString()),
                    new Claim("http://schemas.microsoft.com/identity/claims/avatars", string.IsNullOrEmpty(user.Avatars) ? "" : user.Avatars),
                    new Claim(ClaimTypes.MobilePhone, user.PhoneNumber)
          };
          //Retreive roles for user and add them to the claims listing
          var roles = await _manager.GetRolesAsync(user);
          claims.AddRange(roles.Select(r => new Claim(ClaimsIdentity.DefaultRoleClaimType, r)));
          //Generate final token adding Issuer and Subscriber data, claims, expriation time and Key
          var token = new JwtSecurityToken(_config["Jwt:Issuer"]
              , _config["Jwt:Issuer"],
              claims,
              null,
              expires: DateTime.Now.AddDays(30),
              signingCredentials: credentials
          );
    
          //Return token string
          return new JwtSecurityTokenHandler().WriteToken(token);
        }
    
        public string GenerateRefreshToken()
        {
          var randomNumber = new byte[32];
          using (var rng = RandomNumberGenerator.Create())
          {
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
          }
        }
    
        private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
        {
          var tokenValidationParameters = new TokenValidationParameters
          {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_config["Jwt:Key"])),
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidIssuer = _config["Jwt:Issuer"],
            ValidAudience = _config["Jwt:Issuer"],
          };
    
          var tokenHandler = new JwtSecurityTokenHandler();
          SecurityToken securityToken;
          var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
          var jwtSecurityToken = securityToken as JwtSecurityToken;
          if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
          {
            throw new SecurityTokenException("Invalid token");
          }
    
          return principal;
        }
    ....
    }
    }

    ControllerBase

    下面是測試

    獲取token

     refreshtoken

     

    獲取數據

     

     這裏獲取數據的時候,其實可以不用填入token,因為調用authenticate或refreshtoken是已經記錄了cookie到客戶端,所以在postman測試的時候都可以不用加token也可以訪問

     推廣一下我的開源項目

    基於領域驅動設計(DDD)超輕量級快速開發架構

    https://www.cnblogs.com/neozhu/p/13174234.html

    源代碼

    https://github.com/neozhu/smartadmin.core.urf

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!