112일차 새 프로젝트 시작

오늘 할일
공지사항 reg기능 삭제기능
참여기업 목록 상세 조회 + 수정기능(승인기능)

1. 공지사항 등록 기능

1.1 컨트롤러

값을 담아서 보내주는 것은 비슷하기 때문에 컨트롤러만 보겠다.
view에서 fetchapi로 formdata에 담아서 가져왔다.
성공한 경우에는 성공메시지 실패매시지를 요청에 따라서 돌려주었다.

@PostMapping
  public ResponseEntity<Map<String, Object>> create(
          Notice notice,
          @RequestParam(value = "files", required = false) MultipartFile[] files) {
      log.info("log{}", files != null);
      try {
          boolean ok = noticeService.create(notice, files);
          if (true) {
              // 생성이 성공한 경우에는 적절한 응답을 반환
              Map<String, Object> response = new HashMap<>();
              response.put("message", "등록되었습니다.");
              return ResponseEntity.ok(response);
          } else {
              // 생성이 실패한 경우에는 적절한 응답을 반환
              Map<String, Object> response = new HashMap<>();
              response.put("message", "등록에 실패하였습니다.");
              return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
          }
      } catch (Exception e) {
          throw new RuntimeException(e);
      }
  }

1.2 서비스

어제 작성했던 파일 등록 메소드를 중복으로 사용하기 때문에 코드를 재사용하기 위해서 메소드로 따로 빼서 만들었다.
수정페이지처리와 등록처리에서 모두 사용한다.

 @Override
@Transactional(rollbackFor = Exception.class)
public boolean create(Notice notice, MultipartFile[] files) throws IOException {
    notice.setMemberId("chun");
    // 공지사항 등록
    int cnt = noticeMapper.insert(notice);

    fileToS3(notice, files);

    return cnt == 1;
}

// 파일 등록 메소드
public void fileToS3(Notice notice, MultipartFile[] files) throws IOException {
    // 파일등록
    for (MultipartFile file : files) {
        if (file.getSize() > 0) {
            String objectKey = "career_fair/notice/" + notice.getNoticeId() + "/" + file.getOriginalFilename();

            // s3에 파일 업로드
            PutObjectRequest por = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .acl(ObjectCannedACL.PUBLIC_READ)
                    .key(objectKey)
                    .build();
            RequestBody rb = RequestBody.fromInputStream(file.getInputStream(),
                    file.getSize());

            s3.putObject(por, rb);

            // db에 관련정보저장 (insert)
            noticeMapper.insertFileName(notice.getNoticeId(), file.getOriginalFilename());
        }
    }
}

2. 공지사항 삭제기능

2.1 컨트롤러

드디어 restfulapi에서 post와 get요청이외의 delete요청을 사용했다.
삭제완료시 상태에 따라 메시지를 반환한다.

 @DeleteMapping("{noticeId}")
public ResponseEntity<Map<String, Object>> delete(@PathVariable("noticeId") Integer noticeId) {
    boolean ok = noticeService.delete(noticeId);

    if (ok) {
        // 삭제가 성공한 경우에는 적절한 응답을 반환
        Map<String, Object> response = new HashMap<>();
        response.put("message", "삭제되었습니다.");
        return ResponseEntity.ok(response);
    } else {
        // 삭제가 실패한 경우에는 적절한 응답을 반환
        Map<String, Object> response = new HashMap<>();
        response.put("message", "삭제에 실패하였습니다.");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
}

2.2 서비스

역시나 파일 삭제 메소드를 따로 빼서 만들었다.
파일명을 기준으로 파일들을 삭제해준다.

@Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delete(Integer noticeId) {
        //파일 이름들 불러오기
        List<String> fileNames = noticeMapper.selectFileNamesByNoticeId(noticeId);

        if (fileNames != null && !fileNames.isEmpty()) {
            for (String fileName : fileNames) {
                removeFromS3(noticeId, fileName);
            }
        }

        // 테이블에서 파일 삭제
        noticeMapper.deleteFileNameByNoticeId(noticeId);

        //공지사항 삭제
        int cnt = noticeMapper.deleteById(noticeId);
        return cnt == 1;
    }
}

// 파일 삭제 메소드
public void removeFromS3(Integer noticeId, String fileName) {
    // 파일 삭제
    String objectKey = "career_fair/notice/" + noticeId + "/" + fileName;
    DeleteObjectRequest dor = DeleteObjectRequest.builder()
            .bucket(bucketName)
            .key(objectKey)
            .build();

    s3.deleteObject(dor);
}

3. 참여기업 신청등록

기업회원들이 신청을 하면 채용담당자가 되고 채용공고를 등록할 수 있게 된다.
그를 위한 참여기업 신청등록을 작성해보자

3.1 컨트롤러

fetch api를 통해서 값을 가져오는 것은 비슷하기 때문에 js코드는 넘어가도록 하겟다.
formdata로 담아온것을 컨트롤러에서 받아준다.
등록에 성공시 200응답 실패시 500에러를 돌려주기로 했다.
MultipartFile이 있기때문에 POST요청이다.

@RestController("companyRecruiterControllerAPI ")
@Slf4j
@RequestMapping("/api/recruiter/")
@RequiredArgsConstructor
public class RecruiterControllerAPI {

    @PostMapping
    public ResponseEntity<Map<String, Object>> reg(
            Company company,
            @RequestParam("files") MultipartFile[] files) {
        boolean ok = false;
        try {
            ok = recruiterService.create(company, files);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        Map<String, Object> response = new HashMap<>();

        if (ok) {
            // 성공 시 적절한 응답 반환
            return ResponseEntity.ok(response);
        } else {
            // 실패 시 적절한 응답 반환
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
}

3.2 서비스

회차마다 다르게 등록해야하기 때문에 현재 회차를 받아온다.
현재는 applciation.properties에 넣어둔 값을 가져오고 있는데 후에는 다른 service에서 round값을 받아와서 사용하도록 변경하였다.
memberId는 일단은 아이디 처리가 없기 때문에 값을 임의적으로 넣어주고 있는데 후에는 auth를 통해서 멤버 id를 받아서 넣어줄 것이다.
첫 상태는 review상태이다. 심사중상태로 값을 처리하여 나중에 관리자가 보류 반려 승인의 절차를 걸치게 한다.
이외에는 파일을 등록하는 메소드가 있다.

@Service("companyRecruiterServiceImpl")
@Slf4j
@RequiredArgsConstructor
public class RecruiterServiceImpl implements RecruiterService {

    private final IndustryMapper industryMapper;

    private final RecruiterMapper recruiterMapper;

    @Value("${current.event}")
    String round;

    @Value("${aws.s3.bucketName}")
    private String bucketName;

    private final S3Client s3;

    @Override
    public List<Industry> getIndustryList() {
        return industryMapper.getIndustryList();
    }

    @Override
    public boolean create(Company company, MultipartFile[] files) throws IOException {
        Integer roundInt = Integer.parseInt(round);
        company.setRound(roundInt);
        company.setMemberId("chuncom");
        company.setStatus("review");

        int cnt = recruiterMapper.insert(company);

        fileToS3(company, files);

        return cnt == 1;
    }

    // 파일 등록 메소드
    public void fileToS3(Company company, MultipartFile[] files) throws IOException {
        // 파일등록
        for (MultipartFile file : files) {
            if (file.getSize() > 0) {
                String objectKey = "career_fair/company/" + company.getCompanyId() + "/" + file.getOriginalFilename();

                // s3에 파일 업로드
                PutObjectRequest por = PutObjectRequest.builder()
                        .bucket(bucketName)
                        .acl(ObjectCannedACL.PUBLIC_READ)
                        .key(objectKey)
                        .build();
                RequestBody rb = RequestBody.fromInputStream(file.getInputStream(),
                        file.getSize());

                s3.putObject(por, rb);

                // db에 관련정보저장 (insert)
                recruiterMapper.insertFileName(company.getCompanyId(), file.getOriginalFilename());
            }
        }
    }
}

3.3 매퍼

특이한점은 없으나 이전 서비스에서 파일 업로드에 id가 필요하기 때문에 useGeneratedKeys를 통해 등록하면서 자동으로 등록되는 id값을 가져온다.
싱글톤 객체이기 때문에 계속 같은 domain을 가지고 주고 받아서 처음에는 id가 없지만 insert로 등록하면서 받아온 key를 domain에 set해주게 된다.

<insert id="insert" useGeneratedKeys="true"
        keyProperty="companyId">
    INSERT INTO TB_COMPANIES
    (round, member_id, company_name, registration_number,
    number_of_employees, establishment_date, revenue,
    postal_code, address, detail_address,
    ceo_name, industry_id, status)
    VALUES (#{round}, #{memberId}, #{companyName}, #{registrationNumber},
    #{numberOfEmployees}, #{establishmentDate}, #{revenue},
    #{postalCode}, #{address}, #{detailAddress},
    #{ceoName}, #{industryId}, #{status})
</insert>

<insert id="insertFileName">
    INSERT INTO TB_FILES (company_id, file_name)
    VALUES (#{companyId}, #{originalFilename})
</insert>

4. 관리자 참여기업 목록

특이점은 없다.

4.1 컨트롤러

검색페이징값을 가져와서 컨트롤러에서 처리해준다.

   @GetMapping
  public ResponseEntity<Map<String, Object>> getList(
          @RequestParam(value = "search", required = false) String search,
          @RequestParam(value = "type", required = false) String type,
          @RequestParam(value = "status", required = false) String status,
          @RequestParam(value = "page", defaultValue = "1") Integer page) {
      Map<String, Object> result = recruiterService.getList(search, type, page, status);
      return ResponseEntity.ok(result);
  }

4.2

페이지네이션은 평소와 같다.

 @Override
public Map<String, Object> getList(String search, String type, Integer page, String status) {
    Integer pageSize = 10; // 10개씩
    Integer startNum = (page - 1) * pageSize; // 0 10 20

    //페이지네이션 정보
    //총 글 개수
    Integer count = recruiterMapper.countAll(type, search, status);

    // 마지막 페이지 번호
    // 총 글개수 -1 / pageSize + 1
    Integer lastPage = (count - 1) / pageSize + 1;

    // 페이지네이션 왼쪽번호 1 11 21 31
    Integer leftPageNum = (page - 1) / pageSize * pageSize + 1;
    leftPageNum = Math.max(leftPageNum, 1);

    // 페이지네이션 오른쪽 번호
    Integer rightPageNum = leftPageNum + 9;
    rightPageNum = Math.min(rightPageNum, lastPage);

    // 이전페이지
    Integer prevPageNum = leftPageNum - 10;
    prevPageNum = Math.max(leftPageNum - 10, 1);

    // 다음 페이지
    Integer nextPageNum = leftPageNum + 10;

    Map<String, Object> pageInfo = new HashMap<>();
    pageInfo.put("lastPageNum", lastPage);
    pageInfo.put("leftPageNum", leftPageNum);
    pageInfo.put("rightPageNum", rightPageNum);
    pageInfo.put("currentPageNum", page);
    pageInfo.put("prevPageNum", prevPageNum);
    pageInfo.put("nextPageNum", nextPageNum);

    List<Notice> companyList = recruiterMapper.getList(startNum, pageSize, search, type, status);
    return Map.of("pageInfo", pageInfo, "companyList", companyList);
}

4.3 매퍼

특별한 것은 없이 동적 쿼리를 조건에 따라 실행시켜준다.
대신 상태를 받아오는데 all이 아닐경우와 각 상태
심사중 보류 반려 승인 상태에 따라 보여준다.
그런데이게 and로 연결해주는데 all일때
member_Id LIKE #{pattern} OR company_name LIKE #{pattern} OR registration_number LIKE #{pattern} and하면
값이 제대로 안불러와져서 all 일때는 ()로 감싸주었다.

<select id="getList" resultMap="companyMap">
        <bind name="pattern" value="'%' + search + '%'"/>
        SELECT * FROM TB_COMPANIES
        <where>
            <if test="type eq 'all'">
                (member_Id LIKE #{pattern}
                OR company_name LIKE #{pattern}
                OR registration_number LIKE #{pattern}
                )
            </if>
            <if test="type eq 'memberId'">
                OR member_Id LIKE #{pattern}
            </if>
            <if test="type eq 'companyName'">
                OR company_name LIKE #{pattern}
            </if>
            <if test="type eq 'registrationNumber'">
                OR registration_number LIKE #{pattern}
            </if>
            <if test="(status eq 'all') or (status eq '') ">
            </if>
            <if test="status neq 'all'">
                AND status LIKE #{status}
            </if>
        </where>
        ORDER BY company_id DESC
        LIMIT #{startNum}, #{pageSize}
    </select>

    <select id="countAll" resultType="Integer">
        <bind name="pattern" value="'%' + search + '%'"/>
        SELECT
        count(company_id) FROM TB_COMPANIES
        <where>
            <if test="type eq 'all'">
                (member_Id LIKE #{pattern}
                OR company_name LIKE #{pattern}
                OR registration_number LIKE #{pattern}
                )
            </if>
            <if test="type eq 'memberId'">
                OR member_Id LIKE #{pattern}
            </if>
            <if test="type eq 'companyName'">
                OR company_name LIKE #{pattern}
            </if>
            <if test="type eq 'registrationNumber'">
                OR registration_number LIKE #{pattern}
            </if>
            <if test="(status eq 'all') or (status eq '') ">
            </if>
            <if test="status neq 'all'">
                AND status LIKE #{status}
            </if>
        </where>
    </select>

2023.07.28

특이점은 없는 코드이다.

+ Recent posts