2021年1月22日 星期五

[SpringBoot 1.5] SpringBoot 查詢範例

當我們要將員工資訊顯示出來

這就是一個查詢功能







我們的model為一個Employee類別 屬性如下

public class Employee {

private Integer id;
private String lastName;

private String email;
//1 male, 0 female
private Integer gender;
private Department department;
private Date birth;

然後我們設置一個controller中的方法

@Repository
public class EmployeeDao {
@GetMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();

model.addAttribute("emps",employees);
return "emp/list";
}

我們使用model.addAttribute來設置model中內容

當他點選到該連結, 就可以導向過來controller再return給list頁面

<a class="nav-link active"
th:class="${activeUri=='emps.html'?'nav-link active':'nav-link'}"
href="#" th:href="@{/emps}">

其中搭配著employeeDao為

@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;

@Autowired
private DepartmentDao departmentDao;

static{
employees = new HashMap<Integer, Employee>();

employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1, new Department(101, "D-AA")));
employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1, new Department(102, "D-BB")));
employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0, new Department(103, "D-CC")));
employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0, new Department(104, "D-DD")));
employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1, new Department(105, "D-EE")));
}

private static Integer initId = 1006;


public Collection<Employee> getAll(){
return employees.values();
}
}

而前端的頁面為

<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<td>[[${emp.lastName}]]</td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender} == 0 ? '' : ''"></td>
<td th:text="${emp.department.departmentName}"></td>
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm:ss')}"></td>
<td>
<!--<a th:href="@{/emp/} + ${emp.id}" class="btn btn-sm btn-primary">编辑</a>-->
<a th:href="@{/emp/} + ${emp.id}" class="btn btn-sm btn-primary">编辑</a>
<button th:attr="del_uri = @{/emp/} + ${emp.id}" type="submit" class="btn btn-sm btn-danger deleteBtn">删除</button>
</td>
</tr>

為一個使用th:each來遍歷model中emps的每一筆資料, 時間格是可以用dates.format

查出每筆資料有兩種寫法, 一種為th:text="${model.attribute}"

另外一種為[[${model.attribute}]]


[SpringBoot 1.5] Thymeleaf 判斷顯示根據參數


 











我們在左邊的目錄, 在情境下會想要當點選的時候, 要改變不同的顏色

可以用以下的判斷來顯示

在list.html中使用以下

<div th:replace="commons/bar::#sidebar(activeUri='emps.html')"></div>

用以告訴對方目前頁面是emps.html

而dashboard.html中使用以下

<div th:replace="commons/bar::#sidebar(activeUri='main.html')"></div>

用以告訴對方目前頁面是main.html

而在被鑲嵌的目錄就可以設置以下

<a class="nav-link active"
th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}"
href="#" th:href="@{main.html}">

這個就是判斷如果現在點選該頁面的話, 目前的activeUri被設置為main.html就會判斷出來

然後因此nav-link的css值就會有active

同理, 如果是Customers被選到的時候要讓他有顯示

就用以下這段就行了

<a class="nav-link active"
th:class="${activeUri=='emps.html'?'nav-link active':'nav-link'}"
href="#" th:href="@{/emps}">


2021年1月20日 星期三

[SpringBoot 1.5] thymeleaf template layout介紹

1 抽取公共片段

在會重複使用的code中加入th:fragment="copy"

如以下

<div th:fragment="copy">

OOXXX

</div>


2引入公共片段

然後在要重複的code中插入

<div th:insert="~{footer :: copy}"></div>

這邊也可以寫作

<div th:insert="footer :: copy"></div>

這樣就可以共用程式碼簡化重複的code了

而 ~{footer :: copy}為

 ~{templatename:: selector} 模板名::選擇器

 ~{templatename:: fragmentname} 模板名::片段名

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

三種引用方式

th:insert >將公共片段整段插入到聲明引用的元素中

th:replace >將聲明引入的元素替換為公共片段

th:include >將引入的片段的內容包含進這個標籤中


範例

<footer th:fragment="copy">

    &copy; 2021 The Good Thymes Vitrual Grocery

</footer>


引入方式

<body>

    <div th:insert="footer :: copy"></div>

    <div th:replace="footer :: copy"></div>

    <div th:include="footer :: copy"></div>

</body>


效果

<body>

    <div>

           <footer>

                       &copy; 2021 The Good Thymes Vitrual Grocery

           </footer>

    </div>

            <footer>

                       &copy; 2021 The Good Thymes Vitrual Grocery

           </footer>

            <div>

                       &copy; 2021 The Good Thymes Vitrual Grocery

           </div>

</body>


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

另外一種設定方式, 可以依照id( ~{templatename:: selector} )

<div id="copy-section">

        &copy; 2021 The Good Thymes Vitrual Grocery

</div>


<div th:insert="~{footer : : #copy-section}"></div>

2021年1月19日 星期二

Compax Closing Status Report 紀錄

Compax AAX Technical Service

期間2019/06-2020/12

AAX/TMF developments/tests

------

專案結案會議報告是要納管的

1客戶介紹包含組織成員

2成員角色負責項目開發內容介紹(所有成果要歸功於在座各位)

3專案成果(全部門績效第一) 表達對貢獻的感謝

4專案品質檢驗

5檢討回饋

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

Ian 

point: 177 

defect:12

percentage:6.78%

改善方向: 

1 typo , 測試資料未刪 

2 Null 

3 Exception

建議: 

*寫完程式自行進行校正

*UT應善用null值進行測試

*判斷情境是否Exception正確

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

普遍code review suggestion 

1Exception處理 

2Null處理 

3可讀性

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

*這個案子你學到什麼? 這個部分還有哪些需要調整?

Jena: 帶新人進入會有壓力, Peter進步很快, Leon開朗, 努力

要接受比較嚴厲的建議


Peter: 寫程式很久之前, 有點怕, 感謝Jena, 接受建議並應用, 了解Java開發專案如何進行

(以前都是做測試), 很感謝這邊所有人都給我幫助, 感謝Solomen給我心理輔導


佳蓮: 我第一次完整專案, 一開始只會select後來會join, 比較幸運有上公司課程, 

大家執行TMF專案, 要先討論再來做會比較好


Jackson: TMF國外案子和技術都是蠻有挑戰性, 對人的問題, 大家可以共同努力,

對人照著想做的方式前進, 交給管理層的問題, 對專案成果的紀律問題


Leon: 這是我轉職成功第一個工作, 技術方面學到蠻多東西, java8 和git技巧, 

做事優先順序, 事情大方向, 感謝Jackson指導, 我覺得coding guideline是很好的,

是可以看得到的規範


Lily: 早點統計, 早點留意, 錯誤的類型, 跟大家彼此互相切磋蠻好的


Peter: 人生中比較認真寫程式的, 這個團隊非常好, 來這邊是接受大家的精華, 

感謝Sherry課表讓新人可以快速進入狀況, 要加強就是寫code


Jasmine: 我今年在公司20年, 轉到這個team掙扎, 不知道自己發展會怎麼樣,

我都在這領域待19年, 我想要的是學新的東西, 到不同的世界去看看別的世界做什麼,

自己還需要學什麼, 需要怎麼改善, 在前15個月我都在做國際化的事, 也有機會嘗試到

什麼是test, 這樣聽下來, 都知道大家想要很快進入狀況, 大家都是對自己很有要求,

這是我對這個team覺得不錯的地方, Jena, Solomon都幫助我很多, 我跟大家都學到很多東西


Solomon: 一路上都是Sherry提拔我, 每場面試我都很辛苦, 你的表情你的語氣, 我們都要

觀察這個人適不適合在這專案上, 你自己要考慮要跟這個人要怎麼跟他相處, 一路走來

受到很多人幫忙, 每個人都花最大努力在這專案上, 我相信我們找進來的人都是非常優秀,

這是我們很驕傲的地方, 我們當初選人選對人, 這也是我第一個專案, 我以前都是做測試,

Sherry在各方面帶我很多, 怎麼跟客戶接觸, 怎麼promote自己, 因為我是在做中學, 謝謝

大家的配合, 使這個案子可以得到客戶的賞識, 希望自己學到的可以應用到之後專案上,

->>>專案當中你覺得這個專案比較好, 我們日後可以延續或更好?

Scadule定下去大家都follow是好的, 管理工具上可以改進ex進度表(我早上開會前要去問)


Sherry: 這個案子是第一個自動找上門來的, 這個不是原來班底, 從無到有, 最難的是decipline

每個人有不同的開發習慣, 做事習慣, 每個人要認同公司文化, 對管理上來說挑戰極高,

他只能成功不能失敗, 這案子是被關注的焦點, 我和Solomon是壓力極大, 我們在其中是

需要做包容, 如何做Implove, 我要謝謝Jasmine, Jena, 小立, 第一批進來, 需要引導同仁進入

公司的文化, 如何從frelancer轉為員工, 我們團隊中也擁有很多特別人格特質, 謝謝Jena在

幫我帶人上的幫助, 之前的TMF只能有一個規則, 有來有去都有階段性任務, 也是感謝在

草創期建立起來的名聲, 當初我們設的主軸是分享, 合作, 彼此幫忙幫助對方帶到一個水平,

在這裡面Solomon是被我責難最多的, 在這中我們是很努力在讓團隊一起進步, 每個人都有

優點和不足的部分, 希望我們的包容度更大, 我們要要求自己, 只有自己更好, 團隊才會成功

這裡老闆也是看到數字, 你們可以看看績效, 你們可以去看得到, 這個案子大家也辛苦了


Solomon: 重複的要求, 如果當下做到就好, 就可以多點時間拿去開發, 而不是花在開會時間上

Sherry: 寫文件的練習重要 





RestfulCRUD 簡單介紹

1 比較普通的CRUD和RestfulCRUD

RestfulCRUD樣貌如下

URI:/資源名稱/資源標示 

依照HTTP請求方式區分對資源CRUD操作

比較表如下






RestfulCRUD明顯的方式是對於"資源", 做某項"動作"

2 課程使用的樣式



[SpringBoot 1.5] 登入及攔截器HandlerInterceptor

一般在Controller中我們會使用以下語法

@RequestMapping(value = "/user/login", method = RequestMethod.POST)

但是在較新版中已經簡化成以下

@PostMapping(value = "/user/login")

因為PostMapping已經有將 method = RequestMethod.POST 寫好

controller的程式碼如下

@PostMapping(value = "/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map<String,Object> map){
if(!StringUtils.isEmpty(username)&&"123456".equals(password)){
//登入成功
return "dashboard";
}else{
//登入失敗
map.put("msg","用戶名密碼錯誤");
return "login";
}
}

前端的程式碼

<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" placeholder="Username"
th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="password" class="form-control" placeholder="Password"
th:placeholder="#{login.password}" required="">

這裡的對應是由name來做@RequestParam的對應

那這邊特別紀錄一下, 因為thymeleaf有緩存的設置

所以這邊要在properties檔案中加入這段

spring.thymeleaf.cache=false

以後在修改靜態頁面只要ctrl+f9 刷一下瀏覽器就可以看效果了

而錯誤訊息可以用以下這段在頁面顯示

<p style="color: red;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

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

製作攔截器

1 在controller加入Session檢核, 只要成功登入, 就將username塞到Session中

@PostMapping(value = "/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map<String,Object> map, HttpSession session){
if(!StringUtils.isEmpty(username)&&"123456".equals(password)){
//登入成功
session.setAttribute("loginUser",username);
return "redirect:/main.html";
}else{
//登入失敗
map.put("msg","用戶名密碼錯誤");
return "login";
}
}

2 在component包中製作一個LoginHandlerInterceptor 類別去繼承HandlerInterceptor 

類別, 並複寫三個父類方法

public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}

3 在preHandle中加入以下這段程式碼當作檢核, 如果沒有權限的話, 

會有錯誤訊息, 並且導頁到首頁

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user=request.getSession().getAttribute("loginUser");
if(user==null){
request.setAttribute("msg","沒有權限請先登入");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
return true;
}
}

4在自設定的configuration檔案中的@Bean, 增加addInterceptors的方法

加入攔截器並排除不需要攔截的頁面

@Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter= new WebMvcConfigurerAdapter(){
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/login");
}
};

return adapter;
}

5從Session中取出使用者資訊, 放在畫面上

<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="#">[[${session.loginUser}]]</a>


這樣就完成了, 每當用戶進入到下個頁面就會被攔截器去檢核


2021年1月18日 星期一

[SpringBoot 1.5] 國際化

1 在resources下面建一個資料夾i18n並產生properties檔案







2 在檔案左下方切換到ResourceBundle視圖模式, 並點左上方加號來新增屬性

並在屬性的右邊將屬性在各國語言下的值輸入進去













3 使用thymeleaf語法如下

th:text="#{login.tip}

完整的程式碼如下

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<!--链接(URL@{…} /crud/asserts/css/bootstrap.min.css-->
<link th:href="@{/asserts/css/bootstrap.min.css}" href="asserts/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/asserts/css/signin.css}" href="asserts/css/signin.css" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
<img class="mb-4" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>

<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]] <!--行内表达式-->
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_tw')}">[[#{login.chinese}]]</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">[[#{login.englist}]]</a>
</form>
</body>
</html>

這樣~瀏覽器就會依據你選擇最優先的語言來做顯示

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

原碼分析

在我們的SpringBoot自動配置類別 WebMvcAutoConfiguration

我們可以找到以下程式碼

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties
.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}

這段的意思是, 如果我們有找到固定的LocalResolver配置, 我們就使用固定的

不然我們就會使用new AcceptHeaderLocaleResolver()

其中有一個方法

@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
Locale requestLocale = request.getLocale();
if (isSupportedLocale(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}

他會從request中或取得locale訊息









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

客製化locale

現在要做一個能夠依照選擇來定語言的方式可以這樣做

造一個component.MylLocaleResolver去繼承LocaleResolver類別

繼承方法, 並如下寫

這個想法是, 他要接request的傳參數, 根據參數來做判斷

如果參數有值, 就拿參數值拆去產生locale物件來使用


public class MyLocaleResolver implements LocaleResolver{
@Override
public Locale resolveLocale(HttpServletRequest request) {
String l=request.getParameter("l");
Locale locale=Locale.getDefault();
if(!StringUtils.isEmpty(l)){
String[] split =l.split("_");
locale=new Locale(split[0],split[1]);
}
return locale;
}

@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

}
}


而畫面上的配合要寫成這樣 th:href="@{/index.html(l='zh_tw')}"

如下

<a class="btn btn-sm" th:href="@{/index.html(l='zh_tw')}">[[#{login.chinese}]]</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">[[#{login.englist}]]</a>


然後~有一點還要做的就是, 要在自定義的config檔案中載入Bean讓他可以使用


//使用WebMvcConfigurerAdapter 可以來擴展SpringMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
//control+O 可以找到父類所有可用方法

@Override
public void addViewControllers(ViewControllerRegistry registry) {
//super.addViewControllers(registry);
//瀏覽器發送/atguigu請求來到success
registry.addViewController("/atguigu").setViewName("success");
}

@Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter= new WebMvcConfigurerAdapter(){
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
};
return adapter;
}

@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}

}

第二個Bean的是做導頁, 也就是如果是"/"他會導到login, /index.html他也會導到login



量身訂做建議(37 歲,6 年 Java 後端工程師)from chatgpt

🎯 量身訂做建議(37 歲,6 年 Java 後端工程師) 1️⃣ 先看你的條件 年齡 37 屬於「中高年資」工程師,履歷上的 深度 / 系統設計能力 會比「語言多寡」更重要。 6 年 Java 後端 代表你在 Spring Boot、資料庫、API 設計...