Seize the day

SEARCH RESAULT : 글 검색 결과 - 전체 글 (총 474개)

POST : Backend study

GCP -> Oracle 클라우드로 Mongo db 이전 스터디

구글 클라우드에서 운영중인 독푸딩 서비스가 있다.  도커를 모를 때라 서버에 필요한 프로그램을 한땀한땀 스크립트를 찾아가면서 설치를 했다. 오라클 클라우드로 API 서버와 DB서버를 이전하면서 도커를 이용했다.  도커만 설치되어 있다면 서버 프로그램을 간단히 실행하고 종료시킬 수 있게 되었다.  

GCP 접속하여 Mongo 백업

# ssh 접속
dajkim76@Kims-Mac-mini ~/backend/gcp % ssh -i gcp.key 34.170.xxx.yyy
Enter passphrase for key 'gcp.key':

# mongo 백업
dajkim76@base-backend-1:~$ mongodump --db prod-db --out mongo_221122

# 폴더 압축
dajkim76@base-backend-1:~$ tar cvzf mongo_221122.tar.gz mongo_221122/

dajkim76@base-backend-1:~$ ls -al mongo_221122.tar.gz
-rw-rw-r-- 1 dajkim76 dajkim76 288717 Nov 21 15:45 mongo_221122.tar.gz

 

압축파일 가져오기

dajkim76@Kims-Mac-mini ~/backend/gcp % sftp -i gcp.key 34.170.xxx.yyy
Enter passphrase for key 'gcp.key':
Connected to 34.170.xxx.yyy.
sftp> get mongo_221122.tar.gz
Fetching /home/dajkim76/mongo_221122.tar.gz to mongo_221122.tar.gz
/home/dajkim76/mongo_221122.tar.gz                                                                                  100%  282KB 280.5KB/s   00:01
sftp> exit

 

압축파일 오라클 vm에 업로드

dajkim76@Kims-Mac-mini ~/backend/oci % sftp -i oracle.key opc@132.145.xxx.yyy
Enter passphrase for key 'oracle.key':
Connected to 132.145.xxx.yyy.
sftp> put mongo_221122.tar.gz
Uploading mongo_221122.tar.gz to /home/opc/mongo_221122.tar.gz
mongo_221122.tar.gz                                                                                                 100%  282KB   2.8MB/s   00:00
sftp> exit

 

압축풀기

[opc@instance-20221029-2034 ~]$ tar xvzf mongo_221122.tar.gz

 

mongo 도커 컨테이너에 접속하여 db 복구하기

# 컨테이너 접속 
docker exec -it docker_mongo_1 bash

# Mongo 복구: 호스트의 mongo_221122 디렉토리의 상위 폴더를 volumes에 바인딩시킬것
mongorestore -u root -p example ./mongo_221122

# 몽고 쉘 접속
mongosh -u root -p example

# 프로덕션 db에 유저 생성
use prod-db
db.createUser( { user: "prod-user", pwd: "prod-pwd", roles: [ { role: "readWrite", db: "prod-db" }, ] } )

 

백엔드 도커 파일 최종 정리

dev.env 개발환경변수

MONGO_ROOT_PASSWORD=example
MONGO_STORAGE=~/mongo-storage

docker-compose-mongo.yml

version: '3.1'

services:

  mongo:
    image: mongo:6.0.2
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
    volumes:
      - ${MONGO_STORAGE}:/data/db
      - ./:/app
    working_dir: /app
    hostname: mongo-host
    ports:
      - 27017:27017
    networks:
      - backend


networks:
  backend:
    driver: bridge
    external: true

start-mongo.sh

docker-compose -f docker-compose-mongo.yml --env-file $1.env up -d

mongo 서버 실행

start-mongo.sh dev

서비스 운영 환경이라면 prod.env 파일을 만들어서 start-mongo.sh prod

서버 종료는

docker-compose -f docker-compose-mongo.yml stop

 

 

참고 사이트

https://nota.tistory.com/53

https://velog.io/@killi8n/Mongodb-%EB%8D%A4%ED%94%84%ED%95%98%EA%B3%A0-%EB%B0%B1%EC%97%85%ED%95%98%EA%B8%B0-gejmop2baz

https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-archive.html

top

posted at

2022. 11. 22. 01:29


POST : Backend study

[docker study] load balancer -> nginx -> deno -> mongo 까지 연결 테스트

[Backend study] - [docker study] 오라클 로드밸런서 <--> nginx <--> deno server

이전에 오라클 클라우드에서 로드 밸런서에서 deno 웹 서버까지 연결 테스트 했다. mongo를 docker 켄테이너로 설치하고 deno 서버에서 mongo 서버로 connect까지 해 본다. 

50GB volume을 /mnt/djkim 에 마운트했다.

이미 오라클 vm에 자체 block volume을 붙여놓았다. 50GB 용량을 가지고 있고, 이것은 어떤 VM에도 attach될 수 있다. 나중에 vm이 업그레이드 될 경우에도 block volume을 붙이면 데이타 그대로 유지된다. 현재는 mongo db의 저장소로 사용할 예정이다. 

자세한 내용은  https://thekoguryo.github.io/oci/chapter03/6/3/ 를 참고했다.   

[연결할 명령어는 오래클 클라우드 vm의 볼륨 페이지의 ... 버튼에 있음]

[opc@instance-20221029-2034 ~]$ sudo iscsiadm -m node -o new -T iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e -p 169.254.2.2:3260
New iSCSI node [tcp:[hw=,ip=,net_if=,iscsi_if=default] 169.254.2.2,3260,-1 iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e] added
[opc@instance-20221029-2034 ~]$ sudo iscsiadm -m node -o update -T iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e -n node.startup -v automatic
[opc@instance-20221029-2034 ~]$ sudo iscsiadm -m node -T iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e -p 169.254.2.2:3260 -l
Logging in to [iface: default, target: iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e, portal: 169.254.2.2,3260]
Login to [iface: default, target: iqn.2015-12.com.oracleiaas:46c16679-d0e5-4258-bb65-24260665245e, portal: 169.254.2.2,3260] successful.



[Disk /dev/sdb: 50 GiB, 확인]
[opc@instance-20221029-2034 ~]$ sudo fdisk -l
Disk /dev/sda: 46.6 GiB, 50010783744 bytes, 97677312 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 1048576 bytes
Disklabel type: gpt
Disk identifier: 347FC70E-7421-4C2C-82F1-CCEF19B84F66

Device       Start      End  Sectors  Size Type
/dev/sda1     2048   206847   204800  100M EFI System
/dev/sda2   206848  2303999  2097152    1G Linux filesystem
/dev/sda3  2304000 97675263 95371264 45.5G Linux LVM


Disk /dev/mapper/ocivolume-root: 35.5 GiB, 38088474624 bytes, 74391552 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 1048576 bytes


Disk /dev/mapper/ocivolume-oled: 10 GiB, 10737418240 bytes, 20971520 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 1048576 bytes


Disk /dev/sdb: 50 GiB, 53687091200 bytes, 104857600 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 1048576 bytes




[디스크 포맷할 파일포맷 확인]
[opc@instance-20221029-2034 ~]$ lsblk -f
NAME               FSTYPE      LABEL UUID                                   MOUNTPOINT
sda
|-sda1             vfat              3A04-27E1                              /boot/efi
|-sda2             xfs               92f46bb4-0609-4149-8155-521cb271344d   /boot
`-sda3             LVM2_member       eu3JyT-prH1-YQe4-5zOP-LDd1-fcTk-8XGRod
  |-ocivolume-root xfs               a2c46b56-da9f-4829-be87-1d87b18c8aeb   /
  `-ocivolume-oled xfs               f9d1a191-470c-4bb2-ad65-58f895357a5d   /var/oled
sdb



[포맷]
[opc@instance-20221029-2034 ~]$ sudo mkfs -t xfs /dev/sdb
meta-data=/dev/sdb               isize=512    agcount=4, agsize=3276800 blks
         =                       sectsz=4096  attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=0
         =                       reflink=1
data     =                       bsize=4096   blocks=13107200, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
log      =internal log           bsize=4096   blocks=25600, version=2
         =                       sectsz=4096  sunit=1 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0


[포맷 확인]
[opc@instance-20221029-2034 ~]$ lsblk -f
NAME               FSTYPE      LABEL UUID                                   MOUNTPOINT
sda
|-sda1             vfat              3A04-27E1                              /boot/efi
|-sda2             xfs               92f46bb4-0609-4149-8155-521cb271344d   /boot
`-sda3             LVM2_member       eu3JyT-prH1-YQe4-5zOP-LDd1-fcTk-8XGRod
  |-ocivolume-root xfs               a2c46b56-da9f-4829-be87-1d87b18c8aeb   /
  `-ocivolume-oled xfs               f9d1a191-470c-4bb2-ad65-58f895357a5d   /var/oled
sdb                xfs               4a4f9fdc-1111-43ed-90a6-edc3d552aa08



[마운트할 디렉토리 생성]
[opc@instance-20221029-2034 ~]$ sudo mkdir /mnt/djkim



[/etc/fstab에 마지막 한 줄 추가]
opc@instance-20221029-2034 ~]$ sudo vi /etc/fstab 
/dev/oracleoci/oraclevdb /mnt/djkim xfs defaults,_netdev,nofail 0 2



[마운트하기]
[opc@instance-20221029-2034 ~]$ sudo mount -a



[디렉토리 확인]
[opc@instance-20221029-2034 djkim]$ ls -al /mnt/djkim
total 0
drwxr-xr-x. 2 root root  6 Nov 17 15:08 .
drwxr-xr-x. 3 root root 19 Nov 17 15:14 ..

 

mongo  서버 구동

mongo/docker-compose.yml

version: '3.1'

services:

  mongo:
    image: mongo:6.0.2
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      - /mnt/djkim/mongo-storage:/data/db
      - ./:/app
    working_dir: /app
    hostname: mongo-host
    ports:
      - 27017:27017
    networks:
      - backend


networks:
  backend:
    driver: bridge
    external: true

내가 붙인 50GB에 /mnt/djkim/mongo-storage에 mongo 서버 데이타 디렉토리로 지정한다.  호스트 이름은 mongo-host 로 지정한다.  현재 디렉토리도 /app 으로 연결한다.  

실행과 확인

[opc@instance-20221029-2034 mongo]$ docker-compose up -d
Recreating mongo_mongo_1 ... done
[opc@instance-20221029-2034 mongo]$ sudo docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS         PORTS                                           NAMES
eccd20973e1f   mongo:6.0.2            "docker-entrypoint.s…"   10 seconds ago   Up 6 seconds   0.0.0.0:27017->27017/tcp, :::27017->27017/tcp   mongo_mongo_1
c9a0b542887a   nginx:1.21.6           "/docker-entrypoint.…"   2 days ago       Up 2 days      0.0.0.0:80->80/tcp, :::80->80/tcp               nginx_nginx_1
42b92c017ed8   denoland/deno:1.28.0   "/tini -- docker-ent…"   2 days ago       Up 2 days      0.0.0.0:8080->8080/tcp, :::8080->8080/tcp       deno_service_deno_1
[opc@instance-20221029-2034 mongo]$ sudo docker exec -it mongo_mongo_1 bash
root@mongo-host:/app# pwd
/app

 

deno 서버에서 Mongo 연결

이전 포스트의 deno_service/app.ts 수정.. 딱히 하는 일은 없고 db 서버에 연결만 해본다. 

import { serve } from "https://deno.land/std@0.164.0/http/server.ts";
import { Bson, MongoClient } from "https://deno.land/x/mongo/mod.ts"

class MyMongoClient {
    dbName: string
    url: string
    client: MongoClient

    constructor(dbName: string, url: string) {
        this.dbName = dbName;
        this.url = url;
        this.client = {} as MongoClient;
    }

    async connect() {
        console.log("mongodb connecting ...")
        const client = new MongoClient();
        await client.connect(this.url);
        this.client = client;
        console.log("mongodb connected OK")
    }

    getDatabase() {
        return this.client.database(this.dbName);
    }
}


export const mongoClient = new MyMongoClient("test-db", "mongodb://root:example@mongo-host:27017")
await mongoClient.connect()


const port = 8080;

const handler = (request: Request): Response => {
  const body = `Your user-agent is:\n\n${
    request.headers.get("user-agent") ?? "Unknown"
  }`;

  return new Response(body, { status: 200 });
};

console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
await serve(handler, { port });

실행 확인

[opc@instance-20221029-2034 deno_service]$ docker-compose up -d
Starting deno_service_deno_1 ... done
[opc@instance-20221029-2034 deno_service]$ sudo docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                                           NAMES
eccd20973e1f   mongo:6.0.2            "docker-entrypoint.s…"   19 minutes ago   Up 18 minutes   0.0.0.0:27017->27017/tcp, :::27017->27017/tcp   mongo_mongo_1
c9a0b542887a   nginx:1.21.6           "/docker-entrypoint.…"   2 days ago       Up 2 days       0.0.0.0:80->80/tcp, :::80->80/tcp               nginx_nginx_1
42b92c017ed8   denoland/deno:1.28.0   "/tini -- docker-ent…"   2 days ago       Up 12 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp       deno_service_deno_1

[opc@instance-20221029-2034 deno_service]$ sudo docker logs --tail 10 deno_service_deno_1
Download https://deno.land/x/web_bson@v0.2.5/src/validate_utf8.ts
Download https://deno.land/x/web_bson@v0.2.5/src/float_parser.ts
Download https://deno.land/x/mongo@v0.31.1/src/utils/saslprep/deps.ts
Download https://deno.land/x/mongo@v0.31.1/src/utils/saslprep/load_code_points.ts
Download https://deno.land/x/mongo@v0.31.1/src/utils/saslprep/sparse_bitfield.ts
Download https://deno.land/x/mongo@v0.31.1/src/utils/saslprep/memory_pager.ts
mongodb connecting ...
mongodb connected OK
HTTP webserver running. Access it at: http://localhost:8080/
Listening on http://localhost:8080/

mongodb connected OK가 찍힌걸로 봐서 연결까지 된 듯 하다. 

mongodb://root:example@mongo-host:27017 로 연결은 된 듯 하지만 실제로 읽기나 쓰기를 하면 auth 에러가 날 것이다.  test-db에 권한이 없다고 나올 텐데 권한을 추가하자..  https://dajkim76.tistory.com/560 참고

[opc@instance-20221029-2034 deno_service]$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                                           NAMES
eccd20973e1f   mongo:6.0.2            "docker-entrypoint.s…"   46 minutes ago   Up 46 minutes   0.0.0.0:27017->27017/tcp, :::27017->27017/tcp   mongo_mongo_1
c9a0b542887a   nginx:1.21.6           "/docker-entrypoint.…"   2 days ago       Up 2 days       0.0.0.0:80->80/tcp, :::80->80/tcp               nginx_nginx_1
42b92c017ed8   denoland/deno:1.28.0   "/tini -- docker-ent…"   2 days ago       Up 27 minutes   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp       deno_service_deno_1
[opc@instance-20221029-2034 deno_service]$ docker exec -it mongo_mongo_1 bash

root@mongo-host:/app# mongosh -u root -p example
Current Mongosh Log ID:	63765dee3755cc7187c2c247
Connecting to:		mongodb://<credentials>@127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.6.0
Using MongoDB:		6.0.2
Using Mongosh:		1.6.0

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

------
   The server generated these startup warnings when booting
   2022-11-17T15:25:14.257+00:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
   2022-11-17T15:25:14.257+00:00: vm.max_map_count is too low
------

------
   Enable MongoDB's free cloud-based monitoring service, which will then receive and display
   metrics about your deployment (disk utilization, CPU, operation statistics, etc).

   The monitoring data will be available on a MongoDB website with a unique URL accessible to you
   and anyone you share the URL with. MongoDB may use this information to make product
   improvements and to suggest MongoDB products and deployment options to you.

   To enable free monitoring, run the following command: db.enableFreeMonitoring()
   To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
------

test> use test-db
switched to db test-db
test-db> db.createUser( { user: "testuser", pwd: "testpwd", roles: [ { role: "readWrite", db: "test-db" }, ] } )
{ ok: 1 }
test-db>

이제 mongodb://testuser:testpwd@mongo-host:27017 로 다시 연결을 만들고 read/write를 해보면 문제 없다.  테스트 해보지는 않았지만 아마도 될 듯.

테스트해 보자.. mongosh로 데이타를  하나 넣었다..

test> use test-db
switched to db test-db
test-db> db.user_list.insertOne({name:"name1", age:31})
{
  acknowledged: true,
  insertedId: ObjectId("63766d72b310e7e91006854b")
}
test-db>

app.ts 다시 수정. 쓸데없는 코드 다 빼고 핵심만 보이도록 수정했다. https://deno.land/x/mongo@v0.31.1/tests/cases/02_connect.ts?source. 참고

import { serve } from "https://deno.land/std@0.164.0/http/server.ts";
import { Bson, MongoClient } from "https://deno.land/x/mongo/mod.ts"

export const mongoClient = new MongoClient();
await mongoClient.connect("mongodb://testuser:testpwd@mongo-host:27017/test-db")

interface UserSchema {
  _id: ObjectId;
  name: string;
  age: number;
}


const userCollection = mongoClient.database().collection<UserSchema>("user_list");
const cursor = userCollection.find();
const usersResult = await cursor.toArray();
const json = JSON.stringify(usersResult);
console.log(json);


const port = 8080;

const handler = (request: Request): Response => {
  const body = `Your user-agent is:\n${json}\n${
    request.headers.get("user-agent") ?? "Unknown"
  }`;

  return new Response(body, { status: 200 });
};

console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
await serve(handler, { port });

실행
[opc@instance-20221029-2034 deno_service]$ docker-compose up -d
Starting deno_service_deno_1 ... done


결과

dajkim76@Kims-Mac-mini ~ %  curl https://oci.mdiwebma.com/deno-app
Your user-agent is:
[{"_id":"63766d72b310e7e91006854b","name":"name1","age":31}]
curl/7.79.1

https://oci.mdiwebma.com/deno-app. 누르면 오라클 클라우드 로드밸랜서 -> VM Nginx -> Deno app 서버 -> mongo db 순으로 요청 흐름이 발생하고 test-db의 user_list 콜렉션을 전부 가져와서 응답한다.  nginx, deno, mongo는 모두 docker container로 동작한다. 

top

posted at

2022. 11. 18. 01:04


POST : Android Dev Study

ShareActionProvider의 문제점과 자체 구현으로 해결하기

https://developer.android.com/reference/android/widget/ShareActionProvider 라는 좋은 기능이 있는데 아래 그림 처럼 최근에 선택한 앱을 우측에 표시해 줘서 매번 앱을 다시 선택하는 번거로움을 없애준다. 하지만 이것은 Android 12에서 치명적인 문제가 있는데 보안 때문에 폰에 어떤 앱이 설치되었있는지 조회할 수 없어서 메신저와 같은 주요 앱이 표시되지 않는다. 따라서 사실상 Android 12이상에서는 사용할 수 없는 기능이 되었다. 

 

따라서 이것을 자체적으로 구현했다.  버튼이 많고 타이틀에도 중요한 정보가 표시되어야 해서 요소간 간격을 줄일 필요가 있다. 

자체적으로 구현한 모습이다.

 

툴바를 커스터마이징을 해야해서 NoActionBar theme를 사용해야한다. 

values/styles.xml

    <style name="PhotoViewerActionButton" parent="Widget.AppCompat.ActionButton">
        <item name="android:minWidth">0dp</item>
        <item name="android:paddingStart">8dp</item>
        <item name="android:paddingEnd">8dp</item>
    </style>

    <style name="AppTheme.NoActionBar.PhotoViewer">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:actionButtonStyle">@style/PhotoViewerActionButton</item>
    </style>
    
    <style name="ToolbarPopupTheme" parent="@style/ThemeOverlay.AppCompat.Light" />

actionButtonStyle을 수정해서 메뉴 버튼의 간격도 더 좁게 조정했다. 

values-night/styles.xml 다크 모드일 때 색 대응

    <style name="ToolbarPopupTheme" parent="@style/ThemeOverlay.AppCompat.Dark" />

PhotoViewerActivity의 theme를 변경하기위해서 AndroidManifest.xml 수정

            android:theme="@style/AppTheme.NoActionBar.PhotoViewer">

 

layout/photo_viewer.xml 에 커스텀 툴바 추가

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutDirection="ltr"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:minHeight="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:contentInsetLeft="0dp"
        app:contentInsetStart="0dp"
        app:contentInsetStartWithNavigation="0dp"
        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ToolbarPopupTheme" />

contentInsetLeft 등을 조정해서 Up 버튼과 타이틀의 간격을 줄였다. 

Activity#onCreate에서 Toolbar 초기화

    private void setCustomToolbar() {
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

 

ShareActionProvider 자체구현하기

menu바에 올라가는 두 개의 버튼이 표시되는 layout/menu_viewer_share.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:clipToPadding="false"
    android:focusable="true">

    <ImageView
        android:id="@+id/share_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:tooltipText="@string/share_image"
        android:paddingStart="5dp"
        android:paddingEnd="5dp"
        android:paddingTop="8dp"
        android:paddingBottom="8dp"
        android:layout_gravity="center_vertical"
        android:src="@drawable/ic_share_variant_white_24dp" />

    <FrameLayout
        android:id="@+id/recent_share_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tooltipText="@string/share_image_using"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:layout_gravity="center_vertical"
        android:visibility="gone"
        android:paddingStart="5dp"
        android:paddingEnd="5dp"
        android:paddingTop="8dp"
        android:paddingBottom="8dp">

        <ImageView
            android:id="@+id/recent_share_image"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:layout_gravity="center"
            android:src="@drawable/ic_export_variant_white_24dp" />
    </FrameLayout>
</LinearLayout>

배경 라운드 선을 표시할 drawbale/menu_viewer_share_bg.xml

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/transparent"/>
    <stroke android:width="1dp" android:color="#3fffffff" />
    <corners android:radius="4dp"/>
    <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

Activity의 menu/menu_viewer.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_item_share"
        app:actionLayout="@layout/menu_viewer_share"
        android:title="@string/share_image"
        app:showAsAction="always" />

PhotoViewerActivity 수정

    private MenuItem shareMenuItem;
    private View shareActionView;
    private View recentShareButton;
    private ImageView recentShareImageView;
    private String lastRecentShareData;
...


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_viewer, menu);
        shareMenuItem = menu.findItem(R.id.menu_item_share);
        shareActionView = shareMenuItem.getActionView();
        if (shareActionView != null) {
            shareActionView.findViewById(R.id.share_button).setOnClickListener(v -> onClickShare(null));
            recentShareButton = shareActionView.findViewById(R.id.recent_share_button);
            recentShareButton.setOnClickListener(v -> onClickRecentShare());
            recentShareImageView = shareActionView.findViewById(R.id.recent_share_image);
            updateRecentShareIcon();
        }
        return super.onCreateOptionsMenu(menu);
    }

    private void updateRecentShareIcon() {
        if (shareActionView == null || TextUtils.isEmpty(path)) {
            return;
        }
        String recentShareData = getRecentShareData();
        if (TextUtils.isEmpty(recentShareData)) {
            lastRecentShareData = null;
            shareActionView.setBackground(null);
            recentShareButton.setVisibility(View.GONE);
            return;
        }
        if (TextUtils.equals(lastRecentShareData, recentShareData)) {
            return;
        }

        lastRecentShareData = recentShareData;
        ComponentName componentName = ComponentName.unflattenFromString(recentShareData);
        if (componentName != null) {
            shareActionView.setBackgroundResource(R.drawable.menu_viewer_share_bg);
            recentShareButton.setVisibility(View.VISIBLE);
            Drawable icon = getActivityIcon(this, componentName.getPackageName(), componentName.getClassName());
            if (icon != null) {
                recentShareImageView.setImageDrawable(icon);
            } else {
                recentShareImageView.setImageResource(R.drawable.ic_export_variant_white_24dp);
            }
        } else {
            lastRecentShareData = null;
            shareActionView.setBackground(null);
            recentShareButton.setVisibility(View.GONE);
        }
    }

    @Nullable
    private Drawable getActivityIcon(Context context, String packageName, String activityName) {
        try {
            PackageManager packageManager = context.getPackageManager();
            Intent intent = new Intent();
            intent.setComponent(new ComponentName(packageName, activityName));
            ResolveInfo resolveInfo = packageManager.resolveActivity(intent, 0);
            return resolveInfo.loadIcon(packageManager);
        } catch (Exception ex) {
            return null;
        }
    }
    
    private String getRecentShareData() {
        if (isVideo()) {
            return Settings.recentShareVideoIntent.getValue();
        } else {
            return Settings.recentShareImageIntent.getValue();
        }
    }

updateRecentShareIcon()은 사진 혹은 Video 정보가 바뀔 때 마다 호출해 준다.

공유 버튼 누를 경우 

Intent intent = createShareIntent();
PendingIntent pendingIntent = PendingIntent.getBroadcast(
                            context,
                            0,
                            new Intent(context, IntentChooserReceiver.class).putExtra("isVideo", isVideo()),
                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
                    startActivity(Intent.createChooser(intent, context.getString(R.string.share_image_using), pendingIntent.getIntentSender()));

IntentChooserReceiver.kt 에서 선택한 앱 정보를 받는다

Settings.recentShareVideoIntent 와 Settings.recentShareImageIntent 정보 변경하기

class IntentChooserReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val clickedComponent: ComponentName = intent.getParcelableExtra(EXTRA_CHOSEN_COMPONENT) ?: return
        val isVideo = intent.getBooleanExtra("isVideo", false)
        if (isVideo)
            Settings.recentShareVideoIntent.value = clickedComponent.flattenToString()
        else
            Settings.recentShareImageIntent.value = clickedComponent.flattenToString()
    }
}

공유버튼을 눌러서 새로운 앱을 선택한 경우 바로 앱 아이콘을 바로 업데이트하기위해서

    @Override
    protected void onResume() {
        super.onResume();

        updateRecentShareIcon();
    }

최근 공유한 버튼을 누른 경우

 String data = getRecentShareData();
 if (!TextUtils.isEmpty(data)) {
     ComponentName componentName = ComponentName.unflattenFromString(data)
     try {
          Intent intent = createShareIntent();
          intent.setComponent(componentName); // target activity
          startActivity(intent);
     } catch (ActivityNotFoundException ex) {
          // Maybe the app uninstalled
          retry = true;
     }
}

 

AndroidManifest에 주요 앱을 작성해 주어야 한다. 그래야 아이콘이 제대로 표시되고 그 외는 기본 아이콘으로 표시된다.

    <queries>
        <package android:name="com.whatsapp" />
        <package android:name="com.facebook.orca" />
        <package android:name="com.skype.raider" />
        <package android:name="org.telegram.messenger" />
        <package android:name="com.snapchat.android" />
        <package android:name="org.thoughtcrime.securesms" />
        <package android:name="com.discord" />
        <package android:name="com.Slack" />
        <package android:name="com.viber.voip" />
        <package android:name="kik.android" />
        <package android:name="com.google.android.talk" />
        <package android:name="com.tencent.mm" />
        <package android:name="com.kakao.talk" />
        <package android:name="com.tencent.mobileqq" />
        <package android:name="jp.naver.line.android" />
    </queries>
</manifest>
top

posted at

2022. 11. 17. 22:46


POST : Backend study

[docker study] 오라클 로드밸런서 <--> nginx <--> deno server

[Backend study] - 오라클 클라우드 로드 밸런서 스터디

지난번 스터디에서 오라클 클라우드 로드밸런서에서 백엔드의 Nginx까지 연결에 성공했다. 

오라클 클라우드에서 로드밸런서 ---> Nginx ---->  deno 서버까지 연결을 테스트한다.   (Nginx와 deno 서버는 하나의 vm에 있다)

간단한 deno 앱 서버

deno_service/app.ts

import { serve } from "https://deno.land/std@0.164.0/http/server.ts";

const port = 8080;

const handler = (request: Request): Response => {
  const body = `Your user-agent is:\n\n${
    request.headers.get("user-agent") ?? "Unknown"
  }`;

  return new Response(body, { status: 200 });
};

console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
await serve(handler, { port });

 

docker Network 가 없다면 생성한다.

sudo docker network create backend

 

Deno 서버 구성, 실행

deno_service/docker-compose.yml

version: "3.0"
services:

  deno:
    image: denoland/deno:1.28.0
    restart: always
    volumes:
      - ./:/app
    hostname: deno-app
    ports:
      - 8080:8080
    networks:
      - backend
    working_dir: /app
    command: run --allow-net /app/app.ts

networks:
  backend:
    driver: bridge
    external: true

 실행 

[opc@instance-20221029-2034 deno_service]$ docker-compose up -d
Pulling deno (denoland/deno:1.28.0)...
Creating deno_service_deno_1 ... done

[opc@instance-20221029-2034 deno_service]$ curl localhost:8080
Your user-agent is:

curl/7.61.1

 

deno-app 호스트의 8080가 열려있기 때문에 nginx의 conf를 수정해서 연결한다.

nginx의 conf 확인
https://hub.docker.com/r/denoland/deno  를 보면 conf파일의 경로는 /etc/nginx/conf.d/default.conf  임을 알 수 있다. 

[opc@instance-20221029-2034 nginx]$ sudo docker exec -it nginx_nginx_1 bash
root@39a89ec53c9b:/# cat /etc/nginx/conf.d/default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

 이 파일에서 proxy부분만 수정해서 호스트 pc에 default.conf 생성한다. 
vi default.conf

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    location /deno-app {
        proxy_pass   http://deno-app:8080;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

location /deno-app { 부문만 수정했다. deno-app 호스트는 deno_service/docker-compose.yml에 정의된 호스트 이름이고 8080 포트로 리다이렉트 시킨다.

nginx/docker-compose.yml

services:
  nginx:
    image: nginx:1.21.6
    ports:
      - '80:80'
    networks:
      - backend
    volumes:
      - ./default.conf:/etc/nginx/conf.d/default.conf

networks:
  backend:
    driver: bridge
    external: true

default.conf 파일을 우리가 수정한 파일로 덮어쓴다.
nginx 서버 시작  docker-compose up -d

동작 확인 맥미니에서 오라클 서비스 접속

dajkim76@Kims-Mac-mini ~ % curl https://oci.mdiwebma.com/deno-app
Your user-agent is:

curl/7.79.1

https://oci.mdiwebma.com/deno-app  크롬으로 열어도 잘 연결된다.. ^^

 

top

posted at

2022. 11. 15. 21:08


POST : Backend study

오라클 클라우드 로드 밸런서 스터디

A. 로드 밸런서에 사용할 고정 IP 만들기

Networking > IP Management > Reserved Public IPs 에서 생성..

B. 로드 밸런서에 사용할 무료 SSL 인증서 받기

도메인이 있기 때문에  https://www.sslforfree.com/  에서 무료로 90일 짜리 인증서를 받아보자.

C. 로드 밸랜서 생성하기

Networking > Load Balancers

1단계 Reserved IP Address를 선택한다.

2단계 Choose Backends

이미 만들어진 Backend를 추가한다. 내부 웹서버인 Backend는 SSL을 사용하지 않으므로 Use SSL은 언체크한다. 

3단계 Configure Listener

Http 80 포트는 사용하지 않을 예정이다.   HTTPS를 선택하고 sslforfree.com에서 받은 인증서를 추가한다.
다운받은 certificate.crt  ca_bundle.crt private.key 순으로 각각 파일 선택하여 업로드한다. 

4단계 Manage Logging

기본값 사용

D.  sslforfree.com에서 설정한 domain과 로드밸런서의 고정 IP 연결하기 

cloudflare.com에서 DNS 서비스를 무료로 받고 있다.  연결한다. 

연결하고  curl https://oci.mdiwebma.com   하니 sslforfree.com에서 받은 인증서가 정상적으로 동작하고 있는듯하다. 백엔드를 실행해보자..

dajkim76@Kims-Mac-mini ~ % curl -v https://oci.mdiwebma.com
*   Trying 130.162.131.191:443...
* Connected to oci.mdiwebma.com (130.162.131.191) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=oci.mdiwebma.com
*  start date: Nov 12 00:00:00 2022 GMT
*  expire date: Feb 10 23:59:59 2023 GMT
*  subjectAltName: host "oci.mdiwebma.com" matched cert's "oci.mdiwebma.com"
*  issuer: C=AT; O=ZeroSSL; CN=ZeroSSL RSA Domain Secure Site CA
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: oci.mdiwebma.com
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 502 Bad Gateway
< Date: Sat, 12 Nov 2022 05:11:43 GMT
< Content-Type: text/html
< Content-Length: 145
< Connection: keep-alive
<
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center></center>
</body>
</html>
* Connection #0 to host oci.mdiwebma.com left intact

E. 백엔드 구동시키기..

백엔드의 nginx 80는 헬스 체크에 쓰이므로 필요하고, 443 포트는 SSL 인증서 없이 http 프로토콜로 동작시키면 될까? 테스트..

nginx 폴더 하나 만들고 vi docker-compose.yml

services:
  nginx:
    image: nginx:1.21.6
    ports:
      - '80:80'

 

도커로 기본값으로 nginx를 돌린다. 

[opc@instance-20221029-2034 nginx]$ docker-compose up -d
Recreating nginx_nginx_1 ... done

 

Socket관련 퍼미션 에러가 날 경우

$ sudo chmod 666 /var/run/docker.sock

 

맥미니에서 연결 테스트. 크롬에서 https://oci.mdiwebma.com 열기

dajkim76@Kims-Mac-mini ~ % curl https://oci.mdiwebma.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

연결 성공!!

F. 정리

최종 그림
User1... --->. OCI Load Balancer --> Backend_1 Nginx. --> WAS(Node or Deno)
                                                        --> Backend_2 Nginx. --> WAS(Node or Deno) 

-Nginx의 conf를 수정해서  서비스 혹은 App별로 WAS 인스턴스를 여러개 띄우면서 운영할 수 있을 듯.

top

posted at

2022. 11. 12. 14:13


POST : Backend study

Nodejs with NestJS + Mongodb in docker연결 테스트..

Mongodb는 docker로 구성하고, Node는 Mac에 설치하여 접근하다.
지난번에 스터디하면서 만들었던 docker-compose.yml를 그대로 사용한다. 

# Use root/example as user/password credentials
version: '3.1'

services:

  mongo:
    image: mongo:6.0.2
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      - ~/mongo-storage:/data/db
    ports:
      - 27017:27017
    networks:
      - backend

  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 27018:8081
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: example
      ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
    depends_on:
      - mongo
    networks:
      - backend

networks:
  backend:
    driver: bridge
    external: true
    internal: true

docker-compose up -d
로 mongo 서버 실행

testdb를 사용할 예정이다. 접근하기위해서는 testdb에 접근할 user/pwd를 생성해야 한다. mongosh가 db접속을 위한 클라이언트 실행 이름이다. 

dajkim76@Kims-Mac-mini ~/docker/backend-mongo % docker ps
CONTAINER ID   IMAGE           COMMAND                  CREATED       STATUS          PORTS                      NAMES
d891f0402d03   mongo-express   "tini -- /docker-ent…"   2 weeks ago   Up 53 minutes   0.0.0.0:27018->8081/tcp    backend-mongo-mongo-express-1
745448f94859   mongo:6.0.2     "docker-entrypoint.s…"   2 weeks ago   Up 53 minutes   0.0.0.0:27017->27017/tcp   backend-mongo-mongo-1
dajkim76@Kims-Mac-mini ~/docker/backend-mongo % docker exec -i -t backend-mongo-mongo-1 bash
root@745448f94859:/# mongosh -u root -p example
Current Mongosh Log ID:	63688a63565e9961937cc45a
Connecting to:		mongodb://<credentials>@127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.6.0
Using MongoDB:		6.0.2
Using Mongosh:		1.6.0

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

test> db.createUser( { user: "testuser", pwd: "testpwd", roles: [ { role: "readWrite", db: "testdb" }, ] } )
{ ok: 1 }

 

노드에서 연결 테스트..

const mongoose = require('mongoose');

mongoose.Promise = global.Promise;
const MONGO_HOST = "localhost";
const MONGO_URI = `mongodb://testuser:testpwd@${MONGO_HOST}:27017/testdb`;

function onMongoConnected() {
  console.log('Successfully connected to mongodb')
  
  const Cat = mongoose.model('Cat', { name: String });
  const kitty = new Cat({ name: 'Zildjian' });
  kitty.save().then(() => console.log('meow'));
}

console.log('Connecting mongo server')
mongoose.connect(MONGO_URI)
  .then(onMongoConnected)
  .catch(e => console.error(e));

Node js에서 접근시 인증 에러가 난다.  MongoServerError: Authentication failed.

 

테스트 삼아서.. 
test> db.createUser( { user: "testuser_1", pwd: "testpwd_1", roles: [ { role: "readWrite", db: "testdb" }, ] } )
{ ok: 1 }

서버에 adbmin db에저장된 document를 보면 

roles에 db는 testdb로 저장되어 있지만 위에 db는 "test"로 저장되어 있는데 이게 문제 인듯하다.  위에 db는 createUser시에 파라미터로 넣을 수 없다. 넣으면 에러난다.  따라서 mongo-express에서 document를  "testdb"로 수정하니 인증 오류가 해결되었다. 왜 이런 문제가 생기는지는 잘 모르겠다.  db이름에 끝에 db가 들어가서 문제가 되는듯..

2022/11/14일 추가 
testdb를 선택하지 않아 생기는 문제였다. use testdb 하고 나서 createUser하면 정상적으로 유저가 등록된다...

test> use testdb
switched to db testdb
testdb> db.createUser( { user: "testuser_2", pwd: "testpwd_2", roles: [ { role: "readWrite", db: "testdb" }, ] } )
{ ok: 1 }
testdb>

 

 

Nest JS로 테스트 서버 만들기

Nest jS는 가이드는 https://docs.google.com/presentation/d/1HKl1_lRTfe2RktueA3rWZ50XUUH1DZO6Nre8Ioeb1Os/edit?usp=sharing 이 문서를 참고..

 

Nest JS

Beginning NestJS Make REST API Server

docs.google.com

 

서버  시작시 db 연결하기
main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
const mongoose = require('mongoose');

mongoose.Promise = global.Promise;

const MONGO_HOST = "localhost";
const MONGO_URI = `mongodb://testuser:testpwd@${MONGO_HOST}:27017/testdb`;

mongoose.connect(MONGO_URI)
.then(() => console.log('Successfully connected to mongodb'))
.catch(error => console.error(error))

console.log('Starting nest server port: 3000')
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

 

db 모델을 하나 정의
db_models.ts

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    name: {
        type :String,       
    },

 },{collection:'user_list',
    versionKey: false
 });

 export const User = mongoose.model('User', userSchema)

 

localhost:3000/hello/홍길동
을 입력하면 user_list 콜렉션에 저장되도독
app.controller.ts

import { Controller, Get, Param } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/hello/:name')
  getHello(@Param('name') name: string): string {
    return this.appService.getHello(name);
  }
}

app.service.ts

import { Injectable } from '@nestjs/common';
import { User } from './db_models';

@Injectable()
export class AppService {
  getHello(name: string): string {

    const user = new User({ name: name });
    user.save().then(() => console.log(name + ' saved'));
    
    return 'Hello! ' + name;
  }
}

 

auto increment 자체 구현

서버 db는 여러 유저의 데이타를 가지고 있는데, 유저마다 db를 생성할 수 없기 때문에, collection마다 이 documnet가 누구의 db인지를 구분할는 필드를 넣었다.  유저를 구분하는 용도로 did를 사용한다. 
cid는 collection별로 고정된 값을 가진다. 하나의 콜렉션에서 유저별로 서로 다른 고유하게 증가하는 sequence를 만들기 위한 테스트 작업이다. 

db_models.ts 일부 추가

 const tsListSchema = new mongoose.Schema({
    did: { type: Number, require: true}, // db id
    cid: { type: Number, require: true}, // collection id
    ts: { type: Number, default: 0 }
},{ collection:'ts_list',
    versionKey: false
});

const TsList = mongoose.model('TsList', tsListSchema);

// ref: https://mongoosejs.com/docs/tutorials/findoneandupdate.html
export async function getNextTs(cid: number, did: number) : Promise<Number | undefined> {
    const doc = await TsList.findOneAndUpdate({cid: cid, did: did}, {$inc: { ts: 1} }, {new: true, upsert: true}).exec()
    return doc.ts
}

cid와 did가 일치한 ({cid: .. , did: ...})
document를 찾아서 ts를 1 증가시키고 ($inc ...)
없으면 새로 만든다.(new: true) 
ts가 증가된 document를 리턴한다. (upsert: true)

app.controller.ts

@Get('/hello/:name')
  async getHello(@Param('name') name: string): Promise<string> {
    return this.appService.getHello(name);
  }

async를 지원하도록 수정한다. 

app.service.ts

import { Injectable } from '@nestjs/common';
import { User , getNextTs} from './db_models';

@Injectable()
export class AppService {
  async getHello(name: string): Promise<string> {

    const nextTs = await getNextTs(1, 1)
    console.log("nextTs:" + nextTs)
    const user = new User({ name: name, did: 1, ts: nextTs });
    user.save().then(() => console.log(name + ' saved'));
    
    return 'Hello! ' + name;
  }
}

 

_id 대신에 Number 타입의 id를 쓰기.  auto increment와 연계해서 만들 수는 있으나 _id  입력시 필드는 unique해야 한다 

const userSchema = new mongoose.Schema({
    // _id: ObjectId('6368d64ea66f9be19eee81c2'), 대신에 Number를 쓰겠다.
    _id: {
        type: Number
    },
    name: {
        type :String,       
    },
    did: {
        type: Number,
    },
    ts: {
        type: Number,
    }
 },{_id: false, //_id: ObjectId('6368d64ea66f9be19eee81c2'), 형태는 쓰지 않겠다.
    collection:'user_list',
    versionKey: false
 });

 

_id는 그대로 두고, id를 새로 만든다. id는 중복가능하지만 did와 id의 조합은 unique하다. 이것을 createIndex 문으로 만들 수 있다.  mongosh에서 
db.id_list.createIndex( {cid: 1, did: 1}, { unique: true } )
db.user_list.createIndex({id: 1, did: 1}, { unique: true } )

const userSchema = new mongoose.Schema({
    id: {
        type: Number
    },
    name: {
        type :String,       
    },
    did: {
        type: Number,
    },
    ts: {
        type: Number,
    }
 },{
    collection:'user_list',
    versionKey: false
 });


const nextIdSchema = new mongoose.Schema({
    did: { type: Number, require: true}, // db id
    cid: { type: Number, require: true}, // collection id
    id: { type: Number, default: 0 }
},{ collection:'id_list',
    versionKey: false
});

const NextIdCollection = mongoose.model('NextId', nextIdSchema);

export async function getNextId(cid: number, did: number) : Promise<Number | undefined> {
    const doc = await NextIdCollection.findOneAndUpdate({cid: cid, did: did}, {$inc: { id: 1} }, {new: true, upsert: true}).exec()
    return doc.id
}

 

app.service.ts 수정

import { Injectable } from '@nestjs/common';
import { User , getNextTs, getNextId} from './db_models';

@Injectable()
export class AppService {
  async getHello(name: string): Promise<string> {

    const nextId = await getNextId(1, 1)
    const nextTs = await getNextTs(1, 1)
    console.log("nextTs:" + nextTs)
    const user = new User({ id: nextId, name: name, did: 1, ts: nextTs });
    user.save().then(() => console.log(name + ' saved'));
    
    return 'Hello! ' + name;
  }
}

id도 1씩 증가하고 ts도 1씩 증가한다. 사실 같은 원리다. 

top

posted at

2022. 11. 7. 13:36


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크