문제

카카오맵 API에서 키워드로 검색을 사용하기 위해 quey 파라미터를 주는데 다음과 같은 문제를 발견했다.

query=분식,떡볶이일 떄와 query=떡볶이,분식의 경우에 결과가 다른 것을 볼 수 있다.

keyword를 보면, 파라미터에 분식,떡볶이가 하나의 키워드로 묶여서 검색된 것이다.


해결

이를 해결하기 위해선 query 파라미터에 각 파라미터를 ""로 감싸서 보내면 된다.

qurey="분식","떡볶이" 이렇게 보내면 각각 keyword에 따로 들어가 qurey="분식","떡볶이"와 qurey="떡볶이","분식"의 결과가 같은걸 확인 할 수있다.


sequel pro를 사용해 MySQL을 쓸 때 사용자 지정 함수를 생성하는 방법은 다음과 같다.

DROP FUNCTION 'ufn_board_no'
delimiter ||
CREATE DEFINER=`test`@`%` FUNCTION `ufn_board_no`() RETURNS int(11)
BEGIN
	DECLARE v_result INT(11);
	SELECT MAX(a.no) + 1 INTO v_result 
	   FROM table_sequense a
	WHERE 1=1
	      AND a.name='board_no';
	if v_result is null then
		set v_result = 1;
	end if;
	INSERT INTO TABLE_SEQUENSE (`no`, `name`) VALUES (v_result, 'board_no');
	RETURN (v_result);
END;
||
delimiter;

 

기존에 있는 함수의 syntax를 확인하면 다음과 같다.

CREATE DEFINER=`test`@`%` FUNCTION `ufn_board_no`() RETURNS int(11)
BEGIN
	DECLARE v_result INT(11);
	SELECT MAX(a.no) + 1 INTO v_result 
	   FROM table_sequense a
	WHERE 1=1
	      AND a.name='board_no';
	if v_result is null then
		set v_result = 1;
	end if;
	INSERT INTO TABLE_SEQUENSE (`no`, `name`) VALUES (v_result, 'board_no');
	RETURN (v_result);
END;

 새로 함수를 생성하려면 Query 탭에서 제일 위의 코드와 같이 다음 코드를 추가해주면 된다.

DROP FUNCTION 'ufn_board_no'
delimiter ||

||
delimiter;
// Grid 출력하기
jQuery(document).ready(function(){
	jQuery("#list1").jqGrid({
		url:'bbsListAjax',
		datatype: 'json',
		mtype: 'POST',
		 jsonReader : { 
				page: "page", 
				total: "total", 
				root: "rows", 
				records: function(obj){return obj.length;},
				repeatitems: false, 
				id: "bbs_no"
			},
		colNames:['수정일자','게시글번호','입력구분','등록일자', '등록자','제목','내용'],
		colModel:[
          	{name:'MODI_DATE',index:'MODI_DATE', width:100, align:"center"},
          	{name:'BBS_NO',index:'BBS_NO', width:80, align:"center"},
          	{name:'BBS_DV_CD',index:'BBS_DV_CD', width:150, align:"center"},
          	{name:'REG_DATE',index:'REG_DATE', width:150, align:"center"},
          	{name:'REG_NO',index:'a.auth_nm', width:150, align:"center"},
          	{name:'SUBJECT',index:'SUBJECT', width:60, align:"center"},
          	{name:'CONTENT',index:'CONTENT', width:150, align:"center"},
        ],
		rowNum:20,
		autowidth: true,
		height:'auto',
		pager: jQuery('#pager1'),
		sortname: 'BBS_NO',
		viewrecords: true,
		sortorder: "desc",
		caption:"게시글목록",
		onSelectRow: function(id){
			if (id)	{
				ajaxEdit(id);
			}
		}
	}).navGrid("#pager1",{search:false,edit:false,add:false,del:false});			

});

데이터를 Json 객체로 받아와 화면에 JqGrid로 출력하려고 하는데 화면에 아무것도 출력되지 않았다.

DB에 아에 샘플 데이터가 없는 경우에는 다음과 같이 문제 없이 파싱이 잘 됐는데 샘플 데이터가 있는 경우에는 파싱이 되지 않는 것을 크롬 개발자 도구를 통해 확인할 수 있었다.

파싱 성공
파싱 실패

{"total":1,"record":1,"page":1,"rows":[{"MODI_DATE":"2020-02-02 02:02:02.0","BBS_NO":"1","BBS_DV_CD":"1","REG_DATE":"2020-06-19 00:16:08.0","SUBJECT":"This is Test","DEL_DATE":"2033-03-03 03:03:03.0","CONTENT":"I have a parsing problem"}]}

Json 객체를 보니 TIMESTAMP에 소수점까지 표시되고 있는걸 확인했다.

SQL SELECT문에서 날짜와 관련된 칼럼명을 모두 지우자 다음과 같이 문제없이 파싱이 되었다.

이를 해결하기 위해 mapper.xml에서 SELECT문에서 BBS.MODI_DATE를 다음과 같이 고치자 소수점이 더 이상 출력되지 않았고 파싱도 문제 없이 잘 되었다.

SELECT
	DATE_FORMAT(BBS.MODI_DATE,'%Y-%m-%d %H:%i:%s') AS MODI_DATE

버튼을 눌러 주사위를 굴려 값을 얻는 예제를 실행하기 위해 다음과 같이 코드를 작성하였다.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val rollButton: Button = findViewById(R.id.roll_button)
        rollButton.setOnClickListener{
            rollDice()
        }

        diceImage = findViewById(R.id.dice_image)
    }

    private fun rollDice() {

        val randomInt = Random().nextInt(6) + 1

        val drawableResource = when (randomInt){
            1 -> R.drawable.dice_1
            2 -> R.drawable.dice_2
            3 -> R.drawable.dice_3
            4 -> R.drawable.dice_4
            5 -> R.drawable.dice_5
            else -> R.drawable.dice_6
        }
        val diceImage: ImageView = findViewById(R.id.dice_image)
        diceImage.setImageResource(drawableResource)
    }
}

26번째 줄을 보면 함수가 실행 될 때마다 diceImage 변수를 생성하여 findViewById 함수를 이용하여 초기화를 하기 때문에 비효율적이다.

val diceImage: ImageView = findViewById(R.id.dice_image)

findViewById 함수는 Id의 트리를 모두 탐색하기 때문에 여러번 호출하는 것은 앱의 실행 시간에 영향을 끼칠 수 있다.

 

따라서 다음과 같이 코드를 수정하였다.

class MainActivity : AppCompatActivity() {

    val diceImage: ImageView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val rollButton: Button = findViewById(R.id.roll_button)
        rollButton.setOnClickListener{
            rollDice()
        }

        diceImage = findViewById(R.id.dice_image)
    }

    private fun rollDice() {

        val randomInt = Random().nextInt(6) + 1

        val drawableResource = when (randomInt){
            1 -> R.drawable.dice_1
            2 -> R.drawable.dice_2
            3 -> R.drawable.dice_3
            4 -> R.drawable.dice_4
            5 -> R.drawable.dice_5
            else -> R.drawable.dice_6
        }
        val diceImage: ImageView = findViewById(R.id.dice_image)
        diceImage.setImageResource(drawableResource)
    }
}

3번째 줄에 변수로 diceImage를 생성, null로 초기화해준다.

val diceImage: ImageView? = null

그러나 이 코드 역시 변수를 사용하기 위해서 null 체크를 해줘야 하는 번거로움이 남는다.

이를 다음과 같이 lateinit을 사용하면 나중에 변수의 값을 초기화 해줄 수 있다.

lateinit val diceImage: ImageView

하지만 이 코드를 실행하려고 하면 'lateinit' modifier is allowed only on mutable properties 라는 에러가 발생하는데

이는 lateinit 은 immutable(불변) 타입인 val에 사용할 수 없기 때문이다.

따라서 val 변수을 mutable(가변) 타입인 var으로 바꿔주면된다.

lateinit var diceImage: ImageView
class MainActivity : AppCompatActivity() {

    lateinit var diceImage: ImageView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val rollButton: Button = findViewById(R.id.roll_button)
        rollButton.setOnClickListener{
            rollDice()
        }

        diceImage = findViewById(R.id.dice_image)
    }

    private fun rollDice() {

        val randomInt = Random().nextInt(6) + 1

        val drawableResource = when (randomInt){
            1 -> R.drawable.dice_1
            2 -> R.drawable.dice_2
            3 -> R.drawable.dice_3
            4 -> R.drawable.dice_4
            5 -> R.drawable.dice_5
            else -> R.drawable.dice_6
        }

        diceImage.setImageResource(drawableResource)
    }
}

val 변수의 초기화를 나중에 하고 싶다면 'by lazy'를 사용하면 되는데 val 변수이기 때문에 나중에 한번 초기화한 이후엔 바꿀 수 없다.

val diceImage: ImageView by lazy

 

 

결론

- findViewById 함수를 여러번 호출하는 것은 앱의 실행 시간에 영향을 끼칠 수 있다.

- lateinit val은 불가능 하며 lateinit var를 사용하거나 by lazy를 사용한다.

 

참고
 

Developing Android Apps with Kotlin

Learn the fundamentals of the Kotlin programming language from Kotlin experts at Google.

www.udacity.com

 

세부내용

아래의 Python으로 작성한 이전의 엑셀 양식 작성 프로그램을 웹사이트로 배포하였다.

( ※ 프로그램을 작동시킨 날짜가 양식의 작성일자에 들어갔으나 ERP의 수금일자가 작성일자가 되도록 수정하였음)

 

[개발] 전자세금계산서 엑셀 양식 작성 프로그램: Python

전자세금계산서 엑셀 양식 작성 프로그램 ERP 시스템(전사적 자원 관리 시스템)을 통해 다운로드한 엑셀 파일을 가지고 전자세금계산서 발급을 위한 엑셀 양식 파일을 채우는 프로그램을 만들기로 했다. 기존에 사..

codingslime.tistory.com

 

사이트
 

전자세금계산서 발행

 

mighty-cove-94866.herokuapp.com

 

코드
 

hynjin/runForHeroku

using flask web server. Contribute to hynjin/runForHeroku development by creating an account on GitHub.

github.com

 

사용방법

위의 주소로 접속하면 위와 같은 단순한 UI의 사이트에 접속할 수 있다.

전자세금계산서 발행을 위한 웹 사이트로 ERP 프로그램에서 다운로드 받은 엑셀 파일에서 [사업자번호, 수금날짜, 법인명, 대표자, 업태, 종목, 사업장주소, 공급가액, 세액] 항목과 별도의 이메일 파일에서 [사업자번호, 이메일주소]를 사용하여 전자세금계산서 양식을 채운 엑셀 파일을 다운할 수 있다.

 

  • 모든 파일은 xlsx 파일로 업로드 해야하며 xlsx 파일로 다운로드한다. (xls 파일 불가능)
  • ERP 파일 (업로드)
    - [사업자번호, 수금날짜, 법인명, 대표자, 업태, 종목, 사업장주소, 공급가액, 세액] 항목이 반드시 포함되어 있어야함
    - 사업자명이나 수금액이 공란일 경우 세금계산서에 작성하지 않음

    - 첫번째 행은 공란이어야함(두번째 행에서부터 파일 시작해야함)
  • 이메일 파일 (업로드)
    -
    [사업자번호, 이메일주소] 항목이 필요하나 필수는 아님
    - 이메일 파일에 정보가 없을 경우 세금계산서 항목에 공란으로 작성
  • 전자세금계산서 파일 (다운로드)
    - ERP 파일와 이메일 파일을 모두 업로드 한 후 [다운로드] 버튼을 눌러 다운로드 할 수 있음
    - 필수 작성 항목인 [전자(세금)계산서 종류, 작성일자, 공급받는자 등록번호, 공급받는자 상호, 공급받는자 성명, 공급가액, 세액, 일자1, 공급가액1, 세액1, 영수(01)청구(02)] 항목을 각각 ERP 파일에서 ["01(영수)", 수금일자, 사업자번호, 법인명, 대표자, 공급가액, 세액, 수금일자의 일자, 공급가액, 세액, "01(영수)"] 항목으로 작성
    - 필수 작성 항목 이외에도 [공급받는자 사업장주소, 공급받는자 업태, 공급받는자 종목] 항목을 각각 ERP 파일의
    - [공급받는자 이메일1] 항목은 별도의 이메일 파일에서 사업자번호와 ERP 파일의 사업자번호가 일치하는 이메일 주소로 작성
    - [품목1] 항목은 세금계산서 작성 "월(다운로드한 월) + CCTV 용역료"으로 작성
  • 파일이나 파일 내의 항목이 잘못 된 경우 'Internal Server Error'가 발생할 수 있다. 이 경우 새로고침하여 웹사이트에 재접속 한 후에 파일의 상태를 확인한 후 다시 시도하면 된다.
  • 이 프로그램은 특정 회사의 요청으로 개발 되었으며 그로 인해 [전자(세금)계산서 종류, 품목1, 영수(01)청구(02)] 항목은 특정 회사의 요구사항에 맞게 자동 작성함
Syntex

- 실행 전에 명령이 화면에 출력되고 명령이 실행

- 실행 전 명령 출력을 막으려면 명령어 앞에 @

- 변수는 대문자로, 변수를 사용하려면 $(변수)형태

- 별도의 명령어 없이 makefile만 호출한다면 첫번째 명령어 실행

 

NAME = prog				//NAME 변수에 prog 내용 선언
SRC = source.c				//SRC 변수에 파일 명, 여러 파일 명 선언 가능

all : &(NAME)				//all 명령

$(NAME):				//변수 이름으로 명령어 실행
	gcc -o $(NAME) $(SRC)		//name 변수 값 이름으로 컴파일 (object(*.o)파일 생성)
    
fclean:					//빌드 과정에서 생성된 파일을 삭제
	/bin/rm -f $(NAME)

re: fclean all				//파일 삭제 후 다시 all 명령 실행

 

실행

make (명령어) 통해 실행 가능

response.headers["Content-Disposition"] = "attachment; filename=%s.xlsx" % date

현재의 날짜를 받아와서 날짜를 포함한 파일이름을 저장하려고 하니 다음과 같은 에러가 계속 떴다.

UnicodeEncodeError: 'latin-1' codec can't encode characters

 

dt = datetime.datetime.now()
date = dt.strftime("%m월세금계산서").encode('utf-8')

아무리 변수 타입을 확인해봐도 dt는 str 타입이었고, encode('utf-8')을 했더니 바이트 타입이 되어 그대로 파일명이 'b'12_xec_x9b_x94_xec_x84_xb8_xea_xb8_x88_xea_xb3_x84_xec_x82_xb0_xec_x84_x9c''이 되어버렸다.

 

dt = datetime.datetime.now()
date = dt.strftime("%m월세금계산서").encode('utf-8').decode('iso-8859-1')

encode('utf-8').decode('iso-8859-1') 했더니 문제가 해결되었다.

 

/upload url을 통해서 엑셀 파일을 업로드 한 다음 별도의 파일 저장없이 바로 html 화면에 보여줌

from flask import Flask, render_template, request, Response
from werkzeug import secure_filename

import pandas as pd

app = Flask(__name__)

@app.route("/upload", methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        print(request.files['file'])
        f = request.files['file']
        data_xls = pd.read_excel(f)
        return data_xls.to_html()
    return '''
    <!doctype html>
    <title>Upload an excel file</title>
    <h1>Excel file upload</h1>
    <form action="" method=post enctype=multipart/form-data>
    <p><input type=file name=file><input type=submit value=Upload>
    </form>
    '''

 

+ Recent posts