GithubHelp home page GithubHelp logo

team1-server's Introduction

team1-server

Build and Run

1. Local Development

  • Load local mysql database
% docker-compose -f docker-compose.local.db.yml up -d   
  • Set -Dspring.profiles.active=local in VM options
  • Run application
  • If you want to test local app with docker image, change 'localhost' to 'host.docker.internal' in application.yml file and run below command
% docker build --build-arg PROFILES=local -t wafflytime-local .

2. Prod Development

  • Set -Dspring.profiles.active=prod in VM options

API Document

https://foregoing-venom-883.notion.site/WafflyTime-API-b6aaf2b096924d2fbb251c443d66ba15

Tech Stack

  • Spring Data Jpa
  • Spring Security
  • Spring Mail
  • Spring MVC
  • Spring Webflux (for OAuth)
  • Spring Cloud
  • Redis
  • WebSocket
  • Aws EC2, ECR, RDS, S3
  • Github Action (CI/CD)

Contributions

1. Email Authentication

에타는 해당 대학교 메일을 갖고 있는 유저만 가입할 수 있기 때문에, 메일 인증이 필요하다. 메일 인증을 위해 Spring Mail과 redis를 사용하였다.

  • 유저의 SNU 메일을 request body로 받는다.
  • 랜덤 코드를 생성해 유저의 메일로 코드를 전송한다
  • 랜덤 코드를 redis에 저장하고, 유효시간은 3분+a 로 설정한다
  • 유저는 받은 코드를 3분 안에 인증 확인 화면에 코드를 입력한다
  • 클라이언트는 유저가 적은 코드를 서버로 전송해주고, 서버는 코드를 redis에서 꺼내와 일치하는지 비교한다

2. Image by Presigned URL

유저 프로필 이미지, 게시물 사진들을 aws S3에 저장하고, s3 url를 db 엔티티에 기록한다. 직접적인 s3 url이 외부로 유출 되는 것을 막고, 사진을 업로드하고 다운로드 하는 과정을 서버가 아닌 클라이언트에게 부담하기 위해 aws S3에서 제공하는 Presiged URL을 사용했다. 이미지 업로드, 다운로드를 서버가 책임지면 서버에 큰 부하가 걸리기 떄문에 각각의 클라이언트가 처리한다.

  • 클라이언트는 이미지 파일 이름을 서버로 전송한다
  • 서버는 이미지의 유효시간이 설정된 s3 presiged url을 생성해 클라이언트에게 전송해준다. 실제 s3 url은 db entity로 저장한다
  • 클라이언트는 response로 받은 presigned url로 이미지를 업로드한다
  • 이미지를 GET 해야 하는 경우, 서버는 db enttiy에 기록된 s3 url을 통해 presigned url을 생성해 클라이언트에게 전달한다. 클라이언트는 presigend url을 통해 이미지를 get 해온다.

3. SubQuery -> Redis & Coroutine

에타 홈 화면에서 특정 category에 속한(16개) 게시판 별 최근 4개의 게시물을 보여주는 api를 구현해야 한다. 이를 sql 문으로 바꾸면 다음과 같다

select * from (
    select *, RANK() over (partition by j.board_title order by j.created_at DESC ) as a
    from (select p.*, b.category, b.title as "board_title" from post p left join board b on p.board_id = b.id where b.category in ('BASIC', 'CAREER')) as j
) as f where f.a <=4;

서브쿼리 지옥에 빠지게 되고, mysql 특수 함수들이 있어, Querydsl로 구현할 수 없었다. 또한 이렇게 복잡한 쿼리인 경우 유지보수가 어렵기 때문에, 다른 방법의 필요성을 느꼈다. 다음 3가지 과정을 통해 개선된 방법을 찾아나갔다. 각 방법 별 요청 처리 시간을 기록하였다.

3-1 3-2 3-3 3-4
Takes 140ms 60ms 30ms 15ms

3-1. For loop을 통한 각 게시판 마다 4개의 post를 찾는 query 날리기

  • 특정 카테고리에 속한 게시판들을 알기 위한 query 한 개를 날린다
  • 각 게시판 id 마다 최근 4개의 게시물을 얻어내는 query를 날린다

3-2. 첫번째 방법 + coroutine

  • 한 번의 요청마다 17번의 쿼리를 날려야 하기 때문에 속도가 느리다. 17개의 요청이 네트워크를 타기 때문에 coroutine을 이용하면 더 빨라질 것이라 추측했다
  • 코루틴을 적용하니 2배 이상 빨라졌다
boards.forEach {
  future.add(CoroutineScope(Dispatchers.Default).async {
    findLatestPosts(
      boardId = it.id,
      limit = if (it.type.name.startsWith("CUSTOM")) 2 else 4)
   })
}
runBlocking { future.forEach { it.await() } }

3-3. Redis 적용

  • 여전히 속도가 느리다고 판단했고, 속도를 높이기 위해 redis를 활용하였다.
  • 각 board가 redis의 key가 되고 value는 최근 4개의 게시물을 리스트가 된다. 특정 카테고리에 해당 하는 게시판에 게시물이 생성, 수정, 삭제 될 때마다 redis를 업데이트 해준다

3-4. Redis + coroutine

  • 3번쨰 방법에 coroutine까지 적용하였다. 우리는 local redis를 쓰기 때문에 coroutine을 사용했을 때 시간 차이가 크게 나지 않았다. 만약 실제 환경같이 remote redis 서버를 사용한다면, coroutine의 효과가 더 커질 것이라 추측한다

=> 기존에 약 140ms 정도 걸리던 처리 과정이 15ms까지 줄어들었다

4. Real-Time Chat (WebSocket)

Spring boot에서 제공하는 STOMP를 sub protocol로 사용해 구현하려고 했지만
(1) 테스트 하기 어렵고 커스텀에 관한 정보가 부족했고
(2) 1:1 채팅만이 성립하는 에타 쪽지에서 publish-subscribe 모델까지는 필요하지 않아서
TextWebSocketHandlerHttpSessionHandshakeInterceptor를 상속받은 구현체를 만들어서 구현했다.

한 유저당 서버측에서 유지하는 웹소켓 세션은 하나로 한정했고,
유저 웹소켓 세션이 존재하면 (즉, 쪽지 페이지에 유저가 접속중이면) 웹소켓을 통해 db에 작성된 쪽지 정보를 보내주고,
아니라면 SSE 알림을 보내 유저가 실시간으로 쪽지 수신 여부를 알 수 있게 했다.

메세지는 아래의 간단한 sub protocol을 통해 주고 받는다.

  • 모든 메세지 형식은 json 형식을 따른다.
  • 프론트 -> 서버 메세지 형식은 유저가 송신하는 메세지 정보, 한 종류이다.
{
  "chatId": Long, // 쪽지를 보내는 채팅방 db id
  "contents": String // 쪽지 내용
}
  • 서버 -> 프론트 메세지 형식은 전송/수신한 쪽지 정보인 MESSAGE 타입, rest api 요청을 통해 생성된 새 채팅방 정보인 NEWCHAT 타입, 그리고 한정된 상황에서 db 데이터에 변화가 있어 추가적인 rest api 요청을 통해 정보 최신화 할 것을 요청하는 NEED_UPDATE 타입, 3 종류이다.
{
  "chatId": Long, // 쪽지가 생성된 채팅방 db id
  "messageId": Long, // 생성된 쪽지의 db id
  "sentAt": { // 쪽지 작성 시각
    "year": Int,
    "month": Int,
    "day": Int,
    "hour": Int,
    "minute": Int
  },
  "received": Boolean, // true이면 받은 메세지, false이면 보낸 메세지
  "contents": String, // 쪽지 내용
  "type": "MESSAGE"
}
{
  "chatId": Long, // 생성된 채팅방 db id
  "target": String, // 채팅 상대 이름
  "type": "NEWCHAT"
}
{
  "chatId": [Long, ...],
  "unread": [Int, ...],
  "type": "NEED_UPDATE"
}

5. SSE notification

현재 에타 웹에서는 자신이 작성한 게시물에 댓글이 달리는 경우, 알림이 오지만 이 알림은 새로고침을 해야만 확인할 수 있다. SSE(Server-Sent-Event)를 이용하여 유저가 화면을 보고 있는 중에는 알림이 뜰 수 있게 구현 하였다. (TODO: 프론트가 구현 완료되면 추후 알림 뜨는 사진 첨부)

6. OAuth Code with Redis

소셜 로그인/회원 가입은 클라이언트에서 받아온 OAuth Authorization Code로 서버에서 access token을 받아온 후 로그인/회원가입 처리를 진행한다. 기존 계정이 없어서 로그인 실패하는 경우 회원가입을 진행하려 하였으나, OAuth Authorization code를 재사용 할 수 없어 회원가입이 실패하는 문제가 있었다.

6-1. 임시 닉네임 적용

소셜 로그인 실패시 임시 닉네임을 생성하여 가입시키는 방법으로 해결을 시도하였다.
이 경우 임시 닉네임이 다른 닉네임과 중복되면 안되고, 닉네임이 임시 닉네임이면 게시글/댓글을 못 쓰게 따로 설정해야하는 등 추가로 고려해야 할 요소가 많아지는 문제가 있다.

6-2. Redis 적용

  • 소셜 로그인 실패시 클라이언트로부터 받아온 OAuth Authorization Code를 key로, OAuth Authorization 측에서 얻어온 social email를 value로 하여 redis에 저장해두었다.
  • 회원가입 처리를 해야하는 경우, redis에서 OAuth Authorization Code에 해당하는 social email을 찾고, 닉네임을 받아서 최종적인 회원가입 처리를 진행하였다.
  • 한번 사용한 OAuth Authorization Code는 더 사용할 일이 없으므로 바로 삭제 시켰다.
  • 회원가입을 진행하다 말고 종료하는 유저들의 데이터를 계속 쌓아둘 수 없기에, 저장된지 10분이 지난 데이터는 삭제시켰다.

7. Cursor, Double-Cursor Pagination

게시판 내 게시물을 열람할 때, 에타 웹에서는 이전/다음을 통해 이동하는 페이지 방식이 사용되고, 앱에서는 무한스크롤 방식이 사용된다.
전자에 적합한 것은 offset 기반 페이지네이션, 후자에 적합한 것은 cursor 기반 페이지네이션이므로 Generic 클래스를 사용해 pagination이 적용된 한 api에 두 방식의 요청이 가능하도록 했다.

data class CursorPage<T>(
    val contents: List<T>,
    val page: Long? = null,
    val cursor: Long? = null, // DoubleCursorPage의 경우 Pair<Long, Long>? 타입
    val size: Long,
    val isLast: Boolean
)

query parameter에 page, cursor, size가 존재하고, page가 offset기반 방식에, cursor가 cursor기반 방식에 사용된다.
offset기반 방식에서는 첫 페이지가 0임을 당연히 알 수 있지만, cursor기반 방식에서는 첫 페이지를 요청하기 위해 필요한 cursor가 무엇인지 알 수 없기 때문에 cursor가 주어지지 않은 (null 인) 경우에 첫 페이지를 제공한다.
따라서 offset기반 방식 요청인지 cursor기반 방식 요청인지는 page query parameter의 nullity를 기준으로 한다.

댓글은 대댓글의 존재 때문에 작성 시간 만으로 정렬할 수 없다.
따라서 원댓글의 작성시간이 최신이면 더 큰 값을 할당 받는 replyGroup의 순서를 1순위, 작성시간을 2순위로 정렬한다.
이에 따라 cursor기반 페이지네이션에도 커서가 두개 필요해 이때는 DoubleCursorPage를 사용해 페이지 사이에 누락되는 데이터가 없게 한다.
마찬가지로, 현재 구현의 best게시물은 공감 개수를 1순위, 작성시간을 2순위로 정렬하기 때문에 DoubleCursorPage를 사용한다.

team1-server's People

Contributors

sjm150 avatar woong97 avatar sumin-kim-dev avatar yeonghyeonko avatar

Stargazers

 avatar  avatar ihyungsuk avatar

Watchers

Force, Mingyu avatar

Forkers

sumin-kim-dev

team1-server's Issues

[BUG] 닉네임 설정 오류

image

  1. 닉네임, 이름으로 특수문자가 설정 가능합니다
  2. 새로고침시 로그아웃됩니다
  3. 배너가 제대로 뜨지 않습니다

[Bug] 웹 버그

소셜 로그인이 안됩니다.
로그인 유지를 체크하여도 새로고침하면 로그아웃이 됩니다.
아이디 비밀번호 찾기 클릭이 안됩니다
학교인증하기전에 설명이 있으면 좋을 것 같습니다.
게시글 검색이 잘 작동하지 않습니다(404에러가 뜹니다)
내가쓴글, 댓글단글, 내 스크랩 에서도 글 작성이 가능합니다
글작성 취소하는 버튼이 없습니다
익명으로 댓글을 여러사람이 쓰면 뒤에 숫자가 바뀌면 좋을것 같습니다.

버그들

상단 디자인에 흰색과 회색이 구분되는 게 아쉽습니다.

게시물 작성할 때 제목을 쓸 수 없는 게시판이 많습니다.

'사법대학'이라고 오타가 난듯합니다. 사범대학이 아닌지...

소개팅 게시판은 글 작성 자체가 안되는 것 같습니다.

작성 하는 상태에서 위에서 딴 게시판 눌러서 이동하면 계속 작성하는 상태가 유지됩니다. 누르면 게시물 리스트를 렌더링 해줘야하지 않나 싶습니다.

책방을 누르면 로그인 화면으로 가집니다.

비밀번호 수정 페이지에서 현재비밀번호가 맞지 않는데 체크표시가 뜹니다.

리프레쉬하면 로그인 유지가 되지 않습니다.

로그인 페이지에 아이디 비번 찾기 버튼은 있는데 작동되지 않습니다.

검색칸도 작동되지 않습니다.

스크랩하거나 공감 했을때 게시물 전체 리렌더링은 필요 없을듯 합니다.

그리고 이미 공감 하거나 스크랩 한 게시물이 표시되지 않고 여전히 버튼을 누를 수 있습니다.

[Bug] 게시글 작성 등 오류

  1. 새로고침 시 로그아웃
    image

새로고침을 하면 로그인이 풀리고 로그인 페이지로 연결이 되는데 소셜 로그인 창이 없어 사이트에 재접속해야함

  1. 글 목록 버튼 오류
    image

게시글에서 글 목록 버튼을 누르면 잘못된 url로 연결됨.

  1. 댓글 작성자에게 쪽지 보내기

게시글 작성자에게는 쪽지 버튼을 누르면 쪽지 창이 생성되지만 댓글 작성자에게는 쪽지, 공감, 신고가 모두 불가능

구글 로그인 닉네임 설정 버그

구글 로그인 할 때,
다음 화면에서 두가지 알림과 함께 넘어가지 않습니다.

  1. 닉네임이 invalid하다 (정확하진 않습니다)
  2. 만료된 코드입니다.
    image

대댓글 창

한 게시물에서 a번째 대댓글 창을 열어놓았다가 다른 게시물로 넘어가도 a번째 대댓글 창이 그대로 열려있습니다.

[BUG] 사범대 게시판 등에 게시물 작성 오류

사범대 게시판에 글을 작성하려고 하면

'잘못된 요청입니다. 제목이 존재하지 않는 게시판입니다.' 에러가 나는데, 제목을 입력하든 입력하지 않든 같은 오류가 납니다.
사범대뿐 아니라 다른 여러 게시판에서도 같은 에러가 발생합니다.

[BUG]와플리타임 웹

  1. 웹에서 navbar를 눌렀을 때 선택된 메뉴로 css가 고정되면 좋을 것 같습니다.

  2. 몇몇 게시판에서 '게시글에 게시판이 존재하지 않는 게시글입니다.' 라는 warning message가 뜨고 게시판 게시글이 작성되지 않습니다.(의학과 게시판, 비밀게시판, cpa, 로스클 게시판 등등)

  3. 처음 회원가입을 했을 때 학교이메일 인증을 하도록 강제로 유도해주시면 좋을 것 같습니다. 게시글도 보이지 않고 글도 써지지 않아 의아했는데 게시글을 작성하려 하자 학교 이메일을 인증하라는 message를 보고 그때부터 제대로 이용할 수 있었습니다.

구글 소셜 로그인 실패

구글 계정으로 소셜 로그인을 시도했을때 닉네임 입력 후 다음 버튼을 누르면 아래와 같이 로그인이 살패합니다. 다른 구글 계정으로 시도했는데 같은 에러가 뜨네요. 이 부분 수정이 필요할 것 같습니다!
Screen Shot 2023-02-06 at 1 41 37 PM

[Bug] 카카오 로그인이 제대로 이루어지지 않습니다

홈화면에서 [ 카카오톡으로 로그인 ] 을 눌렀을 시, 아래와 같은 페이지가 뜨고

image (3)

여기서 [ 동의 후 계속하기 ]를 누른 다음,
아래와 같이 닉네임을 입력하고서 [ 다음 ] 을 누르게 되면, 만료된 코드라는 오류 메시지가 뜨게 됩니다. (아래 사진의 우측 상단 참고)

image (4)

그래서 카카오 로그인이 현재는 정상적으로 이루어지지 않고 있는 듯 보입니다 😢

[BUG] 다크모드 및 게시물 관련 버그

다크모드 : 다크모드 적용시 일부 아이콘 및 게시판 제목의 색상 설정이 제대로 되자않아 잘 보이지 않거나, 자세히 봐야 보이는 등의 문제가 있습니다.

댓글 삭제 : 게시물에 댓글 삭제가 실시간으로 이루어지지 않습니다. 경험적으로 봤을 때 해당 댓글 삭제는 빠르게 이루어지지 않을 시 사용자는 정보가 변경되지 않을 줄 알고, 이미 삭제된 댓글을 삭제하려고 하는 경험이 있을 수 있습니다. 그러면 400에러를 통해 해당 메시지는 이미 삭제되었다고 나옵니다.

댓글 수정 : 댓글 수정이 실시간으로 이루어지지 않습니다. 200응답이 나오고 서버에는 반영이 되나 UI 갱신이 안됩니다. 댓글 입력 시 Edit Text가 초기화 되지 않아서 한번 댓글 수정 시 다시 새로운 댓글 입력을 할 수 없고 계속 수정만 이루어집니다. Edit text와 입력을 위한 벡터 아이콘(ic_baseline_edit_24)가 너무 하단에 있어서 불편한 점이 있어서 키보드의 엔터 버튼을 완료 버튼으로 바꾸거나 하단에서 뷰를 살짝 띄우는게 좋을 것 같습니다.

게시물 수정 : 게시물 수정 후 완료버튼 클릭 시 위로 스크롤 해서 새로고침 해야 수정 사항이 반영됩니다. 그리고 한번 수정 시 상단에 메뉴 버튼이 사라져서 새로고침, 게시물 수정, 게시물 삭제 기능을 사용할 수 없습니다. 나갔다 다시 들어와야합니다. 또한 게시물 수정을 통해서 해당 게시물로 이동 시 Edit text의 하단이 짤리는 현상이 있습니다. 이 때 사진 등록을 한번 수행하면 짤림 현상이 해결되어 Edit text가 전부 보이게 됩니다.

게시물 등록 : 게시물 등록, 수정 시 키보드가 UI를 가려서 사용자가 게시물에 글이 제대로 입력되었는지 보려면 뒤로가기를 눌러 키보드를 없애야 볼 수 있습니다. 해당 페이지 Layout을 Relative Layout으로 하면 좋을 것 같습니다. 또한 게시물의 제목과 내용의 텍스트 사이즈가 같아서 제목과 내용이 구별이 안가니 텍스트 사이즈에 차별을 두면 좋을 것 같습니다.

사진 등록 : 게시물에 사진 등록 후 완료버튼 클릭 시java.lang.NullPointerException 에러가 발생하며 게시물로 이동하지 않는 버그가 있습니다. 기다리면 navigate action 및 수정, 등록 사항이 반영이 될 때도 있습니다. 또한 게시물 수정 시에 자신이 등록했던 사진을 볼 수 없습니다. 수정 중 새로 등록하는 사진도 보이지 않습니다.

메뉴 클릭 시 버그

메뉴에서 게시판 이외의 요소들을 클릭할 시 메뉴의 빨간색 표시줄이 게시판으로 이동하는 버그가 있습니다.(한 번 더 클릭하면 제대로 됩니다) 또한 강제 리디렉트(새로고침) 시 로그인이 풀리는 버그도 있는 것 같습니다.

새로고침 시 오류

웹 메인 화면에서 새로고침 시 로그아웃되며 다시 로그인 창으로 돌아갑니다.

여러 버그와 개선할 사항

앱 관련

소셜로그인

네이버, 구글, 깃허브 로그인이 안 됨.

카카오 로그인 시

닉네임 입력창에 엔터가 입력되어
텍스트 입력창이 커짐.
키보드가 내려가지 않고
엔터를 눌러도 입력이
종료되지 않아 회원가입 마무리가 어려움.

다크 모드시

글자색은 변경되는데
보드 배경색은 변경되지 않아
글자가 보이지 않는 버그가 남.

보드 및 게시물

댓글에 서로 다른 익명 유저가 댓글을 달았는데
익명 번호가 모두 같게 나옴

댓글 수정이 밖에 나갔다와야 적용됨.
댓글 삭제가 밖에 나갔다와야 적용됨.

모든 게시판 리스트 뷰에서
한 글을 선택해 디테일뷰에 다녀오면
리스트가 다시 추가되어
같은 항목이 2배로 늘어남.

가끔 게시물에서 상단바의 뒤로가기 버튼이
잘 작동되지 않음.
게시물 좋아요 취소 기능이 없음.
스크랩 여부를 게시물에서 확인할 수 없음.

검색

홈에서 검색 버튼을 누르면 로그인 창이 뜸.

보드의 리스트 디테일뷰에 들어가서
검색 버튼을 눌러도 검색결과가 제대로 표시 안됨.

쪽지

쪽지를 보낸 시간이 제대로 표시되지 않음

웹 관련

소셜로그인

소셜로그인이 작동하지 않음

웹 전반

기능이 작동하지 않음

[BUG] 아이디/비밀번호 찾기

(http://wafflytime.com/) 첫 화면의 '아이디/비밀번호 찾기'를 클릭할 때, 해당 페이지로 넘어가는 것이 아니라 로그인 페이지으로 넘어가게 됩니다. 그리고 이 로그인 페이지에서는 로그인 처리가 되지 않습니다.

[BUG] 비밀번호 변경 및 기타 개선 사항

가끔 게시물 리스트 로딩 시 게시물들이 두배, 세배로 복사가 되는 현상이 있습니다. 경험적으로 한 게시물 탐색 후 뒤로가기 버튼을 누를 때마다 복사가 되는 것 같습니다.

추가적으로 토스트 메시지(게시물 로딩중, 댓글 로딩중, 게시물 로딩 완료, 댓글 로딩 완료) 등이 너무 많고 메시지가 나타났다가 사라지는 시간이 너무 길어서 신경쓰이고, 불편합니다....

비밀번호 변경 : 카카오로그인 시 로그아웃을 수행할 수 없어서(재로그인을 안해봐서) 기억이 희미하지만 아마 비밀번호를 따로 생성하지 않았던 것으로 기억을 합니다. 소셜 로그인 시 비밀번호 변경 버튼을 비활성화해야할 것 같습니다. 비밀번호가 없는데 비밀번호변경 시도시 비정상 종료가 됩니다.

[BUG] 스크랩 게시물 출력오류

스크랩을 실시간 인기게시글에 뜬 2개 게시글에 대해 해봤는데 스크랩한 게시글에는 하나만 뜨는 것 같습니다. 혹시 몰라 3번째 게시글 스크랩해보니 마지막으로 스크랩한 게시글만 뜨도록 설계된 것 같습니다.

아이디/비밀번호 찾기 창 뒤로가기 오류

홈 화면에서 아이디/비밀번호 찾기버튼을 눌러 해당 URL로 접속했을 때, 뒤로가기를 클릭해도 홈 화면에 접근할 수 없습니다. 와플리타임 로고를 클릭하면 홈으로 이동하긴 해서 접근 경로를 막으려는 의도는 아닌 것 같은데, 뒤로가기가 작동하지 않아 버그로 제보합니다!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.