15 KiB
15 KiB
title | tags | categories | date | summary | |||||
---|---|---|---|---|---|---|---|---|---|
毕业设计(4) |
|
毕业设计 | 2022-12-14 12:40:32 | 登录实现 |
十、管理登录逻辑后端实现
管理员账号的账号密码从配置文件中读写
@RestController
@PropertySource("classpath:config/config.properties")
@RequestMapping("/admin")
public class AdminController {
@Value("${admin.username}")
String username;
@Value("${admin.password}")
String password;
@PostMapping("/login")
public ResponseEntity<Resp<HashMap<String, String>>> login(@RequestBody Map<String,String> params){
String user = params.get("phone");
String pass = params.get("password");
HashMap<String, String> map = new HashMap<>();
if (user.equals(username) && pass.equals(password)){
map.put("phone",username);
map.put("password",password);
map.put("type","admin");
String token = Jwt.sign(map);
map.remove("password");
map.put("token",token);
return ResponseEntity.ok(Resp.Ok(map));
}
return ResponseEntity.status(HttpStatus.PAYMENT_REQUIRED).body(Resp.Err(403,null,map));
}
}
从 config.propertie 文件中中读取管理员的账号密码,然后配置登录的controller类,首先判断账号密码是否正确,正确就调用 Jwt 生成登录token,返回token到前端。
鉴权拦截器
在spring mvc的配置文件中配置拦截器,配置除了登陆地址以外全部拦截
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<!-- 配置三个登陆地址不进行鉴权 -->
<mvc:exclude-mapping path="/admin/login"/>
<mvc:exclude-mapping path="/teacher/login"/>
<mvc:exclude-mapping path="/student/login"/>
<bean class="org.gjs.interceptor.AuthInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
拦截器类
@PropertySource("classpath:config/config.properties")
public class AuthInterceptor implements HandlerInterceptor {
@Value("${admin.username}")
String username;
@Value("${admin.password}")
String password;
@Resource
StudentService studentService;
@Resource
TeacherService teacherService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authorization = request.getHeader("Authorization");
if (authorization == null || authorization.equals("")){
response.sendError(401,"the auth fail!");
return false;
}else {
HashMap<String,Object> map = Jwt.verify(authorization.split(" ")[1], HashMap.class);
if (map==null){
throw new MyException("未登录!");
}
request.getSession().setAttribute("login_type",map.get("type"));
Object type = map.get("type");
if ("admin".equals(type)) {
if (map.get("phone").equals(username) && map.get("password").equals(password)) {
return true;
} else {
throw new MyException("未登录!");
}
} else if ("student".equals(type)) {
Student student = new Student();
student.setPhone((String) map.get("phone"));
student.setId((Integer) map.get("id"));
Page<Student> students = studentService.queryByPage(student, PageRequest.of(0, 100));
return students.getContent().size() >= 1;
} else if ("teacher".equals(type)) {
Teacher teacher = new Teacher();
teacher.setPhone((String) map.get("phone"));
teacher.setId((Integer) map.get("id"));
Page<Teacher> teachers = teacherService.queryByPage(teacher, PageRequest.of(0, 100));
return teachers.getContent().size() >= 1;
}
throw new Exception("权限检测错误!");
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
调用jwt解析前端携带的token,检测账号密码是否正确,如果正确则返回 true,否则抛出异常错误。
十一、前端基本配置
项目前端采用 react+ant design编写,语言使用 typescript ,开发工具使用 webstorm 。
首先使用webstorm创建react项目,使用typescript模板,然后添加依赖
添加依赖
yarn add antd
yarn add axios
yarn add react-router-dom
yarn add http-proxy-middleware -D
因为在项目开发过程中,需要解决跨域的问题,所以我们使用开发依赖 http-proxy-middleware,将前端的接口转发到本地开发地址。
解决本地调试跨域
setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use("/api",createProxyMiddleware({
target: "http://127.0.0.1:8080",
changeOrigin: true,
pathRewrite:{
"/api":"/"
}
}))
}
配置项目前端路由,在index.tsx中添加 HashRouter节点
配置根路由
index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {HashRouter} from "react-router-dom";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<HashRouter>
<App />
</HashRouter>
</React.StrictMode>
);
reportWebVitals();
在App.tsx中添加基本页面,因为使用类组件开发,类组件中无法使用hooks等回调,所以将App组件使用WithRouter进行包装一下。
配置主页面
App.tsx
import React, {Component} from 'react';
import './App.css';
import {
useLocation,
useNavigate,
useParams,
Routes,
Route
} from "react-router-dom";
import LoginPage from "./pages/LoginPage";
import {PageProps} from "./models/props";
import AdminPage from "./pages/admin/AdminPage";
import TeacherPage from "./pages/teacher/TeacherPage";
import StudentPage from "./pages/student/StudentPage";
import AddTeacher from "./pages/admin/AddTeacher";
function withRouter(Component: any) {
function ComponentWithRouterProp(props: any) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{location, navigate, params}}
/>
);
}
return ComponentWithRouterProp;
}
class App extends Component<PageProps, any> {
render() {
return <>
<Routes>
<Route path={"/login"} element={<LoginPage router={this.props.router}/>}/>
<Route path={"/admin/*"} element={<AdminPage router={this.props.router}/>}>
</Route>
<Route path={"/teacher"} element={<TeacherPage router={this.props.router}/>}/>
<Route path={"/student"} element={<StudentPage router={this.props.router}/>}/>
<Route path={"/"} element={<LoginPage router={this.props.router}/>}/>
</Routes>
</>
}
}
export default withRouter(App);
十一、前端登录页面实现
首先封装axios,封装axios的接口为工具类
后端返回的json将其封装为 IResponseData
封装axios
request.ts
import axios, {AxiosInstance, AxiosRequestConfig, AxiosResponse} from "axios";
import {message} from "antd";
type TAxiosOption = {
baseURL: string;
timeout: number;
}
class Http {
service: AxiosInstance;
constructor(config:TAxiosOption) {
this.service = axios.create(config);
this.service.defaults.withCredentials = true
this.service.interceptors.request.use(
(value)=>{
if (value.headers !== null){
// @ts-ignore
value.headers.Authorization = "Bearer "+sessionStorage.getItem("work_token")
}
return value
},()=>{
console.log("请求异常")
})
this.service.interceptors.response.use((value:AxiosResponse<IResponseData<any>>)=>{
console.log(value)
return value
},(error)=>{
console.log(error.response.data.message)
if (error.response.data.message !== ""){
message.error(error.response.data.message).then(() => {})
}
if (error.response.status === 401){
window.location.hash = "/login"
}
return error
})
}
get<T>(url: string, params?: object, _object = {}): Promise<AxiosResponse<IResponseData<T>>> {
return this.service.get(url, { params, ..._object })
}
post<T>(url: string, data?: object, _object:AxiosRequestConfig = {}): Promise<AxiosResponse<IResponseData<T>>> {
return this.service.post(url, data, _object)
}
put<T>(url: string, params?: object, _object = {}): Promise<AxiosResponse<IResponseData<T>>> {
return this.service.put(url, params, _object)
}
delete<T>(url: string, params?: any, _object = {}): Promise<AxiosResponse<IResponseData<T>>> {
return this.service.delete(url, { params, ..._object })
}
}
export default Http
export interface IResponseData<T> {
success: boolean;
message?:string;
data:T;
code: number;
error?:string
}
封装调用后端api的接口为一个类
封装接口
api.ts
import Http, {IResponseData} from "./request";
import {
AdminClass,
GetAdminClassResp,
GetStudentResp,
GetTeacherClassResp,
GetTeacherResp,
LoginResp,
Student,
Teacher
} from "../models/resp";
let http = new Http({
baseURL: "/",
timeout: 30000
});
let base = process.env.REACT_APP_BASE_URL
class Api {
login = async function(type:string, username: string, password: string) {
let data = await http.post<LoginResp>(`${base}/${type}/login`,{
"phone": username,
"password": password
});
return data.data
}
}
export default new Api()
登录页面组件采用Layout进行管理,在header组件中添加页面标题,在content组件中添加Form表单。
登录主页面
import {Component} from "react";
import {PageProps} from "../models/props";
import {Button, Checkbox, Form, Input, Layout, Select} from "antd";
import { Col, Row } from 'antd';
import loginImg from "../img/loginnew-img.jpg";
import '../utils/api'
import api from "../utils/api";
const { Header, Footer, Content } = Layout;
const options = [
{
"label": "学生",
"value": "student"
},
{
"label": "老师",
"value": "teacher"
},
{
"label": "家长",
"value": "patriarch"
},
{
"label": "管理员",
"value": "admin"
}
]
class LoginPage extends Component<PageProps, any>{
onSubmit = (value:any) => {
api.login(value.type,value.phone,value.password).then(resp => {
if (resp.code === 200){
sessionStorage.setItem("work_token",resp.data.token)
this.props.router.navigate(`/${value.type}`)
}
})
}
render() {
return (<>
<Layout style={{height: "100%"}}>
<Header style={{backgroundColor:"white",height: "18%"}}>
<Row style={{height: "100%"}}>
<Col style={{height:"100%"}} span={8}>
<h1 style={{textAlign:"right",fontSize:"30px",marginTop:"40px",color:"#88c9b4"}}>分 层 教 学 作 业 收 集 系 统</h1>
</Col>
<Col span={16} />
</Row>
</Header>
<Content>
<Row style={{height: "100%"}}>
<Col style={{height:"100%"}} span={12}>
<img width={"100%"} height={"100%"} src={loginImg} alt="图片"/>
</Col>
<Col span={6}>
<h1 style={{textAlign:"center",fontSize:"30px",paddingLeft:"70px"}}>登 录</h1>
<div style={{height:"5%"}}/>
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
initialValues={{ remember: true }}
autoComplete="off"
onFinish={this.onSubmit}
>
<Form.Item
label="角色"
name="type"
initialValue={"student"}
rules={[{ required: true, message: '请选择角色!' }]}
>
<Select size={"large"} options={options} />
</Form.Item>
<Form.Item
label="账号"
name="phone"
rules={[{ required: true, message: '请输入账号!' }]}
>
<Input size={"large"}/>
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={[{ required: true, message: '请输入密码!' }]}
>
<Input.Password size={"large"} />
</Form.Item>
<Form.Item name="remember" valuePropName="checked" wrapperCol={{ offset: 8, span: 16 }}>
<Checkbox>记住密码</Checkbox>
</Form.Item>
<Form.Item wrapperCol={{ offset: 13, span: 16 }}>
<Button size={"large"} type="primary" htmlType="submit">
登 录
</Button>
</Form.Item>
</Form>
</Col>
<Col span={6}>
</Col>
</Row>
</Content>
<Footer style={{backgroundColor:"white",height: "23%"}} />
</Layout>
</>)
}
}
export default LoginPage