자바 기반 Spring Boot + Vue.js + UniApp를 활용한 애니메이션 웹사이트 개발 사례 및 소스코드 배포 안내

프로젝트 개요

이 프로젝트는 Spring Boot를 백엔드 프레임워크로, Vue.js를 전면 프론트엔드 프레임워크로, 그리고 UniApp를 통해 모바일 웹 및 네이티브 애플리케이션을 구현하는 통합 애니메이션 정보 포털 사이트입니다. 사용자 중심의 콘텐츠 관리, 게시판 시스템, 로그인 인증, 세션 관리 등 핵심 기능을 포함하며, 실제 운영 환경에서 사용 가능한 수준의 코드 구조를 제공합니다.

기술 스택 구성

  • 백엔드: Spring Boot 2.7+, MyBatis-Plus, JWT 기반 인증
  • 프론트엔드: Vue 3 + Vite + Axios + Element Plus UI 컴포넌트 라이브러리
  • 모바일 클라이언트: UniApp (Vue 3 기반 하이브리드 앱)
  • 데이터베이스: MySQL 8.0, UTF-8 인코딩
  • 보안: Token 기반 인증, 요청 필터링, 권한 체크, 비밀번호 암호화 처리

핵심 기능 설계

시스템은 사용자 인증, 콘텐츠 게시/댓글, 주소 관리, 사용자 역할 제어 등을 중심으로 설계되었습니다. 특히, 다양한 접근 방식에 대한 유연한 대응을 위해 미들웨어 기반의 인증 필터가 적용되어 있으며, 공통적인 로직을 추상화하여 유지보수성과 확장성을 극대화했습니다.

코드 예시: 인증 토큰 생성 및 검증

// 토큰 생성 메서드
public String generateSessionToken(Long userId, String userName, String roleType, Date expireTime) {
    String token = RandomStringUtils.randomAlphanumeric(32);
    
    // 기존 토큰 존재 여부 확인
    TokenRecord existing = tokenMapper.selectOne(new QueryWrapper<TokenRecord>()
        .eq("user_id", userId)
        .eq("role", roleType));
    
    if (existing != null) {
        existing.setToken(token);
        existing.setExpireTime(expireTime);
        tokenMapper.updateById(existing);
    } else {
        TokenRecord newRecord = new TokenRecord();
        newRecord.setUserId(userId);
        newRecord.setUsername(userName);
        newRecord.setRole(roleType);
        newRecord.setToken(token);
        newRecord.setExpireTime(expireTime);
        tokenMapper.insert(newRecord);
    }
    
    return token;
}

// 요청 인증 필터
@Component
public class AuthFilter implements HandlerInterceptor {

    private static final String AUTH_HEADER = "X-AUTH-TOKEN";

    @Autowired
    private TokenService tokenService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // CORS 설정
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, X-AUTH-TOKEN");
        response.setHeader("Access-Control-Max-Age", "3600");

        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return false;
        }

        // 무시 대상 엔드포인트 체크
        if (handler instanceof HandlerMethod) {
            IgnoreAuth ignore = ((HandlerMethod) handler).getMethodAnnotation(IgnoreAuth.class);
            if (ignore != null) return true;
        }

        // 헤더에서 토큰 추출
        String authToken = request.getHeader(AUTH_HEADER);
        if (authToken == null || authToken.isEmpty()) {
            sendErrorResponse(response, "인증 토큰이 누락되었습니다.");
            return false;
        }

        TokenRecord tokenInfo = tokenService.validateToken(authToken);
        if (tokenInfo == null) {
            sendErrorResponse(response, "유효하지 않은 또는 만료된 토큰입니다.");
            return false;
        }

        // 세션에 사용자 정보 저장
        request.getSession().setAttribute("userId", tokenInfo.getUserId());
        request.getSession().setAttribute("role", tokenInfo.getRole());
        request.getSession().setAttribute("username", tokenInfo.getUsername());

        return true;
    }

    private void sendErrorResponse(HttpServletResponse response, String message) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.write("{\"code\":401,\"msg\":\"" + message + "\"}");
        }
    }
}

데이터베이스 스키마 예시

CREATE TABLE user_profile (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    username VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    nickname VARCHAR(100),
    gender VARCHAR(10),
    phone VARCHAR(20),
    avatar_url TEXT,
    score DOUBLE DEFAULT 0.0,
    balance DECIMAL(10,2) DEFAULT 0.0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE forum_post (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    title VARCHAR(255) NOT NULL,
    content LONGTEXT NOT NULL,
    parent_id BIGINT DEFAULT NULL,
    user_id BIGINT NOT NULL,
    username VARCHAR(100) NOT NULL,
    avatar_url TEXT,
    status VARCHAR(20) DEFAULT 'active',
    is_pinned TINYINT DEFAULT 0,
    pin_time DATETIME NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE user_address (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    user_id BIGINT NOT NULL,
    recipient_name VARCHAR(100) NOT NULL,
    contact_phone VARCHAR(20) NOT NULL,
    full_address VARCHAR(500) NOT NULL,
    is_default TINYINT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

테스트 사례 요약

테스트 항목입력 값예상 결과실제 결과결과
로그인 성공admin / admin123 / 정확한 캡차토큰 발급 및 메인 페이지 이동정상 동작통과
비밀번호 오류admin / wrongpass / 정확한 캡차오류 메시지 출력비밀번호 불일치통과
캡차 오류admin / admin123 / 잘못된 캡차캡차 오류 알림캡차 오류 발생통과
사용자 추가newuser / 123456 / 일반 사용자DB에 추가 및 목록 표시추가 완료통과
사용자 삭제선택 후 삭제확인 팝업 → 삭제 완료성공적으로 삭제됨통과

배포 및 실행 가이드

프로젝트는 다음 단계로 배포 가능합니다:

  1. MySQL 데이터베이스 생성 및 스키마 적용
  2. IDEA 또는 VS Code에서 백엔드 프로젝트 열기, 의존성 다운로드 (Maven)
  3. application.yml 파일 내 데이터베이스 연결 정보 수정
  4. 백엔드 서버 실행 (`mvn spring-boot:run`)
  5. 프론트엔드 폴더 진입 → `npm install`, `npm run dev` 실행
  6. UniApp 프로젝트를 HBuilderX에서 열고, 백엔드 주소 연결 후 앱 빌드

전체 과정에 대한 영상 설명 및 디버깅 팁은 별도의 설명 영상에서 제공됩니다.

태그: Spring Boot Vue.js UniApp MyBatis jwt

6월 19일 00:52에 게시됨