CDN : 콘텐츠 전송 네트워크 서비스
이미지, 동영상, 문서파일 같은 것만 가능
(.html, .jsp, .htm, .js, .css 이런건 절대 안올라감)
E-러닝, 스트리밍같은 분야에 사용
쇼핑몰 상품판매
파일 업로드, 리스트 출력, 검색, 삭제
Oracle 테이블
/*
CLOB (character large object) : ASCII 파일 내용
BLOB (binary large object) : binary 파일 내용
*/
create table api_img(
AIDX NUMBER(5) GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) NOT NULL, -- 시퀀스 없이 쓰는 구조
ORI_FILE NVARCHAR2(100) NOT NULL, -- 홍길동.JPG 이거 없애고 쓰는 구조가 많음 중복때문에
NEW_FILE NVARCHAR2(100) NOT NULL, -- 20250415.JPG
API_FILE NVARCHAR2(100) NOT NULL, -- 20250415
FILE_URL LONG NOT NULL, -- http://abc.co.kr/product_img/
FILE_BIN BLOB NULL, -- 이미지의 파일내용들을 모두 저장하는 자료형
FILE_DATE TIMESTAMP DEFAULT SYSDATE NOT NULL,
PRIMARY KEY(AIDX)
);
select * from api_img; -- STS4에서 긴 자료형들때문에 결과가 안보임
select ORI_FILE from api_img;
file_DTO.java
package kr.co.koo;
import org.springframework.stereotype.Repository;
import lombok.Data;
@Data
@Repository("file_DTO")
public class file_DTO {
int AIDX;
String ORI_FILE, NEW_FILE, API_FILE, FILE_URL, FILE_DATE;
byte[] FILE_BIN; //BLOB 자료형은 binary라서 byte에 저장 (오라클에는 있고 mysql에는 없음)
}
upload.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일 전송 FTP</title>
</head>
<body>
<form id="frm" method="post" action="./cdn_fileok.do" enctype="multipart/form-data">
<input type="hidden" name="url" value="kbsn.or.kr">
파일첨부 : <input type="file" name="mfile"><br>
<button type="button" id="btn">CDN 파일 업로드</button>
</form>
</body>
<script>
document.querySelector("#btn").addEventListener("click",function(){
if(frm.mfile.value==""){
alert("파일을 첨부해주세요");
}else{
var mb = 1024 * 1024 * 2; //2메가바이트
var fileck = frm.mfile.files[0].size; //멀티파일일경우 반복문 필요
console.log(fileck);
if(mb < fileck){
alert("파일 첨부는 최대 2MB 이하로 해주세요");
frm.mfile.value="";
}else{
var types = frm.mfile.files[0].type;
if(types!="image/png" && types!="image/jpg" && types!="image/jpeg" && types!="image/bmp" && types!="image/webp"){
alert("파일 첨부는 이미지파일만 첨부해주세요");
}else{
frm.submit();
}
}
}
});
</script>
</html>
cdn_mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.co.koo.cdn_mapper">
<!-- 파라미터값 잘 확인하기 -->
<insert id="cdn_insert" parameterType="kr.co.koo.file_DTO">
INSERT INTO API_IMG (ORI_FILE,NEW_FILE,API_FILE,FILE_URL,FILE_BIN,FILE_DATE)
VALUES (#{ORI_FILE},#{NEW_FILE},#{API_FILE},#{FILE_URL},#{FILE_BIN},SYSDATE)
</insert>
<!-- 버전마다 BLOB 사용방법이 오라클 g/c와 마이바티스의 버전 2점대와 3점대 다름 : c는 그냥 이렇게 쓰면 됨 -->
<!-- 맵퍼끼리 이름은 같게, 서비스는 달라도 됨 -->
<!-- 비슷한 쿼리문 많으면 매퍼, 서비스파일 좀 고치더라도 if, choose when 등 사용하여 만들기! -->
<select id="cdn_select" resultType="kr.co.koo.file_DTO" parameterType="Map">
select * from API_IMG
<if test="part==1">
where AIDX=#{AIDX} order by AIDX desc
</if>
<if test="part==2">
order by AIDX desc
</if>
<!-- part3
사용자가 업로드한 파일명으로 검색
mysql에서는 concat사용 -->
<if test="part==3">
where ORI_FILE like '%' || #{word} || '%' order by AIDX desc
</if>
</select>
<select id="cdn_images" resultType="kr.co.koo.file_DTO" parameterType="String">
select FILE_URL from API_IMG where API_FILE=#{APINO}
</select>
<delete id="cdn_delete" parameterType="String">
delete from API_IMG where AIDX=#{AIDX}
</delete>
</mapper>
cdn_mapper.java
package kr.co.koo;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface cdn_mapper {
int cdn_insert(file_DTO dto);
public List<file_DTO> cdn_select(Map<String, Object> map);
// CDN에 파라미터값을 대입하여 해당 이미지에 대한 실제 경로를 로드하는 맵퍼
public List<file_DTO> cdn_images(String APINO);
// 데이터베이스 데이터 삭제
int cdn_delete(String AIDX);
}
cdn_service.java
package kr.co.koo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class cdn_service {
@Autowired
cdn_mapper mp;
public int cdn_insert(file_DTO dto) {
int result = this.mp.cdn_insert(dto);
return result;
}
public List<file_DTO> all(Integer part, file_DTO dto) {
Map<String, Object> map = new HashMap<>();
map.put("part", part);
if(part == 1) { //고유값으로 해당하는 데이터만 찾는 경우
map.put("AIDX", dto.getAIDX());
}else if(part == 3) { //검색어로 데이터를 찾는 경우
map.put("word", dto.getWord());
}
System.out.println(map);
List<file_DTO> result = this.mp.cdn_select(map);
return result;
}
public List<file_DTO> cdn_images(String APINO) {
List<file_DTO> result = this.mp.cdn_images(APINO);
return result;
}
int cdn_delete(String AIDX) {
int result = this.mp.cdn_delete(AIDX);
return result;
}
}
cdn_controller.java
package kr.co.koo;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
//import java.sql.Connection;
//import java.sql.PreparedStatement;
//import java.sql.ResultSet;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
//CDN 전용 컨트롤러
@Controller
public class cdn_controller {
Logger log = LoggerFactory.getLogger(this.getClass());
@Resource(name="file_DTO")
file_DTO file_DTO;
@Resource(name="cdn_model")
cdn_model cdn_model;
@Autowired
private cdn_service cs;
// 원래 디비 오토와이어드 쓰면 안됨 리소스로 불러오기
// @Autowired
// dbinfos dbinfos;
PrintWriter pw = null;
//CDN 서버로 파일 전송 및 DB 저장
@PostMapping("/cdn/cdn_fileok.do")
public String cdn_fileok(
@RequestParam(name="mfile") MultipartFile mfile[],
@RequestParam(name="url") String url,
ServletResponse res
) throws Exception{
//처음부터 배열로 받아야 나중에 기획에 파일 여러개로 받는거로 바뀌었을때 편함
try {
//사용자가 업로드한 파일명을 개발자가 원하는 이름으로 변경 후 FTP 전송
boolean result = this.cdn_model.cdn_ftp(mfile[0], url);
res.setContentType("text/html;charset=utf-8");
this.pw = res.getWriter();
if(result==true) {
this.pw.print("<script>"
+ "alert('정상적으로 등록 완료');"
+ "location.href='./cdn_filelist.do';"
+ "</script>");
}else {
this.pw.print("<script>"
+ "alert('등록 실패');"
+ "history.go(-1);"
+ "</script>");
}
}catch (Exception e) {
System.out.println(e);
}finally {
}
return null;
}
//Controller에서 모든 리스트 화면을 출력시
//1. 한 페이지당 출력하는 데이터 갯수
//2. 데이터 검색어를 받을 경우
//3. 사용자가 페이지 번호를 누를 경우
@GetMapping("/cdn/cdn_filelist.do")
public String cdn_filelist(
@RequestParam(name="word", required = false, defaultValue = "") String word,
Model m){
List<file_DTO> all = null;
if(word.equals("")) { //검색어가 없을 경우
all = this.cs.all(2, this.file_DTO); //part 2로 보냄 => 전체리스트
} else { //검색어가 있을 경우
this.file_DTO.setWord(word); //setter로 DTO에 값을 이관
all = this.cs.all(3, this.file_DTO); //part 3으로 보냄 => 검색
}
m.addAttribute("all", all);
return null;
}
//@ResponseBody : return 값을 즉시실행하여 해당 메소드에 출력하는 역할
//=> 뷰없다이거! 라고 생각하기
@GetMapping("/cdn/image/{api_id}")
@ResponseBody
public byte[] image(
@PathVariable(name="api_id") String id
) throws Exception{
//ResponseBody 해당 메소드에서 출력 => view 따로 없이 바로 전달
byte[] img = null;
if(!id.equals(null) || !id.equals("")) {
List<file_DTO> result = this.cs.cdn_images(id);
try {
URL url = new URL(result.get(0).getFILE_URL());
//AWS, GCP => https, http 둘 다 존재 조심
HttpURLConnection hc = (HttpURLConnection)url.openConnection();
InputStream is = hc.getInputStream();
img = IOUtils.toByteArray(is);
}catch (Exception e) {
this.log.info("CDN 연결실패 : " + e.toString());
}
}
return img;
}
/*
응용문제 : FTP에 파일이 없을 경우 DB가 삭제되지않는 버그가 발생함
해당 버그를 수정하여 파일없어도 삭제되도록 수정
*/
@PostMapping("/cdn/cdn_delete.do")
public String cdn_delete(@RequestParam(name = "cbox") String cbox[]) throws Exception {
// this.log.info(String.valueOf(cbox.length));
int countck = 0; // 성공한 결과만 +1 증가시키는 변수
for (String idx : cbox) {
this.file_DTO.setAIDX(Integer.parseInt(idx)); // 필드에있는 dto에 setter로 이관
this.log.info("idx 확인 : " + idx);
List<file_DTO> one = this.cs.all(1, this.file_DTO); // 서비스에 dto값 전달
//삭제
boolean result = this.cdn_model.cdn_ftpdel(one.get(0).getNEW_FILE(), idx); // 개발자가 생성한 파일명 출력
if (result == true) { // 파일 삭제도 되고, 데이터베이스도 삭제가 된 경우
countck++;
}
}
if (cbox.length == countck) {
this.log.info("정상적으로 모두 삭제 완료 되었습니다.");
} else {
this.log.info("삭제되지 않은 데이터가 있습니다.");
}
return "redirect:/cdn/cdn_filelist.do";
}
/* 방법1
@GetMapping("/cdn/download.do")
public void downloads(@RequestParam(name="filenm") String filenm,
HttpServletResponse res
) throws Exception {
// ftp:// 이런 URL 일때 사용
// 외부에서 이미지 및 파일, 동영상을 FTP URL로 읽어서 byte로 변환시 무조건 File 객체 이용
//File f = new File(filenm); //FTP 경로를 로드한 후에 byte로 변환
//byte[] files = FileUtils.readFileToByteArray(f.gets);
URL url = new URL(filenm);
HttpURLConnection httpcon = (HttpURLConnection)url.openConnection();
InputStream is = new BufferedInputStream(httpcon.getInputStream());
byte[] files = is.readAllBytes();
// this.log.info(files.toString());
res.setHeader("content-transfer-encoding", "binary");
res.setContentType("application/x-download");
OutputStream os = res.getOutputStream();
IOUtils.copy(is, os);
os.flush();
os.close();
is.close();
}
*/
//방법 2
//사용자가 해당 파일을 클릭시 파일을 자신의 PC, Mobile에 다운로드할 수 있는 API 메소드
@GetMapping("/cdn/download.do/{filenm}")
public void downloads(@PathVariable (name="filenm") String filenm,
HttpServletResponse res
) throws Exception {
//서버 경로에 맞는 파일을 로드
URL url = new URL("http://kbsn.or.kr/koona0/" + filenm);
//http
HttpURLConnection httpcon = (HttpURLConnection)url.openConnection();
//해당 경로에 이미지를 byte 읽어들임
InputStream is = new BufferedInputStream(httpcon.getInputStream());
//해당 Controller에서 파일을 다운로드 받을 수 있도록 처리
res.setHeader("content-transfer-encoding", "binary");
res.setContentType("application/x-download");
OutputStream os = res.getOutputStream(); //파일을 저장할 수 있도록 설정
IOUtils.copy(is, os); //서버에 있는 값을 PC로 복제
os.flush();
os.close();
is.close();
}
}
cdn_model.java
package kr.co.koo;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
@Repository("cdn_model")
public class cdn_model {
Logger log = LoggerFactory.getLogger(this.getClass());
//FTP 정보를 외부에서 수정하지 못하게 설정
final String user = "아이디";
final String pass = "비번";
final int port = 21;
FTPClient fc = null;
FTPClientConfig fcc = null;
boolean result = false; //FTP 전송 결과 true(정상) false(오류발생)
@Autowired
private cdn_service cs;
//@Resource로 Controller에서 호출과 Model에서 호출이 다름 => 각각 new해버리는 상황
@Resource(name="file_DTO")
file_DTO file_DTO;
// CDN Server로 해당 파일을 전송하는 역할을 담당한 Model
public boolean cdn_ftp(MultipartFile files, String url) throws Exception{
try {
//신규파일명
String new_file = rename_file(files.getOriginalFilename());
// System.out.println(new_file);
this.fc = new FTPClient();
this.fc.setControlEncoding("utf-8");
this.fcc = new FTPClientConfig();
this.fc.configure(fcc);
this.fc.connect(url, this.port); //FTP 연결
if(this.fc.login(this.user, this.pass)) { //FTP 로그인
//BINARY_FILE_TYPE : 이미지, 동영상, PDF, ZIP...
this.fc.setFileType(FTP.BINARY_FILE_TYPE);
//FTP 디렉토리 경로를 설정 후 해당 파일을 Byte로 전송
this.result = this.fc.storeFile("/koona0/"+new_file, files.getInputStream());
// System.out.println(files.getOriginalFilename());
//Oracle : CLOB(Ascii), BLOB(Binary)
this.file_DTO.setFILE_BIN(files.getBytes());
//DB 저장
this.file_DTO.setORI_FILE(files.getOriginalFilename());
this.file_DTO.setNEW_FILE(new_file);
//html 이미지태그 주소에 .jpg이런거 쓰면 CDN쓰는거 아님 !!! 주의
String api_nm[] = new_file.split("[.]");// .으로 스플릿하면 인식못함 .은 모든글자를 의미 [.]이나 \\.으로 사용
System.out.println("apinm : "+Arrays.toString(api_nm));
this.file_DTO.setAPI_FILE(api_nm[0]); //그래서 속성명 날리고 넣어야함
String api_url = "http://"+url+"/koona0/"+new_file; //https일때 https로 쓰기
this.file_DTO.setFILE_URL(api_url);
//Database에 insert부분 문제 발생시 false
int result2 = this.cs.cdn_insert(this.file_DTO);
if(result2 > 0) {
this.result = true;
}else {
this.result = false;
}
}
} catch (Exception e) {
System.out.println("model e: "+e);
} finally {
this.fc.disconnect(); //FTP Service 연결을 종료
}
return this.result;
}
//실제 파일명을 개발자가 원하는 이름으로 변경하는 메소드
public String rename_file(String ori_file) {
//오늘날짜+시간
Date today = new Date();
SimpleDateFormat sf = new SimpleDateFormat("yyyyMMddHHmmss");
String day = sf.format(today);
//난수
int no = (int)Math.ceil(Math.random()*10);
//파일의 속성명을 담는 변수
String type = ori_file.substring(ori_file.lastIndexOf("."));
//개발자가 원하는 파일명으로 변경완료 코드
String new_file = day + no + type;
return new_file;
}
// CDN Server에 있는 해당 파일을 삭제하는 역할 model
public boolean cdn_ftpdel(String newfiles, String idx) throws Exception {
this.fc = new FTPClient();
this.fcc = new FTPClientConfig();
this.fc.configure(this.fcc);
this.fc.connect("주소", this.port);
this.fc.enterRemotePassiveMode(); // 패시브모드로 전환 (FTP 접속환경)
// PassiveMode : 가상 포트를 이용하여 연결을 설정
//this.fc.removeDirectory("/koona0"); //디렉토리 통째로 삭제
//this.fc.makeDirectory("/koona1"); //디렉토리 생성 (오늘날짜의 폴더 만들기같은거)
//removeDirectory : 디렉토리 삭제
//makeDirectory : 디렉토리 생성
// deleteFile : FTP에 접속된 서버에서 해당 경로에 있는 파일을 삭제하는 class
/*
//기존 에러발생 코드
this.fc.deleteFile("/koona0/" + newfiles);
if(this.result ==true) { //정상적으로 FTP 파일을 삭제 한 후
int r = this.cs.cdn_delete(idx);
}
*/
if (this.fc.login(this.user, this.pass)) { // 로그인
// 파일 존재하는지 확인
if (isFileExistsOnFtp(newfiles)) { // 파일이 ftp에 있으면
this.result = this.fc.deleteFile("/koona0/" + newfiles); // ftp에서 삭제
if (this.result) { //ftp에서 삭제됐으면
int r = this.cs.cdn_delete(idx); //db에서 삭제
if (r > 0) {
this.result = true;
} else {
this.result = false;
}
}
}
else { // 파일이 ftp에 없으면
int r = this.cs.cdn_delete(idx); // DB에서 삭제
if (r > 0) {
this.result = true;
} else {
this.result = false;
}
}
}
return this.result;
}
// FTP Server에 해당 파일이 있는지 확인하는 메소드
public boolean isFileExistsOnFtp(String filePath) throws Exception {
FTPClient tempFc = new FTPClient();
FTPClientConfig tempFcc = new FTPClientConfig();
tempFc.configure(tempFcc);
tempFc.connect("주소", this.port);
boolean exists = false;
try {
if (tempFc.login(this.user, this.pass)) {
tempFc.enterLocalPassiveMode(); // 또는 tempFc.enterRemotePassiveMode(); 환경에 따라 선택
FTPFile[] files = tempFc.listFiles("/koona0/");
if (files != null) {
for (FTPFile file : files) {
if (file.isFile() && file.getName().equals(filePath)) {
exists = true;
break;
}
}
}
tempFc.logout();
}
} catch (Exception e) {
log.error("FTP 파일 존재 확인 오류: {}", e.getMessage());
throw e;
} finally {
if (tempFc.isConnected()) {
try {
tempFc.disconnect();
} catch (Exception e) {
log.error("FTP 연결 종료 오류: {}", e.getMessage());
}
}
}
return exists;
}
}
파일 업로드시 Oracle 데이터베이스에 정보가 저장되고 이미지는 CDN 서버에 저장됨
출력 view
cdn_filelist.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="cr" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="java.util.Date"%>
<% Date date = new Date();%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>cdn 서버 이미지 리스트 목록</title>
<link rel="stylesheet" href="../css/bootstrap.css?v=<%=date%>">
<script src="../js/bootstrap.js?v=<%=date%>"></script>
</head>
<body>
<form id="frm" method="post" action="./cdn_delete.do">
<table class="table table-bordered">
<thead>
<tr align="center">
<th><input type="checkbox"></th>
<th>이미지</th>
<th>사용자 파일명</th>
<th>개발자 파일명</th>
<th>api 파일명</th>
</tr>
</thead>
<tbody id="ls">
<cr:forEach var="fdata" items="${all}">
<tr align="center">
<td><input type="checkbox" name="cbox" class="ck" value="${fdata.AIDX }"></td>
<td><img src="http://localhost:8080/cdn/image/${fdata.API_FILE }" style="width:100px;"></td>
<td>${fdata.ORI_FILE }</td>
<!--
해당 파일을 클릭시 새탭이 오픈되어 보임
<td><a href="${fdata.FILE_URL }" target="_new"> ${fdata.NEW_FILE }</a></td>
-->
<!-- 방법1
<td><a href="./download.do?filenm=${fdata.FILE_URL }"> ${fdata.NEW_FILE }</a></td>
-->
<!-- 방법2
-->
<td><a href="./download.do/${fdata.NEW_FILE }"> ${fdata.NEW_FILE }</a></td>
<!-- 방법2-2
download 속성은 상대경로에서는 발동하지만 절대경로에서는 안됨 아래는 절대경로라서 다운이 안됨
http, https 불가능, 단 로컬호스트에서 /abc.jpg 와 download=abc.jap 이런식이면 가능
<td><a href="${fdata.FILE_URL }" download="${fdata.NEW_FILE }"> ${fdata.NEW_FILE }</a></td>
-->
<td>${fdata.API_FILE }</td>
</tr>
</cr:forEach>
</tbody>
</table>
</form>
<br>
<!-- input은 기본으로 submit 기능이 있음 -->
<form id="search" method="get" action="./cdn_filelist.do">
<div class="input-group mb-3">
<input type="text" name="word" class="form-control" placeholder="검색할 파일명을 입력해주세요">
<button class="btn btn-outline-secondary" type="button" id="sh_btn" >검색</button>
<button class="btn btn-outline-secondary" type="button" id="sh_btn2" >전체목록</button>
</div>
</form>
<button type="button" id="btn" class="btn btn-babypink">선택삭제</button>
</body>
<script type="module">
import {cdn_lists} from "./cdn.js";
document.querySelector("#btn").addEventListener("click",function(){
new cdn_lists().cdn_listdel();
});
document.querySelector("#sh_btn").addEventListener("click",function(){ //서브밋이면 "submit"하고 펑션안에 뭐 넣어서 기본값 잡아줘야함
new cdn_lists().cdn_search();
});
document.querySelector("#sh_btn2").addEventListener("click",function(){ //서브밋이면 "submit"하고 펑션안에 뭐 넣어서 기본값 잡아줘야함
location.href='./cdn_filelist.do';
});
</script>
</html>
cdn.js
export class cdn_lists {
cdn_listdel() {
//querySelector : 한 개의 오브젝트를 가져옴
//querySelectorAll : 한 개 이상의 같은 이름을 가진 오브젝트를 가져오는 역할
var ob = document.querySelectorAll("#ls .ck"); //ls아이디 안에 ck클래스 끌고옴
console.log(ob);
var i = 0;
var count = 0;
do {
if (ob[i].checked == true) {
count++;
}
i++;
} while (i < ob.length);
if (count == 0) {
alert("하나이상 선택해주세요.");
} else {
if (confirm("정말삭제하시겠습니까?")) {
frm.submit();
}
}
}
//검색 기능
cdn_search() {
if(search.word.value==""){
alert("검색어를 입력하세요");
}else{
search.submit();
}
}
}