505 lines
15 KiB
Markdown
505 lines
15 KiB
Markdown
---
|
||
title: 毕业设计(4)
|
||
tags:
|
||
- java
|
||
- ssm
|
||
- react
|
||
- ant design
|
||
- typescript
|
||
categories: 毕业设计
|
||
date: 2022-12-14 12:40:32
|
||
summary: 登录实现
|
||
---
|
||
|
||
## 十、管理登录逻辑后端实现
|
||
|
||
管理员账号的账号密码从配置文件中读写
|
||
|
||
```java
|
||
@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的配置文件中配置拦截器,配置除了登陆地址以外全部拦截
|
||
|
||
```xml
|
||
<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>
|
||
```
|
||
|
||
拦截器类
|
||
|
||
```java
|
||
@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模板,然后添加依赖
|
||
|
||
### 添加依赖
|
||
|
||
```shell
|
||
yarn add antd
|
||
yarn add axios
|
||
yarn add react-router-dom
|
||
|
||
|
||
yarn add http-proxy-middleware -D
|
||
```
|
||
|
||
因为在项目开发过程中,需要解决跨域的问题,所以我们使用开发依赖 **http-proxy-middleware**,将前端的接口转发到本地开发地址。
|
||
|
||
### 解决本地调试跨域
|
||
|
||
**setupProxy.js**
|
||
|
||
```javascript
|
||
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**
|
||
|
||
```typescript
|
||
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**
|
||
|
||
```typescript
|
||
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**
|
||
|
||
```typescript
|
||
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**
|
||
|
||
```typescript
|
||
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表单。
|
||
|
||
### 登录主页面
|
||
|
||
```typescript
|
||
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
|
||
```
|