이번 글에서는 안드로이드에서 Nodejs서버에 이미지파일을 업로드하고 파일로 저장하는법,
파일로 저장한 이미지를 mysql 에 Blob형태로 저장하는방법,
이 저장한 이미지를 다시 안드로이드에서 받는 방법에 대해 알아보도록 하자.
이 작업을 위해서 안드로이드쪽에서 따로 필요한 모듈은 없으니 바로 코드로 들어가보도록 하자!
일단 PNG파일을 안드로이드에서 저장하는 방법에 대해서는 생략하도록 하겠다.
또한 Retrofit으로 Nodejs서버와 통신하는 방법도 생략하도록 하겠다.
이부분이 궁금하신 분들은 아래 링크로 가주시길
https://devrunner96.tistory.com/6
@Multipart
@POST("/db/postImg")
fun postImg(@Part photo: MultipartBody.Part): Call<ResponseDC>
Retrofit쪽 POST함수는 위와같이 작성해주도록 하자
안드로이드에서 retrofit으로 이미지나 영상등을 보낼 때에는 Mutipart를 사용한다.
이때 보내는 데이터는 MUltipartBody.Part가 된다.
var file = File("${getExternalFilesDir(Environment.DIRECTORY_PICTURES)}/tempImg.png")
var requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file)
var body = MultipartBody.Part.createFormData("file", file.name, requestFile)
우선은 보낼 데이터인 body를 만들도록 하자.
file은 우리가 업로드할 이미지 파일을 불러오면 되겠다.
그리고 이 파일에 MediaType.parse로 타입을 붙여서 RequestFile을 만들어주고,
이 파일로 실제로 우리가 보내게 될 데이터인 body를 만들어주면 된다.
Singleton.server.postPhoto(body).enqueue(object : Callback<ResponseDC> {
override fun onResponse(call: Call<ResponseDC>, response: Response<ResponseDC>) {
Toast.makeText(context, "등록되었습니다.", Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<ResponseDC>, t: Throwable) {
Toast.makeText(context, "실패했습니다.", Toast.LENGTH_SHORT).show()
finish()
}
})
이 데이터를 위에서 작성한 함수를 통해 서버로 보내주게되는데, 본인은 Singleton오브젝트를 따로 정의해서
이곳에 retrofit을 정의해놓고 있어서 위와 같은 코드를 작성했다.
서버에 이미지를 보내고, 서버에서 이미지를 처리한 뒤 그 결과를 다시 클라이언트에 보내줌으로써
이미지가 잘 업로드되었는지, 업로드가 잘 안되었는지 확인할 수 있다.
다음은 서버쪽 코드를 살펴보도록 하자.
일단, 클라이언트에서 받은 이미지를 파일로 저장하기 위해서 이 글에서는 multer를 사용할 것이다.
var _storage = multer.diskStorage({
destination: 'uploads/',
filename: function(req, file, cb) {
return crypto.pseudoRandomBytes(16, function(err, raw) {
if(err) {
return cb(err);
}
return cb(null, file.originalname);
});
}
});
app.post("/db/postImg", multer({storage: _storage}).single('file'), (req, res) => {
try {
let file = req.file;
let originalName = '';
let fileName = '';
let mimeType = '';
let size = 0;
if(file) {
originalName = file.originalname;
filename = file.fileName
mimeType = file.mimetype;
size = file.size;
} else{
}
} catch (err) {
console.dir(err.stack);
}
let imgData = readImageFile(`./uploads/tempImg.png`)
let sql = `INSERT INTO tmpTable(img) VALUES(?);`
db.query(sql, [imgData], (err, rows, fields) => {
if(err === null){
console.log("성공")
res.redirect("/uploads/" + req.file.originalname);
}else{
console.log(String(err))
res.send("실패")
}
})
})
multer로 파일을 저장하게되면 우리가 클라이언트에서 저장한 파일 이름으로 저장이된다.
위 코드에선 이 파일을 nodejs의 ./uplodas/에 저장했다.
이렇게 하면 간단하게 nodejs에서 이미지를 받아서 파일로 저장할 수 있다.
다음은 이를 mysql서버에 blob형태로 저장해보자.
그러려면 일단 이미지를 불러와주어야 한다.
이때는 아래의 함수를 사용한다.
function readImageFile(file){
const bitmap = fs.readFileSync(file);
const buf = new Buffer.from(bitmap)
return buf
}
readImageFile함수는 parameter로 우리가 불러올 이미지 파일이 경로를 받는다.
이를 readFile로 읽어온 뒤, Buffer를 뽑아서 반환해주도록 한다.
그러면 이렇게 받은 값을 mysql서버에 저장해주면 된다.
이 작업 중에 오류가 발생하면 클라이언트쪽에 이미지 저장이 실패했다고 알리는 메시지를 전송하고,
정상적으로 완료되었으면 이에대한 메시지를 전송해주도록 한다.
이렇게 이미지를 받아서 파일로 저장하고, DB에도 저장해봤으니
다음으론 이걸 클라이언트에서 다시 받아보도록 하자.
@GET("/db/getImg")
fun getInfo(): Call<ResponseInfo>
먼저 retrofit쪽에서 get함수를 하나 정의해주도록 하자.
Singleton.server.getInfo().enqueue(object : Callback<ResponseInfo> {
override fun onResponse(call: Call<ResponseInfo>, response: Response<ResponseInfo>) {
Log.e("이미지 받기", "성공")
var res = response.body()!!.result as List<Map<*, *>>
getImg(res)
}
override fun onFailure(call: Call<ResponseInfo>, t: Throwable) {
Log.e("이미지 받기", "실패")
}
})
app.get("/db/getImg", (req, res) => {
db.query(`SELECT img FROM tmpTable`, (err, rows, fields) => {
if(err === null){
res.send({"result":rows})
}else{
res.send("실패")
console.log(String(err))
}
})
})
위와 같은 코드로 mysql쿼리를 통해 이미지 데이터들을 받아올 수 있는데,
이때 주의할 점은 받아온 데이터의 자료형을 알맞게 casting해주어야 한다는 점이다.
실제로 mysql쿼리를 통해서 데이터를 안드로이드에서 받을 경우 이 데이터는 Map데이터를 저장하는 List형태로 들어오게된다.
이 List에서 원하는 행을 뽑고, 그 행에서 원하는 열의 이름(현재는 photo)를 뽑아서 사용할 수 있다.
여기서 우리가 원하는 이미지 파일을 하나 뽑아보도록 하자.
private fun getImg(input: List<Map<*, *>){
var data = (input[0])["img"] as LinkedTreeMap<*, *>
var array = data["data"] as ArrayList<Double>
var bitmap: Bitmap = convertBitmap(array)
}
getImg함수는 위와같이 구성했다.
먼저 전체 데이터인 List<Map<*, *>형태의 input을 parameter로 받는다.
여기서 가장 첫 번째 행의 데이터를 뽑아볼텐데, input[0]["img"]과 같이 첫 번째 행의 img열 데이터를 받아온다.
이 데이터는 LinkedTreeMap<*, *>형태로 casting해주면 되겠다.
다음으로 이 data에서 실제 이미지 값들을 뽑아야되는데, data["data"]라고 작성해서 이를 뽑아낼 수 있다.
이 데이터는 다시 ArrayList<Double>로 casting해주고, 이 데이터를 가지고 비트맵을 만들어주도록 한다.
fun exByte(list: ArrayList<Double>): ByteArray {
var list2: MutableList<Byte> = mutableListOf()
for (i in 0..list.size - 1) {
list2.add(list[i].toInt().toByte())
}
var arr = list2.toByteArray()
return arr
}
fun convertBitmap(input: ArrayList<Double>): Bitmap {
var arr = exByte(input)
try {
var bitmap = BitmapFactory.decodeByteArray(arr, 0, arr.size)
return bitmap
} catch (e: Exception) {
throw RuntimeException(e)
}
}
ArrayList를 받아서 bitmap으로 바꿔줄텐데, 이 bitmap은 ByteArray로 만들것이다.
이를 위해선 ArrayList를 ByteArray로 바꿔주어야 하는데, ArrayList는 toByteArray()를 사용할 수가 없다.
그래서 보기는 안좋지만 exByte함수를 사용해서 ArrayList의 각 요소를 뽑아내서 새로운 list2를 만들었다.
그리고 이 list2에 toByteArray()를 사용해서 우리가 원하는 데이터를 만들어낼 수 있다.
이렇게 만든 ByteArray를 BitmapFactory를 통해서 bitmap으로 바꿔준다.
이렇게 이미지를 업로드하고, 파일로 저장하고, mysql에 저장한 뒤, 이를 다시 안드로이드에서 받아오는 방법에 대해 알아봤다.
관련 자료를 찾기가 어려워서 해맨 부분이 많았던 내용이다.
보통은 DB에 이미지 파일의 경로를 저장하고 이를 불러오는 형태로 사용한다고 한다.
'안드로이드 > 개발관련(Kotlin)' 카테고리의 다른 글
GEOcoder 사용 시 java.io.IOException: grpc failed오류 해결 (0) | 2023.02.27 |
---|---|
안드로이드에서 AES256알고리즘을 사용하여 암/복호화 (0) | 2023.02.27 |
안드로이드에서 Zxing을 사용하여 QR코드 생성/스캔 (0) | 2023.02.27 |
안드로이드 AlarmManager로 정해진 시간에 작업 수행(Kotlin) (0) | 2023.02.27 |
안드로이드에서 공용키 암호화(RSA) 사용하기(Kotlin) (0) | 2023.02.27 |