기술 스택 개요
본 프로젝트는 SpringBoot 백엔드 프레임워크와 Vue.js 프론트엔드 프레임워크를 기반으로 하며, 모바일 클라이언트는 UniApp을 사용하여 개발되었습니다. 이 시스템은 일러스트레이터와 고객 간의 효율적인 협업 플랫폼을 제공합니다.
백엔드 구성 - SpringBoot
SpringBoot는 독립 실행 가능한 프로덕션급 애플리케이션을 빠르게 개발할 수 있도록 설계된 프레임워크입니다. 기본 설정을 통해 복잡한 XML 설정 없이도 애플리케이션을 신속하게 구동할 수 있으며, 내장 서버(Tomcat, Jetty 등)를 지원하여 배포 과정을 단순화합니다.
프론트엔드 구성 - Vue.js
Vue.js는 반응형 데이터 바인딩과 가상 DOM 기술을 활용하여 사용자 인터페이스 개발을 용이하게 만듭니다. 컴포넌트 기반 아키텍처를 통해 코드 재사용성을 높이고 유지보수성을 향상시킬 수 있습니다.
데이터 영속성 - MyBatis
MyBatis는 SQL 쿼리와 자바 코드를 분리하여 데이터베이스 작업을 관리합니다. 동적 SQL 기능을 통해 다양한 조건에 따른 유연한 쿼리 생성이 가능하며, 캐싱 메커니즘을 통해 성능 최적화를 실현합니다.
핵심 코드 예제
// 인증 무시 어노테이션
@SkipAuth
@PostMapping("/authenticate")
public ResponseEntity<Map<String, Object>> authenticate(
@RequestParam String userCode,
@RequestParam String passCode,
HttpServletRequest req) {
Userinfo userInfo = userService.queryByAccount(userCode);
if(userInfo == null || !userInfo.getPassCode().equals(passCode)) {
return ResponseEntity.status(401).body(Map.of("message", "인증 실패"));
}
String sessionToken = tokenGenerator.createSessionToken(
userInfo.getId(),
userCode,
userInfo.getUserType()
);
return ResponseEntity.ok(Map.of("token", sessionToken));
}
// 세션 토큰 생성 로직
@Override
public String createSessionToken(Long userId, String account, String userType) {
SessionToken existingToken = tokenRepository.findByUserIdAndType(userId, userType);
String newToken = UUID.randomUUID().toString();
Date expiryTime = new Date(System.currentTimeMillis() + 3600000); // 1시간 후 만료
if(existingToken != null) {
existingToken.setTokenValue(newToken);
existingToken.setExpiryDate(expiryTime);
tokenRepository.update(existingToken);
} else {
tokenRepository.save(new SessionToken(userId, account, userType, newToken, expiryTime));
}
return newToken;
}
/**
* 인증 인터셉터 구현
*/
@Component
public class AuthInterceptor implements HandlerInterceptor {
private static final String AUTH_HEADER = "Authorization-Token";
@Autowired
private TokenService authService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
if(request.getMethod().equals("OPTIONS")) {
response.setStatus(200);
return false;
}
SkipAuth skipAnnotation = ((HandlerMethod) handler).getMethodAnnotation(SkipAuth.class);
if(skipAnnotation != null) {
return true;
}
String authToken = request.getHeader(AUTH_HEADER);
SessionToken tokenInfo = StringUtils.hasText(authToken) ?
authService.validateToken(authToken) : null;
if(tokenInfo != null) {
request.setAttribute("userId", tokenInfo.getUserId());
request.setAttribute("userType", tokenInfo.getUserType());
return true;
}
response.setStatus(401);
return false;
}
}
데이터베이스 구조
DROP TABLE IF EXISTS user_addresses;
CREATE TABLE user_addresses (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
user_id BIGINT NOT NULL,
full_address VARCHAR(200) NOT NULL,
recipient_name VARCHAR(100) NOT NULL,
contact_number VARCHAR(20) NOT NULL,
is_primary BOOLEAN DEFAULT FALSE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS community_posts;
CREATE TABLE community_posts (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
post_title VARCHAR(200),
post_content TEXT NOT NULL,
parent_post_id BIGINT,
author_id BIGINT NOT NULL,
author_name VARCHAR(100),
profile_image TEXT,
status VARCHAR(50),
pinned BOOLEAN DEFAULT FALSE,
pin_time DATETIME
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS clients;
CREATE TABLE clients (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
account_name VARCHAR(100) NOT NULL UNIQUE,
encrypted_password VARCHAR(100) NOT NULL,
full_name VARCHAR(100) NOT NULL,
gender VARCHAR(10),
mobile_phone VARCHAR(20),
profile_picture TEXT,
reward_points DOUBLE DEFAULT 0,
account_balance DOUBLE DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
샘플 데이터 삽입
INSERT INTO products (product_name, unit_price, product_desc, inventory_count)
VALUES ('iPhone 13 Pro', 1099.99, '고성능 스마트폰', 75);
INSERT INTO products (product_name, unit_price, product_desc, inventory_count)
VALUES ('Galaxy S22 Ultra', 999.99, '안드로이드 플래그십', 120);
INSERT INTO products (product_name, unit_price, product_desc, inventory_count)
VALUES ('PlayStation 5 Digital', 399.99, '차세대 게임 콘솔', 40);
테스트 시나리오
| 입력값 | 예상 결과 | 실제 결과 | 검증 상태 |
|---|---|---|---|
| 계정: admin / 비밀번호: 123456 / 인증코드: 정확 | 로그인 성공 | 시스템 접속 완료 | 일치 |
| 계정: admin / 비밀번호: wrong / 인증코드: 정확 | 비밀번호 오류 | "비밀번호 불일치" 표시 | 일치 |
| 계정: admin / 비밀번호: 123456 / 인증코드: 잘못됨 | 인증 실패 | "인증코드 오류" 알림 | 일치 |
사용자 관리 테스트
| 작업 내용 | 예상 결과 | 실제 결과 | 검증 상태 |
|---|---|---|---|
| 신규 사용자 등록 (user1) | 등록 성공 | 사용자 목록에 추가됨 | 일치 |
| 중복 계정 등록 시도 | 등록 거부 | "계정 중복" 경고 발생 | 일치 |
| 사용자 정보 수정 | 변경사항 적용 | 업데이트 완료 | 일치 |
| 사용자 삭제 처리 | 목록에서 제거 | 삭제 확인됨 | 일치 |