2024.2.07 2주동안 5명과 합작해서 Model, View, Controller 파트 나눠 Toy-Project를 진행했다.
주제는 OTT 순위를 크롤링 한 뒤, 관리자/사용자를 나눠 영상 추가, 시청하기, 영상 삭제, 즐겨찾기 등 구현해봤다.
웹기반은 아니고 IDE 콘솔 기반으로 진행해봤다.
설계
시작하자마자 크롤링 해서 영화 정보를 배열에 저장!!(반복문 밖, 한 번만 수행)
1. 회원가입(관리자 id랑 같으면 회원가입 불가)
2. 로그인
0. 종료
OTT
[ADMIN] => 관리자 id,pw 상수화
1. 영상 추가
2. 영상 삭제 => 영상 전체 출력 후 어떤거 삭제할지 번호 입력받아서 삭제 =>즐겨찾기도 삭제
3. 광고 영상 관리 => 전체출력 후 어떤거 광고로 할지 숫자로 입력받아서 추가
이미 광고가 있으면 삭제 할지말지 결정 후 진행
4. 로그아웃
0. 종료
[CLIENT]
1. 시청하기 => 배열 전체 출력해서 뭐 볼지 숫자 입력받아서 고르고 시청 완료! 시청 횟수++
2. 즐겨찾기 => 바로 즐겨찾기 목록 출력 1. 시청하기=> 뭐 볼지 입력받고 선택해서 시청 2. 추가 3. 삭제 0. 뒤로가기
==> 즐겨찾기 목록 리스트에 추가
3. 영상 전체 출력(영상 이름)
4. 영화 이름 검색 => 0 누르면 뒤로가기
5. 로그아웃
0. 종료
User Flow

UML

소스 코드
Client(메인)
import weeks_2.Controller.Controller;
public class Client {
public static void main(String[] args) {
Controller app = new Controller();
app.start();
}
}
Controller
더보기
package weeks_2.Controller;
import weeks_2.Model.*;
import weeks_2.View.AdminView;
import weeks_2.View.ClientView;
import weeks_2.View.View;
import java.util.ArrayList;
import java.util.List;
public class Controller {
private ArrayList<MovieDTO> movieList; // 크롤링한 데이터를 저장할 리스트
private MovieDAO movieDao;
private MemberDAO memberDao;
private AdminView adminView;
private ClientView clientView;
private View baseView;
private Crawling crawling;
private MovieDTO adMovie;
private Admin admin;
private ArrayList<MovieDTO> datas;
// 생성자
public Controller() {
this.movieDao = new MovieDAO();
this.memberDao = new MemberDAO();
this.adminView = new AdminView();
this.clientView = new ClientView();
this.baseView = new View();
this.admin = new Admin();
initializeMovieData();
this.datas=new ArrayList<>();
this.adMovie=null;
}
// 크롤링 데이터 초기화 메소드
private void initializeMovieData() {
// 크롤링으로 데이터 가져오기
this.movieList = Crawling.makeDatas();
// 가져온 데이터를 DB에 저장
if (movieList != null && !movieList.isEmpty()) {
// movieList에 있는 내용 불러오기
for (MovieDTO movie : movieList) {
// 가져온 내용 DB에 저장
movieDao.insert(movie);
}
System.out.println("[로그]영화 데이터 초기화 완료");
} else {
System.out.println("[로그]영화 데이터 초기화 실패");
}
}
// 메인 실행 메소드
public void start() {
while(true) {
// 메인 메뉴 출력
baseView.showMenu();
// 사용자 선택 입력받기
int mainChoice = baseView.inputLogInMenuNum();
// 로그인
if (mainChoice == 1) {
// id 입력
String id = baseView.inputId();
if (id == null) {
continue;
}
// pw 입력
String password = baseView.inputPw();
// password가 null이면 메인메뉴로
if (password == null) {
continue;
}
// MemberDTO 인스턴스화
MemberDTO memberDTO = new MemberDTO();
// 관리자 인스턴스화(보안 위해서)
Admin admin=new Admin();
// 관리자 로그인
if (admin.isAdminLogin(id, password)) {
while(true) {
// 관리자 메뉴 호출
adminView.showAdminMenu();
// 숫자 입력받기
int adminChoice = adminView.inputNum();
// 영상추가
if (adminChoice == 6) {
// 이름 입력받기
String movieName = adminView.inputNewMovie();
if(movieName.equals("0")){
continue;
}
// 평점 입력받기
double rating = adminView.inputRating();
// 평점 뒤로가기는 안 함.
// scope 이슈(객체 생성)
MovieDTO movieDTO = new MovieDTO();
// 이름 DB에 저장
movieDTO.setTitle(movieName);
// 평점 DB에 저장
movieDTO.setRating(rating);
// 성공시
if(movieDao.insert(movieDTO)) {
baseView.printSuccess();
}
// 실패시
else {
baseView.printFail();
}
}
// 영상삭제
else if (adminChoice == 7) {
// 객체 생성
MovieDTO movieDTO = new MovieDTO();
// 영화 전체 불러오기
movieDTO.setCondition("PRINTALL");
ArrayList<MovieDTO> movies = movieDao.selectAll(movieDTO);
// 영화 배열이 비어있는 경우
if(movies.isEmpty()) {
baseView.printEmpty();
}
// 영화 목록 출력
else {
clientView.showMovieList(movies);
}
// 숫자 입력받기
int movieNum = adminView.inputDeleteMovie();
// 0. 뒤로가기
if(movieNum == 0){
continue;
}
// 삭제한 번호 DB에 저장
movieDTO.setMovieId(movieNum);
// 먼저 모든 사용자의 즐겨찾기에서 해당 영화를 제거
memberDao.delete(null, movieDTO);
// 그 다음 영화 데이터베이스에서 영화 삭제
if(movieDao.delete(movieDTO)) {
baseView.printSuccess();
System.out.println("[로그] 영화 삭제 및 모든 사용자의 즐겨찾기에서 제거 완료");
} else {
baseView.printFail();
}
}
// 광고영상관리
else if (adminChoice == 8) {
MovieDTO movieDTO = new MovieDTO();
movieDTO.setCondition("PRINTALL");
// MovieDAO에서 전체 영화 목록 가져오기
ArrayList<MovieDTO> movies = movieDao.selectAll(movieDTO);
// 영화 목록이 비어있는 경우
if(movies.isEmpty()) {
baseView.printEmpty(); // 데이터 없음 메시지 출력
}
// 영화 목록이 있는 경우
else {
clientView.showMovieList(movies); // 영화 목록 출력
}
//광고가 null이면 새로 객체 생성(예외에러)
if(adMovie!=null) {
if(adminView.deleteAD()==true) {
adMovie=null;
}
}
else {
int num=adminView.addNewAd();
if(num == 0){
continue;
}
MovieDTO data=new MovieDTO();
data.setMovieId(num); //dto에 영화 번호 넣기
MovieDTO selectedMovie=movieDao.selectOne(data);//영화 번호로 찾기
movieDao.selectOne(selectedMovie);
baseView.showAd(selectedMovie); //영상 가져와서 출력
adMovie=new MovieDTO();
//광고 영상에 넣기
adMovie.setMovieId(selectedMovie.getMovieId());
adMovie.setTitle(selectedMovie.getTitle());
adMovie.setRating(selectedMovie.getRating());
adMovie.setViewCount(selectedMovie.getViewCount());
}
}
// 로그아웃
else if (adminChoice == 9) {
baseView.printSuccess();
break; // 관리자 메뉴 종료
}
// 프로그램 종료
else if (adminChoice == 0) {
baseView.printExit();
return; // 프로그램 완전 종료
}
}
}
// 사용자 로그인
else {
// id db에 연결
memberDTO.setId(id);
// pw db에 연결
memberDTO.setPw(password);
// DB에서 아이디랑 비밀번호 불러오기
MemberDTO loginResult = memberDao.selectOne(memberDTO);
// loginResult가 null이 아닌지 확인
boolean loginSuccess = (loginResult != null);
// 로그인 성공시
if (loginSuccess) {
while(true) {
// 사용자 메뉴 출력
clientView.showClientMenu(null);
if(adMovie==null) {
baseView.printNull();
}
else {
baseView.showAd(adMovie);
}
// 숫자 입력받기
int userChoice = clientView.inputClientMenuNum();
// 1. 시청하기
if (userChoice == 1) {
MovieDTO movieDTO = new MovieDTO();
// 전체출력
movieDTO.setCondition("PRINTALL");
ArrayList<MovieDTO> movies = movieDao.selectAll(movieDTO);
// 배열이 비어있을 때
if(movies.isEmpty()) {
baseView.printEmpty();
}
// 배열이 안 비어있을 때
else {
clientView.showMovieList(movies); // 영화 목록 출력
int movieChoice = clientView.inputMovieChoiceNum(movies); // 영화 번호 입력받기
// 0. 뒤로가기
if(movieChoice == 0){
continue;
}
// DB에 저장
movieDTO.setMovieId(movieChoice);
MovieDTO selectedMovie = movieDao.selectOne(movieDTO);
// 실패시
if (selectedMovie == null) {
baseView.printFail();
}
// 성공시
else {
// 시청횟수 증가 성공
if (movieDao.update(movieDTO)) {
MovieDTO updatedMovie = movieDao.selectOne(movieDTO);
clientView.showMovieDetail(updatedMovie);
baseView.printSuccess();
}
// 시청횟수 증가 실패
else {
baseView.printFail();
}
}
}
}
// 즐겨찾기 기능
else if (userChoice == 2) {
boolean favoriteMenuActive = true;
while (favoriteMenuActive) {
// 현재 사용자의 즐겨찾기 목록 조회
ArrayList<MovieDTO> favorites = loginResult.getIsPremium();
// 즐겨찾기 목록 표시
if (favorites == null || favorites.isEmpty()) {
baseView.printEmpty();
} else {
clientView.showMovieList(favorites);
}
// 즐겨찾기 메뉴 표시
clientView.showFavoriteMenu();
int favoriteChoice = clientView.inputNum();
// 즐겨찾기 추가
if (favoriteChoice == 1) {
MovieDTO searchCondition = new MovieDTO();
// 즐겨찾기 전체 출력
searchCondition.setCondition("PRINTALL");
ArrayList<MovieDTO> allMovies = movieDao.selectAll(searchCondition);
// 비어있으면
if (allMovies.isEmpty()) {
baseView.printEmpty();
continue;
}
// 즐겨찾기에 추가할 내용 찾기
clientView.showMovieList(allMovies);
int movieId = clientView.inputNum();
if(movieId == 0){
break;
}
// 선택한 영화 찾기
MovieDTO selectedMovie = null;
for (MovieDTO movie : allMovies) {
if (movie.getMovieId() == movieId) {
selectedMovie = movie;
break; // 찾았으면 반복문 종료
}
}
// 선택한 영화가 존재할 때
if (selectedMovie != null) {
// 현재 사용자 id db에 저장
memberDTO.setId(loginResult.getId());
// 즐겨찾기 목록을 저장할 ArrayList 생성
ArrayList<MovieDTO> updatedFavorites = new ArrayList<>();
// 기존 즐겨찾기 목록이 있다면 복사
if (favorites != null) {
updatedFavorites = new ArrayList<>(favorites);
}
// 선택한 영화 즐겨찾기에 추가
updatedFavorites.add(selectedMovie);
// 업데이트 된 즐겨찾기 목록을 db에 저장
memberDTO.setIsPremium(updatedFavorites);
// DB에 즐겨찾기 정보 업데이트
if (memberDao.update(loginResult.getId(), selectedMovie)) {
loginResult.setIsPremium(updatedFavorites);
baseView.printSuccess();
} else {
baseView.printFail();
}
}
}
// 즐겨찾기 삭제 기능 선택
else if (favoriteChoice == 2) {
// 즐겨찾기 목록이 비어있는지 확인
if (favorites == null || favorites.isEmpty()) {
adminView.inputDeleteMovie();
continue;
}
// 현재 즐겨찾기 목록 출력
clientView.showMovieList(favorites);
// 삭제할 영화 번호 입력 받기
int deleteMovieId = clientView.inputNum();
// 삭제할 영화 정보 생성
MovieDTO movieToDelete = new MovieDTO();
movieToDelete.setMovieId(deleteMovieId);
// memberDao의 delete 메소드 호출하여 즐겨찾기에서 삭제
if (memberDao.delete(loginResult, movieToDelete)) {
baseView.printSuccess();
// 로그인 정보의 즐겨찾기 목록도 업데이트
loginResult.setIsPremium(memberDao.selectOne(loginResult).getIsPremium());
} else {
baseView.printFail();
}
}
// 0. 돌아가기
else if (favoriteChoice == 0) {
favoriteMenuActive = false;
}
}
}
// 3. 영상 전체출력
else if (userChoice == 3) {
MovieDTO movieDTO = new MovieDTO();
movieDTO.setCondition("PRINTALL");
// MovieDAO에서 전체 영화 목록 가져오기
ArrayList<MovieDTO> movies = movieDao.selectAll(movieDTO);
// 영화 목록이 비어있는 경우
if(movies.isEmpty()) {
baseView.printEmpty(); // 데이터 없음 메시지 출력
}
// 영화 목록이 있는 경우
else {
clientView.showMovieList(movies); // 영화 목록 출력
}
}
// 영화 검색
else if (userChoice == 4) {
String searchName = clientView.inputSearchKeyword();
// 0 입력시 뒤로가기
if (searchName.equals("0")) {
continue;
}
MovieDTO searchDTO = new MovieDTO();
// 이름 DB에 넘기기
searchDTO.setTitle(searchName);
searchDTO.setCondition("SEARCH");
// db에서 불러오기
List<MovieDTO> results = movieDao.selectAll(searchDTO);
// 없는 경우
if (results.isEmpty()) {
baseView.printFail();
continue;
}
//있는 경우
else {
// 검색 결과 목록 출력
clientView.showMovieList((ArrayList<MovieDTO>) results);
// 상세 정보를 보고 싶은 영화 선택
clientView.showSelectMovieNum();
int selectedId = clientView.inputSelectMovieNum();
// 0. 뒤로가기
if(selectedId == 0){
continue;
}
// 선택한 영화를 저장할 변수 선언
MovieDTO selectedMovie = null;
// 입력받은 ID와 일치하는 영화 찾기
for (MovieDTO movie : results) {
if (movie.getMovieId() == selectedId) {
selectedMovie = movie;
break;
}
}
// 선택한 영화가 있을 때
if (selectedMovie != null) {
clientView.showMovieDetail(selectedMovie);
}
// 선택한 영화가 없을 때
else {
baseView.printFail();
}
}
}
// 로그아웃
else if (userChoice == 5) {
baseView.printSuccess();
break;
}
// 종료
else if (userChoice == 0) {
baseView.printExit();
return;
}
}
}
// 로그인 실패
else {
baseView.printFail();
}
}
}
// 회원가입
// 회원가입
else if (mainChoice == 2) {
while(true) {
// id 입력 받기
String id = baseView.inputId();
// 뒤로가기 처리 (id가 null이면 뒤로가기)
if (id == null) {
break; // View에서 이미 메시지를 출력하므로 여기서는 break만
}
// 관리자 계정 ID와 동일한지 검사
if (id.equals(admin.getAdmin().getId())) {
baseView.printSignInIdDuplicationErr();
continue;
}
// ID 중복 검사
MemberDTO checkDto = new MemberDTO();
checkDto.setId(id);
if (memberDao.selectOne(checkDto) != null) {
baseView.printSignInIdDuplicationErr();
continue;
}
// 비밀번호 입력받기
String password = baseView.inputPw();
// 비밀번호가 null이면 뒤로가기
if (password == null) {
break;
}
MemberDTO memberDto = new MemberDTO();
memberDto.setId(id);
memberDto.setPw(password);
// 성공여부 판단
if (memberDao.insert(memberDto)) {
baseView.printSuccess();
} else {
baseView.printFail();
}
break;
}
continue; // 메인 메뉴로 돌아가기
}
// 종료
else if (mainChoice == 0) {
baseView.printExit();
return;
}
}
}
}
Model
관리자 계정 관련 Model
더보기
package weeks_2.Model;
import weeks_2.View.AdminView;
import weeks_2.View.ClientView;
import weeks_2.View.View;
public class Admin {
private MemberDTO admin; // admin 객체를 필드로 선언
public Admin() {
this.admin = new MemberDTO();
this.admin.setId("윤지팀");
this.admin.setPw("9999");
}
// 관리자 로그인 확인 메소드
public boolean isAdminLogin(String id, String password) {
return id.equals(admin.getId()) && password.equals(admin.getPw());
}
// admin 객체의 getter
public MemberDTO getAdmin() {
return this.admin;
}
}
크롤링 모델
더보기
package weeks_2.Model;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
public class Crawling {public static ArrayList<MovieDTO> makeDatas() {
ArrayList<MovieDTO> datas=new ArrayList<>();
try {
// ChromeDriver 경로 지정
String driverPath = "C:\\Users\\3333c\\Desktop\\school\\ACADEMY\\study\\src\\day018\\class01\\driver\\chromedriver.exe";
if(!Files.exists(Paths.get(driverPath))) { //경로 지정 잘 됐는지 확인
System.out.println("[로그]크롤링 실패: ChromeDriver 경로를 다시 확인해주세요");
return datas;
}
// setProperty()로 ChromeDriver의 위치를 시스템에 넘김
System.setProperty("webdriver.chrome.driver", driverPath);
// Chrome 옵션 설정
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless"); // 브라우저 창을 띄우지 않고 백그라운드에서 실행
options.addArguments("--disable-gpu"); // GPU 하드웨어 가속 비활성화
options.addArguments("--no-sandbox"); // 샌드박스 모드 비활성화
options.addArguments("--disable-dev-shm-usage"); // 공유 메모리 사용 비활성화
// WebDriver 생성
WebDriver driver = new ChromeDriver(options);
try {
// 웹페이지 로드
driver.get("https://m.kinolights.com/ranking/netflix");
// 페이지가 완전히 로드될 때까지 충분히 기다림
Thread.sleep(3000); // 3초 대기
// 명시적 대기 설정
// 최대 대기 10초 설정
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
// .info__title이 나타날 때까지 대기
wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(
By.cssSelector(".info__title")));
// span.score__number 나타날 때까지 대기
wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(
By.cssSelector("span.score__number")));
// .info__title 클래스를 가진 모든 요소를 찾아 리스트로 저장
List<WebElement> titleElements = driver.findElements(
By.cssSelector(".info__title"));
// span.score__number 클래스를 가진 모든 요소를 찾아 리스트로 저장
List<WebElement> scoreElements = driver.findElements(
By.cssSelector("span.score__number"));
if (titleElements.isEmpty() || scoreElements.isEmpty()) { //제목이나 평점이 없을 경우
System.err.println("[로그]크롤링 한 데이터가 없습니다.");
return datas;
}
// 결과 출력 (상위 20개만)
System.out.println("\n===== 넷플릭스 TOP 20 =====");
int count = 0; //영화 번호
for (int i=0;i<titleElements.size() && count<20;i++) { //20개까지
String title = titleElements.get(i).getText().trim(); // 요소의 텍스트를 가져와서 공백 제거
String ratingText=scoreElements.get(i).getText().trim(); // 요소의 텍스트를 가져와서 공백 제거
double rating=0.0; //초기화
if(!ratingText.isEmpty()) { //평점 텍스트로 받은거 double로 형변환
rating=Double.parseDouble(ratingText);
}
if (!title.isEmpty()) { //공백이 아니면
MovieDTO data=new MovieDTO();
data.setMovieId(count+1); //영화 번호
data.setTitle(title); //영화 제목
data.setRating(rating); //영화 평점
data.setViewCount(0); //영화 시청수 0으로 초기화
datas.add(data);
count++;
}
}
} finally {
driver.quit();
// System.out.println("브라우저 종료 완료");
}
} catch (Exception e) {
System.out.println("Crawling 중 오류 발생: " + e.getMessage());
e.printStackTrace(); //예외 내용 출력
}
return datas; //크롤링 한거 반환
}
}
MemberDAO
더보기
package weeks_2.Model;
import java.util.ArrayList;
public class MemberDAO {
ArrayList<MemberDTO> datas; // 사용자 데이터 객체
ArrayList<MovieDTO> isPremium; // 즐겨찾기 목록객체
public MemberDAO() {
this.datas = new ArrayList<>();
this.isPremium = new ArrayList<>();
}
private ArrayList<MemberDTO> selectAll(MemberDTO dto) {
return new ArrayList<>(datas); // 전체 회원 목록 반환
//아직 개발이 안되었지만 외부에서 불러올수있기때문에 메서드를 만들어놓고 private으로 가려놓기
}
public MemberDTO selectOne(MemberDTO dto) {
// 입력값 검증
if (dto == null || dto.getId() == null || dto.getPw() == null) {
return null;
}
if (datas.isEmpty()) {
System.out.println("등록된 회원이 없습니다.");
return null;
}
for (MemberDTO member : datas) {
// 로그인 검사
if (dto.getId().equals(member.getId()) && dto.getPw().equals(member.getPw())) {
//사용자 배열에 아이디,비번과 입력값 아이디,비번이 같은지 확인
MemberDTO result = new MemberDTO();
result.setId(member.getId());
result.setPw(member.getPw());
result.setIsPremium(member.getIsPremium());
//새로운 객체 생성 후
//아이디,비밀번호,즐겨찾기 목록 복사
return result;
//복사값 반환
}
}
System.out.println("로그 : 일치하는 회원이 없습니다");//유효성 검사
return null;
}
/*객체를 새로 생성하여 데이터를 복사하고 복사한 값을 반환한 이유
1.데이터값을 그대로 반환하면 반환된 객체가 수정되면 원본데이터가 변경됨
2.로그아웃후 데이터가 남아있을수있음
*/
// id, pw 만드는 것
public boolean insert(MemberDTO dto) {
try {
// 중복 ID 체크
for (MemberDTO member : datas) {
if (member.getId().equals(dto.getId())) {
//기존 아이디 배열과 입력한 아이디 비교
System.out.println("로그 : 이미 존재하는 아이디입니다.");
return false;//유효성 검사
}
}
MemberDTO data = new MemberDTO();
data.setId(dto.getId());
data.setPw(dto.getPw());
data.setIsPremium(new ArrayList<>());
this.datas.add(data);
//새로운 data 객체 생성후
//입력한 아이디와 비밀번호를 새로 생성한 data에 저장
//새로운 회원이므로 즐겨찾기 빈 리스트로 초기화
//새로운 객체(회원) 기존 회원들 데이터스에 추가
System.out.println("로그 : MemberDTO insert()에서 true값을 반환.입력한 아이디와 비밀번호를 사용자 리스트에 저장");
return true;
}
catch(Exception e) {
System.out.println("로그 : 회원가입 중 오류가 발생했습니다: " + e.getMessage());
return false;//유효성 검사
}
}
// 즐겨찾기 배열에 영화 추가
public boolean update(String id, MovieDTO dto) {//회원 아이디와 영화 DTO 인자받아오기
for (MemberDTO memberDTO : this.datas) {
//데이터를 돌면서 모든 회원들 정보 확인
if (memberDTO.getId().equals(id)) {//입력아이디와 기존아이디가 일치할때
if (memberDTO.getIsPremium() == null) {//유효성 검사 : 즐겨찾기 목록이 null인 경우 초기화
memberDTO.setIsPremium(new ArrayList<>());
}
// 이미 즐겨찾기에 있는지 확인
for (MovieDTO movie : memberDTO.getIsPremium()) {
//즐겨찾기 목록을 돌면서 모든 영화확인
if (movie.getMovieId() == dto.getMovieId()) {
System.out.println("로그 : 이미 즐겨찾기에 존재하는 영화입니다.");
return false;//유효성 검사
}
}
// 즐겨찾기에 추가
memberDTO.getIsPremium().add(dto);//기존 사용자의 즐겨찾기에 추가
System.out.println("로그 : 즐겨찾기에 영화가 추가되었습니다.");
return true;
}
}
System.out.println("로그 : 사용자를 찾을 수 없습니다.");
return false;//유효성 검사
}
// 즐겨찾기에서 영화 삭제
public boolean delete(MemberDTO memberDto, MovieDTO movieDto) {//사용자정보와 영화 정보인자로 받아오기
if (datas.isEmpty()) {
System.out.println("로그 :등록된 회원이 없습니다.");
return false;
}
if (memberDto == null) {
//만약 사용자가 없다면
//관리자가 영화를 삭제하는 경우 (memberDto가 null일 때)
boolean removed = false;
//removed 변수를 만들고 false 저장
for (MemberDTO member : datas) {
ArrayList<MovieDTO> favorites = member.getIsPremium();
if (favorites != null) {
for (int i = favorites.size() - 1; i >= 0; i--) {
//for문으로 배열을 돌때 배열의 값을 삭제할시 끝값부터 검사하며 삭제해야함
if (favorites.get(i).getMovieId() == movieDto.getMovieId()) {
favorites.remove(i);
removed = true;
}
}
}
}
return removed;
}
// 일반 사용자가 자신의 즐겨찾기에서 삭제하는 경우
else {
for (MemberDTO member : datas) {
if (member.getId().equals(memberDto.getId())) {
ArrayList<MovieDTO> favorites = member.getIsPremium();
if (favorites != null) {
for (int i = 0; i < favorites.size(); i++) {
if (favorites.get(i).getMovieId() == movieDto.getMovieId()) {
favorites.remove(i);
System.out.println("로그 : 즐겨찾기에서 삭제됨");
return true;
}
}
}
}
}
System.out.println("로그 : 해당영화가 존재하지않음");
return false;
}
}
}
/* 관리자와 사용자의 즐겨찾기 목록의 삭제가 다른이유
* (관리자만 removed변수를 만들고 false를 저장하여 반환하는 이유)
*
* 사용자는 자신의 배열만없애면 되지만
* 관리자는 모든 즐겨찾기 목록을 확인하기때문
*/
MemberDTO
더보기
package weeks_2.Model;
import java.util.ArrayList;
public class MemberDTO {
private String id;
private String pw;
private String searchCondition;
private ArrayList<MovieDTO> isPremium; // 각자의 즐겨찾기 목록
public MemberDTO() {
this.isPremium = new ArrayList<>(); // 생성자에서 초기화
}
public ArrayList<MovieDTO> getIsPremium() {
return isPremium;
}
public void setIsPremium(ArrayList<MovieDTO> isPremium) {
this.isPremium = isPremium;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
this.pw = pw;
}
public String getSearchCondition() {
return searchCondition;
}
public void setSearchCondition(String searchCondition) {
this.searchCondition = searchCondition;
}
}
MovieDAO
더보기
package weeks_2.Model;
import java.util.ArrayList;
public class MovieDAO {
private ArrayList<MovieDTO> datas;
private int NUM; //영화 추가할 때 넣을 영화 번호
public MovieDAO() {
this.datas=new ArrayList<>();
this.NUM=1;
}
public ArrayList<MovieDTO> selectAll(MovieDTO dto){
try {
if(dto==null) { //입력받은 dto가 null이면
System.out.println("[로그]입력된 데이터가 null 입니다");
return null;
}
else if(dto.getCondition()==null) { //condition이 null이면
System.out.println("[로그]condition이 null 입니다");
return null;
}
if(dto.getCondition().equals("CRAWLING")) { //크롤링 할 때
ArrayList<MovieDTO> datas=Crawling.makeDatas(); //크롤링해서 반환한 데이터 저장
for(int i=0;i<this.datas.size();i++) {
this.datas.add(datas.get(i));
}
return datas;
}
else if(dto.getCondition().equals("PRINTALL")) { //전체출력 할 때
ArrayList<MovieDTO> datas=new ArrayList<>();
for(int i=0;i<this.datas.size();i++) {
MovieDTO data=new MovieDTO();
data.setMovieId(this.datas.get(i).getMovieId());
data.setTitle(this.datas.get(i).getTitle());
data.setRating(this.datas.get(i).getRating());
data.setViewCount(this.datas.get(i).getViewCount());
datas.add(data);
}
return datas;
}
else if(dto.getCondition().equals("SEARCH")) { //영화 검색
ArrayList<MovieDTO> datas=new ArrayList<>();
for(int i=0;i<this.datas.size();i++) {
MovieDTO data=new MovieDTO();
if(this.datas.get(i).getTitle().contains(dto.getTitle())) {
data.setMovieId(this.datas.get(i).getMovieId());
data.setTitle(this.datas.get(i).getTitle());
data.setViewCount(this.datas.get(i).getViewCount());
data.setRating(this.datas.get(i).getRating());
datas.add(data);
}
}
return datas;
}
else {
System.out.println("[로그]condition이 잘못 입력되었습니다");
}
}catch(Exception e) {
System.out.println("[로그]selectAll에서 오류 발생"+e.getMessage());
return null;
}
// return new ArrayList<>();
return null;
}
public MovieDTO selectOne(MovieDTO dto) { //영화 하나 출력할 때
try {
if(dto==null) { //dto가 null이면
System.out.println("[로그]입력된 데이터가 null 입니다");
return null;
}
for(int i=0;i<datas.size();i++) {
if(datas.get(i).getMovieId() == dto.getMovieId()) {
MovieDTO data=null;
data=new MovieDTO();
data.setMovieId(datas.get(i).getMovieId());
data.setTitle(datas.get(i).getTitle());
data.setRating(datas.get(i).getRating());
data.setViewCount(datas.get(i).getViewCount());
return data;
}
}
}catch(Exception e) {
System.out.println("[로그]selectOne에서 오류 발생"+e.getMessage());
return null;
}
return null;
}
public boolean insert(MovieDTO dto) { //영화 추가
try {
if(dto==null) { //dto가 null이면
System.out.println("[로그]입력된 데이터가 null 입니다");
return false;
}
else if(dto.getTitle()==null) { //입력받은 제목이 null이면
System.out.println("영화 제목은 필수입니다");
return false;
}
else if(dto.getRating()<0 || 100<dto.getRating()) { //입력받은 평점이 0~100 아니면
System.out.println("영화 평점은 0~100 사이여야 합니다");
return false;
}
MovieDTO data=new MovieDTO();
data.setMovieId(NUM++); //추가할 때 영화 번호 1씩 증가
data.setTitle(dto.getTitle());
data.setRating(dto.getRating());
data.setViewCount(0); //처음 시청수는 0
this.datas.add(data);
}catch(Exception e) {
System.out.println("insert에서 오류 발생"+e.getMessage());
return false;
}
return true;
}
public boolean update(MovieDTO dto) { //영화 시청할 때 update 메서드로 시청수 +1
try {
if(dto==null) { //인자로 받은 dto가 null
System.out.println("입력된 데이터가 null 입니다");
return false;
}
else if(dto.getMovieId()<=0) {//입력받은 숫자가 0보다 작거나 같으면
System.out.println("영화 번호는 1부터 시작합니다");
return false;
}
for(int i=0;i<this.datas.size();i++) {
if(this.datas.get(i).getMovieId()==dto.getMovieId()) {
this.datas.get(i).setViewCount((this.datas.get(i).getViewCount())+1);
return true;
}
}
}catch(Exception e) {
System.out.println("update에서 오류 발생"+e.getMessage());
}
return false; //영화가 리스트에 없으면
}
public boolean delete(MovieDTO dto) { //영화 번호로 입력받아서 삭제
try {
if(dto==null) { //인자로 받은 dto가 null이면
System.out.println("입력된 데이터가 null 입니다");
return false; //질문
}
else if(dto.getMovieId()<=0) { //입력받은 번호가 0 이하이면
System.out.println("영화 번호는 1부터 시작합니다");
return false;
}
for(int i=0;i<this.datas.size();i++) {
if(this.datas.get(i).getMovieId()==dto.getMovieId()) {
this.datas.remove(i);
return true;
}
}
System.out.println("삭제할 영화가 존재하지 않습니다");
}catch(Exception e) {
System.out.println("delete에서 오류 발생"+e.getMessage());
}
return false;
}
}
MovieDTO
더보기
package weeks_2.Model;
public class MovieDTO {
private int movieId; //번호
private String title; //제목
private int viewCount; //시청수
private double rating; //평점
private String condition;
public int getMovieId() {
return movieId;
}
public void setMovieId(int movieId) {
this.movieId = movieId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getViewCount() {
return viewCount;
}
public void setViewCount(int viewCount) {
this.viewCount = viewCount;
}
public double getRating() {
return rating;
}
public void setRating(double rating) {
this.rating = rating;
}
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
@Override
public String toString() {
return "MovieDTO [movieId=" + movieId + ", title=" + title + ", viewCount=" + viewCount + ", rating=" + rating
+ "]";
}
}
View
base가 되는 View(부모 뷰)
더보기
package weeks_2.View;
import weeks_2.Model.MovieDTO;
import java.util.Scanner;
public class View {
Scanner sc;
private static final int MenuMIN = 0;
private static final int MenuMAX = 2;
public View() {
this.sc = new Scanner(System.in);
}
// ========== 메뉴 출력 ==========
public void showMenu() {
System.out.println("============= NETFLIX =============");
System.out.println("1. 로그인");
System.out.println("2. 회원가입");
System.out.println("0. 프로그램 종료");
System.out.println("=================================");
}
public void printNull() {
System.out.println("");
}
// 광고 출력
public void showAd(MovieDTO movies) {
System.out.println(movies.getMovieId() + " " + movies.getTitle() + " " + movies.getRating());
System.out.println("==========================");
}
// 메뉴 번호 입력
// View 클래스의 메뉴 번호 입력 메소드
public int inputLogInMenuNum() {
int num;
while (true) {
try {
System.out.print("메뉴를 선택해주세요 >> ");
String input = sc.nextLine(); // nextInt() 대신 nextLine() 사용
num = Integer.parseInt(input); // 문자열을 정수로 변환
if (MenuMIN <= num && num <= MenuMAX) {
return num;
}
System.out.println("❌ 잘못된 입력입니다. 다시 입력해주세요.");
} catch (Exception e) {
System.out.println("❌ 숫자로 입력해주세요.");
}
}
}
// ========== 입력 메서드 ==========
// 번호 입력 메서드 안쓰는 메서드
public int inputNum() {
int num;
while (true) {
try {
System.out.print("번호를 선택해주세요 >> ");
num = sc.nextInt();
sc.nextLine(); // 버퍼 비우기
if (num >= 0) {
return num;
} else {
System.out.println("❌ 음수는 입력할 수 없습니다.");
}
} catch (Exception e) {
sc.nextLine(); // 입력 버퍼 비우기
System.out.println("❌ 숫자로 입력해주세요. >> ");
}
}
}
// 메인 메뉴용 입력 메소드 , 이거 말고
public int inputMenuNum() {
int num;
while (true) {
try {
System.out.print("번호를 선택해주세요 >> ");
num = sc.nextInt();
sc.nextLine(); // 버퍼 비우기
return num;
} catch (Exception e) {
sc.nextLine();
System.out.println("❌ 숫자로 입력해주세요.");
continue;
}
}
}
// ID/PW 입력 메서드
// ID >> 공백 체크
public String inputId() {
while (true) {
System.out.print("아이디를 입력해주세요 (0 : 뒤로가기)>> ");
String id = sc.nextLine();
// 입력값 정리 (앞뒤 공백 제거)
id = id.trim();
// 뒤로가기 선택
if (id.equals("0")) {
System.out.println("메인 메뉴로 돌아갑니다.");
return null;
}
// 유효성 검사
if (id.isEmpty()) {
System.out.println("❌ 아이디는 공백일 수 없습니다!");
continue; // 다음 줄에서 바로 입력 프롬프트가 나타나도록
}
if (id.contains(" ")) {
System.out.println("❌ 아이디에 띄어쓰기는 불가능합니다!");
continue; // 다음 줄에서 바로 입력 프롬프트가 나타나도록
}
return id; // 유효한 아이디인 경우 반환
}
}
// PW >> 공백 체크
public String inputPw() {
String pw;
while (true) {
System.out.print("비밀번호를 입력해주세요 (0 : 뒤로가기)>> ");
pw = sc.nextLine().trim();
// 뒤로가기 처리
if (pw.equals("0")) {
System.out.println("메인 메뉴로 돌아갑니다.");
return null;
}
if (pw.isEmpty()) {
System.out.println("❌ 비밀번호는 공백일 수 없습니다!");
} else if (pw.contains(" ")) {
System.out.println("❌ 비밀번호에 띄어쓰기는 불가능합니다!");
} else {
return pw;
}
}
}
// 평점 입력 메서드
public double inputRating() {
double rating;
while (true) {
try {
System.out.print("평점을 입력해주세요 (0.0 ~ 5.0) >> ");
rating = Double.parseDouble(sc.nextLine());
if (0.0 <= rating && rating <= 5.0) {
return rating;
} else {
System.out.print("❌ 평점은 0.0 ~ 5.0 사이여야 합니다.");
}
} catch (Exception e) {
System.out.println("❌ 숫자로 입력해주세요.");
}
}
}
// ========== 결과 출력 메서드 ==========
// 성공 문구
public void printSuccess() {
System.out.println("✅ 작업이 성공적으로 완료되었습니다!");
}
// 실패 문구
public void printFail() {
System.out.println("❌ 작업 수행에 실패했습니다. 다시 시도해주세요.");
}
// 잘못된 입력
public void printWrong() {
System.out.println("❌ 잘못된 입력입니다. 다시 입력해주세요.");
}
// 없는 자료
public void printEmpty() {
System.out.println("⚠️ 검색된 데이터가 없습니다...");
}
// 종료 메서드
// 프로그램 종료
public void printExit() {
System.out.println("🔄 프로그램을 종료합니다...");
String[] progressBar = { "⬛", "⬛", "⬛", "⬛", "⬛" };
for (int i = 0; i < progressBar.length; i++) {
System.out.print(progressBar[i]);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("\n프로그램이 종료되었습니다.");
}
// 로그아웃
public void printLogOut() {
System.out.println("👋 안전하게 로그아웃되었습니다.");
}
// ID 입력
public void printSignInIDErr(){
System.out.println("[안내] ID를 입력해주세요");
}
// 사용중인 ID
public void printSignInIdDuplicationErr(){
System.out.println("[안내] 이미 사용 중인 ID입니다.");
}
}
AdminView
더보기
package weeks_2.View;
public class AdminView extends View {
private static final int adminMenuMAX = 9;
private static final int adminMenuMIN = 6;
private boolean hasAD;
private int adMovieId;
public int getAdMovieId() {
return adMovieId;
}
public void setAdMovieId(int adMovieId) {
this.adMovieId = adMovieId;
}
public AdminView() {
super();
}
// 관리자 메뉴 출력
public void showAdminMenu() {
System.out.println("\n======== 관리자 메뉴 ========");
System.out.println("6. 영상 추가");
System.out.println("7. 영상 삭제");
System.out.println("8. 광고 영상 관리");
System.out.println("9. 로그아웃");
System.out.println("0. 프로그램 종료");
System.out.println("=========================");
}
// 관리자 메뉴 번호 입력 (Override)
@Override
public int inputNum() {
while (true) {
try {
System.out.print("메뉴를 선택해주세요 >> ");
int num = Integer.parseInt(sc.nextLine());
if ((adminMenuMIN <= num && num <= adminMenuMAX) || num == 0) {
return num;
}
System.out.println("❌ 잘못된 메뉴 번호입니다. 다시 입력해주세요.");
} catch (NumberFormatException e) {
System.out.println("❌ 숫자로 입력해주세요.");
}
}
}
// ========== 영상 추가 파트 ==========
// 새로운 영상 이름 입력
public String inputNewMovie() {
System.out.println("\n======== 새 영상 추가 ========");
String movieName;
while (true) {
System.out.print("영상 제목을 입력해주세요 (0 : 뒤로가기) >> ");
movieName = sc.nextLine().trim();
if(movieName.isEmpty()) {
System.out.println("❌ 영상 제목이 없다면 등록할 수 없습니다!");
continue;
}
return movieName;
}
}
// 영상 평점 입력
public double inputRating() {
double rating;
while (true) {
try {
System.out.print("평점을 입력해주세요 (0.0 ~ 100.0) >> ");
rating = Double.parseDouble(sc.nextLine());
if (0.0 <= rating && rating <= 100.0) {
return rating;
} else {
System.out.println("❌ 평점은 0.0 ~ 100.0 사이여야 합니다.");
}
} catch (Exception e) {
System.out.println("❌ 숫자로 입력해주세요.");
}
}
}
// ========== 영상 삭제 파트 ==========
// 삭제할 영상 번호 입력
public int inputDeleteMovie() {
System.out.println("\n======== 영상 삭제 ========");
while (true) {
try {
System.out.print("삭제할 영상 번호를 입력해주세요 (0 : 뒤로가기) >> ");
return Integer.parseInt(sc.nextLine());
} catch (NumberFormatException e) {
System.out.println("❌ 올바른 숫자를 입력해주세요.");
}
}
}
// 영상 삭제 확인
public boolean printCheckDelete() {
while (true) {
System.out.print("정말 해당 영상을 삭제하시겠습니까? (Y/N) >> ");
String answer = sc.nextLine().trim().toUpperCase();
if (answer.equals("Y"))
return true;
if (answer.equals("N"))
return false;
System.out.println("❌ Y 또는 N으로 입력해주세요.");
}
}
// ========== 광고 파트 ==========
// 새 광고 영상 등록
public boolean hasAD() {
return this.hasAD;
}
// hasAD값 받기
public void setAdStatus() {
System.out.print("광고가 이미 등록되어 있습니까? (Y/N) >> ");
String answerAD = sc.nextLine().toUpperCase();
if (answerAD.equals("Y")) {
this.hasAD = true;
}
if (answerAD.equals("N")) {
this.hasAD = false;
}
}
public int addNewAd() {
System.out.println("\n======== 광고 영상 등록 ========");
while (true) {
try {
System.out.print("광고로 등록할 영상 번호를 입력해주세요 (0 : 뒤로가기) >> ");
return Integer.parseInt(sc.nextLine());
} catch (NumberFormatException e) {
System.out.println("❌ 올바른 숫자를 입력해주세요.");
}
}
}
// 기존 광고 삭제 확인
public boolean deleteAD() {
System.out.println("\n⚠️ 광고가 이미 등록되어 있습니다.");
while (true) {
System.out.print("현재 광고를 삭제하시겠습니까? (Y/N) >> ");
String answer = sc.nextLine().trim().toUpperCase();
if (answer.equals("Y"))
return true;
if (answer.equals("N"))
return false;
System.out.println("❌ Y 또는 N으로 입력해주세요.");
}
}
// 관리자 로그아웃 메시지
public void logoutAdmin() {
System.out.println("\n✅ 관리자 계정에서 로그아웃되었습니다.");
System.out.println("메인 메뉴로 돌아갑니다.");
}
// ========== 광고 삭제 파트 ========== 안쓰는 메서드
//public void printDelete() {
// System.out.println("이미 광고가 있습니다. 삭제하시려면 Y를 입력, 아니면 N을 입력해주세요");
// }
// y, n 입력
//public String yOrn() {
//String input = sc.next();
//return input;
}
ClientView
더보기
package weeks_2.View;
import weeks_2.Model.MovieDTO;
import java.util.ArrayList;
public class ClientView extends View {
private static final int clientMenuMAX = 5;
public ClientView() {
super(); // 부모 클래스의 Scanner 초기화
}
// 클라이언트 메뉴 출력 및 선택 번호 반환
public void showClientMenu(MovieDTO dto) {
System.out.println("\n======== NETFLIX 메인 메뉴 ========");
System.out.println("1. 시청하기");
System.out.println("2. 즐겨찾기");
System.out.println("3. 영상 목록 출력");
System.out.println("4. 영화 이름 검색");
System.out.println("5. 로그아웃");
System.out.println("0. 프로그램 종료");
if (dto != null) {
System.out.println(dto.getMovieId() + " " + dto.getTitle() + " " + dto.getRating());
}
System.out.println("================================");
}
// 클라이언트 메뉴 번호 입력받아 반환
public int inputClientMenuNum() {
int num;
while (true) {
try {
System.out.print("메뉴를 선택해주세요 >> ");
num = Integer.parseInt(sc.nextLine());
if (0 <= num && num <= clientMenuMAX) {
return num;
}
System.out.println("❌ 잘못된 입력입니다. 다시 입력해주세요.");
} catch (Exception e) {
System.out.println("❌ 숫자로 입력해주세요.");
}
}
}
// 시청하기 기능 안쓰는 메서드
//public void watchingMovie() {
//System.out.println("\n🎬 영화 재생을 시작합니다...");
//String watching = "▶ 영..화..상..영..중..";
//for (char ch : watching.toCharArray()) {
// System.out.print(ch);
// try {
// Thread.sleep(500);
//} catch (InterruptedException e) {
//e.printStackTrace();
//}
// }
//System.out.println("\n✅ 영상 시청이 종료되었습니다!");
//}
// 즐겨찾기 메뉴 출력
public void showFavoriteMenu() {
System.out.println("\n======== 즐겨찾기 메뉴 ========");
System.out.println("1. 즐겨찾기 추가");
System.out.println("2. 즐겨찾기 삭제");
System.out.println("0. 이전 메뉴로");
System.out.println("============================");
}
// 영화 목록 출력
public void showMovieList(ArrayList<MovieDTO> movies) {
System.out.println("\n======== 영화 목록 ========");
if (movies.isEmpty()) {
System.out.println("등록된 영화가 없습니다.");
} else {
System.out.println("[번호] 제목 평점 ");
System.out.println("----------------------------");
for (MovieDTO movie : movies) {
System.out.printf("[%d] %-12s %.1f \n", movie.getMovieId(), movie.getTitle(), movie.getRating());
}
}
System.out.println("==========================");
}
// 영화 검색 키워드 입력받아 반환
public String inputSearchKeyword() {
while (true) {
System.out.print("검색할 영화 제목을 입력해주세요 (0 : 뒤로가기)>> ");
String keyword = sc.nextLine().trim();
//if (keyword.equals("0")) { //빼기
// return keyword;
//}
if (!keyword.isEmpty()) {
return keyword;
}
System.out.println("❌ 검색어 입력은 필수입니다!");
}
}
// 영화 상세 정보 출력
public void showMovieDetail(MovieDTO movie) {
if (movie == null) {
System.out.println("⚠️ 해당 영상의 정보를 찾을 수 없습니다...");
return;
}
System.out.println("\n======== 영화 상세 정보 ========");
System.out.println("번 호: " + movie.getMovieId());
System.out.println("제 목: " + movie.getTitle());
System.out.println("평 점: " + movie.getRating());
System.out.println("시청횟수: " + movie.getViewCount());
System.out.println("============================");
}
// 번호 입력받아 반환
@Override
public int inputNum() {
int num;
while (true) {
try {
System.out.print("번호를 선택해주세요 (0 : 뒤로가기) >> ");
num = sc.nextInt();
sc.nextLine(); // 버퍼 비우기
if (num >= 0) {
return num;
} else {
System.out.println("❌ 음수는 입력할 수 없습니다.");
}
} catch (Exception e) {
sc.nextLine(); // 입력 버퍼 비우기
System.out.println("❌ 숫자로 입력해주세요. >> ");
}
}
}
// 정상 작동하면 Controller > 1. 시청하기 >> 배열이 안비어있을 때 안에 movies 넣어야함
public int inputMovieChoiceNum(ArrayList<MovieDTO> movies) {
while (true) {
System.out.print("번호를 선택해주세요 ");
int num;
try {
num = sc.nextInt();
sc.nextLine(); // 버퍼 비우기
// if (num == 0) { //빼기
// return num;
// }
if (num > 0 && num <= movies.size()) {
return num;
} else {
System.out.println("❌ 등록되지 않은 영상입니다.");
}
} catch (Exception e) {
sc.nextLine();
System.out.println("❌ 숫자로 입력해주세요.");
}
}
}
public void showSelectMovieNum() {
System.out.println("상세 정보를 볼 영화의 번호를 선택하세요 (0 : 뒤로가기)");
}
public int inputSelectMovieNum() {
int num = inputNum();
return num;
}
}
'Back-End' 카테고리의 다른 글
| [JAVA] SQL과 JAVA를 합친 JDBC (1) | 2025.02.18 |
|---|---|
| [JAVA] 오답노트 및 발표 피드백 (1) | 2025.02.17 |
| [크롤링] Jsoup과 Selenium (3) | 2025.02.01 |
| [JAVA] 기초 공부 15 크롤링 (5) | 2025.01.24 |
| [JAVA] 기초 공부 14 MVC-1 (1) | 2025.01.23 |