Feat: Add Passport + JWT
增加 Passport 和 JWT 验证支持(目前为假验证)前端登录页面,部分页面内容和样式优化
This commit is contained in:
parent
b1a9e09396
commit
7823572503
|
|
@ -10,7 +10,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@midwayjs/hooks": "^3.0.0",
|
"@midwayjs/hooks": "^3.0.0",
|
||||||
"@midwayjs/hooks-kit": "^3.0.0",
|
"@midwayjs/hooks-kit": "^3.0.0",
|
||||||
|
"@midwayjs/jwt": "^3.3.5",
|
||||||
"@midwayjs/koa": "^3.3.0",
|
"@midwayjs/koa": "^3.3.0",
|
||||||
|
"@midwayjs/passport": "^3.3.5",
|
||||||
"@midwayjs/redis": "^3.3.2",
|
"@midwayjs/redis": "^3.3.2",
|
||||||
"@midwayjs/rpc": "^3.0.0",
|
"@midwayjs/rpc": "^3.0.0",
|
||||||
"@prisma/client": "^3.12.0",
|
"@prisma/client": "^3.12.0",
|
||||||
|
|
@ -18,6 +20,8 @@
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"isomorphic-unfetch": "^3.1.0",
|
"isomorphic-unfetch": "^3.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"passport": "^0.5.2",
|
||||||
|
"passport-jwt": "^4.0.0",
|
||||||
"prismjs": "^1.27.0",
|
"prismjs": "^1.27.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
|
@ -27,6 +31,7 @@
|
||||||
"@midwayjs/mock": "^3.3.0",
|
"@midwayjs/mock": "^3.3.0",
|
||||||
"@types/ioredis": "^4.28.10",
|
"@types/ioredis": "^4.28.10",
|
||||||
"@types/lodash": "^4.14.181",
|
"@types/lodash": "^4.14.181",
|
||||||
|
"@types/passport-jwt": "^3.0.6",
|
||||||
"@types/prismjs": "^1.26.0",
|
"@types/prismjs": "^1.26.0",
|
||||||
"@types/react": "^17.0.44",
|
"@types/react": "^17.0.44",
|
||||||
"@types/react-dom": "^17.0.15",
|
"@types/react-dom": "^17.0.15",
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ a{
|
||||||
.bili-info p{
|
.bili-info p{
|
||||||
-webkit-line-clamp: 1;
|
-webkit-line-clamp: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bili-box .bili-img{
|
.bili-box .bili-img{
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,15 @@ export const Donate = () => {
|
||||||
<h3>赞助我:</h3>
|
<h3>赞助我:</h3>
|
||||||
<p>本服务将长期免费提供,运行至今暂无任何打赏记录,故不进行展示。如您能为本站献上微薄之力,就是对我的最大支持了~ 记得备注「支持保罗的 API」噢!</p>
|
<p>本服务将长期免费提供,运行至今暂无任何打赏记录,故不进行展示。如您能为本站献上微薄之力,就是对我的最大支持了~ 记得备注「支持保罗的 API」噢!</p>
|
||||||
<div className="row center">
|
<div className="row center">
|
||||||
<div className="col-6 col-m-4 center-fixed">
|
<div className="col-4 center-fixed">
|
||||||
<img src={Alipay} alt="支付宝" />
|
<img src={Alipay} alt="支付宝" />
|
||||||
<p>支付宝</p>
|
<p>支付宝</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-6 col-m-4 center-fixed">
|
<div className="col-4 center-fixed">
|
||||||
<img src={WeChat} alt="微信支付" />
|
<img src={WeChat} alt="微信支付" />
|
||||||
<p>微信支付</p>
|
<p>微信支付</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-6 col-m-4 center-fixed">
|
<div className="col-4 center-fixed">
|
||||||
<img src={QQ} alt="QQ 钱包" />
|
<img src={QQ} alt="QQ 钱包" />
|
||||||
<p>QQ 钱包</p>
|
<p>QQ 钱包</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
import { IGlobalData } from "@/types/GlobalData";
|
||||||
|
|
||||||
|
import { initalState, IAction } from "@/hooks/useGlobalData";
|
||||||
|
|
||||||
|
const GlobalContext = createContext<{ globalData: IGlobalData, setGlobalData: React.Dispatch<IAction> }>({
|
||||||
|
globalData: initalState,
|
||||||
|
setGlobalData() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
GlobalContext.displayName = "GlobalContext";
|
||||||
|
|
||||||
|
export default GlobalContext;
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
// React
|
// React
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import useGlobalContext from "@/hooks/useGlobalContext";
|
||||||
|
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import Avatar from "../../images/avatar.jpg";
|
import Avatar from "@/images/avatar.jpg";
|
||||||
|
|
||||||
|
|
||||||
|
// Interface
|
||||||
|
import { MouseEvent } from "react";
|
||||||
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
function Aside() {
|
function Aside() {
|
||||||
|
const { globalData: { profile } } = useGlobalContext();
|
||||||
const [sideOpen, setSideOpen] = useState(false);
|
const [sideOpen, setSideOpen] = useState(false);
|
||||||
|
|
||||||
const toggleClick = () => {
|
const onMenuClick = (ev: MouseEvent<HTMLElement>) => {
|
||||||
|
let el = ev.target as HTMLElement;
|
||||||
|
|
||||||
|
el.tagName === "A" && setSideOpen(!sideOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onToggleClick = () => {
|
||||||
setSideOpen(!sideOpen);
|
setSideOpen(!sideOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={sideOpen ? "sidebar active" : "sidebar"}>
|
<aside className={sideOpen ? "sidebar active" : "sidebar"} onClick={onMenuClick}>
|
||||||
<div className="toggle" onClick={toggleClick}></div>
|
<div className="toggle" onClick={onToggleClick}></div>
|
||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<h1>保罗|API</h1>
|
<h1>保罗|API</h1>
|
||||||
|
|
@ -40,8 +51,10 @@ function Aside() {
|
||||||
</nav>
|
</nav>
|
||||||
</nav>
|
</nav>
|
||||||
<div className="user-area no-login">
|
<div className="user-area no-login">
|
||||||
<img src={Avatar} alt="头像" />
|
<Link to="/login">
|
||||||
<span className="username">未登录</span>
|
<img src={profile.avatar || Avatar} alt="头像" />
|
||||||
|
<span className="username">{profile.name || "未登录"}</span>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
// React
|
// React
|
||||||
import React, { useEffect, useLayoutEffect } from "react";
|
import React, { useEffect, useLayoutEffect } from "react";
|
||||||
|
import useGlobalData from "@/hooks/useGlobalData";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
import GlobalContext from "@/components/GlobalContext";
|
||||||
import Aside from "@/components/Layout/Aside";
|
import Aside from "@/components/Layout/Aside";
|
||||||
import Footer from "@/components/Layout/Footer";
|
import Footer from "@/components/Layout/Footer";
|
||||||
|
|
||||||
|
|
@ -18,6 +20,7 @@ interface FrontWrapperProps {
|
||||||
// Components
|
// Components
|
||||||
function FrontWrapper(props: FrontWrapperProps) {
|
function FrontWrapper(props: FrontWrapperProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
let [globalData, setGlobalData] = useGlobalData();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const name = import.meta.env.PAUL_SITENAME;
|
const name = import.meta.env.PAUL_SITENAME;
|
||||||
|
|
@ -35,11 +38,11 @@ function FrontWrapper(props: FrontWrapperProps) {
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<GlobalContext.Provider value={{ globalData, setGlobalData }}>
|
||||||
<Aside />
|
<Aside />
|
||||||
{props.element}
|
{props.element}
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</GlobalContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { useContext } from "react";
|
||||||
|
|
||||||
|
import GlobalContext from "@/components/GlobalContext";
|
||||||
|
|
||||||
|
function useGlobalContext() {
|
||||||
|
return useContext(GlobalContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useGlobalContext;
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { useReducer } from "react";
|
||||||
|
|
||||||
|
import { IGlobalData } from "@/types/GlobalData";
|
||||||
|
|
||||||
|
export type IAction =
|
||||||
|
| { type: "all", value: IGlobalData }
|
||||||
|
| { type: "profile", value: Partial<IGlobalData["profile"]> }
|
||||||
|
|
||||||
|
export const initalState: IGlobalData = {
|
||||||
|
profile: {
|
||||||
|
name: "",
|
||||||
|
avatar: ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function reducer(state: IGlobalData, action: IAction): IGlobalData {
|
||||||
|
if (!action) return state;
|
||||||
|
|
||||||
|
if (action.type === "all") {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (action.type === "profile") {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
profile: {
|
||||||
|
...state.profile,
|
||||||
|
...action.value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useGlobalData(): [IGlobalData, React.Dispatch<IAction>]{
|
||||||
|
let [state, dispatch] = useReducer(reducer, initalState);
|
||||||
|
|
||||||
|
return [state, dispatch];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useGlobalData;
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export interface IParamsOption {
|
||||||
|
defaultValue?: Record<string, any>;
|
||||||
|
removeBlank?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function useStat<T extends Record<string, any>>(defaultValue: T, options?: IParamsOption | undefined): [T, ((newState: Partial<T>) => T), (value: React.SetStateAction<T>) => void];
|
||||||
|
|
||||||
|
// 适合组件状态设置的归档和存储,不可还原,需要还原成初始字段可使用 useParams
|
||||||
|
function useStat(initValue = {}, options?: IParamsOption | undefined) {
|
||||||
|
const [state, setState] = useState(initValue);
|
||||||
|
|
||||||
|
// 删除没用的
|
||||||
|
const removeUndefined = (obj: Record<string, any>) => {
|
||||||
|
const _keys = Object.keys(obj);
|
||||||
|
|
||||||
|
_keys.forEach(item => {
|
||||||
|
if (options?.removeBlank) {
|
||||||
|
(obj[item] === undefined || obj[item] === "") && delete obj[item];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
obj[item] === undefined && delete obj[item];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 覆盖部分值
|
||||||
|
const setStat = (newState: Record<string, any> = {}) => {
|
||||||
|
setState(prevState => removeUndefined({ ...prevState, ...newState }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [state, setStat, setState];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useStat;
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Paul API
|
# Paul API
|
||||||
# By: Dreamer-Paul
|
# By: Dreamer-Paul
|
||||||
# Last Update: 2021.12.16
|
# Last Update: 2022.5.3
|
||||||
|
|
||||||
保罗的首页、作品和 API 页通用模板
|
保罗的首页、作品和 API 页通用模板
|
||||||
|
|
||||||
|
|
@ -108,6 +108,7 @@ button{
|
||||||
color: #fff;
|
color: #fff;
|
||||||
width: 10em;
|
width: 10em;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
user-select: none;
|
||||||
background-color: #3498db;
|
background-color: #3498db;
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
transition: transform .3s;
|
transition: transform .3s;
|
||||||
|
|
@ -146,6 +147,7 @@ button{
|
||||||
}
|
}
|
||||||
.sidebar nav i{
|
.sidebar nav i{
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
|
pointer-events: none;
|
||||||
transition: transform .3s;
|
transition: transform .3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +160,7 @@ button{
|
||||||
}
|
}
|
||||||
.sidebar a.active:before ,.sidebar .has-child > a:before{
|
.sidebar a.active:before ,.sidebar .has-child > a:before{
|
||||||
right: 0;
|
right: 0;
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border: .75em solid transparent;
|
border: .75em solid transparent;
|
||||||
border-right-color: #fff;
|
border-right-color: #fff;
|
||||||
|
|
@ -177,22 +179,28 @@ button{
|
||||||
box-shadow: .25em 0 .5em rgba(0, 0, 0, .2);
|
box-shadow: .25em 0 .5em rgba(0, 0, 0, .2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar a.active:before ,.sidebar .has-child > a:before{
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
aside .toggle{
|
aside .toggle{
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 10em;
|
left: 10em;
|
||||||
z-index: 3;
|
|
||||||
color: #fff;
|
|
||||||
width: 2.5em;
|
|
||||||
height: 2.5em;
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
|
z-index: 3;
|
||||||
|
width: 3em;
|
||||||
|
height: 3em;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 3em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
box-shadow: .25em 0 .5em rgba(0, 0, 0, .2);
|
box-shadow: .25em 0 .5em rgba(0, 0, 0, .2);
|
||||||
}
|
}
|
||||||
aside .toggle:before{
|
aside .toggle:before{
|
||||||
content: "\f0c9";
|
content: "\f0c9";
|
||||||
line-height: 2.5em;
|
font-size: 1.2em;
|
||||||
font-family: "FontAwesome";
|
font-family: "FontAwesome";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,20 +215,28 @@ button{
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .user-area{
|
.sidebar .user-area{
|
||||||
cursor: pointer;
|
transition: background-color .3s;
|
||||||
user-select: none;
|
background-color: rgba(0, 0, 0, .1);
|
||||||
padding: .75em 1em;
|
|
||||||
transition: background .3s;
|
|
||||||
border-top: 1px solid rgba(0, 0, 0, .15);
|
|
||||||
}
|
}
|
||||||
.sidebar .user-area:hover{ background: rgba(0, 0, 0, .2) }
|
.sidebar .user-area:hover{ background-color: rgba(0, 0, 0, .2) }
|
||||||
|
|
||||||
|
.sidebar .user-area a{
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
padding: .75em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar .user-area img{
|
.sidebar .user-area img{
|
||||||
width: 2em;
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
object-fit: cover;
|
||||||
border-radius: 66%;
|
border-radius: 66%;
|
||||||
|
pointer-events: none;
|
||||||
border: 2px solid #fff;
|
border: 2px solid #fff;
|
||||||
}
|
}
|
||||||
.sidebar .user-area .username{
|
.sidebar .user-area .username{
|
||||||
margin-left: .5em;
|
margin-left: .5em;
|
||||||
|
pointer-events: none;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
import Home from "./pages/index";
|
import Home from "./pages/index";
|
||||||
|
import Login from "./pages/login";
|
||||||
import Notice from "./pages/notice";
|
import Notice from "./pages/notice";
|
||||||
import Log from "./pages/log";
|
import Log from "./pages/log";
|
||||||
import Netease from "./pages/netease";
|
import Netease from "./pages/netease";
|
||||||
|
|
@ -32,6 +33,9 @@ function App() {
|
||||||
<Route path="/" element={
|
<Route path="/" element={
|
||||||
<FrontWrapper element={<Home />} />}
|
<FrontWrapper element={<Home />} />}
|
||||||
/>
|
/>
|
||||||
|
<Route path="/login" element={
|
||||||
|
<FrontWrapper title="登录" element={<Login />} /> }
|
||||||
|
/>
|
||||||
<Route path="/notice" element={
|
<Route path="/notice" element={
|
||||||
<FrontWrapper title="使用约定" element={<Notice />} /> }
|
<FrontWrapper title="使用约定" element={<Notice />} /> }
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ const apiMap: Record<string, string> = {
|
||||||
|
|
||||||
function Index() {
|
function Index() {
|
||||||
|
|
||||||
const { data: stat } = useRequest(() => getStat());
|
const { data: stat } = useRequest(getStat);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
|
|
@ -87,7 +87,6 @@ function Index() {
|
||||||
<About />
|
<About />
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
// React
|
||||||
|
import React from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useRequest } from "ahooks";
|
||||||
|
import useStat from "@/hooks/useStat";
|
||||||
|
import useGlobalContext from "@/hooks/useGlobalContext";
|
||||||
|
|
||||||
|
|
||||||
|
// UI
|
||||||
|
import ArticleHead from "@/components/Layout/ArticleHead";
|
||||||
|
|
||||||
|
|
||||||
|
// Tool
|
||||||
|
import loginRequest from "../server/api/auth/login";
|
||||||
|
|
||||||
|
|
||||||
|
// Interface
|
||||||
|
import { MouseEvent, ChangeEvent } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
// Components
|
||||||
|
function Login() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { globalData, setGlobalData } = useGlobalContext();
|
||||||
|
const { loading, run } = useRequest(
|
||||||
|
(params) => loginRequest({
|
||||||
|
query: params
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
setGlobalData({
|
||||||
|
type: "profile",
|
||||||
|
value: data.profile
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate("/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [input, setInput] = useStat({
|
||||||
|
username: "",
|
||||||
|
password: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (ev: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
run(input);
|
||||||
|
|
||||||
|
console.log("ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
const onInputChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setInput({ [ev.target.name]: ev.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<ArticleHead title="登录" desc="目前来说,登录是没有用的" />
|
||||||
|
<form>
|
||||||
|
<fieldset>
|
||||||
|
<label>
|
||||||
|
<input type="text" name="username" placeholder="用户名"
|
||||||
|
value={input.username} onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="password" name="password" placeholder="密码"
|
||||||
|
value={input.password} onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<button className="btn green" onClick={onSubmit}>
|
||||||
|
{
|
||||||
|
loading ? <i className="fa fa-spinner fa-spin"></i> : "登录"
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Api, Get, Query, useContext, useInject } from "@midwayjs/hooks";
|
import { Api, Get, Query, useContext, useInject } from "@midwayjs/hooks";
|
||||||
import { RedisService } from "@midwayjs/redis";
|
import { RedisService } from "@midwayjs/redis";
|
||||||
import { prisma } from "../../utils/prisma";
|
import { prisma } from "../utils/prisma";
|
||||||
|
|
||||||
import { getSong } from "../../utils/netease";
|
import { getSong } from "../utils/netease";
|
||||||
|
|
||||||
export default Api(
|
export default Api(
|
||||||
Get(),
|
Get(),
|
||||||
|
|
@ -20,7 +20,7 @@ export default Api(
|
||||||
|
|
||||||
const dbCount = await prisma.aCGM.count();
|
const dbCount = await prisma.aCGM.count();
|
||||||
const skip = Math.floor(Math.random() * dbCount);
|
const skip = Math.floor(Math.random() * dbCount);
|
||||||
|
|
||||||
const item = await prisma.aCGM.findMany({
|
const item = await prisma.aCGM.findMany({
|
||||||
take: 1,
|
take: 1,
|
||||||
skip
|
skip
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import { Api, Post, Query, useContext, useInject } from "@midwayjs/hooks";
|
import { Api, Post, Query, useContext, useInject, Middleware } from "@midwayjs/hooks";
|
||||||
import { RedisService } from "@midwayjs/redis";
|
import { RedisService } from "@midwayjs/redis";
|
||||||
import { prisma } from "../../utils/prisma";
|
import { JwtPassportMiddleware } from "../../../middleware/jwt.middleware";
|
||||||
|
|
||||||
import { getSong } from "../../utils/netease";
|
import { prisma } from "../../../utils/prisma";
|
||||||
|
|
||||||
|
import { getSong } from "../../../utils/netease";
|
||||||
|
|
||||||
export default Api(
|
export default Api(
|
||||||
Post(),
|
Post(),
|
||||||
|
Middleware(JwtPassportMiddleware),
|
||||||
Query<{ id: string, bangumi: string }>(),
|
Query<{ id: string, bangumi: string }>(),
|
||||||
async () => {
|
async () => {
|
||||||
const ctx = useContext();
|
const ctx = useContext();
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Api, Get, Query, useContext, useInject } from "@midwayjs/hooks";
|
||||||
|
import { JwtService } from '@midwayjs/jwt';
|
||||||
|
|
||||||
|
export default Api(
|
||||||
|
Get(),
|
||||||
|
Query<{ username: string, password: string }>(),
|
||||||
|
async () => {
|
||||||
|
const ctx = useContext();
|
||||||
|
|
||||||
|
const jwt = await useInject(JwtService);
|
||||||
|
|
||||||
|
const token = await jwt.sign({ name: "Paul" });
|
||||||
|
|
||||||
|
ctx.cookies.set("paul-token", token, {
|
||||||
|
maxAge: 60 * 60 * 24 * 2,
|
||||||
|
httpOnly: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
profile: {
|
||||||
|
name: "Paul",
|
||||||
|
avatar: "https://sdn.geekzu.org/avatar/d22eb460ecab37fcd7205e6a3c55c228?s=200&r=X&d=",
|
||||||
|
},
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -2,6 +2,8 @@ import { createConfiguration, hooks } from "@midwayjs/hooks";
|
||||||
import * as Koa from "@midwayjs/koa";
|
import * as Koa from "@midwayjs/koa";
|
||||||
import * as dotenv from "dotenv";
|
import * as dotenv from "dotenv";
|
||||||
import * as redis from "@midwayjs/redis";
|
import * as redis from "@midwayjs/redis";
|
||||||
|
import * as jwt from "@midwayjs/jwt";
|
||||||
|
import * as passport from "@midwayjs/passport";
|
||||||
|
|
||||||
const env = dotenv.config();
|
const env = dotenv.config();
|
||||||
|
|
||||||
|
|
@ -9,7 +11,9 @@ const env = dotenv.config();
|
||||||
* setup midway server
|
* setup midway server
|
||||||
*/
|
*/
|
||||||
export default createConfiguration({
|
export default createConfiguration({
|
||||||
imports: [Koa, redis, hooks()],
|
imports: [
|
||||||
|
Koa, redis, hooks(), jwt, passport
|
||||||
|
],
|
||||||
importConfigs: [{
|
importConfigs: [{
|
||||||
default: {
|
default: {
|
||||||
keys: "session_keys",
|
keys: "session_keys",
|
||||||
|
|
@ -20,6 +24,13 @@ export default createConfiguration({
|
||||||
db: 0,
|
db: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
jwt: {
|
||||||
|
secret: "Test000666hhhh",
|
||||||
|
expiresIn: "5h",
|
||||||
|
},
|
||||||
|
passport: {
|
||||||
|
session: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { Middleware } from "@midwayjs/decorator";
|
||||||
|
import { PassportMiddleware } from "@midwayjs/passport";
|
||||||
|
import { JwtStrategy } from "../strategy/jwt.strategy";
|
||||||
|
import * as passport from "passport";
|
||||||
|
import { NextFunction, Context } from '@midwayjs/koa';
|
||||||
|
|
||||||
|
@Middleware()
|
||||||
|
export class JwtPassportMiddleware extends PassportMiddleware(JwtStrategy) {
|
||||||
|
getAuthenticateOptions(): Promise<passport.AuthenticateOptions> | passport.AuthenticateOptions {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve() {
|
||||||
|
// return async (ctx: Context, next: NextFunction) => {
|
||||||
|
// const result = await next();
|
||||||
|
|
||||||
|
// console.log(result);
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// code: 0,
|
||||||
|
// msg: '111',
|
||||||
|
// data: result,
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
static getName(): string {
|
||||||
|
return "auth";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { useContext } from "@midwayjs/hooks";
|
||||||
|
import { CustomStrategy, PassportStrategy } from '@midwayjs/passport';
|
||||||
|
import { Strategy, ExtractJwt } from 'passport-jwt';
|
||||||
|
import { Config } from '@midwayjs/decorator';
|
||||||
|
|
||||||
|
@CustomStrategy()
|
||||||
|
export class JwtStrategy extends PassportStrategy(
|
||||||
|
Strategy,
|
||||||
|
'jwt'
|
||||||
|
) {
|
||||||
|
@Config('jwt')
|
||||||
|
jwtConfig;
|
||||||
|
|
||||||
|
async validate(payload) {
|
||||||
|
console.log('payload', payload);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStrategyOptions(): any {
|
||||||
|
const ctx = useContext();
|
||||||
|
|
||||||
|
console.log(ctx.headers, ctx.cookies.get("paul-token"));
|
||||||
|
|
||||||
|
return {
|
||||||
|
secretOrKey: this.jwtConfig.secret,
|
||||||
|
jwtFromRequest: () => ctx.cookies.get("paul-token") //ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IGlobalData {
|
||||||
|
profile: {
|
||||||
|
name: string
|
||||||
|
avatar: string
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue