Toggle navigation
Toggle navigation
This project
Loading...
Sign in
오윤석
/
maplespec.ga
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
4
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
오윤석
2020-06-18 04:53:45 +0900
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
99852b26f6224c7668fb172b681688b67b414d5e
99852b26
1 parent
3807d7cb
character model
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
255 additions
and
248 deletions
app/node/model/character.js
app/node/routes/character.js
app/node/model/character.js
0 → 100644
View file @
99852b2
axios
=
require
(
'axios'
);
const
crwalCharacterCode
=
async
function
(
nickname
,
isReboot
=
false
)
{
try
{
const
resp
=
await
axios
.
get
(
"https://maplestory.nexon.com/Ranking/World/Total?c="
+
encodeURI
(
nickname
)
+
"&w="
+
(
isReboot
?
"0"
:
"254"
));
const
regex
=
new
RegExp
(
`<dt><a href=\\"\\/Common\\/Character\\/Detail\\/[^\\?]+?\\?p=(.+?)\\"\\s+target=.+?\\/>
${
nickname
}
<\\/a><\\/dt>`
);
const
regexResult
=
regex
.
exec
(
resp
.
data
);
if
(
!
regexResult
)
{
if
(
isReboot
)
return
-
2
;
else
return
await
crwalCharacterCode
(
nickname
,
true
);
}
return
regexResult
[
1
];
}
catch
(
error
)
{
console
.
log
(
error
);
return
-
1
;
}
};
const
getCharacterInfo
=
async
function
(
nickname
,
characterCode
)
{
try
{
const
resp
=
await
axios
.
get
(
"https://maplestory.nexon.com/Common/Character/Detail/"
+
encodeURI
(
nickname
)
+
"?p="
+
characterCode
);
if
(
resp
.
data
.
indexOf
(
"공개하지 않은 정보입니다."
)
>=
0
)
{
throw
new
Error
(
"private_character"
);
}
if
(
resp
.
data
.
indexOf
(
"메이플스토리 게임 점검 중에는 이용하실 수 없습니다."
)
>=
0
)
{
throw
new
Error
(
"game_checking"
);
}
const
character
=
{
nickname
:
nickname
,
characterCode
:
characterCode
,
job
:
null
,
level
:
null
,
avatar
:
null
,
server
:
{
icon
:
null
,
name
:
null
},
majorName
:
null
,
attackPowerName
:
null
};
const
stats
=
{
major
:
0
,
minor
:
0
,
majorHyper
:
0
,
damageHyper
:
0
,
criticalDamage
:
0
,
bossAttackDamage
:
0
,
ignoreGuard
:
0
,
statAttackPower
:
0
};
const
{
JSDOM
}
=
require
(
'jsdom'
);
const
dom
=
new
JSDOM
(
resp
.
data
);
const
$
=
(
require
(
'jquery'
))(
dom
.
window
);
const
jobModel
=
require
(
'../model/job'
);
const
statModel
=
require
(
'../model/stat'
);
character
.
job
=
$
(
".tab01_con_wrap .table_style01:eq(0) tbody tr:eq(0) td:eq(1) span"
).
text
();
character
.
level
=
parseInt
(
$
(
".char_info dl:eq(0) dd"
).
text
().
substring
(
3
));
character
.
avatar
=
$
(
".char_img img"
).
attr
(
"src"
);
character
.
server
=
{
name
:
$
(
".char_info dl:eq(2) dd"
).
text
(),
icon
:
$
(
".char_info dl:eq(2) dd img"
).
attr
(
"src"
)
};
character
.
majorName
=
jobModel
[
character
.
job
].
major
;
character
.
attackPowerName
=
character
.
majorName
==
"INT"
?
"마력"
:
"공격력"
;
const
$statInfo
=
$
(
".tab01_con_wrap .table_style01:eq(1)"
);
$
(
"tbody tr"
,
$statInfo
).
each
(
function
()
{
if
(
$
(
"th"
,
this
).
length
==
1
)
{
if
(
$
(
"th span"
,
this
).
text
()
==
"하이퍼스탯"
)
{
const
values
=
$
(
"td span"
,
this
).
html
().
split
(
"<br>"
);
const
regexMajor
=
new
RegExp
(
`
${
statModel
[
character
.
majorName
].
korean
}
(\\d+) 증가`
);
const
regexDamage
=
new
RegExp
(
`^데미지 (\\d+)% 증가`
);
let
regexResult
;
for
(
let
i
=
0
;
i
<
values
.
length
;
i
++
)
{
if
(
regexResult
=
regexMajor
.
exec
(
values
[
i
]))
stats
[
'majorHyper'
]
=
parseInt
(
regexResult
[
1
]);
else
if
(
regexResult
=
regexDamage
.
exec
(
values
[
i
]))
stats
[
'damageHyper'
]
=
parseInt
(
regexResult
[
1
]);
}
}
}
else
{
for
(
let
i
=
0
;
i
<
2
;
i
++
)
{
const
statName
=
$
(
`th:eq(
${
i
}
) span`
,
this
).
text
();
const
value
=
$
(
`td:eq(
${
i
}
) span`
,
this
).
text
().
replace
(
/
\,
/g
,
""
);
switch
(
statName
)
{
case
character
.
majorName
:
stats
[
'major'
]
=
parseInt
(
value
);
break
;
case
jobModel
[
character
.
job
].
minor
:
stats
[
'minor'
]
=
parseInt
(
value
);
break
;
case
"크리티컬 데미지"
:
stats
[
'criticalDamage'
]
=
parseInt
(
value
);
break
;
case
"보스공격력"
:
stats
[
'bossAttackDamage'
]
=
parseInt
(
value
);
break
;
case
"방어율무시"
:
stats
[
'ignoreGuard'
]
=
parseInt
(
value
);
break
;
case
"스탯공격력"
:
stats
[
'statAttackPower'
]
=
parseInt
(
value
.
split
(
' ~ '
)[
1
]);
}
}
}
});
return
{
character
:
character
,
stats
:
stats
};
}
catch
(
error
)
{
console
.
log
(
error
);
if
(
error
.
message
==
"private_character"
)
return
-
1
;
else
if
(
error
.
message
==
"game_checking"
)
return
-
2
;
else
return
-
999
;
}
}
const
analyzeEquipment
=
async
function
(
nickname
,
characterCode
,
job
)
{
try
{
const
resp
=
await
axios
.
get
(
"https://maplestory.nexon.com/Common/Character/Detail/"
+
encodeURI
(
nickname
)
+
"/Equipment?p="
+
characterCode
);
if
(
resp
.
data
.
indexOf
(
"공개하지 않은 정보입니다."
)
>=
0
)
{
throw
new
Error
(
"private_character"
);
}
if
(
resp
.
data
.
indexOf
(
"메이플스토리 게임 점검 중에는 이용하실 수 없습니다."
)
>=
0
)
{
throw
new
Error
(
"game_checking"
);
}
const
{
JSDOM
}
=
require
(
'jsdom'
);
const
dom
=
new
JSDOM
(
resp
.
data
);
const
$
=
(
require
(
'jquery'
))(
dom
.
window
);
// 아케인심볼 분석
let
majorArcane
=
0
;
const
arcaneURLs
=
[];
$
(
".tab03_con_wrap .arcane_weapon_wrap .item_pot li span a"
).
each
(
async
function
()
{
if
(
!!
$
(
this
).
attr
(
"href"
))
arcaneURLs
.
push
(
"https://maplestory.nexon.com"
+
$
(
this
).
attr
(
"href"
));
});
for
(
let
i
=
0
;
i
<
arcaneURLs
.
length
;
i
++
)
{
const
equipmentResp
=
await
axios
.
get
(
arcaneURLs
[
i
],
{
headers
:
{
'X-Requested-With'
:
'XMLHttpRequest'
}
});
const
equipmentDom
=
new
JSDOM
(
equipmentResp
.
data
.
view
);
const
$equipment
=
(
require
(
'jquery'
))(
equipmentDom
.
window
);
majorArcane
+=
parseInt
(
$equipment
(
".stet_info ul li:eq(2) .point_td font:eq(0)"
).
text
().
substring
(
1
));
}
// 장비 분석
const
jobModel
=
require
(
'../model/job'
);
let
damagePercent
=
0
;
let
majorPercent
=
0
;
let
attackPowerPercent
=
0
;
let
weapon
=
undefined
;
const
equipmentURLs
=
[];
$
(
".tab01_con_wrap .weapon_wrap .item_pot li span a"
).
each
(
async
function
()
{
equipmentURLs
.
push
(
"https://maplestory.nexon.com"
+
$
(
this
).
attr
(
"href"
));
});
for
(
let
i
=
0
;
i
<
equipmentURLs
.
length
;
i
++
)
{
const
equipmentResp
=
await
axios
.
get
(
equipmentURLs
[
i
],
{
headers
:
{
'X-Requested-With'
:
'XMLHttpRequest'
}
});
const
equipmentDom
=
new
JSDOM
(
equipmentResp
.
data
.
view
);
const
$equipment
=
(
require
(
'jquery'
))(
equipmentDom
.
window
);
const
equipmentType
=
$equipment
(
".item_ability .ablilty02:eq(1) .job_name em"
).
text
();
if
(
equipmentType
.
indexOf
(
"손무기"
)
>=
0
&&
equipmentType
.
indexOf
(
"블레이드"
)
<
0
&&
equipmentType
.
indexOf
(
"대검"
)
<
0
)
{
weapon
=
equipmentType
.
split
(
" ("
)[
0
];
}
$equipment
(
".stet_info ul li"
).
each
(
function
()
{
const
regexMajor1
=
new
RegExp
(
`
${
jobModel
[
job
].
major
}
: \\+(\\d+)%`
);
const
regexMajor2
=
new
RegExp
(
`올스탯 : \\+(\\d+)%`
);
const
regexAttackPower
=
(
jobModel
[
job
].
major
==
"INT"
)
?
new
RegExp
(
`마력 : \\+(\\d+)%`
)
:
new
RegExp
(
`공격력 : \\+(\\d+)%`
);
const
regexDamage
=
new
RegExp
(
`^데미지 : \\+(\\d+)%`
);
if
(
$
(
this
).
find
(
".stet_th span"
).
text
()
==
"올스탯"
)
{
majorPercent
+=
parseInt
(
$
(
this
).
find
(
".point_td font:eq(0)"
).
text
().
substring
(
1
));
}
else
if
(
$
(
this
).
find
(
".stet_th span"
).
text
().
indexOf
(
"잠재옵션"
)
>=
0
)
{
const
values
=
$
(
this
).
find
(
".point_td"
).
html
().
split
(
"<br>"
);
for
(
let
j
=
0
;
j
<
values
.
length
;
j
++
)
{
const
value
=
values
[
j
].
trim
();
let
regexResult
;
if
(
regexResult
=
(
regexMajor1
.
exec
(
value
)
||
regexMajor2
.
exec
(
value
)))
{
majorPercent
+=
parseInt
(
regexResult
[
1
]);
}
else
if
(
regexResult
=
regexAttackPower
.
exec
(
value
))
{
attackPowerPercent
+=
parseInt
(
regexResult
[
1
]);
}
else
if
(
regexResult
=
regexDamage
.
exec
(
value
))
{
damagePercent
+=
parseInt
(
regexResult
[
1
]);
}
}
}
})
}
return
{
majorArcane
:
majorArcane
,
majorPercent
:
majorPercent
,
attackPowerPercent
:
attackPowerPercent
,
damagePercent
:
damagePercent
,
weapon
:
weapon
};
}
catch
(
error
)
{
console
.
log
(
error
);
if
(
error
.
message
==
"private_character"
)
return
-
1
;
else
if
(
error
.
message
==
"game_checking"
)
return
-
2
;
else
return
-
999
;
}
}
module
.
exports
=
{
crwalCharacterCode
:
crwalCharacterCode
,
getCharacterInfo
:
getCharacterInfo
,
analyzeEquipment
:
analyzeEquipment
,
}
\ No newline at end of file
app/node/routes/character.js
View file @
99852b2
axios
=
require
(
'axios'
);
const
crwalCharacterCode
=
async
function
(
nickname
,
isReboot
=
false
)
{
try
{
const
resp
=
await
axios
.
get
(
"https://maplestory.nexon.com/Ranking/World/Total?c="
+
encodeURI
(
nickname
)
+
"&w="
+
(
isReboot
?
"0"
:
"254"
));
const
regex
=
new
RegExp
(
`<dt><a href=\\"\\/Common\\/Character\\/Detail\\/[^\\?]+?\\?p=(.+?)\\"\\s+target=.+?\\/>
${
nickname
}
<\\/a><\\/dt>`
);
const
regexResult
=
regex
.
exec
(
resp
.
data
);
if
(
!
regexResult
)
{
if
(
isReboot
)
return
-
2
;
else
return
await
crwalCharacterCode
(
nickname
,
true
);
}
return
regexResult
[
1
];
}
catch
(
error
)
{
console
.
log
(
error
);
return
-
1
;
}
}
const
getCharacterInfo
=
async
function
(
nickname
,
characterCode
)
{
try
{
const
resp
=
await
axios
.
get
(
"https://maplestory.nexon.com/Common/Character/Detail/"
+
encodeURI
(
nickname
)
+
"?p="
+
characterCode
);
if
(
resp
.
data
.
indexOf
(
"공개하지 않은 정보입니다."
)
>=
0
)
{
throw
new
Error
(
"private_character"
);
}
if
(
resp
.
data
.
indexOf
(
"메이플스토리 게임 점검 중에는 이용하실 수 없습니다."
)
>=
0
)
{
throw
new
Error
(
"game_checking"
);
}
const
character
=
{
nickname
:
nickname
,
characterCode
:
characterCode
,
job
:
null
,
level
:
null
,
avatar
:
null
,
server
:
{
icon
:
null
,
name
:
null
},
majorName
:
null
,
attackPowerName
:
null
};
const
stats
=
{
major
:
0
,
minor
:
0
,
majorHyper
:
0
,
damageHyper
:
0
,
criticalDamage
:
0
,
bossAttackDamage
:
0
,
ignoreGuard
:
0
,
statAttackPower
:
0
};
const
{
JSDOM
}
=
require
(
'jsdom'
);
const
dom
=
new
JSDOM
(
resp
.
data
);
const
$
=
(
require
(
'jquery'
))(
dom
.
window
);
const
jobModel
=
require
(
'../model/job'
);
const
statModel
=
require
(
'../model/stat'
);
character
.
job
=
$
(
".tab01_con_wrap .table_style01:eq(0) tbody tr:eq(0) td:eq(1) span"
).
text
();
character
.
level
=
parseInt
(
$
(
".char_info dl:eq(0) dd"
).
text
().
substring
(
3
));
character
.
avatar
=
$
(
".char_img img"
).
attr
(
"src"
);
character
.
server
=
{
name
:
$
(
".char_info dl:eq(2) dd"
).
text
(),
icon
:
$
(
".char_info dl:eq(2) dd img"
).
attr
(
"src"
)
};
character
.
majorName
=
jobModel
[
character
.
job
].
major
;
character
.
attackPowerName
=
character
.
majorName
==
"INT"
?
"마력"
:
"공격력"
;
const
$statInfo
=
$
(
".tab01_con_wrap .table_style01:eq(1)"
);
$
(
"tbody tr"
,
$statInfo
).
each
(
function
()
{
if
(
$
(
"th"
,
this
).
length
==
1
)
{
if
(
$
(
"th span"
,
this
).
text
()
==
"하이퍼스탯"
)
{
const
values
=
$
(
"td span"
,
this
).
html
().
split
(
"<br>"
);
const
regexMajor
=
new
RegExp
(
`
${
statModel
[
character
.
majorName
].
korean
}
(\\d+) 증가`
);
const
regexDamage
=
new
RegExp
(
`^데미지 (\\d+)% 증가`
);
let
regexResult
;
for
(
let
i
=
0
;
i
<
values
.
length
;
i
++
)
{
if
(
regexResult
=
regexMajor
.
exec
(
values
[
i
]))
stats
[
'majorHyper'
]
=
parseInt
(
regexResult
[
1
]);
else
if
(
regexResult
=
regexDamage
.
exec
(
values
[
i
]))
stats
[
'damageHyper'
]
=
parseInt
(
regexResult
[
1
]);
}
}
}
else
{
for
(
let
i
=
0
;
i
<
2
;
i
++
)
{
const
statName
=
$
(
`th:eq(
${
i
}
) span`
,
this
).
text
();
const
value
=
$
(
`td:eq(
${
i
}
) span`
,
this
).
text
().
replace
(
/
\,
/g
,
""
);
switch
(
statName
)
{
case
character
.
majorName
:
stats
[
'major'
]
=
parseInt
(
value
);
break
;
case
jobModel
[
character
.
job
].
minor
:
stats
[
'minor'
]
=
parseInt
(
value
);
break
;
case
"크리티컬 데미지"
:
stats
[
'criticalDamage'
]
=
parseInt
(
value
);
break
;
case
"보스공격력"
:
stats
[
'bossAttackDamage'
]
=
parseInt
(
value
);
break
;
case
"방어율무시"
:
stats
[
'ignoreGuard'
]
=
parseInt
(
value
);
break
;
case
"스탯공격력"
:
stats
[
'statAttackPower'
]
=
parseInt
(
value
.
split
(
' ~ '
)[
1
]);
}
}
}
});
return
{
character
:
character
,
stats
:
stats
};
}
catch
(
error
)
{
console
.
log
(
error
);
if
(
error
.
message
==
"private_character"
)
return
-
1
;
else
if
(
error
.
message
==
"game_checking"
)
return
-
2
;
else
return
-
999
;
}
}
const
analyzeEquipment
=
async
function
(
nickname
,
characterCode
,
job
)
{
try
{
const
resp
=
await
axios
.
get
(
"https://maplestory.nexon.com/Common/Character/Detail/"
+
encodeURI
(
nickname
)
+
"/Equipment?p="
+
characterCode
);
if
(
resp
.
data
.
indexOf
(
"공개하지 않은 정보입니다."
)
>=
0
)
{
throw
new
Error
(
"private_character"
);
}
if
(
resp
.
data
.
indexOf
(
"메이플스토리 게임 점검 중에는 이용하실 수 없습니다."
)
>=
0
)
{
throw
new
Error
(
"game_checking"
);
}
const
{
JSDOM
}
=
require
(
'jsdom'
);
const
dom
=
new
JSDOM
(
resp
.
data
);
const
$
=
(
require
(
'jquery'
))(
dom
.
window
);
// 아케인심볼 분석
let
majorArcane
=
0
;
const
arcaneURLs
=
[];
$
(
".tab03_con_wrap .arcane_weapon_wrap .item_pot li span a"
).
each
(
async
function
()
{
if
(
!!
$
(
this
).
attr
(
"href"
))
arcaneURLs
.
push
(
"https://maplestory.nexon.com"
+
$
(
this
).
attr
(
"href"
));
});
for
(
let
i
=
0
;
i
<
arcaneURLs
.
length
;
i
++
)
{
const
equipmentResp
=
await
axios
.
get
(
arcaneURLs
[
i
],
{
headers
:
{
'X-Requested-With'
:
'XMLHttpRequest'
}
});
const
equipmentDom
=
new
JSDOM
(
equipmentResp
.
data
.
view
);
const
$equipment
=
(
require
(
'jquery'
))(
equipmentDom
.
window
);
majorArcane
+=
parseInt
(
$equipment
(
".stet_info ul li:eq(2) .point_td font:eq(0)"
).
text
().
substring
(
1
));
}
// 장비 분석
const
jobModel
=
require
(
'../model/job'
);
let
damagePercent
=
0
;
let
majorPercent
=
0
;
let
attackPowerPercent
=
0
;
let
weapon
=
undefined
;
const
equipmentURLs
=
[];
$
(
".tab01_con_wrap .weapon_wrap .item_pot li span a"
).
each
(
async
function
()
{
equipmentURLs
.
push
(
"https://maplestory.nexon.com"
+
$
(
this
).
attr
(
"href"
));
});
for
(
let
i
=
0
;
i
<
equipmentURLs
.
length
;
i
++
)
{
const
equipmentResp
=
await
axios
.
get
(
equipmentURLs
[
i
],
{
headers
:
{
'X-Requested-With'
:
'XMLHttpRequest'
}
});
const
equipmentDom
=
new
JSDOM
(
equipmentResp
.
data
.
view
);
const
$equipment
=
(
require
(
'jquery'
))(
equipmentDom
.
window
);
const
equipmentType
=
$equipment
(
".item_ability .ablilty02:eq(1) .job_name em"
).
text
();
if
(
equipmentType
.
indexOf
(
"손무기"
)
>=
0
&&
equipmentType
.
indexOf
(
"블레이드"
)
<
0
&&
equipmentType
.
indexOf
(
"대검"
)
<
0
)
{
weapon
=
equipmentType
.
split
(
" ("
)[
0
];
}
$equipment
(
".stet_info ul li"
).
each
(
function
()
{
const
regexMajor1
=
new
RegExp
(
`
${
jobModel
[
job
].
major
}
: \\+(\\d+)%`
);
const
regexMajor2
=
new
RegExp
(
`올스탯 : \\+(\\d+)%`
);
const
regexAttackPower
=
(
jobModel
[
job
].
major
==
"INT"
)
?
new
RegExp
(
`마력 : \\+(\\d+)%`
)
:
new
RegExp
(
`공격력 : \\+(\\d+)%`
);
const
regexDamage
=
new
RegExp
(
`^데미지 : \\+(\\d+)%`
);
if
(
$
(
this
).
find
(
".stet_th span"
).
text
()
==
"올스탯"
)
{
majorPercent
+=
parseInt
(
$
(
this
).
find
(
".point_td font:eq(0)"
).
text
().
substring
(
1
));
}
else
if
(
$
(
this
).
find
(
".stet_th span"
).
text
().
indexOf
(
"잠재옵션"
)
>=
0
)
{
const
values
=
$
(
this
).
find
(
".point_td"
).
html
().
split
(
"<br>"
);
for
(
let
j
=
0
;
j
<
values
.
length
;
j
++
)
{
const
value
=
values
[
j
].
trim
();
let
regexResult
;
if
(
regexResult
=
(
regexMajor1
.
exec
(
value
)
||
regexMajor2
.
exec
(
value
)))
{
majorPercent
+=
parseInt
(
regexResult
[
1
]);
}
else
if
(
regexResult
=
regexAttackPower
.
exec
(
value
))
{
attackPowerPercent
+=
parseInt
(
regexResult
[
1
]);
}
else
if
(
regexResult
=
regexDamage
.
exec
(
value
))
{
damagePercent
+=
parseInt
(
regexResult
[
1
]);
}
}
}
})
}
return
{
majorArcane
:
majorArcane
,
majorPercent
:
majorPercent
,
attackPowerPercent
:
attackPowerPercent
,
damagePercent
:
damagePercent
,
weapon
:
weapon
};
}
catch
(
error
)
{
console
.
log
(
error
);
if
(
error
.
message
==
"private_character"
)
return
-
1
;
else
if
(
error
.
message
==
"game_checking"
)
return
-
2
;
else
return
-
999
;
}
}
const
characterModel
=
require
(
'../model/character'
);
const
analyzeStats
=
function
(
characterInfo
,
analysisEquipment
)
{
const
jobModel
=
require
(
'../model/job'
);
...
...
@@ -401,7 +157,7 @@ module.exports = {
}
const
nickname
=
req
.
query
.
nickname
;
const
characterCode
=
await
crwalCharacterCode
(
req
.
query
.
nickname
);
const
characterCode
=
await
c
haracterModel
.
c
rwalCharacterCode
(
req
.
query
.
nickname
);
if
(
characterCode
==
-
1
)
{
res
.
status
(
500
).
send
();
...
...
@@ -411,7 +167,7 @@ module.exports = {
return
;
}
const
characterInfo
=
await
getCharacterInfo
(
nickname
,
characterCode
);
const
characterInfo
=
await
characterModel
.
getCharacterInfo
(
nickname
,
characterCode
);
if
(
characterInfo
==
-
1
)
{
// 접근 권한 설정 필요
res
.
status
(
403
).
send
();
...
...
@@ -425,7 +181,7 @@ module.exports = {
return
;
}
const
analysisEquipment
=
await
analyzeEquipment
(
nickname
,
characterCode
,
characterInfo
.
character
.
job
);
const
analysisEquipment
=
await
characterModel
.
analyzeEquipment
(
nickname
,
characterCode
,
characterInfo
.
character
.
job
);
if
(
analysisEquipment
==
-
1
)
{
// 접근 권한 설정 필요
res
.
status
(
403
).
send
();
...
...
Please
register
or
login
to post a comment