관리 메뉴

거니의 velog

231206_SPRING 2 (11-3 : 과제 _ 아이디, 비밀번호 찾기) 본문

대덕인재개발원_웹기반 애플리케이션

231206_SPRING 2 (11-3 : 과제 _ 아이디, 비밀번호 찾기)

Unlimited00 2023. 12. 6. 15:08
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<div class="">
    <div class="card card-outline card-primary">
        <div class="card-header text-center">
            <p class="h4">
                <b>아이디찾기</b>
            </p>
        </div>
        <div class="card-body">
            <p class="login-box-msg">아이디 찾기는 이메일, 이름을 입력하여 찾을 수 있습니다.</p>
            <form action="" method="post" id="idFindForm" name="idFindForm">
                <div class="input-group mb-3">
                    <input type="text" class="form-control" name="memEmail" id="memEmail" placeholder="이메일을 입력해주세요.">
                </div>
                <div class="input-group mb-3">
                    <input type="text" class="form-control" name="memName" id="memName" placeholder="이름을 입력해주세요.">
                </div>
                <div class="input-group mb-3">
                    <p class="idText">
                        회원님의 아이디는 [<font id="id" color="red" class="h2"></font>] 입니다.
                    </p>
                </div>
                <div class="row">
                    <div class="col-12">
                        <button type="button" class="btn btn-primary btn-block" id="idFindBtn">아이디찾기</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
    <br />
    <div class="card card-outline card-primary">
        <div class="card-header text-center">
            <p class="h4">
                <b>비밀번호찾기</b>
            </p>
        </div>
        <div class="card-body">
            <p class="login-box-msg">비밀번호 찾기는 아이디, 이메일, 이름을 입력하여 찾을 수 있습니다.</p>
            <form action="/notice/pwFind.do" method="post" id="pwFindForm" name="pwFindForm">
                <div class="input-group mb-3">
                    <input type="text" class="form-control" id="memId" name="memId" placeholder="아이디를 입력해주세요.">
                </div>
                <div class="input-group mb-3">
                    <input type="text" class="form-control" id="memEmail2" name="memEmail" placeholder="이메일을 입력해주세요.">
                </div>
                <div class="input-group mb-3">
                    <input type="text" class="form-control" id="memName2" name="memName" placeholder="이름을 입력해주세요.">
                </div>
                <div class="input-group mb-3">
                    <p class="pwText">
                        회원님의 비밀번호는 [<font color="red" class="h2" id="password"></font>] 입니다.
                    </p>
                </div>
                <div class="row">
                    <div class="col-12">
                        <button type="button" class="btn btn-primary btn-block" id="pwFindBtn">비밀번호찾기</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
    <br />
    <div class="card card-outline card-secondary">
        <div class="card-header text-center">
            <h4>MAIN MENU</h4>
            <button type="button" class="btn btn-secondary btn-block" id="loginBtn">로그인</button>
        </div>
    </div>
</div>
<script type="text/javascript">
    $(function() {
        // 로그인 페이지를 요청 시, background에 이미지 삽입
        $("body").css("background-image", "url('${pageContext.request.contextPath}/resources/dist/img/background04.jpg')").css("background-size", "cover");
        
        var idFindBtn = $("#idFindBtn");
        var pwFindBtn = $("#pwFindBtn");
        var loginBtn = $("#loginBtn");
        var idFindForm = $("#idFindForm");
        var pwFindForm = $("#pwFindForm");
        
        idFindBtn.on("click", function(){
        	var memEmail = $("#memEmail").val();
        	var memName = $("#memName").val();
        	
        	if(!memEmail) {
        		alert("이메일을 입력해 주세요.");
        		$("#memEmail").focus();
        		return false;
        	}
        	
        	if(!memName) {
        		alert("이름을 입력해 주세요.");
        		$("#memName").focus();
        		return false;
        	}
        	
        	var data = {
        		memEmail : memEmail,
        		memName : memName
        	};
        	
        	$.ajax({
        		type: "post",
        		url: "/notice/idFind.do",
        		data: JSON.stringify(data),
        		contentType: "application/json;charset=utf-8",
        		success: function(res) {
					console.log("결과 : " + res);
					
					// 회원님의 아이디는 [<font id="id" color="red" class="h2"></font>] 입니다.
					if(!res) { // 리턴받은 결과가 없음, 즉 아이디 없음
						$(".idText").html("회원님의 아이디를 찾을 수 없습니다.");
					}else { // 아이디 있음
						var id = res.memId;
						$(".idText").html("회원님의 아이디는 [<font id='id' class='h2' style='color:red'>"+id+"</font>] 입니다.");
					}
				}
        	});
        });
        
        pwFindBtn.on("click", function(){
        	var memId = $("#memId").val();
        	var memEmail = $("#memEmail2").val();
        	var memName = $("#memName2").val();
        	
        	if(!memId) {
        		alert("아이디를 입력해 주세요.");
        		$("#memId").focus();
        		return false;
        	}
        	
        	if(!memEmail) {
        		alert("이메일을 입력해 주세요.");
        		$("#memEmail2").focus();
        		return false;
        	}
        	
        	if(!memName) {
        		alert("이름을 입력해 주세요.");
        		$("#memName2").focus();
        		return false;
        	}
        	
        	var data = {
        		memId : memId,
           		memEmail : memEmail,
           		memName : memName
           	};
           	
           	$.ajax({
           		type: "post",
           		url: "/notice/pwFind.do",
           		data: JSON.stringify(data),
           		contentType: "application/json;charset=utf-8",
           		success: function(res) {
   					console.log("결과 : " + res);
   					
 					// 회원님의 비밀번호는 [<font color="red" class="h2" id="password"></font>] 입니다.
   					if(!res) { // 리턴받은 결과가 없음, 즉 비밀번호 없음
						$(".pwText").html("회원님의 비밀번호를 찾을 수 없습니다.");
					}else { // 비밀번호 있음
						var pw = res.memPw;
						$(".pwText").html("회원님의 비밀번호는 [<font id='password' class='h2' style='color:red'>"+pw+"</font>] 입니다.");
					}
   				}
           	});
        	
        });
        
        loginBtn.on("click", function(){
        	location.href = "/notice/login.do";
        });
    });
</script>

package kr.or.ddit.controller.crud.notice;

import java.util.Map;

import javax.inject.Inject;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeMemberVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/notice")
public class NoticeForgetController {

	@Inject
	private INoticeService noticeService;
	
	@RequestMapping(value = "idFind.do", method = RequestMethod.POST)
	public ResponseEntity<NoticeMemberVO> idFind(
			@RequestBody Map<String, String> map
			) {
		
		NoticeMemberVO result = noticeService.idFind(map);
		return new ResponseEntity<NoticeMemberVO>(result, HttpStatus.OK);
		
	}
	
	@RequestMapping(value = "pwFind.do", method = RequestMethod.POST)
	public ResponseEntity<NoticeMemberVO> pwFind(
			@RequestBody Map<String, String> map
			) {
		
		NoticeMemberVO result = noticeService.pwFind(map);
		return new ResponseEntity<NoticeMemberVO>(result, HttpStatus.OK);
		
	}
	
}
package kr.or.ddit.service;

import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import kr.or.ddit.ServiceResult;
import kr.or.ddit.vo.crud.NoticeFileVO;
import kr.or.ddit.vo.crud.NoticeMemberVO;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

public interface INoticeService {

	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO);
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO);
	public ServiceResult insertNotice(HttpServletRequest req, NoticeVO noticeVO);
	public NoticeVO selectNotice(int boNo);
	public ServiceResult updateNotice(HttpServletRequest req, NoticeVO noticeVO);
	public ServiceResult deleteNotice(HttpServletRequest req, int boNo);
	
	public NoticeMemberVO loginCheck(NoticeMemberVO member);
	public ServiceResult idCheck(String memId);
	public ServiceResult signup(HttpServletRequest req, NoticeMemberVO memberVO);
	
	public NoticeFileVO noticeDownload(int fileNo);
	
	public NoticeMemberVO selectMember(String memId);
	public ServiceResult profileUpdate(HttpServletRequest req, NoticeMemberVO memberVO);
	
	public NoticeMemberVO idFind(Map<String, String> map);
	public NoticeMemberVO pwFind(Map<String, String> map);

}
package kr.or.ddit.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import kr.or.ddit.ServiceResult;
import kr.or.ddit.controller.crud.notice.TelegramSendController;
import kr.or.ddit.mapper.ILoginMapper;
import kr.or.ddit.mapper.INoticeMapper;
import kr.or.ddit.mapper.IProfileMapper;
import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeFileVO;
import kr.or.ddit.vo.crud.NoticeMemberVO;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

@Service
public class NoticeServiceImpl implements INoticeService {

	@Inject
	private INoticeMapper noticeMapper;
	
	@Inject
	private ILoginMapper loginMapper;
	
	@Inject
	private IProfileMapper profileMapper;
	
	private TelegramSendController tst = new TelegramSendController();

	@Override
	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO) {
		return noticeMapper.selectNoticeCount(pagingVO);
	}

	@Override
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO) {
		return noticeMapper.selectNoticeList(pagingVO);
	}

	@Override
	public ServiceResult insertNotice(HttpServletRequest req, NoticeVO noticeVO) {
		ServiceResult result = null;

		int status = noticeMapper.insertNotice(noticeVO);
		if (status > 0) {	// 게시글 등록이 성공했을 때
			List<NoticeFileVO> noticeFileList = noticeVO.getNoticeFileList();
			try {
				// 공지사항 파일 업로드 처리
				noticeFileUpload(noticeFileList, noticeVO.getBoNo(), req);
			}catch(IOException e) {
				e.printStackTrace();
			}
			
			// Telegram Bot API를 이용한 실시간 메세지 처리
			try {
				tst.sendGet("홍길동", noticeVO.getBoTitle());
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			result = ServiceResult.OK;
		} else {
			result = ServiceResult.FAILED;
		}

		return result;
	}

	@Override
	public NoticeVO selectNotice(int boNo) {
		noticeMapper.incrementHit(boNo); // 게시글 조회수 증가
		return noticeMapper.selectNotice(boNo); // 게시글 번호에 해당하는 게시글 정보 가져오기
	}

	@Override
	public ServiceResult updateNotice(HttpServletRequest req, NoticeVO noticeVO) {
		ServiceResult result = null;

		int status = noticeMapper.updateNotice(noticeVO); // 게시글 수정
		if (status > 0) { // 게시글 수정 완료
			// 게시글 정보에서 파일 목록을 가져오기
			List<NoticeFileVO> noticeFileList = noticeVO.getNoticeFileList();
			try {
				// 공지사항 업로드 진행
				noticeFileUpload(noticeFileList, noticeVO.getBoNo(), req);
				
				// 기존에 등록되어 있는 파일 목록들 중, 수정하기 위해서 X버튼을 눌러 삭제 처리로 넘겨준 파일 번호들
				Integer[] delNoticeNo = noticeVO.getDelNoticeNo();
				if(delNoticeNo != null) {
					for(int i = 0; i < delNoticeNo.length; i++) {
						// 삭제할 파일 번호 목록들 중, 파일 번호에 해당하는 공지사항 파일 정보를 가져온다.
						NoticeFileVO noticeFileVO = noticeMapper.selectNoticeFile(delNoticeNo[i]);
						noticeMapper.deleteNoticeFile(delNoticeNo[i]); // 파일번호에 해당하는 파일 데이터를 삭제
						File file = new File(noticeFileVO.getFileSavepath());
						file.delete(); // 기존 파일이 업로드 되어 있던 경로에 파일 삭제
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			result = ServiceResult.OK;
		} else { // 게시글 수정 실패
			result = ServiceResult.FAILED;
		}

		return result;
	}

	@Override
	public ServiceResult deleteNotice(HttpServletRequest req, int boNo) {
		ServiceResult result = null;

		// 공지사항 파일 데이터를 삭제하기 위한 준비(파일 적용시)
		NoticeVO noticeVO = noticeMapper.selectNotice(boNo); // 게시글 번호에 해당하는 공지사항 게시글 정보 가져오기
		noticeMapper.deleteNoticeFileByBoNo(boNo); // 게시글 번호에 해당하는 파일 데이터 삭제
		
		int status = noticeMapper.deleteNotice(boNo); // 일반적인 게시글 삭제
		if (status > 0) {
			List<NoticeFileVO> noticeFileList = noticeVO.getNoticeFileList();
			if(noticeFileList != null && noticeFileList.size() > 0) {
				// D:\99_Class...\workspace\.metadate\.plugins.....\Project명\resources\notice\boNo
				// 12d3uiy21euy1e2d.jpg
				// '/' 기준으로 잘라준다.
				String[] filePath = noticeFileList.get(0).getFileSavepath().split("/");
				
				String path = filePath[0];
				deleteFolder(req, path);
			}
			
			result = ServiceResult.OK;
		} else {
			result = ServiceResult.FAILED;
		}

		return result;
	}

	private void deleteFolder(HttpServletRequest req, String path) {
		// UUID + 원본파일명 전 폴더 경로를 folder 파일 객체로 잡는다.
		File folder = new File(path);
		
		if(folder.exists()) { // 경로가 존재한다면
			File[] folderList = folder.listFiles(); // 폴더 안에 있는 파일들의 목록을 가져온다.
			
			for(int i = 0; i < folderList.length; i++) {
				if(folderList[i].isFile()) { // 폴더 안에 있는 파일이 파일일 때
					// 폴더 안에 파일을 차례대로 삭제
					folderList[i].delete();
				}else {
					// 폴더 안에 있는 파일이 폴더일 때 재귀함수 호출(폴더 안으로 들어가서 재 처리할 수 있도록)
					deleteFolder(req, folderList[i].getPath());
				}
			}
			
			folder.delete(); // 폴더 삭제
		}
	}

	@Override
	public NoticeMemberVO loginCheck(NoticeMemberVO member) {
		return loginMapper.loginCheck(member);
	}

	@Override
	public ServiceResult idCheck(String memId) {
		ServiceResult result = null;

		NoticeMemberVO member = loginMapper.idCheck(memId);
		if (member != null) {
			result = ServiceResult.EXIST;
		} else {
			result = ServiceResult.NOTEXIST;
		}

		return result;
	}

	@Override
	public ServiceResult signup(HttpServletRequest req, NoticeMemberVO memberVO) {
		ServiceResult result = null;

		// 회원가입 시, 프로필 이미지로 파일을 업로드 하는데 이때 업로드 할 서버 경로
		String uploadPath = req.getServletContext().getRealPath("/resources/profile");
		File file = new File(uploadPath);
		if (!file.exists()) {
			file.mkdirs();
		}

		String proFileImg = ""; // 회원정보에 추가될 프로필 이미지 경로
		try {
			// 넘겨받은 회원정보에서 파일 데이터 가져오기
			MultipartFile proFileImgFile = memberVO.getImgFile();

			// 넘겨받은 파일 데이터가 존재할 때
			if (proFileImgFile.getOriginalFilename() != null && !proFileImgFile.getOriginalFilename().equals("")) {
				String fileName = UUID.randomUUID().toString(); // UUID 파일명 생성
				fileName += "_" + proFileImgFile.getOriginalFilename(); // UUID_원본파일명으로 파일명 생성
				uploadPath += "/" + fileName; // /resources/profile/uuid_원본파일명

				proFileImgFile.transferTo(new File(uploadPath)); // 해당 위치에 파일 복사
				proFileImg = "/resources/profile/" + fileName; // 파일 복사가 일어난 파일의 위치로 접근하기 위한 URI 설정
			}

			memberVO.setMemProfileImg(proFileImg);
		} catch (Exception e) {
			e.printStackTrace();
		}

		int status = loginMapper.signup(memberVO);

		if (status > 0) { // 등록 성공
			result = ServiceResult.OK;
		} else {
			result = ServiceResult.FAILED;
		}

		return result;
	}
	
	private void noticeFileUpload(
			List<NoticeFileVO> noticeFileList, 
			int boNo, 
			HttpServletRequest req
			) throws IOException {
		
		String savePath = "/resources/notice/";
		
		if(noticeFileList != null) { // 넘겨받은 파일 데이터가 존재할 때
			if(noticeFileList.size() > 0) {
				for(NoticeFileVO noticeFileVO : noticeFileList) {
					String saveName = UUID.randomUUID().toString(); // UUID의 랜덤 파일명 생성
					
					// 파일명을 설장할 때, 원본 파일명의 공백을 '_'로 변경한다.
					saveName = saveName + "_" + noticeFileVO.getFileName().replaceAll(" ", "_");
					// 디버깅 및 확장자 추출 참고
					String endFileName = noticeFileVO.getFileName().split("\\.")[1];
					
					String saveLocate = req.getServletContext().getRealPath(savePath + boNo);
					File file = new File(saveLocate);
					if(!file.exists()) {
						file.mkdirs();
					}
					saveLocate += "/" + saveName;
					
					noticeFileVO.setBoNo(boNo);						// 게시글 번호 설정
					noticeFileVO.setFileSavepath(saveLocate); 		// 파일 업로드 경로 설정
					noticeMapper.insertNoticeFile(noticeFileVO);	// 게시글 파일 데이터 추가
					
					File saveFile = new File(saveLocate);
					
					// 방법 1
//					InputStream is = noticeFileVO.getItem().getInputStream();
//					FileUtils.copyInputStreamToFile(is, saveFile);
					
					// 방법 2
					noticeFileVO.getItem().transferTo(saveFile); // 파일 복사
				}
			}
		}
		
	}

	@Override
	public NoticeFileVO noticeDownload(int fileNo) {
		NoticeFileVO noticeFileVO = noticeMapper.noticeDownload(fileNo);
		if(noticeFileVO == null) {
			throw new RuntimeException();
		}
		noticeMapper.incrementNoticeDowncount(fileNo); // 다운로드 횟수 증가
		return noticeFileVO;
	}

	@Override
	public NoticeMemberVO selectMember(String memId) {
		return profileMapper.selectMember(memId);
	}

	@Override
	public ServiceResult profileUpdate(HttpServletRequest req, NoticeMemberVO memberVO) {
		ServiceResult result = null;
		
		// 프로필 이미지를 업로드 하기 위한 서버 경로(/resources/profile)
		String uploadPath = req.getServletContext().getRealPath("/resources/profile");
		File file = new File(uploadPath);
		if(!file.exists()) {
			file.mkdirs();
		}
		
		String profileImg = "";
		try {
			
			MultipartFile profileImgFile = memberVO.getImgFile();
			if(profileImgFile.getOriginalFilename() != null && !profileImgFile.getOriginalFilename().equals("")) {
				String fileName = UUID.randomUUID().toString();
				fileName += "_" + profileImgFile.getOriginalFilename();
				uploadPath += "/" + fileName;
				profileImgFile.transferTo(new File(uploadPath));
				profileImg = "/resources/profile/" + fileName;
			}
			
			memberVO.setMemProfileImg(profileImg);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		int status = profileMapper.profileUpdate(memberVO);
		if(status > 0) { // 수정 성공
			result = ServiceResult.OK;
		}else { // 수정 실패
			result = ServiceResult.FAILED;
		}
		
		return result;
	}

	@Override
	public NoticeMemberVO idFind(Map<String, String> map) {
		NoticeMemberVO member = loginMapper.idFind(map);
		return member;
	}

	@Override
	public NoticeMemberVO pwFind(Map<String, String> map) {
		NoticeMemberVO member = loginMapper.pwFind(map);
		return member;
	}

}
package kr.or.ddit.mapper;

import java.util.Map;

import kr.or.ddit.vo.crud.NoticeMemberVO;

public interface ILoginMapper {

	public NoticeMemberVO loginCheck(NoticeMemberVO member);
	public NoticeMemberVO idCheck(String memId);
	public int signup(NoticeMemberVO memberVO);
	public NoticeMemberVO idFind(Map<String, String> map);
	public NoticeMemberVO pwFind(Map<String, String> map);

}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.or.ddit.mapper.ILoginMapper">
	
	<!-- ctrl + shift + y : 소문자 변환 -->
	<select id="loginCheck" parameterType="noticeMemberVO" resultType="noticeMemberVO">
		select 
			mem_no,
			mem_id,
			mem_pw,
			mem_name,
			mem_gender,
			mem_email,
			mem_phone,
			mem_postcode,
			mem_address1,
			mem_address2,
			mem_agree,
			mem_profileimg,
			mem_regdate
		from noticemember 
		where mem_id = #{memId} 
		and mem_pw = #{memPw}
	</select>
	
	<select id="idCheck" parameterType="string" resultType="noticeMemberVO">
		select 
			mem_no,
			mem_id,
			mem_pw,
			mem_name,
			mem_gender,
			mem_email,
			mem_phone,
			mem_postcode,
			mem_address1,
			mem_address2,
			mem_agree,
			mem_profileimg,
			mem_regdate
		from noticemember 
		where mem_id = #{memId} 
	</select>
	
	<insert id="signup" parameterType="noticeMemberVO">
		insert into noticemember (
			mem_no,
			mem_id,
			mem_pw,
			mem_name,
			mem_gender,
			mem_email,
			mem_phone,
			mem_postcode,
			mem_address1,
			mem_address2,
			mem_agree,
			mem_profileimg,
			mem_regdate,
			enabled
		) values (
			seq_noticemember.nextval,
			#{memId},
			#{memPw},
			#{memName},
			#{memGender},
			#{memEmail},
			#{memPhone},
			#{memPostCode},
			#{memAddress1},
			#{memAddress2},
			#{memAgree},
			#{memProfileImg},
			sysdate,
			1
		)
	</insert>
	
	<select id="idFind" parameterType="java.util.HashMap" resultType="noticeMemberVO">
	    select 
	        mem_no,
	        mem_id,
	        mem_pw,
	        mem_name,
	        mem_gender,
	        mem_email,
	        mem_phone,
	        mem_postcode,
	        mem_address1,
	        mem_address2,
	        mem_agree,
	        mem_profileimg,
	        mem_regdate
	    from noticemember 
	    where 1=1 
	    and mem_email = #{memEmail} 
	    and mem_name = #{memName} 
	</select>
	
	<select id="pwFind" parameterType="java.util.HashMap" resultType="noticeMemberVO">
	    select 
	        mem_no,
	        mem_id,
	        mem_pw,
	        mem_name,
	        mem_gender,
	        mem_email,
	        mem_phone,
	        mem_postcode,
	        mem_address1,
	        mem_address2,
	        mem_agree,
	        mem_profileimg,
	        mem_regdate
	    from noticemember 
	    where 1=1 
	    and mem_id = #{memId} 
	    and mem_email = #{memEmail} 
	    and mem_name = #{memName} 
	</select>
	
</mapper>

- http://localhost/notice/forget.do


 

'대덕인재개발원_웹기반 애플리케이션' 카테고리의 다른 글

231207_SPRING 2 (12-2)  (1) 2023.12.07
231207_SPRING 2 (12-1)  (1) 2023.12.07
231206_SPRING 2 (11-2)  (1) 2023.12.06
231206_SPRING 2 (11-1)  (1) 2023.12.06
231205_SPRING 2 (10-2)  (0) 2023.12.05