1년만에 홈페이지 만들기를 다시 올려보네요...
너무 게을렀네요.. 앞으로 추가적인 기능을 더 올려보도록 하겠습니다.
1년전과 이어서 오늘은 아이디&비밀번호 찾기에 대해 포스팅해보겠습니다.
아래는 구현 영상입니다. 흔히 사용하는 기능을 간단히 만들어보았습니다.
디자인 압축파일은 아래있으니 다운받으셔서 활용하셔도 괜찮습니다.
login.jsp
1
2
3
4
5
6
|
<a href="#" class="btn btn-primary btn-user btn-block" onclick="return frmCheck();">로그인</a>
<hr>
<a href="/account/register" class="btn btn-google btn-user btn-block">회원가입</a>
<a href="/account/search_id" class="btn btn-facebook btn-user btn-block">아이디 찾기</a>
<a href="/account/search_pwd" class="btn btn-warning btn-user btn-block">비밀번호찾기 찾기</a>
|
cs |
우선 로그인화면에서 아이디 찾기와 비밀번호 찾기 버튼을 만들어줍니다.
AccountController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@RequestMapping(value = "/account/search_id", method = RequestMethod.GET)
public String search_id(HttpServletRequest request, Model model,
memberVO searchVO) {
return "/account/search_id";
}
@RequestMapping(value = "/account/search_pwd", method = RequestMethod.GET)
public String search_pwd(HttpServletRequest request, Model model,
memberVO searchVO) {
return "/account/search_pwd";
}
|
cs |
서버와 jsp를 생성시켜줍니다.
search_id : 아이디 찾기 페이지
search_result_id : 아이디 찾기결과 페이지
search_pwd : 비밀번호 찾기 페이지
search_result_pwd : 비밀번호 찾기 결과 페이지
search_id.jsp
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var path = "${pageContext.request.contextPath }";
$(document).ready(function() {
var msg = "${msg}";
if(msg != ""){
alert(msg);
}
});
function fnSubmit() {
var email_rule = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i;
var tel_rule = /^\d{2,3}-\d{3,4}-\d{4}$/;
if ($("#me_name").val() == null || $("#me_name").val() == "") {
alert("이름을 입력해주세요.");
$("#me_name").focus();
return false;
}
if ($("#me_tel").val() == null || $("#me_tel").val() == "") {
alert("전화번호를 입력해주세요.");
$("#me_tel").focus();
return false;
}
if(!tel_rule.test($("#me_tel").val())){
alert("전화번호 형식에 맞게 입력해주세요.");
return false;
}
if (confirm("아이디를 찾으시겠습니까?")) {
$("#createForm").submit();
return false;
}
}
</script>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin 2 - Register</title>
<!-- Custom fonts for this template-->
<link href="/resources/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="/resources/css/sb-admin-2.min.css" rel="stylesheet">
</head>
<form commandName="searchVO" id="createForm" action="${path}/account/search_result_id" method="post">
<body class="bg-gradient-primary">
<div class="container">
<!-- Outer Row -->
<div class="row justify-content-center">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-password-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-2">Forgot Your ID?</h1>
<p class="mb-4">We get it, stuff happens. Just enter your name and phon number below
and we'll send you a link to reset your ID!</p>
</div>
<div class="form-group">
<input type="text" class="form-control form-control-user"
id="me_name" name="me_name"
placeholder="Enter name...">
</div>
<div class="form-group">
<input type="email" class="form-control form-control-user"
id="me_tel" name="me_tel"
placeholder="Enter phon number...">
</div>
<a href="javascript:void(0)" onclick="fnSubmit(); return false;" class="btn btn-primary btn-user btn-block">
Search ID
</a>
<hr>
<div class="text-center">
<a class="small" href="/account/register">Create an Account!</a>
</div>
<div class="text-center">
<a class="small" href="/account/login">Already have an account? Login!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>
</body>
</form>
</html>
|
cs |
아이디 찾기 페이지에서는 특별한 것은 없습니다.
이름과 전화번호를 담아주고 유효성 검사한 다음,
아이디 결과 찾기 주소인 /account/search_result_id 로 보내주면 됩니다.
AccountController (/account/search_result_id)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@RequestMapping(value = "/account/search_result_id")
public String search_result_id(HttpServletRequest request, Model model,
@RequestParam(required = true, value = "me_name") String me_name,
@RequestParam(required = true, value = "me_tel") String me_tel,
memberVO searchVO) {
try {
searchVO.setMe_name(me_name);
searchVO.setMe_tel(me_tel);
memberVO memberSearch = accountService.memberIdSearch(searchVO);
model.addAttribute("searchVO", memberSearch);
} catch (Exception e) {
System.out.println(e.toString());
model.addAttribute("msg", "오류가 발생되었습니다.");
}
return "/account/search_result_id";
}
|
cs |
서버에서는 이름과 전화번호를 RequestParam으로 받은 다음,
회원 정보를 조회한다음 model.addAttribute로 빼주기만 하면 됩니다.
service / serviceImpl / DAO / xml
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
|
service
memberVO memberIdSearch(memberVO searchVO);
serviceimpl
@Override
public memberVO memberIdSearch(memberVO searchVO) {
return mapper.memberIdSearch(searchVO);
}
dao
memberVO memberIdSearch(memberVO searchVO);
xml
<select id="memberIdSearch" parameterType="com.spring.web.vo.memberVO" resultType="com.spring.web.vo.memberVO">
<![CDATA[
select
*
from
tbl_member
where
me_name = #{me_name}
and
me_tel = #{me_tel}
]]>
</select>
|
cs |
search_result_id.jsp
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var path = "${pageContext.request.contextPath }";
$(document).ready(function() {
});
</script>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin 2 - Register</title>
<!-- Custom fonts for this template-->
<link href="/resources/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="/resources/css/sb-admin-2.min.css" rel="stylesheet">
</head>
<body class="bg-gradient-primary">
<div class="container">
<!-- Outer Row -->
<div class="row justify-content-center">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-password-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-2">Please check your ID</h1><br><br>
<c:choose>
<c:when test="${empty searchVO}">
<p class="mb-4">조회결과가 없습니다.</p>
</c:when>
<c:otherwise>
<p class="mb-4">${searchVO.me_id}</p>
</c:otherwise>
</c:choose>
</div>
<hr>
<div class="text-center">
<a class="small" href="/account/search_id">Forgot Your ID?</a>
</div>
<div class="text-center">
<a class="small" href="/account/register">Create an Account!</a>
</div>
<div class="text-center">
<a class="small" href="/account/login">Already have an account? Login!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>
</body>
</html>
|
cs |
jstl을 이용하여 searchVO가 비어있다면 조회결과 없다고 뜨고,
있다면 아이디를 보여주면 됩니다.
간단하죠? 다음은 패스워드 찾기를 해보겠습니다.
search_pwd.jsp
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var path = "${pageContext.request.contextPath }";
$(document).ready(function() {
var msg = "${msg}";
if(msg != ""){
alert(msg);
}
});
function fnSubmit() {
var email_rule = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i;
var tel_rule = /^\d{2,3}-\d{3,4}-\d{4}$/;
if ($("#me_id").val() == null || $("#me_id").val() == "") {
alert("아이디를 입력해주세요.");
$("#me_id").focus();
return false;
}
if ($("#me_name").val() == null || $("#me_name").val() == "") {
alert("이름을 입력해주세요.");
$("#me_name").focus();
return false;
}
if ($("#me_tel").val() == null || $("#me_tel").val() == "") {
alert("전화번호를 입력해주세요.");
$("#me_tel").focus();
return false;
}
if(!tel_rule.test($("#me_tel").val())){
alert("전화번호 형식에 맞게 입력해주세요.");
return false;
}
if (confirm("비밀번호를 찾으시겠습니까?")) {
$("#createForm").submit();
return false;
}
}
</script>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin 2 - Register</title>
<!-- Custom fonts for this template-->
<link href="/resources/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="/resources/css/sb-admin-2.min.css" rel="stylesheet">
</head>
<form commandName="searchVO" id="createForm" action="${path}/account/search_result_pwd" method="post">
<input type="hidden" id="me_id_yn" name="me_id_yn" value="N"/
<body class="bg-gradient-primary">
<div class="container">
<!-- Outer Row -->
<div class="row justify-content-center">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-password-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-2">Forgot Your Password?</h1>
<p class="mb-4">We get it, stuff happens. Just enter your ID, name, phon number below
and we'll send you a link to reset your password!</p>
</div>
<form class="user">
<div class="form-group">
<input type="text" class="form-control form-control-user"
id="me_id" name="me_id"
placeholder="Enter ID...">
</div>
<div class="form-group">
<input type="text" class="form-control form-control-user"
id="me_name" name="me_name"
placeholder="Enter name...">
</div>
<div class="form-group">
<input type="email" class="form-control form-control-user"
id="me_tel" name="me_tel"
placeholder="Enter phon number...">
</div>
<a href="javascript:void(0)" onclick="fnSubmit(); return false;" class="btn btn-primary btn-user btn-block">
Reset Password
</a>
</form>
<hr>
<div class="text-center">
<a class="small" href="/account/register">Create an Account!</a>
</div>
<div class="text-center">
<a class="small" href="/account/login">Already have an account? Login!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>
</body>
</form>
</html>
|
cs |
여기서는 아이디 찾기에서 이름과 전화번호 값에 아이디 값을 더해서 서버에 보내주는 역활을 합니다.
마찬가지로 유효성 검사 뒤, /account/search_result_pwd로 보내주는 거죠.
AccountController (/account/search_result_pwd)
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
|
@RequestMapping(value = "/account/search_result_pwd", method = RequestMethod.POST)
public String search_result_pwd(HttpServletRequest request, Model model,
@RequestParam(required = true, value = "me_name") String me_name,
@RequestParam(required = true, value = "me_tel") String me_tel,
@RequestParam(required = true, value = "me_id") String me_id,
memberVO searchVO) {
try {
searchVO.setMe_name(me_name);
searchVO.setMe_tel(me_tel);
searchVO.setMe_id(me_id);
int memberSearch = accountService.memberPwdCheck(searchVO);
if(memberSearch == 0) {
model.addAttribute("msg", "기입된 정보가 잘못되었습니다. 다시 입력해주세요.");
return "/account/search_pwd";
}
String newPwd = RandomStringUtils.randomAlphanumeric(10);
String enpassword = encryptPassword(newPwd);
searchVO.setMe_pwd(enpassword);
accountService.passwordUpdate(searchVO);
model.addAttribute("newPwd", newPwd);
} catch (Exception e) {
System.out.println(e.toString());
model.addAttribute("msg", "오류가 발생되었습니다.");
}
return "/account/search_result_pwd";
}
|
cs |
여기서는 파라미터로 받은 이름 / 전화번호 / 아이디를 조회해서
memberSearch가 0이면 다시 패스워드 찾기 페이지로 보내고,
정보가 있다면,
내장 함수인 randomAlphanumeric를 이용하여 새로운 패스워드를 만든 뒤,
암호화하여 패스워드만 업데이트 시킵니다.
그리고 마지막으로 암호화되기 전 새로운 패스워드를 빼어줍니다.
service / serviceImpl / DAO / xml
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
|
service
int memberPwdCheck(memberVO searchVO);
void passwordUpdate(memberVO searchVO);
serviceimpl
@Override
public int memberPwdCheck(memberVO searchVO) {
return mapper.memberPwdCheck(searchVO);
}
@Override
public void passwordUpdate(memberVO searchVO) {
mapper.passwordUpdate(searchVO);
}
dao
int memberPwdCheck(memberVO searchVO);
void passwordUpdate(memberVO searchVO);
xml
<select id="memberPwdCheck" parameterType="com.spring.web.vo.memberVO" resultType="java.lang.Integer">
<![CDATA[
select
count(*)
from
tbl_member
where
me_name = #{me_name}
and
me_tel = #{me_tel}
and
me_id = #{me_id}
]]>
</select>
<update id="passwordUpdate" parameterType="com.spring.web.vo.memberVO" >
UPDATE
tbl_member
SET
me_pwd = #{me_pwd}
where
me_name = #{me_name}
and
me_tel = #{me_tel}
and
me_id = #{me_id}
</update>
|
cs |
search_result_pwd.jsp
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var path = "${pageContext.request.contextPath }";
$(document).ready(function() {
});
</script>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin 2 - Register</title>
<!-- Custom fonts for this template-->
<link href="/resources/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="/resources/css/sb-admin-2.min.css" rel="stylesheet">
</head>
<body class="bg-gradient-primary">
<div class="container">
<!-- Outer Row -->
<div class="row justify-content-center">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-password-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-2">Please check your Password</h1><br><br>
<p class="mb-4">${newPwd}</p>
</div>
<hr>
<div class="text-center">
<a class="small" href="/account/search_pwd">Forgot Your Password?</a>
</div>
<div class="text-center">
<a class="small" href="/account/register">Create an Account!</a>
</div>
<div class="text-center">
<a class="small" href="/account/login">Already have an account? Login!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>
</body>
</html>
|
cs |
마지막으로 비밀번호 찾기 결과 페이지를 만들어서 새로운 암호를 넣어주면 완료가 됩니다.
'Website Production' 카테고리의 다른 글
spring 홈페이지 - (18) 마이페이지 정보&비밀번호 수정 (1) | 2022.09.12 |
---|---|
이클립스 War 파일 리눅스 압축 풀기 (프로젝트 서버 반영 작업) (0) | 2021.10.08 |
[Chrome] 특정 글자 찾아 변경해주는 프로그램 (Find & Replace for Text Editing) (0) | 2021.09.10 |
spring 홈페이지 - (16) 로그인/로그아웃 작업 (feat.아이디 기억하기) (21) | 2021.05.07 |
spring 홈페이지 - (15) 회원가입 작업 (feat.아이디 중복체크/비밀번호 암호화) (12) | 2021.04.01 |
댓글