Spring Projcect/학습 관리 시스템 & 백오피스 구축

Chapter 13. 강좌 목록

계란💕 2022. 8. 23. 02:50

13.1 강좌 entity 및 repository 구성

 

  Ex)

    - course 패키지 아래 Course 클래스 만들기 

<hide/>
package com.zerobase.fastlms.course;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDate;
@Data
@Entity
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    String imagePath;;
    String keyword;
    String subject;

    @Column(length = 1000)
    String summary;

    @Lob
    String contents;
    long price;
    long salesPrice;
    LocalDate salesEndDt;
}

 

    - CourseRepository 인터페이스

<hide/>
package com.zerobase.fastlms.admin.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
    String categoryName;
    int sortValue;
    boolean usingYn;    // 사용가능한지

}

 

    - AdminCourseController 

<hide/>
package com.zerobase.fastlms.course.controller;
import com.zerobase.fastlms.admin.service.CategoryService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
@RequiredArgsConstructor
public class AdminCourseController {
    private final CategoryService categoryService;

    @GetMapping("/admin/course/list.do")
    public String list(Model model){
        return "admin/course/list";
    }
}

 

 

 

13.2 강좌 기능 심플화 등록 및 심플화 목록 구현

  Ex)

    - 컨트롤러

<hide/>
@GetMapping("/admin/course/add.do")
public String add(Model model){
    return "admin/course/add";
}

 

    - add 페이지를 만든다. (Cource 엔티티 클래스의 변수 이름을 보고 참고해야한다.)

<hide/>
<!doctype html>
<html lang="ko"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>관리자 화면</title>
    <style>
        .detail table{
            width: 100%;
            border-collapse: collapse;
        }
        .detail table th, .detail table td{
            border: solid 1px #000;
        }
        .buttons{
            margin-top: 20px;
            text-align: center;
        }
        .buttons a, .buttons button{
            border-width: 0;
            background-color: transparent;
            text-decoration: underline;
            font-size: 12px;
            line-height: 20px;
            height: 20px;
            cursor: pointer;
        }

    </style>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
    <script>
        $(document).ready(function (){

        });

    </script>
</head>
<body>
<h1>강좌 관리 - 강좌 등록</h1>

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

<div class="detail">

     <form method="post">
         <table>
             <tbody>
             <tr>

                 <th>
                     강좌명:
                 </th>
                 <td>
                     <input type="text" name="subject" required placeholder="강좌명 입력"/>
                 </td>

             </tr>
             </tbody>
         </table>
         <div class="buttons">
             <button type="submit">강좌 등록 하기</button>
             <a href="/admin/course/list.do">목록으로 이동</a>
         </div>
     </form>
</div>
</body>
</html>

 

    - 코스 서비스 

<hide/>
package com.zerobase.fastlms.course.service;
import com.zerobase.fastlms.course.model.CourseInput;
public interface CourseService {
    /**
     * 강좌 등록 
     */
    boolean add(CourseInput parameter);   
}

 

    - 이를 구현한 서비스 임플

<hide/>
package com.zerobase.fastlms.course.service;
import com.zerobase.fastlms.course.entity.Course;
import com.zerobase.fastlms.course.model.CourseInput;
import com.zerobase.fastlms.course.repository.CourseRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
public class CourseServiceImpl  implements CourseService{
    private final CourseRepository courseRepository;

    @Override
    public boolean add(CourseInput parameter){

        Course course = Course.builder()
                .subject(parameter.getSubject())
                .regDt(LocalDateTime.now())
                .build();

        courseRepository.save(course);
        return true;
    }
}

     

    - 컨트롤러

<hide/>
@PostMapping("/admin/course/add.do")
public String addSubmit(Model model, CourseInput parameter){
    boolean result = courseService.add(parameter);
    return "redirect:/admin/course/list.do";
}

 

 

    ==============================   오류   ==============================   

    

  - 오류: 강좌 등록하기 버튼을 누르면

  - 원인: add.html 페이지에서 <form method="post"> 라고 작성해야하는데 method 대신 action을 입력했다가  오류났다.

 

    -> 수정 후 정상 화면

 

  Ex) 강좌 목록 보여주기

 

     - 마이바티스로 리스트를 구성해야한다.

     - common param 만든다. 공통 속성

<hide/>
package com.zerobase.fastlms.admin.model;
import lombok.Data;
@Data
public class CommonParam {

    long pageIndex;
    long pageSize;
    String searchType;
    String searchValue;

    public long getPageStart() {
        init();
        return (pageIndex - 1) * pageSize;
    }

    public long getPageEnd() {
        init();
        return pageSize;
    }

    public void init() {
        if (pageIndex < 1) {
            pageIndex = 1;
        }
        if (pageSize < 10) {
            pageSize = 10;
        }
    }

    public String getQueryString() {
        init();
        StringBuilder sb = new StringBuilder();

        if(searchType != null && searchType.length() > 0){
            sb.append(String.format("searchType=%s", searchType));
        }

        if(searchValue != null && searchValue.length() > 0){
            if(sb.length() > 0){
                sb.append("&");
            }
            sb.append(String.format("searchValue=%s", searchValue));
        }
        return sb.toString();
    }
}

 

    - 바뀐 MemberParam

<hide/>
package com.zerobase.fastlms.admin.model;
import lombok.Data;
@Data
public class MemberParam extends CommonParam {
    String userId;
}

 

    - courseParam

<hide/>
package com.zerobase.fastlms.course.model;
import com.zerobase.fastlms.admin.model.CommonParam;
import lombok.Data;
@Data
public class CourseParam  extends CommonParam {
    
}

 

    - courseDto

<hide/>
package com.zerobase.fastlms.course.dto;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class CourseDto {

    Long id;
    String imagePath;;
    String keyword;
    String subject;
    String summary;
    String contents;

    long price;
    long salesPrice;

    LocalDate salesEndDt;
    LocalDateTime regDt;    // 등록일 - 추가 날짜
    LocalDateTime udtDt;    // 수정일 - 수정 날짜

    long totalCount;
    long seq;
}

 

    - courseService

<hide/>
/**
 * 강좌 목록
 */
List<CourseDto> list(CourseParam parameter);

 

    - 컨트롤러

      -> 자주 쓰이는 부분을 위해 베이스 컨트롤러도 만든다.

<hide/>
@Controller
@RequiredArgsConstructor
public class AdminCourseController  extends  BaseController{
    private final CourseService courseService;
    
    @GetMapping("/admin/member/list.do")
    public String list(Model model, CourseParam parameter){

        parameter.init();   // 유효한 값이 되도록 만든다.
        List<CourseDto> courseList = courseService.list(parameter);    // 값이 저장되면 토탈카운트가 존재한다.

        long totalCount = 0;

        if(CollectionUtils.isEmpty(courseList)){
            totalCount = courseList.get(0).getTotalCount();
        }

        String queryString = parameter.getQueryString();
        PageUtil pageUtil = new PageUtil(totalCount, parameter.getPageSize(), parameter.getPageIndex(), queryString);

        model.addAttribute("list", courseList);
        model.addAttribute("totalCount", totalCount);   // 데이터를 반환
        model.addAttribute("pager", pageUtil.pager());   // 데이터를 반환
        return "admin/member/list";
    }
// 생략
}

 

     - 베이스

<hide/>
package com.zerobase.fastlms.course.controller;
import com.zerobase.fastlms.util.PageUtil;
public class BaseController {
    public String getPaperHtml(long totalCount, long pageSize, long pageIndex, String queryString){
        PageUtil pageUtil = new PageUtil(totalCount, pageSize, pageIndex, queryString);
        return pageUtil.pager();
    }
}

 

    - AdminCourseCtrl   => (여기서 if문에 !를 빼먹었다가 오류가 났다. - TempleteInputExcp)

<hide/>
@GetMapping("/admin/member/list.do")
public String list(Model model, CourseParam parameter){

    parameter.init();   // 유효한 값이 되도록 만든다.
    List<CourseDto> courseList = courseService.list(parameter);    // 값이 저장되면 토탈카운트가 존재한다.
    long totalCount = 0;

    if(!CollectionUtils.isEmpty(courseList)){
        totalCount = courseList.get(0).getTotalCount();
    }

    String queryString = parameter.getQueryString();
    String paperHtml = getPaperHtml(totalCount, parameter.getPageSize(), parameter.getPageIndex(), queryString);

    model.addAttribute("list", courseList);
    model.addAttribute("totalCount", totalCount);   // 데이터를 반환
    model.addAttribute("pager", paperHtml);   // 데이터를 반환
    return "admin/member/list";
}

 

    - AdminMemberCtrl도 변경 가능

<hide/>
@GetMapping("/admin/member/list.do")
public String list(Model model, MemberParam parameter){

    parameter.init();   // 유효한 값이 되도록 만든다.
    List<MemberDto> members = memberService.list(parameter);    // 값이 저장되면 토탈카운트가 존재한다.

    long totalCount = 0;
    if(members != null && members.size() > 0){
        totalCount = members.get(0).getTotalCount();
    }
    String queryString = parameter.getQueryString();
    String pagerHtml = getPaperHtml(totalCount, parameter.getPageSize(), parameter.getPageIndex(), queryString);

    model.addAttribute("list", members);
    model.addAttribute("totalCount", totalCount);   // 데이터를 반환
    model.addAttribute("pager", pagerHtml);   // 데이터를 반환
    return "admin/member/list";
}

 

    - course 내부 list

<hide/>
<!doctype html>
<html lang="ko"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>관리자 화면</title>
    <style>
        .list table{
            width: 100%;
            border-collapse: collapse;
        }
        .list table th, .list table td{
            border: solid 1px #000;
        }
        p.nothing {
            text-align: center;
            padding: 100px;
        }
        .pager{
            margin-top: 10px;
            text-align: center;
        }
        .pager a.on{
            font-weight: bold;
            color: red;
        }

    </style>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
    <script>
        $(document).ready(function (){

        });

    </script>
</head>
<body>
<h1>강좌 관리</h1>
<!--   <div th:replace="/fragments/layout.html :: fragment-admin-body-menu" ></div>-->



<div>
    <a href="/admin/course/add.do">강좌 등록</a>
</div>



<div class="list">
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>강좌명</th>
                <th>등록일</th>
            </tr>
        </thead>
        <tbody>
        <tr th:each="x : ${list}">
            <td th:text="${x.seq}">1</td>
            <td>
               <p th:text="${x.subject}"></p>
            </td>

            <td>
                <p th:text="${x.regDt}">2022-01-01</p>
            </td>

        </tr>
        </tbody>
    </table>
    <div class="pager" th:utext="${pager}"></div>
</div>
</body>
</html>

 

    - CourseServiceImpl

<hide/>
package com.zerobase.fastlms.course.service;
import com.zerobase.fastlms.course.dto.CourseDto;
import com.zerobase.fastlms.course.entity.Course;
import com.zerobase.fastlms.course.mapper.CourseMapper;
import com.zerobase.fastlms.course.model.CourseInput;
import com.zerobase.fastlms.course.model.CourseParam;
import com.zerobase.fastlms.course.repository.CourseRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.List;

@Service
@RequiredArgsConstructor
public class CourseServiceImpl implements CourseService{
    private final CourseRepository courseRepository;
    private final CourseMapper courseMapper;

    @Override
    public boolean add(CourseInput parameter){

        Course course = Course.builder()
                .subject(parameter.getSubject())
                .regDt(LocalDateTime.now())
                .build();
        courseRepository.save(course);
        return true;
    }

    @Override
    public List<CourseDto> list(CourseParam parameter) {
        long totalCount = courseMapper.selectListCount(parameter);
        List<CourseDto> list = courseMapper.selectList(parameter);

        if(!CollectionUtils.isEmpty(list)){
            int i = 0;
            for(CourseDto x : list){
                x.setTotalCount(totalCount);    // 전체 개수를 하나씩 다 넣어준다.
                x.setSeq(totalCount - parameter.getPageStart() - i);
                ++i;
            }
        }
        return list;
    }
}

 

    - 코스 매퍼 인터페이스 만들기

      -> 마이바티스의 매퍼 어노테이션 

      -> Mapper를 만들면 짝을 이루는 xml 파일이 존재해야한다.

<hide/>
package com.zerobase.fastlms.course.mapper;
import com.zerobase.fastlms.course.dto.CourseDto;
import com.zerobase.fastlms.course.model.CourseParam;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface CourseMapper {
    long selectListCount(CourseParam parameter);
    List<CourseDto> selectList(CourseParam memberParam);
}

  

    cf) SQL문을 DML태그에 추가 - 동일한 쿼리를 여러 번 삽입 가능하다.

<include refid="selectListWhere"></include>

 

      - CouerserMapper.xml 파일

        -> 아래 쪽의 <SELECT...> 뒤에 슬래시를 잘못 붙였다가 에러가 났다.

<hide/>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zerobase.fastlms.course.mapper.CourseMapper">

    <sql id="selectListWhere">
        <if test="searchValue != null and searchType != null">
            <choose>
                <when test="searchType == 'userId'">
                    and user_id like concat('%', #{searchValue}, '%')
                </when>
                <when test="searchType == 'userName'">
                    and user_name like concat('%', #{searchValue}, '%')
                </when>
                <when test="searchType == 'phone'">
                    and phone like concat('%', #{searchValue}, '%')
                </when>

                <otherwise>
                    and
                    (
                    user_id like concat('%', #{searchValue}, '%')
                    or
                    user_name like concat('%', #{searchValue}, '%')
                    or
                    phone like concat('%', #{searchValue}, '%')
                    )
                </otherwise>
            </choose>
        </if>
    </sql>

    <select id="selectListCount"  resultType="long">
        SELECT COUNT(*)
        FROM member
        WHERE 1 = 1
            <include refid="selectListWhere"></include>
    </select>

    <select id="selectList"  resultType="com.zerobase.fastlms.course.dto.CourseDto">/
        SELECT *
        FROM member
        WHERE 1 = 1
            <include refid="selectListWhere"></include>
        LIMIT #{pageStart} , #{pageEnd}
    </select>

</mapper>

 

     - 임플 list() 메서드

<hide/>
@Override
public List<CourseDto> list(CourseParam parameter) {
    long totalCount = courseMapper.selectListCount(parameter);
    List<CourseDto> list = courseMapper.selectList(parameter);

    if(!CollectionUtils.isEmpty(list)){
        int i = 0;
        for(CourseDto x : list){
            x.setTotalCount(totalCount);    // 전체 개수를 하나씩 다 넣어준다.
            x.setSeq(totalCount - parameter.getPageStart() - i);
            ++i;
        }
    }
    return list;
}

 

    - courseDto

<hide/>
package com.zerobase.fastlms.course.dto;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class CourseDto {

    Long id;
    String imagePath;;
    String keyword;
    String subject;
    String summary;
    String contents;

    long price;
    long salesPrice;

    LocalDate salesEndDt;
    LocalDateTime regDt;    // 등록일 - 추가 날짜
    LocalDateTime udtDt;    // 수정일 - 수정 날짜

    long totalCount;
    long seq;
}

 

 

 =============================== 오류  ===============================

    

  - 오류: 다른 페이지는 잘 뜨는데 강좌 관리 페이지에서 SQL 예외가 발생한다. SQLSyntaxErrorException

  - 원인: xml 파일에서 테이블 이름 course로 바꿈. 그니고 가장 마지막의 <SELECT ...> 뒤에 슬래시가 들어가는 바람에 SQL문법 오류가 났다. ..오타 그만 내기!!

 

    Sol) xml 파일 (수정 후)

<hide/>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zerobase.fastlms.course.mapper.CourseMapper">

    <sql id="selectListWhere">
        <if test="searchValue != null and searchType != null">
            <choose>
                <when test="searchType == 'userId'">
                    and user_id like concat('%', #{searchValue}, '%')
                </when>
                <when test="searchType == 'userName'">
                    and user_name like concat('%', #{searchValue}, '%')
                </when>
                <when test="searchType == 'phone'">
                    and phone like concat('%', #{searchValue}, '%')
                </when>
                <otherwise>
                    and
                    (
                    user_id like concat('%', #{searchValue}, '%')
                    or
                    user_name like concat('%', #{searchValue}, '%')
                    or
                    phone like concat('%', #{searchValue}, '%')
                    )
                </otherwise>
            </choose>
        </if>
    </sql>

    <select id="selectListCount"
            resultType="long">

        SELECT COUNT(*)
        FROM course
        WHERE 1 = 1
            <include refid="selectListWhere"/>
    </select>

    <select id="selectList"
            resultType="com.zerobase.fastlms.course.dto.CourseDto">

        SELECT *
        FROM course
        WHERE 1 = 1
            <include refid="selectListWhere"/>
        LIMIT #{pageStart}, #{pageEnd}
    </select>
</mapper>

  Note) 실행 결과 - 정상 실행

 

 

  ========================= 오류  ========================= 

  - 오류 :TemplateInputException

  - 원인: AdminCourseController에서 list() 메서드 if문에 '!'를 빼먹었다.

<hide/>
if(!CollectionUtils.isEmpty(courseList)){
    totalCount = courseList.get(0).getTotalCount();
}

    -> 수정 후, 정상 화면

 

 

 

13.3 강좌 상세 정보 등록 및 수정 구현 (1)

 

  Ex) 강좌 상세 정보 수정

    - 리스트에 강좌명 하이퍼링크 추가

    - 컨트롤러

<hide/>
@GetMapping(value = {"/admin/course/add.do" , "/admin/course/edit.do"})
public String add(Model model, HttpServletRequest request, CourseInput parameter){

    boolean editMode = request.getRequestURI().contains("/edit.do");

    if(editMode){
        long id = parameter.getId();    // 데이터가 존재하는지 체크
        CourseDto existCourse = courseService.getById(id);

        if(existCourse == null){    // 수정할 데이터가 없는 경우 - 에러 처리
            model.addAttribute("message", "강좌 정보가 존재하지 않습니다.");
            return "common/error";
        }
        model.addAttribute("detail", existCourse);
    }
    return "admin/course/add";
}

 

    - 서비스에 getById() 추가

<hide/>
/**
 * 강좌 상세 정보
 */
CourseDto getById(long id);

 

    - CourseServiceImpl - getById()

<hide/>
@Override
public CourseDto getById(long id) {
    return  courseRepository.findById(id).map(CourseDto::of).orElse(null);
}

 

    - of() 메서드

<hide/>
public static CourseDto of(Course course) {

    CourseDto courseDto = CourseDto.builder()
            .id(course.getId())
            .imagePath(course.getImagePath())
            .keyword(course.getKeyword())
            .subject(course.getSubject())
            .summary(course.getSummary())
            .contents(course.getContents())
            .price(course.getPrice())
            .salesPrice(course.getSalesPrice())
            .salesEndDt(course.getSalesEndDt())
            .regDt(course.getRegDt())
            .udtDt(course.getUdtDt())
            .build();
    return courseDto;
}

 

    - 에러 페이지

      -> 방금 컨트롤러에서 model에 "massage"를 넣어서  변수를 이용 가능하다.

<hide/>
<!doctype html>
<html lang="ko"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>에러 화면</title>
</head>
<body>
   <h1>에러 화면</h1>
    <p th:text="${message}"></p>
    <div>
        <a href="/">홈으로 이동</a>
    </div>

</body>
</html>

  Note) 실행 결과 - 존재하지 않는 강좌 정보 페이지로 이동할 때

 

 

    - 컨트롤러 수정

<hide/>
@GetMapping(value = {"/admin/course/add.do" , "/admin/course/edit.do"})
public String add(Model model, HttpServletRequest request, CourseInput parameter){

    boolean editMode = request.getRequestURI().contains("/edit.do");
    CourseDto detail = new CourseDto();

    if(editMode){
        long id = parameter.getId();    // 데이터가 존재하는지 체크
        CourseDto existCourse = courseService.getById(id);

        if(existCourse == null){    // 수정할 데이터가 없는 경우 - 에러 처리
            model.addAttribute("message", "강좌 정보가 존재하지 않습니다.");
            return "common/error";
        }
        detail = existCourse;   // 널이 아닐 때 넣어준다.

    }
    model.addAttribute("editMode", editMode);
    model.addAttribute("detail", detail);
    return "admin/course/add";
}

 

    -  이렇게  @PostMapping에 value를 넣어야 둘다 처리 가능하다.

<hide/>
@PostMapping(value={"/admin/course/add.do", "/admin/course/edit.do"})
public String addSubmit(Model model, HttpServletRequest request ,CourseInput parameter){
    boolean editMode = request.getRequestURI().contains("/edit.do");

    // 유효성 검사
    if(editMode){
        long id = parameter.getId();    // 데이터가 존재하는지 체크
        CourseDto existCourse = courseService.getById(id);
        if(existCourse == null){    // 수정할 데이터가 없는 경우 - 에러 처리
            model.addAttribute("message", "강좌 정보가 존재하지 않습니다.");
            return "common/error";
        }
        boolean result = courseService.set(parameter);  // 수정 전용 메서드
    }else{
        boolean result = courseService.add(parameter);
    }
    return "redirect:/admin/course/list.do";
}

  

    - 서비스 - set() 메서드

boolean set(CourseInput parameter);

  

    - 임플에 set() 구현

<hide/>
@Override
public boolean set(CourseInput parameter) {

    Optional<Course> optionalCourse = courseRepository.findById(parameter.getId());
    if(!optionalCourse.isPresent()){    // 수정할 데이터가 없는 경우
        return false;
    }
    Course course = optionalCourse.get();
    course.setSubject(parameter.getSubject());
    course.setUdtDt(LocalDateTime.now());
    courseRepository.save(course);
    return true;
}

  Note) 실행 결과 - 수정

    - 수정 후, 리스트로 이동한다.

 

 

  Ex)  강좌 등록

 

    - 컨트롤러 add()

<hide/>
@GetMapping(value = {"/admin/course/add.do" , "/admin/course/edit.do"})
public String add(Model model, HttpServletRequest request, CourseInput parameter){

    // 카테고리 정보를 내려줘야한다.
    model.addAttribute("category", categoryService.list()); // 카테고리가 클라이언트에 내려간다.
    boolean editMode = request.getRequestURI().contains("/edit.do");
    CourseDto detail = new CourseDto();

    if(editMode){
        long id = parameter.getId();    // 데이터가 존재하는지 체크
        CourseDto existCourse = courseService.getById(id);

        if(existCourse == null){    // 수정할 데이터가 없는 경우 - 에러 처리
            model.addAttribute("message", "강좌 정보가 존재하지 않습니다.");
            return "common/error";
        }
        detail = existCourse;   // 널이 아닐 때 넣어준다.

    }
    model.addAttribute("editMode", editMode);
    model.addAttribute("detail", detail);
    return "admin/course/add";
}

 

    - 강좌 add 페이지에 카테고리 옵션이 나오도록 한다.

<hide/>
<!doctype html>
<html lang="ko"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>관리자 화면</title>
    <style>
        .detail table{
            width: 100%;
            border-collapse: collapse;
        }
        .detail table th, .detail table td{
            border: solid 1px #000;
        }
        .buttons{
            margin-top: 20px;
            text-align: center;
        }
        .buttons a, .buttons button{
            border-width: 0;
            background-color: transparent;
            text-decoration: underline;
            font-size: 12px;
            line-height: 20px;
            height: 20px;
            cursor: pointer;
        }


    </style>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
    <script>
        $(document).ready(function (){

        });

    </script>
</head>
<body>
<h1>강좌 관리 - 강좌 등록</h1>

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

<div class="detail">

     <form method="post">
         <table>
             <tbody>
             <tr>
                 <th>강좌 카테고리</th>
                 <td>
                     <select name="categoryId" required>
                         <option value="">카테고리 선택</option>
                         <option th:each="x : ${category}"  th:value="${x.id}"   th:text="${x.categoryName}">기획</option>
                     </select>
                 </td>
             </tr>
             <tr>
                 <th>
                     강좌명:
                 </th>
                 <td>
                     <input th:value="${detail.subject}" type="text" name="subject" required placeholder="강좌명 입력"/>
                 </td>

             </tr>
             </tbody>
         </table>
         <div class="buttons">
             <button th:if="${editMode}" type="submit">강좌 수정 하기</button>
             <button th:if="${!editMode}" type="submit">강좌 등록 하기</button>
             <a href="/admin/course/list.do">목록으로 이동</a>
         </div>
     </form>
</div>
</body>
</html>

 

  Note) 실행 결과

 

 

 

  Ex) 수정한 카테고리 저장하기

 

    - categoryId에 대해서 값을 Course가 저장해야한다. Course에 멤버 추가

long categoryId;

 

    - 임플에 add()에 추가, set()에도 동일하게 추가한다.

<hide/>
@Override
public boolean add(CourseInput parameter){
    Course course = Course.builder()
            .categoryId(parameter.getCategoryId())
            .subject(parameter.getSubject())
            .regDt(LocalDateTime.now())
            .build();
    courseRepository.save(course);
    return true;
}

  Note) 실행 결과 - 업데이트 날짜가 뜬다.

 

 

 

13.4 강좌 상세 정보 등록 및 수정 구현 (2)

 

  Ex)

    - Category에 categoryId를 추가

    - add

<hide/>
<tr>
     <th>강좌 카테고리</th>
     <td>
         <select name="categoryId" required>
             <option value="">카테고리 선택</option>
             <option
                     th:selected="${detail.categoryId == x.id}"
                     th:each="x : ${category}"  th:value="${x.id}"   th:text="${x.categoryName}">기획</option>
         </select>
     </td>
 </tr>
 <tr>
     <th>
         강좌명:
     </th>
     <td>
         <input th:value="${detail.subject}" type="text" name="subject" required placeholder="강좌명 입력"/>
     </td>
 </tr>
 <tr>
     <th>
         키워드
     </th>
     <td>
         <input th:value="${detail.keyword}" type="text" name="keyword" required placeholder="키워드 입력"/>
     </td>
 </tr>
 <tr>
     <th>
         요약문구
     </th>
     <td>
         <textarea th:text="${detail.summary}" name="summary" required placeholder="요약문구 입력"></textarea>
     </td>
 </tr>
 <tr>
     <th>
         내용
     </th>
     <td>
         <textarea th:text="${detail.contents}" name="contents" required placeholder="내용 입력"/>
     </td>
 </tr>
 <tr>
     <th>
         정가
     </th>
     <td>
         <input th:value="${detail.price}" name="price" required placeholder="정가 입력"></input>
     </td>
 </tr>
 <tr>
     <th>
         판매가
     </th>
     <td>
         <input th:value="${detail.salesPrice}" name="salesPrice" required placeholder="판매가 입력"></input>
     </td>
 </tr>
 <tr>
     <th>
         할인 종료일
     </th>
     <td>
         <input th:value="${detail.salesEndDt}" name="salesEndDt" required placeholder="할인 종료일 입력"></input>
     </td>
 </tr>

  Note) 실행 결과

    -  수정하기 누르고 다시 들어오면 저장한 값이 보인다.

    - 데이터베이스에  잘 들어간다.

 

 

 Ex) 할인 종료일


    - 임플 클래스에 날짜 형식 바꾸는 메서드 getLocalDate()추가

<hide/>
private LocalDate  getLocalDate(String value){
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    try{
       return LocalDate.parse(value, formatter);
    }catch (Exception e){
    }
    return null;
}

 

    - set(), add() 에 둘다  추가해서 course에 넣는다.

LocalDate saleEndDt = getLocalDate(parameter.getSaleEndDtText());

 

  Note) 실행 결과 - 정상

 

 

 

13.5 강좌 전체 삭제 및 선택 삭제 구현

 

  Ex) 강좌선택 삭제 구현하기

    - 체크 박스 만들기

<hide/>
<thead>
    <tr>
        <th>
            <input type="checkbox"/>
        </th>
        <th>NO</th>
        <th>강좌명</th>
        <th>등록일</th>
    </tr>
</thead>
<tbody id ="dataList">
<tr th:each="x : ${list}">
    <td>
        <input type="checkbox" th:value="${x.id}" />
    </td>

    <td th:text="${x.seq}">1</td>
    <td>
       <p>
           <a th:href="'edit.do?id=' + ${x.id}" th:text="${x.subject}">강좌명</a>
       </p>
    </td>
    <td>
        <p th:text="${x.regDt}">2022-01-01</p>
    </td>
</tr>
</tbody>

 

    - 버튼 삭제 자바스크립트

<hide/>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
    $(document).ready(function (){
        $('#deleteButton').on('click', function (){

            var $checked = $('#dataList input[type=checkbox]:checked');
            if($checked.length < 1){
                alert("삭제할 데이터를 선택해주세요.")
                return false;
            }
            if(!confirm('선택한 데이터를 삭제하겠습니까?')){  // 체크 박스를 누르고나서 선택 삭제를 누른 경우에만 실행된다.
                return false;
            }
            var idList = [];
            $.each($checked, function (k, v){
                   idList.push($(this).val());
            });
            console.log(idList);
            console.log(idList.join(','));

            var $deleteForm = $('form[name=deleteForm]');
            $deleteForm.find('input[name=idList]').val(idList.join(','));
            $deleteForm.submit();   // 서버로 보낸다.
        });
    });
</script>

 

    - deleteForm에다 삭제한 정보를 채워서 course의 delete 적용시킨다.

<hide/>
<form name="deleteForm" method="post" action="/admin/course/delete.do">
    <input type="hidden" name="idList"/>
</form>

 

    - 컨트롤러에 삭제 구현

<hide/>
@PostMapping("/admin/course/delete.do")
public String del(Model model, HttpServletRequest request ,CourseInput parameter){
    boolean result = courseService.del(parameter.getIdList());
    return "redirect:/admin/course/list.do";
}

 

    - courseService

<hide/>
/**
 * 강좌 내용 삭제
 */
boolean del(String idList);

 

    - 임플에 del() 구현

<hide/>
@Override
public boolean del(String idList) {
    if(idList != null && idList.length() > 0){
        String[] ids = idList.split(",");
        for(String x : ids){
            long id = 0L;
            try{
                 id = Long.parseLong(x);
            }catch (Exception e){
            }
            if(id > 0){
                courseRepository.deleteById(id);
            }
        }
    }
    return true;
}

  Note) 실행 결과

    - 선택 삭제 누른 다음

 

 

  Ex) 전체 삭제 구현

    - 리스트에서 맨위 체크 박스에 selectAll이라는 이름을 준다. 

<hide/>
<thead>
    <tr>
        <th>
            <input id="selectAll" type="checkbox"/>
        </th>
        <th>NO</th>
        <th>강좌명</th>
        <th>등록일</th>
    </tr>
</thead>

 

  cf) .attr()과 .prop()의 차이는?

    - .attr(): HTML의 속성을 취급한다.

    - .prop(): JavaScript의 프로퍼티를 취급한다.

 

   - prop() 로 체크한다.

<hide/>
<script>
    $(document).ready(function (){
        $('#selectAll').on('click', function (){
            var checked = $(this).is(':checked');
            $('#dataList input[type=checkbox]').each(function (k, v){
                $(this).prop('checked', checked);

            });
        });
        $('#deleteButton').on('click', function (){

            var $checked = $('#dataList input[type=checkbox]:checked');

            if($checked.length < 1){
                alert("삭제할 데이터를 선택해주세요.")
                return false;
            }
            if(!confirm('선택한 데이터를 삭제하겠습니까?')){  // 체크 박스를 누르고나서 선택 삭제를 누른 경우에만 실행된다.
                return false;
            }

            var idList = [];

            $.each($checked, function (k, v){
                   idList.push($(this).val());
            });
            console.log(idList);
            console.log(idList.join(','));
            var $deleteForm = $('form[name=deleteForm]');
            $deleteForm.find('input[name=idList]').val(idList.join(','));
            $deleteForm.submit();   // 서버로 보낸다.
        });
    });
</script>

  Note) 실행 결과

    - 맨 위 박스 누른 화면

    - 선택 삭제 누른 다음 화면 =>  10개가 삭제된다.

 

 

 

13.6 강좌 수정 화면 스마트에디터(SmartEditor) 기능 적용

 

  Ex) 스마트 에디터(SmartEditor)

    -  https://github.com/naver/smarteditor2/releases  접속하여 2-2.10.0 다운로드

 

    - 압축 해제하고 dist폴더를 static 하위로 이동시킨다.

 

    - http://naver.github.io/smarteditor2/user_guide/2_install/setting.html 사이트의 2.0 버전 설치 탭의 코드 참고

    -> 강좌 add  파일의 아래에 추가한다.

<script type="text/javascript" src="../se2/js/service/HuskyEZCreator.js" charset="utf-8"></script>

 

    - 위 사이트의 4번 탭을 참고해서 강의 add파일에 자바스크립트 내용을 추가한다. (res앞에 슬래시 꼭 붙인다! 안 붙이면 오류난다.)

<hide/>
<script type="text/javascript" src="/res/se2/js/service/HuskyEZCreator.js" charset="utf-8"></script>
<script type="text/javascript">
    var oEditors = [];
    nhn.husky.EZCreator.createInIFrame({
        oAppRef: oEditors,
        elPlaceHolder: "contents",
        sSkinURI: "/res/se2/SmartEditor2Skin.html",
        fCreator: "createSEditor2"
    });
</script>

 

    - 시큐리티의 Configure() http.headers.frameOptions().sameOrigin()을 추가한다.

<hide/>
@Override
protected void configure(HttpSecurity http) throws Exception {

    http.csrf().disable();      // csrf: 토큰
    http.headers().frameOptions().sameOrigin();

    http.authorizeRequests()
            .antMatchers(
                    "/"
                    , "/member/register"
                    , "/member/email-auth"
                    , "/member/find-password"
//                        , "/member/reset/password"
//                        , "/admin"
            )
            .permitAll();

    http.authorizeRequests()
                    .antMatchers("/admin/**")
                            .hasAnyAuthority("ROLE_ADMIN");

    http.formLogin()
            .loginPage("/member/login")
            .failureHandler(getFailureHandler())
            .permitAll();

    http.logout()
            .logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
            .logoutSuccessUrl("/")          // 로그아웃 성공하면 메인페이지 이동
            .invalidateHttpSession(true);   // 로그아웃했으니 새션 초기화

    // 추가 - 접근 제한
    http.exceptionHandling()
            .accessDeniedPage("/error/denied");
    super.configure(http);
}

 

  Note) 실행 결과

    - 다음과 같이 입력 수정