다음과 관련하여 제가 작성한 튜토리얼 시리즈를 기억하십니까? Node.js 및 NoSQL로 사용자 프로필 저장소 만들기? 이 튜토리얼에서는 Node.js로 RESTful API 만들기, 사용자 세션 처리, 데이터 모델링, 사용자와 관련된 데이터 저장 등 많은 내용을 다루었습니다.
동일한 개념을 Node.js를 사용하는 자바스크립트 대신 골랑으로 적용하고 싶다면 어떻게 해야 할까요?
Golang을 사용하여 사용자 프로필 저장소를 개발하는 방법과 카우치베이스 서버 를 대체할 수 있는 모듈식 대체 솔루션입니다.
앞으로는 Go를 설치 및 구성하고 Couchbase Server를 설치했다고 가정하겠습니다. 만약 여러분이 이미 이 튜토리얼의 Node.js 버전 모든 것을 다시 살펴볼 것이기 때문입니다.
사용자 프로필 저장소가 무엇인지, 어떤 기능을 하는지 잘 모르시는 분들을 위해 간단히 설명하자면, 사용자에 대한 정보 및 사용자와 관련된 정보를 저장하는 솔루션입니다. 블로그를 예로 들어 보겠습니다. 블로그에는 엄밀히 말해 사용자에 해당하는 수많은 작성자가 있을 수 있습니다. 각 작성자는 콘텐츠를 작성하고 해당 콘텐츠는 해당 콘텐츠를 작성한 특정 사용자와 연결됩니다. 각 작성자는 블로그에 로그인하는 고유한 방법도 갖게 됩니다.
사용자 데이터 모델은 매우 자주 변경될 수 있기 때문에 유연한 스토리지 모델을 갖춘 NoSQL 데이터베이스를 사용하는 것이 RDBMS 대안보다 더 효과적인 경우가 많습니다. 데이터 모델링 측면에 대한 자세한 내용은 이 문서에서 확인할 수 있습니다, 사용자 프로필 저장소: 고급 데이터 모델링Kirk Kirkconnell이 쓴 글입니다.
새 프로젝트 생성을 위한 Golang 사용자 관리
우리는 하나의 Go 파일에 모든 시간을 할애할 것입니다. 여러분의 $GOPATH라는 파일을 만듭니다. main.go.
또한 Couchbase와 다른 패키지에 대한 몇 가지 종속성이 필요합니다. 명령줄에서 다음을 실행합니다:
1 2 3 4 5 |
go get github.com/카우치베이스/gocb go get github.com/고릴라/컨텍스트 go get github.com/고릴라/핸들러 go get github.com/고릴라/mux go get github.com/사토리/go.uuid |
위의 종속성을 통해 Couchbase Server와 통신하고, UUID 값을 생성하고, CORS(교차 출처 리소스 공유) 처리를 통해 RESTful API를 생성할 수 있습니다.
다음 단계는 프로젝트의 상용구 코드를 작성하는 것입니다. 프로젝트의 main.go 파일을 열고 다음을 포함하세요:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
패키지 메인 가져오기 ( "encoding/json" "fmt" "log" "net/http" "문자열" "time" "golang.org/x/crypto/bcrypt" "github.com/couchbase/gocb" "github.com/gorilla/context" "github.com/gorilla/handlers" "github.com/gorilla/mux" uuid "github.com/satori/go.uuid" ) 유형 계정 구조체 { 유형 문자열 `json:"유형,생략"` Pid 문자열 `json:"pid,생략"` 이메일 문자열 `json:"이메일, 생략"` 비밀번호 문자열 `json:"비밀번호, 생략"` } 유형 프로필 구조체 { 유형 문자열 `json:"유형,생략"` 이름 문자열 `json:"이름, 생략"` 성 문자열 `json:"성, 생략"` } 유형 세션 구조체 { 유형 문자열 `json:"유형,생략"` Pid 문자열 `json:"pid,생략"` } 유형 블로그 구조체 { 유형 문자열 `json:"유형,생략"` Pid 문자열 `json:"pid,생략"` 제목 문자열 `json:"제목,생략"` 콘텐츠 문자열 `json:"콘텐츠, 생략"` 타임스탬프 int `json:"타임스탬프, 생략"` } var 버킷 *gocb.버킷 func 유효성 검사(다음 http.핸들러 펑크) http.핸들러 펑크 {} func 등록 엔드포인트(w http.응답서 작성기, 요청 *http.요청) {} func 로그인 엔드포인트(w http.응답서 작성기, 요청 *http.요청) {} func 계정 엔드포인트(w http.응답서 작성기, 요청 *http.요청) {} func 블로그엔드포인트(w http.응답서 작성기, 요청 *http.요청) {} func 블로그 엔드포인트(w http.응답서 작성기, 요청 *http.요청) {} func 메인() { fmt.Println("Go 서버 시작 중...") 라우터 := mux.새 라우터() 클러스터, _ := gocb.연결("couchbase://localhost") 버킷, _ = 클러스터.OpenBucket("default", "") 라우터.핸들펀크("/계정", 등록 엔드포인트).방법("POST") 라우터.핸들펀크("/로그인", 로그인 엔드포인트).방법("POST") 라우터.핸들펀크("/계정", 유효성 검사(계정 엔드포인트)).방법("GET") 라우터.핸들펀크("/blogs", 유효성 검사(블로그엔드포인트)).방법("GET") 라우터.핸들펀크("/blog", 유효성 검사(블로그 엔드포인트)).방법("POST") 로그.치명적(http.ListenAndServe(":3000", 핸들러.CORS(핸들러.허용된 헤더([]문자열{"X-요청-함께", "콘텐츠 유형", "권한 부여"}), 핸들러.허용된 메소드([]문자열{"GET", "POST", "PUT", "HEAD", "옵션"}), 핸들러.허용된 출처([]문자열{"*"}))(라우터))) } |
위에서는 데이터를 표현하기 위해 몇 가지 데이터 구조를 만든 것을 볼 수 있습니다. 여기서는 블로그 플랫폼이라는 개념을 사용합니다.
애플리케이션에는 5개의 RESTful API 엔드포인트, 사용자 세션에 대한 유효성 검사기 메서드, 애플리케이션의 어느 곳에서나 오픈 인스턴스에 액세스할 수 있는 글로벌 Couchbase 변수가 있습니다.
1 2 3 |
라우터.핸들펀크("/계정", 유효성 검사(계정 엔드포인트)).방법("GET") 라우터.핸들펀크("/blogs", 유효성 검사(블로그엔드포인트)).방법("GET") 라우터.핸들펀크("/blog", 유효성 검사(블로그 엔드포인트)).방법("POST") |
위의 세 가지 엔드포인트에는 다음과 같은 유효성 검사
함수가 첨부되어 있어야 합니다. 즉, 사용자가 인증을 완료하고 유효한 세션을 제공해야 진행이 가능합니다. 이러한 의미에서 유효성 검사
함수는 미들웨어 역할을 합니다.
특정 사용자에 대한 데이터, 더 구체적으로는 블로그 글을 쿼리할 계획이므로 인덱스를 생성해야 합니다. 웹 대시보드, Couchbase CLI 또는 Go 애플리케이션을 사용하여 다음을 실행합니다:
1 |
만들기 INDEX `블로그 사용자` 켜기 `기본값`(유형, pid); |
위의 인덱스를 사용하면 다음을 기준으로 쿼리할 수 있습니다. 유형
속성뿐만 아니라 pid
속성입니다.
이제 각 API 엔드포인트에 대한 구멍을 채우기 시작할 수 있습니다.
사용자가 프로필 스토어에 새 정보를 등록할 수 있도록 허용하기
현재 프로필 스토어에 사용자가 없으므로 새 사용자 생성을 지원하는 엔드포인트를 만드는 것이 좋습니다.
사용자 아이디와 비밀번호 유형의 자격증명 정보를 실제 사용자 정보와 함께 저장하지 않는 것이 좋습니다. 이러한 이유로 새 사용자를 만들려면 새 사용자를 만들 때 프로필 문서뿐만 아니라 계정 문서. 문서 계정 문서는 프로필 문서가 필요합니다. 두 가지 모두 상용구 코드에서 보았던 Go 데이터 구조를 모델로 삼을 것입니다.
에서 main.go 파일에 다음을 추가합니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
func 등록 엔드포인트(w http.응답서 작성기, 요청 *http.요청) { var 데이터 지도[문자열]인터페이스{} _ = json.뉴디코더(req.본문).디코딩(&데이터) id := uuid.NewV4().문자열() 비밀번호해시, _ := bcrypt.비밀번호로부터 생성([]바이트(데이터["비밀번호"].(문자열)), 10) 계정 := 계정{ 유형: "계정", Pid: id, 이메일: 데이터["이메일"].(문자열), 비밀번호: 문자열(비밀번호해시), } 프로필 := 프로필{ 유형: "프로필", 이름: 데이터["이름"].(문자열), 성: 데이터["성"].(문자열), } _, err := 버킷.삽입(id, 프로필, 0) 만약 err != nil { w.WriteHeader(401) w.쓰기([]바이트(err.오류())) 반환 } _, err = 버킷.삽입(데이터["이메일"].(문자열), 계정, 0) 만약 err != nil { w.WriteHeader(401) w.쓰기([]바이트(err.오류())) 반환 } json.새 인코더(w).인코딩(계정) } |
위의 엔드포인트 코드에는 몇 가지 중요한 일이 일어나고 있습니다.
먼저 클라이언트 요청에서 POST 본문과 함께 전송된 JSON 데이터를 수신합니다. 이 데이터에 대한 고유 ID를 생성합니다. 프로필 문서와 비밀번호를 해싱하여 BCrypt로 안전하게 보관하세요.
실제로 데이터를 저장할 때 사용자 프로필에는 고유 ID가, 계정에는 이메일 주소와 문서 내의 프로필 ID에 대한 참조가 ID로 제공됩니다.
이 접근 방식을 따르면 계정 문서를 다른 형태의 자격 증명으로 쉽게 확장할 수 있습니다. 예를 들어, 계정 문서를 다음과 같이 리브랜딩할 수 있습니다. basicauth프로필 정보를 참조하는 Facebook, Twitter 등이 있을 수 있습니다.
사용자를 위한 세션 토큰 구현하기
두 문서를 통해 애플리케이션에 로그인하는 방법은 약간 다릅니다. 사용자 아이디와 비밀번호를 꼭 필요한 것보다 더 많이 알려주는 것은 결코 좋은 생각이 아닙니다.
따라서 사용자를 나타내는 세션 토큰을 사용하는 것이 좋습니다. 이 토큰은 만료될 수 있으며 민감한 정보를 포함하지 않습니다.
다음 로그인 코드를 사용하여 main.go file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
func 로그인 엔드포인트(w http.응답서 작성기, 요청 *http.요청) { var 데이터 계정 var 계정 계정 _ = json.뉴디코더(req.본문).디코딩(&데이터) _, err := 버킷.Get(데이터.이메일, &계정) 만약 err != nil { w.WriteHeader(401) w.쓰기([]바이트(err.오류())) 반환 } err = bcrypt.해시 및 비밀번호 비교([]바이트(계정.비밀번호), []바이트(데이터.비밀번호)) 만약 err != nil { w.WriteHeader(401) w.쓰기([]바이트(err.오류())) 반환 } 세션 := 세션{ 유형: "세션", Pid: 계정.Pid, } var 결과 지도[문자열]인터페이스{} 결과 = make(지도[문자열]인터페이스{}) 결과["sid"] = uuid.NewV4().문자열() _, err = 버킷.삽입(결과["sid"].(문자열), &세션, 3600) 만약 err != nil { w.WriteHeader(401) w.쓰기([]바이트(err.오류())) 반환 } json.새 인코더(w).인코딩(결과) } |
이메일과 비밀번호가 이 엔드포인트로 전달되면 계정 문서가 제공된 이메일을 기반으로 검색됩니다. 그런 다음 이 문서 내부의 해시된 비밀번호를 해시되지 않은 비밀번호와 비교합니다.
자격 증명이 유효한 경우 세션 문서가 생성됩니다. 이 세션 문서에는 고유 키가 있지만, 이 키는 프로필 문서에 추가합니다. 문서에 만료 시간도 추가됩니다. 만료 시간이 지나면 애플리케이션이나 사용자의 개입 없이도 문서가 Couchbase에서 자동으로 제거됩니다. 이는 계정을 보호하는 데 도움이 됩니다.
계정이 작동하면 사용자와 정보를 연결하는 것에 대해 고민해야 합니다.
세션 토큰을 통해 프로필 스토어에서 사용자 정보 관리하기
사용자가 특정 작업을 수행하려고 할 때 사용자가 본인인지, 변경하려는 정보가 올바른 사람에게 적용되는지 확인해야 합니다.
여기에서 세션 토큰 유효성 검사 미들웨어가 작동합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func 유효성 검사(다음 http.핸들러 펑크) http.핸들러 펑크 { 반환 http.핸들러 펑크(func(w http.응답서 작성기, 요청 *http.요청) { authorizationHeader := req.헤더.Get("권한 부여") 만약 authorizationHeader != "" { 베어러토큰 := 문자열.분할(authorizationHeader, " ") 만약 len(베어러토큰) == 2 { var 세션 세션 _, err := 버킷.Get(베어러토큰[1], &세션) 만약 err != nil { w.WriteHeader(401) w.쓰기([]바이트(err.오류())) 반환 } 컨텍스트.설정(req, "pid", 세션.Pid) 버킷.터치(베어러토큰[1], 0, 3600) 다음(w, req) } } else { w.WriteHeader(401) w.쓰기([]바이트("인증 헤더가 필요합니다.")) 반환 } }) } |
세 가지 특수 엔드포인트 중 하나에 대한 모든 요청에는 세션 ID가 포함된 무기명 토큰이 포함된 인증 헤더가 필요합니다. 무기명 토큰이 없으면 요청이 실패합니다. 부정확하거나 만료된 무기명 토큰은 요청이 실패한다는 의미입니다.
유효성 검사는 세션 ID를 요청의 다음 단계에서 사용할 프로필 ID로 교환합니다.
간단하게 시작하여 특정 사용자의 프로필 정보를 반환하고 싶다고 가정해 보겠습니다. 엔드포인트는 다음과 같이 보일 수 있습니다:
1 2 3 4 5 6 7 8 9 10 11 |
func 계정 엔드포인트(w http.응답서 작성기, 요청 *http.요청) { pid := 컨텍스트.Get(req, "pid").(문자열) var 프로필 프로필 _, err := 버킷.Get(pid, &프로필) 만약 err != nil { w.WriteHeader(401) w.쓰기([]바이트(err.오류())) 반환 } json.새 인코더(w).인코딩(프로필) } |
그리고 pid
가 유효성 검사 미들웨어에서 전달되고 프로필 ID로 조회가 수행됩니다.
지금까지는 그렇게 어렵지 않았죠?
한 단계 더 나아가 프로젝트에 몇 가지 N1QL 쿼리를 도입해 보겠습니다. 특정 사용자에 대한 모든 블로그 게시물을 가져오고 싶다고 가정해 보겠습니다. 이렇게 하면 인덱스와 SQL과 유사한 쿼리를 사용할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func 블로그엔드포인트(w http.응답서 작성기, 요청 *http.요청) { var n1qlParams []인터페이스{} n1qlParams = 추가(n1qlParams, 컨텍스트.Get(req, "pid").(문자열)) 쿼리 := gocb.NewN1qlQuery("SELECT `" + 버킷.이름() + "`.* FROM `" + 버킷.이름() + "` WHERE type = 'blog' AND pid = $1") 쿼리.일관성(gocb.요청 플러스) 행, err := 버킷.ExecuteN1qlQuery(쿼리, n1qlParams) 만약 err != nil { w.WriteHeader(401) w.쓰기([]바이트(err.오류())) 반환 } var 행 블로그 var 결과 []블로그 에 대한 행.다음(&행) { 결과 = 추가(결과, 행) 행 = 블로그{} } 행.닫기() 만약 결과 == nil { 결과 = make([]블로그, 0) } json.새 인코더(w).인코딩(결과) } |
위의 엔드포인트 코드에서, 우리는 pid
를 유효성 검사 미들웨어에서 가져와 매개변수화된 쿼리의 매개변수로 추가합니다.
반복해서 쿼리 결과
를 쿼리에서 반환하고 이를 []블로그
변수를 반환합니다. 결과를 찾을 수 없으면 빈 슬라이스를 반환하면 됩니다.
키를 기반으로 직접 조회하는 것이 N1QL 쿼리보다 항상 빠르지만, 속성 정보로 쿼리해야 할 때는 N1QL 쿼리가 매우 유용합니다.
최종 엔드포인트는 앞서 살펴본 것과 크게 다르지 않습니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
func 블로그 엔드포인트(w http.응답서 작성기, 요청 *http.요청) { var 블로그 블로그 _ = json.뉴디코더(req.본문).디코딩(&블로그) 블로그.유형 = "blog" 블로그.Pid = 컨텍스트.Get(req, "pid").(문자열) 블로그.타임스탬프 = int(시간.지금().Unix()) _, err := 버킷.삽입(uuid.NewV4().문자열(), 블로그, 0) 만약 err != nil { w.WriteHeader(401) w.쓰기([]바이트(err.오류())) 반환 } json.새 인코더(w).인코딩(블로그) } |
위 코드에서는 클라이언트로부터 POST 본문뿐만 아니라 pid
를 사용하여 유효성 검사 미들웨어에서 정보를 수집합니다. 그런 다음 해당 정보는 Couchbase에 저장됩니다.
결론
Go 프로그래밍 언어를 사용하여 기본 사용자 프로필 스토어(이 경우 블로그 플랫폼)를 만드는 방법과 카우치베이스 서버. 이 튜토리얼은 이전 튜토리얼 같은 주제로 썼던 글이지만 Node.js를 사용했습니다.
이 튜토리얼을 한 단계 더 발전시키고 싶으신가요? 다음 튜토리얼에서 Angular를 사용한 웹 클라이언트 프런트엔드 또는 네이티브스크립트를 사용한 모바일 클라이언트 프런트엔드.
Golang과 함께 Couchbase를 사용하는 방법에 대한 자세한 내용은 다음을 참조하세요. 카우치베이스 개발자 포털.