# 01.後端規範
###### tags: `開發規範`.
## 1.目錄結構
* ykkap-pro專案底下會有固定server與clent模組加上ykk系統功能模組。
* server模組:後端程式系統主要模組,負責整個系統的設定如控管。
* client模組:前端程式系統主要模組,負責前端專案所有事項。
* adm模組:ykk adm功能模組負責adm模組功能、共用功能。
* 其餘模組: 負責各模組的repository、service、controller與vm開發。
```
├── adm
│ └── src
│ └── main/java/com/ykkap/pro
│ └── common
│ │ └── domain
│ │ └── annotation
│ │ └── aop
│ │ └── utils
│ │ └── vm
│ └── adm
│ └── repository
│ └── service
│ └── controller
│ └── vm
├── bom
│ └── src
│ └── main/java/com/ykkap/pro
│ └── bom
│ └── repository
│ └── service
│ └── controller
│ └── vm
├── ......各個功能模組
├── client
│ ├── public
│ └── src
│ ├── App.vue
│ ├── assets
│ ├── common
│ │ └── js
│ │ └── mixins
│ │ ├── commonMixin.js
│ │ └── index.js
│ ├── components
│ ├── config
│ ├── router
│ ├── store
│ ├── styles
│ └── views
│ └── adm
│ │ └── lookups-manage
│ │ ├── LookupsManage.vue
│ │ └── LookupsManageDetailF.vue
│ └── ....各個功能模組
│ └── Home.vue
└── server
└── src
└── main/java/com/ykkap/pro
├── server
│ ├── ServerApplication.java
│ ├── ServletInitializer.java
│ ├── config
│ │ └── security
│ │ ├── KeycloakSecurityConfig.java
│ │ └── MyKeycloakAuthenticationProcessingFilter.java
│ ├── constants
│ │ └── WebErrorCodeEnum.java
│ ├── handler
│ │ └── GlobalExceptionHandler.java
│ └── vm
│ └── WebRespVM.java
└── resources
├── application-dev.yml
└── application.yml
```
## 2.命名規則
* **Domain(Entity):**
以Table命名,每個單字第一個字母大寫區分,若有下底線則移除。
如:以Table Name:AUTH_ORG_USERS ,則Domain Class Name:AuthOrgUsers.java
* **Repository(DAO):**
Domain名 + Repository
* **Service(服务层):**
功能名稱 + Service
* **Controller(控制器层):**
功能名稱 + Controller
* **URL Peth:**
*URL請求中不採用大小寫混合的駝峰命名方式,採用蛇形命名法一律小寫單字,如果需要連接多個單字,則採用連接符-連接單字。(備註:不採用底線_連接符號,是因為與Html的超連結樣式重疊容易混淆)
*路徑組成規範:api/模組名稱/功能名稱/method名稱。
* **Util(工具层):**
功能名稱 + Utils
## 3.Repository 開發規範
### 3.1.JPQL使用規範
* 使用時機:單一Table查詢,查詢結果與Where條件無使用到其他Domain物件。
```java
@Query(value = "select name,author,price from Book b where b.price>:price1")
List<Book> findByPrice(@Param("price1") long price1);
```
### 3.2.Native SQL Query使用規範
* 使用時機:除了JPQL使用規範外,其餘查詢方式一律使用Native SQL,SQL語法一律小寫。
```java
@Query(nativeQuery = true , value =
"select EAM.SUBJECT,\n" +
" EAM.AUTHOR,\n" +
" AU.DISPLAY_NAME,\n" +
" EAM.DEPT_CODE,\n" +
" EAM.DEPT_NAME,\n" +
" TO_CHAR(EAM.PUBLIC_DATE, 'YYYY-MM-DD'),\n" +
" TO_CHAR(EAM.EXPIRE_DATE, 'YYYY-MM-DD'),\n" +
" TO_CHAR(EAM.CREATED_DATE, 'YYYY-MM-DD'),\n" +
" EAM.B_TYPE,\n" +
" EAM.S_TYPE\n" +
" from EIP_ANN_MAIN EAM,\n" +
" AUTH_USERS AU \n" +
" where EAM.AUTHOR=AU.USER_ACCOUNT "
" and EAM.ANN_ID=:annId")
List<Object[]> getEipAnnInfo(@Param("annId") int annId);
```
### 3.3.其餘規則
* 參數:repository中一律使用@Param引用参数
* 分頁:分頁查詢的repository function 名稱以P結尾,以利辨識該功能為分頁查詢。
* 不要使用SPEL語法
## 4.一般規範
### 4.1.物件間的轉換
#### 4.1.1.VM to Domin(Object to Object)轉換:
透過org.springframework.beans.BeanUtils中的copyProperties功能
```java=
log.info("copy before A1:{} B:{}",a,b );
BeanUtils.copyProperties(a,b);
log.info("copy after A1:{} B:{}",a,b );
```
執行結果:
copy before A1:A(name=eric, addr=TW) B:B(name=ken, phone=01111)
copy after A1:A(name=eric, addr=TW) B:B(name=eric, phone=01111)
#### 4.1.2. List<Object[]> to List<Map<String,Object>>
```java=
private final String[] delegateFields = new String[]{"assigneeName", "attorneyName", "creatorName" };
List<Lookups> lookList = lookupsRepository.findByLookupType("PAYMENT");
Page<Map<String, Object>> list = ObjectExtensions.mapping(closeRepository.findByClosePageList(counterList, paymentToolCode,pageable),CLOSE_LIST_COLUMNS);
```
第一行:須根據Repository查詢結果順序定義欄位名稱,備註查詢結果的順序一定要一致。
第三行:透過ObjectExtensionUtils工具包的mapping功能進行轉換
#### 4.1.3. List<Map<String,Object>> to Object(VM,Domain)
夠過 com.fasterxml.jackson.databind.ObjectMapper中的convertValue功能
```java=
Map<String, Object> fooMap = om.convertValue(foo, Map.class);
Foo fooMapFoo = om.convertValue(fooMap, Foo.class);
log.info("fooMapFoo: {}", fooMapFoo);
```
```java=
Map<String, String > reportParameter = new HashMap<>();
reportParameter.put("commissionNo", "G210001");
reportParameter.put("projectNo", "A210003");
reportParameter.put("...", "...");
.
.
.
ReportUtils.Download("保固票退還申請書",reportParameter);
```
### 4.2.One to Many相關操作
#### 4.2.1. Master 類別 Domain設定
```java=
@Data
@NoArgsConstructor
@Entity
@Table(name = "CITY")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class City {
@Id
@Column(name = "CITY")
private String city;
@Column(name = "CITY_CH")
private String cityCh;
@Column(name = "CITY_ENG")
private String cityEng;
@OneToMany(mappedBy = "cityObject", fetch = FetchType.LAZY)
@JsonManagedReference
private List<CityZip> cityZipList;
}
```
* 第17行:
mappedBy:指出是要對應到那一個類別。
FetchType.LAZY:懒加载,@OneToMany預設的Fetch模式是FetchType.LAZY,除非真正要使用到該屬性的值,否則不會真正將資料從表格中載入物件。
**Detail 類別 Domain設定**
```java=
@Data
@NoArgsConstructor
@Entity
@Table(name = "CITY_ZIP")
@EntityListeners(AuditingEntityListener.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CityZip {
@Column(name = "CITY")
private String city;
@Id
@Column(name = "ZIP")
private String zip;
@Column(name = "ZIP_CH")
private String zipCh;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CITY" ,insertable = false, updatable = false)//,
@JsonBackReference
private City cityObject;
}
```
* 第10行:需定義city屬性,這就能直接對CityZipy做CRUD操作。
* 第20行:
@JsonManagedReference and @JsonBackReference. 成對使用目的解決無限迴圈。
@JsonManagedReference標記在父類對子類的引用變數上。
@JsonBackReference標記在子類對父類的引用變數上。
#### 4.2.2. 查詢
可透過查詢Master直接取得Detail資料,只有需要用到Detail資料的時候,才會去資料出抓取資料載入所以FetchType必須設定為LAZY(fetch = FetchType.LAZY)
#### 4.2.3. 刪除
可透過刪除Master物件時直接連相關的Detail資料耶刪除,再見建立資料庫FK 需加上ON DELETE CASCADE較件,
如下:
```sql
CREATE TABLE UI_DEMO.CITY_ZIP (
CITY VARCHAR2(3 BYTE) NOT NULL,
ZIP VARCHAR2(5 BYTE) NOT NULL,
ZIP_CH VARCHAR2(30 BYTE) NULL,
CONSTRAINT PK_CITY_ZIP PRIMARY KEY(ZIP)
NOT DEFERRABLE VALIDATE
)
;
ALTER TABLE UI_DEMO.CITY_ZIP
ADD ( CONSTRAINT FK_CITY_ZIP
FOREIGN KEY(CITY)
REFERENCES UI_DEMO.CITY(CITY)
ON DELETE CASCADE
NOT DEFERRABLE INITIALLY IMMEDIATE VALIDATE )
;
```
#### 4.2.4. 新增/修改
Master物件只修改Master物件資料,在Master物件資料中修改Deatal資料不會有作用,
若要修改或新增Deatal資料直接在Deatal物件進行操作。
### 4.3.Call Procedures資料庫
所有模組所有功能需要呼叫資料庫Procedures,統一使用EntityManager.createStoredProcedureQuery呼叫 寫法如下
如下:
```java
@AllArgsConstructor
@Service
@Slf4j
public class ActRuDelegateService {
@PersistenceContext
private EntityManager entityManager;
public void call() {
StoredProcedureQuery store = this.entityManager.createStoredProcedureQuery("UPDATE_CHANGED_COST_TO_Q3");
store.registerStoredProcedureParameter("p_commission_no", String.class, ParameterMode.IN);
store.registerStoredProcedureParameter("p_commission_ver", Integer.class, ParameterMode.IN);
store.registerStoredProcedureParameter("p_status", String.class, ParameterMode.OUT);
store.registerStoredProcedureParameter("p_status2", String.class, ParameterMode.OUT);
store.setParameter("p_commission_no","rrrr");
store.setParameter("p_commission_ver",1);
store.execute();
log.info("store getOutputParameterValue p_status:{} p_status2:{}",store.getOutputParameterValue("p_status"),store.getOutputParameterValue("p_status2"));
}
}
```
### 4.4.Select API 相關規格

api接口名稱規則:加上原接口名稱-select
api回傳格式:統一使用SelectionVM
如下:
```java
@Data
@NoArgsConstructor
//@AllArgsConstructor
public class SelectionVM {
private String label;
private String value;
private boolean inactive;//非必要,可不設定值
public SelectionVM(String label,String value){
this.label= label;
this.value= value;
}
public SelectionVM(String label,String value,boolean inactive){
this.label= label;
this.value= value;
this.inactive=inactive;
}
}
```
### 4.5.LOV Service規格

* 所有此類型查詢接口一律需要分頁功能,所以必須為可分頁查詢。(避免畫面一次將所有資料一次撈出,產生效能問題)
* 所有功能有必須有filterVal參數:其目的為了提供所有結果欄位的模糊Like查詢(可排除日期欄位)
* api接口名稱規則:/page/find-color-lov
範例
```java
@GetMapping("/page/find-color-lov")
public Page<SelectionVM> findColorLOVPage(String filterVal,String type, String cordno, String itemno, String partno, Pageable pageable) {
log.info("getColorListP filterVal:{}",filterVal);
if (StringUtils.isBlank(filterVal)){
filterVal="";
}else{
filterVal=filterVal.toLowerCase();
}
return comboLOVService.getColorListPage(filterVal,type,cordno,itemno,partno,pageable);
}
```
* service function名稱規則:function+LOV+Page
範例
```java
public Page<SelectionVM> findColorLOVPage(String filterVal,String type, String cordno, String itemno, String partno,Pageable pageable) {
if(!StringUtils.isBlank(cordno) && (StringUtils.isBlank(type) || "B_ADD".equals(type))){
return partRepository.getColorByCordP(cordno,filterVal,pageable).map(color -> new SelectionVM( color, color));
}else {
return partRepository.getColorByMPartItemP(partno,filterVal,pageable).map(color -> new SelectionVM( color, color));
}
}
```
* repository function 名稱規則:function+LOV+Page
範例
```java
@Query(nativeQuery = true , value =
"select distinct COLOR\n" +
" from cord_color@Q3 \n" +
" where cordno=:cordno and LOWER(COLOR) like %?2%"
,countQuery ="select count(a.*) from (select distinct COLOR\n" +
" from cord_color@Q3 \n" +
" where cordno=:cordno and LOWER(COLOR) like %:filterVal%) a")
public Page<String> findColorLOVPage(@Param("cordno") String cordno,@Param("filterVal") String filterVal, Pageable pageable);
```
### 4.6 service @Transactional用法