一、技术选型
前端:三件套 + React + 组件库 Ant Design + Umi + Ant Design Pro(现成的管理系统)
后端:
- java
- spring(依赖注入框架,帮助你管理 Java 对象,集成一些其他的内容)
- springmvc(web 框架,提供接口访问、restful接口等能力)
- druid连接池
- mybatis(Java 操作数据库的框架,持久层框架,对 jdbc 的封装)
- mybatis-plus(对 mybatis 的增强,不用写 sql 也能实现增删改查)
- springboot(快速启动 / 快速集成项目。不用自己管理 spring 配置,不用自己整合各种框架)
- junit 单元测试库
- mysql 数据库
部署:服务器 / 容器(平台)
二、初始化项目
1.前端初始化
1.初始化项目:使用umijs脚手架初始化项目√
2.引入组件:√
3.项目瘦身:删除不需要文件√
2.后端初始化
1.初始化项目:使用springboot模板初始化,引入依赖,√
2.数据库连接池:druid连接池整合√
3.持久层工具:mybatis-plus整合√
4.数据库连接:数据库连接测试√
5.数据库表:数据库表设计√
三、后端
一、用户表设计
id(主键)bigint
username 昵称 varchar
user_account 登录账号
avatar_url 头像 varchar
gender 性别 tinyint
user_password 密码 varchar
phone 电话 varchar
email 邮箱 varchar
user_status 用户状态 int 0 - 正常
create_time 创建时间(数据插入时间)datetime
update_time 更新时间(数据更新时间)datetime
is_delete 是否删除 0 1(逻辑删除)tinyint
user_role 用户角色 0 - 普通用户 1 - 管理员
二、注册接口
/api/user/register POST
1.用户输入:用户在前端输入账号、密码、校验密码
2.后端校验:校验用户账号、密码、校验密码是否有效
- 每个都不能为空 √
- 账号长度不小于4位√
- 密码不小于8位√
- 账号不能包含特殊字符√
- 密码和校验密码相同√
- 账号不能重复√
3.密码加密:使用sm4算法密码加密√
4.插入数据库表:将该用户insert用户表中√
5.返回数据:对成功数据封装响应类返回
6.测试√ 校验错误处都抛出业务异常
三、登录接口
/api/user/login POST
1.用户输入:用户在前端输入账号、密码
2.后端校验:校验用户账号、密码是否有效
3.数据库查询:查询用户,如果user为空则返回
4.查询结果:存在,对前端密码加密和查询到的对比
5.密码正确:为用户设置session
6.返回数据:正确则脱除敏感信息后封装成响应类返回信息
7.测试√
校验错误处都抛出业务异常
四、其他接口
/api/user/current GET // 查询当前用户
/api/user/logout POST // 退出登录
/api/user/selectAll GET // 查询所有
/api/user/remove POST // 删除
…
五、封装常量类
public class UserConstant {
/**
* 用户登录态key
*/
public static final String USER_LOGIN_STATE = "userLoginState";
/**
* 普通用户
*/
public static final int DEFAULT_ROLE = 0;
/**
* 管理员
*/
public static final int ADMIN_ROLE = 1;
}
六、封装通用类
1.封装通用响应类
public class Result<T> implements Serializable {
private int code;
private T data;
private String message;
private String description;
public Result(int code, T data, String message) {
this.code = code;
this.data = data;
this.message = message;
}
// 以下重载构造函数可以根据自己业务需要书写提供语法糖
public Result(int code,T data) {
this(code,data,"成功");
}
// 失败不返回数据
public Result(ErrorCode errorCode){
this(errorCode.getCode(),null, errorCode.getMessage());
}
public Result(ErrorCode errorCode,String description) {
this.code = errorCode.getCode();
this.data = null;
this.message = errorCode.getMessage();
this.description = description;
}
public Result(int code,T data,String message,String description) {
this.code = code;
this.data = data;
this.message = message;
this.description = description;
}
}
2.封装工具类创建响应对象
public class ResultUtils {
// 成功
public static <T> Result<T> success(T data) {
return new Result<>(20000,data);
}
// 失败
public static <T> Result<T> err(ErrorCode errorCode) {
return new Result<>(errorCode);
}
// 以下重载构造函数可以根据自己业务需要书写提供语法糖
public static <T> Result<T> err(ErrorCode errorCode,String description) {
return new Result<>(errorCode,description);
}
public static <T> Result<T> err(int code,String message,String description) {
return new Result<>(code,null,message,description);
}
}
3.封装业务错误码类
public enum ErrorCode {
NOT_LOGIN(40000,"未登录"),
PARAMS_ERROR(40001,"请求参数错误"),
USER_USED_ERROR(40002,"账号已存在"),
NOT_AUTHORITY(40003,"无权限"),
NOT_FOUND_ERROR(40004,"未找到"),
PASSWORD_ERROR(40005,"密码错误"),
SYSTEM_ERROR(50000,"运行时异常")
;
private int code;
private String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
七、封装业务异常类
业务返回错误处直接抛出异常,在全局异常处理器集中处理异常,并返回给前端
public class BusinessException extends RuntimeException{
private final int code;
private final String description;
public BusinessException(int code, String message, String description) {
super(message);
this.code = code;
this.description = description;
}
public BusinessException(ErrorCode errorCode, String description) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = description;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = "";
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}
八、全局异常处理器
使用spring提供@RestControllerAdvice注解,全局异常控制器处理抛出的异常,使用lombok的@slf4j来记录日志
@Slf4j
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(BusinessException.class)
public Result businessExceptionHandler(BusinessException e) {
log.error("businessException:",e.getMessage(),e.getDescription());
return ResultUtils.err(e.getCode(),e.getMessage(),e.getDescription());
}
@ExceptionHandler(RuntimeException.class)
public Result runtimeExceptionHandler(RuntimeException e) {
log.error("runtimeException:",e);
return ResultUtils.err(ErrorCode.SYSTEM_ERROR,"运行时异常");
}
}
九、全局拦截器
实现HandlerInterceptor接口实现preHandle()方法在请求处理前进行全局的处理
@Component
public class GlobalPreInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false); // 参数设置为false,如果Session不存在则返回null
if (session == null || session.getAttribute(UserConstant.USER_LOGIN_STATE) == null) {
// Session不存在或用户未登录,可以进行相应的处理
throw new BusinessException(ErrorCode.NOT_LOGIN,"未登录");
}
return true;
}
}
十、注册拦截器
将全局拦截器注册到配置类中
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private GlobalPreInterceptor globalPreInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(globalPreInterceptor)
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/user/login","/user/register",
"/user/logout"); // 不拦截登录路径
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedOriginPatterns("*")
.allowedMethods("*")
.allowedHeaders("*")
.maxAge(3600); // 允许携带身份验证信息
}
}
十一、多环境
springboot可以根据配置文件后缀识别当前环境,application.yml为基本配置,启用某个环境,环境中与基本配置中相同的属性会覆盖掉,其他的会使用基本配置. application.yml,application-production.yml,application-test.yml多个配置文件 可通过以下命令来启用某环境配置
java -jar xxxx.jar --spring.profiles.active=production
//启用生产环境配置
四、前端
一、umijs
1.app.tsx 运行时配置 getInitialState()函数
-
每次初始化getInitialState()会查询当前用户,将一些返回值存入InitialState,全局可取
-
每次刷新页面都会执行getInitialState()
-
切换页面不执行
layout: RunTimeLayoutConfig
- onPageChange()只要改变有路由的页面就会执行,无论手动还是页面的跳转
二、目录结构
.
├── config
│ └── config.ts
├── dist
├── mock
│ └── app.ts|tsx
├── src
│ ├── .umi
│ ├── .umi-production
│ ├── layouts
│ │ ├── BasicLayout.tsx
│ │ ├── index.less
│ ├── models
│ │ ├── global.ts
│ │ └── index.ts
│ ├── pages
│ │ ├── index.less
│ │ └── index.tsx
│ ├── utils
│ │ └── index.ts
│ ├── services
│ │ └── api.ts
│ ├── app.(ts|tsx)
│ ├── global.ts
│ ├── global.(css|less|sass|scss)
│ ├── overrides.(css|less|sass|scss)
│ ├── favicon.(ico|gif|png|jpg|jpeg|svg|avif|webp)
│ └── loading.(tsx|jsx)
├── node_modules
│ └── .cache
│ ├── bundler-webpack
│ ├── mfsu
│ └── mfsu-deps
├── .env
├── plugin.ts
├── .umirc.ts
├── package.json
├── tsconfig.json
└── typings.d.ts
三、配置文件
umi max提供了很多插件如
export default defineConfig({
routes,//路由的配置
fastRefresh: true,//快速热更新配置
model: {},//数据流插件
initialState: {},//一个全局的初始数据流,可以用它在插件之间共享数据
antd: {},//antd 插件
request: {},//网络请求配置
access: {},//权限插件
headScripts: [
// 解决首次加载时白屏的问题
{
src: '/scripts/loading.js',
async: true,
},
],
mfsu: {//打包提速
strategy: 'normal',
},
proxy: proxy.dev//代理
})
四、配置代理
将带有/api前缀请求代理到后端地址解决开发时跨域问题,build后失效
export default proxyConfig{
dev: {
'/api/': {
target: 'http://localhost:8080/',
changeOrigin: true,
},
},
}
五、命名空间API内定义类型
如定义响应、注册params、注册响应数据类型
declare namespace API {
type Response<T> = {
code: number;
data: T;
message: string;
description?: string;
},
type RegisterParams={
userAccount:string;
userPassword:string;
checkPassword:string;
type?: string;
},
type RegisterResult = number;
type CurrentUser = {
userRole: number;
...//其他属性
}
}
六、封装umi提供的request接口
如下
export async function register(body: API.RegisterParams, options?: { [key: string]: any }) {
return request<API.Response<API.RegisterResult>>('/api/user/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
...//其他接口
七、request配置多环境
可以配置请求拦截器和相应拦截器,来进行个性化处理,可以配置根据当前环境更改基础请求地址,方便开发
export const request = {
baseURL: process.env.NODE_ENV==='production'?"https://production.envriment.com":"http://localhost:8080",
// 请求拦截器
requestInterceptors: [
(config) => {
// 拦截请求配置,进行个性化处理。
const url = config.url.concat('?token = 123');
return { ...config, url};
}
],
// 响应拦截器
responseInterceptors: [
(response) => {
// 拦截响应数据,进行个性化处理
const { data } = response;
if(!data.success){
message.error('请求失败!');
}
return response;
}
]
};
八、权限管理
/src/access.ts是默认的权限定义文件
// /src/access.ts
export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) {
const { currentUser } = initialState ?? {};
return {
canAdmin: currentUser && currentUser.userRole === 1,
};
}
路由配置文件中,只有是canAdmin才能访问/admin路径下页面
export default[
{ path: '/welcome', name: '欢迎', icon: 'smile', component: './Welcome' },
{
path: '/admin',
name: '管理页',
icon: 'crown',
access: 'canAdmin', // 只有canAdmin能访问
routes: [
{ path: '/admin', redirect: '/admin/user-manage' },
],
},
]
九、加入注册、用户管理页面
/src/pages/目录是页面目录,在该目录下加入自己需要的页面。需要其他组件在/src/components/ 目录下进行开发或引入
五、前后端联调
对已有接口进行前后端联调,检查bug,解决
六、前后端分离,跨域解决
一、nginx配置代理解决跨域
location ^~ /api/ {
proxy_pass http://127.0.0.1:8080/api/;
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers '*';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X- Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0; return 204;
}
}
二、后端解决跨域问题
1.配置 @CrossOrigin 注解
@CrossOrigin(origins = { "http://xxx.com"}, methods = { RequestMethod.DELETE },allowCredentials = "true")
origins: 同样是指定允许跨域请求的源,即允许访问资源的域名。在这里,同样只允许来自 “http://xxx.com” 的跨域请求。methods: 允许请求的方法。allowCredentials: 指定是否允许发送身份验证信息(例如 cookie)到目标域。在这里设置为true,表示允许发送身份验证信息。
2.在web全局请求拦截器配置
@Configuration
public class WebMvcConfg implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//设置允许跨域的路径
registry.addMapping("/**")
//设置允许跨域请求的域名
//当**Credentials为true时,**Origin不能为星号,需为 具体的ip地址【如果接口不带cookie,ip无需设成具体ip】
.allowedOrigins(
"http://127.0.0.1:9527",
"http://127.0.0.1:8082",
"http://127.0.0.1:8083")
//是否允许证书 不再默认开启
.allowCredentials(true)
//设置允许的方法
.allowedMethods("*")
//跨域允许时间
.maxAge(3600);
}
// 或
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
//当 Credentials为true时,Origin不能为星号,需为 具体的ip地址【如果接口不带cookie,ip无需设成具体ip】,使用allowedOriginPatterns可以带*
.allowedOriginPatterns("*")
//设置允许的方法
.allowedMethods("*")
//指定是否允许发送身份验证信息(例如 cookie)到目标域
.allowCredentials(true)
//跨域允许时间
.maxAge(3600); // 允许携带身份验证信息
}
}
七、nginx配置ssl
这个配置文件最后引入到nginx.conf主配置文件的http区域中
server {
#SSL 默认访问端口号为 443
listen 443 ssl;
#请填写绑定证书的域名
server_name example.com;
# gzip config
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
root /usr/share/nginx/html;
index index.html index.htm;
include /etc/nginx/mime.types;
#请填写证书文件的相对路径或绝对路径 注意容器nginx配置路径是挂载对应目录
ssl_certificate /ssl/domain_bundle.crt;
#请填写私钥文件的相对路径或绝对路径
ssl_certificate_key /ssl/domain.key;
ssl_session_timeout 5m;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
#请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
// 网关支持
location ^~ /api/ {
rewrite ^/api(.*)$ $1 break;
proxy_pass http://172.17.0.1:8080/;
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers '*';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X- Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0; return 204;
}
}
location / {
try_files $uri /index.html;
}
if ($host != "example.com") {
return 403; # 如果请求的域名不匹配,返回 403 禁止访问
}
}
server {
listen 80;
#请填写绑定证书的域名
server_name example;
#把http的域名请求转成https
return 301 https://$host$request_uri;
}
八、多方式部署上线
一、原生部署
1.前端:在服务器端手动安装nginx
或者:centos直接用yum下载不需要下面这么麻烦,
使用yum直接下载nginx,将构建的dist目录放在配置的root目录下 √
# 下载 nginx curl -o nginx-1.21.6.tar.gz http://nginx.org/download/nginx-1.21.6.tar.gz # 解压 nginx tar -zxvf nginx-1.21.6.tar.gz # 进入 nginx 目录
cd nginx-1.21.6 # 安装 nginx 需要的环境
yum install pcre pcre-devel -y
yum install openssl openssl-devel -y
yum -y install gcc-c++
# 配置文件检查
./configure --with-http_ssl_module --with-http_v2_module --with-stream
# 编译
make
make install
# 查看是否有 nginx 的可执行文件
ls /usr/local/nginx/sbin/nginx
# 配置全局环境变量, shift+g 定位到最后一行,再按 o 在最后一行插入
vim /etc/profile
#在最后一行添加:export PATH=$PATH:/usr/local/nginx/sbin
#修改环境变量后执行:source /etc/profile
# 启动nginx
nginx
# 查看启动情况
netstat -ntlp #查看启动情况
# 修改 nginx 配置后,需要运行如下命令,才生效
nginx -s reload
#####
#补充:后端打包后。将编译好的文件压缩为zip
#使用命令解压压缩文件到指定目录
unzip dist.zip -d your_project_directory
#随后进入其中的dist目录使用命令
mv * ../
#将dist目录的所有文件移至外层即your_project_directory目录,并对dist文件夹进行删除
#随后我们对nginx.conf配置文件进行更改
#tips好习惯:在对配置文件进行更改的时候,先对配置文件进行备份
cp nginx.conf nginx.default.conf
#使用自带的vim编译器进行编辑
vim nginx.conf
#tips :报错退出 esc + :wq
#i 插入模式 o 新增一行
#主要对user进行编辑,修改默认值为root,同时修改lcoation下的root 后为我们解压好的目录
#随后移动这个文件
cp nginx.conf /usr/local/nginx
mv nginx.conf ./conf/
#随后开启nginx服务器,访问服务器的默认公网地址即可
nginx
后端:下载好java,将打包好的jar传到linux服务器,linux下载java,直接命令启动√
// 运行 jar 包
jar -jar ./Your_project.jar --server.port=8080 -spring.profiles.active=production
// 后台运行
# 后台运行 jar 包
nohup java -jar ./Your_project.jar --server.port=8080 -- spring.profiles.active=production &
jps:查看所有运行的java程序
二、宝塔面板部署
非常方便简单
三、docker容器部署
前端
1.Dockerfile 根据自己需求书写
// Dockerfile
# 使用 NGINX 官方提供的稳定版作为基础镜像
FROM nginx:stable
# 暴露 NGINX 默认的 HTTP 端口
EXPOSE 80
# 在容器启动时自动启动 NGINX 服务
CMD ["nginx", "-g", "daemon off;"]
2.构建镜像,运行
-
将dockerfile、dist、nginx配置文件放在目录中进行构建镜像
-
构建镜像:docker build -t project_name:version .
-
运行容器配置挂载目录,宿主机挂载目录根据自己需求而定
-
docker run -id --name=Your_project_name -p 80:80 -v /service/your_project/dist:/usr/share/nginx/html -v /service/your_project/logs:/var/log/nginx -v /service/your_project/nginx:/etc/nginx/conf.d -v /service/your_project/ssl:/ssl 镜像:版本
后端
1.Dockerfile 根据自己需求书写
# 使用 maven jdk8作为基础镜像
FROM maven:3.5-jdk-8-alpine as builder
# 指定工作目录/app,cp pom.xml和src ==》 /app目录下
WORKDIR /app
COPY pom.xml .
COPY src ./src
# maven打包跳过测试
RUN mvn package -DskipTests
# 运行,指定环境
CMD ["java","-jar","/app/target/your_project.jar","--spring.profiles.active=production"]
2.构建镜像,运行
-
如果项目在github仓库中直接clone过来,否则自己打包上传
-
构建镜像:docker build -t project_name:version .
-
运行容器配置挂载目录,宿主机挂载目录根据自己需求而定
-
docker run -id --name=Your_project_name 镜像:版本
四、云平台托管
云服务商容器平台(腾讯、阿里、微信云)等,可快速部署,更省心省力,注意收费规则
容器平台的好处:
-
不用输命令来操作,更方便省事
-
不用在控制台操作,更傻瓜式、更简单
-
大厂运维,比自己运维更省心
-
额外的能力,比如监控、告警、其他(存储、负载均衡、自动扩缩容、流水线)
九、域名绑定,开放端口
一、注册域名
到各大运营商(腾讯、阿里等)注册想要的域名
二、域名解析
将域名解析到服务器地址,nginx绑定相应server_name,如果是云托管平台,使用CNAME解析到平台提供的地址上