2019年9月14日 星期六

Excel VBA 五 String、日期、Function、Sub

※String

Dim str As String
str = "abcdefg"
Cells(1, "A").Value = Now
Cells(1, "B").Value = Left(str, 4) 'abcd
Cells(2, "B").Value = Right(str, 4) 'defg
Cells(3, "B").Value = Mid(str, 4, 2) 'de
Cells(4, "B").Value = Len(str) '7
Cells(5, "B").Value = InStr(str, "cd") '3

※VBA 的 index 是從 1 開始的



※日期

Dim d1 As Date
Dim d2 As Date

d1 = DateValue("Jan 21, 1999")
Cells(1, "A").Value = d1 '1999/1/21
Cells(2, "A").Value = Year(d1) '1999
Cells(3, "A").Value = Month(d1) '1
Cells(4, "A").Value = Day(d1) '21

d2 = TimeValue("17:25:37")  '等同 TimeValue("5:25:37 pm")
Cells(5, "A").Value = Hour(d2) '17
Cells(6, "A").Value = Minute(d2) '25
Cells(7, "A").Value = Second(d2) '37


.增加
Dim d As Date
d = DateValue("Jan 21, 1999")
d = DateAdd("m", -1, d)
MsgBox (d)

※第一個參數如下:
yyyy - 年
q - 季度
m - 月
y - 当年的第几天
d - 日
w - 当周的第几天
ww - 周
h - 小时
n - 分钟
s - 秒


 ※Function 和 Sub

Sub hello() '我是註解
  'MsgBox 3 * fun1()
  'MsgBox fun2(3, 2)
  'Call s1
  's1
  Call s2(3, 2)
  s2 3, 2
End Sub

Function fun1() As Integer
  fun1 = 2
End Function

Function fun2(a As Integer, b As Integer) As Integer
  fun2 = a * b
End Function

Sub s1()
  MsgBox fun2(3, 2)
End Sub

Sub s2(a As Integer, b As Integer)
  MsgBox fun2(a, b)
End Sub

※Function 有沒有回傳值都可以,Sub 不能有回傳值

※呼叫時,Sub 可用 Call,如果不想用 Call,後面一定不能有圓括號

2019年9月13日 星期五

telegram BotAPI

安裝好 telegram 後,開啟 https://core.telegram.org/bots 文檔

開啟你的 telegram,然後點紅框的連結,即可加入 BotFather
指令查詢:輸入 /start 或 /help 會出現可以打得指令
/newbot 新增一個 bot

出現 Alright, a new bot. How are we going to call it? Please choose a name for your bot. 後
為你的 bot 取一個名字

取好後,會出現
Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.

再取一個使用者名稱,一定要 bot 結尾
這個地方我試了好幾個名稱都不行

不行的訊息:
Sorry, this username is already taken. Please try something different.

成功後,會出現 Done! Congratulations on your new bot. You will find it at 開頭的
同時 telegram 也會增加一個 bot
Use this token to access the HTTP API 是取得的 token




可以使用 /token 和 /revoke 重新產生 token 和註消 token

如果按了清螢幕,所以看不到 token 了,還可以用 /mybots
然後點擊你取的 username,再點擊 API Token 即可

開啟群組:

/setjoingroups
@你的 username
然後開啟或關閉
這個功能能讓你的 bot 加入其他的群組
然後在 xxxbot 隨便打幾個字,然後新建群組後,將 xxxbot 加進去就可以對這個群發送訊息了




https://api.telegram.org/bot{你的 token}/getUpdates 可以取得你向你的 bot 發訊息的 json 內容
其中 id 表示此群的 id,如果有加入群組,每個群組的 id 會不一樣

加入群組:
創一個群組後,將機器人加入,並發訊息,然後在上面的網址 getUpdates 裡會有 id


使用 java 11 發送訊息:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class TestTelegram {
    private final String TELEGRAM_TOKEN = "你的 token";
    private final String CHAT_ID = "你的群組 id 或機器人 id";
    private final String TEXT = "要發送的訊息";

    public static void main(String[] args) throws Exception {
        TestTelegram telegram = new TestTelegram();
        HttpClient client = HttpClient.newBuilder().build();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(
                        "https://api.telegram.org/bot" + telegram.TELEGRAM_TOKEN +
                                "/sendMessage")
                )
                .header("Content-Type", "application/x-www-form-urlencoded")
                .POST(HttpRequest.BodyPublishers.ofString("chat_id=" + telegram.CHAT_ID + "&text=" + telegram.TEXT))
                .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.statusCode());
    }
}




使用 apache 發送訊息:
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost("https://api.telegram.org/bot" + 得到的 token +
"/sendMessage");

httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
List<NameValuePair> urlParameters = new ArrayList<>();
urlParameters.add(new BasicNameValuePair("chat_id", CHAT_ID));
urlParameters.add(new BasicNameValuePair("text", TEXT));
httpPost.setEntity(new UrlEncodedFormEntity(urlParameters));

CloseableHttpResponse execute = httpClient.execute(httpPost);
execute.close();

2019年9月11日 星期三

Excel VBA 四 宣告變數、陣列、If、迴圈、Select Case

※宣告變數

Dim x As Integer
'x = "d"
x = 1 + 2
MsgBox (7 + x)
MsgBox ("x=" & x)

※雖然不用宣告也可以執行,但如果不宣告,註解那行是可以執行的

※類型
Byte (1):無符號,0-255
Integer (2)
Long (4)
Double (8)
Boolean (2)
Decimal (14)
String

Date (8)
Currency (8)
Single (4)
Object (4)
Variant (根据分配确定)

數字預設是 Integer



※陣列

Dim a(6 To 10) As String
a(6) = "a"
a(8) = "b"
a(10) = "c"
MsgBox a(6) 'a
MsgBox a(7) '

※如果不在範圍內 (6-10),如 a(5) 會報 Subscript out of range

.二維陣列

Dim b(1 To 3, 2 To 4) As String
b(1, 2) = "kkk"
MsgBox b(1, 2) 'kkk


※If

Dim i As Byte
i = 7
If i = 1 Then
  MsgBox (1)
ElseIf i = 2 Then
  MsgBox (2)
ElseIf i = 7 Then
  MsgBox (7)
Else
  MsgBox ("hahaha")
End If

※不等於用 <>


Dim i As Byte
i = 7
If i = 0 Then
  MsgBox (0)
ElseIf i >= 1 & i <= 10 Or i = 100 Then
  MsgBox ("1~10 or 100")
Else
  MsgBox ("hahaha")
End If

※& 和 Or 可以更進一步的判斷


※迴圈

※Do ~ Loop

Dim i As Integer
'i = 10
Do While i < 10
  i = i + 1
Loop
MsgBox (i)

※至少會跑一次


※For ~ Next

Dim i As Integer
Dim sum As Long

For i = 1 To 10 'Step 1
  sum = sum + i
Next i
MsgBox (sum)

※Step 預設就是 1 了


For i = 1 To 9
  For j = 1 To 9
    Cells(i, j).Value = i & "x" & j & "=" & i * j
  Next j
Next i

※可以嵌套


※For Each ~ Next

For i = 1 To 5 Step 1
    Cells(i, "a").Value = i * 9
Next i

For Each j In Range("A1: A5")
  If j = 36 Then
    GoTo xx
    'Exit For
  End If
  MsgBox (j)
  xx:
Next j

※Exit For 就是 break

※GoTo 到一個標籤,模擬 continue



※Select Case

n = -9
Dim str As String

Select Case n
  Case 1
    n = "a"
  Case 2, 3
    n = "b"
  Case 4 To 6
    n = "c"
  Case Is > 6
    n = "d"
  Case Else
    n = "other"
  End Select
MsgBox (n)

2019年9月10日 星期二

Excel VBA 三 選取、複製、清除內容、總數、常用方法

※選取

Set r = Range("C4: E5")
r.Value = 999
r.Select
'r.Rows(1).Select
'r.Columns(1).Select


分別為 Select、Rows(1).Select、Columns(1).Select
R 橫 C 直

※複製

Set r = Range("C4: E5")
r.Value = 888

'方法一
'r.Select
'Selection.Copy

'Range("A7").Select
'ActiveSheet.Paste

'方法二
Range("A7:C8").Value = r.Value

推薦用方法二比較簡潔有力


※清除內容

Range("A7:C8").ClearContents
'Range("A7:C8").Value = ""



※總數

Set r = Range("C4: E5")
r.Value = 888
Cells(1, "A").Value = r.Count '6
Cells(2, "A").Value = r.Rows.Count '2
Cells(3, "A").Value = r.Columns.Count '3


※常用方法

Cells(4, "A").Value = "abc"
Cells(4, "A").Interior.Color = vbYellow '儲存格背景顏色
Cells(4, "A").Font.Color = RGB(100, 200, 255) '字體顏色
Cells(4, "A").Font.Bold = True '粗體
Cells(4, "A").Font.Italic = True '斜體
Cells(4, "A").Font.Underline = True '底線
Cells(4, "A").Font.Size = 20 '字體大小
Cells(4, "A").ColumnWidth = 20 '儲存格寬度
Cells(4, "A").EntireColumn.AutoFit '自動調整適合的儲存格寬度
'Cells(4, "A").ClearContents '清除儲存格內容
'Cells(4, "A").ClearFormats '清除儲存格非內容的狀態 (背景色、字體色、字體大小…等)

Excel VBA 二 插入按鈕、Cell、Range



1.Insert 有很多的圖形可以控制,這裡以左上角的按鈕為例




2.選完第一張圖的按鈕後,在 Excel 工作區隨便畫一個長方形,如此圖的上半部分
滑鼠放開後,就會看到下半部分
然後名稱預設是 CommandButton1,想改名可按上圖的下半部分操作



3.第一和第二張圖都有個 View Code 可到這個畫面來,然後打程式碼
Private Sub CommandButton1_Click()
  Cells(2, "B").Value = "haha"
  MsgBox ("insert" & vbNewLine & "success")
End Sub
打完按三角按鈕即可執行



4.此時按鈕就可以按了,但發現按右鍵沒反應,此時可以發現 Properties 是灰色的
按下橘框的 Design Mode,會發現 Properties 又可以用了且按右鍵也可以了


※Cell、Range

Cells(1, "A").Value = 123  '第二個參數用數字也可以;1就是A,2就是B,依此類推
Cells(2, "B").Value = "haha"

Range("C3").Value = "I'm C3"
Range("C4: E5").Value = "c4-e5"
Range("C4: E5, A7: B13, G1").Value = "yeah"

以上是針對當前工作表,如果想控制其他的工作表可用如下三個方法:

Worksheets("Sheet1").Range("A2").Value = "A7"
Worksheets(1).Range("A3").Value = "A3" 'Worksheets.Count
Sheet1.Range("A4").Value = "A4"


如上圖右邊,Sheet1 和 Sheet2 都是 Excel 幫我們定好的,後面的括號是別名,會對應到上圖左邊下面的名稱
注意 Sheet2 的別名是 Sheet1

第一種是用別名的方式
第二種是以上圖左邊下面去算的,最左邊的 xxx 是1,Sheet1 是 2,但滑鼠按著其中一個去換,是可以換的,這時順序會改變,所以這種方法不推薦
第三種是用 Excel 幫我們定好的名稱
使用 Worksheets.Count 可以知道有幾張工作表

※Range 的另外一種用法

Range("xxx"),裡面的 xxx 可以自己定義,方法如下:


1.這次使用的不是 Developer 了,是 Formulas



2.Scope 是工作表的別名,橘框按了以後視窗會變小,這時在 Excel 拉需要的範圍即可,如果要多個範圍,就按 Ctrl



3.在左上角打上自己取得名稱,就會反白範圍



4.這個畫面可以編輯和刪除,但 Scope 不能改

Excel VBA 一 環境


1.在 File/Options (檔案/選項) 裡會出現如上的畫面



2.勾了第一張圖的 Developer 後,就有活頁標籤可以使用,選 Visual Basic 寫程式



3.大小寫要正確才行,打完後按橘框的按鈕可測試

Sub hello() '我是註解
  MsgBox ("hello, VBA")
End Sub

註解的按鈕和「"」是同一個

如果整行都要註解,可用 Rem xxx

MsgBox 的圖括號可省略




4.選左邊的程式,然後按 Run 即可
Macro Name 是搜尋,有可能有很多巨集
一個Sub ~ End Sub 就是一個巨集,可以在同一個地方寫很多巨集



5.存檔要存成 xlsm 才行,下次再進來選 Macros (巨集),會出現上一張圖的畫面,選 Edit (編輯) 就會出現打程式的地方了

官方文檔連結
可參考的 API

2019年9月6日 星期五

Scala 2.x 五 泛型、隱式轉換

※泛型類別、泛型方法

def genericMethod[T](t: T, list: List[T]): Unit = {}

class Chicken[T] extends Bird {
  def genericMethod(t: T, list: List[T]): Unit = {}
}

※寫在類別名稱或方法名稱後面



※傳參

class Animal{}
class Bird extends Animal{}
class Chicken extends Bird{}


def main(args: Array[String]): Unit = {
  val listA = List[Animal](new Animal)
  val listB = List[Bird](new Bird)
  val listC = List[Chicken](new Chicken)
  xxx(listA, listB, listC)
}

def xxx(a: List[Animal], b: List[Bird], c: List[Chicken]): Unit = {
  // ooo(a)
  ooo(b)
  ooo(c)
}

def ooo[T](list: List[Bird]) {}



※賦值

val listA = ListBuffer[Animal](new Animal)
val listB = ListBuffer[Bird](new Bird)
val listC = ListBuffer[Chicken](new Chicken)

val a = new Animal()
val b = new Bird()
val c = new Chicken()

listA += a
listA += b
listA += c

//    listB += a
listB += b
listB += c

//    listC += a
//    listC += b
listC += c


※以上都和 java 一樣

※沒有通配符「?」




※<:、>:


<::類似 java 的 extends
>::類似 java 的 super
.只能跟著自定義名稱,如 T

val listA = ListBuffer[Animal](new Animal)
    val listB = ListBuffer[Bird](new Bird)
    val listC = ListBuffer[Chicken](new Chicken)
    ooo(listA)
    ooo(listB)
//    ooo(listC)


def ooo[T >: Bird](listBuffer: ListBuffer[T]) = {}



※+T、-T

如果只有 T,沒有辦法將子類轉成父類或父類轉成子類

class GenericTest[T](t: T){} // +T、-T

class Animal{}
class Bird extends Animal{}
class Chicken extends Bird{}



.+T:表示子類可轉成父類

val a = new GenericTest[Animal](new Animal())
val b = new GenericTest[Bird](new Bird())
val c = new GenericTest[Chicken](new Chicken())

val d:GenericTest[Animal]  = b
val f:GenericTest[Bird]  = c
val e:GenericTest[Animal]  = c


.-T:表示父類可轉成子類

val a = new GenericTest[Animal](new Animal())
val b = new GenericTest[Bird](new Bird())
val c = new GenericTest[Chicken](new Chicken())

val d:GenericTest[Chicken]  = b
val f:GenericTest[Bird]  = a
val e:GenericTest[Chicken]  = a



※隱式轉換


.隱式方法

def printStr(data: String): Unit = println(data)

//  implicit def int2Str(i: Int):String = i + "xxx"
implicit def intToStr(j: Int):String = j.toString
// -------------------
printStr("abc")
printStr(123)

※用 java 的 overloading 也可以做到轉換的功能,但這裡說的是 scala 的隱式轉換

※如果沒有 implicit 的方法時,給 int 會編譯錯誤

※printStr 要的是 String 參數,但給的是 int,所以會去找參數是 int,回傳是 String 的隱式方法,如果有兩個(含)以上會報錯


再一個例子:
class Animal(name: String) {
  private var iname: String = ""

  def sname = name
  def sname_=(name: String): Unit = {
    this.iname = name
  }
}

class Bird(name: String) {
  private var iname: String = ""

  def sname = name
  def sname_=(name: String): Unit = {
    this.iname = name
  }
}

※兩個類別,然後給 getter/setter 方法

def printNameOfAnimal(animal: Animal): Unit = println(animal.sname)

implicit def birdToAnimal(b: Bird): Animal = {
new Animal("b" + b.sname)
}
// -------------------
printNameOfAnimal(new Animal("chicken"))
printNameOfAnimal(new Bird("chicken"))



.隱式值

object ScalaTest {
  implicit val xxx = new Animal("qoo")

  def main(args: Array[String]): Unit = {
    xxx("x")
  }

  def xxx(name: String)(implicit a: Animal): Unit = {
    println(a.sname)
  }

  class Animal(name: String) {
    private var iname: String = ""

    def sname = name
    def sname_=(name: String): Unit = {
      this.iname = name
    }
  }
}

※有了隱式值了以後,呼叫 xxx 方法可以不用參數,當然 xxx 方法也要有 implicit 關鍵字

※如果值在其他的類別,就需要 import


.隱式參數

def printStr(implicit data: String, num: Int): Unit = println(data, num)
// def printStr[T <% String](data: String, num: Int): Unit = println(data, num)
implicit def intToStr(j: Int):String = j.toString

printStr("abc", 7)
printStr(123, 7)

※這裡的隱式參數,雖然不寫也能找得到

※還可以用隱式視圖「<%」,但 IDE 提示我已經 deprecated 了,用隱式參數取代



.隱式類別

def main(args: Array[String]): Unit = {
  println(new Animal("xxx").show())
  println("qoo".show())
}

implicit class Animal(name: String) {
  def show(): String = {
    name
  }
}

※第二個 println 原本是不行的,但因為有隱式類別所以可以用

※隱式類別只能有一個參數,太多太少都不行

2019年9月5日 星期四

Scala 2.x 四 集合

※List、Set、Map 宣告

println(List(1, 1, 2, 2, null))
println(Set(1, 1, 2, 2, null))
println(Map("a" -> 1, "b" -> null)) // key 不能 null,value 可以;但如果有宣告泛型都不能是 null

限定某個類型,類似 java 的泛型
val value:List[Int] = List(1, 1, 2, 2, null)
以上都是不可變的,預設都是 scala.collection.immutable,可變的在 scala.collection.mutable
Set 和 Map都有同名的類別,但 List 的可變類別是 ListBuffer


※List

var xxx = 1 :: (2 :: (3 :: Nil)) // List(1,2,3),最後一定要寫 Nil
xxx.foreach(n => println(n))
println(xxx.head) // 1
println(xxx.tail) // List(2, 3), 返回第一個以外的元素
println(xxx.isEmpty) // false


var xxx = List(1, 2, 3)
var ooo = List(4, 5, 6)
var zzz = xxx.concat(ooo) // xxx ::: ooo
zzz.foreach(n => println(n)) // 1-6

※::: 等同於 concat

※可變的 List

import scala.collection.mutable.ListBuffer

val listBuffer = ListBuffer(1,2,3)
listBuffer += 4 // unit.addOne(4)
println(listBuffer)


※Set

import scala.collection.mutable.Set
val mutableSet:Set[String] = Set("a", "b", "c")
println(mutableSet.getClass.getName) // scala.collection.mutable.HashSet


mutableSet.add("d") // a b c d
mutableSet.remove("b") // a c d
mutableSet += "e" // a c d e
mutableSet -= "a" // c d e

※這裡用的是可變的


※整合兩個集合 (List、Set 、Map 混合都可以)

val s1 = Set("a", "b", "c")
val s2 = Set("d", "e")
val s3= s1 ++ s2
println(s3)


※最大值、最小值

val n = Set(78,44,88,115)
println(n.min)
println(n.max)



※Map

import scala.collection.mutable.Map


val map: Map[String, Int] = Map("a" -> 1, "b" -> 2)
map += ("c" -> 88)
map.keys.foreach(k => println(k + "-" + map(k)))


※整合兩個集合(List、Set、Map 混合都可以)

val map1: Map[String, Int] = Map("a" -> 1, "b" -> 2)
val map2: Map[String, Int] = Map("c" -> 3, "d" -> 4)
val map3 = map1 ++ map2
println(map3)



※Tuple

裡面可以放入不同類型,可以重複,但都是不可變的,可以回傳多個值 (1~22)
也就是 List,但值可以是多種型態

val t = ("x", true, 3): Tuple3[String, Boolean, Int]
println(t._1) // x
println(t._2) // true
println(t._3) // 3


// 解構賦值
val (s, b, i) = t
println(s) // x
println(b)// true
println(i) // 3

※scala 內鍵從 Tuple1~Tuple22,數字表示參數數量


// 匹配

val list = List(("a", 1), ("b", 2), ("c", 3), ("d", 4))
list.foreach(t => {
  t match {
case ("a", v) => println("a=" + v)
case (k, 2) => println(k + "=2")
// case k if k._1 == "c" => println(s"${k._1}=${k._2}")
case k => if(k._1 == "c") println(s"${k._1}=${k._2}")
case _ => println(".............")
  }
})

※如果匹配到多個會以第一個為主

※不用也不可寫 break 和 continue,只會判斷一個,但下面有個例外

※=> 前面還可以寫 if,再做一次判斷,圓括號可省略

※如果 if 寫在 => 前面,當沒有匹配到時,會再去 _ 的判斷式裡,如果沒有寫 _ 判斷式,會報 scala.MatchError;但 if 寫在 => 後面,沒判斷到就 continue 了

// for 迴圈

val list = List(("a", 1), ("b", 2), ("c", 3), ("d", 4))
for ((x, y) <- list) {
  println(x + y) // a1 b2 c3 d4
}

2019年9月4日 星期三

Scala 2.x 三 Function

※Function

類似 java 的 lambda
val showNum = () => 9
println(showNum()) // 9

val add = (x: Int) => x + 1
println(add(7))

val add2 = (x: Int, y: Int) => x + y
println(add2(1, 2)) // 3

※分別為 0~2 個參數


※傳 Function

val n = Seq(9, 7, 3)
val func = (x: Int) => x * 2

println(n.map(func))
println(n.map((x: Int) => x * 2))
println(n.map((x) => x * 2))
println(n.map(x => x * 2))
println(n.map(_ * 2))

※以上 5 種寫法都是一樣的



※接收 Function

def plusOne1(list: List[Int]): List[Int] =
list.map(n => n + 1)

def substractOne1(list: List[Int]): List[Int] =
list.map(n => n - 1)

def multyDouble1(list: List[Int]): List[Int] =
list.map(n => n * 2)


def plusOne2(list: List[Int]): List[Int] =
changeNumber(list, n => n + 1)

def substractOne2(list: List[Int]): List[Int] =
changeNumber(list, n => n - 1)

def multyDouble2(list: List[Int]): List[Int] =
changeNumber(list, n => n * 2)

/*private*/ def changeNumber(list: List[Int], calc: Int => Int): List[Int] =
list.map(calc)


val list = List(1,2,3);
println(plusOne1(list))
println(substractOne1(list))
println(multyDouble1(list))
println(changeNumber(list, n => n * 9))

※原本的 3 個 Xxx1 方法,回傳時,只有算法不同,所以可以提出變成公用的 changeNumber



※回傳 Function

def urlBuilder(ssl: Boolean = false, domainName: String): (String, Int) => String = {
  val protocol = if (ssl) "https://" else "http://"
  (endpoint: String, pageNum: Int) => s"$protocol$domainName/$endpoint?page=$pageNum"
}

val getURL = urlBuilder(ssl = true, domainName = "www.google.com")
println(getURL("queryList", 10))

※這是修改官方的例子,回傳接收兩個參數,String 和 Int,並回傳 String

2019年9月3日 星期二

Scala 2.x 二 Trait、Object、正則

※Trait

trait 類似 java 的 interface

trait Animal {
  def setName(name: String): Unit
  def getName(): Unit = print("taipei zoo")
}

class Cat extends Animal {
  override def setName(name: String): Unit = {
    println(name)
  }
}

val c = new Cat()
c.setName("tiger")
c.getName()

※也可以直接在 trait 裡實作


※加泛型

trait Add[I1,I2,S] {
  def calc(p1: I1, p2: I2): S
}

class Operator extends Add[Int, Int, Int] {
  override def calc(p1: Int, p2: Int): Int = p1 + p2
}

val op = new Operator()
print(op.calc(9,7))


※多繼承

abstract class A {
  val msg: String
}
class B extends A {
  override val msg = "xxx"
}
trait C1 extends A
trait C2

class D extends B with C1 with C2

※和 java 一樣,extends 還是只能繼承一個,實作可多個,但關鍵字變成 with 了



※Object

類似 java 的 static

object Singleton {
private var count = 0
def instance(): Int = {
  count += 1
  count
}
}

val c1: Int = Singleton.instance()
println(c1) // 1
val c2: Int = Singleton.instance()
println(c2) // 2



※正則


// val regex = "\\d+".r
val regex = new Regex("\\d+")
println(regex.matches("123"))

regex.findFirstMatchIn("123") match {
  case Some(_) => println("good")
  case None => println("bad")
}

※字串加 .r 可以變成正則

Scala 2.x 一 基本常識、迴圈、Method、類別、Case

scala 是基於 java 的,所以 java 要先安裝好
scala 安裝完並設定環境變數後 (我安裝時已自動加了環境變數),在命令提示字元有 scala 可以用即可
IntelliJ -> File/Settings/Plugins,搜尋 scala 並安裝好重啟 Intellij
新增專案後,在專案按右鍵 Add Framework Support,選中 scala,這樣按右鍵就有新增 scala 的選項了

這系列文章使用的是 2.13.0 版做測試

※main 方法

object ScalaTest {
  def main(args: Array[String]): Unit = {
  println("xxx")
  }
}

※args 為參數名稱

※檔名為 ScalaTest.scala


※基本常識

.Value and Variable:
val 為常數,一開始一定要賦值,和 js 一樣
val i: Int = 1,: Int 是確定它的型態

var 宣告變數如可以改變用這個

沒有前|後置遞增|減
沒有 static
沒有 switch,但有 case



※型態的繼承

Nothing -> Byte、Short、Int、Long、Float、Double、Char、Boolean、Unit -> AnyVal -> Any
Nothing -> Null -> List、Option、CustomClass -> AnyRef(java.lang.Object) -> Any

※ java 的八種型態全有,但 Int 和 Char 取名不同,然後多一種 Unit,相當於 java 的 void

※型態轉換規則

1.Byte -> Short -> Int -> Long -> Float -> Double
2.Char -> Int
3.不可強轉

val x: Int = 123456
val y: Float = x
// val z: Long = y
// val z: Long = (Long)y
val z: Double = y

※註解的兩行會編譯錯譯,沒有什麼強轉的


※宣告陣列

// 不定長度
println(Array(1, 2, 3).apply(0))
println(Array("a", "b", "c").apply(0))

// 固定長度
var arr = new Array[String](3)
println(arr(0))
arr(0) = "s"
println(arr(0))

// 多維陣列
var arr3 = Array.ofDim[String](2, 3, 4) // 內鍵到五維
arr3(0)(0)(0) = "000"
println(arr3(0)(0)(0))
println(arr3.length) // 2
println(arr3(0).length) // 3
println(arr3(0)(0).length) // 4

// 固定範圍陣列
val ints = Array.range(5, 10)
for(n <- ints){
  println(n) // 5-9
}

val ints2 = Array.range(5, 17, 3)
for(n <- ints2){
  println(n) // 5 8 11 14
}


※迴圈

while、do...while、for,只有 for 和 java 不一樣,只有 java 的增強型 for 迴圈,但中間不是 :,是 <-,還多出一種迴圈 叫 until

val value = List(1,2,3)
// for(v <- value){
// for(v <- 1 to 10){
for(v <- 1 until 10){ // 不包含10
  println(v)
}

.多層迴圈

scala 的多層 for 迴圈可以一行就搞定,當然寫多行也是可以

for(i <- 1 to 9; j <- 1 to 9){
  println(i + "x" + j + "=" + i * j)
}


for(i <- 1 to 3; j <- 1 to 3; k <- 1 to 3 if j != 2){
  println(i + "-" + j + "-" + k)
}

※如上上兩層和三層的迴圈,後面還可以加 if


.儲存迴圈的值

var xxx = for{i <- 1 to 10 if i != 3; if i < 8}
  yield i

for(x <- xxx) {
  println(x) // 1 2 4 5 6 7
}


var xxx = for(i <- 1 to 10; j <- 1 to 3 if i != 3; if i < 8)
  yield (i,j)

for(x <- xxx) {
  println(x)
}

※不可以有實體 (花括號)


.中斷與繼續

break 和 continue,但 scala 沒有 continue

var loop = new Breaks;
for(v <- 1 until 10){
  if(v == 7) loop.break()
  println(v)
}


var loop = new Breaks;
for(v <- 1 until 10){
  loop.breakable {
if (v == 7) loop.break()
println(v)
  }
}

※new Breaks 後,break() 就是 break;breakable 就是 continue



※Method

格式為:def 方法名(方法參數): 回傳值 = {內容}

def showNum() = 9
println(showNum()) // 9

def showNum:Int = 9
println(showNum) // 9

def add(x: Int): Int = x + 1
println(add(7)) // 8

def add2(x: Int, y: Int):Int = x + y
println(add2(1, 2)) // 3

def add2(x: Int)(y: Int):Int = x + y
println(add2(1)(2)) // 3

※()可以用多個,但用多個時,呼叫也要用多個



※方法裡的方法


java 不允許方法裡再放方法,但 scala 可以

def accu(start:Int, end: Int): Int = {
if(start >= end) return 0
var vstart = start - 1

def accu2(acc: Int, end: Int): Int = {
  //println("acc=" + acc + ", vstart=" + vstart)
  if (vstart == end)
return acc
  else
vstart += 1
accu2(acc + vstart, end)
}
accu2(0, end)
}
println(accu(0, 10))

※加總的程式


※類別

object ScalaTest {
  def main(args: Array[String]): Unit = {
    val a = new A
    val b = new B()
    val c = new C
    val d1 = new D("k", 1)
    println(d1.toString)
    d1.customMethod(99)

    val d2 = new D("k")
    println(d2.toString)

    val e = new E(value=1)
    println(e.toString)
  }

  class B() {}
  class C
  class D(var key:String, value:Int = 0) {
    override def toString: String = s"($key, $value)"

    def customMethod(xx: Int): Unit = {
      println(xx)
    }
  }
  class E(var key:String="xxx", value:Int) {
    override def toString: String = s"($key, $value)"
  }
}
class A {}

※也有內部類別,類別和建構子整合在一起,如果是空的參數,可以省略圓括號

※建構子可以有預設值,這樣就有 overloading 了

※使用 s"($xxx)" 可取得參數值

※class E,第一個建構子有預設值,如果第一個參數給 null,就真的抓到 null 了

※建構子中,有 val、var 時,new 完才可看見,如果不寫就是 private



※訪問修飾子

public、protected、private
以 java 來對比的話,沒有預設不寫的修飾子
protected 只有子類別可訪問,同包不行,這點和 java 不同

.作用域
可以在修飾子後面用方括號包起一個類別
如 private[.ooo.Xxx] :表示除了 Xxx 這個類別外,其他都是 private 的




※setter/getter

class Xxx {
  private var pid: Int = 0

  def id = pid
  def id_= (id: Int): Unit = {
this.pid = id
  }

  // def setId(id: Int): Unit = {
  //   this.id = id
  // }
  //

  // def getId() :Int = {
  //   this.id
  // }

  private def add(x: Int): Int = x + 1
}

val x = new Xxx()
x.id = 999
println(x.id)

※註解的部分是 java 的寫法,也是可以用

※setter 宣告方法:
1.宣告一個外面看不見的變數 (pid)
2.宣告一個外面看的見的變數 (id),這樣外面才可以用這個名稱,
3.然後再宣告一個方法,是 2 的後面加 _=,在 scala 表示 setter

※getter 就是變數名稱 (pid)

※沒有特別寫修飾子,預設是 public,但這個字不能寫,會編譯錯誤



※case

類似 java 的 switch

def xxx(str: String):Int = str match {
case "a" => 1
case "b" => 2
// case _ => 3
}
println(xxx("c"))

※匹配 _ 就是當什麼都匹配不到時用的,此例會報錯,必須將註解打開

※不需要也不能寫 break


※case class

在 scala 可以將 case 用在 class

case class Animal(id: Int, name: String)

val a1 = Animal(1, "dragon")
val a2 = Animal(2, "dragon")
val a3 = Animal(1, "dragon")
val a4 = new Animal(1, "dragon") // 可省略 new
println(a1 == a2) // false
println(a1 == a3) // true
println(a1 == a4) // true


.迴圈判斷

val list = List(
Animal(1, "pig"),
Animal(2, "dog"),
Animal(3, "chicken")
)

for(l <- list) {
l match {
case Animal(1, "pig") => println("I'm pig")
case Animal(2, "dog") => println("I'm dog")
case _ => println("Who am I")
}
}

2019年9月2日 星期一

java 11 功能

※HttpClient

org.apache.http.client.HttpClient.HttpClient;
java.net.HttpURLConnection;
java.net.http.HttpClient;

apache 也有 HttpClient,但聽說效能沒 HttpURLConnection 好,所以就越來越少人用了,但這一版新增了替代的抽象類別,名字也叫 HttpClient,可以支援 http 2.0 的,至於寫法,API 已有提供範例


※Optional 增加 1 個方法

.isEmpty
Optional<String> op = Optional.ofNullable(null);
if (op.isEmpty()) {
System.out.println("空的");
}



※String 新增 6 個方法

.strip、stripLeading、stripTrailing
System.out.println(" \t \n \r".strip().length()); // 0,去掉空格(全半形)
String s = " a\t \n \ra  ";
System.out.println(s.stripLeading().length()); // 9,去掉前空格(全半形)
System.out.println(s.stripTrailing().length()); // 8,去掉後空格(全半形)

.isBlank
System.out.println("".isBlank()); // true
System.out.println(" \t \n \r".isBlank()); // false

.repeat、lines
System.out.println("abc".repeat(3)); // abcabcabc
System.out.println("asdf\r\nzxcv\r\n".lines().count()); // 2
System.out.println("asdf\r\nzxcv\r\nqwer".lines().count()); // 3
lines() 回傳 Stream<String>

2019年9月1日 星期日

java 10 功能

※var

var i = 11;
只能寫在區域變數且一定要賦非 null 的值
var j = new int[]{1,2,3}; // new int[] 不能省略
可以寫在 lambda 的參數,但 lambda 本來就可以省略,多此一舉
可以寫在迴圈,新舊都可以
不能寫在方法的參數和回傳值


※Optional 增加 1 個方法

.orElseThrow
Integer integer = Optional.ofNullable(1).orElseThrow(NullPointerException::new);

如果是 null 就會報 orElseThrow 裡寫的 exception



※List.copyOf

List<Integer> list1 = List.of(1, 2, 3);
List<Integer> copyList1 = List.copyOf(list1);
System.out.println(list1 == copyList1); // true

List<Integer> list2 = Stream.of(1, 2, 3).collect(Collectors.toList());
List<Integer> copyList2 = List.copyOf(list2);
System.out.println(list2 == copyList2); // false

※此方法只會複製不可變的,所以如果本來就是不可變的,底層會判斷能不能 instanceof成 AbstractImmutableList,傳回來會不一樣

※copyOf 是淺複製

class Animal {
    private int id;

// getter/setter

    public Animal(int id) {
        this.id = id;
    }
}

List<Animal> animals = List.of(new Animal(1), new Animal(2), new Animal(3));
List<Animal> copyAnimals = List.copyOf(animals);
System.out.println(animals == copyAnimals); // true

animals.forEach(a -> System.out.println(a.getId())); // 1 2 3
animals.get(0).setId(3);
animals.forEach(a -> System.out.println(a.getId())); // 3 2 3
copyAnimals.forEach(a -> System.out.println(a.getId())); // 3 2 3
System.out.println(animals == copyAnimals); // true

Java 9 功能

※Module



新建一個專案,然後增加兩個 module,如上圖,Module1 和 Module2
在 Module1 增加 class M1;Module2 增加 class M2
M1 想用 M2 預設是沒辦法的,所以在兩個 module 的 src 按右鍵增加 module-info

1.M2 要給別人用,所以要匯出,都是以 package 為單位
module Module2 {
    exports xxx.ooo;
}

2.M1 要用別人的,所以要要求使用,如果要用 java 內鍵的,按下 content assist 會有提示
module Module1 {
    requires Module2;
}

3.此時還是會編譯錯誤,還得如下使用:

4.好文章


※介面可用 private 方法


※String 和 AbstractStringBuilder 

從 char[] 改成 byte[],AbstractStringBuilder 是 StringBuffer 和 StringBuilder 的抽象父類別


※jshell

java 的 shell,在安裝目錄的 bin 裡,每次要測簡單的東西,都要寫 class,然後在寫個 main 方法是很累人的,所以直接在裡面打即可
1.除了 java.lang 外,打 /imports 是預設有 import 的
2.不用 try catch
3.屬性和方法會後者蓋前者



※省略泛型和增強 try-with-resource

new Comparable<>(){
@Override
public int compareTo(Object o) {
return 0;
}
};

※在 java 7 就可以省略 <> 裡的東西了,但匿名類別不行,在這一版可以了


InputStreamReader isr = new InputStreamReader(System.in);
try(isr) {

} catch(Exception e) {

}

※在 java 7 就可以有 try () 了,但宣告都要在裡面才行,在這一版可以寫在外面,裡面寫變數名稱即可

※不能改 try 裡的東西,裡面是 final 的



※不可變的可讀集合

List.of(1, 2, 3, 4, 5);
Set.of(1, 1, 2, 2, 3);
Map.of("a", 1, "b", 2);
Map.ofEntries(Map.entry("c", 3), Map.entry("d", 4));



※Optional 增加 3 個方法

.stream
List<Integer> list = List.of(1, 2, 3);
Optional<List<Integer>> oList = Optional.ofNullable(list);
Stream<List<Integer>> stream = oList.stream();

※回傳的和 Optional 的泛型一樣

.or
Optional<Integer> op = Optional.ofNullable(null);
op = op.or(() -> Optional.of(7));
System.out.println(op.get());

※如果不是 null 就回傳,否則就回傳 7


.ifPresentOrElse
Optional<Integer> op = Optional.ofNullable(null);
op.ifPresentOrElse(x -> System.out.println("x=" + x), () -> System.out.println("hahaha"));

※回傳 x 或 hahaha


※Stream 增加 4 個方法

Stream.iterate(0, n -> n < 10, n -> ++n).forEach(System.out::println);

※這是個 overloading 方法,java 8 只有一個,要加 limit 才不會變成無限流,現在有了中間參數是 Predicate,可以不用 limit 了


Stream.of(1, 2, 6, 7, 4, 3).takeWhile(n -> n < 6).forEach(System.out::println); // 1 2
Stream.of(1, 2, 6, 7, 4, 3).dropWhile(n -> n < 6).forEach(System.out::println); // 6 7 4 3
takeWhile:從第一個開始判斷,如果條件成立( < 6),就抓,但只要條件不成立,馬上返回 (雖然後面也有條件成立的,但不管)

dropWhile:takeWhile 的相反,從第一個開始判斷,如果條件成立就刪除,但只要條件不成立,馬上返回 (雖然後面也有條件成立的,但不管)

Stream.of(null, null).count(); // 2 這個不是新增的,如果有兩個以上 (包括兩個) 的元素,不會報錯,但如果只有一個 null,會報空指針,所以可以用新方法 ofNullable,但回傳的是 0
Stream.ofNullable(null).count(); // 0



※ElementType 增加 MODULE

使用在 module-info 裡,例子可看這篇最下面

2019年8月15日 星期四

java 的 ~、^、<<、>>、>>>、<<=、>>=、>>>=



final byte x = 40;
final byte y = -40;
    
System.out.println(~x); // -41
System.out.println(~y); // 39
    
System.out.println(5 ^ 6); // 3
System.out.println(x ^ y); // -16
System.out.println(Integer.toBinaryString(x)); // 101000
System.out.println(Integer.toBinaryString(y)); // 11111111111111111111111111011000
System.out.println(Integer.toBinaryString(-16)); // 11111111111111111111111111110000
    
System.out.println(x << 2); // 160
System.out.println(x >> 2); // 10
System.out.println(x >>> 2); // 10
    
System.out.println(y << 2); // -160
System.out.println(y >> 2); // -10
System.out.println(y >>> 2); // 1073741814
System.out.println(y >>> 4); // 268435453

※這裡的符號都會轉換成二進制,可用 Integer.toBinaryString 或 Long.toBinaryString 查看,這兩個差在 32 位和64 位


40 的二進制:(前面沒有視同 0,總共 32 或 64 位) 10 1000
-40 的二進制:1111 1111 1111 1111 1111 1111 1101 1000


※正二進制轉負二進制

二進制取反 +1
如:-40 的二進制就是 40 的二進制取反 +1
40 -> 10 1000 取反 --> 01 0111 加 1 -> (前面很多1)  01 1000

※負的二進制轉成 10 進制

取反後 +1 的 10 進制乘 -1

※~ 取反(簡單公式就是 (x+1)*-1)

正數:二進制+1後轉10進制,然後乘-1
負數:二進制取反
.40 轉二進制 -> 10 1000 -> 10 1001 = 41 * -1 -> -41

.-40 轉二進制 -> (前面很多1) 01 1000 取反->10 0111 = 39



※ ^ XOR 互相排斥,一正一反為 true,兩正兩反為 false

.5 ^ 6
5 -> 101
6-> 110
XOR 後,為 011 -> 3

.40 ^ -40
40 -> 101000
-40 -> 1111 1111 1111 1111 1111 1111 1101 1000
XOR 後,為 (前面很多1) 11 0000

驗證:
(前面很多1) 11 0000 取反 -> 1111 + 1 -> 1 0000 -> 16 * -1 = -16

※有兩個數想要互換,但不能用中間的 temp 變數,有以下兩種方法

一個數對另一個數互斥兩次,值不會變
var x = 66;
var y = 77;
x = x ^ y;
y = x ^ y;
x = x ^ y;
要小心 x 和 y 的值一樣時,不能用這招,直接 return 即可
----------------------------------
先取得兩數的和再進行減法運算
var x = 66;
var y = 77;
x = x + y;
y = x - y;
x = x - y;

要小心 x+y 超過類型的範圍就不行了


※ << 左移運算符

.40 -> 10 1000
<< 2 往左二位,就相當於在最右邊增加 2 個 0
1010 0000 -> 2 的 7 次方 + 2 的 5 次方 -> 128 + 32 = 160
以十進位來說, <<2 就是乘 2 的 2 次方;<<3 就是乘 2 的 3 次方

.-40 ->  (前面很多1)  01 1000 -> 0110 0000

驗證:
(前面很多1) 0110 0000 取反 -> 1001 1111 + 1 -> 1010 0000 -> 32 + 128 -> 160 * -1 = -160


可以背快速的用法:
例一:40 << 4:4 表示 2 的 4 次方,結果為 40 *16
例二:1 << 3:1 * 8

※ >> 右移運算符

.40 -> 10 1000
>> 2 往右二位,就相當於最右邊刪除 2 位,如果是正數,最左邊補 2 個 0
1010 = 10
以十進位來說, >>2 就是除 2 的 2 次方;>>3 就是除 2 的 3 次方,如果有小數點都是無條件捨去

.-40 -> (前面很多1)  01 1000 ->  (前面很多1) 0110,負數最左邊是補 1

驗證:
(前面很多1) 0110 取反 -> 1001 + 1 -> 1010 = 10 * -1 = -10


※左、右移運算符的應用可看這篇




※ >>> 無符號右移運算符 (就是只有正數,正數結果和 >> 一樣)

※就算宣告成 byte,結果還是 32 位,也不會報錯,可以正常使用

※因為只有正數,左邊一定是 0,至於有幾個 0,要看 >>> 3 給 3 那就是 3 個 0;右邊的處理和 >> 一樣,刪 3 個最右邊的

※>> 右移運算符的負數是最左邊補 1,無符號是補 0,最左邊補0後,換算時就不是負的了,所以說只有正數,不用再取反+1了

-40 -> (前面很多1)  01 1000

.>>> 2,就相當於最右邊刪除 2 位,最左邊 2 個改 0
‭0011 1111 1111 1111 1111 1111 1111 0110‬
最左邊的 0 可以刪除


.>>> 4,就相當於最右邊刪除 4 位,最左邊 4 個改 0
‭0000 1111 1111 1111 1111 1111 1111 1101
最左邊的 0 可以刪除‬,最下面小算盤的圖就是省略了

從最右邊的 2的0次方到最左邊的 2 的 31 次方,一個一個加起來就是 10 進位了,但這個用人工算太累了,可用小算盤,如下:




※<<=、>>=、>>>=


int x = 40 << 2;

int o = 40;
o <<= 2; // o = o << 2;

此時 x 和 o 是一樣的意思,一定要分兩行,否則編譯錯誤,右移和無符號右移也是一樣,這在 jdk7 的 HashMap 原碼看到的

2019年7月27日 星期六

設定中心 ( SpringCloud 2.x 七)

因為每一個微服務都有一個 application.yml,這裡是做一個統合的管理

※Server 端設定


※新增一個全新的專案,放在 github 上,裡面就放一個 application.yml,內容如下:
spring:
  profiles:
    active: dev
---
spring:
  profiles: dev
  application:
    name: xxx-dev
---
spring:
  profiles: uat
  application:
    name: xxx-uat
---
spring:
  profiles: test
  application:
    name: xxx-test


※ 連 --- 也不能省略

※spring.profiles.active 還可以配合 @Profile 使用



※新增一個 module,增加 pom

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>




※application.yml
server:
  port: 1111
spring:
  application:
    name: config-center
  cloud:
    config:
      server:
        git:
          uri: https://github.com/bruce12452002/SpringCloudConfigCenterPractice.git # git網址


※和 uri 同層的還有 username 和 password,是 github 的帳號和密碼,但本來 git clone 就不用打帳號密碼,所以沒有要 push 的話,可以不用寫

※uri 一打,啟動後的控製台就沒有預設的 8888 port 了

※main 方法增加 @EnableConfigServer,然後啟動測試看看

※hosts 增加 127.0.0.1       config.ooo1111 來模擬

※打上如下的網址,即可看到結果:
http://config.ooo1111:1111/application-dev.yml
http://config.ooo1111:1111/master/application-dev.yml

spring:
  application:
    name: xxx-dev
  profiles:
    active: dev



http://config.ooo1111:1111/application-uat.yml
spring:
  application:
    name: xxx-uat
  profiles:
    active: dev



http://config.ooo1111:1111/application-xxx.yml
spring:
  profiles:
    active: dev

※格式不是亂打的,看官網,label 是分支的意思



※Client 端設定


※在github 增加 abcxxx.yml,內容如下:
spring:
  profiles:
    active: dev
---
server:
  port: 8001
spring:
  profiles: dev
  application:
    name: my-config-dev
eureka:
  client:
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka
---
server:
  port: 8002
spring:
  profiles: uat
  application:
    name: my-config-uat
eureka:
  client:
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka
---
server:
  port: 8003
spring:
  profiles: test
  application:
    name: my-config-test
eureka:
  client:
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka




※新建一個 model,加入 pom
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>


※父 pom 已經有 spring-cloud-starter-config,所以不用加


※寫一個測試的方法
@RestController
public class ClientConfigInfo {
    @Value("${spring.application.name}")
    private String appName;
    
    @Value("${eureka.client.service-url.defaultZone}")
    private String eurekaServerName;
    
    @Value("${server.port}")
    private Integer port;
    
    @GetMapping("/configInfo")
    public String getConfigInfo() {
        StringBuilder sb = new StringBuilder();
        sb.append("appName=").append(appName)
        .append(", eurekaServerName=").append(eurekaServerName)
        .append(", port=").append(port);
        return sb.toString();
    }
}




※以下只能寫在 bootstrap.yml
spring:
  cloud:
    config:
      name: abcxxx # 讀 github 的 yml,但不能寫副檔名
      profile: dev
      label: master
      uri: http://config.ooo1111:1111  # 找 config 伺服器





※測試

hosts 增加 127.0.0.1       config-client.ooo 模擬,不能用「_」,會報錯

啟動時控製台出現以下訊息:


 ※表示連到了 8003,然後在網址打 http://config-client.ooo:8003/configInfo,即可看到結果

※修改 bootstrap.yml 裡的 spring.cloud.config.profile,只要 github 有的,重新啟動後即可抓到新的設定

Zuul ( SpringCloud 2.x 六)

增加一個 model

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>



server:
  port: 9099
    
spring:
  application:
    name: my-zuul
    
eureka:
  client:
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka,http://xxx.ooo9052:9052/eureka,http://xxx.ooo9053:9053/eureka
  instance:
    instance-id: bruce-zuul
    prefer-ip-address: true
    nonSecurePort: ${server.port}
    
info:
  xxx.ooo: xxoo.aa
  name: zuul.info
  jdk_version: @java.version@
  version: @version@
  chi_test: 梅山小路用9099
  ppp: @aaa.bbb@
    
zuul:
  routes:
    mycloud: # 隨便寫
      serviceId: PROVIDER1
      path: /xxx/**
  # prefix: /abc
  # ignored-services: PROVIDER1 # 大寫微服務名稱


 ※ 有些版本訪問 http://zuul.ooo9099:9099/PROVIDER1/testGet 是可以成功的,但這樣就暴露了微服務名稱了,所以可以設定 ignored-services,如果全部都不給訪問,可以用「"*"」

※serviceId 會被 path 取而代之



※main 方法加入三個 annotation 即可

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient

※模擬用戶發 request 到 zuul,所以在 hosts 增加 127.0.0.1 zuul.ooo9099



※測試

開啟 eureka -> provider (9001 port) -> zuul

然後在網址打上 http://zuul.ooo9099:9099/xxx/testGet 即可訪問

又如果有加 prefix,那就要換成如下的網址
http://zuul.ooo9099:9099/abc/xxx/testGet


※遇到的問題

application.yml 一定要配 zuul,可能其他版本可以,但我試的結果就是不行

2019年7月21日 星期日

Hystrix ( SpringCloud 2.x 五)

 Hystrix 就像保險絲一樣,可以處理異常


※server 端的異常


※1.複製 provider 後,增加 pom

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>


※這樣才有 @EnableCircuitBreaker、@HystrixCommand 可用

※順便改個 yml,instance.instance-id: bruceProvider-hystrix



※2.增加 provider-hystrix 的 controller

@RestController
public class TestController {
    @GetMapping("/testGet")
    public ApiBean get() {
        ApiBean ab = new ApiBean();
        ab.setId(1);
        ab.setName("xxx9001");
        return ab;
    }
    
    @HystrixCommand(fallbackMethod = "xxx")
    @GetMapping("/testHystrix/{id}")
    public ApiBean getXxxById(@PathVariable("id") Integer id) { // throws ParseException {
        if (id == 1) {
            ApiBean ab = new ApiBean();
            ab.setId(1);
            ab.setName("xxx9001");
            return ab;
        } else {
            throw new RuntimeException("耶!掛了");
            // throw new ParseException("xxx", -1);
        }
    }
    
    private ApiBean xxx(@PathVariable("id") Integer id) {
        ApiBean ab = new ApiBean();
        ab.setId(-1);
        ab.setName("沒有" + id);
        return ab;
    }
}


※只要拋異常都會被 xxx 方法所接收並回傳

※main 方法要加 @EnableCircuitBreake,但加 @EnableHystrix也可以,因為這個註解裡面也用 @EnableCircuitBreake,所以二選一即可

※如果很多方法都要用同一個,可以用全域的方式,如下:
一. 在方法上加 @HystrixCommand,但不寫 fallbackMethod
二. 在 class 上加 @DefaultProperties(defaultFallback="方法名") 即可
本來的 @HystrixCommand(fallbackMethod="方法名") 並不會被影響



※3.consumerFegin 也增加呼叫對應的方法


@RestController
public class ConsumerController {
    @Resource
    private MyService myService;
    
    @GetMapping("/xxx")
    public ApiBean get() {
        return myService.get();
    }
    
    @GetMapping("/ooo/{id}")
    public ApiBean getHystrix(@PathVariable("id") Integer id) {
        return myService.get(id);
    }
}





※4.api 也增加對應的方法

@FeignClient("PROVIDER1")
public interface MyService {
    @GetMapping("/testGet")
    ApiBean get();
    
    @GetMapping("/testHystrix/{id}")
    ApiBean get(@PathVariable("id") Integer id);
}


※使用 http://localhost/ooo/1 是正常的,但只要不是 1,都會出現 hystrix 提供的功能



※client 端的異常


由於 server 端的做法太過於高耦合了,在 client 端可以針對介面做處理,就算 server 端掛了,還是可以儘量的顯示友好的訊息

※1.因為要解耦,所以不要 @HystrixCommand,連 xxx 方法也不需要了,然後在 api 專案的 @FeignClient 有個 fallbackFactory,指定一個類別,然後做異常處理

@FeignClient(name = "PROVIDER1", fallbackFactory = MyFallbackFactory.class)
public interface MyService {
    @GetMapping("/testGet")
    ApiBean get();
    
    @GetMapping("/testHystrix/{id}")
    ApiBean get(@PathVariable("id") Integer id);
}





※2.異常處理類
@Component
public class MyFallbackFactory implements FallbackFactory<MyService> {
    @Override
    public MyService create(Throwable throwable) {
        return new MyService() {
            @Override
            public ApiBean get() {
                return null;
            }
    
            @Override
            public ApiBean get(Integer id) {
                ApiBean ab = new ApiBean();
                ab.setId(-1);
                ab.setName("沒有" + id);
                return ab;
            }
        };
    }
}


※記得要有 @Component 之類的註解,否則出錯時會出現 feign.FeignException: status 500 reading MyService#get(Integer) 的錯誤訊息


※3.cousumerfeign 的 yml 要加上 hystrix.enabled: true


※4.cousumerfeign 啟動類別
@SpringBootApplication
@ComponentScan({"controller", "service"})
@EnableEurekaClient
@EnableFeignClients("service")
public class ConsumerFeignMain {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerFeignMain.class, args);
    }
}


※注意 @ComponentScan 要加上 api MyFallbackFactory 的包名,只要這個沒加或 yml 沒設定為 true,在啟動時都會出現 No fallbackFactory instance of type class service.MyFallbackFactory found for feign client PROVIDER1

※測試時和 server 端一樣,但是多一個,將 provider 關閉時,還是能出現 client 端設定的訊息,不管是不是 1 都是這個結果,所以訊息可以改得好一點的



※Hystrix 儀錶版


就是監控 hystrix 的

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>


※新增一個 module,然後增加 pom

※yml 很簡單,就是 port 而已,server.port: 9010



@SpringBootApplication
@EnableHystrixDashboard
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}


※啟動類別增加 @EnableHystrixDashboard

※訪問 http://localhost:9010/hystrix 就可看到帶刺的豬

測試:開啟 eureka -> provider_hystrix -> consumerFeign -> hystrix_dashboard

被監視的 provider_hystrix 要加 management.endpoints.web.exposure.include=*,否則點下 Monitor Stream 的按鈕時,中間會有 Unable to connect to Command Metric Stream. 的紅字,成功是 Loading ...

※以下沒試出來
1.上圖反白的網址是要監控的網址,所以是 provider_hystrix的,改成 http://localhost:9001/actuator/hystrix.stream,然後開啟新網頁貼上會看到網頁一直再跑,我試的結果居是出現問我要不要下載

2.但第1項是文字介面的,所以在上圖可打網址的地方貼上1的網址就可看到圖形介面

3.用 consumerFeign 訪問 provider_hystrix 即可看到 dashboard 的球變大

2019年7月20日 星期六

Feign ( SpringCloud 2.x 四)

和 Ribbon 都是客户端的負載均衡,Ribbon 用 RestTemplate 封裝 HTTP 的請求;Feign 在這個基礎上,加上大家都熟悉的接口式編程

基本的 consumer 已經很複雜了,所以複製 consumer 成為一個新 module,刪除ribbon相關的內容(兩個 class 和 @RibbonClien,連 ConfigRestTemplate也不要了)


1.api 和 consumerFeign 的 pom
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>


※這樣才有 @FeignClient 和 @EnableFeignClients 可用


2.
@FeignClient("PROVIDER1")
public interface MyService {
    @GetMapping("/testGet")
    ApiBean get();
}


※@FeignClient 指定想訪問的 spring.application.name


3.
@SpringBootApplication
@ComponentScan("controller")
@EnableEurekaClient
@EnableFeignClients("service")
public class ConsumerFeignMain {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerFeignMain.class, args);
    }
}


※@EnableFeignClients 指定 api 裡的包路徑,只要錯了,啟動時就會出現 A component required a bean of type 'service.MyService' that could not be found.


4.
@RestController
public class ConsumerController {
//    private static final String PROVIDER_URI = "http://localhost:9001";
/*    private static final String PROVIDER_URI = "http://"+ "provider1".toUpperCase(); // 網址列不分大小寫,但還是以 eureka 為準的好
    @Resource
    private RestTemplate restTemplate;
    
    @GetMapping("/xxx")
    public ApiBean get() {
        return restTemplate.getForObject(PROVIDER_URI + "/testGet", ApiBean.class);
    }
*/
    
    @Resource
    private MyService myService;
    
    @GetMapping("/xxx")
    public ApiBean get() {
        return myService.get();
    }
}


※使用 feign 後,可以不用寫服務名

※預設是輪詢算法,可以將上一篇的 MyCustomRibbonRule 複製過來,改變自己想要的算法或內鍵的其他算法

2019年7月14日 星期日

Ribbon ( SpringCloud 2.x 三)

Ribbon 為客戶端的負載均衡,所以會改 consumer

@Configuration
public class ConfigRestTemplate {
    @Bean
    // @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}


※新增一個類別,因為要使用到 @LoadBananced 這個註解,只好放棄使用 @Import,但在使用前先註解,確保之前的程式是可以跑的


// @Import(RestTemplate.class)
public class consumerController {
    // private static final String PROVIDER_URI = "http://localhost:9001";
    private static final String PROVIDER_URI = "http://"+ "provider1".toUpperCase();
    // ...
}


※確保之前的程式能跑後,使用 ribbon 要三步
1.打開 @LoadBananced
2.訪問路徑改成 provider 的 spring.application.name 名稱,大小寫都可以,但 eureka 是大寫,最好都用一樣的

※以上缺一都會報錯

※不需要 ribbon 的 jar 包,eureka-client 已經有依賴了,這個 eureka-client 在第一篇已經加過了

※遇到的問題:

如果出現 Request URI does not contain a valid hostname 的錯,表示 spring.application.name 的名字找不到,不要忘了要加 http://
另外一個是 name 名稱不能有「_」,都會報這樣的錯

這個專案在隔天 run 時,居然出現找不到 PROVIDER1 的錯,結果 mvn clean install 就解決了



※測試 ribbon 預設的模式

新增兩個專案:
1.複製 pom.xml
2.複製 啟動類別和 controller,為了測試區別,/xxx 的內容三支都修改 ab.setName("xxx9002"); // 9001-9003
3.instance.instance-id 修改不同的名稱,但 spring.application.name 一樣
4.http://localhost/xxx 每重整一次會發現是有順序性的,如第一次循環是 132,就會一直按 132 的方式循環

畫面如下:
可看見 PROVIDER1 有三個實例



※改變預設模式


@Configuration
public class ConfigRestTemplate {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
    
    @Bean
    public IRule myRibbonRule() {
    // return new RoundRobinRule();
    // return new RandomRule();
        return new RetryRule();
    }
}

※在自訂的 ConfigRestTemplate 增加 IRule 的回傳 Bean,RoundRobinRule 是預設的、
RandomRule 是隨機的、RetryRule 和預設的很像,差在如果其中有 provider 掛了就不一樣了,假設是 132 一直循環,然後 2 掛了,那就會是 13掛、13掛、經過幾次之後,就只會有13而已,可以將三個 provider 啟好後,關閉其中一個測試

※也可以寫個 class,然後回傳 IRule



※自定義規則



@Configuration
public class MyCustomRibbonRule {
    @Bean
    public IRule myRibbonRule() {
        // return new RandomRule();
        return new CalcRibbonRule();
    }
}


※改變預設模式的 @Bean 要註解或改名,不然會有 2 個 IRule


public class CalcRibbonRule extends AbstractLoadBalancerRule {
    private int currentIndex = 1; //  PROVIDER1 的機器號碼
    
    private Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;
    
        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers(); // 到的有幾台機器
            List<Server> allList = lb.getAllServers(); // 全部有幾台機器
    
            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }
    
            // 主要邏輯在這
            if (upList.size() % 2 == 1) {
                server = upList.get(currentIndex);
                currentIndex++;
                System.out.println("size==>" + upList.size());
                System.out.println("currentIndex==>" + currentIndex);
                if (currentIndex >= serverCount) {
                    currentIndex = 1;
                }
            }
    
            if (server == null) {
                Thread.yield();
                continue;
            }
    
            if (server.isAlive()) {
                return (server);
            }
            server = null;
            Thread.yield();
        }
        return server;
    }
    
    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }
    
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {}
}


※我想不到什麼好規則,所以就是奇數的機器才去訪問,但並不是 9051 和 9053 這兩台機器的意思,機器的順序不是我們決定的,假設是 132,那就是 1 和 2

※本來是將 CalcRibbonRule 寫在 MyCustomRibbonRule 裡面,成為內部類別,但訪問的時候報錯了,NoSuchMethodException: controller.MyCustomRibbonRule$CalcRibbonRule.

※最後 consumer 的 main 方法要加上 @RibbonClient(value = "PROVIDER1", configuration = MyCustomRibbonRule.class) 即可

2019年7月13日 星期六

Eureka 集群 ( SpringCloud 2.x 二)

要做集群,在本機要訪問網址不方便,所以在 hosts 新增如下的網址
Windows 的 C:\Windows\system32\drivers\etc 或 unix 的 /etc
127.0.0.1       xxx.ooo9051
127.0.0.1       xxx.ooo9052
127.0.0.1       xxx.ooo9053


※新增兩個 model,並複製 pom 裡的 spring-cloud-starter-netflix-eureka-server,然後修改三個 application.yml,以9052為例

server:
  port: 9052
    
eureka:
  instance:
    hostname: xxx.ooo9052
  client:
    register-with-eureka: false     #不註冊自己
    fetch-registry: false     #取得註冊資訊,因為自己就是註冊中心,不需要取得註冊資訊
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka,http://xxx.ooo9053:9053/eureka


※9051 內容寫 9052 和 9053;9052 內容寫 9051 和 9053

※自己的寫在 eureka.instance.hostname


eureka:
  client:
    service-url:
      #defaultZone: http://localhost:9051/eureka
      defaultZone: http://xxx.ooo9051:9051/eureka,http://xxx.ooo9052:9052/eureka,http://xxx.ooo9053:9053/eureka


※provider 和 consumer 的 defaultZone 也得改

※結果畫面如下,以 xxx.ooo9052:9052 為例
※可以看見 DS Replicas 還有兩個 eureka,內容有 consumer1 和 provider1

2019年7月12日 星期五

向註冊中心註冊與發現服務 ( SpringCloud 2.x 一)

※POM

※父POM

<packaging>pom</packaging>
<modules>
    <module>provider</module>
    <module>consumer</module>
    <module>api</module>
    <module>eureka</module>
</modules>
    
<groupId>scp</groupId>
<artifactId>SpringCloudPractice</artifactId>
<version>1.0-SNAPSHOT</version>
    
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring-cloud-release.version>Greenwich.RELEASE</spring-cloud-release.version>
</properties>
    
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud-release.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.5.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
    
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>


※生產者和消費者向註冊中心註冊,api 為生產者和消費者共用的方法


※consumer pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<artifactId>consumer</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <artifactId>api</artifactId>
        <groupId>scp</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>



※provider pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
    
<artifactId>provider</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <artifactId>api</artifactId>
        <groupId>scp</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>



※api pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<artifactId>api</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>
    
<build>
    <finalName>cyc</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <classifier>exec</classifier>
            </configuration>
        </plugin>
    </plugins>
</build>

※因為 api 有程式碼給 consumer 和 provider 使用,maven install 會失敗,所以要在 api 加上 plugin 才可以

※eureka pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<artifactId>eureka</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        <!--<artifactId>spring-cloud-netflix-eureka-server</artifactId>没有 starter 是错的-->
    </dependency>
</dependencies>




※java 類別


※provider

package controller;
    
@RestController
public class TestController {
    @GetMapping("/testGet")
    public ApiBean get() {
        System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        ApiBean ab = new ApiBean();
        ab.setId(1);
        ab.setName("xxx");
        return ab;
    }
}



※consumer

package controller;
    
@RestController
@Import(RestTemplate.class)
public class consumerController {
    @Resource
    private RestTemplate restTemplate;
    
    @GetMapping("/xxx")
    public ApiBean get() {
        return restTemplate.getForObject("http://localhost:9001" + "/testGet", ApiBean.class);
    }
}


※因為 RestTemplate 是內鍵的 class,並沒有類似 @Component 這種註解,spring 不知道,所以用了 @Import 將它註冊到 spring

※api

package scp;
    
@Data
public class ApiBean implements Serializable {
    private int id;
    private String name;
}




※eureka

package main;
    
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain.class, args);
    }
}




※application.yml


※provider

server:
  port: 9001
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9051/eureka



※consumer

server:
  port: 80
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9051/eureka



※eureka

server:
  port: 9051
eureka:
  client:
    register-with-eureka: false     # 不註冊自己
    fetch-registry: false     # 取得註冊資訊,因為自己就是註冊中心,不需要取得註冊資訊
    service-url:
      defaultZone: http://localhost:${server.port}/eureka/   # Eureka Server 的位址


※ fetch-registry 一定要 false 才有辦法啟動,否則會報 Cannot execute request on any known server

※ register-with-eureka 如果註冊自己可以順利啟動成功,只是沒什麼意義,感覺就像麥當勞的員工自己也在排隊買漢堡一樣

※server.port 如果給 0,會隨機產生一個,看控製台即可知道


※遇到的問題

invalid LOC header
jar 檔簽名壞了,從倉庫刪除後重新下載即可

Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package
main 方法要有 package,還有另外一個錯誤是要有 main 方法,但錯誤訊息很明顯,所以一個專案放一個有 package 的 main 方法就可以了,方法裡什麼都不寫也行



※Eureka 註冊中心

 啟動後會有現都是 UNKNOWN,所以 consumer 和 provider 還要在 application.yml 加上 spring.application.name,這樣 eureka 才會分辯的出來,修改後如下,eureka 會變成全部大寫英文


 如果想改右邊的 Status 顯示的名稱,還得在 consumer 和 provider 加上 eureka.instance.instance-id,修改後如下,這裡 eureka 不會變成大寫


上上一張圖的左下角有網址,是滑鼠在 status 的 consumer 或 provider 上時顯示的,我記得不改是 localhost 之類的,但不知哪一版改了,如果是 localhost 想改成 ip,還可以加上 eureka.instance.prefer-ip-address: true 就可以了
還有一個很像的設定 eureka.instance.ip-address: ,因為有可能有多張網卡,可以寫死其中一張網卡的 IP,如果兩個都設定,最終註冊在 eureka 會是這裡的設定



※啟動順序 

必需先啟動 eureka,再啟動 provider,最後是 consumer
如果 eureka 在啟動前有其他的專案先啟動了,eureka 抓不到
如果 consumer 在 provider 之前,我試的結果沒問題,但理論上是先有提供者,這樣消費者才抓的到
如果已經照順序啟動好了,將 provider 或 consumer 的 spring.application.name 名字換掉,只重啟改掉的專案,會發現 eureka 會有紅色的警告,因為有個 CAP 理論

Consistency(一致性)
Availability(可用性)
Partition tolerance(分区容错性)

目前不可能全部做到,會犧牲一個


eureka 保證 AP
zookeeper 保證 CP

長時間沒有人連 eureka 會報這個警告,或者我將名稱換掉也會,因為他要保證可用性,有可能只是網路不順,寧可記錄錯的資料也不要刪掉,而預設過了 90 秒後,真的都沒有人連了,才會刪除,如果不喜歡這個功能,可以關掉
eureka.server.enable-self-preservation: false


※actuator/info

上面的 Status 一點進去就掛了,這裡是設定裡面的資訊

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
-----------------------------
<build>
    <finalName>SpringCloudPractice</finalName>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <configuration>
                <delimiters>
                    <delimit>@</delimit>
                </delimiters>
            </configuration>
        </plugin>
    </plugins>
</build>


※這裡的 pom 是全域的

※delimit 裡面的符號是用在註冊進 eureka 的專案要用到的,前後用這個符號包起來即可

※以下以 provider 為例,在 application.yml 增加如下的設定

info:
  xxx.ooo: xxoo.aa
  name: provider.info
  jdk_version: @java.version@
  version: @version@
  chi_test: 梅山小路用


※info 下的 key 和 value 都可以隨便亂打,但 @ 包起來的部分會去 pom 抓,有自己的就抓自己的,沒有就抓父 pom 的,雖然 provider 有 api,但不會去抓,如果都沒有,啟動會報錯




※服務發現


使用 DiscoveryClient 可以發現 Eureka 上註冊的服務

@Resource
private DiscoveryClient client;
    
@GetMapping("/discovery")
public DiscoveryClient discoveryTest() {
    List<String> services = client.getServices();
    System.out.println("所有服務名稱=" + services);
    
    // List<ServiceInstance> instances = client.getInstances("provider1".toUpperCase()); // 一定要大寫
    services.forEach(serviceName -> {
        List<ServiceInstance> instances = client.getInstances(serviceName);
        instances.forEach(s -> {
            System.out.println("host=" + s.getHost()); // 192.168.254.104
            System.out.println("instanceId=" + s.getInstanceId()); // bruceProvider1
            System.out.println("scheme=" + s.getScheme()); // null
            System.out.println("serviceId=" + s.getServiceId()); // PROVIDER1
            System.out.println("port=" + s.getPort()); //  9001
            System.out.println("uri=" + s.getUri()); // http://192.168.254.104:9001
    
            Map<String, String> metadata = s.getMetadata();
            metadata.forEach((k, v) -> {
                System.out.print("key=" + k); // management.port
                System.out.println(", value=" + v); // 9001
            });
        });
        System.out.println("------------------------------------");
    });
    return client;
}


※在 provider 加上上面的程式碼

※注意 DiscoveryClient是
org.springframework.cloud.client.discovery 的包

※在 provider 的啟動類上加上 @EnableDiscoveryClient,但我試的結果,不加也可以



private static final String PROVIDER_URI = "http://localhost:9001";
    
@GetMapping("/consumer/discovery")
public Object discovery() {
    return restTemplate.getForObject(PROVIDER_URI + "/discovery", Object.class);
}


※provider 最後是要給 consumer 的,所以這裡新增這一段程式碼,但回傳值我給 DiscoveryClient 後,打上網址 localhost/consumer/discovery 會報錯



※Eureka 增加帳號密碼

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>


※在 Eureka server 增加 security 的 POM

※application.yml 增加 spring.security.user,下一層有 name 和 password 可以設定帳號密碼

※如果有集群,每個都要加 pom 和設定密碼,也可以設定不一樣的帳密

※如果只有加 pom,連網址就會出現打帳密的視窗,但因為沒有帳密進不去


@SpringBootApplication
@EnableEurekaServer
public class EurekaMain extends WebSecurityConfigurerAdapter {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain.class, args);
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // http.csrf().disable(); // 不處理 CSRF 攻擊
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}


※繼承 WebSecurityConfigurerAdapter 後,覆寫 configure,參數是 HttpSecurity 的

※也可以自己寫一個類別繼承 WebSecurityConfigurerAdapte

※不繼承,server 端不會有問題,差在 client 端要連的時候會報 Cannot execute request on any known server 的錯

※用戶端連 eureka 時,只要在 application.yml 的 defaultZone 改一下就可以,如
http://xxx:ooo@xxx.ooo9051:9051/eureka
就是在原本的網址加上「帳號:密碼@」