2021年12月27日 星期一

多益線上家教 收費300元 版本202112

 *具有多年讀書會經驗

*聽力穩定450分以上(490分2次)

*懂得如何從低分群準備到高分群

*多次考試經驗, 並從準備考試中建構會話基礎 主教多益聽力部分,以及準備方法 每周上課1小時,會出一天份量的回家作業 藉由每周進度,幫助久未接觸英文的工作人 找回英文的熟悉度,並累積英文基礎能力 養成良好的學習模式 本次學生程度為400-600 目前人數:4~6 地點:Teams 線上 時間:周日 晚上8:30 - 10:00 收費:300 聯絡方式 : 站內私訊,或是line: iw5420 教材:由老師提供,也可自行購買課本

補充 : 程度較好的同學如要拚600-800分以上,
可以白天另外約時間分享準備方式,
藉由進行一段時間的家教陪讀,
程度達成一定的提升

2021年12月19日 星期日

[Spring Boot] Cookie Without Secure Flag

目前情況如下圖






我們可以看到endpoint api的Cookie, 它的HttpOnly和Secure是空白的

根據下面這一篇
https://stackoverflow.com/questions/34489406/adding-httponly-and-secure-flag-for-set-cookie-in-java-web-application










我們可以得知, sesson-config中可以設置安全cookie相關項目
因此, 推測在appication.properties檔案應該有相對應的配置
在看過下面這一篇
https://stackoverflow.com/questions/40974955/spring-boot-java-config-set-session-timeout
得知有server,servlet.session項目, 因此推測在他之下可能能設置cookie
很幸運的回去appication.properties檔案嘗試, 的確有cookie的設置





設置完的結果發現, JSESSIONID 的cookie secure設置確有改變, 如下圖







可是其他的cookie都沒有改變, 怎麼辦呢?
剛好我有查到這一篇
https://rules.sonarsource.com/java/tag/owasp/RSPEC-2092





可以發現在new出來Cookie的時候, 再setSecue就可以了

解決方式->在專案全局中搜尋Cookie字樣, 將其設置secure再回傳
範例

1產生一個處理類

@Component
public class CookieSecureUtils {
	public static Cookie setSecure(Cookie targetCookie) {
		targetCookie.setHttpOnly(true);
		targetCookie.setSecure(true);
		return targetCookie;
	}
}

2載入類


	@Autowired 
	private CookieSecureUtils cookieSecureUtils;

2送出cookie前,設置安全


	response.addCookie(cookieSecureUtils.setSecure(new Cookie("domain", resultObj.getString("domain"))));
	response.addCookie(cookieSecureUtils.setSecure(new Cookie("account", account)));
	response.addCookie(cookieSecureUtils.setSecure(new Cookie("lang", request.getParameter("lang"))));
成果如下圖



2021年12月8日 星期三

ICUmodle 專案 dropdown出來較其他功能緩慢問題

整串的邏輯大約是這樣

1開始載入時 撈取下拉選單,回給前端排列畫面,並產生全域List供篩選使用

2按鈕功能觸發查詢該病患資料

3依照病患id查詢病患該表項資料

4顯示該病患該表項資料


問題

當4顯示資料的時候, 有去使用到1的下拉選單的List

但是發現, 我操作查詢該病患的表項資料速度快的時候,

會發生顯示問題, 開f12發現, 顯示需要的list並沒有撈回來, 他就已經執行篩選顯示


找尋根源問題

一開始推測, getDropdown需要更多的時間, 所以讓他在網頁中載入順序提前,

但是發現, 這還是沒有解決問題

因此, 我做了一個操作




我嘗試在下拉選單組完選單內容的最後印出"finished"字樣

好讓我知道, 什麼時候才把下拉選單組完

此時我發現, 這個finished差不多讓我等了6秒鐘

很明顯, 如果說我整套操作動作在6秒鐘內完成

很有可能會組資料的時候List還沒有準備好


因此

到底為什麼撈取dropdown會需要撈取這麼久呢?

就是這裡的核心問題






我注意到在等待dropdown回傳的過程中,

他在等待account做了很多查詢

我原本以為是權限相關

回去看code發現

原來為了取得帳號名稱

程式查找出整個account內容

因為其權限是eager撈出來的不是lazyload(當初設計上問題)

因此這邊撈出account會再重新把所有權限功能子功能run一遍

整體就是慢在這邊


解法

我使用另一個只撈id和name的Repo function, 準確的撈取我要的資料->帳號名稱

代替原本把account下面所有關聯查出來

大約看到finished在console中, 不到1秒就跑出來了

而證實拖累效能問題的確在這裡


finished!



2021年12月2日 星期四

模糊查詢遇到null情況的小坑


String medicalId = pbSearch.getMedicalId()==""?null:pbSearch.getMedicalId(); 		
String identityId = pbSearch.getIdentityId()==""?null:pbSearch.getIdentityId();
String fullname = pbSearch.getFullname()==""?"":pbSearch.getFullname();
String mode = pbSearch.getMode();
PageRequest pageRequest =  size == 0 ? null:  PageRequest.of(--page, size, Sort.by("sysTime").descending());	
PagepatientBasicList = patientBasicRepo.findVoByDomainAndMedicalIdOrIdentityIdOrFullnameOrMode(domain
				,medicalId, identityId, fullname,mode,pageRequest);
                
@Query("select new com.ICU.ICUmodule.vo.caseManage.PatientBasicVo(pb.id as id, pb.medicalId as medicalId, pb.identityId as identityId, pb.fullname as fullname, "
+ "pb.telephone as telephone, pb.localDate as localDate, pb.treatment as treatment, pr.mode as mode) "
+ "from patient_basic pb left join patient_returned pr on pb.id=pr.patientBasic.id "
+ "where pb.domain=:doamin and (:medicalId is null or pb.medicalId =:medicalId) and (:identityId is null or pb.identityId =:identityId) and (:fullname is '' or pb.fullname LIKE '%' || :fullname||'%') and (:mode is null or pr.mode =:mode)")
Page findVoByDomainAndMedicalIdOrIdentityIdOrFullnameOrMode(String doamin, String medicalId,
			String identityId, String fullname, String mode, Pageable pageRequest);

在一般情況下, 若是要判斷是否為 null 再拉進JPQL裡面做is null or object.xxx = :xxx

然而在模糊查詢的時候, 這裡若為 null 帶入到JPQL LIKE語法, 會產生判斷JPQL語法錯誤問題

因此在這裡LIKE語法是不能帶null, 而要給""字串來做判斷, 這樣JPQL就不會run出問題, 也可以進行模糊查詢及一般查詢

2021年12月1日 星期三

日期組成流水號

String no = repo.getLastIoNo();
String prefix = new SimpleDateFormat("yyyyMMdd").format(new Date());
prefix = prefix.substring(prefix.length() - 6);

        if(no != null && no.matches("^" + prefix + "\\d{3}$")) {
            no = String.valueOf(Integer.valueOf(no) + 1);
        } else if(no == null || !no.matches("^" + prefix + "\\d{3}$")) {
            no = String.format("%s%s", prefix, "001");
        } else {
            throw new Exception("Create StockIoMaster io_no fail");
        }

簡單的說, 這個組成的流水碼最後會變成yyMMdd001, 後面會隨數字變多yyMMdd002, 接續增加

而這組流水碼, 會從001開始, 如果資料庫有取出東西, 且同一prefix的情況, 會自動將序號轉成數字

加一, 這樣就產生一組新的序號在資料庫中

取資料的寫法如下


@Query("select max(m.ioNo) from stock_io_master as m")
String getLastIoNo();

2021年11月13日 星期六

英文閱讀的技巧

 utube筆記


增進英文閱讀方式全解析 

https://www.youtube.com/watch?v=DMQNNq3R3Hs

閱讀有兩種 

1 Intensive reading 密集閱讀 

少而精 ex 課文, 英文雜誌, 新聞 會提取出最多語文知識, 通常有老師幫助, 會較有挑戰

學習模式專注度高>突破舒適圈

2 Extensive reading 廣泛閱讀

多而廣的涉略 ex 小說 培養閱讀習慣語感, 可以提升閱讀文章速度, 大約自己的程度

放鬆休閒 舒適即可>有興趣最重要

補充: https://elt.oup.com/student/readersleveltest/?cc=tw&selLanguage=zh


英文速讀法 語言學觀點教你提升閱讀速度

https://www.youtube.com/watch?v=vSpUGt0CeNU


找topic sentence & 找關鍵字>skim and scan跳著看抓到重點

從語言學去看

1 閱讀速度快的能力

a 具有word recognition辨識字能力, 不需要有意識的去思考意思(下意思), 看到一個字不需要多花0.5秒去想他的意思, 不是認字能力, 而是對這個字的熟悉度, 看到一個字不會在上面停留太久

b 具有prosody拆句子能力, 一段一段拆開, 在轉折時拆開, 對句子結構拆開, 把一整團的字組合成一個小的意義單位, 從word轉成multi-word unit多字意義組合, 再轉成meaing意義

2 加速閱讀的練習方法

a extensive reading 大量閱讀, 閱讀會持續不間斷

b repeated reading 目的為了, 看到同一個字能很快反應, 那就是重複的看到同一個字, 每一次看都會越看越快, 可以提升自信心跟閱讀速度

3 保持閱讀速度的小技巧

她本身會打拍子


如何訓練閱讀能力 https://www.youtube.com/watch?v=eT962X3R2Mk

看不太懂的地方, 要重覆看, 不要直接下去


英文自學 https://www.youtube.com/watch?v=abqAHF7KSr8

長句中, 要看懂, 看主詞 主要動詞, 其他的是用來形容, 有形容主詞, 有形容動作, 有形容受詞

基本上這些形容刪去, 並不會影響意思, 所以抓住主軸反而簡化, 好理解



2021年10月20日 星期三

js 使用class時 引入, 以及全域變數問題 (Uncaught SyntaxError: Cannot use import statement outside a module)


當我們使用class的時候

可能如下









這種時候引入有兩方面

一面

引入的js地方要加上

import CheckFormatForDate from '../common/CheckFormatForDate.js';

另外該js在被引入html的時候, 要宣告type = "module"

如下



如果沒有宣告type="module"則會報 

Uncaught SyntaxError: Cannot use import statement outside a module


另外~當宣告成module, 該檔案中的變數就會和其他檔案的變數隔離

如果要做全域變數, 讓所有引入的module都能夠使用

可以用

window.aaa="aaa"; //宣告

var aaa = window.aaa; //取值

也可以用session

我自己想到另外的方式, 就是把它塞在畫面上隱藏的input中

也是可以解決變數被隔離但是你想做到全域變數的效果


回歸原本 如果不用class 直接引入js也是有全域變數的效果

2021年10月19日 星期二

save Vo 時 關聯被刪掉

簡單的說

因為雙向關聯的關係, 我們有可能會另外造一個Vo去接畫面上修改的內容

比方說, 角色, 更改名字的時候

但是這時候因為Vo中並沒有把關連帶進去

以至於我們直接拿Vo去new一個物件塞起來存, 他原本的關聯就會被儲存為沒關聯


因此, 若是在修改的動作下, 前面Vo丟進來的時候, 應該要先Get到該物件的entity

然後再把其中的屬性值Copy過去才對

原本有問題的寫法





改成



2021年9月28日 星期二

[ postgresql ] 手動修改資料表資料 及 序號歸零計算

 

手動修改資料如下

先在Data Output上點到想修改的data, 直接修改











然後改完之後在上方有一個資料表箭頭向下的button

點選這個它就會把你修改的部分儲存下來






序號歸零可以使用以下語法

TRUNCATE TABLE doctor_detail RESTART IDENTITY;


參考

https://stackoverflow.com/questions/5342440/reset-auto-increment-counter-in-postgres

2021年9月8日 星期三

JPQL中日期相減及跳脫字元(postgresql)

目前需求是, 某欄位的日期要等於今天

簡單的說, 就是今天應到的人數統計

這部分我是用

extract(day from 'DATE_A'::timestamp - 'DATE_B'::timestamp) = 1

select count(sb.id) from schedule_bed sb where sb.code_id=244 and sb.book_period=238 and (select extract(day from sb.book_date::timestamp - current_date::timestamp)=0)

然而還有第二個問題

就是在JPQL中, 及使用native SQL 它的冒號也是有意義的, 表示參數

後來查了資料得知, 原來要用雙斜線解決\\, 每個冒號前面都要來兩個, 如下



灑花~~~

//另外補充 撈取是否為本月資料如下

SELECT count(pr.id) FROM patient_returned pr WHERE DATE_PART('MONTH', pr.dead_date) = DATE_PART('MONTH', NOW()) 

參考:

https://stackoverflow.com/questions/24929735/how-to-calculate-date-difference-in-postgresql

https://stackoverflow.com/questions/4169582/escaping-the-colon-character-in-jpa-queries

2021年5月18日 星期二

[Spring Boot]Thymleaf使用fragment及遇到中文亂碼問題

 如果每個畫面都有固定片段的話

可以將該片段提取出來當作一個fragment

fragment設以下標籤

 <head th:fragment="head(title)">

...裡面為重複的程式片段

 </head>

然後在引用的部分, 用replace來帶掉

  <meta charset="UTF-8"/>

  <head th:replace="_fragments :: head(~{::title})">

    <title>AIoT智能腎臟病照護加值服務平台login</title>

  </head>

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

如果引用之後, 本來的頁面的中文變成亂碼呈現

可以在內容加入<meta charset="UTF-8"/>

如下圖



[Spring Boot] Thymeleaf 引用靜態資源

 拿到新的前端包,

要進到spring boot使用Thymleaf引入, 遇到了問題

還有爆

Refused to apply style from 'OOXXX' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.

查了一下得知, 原來是資源沒有引入的問題,

後來查了一下才知道

放在static下面的靜態資源要這樣引入





引入程式碼如下



<script th:src="@{plugin/jquery.i18n-1.0.7/jquery.i18n.emitter.min.js}"></script>

 <script th:src="@{plugin/jquery.i18n-1.0.7/jquery.i18n.language.min.js}"></script>

<link rel="stylesheet" th:href="@{plugin/flatpickr-4.6.6/flatpickr.min.css}">

<link rel="stylesheet" th:href="@{plugin/flatpickr-4.6.6/themes/wistron.css}">


另外解決一直跳轉到login問題

import org.springframework.context.annotation.Configuration;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

 

@Configuration

@EnableWebSecurity

public class SecurityConfigSEVEN extends WebSecurityConfigurerAdapter {

 

    @Override

    protected void configure(HttpSecurity http) throws Exception {

        //super.configure(http);

        //配置不需要登陆验证

        http.authorizeRequests().anyRequest().permitAll().and().logout().permitAll();

    }

 

}

參考網址:

https://blog.csdn.net/SEVENY_/article/details/104660418


參考網址: 

https://blog.csdn.net/chenbetter1996/article/details/84994801

https://stackoverflow.com/questions/48248832/stylesheet-not-loaded-because-of-mime-type

2021年5月6日 星期四

工程師三年, 年薪百萬-2 轉折

有的人在很年輕的時候, 就找到自己的志向

也有的人找了一輩子, 也沒有找到, 最後只好嘆一口氣說"這就是人生!"

XXXXX

那年我29歲,  領的薪水32000, 處理著各家外商公司的薪資報表

我內心無法平靜

憑什麼聰明如我, 在各方面的項目都能取得傑出成就的我

要在薪水上輸給別人?

我相信, 會有其他行業, 能夠讓我發揮得更出色, 我絕對不只如此


其後, 在104, 1111人力銀行的分析之下, 我發現我的確適合動腦相關的工作

研究員, 工程師, 設計師

因為學歷上和資歷上, 我並沒法成為財金研究員

而工程師? 還記得研究所的時候班上一半的人都放棄程式

我是那沒放棄的另外一半, 研究論文也是寫了一部份程式才做出來

我覺得, 自己比一般人聰明, 認真, 英文也不錯, 做這行業比不上高手至少也可以持平

顯然持平的話, 薪資水準已經很不一樣

剩下錢的部份我也可以靠理財


因此當年7月就辭職離開工作, 9月就進 北科大"Java&Android程式設計人才班"

這年我將股票玉山金賺的錢和之前的存款拿來, 還記得是20多萬

預計能繳學費, 並維持6+3個月生活花費


2021年4月28日 星期三

The server time zone value '¥x¥_¼Ð·Ç®É¶¡' is unrecognized

這是使用Spring boot 建立資料庫連線遇到的問題

解決方法

在後方加入?serverTimezone=UTC

如果要解決中文亂碼問題

也可以加入?useUnicode=true&characterEncoding=UTF-8

合起來可以 ?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC



2021年4月9日 星期五

工程師三年, 年薪百萬-1 緣起

曾經, 我以為這個目標可能還要兩三年

沒想到有公司如此欣賞我

願意給我這樣的價格

雖然, 我達成了目標, 讓我將這段時間的努力, 寫成文章記錄

一個商學院的學生, 轉職成為工程師的故事

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

背景, 我大學念元智國企, 研究所念銘傳財金

其實並沒有說多強悍

只是研究所期間寫點交易&統計程式

多益拿過890金色證照, 業餘圍棋5段, 當過合唱教學, 小提琴家教

也是在有興趣的領域, 認真專注, 也小有收穫

當初出社會跌跌撞撞並沒有銀行收我, 直到一家會計師事務所

我看這地方有點品質, 是做外商的, 想說, 喜歡投資的這部分

有機會進來加強一下公司會計帳務的知識,沒想到~~

會計太弱了, 只好去做薪資組, 當時我一連管了10多家薪資報表

自以為很厲害, 但是看著各家員工的薪資與自己薪資的差距, 我急了

於是我問當初的部門經理:"如果我在這家公司工作8年10年, 有沒有機會領到8-10萬?"

經理斬釘截鐵地說: "絕對沒有可能"

於是我當下就決定要離職

"這個行業沒機會, 但是其他的有"

看著這些外商給的薪資, 我認為自己並沒有比較差,

只是還找不到哪一種類工作能讓我好好發揮

我到底還能做什麼?


2021年4月8日 星期四

[SpringBoot] properties檔案出現亂碼, Unicode的解決方式

我自己遇到問題的情況

是在接手的專案中

多國語言中有message.properties檔案的問題

內容的字發生問題如下

\u4e2d\u6587\u5b57

解決方式如下

必須去idea中的 file>Settings







記得要將Default encoding改為UTF-8
後面的Transparent native-to-ascii conversion也要勾選起來就能解決

另外idea使用上, ctrl + shift + F




2021年3月17日 星期三

[面試考題] 檢查變位字

考題: 給定兩個字串, 寫一個方法判斷一個是否是另一個的變位字


思維:造一個方法

1 先比較其長度是否相等

2 比較兩個值做順序排序, 內容是否會相等

順序排序:

a 產生一個字元char陣列

b使用Arrays.sort

c產生新的字串


public static void main(String[]args){
System.out.println(permutation("see","ese"));
}
static String sort(String s){
char[]content = s.toCharArray();
java.util.Arrays.sort(content);
return new String(content);
}

static boolean permutation(String s, String t){
if(s.length()!=t.length()){
return false;
}
return sort(s).equals(sort(t));
}


[面試考題] 不重複

考題: 實作一個演算法來判斷一個字串中的字元是否不重複. 如果不能使用其他資料結構怎麼辦?


思維:假設字元有128個, 每一個都有特殊的數字, 因此~

1 造一個布林陣列

2 跑迴圈, 當遇到新的字元數字, 布林陣列[數字] 改為true

3 下一次再來, 如果發現其布林陣列[數字]為true , 代表該字元已經出現過了,

然後整個function就要回傳false



public static void main(String[] args) {
System.out.println(isUniqueChars("abbs"));
}

static boolean isUniqueChars(String str) {
if(str.length() > 128) return false;

boolean[]char_set = new boolean[128];

for(int i =0 ; i < str.length() ; i++) {
int val =str.charAt(i);
System.out.println(val);
if(char_set[val]) {
return false;
}
char_set[val]=true;
}
return true;

} 

2021年2月11日 星期四

[SpringBoot 1.5] 引入靜態頁面相關問題及使用fragment

引入靜態頁面的時候

我們原本可能會使用

<link rel="stylesheet" href="../../static/css/me.css">

這個語法在模板templates(有引入thymeleaf)下, 並不能讀取到









因此~需要改為

<link rel="stylesheet" href="../../static/css/me.css" th:href="@{/css/me.css}">

用th:href="@{}"來引入資源

然後重新build再刷新頁面就沒問題了






---------------------------------------------------------------------------------------

使用fragment

1 首先產生一個html檔案命名為_fragments

2 然後我們將大部分頁面上會重複的程式碼複製到這頁面

ex

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客詳情</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<link rel="stylesheet" href="../static/css/typo.css">
<link rel="stylesheet" href="../static/css/animate.css">
<link rel="stylesheet" href="../static/lib/prism/prism.css">
<link rel="stylesheet" href="../static/lib/tocbot/tocbot.css">
<link rel="stylesheet" href="../static/css/me.css">
</head>


3 將引入方式修改為th:href="@{}"

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<link rel="stylesheet" href="../static/css/typo.css" th:href="@{css/typo.css}">
<link rel="stylesheet" href="../static/css/animate.css" th:href="@{css/animate.css}">
<link rel="stylesheet" href="../static/lib/prism/prism.css" th:href="@{/lib/prism/prism.css}">
<link rel="stylesheet" href="../static/lib/tocbot/tocbot.css" th:href="@{/lib/tocbot/tocbot.css}">
<link rel="stylesheet" href="../static/css/me.css" th:href="@{/css/me.css}">


4 將重複的片段掛上fragment

<head th:fragment="head(title)">


5 如果遇到每個頁面有共通元素, 但是內容不一樣,

例如 <title>博客詳情</title>

這時候我們就要使用傳參數的方式(注意取參數要用$)

<head th:fragment="head(title)">
<title th:replace="${title}">博客詳情</title>
</head>

這樣來做搭配


6 而在要引入的頁面(比方說index.html)

我們可以這樣做, 這樣就是將title裡面的參數傳遞過去

<head th:replace="_fragments :: head(~{::title})">


完整的程式碼:

_fragments.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head th:fragment="head(title)">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:replace="${title}">博客詳情</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<link rel="stylesheet" href="../static/css/typo.css" th:href="@{css/typo.css}">
<link rel="stylesheet" href="../static/css/animate.css" th:href="@{css/animate.css}">
<link rel="stylesheet" href="../static/lib/prism/prism.css" th:href="@{/lib/prism/prism.css}">
<link rel="stylesheet" href="../static/lib/tocbot/tocbot.css" th:href="@{/lib/tocbot/tocbot.css}">
<link rel="stylesheet" href="../static/css/me.css" th:href="@{/css/me.css}">
</head>
<body>

</body>
</html>


index.html 的header

<head th:replace="_fragments :: head(~{::title})">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<link rel="stylesheet" href="../static/css/me.css" >
</head>


2021年2月2日 星期二

[SpringBoot 1.5] AOP概念及日誌範例

關於橫切面AOP的思維

個人的領會是, 在程式進行的流程為一個直向的流層, 但是, 如果要在其中幾個物件被使用的時候,都插入一段程式碼, 這種的思想就是橫切面設計, 

具體的範例, 是一種鑲嵌, 縫合程式碼的思維, 比方說在每一次從某些controller的時候都警示該功能已經被呼叫, 就是都額外run一段程式碼, 如果說要在每個controller上面加上這段, 就是一個麻煩的事, 如果我們在後面才設計出要被加入的這段程式碼, 那還不如能用縫合的, 將其呼叫的時機設定好, 然後不用每個controller都重新鑲入該程式碼, 那這下面會有些關鍵字的思維可以釐清


1 切面Aspect

切面是一個模塊關注點的模塊化, 他由切入點PointCut和通知Advice組成

@Aspect
@Component
public class LogAspect {
}


2 目標Target

將要被縫合的類別, 比方說, 我們就設計了一個controller, 等下測試縫合程式碼的執行順序

@Controller
public class IndexController {

@GetMapping("/{id}/{name}")
public String index(@PathVariable Integer id, @PathVariable String name){
System.out.println("~~~~~~~~~~~~index~~~~~~~~~~");
return "index";
}
}


3 連接點JoinPoint

被視為切入連接點, 是下列範例中的log()方法


@Before("log()")
public void doBefore(JoinPoint joinPoint){
 logger.info("~~~~~~~Before~~~~~~~");
}


4 切入點PointCut

而切入點則是下面括號中指定execution中的東西,* 是指說返回任何東西

 com.lrm.web.*.*(..)是AOP要切入的對象, web後面*表示任何類,第二個*表示任何方法

而(..)表示任何參數, 當然也可以使用web.IndexController 這樣就只有這個Controller會被

橫切插入程式

@Pointcut("execution(* com.lrm.web.*.*(..))")
public void log(){
}


5 通知Advice

有不同被通知的種類, 比方說Before, After, 

@Before("log()")
public void doBefore(JoinPoint joinPoint){
 logger.info("~~~~~~~Before~~~~~~~");
}

@After("log()")
public void doAfter(){
logger.info("~~~~~~~After~~~~~~~");

}


6 織入Weaving

將切面Aspect與對象Target連接起來, 織入可以在類別加載的的時候完成, 也可以在編譯, 或是運行的時候完成...如果是編譯時完成就是靜態代理, 如果在運行時完成就是動態代理

--------------------------------------------------------------------------------------

以下是一個完整的程式碼

會設計一個內部類別, 然後將其Url, ip, 目標的物件名稱及參數都印出來

package com.lrm.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Aspect
@Component
public class LogAspect {

private final Logger logger= LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* com.lrm.web.*.*(..))")
public void log(){

}

@Before("log()")
public void doBefore(JoinPoint joinPoint){
logger.info("~~~~~~~before~~~~~~~");
ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request=attributes.getRequest();
String url =request.getRequestURL().toString();
String ip=request.getRemoteAddr();
String classMethod=joinPoint.getSignature().getDeclaringTypeName() + "." +joinPoint.getSignature().getName();
Object[] args= joinPoint.getArgs();
RequestLog requestLog= new RequestLog(url,ip,classMethod,args);
logger.info("Request : {}",requestLog);
}

@After("log()")
public void doAfter(){
logger.info("~~~~~~~After~~~~~~~");

}

@AfterReturning(returning = "result" , pointcut = "log()")
public void doAfterReturn(Object result){
logger.info("Result: {}",result);
}

private class RequestLog{
private String url;
private String ip;
private String classMethod;
private Object[] args;

public RequestLog(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}

@Override
public String toString() {
return "RequestLog{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}
}
}

再搭配上前面的IndexController, 開啟然後輸入 http://localhost:8081/1/hat

後面兩個參數是先隨意給的, 以下是結果的截圖




參考網址:

https://zhuanlan.zhihu.com/p/60842627

https://matthung0807.blogspot.com/2018/02/spring-aop.html

2021年1月31日 星期日

[SpringBoot 1.5] 錯誤處理 @ControllerAdvice, @ExceptionHandler, @ReponseStatus

 SpringBoot有預設的錯誤頁面處理

要實現這個我們首先可以在resource>templates>error的資料夾下

加入兩個頁面500.html , 404.html

並在resource>templates下面放index.html

然後造一個controller

@Controller
public class IndexController {

@GetMapping("/")
public String index(){
int i = 9/0;
return "index";
}
}

由於9/0會報錯

畫面就會自動導到命名為500的頁面

將報錯的那一行註解掉

我們在Build中點Recompile

然後刷新頁面

畫面就會來到首頁了

而當我們的URL打一個沒有資源的

ex http://127.0.0.1:8081/123456

他就會導到404的頁面

------------------------------------------------

而~如果我們要使用錯誤頁面, 並用其來顯示錯誤訊息, 可以以下搭配

1 先在error資料夾下產生一個error.xml檔案如下

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>錯誤</title>
</head>
<body>
<h1>錯誤</h1>

<div>
<div th:utext="'&lt;!--'" th:remove="tag"></div>
<div th:utext="'Failed Request URL : ' + ${url}" th:remove="tag"></div>
<div th:utext="'Exception message : ' + ${exception.message}" th:remove="tag"></div>
<ul th:remove="tag">
<li th:each="st : ${exception.stackTrace}" th:remove="tag"><span th:utext="${st}" th:remove="tag"></span></li>
</ul>
<div th:utext="'--&gt;'" th:remove="tag"></div>
</div>

</body>
</html>

2產生一個掛@ControllerAdvice的類別

他主要用法是一個搭配ExceptionHandler攔截Exception的一個功能

下列程式碼, 基本上攔截後, 如果他有掛ResponseStatus類別的話, 他會繼續throw e

而如果沒有掛ResponseStatus就會回傳一個ModelAndView物件並導到error頁面

@ControllerAdvice
public class ControllerExceptionHandler {

private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) throws Exception {
logger.error("Request URL : {}, Exception : {}",request.getRequestURL(),e);

if(AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class)!=null){
throw e;
}

ModelAndView mv= new ModelAndView();
mv.addObject("url",request.getRequestURL());
mv.addObject("exception",e);
mv.setViewName("error/error");
return mv;
}
}


而, 如果我們想要做出對應404的Exception

1 產生一個個性化的Exception類別並掛上ResponseStatus

@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException{
public NotFoundException() {
}

public NotFoundException(String message) {
super(message);
}

public NotFoundException(String message, Throwable cause) {
super(message, cause);
}
}


2在indexController中加入以下這段

String blog=null;
if(blog==null){
throw new NotFoundException("博客不存在");
}

因為剛剛已經在ControllerExceptionHandler中設置要繼續拋出e了

他就會跳轉到404的頁面



2021年1月30日 星期六

MySQL安裝問題

 一開始在安裝的時候, 如果一直點下一步的話

可能會遇到沒有安裝server的問題

事實上比較主要的部份包括介面, 還有server







所以, 如果裝完之後, 發現少了什麼, 要回來installer檢查一下

2021年1月26日 星期二

20210126月會 工作啟發

因為我們每兩周都有(同仁)分享報告

而主管也有要求我們要回饋分享的報告內容

主管也會統計這些回饋分配

由這樣的回饋我觀察有以下幫助

1 訓練對於簡報的報告品質(因為在回饋的同時也會注意到自己對報告的要求)

2 因為有一次一次的回饋, 同仁可以透過別人的回饋而改善下一次的報告品質

3 幫助同仁有互相幫助和欣賞的機會

4 由於差不多一兩個月就需要分享一次, 需要平時就累積工作上的職能(個人職能提升)

5 對於在場的聽者, 可以聽到不同與職能相關項目, 有助於開拓視野


2021年1月22日 星期五

寫完20篇心情和前輩交流紀錄

從12月至今, 已經完成了20篇文章,
這段時間, 考了兩次多益, 結果同分, 閱讀還略退步了點,
托福講師說, 還是從閱讀中來學單字, 會比單獨背單字會有效一點
這些也是題外話, 這是寫到一半, 大約斷了兩周的原因, 
然後~之後因為會準備考托福的緣故, 幾乎整周的時間表都確定
4天補英文, 3天+1天(不固定)會舉行英文讀書會, 在民權的路易莎

這段期間小有收穫, 幾次下來讀書會總成員已經有5位, 1女4男
有特別挑過是真心想讀書(把愛拉賽的都挑掉), 都可以坐上兩個小時,
能一起讀書, 的確比之前自己拚的感覺好, 一個人的進修是真的很煎熬,
特別取了名字Elite Programmer Taipei, 這是我對於自己的期許, 
我希望自己能成為Programmer中的菁英

來記錄一下想寫網誌的心情,
原本一開始, 是因為在工作上覺得有些東西, 受了前輩很大的幫助,
如果紀錄下來, 我覺得這些對於我在工作上會很好,
後來有一天, 我看到保哥的網誌, 我注意到了他年月出的文章,
剛開始的兩年, 幾乎每個月能出32篇文章,
他是.net領域的神級人物, 如果說, 這是他的變強途徑,
那麼我能不能複製呢?
想到這裡, 我就開始複製同樣的想法, 我也要每個月寫個30篇

關於內容上, 目前是以看Utube教學, 邊做, 邊寫筆記,
這一部分還是比較屬於開專案所要使用到的基本技能,
重點來了~~~~
---------------------------------------------------------------------------
今天讀書會的前輩跟我講說
其實這些spring的各項目IOC,AOP, JPA, SpringCloud
都是一個一個主題, 
一開始可以粗略地去懂怎麼使用就好,
但是更進階的, 可以去了解它的原碼,
懂得其運作的緣由, 進一步就可以對其設定進行操作,
有些JVM的調教, 提升效率的方式, 還是要對其原碼懂得操作,
才能夠設計得到位,
除此之外, 可以在每個新版本推出來的項目上,
比較其和舊版本中改善的項目, 或是新出現的項目,
那可能解決的問題, 就是目前該領域中最Hot最前衛的技術
研究其技術, 就自然會在該領域中作為技術的領導角色

聽完這些話真是價值連城阿,
一直在想, 為什麼公司裡屌不拉基的前輩為什麼可以這麼厲害,
在讀書會中, 聽到讀書會的前輩分享這些他關注的事情,
讓我體會出, 到底要如何才能更往高的階段去
---------------------------------------------------------------------------
目前一切都覺得進行不錯,
有穩定的英文和程式時間, 
擁有英文讀書會和程式讀書會各一, 都為主招集人,
幾乎每天寫技術文章, 有時一天還出兩篇
運動時間兩天, 相親時間一晚 也慢慢排入,
而最後大概就一天晚上玩遊戲,
我想, 成為大神的路慢慢的, 不會是夢想
現在比較主要的, 累積一些後, 跳到更厲害的地方去,
這樣就能跟更厲害的人切磋學習,

透過觀察大神所走過的路徑, 我相信我能更快的完成夢想


[SpringBoot 1.5] SpringBoot 新增範例

我們說到新增功能

他應該要包含兩個步驟

1 點進去進入到一個可以填寫空白資訊的頁面(employee>Get)

2 輸入完新增的資訊然後按下儲存鍵, 完成新增項目的儲存(employee>Post)

從畫面過來, 一開始是點擊這個標籤

<a href="emp" th:href="@{/emp}" class="btn btn-sm btn-success">員工添加</a>

然後會進到controller, 因為裡面會要將所有可以選取的Department顯示出來,

所以要先getDepartments再放到model中

@GetMapping("/emp")
public String toAddPage(Model model){
// 來到添加頁面, 查出所有的部門, 在頁面顯示
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts",departments);
return "emp/add";
}

這裡的Department類別

public class Department {

private Integer id;
private String departmentName;
}

這是Dao

@Repository
public class DepartmentDao {

private static Map<Integer, Department> departments = null;

static{
departments = new HashMap<Integer, Department>();

departments.put(101, new Department(101, "D-AA"));
departments.put(102, new Department(102, "D-BB"));
departments.put(103, new Department(103, "D-CC"));
departments.put(104, new Department(104, "D-DD"));
departments.put(105, new Department(105, "D-EE"));
}

public Collection<Department> getDepartments(){
return departments.values();
}

public Department getDepartment(Integer id){
return departments.get(id);
}

}

然後畫面上的form是這樣

<form th:action="@{/emp}" method="post">

<input type="hidden" name="_method" value="put" th:if="${emp != null}">
<
input type="hidden" name="id" th:value="${emp != null} ? ${emp.id}">
<
div class="form-group">
<
label>LastName</label>
<
input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp != null} ? ${emp.lastName}">
</
div>
<
div class="form-group">
<
label>Email</label>
<
input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp != null} ? ${emp.email}">
</
div>
<
div class="form-group">
<
label>Gender</label><br/>
<
div class="form-check form-check-inline">
<
input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp != null} ? ${emp.gender == 1}">
<
label class="form-check-label"></label>
</
div>
<
div class="form-check form-check-inline">
<
input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp != null} ? ${emp.gender == 0}">
<
label class="form-check-label"></label>
</
div>
</
div>
<
div class="form-group">
<
label>department</label>
<
select class="form-control" name="department.id">
<option th:value="${dept.id}" th:each="dept : ${depts}" th:text="${dept.departmentName}" th:selected="${emp != null} ? ${dept.id == emp.department.id}"></option>
</
select>
</
div>
<
div class="form-group">
<
label>Birth</label>
<
input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp != null} ? ${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm:ss')}">
</
div>
<
button type="submit" class="btn btn-primary" th:text="${emp != null} ? '修改' : '添加'"></button>
</
form>

其中我們可以看到拿出depts塞到dept中, 他會需要將id塞到value中, 要特別注意一下













那麼他送出, 就是一個Employee的model

這樣畫面上是使用name來做對應屬性

而department則要用department.id來對應

這個form發出的是Post請求

@PostMapping("/emp")
public String addEmp(Employee employee){
System.out.println("保存的員工信息: "+employee);
employeeDao.save(employee);
return "redirect:/emps";
}

但是記得進入的時候要重新導向redirect

而以下是dao的 save功能

private static Integer initId = 1006;
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}

employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}

還有一個日期要做補充

通常預設日期會為20xx/xx/xx 

如果要格式化日期, 是可以在application.properties中做控制的, 如以下

spring.mvc.date-format=yyyy-mm-dd


[leetcode] [KMP] KMP

ABCDABD... ABCDABF... 簡單的說, 傳統解兩字串匹配部分 可能會來個雙迴圈, 哀個比對, 當不匹配的時候, 會將下方列再後移1位 然後不匹配再後移 然而 如果像上放已經有4個屬於匹配的字串, 她就應該直接往後移四位來匹配, 而不是只移動1位 隱藏的思維是, 當...