post-custom-banner

나만의 무기

오늘도 여러가지 작업을 했는데, 그 중 몇가지를 소개해보려고 한다.

회원가입

우선 회원가입 페이지를 전반적으로 구상했다.

위와 같이, 각종 케이스들에 대한 예외처리도 해주었다 ! 원래는 1시간 이내로 해보겠다고 했는데, API수정, DB연동까지 병행해서 하다보니까 생각보다 오래 걸렸다.

우선, 원래는 true, false만 리턴하던 controller 코드를 위와 같이 수정했다.

  • 아이디 중복체크가 가능하도록
  • 회원가입에 실패했을 때는 어떤 사유에서인지 에러메세지를 함께 리턴
  • 회원가입에 성공했을 때는 부여받은 unique한 id(정수) 값을 함께 리턴
// Node.js (BE)
// user.controller.js

Controller.create = async (data) => {
  const { account_name, password, phone_number } = data;
  const friends = "";
  if (!account_name | !password | !phone_number) {
    return [false, "누락된 정보가 있습니다"];
  }
  
  // 중복 생성 체크
  const exist = await User.findOne({where: {account_name}});
  if (exist){
    return [false, "이미 존재하는 아이디입니다"];
  }

  const new_user = await User.create({friends, account_name, password, phone_number});
  if (!new_user) {
    return [false, "생성에 실패했습니다. 다시 시도해주세요"];
  }
  // return new_user.dataValues.id;
  return [true, new_user.dataValues.id];
}

이를 기반으로, POST 요청이 왔을 때 돌려주는 값 역시 수정했다.

  • 회원가입에 실패했을 때는 "msg" 키에 대한 값으로 에러메세지를 함께 리턴
  • 회원가입에 성공했을 때는 "id" 키에 대한 값으로 id(정수) 값을 함께 리턴
// Node.js (BE)
// user.service.js

  router.post('/signup', async (req, res) => {
    const {body} = req;
    const result = await users.create(body);
    if (result[0]) {
      res.status(201).send({"result": "success", "id":result[1]})
    } else {
      res.status(400).send({"result": "fail", "msg":result[1]})
    }
  });

이렇게 BE쪽 작업을 마치고, 이번엔 FE쪽 작업을 하였다.
우선, postUser 라는 함수를 만들어서 Flutter에서 POST요청을 정상적으로 보내도록 하였다.

// Flutter (FE)
// signup.dart

postUser(account, password, phonenumber) async {
    try {
      var dio = Dio();
      var param = {
        'account_name': '$account',
        'password': '$password',
        'phone_number': '$phonenumber'
      };
      Response response = await dio
          .post('http://34.64.217.3:3000/api/user/signup', data: param);      
          
      if (response.statusCode == 201) {
        final json = response.data;
        setState(() {
          uniqueID = json['id'];
        });
        return true;
      } else {
        return false;
      }
    } on DioError catch (e) {
      final errorjson = jsonDecode(e.response.toString());
      setState(() {
        errorDetail = errorjson['msg'];
      });
      return false;
    }
  }

그리고 이 postUser 함수에 대한 리턴값을 참조해서 POST에 실패했다면 그 에러메세지가 뜰 수 있도록 하였다.

다시 한 번 gif를 보면, 아마도 의문이 들 것이다. 비밀번호, 휴대전화번호에 대한 유효처리가 왜 없지?
왜냐하면, 애초에 POST 요청을 보내기 전에 모두 확인을 하도록 코드를 짰기 때문이다.

// Flutter (FE)
// signup.dart
			    
ElevatedButton(
style: ElevatedButton.styleFrom(fixedSize: Size(40, 40)),
	child: Text('확인'),
	onPressed: () async {
		RegExp accountNameExp = RegExp(r'[a-z0-9]{5,20}$');
		RegExp passwordExp = RegExp(r'[A-Za-z0-9!@^]{5,16}$');
		RegExp phoneNumberExp = RegExp(r'010\d{8}');
		if (!accountNameExp.hasMatch(accountName)) {
			errorDialog('아이디는 5~20자의 영문 소문자, 숫자만 사용 가능합니다.');
		} else if (password != passwordAgain) {
			errorDialog('비밀번호가 일치하지 않습니다');
		} else if (!passwordExp.hasMatch(password)) {
			errorDialog('비밀번호는 5~16자의 영문 대소문자, 숫자, @!^ 만 사용 가능합니다.');
		} else if (!phoneNumberExp.hasMatch(phoneNumber)) {
			errorDialog('유효하지 않은 전화번호입니다.\n -을 제외한 11자리 번호만 입력해주세요!');
		} else {
			// Now, Trying to POST signup. . . 
			if (await postUser(accountName, password, phoneNumber) ==
			true) {
				Navigator.pushNamed(context, '/namecard',
				arguments: {'nowId': uniqueID});
			} else {
				errorDialog(errorDetail);
			}
		}
	}
),

이렇게 짠 이유는, API 요청을 최대한 덜 보내기 위해서이다.
조건에 부합하지 않는 아이디/비밀번호/휴대전화번호 인데 그걸 매번 POST 요청을 보내는 것은 효율적이지 않다고 생각했기 때문이다. 따라서, 굳이 DB를 확인하지 않아도 FE쪽에서 처리 가능한 것은 최대한 처리하고, 조건을 모두 충족하는 경우에만 POST 요청을 보내도록 구성한 것이다!

프로필페이지 접속 (마이페이지, 친구페이지)

또한 프로필페이지로 넘기는 로직도 개선했다. 프로필페이지로 넘기는 루트는 2가지가 있다.

  • 현재 접속중인 유저가 '마이페이지' 탭을 누르는 경우
// Flutter (FE)
// mypage.dart

class _MyProfileState extends State<MyProfile> {
  ...
  @override
  Widget build(BuildContext context) {
    return ProfilePage();
  }
}
  • 현재 접속중인 유저가 명함첩에 있는 친구를 탭하는 경우
// Flutter (FE)
// contacts.dart

class _ContactsPageState extends State<ContactsPage> {
	...
    @override
  	Widget build(BuildContext context) {
    	return Scaffold(
      		appBar: AppBar(...),
            body: ListView.separated(
          		padding: EdgeInsets.fromLTRB(15, 10, 15, 10),
          		itemCount: Friends.length,
          		itemBuilder: (c, i) {
            		return InkWell(
              			onTap: () => Navigator.push(
			                  context,
            			      MaterialPageRoute(
                      			builder: (c) => ProfilePage(friendId: friendId))),
              			child: Container(...)
                        ...

프로필페이지의 UI는 마이페이지나 친구페이지나 동일한데, 넘기는 루트가 다르다보니 어떻게 처리해줘야하나 고민이 많았다. 우선 돌아가기 위해 생각한 방법은, 프로필페이지에 인자가 넘어오면 친구페이지로, 안 넘어오면 마이페이지로 가도록 만드는 것이다. 임시방편으로..

saveData() async {
    var storage = await SharedPreferences.getInstance();
    setState(() {
      myId = storage.getInt('id');
    });
    if (widget.friendId != null) {
      getCard(widget.friendId);
    } else {
      getCard(myId);
    }
  }

하고나서 생각해보니까, 어차피 사용자가 '마이페이지'로 접속하려면 로그인을 한 후에야 가능하니까, 마이페이지로 넘길 때도 그냥 id값으로 넘기게 하면 더 직관적일것 같다. 조만간 반영해야겠다 !

알고리즘 스터디

오늘 푼 문제는 2xn 타일링 이라는 문제였다.
어디서 본 거 같은데...? 심지어 이번엔 2x2 크기의 박스도 없다...?
이런.... 그럼 그냥 피보나치 수열이다.
왜냐하면, n이 3이상일 때부터는 다음과 같은 점화식이 성립하기 때문이다.

우선, 가로 길이가 n-1였을 때를 기준으로 길이를 n으로 만드는 방법은 세로 직사각형을 하나 붙이는 방법 딱 하나이다. 그리고 가로 길이가 n-2였을 때를 기준으로 길이를 n으로 만드는 방법은 세로로 2개 붙이는 방법, 그리고 가로로 2개를 붙이는 방법 해서 2가지이다. 하지만 세로로 붙이는 방법의 경우, 앞서 가로 길이가 n-1이었을 때 이미 처리해주었기에 중복이다. 따라서, 이 경우에도 결국 방법은 1가지다.

이를 코드를 구성하면 다음과 같다. 슛 !

# 가로 2 세로 1
def solution(n):
    DP = [0]*(n+1)
    DP[1] = 1
    DP[2] = 2
    # DP[3] = DP[2]*1 (-1에서는 세로로 하나 세우기라 한가지 방법뿐) + DP[1]*1 (-2에서는 옆으로 두개 눕히기 한가지 방법뿐)
    for i in range(3,n+1):
        DP[i] = (DP[i-1] + DP[i-2]) % 1000000007
    return DP[n] 
    
post-custom-banner

0개의 댓글