admin 사용자 등록 기능 추가

This commit is contained in:
hehihoho3@gmail.com 2025-05-07 11:50:28 +09:00
parent a6093f9961
commit 82bda62e3f
11 changed files with 232 additions and 16 deletions

View File

@ -0,0 +1,17 @@
package com.itn.admin.cmn.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Slf4j
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -37,14 +37,19 @@ public class SecurityConfig {
private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
// 생성자를 통해 의존성 주입
public SecurityConfig(CustomUserDetailsService customUserDetailsService, UserService userService, UserMapper userMapper) {
public SecurityConfig(CustomUserDetailsService customUserDetailsService
, UserService userService
, UserMapper userMapper
, PasswordEncoder passwordEncoder) {
this.customUserDetailsService = customUserDetailsService;
this.userService = userService;
this.userMapper = userMapper;
this.passwordEncoder = passwordEncoder;
}
/*
// 비밀번호 암호화를 위한 BCryptPasswordEncoder 생성
@Bean
public PasswordEncoder passwordEncoder() {
@ -52,7 +57,7 @@ public class SecurityConfig {
log.info(" + UserRestController 등록된 PasswordEncoder instance: {}", encoder);
return encoder;
}
}*/
// Spring Security의 보안 필터 체인을 설정하는 핵심 메서드
@Bean
@ -170,7 +175,7 @@ public class SecurityConfig {
log.info("로그인 실패 - DB 비번(encoded): [{}]", user.getPassword());
// 비번 매칭 테스트
boolean match = passwordEncoder().matches(rawPassword, user.getPassword());
boolean match = passwordEncoder.matches(rawPassword, user.getPassword());
log.info("비밀번호 일치 여부: [{}]", match);
} else {
log.warn("로그인 실패 - 아이디에 해당하는 사용자 없음");

View File

@ -96,9 +96,6 @@ public class CommuteServiceImpl implements CommuteService {
userAllVO.forEach(userVO -> log.info("userVO : [{}][{}]", userVO.getBiostarId(), userVO) );
commuteList.stream().forEach(t->{
// 지각 체크
t.setFirstActivityTimeMemo(this.getLateChk(t.getFirstActivityTime()));
// 조기퇴근 체크

View File

@ -47,4 +47,5 @@ public interface UserMapper {
@Select("SELECT * FROM users WHERE user_name LIKE CONCAT('%', #{userName}, '%')")
List<UserVO> findByUniqUserName(String userName);
void insertUser(UserVO userVO);
}

View File

@ -27,4 +27,6 @@ public interface UserService {
RestResponse findByUniqUserName(String userName);
RestResponse findByUniqApprovalUser(String userName);
RestResponse insertUser(UserVO userVO);
}

View File

@ -14,6 +14,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.DayOfWeek;
@ -21,10 +22,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
@Slf4j
@ -38,6 +36,8 @@ public class UserServiceImpl implements UserService {
@Autowired
TCodeUtils tCodeUtils;
@Autowired
private PasswordEncoder passwordEncoder;
public Map<String, Object> getList(UserVO userVO) {
@ -160,4 +160,19 @@ public class UserServiceImpl implements UserService {
}
@Override
public RestResponse insertUser(UserVO userVO) {
userVO.setUniqId("ITN_"+ UUID.randomUUID().toString().replace("-", "").substring(0, 10));
userVO.setPassword(passwordEncoder.encode("itn123")); // 초기 비밀번호
userVO.setRole(Role.ROLE_GUEST);
log.info("userVO : [{}]", userVO);
userMapper.insertUser(userVO);
return RestResponse.builder()
.status(HttpStatus.OK) // 200 성공
.data(userVO.getUserName())
.msg("등록되었습니다.")
.build();
}
}

View File

@ -43,7 +43,11 @@ public class LoginController {
}
@PostMapping("/user/register")
public String register(@Valid @ModelAttribute("user") UserDTO userDTO, BindingResult result, Model model, RedirectAttributes redirectAttributes) {
public String register(@Valid @ModelAttribute("user") UserDTO userDTO
, BindingResult result
, Model model
, RedirectAttributes redirectAttributes)
{
if (result.hasErrors()) {
return "register";
}

View File

@ -80,4 +80,13 @@ public class UserRestController {
return ResponseEntity.ok().body(userService.changepassword(setUserVO));
}
@PostMapping("/api/admin/user")
public ResponseEntity<RestResponse> createUser(@RequestBody UserVO userVO, @AuthenticationPrincipal CustomUserDetails loginUser) {
userVO.setFrstRegisterId(loginUser.getUser().getUserId());
userVO.setLastUpdusrId(loginUser.getUser().getUserId());
return ResponseEntity.ok().body(userService.insertUser(userVO));
}
}

View File

@ -102,4 +102,46 @@
AND u.active_yn = 'Y'
</select>
<insert id="insertUser" parameterType="userVO">
INSERT INTO users (
uniq_id,
user_id,
user_pw,
user_name,
mobile_phone,
rank_cd,
dept_cd,
role,
gw_id,
biostar_id,
active_yn,
hire_date,
resign_date,
FRST_REGISTER_ID,
FRST_REGIST_PNTTM,
LAST_UPDUSR_ID,
LAST_UPDT_PNTTM
)
VALUES (
#{uniqId},
#{userId},
#{password},
#{userName},
#{mobilePhone},
#{rankCd},
#{deptCd},
#{role},
#{gwId},
#{biostarId},
#{activeYn},
#{hireDate},
#{resignDate},
#{frstRegisterId},
NOW(),
#{lastUpdusrId},
NOW()
)
</insert>
</mapper>

View File

@ -169,13 +169,14 @@
<script>
const commonExportOptions = {
columns: ':visible',
columns: function (idx, data, node) {
return idx !== 12; // 관리 열 제외
},
format: {
body: function (data, row, column, node) {
if ($(node).find('select').length) {
return $(node).find('select option:selected').text();
}
// 태그 제거: 보이는 텍스트만 반환
return $(node).text();
}
}
@ -201,6 +202,7 @@
{
extend: 'excel',
charset: 'UTF-8',
bom: true,
exportOptions: commonExportOptions
},
{

View File

@ -63,7 +63,16 @@
<div class="card">
<div class="card-header">
<h3 class="card-title">목록</h3>
<div class="row w-100">
<div class="col-sm-6 align-self-center">
<h3 class="card-title">목록</h3>
</div>
<div class="col-sm-6 text-right">
<button class="btn btn-primary btn-sm" onclick="openUserRegisterModal()">
<i class="fas fa-user-plus"></i> 사용자 등록
</button>
</div>
</div>
</div>
<!-- /.card-header -->
<div class="card-body">
@ -127,7 +136,6 @@
</tfoot>
</table>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
</div>
@ -214,6 +222,84 @@
</div>
</div>
<!-- 공통 코드 그룹 등록 모달 -->
<div class="modal fade" id="regUserInfoModal" tabindex="-1" role="dialog" aria-labelledby="regUserInfoModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="regUserInfoModalLabel">사용자 정보 등록</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="regUserInfoForm">
<div class="form-group">
<label for="uniqId">고유 ID</label>
<input type="text" class="form-control" id="regUniqId" name="uniqId" placeholder="등록하면 자동 생성됩니다." readonly>
</div>
<div class="form-group">
<label for="userId">ID</label>
<input type="text" class="form-control" id="regUserId" name="userId" placeholder="그룹웨어와 같은 ID로 등록해 주세요.">
</div>
<div class="form-group">
<label for="biostarId">출퇴근 ID</label>
<input type="text" class="form-control" id="biostarId" name="biostarId" placeholder="출퇴근 관리 목록 이름에 '&@~'로 시작하고 '=='로 끝나는 문자 넣어 주세요">
</div>
<div class="form-group">
<label for="mobilePhone">핸드폰번호</label>
<input type="text" class="form-control" id="regMobilePhone" name="mobilePhone">
</div>
<div class="form-group">
<label for="userName">이름</label>
<input type="text" class="form-control" id="regUserName" name="userName">
</div>
<div class="form-group">
<label for="rankCd">직급</label>
<select class="form-control" id="regRankCd" name="rankCd">
<option value="" th:selected>-- 선택 --</option>
<option th:each="code : ${@TCodeUtils.getCodeList('RANK')}"
th:value="${code.codeId}"
th:text="${code.codeName}">
</option>
</select>
</div>
<div class="form-group">
<label for="deptCd">부서명</label>
<select class="form-control" id="regDeptCd" name="deptCd">
<option value="" th:selected>-- 선택 --</option>
<option th:each="code : ${@TCodeUtils.getCodeList('DEPT')}"
th:value="${code.codeId}"
th:text="${code.codeName}">
</option>
</select>
</div>
<div class="form-group">
<label for="activeYn">재직여부</label>
<select class="form-control" id="regActiveYn" name="activeYn">
<option value="Y">재직</option>
<option value="N">퇴사</option>
</select>
</div>
<div class="form-group">
<label for="hireDate">입사일</label>
<input type="date" id="regHireDate" name="hireDate" class="form-control" required>
</div>
<div class="form-group">
<label for="resignDate">퇴사일</label>
<input type="date" id="regResignDate" name="resignDate" class="form-control" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">닫기</button>
<button type="button" class="btn btn-primary" onclick="createUser()">등록</button>
</div>
</div>
</div>
</div>
<!-- 비밀번호 변경 모달 -->
<div class="modal fade" id="changePasswordModal" tabindex="-1" role="dialog" aria-labelledby="changePasswordModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
@ -461,6 +547,42 @@
});
}
window.openUserRegisterModal = function() {
$('#regUserInfoForm')[0].reset();
$('#regUserInfoModal').modal('show');
};
window.createUser = function() {
const formId = "#regUserInfoForm";
// 필수값 검증 (예시로 이름만)
if (!$(formId + ' #regUserName').val()) {
alert("이름을 입력하세요.");
return;
}
const formData = {};
$(formId).serializeArray().forEach(field => {
formData[field.name] = field.value;
});
$.ajax({
url: '/api/admin/user', // POST로 등록
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(formData),
success: function(response) {
$('#regUserInfoModal').modal('hide');
fn_successAlert("등록 완료", response.data + "님 등록됨");
location.reload();
},
error: function() {
alert("등록 실패");
}
});
};
</script>