울면서개발하기
VERA ERP 개발일지

새 디자인을 기본으로, 체험 계정과 무중단 배포 준비

· 커밋 37개

커밋이 가장 많은 날이다. 오전에는 새 디자인을 다듬다가 아예 기본이자 유일한 디자인으로 정했다. 낮에는 공개 데모에 누구나 들어와 볼 수 있도록 조회 전용 체험 계정을 만들었고, 그 사이사이 화면에서 올라온 프로그램 요청을 처리했다. 오후에는 배포할 때 로그인이 끊기지 않게 세션 저장 방식을 바꿨다.

할 일 목록도 이날부터 저장소 안 문서로 관리하기 시작했다.

한 일

새 디자인이 유일한 디자인으로

  • 로그인 화면을 새로 그렸다(왼쪽 소개 띠 + 오른쪽 입력 카드)
  • 라디오·체크박스·콤보 화살표·트리 펼침·숫자 칸 위아래 단추 같은 작은 그림을 모두 강조색 SVG 로 바꿨다
  • 창 제목줄과 팝업 제목줄은 흰 바탕, 그리드 칸은 안쪽 여백 4px 에 잘린 값 끝은 “…”
  • 메뉴 트리에서 지금 앞에 있는 창의 메뉴를 강조한다
  • 전체 화면 230개를 기존/새 디자인으로 번갈아 열어 비교했다. 새로 생긴 글자 넘침은 없었고 남은 주황색도 0곳이었다
  • 그다음 기존 디자인 고르기를 없앴다. 개인환경설정의 테마 값은 DB 에 저장만 하고(AS-IS 에서는 원래 뜻대로 쓰인다) TO-BE 는 늘 새 디자인이다
  • 파비콘을 만들었다. 그 전에는 아이콘 연결이 없어 /favicon.ico 요청이 로그인 HTML 을 받고 있었다

통신 중 표시

서버 거래가 0.15초를 넘으면 화면 위쪽에 흐르는 막대를, 0.6초를 넘으면 가운데에 작은 표시를 띄운다. 원본 넥사크로 로딩 그림이 있던 자리다.

조회 전용 체험 계정

공개 데모를 누구나 둘러볼 수 있게 하려면, 들어온 사람이 데이터를 바꾸지 못해야 한다. 화면 단추를 끄는 것만으로는 부족하니 서버가 막는다.

  • 어떤 권한이 조회 전용인지, 어떤 단추를 끌지, 예외로 허용할 쓰기 문장은 무엇인지를 공통코드로 정한다
  • 서버는 조회 전용 사용자의 insert·update·delete·프로시저 호출을 MyBatis 단계에서 막고, 저장·파일 경로는 요청 단계에서 한 번 더 막는다
  • 화면은 서버가 알려 준 목록대로 단추를 끄고, 상단바에 “조회 전용”을 표시한다
  • 로그인 화면의 “체험하기” 단추로 비밀번호 없이 체험 계정에 들어간다. 자동화된 요청을 막으려고 Cloudflare Turnstile 확인과 요청 횟수 제한을 걸었고, 대상 계정이 조회 전용이 아니면 거절한다. 체험 세션은 30분이고 자동 로그인은 주지 않는다

프로그램 요청으로 들어온 것들

화면의 연필 아이콘으로 올라온 요청과 운영에서 직접 본 문제를 고쳤다.

  • 그리드 행을 끌어 다른 예산계획서로 옮기는 기능(예산신청). 이 과정에서 xfdl 이벤트 처리기가 돌려준 값이 버려지던 것도 고쳤다. canitemchange 에서 false 를 돌려줘도 막히지 않고 있었다
  • 그리드 칸 편집기에 글자를 칠 때마다 오는 oninput
  • 입력하다가 마우스로 다른 곳을 누르면 값이 반영되지 않던 것. 입력 중에 칸이 다시 그려지면서 옛 값으로 돌아가고 있었다
  • 새 디자인에서 그리드 “상태” 칸의 수정·신규 아이콘이 선택 줄 배경에 덮여 지워지던 것
  • 세로 방향 라디오가 원본과 달리 세로로 쌓이던 것. 원본은 높이에 들어가는 줄 수만큼 채우고 옆 칸으로 넘어간다
  • 우편번호 찾기. 외부 스크립트 주소가 바뀌어 404 가 났고, 보이지 않는 iframe 이 여는 새 창은 브라우저가 팝업으로 막았다. 새 주소로 바꾸고 화면 가운데 상자로 띄운다
  • 거래처관리에서 사업자 구분에 맞는 탭이 골라지지 않던 것, 그리드 머리의 필수 표시가 새 디자인 배경에 덮이던 것
  • 열 너비를 머리 경계를 끌어 바꾸기, 메뉴 창·팝업 안 Tab 키 이동을 탭 순서대로
  • 회계구분을 지우려 하면 DB 함수가 ORA-01489(문자열 연결 결과가 너무 김)로 실패하던 것

배포 뒤 새로고침 안 한 창

배포하면 해시가 붙은 옛 화면 파일이 사라진다. 배포 전에 열어 둔 창에서 새 메뉴를 열면 그 파일을 못 받아 화면이 열리지 않았다. Vite 의 vite:preloadError 이벤트를 받아 새로고침할지 묻게 했다. 서버 쪽에서도 옛 파일을 최근 몇 번의 배포까지는 남겨 두게 했다.

무중단 배포 준비: 세션을 DB 로

블루·그린 배포로 넘어가려면 컨테이너가 바뀌어도 로그인이 유지돼야 한다.

  • 세션 저장소를 파일에서 Spring Session JDBC(MySQL)로 바꿨다. 표는 앱이 만들지 않고 Spring Session 이 제공하는 스키마 SQL 을 따로 돌린다
  • 종료할 때 진행 중인 요청을 기다리는 graceful shutdown(20초)
  • 배포 스크립트가 새 서버가 떴는지 확인할 수 있게 actuator health 만 열었다

그런데 이것을 반영하자마자 로그인 세션 저장이 실패했다. 아래 기술 메모의 내용이다.

기술 메모

Spring Session JDBC 와 record principal. Spring Session JDBC 는 Authentication.getName() 값을 세션 표의 PRINCIPAL_NAME 열(100자)에 넣는다. getName() 은 principal 이 Principal 이면 그 getName() 을, 아니면 toString() 을 쓴다. 로그인 사용자를 Java record 로 만들어 두었더니 toString() 이 모든 필드를 늘어놓은 긴 문자열이 되어 Data too long 으로 저장이 실패했다. record 에 Principal 을 구현해 이름이 아이디가 되게 했다. JSON 으로 화면에 줄 때 name 필드가 새로 생기지 않게 @JsonIgnore 를 붙였다.

public record LoginUser(
        String userId,
        String userName,
        // ...
        String lastLoginAt) implements Serializable, Principal {

    @Override
    @JsonIgnore
    public String getName() {
        return userId;
    }
}

LISTAGG 4000바이트 한도. 기초코드를 지워도 되는지 검사하는 DB 함수가 사용처 표를 LISTAGG 로 이어 붙여 하나의 동적 SQL 로 만들고 있었다. 검사할 칸을 가진 표가 많아지면서 결과가 VARCHAR2 한도(MAX_STRING_SIZE = STANDARD 에서 4000바이트)를 넘어 ORA-01489 가 났다. 표마다 하나씩 EXECUTE IMMEDIATE ... USING 으로 검사하고 값은 바인드 변수로 넘기게 고쳤다. Oracle 12c R2 부터는 LISTAGG ... ON OVERFLOW TRUNCATE 로 오류를 피할 수는 있지만, 이 경우는 잘린 SQL 이 되므로 구조를 바꾸는 쪽이 맞았다.

화면 차단은 서버가. 조회 전용 사용자를 화면에서 단추만 꺼서 처리하면, 개발자 도구에서 요청을 직접 보내면 그만이다. 그래서 막는 기준(쓰기 문장, 저장 경로)은 서버에 두고, 화면의 단추 끄기는 보기 좋으라고 하는 것으로만 생각했다.

다음에

  • 할 일 목록에 다크 모드 지원을 올려 두었다