메뉴 여닫기
개인 메뉴 토글
로그인하지 않음
만약 지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.

Autoit web 하이브리드 프로그래밍

데브카페

좋습니다. AutoIt이 로컬 HTTP 서버 + SQLite 백엔드 역할을 하고, 브라우저에서 jspreadsheet-ce(엑셀형 그리드)로 화면을 띄우는 구조로 만들어 드릴게요.

구조

project/
├── main.au3          ← HTTP 서버 + SQLite
├── sqlite3.dll       ← SQLite UDF용 (AutoIt SQLite UDF에 포함)
└── web/
    └── index.html    ← 엑셀형 UI


동작: AutoIt이 127.0.0.1:8765 에서 listen → 자동으로 기본 브라우저 오픈 → 그리드에서 입력/수정 → /api/save, /api/load 로 SQLite와 통신.

main.au3

#include <SQLite.au3>
#include <SQLite.dll.au3>

Opt("TCPTimeout", 100)
OnAutoItExitRegister("OnExit")

Global $g_sDBPath  = @ScriptDir & "\sheet.db"
Global $g_sWebRoot = @ScriptDir & "\web"
Global $g_iPort    = 8765
Global $g_hDB

_SQLite_Startup()
If @error Then Exit MsgBox(16, "Err", "SQLite 시작 실패")
$g_hDB = _SQLite_Open($g_sDBPath)
_SQLite_Exec($g_hDB, _
    "CREATE TABLE IF NOT EXISTS sheet_data(" & _
    "  row_idx INTEGER, col_idx INTEGER, value TEXT," & _
    "  PRIMARY KEY(row_idx, col_idx));")

TCPStartup()
Local $iListen = TCPListen("127.0.0.1", $g_iPort)
If $iListen = -1 Then Exit MsgBox(16, "Err", "포트 사용중: " & $g_iPort)

ShellExecute("http://127.0.0.1:" & $g_iPort & "/")

While 1
    Local $iSock = TCPAccept($iListen)
    If $iSock >= 0 Then _Handle($iSock)
    Sleep(10)
WEnd

Func _Handle($iSock)
    Local $sReq = "", $iWait = 0
    While $iWait < 300
        Local $c = TCPRecv($iSock, 8192)
        If $c <> "" Then
            $sReq &= $c
            If StringInStr($sReq, @CRLF & @CRLF) Then ExitLoop
        EndIf
        Sleep(5)
        $iWait += 1
    WEnd

    Local $iSplit = StringInStr($sReq, @CRLF & @CRLF)
    Local $sHead  = StringLeft($sReq, $iSplit - 1)
    Local $sBody  = StringMid($sReq, $iSplit + 3)

    Local $aCL = StringRegExp($sHead, "(?i)Content-Length:\s*(\d+)", 1)
    Local $iCL = (IsArray($aCL)) ? Int($aCL[0]) : 0
    While StringLen(BinaryToString(StringToBinary($sBody, 4), 4)) < $iCL
        Local $c = TCPRecv($iSock, 8192)
        If $c <> "" Then $sBody &= $c
        Sleep(5)
    WEnd

    Local $aFirst = StringRegExp($sHead, "^(\S+)\s+(\S+)", 1)
    If Not IsArray($aFirst) Then
        TCPCloseSocket($iSock)
        Return
    EndIf
    Local $sMethod = $aFirst[0], $sPath = $aFirst[1]

    Switch $sPath
        Case "/", "/index.html"
            _SendFile($iSock, $g_sWebRoot & "\index.html", "text/html; charset=utf-8")
        Case "/api/load"
            _ApiLoad($iSock)
        Case "/api/save"
            _ApiSave($iSock, $sBody)
        Case "/api/clear"
            _SQLite_Exec($g_hDB, "DELETE FROM sheet_data;")
            _SendJson($iSock, '{"ok":true}')
        Case Else
            _Send404($iSock)
    EndSwitch

    TCPCloseSocket($iSock)
EndFunc

Func _ApiLoad($iSock)
    Local $aRes, $iRows, $iCols
    _SQLite_GetTable2d($g_hDB, _
        "SELECT row_idx,col_idx,value FROM sheet_data ORDER BY row_idx,col_idx;", _
        $aRes, $iRows, $iCols)
    Local $s = '{"cells":['
    For $i = 1 To $iRows
        If $i > 1 Then $s &= ","
        $s &= '{"r":' & $aRes[$i][0] & ',"c":' & $aRes[$i][1] & _
              ',"v":' & _JsonStr($aRes[$i][2]) & '}'
    Next
    $s &= ']}'
    _SendJson($iSock, $s)
EndFunc

Func _ApiSave($iSock, $sBody)
    _SQLite_Exec($g_hDB, "BEGIN;")
    _SQLite_Exec($g_hDB, "DELETE FROM sheet_data;")
    Local $aM = StringRegExp($sBody, _
        '\{"r":(\d+),"c":(\d+),"v":"((?:\\.|[^"\\])*)"\}', 3)
    If Not @error Then
        Local $sStmt
        For $i = 0 To UBound($aM) - 1 Step 3
            Local $v = _JsonUnesc($aM[$i + 2])
            $v = StringReplace($v, "'", "''")
            _SQLite_Exec($g_hDB, _
                "INSERT INTO sheet_data VALUES(" & _
                $aM[$i] & "," & $aM[$i+1] & ",'" & $v & "');")
        Next
    EndIf
    _SQLite_Exec($g_hDB, "COMMIT;")
    _SendJson($iSock, '{"ok":true}')
EndFunc

Func _SendFile($iSock, $sFile, $sCT)
    If Not FileExists($sFile) Then Return _Send404($iSock)
    Local $h = FileOpen($sFile, 16)
    Local $b = FileRead($h)
    FileClose($h)
    Local $sH = "HTTP/1.1 200 OK" & @CRLF & _
        "Content-Type: " & $sCT & @CRLF & _
        "Content-Length: " & BinaryLen($b) & @CRLF & _
        "Connection: close" & @CRLF & @CRLF
    TCPSend($iSock, $sH)
    TCPSend($iSock, $b)
EndFunc

Func _SendJson($iSock, $sJson)
    Local $b = StringToBinary($sJson, 4)
    Local $sH = "HTTP/1.1 200 OK" & @CRLF & _
        "Content-Type: application/json; charset=utf-8" & @CRLF & _
        "Content-Length: " & BinaryLen($b) & @CRLF & _
        "Connection: close" & @CRLF & @CRLF
    TCPSend($iSock, $sH)
    TCPSend($iSock, $b)
EndFunc

Func _Send404($iSock)
    TCPSend($iSock, "HTTP/1.1 404 Not Found" & @CRLF & "Content-Length: 0" & @CRLF & @CRLF)
EndFunc

Func _JsonStr($s)
    $s = StringReplace($s, "\", "\\")
    $s = StringReplace($s, '"', '\"')
    $s = StringReplace($s, @CR, "\r")
    $s = StringReplace($s, @LF, "\n")
    $s = StringReplace($s, @TAB, "\t")
    Return '"' & $s & '"'
EndFunc

Func _JsonUnesc($s)
    $s = StringReplace($s, "\n", @LF)
    $s = StringReplace($s, "\r", @CR)
    $s = StringReplace($s, "\t", @TAB)
    $s = StringReplace($s, '\"', '"')
    $s = StringReplace($s, "\\", "\")
    Return $s
EndFunc

Func OnExit()
    _SQLite_Close($g_hDB)
    _SQLite_Shutdown()
    TCPShutdown()
EndFunc


web/index.html

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>웹 시트 (AutoIt + SQLite)</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspreadsheet-ce@4/dist/jspreadsheet.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsuites@4/dist/jsuites.css">
<style>
  body { font-family:'맑은 고딕',sans-serif; padding:12px; background:#fafafa; }
  .bar { margin-bottom:10px; display:flex; align-items:center; gap:8px; }
  button { padding:7px 14px; border:1px solid #ccc; background:#fff;
           border-radius:4px; cursor:pointer; }
  button:hover { background:#f0f0f0; }
  #status { margin-left:10px; color:#555; font-size:13px; }
  h2 { margin:0 0 12px; font-size:18px; }
</style>
</head>
<body>
<h2>📊 웹 스프레드시트</h2>
<div class="bar">
  <button onclick="saveAll()">💾 저장</button>
  <button onclick="loadAll()">🔄 불러오기</button>
  <button onclick="clearAll()">🗑 전체삭제</button>
  <button onclick="exportCsv()">📤 CSV</button>
  <span id="status">준비</span>
</div>
<div id="sheet"></div>

<script src="https://cdn.jsdelivr.net/npm/jsuites@4/dist/jsuites.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspreadsheet-ce@4/dist/jspreadsheet.js"></script>
<script>
const COLS = 10, ROWS = 50;
let sheet;
const setStatus = s => document.getElementById('status').textContent = s;
const emptyData = () => Array.from({length:ROWS}, () => Array(COLS).fill(''));

function initSheet(data){
  const el = document.getElementById('sheet');
  el.innerHTML = '';
  sheet = jspreadsheet(el, {
    data,
    columns: Array.from({length:COLS}, (_,i) => ({
      title: String.fromCharCode(65+i), width: 110
    })),
    allowInsertRow: true,
    minDimensions: [COLS, ROWS],
    onchange: () => setStatus('수정됨 (저장 필요)')
  });
}

async function loadAll(){
  setStatus('불러오는 중...');
  const data = emptyData();
  const r = await fetch('/api/load');
  const j = await r.json();
  for (const c of j.cells){
    while (data.length <= c.r) data.push(Array(COLS).fill(''));
    while (data[c.r].length <= c.c) data[c.r].push('');
    data[c.r][c.c] = c.v;
  }
  initSheet(data);
  setStatus(`불러옴 (${j.cells.length}셀)`);
}

async function saveAll(){
  setStatus('저장 중...');
  const data = sheet.getData();
  const cells = [];
  for (let r=0; r<data.length; r++)
    for (let c=0; c<data[r].length; c++){
      const v = data[r][c];
      if (v !== '' && v != null) cells.push({r, c, v: String(v)});
    }
  const res = await fetch('/api/save', {
    method:'POST',
    headers:{'Content-Type':'application/json'},
    body: JSON.stringify({cells})
  });
  const j = await res.json();
  setStatus(j.ok ? `저장 완료 (${cells.length}셀)` : '저장 실패');
}

async function clearAll(){
  if (!confirm('전체 데이터를 삭제할까요?')) return;
  await fetch('/api/clear', {method:'POST'});
  initSheet(emptyData());
  setStatus('삭제됨');
}

function exportCsv(){
  const data = sheet.getData();
  const csv = data.map(r => r.map(v =>
    `"${String(v ?? '').replace(/"/g,'""')}"`).join(',')).join('\n');
  const blob = new Blob(['\uFEFF'+csv], {type:'text/csv;charset=utf-8'});
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = 'sheet.csv';
  a.click();
}

loadAll();
</script>
</body>
</html>


실행 방법

1. 위 구조대로 폴더 생성, 두 파일 저장 2. sqlite3.dll을 main.au3 옆에 두기 (AutoIt SQLite UDF 폴더에 포함) 3. main.au3 더블클릭 → 브라우저 자동 오픈 4. 셀에 입력 후 💾 저장 → SQLite에 즉시 저장됨

확장 포인트

• 시트 여러 개: sheet_data에 sheet_name 컬럼 추가 + 상단 탭 UI • 셀 서식: value 외에 style_json 컬럼 추가, jspreadsheet getStyle()/setStyle() 활용 • 수식: jspreadsheet-ce가 기본 SUM/AVG 등 지원 — 자동 동작함 • 오라클 연동: /api/oracle/query 엔드포인트 추가해서 AutoIt에서 sqlplus.exe 호출 결과를 그리드에 표시

CSV import나 다중 시트, 수식 자동계산 결과 저장 등 원하시는 방향 있으면 이어서 만들어 드릴게요.

Comments