ThinkPHP与React集成技术方案专业报告
一、ThinkPHP与React集成架构设计
1.1 前后端分离架构
ThinkPHP作为后端API服务,React作为前端SPA应用,采用前后端分离架构:
+----------------+ +----------------+ +----------------+
| React SPA | | Nginx反向 | | ThinkPHP |
| 前端应用 | | 代理 | | 后端API |
| (端口3000) | | (端口80) | | (端口8000) |
+----------------+ +----------------+ +----------------+
| | |
| HTTP请求 | 反向代理 | API响应
+-------------------> | +-----------------> |
| | |
| | <-----------------+ |
| <-------------------+ |
| | |
+----------------+ +----------------+ +----------------+
| 浏览器 | | 静态资源 | | 数据库 |
| (用户访问) | | 服务器 | | (MySQL) |
+----------------+ +----------------+ +----------------+
1.2 技术栈选型
后端技术栈:
- ThinkPHP 6.x
- MySQL 8.0
- Redis 6.0
- JWT认证
- Swagger API文档
前端技术栈:
- React 18.x
- TypeScript 4.x
- Ant Design 5.x
- Axios
- React Router 6.x
- Redux Toolkit / Zustand
二、ThinkPHP后端API开发
2.1 项目初始化与配置
# 创建ThinkPHP项目
composer create-project topthink/think tp-react-api
# 进入项目目录
cd tp-react-api
# 安装JWT扩展
composer require firebase/php-jwt
2.2 数据库配置
// config/database.php
return [
'default' => env('database.driver', 'mysql'),
'connections' => [
'mysql' => [
'type' => 'mysql',
'hostname' => env('database.hostname', '127.0.0.1'),
'database' => env('database.database', 'tp_react'),
'username' => env('database.username', 'root'),
'password' => env('database.password', ''),
'hostport' => env('database.hostport', '3306'),
'charset' => 'utf8mb4',
'prefix' => '',
'break_reconnect' => true,
'deploy' => 0,
'rw_separate' => false,
'master_num' => 1,
'slave_no' => '',
'fields_strict' => true,
'resultset_type' => 'array',
'auto_timestamp' => false,
'datetime_format' => 'Y-m-d H:i:s',
'sql_explain' => false,
],
],
];
2.3 JWT认证中间件
// app/middleware/JwtAuth.php
<?php
declare (strict_types = 1);
namespace app\middleware;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use think\facade\Config;
use think\Response;
class JwtAuth
{
public function handle($request, \Closure $next)
{
$token = $request->header('Authorization');
if (!$token) {
return json(['code' => 401, 'msg' => 'Token不存在']);
}
$token = str_replace('Bearer ', '', $token);
try {
$jwtConfig = Config::get('jwt');
$decoded = JWT::decode($token, new Key($jwtConfig['secret'], $jwtConfig['algo']));
$request->user = $decoded;
} catch (\Exception $e) {
return json(['code' => 401, 'msg' => 'Token无效']);
}
return $next($request);
}
}
2.4 用户认证API
// app/controller/api/Auth.php
<?php
declare (strict_types = 1);
namespace app\controller\api;
use app\BaseController;
use app\model\User;
use Firebase\JWT\JWT;
use think\facade\Config;
use think\facade\Request;
class Auth extends BaseController
{
public function login()
{
$username = Request::param('username');
$password = Request::param('password');
$user = User::where('username', $username)->find();
if (!$user || !password_verify($password, $user->password)) {
return json(['code' => 400, 'msg' => '用户名或密码错误']);
}
$payload = [
'sub' => $user->id,
'username' => $user->username,
'iat' => time(),
'exp' => time() + Config::get('jwt.expire'),
];
$token = JWT::encode($payload, Config::get('jwt.secret'), Config::get('jwt.algo'));
return json([
'code' => 200,
'msg' => '登录成功',
'data' => [
'token' => $token,
'user' => [
'id' => $user->id,
'username' => $user->username,
'nickname' => $user->nickname,
]
]
]);
}
public function register()
{
$data = Request::param();
$validate = new \app\validate\User();
if (!$validate->check($data)) {
return json(['code' => 400, 'msg' => $validate->getError()]);
}
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
$user = User::create($data);
return json([
'code' => 200,
'msg' => '注册成功',
'data' => [
'id' => $user->id,
'username' => $user->username,
]
]);
}
public function userInfo()
{
$user = Request::user;
$userInfo = User::field('id,username,nickname,email,avatar,created_at')
->find($user->sub);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $userInfo
]);
}
}
2.5 用户管理API
// app/controller/api/User.php
<?php
declare (strict_types = 1);
namespace app\controller\api;
use app\BaseController;
use app\model\User;
use think\facade\Request;
class User extends BaseController
{
public function index()
{
$page = Request::param('page', 1);
$size = Request::param('size', 10);
$keyword = Request::param('keyword', '');
$query = User::field('id,username,nickname,email,avatar,status,created_at');
if ($keyword) {
$query->whereLike('username|nickname|email', "%{$keyword}%");
}
$users = $query->paginate([
'list_rows' => $size,
'page' => $page,
]);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $users->items(),
'total' => $users->total(),
'page' => $users->currentPage(),
'size' => $users->listRows(),
]
]);
}
public function update($id)
{
$data = Request::param();
$user = User::find($id);
if (!$user) {
return json(['code' => 404, 'msg' => '用户不存在']);
}
if (isset($data['password'])) {
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
}
$user->save($data);
return json(['code' => 200, 'msg' => '更新成功']);
}
public function delete($id)
{
$user = User::find($id);
if (!$user) {
return json(['code' => 404, 'msg' => '用户不存在']);
}
$user->delete();
return json(['code' => 200, 'msg' => '删除成功']);
}
}
2.6 路由配置
// route/app.php
<?php
use think\facade\Route;
// 认证路由
Route::post('api/auth/login', 'api/auth/login');
Route::post('api/auth/register', 'api/auth/register');
Route::get('api/auth/userInfo', 'api/auth/userInfo');
// 用户管理路由
Route::get('api/users', 'api/user/index');
Route::put('api/users/:id', 'api/user/update');
Route::delete('api/users/:id', 'api/user/delete');
// 需要认证的路由组
Route::group(function () {
Route::get('api/profile', 'api/user/profile');
Route::put('api/profile', 'api/user/updateProfile');
})->middleware(\app\middleware\JwtAuth::class);
2.7 跨域中间件配置
// app/middleware/Cors.php
<?php
declare (strict_types = 1);
namespace app\middleware;
class Cors
{
public function handle($request, \Closure $next)
{
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With');
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE');
header('Access-Control-Allow-Credentials: true');
if ($request->isOptions()) {
return response();
}
return $next($request);
}
}
三、React前端开发
3.1 项目初始化
# 创建React项目
npx create-react-app tp-react-frontend --template typescript
# 进入项目目录
cd tp-react-frontend
# 安装依赖
npm install antd axios react-router-dom @reduxjs/toolkit react-redux
npm install @types/react-router-dom -D
3.2 项目结构
src/
├── api/ # API接口
├── components/ # 公共组件
├── pages/ # 页面组件
├── store/ # 状态管理
├── utils/ # 工具函数
├── App.tsx
├── index.tsx
└── index.css
3.3 API请求封装
// src/api/request.ts
import axios from 'axios';
import { message } from 'antd';
const request = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL || 'http://localhost:8000',
timeout: 10000,
});
// 请求拦截器
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
request.interceptors.response.use(
(response) => {
const { data } = response;
if (data.code === 200) {
return data.data;
} else {
message.error(data.msg);
return Promise.reject(new Error(data.msg));
}
},
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
message.error(error.response?.data?.msg || '请求失败');
return Promise.reject(error);
}
);
export default request;
3.4 用户认证API
// src/api/auth.ts
import request from './request';
export interface LoginParams {
username: string;
password: string;
}
export interface RegisterParams {
username: string;
password: string;
nickname: string;
email: string;
}
export interface UserInfo {
id: number;
username: string;
nickname: string;
email: string;
avatar: string;
created_at: string;
}
export const login = (params: LoginParams) => {
return request.post('/api/auth/login', params);
};
export const register = (params: RegisterParams) => {
return request.post('/api/auth/register', params);
};
export const getUserInfo = () => {
return request.get('/api/auth/userInfo');
};
3.5 用户管理API
// src/api/user.ts
import request from './request';
export interface User {
id: number;
username: string;
nickname: string;
email: string;
avatar: string;
status: number;
created_at: string;
}
export interface UserListParams {
page?: number;
size?: number;
keyword?: string;
}
export interface UserListResponse {
list: User[];
total: number;
page: number;
size: number;
}
export const getUserList = (params: UserListParams) => {
return request.get<UserListResponse>('/api/users', { params });
};
export const updateUser = (id: number, data: Partial<User>) => {
return request.put(`/api/users/${id}`, data);
};
export const deleteUser = (id: number) => {
return request.delete(`/api/users/${id}`);
};
3.6 状态管理
// src/store/authSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { getUserInfo, UserInfo } from '../api/auth';
interface AuthState {
user: UserInfo | null;
token: string | null;
loading: boolean;
}
const initialState: AuthState = {
user: null,
token: localStorage.getItem('token'),
loading: false,
};
export const fetchUserInfo = createAsyncThunk('auth/fetchUserInfo', async () => {
const response = await getUserInfo();
return response;
});
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setToken: (state, action) => {
state.token = action.payload;
localStorage.setItem('token', action.payload);
},
logout: (state) => {
state.token = null;
state.user = null;
localStorage.removeItem('token');
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUserInfo.pending, (state) => {
state.loading = true;
})
.addCase(fetchUserInfo.fulfilled, (state, action) => {
state.loading = false;
state.user = action.payload;
})
.addCase(fetchUserInfo.rejected, (state) => {
state.loading = false;
state.token = null;
localStorage.removeItem('token');
});
},
});
export const { setToken, logout } = authSlice.actions;
export default authSlice.reducer;
// src/store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import authReducer from './authSlice';
export const store = configureStore({
reducer: {
auth: authReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
3.7 登录页面
// src/pages/Login.tsx
import React, { useState } from 'react';
import { Form, Input, Button, Card, message } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { login } from '../api/auth';
import { setToken } from '../store/authSlice';
import { useAppDispatch } from '../store/hooks';
const Login: React.FC = () => {
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const dispatch = useAppDispatch();
const onFinish = async (values: any) => {
setLoading(true);
try {
const response = await login(values);
dispatch(setToken(response.token));
message.success('登录成功');
navigate('/');
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<Card title="系统登录" style={{ width: 400 }}>
<Form
name="login"
initialValues={{ remember: true }}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
name="username"
rules={[{ required: true, message: '请输入用户名!' }]}
>
<Input prefix={<UserOutlined />} placeholder="用户名" />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: '请输入密码!' }]}
>
<Input.Password prefix={<LockOutlined />} placeholder="密码" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading} style={{ width: '100%' }}>
登录
</Button>
</Form.Item>
</Form>
</Card>
</div>
);
};
export default Login;
3.8 用户列表页面
// src/pages/UserList.tsx
import React, { useEffect, useState } from 'react';
import { Table, Button, Space, Input, Modal, message } from 'antd';
import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
import { getUserList, deleteUser, User } from '../api/user';
const UserList: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [size, setSize] = useState(10);
const [keyword, setKeyword] = useState('');
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
},
{
title: '用户名',
dataIndex: 'username',
key: 'username',
},
{
title: '昵称',
dataIndex: 'nickname',
key: 'nickname',
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: number) => (status === 1 ? '正常' : '禁用'),
},
{
title: '创建时间',
dataIndex: 'created_at',
key: 'created_at',
},
{
title: '操作',
key: 'action',
render: (record: User) => (
<Space size="middle">
<Button type="link" size="small">
编辑
</Button>
<Button
type="link"
size="small"
danger
onClick={() => handleDelete(record.id)}
>
删除
</Button>
</Space>
),
},
];
const fetchUsers = async () => {
setLoading(true);
try {
const response = await getUserList({ page, size, keyword });
setUsers(response.list);
setTotal(response.total);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const handleDelete = (id: number) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除该用户吗?',
onOk: async () => {
try {
await deleteUser(id);
message.success('删除成功');
fetchUsers();
} catch (error) {
console.error(error);
}
},
});
};
const handleSearch = () => {
setPage(1);
fetchUsers();
};
useEffect(() => {
fetchUsers();
}, [page, size]);
return (
<div>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
<Space>
<Input
placeholder="搜索用户名、昵称或邮箱"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
style={{ width: 300 }}
onPressEnter={handleSearch}
/>
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
搜索
</Button>
</Space>
<Button type="primary" icon={<PlusOutlined />}>
新增用户
</Button>
</div>
<Table
columns={columns}
dataSource={users}
rowKey="id"
loading={loading}
pagination={{
current: page,
pageSize: size,
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `共 ${total} 条`,
onChange: (page, size) => {
setPage(page);
setSize(size || 10);
},
}}
/>
</div>
);
};
export default UserList;
3.9 路由配置
// src/App.tsx
import React, { useEffect } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from './store/hooks';
import { fetchUserInfo } from './store/authSlice';
import Login from './pages/Login';
import UserList from './pages/UserList';
import Layout from './components/Layout';
const App: React.FC = () => {
const dispatch = useAppDispatch();
const { token } = useAppSelector((state) => state.auth);
useEffect(() => {
if (token) {
dispatch(fetchUserInfo());
}
}, [token, dispatch]);
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={!token ? <Login /> : <Navigate to="/" replace />} />
<Route
path="/*"
element={token ? <Layout /> : <Navigate to="/login" replace />}
/>
</Routes>
</BrowserRouter>
);
};
export default App;
3.10 主布局组件
// src/components/Layout.tsx
import React from 'react';
import { Layout, Menu } from 'antd';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import { UserOutlined } from '@ant-design/icons';
const { Header, Content, Sider } = Layout;
const AppLayout: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const menuItems = [
{
key: '/',
icon: <UserOutlined />,
label: '用户管理',
},
];
const handleMenuClick = (key: string) => {
navigate(key);
};
return (
<Layout style={{ minHeight: '100vh' }}>
<Header style={{ padding: 0, background: '#fff' }}>
<div style={{ padding: '0 24px', fontSize: '18px', fontWeight: 'bold' }}>
ThinkPHP + React 管理系统
</div>
</Header>
<Layout>
<Sider width={200} style={{ background: '#fff' }}>
<Menu
mode="inline"
selectedKeys={[location.pathname]}
items={menuItems}
onClick={({ key }) => handleMenuClick(key)}
style={{ height: '100%', borderRight: 0 }}
/>
</Sider>
<Layout style={{ padding: '24px' }}>
<Content
style={{
padding: 24,
margin: 0,
background: '#fff',
minHeight: 280,
}}
>
<Outlet />
</Content>
</Layout>
</Layout>
</Layout>
);
};
export default AppLayout;
四、部署与配置
4.1 环境变量配置
# .env.development
REACT_APP_API_BASE_URL=http://localhost:8000
# .env.production
REACT_APP_API_BASE_URL=https://api.yourdomain.com
4.2 Nginx配置
# ThinkPHP后端配置
server {
listen 8000;
server_name localhost;
root /path/to/tp-react-api/public;
index index.php index.html index.htm;
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last;
break;
}
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
# React前端配置
server {
listen 80;
server_name yourdomain.com;
root /path/to/tp-react-frontend/build;
index index.html index.htm;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
4.3 数据库迁移
-- 创建数据库
CREATE DATABASE tp_react CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建用户表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(255) NOT NULL COMMENT '密码',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像',
`status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-正常',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 插入测试数据
INSERT INTO `user` (`username`, `password`, `nickname`, `email`) VALUES
('admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', '管理员', 'admin@example.com');
五、性能优化建议
5.1 后端性能优化
- 开启OPcache:生产环境务必开启OPcache扩展
- 配置缓存:使用Redis作为缓存驱动
- 数据库优化:为常用查询字段添加索引
- 开启路由缓存:
route_check_cache设置为true - 使用连接池:配置数据库连接池和Redis连接池
5.2 前端性能优化
- 代码分割:使用React.lazy和Suspense实现路由懒加载
- 图片优化:使用WebP格式图片,配置图片压缩
- CDN加速:静态资源使用CDN加速
- 缓存策略:配置HTTP缓存头
- Tree Shaking:生产环境移除未使用的代码
5.3 安全建议
- HTTPS:生产环境使用HTTPS协议
- XSS防护:对用户输入进行转义和过滤
- CSRF防护:使用CSRF令牌验证
- SQL注入防护:使用预处理语句
- JWT安全:设置合理的token过期时间,使用HTTPS传输
六、总结
ThinkPHP与React的集成方案提供了完整的前后端分离开发体验。ThinkPHP作为后端API服务,提供了稳定的数据接口和业务逻辑处理;React作为前端SPA应用,提供了良好的用户体验和交互效果。通过JWT认证、Redux状态管理、Ant Design UI组件等技术,构建了现代化的Web应用架构。
该方案具有以下优势:
- 前后端分离:前后端独立开发部署,提高开发效率
- 技术栈成熟:ThinkPHP和React都是经过验证的成熟技术
- 性能优良:前后端分离架构,前端SPA加载速度快
- 扩展性强:模块化设计,便于功能扩展和维护
- 生态丰富:拥有丰富的第三方库和工具支持
在实际项目中,可以根据具体需求进行定制化开发,如添加权限管理、数据统计、文件上传等功能模块。
若内容若侵犯到您的权益,请发送邮件至:platform_service@jienda.com我们将第一时间处理!
所有资源仅限于参考和学习,版权归JienDa作者所有,更多请访问JienDa首页。
