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
|
|||
|
```
|