<윤로그> 기술 설명서

정윤서·2024년 1월 29일
0
post-custom-banner

✔️ 개요 및 목적

🔗 개요

  • 개인적인 경험, 생각, 개발 관련 내용을 공유하는 블로그

🔗 목적

  • 사용자가 자신의 일상, 개발 경험, 또는 생각을 공유할 수 있는 공간 제공
  • 다른 사용자들과 소통하며 커뮤니티를 형성을 통해 정보 교환, 의견 공유, 상호 작용의 장을 마련

✔️ Github


✔️ 윤로그 주소


✔️ 사용된 기능

🔗 버전관리

  • Github

🔗 배포

  • AWS
  • Ubuntu
  • FileZila
  • mobaXterm
  • PostgreSQL
  • ngnix

🔗 개발환경

  • IntelliJ IDEA
  • MySQL
  • Windows11
  • Chrome

🔗 기술스택

  • Java
  • Spring Boot
  • HTML, CSS, JS
  • Bootstrap

🔗 API

  • Kakao OAuth 2.0
  • Google OAuth 2.0
  • Naver OAuth 2.0
  • OpenWeatherMap API

✔️ ERD


✔️ 요구사항

📚 게시물

- 게시물 추가, 수정, 삭제, 좋아요, 조회수 표시
- 댓글 추가, 수정, 삭제, 추천
- 해시태그 추가, 해시태그 별 조회
- 카테고리 별 조회
- 게시물 검색

🙍 회원

- 회원가입
- 로그인
- 로그아웃
- 소셜 로그인 (구글, 네이버, 카카오)
- 비밀번호 찾기 (이메일로 임시 비밀번호 발급)
- 비밀번호 변경
- 회원정보 수정
- 다른 회원 정보 조회, 구독
- 사용자 닉네임 검색

🎸 기타

- 오늘의 명언
- 현재 대전 날씨, 시간


✔️ 주요 기능 설명

📌 게시판

  • 게시판 글쓰기 (카테고리 선택, 토스트 에디터 적용, 태그 입력)

글쓰기 view

<h3 class="border-bottom pb-2 mt-3">글쓰기</h3>
    <div>
        <form method="post" th:object="${postForm}">
            <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
            <div th:replace="~{form_errors :: formErrorsFragment}"></div>
            <div class="my-3" style="width:20%">
                <select th:field="*{category}" class="form-select">
                    <option value="" selected>카테고리를 선택하세요</option>
                    <option value="일기장">일기장</option>
                    <option value="개발 일지">개발 일지</option>
                    <option value="잡담">잡담</option>
                </select>
            </div>
            <div class="mb-3">
                <label for="subject" class="form-label fs-5">제목</label>
                <input type="text" class="form-control" th:field="*{subject}" style="font-size:13pt;">
            </div>
            <div class="mb-3">
                <label for="content" class="form-label fs-5">내용</label>
                <div id="editor"></div>
            </div>
            <div class="mb-3">
                <label for="tag" class="form-label fs-5">태그</label>
                <div class="input-group">
                    <input type="text" class="form-control" style="font-size:13pt;" id="inputTag" placeholder="#태그이름" name="inputTag">
                    <div class="btn btn-outline-light" style="background:lightcoral; border:lightcoral;">등록
                    </div>
                </div>
            </div>
            <div class="d-flex justify-content-end">
                <input onclick="saveEditorContent()" type="submit" value="저장" class="btn btn-outline-light" style="background:lightcoral">
            </div>
            <input type="hidden" th:field="*{content}" id="content">
        </form>
    </div>

글쓰기 controller

@PreAuthorize("isAuthenticated()")
@GetMapping("/create")
public String create(PostForm postForm, Model model, Principal principal, @RequestParam(value = "nickname", defaultValue = "") String nickname) {
  SiteUser user = this.userService.getUser(principal.getName());
  List<SiteUser> authorList = this.userService.searchUser(nickname);
  model.addAttribute("user", user);
  model.addAttribute("authorList", authorList);
  model.addAttribute("searchKw", nickname);
  return "post_form";
}

@PreAuthorize("isAuthenticated()")
@PostMapping("/create")
public String create(@Valid PostForm postForm, BindingResult bindingResult, Principal principal, Model model, @RequestParam(value = "inputTag", defaultValue = "") String hashtag) {
  SiteUser user = this.userService.getUser(principal.getName());
  model.addAttribute("user", user);
  if (bindingResult.hasErrors()) {
    return "post_form";
  }
  this.postService.createPost(postForm.getSubject(), postForm.getContent(), postForm.getCategory(), user, hashtag);
  return "redirect:/";
}

📌 날씨 및 명언

  • 현재 대전 날씨, 현재 시간 표시, 랜덤 명언 표시

랜덤 명언 스크립트

const quotes = [
                                "자신의 능력을 믿어야 한다. 그리고 끝까지 굳세게 밀고 나가라. -로잘린 카터",
                                "할 수 있는 일을 해낸다면, 우리 자신이 가장 놀라게 될 것이다. -토마스 A. 에디슨",
                                "불가능해 보이는 것은 불확실한 가능성보다 항상 더 낫다. -아리스토텔레스",
                                "나는 삶에서 언제나 치열함을 추구하라고, 삶을 만끽하라고 배웠다. -헬렌 켈러",
                                "성공한 사람이 아니라 가치있는 사람이 되기 위해 힘쓰라 -알버트 아인슈타인",
                                "시간은 환상이다. 점심시간은 두 배로 그렇다. -더글러스 애덤스",
                                "미래는 현재 우리가 무엇을 하는가에 달려 있다. -마하트마 간디",
                                "낭비한 시간에 대한 후회는 더 큰 시간 낭비이다. -메이슨 쿨리",
                                "현재뿐 아니라 미래까지 걱정한다면 인생은 살 가치가 없을 것이다. -윌리엄 서머셋 모옴",
                                "왜 굳이 의미를 찾으려 하는가? 인생은 욕망이지, 의미가 아니다. -찰리 채플린"
                            ];
function updateQuote() {
  const quoteContainer = document.getElementById("quote-container");
  const randomIndex = Math.floor(Math.random() * quotes.length);
  const newQuote = quotes[randomIndex];
  quoteContainer.innerHTML = `<p>${newQuote}</p>`;
}
updateQuote();

날씨 및 시간 스크립트

const apiUrl = "https://api.openweathermap.org/data/2.5/weather?q=Daejeon&lang=kr&appid=c91e22b768bbc74e94460d26396a2d5f"

const temperatureElement = document.getElementById("temperature");
const weatherDescriptionElement = document.getElementById("weather-description");
const weatherIconElement = document.getElementById("weather-icon");

fetch(apiUrl)
  .then(response => response.json())
  .then(data => {
  const weatherDescription = data.weather[0].description;
  const temperatureKelvin = data.main.temp;

  const temperatureCelsius = (temperatureKelvin - 273.15).toFixed(1);

  temperatureElement.textContent = `${temperatureCelsius}°C`;
  weatherDescriptionElement.textContent = `${weatherDescription}`;

  const iconCode = data.weather[0].icon;
  const iconUrl = `https://openweathermap.org/img/wn/${iconCode}.png`;
  const iconImage = document.createElement("img");
  iconImage.src = iconUrl;
  weatherIconElement.appendChild(iconImage);
})
  .catch(error => {
  console.error("날씨 정보를 가져오는 중 오류가 발생했습니다.", error);
});

const currentDateElement = document.getElementById("current-date");


const currentDate = new Date();


const year = currentDate.getFullYear();
const month = currentDate.getMonth() + 1;
const day = currentDate.getDate();

const formattedDate = `${year}${month}${day}`;

currentDateElement.textContent = `${formattedDate}`;

const currentTimeElement = document.getElementById("current-time");

function displayCurrentTime() {
  const currentTime = new Date();
  const hours = currentTime.getHours().toString().padStart(2, '0');
  const minutes = currentTime.getMinutes().toString().padStart(2, '0');
  const seconds = currentTime.getSeconds().toString().padStart(2, '0');
  const formattedTime = `${hours}:${minutes}:${seconds}`;
  currentTimeElement.textContent = `${formattedTime}`;
}
displayCurrentTime();
setInterval(displayCurrentTime, 1000);

📌 구독

  • 유저 페이지에서 구독
  • 구독 후 메인페이지 구독 리스트
  • 구독한 유저 클릭 시 최근 게시물

구독 controller

@PostMapping("/subscribe/{username}")
public String subscribe(@PathVariable("username") String username, Principal principal) {
  SiteUser user1 = this.userService.getUser(principal.getName());
  SiteUser user2 = this.userService.getUser(username);
  this.userService.subscribeUser(user1, user2);
  return String.format("redirect:/user/page/%s", username);
}
post-custom-banner

0개의 댓글