Spring

부트캠프88일차(ajax 2)

동곤일상 2025. 6. 17. 17:24
반응형

1) text파일과 ajax활용 select바 활성화

2) 웹크롤링 (ajax활용)

2-1) html(문자열)전송방식

2-2) Map객체(JSON방식)  더 많이사용

 

3) 작성자 별 게시판 등록갯수 조회

 


1) text파일과 ajax활용 select바 활성화

sido.txt
0.70MB
위의 txt파일을 webapp/data/에 넣음

 

 

layout쪽에 해당 스크립트를 추가해주자

    $(function(){
		getSido();
    })
    function getSido(){

    	$.ajax({
			url:"/ajax/select1",
			success:function(data){
				console.log(data)
				let arr=data.substring
					(data.indexOf('[')+1, data.indexOf(']')).split(",");
			$.each(arr,function(i,item){
					$("select[name=si]").append(function(){
						return "<option>"+item+"</option>"
					})
				})
			}
    	})

    }

스크립트에서 사용한 substring문의 이해를 돕기위한 코드

 

 

ajaxController의 일부분

package kr.gdu.controller;

@RestController
@RequestMapping("ajax")
public class AjaxController {	
	@Autowired
	BoardService service;	
	
	//produces="text/plain; charset=UTF-8" : 전송될 데이터의 형식
	@RequestMapping(value="select1",produces = "text/plain; charset=UTF-8")
	public String sidoSelect1(String si,String gu) {
		return service.sidoSelect1(si,gu);
	}	
}

 

 

BoardService의 sidoSelect1메서드 부분

	public String sidoSelect1(String si, String gu) {
		BufferedReader fr = null;
		String path  = UPLOAD_IMAGE_DIR+"data/sido.txt";
		try {
			//sido.txt파일을 읽어 fr에저장
			fr = new BufferedReader(new FileReader(path));
		} catch (Exception e) {
			e.printStackTrace();
		}
		Set<String> set = new LinkedHashSet<>();
		String data = null;
		if(si==null&&gu==null) {
			try {
				while((data=fr.readLine())!=null) {
					// '\\s+' : 1개이상의 공백
					String[] arr = data.split("\\s+");
					if(arr.length>=3) {
						//시군구가 존재할 때 시만 set에 넣는다(중복방지)
						set.add(arr[0].trim());
					}
				}
			}
			catch (IOException e) {
				e.printStackTrace();
			}
		}
		List<String> list = new ArrayList<>(set);
		return list.toString();//[서울특별시,경기도,강원도,,..,,]
	}

 

컨트롤러와서비스 구현을 마쳤다면

다음과같이 console을 확인가능함 

아까 [ ,] 를제외하고 , 를 기준으로 배열을 만들었음


1-2) 이제 시를 선택하면 그 시에 해당하는 구 동을 뽑아보자

 

layout.jsp의 일부분

<footer class="footer text-muted">
		&copy; 2025 MyAdmin. All <span id="si"> <select name="si"
			onchange="getText('si')">
				<option value="">시도를 선택하세요</option>
		</select>
		</span> <span id="gu"> <select name="gu" onchange="getText('gu')">
				<option value="">구군을 선택하세요</option>
		</select>
		</span> <span id="dong"> <select name="dong">
				<option value="">동리를 선택하세요</option>
		</select>
		</span>
	</footer>


<script>
function getText(name){
		let city = $("select[name='si']").val();
		let gu = $("select[name='gu']").val();
		let disname;
		//최상단에 뜰 text
		let toptext="구군을 선택하세요";
		let params="";
		
		if(name=="si"){
			params = "si="+city.trim();
			disname="gu";
		}
		else if(name="gu"){
			params="si="+city.trim()+"&gu="+gu.trim();
			disname="dong";
			toptext="동리를 선택하세요";	
		}
		else{
			return;
		}
		$.ajax({
				url:"/ajax/select2",
				type:"POST",
				data:params,
				success:function(arr){//서버에서 배열객체로전달받음
   					console.log(arr);
					$("select[name="+disname+"] option").remove();
					$("select[name="+disname+"]").append(function(){
						return "<option value=''>"+toptext+"</option>"
					})
					
					$.each(arr,function(i,item){
						$("select[name="+disname+"]").append(function(){
							return "<option>"+item+"</option>"
						})
					})
					
				}
		    })
    }
</script>

 

요청url이 ajax로 매핑이되니깐 그에 해당하는 ajaxController로 가야겠죠?

 

ajaxController의 일부분

 

/ajax/select2의 요청을 받을 수 있게끔 value를 설정

(List를 반환하므로 produces는 설정X)

@RestController
@RequestMapping("ajax")
public class AjaxController {	
	@Autowired
	BoardService service;	
@RequestMapping(value="select2")
	public List<String> sigunSelect2(String si ,String gu) {
		return service.sigunSelect2(si,gu); //list객체를 클라이언트로 전달
	}
}

 

Service의 일부

public List<String> sigunSelect2(String si, String gu) {		
		BufferedReader fr = null;
		String path  = UPLOAD_IMAGE_DIR+"data/sido.txt";
		try {
			//sido.txt파일을 읽어 fr에저장
			fr = new BufferedReader(new FileReader(path));
		} catch (Exception e) {
			e.printStackTrace();
		}
		Set<String> set = new LinkedHashSet<>();
		String data = null;
		if(si==null&&gu==null) {
			try {
				while((data=fr.readLine())!=null) {
					// '\\s+' : 1개이상의 공백
					String[] arr = data.split("\\s+");
					if(arr.length>=3) {
						//시군구가 존재할 때 시만 set에 넣는다(중복방지)
						set.add(arr[0].trim());
					}
				}
			}
			catch (IOException e) {
				e.printStackTrace();
			}
		}
		else if(gu==null) {
			si = si.trim();
			try {
				while((data=fr.readLine())!=null) {
					String[] arr = data.split("\\s+");
					// 0번쨰요소가 si인
					if(arr.length>=3 && arr[0].equals(si)
					 && !arr[1].contains(arr[0])) {
						
						set.add(arr[1].trim());
				
					}
				}
			}
			catch(IOException e) {
				e.printStackTrace();
			}
		}
		else {
			si = si.trim();
			gu=gu.trim();
			try {
				while((data=fr.readLine())!=null) {
					String[] arr = data.split("\\s+");
					//0번쨰요소가si , 1번쨰요소가gu (중복금지)
					if(arr.length>=3 && arr[0].equals(si)
							 && arr[1].equals(gu) && !arr[0].equals(arr[1])
							&& !arr[2].contains(arr[1])) {
						
						if(arr.length>3) {
                        /*배열크기가 3초과 일 경우에는
                        arr[3]를 arr[2]에 합침
                        */
							if(arr[3].contains(arr[1])) {
								continue;
							}
							arr[2] += " "+arr[3];
						}
						set.add(arr[2].trim());
				
					}
				}
			}
			catch(IOException e) {
				e.printStackTrace();
			}
		}
		ArrayList<String> list = new ArrayList<>(set);
		System.out.println("list ::::: "+list);
		return list;
	}

 

 


2) 웹크롤링 (ajax활용)

두가지의 방식으로 진행해 볼 예정

 

2-1) html(문자열)전송방식

 

layout.jsp의 일부분

 $(function(){
		exchangeRate();
    })
function exchangeRate(){
		$.ajax("/ajax/exchange1",{
			success: function(data){
				console.log(data);
				$("#exchange").html(data)
			},
			error:function(e){
				alert("환율조회시 서버오류발생",e.status)
			}
		})
    }

 

ajaxcontroller

@RequestMapping(value="exchange1",produces = "text/plain; charset=UTF-8")
	public String exchange() {
		//미국달러,중국,일본,유로 4개통화만		
		return service.exchange1();
	}

 

service

(url : 한국수출입은행사이트url)

public String exchange1() {
		Document doc = null;
		List<List<String>> trlist = new ArrayList<>();
		String url = "https://www.koreaexim.go.kr/wg/HPHKWG057M01";
		String exdate = null;
		try {
			doc = Jsoup.connect(url).get();
			Elements trs = doc.select("tr"); //tr태그를 다 가져와
			exdate = doc.select("p.table-unit").html();
			for(Element tr : trs) {
				List<String> tdlist = new ArrayList<>();
				Elements tds = tr.select("td");//tr태그내의 td를 모두가져와
				for(Element td : tds) {
					tdlist.add(td.html());//td태그내의 내용들 (innerHTML이라생각하면댐)
					//[USD,미국달러,1325,..]
				}
				if(tdlist.size()>0) {
					if(tdlist.get(0).equals("USD")|| tdlist.get(0).equals("CNH")
							|| tdlist.get(0).equals("JPY(100)")|| tdlist.get(0).equals("EUR")) {
						trlist.add(tdlist);
					}
				}
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		StringBuilder sb = new StringBuilder();
		sb.append("<h4 class='w3-center'>수출입은행<br>"+exdate+"</h4>");
		sb.append("<table class='w3-table-all'>");
		sb.append("<tr><th>통화</th><th>기준율</th><th>받으실 때</th><th>보내실 때</th></tr>");
		for (List<String> tds : trlist) {
			sb.append("<tr><td>"+tds.get(0)+"<br>"+tds.get(1)+"</td><td>"+tds.get(4)+"</td>");
			sb.append("<td>"+tds.get(2)+"</td><td>"+tds.get(3)+"</td><tr>");
		}
		sb.append("</table>");
		return sb.toString();
	}

 


2-2) Map객체(JSON방식)  더 많이사용

 

layout.jsp의 script부분

function exchangeRate2(){ //json방식
		$.ajax("/ajax/exchange2",{ 
			success: function(json){ //서버Map객체로 전송 , 클라이언트 : JSON형식
				console.log(json);
				let html = "<h4 class='text-center my-3'>수출입은행<br>"+json.exdate +"</h4>";
				html += "<table class='table table-bordered table-hover text-center'>";
				html += "<thead><tr><th>통화</th><th>기준율</th><th>받으실때</th>"
						+"<th>보내실때</th></tr></thead>";			
				$.each(json.trlist,function(i,tds){
					html += "<tr><td>"+tds[0]+"<br>"+tds[1]+"</td><td>"+tds[4]+"</td>"
					+"<td>"+tds[2]+"</td><td>"+tds[3]+"</td></tr>"
				})
				html += "</table>"
				$("#exchange").html(html);
			},
			error:function(e){
				alert("환율조회시 서버오류발생",e.status)
			}
		})
    }

 

Ajaxcontroller

@RequestMapping(value="exchange2")
	public Map<String, Object> exchange2() { //json 데이터로 전송
		return service.exchange2();
	}

 

Service

//JSON
	public Map<String, Object> exchange2() {
		Document doc = null;
		List<List<String>> trlist = new ArrayList<>();
		String url = "https://www.koreaexim.go.kr/wg/HPHKWG057M01";
		String exdate = null;
		try {
			doc = Jsoup.connect(url).get();
			Elements trs = doc.select("tr"); //tr태그를 다 가져와
			//exdate = p태그의 class = table-unit
			exdate = doc.select("p.table-unit").html();
			for(Element tr : trs) {
				List<String> tdlist = new ArrayList<>();
				Elements tds = tr.select("td");//tr태그내의 td를 모두가져와
				for(Element td : tds) {
					tdlist.add(td.html());//td태그내의 내용들 (innerHTML이라생각하면댐)
					//[USD,미국달러,1325,..]
				}
				if(tdlist.size()>0) {
					if(tdlist.get(0).equals("USD")|| tdlist.get(0).equals("CNH")
							|| tdlist.get(0).equals("JPY(100)")|| tdlist.get(0).equals("EUR")) {
						trlist.add(tdlist);
					}
				}
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		Map<String, Object> map = new HashMap<>();
        //map에 담기
		map.put("exdate", exdate);
		map.put("trlist", trlist);
		return map;
	}

 


3) 작성자 별 게시판 등록갯수 조회

 

layout의 body부분

<div id="mainContent" class="main-content">
		<div class="radio-toggle">			
			<div class="btn-group" role="group" aria-label="Pie chart selection">
				<label class="btn btn-outline-primary active"> <input
					type="radio" name="pie" onchange="piegraph(2)" checked>
					자유게시판
				</label> <label class="btn btn-outline-primary"> <input type="radio"
					name="pie" onchange="piegraph(3)"> Q&A
				</label>
			</div>
			<div id="piecontainer" style="width: 100%; height: 250px;"></div>
		</div>
		<sitemesh:write property="body" />
	</div>

자유게시판 , Q&A버튼을 만들어  

클릭 시 각각 다른함수가 호출되게끔 작성함

 

	<script
		src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.min.js"></script>
	<script
		src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
$(function(){
      getSido1();
      //exchangeRate(); //수출입은행 환율정보조회 서버에서 HTML형식(문자열)으로 리턴
      exchangeRate2();   //서버에서 Map형식(JSON)으로 리턴
      piegraph(2) //글쓴이별 게시글 건수를 파이그래프로출력
      boardImg();
    })
    
    let randomColorFactor = function(){
      return Math.round(Math.random()*255)
    }
    
    let randomColor = function(opa){
      return "rgba("+randomColorFactor()+","   
      +randomColorFactor()+","   
      +randomColorFactor()+","   
      +(opa || ".3") + ")";
    } //각각의 색깔
    
    function piegraph(id){
      $.ajax("/ajax/graph1?id="+id,{
         success : function(json){
            // 캔버스 생성 시 높이 속성 추가
            let canvas = "<canvas id='canvas1' style='width:100%; height:100px;'></canvas>"; // 캔버스 높이 직접 지정 (예: 100px)
            $("#piecontainer").html(canvas);
            //canvas에 그래프를 넣을것임
            pieGraphPrint(json,id);
         },
         error : function(e){
            alert("서버오류 : "+e.status);
         }
      })
    }
    
    function pieGraphPrint(arr,id){
      let colors = [];
      let writers = [];
      let datas = [];
      $.each(arr,function(index){
         colors[index] = randomColor(0.5);
         for(key in arr[index]){
            writers.push(key);
            datas.push(arr[index][key]);
         }
      })
      let title = (id == 2)?"자유게시판":"Q&A";
      let config = {
         type:'pie',
         data : {
            datasets : [{ data:datas,
               backgroundColor : colors}],
            labels : writers
         },
         options : {
            responsive : true,
            maintainAspectRatio: false, // 가로-세로 비율 유지를 비활성화합니다.
            legend : {
                display:true,
                position:"right",
                labels: {
                    boxWidth: 15, // 범례 상자의 너비를 더 줄입니다.
                    padding: 5   // 범례 항목 간의 간격을 더 줄입니다.
                }
            },
            title : {
               display : true,
               text : '글쓴이 별 '+title+"등록건수",
               position : 'bottom',
               fontSize: 10 // 제목 글씨 크기를 더 줄입니다.
            },
            layout: {
                padding: {
                    left: 5, // 그래프 내부 여백을 더 줄입니다.
                    right: 5,
                    top: 5,
                    bottom: 5
                }
            },
            elements: {
                arc: {
                    borderWidth: 0 // 조각의 테두리를 제거하여 시각적으로 더 작게 보이게 할 수 있습니다.
                }
            }
         }
      }
      let ctx = document.getElementById("canvas1")
      new Chart(ctx,config)      
    }

 

 


AjaxController

@RequestMapping(value="graph1")
	public List<Map.Entry<String, Integer>> graph1(String id) {
		Map<String,Integer> map = service.graph1(id);
		List<Map.Entry<String, Integer>> list = new ArrayList<>();
		for (Entry<String, Integer> m : map.entrySet()) {
			list.add(m);			
		}
		
		//value 크기별 정렬(내림차순)!!
		Collections.sort(list,(m1,m2)->m2.getValue() - m1.getValue());
		return list;
	}

 

service

(dao와mapper는 생략하겠음  where boardid=#{boardid}로 설정 후

상위7개컬럼의 작성자,작성게시물갯수(count(*))을 뽑는 내용)

public Map<String, Integer> graph1(String id) {
		List<Map<String,Object>> list = boardDao.graph1(id);
		Map<String,Integer> map = new HashMap<>();
		for (Map<String, Object> m : list) {
			String writer = (String)m.get("writer");
			long cnt = (Long)m.get("cnt");
			map.put(writer, (int)cnt);
		}
		
		return map;
	}