14. 강좌 목록 구현 및 강좌 신청

kdew0308·2023년 4월 25일
0

학습 관리 시스템

목록 보기
15/17

1. client 입장 - 강좌 목록 구현

1-1) 강좌 목록 페이지 구현

1) layout.html 추가

<a href="/course">강좌 목록</a>

2) CourseController.java 생성

@RequiredArgsConstructor
@Controller
public class CourseController extends BaseController{

    private final CourseService courseService;

    @GetMapping("/course")
    public String course(Model model, CourseParam parameter) {

        
        return "course/index";
    }
}

3) index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>강좌 정보 페이지</title>
</head>
<body>
  <h1> 강좌 정보 페이지 </h1>

  <div th:replace="/fragments/layout.html :: fragment-body-menu"></div>

</body>
</html>

1-2) 강좌 목록 리스트로 구현

1) index.html 추가

<div th:text="${list}"></div>

2) CourseController.java 추가

List<CourseDto> list = courseService.frontList(parameter);
        model.addAttribute("list", list); // list 목록이 이제 내려감

3) CourseService.java 추가

List<CourseDto> frontList(CourseParam parameter);

List<CourseDto> list(CourseParam parameter); 이거를 쓸 수도 있지만
강좌에 대해서 관리자가 강좌를 화면에 안나오게 하거나 강좌 판매를 중단할 수 있기 때문에 따로 만들었음
즉 관리자 페이지에 나오는 리스트랑 프론트에 나오는 목록 리스트랑 같으면 안됨 프론트에 필요한 내용만 나오게 만들었음!

4) CourseDto.java 추가

courseList를 가져올 수 있도록

public static List<CourseDto> of(List<Course> courses) {

        if (courses == null) {
            return null;
        }

        List<CourseDto> courseList = new ArrayList<>();
        for(Course x : courses) {
            courseList.add(CourseDto.of(x));
        }
        return courseList;
    }

5) CourseServiceImpl.java 추가

@Override
    public List<CourseDto> frontList(CourseParam parameter) {

        List<Course> courseList = courseRepository.findAll();

        return CourseDto.of(courseList);

    }

1-3) 강좌 목록 페이지 다듬기

1) index.html 추가

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8">
    <title>강좌 정보 페이지</title>

    <style>
      span.price {
        text-decoration: line-through;
      }
      span.salePrice {
        font-weight: bold;
      }
    </style>

  </head>
  <body>
    <div th:replace="/fragments/layout.html :: fragment-body-menu"></div>

    <h1> 강좌 정보 페이지 </h1>

    <div>
      <a href="/course">
        전체 (<span th:text="${courseTotalCount}">0</span>)
      </a>

      <th:block th:each="y : ${categoryList}">
        |
        <a th:href="'/course?categoryId=' + ${y.id}">
          <span th:text="${y.categoryName}">카테고리명</span> (<span th:text="${y.courseCount}">0</span>)
        </a>
      </th:block>
      <hr>
    </div>



    <ul>
      <li th:each="x : ${list}">
        <div>
          <a th:href="'/course/' + ${x.id}">
            <h3 th:text="${x.subject}">강좌명</h3>
          </a>
          <div>
            <p th:text="${x.summary}"></p>
            <p>
              판매가 : <span class="price" th:text="${x.price}"></span>
              할인가 : <span class="salePrice"th:text="${x.salePrice}"></span>
            </p>
          </div>
        </div>
      </li>
    </ul>
  </body>
</html>

2) CategoryMapper.java 생성

List<CategoryDto> select(CategoryDto parameter);

3) CategoryMapper.xml 생성

<?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="com.kdew.dewlms.admin.mapper.CategoryMapper">

    <select id="select"
            resultType="com.kdew.dewlms.admin.dto.CategoryDto">

        SELECT c.*, (SELECT COUNT(*) FROM course WHERE category_id = c.id) AS course_count
        FROM category c
        WHERE using_yn = 1
        ORDER BY sort_value DESC

    </select>
</mapper>

4) CategoryDto.java 추가

course_count가 mapper에서 새로 만들어졌기 때문에 dto에서도 추가!

int courseCount;


카테고리 클릭하면 해당하는 카테고리 id로 페이지 이동

5) CourseController.java 수정

@GetMapping("/course")
    public String course(Model model, CourseParam parameter) {

        // 카테고리id 값 가져오기


        List<CourseDto> list = courseService.frontList(parameter);
        model.addAttribute("list", list); // list 목록이 이제 내려감



        int courseTotalCount = 0;
        List<CategoryDto> categoryList = categoryService.frontList(CategoryDto.builder().build());
        if (categoryList != null) {
            for(CategoryDto x : categoryList ) {
                courseTotalCount += x.getCourseCount();
            }
        }
        model.addAttribute("categoryList", categoryList);
        model.addAttribute("courseTotalCount", courseTotalCount);

        return "course/index";
    }

6) CourseServiceImpl.java 수정

@Override
    public List<CourseDto> frontList(CourseParam parameter) {

        if (parameter.getCategoryId() < 1) {
            // 전체를 다 가져온 것이기 때문
            List<Course> courseList = courseRepository.findAll();
            return CourseDto.of(courseList);
        }

        Optional<List<Course>> optionalCourse = courseRepository.findByCategoryId(parameter.getCategoryId());
        if (optionalCourse.isPresent()) {
            return CourseDto.of(optionalCourse.get());
        }
        return null;

    }

7) CourseRepository.java 추가

Optional<List<Course>> findByCategoryId(long categoryId);

8) CourseParam.java 추가

long categoryId;

2. 강좌 상세 정보 구현

2-1) 강좌 상세 정보 페이지 기본

1) CourseParam.java 추가

long id;

2) CourseController.java 추가

@GetMapping("/course/{id}")
    public String courseDetail(Model model, CourseParam parameter) {

        CourseDto detail = courseService.frontDetail(parameter.getId());
        model.addAttribute("detail",detail);


        return "course/detail";
    }

"/course/{id}"에서 id는 CourseParam에 id로 자동 매핑이 됨

3) detail.html 생성

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>강좌 상세 페이지</title>

</head>
<body>

  <h1>강좌 상세 정보</h1>
  <div th:replace="/fragments/layout.html :: fragment-admin-body-menu"></div>

  <div th:text="${detail}">

  </div>

</body>
</html>

4) CourseService.java 추가

CourseDto frontDetail(long id);

5) CourseServiceImpl.java 추가

@Override
    public CourseDto frontDetail(long id) {

        Optional<Course> optionalCourse = courseRepository.findById(id);
        if (optionalCourse.isPresent()) {
            return CourseDto.of(optionalCourse.get());
        }
        return null;
    }

2-2) 화면 정렬

1) detail.html 추가

<h1>강좌 상세 정보</h1>
  <div th:replace="/fragments/layout.html :: fragment-admin-body-menu"></div>

  <div>
    <h2>강좌명 : <span th:text="${detail.subject}">강좌</span></h2>
    <div th:utext="${detail.contents}"></div>
    <div>
      <p>가격 : <span th:text="${detail.price}">0</span></p>
      <p>할인가격 : <span th:text="${detail.salePrice}">0</span></p>
    </div>

    <div>
      <button type="button">수강신청</button>
      <a href="/course">강좌목록</a>
    </div>

  </div>

3. 강좌 신청

3-1) 테이블 생성

1) TakeCourseCode.java 생성

public interface TakeCourseCode {

    String STATUS_REQ = "REQ"; // 수강신청
    String STATUS_COMPLETE = "COMPLETE"; // 결제완료
    String STATUS_CANCEL = "CANCEL"; // 수강취소

}

2) TakeCourse.java 생성

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Data
public class TakeCourse implements TakeCourseCode{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    long courseId;
    String userId;
    long payPrice; // 결제 금액(할인된 금액으로)
    String status; // 상태(수강신청,결제완료, 수강취소)

    LocalDateTime regDt; // 신청일
}

3) TakeCourseRepository.java 생성

public interface TakeCourseRepository extends JpaRepository<TakeCourse, Long> {

}

3-2) axios 포함

1) axios 복사

<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>

2) detail.html 생성

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>강좌 상세 페이지</title>

  <script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
  <script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script>
  <script>

    $(function () {

      $('#submitForm').on('submit', function () {
    	if (!confirm('수강 신청을 하시겠습니까?')) {
          return false;
        }
    
        var $thisForm = $(this);

        var url = '/api/course/req.api';
        var parameter = {
          courseId: $thisForm.find('input[name=id]').val()
        }
        axios.post(url, parameter).then(function (response) {

        }).catch(function (err) {

        });

        return false;
      });
    });

  </script>
</head>
<body>

  <h1>강좌 상세 정보</h1>
  <div th:replace="/fragments/layout.html :: fragment-admin-body-menu"></div>

  <div>
    <h2>강좌명 : <span th:text="${detail.subject}">강좌</span></h2>
    <div th:utext="${detail.contents}"></div>
    <div>
      <p>가격 : <span th:text="${detail.price}">0</span></p>
      <p>할인가격 : <span th:text="${detail.salePrice}">0</span></p>
    </div>

    <div>
      <form id="submitForm" method="post">
        <input type="hidden" name="id" th:value="${detail.id}">
        <button type="submit">수강신청</button>
        <a href="/course">강좌목록</a>
      </form>
    </div>

  </div>

</body>
</html>

적용 완료!

3-3) 서버에서 받기

@Controller : 뷰 엔진의 파일 형태를 리턴
@RestController : JSON 바디 형태로 데이터가 리턴

1) TakeCourseInput.java 생성

long courseId;
String userId;

2) ApiCourseController.java 생성

@RequiredArgsConstructor
@RestController
public class ApiCourseController extends BaseController{

    @PostMapping("/api/course/req.api")
    public ResponseEntity<?> courseReq(Model model
            , @RequestBody TakeCourseInput parameter
            , Principal principal) {

        parameter.setUserId(principal.getName());
        
        boolean result = courseService.req(parameter);
        if(!result) {
            return ResponseEntity.badRequest().body("수강신청에 실패하였습니다.");
        }


        return ResponseEntity.ok().body(parameter);

    }
}

3) CourseService.java 추가

boolean req(TakeCourseInput parameter);

courseId와 userId를 잘 가져왔음!

4) CourseServiceImpl.java 추가

데이터 받기!

private final TakeCourseRepository takeCourseRepository;

@Override
    public boolean req(TakeCourseInput parameter) {

        Optional<Course> optionalCourse = courseRepository.findById(parameter.getCourseId());
        if (!optionalCourse.isPresent()) {
            return false;
        }
        Course course = optionalCourse.get();

        TakeCourse takeCourse = TakeCourse.builder()
                .courseId(course.getId())
                .userId(parameter.getUserId())
                .payPrice(course.getSalePrice())
                .regDt(LocalDateTime.now())
                .status(TakeCourse.STATUS_REQ)
                .build();

        takeCourseRepository.save(takeCourse);

        return true;
    }



3-4) 같은 이름으로 같은 강좌 신청했을 때 처리

1) ServiceResult.java 생성

@Data
public class ServiceResult {

    boolean result; // 서비스가 true를 리턴할 것인지 false를 리턴할 것인지
    String message; // 만약 false면 그 에러 메세지는 무엇인지

}

2) TakeCourseRepository.java 생성

long countByCourseIdAndUserIdAndStatusIn(long courseId, String userId, Collection<String> statusList);

3) CourseService.java 수정

ServiceResult req(TakeCourseInput parameter);

4) CourseServiceImpl.java 수정

public ServiceResult req(TakeCourseInput parameter) {

        ServiceResult result = new ServiceResult();

        // 이미 신청 정보가 있는지 확인 (아이디와 강좌 아이디를 통해!)
        String[] statusList = {TakeCourse.STATUS_REQ, TakeCourse.STATUS_COMPLETE};
        long count = takeCourseRepository.countByCourseIdAndUserIdAndStatusIn(course.getId()
        , parameter.getUserId(), List.of(statusList));

        if(count > 0) {
            result.setResult(false);
            result.setMessage("이미 신청한 강좌 정보가 존재합니다.");
            return result;

        }

        result.setResult(true);
        return result;
    }

5) ApiCourseController.java 수정

ServiceResult result = courseService.req(parameter);
if(!result.isResult()) {
	return ResponseEntity.badRequest().body(result.getMessage());
}

3-5) 강좌 정보가 존재하지 않을 때

Optional<Course> optionalCourse = courseRepository.findById(parameter.getCourseId());
if (!optionalCourse.isPresent()) {
  result.setResult(false);
  result.setMessage("강좌 정보가 존재하지 않습니다.");
  return result;
}
Course course = optionalCourse.get();

3-6) 오류 메세지를 클라이언트가 볼 수 있도록

서버에서 200을 내리고 -> alert로 처리!

1) ApiCourseController.java 수정

ServiceResult result = courseService.req(parameter);
        if(!result.isResult()) {

            ResponseResult responseResult = new ResponseResult(false, result.getMessage());

            return ResponseEntity.ok().body(responseResult);
        }
        ResponseResult responseResult = new ResponseResult(true);
        return ResponseEntity.ok().body(responseResult);

2) ResponseResult.java 생성

@Data
public class ResponseResult {

    ResponseReultHeader header;
    Object body;

    public ResponseResult(boolean result, String message) {
        header = new ResponseReultHeader(result, message);
    }

    public ResponseResult(boolean result) {
        header = new ResponseReultHeader(result);
    }
}

3) ResponseReultHeader.java 생성

@Data
public class ResponseReultHeader {

    boolean result;
    String message;

    public ResponseReultHeader(boolean result, String message) {
        this.result = result;
        this.message = message;
    }

    public ResponseReultHeader(boolean result) {
        this.result = result;
    }
}

2) detail.html 추가

response.data = response.data || {};
response.data.header = response.data.header || {};
// 강좌 등록에 실패할 때

if (!response.data.header.result) {
  alert(response.data.header.message);
  return false;
}

// 강좌 등록에 정상적일때
alert(' 강좌가 정상적으로 신청되었습니다.');
location.href='/';



0개의 댓글

관련 채용 정보