分類: 3C資訊

  • 堡壘機的核心武器:WebSSH錄像實現

    堡壘機的核心武器:WebSSH錄像實現

    WebSSH終端錄像的實現終於來了

    前邊寫了兩篇文章和深入介紹了終端錄製工具Asciinema,我們已經可以實現在終端下對操作過程的錄製,那麼在WebSSH中的操作該如何記錄並提供後續的回放審計呢?

    一種方式是文章最後介紹的自動錄製審計日誌的方法,在主機上添加個腳本,每次連接自動進行錄製,但這樣不僅要在每台遠程主機添加腳本,會很繁瑣,而且錄製的腳本文件都是放在遠程主機上的,後續播放也很麻煩

    那該如何更好處理呢?下文介紹一種優雅的方式來實現,核心思想是不通過錄製命令進行錄製,而在Webssh交互執行的過程中直接生成可播放的錄像文件

    設計思路

    通過上邊兩篇文章的閱讀,我們已經知道了Asciinema錄像文件主要由兩部分組成:header頭和IO流數據

    header頭位於文件的第一行,定義了這個錄像的版本、寬高、開始時間、環境變量等參數,我們可以在websocket連接創建時將這些參數按照需要的格式寫入到文件

    header頭數據如下,只有開頭一行,是一個字典形式

    {"version": 2, "width": 213, "height": 55, "timestamp": 1574155029.1815443, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}, "title": "ops-coffee"}

    整個錄像文件除了第一行的header頭部分,剩下的就都是輸入輸出的IO流數據,從websocket連接建立開始,隨着操作的進行,IO流數據是不斷增加的,直到整個websocket長連接的結束,那就需要在整個WebSSH交互的過程中不斷的往錄像文件追加輸入輸出的內容

    IO流數據如下,每一行一條,列表形式,分別表示操作時間,輸入或輸出(這裏我們為了方便就寫固定字符串輸出),IO數據

    [0.2341010570526123, "o", "Last login: Tue Nov 19 17:11:30 2019 from 192.168.105.91\r\r\n"]

    似乎很完美,按照上邊的思路錄像文件就應該沒有問題了,但還有一些細節需要處理

    首先是需要歷史連接列表,在這個列表裡可以看到什麼時間,哪個用戶連接了哪台主機,當然也需要提供回放功能,新建一張表來記錄這些信息

    class Record(models.Model):
        create_time = models.DateTimeField(auto_now_add=True, verbose_name='創建時間')
    
        host = models.ForeignKey(Host, on_delete=models.CASCADE, verbose_name='主機')
        user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用戶')
    
        filename = models.CharField(max_length=128, verbose_name='錄像文件名稱')
    
        def __str__(self):
            return self.host

    其次還需要考慮的一個問題是header和後續IO數據流要寫入同一個文件,這就需要在整個websocket的連接過程中有一個固定的文件名可被讀取,這裏我使用了主機+用戶+當前時間作為文件名,同一用戶在同一時間不能多次連接同一主機,這樣可保證文件名不重複,同時避免操作寫入錯誤的錄像文件,文件名在websocket建立時初始化

    def __init__(self, host, user, websocket):
        self.host = host
        self.user = user
    
        self.time = time.time()
        self.filename = '%s.%s.%d.cast' % (host, user, self.time)

    IO流數據會持續不斷的寫入文件,這裏以一個獨立的方法來處理寫入

    def record(self, type, data):
        RECORD_DIR = settings.BASE_DIR + '/static/record/'
        if not os.path.isdir(RECORD_DIR):
            os.makedirs(RECORD_DIR)
    
        if type == 'header':
            Record.objects.create(
                host=Host.objects.get(id=self.host),
                user=self.user,
                filename=self.filename
            )
    
            with open(RECORD_DIR + self.filename, 'w') as f:
                f.write(json.dumps(data) + '\n')
        else:
            iodata = [time.time() - self.time, 'o', data]
            with open(RECORD_DIR + self.filename, 'a', buffering=1) as f:
                f.write((json.dumps(iodata) + '\n'))

    record接收兩個參數type和data,type標識本次寫入的是header頭還是IO流,data則是具體的數據

    header只需要執行一次寫入,所以將其放在ssh的connect方法中,只在ssh連接建立時執行一次,在執行header寫入時同時往數據庫插入新的歷史記錄數據

    調用record方法寫入header

    def connect(self, host, port, username, authtype, password=None, pkey=None,
                term='xterm-256color', cols=80, rows=24):
        ...
    
        # 構建錄像文件header
        self.record('header', {
            "version": 2,
            "width": cols,
            "height": rows,
            "timestamp": self.time,
            "env": {
                "SHELL": "/bin/bash",
                "TERM": term
            },
            "title": "ops-coffee"
        })

    IO流數據則需要與返回給前端的數據保持一致,這樣就能保證前端显示什麼錄像就播放什麼了,所以所有需要返回前端數據的地方都同時寫入錄像文件即可

    調用record方法寫入io流數據

    def connect(self, host, port, username, authtype, password=None, pkey=None,
                term='xterm-256color', cols=80, rows=24):
        ...
    
        # 連接建立一次,之後交互數據不會再進入該方法
        for i in range(2):
            recv = self.ssh_channel.recv(65535).decode('utf-8', 'ignore')
            message = json.dumps({'flag': 'success', 'message': recv})
            self.websocket.send(message)
    
            self.record('iodata', recv)
    
    ...
    
    def _ssh_to_ws(self):
        try:
            with self.lock:
                while not self.ssh_channel.exit_status_ready():
                    data = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
                    if len(data) != 0:
                        message = {'flag': 'success', 'message': data}
                        self.websocket.send(json.dumps(message))
    
                        self.record('iodata', data)
                    else:
                        break
        except Exception as e:
            message = {'flag': 'error', 'message': str(e)}
            self.websocket.send(json.dumps(message))
            self.record('iodata', str(e))
            
            self.close()

    由於命令執行與返回都是多線程的操作,這就會導致在寫入文件時出現文件亂序影響播放的問題,典型的操作有vim、top等,通過加鎖self.lock可以順利解決

    最後歷史記錄頁面,當用戶點擊播放按鈕時,調用js彈出播放窗口

    <div class="modal fade" id="modalForm">
      <div class="modal-dialog modal-lg">
        <div class="modal-content">
          <div class="modal-body" id="play">
          </div>
        </div>
      </div>
    </div>
    
    // 播放錄像
    function play(host,user,time,file) {
      $('#play').html(
        '<asciinema-player id="play" title="WebSSH Record" author="ops-coffee.cn" author-url="https://ops-coffee.cn" author-img-url="/static/img/logo.png" src="/static/record/'+file+'" speed="3" '+
        'idle-time-limit="2" poster="data:text/plain,\x1b[1;32m'+time+
        '\x1b[1;0m用戶\x1b[1;32m'+user+
        '\x1b[1;0m連接主機\x1b[1;32m'+host+
        '\x1b[1;0m的錄像記錄"></asciinema-player>'
      )
    
      $('#modalForm').modal('show');
    }

    asciinema-player標籤的詳細參數介紹可以看這篇文章

    演示與總結

    在寫入文件的方案中,考慮了實時寫入和一次性寫入,實時寫入就像上邊這樣,所有的操作都會實時寫入錄像文件,好處是錄像不丟失,且能在操作的過程中進行實時的播放,缺點也很明顯,就是會頻繁的寫文件,造成IO開銷

    一次性寫入可以在用戶操作的過程中將錄像數據寫入內存,在websocket關閉時一次性異步寫入到文件中,這種方案在最終寫入文件時可能因為種種原因而失敗,從而導致錄像丟失,還有個缺點是當你WebSSH操作時間過長時,會導致內存的持續增加

    兩種方案一種是對磁盤的消耗另一種是對內存的消耗,各有利弊,當然你也可以考慮批量寫入,例如每分鐘寫一次文件,一分鐘之內的保存在內存中,平衡內存和磁盤的消耗,期待你的實現

    相關文章推薦閱讀:

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

  • NetCore3.0 文件上傳與大文件上傳的限制

    NetCore文件上傳兩種方式

      NetCore官方給出的兩種文件上傳方式分別為“緩衝”、“流式”。我簡單的說說兩種的區別,

      1.緩衝:通過模型綁定先把整個文件保存到內存,然後我們通過IFormFile得到stream,優點是效率高,缺點對內存要求大。文件不宜過大。

      2.流式處理:直接讀取請求體裝載后的Section 對應的stream 直接操作strem即可。無需把整個請求體讀入內存,

    以下為官方微軟說法

    緩衝

      整個文件讀入 IFormFile,它是文件的 C# 表示形式,用於處理或保存文件。 文件上傳所用的資源(磁盤、內存)取決於併發文件上傳的數量和大小。 如果應用嘗試緩衝過多上傳,站點就會在內存或磁盤空間不足時崩潰。 如果文件上傳的大小或頻率會消耗應用資源,請使用流式傳輸。

    流式處理   

      從多部分請求收到文件,然後應用直接處理或保存它。 流式傳輸無法顯著提高性能。 流式傳輸可降低上傳文件時對內存或磁盤空間的需求。

    文件大小限制

      說起大小限制,我們得從兩方面入手,1應用服務器Kestrel 2.應用程序(我們的netcore程序),

    1.應用服務器Kestre設置

      應用服務器Kestrel對我們的限制主要是對整個請求體大小的限制通過如下配置可以進行設置(Program -> CreateHostBuilder),超出設置範圍會報 BadHttpRequestException: Request body too large 異常信息

    public static IHostBuilder CreateHostBuilder(string[] args) =>
               Host.CreateDefaultBuilder(args)
                   .ConfigureWebHostDefaults(webBuilder =>
                   {
                       webBuilder.ConfigureKestrel((context, options) =>
                       {
                           //設置應用服務器Kestrel請求體最大為50MB
                           options.Limits.MaxRequestBodySize = 52428800;
                       });
                       webBuilder.UseStartup<Startup>();
    });

    2.應用程序設置

      應用程序設置 (Startup->  ConfigureServices) 超出設置範圍會報InvalidDataException 異常信息

    services.Configure<FormOptions>(options =>
     {
                 options.MultipartBodyLengthLimit = long.MaxValue;
     });

    通過設置即重置文件上傳的大小限制。

    源碼分析

      這裏我主要說一下 MultipartBodyLengthLimit  這個參數他主要限制我們使用“緩衝”形式上傳文件時每個的長度。為什麼說是緩衝形式中,是因為我們緩衝形式在讀取上傳文件用的幫助類為 MultipartReaderStream 類下的 Read 方法,此方法在每讀取一次後會更新下讀入的總byte數量,當超過此數量時會拋出  throw new InvalidDataException($Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.);  主要體現在 UpdatePosition 方法對 _observedLength  的判斷

    以下為 MultipartReaderStream 類兩個方法的源代碼,為方便閱讀,我已精簡掉部分代碼

    Read

    public override int Read(byte[] buffer, int offset, int count)
     {
              
              var bufferedData = _innerStream.BufferedData;
          int read;
          read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));
              return UpdatePosition(read);
    }

    UpdatePosition

    private int UpdatePosition(int read)
            {
                _position += read;
                if (_observedLength < _position)
                {
                    _observedLength = _position;
                    if (LengthLimit.HasValue && _observedLength > LengthLimit.GetValueOrDefault())
                    {
                        throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.");
                    }
                }
                return read;
    }

    通過代碼我們可以看到 當你做了 MultipartBodyLengthLimit 的限制后,在每次讀取後會累計讀取的總量,當讀取總量超出

     MultipartBodyLengthLimit  設定值會拋出 InvalidDataException 異常,

    最終我的文件上傳Controller如下

      需要注意的是我們創建 MultipartReader 時並未設置 BodyLengthLimit  (這參數會傳給 MultipartReaderStream.LengthLimit )也就是我們最終的限制,這裏我未設置值也就無限制,可以通過 UpdatePosition 方法體現出來

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.WebUtilities;
    using Microsoft.Net.Http.Headers;
    using System.IO;
    using System.Threading.Tasks;
     
    namespace BigFilesUpload.Controllers
    {
        [Route("api/[controller]")]
        public class FileController : Controller
        {
            private readonly string _targetFilePath = "C:\\files\\TempDir";
     
            /// <summary>
            /// 流式文件上傳
            /// </summary>
            /// <returns></returns>
            [HttpPost("UploadingStream")]
            public async Task<IActionResult> UploadingStream()
            {
     
                //獲取boundary
                var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
                //得到reader
                var reader = new MultipartReader(boundary, HttpContext.Request.Body);
                //{ BodyLengthLimit = 2000 };//
                var section = await reader.ReadNextSectionAsync();
     
                //讀取section
                while (section != null)
                {
                    var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
                    if (hasContentDispositionHeader)
                    {
                        var trustedFileNameForFileStorage = Path.GetRandomFileName();
                        await WriteFileAsync(section.Body, Path.Combine(_targetFilePath, trustedFileNameForFileStorage));
                    }
                    section = await reader.ReadNextSectionAsync();
                }
                return Created(nameof(FileController), null);
            }
     
            /// <summary>
            /// 緩存式文件上傳
            /// </summary>
            /// <param name=""></param>
            /// <returns></returns>
            [HttpPost("UploadingFormFile")]
            public async Task<IActionResult> UploadingFormFile(IFormFile file)
            {
                using (var stream = file.OpenReadStream())
                {
                    var trustedFileNameForFileStorage = Path.GetRandomFileName();
                    await WriteFileAsync(stream, Path.Combine(_targetFilePath, trustedFileNameForFileStorage));
                }
                return Created(nameof(FileController), null);
            }
     
     
            /// <summary>
            /// 寫文件導到磁盤
            /// </summary>
            /// <param name="stream"></param>
            /// <param name="path">文件保存路徑</param>
            /// <returns></returns>
            public static async Task<int> WriteFileAsync(System.IO.Stream stream, string path)
            {
                const int FILE_WRITE_SIZE = 84975;//寫出緩衝區大小
                int writeCount = 0;
                using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write, FILE_WRITE_SIZE, true))
                {
                    byte[] byteArr = new byte[FILE_WRITE_SIZE];
                    int readCount = 0;
                    while ((readCount = await stream.ReadAsync(byteArr, 0, byteArr.Length)) > 0)
                    {
                        await fileStream.WriteAsync(byteArr, 0, readCount);
                        writeCount += readCount;
                    }
                }
                return writeCount;
            }
     
        }
    }

     

     總結:

    如果你部署 在iis上或者Nginx 等其他應用服務器 也是需要注意的事情,因為他們本身也有對請求體的限制,還有值得注意的就是我們在創建文件流對象時 緩衝區的大小盡量不要超過netcore大對象的限制。這樣在併發高的時候很容易觸發二代GC的回收.

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 【Stream—5】MemoryStream相關知識分享

    【Stream—5】MemoryStream相關知識分享

    一、簡單介紹一下MemoryStream

    MemoryStream是內存流,為系統內存提供讀寫操作,由於MemoryStream是通過無符號字節數組組成的,可以說MemoryStream的性能可以算比較出色,所以它擔當起了一些其他流進行數據交互安時的中間工作,同時可降低應用程序中對臨時緩衝區和臨時文件的需求,其實MemoryStream的重要性不亞於FileStream,在很多場合,我們必須使用它來提高性能

    二、MemoryStream和FileStream的區別

    前文中也提到了,FileStream主要對文件的一系列操作,屬於比較高層的操作,但是MemoryStream卻很不一樣,他更趨向於底層內存的操作,這樣能夠達到更快速度和性能,也是他們的根本區別,很多時候,操作文件都需要MemoryStream來實際進行讀寫,最後放入相應的FileStream中,不僅如此,在諸如XmlWriter的操作中也需要使用MemoryStream提高讀寫速度

    三、分析MemoryStream最常見的OutOfMemory異常

    先看一下下面一段簡單的代碼

     1             //測試byte數組 假設該數組容量是256M
     2             var testBytes = new byte[256 * 1024 * 1024];
     3             var ms = new MemoryStream();
     4             using (ms)
     5             {
     6                 for (int i = 0; i < 1000; i++)
     7                 {
     8                     try
     9                     {
    10                         ms.Write(testBytes, 0, testBytes.Length);
    11                     }
    12                     catch
    13                     {
    14                         Console.WriteLine("該內存流已經使用了{0}M容量的內存,該內存流最大容量為{1}M,溢出時容量為{2}M",
    15                             GC.GetTotalMemory(false) / (1024 * 1024),//MemoryStream已經消耗內存量
    16                             ms.Capacity / (1024 * 1024), //MemoryStream最大的可用容量
    17                             ms.Length / (1024 * 1024));//MemoryStream當前流的長度(容量)
    18                         break;
    19                     }
    20                 }
    21             }
    22             Console.ReadLine();

    輸出結果:

     

     

     從輸出結果來看,MemoryStream默認可用最大容量是1024M,發生異常時正好是其最大容量。

    問題來了,假設我們需要操作比較大的文件,該怎麼辦呢?其實有2種方法可以搞定,一種是分段處理,我們將Byte數組分成等份進行處理,還有一種方式便是增加MomoryStream的最大可用容量(字節),我們可以在聲明MomoryStream的構造函數時利用它的重載版本:MemoryStream(int capacity)

    到底使用哪種方法比較好呢?其實筆者認為具體項目具體分析,前者分段處理的確能夠解決大數據量操作的問題,但是犧牲了性能和時間(多線程暫時不考慮),後者可以得到性能上的優勢,但是其允許最大容量是 int.Max,所以無法給出一個明確的答案,大家在做項目時,按照需求自己定製即可,最關鍵的還是要取到性能和開銷的最佳點位,還有一種更噁心的溢出方式,往往會讓大家抓狂,就是不定時溢出,就是MemoryStream處理的文件可能只有40M或更小時,也會發生OutOfMemory的異常,關於這個問題,終於在老外的一篇文章中得到了解釋,運氣還不錯,可以看看這篇博文:,由於涉及到windows的內存機制,包括內存也,進程的虛擬地址空間等,比較複雜,所以大家看他的文章前,我先和大家簡單的介紹一下頁和進程的虛擬地址:

    內存頁:內存頁分為:文件頁和計算頁

    內存中的文件頁是文件緩存區,即文件型的內存頁,用於存放文件數據的內存頁(也稱永久頁),作用在於讀寫文件時可以減少對磁盤的訪問,如果它的大小設置的太小,會引起系統頻繁的訪問磁盤,增加磁盤I/O,設置太大,會浪費內存資源。

    內存中的計算頁也稱為計算型的內存頁,主要用於存放程序代碼和臨時使用的數據。

    進程的虛擬地址:每一個進程被給予它的非常自由的虛擬地址空間。對於32位的進程,地址空間是4G,因為一個32位指針能夠從0x00000000到0xffffffff之間的任意值,這個範圍允許指針從4294967296個值得一個,覆蓋了一個進程得4G範圍,對於64位進程,地址空間是16eb因為一個64位指針能夠指向18,446,744,073,709,551,616個值中的一個,覆蓋一個進程的16eb範圍,這是十分寬廣的範圍,上述概念都在自windows核心編程這本書,其實這本書對於我們程序員來說很重要,對於內存的操作,本人也是小白。

    四、MemoryStream的構造函數

    1、MemoryStream()

    MemoryStream允許不帶參數的構造

    2、MemoryStream(byte[] byte)

    Byte數組是包含了一定數據的byte數組,這個構造很重要,初學者或者用的不是很多的程序員會忽略這個構造函數導致後面讀取或寫入數據時發現MemoryStream中沒有byte數據,會導致很鬱悶的感覺,大家注意一下就行,有時候也可能無需這樣,因為很多方法返回值已經是MemoryStream了。

    3、MemoryStream(int capacity)

    這個是重中之重,為什麼這麼說呢?我在本文探討關於OutMemory異常中也提到了,如果你想額外提高MemoryStream的吞吐量,也只能靠這個方法提升一定的吞吐量,最多也只能到int.Max,這個方法也是解決OutOfMemory的一個可行方案。

    4、MemoryStream(byte[] byte,bool writeable)

    writeable參數定義該流是否可寫

    5、MemoryStream(byte[] byte,int index,int count)

    index:參數定義從byte數組中的索引index

    count:參數是獲取的數據量的個數

    6、MemoryStream(byte[] byte,int index,int count,bool writeable,bool publiclyVisible)

    publiclyVisible:參數表示true可以啟用GetBuffer方法,它返回無符號字節數組,流從該數組創建,否則為false。(大家一定覺得這個很難理解,別急,下面的方法中我會詳細的講一下這個東西)

    五、MemoryStream的屬性

     Memory的屬性大致都是和其父類很相似,這些功能在我的這篇文章中已經詳細討論過,所以我簡單列舉以下其屬性:

     

     其獨有的屬性:

    Capacity:這個前文其實已經提及,它表示該流的可支配容量(字節),非常重要的一個屬性。

    六、MemoryStream的方法

    對於重寫的方法,這裏不再重複說明,大家可以去看一下

     以下是MemoryStream獨有的方法

    1、virtual byte[] GetBuffer()

    這個方法使用時需要小心,因為這個方法返回無符號字節數組,也就是說,即使我只輸入幾個字符例如“HellowWorld”我們只希望返回11個數據就行,可是這個方法會把整個緩衝區的數據,包括那些已經分配但是實際上沒有用到的字符數據都返回回來了,如果想啟用這個方法那必須使用上面最後一個構造函數,將publiclyVisible屬性設置成true就行,這也是上面那個構造函數的錯用所在。

    2、virtual void WriteTo(Stream stream)

    這個方法的目的其實在本文開始的時候討論性能問題時已經指出,MemoryStream常用起中間流的作用,所以讀寫在處理完后將內存吸入其他流中。

    七、示例:

    1、XmlWriter中使用MemoryStream

     1         public static void UseMemoryStreamInXmlWriter()
     2         {
     3             var ms = new MemoryStream();
     4             using (ms)
     5             {
     6                 //定義一個XmlWriter
     7                 using (XmlWriter writer= XmlWriter.Create(ms))
     8                 {
     9                     //寫入xml頭
    10                     writer.WriteStartDocument(true);
    11                     //寫入一個元素
    12                     writer.WriteStartElement("Content");
    13                     //為這個元素增加一個test屬性
    14                     writer.WriteStartAttribute("test");
    15                     //設置test屬性的值
    16                     writer.WriteValue("萌萌小魔王");
    17                     //釋放緩衝,這裏可以不用釋放,但是在實際項目中可能要考慮部分釋放對性能帶來的提升
    18                     writer.Flush();
    19                     Console.WriteLine($"此時內存使用量為:{GC.GetTotalMemory(false)/1024}KB,該MemoryStream已使用容量為:{Math.Round((double)ms.Length,4)}byte,默認容量為:{ms.Capacity}byte");
    20                     Console.WriteLine($"重新定位前MemoryStream所在的位置是{ms.Position}");
    21                     //將流中所在當前位置往後移動7位,相當於空格
    22                     ms.Seek(7, SeekOrigin.Current);
    23                     Console.WriteLine($"重新定位后MemoryStream所存在的位置是{ms.Position}");
    24                     //如果將流所在的位置設置位如下示的位置,則XML文件會被打亂
    25                     //ms.Position = 0;
    26                     writer.WriteStartElement("Content2");
    27                     writer.WriteStartAttribute("testInner");
    28                     writer.WriteValue("萌萌小魔王2");
    29                     writer.WriteEndElement();
    30                     writer.WriteEndElement();
    31                     //再次釋放
    32                     writer.Flush();
    33                     Console.WriteLine($"此時內存使用量為:{GC.GetTotalMemory(false) / 1024}KB,該MemoryStream已使用容量為:{Math.Round((double)ms.Length, 4)}byte,默認容量為:{ms.Capacity}byte");
    34                     //建立一個FileStream 文件創建目的地是f:\test.xml
    35                     var fs=new FileStream(@"f:\test.xml",FileMode.OpenOrCreate);
    36                     using (fs)
    37                     {
    38                         //將內存流注入FileStream
    39                         ms.WriteTo(fs);
    40                         if (ms.CanWrite)
    41                         {
    42                             //釋放緩衝區
    43                             fs.Flush();
    44                         }
    45                     }
    46                     Console.WriteLine();
    47                 }
    48             }
    49         }

    運行結果:

     

     咱看一下XML文本是什麼樣的?

     

     2、自定義處理圖片的HttpHandler

    有時項目里我們必須將圖片進行一定的操作,例如:水印,下載等,為了方便和管理我們可以自定義一個HttpHandler來負責這些工作

    後台代碼:

     1     public class ImageHandler : IHttpHandler
     2     {
     3         /// <summary>
     4         /// 實現IHttpHandler接口中ProcessRequest方法
     5         /// </summary>
     6         /// <param name="context"></param>
     7         public void ProcessRequest(HttpContext context)
     8         {
     9             context.Response.Clear();
    10             //得到圖片名
    11             var imageName = context.Request["ImageName"] ?? "小魔王";
    12             //得到圖片地址
    13             var stringFilePath = context.Server.MapPath($"~/Image/{imageName}.jpg");
    14             //聲明一個FileStream用來將圖片暫時放入流中
    15             FileStream stream=new FileStream(stringFilePath,FileMode.Open);
    16             using (stream)
    17             {
    18                 //通過GetImageFromStream方法將圖片放入Byte數組中
    19                 var imageBytes = GetImageFromStream(stream, context);
    20                 //上下文確定寫道客戶端時的文件類型
    21                 context.Response.ContentType = "image/jpeg";
    22                 //上下文將imageBytes中的數組寫到前端
    23                 context.Response.BinaryWrite(imageBytes);
    24             }
    25         }
    26 
    27         public bool IsReusable => true;
    28 
    29         /// <summary>
    30         /// 將流中的圖片信息放入byte數組后返回該數組
    31         /// </summary>
    32         /// <param name="stream">文件流</param>
    33         /// <param name="context">上下文</param>
    34         /// <returns></returns>
    35         private byte[] GetImageFromStream(FileStream stream, HttpContext context)
    36         {
    37             //通過Stream到Image
    38             var image = Image.FromStream(stream);
    39             //加上水印
    40             image = SetWaterImage(image, context);
    41             //得到一個ms對象
    42             MemoryStream ms = new MemoryStream();
    43             using (ms)
    44             {
    45                 //將圖片保存至內存流
    46                 image.Save(ms,ImageFormat.Jpeg);
    47                 byte[] imageBytes = new byte[ms.Length];
    48                 ms.Position = 0;
    49                 //通過內存流放到imageBytes
    50                 ms.Read(imageBytes, 0, imageBytes.Length);
    51                 //ms.Close();
    52                 //返回imageBytes
    53                 return imageBytes;
    54             }
    55         }
    56 
    57         private Image SetWaterImage(Image image, HttpContext context)
    58         {
    59             Graphics graphics = Graphics.FromImage(image);
    60             Image waterImage = Image.FromFile(context.Server.MapPath("~/Image/logo.jpg"));
    61             graphics.DrawImage(waterImage, new Point { X = image.Size.Width - waterImage.Size.Width, Y = image.Size.Height - waterImage.Size.Height });
    62             return image;
    63         }
    64     }

    別忘了,還要再web.config中進行配置,如下:

     

     這樣前台就能使用了

     

     讓我們來看一下輸出結果:

     

     哈哈,還不錯。

    好了,MemoryStream相關的知識就先分享到這裏了。同志們,再見!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

  • fastjason常用方法

    fastjason常用方法

    什麼是fastjson?

    Fastjson是一個Java語言編寫的高性能功能完善的JSON庫。它採用一種“假定有序快速匹配”的算法,把JSON Parse的性能提升到極致,是目前Java語言中最快的JSON庫。Fastjson接口簡單易用,已經被廣泛使用在緩存序列化、協議交互、Web輸出、Android客戶端等多種應用場景。

    主要特點:

    • 快速FAST (比其它任何基於Java的解析器和生成器更快,包括jackson)
    • 強大(支持普通JDK類包括任意Java Bean Class、Collection、Map、Date或enum)
    • 零依賴(沒有依賴其它任何類庫除了JDK)

    背景

    最近關於fastjson的消息,引起了很多人的關注!

    fastjson爆出重大漏洞,攻擊者可使整個業務癱瘓

    漏洞描述

    常用JSON組件FastJson存在遠程代碼執行漏洞,攻擊者可通過精心構建的json報文對目標服務器執行任意命令,從而獲得服務器權限。此次爆發的漏洞為以往漏洞中autoType的繞過。

    影響範圍

    FastJson < 1.2.48

    很多開發者才猛然發現,fastjson已經深入到我們開發工作的方方面面。那麼除了趕快升級你的json外,我們來挖挖fastjson最常用的用法。

    fastjson常用方式

    1.maven依賴(記得升級到1.2.48以上版本哦)

            <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
            <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>fastjson</artifactId>
             <version>1.2.62</version>
            </dependency>    

    2.FastJson對於json格式字符串的解析主要用到了一下三個類:

    (1)JSON:fastJson的解析器,用於JSON格式字符串與JSON對象及javaBean之間的轉換。

    (2)JSONObject:fastJson提供的json對象。

    (3)JSONArray:fastJson提供json數組對象。

    3.常用方式

    3.1 string和java對象

     

    實例1:對象轉json字符串

            Map<String,String> map=new HashMap<String,String>();
            map.put("code","0");
            map.put("message","ok");
            String json=JSON.toJSONString(map);
            System.out.println(json);

    輸出結果為:

    {"code":"0","message":"ok"}

    實例2:字符串轉對象

            Map<String,String> map=new HashMap<String,String>();
            map.put("code","0");
            map.put("message","ok");
            String json=JSON.toJSONString(map);
            System.out.println(json);
            
            Map obj=(Map)JSON.parse(json);
            System.out.println("code="+obj.get("code")+",message="+obj.get("message"));

    輸出結果

    {"code":"0","message":"ok"}
    code=0,message=ok

    3.2 工具類JSONObject

        public static void main(String[] args) {
            Map<String,String> map=new HashMap<String,String>();
            map.put("code","0");
            map.put("message","ok");
            String json=JSON.toJSONString(map);
            System.out.println(json);
            
            Map obj=(Map)JSON.parse(json);
            System.out.println("code="+obj.get("code")+",message="+obj.get("message"));        
            
            String code=JSON.parseObject(json).getString("code");
            String message=JSON.parseObject(json).getString("message");
            System.out.println("code="+code+",message="+message);
        }

    輸出結果

    {"code":"0","message":"ok"}
    code=0,message=ok
    code=0,message=ok

    3.3 數組對象

    List<user> list=new ArrayList<user>(JSONArray.parseArray(jsonString,user.class)); 

    Fastjson 與各種JSON庫的性能比較:

     

    json庫 序列化性能 反序列化性能 jar大小
    fastjson 1201 1216 fastjson-1.1.26.jar(356k)
    fastjson-1.1.25-android.jar(226k)
    jackson 1408 1915 jackson-annotations-2.1.1.jar(34k)
    jackson-core-2.1.1.jar(206k)
    jackson-databind-2.1.1.jar(922k)
    總共1162k
    gson 7421 5065 gson-2.2.2.jar(189k)
    json-lib 27555 87292 json-lib-2.4-jdk15.jar(159k)


    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

  • Spring Boot2 系列教程(二十五)Spring Boot 整合 Jpa 多數據源

    Spring Boot2 系列教程(二十五)Spring Boot 整合 Jpa 多數據源

    本文是 Spring Boot 整合數據持久化方案的最後一篇,主要和大夥來聊聊 Spring Boot 整合 Jpa 多數據源問題。在 Spring Boot 整合JbdcTemplate 多數據源、Spring Boot 整合 MyBatis 多數據源以及 Spring Boot 整合 Jpa 多數據源這三個知識點中,整合 Jpa 多數據源算是最複雜的一種,也是很多人在配置時最容易出錯的一種。本文大夥就跟着松哥的教程,一步一步整合 Jpa 多數據源。

    工程創建

    首先是創建一個 Spring Boot 工程,創建時添加基本的 Web、Jpa 以及 MySQL 依賴,如下:

    創建完成后,添加 Druid 依賴,這裏和前文的要求一樣,要使用專為 Spring Boot 打造的 Druid,大夥可能發現了,如果整合多數據源一定要使用這個依賴,因為這個依賴中才有 DruidDataSourceBuilder,最後還要記得鎖定數據庫依賴的版本,因為可能大部分人用的還是 5.x 的 MySQL 而不是 8.x。完整依賴如下:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.28</version>
        <scope>runtime</scope>
    </dependency>

    如此之後,工程就創建成功了。

    基本配置

    在基本配置中,我們首先來配置多數據源基本信息以及 DataSource,首先在 application.properties 中添加如下配置信息:

    #  數據源一
    spring.datasource.one.username=root
    spring.datasource.one.password=root
    spring.datasource.one.url=jdbc:mysql:///test01?useUnicode=true&characterEncoding=UTF-8
    spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
    
    #  數據源二
    spring.datasource.two.username=root
    spring.datasource.two.password=root
    spring.datasource.two.url=jdbc:mysql:///test02?useUnicode=true&characterEncoding=UTF-8
    spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
    
    # Jpa配置
    spring.jpa.properties.database=mysql
    spring.jpa.properties.show-sql=true
    spring.jpa.properties.database-platform=mysql
    spring.jpa.properties.hibernate.ddl-auto=update
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect

    這裏 Jpa 的配置和上文相比 key 中多了 properties,多數據源的配置和前文一致,然後接下來配置兩個 DataSource,如下:

    @Configuration
    public class DataSourceConfig {
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.one")
        @Primary
        DataSource dsOne() {
            return DruidDataSourceBuilder.create().build();
        }
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.two")
        DataSource dsTwo() {
            return DruidDataSourceBuilder.create().build();
        }
    }

    這裏的配置和前文的多數據源配置基本一致,但是注意多了一個在 Spring 中使用較少的註解 @Primary,這個註解一定不能少,否則在項目啟動時會出錯,@Primary 表示當某一個類存在多個實例時,優先使用哪個實例。

    好了,這樣,DataSource 就有了。

    多數據源配置

    接下來配置 Jpa 的基本信息,這裏兩個數據源,我分別在兩個類中來配置,先來看第一個配置:

    @Configuration
    @EnableJpaRepositories(basePackages = "org.javaboy.jpa.dao",entityManagerFactoryRef = "localContainerEntityManagerFactoryBeanOne",transactionManagerRef = "platformTransactionManagerOne")
    public class JpaConfigOne {
        @Autowired
        @Qualifier(value = "dsOne")
        DataSource dsOne;
        @Autowired
        JpaProperties jpaProperties;
        @Bean
        @Primary
        LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanOne(EntityManagerFactoryBuilder builder) {
            return builder.dataSource(dsOne)
                    .packages("org.javaboy.jpa.model")
                    .properties(jpaProperties.getProperties())
                    .persistenceUnit("pu1")
                    .build();
        }
        @Bean
        PlatformTransactionManager platformTransactionManagerOne(EntityManagerFactoryBuilder builder) {
            LocalContainerEntityManagerFactoryBean factoryBeanOne = localContainerEntityManagerFactoryBeanOne(builder);
            return new JpaTransactionManager(factoryBeanOne.getObject());
        }
    }

    首先這裏注入 dsOne,再注入 JpaProperties,JpaProperties 是系統提供的一個實例,裡邊的數據就是我們在 application.properties 中配置的 jpa 相關的配置。然後我們提供兩個 Bean,分別是 LocalContainerEntityManagerFactoryBean 和 PlatformTransactionManager 事務管理器,不同於 MyBatis 和 JdbcTemplate,在 Jpa 中,事務一定要配置。在提供 LocalContainerEntityManagerFactoryBean 的時候,需要指定 packages,這裏的 packages 指定的包就是這個數據源對應的實體類所在的位置,另外在這裏配置類上通過 @EnableJpaRepositories 註解指定 dao 所在的位置,以及 LocalContainerEntityManagerFactoryBean 和 PlatformTransactionManager 分別對應的引用的名字。

    好了,這樣第一個就配置好了,第二個基本和這個類似,主要有幾個不同點:

    • dao 的位置不同
    • persistenceUnit 不同
    • 相關 bean 的名稱不同

    注意實體類可以共用。

    代碼如下:

    @Configuration
    @EnableJpaRepositories(basePackages = "org.javaboy.jpa.dao2",entityManagerFactoryRef = "localContainerEntityManagerFactoryBeanTwo",transactionManagerRef = "platformTransactionManagerTwo")
    public class JpaConfigTwo {
        @Autowired
        @Qualifier(value = "dsTwo")
        DataSource dsTwo;
        @Autowired
        JpaProperties jpaProperties;
        @Bean
        LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanTwo(EntityManagerFactoryBuilder builder) {
            return builder.dataSource(dsTwo)
                    .packages("org.javaboy.jpa.model")
                    .properties(jpaProperties.getProperties())
                    .persistenceUnit("pu2")
                    .build();
        }
        @Bean
        PlatformTransactionManager platformTransactionManagerTwo(EntityManagerFactoryBuilder builder) {
            LocalContainerEntityManagerFactoryBean factoryBeanTwo = localContainerEntityManagerFactoryBeanTwo(builder);
            return new JpaTransactionManager(factoryBeanTwo.getObject());
        }
    }

    接下來,在對應位置分別提供相關的實體類和 dao 即可,數據源一的 dao 如下:

    package org.javaboy.jpa.dao;
    public interface UserDao extends JpaRepository<User,Integer> {
        List<User> getUserByAddressEqualsAndIdLessThanEqual(String address, Integer id);
        @Query(value = "select * from t_user where id=(select max(id) from t_user)",nativeQuery = true)
        User maxIdUser();
    }

    數據源二的 dao 如下:

    package org.javaboy.jpa.dao2;
    public interface UserDao2 extends JpaRepository<User,Integer> {
        List<User> getUserByAddressEqualsAndIdLessThanEqual(String address, Integer id);
    
        @Query(value = "select * from t_user where id=(select max(id) from t_user)",nativeQuery = true)
        User maxIdUser();
    }

    共同的實體類如下:

    package org.javaboy.jpa.model;
    @Entity(name = "t_user")
    public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
        private String username;
        private String address;
        //省略getter/setter
    }

    到此,所有的配置就算完成了,接下來就可以在 Service 中注入不同的 UserDao,不同的 UserDao 操作不同的數據源。

    其實整合 Jpa 多數據源也不算難,就是有幾個細節問題,這些細節問題解決,其實前面介紹的其他多數據源整個都差不多。

    好了,本文就先介紹到這裏。

    相關案例已經上傳到 GitHub,歡迎小夥伴們們下載:

    掃碼關注松哥,公眾號後台回復 2TB,獲取松哥獨家 超2TB 免費 Java 學習乾貨

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • CSS:CSS彈性盒子布局 Flexible Box

    CSS:CSS彈性盒子布局 Flexible Box

    一、簡介

    flexbox:全稱Flexible Box, 彈性盒子布局。可以簡單實現各種伸縮性的設計,它是由伸縮容器和伸縮項目組成。任何一個元素都可以指定為flexbox布局。這種新的布局方案在2009年是由W3C組織提出來的,在此之前,Web開發一般使用基於盒子模型的傳統頁面布局,依賴定位屬性、流動屬性和显示屬性來解決,參看鏈接:。彈性盒子布局的出現,極大的方便了開發者,在如今的ReactNative開發中,也已經被引入使用。

    伸縮流布局結構圖如下:

    彈性盒子布局具備的特徵:

    1、伸縮容器的子元素稱為伸縮項目,伸縮項目使用伸縮布局來排版。伸縮布局和傳統布局不一樣,它按照伸縮流方向布局。

    2、伸縮容器由兩條軸構成,分別為主軸(main axis)和交叉軸(cross axis)。主軸既可以用水平軸,也可以是豎直軸,根據開發者需要來決定。

    3、主軸的起點叫main start,終點叫main end,主軸的空間用main size表示。

    4、交叉軸的起點叫cross start,終點叫cross end,交叉軸的空間用cross size表示。

    5、默認情況下,伸縮項目總是沿着主軸方向排版,從開始位置到終點位置。至於換行显示,則通過設置伸縮屬性來實現。

    6、伸縮容器的屬性有:display、flex-direction、flex-wrap、flex-flow、justify-content、align-items、align-content

    7、伸縮項目的屬性有: order、flex-grow、flex-shrink、flex-basis、flex、align-self

     

    二、伸縮容器的屬性,全局設置排版

    HTML:[注意:下面的演示截圖項目個數會根據需要選擇性註釋“flex-item”,有時用不到5個]

    <!DOCTYPE html>
    <html>
    <head>
        <title>Flexbox</title>
        <!--  採用外聯方式導入css文件 -->
        <link rel="stylesheet" type="text/css" href="./css_test.css">
    </head>
    <body>
        <span class="flex-container"> 
            <span class="flex-item" id="item1" style="color:white;font-size:20px">1</span>
            <span class="flex-item" id="item2" style="color:white;font-size:20px">2</span>
            <span class="flex-item" id="item3" style="color:white;font-size:20px">3</span>
            <span class="flex-item" id="item4" style="color:white;font-size:20px">4</span>
            <span class="flex-item" id="item5" style="color:white;font-size:20px">5</span>
        </span>
    </body>
    </html> 

    1、display:決定元素是否為伸縮容器

    • flex:產生塊級伸縮容器
      .flex-container {
           display: flex;
       }
    • inline-flex:產生行內塊級伸縮容器
    •  .flex-container {
           display: inline-flex;
       }

    2、flex-direction:指定伸縮容器主軸的方向

    • row:水平方向,從左到右
       .flex-container {
           display: flex;
           flex-direction: row;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; 
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • row-reverse:水平方向,從右到左
       .flex-container {
           display: flex;
           flex-direction: row-reverse;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; 
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • column:豎直方向,從上到下
       .flex-container {
           display: flex;
           flex-direction: column;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px; 
           background-color: green;
           margin: 1px;
       }

    • column-reverse:豎直方向,從下到上
       .flex-container {
           display: flex;
           flex-direction: column-reverse;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px; 
           background-color: green;
           margin: 1px;
       }

    3、flex-wrap:指定伸縮容器主軸方向空間不足時,決定是否換行以及換行方式

    • nowarp:不換行
      .flex-container {
           display: flex;
           flex-direction: row;
           flex-wrap: nowrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //下圖單行狀態寬度被重新計算
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • warp:換行,若主軸為水平方向,換行方向是從上到下
       .flex-container {
           display: flex;
           flex-direction: row;
           flex-wrap: wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • wrap-reverse:換行,若主軸為水平方向,換行方向是從下到上
       .flex-container {
           display: flex;
           flex-direction: row;
           flex-wrap: wrap-reverse;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    4、flex-flow:flex-direction和flex-wrap的縮寫,同時指定伸縮容器主軸方向和換行設置

    • row nowrap:默認主軸是水平方向,從左到右,且不換行
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //下圖單行狀態寬度被重新計算
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    5、justify-content:決定伸縮項目沿着主軸線的對齊方式

    • flex-start:與主軸線起始位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: flex-start;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • flex-end:與主軸線結束位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: flex-end;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • center:與主軸線中間位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: center;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • space-between:平均分配到主軸線里,第一個項目靠齊起始位置,最後一個項目靠齊終點位置
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: space-between;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • sapce-around:平均分配到主軸線里,兩端保留一半的空間
      .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: space-around;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    6、align-items:決定伸縮項目不能換行時沿着交叉軸線的對齊方式

    • flex-start:與交叉軸線起始位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: flex-start;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //下圖單行狀態寬度被重新計算
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • flex-end:與交叉軸線結束位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: flex-end;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //下圖單行狀態寬度被重新計算
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • center:與交叉軸線中間位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: center;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • baseline:根據基線對齊
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: baseline;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item1 {
           padding-top: 25px;
       }
      
       #item2 {
           padding-top: 20px;
       }
        
       #item3 {
           padding-top: 15px;
       }
      
       #item4 {
           padding-top: 10px;
       }
        
       #item5 {
           padding-top: 5px;
       }

    • stretch:沿着交叉軸線拉伸填充整個伸縮容器
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: stretch;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;//此時可以設置寬度,但不能設置高度,否則無法拉伸
           background-color: green;
           margin: 1px;
       }

    7、align-content:決定伸縮項目可以換行時沿着交叉軸線的對齊方式,flex-warp:warp一定要開啟

    • flex-start:與交叉軸線起始位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:flex-start;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • flex-end:與交叉軸線結束位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:flex-end;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • center:與主軸線中間位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:center;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • space-between:平均分配到主軸線里,第一行項目靠齊起始位置,最後一行項目靠齊終點位置
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:space-between;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • sapce-around:所有行平均分配到主軸線里,兩端保留一半的空間
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:space-around;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • stretch:沿着交叉軸
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:stretch;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //不要設置高度,不然無法拉伸
           background-color: green;
           margin: 1px;
       }

     

    三、伸縮項目的屬性,單個設置排版

    1、order:定義伸縮項目的排列順序。數值越小,排列越靠前,默認值為0。

    • 表達式 order: integer;
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item4 {
           order: -1;
       }
      
       #item5 {
           order: -2;
       }

    2、flex-grow:定義伸縮項目的放大比例,默認值為0,表示即使存在剩餘空間,也不放大。

    • 表達式 flex-grow: number;
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item2 {
           flex-grow: 1; //空間不足,item2不會放大
       }
      
       #item4 {
           flex-grow: 1; //item4放大填滿剩餘空間
       }

    3、flex-shrink:定義伸縮項目的收縮比例,默認值為1。

    • 表達式 flex-shrink: numer;
      .flex-container {
           display: flex;
           flex-flow: row nowrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item4 {
           flex-shrink:3; //單行,空間有限,item4縮小為原來的1/3
       }
      
       #item5 {
           flex-shrink:4;  //單行,空間有限,item5縮小為原來的1/5
      }

    4、flex-basis:定義伸縮項目的基準值,剩餘空間按照比例進行伸縮,默認auto。

    •  auto
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item5 {
           flex-basis:auto;
       }

                

    • flex-basis: length 
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item5 {
           flex-basis:200px;
       }

    5、flex:是flex-grow、flex-shrink、flex-basis的縮寫,默認值 0 1 auto。

    • none: 不設置
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item2 {
           flex: none; /* 等同於 flex: 0 0 auto */
       }

    • flex-grow flex-shrink flex-basis: 設置放大或縮小或基準線
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item2 {
           flex: 1; /* 等同於 flex: 1 1 auto 或者 等同於 flex: auto*/
       }

    6、align-self:用來設置伸縮項目在交叉軸的對齊方式。

    • auto:自動對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item3 {
           align-self: auto;
       }

    • flex-start: 向交叉軸的開始位置對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item3 {
           align-self: flex-start;
       }

    • flex-end: 向交叉軸的結束位置對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item3 {
           align-self: flex-end;
       }

    • center: 向交叉軸的中間位置對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item3 {
           align-self: center;
       }

    • baseline:向交叉軸的基準線對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item1 {
           align-self: baseline;
           margin-top: 50px;
       }
      
       #item2 {
           align-self: baseline;
       }

    • stretch: 在交叉軸拉伸填滿伸縮容器
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item1 {
           align-self: stretch;
       }
      
       #item2 {
           align-self: stretch;
       }
      
       #item3 {
           align-self: stretch;
       }

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

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

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

  • 電動車電池成長,帶動致茂營收倍翻

    受惠於電動車動力電池需求增加,致茂電子的七月合併營收創下歷史新高。今年前七個月的累計營收更已與去年全年相當。

    致茂電子表示,因電動車動力電池製造的關鍵技術(turnkey solutions)銷售蓬勃,帶動七月營收大漲,合併營收達新台幣16.2億元,不僅較上月成長71%、更較去年七月成長101%,營收數字創下歷史新高。

    此外,母公司的7月單月營收也有128%的月增與172%的年增,達新台幣12.6億元。強勁的需求使致茂今年前七個月的合併營收年增27%來到69.1億新台幣;母公司前七個月的累計營收新台幣45億元,也已相當於去年全年水準。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

  • 傳蘋果電動汽車將採用韓國公司的電池技術

    傳蘋果電動汽車將採用韓國公司的電池技術

    根據行業消息,蘋果近期與一家韓國電池開發商簽署了保密協議,聯合為代號為“泰坦”的汽車專案開發電池。從今年初開始,他們一直在韓國做行政工作。一名蘋果員工一直在這家韓國公司進行參觀活動,他屬於與蘋果電動汽車電池開發相關的部門。  
      業界認為,這家韓國公司並不是唯一一家負責蘋果電池開發的公司。不過,有消息稱,儘管蘋果從一開始就從完全不同的設計、功能以及性能角度來開發電池,但是他們仍舊一直在挖掘創新技術。業界相信,蘋果專注于開發出只能存在於蘋果自動駕駛汽車的創新電池技術。   這家韓國電池開發商由大約20名電池專家組成,持有空芯電池的國際專利技術。這些電池是圓柱形鋰離子二次電池,有兩根手指那麼厚,不同於其它空芯電池。蘋果並未選擇當前電動汽車普遍使用的標準圓形或矩形電池,但計畫根據韓國公司的空芯電池技術為其電動汽車開發自主電池。   文章來源:鳳凰科技

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

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 蘋果傳將攜手韓廠研發電動車用電池

    蘋果公司持續擴大業務範圍,除新成立的蘋果能源(Apple Energy)成功取得售電許可外,市場上關注度十足的蘋果電動車專案「泰坦計畫」(Project Titan)也時常傳出新消息。近期傳言蘋果將與南韓某企業合作,運用該南韓公司的電池專利,攜手開發電動車用電池。

    根據日本蘋果情報網站iPhone Mania、南韓媒體ET News 等報導,蘋果看上某家南韓廠商所取得的鋰電池專利,打算與該廠合作開發電動車用電池。被看上的鋰電池專利技術,電池中央部位採中空設計,可使空氣流通來冷卻電池,等同降低冷卻系統的需求,騰出更多空間來增加電池容量。

    日、韓媒體並未披露可能將與蘋果合作的韓廠名稱,但美國MacRumors 透剁歐洲專利局得知,這款「中央中空」的電池是由一家稱為Orange Power 的南韓廠商取得。該廠係一小廠,員工人數僅33人,其中多數為研發人員。

    蘋果電動車計畫自曝光以來即備受矚目,但至今仍然只聞樓梯響。據了解,目前共有約1,000人參與蘋果電動車企劃,且蘋果已在柏林設立秘密開發實驗室。MoneyDJ引述The Information網站的說法,表示蘋果電動車可能在2021年亮相;但也有分析師認為,蘋果電動車可能會步上蘋果電視的後塵,胎死腹中。

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

  • 德國頒佈最新電動汽車補貼計畫 共投入12億歐元

    德國頒佈最新電動汽車補貼計畫 共投入12億歐元

    據報導,自7月1日起,德國頒佈最新的電動車補貼計畫,截止目前已經有近2000位申請者,其中寶馬車主占多數。  
      為了促進電動車等環保車型的普及,德國為每位購買電動車的消費者提供4000歐元的補貼,插電式混合動力車的補貼為3000歐元。在計畫實施後,有1791位插電式混合動力車的車主申請了補貼,其中有581位購買了寶馬的車型。同時還有444位申請者購買了雷諾車型,大眾汽車買主為154位。   據統計,目前德國人汽車擁有量為4500萬輛,而其中僅有5萬輛是純電動或者是混合動力車輛。為改善這一情況,德國此次計畫共投入12億歐元,由政府和汽車製造商平攤,希望能夠在2019年6月底,即計畫截止期前售出40萬輛電動車。   文章來源:環球網

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

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整