일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 인터페이스
- 정수형타입
- 사용자예외클래스생성
- exception
- 대덕인재개발원
- 자바
- 한국건설관리시스템
- 생성자오버로드
- 자동차수리시스템
- 제네릭
- Java
- EnhancedFor
- GRANT VIEW
- NestedFor
- 환경설정
- 객체 비교
- 컬렉션프레임워크
- 컬렉션 타입
- 오라클
- 예외처리
- 예외미루기
- 어윈 사용법
- cursor문
- 추상메서드
- 다형성
- 집합_SET
- oracle
- 참조형변수
- 메소드오버로딩
- abstract
- Today
- Total
거니의 velog
(16) 보강 11 본문
* 파일 업로드
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style type="text/css">
* {
box-sizing: border-box;
}
#wrapper {
text-align: center;
}
#list {
width:70%;
height: 400px;
border:5px solid rgb(166, 0, 255);
overflow: auto;
}
#toolbar{
width:70%;
border:5px solid orange;
padding-bottom: 5%;
/*height: 50px;*/
}
div {
margin: 0 auto;
min-width: 400px;
}
input[type=button] {
/* 아래로 9px 이동 크기 1.3배 */
transform: translateY(9px) scale(1.3);
}
#muTable {
margin: 0 auto;
}
</style>
</head>
<body>
<div id="wrapper">
<h1>RESTFUL API 테스통</h1>
<div id="list"></div>
<div id="toolbar">
<br>
<!--
enctype="multipart/form-data"
바이너리로 전송되는데,
------------------------
내용
------------------------
내용
------------------------
이런 식으로 멀티파트식이 전송됨.
-->
<form>
<table id="muTable">
<tr>
<td>넘</td>
<td><input type="text" name="sujinNum" value="" ></td>
</tr>
<tr>
<td>이름</td>
<td><input type="text" name="sujinName" value="" ></td>
</tr>
<tr>
<td>내용</td>
<td><input type="text" name="sujinContent" value="" ></td>
</tr>
<tr>
<td>파일</td>
<td><input type="text" name="sujinFile" value="" ></td>
</tr>
<tr>
<td>파일선택</td>
<!-- directory, webkitdirectory 속성은 파일 클라우드를 제작할 때 사용하는 속성 -->
<td><input myFile type="file" name="sujinFile2" value="" multiple></td>
</tr>
</table>
</form>
<input type="button" value="입력" onclick="fPostInput()">
<input type="button" value="수정" onclick="fPutUpdate()">
<input type="button" value="삭제" onclick="fDeleteDel()">
<!-- 파일 한개만 올리는 방법 -->
<input type="button" value="파일업로드" onclick="fFileUp()" >
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
// 커스텀 속성 기법, CSS 선택자로 선택
const sujinFile2 = document.querySelector("[myFile]");
function fFileUp(){
console.log("항상 체킁 : ", sujinFile2.files); // 눈으로 보아용
// 아작스로 파일 보낼 땐 FormData가 필요!
let formData = new FormData(); // 무조건 전송방식이 multipart/form-data 가 된다.
//formData.append("키값", 실제파일객체);
formData.append("키값", sujinFile2.files[0]);
let xhr = new XMLHttpRequest();
xhr.open("post", "url", true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log("체킁 : ", xhr.responseText);
}
};
//xhr.send();
}
const myForm = document.forms[0];
const myList = document.querySelector("#list");
// 기능 함수
// 테이블 TR 요런건 기본으로 제공하는 게 좋음
const fTrClickMouseOverOut = ()=>{
let trs = document.querySelectorAll("tr");
for(let i=0; i<trs.length; i++){
trs[i].addEventListener("mouseover",()=>{
trs[i].style.backgroundColor = "black";
trs[i].style.color = "orange";
});
trs[i].addEventListener("mouseout",function(){
this.style.backgroundColor="white";
this.style.color = "black";
this.style.fontWeight = "normal";
});
}
}
// 테이블 맹그는 함수
const fMakeTable = (sujins) =>{
let tableStr = `<table border=1 style="width:100%"><tbody>`;
tableStr += `<tr><th>Num</th><th>Name</th><th>Content</th><th>File</th></tr>`;
for(let i=0; i<sujins.length; i++){
let sujin = sujins[i];
tableStr += `<tr onclick="fGetOne(${sujin.sujinNum})">`;
tableStr += `<td>${sujin.sujinNum}</td>`;
tableStr += `<td>${sujin.sujinName}</td>`;
tableStr += `<td>${sujin.sujinContent}</td>`;
tableStr += `<td>${sujin.sujinFile}</td>`;
tableStr += `</tr>`;
}
tableStr += `<tr></tbody></table>`;
myList.innerHTML = tableStr;
//테이블이 동적으로 새로 맹글어지므로, TR이벤트도 그때마당
fTrClickMouseOverOut();
}
// 백엔드 Restful SujinController에 대응하는 함수들
// get으로 list(sujins)가져오깅
const fGetList = () => {
$.ajax({
type: "get",
url: "/api/sujins",
dataType: "json",
success: function(res){
console.log("항상 결과 확인! : ", res);
fMakeTable(res);
}
});
}
fGetList(); // 그냥 바로 리스트 콜!
// get으로 1개 row(sujin) 가져오깅
const fGetOne = (sujinNum) => {
$.ajax({
type: "get",
url: `/api/sujins/${sujinNum}`,
dataType: "json",
success: function(res){
console.log("항상 결과 확인! : ", res);
// form에 출력
myForm.sujinNum.value = res.sujinNum;
myForm.sujinName.value = res.sujinName;
myForm.sujinContent.value = res.sujinContent;
myForm.sujinFile.value = res.sujinFile;
}
});
}
// post로 insert 1개 row(sujin)
const fPostInput = () => {
let sujinVO = {
//sujinNum: // 자동 시퀀스 생성이라 필요없음
sujinName: myForm.sujinName.value,
sujinContent: myForm.sujinContent.value,
sujinFile: myForm.sujinFile.value
};
console.log("sujinVO : ", sujinVO); // 항상 눈으로 값이 잘 들어갔는지 체킁!
$.ajax({
type: "post",
url: "/api/sujins",
contentType: "application/json;charset=UTF-8", // json 형식의 문자열 보냄을 표시
data: JSON.stringify(sujinVO), // 객체를 그냥 넘기면 안 됨!
dataType: "text", // 돌아오는 값이 단순 숫자 JSON.parse 하면 안 됨!
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("정말 잘 입력되었네용~!");
// tr 태그만 한개 맹글어서 추가해도 되공
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
setTimeout(() => { // 변칙 테크닉
myList.scrollTo(0, myList.scrollHeight); // 스크롤 끝으로 내리깅!
}, 50);
}
}
});
}
// put으로 update 수정 부르깅
const fPutUpdate = () => {
let sujinVO = {
sujinNum: myForm.sujinNum.value, // 해당하는 번호의 글을 수정해야 하므로 필요!
sujinName: myForm.sujinName.value,
sujinContent: myForm.sujinContent.value,
sujinFile: myForm.sujinFile.value
};
$.ajax({
type: "put",
url: "/api/sujins",
contentType: "application/json;charset=UTF-8", // json 형식의 문자열 보냄을 표시
data: JSON.stringify(sujinVO),
dataType: "text", // 돌아오는 값이 단순 숫자 JSON.parse 하면 안 됨!
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("정말 잘 수정되었네용~!");
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
setTimeout(() => { // 변칙 테크닉
myList.scrollTo(0, myList.scrollHeight); // 스크롤 끝으로 내리깅!
}, 50);
}
}
});
}
// delete 메소드로 요청해서 지우깅
const fDeleteDel = () => {
let seqNum = myForm.sujinNum.value;
$.ajax({
type: "delete",
url: `/api/sujins/${seqNum}`,
dataType: "text", // 돌아오는 값이 단순 숫자 JSON.parse 하면 안 됨!
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("잘 지워졌어용~");
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
}
}
});
}
</script>
</body>
</html>
- http://localhost:8017/
// 커스텀 속성 기법, CSS 선택자로 선택
const sujinFile2 = document.querySelector("[myFile]");
function fFileUp(){
console.log("항상 체킁 : ", sujinFile2.files); // 눈으로 보아용
// 아작스로 파일 보낼 땐 FormData가 필요!
let formData = new FormData(); // 무조건 전송방식이 multipart/form-data 가 된다.
//formData.append("키값", 실제파일객체);
formData.append("mc", sujinFile2.files[0]);
let xhr = new XMLHttpRequest();
xhr.open("post", "/api/sfile", true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log("체킁 : ", xhr.responseText);
}
};
xhr.send(formData);
}
package com.e7e.merong.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.e7e.merong.service.ISujinService;
import com.e7e.merong.vo.SujinVO;
import lombok.extern.slf4j.Slf4j;
// 컨트롤러는 접수창고당, 모든 고객요청은 접수창고를 거쳐야 한당!
//@Controller
@Slf4j
@RestController // @Controller + @ResponseBody(AJAX용)
@RequestMapping("/api")
public class SujinController {
/*
* Restful api 서비스에서
* - get은 조회, post는 생성, put은 수정, delete는 삭제를 의미한다.
* - 강제 사항은 아니고, 관례적인 약속!
*/
@Autowired // 컨트롤러는 서비스를 부름!
private ISujinService sujinService;
@GetMapping("/sujins") // 보통, 복수형 s 를 붙여서 url 을 작성한다.
public List<SujinVO> listSujin() {
return sujinService.listSujin();
}
@GetMapping("/sujins/{seqNum}")
public SujinVO getSujin(@PathVariable int seqNum) {
SujinVO sujinVO = new SujinVO();
sujinVO.setSujinNum(seqNum);
return sujinService.getSujin(sujinVO);
}
@PostMapping("/sujins")
public int insertSujin(@RequestBody SujinVO sujinVO) {
return sujinService.insertSujin(sujinVO);
}
@PutMapping("/sujins")
public int updateSujin(@RequestBody SujinVO sujinVO) {
return sujinService.updateSujin(sujinVO);
}
@DeleteMapping("/sujins/{seqNum}")
public int deleteSujin(@PathVariable int seqNum) {
SujinVO sujinVO = new SujinVO();
sujinVO.setSujinNum(seqNum);
return sujinService.deleteSujin(sujinVO);
}
// 파일 처리
@PostMapping("/sfile")
public String postFile(MultipartFile mc) {
log.info("파일이름 : " + mc.getOriginalFilename());
log.info("파일사이즈 : " + mc.getSize());
return "일단 OK";
}
}
// 파일 처리
@PostMapping("/sfile")
public String postFile(MultipartFile mc) throws Exception {
log.info("파일이름 : " + mc.getOriginalFilename());
log.info("파일사이즈 : " + mc.getSize());
// 저장
mc.transferTo(new File("d:/myUpload/" + mc.getOriginalFilename()));
return "일단 OK";
}
package com.e7e.merong.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration // 얘만 붙이면 설정 파일로 인식!
//@EnableWebMvc, 그냥 스프링에서 필요한 어노테이션
public class MyConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
System.out.println("요기가 실행되었는지 check?");
registry.addResourceHandler("/mcimg/**") // 웹 접근 경로
.addResourceLocations("file:///d:/myUpload/"); // 서버내 실제 경로
}
}
* 주소 표시줄에 /mcimg/aaa.jpg(웹 경로) 이렇게 쓰면 서버가 받아서 자기 d:/myUpload/aaa.jpg(실제 경로)를 찾는다.
http://localhost:8017/mcimg/Node.png
// 파일 처리
@PostMapping("/sfile")
public String postFile(MultipartFile mc) throws Exception {
log.info("파일이름 : " + mc.getOriginalFilename());
log.info("파일사이즈 : " + mc.getSize());
// 저장
mc.transferTo(new File("d:/myUpload/" + mc.getOriginalFilename()));
// 파일에 대한 웹 접근 경로를 리턴
return "/mcimg/" + mc.getOriginalFilename();
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style type="text/css">
* {
box-sizing: border-box;
}
#wrapper {
text-align: center;
}
#list {
width:70%;
height: 400px;
border:5px solid rgb(166, 0, 255);
overflow: auto;
}
#toolbar{
width:70%;
border:5px solid orange;
padding-bottom: 5%;
/*height: 50px;*/
}
div {
margin: 0 auto;
min-width: 400px;
}
input[type=button] {
/* 아래로 9px 이동 크기 1.3배 */
transform: translateY(9px) scale(1.3);
}
#muTable {
margin: 0 auto;
}
</style>
</head>
<body>
<div id="wrapper">
<h1>RESTFUL API 테스통</h1>
<div id="list"></div>
<div id="toolbar">
<br>
<!--
enctype="multipart/form-data"
바이너리로 전송되는데,
------------------------
내용
------------------------
내용
------------------------
이런 식으로 멀티파트식이 전송됨.
-->
<form>
<table id="muTable">
<tr>
<td>넘</td>
<td><input type="text" name="sujinNum" value="" ></td>
</tr>
<tr>
<td>이름</td>
<td><input type="text" name="sujinName" value="" ></td>
</tr>
<tr>
<td>내용</td>
<td><input type="text" name="sujinContent" value="" ></td>
</tr>
<tr>
<td>파일</td>
<td><input type="text" name="sujinFile" value="" ></td>
</tr>
<tr>
<td>파일선택</td>
<!-- directory, webkitdirectory 속성은 파일 클라우드를 제작할 때 사용하는 속성 -->
<td><input myFile type="file" name="sujinFile2" value="" multiple></td>
</tr>
</table>
</form>
<div id="merong"></div>
<input type="button" value="입력" onclick="fPostInput()">
<input type="button" value="수정" onclick="fPutUpdate()">
<input type="button" value="삭제" onclick="fDeleteDel()">
<!-- 파일 한개만 올리는 방법 -->
<input type="button" value="파일업로드" onclick="fFileUp()" >
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
const merong = document.querySelector("#merong");
// 커스텀 속성 기법, CSS 선택자로 선택
const sujinFile2 = document.querySelector("[myFile]");
function fFileUp(){
console.log("항상 체킁 : ", sujinFile2.files); // 눈으로 보아용
// 아작스로 파일 보낼 땐 FormData가 필요!
let formData = new FormData(); // 무조건 전송방식이 multipart/form-data 가 된다.
//formData.append("키값", 실제파일객체);
formData.append("mc", sujinFile2.files[0]);
let xhr = new XMLHttpRequest();
xhr.open("post", "/api/sfile", true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log("체킁 : ", xhr.responseText);
let img = document.createElement("img");
img.src = xhr.responseText;
merong.appendChild(img); // 화면에 이미지 붙여넣기
}
};
xhr.send(formData);
}
const myForm = document.forms[0];
const myList = document.querySelector("#list");
// 기능 함수
// 테이블 TR 요런건 기본으로 제공하는 게 좋음
const fTrClickMouseOverOut = ()=>{
let trs = document.querySelectorAll("tr");
for(let i=0; i<trs.length; i++){
trs[i].addEventListener("mouseover",()=>{
trs[i].style.backgroundColor = "black";
trs[i].style.color = "orange";
});
trs[i].addEventListener("mouseout",function(){
this.style.backgroundColor="white";
this.style.color = "black";
this.style.fontWeight = "normal";
});
}
}
// 테이블 맹그는 함수
const fMakeTable = (sujins) =>{
let tableStr = `<table border=1 style="width:100%"><tbody>`;
tableStr += `<tr><th>Num</th><th>Name</th><th>Content</th><th>File</th></tr>`;
for(let i=0; i<sujins.length; i++){
let sujin = sujins[i];
tableStr += `<tr onclick="fGetOne(${sujin.sujinNum})">`;
tableStr += `<td>${sujin.sujinNum}</td>`;
tableStr += `<td>${sujin.sujinName}</td>`;
tableStr += `<td>${sujin.sujinContent}</td>`;
tableStr += `<td>${sujin.sujinFile}</td>`;
tableStr += `</tr>`;
}
tableStr += `<tr></tbody></table>`;
myList.innerHTML = tableStr;
//테이블이 동적으로 새로 맹글어지므로, TR이벤트도 그때마당
fTrClickMouseOverOut();
}
// 백엔드 Restful SujinController에 대응하는 함수들
// get으로 list(sujins)가져오깅
const fGetList = () => {
$.ajax({
type: "get",
url: "/api/sujins",
dataType: "json",
success: function(res){
console.log("항상 결과 확인! : ", res);
fMakeTable(res);
}
});
}
fGetList(); // 그냥 바로 리스트 콜!
// get으로 1개 row(sujin) 가져오깅
const fGetOne = (sujinNum) => {
$.ajax({
type: "get",
url: `/api/sujins/${sujinNum}`,
dataType: "json",
success: function(res){
console.log("항상 결과 확인! : ", res);
// form에 출력
myForm.sujinNum.value = res.sujinNum;
myForm.sujinName.value = res.sujinName;
myForm.sujinContent.value = res.sujinContent;
myForm.sujinFile.value = res.sujinFile;
}
});
}
// post로 insert 1개 row(sujin)
const fPostInput = () => {
let sujinVO = {
//sujinNum: // 자동 시퀀스 생성이라 필요없음
sujinName: myForm.sujinName.value,
sujinContent: myForm.sujinContent.value,
sujinFile: myForm.sujinFile.value
};
console.log("sujinVO : ", sujinVO); // 항상 눈으로 값이 잘 들어갔는지 체킁!
$.ajax({
type: "post",
url: "/api/sujins",
contentType: "application/json;charset=UTF-8", // json 형식의 문자열 보냄을 표시
data: JSON.stringify(sujinVO), // 객체를 그냥 넘기면 안 됨!
dataType: "text", // 돌아오는 값이 단순 숫자 JSON.parse 하면 안 됨!
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("정말 잘 입력되었네용~!");
// tr 태그만 한개 맹글어서 추가해도 되공
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
setTimeout(() => { // 변칙 테크닉
myList.scrollTo(0, myList.scrollHeight); // 스크롤 끝으로 내리깅!
}, 50);
}
}
});
}
// put으로 update 수정 부르깅
const fPutUpdate = () => {
let sujinVO = {
sujinNum: myForm.sujinNum.value, // 해당하는 번호의 글을 수정해야 하므로 필요!
sujinName: myForm.sujinName.value,
sujinContent: myForm.sujinContent.value,
sujinFile: myForm.sujinFile.value
};
$.ajax({
type: "put",
url: "/api/sujins",
contentType: "application/json;charset=UTF-8", // json 형식의 문자열 보냄을 표시
data: JSON.stringify(sujinVO),
dataType: "text", // 돌아오는 값이 단순 숫자 JSON.parse 하면 안 됨!
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("정말 잘 수정되었네용~!");
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
setTimeout(() => { // 변칙 테크닉
myList.scrollTo(0, myList.scrollHeight); // 스크롤 끝으로 내리깅!
}, 50);
}
}
});
}
// delete 메소드로 요청해서 지우깅
const fDeleteDel = () => {
let seqNum = myForm.sujinNum.value;
$.ajax({
type: "delete",
url: `/api/sujins/${seqNum}`,
dataType: "text", // 돌아오는 값이 단순 숫자 JSON.parse 하면 안 됨!
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("잘 지워졌어용~");
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
}
}
});
}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style type="text/css">
* {
box-sizing: border-box;
}
#wrapper {
text-align: center;
}
#list {
width:70%;
height: 400px;
border:5px solid rgb(166, 0, 255);
overflow: auto;
}
#toolbar{
width:70%;
border:5px solid orange;
padding-bottom: 5%;
/*height: 50px;*/
}
div {
margin: 0 auto;
min-width: 400px;
}
input[type=button] {
/* 아래로 9px 이동 크기 1.3배 */
transform: translateY(9px) scale(1.3);
}
#muTable {
margin: 0 auto;
}
</style>
</head>
<body>
<div id="wrapper">
<h1>RESTFUL API 테스통</h1>
<div id="list"></div>
<div id="toolbar">
<br>
<!--
enctype="multipart/form-data"
바이너리로 전송되는데,
------------------------
내용
------------------------
내용
------------------------
이런 식으로 멀티파트식이 전송됨.
-->
<form>
<table id="muTable">
<tr>
<td>넘</td>
<td><input type="text" name="sujinNum" value="" ></td>
</tr>
<tr>
<td>이름</td>
<td><input type="text" name="sujinName" value="" ></td>
</tr>
<tr>
<td>내용</td>
<td><input type="text" name="sujinContent" value="" ></td>
</tr>
<tr>
<td>파일</td>
<td><input type="text" name="sujinFile" value="" ></td>
</tr>
<tr>
<td>파일선택</td>
<!-- directory, webkitdirectory 속성은 파일 클라우드를 제작할 때 사용하는 속성 -->
<td><input myFile type="file" name="sujinFile2" value="" multiple></td>
</tr>
</table>
</form>
<div id="merong"></div>
<input type="button" value="입력" onclick="fPostInput()">
<input type="button" value="수정" onclick="fPutUpdate()">
<input type="button" value="삭제" onclick="fDeleteDel()">
<!-- 파일 한개만 올리는 방법 -->
<input type="button" value="파일업로드" onclick="fFileUp()" >
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
const merong = document.querySelector("#merong");
// 커스텀 속성 기법, CSS 선택자로 선택
const sujinFile2 = document.querySelector("[myFile]");
function fFileUp(){
console.log("항상 체킁 : ", sujinFile2.files); // 눈으로 보아용
// 아작스로 파일 보낼 땐 FormData가 필요!
let formData = new FormData(); // 무조건 전송방식이 multipart/form-data 가 된다.
//formData.append("키값", 실제파일객체);
formData.append("mc", sujinFile2.files[0]);
let xhr = new XMLHttpRequest();
xhr.open("post", "/api/sfile", true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log("체킁 : ", xhr.responseText);
let img = document.createElement("img");
img.src = xhr.responseText;
img.width = 100;
img.height = 100;
img.style.display = "block"; // 옆에 아무도 못 오겡!, 혼자 한줄
// 이미지 다운로드 처리
let aTag = document.createElement("a");
aTag.href = xhr.responseText;
aTag.innerHTML = "다운로등";
aTag.download = "예원졸림.jpg";
merong.appendChild(img); // 화면에 이미지 붙여넣기
merong.appendChild(aTag);
}
};
xhr.send(formData);
}
const myForm = document.forms[0];
const myList = document.querySelector("#list");
// 기능 함수
// 테이블 TR 요런건 기본으로 제공하는 게 좋음
const fTrClickMouseOverOut = ()=>{
let trs = document.querySelectorAll("tr");
for(let i=0; i<trs.length; i++){
trs[i].addEventListener("mouseover",()=>{
trs[i].style.backgroundColor = "black";
trs[i].style.color = "orange";
});
trs[i].addEventListener("mouseout",function(){
this.style.backgroundColor="white";
this.style.color = "black";
this.style.fontWeight = "normal";
});
}
}
// 테이블 맹그는 함수
const fMakeTable = (sujins) =>{
let tableStr = `<table border=1 style="width:100%"><tbody>`;
tableStr += `<tr><th>Num</th><th>Name</th><th>Content</th><th>File</th></tr>`;
for(let i=0; i<sujins.length; i++){
let sujin = sujins[i];
tableStr += `<tr onclick="fGetOne(${sujin.sujinNum})">`;
tableStr += `<td>${sujin.sujinNum}</td>`;
tableStr += `<td>${sujin.sujinName}</td>`;
tableStr += `<td>${sujin.sujinContent}</td>`;
tableStr += `<td>${sujin.sujinFile}</td>`;
tableStr += `</tr>`;
}
tableStr += `<tr></tbody></table>`;
myList.innerHTML = tableStr;
//테이블이 동적으로 새로 맹글어지므로, TR이벤트도 그때마당
fTrClickMouseOverOut();
}
// 백엔드 Restful SujinController에 대응하는 함수들
// get으로 list(sujins)가져오깅
const fGetList = () => {
$.ajax({
type: "get",
url: "/api/sujins",
dataType: "json",
success: function(res){
console.log("항상 결과 확인! : ", res);
fMakeTable(res);
}
});
}
fGetList(); // 그냥 바로 리스트 콜!
// get으로 1개 row(sujin) 가져오깅
const fGetOne = (sujinNum) => {
$.ajax({
type: "get",
url: `/api/sujins/${sujinNum}`,
dataType: "json",
success: function(res){
console.log("항상 결과 확인! : ", res);
// form에 출력
myForm.sujinNum.value = res.sujinNum;
myForm.sujinName.value = res.sujinName;
myForm.sujinContent.value = res.sujinContent;
myForm.sujinFile.value = res.sujinFile;
}
});
}
// post로 insert 1개 row(sujin)
const fPostInput = () => {
let sujinVO = {
//sujinNum: // 자동 시퀀스 생성이라 필요없음
sujinName: myForm.sujinName.value,
sujinContent: myForm.sujinContent.value,
sujinFile: myForm.sujinFile.value
};
console.log("sujinVO : ", sujinVO); // 항상 눈으로 값이 잘 들어갔는지 체킁!
$.ajax({
type: "post",
url: "/api/sujins",
contentType: "application/json;charset=UTF-8", // json 형식의 문자열 보냄을 표시
data: JSON.stringify(sujinVO), // 객체를 그냥 넘기면 안 됨!
dataType: "text", // 돌아오는 값이 단순 숫자 JSON.parse 하면 안 됨!
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("정말 잘 입력되었네용~!");
// tr 태그만 한개 맹글어서 추가해도 되공
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
setTimeout(() => { // 변칙 테크닉
myList.scrollTo(0, myList.scrollHeight); // 스크롤 끝으로 내리깅!
}, 50);
}
}
});
}
// put으로 update 수정 부르깅
const fPutUpdate = () => {
let sujinVO = {
sujinNum: myForm.sujinNum.value, // 해당하는 번호의 글을 수정해야 하므로 필요!
sujinName: myForm.sujinName.value,
sujinContent: myForm.sujinContent.value,
sujinFile: myForm.sujinFile.value
};
$.ajax({
type: "put",
url: "/api/sujins",
contentType: "application/json;charset=UTF-8", // json 형식의 문자열 보냄을 표시
data: JSON.stringify(sujinVO),
dataType: "text", // 돌아오는 값이 단순 숫자 JSON.parse 하면 안 됨!
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("정말 잘 수정되었네용~!");
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
setTimeout(() => { // 변칙 테크닉
myList.scrollTo(0, myList.scrollHeight); // 스크롤 끝으로 내리깅!
}, 50);
}
}
});
}
// delete 메소드로 요청해서 지우깅
const fDeleteDel = () => {
let seqNum = myForm.sujinNum.value;
$.ajax({
type: "delete",
url: `/api/sujins/${seqNum}`,
dataType: "text", // 돌아오는 값이 단순 숫자 JSON.parse 하면 안 됨!
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("잘 지워졌어용~");
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
}
}
});
}
</script>
</body>
</html>
* a 태그의 download 속성에는 한가지 제약사항이 있다. : 내 서버에 있는 것만 다운로드가 가능. 다른 사이트에서는 창 열림으로 바뀌면서 "Cross-Origin" 제약사항이 걸림
const merong = document.querySelector("#merong");
// 커스텀 속성 기법, CSS 선택자로 선택
const sujinFile2 = document.querySelector("[myFile]");
function fFileUp(){
console.log("항상 체킁 : ", sujinFile2.files); // 눈으로 보아용
// 아작스로 파일 보낼 땐 FormData가 필요!
let formData = new FormData(); // 무조건 전송방식이 multipart/form-data 가 된다.
//formData.append("키값", 실제파일객체);
formData.append("mc", sujinFile2.files[0]);
let xhr = new XMLHttpRequest();
xhr.open("post", "/api/sfile", true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log("체킁 : ", xhr.responseText);
let img = document.createElement("img");
img.src = xhr.responseText;
img.width = 100;
img.height = 100;
img.style.display = "block"; // 옆에 아무도 못 오겡!, 혼자 한줄
// 이미지 다운로드 처리
let aTag = document.createElement("a");
aTag.href = xhr.responseText;
aTag.innerHTML = "다운로등";
// 원래 파일명을 주려면?
/* //aTag.download = "예원졸림.jpg";
var originFilePath = xhr.responseText;
// 경로를 '/'로 분할
var pathParts = originFilePath.split('/');
// 마지막 부분을 선택 (파일 이름)
var fileName = pathParts[pathParts.length - 1];
aTag.download = fileName; */
let pathArr = xhr.responseText.split('/');
aTag.download = pathArr[pathArr.length - 1];
merong.appendChild(img); // 화면에 이미지 붙여넣기
merong.appendChild(aTag);
aTag.click(); // 그냥 클릭 (이건 장난)
}
};
xhr.send(formData);
}
* 파일의 원래 이름으로 다운로드 하기!
# 아래 2개는 참공
logging.level.com.e7e.merong=debug
server.port=8017
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/xe
spring.datasource.username=pc_09
spring.datasource.password=java
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.jdbc-type-for-null=varchar
mybatis.type-aliases-package=com.e7e.merong.vo
mybatis.mapper-locations=classpath:mybatis/mapper/*-Mapper.xml
# 아래도 파일업로드 용량설정이당 그냥 참고하장
# default 128K
spring.servlet.multipart.max-file-size=10MB
# request 사이즈 > response 사이즈(파일 + 기타 정보)
spring.servlet.multipart.max-request-size=12MB
spring.servlet.multipart.file-size-threshold=12MB
* 파일 업로드 용량을 증가시킬 수 있다.
const merong = document.querySelector("#merong");
// 커스텀 속성 기법, CSS 선택자로 선택
const sujinFile2 = document.querySelector("[myFile]");
function jQFileUp(){
console.log("항상 체킁 : ", sujinFile2.files); // 눈으로 보아용
// 아작스로 파일 보낼 땐 FormData가 필요!
let formData = new FormData(); // 무조건 전송방식이 multipart/form-data 가 된다.
//formData.append("키값", 실제파일객체);
formData.append("mc", sujinFile2.files[0]);
// jQuery는 default 값으로 contentType을 application/x-www-form-urlencoded 로 지정
// default 값을 process(후처리)
$.ajax({
type: "post",
url: "/api/sfile",
contentType: false, // 파일 전송 처리 시 주어야 할 필수 값
processData: false, // 파일 전송 처리 시 주어야 할 필수 값
cache: false, // 요건 옵션, 전송 캐쉬하지망. 파일은 비동기 시 같은 파일을 또 보낼 일이 거의 없기 때문.
data: formData,
dataType: "text",
success: function(res){
console.log("체킁 : ", res);
let $img = $("<img>");
$img.attr("src", res);
$img.css("width", "100px").css("height", "100px").css("display", "block");
// 이미지 다운로드 처리
let $aTag = $("<a>");
$aTag.attr("href", res);
$aTag.html("다운로등");
let pathArr = res.split('/');
$aTag.attr("download", pathArr[pathArr.length - 1]);
console.log("체에킁 : ", $img); // 제이쿼리 객체(박스)가 찍힘
console.log("체에킁 : ", $img[0]);
console.log("체에킁 : ", $aTag[0]);
merong.appendChild($img[0]); // 화면에 이미지 붙여넣기
merong.appendChild($aTag[0]);
}
});
}
package com.e7e.merong.vo;
import org.springframework.web.multipart.MultipartFile;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString // 디버깅에 유리, 1개씩 안 찍고 전체 속성 찍어줌
public class SujinVO {
private int sujinNum;
private String sujinName;
private String sujinContent;
private String sujinFile;
private MultipartFile sujinFile2;
}
// 파일이 포함 안되어 있을 때 @RequestBody 필요
// @PostMapping("/sujins")
// public int insertSujin(@RequestBody SujinVO sujinVO) {
// return sujinService.insertSujin(sujinVO);
// }
// 파일이 포함 되어 있을 때 @RequestBody 불필요
@PostMapping("/sujins")
public int insertSujin(SujinVO sujinVO) {
log.debug("체킁 sujinVO {}", sujinVO); // 객체가 온다는 표시로 {}. ,를 구분자로 사용
return 1;
//return sujinService.insertSujin(sujinVO);
}
// post로 insert 1개 row(sujin), 파일 전송용
const fPostInput = () => {
// 아작스 파일 전송 시 무조건 FormData 필요, 전송 인코딩 방식문제!
let formData = new FormData(); // 매개변수로 form 객체를 주면 자동으로 입력
formData.append("sujinName", myForm.sujinName.value);
formData.append("sujinContent", myForm.sujinContent.value);
formData.append("sujinFile2", myForm.sujinFile2.files[0]);
// formData에 들어있는 값 눈으로 확인!
for(let [key, value] of formData) {
console.log("체킁 : ", key, value);
}
$.ajax({
type: "post",
url: "/api/sujins",
contentType: false, // 필수
processData: false, // 필수
cache: false, // 옵션
data: formData, // 문자열화(시리얼라이즈) 하면 안됨
dataType: "text",
success: function(res){
console.log("항상 결과 확인! : ", res);
if(res == 1) {
alert("정말 잘 입력되었네용~!");
// tr 태그만 한개 맹글어서 추가해도 되공
fGetList(); // 리스트 다시 뿌리깅
myForm.sujinNum.value = "";
myForm.sujinName.value = "";
myForm.sujinContent.value = "";
myForm.sujinFile.value = "";
setTimeout(() => { // 변칙 테크닉
myList.scrollTo(0, myList.scrollHeight); // 스크롤 끝으로 내리깅!
}, 50);
}
}
});
}
// 파일이 포함 되어 있을 때 @RequestBody 불필요
@PostMapping("/sujins")
public int insertSujin(SujinVO sujinVO) throws Exception {
log.debug("체킁 sujinVO {}", sujinVO); // 객체가 온다는 표시로 {}. ,를 구분자로 사용
MultipartFile recFile = sujinVO.getSujinFile2();
String baseFolder = "d:/myUpload/";
String fileName = recFile.getOriginalFilename();
recFile.transferTo(new File(baseFolder + fileName)); // 실제 파일 저장 경로를 작성
String webBase = "/mcimg/";
sujinVO.setSujinFile(webBase + fileName); // 웹 접근 경로를 작성
return sujinService.insertSujin(sujinVO);
}
// 테이블 맹그는 함수
const fMakeTable = (sujins) =>{
let tableStr = `<table border=1 style="width:100%"><tbody>`;
tableStr += `<tr><th>Num</th><th>Name</th><th>Content</th><th>File</th></tr>`;
for(let i=0; i<sujins.length; i++){
let sujin = sujins[i];
tableStr += `<tr onclick="fGetOne(${sujin.sujinNum})">`;
tableStr += `<td>${sujin.sujinNum}</td>`;
tableStr += `<td>${sujin.sujinName}</td>`;
tableStr += `<td>${sujin.sujinContent}</td>`;
tableStr += `<td><a href="${sujin.sujinFile}" download="민채.jpg">다운로드</a></td>`;
tableStr += `</tr>`;
}
tableStr += `<tr></tbody></table>`;
myList.innerHTML = tableStr;
//테이블이 동적으로 새로 맹글어지므로, TR이벤트도 그때마당
fTrClickMouseOverOut();
}
// 테이블 맹그는 함수
const fMakeTable = (sujins) =>{
let tableStr = `<table border=1 style="width:100%"><tbody>`;
tableStr += `<tr><th>Num</th><th>Name</th><th>Content</th><th>File</th></tr>`;
for(let i=0; i<sujins.length; i++){
let sujin = sujins[i];
tableStr += `<tr onclick="fGetOne(${sujin.sujinNum})">`;
tableStr += `<td>${sujin.sujinNum}</td>`;
tableStr += `<td>${sujin.sujinName}</td>`;
tableStr += `<td>${sujin.sujinContent}</td>`;
tableStr += `<td><a href="${sujin.sujinFile}" download="민채.jpg"><img src="${sujin.sujinFile}" width=50 height=50 /></a></td>`;
tableStr += `</tr>`;
}
tableStr += `<tr></tbody></table>`;
myList.innerHTML = tableStr;
//테이블이 동적으로 새로 맹글어지므로, TR이벤트도 그때마당
fTrClickMouseOverOut();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// destructure (구조분해)
// 프론트엔드 프레임워크에서 특히 아주 많이 사용됨!
// 별거 아님!
// 1. 배열 비구조화 할당
let myArr = ["흥", "치", "뿡"];
[aaa, , bbb] = myArr; // 배열에서 뽑아오깅
alert(aaa); // 흥, 첫 번째 거
alert(bbb); // 뿡, 세 번째 거
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// destructure (구조분해)
// 프론트엔드 프레임워크에서 특히 아주 많이 사용됨!
// 별거 아님!
// 1. 배열 비구조화 할당
let myArr = ["흥", "치", "뿡"];
[aaa, , bbb] = myArr; // 배열에서 뽑아오깅
//alert(aaa); // 흥, 첫 번째 거
//alert(bbb); // 뿡, 세 번째 거
[, ...sec] = myArr; // ...은 Spread Operator(전개 연산자)라 불림
alert(sec); // 치, 뿡
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// destructure (구조분해)
// 프론트엔드 프레임워크에서 특히 아주 많이 사용됨!
// 별거 아님!
// 1. 배열 비구조화 할당
let myArr = ["흥", "치", "뿡"];
[aaa, , bbb] = myArr; // 배열에서 뽑아오깅
//alert(aaa); // 흥, 첫 번째 거
//alert(bbb); // 뿡, 세 번째 거
[, ...sec] = myArr; // ...은 Spread Operator(전개 연산자)라 불림
//alert(sec); // 치, 뿡
let ccc = ["흥", "치", "피"];
let ddd = ["헉", "퍽", "윽"];
let hap = [...ccc, ...ddd, "메롱 안 졸려"]; // 배열 ccc, ddd 합치깅
alert(hap);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// destructure (구조분해)
// 프론트엔드 프레임워크에서 특히 아주 많이 사용됨!
// 별거 아님!
// 1. 배열 비구조화 할당
let myArr = ["흥", "치", "뿡"];
[aaa, , bbb] = myArr; // 배열에서 뽑아오깅
//alert(aaa); // 흥, 첫 번째 거
//alert(bbb); // 뿡, 세 번째 거
[, ...sec] = myArr; // ...은 Spread Operator(전개 연산자)라 불림
//alert(sec); // 치, 뿡
let ccc = ["흥", "치", "피"];
let ddd = ["헉", "퍽", "윽"];
let hap = [...ccc, ...ddd, "메롱 안 졸려"]; // 배열 ccc, ddd 합치깅
//alert(hap);
function fCheck(...all){
console.log("체킁 : ", all);
alert(all[3]); // 세
}
fCheck("안", "녕", "하", "세", "요");
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// destructure (구조분해)
// 프론트엔드 프레임워크에서 특히 아주 많이 사용됨!
// 별거 아님!
// 1. 배열 비구조화 할당
let myArr = ["흥", "치", "뿡"];
[aaa, , bbb] = myArr; // 배열에서 뽑아오깅
//alert(aaa); // 흥, 첫 번째 거
//alert(bbb); // 뿡, 세 번째 거
[, ...sec] = myArr; // ...은 Spread Operator(전개 연산자)라 불림
//alert(sec); // 치, 뿡
let ccc = ["흥", "치", "피"];
let ddd = ["헉", "퍽", "윽"];
let hap = [...ccc, ...ddd, "메롱 안 졸려"]; // 배열 ccc, ddd 합치깅
//alert(hap);
// p1, p2는 필수 옵션, 나머지 ...all 에 선택 옵션으로 들어갈 때 사용.
function fCheck(p1, p2, ...all){
console.log("체킁 : ", all);
alert(all[2]); // 요
}
fCheck("안", "녕", "하", "세", "요");
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// destructure (구조분해)
// 프론트엔드 프레임워크에서 특히 아주 많이 사용됨!
// 별거 아님!
// 1. 배열 비구조화 할당
let myArr = ["흥", "치", "뿡"];
[aaa, , bbb] = myArr; // 배열에서 뽑아오깅
//alert(aaa); // 흥, 첫 번째 거
//alert(bbb); // 뿡, 세 번째 거
[, ...sec] = myArr; // ...은 Spread Operator(전개 연산자)라 불림
//alert(sec); // 치, 뿡
let ccc = ["흥", "치", "피"];
let ddd = ["헉", "퍽", "윽"];
let hap = [...ccc, ...ddd, "메롱 안 졸려"]; // 배열 ccc, ddd 합치깅
//alert(hap);
// p1, p2는 필수 옵션, 나머지 ...all 에 선택 옵션으로 들어갈 때 사용.
function fCheck(p1, p2, ...all){
console.log("체킁 : ", all);
alert(all[2]); // 요
}
//fCheck("안", "녕", "하", "세", "요");
// 2. 객체 비구조화 할당
let minChae = {
name : "오 민채",
role : "여주인공"
};
let {name} = minChae;
alert("이름 : " + name);
</script>
</body>
</html>
* LightHouse?
- 프론트 개발자(FE : 프론트 엔지니어)
- 엔지니어? 엔진에서 나옴. 성능의 대표적인 대명사. 즉, 성능개선. 프론트엔드 분야가 크고 무거워 졌다.
- 엔지니어가 붙으면 10만불.
- 이 성능을 평가하는 툴이 라이트하우스. 구글은 크롬 > 자기 개발 툴에 기본적으로 넣어 버렸다.
- jsp로 만들면 서버 사이드 렌더링(SSR)이 된다. SEO 점수가 높다.
- 비동기로 만들면 CSR이라 부른다. 클라이언트 사이드 렌더링. SEO는 낮다.
- SPA(Single Page Application)이 검색 엔진 최적화(SEO)에 약하므로 서버 사이드 렌더링 기술을 추가하여 작성해야 한다.
- SSG : Server Side Generation. 요즘 회사들은 성능에 민감한 시대가 와서 물어보는 경우가 있다. 현실에 비유하면 길동이가 운영하는 맛집 식당이 있는데, 메인 메뉴가 고르곤졸라 피자. 손님들이 오후 6시에 많이 사용한다면 미리 피자를 많이 만들어 두는 것. 페이지를 미리 생성해 두는 것. 요청이 왔을 때 페이지를 생성하지 않고 만들어 둔 것을 바로 출력. 고정적인 통계 자료를 기반으로 해야 한다. 미리 만들어 둔 것을 버려야 하고, 또 성능 저하가 올 수 있다.
* NGROK? : 무료 도메인(1개만 무료로 쓸 수 있음), https를 사용할 수 있다.
* 고유 인증 키 값 복사
- https://witty-finally-grouper.ngrok-free.app/
* 네트워크 터널링(멋진 말), 트로이목마(나쁜 말)
'대덕인재개발원_final project' 카테고리의 다른 글
(18) 토요일 수업 3 (0) | 2024.01.08 |
---|---|
(17) Tomcat Servers 설정 (1) | 2024.01.05 |
(15) Oracle SQL 쿼리문 (2) | 2024.01.04 |
(14) 보강 10 (2) | 2024.01.04 |
(13) 보강 9 (1) | 2024.01.03 |