分類
發燒車訊

mysql 索引筆記

MyISAM引擎的B+Tree的索引

通過上圖可以直接的看出, 在MyISAM對B+樹的運用中明顯的特點如下:

  • 所有的非恭弘=叶 恭弘子節點中存儲的全部是索引信息
  • 在恭弘=叶 恭弘子節點中存儲的 value值其實是 數據庫中某行數據的index

MyISAM引擎 索引文件的查看:

在 /var/lib/mysql目錄中

.myd 即 my data , 數據庫中表的數據文件

.myi 即 my index , 數據庫中 索引文件

.log 即 mysql的日誌文件

InnoDB引擎 索引文件的查看:

同樣在 /var/lib/mysql 目錄下面

InnoDB引擎的B+Tree的索引

InnoDB的實現方式業內也稱其為聚簇索引, 什麼是聚簇索引呢? 就是相鄰的行的簡直被存儲到一起, 對比上面的兩幅圖片就會發現, 在InnDB中, B+樹的恭弘=叶 恭弘子節點中存儲的是數據行中的一行行記錄, 缺點: 因為索引文件被存放在硬盤上, 所以很占硬盤的空間

一般我們會在每一個表中添加一列 取名 id, 設置它為primary key , 即將他設置成主鍵, 如果使用的存儲引擎也是InnoDB的話, 底層就會建立起主鍵索引, 也是聚簇索引, 並且會自動按照id的大小為我們排好序,(因為它的一個有序的樹)

局部性原理

局部性原理是指CPU訪問存儲器時,無論是存取指令還是存取數據,所訪問的存儲單元都趨於聚集在一個較小的連續區域中。 更進一步說, 當我們通過程序向操作系統發送指令讓它讀取我們指定的數據時, 操作系統會一次性讀取一頁(centos 每頁4kb大小,InnoDB存儲引擎中每一頁16kb)的數據, 它遵循局部性理論, 猜測當用戶需要使用某個數據時, 用戶很可能會使用這個數據周圍的數據,故而進行一次

InnoDB的頁格式

什麼是頁呢? 簡單說,就是一條條數據被的存儲在磁盤上, 使用數據時需要先將數據從磁盤上讀取到內存中, InnoDB每次讀出數據時同樣會遵循 局部性原理, 而不是一條條讀取, 於是InnoDB將數據劃分成一個一個的頁, 以頁作為和磁盤之間交互的基本單位

通過如下sql, 可以看到,InnoDB中每一頁的大小是16kb

show global status like 'Innodb_page_size';

名稱 簡述
File Header 文件頭部, 存儲頁的一些通用信息
Page Header 頁面頭部, 存儲數據頁專有的信息
Infinum + supremum 最大記錄和最小記錄, 這是兩個虛擬的行記錄
User Records 用戶記錄, 用來實際存儲行記錄中的內容
Free Space 空閑空間, 頁中尚位使用的空間
Page Directory 頁面目錄, 存儲頁中某些記錄的位置
File Tailer 文件尾部 , 用來校驗頁是否完整

InnoDB的行格式 compact

每一頁中存儲的行數據越多. 整體的性能就會越強

compact的行格式如下圖所示

可以看到在行格式中在存儲真正的數據的前面會存儲一些其他信息, 這些信息是為了描述這條記錄而不得不添加的一些信息, 這些額外的信息就是上圖中的前三行

  • 變長字段的長度列表

在mysql中char是固定長度的類型, 同時mysql還支持諸如像 varchar這樣可變長度的類型, 不止varchar , 想 varbinary text blob這樣的變長數據類型, 因為 變長的數據類型的列存儲的數據的長度是不固定的, 所以說我們在存儲真正的數據時, 也得將這些數據到底佔用了多大的長度也給保存起來

  • NULL標誌位

compact行格式會將值可以為NULL的列統一標記在 NULL標誌位中, 如果數據表中所有的字段都被標記上not null , 那麼就沒有NULL值列表

  • 記錄頭信息

記錄頭信息, 顧名思義就是用來描述記錄頭中的信息, 記錄頭信息由固定的5個字節組成, 一共40位, 不同位代表的意思也不同, 如下錶

名稱 單位 bit 簡介
預留位1 1 未使用
預留位2 1 未使用
delete_mark 1 標記改行記錄是否被刪除了
min_rec_mark 1 標記在 B+樹中每層的非恭弘=叶 恭弘子節點中最小的node
n_owned 4 表示當前記錄擁有的記錄數
heap_no 13 表示當前記錄在堆中的位置
record_type 3 表示當前記錄的類型 , 0表示普通記錄, 1表示B+樹中非恭弘=叶 恭弘子節點記錄, 2表示最小記錄 ,3表示最大記錄
next_record 16 表示下一條記錄的相對位置

行溢出

在mysql中每一行, 能存儲的最大的字節數是65535個字節數, 此時我們使用下面的sql執行時就會出現行溢出現象

CREATE TABLE test ( c VARCHAR(65535) ) CHARSET=ascii ROW_FORMAT=Compact;

給varchar申請最大65535 , 再加上compact行格式中還有前面三個非數據列佔用內存,所以一準溢出, 如果不想溢出, 可以適當的將 65535 – 3

頁溢出

前面說了, InnoDB中數據的讀取按照頁為單位, 每一頁的大小是 16kb, 換算成字節就是16384個字節, 但是每行最多存儲 65535個字節啊, 也就是說一行數據可能需要好幾個頁來存儲

怎麼辦呢?

  • compact行格式會在存儲真實數據的列中多存儲一部分數據, 這部分數據中存儲的就是下一頁的地址
  • dynamic行格式 中直接存儲數據所在的地址, 換句話說就是數據都被存儲在了其他頁上
  • compressed行格式會使用壓縮算法對行格式進行壓縮處理

一般我們都是將表中的id列設置為主鍵, 這就會形成主鍵索引, 於是我們需要注意了:

主鍵的佔用的空間越小,整體的檢索效率就會越高

為什麼這麼說呢? 這就可以結合頁的概念來解析, 在B+樹這種數據結果中, 恭弘=叶 恭弘子節點中用來存儲數據, 存儲數據的格式類似Key-value key就是索引值, value就是數據內容, 如果索引佔用的空間太大的話, 單頁16kb能存儲的索引就越小, 這就導致數據被分散在更多的頁上, 致使查詢的效率降低

建立索引的技巧

為某一列建立索引

給text表中的title列創建索引, 索引名字 my_index
alter table text add index my_index (title);

雖然建立索引能提升查詢的效率, 根據前人的經驗看, 這並不是一定的, 建立索引本身會直接消耗內存空間, 同時索, 插入,刪除, 這種寫操作就會打破B+樹的平衡面臨索引的重建, 一般出現如下兩種情況時,是不推薦建立索引的

  1. 表中的數據本身就很少
  2. 我們計算一下索引的選擇性很低

兼顧 – 索引的選擇性與前綴索引

所謂選擇性,其實就是說不重複出現的索引值(基數,Cardinality) 與 表中的記錄數的比值

即: 選擇性= 基數 / 記錄數

選擇性的取值範圍在(0,1]之間, 選擇性越接近1 , 說明建立索引的必要性就越強, 比如對sex列進行建立索引,這裏面非男即女, 如果對它建立索引的話, 其實是沒意義的, 還不如直接進行全表掃描來的快

如何使用sql計算選擇性呢? 嚴格遵循上面的公式

SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
count(基數/記錄數)
DISTINCT(title) / /count(*)

更詳細的例子看下面的連接

索引失效問題

注意事項

  • 索引無法存儲null值

  • 如果條件中有or, 即使條件中存在索引也不會使用索引,如果既想使用or,又想使用索引, 就給所有or條件控制的列加上索引

  • 使用like查詢時, 如果以%開頭,肯定是進行全表掃描

  • 使用like查詢時, 如果%在條件後面

    • 對於主鍵索引, 索引失效
    • 對於普通索引, 索引不失效
  • 如果列的類型是字符串類型, 那麼一定要在條件中將數據用引號引起來,不然也會是索引失效

  • 如果mysql認為全表掃描比用索引塊, 同樣不會使用索引

聯合索引

什麼是聯合索引

聯合索引, 也叫複合索引,說白了就是多個字段一起組合成一個索引

像下面這樣使用 id + title 組合在一起構成一個聯合索引

CREATE TABLE `text` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  PRIMARY KEY (`id`,`title`)
) ENGINE=InnoDB AUTO_INCREMENT=3691 DEFAULT CHARSET=utf8
  • 如果我們像上圖那樣創建了索引,我們只要保證我們的 id+title 兩者結合起來全局唯一就ok
  • 建立聯合索引同樣是需要進行排序的,排序的規則就是按照聯合索引所有列組成的字符串的之間的先後順序進行排序, 如a比b優先

左前原則

使用聯合索引進行查詢時一定要遵循左前綴原則, 什麼是左前綴原則呢? 就是說想讓索引生效的話,一定要添加上第一個索引, 只使用第二個索引進行查詢的話會導致索引失效

比如上面創建的聯合索引, 假如我們的查詢條件是 where id = ‘1’ 或者 where id = ‘1’ and title = ‘唐詩宋詞’ 索引都會不失效

但是如果我們不使用第一個索引id, 像這樣 where title = ‘唐詩’ , 結果就是導致索引失效

聯合索引的分組&排序

還是使用這個例子:

CREATE TABLE `text` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  PRIMARY KEY (`id`,`title`)
) ENGINE=InnoDB AUTO_INCREMENT=3691 DEFAULT CHARSET=utf8

demo1: 當我們像下面這樣寫sql時, 就會先按照id進行排序, 當id相同時,再按照title進行排序

select * form text order by id, title;

demo2: 當我們像下面這樣寫sql時, 就會先將id相同的劃分為一組, 再將title相同的劃分為一組

select id,title form text group by id, title;

demo3: ASC和DESC混用, 其實大家都知道底層使用B+樹, 本身就是有序的, 要是不加限制的話,默認就是ASC, 反而是混着使用就使得索引失效

select * form text order by id ASC, title DESC;

如何定位慢查詢

相關參數

名稱 簡介
slow_query_log 慢查詢的開啟狀態
slow_query_log_file 慢查詢日誌存儲的位置
long_query_time 查詢超過多少秒才記錄下來

常用sql

# 查看mysql是否開啟了慢查詢
show variables like 'slow_query_log';   
# 將全局變量設置為ON
set global slow_query_log ='on';
# 查看慢查詢日誌存儲的位置
show variables like 'slow_query_log_file';
# 查看規定的超過多少秒才被算作慢查詢記錄下來
show variables like 'long_query_time';
show variables like 'long_query%';
# 超過一秒就記錄 , 每次修改這個配置都重新建立一次鏈接
set global long_query_time=1; 

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

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

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

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

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

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

分類
發燒車訊

基於.NetStandard的簡易EventBus實現-基礎實現

一、問題背景

  最近離職來到了一家新的公司,原先是在乙方工作,這回到了甲方,在這一個月中,發現目前的業務很大一部分是靠輪詢實現的,例如:通過輪詢判斷數據處於B狀態了,則輪詢到數據后執行某種動作,這個其實是非常浪費的,並且對於數據的實時性也會不怎麼友好,基於以上的情況,在某天開車堵車時候,想到了之前偶然了解過的事件總線(EventBus),對比了公司當前的場景后,覺得事件總線應該是可以滿足需求的(PS:只是我覺得這個有問題,很多人不覺得有問題),那既然想到了,那就想自己是否可以做個事件總線的輪子

二、什麼是事件總線

  我們知道事件是由一個Publisher跟一個或多個的Subsriber組成,但是在實際的使用過程中,我們會發現,Subsriber必須知道Publisher是誰才可以註冊事件,進而達到目的,那這其實就是一種耦合,為了解決這個問題,就出現了事件總線的模式,事件總線允許不同的模塊之間進行彼此通信而又不需要相互依賴,如下圖所示,通過EventBus,讓Publisher以及Subsriber都只需要對事件源(EventData)進行關注,不用管Publisher是誰,那麼EventBus主要是做了一些什麼事呢?

三、EventBus做了什麼事?

  1、EventBus實現了對於事件的註冊以及取消註冊的管理

  2、EventBus內部維護了一份事件源與事件處理程序的對應關係,並且通過這個對應關係在事件發布的時候可以找到對應的處理程序去執行

  3、EventBus應該要支持默認就註冊事件源與處理程序的關係,而不需要開發人員手動去註冊(這裏可以讓開發人員去控制自動還是手動)

四、具體實現思路

   首先在事件總線中,存在註冊、取消註冊以及觸發事件這三種行為,所以我們可以將這三種行為抽象一個接口出來,最終的接口代碼如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace MEventBus.Core
{
    public interface IEventBus
    {
        #region 接口註冊
        void Register<TEventData>(Type handlerType) where TEventData : IEventData;
        void Register(Type eventType, Type handlerType);
        void Register(string eventType, Type handlerType);
        #endregion

        #region 接口取消註冊
        void Unregister<TEventData>(Type handler) where TEventData : IEventData;
        void Unregister(Type eventType, Type handlerType);
        void Unregister(string eventType, Type handlerType);
        #endregion


        void Trigger(string pubKey, IEventData eventData);
        Task TriggerAsync(string pubKey, IEventData eventData);
        Task TriggerAsync<TEventData>(TEventData eventData) where TEventData : IEventData;
        void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData;
    }
}

  在以上代碼中發現有些方法是有IEventData約束的,這邊IEventData就是約束入參行為,原則上規定,每次觸發的EventData都需要繼承IEventData,而註冊的行為也是直接跟入參類型相關,具體代碼如下:

using System;
using System.Collections.Generic;
using System.Text;

namespace MEventBus.Core
{
    public interface IEventData
    {
        string Id { get; set; }
        DateTime EventTime { get; set; }
        object EventSource { get; set; }
    }
}

  接下來我們看下具體的實現代碼

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace MEventBus.Core
{
    public class EventBus : IEventBus
    {
        private static ConcurrentDictionary<string, List<Type>> dicEvent = new ConcurrentDictionary<string, List<Type>>();
        private IResolve _iresolve { get; set; }
        public EventBus(IResolve resolve)
        {
            _iresolve = resolve;
            InitRegister();
        }

        public void InitRegister()
        {
            if (dicEvent.Count > 0)
            {
                return;
            }
            //_iresolve = ioc_container;
            dicEvent = new ConcurrentDictionary<string, List<Type>>();
            //自動掃描類型並且註冊
            foreach (var file in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"))
            {
                var ass = Assembly.LoadFrom(file);
                foreach (var item in ass.GetTypes().Where(p => p.GetInterfaces().Contains(typeof(IEventHandler))))
                {
                    if (item.IsClass)
                    {
                        foreach (var item1 in item.GetInterfaces())
                        {
                            foreach (var item2 in item1.GetGenericArguments())
                            {
                                if (item2.GetInterfaces().Contains(typeof(IEventData)))
                                {
                                    Register(item2, item);
                                }
                            }
                        }
                    }
                }
            }
        }
        //註冊以及取消註冊的時候需要加鎖處理
        private static readonly object obj = new object();

        #region 註冊事件
        public void Register<TEventData>(Type handlerType) where TEventData : IEventData
        {
            //將數據存儲到mapDic
            var dataType = typeof(TEventData).FullName;
            Register(dataType, handlerType);
        }
        public void Register(Type eventType, Type handlerType)
        {
            var dataType = eventType.FullName;
            Register(dataType, handlerType);
        }
        public void Register(string pubKey, Type handlerType)
        {
            lock (obj)
            {
                //將數據存儲到dicEvent
                if (dicEvent.Keys.Contains(pubKey) == false)
                {
                    dicEvent[pubKey] = new List<Type>();
                }
                if (dicEvent[pubKey].Exists(p => p.GetType() == handlerType) == false)
                {
                    //IEventHandler obj = Activator.CreateInstance(handlerType) as IEventHandler;
                    dicEvent[pubKey].Add(handlerType);
                }
            }
        }



        #endregion

        #region 取消事件註冊
        public void Unregister<TEventData>(Type handler) where TEventData : IEventData
        {
            var dataType = typeof(TEventData);
            Unregister(dataType, handler);
        }

        public void Unregister(Type eventType, Type handlerType)
        {
            string _key = eventType.FullName;
            Unregister(_key, handlerType);
        }
        public void Unregister(string eventType, Type handlerType)
        {
            lock (obj)
            {
                if (dicEvent.Keys.Contains(eventType))
                {
                    if (dicEvent[eventType].Exists(p => p.GetType() == handlerType))
                    {
                        dicEvent[eventType].Remove(dicEvent[eventType].Find(p => p.GetType() == handlerType));
                    }
                }
            }
        }
        #endregion

        #region Trigger觸發
        //trigger時候需要記錄到數據庫
        public void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData
        {
            var dataType = eventData.GetType().FullName;
            //獲取當前的EventData綁定的所有Handler
            Notify(dataType, eventData);
        }

        public void Trigger(string pubKey, IEventData eventData)
        {
            //獲取當前的EventData綁定的所有Handler
            Notify(pubKey, eventData);
        }
        public async Task TriggerAsync<TEventData>(TEventData eventData) where TEventData : IEventData
        {
            await Task.Factory.StartNew(new Action(()=> 
            {
                var dataType = eventData.GetType().FullName;
                Notify(dataType, eventData);
            }));
        }
        public async Task TriggerAsync(string pubKey, IEventData eventData)
        {
            await Task.Factory.StartNew(new Action(() =>
            {
                var dataType = eventData.GetType().FullName;
                Notify(pubKey, eventData);
            }));
        }
        //通知每成功執行一個就需要記錄到數據庫
        private void Notify<TEventData>(string eventType, TEventData eventData) where TEventData : IEventData
        {
            //獲取當前的EventData綁定的所有Handler
            var handlerTypes = dicEvent[eventType];
            foreach (var handlerType in handlerTypes)
            {
                var resolveObj = _iresolve.Resolve(handlerType);
                IEventHandler<TEventData> handler = resolveObj as IEventHandler<TEventData>;
                handler.Handle(eventData);

            }
        }
        #endregion
    }
}

  代碼說明:

  1、如上的EventBus是繼承了IEventBus后的具體實現,小夥伴可能看到在構造函數里,有一個接口參數IResolve,這個主要是為了將解析的過程進行解耦,由於在一些WebApi的項目中,更加多的是使用IOC的機制進行對象的創建,那基於IResolve就可以實現不同的對象創建方式(內置的是通過反射實現)

  2、InitRegister方法通過遍歷當前目錄下的dll文件,去尋找所有實現了IEventHandler<IEventData>接口的信息,並且自動註冊到EventBus中,所以在實際使用過程中,應該是沒有機會去適用register註冊的

  3、觸發機制實現了同步以及異步的調用,這個從方法命名中就可以看出來

五、程序Demo

  TestHandler2(繼承IEventHandler)

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using MEventBus.Core;

namespace MEventBusHandler.Test
{
    public class TestHandler2 : IEventHandler<TestEventData>
    {
        public void Handle(TestEventData eventData)
        {
            Thread.Sleep(2000);
            MessageBox.Show(eventData.EventTime.ToString());
        }
    }
}

  TestEventData(繼承EventData,EventData是繼承了IEventData的代碼)

using MEventBus.Core;
using System;
using System.Collections.Generic;
using System.Text;

namespace MEventBusHandler.Test
{
    public class TestEventData : EventData
    { }
}

  調用代碼

using MEventBus.Core;
using MEventBusHandler.Test;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MEventBus.Test
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            TestHandler.OnOut += TestHandler_OnOut;
        }

        private void TestHandler_OnOut(object sender, EventArgs e)
        {
            MessageBox.Show("Hello World");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            var task = new MEventBus.Core.EventBus(new ReflectResolve()).TriggerAsync(new TestEventData());
            task.ContinueWith((obj) => {
                MessageBox.Show("事情全部做完");
            });
        }

        private void button2_Click(object sender, EventArgs e)
        {
           new EventBus(new ReflectResolve()).Trigger(new TestEventData());
        }
    }


}

  執行結果

 

 

 

 

 我在真正的Demo中,其實是註冊了2個handler,可以在後續公布的項目地址里看到

六、總結

  從有這個想法開始,到最終實現這個事件總線,大概總共花了2,3天的時間(PS:晚上回家獨自默默幹活),目前只能說是有一個初步可以使用的版本,並且還存在着一些問題:

  1、在.NetFrameWork下(目前公司還不想升級到.NetCore,吐血。。),如果使用AutoFac創建EventBus(單例模式下),如果Handler也使用AutoFac進行創建,會出現要麼對象創建失敗,要麼handler里的對象與調用方的對象不是同一個實例,為了解決這個問題,我讓EventBus不再是單例模式,將dicEvent變成了靜態,暫時表面解決

  2、未考慮跨進程的實現(感覺用savorboard大佬的就可以了)

  3、目前這個東西在一個小的新項目里使用,暫時在測試環境還算沒啥問題,各位小夥伴如果有類似需求,可以做個參考

  由於個人原因,在測試上可能會有所不夠,如果有什麼bug的話,還請站內信告知,感謝(ps:文字表達弱雞,技術渣渣,各位多多包涵)

  最後:附上項目地址:

 

 

作者: Mango

出處: 

關於自己:專註.Net桌面開發以及Web後台開發,對.NetCore、微服務、DevOps,K8S等感興趣,最近到了個甲方公司準備休養一段時間

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,如有問題, 可站內信告知.

 

 

 

 

 

 

 

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

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

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

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

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

分類
發燒車訊

java中hashmap容量的初始化

HashMap使用HashMap(int initialCapacity)對集合進行初始化。

在默認的情況下,HashMap的容量是16。但是如果用戶通過構造函數指定了一個数字作為容量,那麼Hash會選擇大於該数字的第一個2的冪作為容量。比如如果指定了3,則容量是4;如果指定了7,則容量是8;如果指定了9,則容量是16。

為什麼要設置HashMap的初始化容量

在《阿里巴巴Java開發手冊》中,有一條開發建議是建議我們設置HashMap的初始化容量。

下面我們通過具體的代碼來了解下為什麼會這麼建議。

我們先來寫一段代碼在JDK1.7的環境下運行,來分別測試下,在不指定初始化容量和指定初始化容量的情況下性能情況的不同。

public static void main(String[] args) {
    int aHundredMillion = 10000000;

    // 未初始化容量
    Map<Integer, Integer> map = new HashMap<>();
    long s1 = System.currentTimeMillis();
    for (int i = 0; i < aHundredMillion; i++) {
        map.put(i, i);
    }
    long s2 = System.currentTimeMillis();
    System.out.println("未初始化容量,耗時: " + (s2 - s1)); // 14322

    // 初始化容量為50000000
    Map<Integer, Integer> map1 = new HashMap<>(aHundredMillion / 2);
    long s3 = System.currentTimeMillis();
    for (int i = 0; i < aHundredMillion; i++) {
        map1.put(i, i);
    }
    long s4 = System.currentTimeMillis();
    System.out.println("初始化容量5000000,耗時: " + (s4 - s3)); // 11819

    // 初始化容量為100000000
    Map<Integer, Integer> map2 = new HashMap<>(aHundredMillion);
    long s5 = System.currentTimeMillis();
    for (int i = 0; i < aHundredMillion; i++) {
        map2.put(i, i);
    }
    long s6 = System.currentTimeMillis();
    System.out.println("初始化容量為10000000,耗時: " + (s6 - s5)); // 7978
}

從以上的代碼不難理解,我們創建了3個HashMap,分別使用默認的容量(16)、使用元素個數的一半(5千萬)作為初始容量和使用元素個數(一億)作為初始容量進行初始化,然後分別向其中put一億個KV。

從上面的打印結果中可以得到一個初步的結論:在已知HashMap中將要存放的KV個數的時候,設置一個合理的初始化容量可以有效地提高性能。下面我們來簡單分析一下原因。

我們知道,HashMap是有擴容機制的。所謂的擴容機制,指的是當達到擴容條件的時候,HashMap就會自動進行擴容。而HashMap的擴容條件就是當HashMap中的元素個數(Size)超過臨界值(Threshold)的情況下就會自動擴容。

threshold = loadFactor * capacity

在元素個數超過臨界值的情況下,隨着元素的不斷增加,HashMap就會發生擴容,而HashMap中的擴容機制決定了每次擴容都需要重建hash表,這一操作需要消耗大量資源,是非常影響性能的。因此,如果我們沒有設置初始的容量大小,HashMap就可能會不斷髮生擴容,也就使得程序的性能降低了。

另外,在上面的代碼中我們會發現,同樣是設置了初始化容量,設置的數值不同也會影響性能,那麼當我們已知HashMap中即將存放的KV個數的時候,容量的設置就成了一個問題。

HashMap中容量的初始化

開頭提到,在默認的情況下,當我們設置HashMap的初始化容量時,實際上HashMap會採用第一個大於該數值的2的冪作為初始化容量。

Map<String, String> map = new HashMap<>(1);
map.put("huangq", "yanggb");

Class<?> mapType = map.getClass();
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map)); // 2

當初始化的容量設置成1的時候,通過反射取出來的capacity卻是2。在JDK1.8中,如果我們傳入的初始化容量為1,實際上設置的結果也是1。上面的代碼打印的結果為2的原因,是代碼中給map塞入值的操作導致了擴容,容量從1擴容到了2。事實上,在JDK1.7和JDK1.8中,HashMap初始化容量(capacity)的時機不同。在JDK1.8中,調用HashMap的構造函數定義HashMap的時候,就會進行容量的設定。而在JDK1.7中,要等到第一次put操作時才進行這一操作。

因此,當我們通過HashMap(int initialCapacity)設置初始容量的時候,HashMap並不一定會直接採用我們傳入的數值,而是經過計算,得到一個新值,目的是提高hash的效率。比如1->1、3->4、7->8和9->16。

HashMap中初始容量的合理值

通過上面的分析我們可以知道,當我們使用HashMap(int initialCapacity)來初始化容量的時候,JDK會默認幫我們計算一個相對合理的值當做初始容量。那麼,是不是我們只需要把已知的HashMap中即將存放的元素個數直接傳給initialCapacity就可以了呢?

initialCapacity = (需要存儲的元素個數 / 負載因子) + 1

這裏的負載因子就是loaderFactor,默認值為0.75。

initialCapacity = expectedSize / 0.75F + 1.0F

上面這個公式是《阿里巴巴Java開發手冊》中的一個建議,在Guava中也是提供了相同的算法,更甚之,這個算法實際上是JDK8中putAll()方法的實現。這是公式的得出是因為,當HashMap內部維護的哈希表的容量達到75%時(默認情況下),就會觸發rehash(重建hash表)操作。而rehash的過程是比較耗費時間的。所以初始化容量要設置成expectedSize/0.75 + 1的話,可以有效地減少衝突,也可以減小誤差。

總結

當我們想要在代碼中創建一個HashMap的時候,如果我們已知這個Map中即將存放的元素個數,給HashMap設置初始容量可以在一定程度上提升效率。

但是,JDK並不會直接拿用戶傳進來的数字當做默認容量,而是會進行一番運算,最終得到一個2的冪。而為了最大程度地避免擴容帶來的性能消耗,通常是建議可以把默認容量的数字設置成expectedSize / 0.75F + 1.0F。

在日常開發中,可以使用Guava提供的一個方法來創建一個HashMap,計算的過程Guava會幫我們完成。

Map<String, String> map = Maps.newHashMapWithExpectedSize(10);

最後要說的一點是,這種算法實際上是一種使用內存換取性能的做法,在真正的應用場景中要考慮到內存的影響。

 

“當你認真喜歡一個人的時候,你的全世界都是她。”

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

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

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

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

分類
發燒車訊

如何教會女友遞歸算法?

一到周末就開始放蕩自我,這不帶着女朋友去萬達電影院看電影(其實是由於整天呆在家敲代碼硬是

被女朋友強行拖拽去看電影,作為一個有理想的程序員,我想各位應該都能體諒我),一到電影院,

女朋友說要買爆米花和可樂,我當時二話沒說,臣本布衣躬耕於南陽,壤中羞澀,所以單買了爆米

花,買完都不帶回頭看老闆的那種,飲料喝多了不好,出門的時候我帶了白開水,還得虧我長得銷

魂,乍一看就能看出是個社會精神小伙,女朋友也沒多說什麼,只是對我微了微笑(我估計是被我的

顏值以及獨到的見解所折服),剛坐下沒多久,女朋友突然問我,咱們現在坐在第幾排啊?電影院里

面太黑了,看不清,沒法數,這個時候,如果是你現在你怎麼辦?別忘了你我是程序員,這個可難不

倒我,遞歸就開始排上用場了。於是我就問前面一排的人他是第幾排,你想只要在他的数字上加一,

就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也問他前面的人。就這樣一排一排往前

問,直到問到第一排的人,說我在第一排,然後再這樣一排一排再把数字傳回來。直到你前面的人告

訴你他在哪一排,於是你就知道答案了。這就是一個非常標準的遞歸求解問題的分解過程,去的過程

叫“遞”,回來的過程叫“歸”。基本上,所有的遞歸問題都可以用遞推公式來表示。我們用遞推公式將

它表示出來就是這樣的

f ( n ) = f (n – 1) + 1 其中,f ( 1 ) = 1

f(n)表示你想知道自己在哪一排,f(n-1)表示前面一排所在的排數,f(1)=1表示第一排的人知道自己在

第一排。有了這個遞推公式,我們就可以很輕鬆地將它改為遞歸代碼,如下:

int f(int n) {
  if (n == 1) return 1;
  return f(n-1) + 1;
}

女朋友不懂遞歸,於是我給她講遞歸需要滿足的三個條件:

1.一個問題的解可以分解為幾個子問題的解

何為子問題?子問題就是數據規模更小的問題。就好比,在電影院,你要知道,“自己在哪一排”的問

題,可以分解為“前一排的人在哪一排”這樣一個子問題。

2.這個問題與分解之後的子問題,除了數據規模不同,求解思路完全一樣

你求解“自己在哪一排”的思路,和前面一排人求解“自己在哪一排”的思路,是一模一樣的。

3.存在遞歸終止條件

把問題分解為子問題,把子問題再分解為子子問題,一層一層分解下去,不能存在無限循環,這就需

要有終止條件。就好比,第一排的人不需要再繼續詢問任何人,就知道自己在哪一排,也就是

f(1)=1,這就是遞歸的終止條件。

如何教女友敲遞歸代碼?

剛剛鋪墊了這麼多,現在我們來看,如何來教女友敲遞歸代碼?個人覺得,寫遞歸代碼最關鍵的是寫

出遞推公式,找到終止條件,剩下將遞推公式轉化為代碼就很簡單了。

你先記住這個理論。我舉一個例子,帶你一步一步實現一個遞歸代碼,幫你理解。

假如這裡有n個台階,每次你可以跨1個台階或者2個台階,請問走這n個台階有多少種走法?如果有7個台階,你可以2,2,2,1這樣子上去,也可以1,2,1,1,2這樣子上去,總之走法有很多,那如何用編程求得總共有多少種走法呢?

我們仔細想下,實際上,可以根據第一步的走法把所有走法分為兩類,第一類是第一步走了1個台

階,另一類是第一步走了2個台階。所以n個台階的走法就等於先走1階后,n-1個台階的走法 加上先

走2階后,n-2個台階的走法。用公式表示就是:

f ( n ) = f (n – 1) + f ( n – 2 )

有了遞推公式,遞歸代碼基本上就完成了一半。我們再來看下終止條件。當有一個台階時,我們不需

要再繼續遞歸,就只有一種走法。所以f(1)=1。這個遞歸終止條件足夠嗎?我們可以用n=2,n=3這樣

比較小的數試驗一下。

n=2時,f(2)=f(1)+f(0)。如果遞歸終止條件只有一個f(1)=1,那f(2)就無法求解了。所以除了f(1)=1這一

個遞歸終止條件外,還要有f(0)=1,表示走0個台階有一種走法,不過這樣子看起來就不符合正常的

邏輯思維了。所以,我們可以把f(2)=2作為一種終止條件,表示走2個台階,有兩種走法,一步走完

或者分兩步來走。

所以,遞歸終止條件就是f(1)=1,f(2)=2。這個時候,你可以再拿n=3,n=4來驗證一下,這個終止條

件是否足夠並且正確。

我們把遞歸終止條件和剛剛得到的遞推公式放到一起就是這樣的:

f(1) = 1;
f(2) = 2;
f(n) = f(n-1)+f(n-2)

有了這個公式,我們轉化成遞歸代碼就簡單多了。最終的遞歸代碼是這樣的:

int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  return f(n-1) + f(n-2);
}

我總結一下,寫遞歸代碼的關鍵就是找到如何將大問題分解為小問題的規律,並且基於此寫出遞推公式,然後再推敲終止條件,最後將遞推公式和終止條件翻譯成代碼。

如果以後再遇到類似問題,A可以分解為若干子問題B、C、D情況,你可以假設子問題B、C、D已經

解決,在此基礎上思考如何解決問題A。而且,你只需要思考問題A與子問題B、C、D兩層之間的關

系即可,不需要一層一層往下思考子問題與子子問題,子子問題與子子子問題之間的關係。屏蔽掉遞

歸細節,這樣子理解起來就簡單多了。

因此,編寫遞歸代碼的關鍵是,只要遇到遞歸,我們就把它抽象成一個遞推公式,不用想一層層的調

用關係,不要試圖用人腦去分解遞歸的每個步驟

如何教女友玩轉漢羅塔

好了,講完了遞歸算法,再回到電影院,不說別的,我還真那麼做了,我真問了前面一排的人他是第

幾排如果不清楚並讓他跟我一樣問他的上一排,顯然,沒循環到第三人,我差點被認為是神經病,差

點沒被幾個社會精神小伙打si,座位事情暫時告一段落,話說這電影屬實夠無聊,於是我不知是趁熱

打鐵,還是心血來潮,非要給女朋友玩一個漢羅塔遊戲,我這暴脾氣,剛實踐遞歸算法被懟,是時候

挽回形象了,不秀一把遞歸算法我就不得勁。就是這個遊戲,至於遊戲規則,我覺得你體驗一兩把絕

對比我說的更加記憶深刻,,別看4399覺得有點弱zhi,再怎麼說也承

載着童年

果然,女朋友是個哈皮,剛過第三關就撲街了,這個時候,頭冒五丈光芒的我身披金甲挺身而出(貌

似有一點點小誇張,劇情需要嘛)一聲不吭地敲了幾行靚麗的代碼

public class TestHanoi {

    public static void main(String[] args) {
        hanoi(5,'A','B','C');  //可以理解為5個圈或者第5關
    }
    
    /**
     * @param n     共有N個圈
     * @param A    開始的柱子
     * @param B 中間的柱子
     * @param C 目標的柱子
     * 無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。
     */
    public static void hanoi(int n,char A,char B,char C) {
        //只有一個圈。
        if(n==1) {
            System.out.println("第1個盤子從"+A+"移到"+C);
        //無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。
        }else {
            //移動上面所有的圈到中間位置
            hanoi(n-1,A,C,B);
            //移動下面的圈
            System.out.println("第"+n+"個圈從"+A+"移到"+C);
            //把上面的所有圈從中間位置移到目標位置
            hanoi(n-1,B,A,C);
        }
    }

}

只要main方法一致行,對着結果移動即可,就跟開了掛一樣的,其實漢羅塔問題核心關鍵是無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。

到這裏,教女友敲遞歸算法代碼,你學會了嗎?

哦豁,明天還是一個晴天~老天賜給宜春一個女朋友吧~畢竟我們程序員長得又帥敲代碼又好看,是吧哥幾個~~

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

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

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

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

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

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

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

分類
發燒車訊

[springboot 開發單體web shop] 7. 多種形式提供商品列表

上文回顧

我們實現了仿jd的輪播廣告以及商品分類的功能,並且講解了不同的注入方式,本節我們將繼續實現我們的電商主業務,商品信息的展示。

需求分析

首先,在我們開始本節編碼之前,我們先來分析一下都有哪些地方會對商品進行展示,打開jd首頁,鼠標下拉可以看到如下:

可以看到,在大類型下查詢了部分商品在首頁進行展示(可以是最新的,也可以是網站推薦等等),然後點擊任何一個分類,可以看到如下:

我們一般進到電商網站之後,最常用的一個功能就是搜索, 結果如下:

選擇任意一個商品點擊,都可以進入到詳情頁面,這個是單個商品的信息展示。
綜上,我們可以知道,要實現一個電商平台的商品展示,最基本的包含:

  • 首頁推薦/最新上架商品
  • 分類查詢商品
  • 關鍵詞搜索商品
  • 商品詳情展示

接下來,我們就可以開始商品相關的業務開發了。

首頁商品列表|IndexProductList

開發梳理

我們首先來實現在首頁展示的推薦商品列表,來看一下都需要展示哪些信息,以及如何進行展示。

  • 商品主鍵(product_id)
  • 展示圖片(image_url)
  • 商品名稱(product_name)
  • 商品價格(product_price)
  • 分類說明(description)
  • 分類名稱(category_name)
  • 分類主鍵(category_id)
  • 其他…

編碼實現

根據一級分類查詢

遵循開發順序,自下而上,如果基礎mapper解決不了,那麼優先編寫SQL mapper,因為我們需要在同一張表中根據parent_id遞歸的實現數據查詢,當然我們這裏使用的是錶鏈接的方式實現。因此,common mapper無法滿足我們的需求,需要自定義mapper實現。

Custom Mapper實現

和根據一級分類查詢子分類一樣,在項目mscx-shop-mapper中添加一個自定義實現接口com.liferunner.custom.ProductCustomMapper,然後在resources\mapper\custom路徑下同步創建xml文件mapper/custom/ProductCustomMapper.xml,此時,因為我們在上節中已經配置了當前文件夾可以被容器掃描到,所以我們添加的新的mapper就會在啟動時被掃描加載,代碼如下:

/**
 * ProductCustomMapper for : 自定義商品Mapper
 */
public interface ProductCustomMapper {

    /***
     * 根據一級分類查詢商品
     *
     * @param paramMap 傳遞一級分類(map傳遞多參數)
     * @return java.util.List<com.liferunner.dto.IndexProductDTO>
     */
    List<IndexProductDTO> getIndexProductDtoList(@Param("paramMap") Map<String, Integer> paramMap);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liferunner.custom.ProductCustomMapper">
    <resultMap id="IndexProductDTO" type="com.liferunner.dto.IndexProductDTO">
        <id column="rootCategoryId" property="rootCategoryId"/>
        <result column="rootCategoryName" property="rootCategoryName"/>
        <result column="slogan" property="slogan"/>
        <result column="categoryImage" property="categoryImage"/>
        <result column="bgColor" property="bgColor"/>
        <collection property="productItemList" ofType="com.liferunner.dto.IndexProductItemDTO">
            <id column="productId" property="productId"/>
            <result column="productName" property="productName"/>
            <result column="productMainImageUrl" property="productMainImageUrl"/>
            <result column="productCreateTime" property="productCreateTime"/>
        </collection>
    </resultMap>
    <select id="getIndexProductDtoList" resultMap="IndexProductDTO" parameterType="Map">
        SELECT
        c.id as rootCategoryId,
        c.name as rootCategoryName,
        c.slogan as slogan,
        c.category_image as categoryImage,
        c.bg_color as bgColor,
        p.id as productId,
        p.product_name as productName,
        pi.url as productMainImageUrl,
        p.created_time as productCreateTime
        FROM category c
        LEFT JOIN products p
        ON c.id = p.root_category_id
        LEFT JOIN products_img pi
        ON p.id = pi.product_id
        WHERE c.type = 1
        AND p.root_category_id = #{paramMap.rootCategoryId}
        AND pi.is_main = 1
        LIMIT 0,10;
    </select>
</mapper>

Service實現

serviceproject 創建com.liferunner.service.IProductService接口以及其實現類com.liferunner.service.impl.ProductServiceImpl,添加查詢方法如下:

public interface IProductService {

    /**
     * 根據一級分類id獲取首頁推薦的商品list
     *
     * @param rootCategoryId 一級分類id
     * @return 商品list
     */
    List<IndexProductDTO> getIndexProductDtoList(Integer rootCategoryId);
    ...
}

---
    
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ProductServiceImpl implements IProductService {

    // RequiredArgsConstructor 構造器注入
    private final ProductCustomMapper productCustomMapper;

    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public List<IndexProductDTO> getIndexProductDtoList(Integer rootCategoryId) {
        log.info("====== ProductServiceImpl#getIndexProductDtoList(rootCategoryId) : {}=======", rootCategoryId);
        Map<String, Integer> map = new HashMap<>();
        map.put("rootCategoryId", rootCategoryId);
        val indexProductDtoList = this.productCustomMapper.getIndexProductDtoList(map);
        if (CollectionUtils.isEmpty(indexProductDtoList)) {
            log.warn("ProductServiceImpl#getIndexProductDtoList未查詢到任何商品信息");
        }
        log.info("查詢結果:{}", indexProductDtoList);
        return indexProductDtoList;
    }
}

Controller實現

接着,在com.liferunner.api.controller.IndexController中實現對外暴露的查詢接口:

@RestController
@RequestMapping("/index")
@Api(value = "首頁信息controller", tags = "首頁信息接口API")
@Slf4j
public class IndexController {
    ...
    @Autowired
    private IProductService productService;

    @GetMapping("/rootCategorys")
    @ApiOperation(value = "查詢一級分類", notes = "查詢一級分類")
    public JsonResponse findAllRootCategorys() {
        log.info("============查詢一級分類==============");
        val categoryResponseDTOS = this.categoryService.getAllRootCategorys();
        if (CollectionUtils.isEmpty(categoryResponseDTOS)) {
            log.info("============未查詢到任何分類==============");
            return JsonResponse.ok(Collections.EMPTY_LIST);
        }
        log.info("============一級分類查詢result:{}==============", categoryResponseDTOS);
        return JsonResponse.ok(categoryResponseDTOS);
    }
    ...
}

Test API

編寫完成之後,我們需要對我們的代碼進行測試驗證,還是通過使用RestService插件來實現,當然,大家也可以通過Postman來測試,結果如下:

商品列表|ProductList

如開文之初我們看到的京東商品列表一樣,我們先分析一下在商品列表頁面都需要哪些元素信息?

開發梳理

商品列表的展示按照我們之前的分析,總共分為2大類:

  • 選擇商品分類之後,展示當前分類下所有商品
  • 輸入搜索關鍵詞后,展示當前搜索到相關的所有商品

在這兩類中展示的商品列表數據,除了數據來源不同以外,其他元素基本都保持一致,那麼我們是否可以使用統一的接口來根據參數實現隔離呢? 理論上不存在問題,完全可以通過傳參判斷的方式進行數據回傳,但是,在我們實現一些可預見的功能需求時,一定要給自己的開發預留後路,也就是我們常說的可拓展性,基於此,我們會分開實現各自的接口,以便於後期的擴展。
接着來分析在列表頁中我們需要展示的元素,首先因為需要分上述兩種情況,因此我們需要在我們API設計的時候分別處理,針對於
1.分類的商品列表展示,需要傳入的參數有:

  • 分類id
  • 排序(在電商列表我們常見的幾種排序(銷量,價格等等))
  • 分頁相關(因為我們不可能把數據庫中所有的商品都取出來)
    • PageNumber(當前第幾頁)
    • PageSize(每頁显示多少條數據)

2.關鍵詞查詢商品列表,需要傳入的參數有:

  • 關鍵詞
  • 排序(在電商列表我們常見的幾種排序(銷量,價格等等))
  • 分頁相關(因為我們不可能把數據庫中所有的商品都取出來)
    • PageNumber(當前第幾頁)
    • PageSize(每頁显示多少條數據)

需要在頁面展示的信息有:

  • 商品id(用於跳轉商品詳情使用)
  • 商品名稱
  • 商品價格
  • 商品銷量
  • 商品圖片
  • 商品優惠

編碼實現

根據上面我們的分析,接下來開始我們的編碼:

根據商品分類查詢

根據我們的分析,肯定不會在一張表中把所有數據獲取全,因此我們需要進行多表聯查,故我們需要在自定義mapper中實現我們的功能查詢.

ResponseDTO 實現

根據我們前面分析的前端需要展示的信息,我們來定義一個用於展示這些信息的對象com.liferunner.dto.SearchProductDTO,代碼如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SearchProductDTO {
    private String productId;
    private String productName;
    private Integer sellCounts;
    private String imgUrl;
    private Integer priceDiscount;
    //商品優惠,我們直接計算之後返回優惠后價格
}

Custom Mapper 實現

com.liferunner.custom.ProductCustomMapper.java中新增一個方法接口:

    List<SearchProductDTO> searchProductListByCategoryId(@Param("paramMap") Map<String, Object> paramMap);

同時,在mapper/custom/ProductCustomMapper.xml中實現我們的查詢方法:

<select id="searchProductListByCategoryId" resultType="com.liferunner.dto.SearchProductDTO" parameterType="Map">
        SELECT
        p.id as productId,
        p.product_name as productName,
        p.sell_counts as sellCounts,
        pi.url as imgUrl,
        tp.priceDiscount
        FROM products p
        LEFT JOIN products_img pi
        ON p.id = pi.product_id
        LEFT JOIN
        (
        SELECT product_id, MIN(price_discount) as priceDiscount
        FROM products_spec
        GROUP BY product_id
        ) tp
        ON tp.product_id = p.id
        WHERE pi.is_main = 1
        AND p.category_id = #{paramMap.categoryId}
        ORDER BY
        <choose>
            <when test="paramMap.sortby != null and paramMap.sortby == 'sell'">
                p.sell_counts DESC
            </when>
            <when test="paramMap.sortby != null and paramMap.sortby == 'price'">
                tp.priceDiscount ASC
            </when>
            <otherwise>
                p.created_time DESC
            </otherwise>
        </choose>
    </select>

主要來說明一下這裏的<choose>模塊,以及為什麼不使用if標籤。
在有的時候,我們並不希望所有的條件都同時生效,而只是想從多個選項中選擇一個,但是在使用IF標籤時,只要test中的表達式為 true,就會執行IF 標籤中的條件。MyBatis 提供了 choose 元素。IF標籤是與(and)的關係,而 choose 是或(or)的關係。
它的選擇是按照順序自上而下,一旦有任何一個滿足條件,則選擇退出。

Service 實現

然後在servicecom.liferunner.service.IProductService中添加方法接口:

    /**
     * 根據商品分類查詢商品列表
     *
     * @param categoryId 分類id
     * @param sortby     排序方式
     * @param pageNumber 當前頁碼
     * @param pageSize   每頁展示多少條數據
     * @return 通用分頁結果視圖
     */
    CommonPagedResult searchProductList(Integer categoryId, String sortby, Integer pageNumber, Integer pageSize);

在實現類com.liferunner.service.impl.ProductServiceImpl中,實現上述方法:

    // 方法重載
    @Override
    public CommonPagedResult searchProductList(Integer categoryId, String sortby, Integer pageNumber, Integer pageSize) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("categoryId", categoryId);
        paramMap.put("sortby", sortby);
        // mybatis-pagehelper
        PageHelper.startPage(pageNumber, pageSize);
        val searchProductDTOS = this.productCustomMapper.searchProductListByCategoryId(paramMap);
        // 獲取mybatis插件中獲取到信息
        PageInfo<?> pageInfo = new PageInfo<>(searchProductDTOS);
        // 封裝為返回到前端分頁組件可識別的視圖
        val commonPagedResult = CommonPagedResult.builder()
                .pageNumber(pageNumber)
                .rows(searchProductDTOS)
                .totalPage(pageInfo.getPages())
                .records(pageInfo.getTotal())
                .build();
        return commonPagedResult;
    }

在這裏,我們使用到了一個mybatis-pagehelper插件,會在下面的福利講解中分解。

Controller 實現

繼續在com.liferunner.api.controller.ProductController中添加對外暴露的接口API:

@GetMapping("/searchByCategoryId")
    @ApiOperation(value = "查詢商品信息列表", notes = "根據商品分類查詢商品列表")
    public JsonResponse searchProductListByCategoryId(
        @ApiParam(name = "categoryId", value = "商品分類id", required = true, example = "0")
        @RequestParam Integer categoryId,
        @ApiParam(name = "sortby", value = "排序方式", required = false)
        @RequestParam String sortby,
        @ApiParam(name = "pageNumber", value = "當前頁碼", required = false, example = "1")
        @RequestParam Integer pageNumber,
        @ApiParam(name = "pageSize", value = "每頁展示記錄數", required = false, example = "10")
        @RequestParam Integer pageSize
    ) {
        if (null == categoryId || categoryId == 0) {
            return JsonResponse.errorMsg("分類id錯誤!");
        }
        if (null == pageNumber || 0 == pageNumber) {
            pageNumber = DEFAULT_PAGE_NUMBER;
        }
        if (null == pageSize || 0 == pageSize) {
            pageSize = DEFAULT_PAGE_SIZE;
        }
        log.info("============根據分類:{} 搜索列表==============", categoryId);

        val searchResult = this.productService.searchProductList(categoryId, sortby, pageNumber, pageSize);
        return JsonResponse.ok(searchResult);
    }

因為我們的請求中,只會要求商品分類id是必填項,其餘的調用方都可以不提供,但是如果不提供的話,我們系統就需要給定一些默認的參數來保證我們的系統正常穩定的運行,因此,我定義了com.liferunner.api.controller.BaseController,用於存儲一些公共的配置信息。

/**
 * BaseController for : controller 基類
 */
@Controller
public class BaseController {
    /**
     * 默認展示第1頁
     */
    public final Integer DEFAULT_PAGE_NUMBER = 1;
    /**
     * 默認每頁展示10條數據
     */
    public final Integer DEFAULT_PAGE_SIZE = 10;
}

Test API

測試的參數分別是:categoryId : 51 ,sortby : price,pageNumber : 1,pageSize : 5

可以看到,我們查詢到7條數據,總頁數totalPage為2,並且根據價格從小到大進行了排序,證明我們的編碼是正確的。接下來,通過相同的代碼邏輯,我們繼續實現根據搜索關鍵詞進行查詢。

根據關鍵詞查詢

Response DTO 實現

使用上面實現的com.liferunner.dto.SearchProductDTO.

Custom Mapper 實現

com.liferunner.custom.ProductCustomMapper中新增方法:

List<SearchProductDTO> searchProductList(@Param("paramMap") Map<String, Object> paramMap);

mapper/custom/ProductCustomMapper.xml中添加查詢SQL:

<select id="searchProductList" resultType="com.liferunner.dto.SearchProductDTO" parameterType="Map">
        SELECT
        p.id as productId,
        p.product_name as productName,
        p.sell_counts as sellCounts,
        pi.url as imgUrl,
        tp.priceDiscount
        FROM products p
        LEFT JOIN products_img pi
        ON p.id = pi.product_id
        LEFT JOIN
        (
        SELECT product_id, MIN(price_discount) as priceDiscount
        FROM products_spec
        GROUP BY product_id
        ) tp
        ON tp.product_id = p.id
        WHERE pi.is_main = 1
        <if test="paramMap.keyword != null and paramMap.keyword != ''">
            AND p.item_name LIKE "%${paramMap.keyword}%"
        </if>
        ORDER BY
        <choose>
            <when test="paramMap.sortby != null and paramMap.sortby == 'sell'">
                p.sell_counts DESC
            </when>
            <when test="paramMap.sortby != null and paramMap.sortby == 'price'">
                tp.priceDiscount ASC
            </when>
            <otherwise>
                p.created_time DESC
            </otherwise>
        </choose>
    </select>

Service 實現

com.liferunner.service.IProductService中新增查詢接口:

    /**
     * 查詢商品列表
     *
     * @param keyword    查詢關鍵詞
     * @param sortby     排序方式
     * @param pageNumber 當前頁碼
     * @param pageSize   每頁展示多少條數據
     * @return 通用分頁結果視圖
     */
    CommonPagedResult searchProductList(String keyword, String sortby, Integer pageNumber, Integer pageSize);

com.liferunner.service.impl.ProductServiceImpl實現上述接口方法:

    @Override
    public CommonPagedResult searchProductList(String keyword, String sortby, Integer pageNumber, Integer pageSize) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("keyword", keyword);
        paramMap.put("sortby", sortby);
        // mybatis-pagehelper
        PageHelper.startPage(pageNumber, pageSize);
        val searchProductDTOS = this.productCustomMapper.searchProductList(paramMap);
        // 獲取mybatis插件中獲取到信息
        PageInfo<?> pageInfo = new PageInfo<>(searchProductDTOS);
        // 封裝為返回到前端分頁組件可識別的視圖
        val commonPagedResult = CommonPagedResult.builder()
                .pageNumber(pageNumber)
                .rows(searchProductDTOS)
                .totalPage(pageInfo.getPages())
                .records(pageInfo.getTotal())
                .build();
        return commonPagedResult;
    }

上述方法和之前searchProductList(Integer categoryId, String sortby, Integer pageNumber, Integer pageSize)唯一的區別就是它是肯定搜索關鍵詞來進行數據查詢,使用重載的目的是為了我們後續不同類型的業務擴展而考慮的。

Controller 實現

com.liferunner.api.controller.ProductController中添加關鍵詞搜索API:

    @GetMapping("/search")
    @ApiOperation(value = "查詢商品信息列表", notes = "查詢商品信息列表")
    public JsonResponse searchProductList(
        @ApiParam(name = "keyword", value = "搜索關鍵詞", required = true)
        @RequestParam String keyword,
        @ApiParam(name = "sortby", value = "排序方式", required = false)
        @RequestParam String sortby,
        @ApiParam(name = "pageNumber", value = "當前頁碼", required = false, example = "1")
        @RequestParam Integer pageNumber,
        @ApiParam(name = "pageSize", value = "每頁展示記錄數", required = false, example = "10")
        @RequestParam Integer pageSize
    ) {
        if (StringUtils.isBlank(keyword)) {
            return JsonResponse.errorMsg("搜索關鍵詞不能為空!");
        }
        if (null == pageNumber || 0 == pageNumber) {
            pageNumber = DEFAULT_PAGE_NUMBER;
        }
        if (null == pageSize || 0 == pageSize) {
            pageSize = DEFAULT_PAGE_SIZE;
        }
        log.info("============根據關鍵詞:{} 搜索列表==============", keyword);

        val searchResult = this.productService.searchProductList(keyword, sortby, pageNumber, pageSize);
        return JsonResponse.ok(searchResult);
    }

Test API

測試參數:keyword : 西鳳,sortby : sell,pageNumber : 1,pageSize : 10

根據銷量排序正常,查詢關鍵詞正常,總條數32,每頁10條,總共3頁正常。

福利講解

在本節編碼實現中,我們使用到了一個通用的mybatis分頁插件mybatis-pagehelper,接下來,我們來了解一下這個插件的基本情況。

mybatis-pagehelper

如果各位小夥伴使用過:, 那麼對於這個就很容易理解了,它其實就是基於來實現的,當攔截到原始SQL之後,對SQL進行一次改造處理。
我們來看看我們自己代碼中的實現,根據springboot編碼三部曲:

1.添加依賴

        <!-- 引入mybatis-pagehelper 插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>

有同學就要問了,為什麼引入的這個依賴和我原來使用的不同?以前使用的是:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>

答案就在這裏:

我們使用的是springboot進行的項目開發,既然使用的是springboot,那我們完全可以用到它的自動裝配特性,作者幫我們實現了這麼一個,我們只需要參考示例來編寫就ok了。

2.改配置

# mybatis 分頁組件配置
pagehelper:
  helperDialect: mysql #插件支持12種數據庫,選擇類型
  supportMethodsArguments: true

3.改代碼

如下示例代碼:

    @Override
    public CommonPagedResult searchProductList(String keyword, String sortby, Integer pageNumber, Integer pageSize) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("keyword", keyword);
        paramMap.put("sortby", sortby);
        // mybatis-pagehelper
        PageHelper.startPage(pageNumber, pageSize);
        val searchProductDTOS = this.productCustomMapper.searchProductList(paramMap);
        // 獲取mybatis插件中獲取到信息
        PageInfo<?> pageInfo = new PageInfo<>(searchProductDTOS);
        // 封裝為返回到前端分頁組件可識別的視圖
        val commonPagedResult = CommonPagedResult.builder()
                .pageNumber(pageNumber)
                .rows(searchProductDTOS)
                .totalPage(pageInfo.getPages())
                .records(pageInfo.getTotal())
                .build();
        return commonPagedResult;
    }

在我們查詢數據庫之前,我們引入了一句PageHelper.startPage(pageNumber, pageSize);,告訴mybatis我們要對查詢進行分頁處理,這個時候插件會啟動一個攔截器com.github.pagehelper.PageInterceptor,針對所有的query進行攔截,添加自定義參數和添加查詢數據總數。(後續我們會打印sql來證明。)

當查詢到結果之後,我們需要將我們查詢到的結果通知給插件,也就是PageInfo<?> pageInfo = new PageInfo<>(searchProductDTOS);com.github.pagehelper.PageInfo是對插件針對分頁做的一個屬性包裝,具體可以查看)。

至此,我們的插件使用就已經結束了。但是為什麼我們在後面又封裝了一個對象來對外進行返回,而不是使用查詢到的PageInfo呢?這是因為我們實際開發過程中,為了數據結構的一致性做的一次結構封裝,你也可不實現該步驟,都是對結果沒有任何影響的。

SQL打印對比

2019-11-21 12:04:21 INFO  ProductController:134 - ============根據關鍵詞:西鳳 搜索列表==============
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4ff449ba] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1980420239 wrapping com.mysql.cj.jdbc.ConnectionImpl@563b22b1] will not be managed by Spring
==>  Preparing: SELECT count(0) FROM products p LEFT JOIN products_img pi ON p.id = pi.product_id LEFT JOIN (SELECT product_id, MIN(price_discount) AS priceDiscount FROM products_spec GROUP BY product_id) tp ON tp.product_id = p.id WHERE pi.is_main = 1 AND p.product_name LIKE "%西鳳%" 
==> Parameters: 
<==    Columns: count(0)
<==        Row: 32
<==      Total: 1
==>  Preparing: SELECT p.id as productId, p.product_name as productName, p.sell_counts as sellCounts, pi.url as imgUrl, tp.priceDiscount FROM product p LEFT JOIN products_img pi ON p.id = pi.product_id LEFT JOIN ( SELECT product_id, MIN(price_discount) as priceDiscount FROM products_spec GROUP BY product_id ) tp ON tp.product_id = p.id WHERE pi.is_main = 1 AND p.product_name LIKE "%西鳳%" ORDER BY p.sell_counts DESC LIMIT ? 
==> Parameters: 10(Integer)

我們可以看到,我們的SQL中多了一個SELECT count(0),第二條SQL多了一個LIMIT參數,在代碼中,我們很明確的知道,我們並沒有显示的去搜索總數和查詢條數,可以確定它就是插件幫我們實現的。

源碼下載

下節預告

下一節我們將繼續開發商品詳情展示以及商品評價業務,在過程中使用到的任何開發組件,我都會通過專門的一節來進行介紹的,兄弟們末慌!

gogogo!

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

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

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

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

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

分類
發燒車訊

面向對象和面向過程詳解

1.前言

其實一直對面向過程和面向對象的概念和區別沒有很深入的理解,在自己不斷想完善自己的知識體系中,今天借這個時間,寫一篇博客。來深入的了解面向過程與面向對象!好記性不如爛筆頭!!  

2.面向對象與面向過程的區別

面向過程就是分析出解決問題所需要的步驟,然後用函數把這些步驟一步一步實現,使用的時候一個一個依次調用就可以了;面向對象是把構成問題事務分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描敘某個事物在整個解決問題的步驟中的行為。

舉一個下五子棋通俗例子吧 哈哈哈 我感覺這兒例子很容易讓人理解

面向過程的設計思路就是首先分析問題的步驟:

1、開始遊戲,

2、黑子先走,

3、繪製畫面,

4、判斷輸贏,

5、輪到白子,

6、繪製畫面,

7、判斷輸贏,

8、返回步驟2,

9、輸出最後結果。

把上面每個步驟用分別的函數來實現,問題就解決了。

面向對象的設計則是從另外的思路來解決問題。整個五子棋可以分為

1、黑白雙方,這兩方的行為是一模一樣的,

2、棋盤系統,負責繪製畫面,

3、規則系統,負責判定諸如犯規、輸贏等。第一類對象(玩家對象)負責接受用戶輸入,並告知第二類對象(棋盤對象)棋子布局的變化,

棋盤對象接收到了棋子的i變化就要負責在屏幕上面显示出這種變化,同時利用第三類對象(規則系統)來對棋局進行判定。

可以明顯地看出,面向對象是以功能來劃分問題,而不是步驟。同樣是繪製棋局,這樣的行為在面向過程的設計中分散在了總多步驟中,很可能出現不同的繪製版本,因為通常設計人員會考慮到實際情況進行各種各樣的簡化。而面向對象的設計中,繪圖只可能在棋盤對象中出現,從而保證了繪圖的統一。

功能上的統一保證了面向對象設計的可擴展性。比如要加入悔棋的功能,如果要改動面向過程的設計,那麼從輸入到判斷到显示這一連串的步驟都要改動,甚至步驟之間的循序都要進行大規模調整。如果是面向對象的話,只用改動棋盤對象就行了,棋盤系統保存了黑白雙方的棋譜,簡單回溯就可以了,而显示和規則判斷則不用顧及,同時整個對對象功能的調用順序都沒有變化,改動只是局部的。

再比如我要把這個五子棋遊戲改為圍棋遊戲,如果你是面向過程設計,那麼五子棋的規則就分佈在了你的程序的每一個角落,要改動還不如重寫。但是如果你當初就是面向對象的設計,那麼你只用改動規則對象就可以了,五子棋和圍棋的區別不就是規則嗎?(當然棋盤大小好像也不一樣,但是你會覺得這是一個難題嗎?直接在棋盤對象中進行一番小改動就可以了。)而下棋的大致步驟從面向對象的角度來看沒有任何變化。

當然,要達到改動只是局部的需要設計的人有足夠的經驗,使用對象不能保證你的程序就是面向對象,初學者或者很蹩腳的程序員很可能以面向對象之虛而行面向過程之實,這樣設計出來的所謂面向對象的程序很難有良好的可移植性和可擴展性

三、面向過程與面向對象的優缺點
很多資料上全都是一群很難理解的理論知識,整的我頭都大了,後來發現了一個比較好的文章,寫的真是太棒了,通俗易懂,想要不明白都難!

用面向過程的方法寫出來的程序是一份蛋炒飯,而用面向對象寫出來的程序是一份蓋澆飯。所謂蓋澆飯,北京叫蓋飯,東北叫燴飯,廣東叫碟頭飯,就是在一碗白米飯上面澆上一份蓋菜,你喜歡什麼菜,你就澆上什麼菜。我覺得這個比喻還是比較貼切的。

蛋炒飯製作是把米飯和雞蛋混在一起炒勻。蓋澆飯呢,則是把米飯和蓋菜分別做好,你如果要一份紅燒肉蓋飯呢,就給你澆一份紅燒肉;如果要一份青椒土豆蓋澆飯,就給澆一份青椒土豆絲。

蛋炒飯的好處就是入味均勻,吃起來香。如果恰巧你不愛吃雞蛋,只愛吃青菜的話,那麼唯一的辦法就是全部倒掉,重新做一份青菜炒飯了。蓋澆飯就沒這麼多麻煩,你只需要把上面的蓋菜撥掉,更換一份蓋菜就可以了。蓋澆飯的缺點是入味不均,可能沒有蛋炒飯那麼香。

到底是蛋炒飯好還是蓋澆飯好呢?其實這類問題都很難回答,非要比個上下高低的話,就必須設定一個場景,否則只能說是各有所長。如果大家都不是美食家,沒那麼多講究,那麼從飯館角度來講的話,做蓋澆飯顯然比蛋炒飯更有優勢,他可以組合出來任意多的組合,而且不會浪費。

蓋澆飯的好處就是”菜”“飯”分離,從而提高了製作蓋澆飯的靈活性。飯不滿意就換飯,菜不滿意換菜。用軟件工程的專業術語就是”可維護性“比較好,”飯” 和”菜”的耦合度比較低。蛋炒飯將”蛋”“飯”攪和在一起,想換”蛋”“飯”中任何一種都很困難,耦合度很高,以至於”可維護性”比較差。軟件工程追求的目標之一就是可維護性,可維護性主要表現在3個方面:可理解性、可測試性和可修改性。面向對象的好處之一就是顯著的改善了軟件系統的可維護性。
看了這篇文章,簡單的總結一下!

面向過程

優點:性能比面向對象高,因為類調用時需要實例化,開銷比較大,比較消耗資源;比如嵌入式開發、 Linux/Unix等一般採用面向過程開發,性能是最重要的因素。
缺點:沒有面向對象易維護、易復用、易擴展
面向對象

優點:易維護、易復用、易擴展,由於面向對象有封裝、繼承、多態性的特性,可以設計出低耦合的系統,使系統 更加靈活、更加易於維護

缺點:性能比面向過程低

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

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

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

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

分類
發燒車訊

堡壘機的核心武器: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 是電子產業重要的元件?

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

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

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

分類
發燒車訊

Maven系列第9篇:多環境構建支持,核心開發必備!

maven系列目標:從入門開始開始掌握一個高級開發所需要的maven技能。

這是maven系列第9篇。

整個maven系列的內容前後是有依賴的,如果之前沒有接觸過maven,建議從第一篇看起,本文尾部有maven完整系列的連接。

如果你作為公司核心開發,打算使用maven來搭建項目骨架,這篇文章的內容是你必須要掌握的。

平時我們在開發系統的時候,會有開發環境、測試環境、線上環境,每個環境中配置文件可能都是不一樣的,比如:數據庫的配置,靜態資源的配置等等,所以我們希望構建工具能夠適應不同環境的構建工作,能夠靈活處理,並且操作足夠簡單。Maven作為一款優秀的構建工具,這方面做的足夠好了,能夠很好的適應不同環境的構建工作,本文主要講解maven如何靈活的處理各種不同環境的構建工作,廢話不多說,上乾貨。

重點提示

本文中的所有案例均在上一篇的b2b項目上進行操作,上一篇還沒有看的可以移步過去看一下:

所有mvn命令均在b2b/pom.xml所在目錄執行。

Maven屬性

自定義屬性

maven屬性前面我們有用到過,可以自定義一些屬性進行重用,如下:

<properties>
    <spring.verion>5.2.1.RELEASE</spring.verion>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.verion}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.verion}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.verion}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.verion}</version>
    </dependency>
</dependencies>

可以看到上面依賴了4個spring相關的構建,他們的版本都是一樣的,在properties元素中自定義了一個spring.version屬性,值為spring的版本號,其他幾個地方使用${}直接進行引用,這種方式好處還是比較明顯的,升級spring版本的時候非常方便,只需要修改一個地方,方便維護。

上面這個是maven自定義屬性,需要先在properties中定義,然後才可以在其他地方使用${屬性元素名稱}進行引用。

maven的屬性主要分為2大類,第一類就是上面說的自定義屬性,另外一類是不需要自定義的,可以直接拿來使用的。

2類屬性在pom.xml中都是採用${屬性名稱}進行引用,maven運行的時候會將${}替換為屬性實際的值。

下面我們來看一下maven中不需要自定義的5類屬性。

內置屬性

${basedir}:表示項目根目錄,即包含pom.xml文件的目錄
${version}:表示項目的版本號

POM屬性

用戶可以使用該屬性引用pom.xml文件中對應元素的值,例如${project.artifactId}就可以取到project->artifactId元素的值,常用的有:

${pom.build.sourceDirectory}:項目的主源碼目錄,默認為src/main/java/
${project.build.testSourceDirectory}:項目的測試源碼目錄,默認為src/test/java/
${project.build.directory}:項目構建輸出目錄,默認為target/
${project.build.outputDirectory}:項目主代碼編譯輸出目錄,默認為target/classes
${project.build.testOutputDirectory}:項目測試代碼編譯輸出目錄,默認為target/test-classes
${project.groupId}:項目的groupId
${project.artifactId}:項目的artifactId
${project.version}:項目的version,與${version}等價
${project.build.finalName}:項目打包輸出文件的名稱,默認為${project.artifactId}-${project.version}

Settings屬性

這種屬性以settings.開頭來引用~/.m2/settings.xml中的內容,如:

${settings.localRepository}

指向用戶本地倉庫的地址。

java系統屬性

所有java系統屬性都可以使用maven屬性來進行引用,例如${user.home}指向了當前用戶目錄。

java系統屬性可以通過mvn help:system命令看到。

環境變量屬性

所有的環境變量都可以使用env.開頭的方式來進行引用,如:

${env.JAVA_HOME}

可以獲取環境變量JAVA_HOME的值。

用戶可以使用mvn help:system命令查看所有環境變量的值。

上面的maven屬性,我們在pom.xml中通過${屬性名稱}可以靈活的引用,對我們寫pom.xml文件幫助還是比較大的。

實操案例

將下面配置放在b2b-account-service/pom.xml中:

<properties>
    <!-- 項目的主源碼目錄,默認為src/main/java/ -->
    <a>${pom.build.sourceDirectory}</a>
    <!-- 項目的測試源碼目錄,默認為src/test/java/ -->
    <b>${project.build.testSourceDirectory}</b>
    <!-- 項目構建輸出目錄,默認為target/ -->
    <c>${project.build.directory}</c>
    <!-- 項目主代碼編譯輸出目錄,默認為target/classes -->
    <d>${project.build.outputDirectory}</d>
    <!-- 項目測試代碼編譯輸出目錄,默認為target/test-classes -->
    <e>${project.build.testOutputDirectory}</e>
    <!-- 項目的groupId -->
    <f>${project.groupId}</f>
    <!-- 項目的artifactId -->
    <g>${project.artifactId}</g>
    <!-- 項目的version,與${version}等價-->
    <h>${project.version}</h>
    <!-- 項目打包輸出文件的名稱,默認為${project.artifactId}-${project.version} -->
    <i>${project.build.finalName}</i>

    <!-- setting屬性 -->
    <!-- 獲取本地倉庫地址-->
    <a1>${settings.localRepository}</a1>

    <!-- 系統屬性 -->
    <!-- 用戶目錄 -->
    <a2>${user.home}</a2>

    <!-- 環境變量屬性,獲取環境變量JAVA_HOME的值 -->
    <a3>${env.JAVA_HOME}</a3>
</properties>

然後在b2b/pom.xml所在目錄執行下面命令:

D:\code\IdeaProjects\b2b>mvn help:effective-pom -pl :b2b-account-service > 1.xml

上面這個命令會將mvn ...執行的結果輸出到b2b/1.xml目錄,mvn這段命令有看不懂的朋友,可以去看看前面的文章。

b2b目錄中產生了一個1.xml,如下:

我們打開1.xml看一下,部分內容如下:

<properties>
  <a>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\src\main\java</a>
  <a1>${settings.localRepository}</a1>
  <a2>C:\Users\Think</a2>
  <a3>D:\installsoft\Java\jdk1.8.0_121</a3>
  <b>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\src\test\java</b>
  <b2>D:\code\IdeaProjects\b2b</b2>
  <c>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target</c>
  <d>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\classes</d>
  <e>D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\test-classes</e>
  <f>com.javacode2018</f>
  <g>b2b-account-service</g>
  <h>1.0-SNAPSHOT</h>
  <i>b2b-account-service-1.0-SNAPSHOT</i>
</properties>

大家去和上面的pom.xml中的對比一下,感受一下!

多套環境構建問題

b2b-account-service會操作數據庫,所以我們需要一個配置文件來放數據庫的配置信息,配置文件一般都放在src/main/resources中,在這個目錄中新建一個jdbc.properties文件,內容如下:

jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

現在系統需要打包,我們運行下面命令

mvn clean package -pl :b2b-account-service

問題來了:

上面jdbc的配置的是開發庫的db信息,打包之後生成的jar中也是上面開發環境的配置,那麼上測試環境是不是我們需要修改上面的配置,最終上線的時候,上面的配置是不是又得重新修改一次,相當痛苦的。

我們能不能寫3套環境的jdbc配置,打包的時候去指定具體使用那套配置?

還是你們聰明,maven支持這麼做,pom.xml的project元素下面提供了一個profiles元素可以用來對多套環境進行配置。

在講profiles的使用之前,需要先了解資源文件打包的過程。

理解資源文件打包過程

我們將src/main/resouces/jdbc.properties複製一份到src/test/resources目錄,2個文件如下:

我們先運行一下下面命令:

mvn clean package -pl :b2b-account-service

輸出如下:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.572 s
[INFO] Finished at: 2019-11-21T17:13:20+08:00
[INFO] ------------------------------------------------------------------------

再去看一下項目的target目錄,如下圖:

下面我們來對這個過程做詳細分析:

從輸出中可以看到有下面幾行:

[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource

從上面輸出中可以看出,使用了插件maven-resources-plugin的resources目標,將src/main/resouces目錄中的資源文件複製到了target/classess目錄,複製了一個文件,複製過程中使用是GBK編碼。

還有幾行輸出如下:

[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource

從上面輸出中可以看出,使用了插件maven-resources-plugin的testResources目標,將src/main/resouces目錄中的資源文件複製到了target/classess目錄,複製了一個文件,複製過程中使用是GBK編碼。

resources目錄中的文件一般放的都是配置文件,配置文件一般最好我們都不會寫死,所以此處有幾個問題:

  1. 這個插件複製資源文件如何設置編碼?
  2. 複製的過程中是否能夠對資源文件進行替換,比如在資源文件中使用一些佔位符,然後複製過程中對這些佔位符進行替換。

maven-resources-plugin這個插件還真好,他也想到了這個功能,幫我們提供了這樣的功能,下面我們來看看。

設置資源文件複製過程採用的編碼

這個之前有提到過,有好幾種方式,具體可以去前面的文章看一下。這裏只說一種:

<properties>
    <encoding>UTF-8</encoding>
</properties>

設置資源文件內容動態替換

資源文件中可以通過${maven屬性}來引用maven屬性中的值,打包的過程中這些會被替換掉,替換的過程默認是不開啟的,需要手動開啟配置。

修改src/main/resource/jdbc.properties內容如下:

jdbc.url=${jdbc.url}
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}

修改src/test/resource/jdbc.properties內容如下:

jdbc.url=${jdbc.url}
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}

b2b-account-service/pom.xml中加入下面內容:

<properties>
    <!-- 指定資源文件複製過程中採用的編碼方式 -->
    <encoding>UTF-8</encoding>
    <jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
    <jdbc.username>root</jdbc.username>
    <jdbc.password>root</jdbc.password>
</properties>

開啟動態替換配置,需要在pom.xml中加入下面配置:

<build>
    <resources>
        <resource>
            <!-- 指定資源文件的目錄 -->
            <directory>${project.basedir}/src/main/resources</directory>
            <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
            <filtering>true</filtering>
        </resource>
    </resources>
    <testResources>
        <testResource>
            <!-- 指定資源文件的目錄 -->
            <directory>${project.basedir}/src/test/resources</directory>
            <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
            <filtering>true</filtering>
        </testResource>
    </testResources>
</build>

注意上面開啟動態替換的元素是filtering

上面build元素中的resourcestestResources是用來控制構建過程中資源文件配置信息的,比資源文件位於哪個目錄,需要複製到那個目錄,是否開啟動態過濾等信息。

resources元素中可以包含多個resource,每個resource表示一個資源的配置信息,一般使用來控制主資源的複製的。

testResources元素和testResources類似,是用來控制測試資源複製的。

最終pom.xml內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
        <jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
        <jdbc.username>root</jdbc.username>
        <jdbc.password>root</jdbc.password>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>
    </build>

</project>

運行下面命令看效果:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.798 s
[INFO] Finished at: 2019-11-21T17:53:48+08:00
[INFO] ------------------------------------------------------------------------

4個文件截圖看一下效果,如下:

target中的2個資源文件內容被動態替換掉了。

上面會將資源文件中${}的內容使用maven屬性中的值進行替換,${}中包含的內容默認會被替換,那麼我們是否可以自定義${}這個格式,比如我希望被##包含內容進行替換,這個就涉及到替換中分隔符的指定了,需要設置插件的一些參數。

自定義替換的分隔符

自定義分隔符,需要我們配置maven-resources-plugin插件的參數,如下:

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.6</version>
        <configuration>
            <!-- 是否使用默認的分隔符,默認分隔符是${*}和@ ,這個地方設置為false,表示不啟用默認分隔符配置-->
            <useDefaultDelimiters>false</useDefaultDelimiters>
            <!-- 自定義分隔符 -->
            <delimiters>
                <delimiter>$*$</delimiter>
                <delimiter>##</delimiter>
            </delimiters>
        </configuration>
    </plugin>
</plugins>

delimiters中可以配置多個delimiter,可以配置#*#,其中的*表示屬性名稱,那麼資源文件中的#屬性名#在複製的過程中會被替換掉,*前後都是#,表示前後分隔符都一樣,那麼可以簡寫為#,所以#*##寫法是一樣的,我們去看一下源碼,delimiters的默認值如下:

this.delimiters.add("${*}");
this.delimiters.add("@");

案例

我們將pom.xml修改成下面這樣:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
        <jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
        <jdbc.username>root</jdbc.username>
        <jdbc.password>root</jdbc.password>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!-- 是否使用默認的分隔符,默認分隔符是${*}和@ ,這個地方設置為false,表示不啟用默認分隔符配置-->
                    <useDefaultDelimiters>false</useDefaultDelimiters>
                    <!-- 自定義分隔符 -->
                    <delimiters>
                        <delimiter>$*$</delimiter>
                        <delimiter>##</delimiter>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

將兩個jdbc.properties修改成下面這樣:

jdbc.url=${jdbc.url}
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

再運行一下下面的命令:

mvn clean package -pl :b2b-account-service

看一下target/classes中的jdbc.properties文件,變成了下面這樣:

jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=root

可以看到後面兩符合分隔符的要求,被替換了,第一個沒有被替換。

指定需要替換的資源文件

src/main/resources中新增一個const.properties文件,內容如下:

email=javacode2018@163.com
jdbc.url=${jdbc.url}
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

執行下面命令:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.667 s
[INFO] Finished at: 2019-11-21T18:26:01+08:00
[INFO] ------------------------------------------------------------------------

看一下target/classes中,如下圖:

target/classes/const.properties內容變成下面這樣了:

email=javacode2018@163.com
jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=root

說明const.properteis也被替換了。2個資源文件都被複制到了target中,如果我們不想讓cont.properties被複制到target/classes目錄,我們怎麼做?我們需要在資源構建的過程中排除他,可以使用exclude元素信息進行排除操作。

修改pom.xml中resources元素配置如下:

<resources>
    <resource>
        <!-- 指定資源文件的目錄 -->
        <directory>${project.basedir}/src/main/resources</directory>
        <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
        <filtering>true</filtering>
        <includes>
            <include>**/jdbc.properties</include>
        </includes>
        <excludes>
            <exclude>**/const.properties</exclude>
        </excludes>
    </resource>
</resources>

上面使用includes列出需要被處理的,使用excludes排除需要被處理的資源文件列表,採用通配符的寫法,**匹配任意深度的文件路徑,*匹配任意個字符。

再執行下面命令:

mvn clean package -pl :b2b-account-service

再看一下target/classes中,如下:

const.properties被排除了,確實沒有被複制過來。

如果此時我想讓const.propertis只是被複制到target下面,但是不要去替換裏面的內容,該怎麼做呢?此時需要配置多個resouce元素了,如下案例。

多個resource元素的使用案例

修改pom.xml中resources,如下:

<resources>
    <resource>
        <!-- 指定資源文件的目錄 -->
        <directory>${project.basedir}/src/main/resources</directory>
        <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
        <filtering>true</filtering>
        <includes>
            <include>**/jdbc.properties</include>
        </includes>
    </resource>
    <resource>
        <directory>${project.basedir}/src/main/resources</directory>
        <includes>
            <include>**/const.properties</include>
        </includes>
    </resource>
</resources>

現在pom.xml內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
        <jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
        <jdbc.username>root</jdbc.username>
        <jdbc.password>root</jdbc.password>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
                <includes>
                    <include>**/jdbc.properties</include>
                </includes>
            </resource>
            <resource>
                <directory>${project.basedir}/src/main/resources</directory>
                <includes>
                    <include>**/const.properties</include>
                </includes>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!-- 是否使用默認的分隔符,默認分隔符是${*}和@ ,這個地方設置為false,表示不啟用默認分隔符配置-->
                    <useDefaultDelimiters>false</useDefaultDelimiters>
                    <!-- 自定義分隔符 -->
                    <delimiters>
                        <delimiter>$*$</delimiter>
                        <delimiter>##</delimiter>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

執行命令看效果:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.116 s
[INFO] Finished at: 2019-11-21T18:38:59+08:00
[INFO] ------------------------------------------------------------------------

上面有如下輸出:

[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 1 resource

插件處理了2個資源文件。

我們看一下target/classes目錄下兩個文件的內容。

jdbc.proerties:

jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=root

const.properties:

email=javacode2018@163.com
jdbc.url=${jdbc.url}
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

2個資源都被複制到target/classes目錄了,只有jdbc.properties中的內容被替換了。

關於資源文件處理的,更詳細的過程可以去看這個插件的源碼,下面我們來說多環境處理的問題。

使用profiles處理多環境構建問題

maven支持讓我們配置多套環境,每套環境中可以指定自己的maven屬性,mvn命令對模塊進行構建的時候可以通過-P參數來指定具體使用哪個環境的配置,具體向下看。

profiles元素支持定義多套環境的配置信息,配置如下用法:

<profiles>
    <profile>測試環境配置信息</profile>
    <profile>開發環境配置信息</profile>
    <profile>線上環境配置信息</profile>
    <profile>環境n配置信息</profile>
</profiles>

profiles中包含多個profile元素,每個profile可以表示一套環境,profile示例如下:

<profile>
    <id>dev</id>
    <properties>
        <jdbc.url>dev jdbc url</jdbc.url>
        <jdbc.username>dev jdbc username</jdbc.username>
        <jdbc.password>dev jdbc password</jdbc.password>
    </properties>
</profile>

id:表示這套環境的標識信息,properties可以定義環境中使用到的屬性列表。

執行mvn命令編譯的時候可以帶上一個-P profileid來使用指定的環境進行構建。

指定環境進行構建

b2b-account-service/pom.xml加入下面配置:

<profiles>
    <!-- 開發環境使用的配置 -->
    <profile>
        <id>dev</id>
        <properties>
            <jdbc.url>dev jdbc url</jdbc.url>
            <jdbc.username>dev jdbc username</jdbc.username>
            <jdbc.password>dev jdbc password</jdbc.password>
        </properties>
    </profile>
    <!-- 測試環境使用的配置 -->
    <profile>
        <id>test</id>
        <properties>
            <jdbc.url>test jdbc url</jdbc.url>
            <jdbc.username>test jdbc username</jdbc.username>
            <jdbc.password>test jdbc password</jdbc.password>
        </properties>
    </profile>
    <!-- 線上環境使用的配置 -->
    <profile>
        <id>prod</id>
        <properties>
            <jdbc.url>test jdbc url</jdbc.url>
            <jdbc.username>test jdbc username</jdbc.username>
            <jdbc.password>test jdbc password</jdbc.password>
        </properties>
    </profile>
</profiles>

將pom.xml的project->properties中的下面幾個元素幹掉,這些和profile中的重複了,需要幹掉:

<jdbc.url>jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8</jdbc.url>
<jdbc.username>root</jdbc.username>
<jdbc.password>root</jdbc.password>

此時pom.xml內容如下,建議大家直接貼進去:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
    </properties>

    <!-- 配置多套環境 -->
    <profiles>
        <!-- 開發環境使用的配置 -->
        <profile>
            <id>dev</id>
            <properties>
                <jdbc.url>dev jdbc url</jdbc.url>
                <jdbc.username>dev jdbc username</jdbc.username>
                <jdbc.password>dev jdbc password</jdbc.password>
            </properties>
        </profile>
        <!-- 測試環境使用的配置 -->
        <profile>
            <id>test</id>
            <properties>
                <jdbc.url>test jdbc url</jdbc.url>
                <jdbc.username>test jdbc username</jdbc.username>
                <jdbc.password>test jdbc password</jdbc.password>
            </properties>
        </profile>
        <!-- 線上環境使用的配置 -->
        <profile>
            <id>prod</id>
            <properties>
                <jdbc.url>prod jdbc url</jdbc.url>
                <jdbc.username>prod jdbc username</jdbc.username>
                <jdbc.password>prod jdbc password</jdbc.password>
            </properties>
        </profile>
    </profiles>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
                <includes>
                    <include>**/jdbc.properties</include>
                </includes>
            </resource>
            <resource>
                <directory>${project.basedir}/src/main/resources</directory>
                <includes>
                    <include>**/const.properties</include>
                </includes>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!-- 是否使用默認的分隔符,默認分隔符是${*}和@ ,這個地方設置為false,表示不啟用默認分隔符配置-->
                    <useDefaultDelimiters>false</useDefaultDelimiters>
                    <!-- 自定義分隔符 -->
                    <delimiters>
                        <delimiter>$*$</delimiter>
                        <delimiter>##</delimiter>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

修改src/main/resource/jdbc.properties內容如下:

jdbc.url=$jdbc.url$
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

運行下面的構建命令:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service -Pdev
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.361 s
[INFO] Finished at: 2019-11-21T18:51:16+08:00
[INFO] ------------------------------------------------------------------------

注意上面命令中多了一個-Pdev參數,-P後面跟的是pom.xml中profile的id,表示需要使用那套環境進行構建。此時我們使用的是dev環境,即開發環境。

看一下target/classes/jdbc.properties,內容變成了下面這樣:

jdbc.url=dev jdbc url
jdbc.username=dev jdbc username
jdbc.password=dev jdbc password

上面文件的內容和pom.xml中dev的profile中的properties對比一下,內容是不是一樣的,說明資源文件中內容被替換為了dev環境的配置,我們再來試試test環境,執行下面命令:

mvn clean package -pl :b2b-account-service -Ptest

看一下target/classes/jdbc.properties,內容變成了下面這樣:

jdbc.url=test jdbc url
jdbc.username=test jdbc username
jdbc.password=test jdbc password

看到了么,神奇的效果出現了,test環境起效了。

開啟默認環境配置

我們在執行下面命令:

mvn clean package -pl :b2b-account-service

看一下target/classes/jdbc.properties,內容變成了下面這樣:

jdbc.url=$jdbc.url$
jdbc.username=$jdbc.username$
jdbc.password=##jdbc.password##

內容沒有被替換,因為我們沒有通過-P來指定具體使用哪個環境進行構建,所以出現了這種現象。

但是我們可以指定一個默認開啟的配置,我們默認開啟dev的配置,修改dev的profile元素,在這個元素下面加上:

<activation>
    <activeByDefault>true</activeByDefault>
</activation>

activeByDefault表示默認開啟這個環境的配置,默認值是false,這個地方我們設置為true,表示開啟默認配置。

此時dev的配置如下:

<profile>
    <id>dev</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
        <jdbc.url>dev jdbc url</jdbc.url>
        <jdbc.username>dev jdbc username</jdbc.username>
        <jdbc.password>dev jdbc password</jdbc.password>
    </properties>
</profile>

執行下面命令:

mvn clean package -pl :b2b-account-service

看一下target/classes/jdbc.properties,內容變成了下面這樣:

jdbc.url=dev jdbc url
jdbc.username=dev jdbc username
jdbc.password=dev jdbc password

這次我們沒有指定環境,默認使用了dev環境。

通過maven屬性來控制環境的開啟

剛才上面說了通過-P profileId的方式來指定環境,現在我們想通過自定義的屬性值來控制使用哪個環境。

可以在profile元素中加入下面配置

<activation>
    <property>
        <name>屬性xx</name>
        <value>屬性xx的值</value>
    </property>
</activation>

那麼我們可以在mvn後面跟上下面的命令可以開啟匹配的環境:

mvn ... -D屬性xx=屬性xx的值

-D可以通過命令行指定一些屬性的值,這個前面有講過,-D後面的屬性會和activation->properties中的name、value進行匹配,匹配成功的環境都會被開啟。

將pom.xml中profiles元素修改成下面這樣:

<!-- 配置多套環境 -->
<profiles>
    <!-- 開發環境使用的配置 -->
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
            <property>
                <name>env</name>
                <value>env_dev</value>
            </property>
        </activation>
        <properties>
            <jdbc.url>dev jdbc url</jdbc.url>
            <jdbc.username>dev jdbc username</jdbc.username>
            <jdbc.password>dev jdbc password</jdbc.password>
        </properties>
    </profile>
    <!-- 測試環境使用的配置 -->
    <profile>
        <id>test</id>
        <activation>
            <property>
                <name>env</name>
                <value>env_test</value>
            </property>
        </activation>
        <properties>
            <jdbc.url>test jdbc url</jdbc.url>
            <jdbc.username>test jdbc username</jdbc.username>
            <jdbc.password>test jdbc password</jdbc.password>
        </properties>
    </profile>
    <!-- 線上環境使用的配置 -->
    <profile>
        <id>prod</id>
        <activation>
            <property>
                <name>env</name>
                <value>env_prod</value>
            </property>
        </activation>
        <properties>
            <jdbc.url>prod jdbc url</jdbc.url>
            <jdbc.username>prod jdbc username</jdbc.username>
            <jdbc.password>prod jdbc password</jdbc.password>
        </properties>
    </profile>
</profiles>

運行命令:

mvn clean package -pl :b2b-account-service -Denv=env_prod

target/classes/jdbc.properties內容如下:

jdbc.url=prod jdbc url
jdbc.username=prod jdbc username
jdbc.password=prod jdbc password

說明使用到了prod環境的配置。

啟動的時候指定多個環境

可以在-P參數後跟多個環境的id,多個之間用逗號隔開,當使用多套環境的時候,多套環境中的maven屬性會進行合併,如果多套環境中屬性有一樣的,後面的會覆蓋前面的。

運行下面命令看效果:

mvn clean package -pl :b2b-account-service -Pdev,test,prod

修改pom.xml中的profiles元素,如下:

<!-- 配置多套環境 -->
<profiles>
    <!-- 開發環境使用的配置 -->
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
            <property>
                <name>env</name>
                <value>env_dev</value>
            </property>
        </activation>
        <properties>
            <jdbc.url>dev jdbc url</jdbc.url>
        </properties>
    </profile>
    <!-- 測試環境使用的配置 -->
    <profile>
        <id>test</id>
        <activation>
            <property>
                <name>env</name>
                <value>env_test</value>
            </property>
        </activation>
        <properties>
            <jdbc.username>test jdbc username</jdbc.username>
        </properties>
    </profile>
    <!-- 線上環境使用的配置 -->
    <profile>
        <id>prod</id>
        <activation>
            <property>
                <name>env</name>
                <value>env_prod</value>
            </property>
        </activation>
        <properties>
            <jdbc.password>prod jdbc password</jdbc.password>
        </properties>
    </profile>
</profiles>

注意看一下上面3個環境中都只有一個自定義屬性了

下面我們同時使用3個環境,執行下面命令:

mvn clean package -pl :b2b-account-service -Pdev,test,prod

target中的jdbc.properties文件變成了這樣:

jdbc.url=dev jdbc url
jdbc.username=test jdbc username
jdbc.password=prod jdbc password

查看目前有哪些環境

命令:

mvn help:all-profiles

案例:

D:\code\IdeaProjects\b2b>mvn help:all-profiles -pl :b2b-account-service
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:all-profiles (default-cli) @ b2b-account-service ---
[INFO] Listing Profiles for Project: com.javacode2018:b2b-account-service:jar:1.0-SNAPSHOT
  Profile Id: dev (Active: true , Source: pom)
  Profile Id: prod (Active: false , Source: pom)
  Profile Id: test (Active: false , Source: pom)

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.527 s
[INFO] Finished at: 2019-11-21T19:16:34+08:00
[INFO] ------------------------------------------------------------------------

從輸出中可以看到有3個環境,他們的id,默認激活的是那個等信息。

查看目前激活的是哪些環境

命令:

mvn help:active-profiles

案例:

D:\code\IdeaProjects\b2b>mvn help:active-profiles -pl :b2b-account-service -Ptest,prod
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:active-profiles (default-cli) @ b2b-account-service ---
[INFO]
Active Profiles for Project 'com.javacode2018:b2b-account-service:jar:1.0-SNAPSHOT':

The following profiles are active:

 - test (source: com.javacode2018:b2b-account-service:1.0-SNAPSHOT)
 - prod (source: com.javacode2018:b2b-account-service:1.0-SNAPSHOT)



[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.387 s
[INFO] Finished at: 2019-11-21T19:25:56+08:00
[INFO] ------------------------------------------------------------------------

從輸出中可以看到啟用了2套環境,分別是testprod

新問題:配置太分散了

我們的系統中有2個模塊需要用到數據庫的配置,這兩個模塊是:

b2b-account-service
b2b-order-service

上面我們介紹了b2b-account-service不同環境的構建操作,是在pom.xml中進行配置的,b2b-order-service中數據的配置也可以這麼做,如果以後有更多的模塊都需要連接不同的數據庫,是不是每個模塊中都需要配置這樣的pom.xml,那時候數據庫的配置分散在幾十個pom.xml文件中,如果運維需要修改數據庫的配置的時候,需要去每個模塊中去修改pom.xml中的屬性,這種操作會讓人瘋掉的,我們可以怎麼做?

我們可以將數據庫所有的配置放在一個文件中,運維只用修改這一個文件就行了,然後執行構件操作,重新發布上線,就ok了。

maven支持我們這麼做,可以在profile中指定一個外部屬性文件xx.properties,文件內容是這種格式的:

key1=value1
key2=value2
keyn=value2

然後在profile元素中加入下面配置:

<build>
    <filters>
        <filter>xx.properties文件路徑(相對路徑或者完整路徑)</filter>
    </filters>
</build>

上面的filter元素可以指定多個,當有多個外部屬性配置文件的時候,可以指定多個來進行引用。

然後資源文件複製的時候就可以使用下面的方式引用外部資源文件的內容:

xxx=${key1}

案例

新建文件b2b/config/dev.properties,內容如下:

# b2b-account-service jdbc配置信息
b2b-account-service.jdbc.url=dev_account_jdbc_url
b2b-account-service.jdbc.username=dev_account_jdbc_username
b2b-account-service.jdbc.password=dev_account_jdbc_password

# b2b-order-service jdbc配置信息
b2b-order-service.jdbc.url=dev_order_jdbc_url
b2b-order-service.jdbc.username=dev_order_jdbc_username
b2b-order-service.jdbc.password=dev_order_jdbc_password

新建文件b2b/config/test.properties,內容如下:

# b2b-account-service jdbc配置信息
b2b-account-service.jdbc.url=test_account_jdbc_url
b2b-account-service.jdbc.username=test_account_jdbc_username
b2b-account-service.jdbc.password=test_account_jdbc_password

# b2b-order-service jdbc配置信息
b2b-order-service.jdbc.url=test_order_jdbc_url
b2b-order-service.jdbc.username=test_order_jdbc_username
b2b-order-service.jdbc.password=test_order_jdbc_password

新建文件b2b/config/prod.properties,內容如下:

# b2b-account-service jdbc配置信息
b2b-account-service.jdbc.url=prod_account_jdbc_url
b2b-account-service.jdbc.username=prod_account_jdbc_username
b2b-account-service.jdbc.password=prod_account_jdbc_password

# b2b-order-service jdbc配置信息
b2b-order-service.jdbc.url=prod_order_jdbc_url
b2b-order-service.jdbc.username=prod_order_jdbc_username
b2b-order-service.jdbc.password=prod_order_jdbc_password

b2b-account-service/src/main/resource/jdbc.properties文件內容:

jdbc.url=${b2b-account-service.jdbc.url}
jdbc.username=${b2b-account-service.jdbc.username}
jdbc.password=${b2b-account-service.jdbc.password}

看到了沒,這裏面${}中的屬性名稱取的是上面b2b/config目錄中資源文件中的屬性名稱。

b2b-order-service/src/main/resource/jdbc.properties文件內容:

jdbc.url=${b2b-order-service.jdbc.url}
jdbc.username=${b2b-order-service.jdbc.username}
jdbc.password=${b2b-order-service.jdbc.password}

b2b-account-service/pom.xml內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-account-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.javacode2018</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
    </properties>

    <!-- 配置多套環境 -->
    <profiles>
        <!-- 開發環境使用的配置 -->
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <property>
                    <name>env</name>
                    <value>env_dev</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/dev.properties</filter>
                </filters>
            </build>
        </profile>
        <!-- 測試環境使用的配置 -->
        <profile>
            <id>test</id>
            <activation>
                <property>
                    <name>env</name>
                    <value>env_test</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/test.properties</filter>
                </filters>
            </build>
        </profile>
        <!-- 線上環境使用的配置 -->
        <profile>
            <id>prod</id>
            <activation>
                <property>
                    <name>env</name>
                    <value>env_prod</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/prod.properties</filter>
                </filters>
            </build>
        </profile>
    </profiles>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>
    </build>

</project>

b2b-order-service/pom.xml內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacode2018</groupId>
    <artifactId>b2b-order-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>b2b-account-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>b2b-order-api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <properties>
        <!-- 指定資源文件複製過程中採用的編碼方式 -->
        <encoding>UTF-8</encoding>
    </properties>

    <!-- 配置多套環境 -->
    <profiles>
        <!-- 開發環境使用的配置 -->
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <property>
                    <name>env</name>
                    <value>env_dev</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/dev.properties</filter>
                </filters>
            </build>
        </profile>
        <!-- 測試環境使用的配置 -->
        <profile>
            <id>test</id>
            <activation>
                <property>
                    <name>env</name>
                    <value>env_test</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/test.properties</filter>
                </filters>
            </build>
        </profile>
        <!-- 線上環境使用的配置 -->
        <profile>
            <id>prod</id>
            <activation>
                <property>
                    <name>env</name>
                    <value>env_prod</value>
                </property>
            </activation>
            <build>
                <filters>
                    <filter>../../config/prod.properties</filter>
                </filters>
            </build>
        </profile>
    </profiles>

    <build>
        <resources>
            <resource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/main/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <!-- 指定資源文件的目錄 -->
                <directory>${project.basedir}/src/test/resources</directory>
                <!-- 是否開啟過濾替換配置,默認是不開啟的 -->
                <filtering>true</filtering>
            </testResource>
        </testResources>
    </build>

</project>

見證奇迹的時刻來了。

執行下面命令:

D:\code\IdeaProjects\b2b>mvn clean package -pl :b2b-account-service,:b2b-order-service -Pdev
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] b2b-account-service                                                [jar]
[INFO] b2b-order-service                                                  [jar]
[INFO]
[INFO] ----------------< com.javacode2018:b2b-account-service >----------------
[INFO] Building b2b-account-service 1.0-SNAPSHOT                          [1/2]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-account-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-account-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-account-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-account-service ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-account-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-account\b2b-account-service\target\b2b-account-service-1.0-SNAPSHOT.jar
[INFO]
[INFO] -----------------< com.javacode2018:b2b-order-service >-----------------
[INFO] Building b2b-order-service 1.0-SNAPSHOT                            [2/2]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ b2b-order-service ---
[INFO] Deleting D:\code\IdeaProjects\b2b\b2b-order\b2b-order-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ b2b-order-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ b2b-order-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ b2b-order-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\code\IdeaProjects\b2b\b2b-order\b2b-order-service\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ b2b-order-service ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ b2b-order-service ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ b2b-order-service ---
[INFO] Building jar: D:\code\IdeaProjects\b2b\b2b-order\b2b-order-service\target\b2b-order-service-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for b2b-account-service 1.0-SNAPSHOT:
[INFO]
[INFO] b2b-account-service ................................ SUCCESS [  2.510 s]
[INFO] b2b-order-service .................................. SUCCESS [  0.257 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.026 s
[INFO] Finished at: 2019-11-21T20:11:32+08:00
[INFO] ------------------------------------------------------------------------

看一下兩個模塊target/classes下面jdbc.properties內容,如下圖:

使用了dev環境的配置,dev環境中處理資源文件替換的時候採用了config/dev.properties文件的內容,這部分配置如下,重點在於build->filters元素

<profile>
    <id>dev</id>
    <activation>
        <activeByDefault>true</activeByDefault>
        <property>
            <name>env</name>
            <value>env_dev</value>
        </property>
    </activation>
    <build>
        <filters>
            <filter>../../config/dev.properties</filter>
        </filters>
    </build>
</profile>

我們再使用prod環境看一下效果,執行下面命令:

mvn clean package -pl :b2b-account-service,:b2b-order-service -Pprod

效果圖如下:

感受一下,這次使用了b2b/config/prod.properties文件的內容對資源文件進行了替換,這次將所有配置的信息集中在config目錄的幾個文件中進行處理了,對於運維和開發來說都非常方便了。

profile元素更強大的功能

profile元素可以用於對不同環境的構建進行配置,project中包含的元素,在profile元素中基本上都有,所以profile可以定製更複雜的構建過程,不同的環境依賴的構件、插件、build過程、測試過程都是不一樣的,這些都可以在profile中進行指定,也就是說不同的環境所有的東西都可以通過profile元素來進行個性化的設置,這部分的功能有興趣的大家可以自己去研究一下,截個圖大家感受一下:

從上圖中可以看出這些屬性都可以根據環境進行定製的。

還是那句話,上面這些用法大家會經常用到的,建議大家下去了多練練。看和操作,所獲取到的是不能比的,看的過程中可能覺得一切都知道了,但是實際操作就不一樣了,可能中間會遇到各種問題,然後自己會想辦法解決這些問題,領會和學到的東西是不一樣的!

Maven系列目錄

更多好文章

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

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

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

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

分類
發燒車訊

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

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

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

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

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

【其他文章推薦】

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

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

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

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

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