用户中心项目笔记
用户中心
项目介绍:用户中心是一个企业核心的用户中心系统,实现了对企业内项目的用户数据的统一集中管理 ,具有用户注册、登录、查询等基础功能。
目标:完整了解企业做项目的思路,接触一些企业级的开发技术,并在之后都能轻松做出各类后台管理系统
企业做项目流程
需求分析 => 设计(概要设计、详细设计)=> 技术选型 =>
初始化 / 引入需要的技术 => 写 Demo => 写代码(实现业务逻辑) => 测试(单元测试)=> 代码提交 / 代码评审 => 部署=> 发布
需求分析
- 登录 / 注册
- 用户管理(仅管理员可见)对用户的查询或者修改
- 用户校验 (仅内部用户)
技术选型
前端:三件套(HTML、CSS、JavaScript) + React + 组件库 Ant Design + Umi + Ant Design Pro(阿里蚂蚁金服团队开发的管理系统)
后端:
- java
- spring(依赖注入框架,帮助你管理 Java 对象,集成一些其他的内容)
- springmvc(web 框架,提供接口访问、restful接口等能力)
- mybatis(Java 操作数据库的框架,持久层框架,对 jdbc 的封装)
- mybatis-plus(对 mybatis 的增强,不用写 sql 也能实现增删改查)
- springboot(快速启动 / 快速集成项目。不用自己管理 spring 配置,不用自己整合各种框架)
- junit 单元测试库
- mysql数据库
部署:服务器 / 容器(平台)
计划
-
初始化项目
-
前端初始化
- 初始化前端项目
- 引入一些开发所需组件、依赖等
- 框架瘦身(移除不需要的功能模块)
-
后端初始化
- 准备环境(MySQL数据库等)验证 MySQL 是否安装成功 - 连接测试
- 初始化后端项目,引入框架(整合框架)
-
-
登录 / 注册
- 前端
- 后端
-
用户管理(仅管理员可见)
- 前端
- 后端
笔记
三种初始化 Java 项目的方式
- 从GitHub等开源代码平台搜索现成的 springboot 项目模板
- 使用 SpringBoot 官方的模板生成器(https://start.spring.io/)
- 直接在 IDEA 开发工具中生成(推荐)
如果要引入 java 的包,可以去 maven 中心仓库寻找(http://mvnrepository.com/)
数据库设计
什么是数据库?存储和管理数据的仓库
数据库里有什么?各种数据表(可理解为 excel 表格)
为什么用 java 操作数据库?程序代替人工
什么是设计数据库表?
有哪些表(模型)?表中有哪些字段?字段的类型?数据库字段添加索引?表与表之间的关联?
举例:性别是否需要加索引?
用户表设计
字段名 | 类型 | 说明 |
---|---|---|
id | bigint | 主键 |
username | varchar | 昵称 |
userAccount | varchar | 登录账号 |
avatarUrl | varchar | 头像 |
gender | tinyint | 性别 |
userPassword | varchar | 密码 |
phone | varchar | 电话 |
varchar | 邮箱 | |
userStatus | tinyint | 用户状态 0 - 正常 |
createTime | datetime | 创建时间(数据插入时间) |
updateTime | datetime | 更新时间(数据更新时间) |
isDelete | tinyint | 是否删除 0 1(逻辑删除) |
userRole | tinyint | 用户角色 0 - 普通用户 1 - 管理员 |
userCode | tinyint | 用户编号 |
自动生成器的使用
MyBatisX 插件,自动根据数据库生成:
- domain:实体对象
- mapper:操作数据库的对象
- mapper.xml:定义了 mapper 对象和数据库的关联,可以在里面自己写 SQL
- service:包含常用的增删改查
- serviceImpl:具体实现 service
从而提高开发效率
注册逻辑设计
- 用户在前端输入账户和密码、以及校验码(todo)
- 校验用户的账户、密码、校验密码,是否符合要求
- 非空
- 账户长度 不小于 4 位
- 密码就 不小于 8 位吧
- 账户不能重复
- 账户不包含特殊字符
- 密码和校验密码相同
- 对密码进行加密(密码千万不要直接以明文存储到数据库中)
- 向数据库插入用户数据
登录功能
接口设计
接受参数:用户账户、密码
请求类型:POST
请求体:JSON 格式的数据
请求参数很长时不建议用 get
返回值:用户信息( 脱敏 )
登录逻辑
-
校验用户账户和密码是否合法
- 非空
- 账户长度不小于 4 位
- 密码就不小于 8 位
- 账户不包含特殊字符
-
校验密码是否输入正确,要和数据库中的密文密码去对比
-
用户信息脱敏,隐藏敏感信息,防止数据库中的字段泄露
-
我们要记录用户的登录态(session),将其存到服务器上(用后端 SpringBoot 框架封装的服务器 tomcat 去记录)
cookie
-
返回脱敏后的用户信息
实现
控制层 Controller 封装请求
application.yml 指定接口全局路径前缀:
servlet:
context-path: /api
控制器注解:
@RestController 适用于编写 restful 风格的 api,返回值默认为 json 类型
校验写在哪里?
- controller 层倾向于对请求参数本身的校验,不涉及业务逻辑本身(越少越好)
- service 层是对业务逻辑的校验(有可能被 controller 之外的类调用)
如何知道是哪个用户登录了?
javaweb 这一块的知识
-
连接服务器端后,得到一个 session 状态(匿名会话),返回给前端
-
登录成功后,得到了登录成功的 session,并且给该sessio n设置一些值(比如用户信息),返回给前端一个设置 cookie 的 ”命令“
session => cookie
-
前端接收到后端的命令后,设置 cookie,保存到浏览器内
-
前端再次请求后端的时候(相同的域名),在请求头中带上cookie去请求
-
后端拿到前端传来的 cookie,找到对应的 session
-
后端从 session 中可以取出基于该 session 存储的变量(用户的登录信息、登录名)
用户管理
接口设计关键:必须要鉴权
- 查询用户(允许根据用户名查询)
- 删除用户
写代码流程
- 先做设计
- 代码实现
- 持续优化(复用代码、提取公共逻辑 / 常量)
前后端交互
前端需要向后端发送请求才能获取数据 / 执行操作。
怎么发请求:前端使用 ajax 来请求后端
前端请求库及封装关系
- axios 封装了 ajax
- request 是 ant design 项目又封装了一次
追踪 request 源码:用到了 umi 的插件、requestConfig 配置文件
代理
正向代理:替客户端向服务器发送请求,可以解决跨域问题
反向代理:替服务器统一接收请求。
怎么实现代理?
- Nginx 服务器
- Node.js 服务器
举例
原本请求:http://localhost:8000/api/user/login
代理到请求:http://localhost:8080/api/user/login
前端框架
Ant Design Pro(Umi 框架)权限管理
- app.tsx:项目全局入口文件,定义了整个项目中使用的公共数据(比如用户信息)
- access.ts 控制用户的访问权限
获取初始状态流程:首次访问页面(刷新页面),进入 app.tsx,执行 getInitialState 方法,该方法的返回值就是全局可用的状态值。
ProComponents 高级表单
- 通过 columns 定义表格有哪些列
- columns 属性
- dataIndex 对应返回数据对象的属性
- title 表格列名
- copyable 是否允许复制
- ellipsis 是否允许缩略
- valueType:用于声明这一列的类型(dateTime、select)
框架关系
Ant Design 组件库 => 基于 React 实现
Ant Design Procomponents => 基于 Ant Design 实现
Ant Design Pro 后台管理系统 => 基于 Ant Design + React + Ant Design Procomponents + 其他的库实现
其他
MFSU:前端编译优化技术,在首次编译得以完成后,能够显著提升下次编译的速度。
后端优化
通用返回对象
目的:给对象补充一些信息,告诉前端这个请求在业务层面上是成功还是失败
以腾讯云的负载均衡API为例:
成功返回结果
错误返回结果
状态码定义
code | massage |
---|---|
0 | ok |
40000 | 请求参数错误 |
40001 | 请求数据为空 |
40100 | 未登录 |
40101 | 无权限 |
50000 | 系统内部异常 |
{
"name": "feifan"
}
↓
// 成功
{
"code": 0 // 业务状态码
"data": {
"name": "feifan"
},
"message": "ok"
}
// 错误
{
"code": 50001 // 业务状态码
"data": null
"message": "用户操作异常、xxx"
}
封装全局异常处理器
实现
-
定义业务异常类
- 相对于 java 的异常类,支持更多字段
- 自定义构造函数,更灵活 / 快捷的设置字段
-
编写全局异常处理器(利用 Spring AOP,在调用方法前后进行额外的处理)
作用
- 捕获代码中所有的异常,内部消化,让前端得到更详细的业务报错 / 信息
- 同时屏蔽掉项目框架本身的异常(不暴露服务器内部状态)
- 集中处理,比如记录日志
前端优化
全局响应处理
应用场景:我们需要对接口的 通用响应 进行统一处理,比如从 response 中取出 data;或者根据 code 去集中处理错误,比如用户未登录、没权限之类的。
优势:不用在每个接口请求中都去写相同的逻辑
实现:参考使用的请求封装工具的官方文档,比如 umi-request(https://github.com/umijs/umi-request#interceptor、https://blog.csdn.net/huantai3334/article/details/116780020)
如果使用 axios,参考 axios 的文档。
创建新的文件,在该文件中配置一个全局请求类。在发送请求时,使用自己定义的全局请求类。
用户校验
仅适用于用户可信的情况
用户填写:2 - 5 位编号
后台补充对编号的校验:长度校验、唯一性校验
前端补充输入框,适配后端。
多环境
本地开发:项目在自己的电脑上开发,通过localhost(127.0.0.1)访问
多环境:指同一套项目代码在不同的阶段需要根据实际情况来调整配置并且部署到不同的机器上。
为什么需要?
- 每个环境互不影响
- 区分不同的阶段:开发/测试/生产
- 对项目进行优化:
- 本地日志级别(上线时隐藏 debug 日志等)
- 精简依赖,节省项目体积
- 项目的环境 / 参数可以调整,比如通过配置 JVM 参数调整堆内存大小
针对不同环境做不同的事情。
多环境分类:
-
本地环境:就是自己的电脑,是前端或后端独立开发、自主测试的环境。
-
开发环境:大家远程连接同一台机器,方便进行协作开发,是前端和后端(或者多个程序员)一起协作开发、联调的环境。
-
测试环境:给开发(开发新功能) / 测试(有没有bug?) / 产品(新功能是否符合要求?)测试使用,是前端和后端开发和联调完成,做出完整的新功能后,交给测试同学去找 Bug 的环境,拥有独立的数据库和独立的服务器,这样测试所产生的数据及对数据库结构的改动就不会对线上环境的用户产生影响。
测试分类:
- 单元测试
- 性能测试
- 功能测试
- 系统集成测试
-
预发布环境:相当于一个体验服,只对部分用户开放(开发者、项目团队成员等),基本和正式环境一致,一般是测试验证通过、产品经理体验过后,才能将项目发布到这个环境,使用的是正式数据库,更严谨,帮助项目团队在项目/新功能正式上线前查出更多问题。
-
正式环境:又叫线上环境,是给所有真实用户使用的环境。尽可能保证上线前代码是 “完美” 运行的,上线后尽量不要改动。
-
沙箱环境:是为了验证某个特定功能而建立的一个完全隔离的临时环境。
前端多环境
-
请求地址
- 开发环境:localhost:8000
- 线上环境:user-backend.xxx.xxx(非真实地址)
示例:
startFront(env) {
// 如果是生产环境
if(env === 'prod') {
// 不输出注释
// 项目优化
// 修改请求地址
} else {
// 保持本地开发逻辑
}
}
本项目使用了 umi 框架,在 build 时会自动传入 NODE_ENV == production 参数,而运行 start 时, NODE_ENV 参数为 development
-
启动方式
- 开发环境:npm/yarn run start(本地启动,监听端口、自动更新)
- 线上环境:npm/yarn run build(项目构建打包),可以使用 serve 工具启动
-
项目的配置
不同的项目(框架)都有不同的配置文件,umi 的配置文件是 config,可以在配置文件后添加对应的环境名称后缀来区分开发环境和生产环境。参考文档:https://umijs.org/docs/introduce/introduce
- 开发环境:config.dev.ts
- 生产环境:config.prod.ts
- 公共配置:config.ts 不带后缀
后端多环境
SpringBoot 项目,通过 application.yml 添加不同的后缀来区分配置文件
可以在启动项目时传入环境变量:
java -jar .\user-center-backend-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
主要是改:
-
依赖的环境地址
- 数据库地址
- 缓存地址
- 消息队列地址
- 项目端口号
-
服务器(Tomcat)配置
项目部署
需要 Linux 服务器,建议使用 Ubuntu / CentOS(官方已于2024年6月30日停止对 CentOS 的维护,可以使用 OpenCloudOS 来代替)
原始部署
所有软件/环境都由自己手动安装
前端
项目运行需要 web 服务器:nginx 、apache、tomcat
安装 nginx 服务器:
-
用系统自带的软件包管理器快速安装,比如 centos 的 yum / dnf(推荐)
-
从官网下载安装包手动安装( 参考文章:https://developer.aliyun.com/article/1395437 )
# 安装依赖 yum install -y wget gcc-c++ pcre-devel zlib-devel openssl-devel # 下载 nginx 源代码 wget https://nginx.org/download/nginx-1.24.0.tar.gz # 解压源代码 tar -zxvf nginx-1.24.0.tar.gz # 切换到 Nginx 解压目录 cd nginx-1.24.0 # 配置 Nginx,启用 SSL、HTTP/2 和 Stream 模块 ./configure --with-http_ssl_module --with-http_v2_module --with-stream # 编译并安装 make && make install # 进入 Nginx 的安装目录 cd /usr/local/nginx/sbin # 启动 Nginx 服务器 ./nginx # 添加环境变量 vim /etc/profile export PATH=$PATH:/usr/local/nginx/sbin # 重新加载 Nginx 配置 nginx -s reload # 启动后查看 nginx 启动及端口占用情况情况 netstat -ntlp
完成上述步骤后就可以用 nginx 服务器启动前端项目了,后续还可以将 nginx 配置为系统服务,并设置开机自启动,服务启动时需注意 nginx 权限问题
后端
java、maven(非必须,可在本地打包完成后再上传到服务器)
安装:
# 安装 konajdk
dnf install -y java-8-konajdk-devel.x86_64
# 下载 maven 安装包
wget https://dlcdn.apache.org/maven/maven-3/3.9.8/binaries/apache-maven-3.9.8-bin.tar.gz
# 解压 maven 安装包
tar -zxvf apache-maven-3.9.8-bin.tar.gz
# 将后端代码克隆到服务器上
git clone https://github.com/feifan0620/user-center-backend.git
# 使用 maven 将后端代码打包构建成 jar 包,并跳过测试
mvn package -DskipTests
# 在生产环境中运行 jar 包
java -jar ./user-center-backend-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
# 使用 nohup 命令可以让程序在终端关闭或会话中断后继续运行,而结合 & 符号可以使命令在后台启动
nohup java -jar ./user-center-backend-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod &
通过以上步骤项目的后端实际就已经启动了,可以使用 ps -ef | grep 'java' 命令来查看所有的 java 进程,以此来确定后端是否成功部署
宝塔 Linux 面板部署
一款为Linux运维设计的可视化面板,它能够简化服务器管理流程,支持一键安装多种常用环境,从而更加高效地部署项目。
官方安装教程:https://www.bt.cn/new/download.html
Docker 部署
什么是 docker
docker 是一种容器技术,可以将项目的环境(比如 java、nginx、maven)和项目的代码整体打包成一个镜像,这个镜像是所有同学都能够下载的,更利于项目的分发和移植。
启动项目时,不再需要输入大量命令来配置环境,而是直接下载镜像、启动镜像就可以了。
docker 可以理解为软件安装包。
Docker 安装
- 从官网下载安装:https://www.docker.com/get-started/
- 使用宝塔面板安装
Dockerfile:用于指定构建 Docker 镜像的方法
Dockerfile 一般情况下不需要完全从 0 自己写,建议去 github、gitee 等代码托管平台参考同类项目(比如 springboot)
docker 镜像打包
后端
在项目后端根目录新建 Dockerfile 文件
Dockerfile 编写:
- FROM 依赖的基础镜像
- WORKDIR 工作目录
- COPY 从本机复制文件
- RUN 执行命令
- CMD / ENTRYPOINT(附加额外参数)指定运行容器时默认执行的命令
前端
同样新建 Dockerfile 文件,并新建一个 Docker 文件夹用来存放 nginx.conf 配置文件
在 nginx.conf 中添加如下配置:
server {
listen 80;
# 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;
include /etc/nginx/mime.types;
# 尝试寻找 URL 对应的文件,找不到则访问上级目录的 index.html
location / {
try_files $uri /index.html;
}
}
Dockerfile 编写:
与后端编写方式一致,需要注意的是这里的 nginx.conf 需要复制到 doker 镜像中以覆盖默认的配置文件,这样在镜像打包前就将 nginx 配置好了,不需要再到服务器中修改。
其它:
- USER 运行项目的用户(非必须)
- EXPOSE 告诉开发者项目运行的端口号
根据 Dockerfile 构建镜像:
将项目代码连同 Dockerfile 上传到服务器(也可以使用 git 将代码从仓库中克隆下来),然后就可以使用 docker 命令将其打包成镜像了。
# 后端
docker build -t user-center-backend:v0.0.1 .
# 前端
docker build -t user-center-front:v0.0.1 .
说明:
-t 参数用于指定镜像的标记,格式为 repository:tag。
在这里,user-center-backend/user-center-front 是镜像的仓库名称,v0.0.1 是标签名称。
. 指的是当前目录,即 Dockerfile 和其他构建所需文件所在的目录
Docker 构建优化:减少尺寸、减少构建时间(比如多阶段构建,可以丢弃之前阶段不需要的内容)
最后可以使用 docker images 命令来查看构建好的 docker 镜像列表
docker run 启动镜像:
# 前端
docker run -p 8101:80 -d user-center-frontend:v0.0.1
# 后端
docker run -p 8080:8080 user-center-backend:v0.0.1
说明:-p 参数用于指定端口映射(一般保持一致),-d :后台运行容器,并返回容器ID
docker 虚拟化
- 端口映射:把本机的端口(实际访问地址)和容器内部的端口(应用启动端口)进行关联
- 目录映射:把本机的资源和容器应用的资源进行关联
进入容器:
docker exec -i -t fee2bbb7c9ee /bin/bash
查看进程:
docker ps
查看日志:
docker logs -f [container-id]
停止容器:
docker stop
强制删除容器:
docker rmi -f
容器平台部署
- 云服务商的容器平台(腾讯云、阿里云)
- 面向某个领域的托管服务:Webify、微信云托管等
优点:
- 不用输命令来操作,更方便省事
- 不用在控制台操作,更傻瓜式、更简单
- 大厂运维,比自己运维更省心
- 额外的能力,比如监控、告警、其他(存储、负载均衡、自动扩缩容、流水线)
缺点:要花钱
Webify 托管服务
Webify(中文名 Web 应用托管)是由于腾讯云提供的一个 Web 应用托管服务,极大地简化了Web应用的部署流程,省去了代码打包、上传、配置等复杂操作。只需将代码从 github 等远程仓库中导入,无需输入任何命令,即可简单快捷地部署 Web 应用。
优势:
- Webify 会为应用分配一个专属的默认域名,省去了自行配置域名的麻烦(也可根据需要自定义域名)
- 可以为站点提供 CDN 接入能力,提高用户在特定情况下的访问速度
- 内置 CI/CD 能力,当指定代码仓库中的代码发生更新时,会触发自动构建,并发布新版本
- ...
微信云托管
一个与 Webify 类似的项目托管服务(适合部署后端服务)
参考文档:https://developers.weixin.qq.com/miniprogram/dev/wxcloudrun/src/quickstart/custom/java.html
绑定域名
域名解析
以阿里云的域名解析服务为例
域名解析完成后,我们就能够通过域名访问到云服务器了。通常,这种访问默认指向的是服务器的 HTTP 服务,即运行在 80 端口上的服务。为了确保域名能正确访问到相对应的项目,需要配置反向代理。
项目访问流程
前端:用户输入网址 => DNS 域名解析服务器(把网址解析为 ip 地址 / 交给其他的域名解析服务) => 云服务器 =>(防火墙)=> nginx 接收请求,找到对应的文件,返回文件给前端 => 前端加载文件到浏览器中(js、css) => 渲染页面
后端:用户输入网址 => 域名解析服务器 => 云服务器 => nginx 接收请求 => 后端项目(比如 8080端口)
nginx 反向代理
替服务器接收请求,转发请求
例如,当用户通过域名访问到服务器的 80 端口时,Nginx 能够监听并拦截该请求,并将其转发到服务器内部运行在其他端口(例如 8080 端口)上的项目。这样一来,用户无需在域名后附加任何端口号,即可直接访问到目标项目,提升了访问的便捷性和用户体验。
跨域问题
为什么会产生跨域问题?
浏览器为了用户的安全,仅允许向 同域名、同端口 的服务器发送请求。
如何解决跨域问题?
一、项目前后端使用同一个域名、端口(最简单、直接的方式)
二、设置 CORS 响应头
添加响应头,允许任何源(根据客户端请求的 Origin 头部)进行跨域请求
让服务器告诉浏览器:允许跨域(返回 Access-Control-Allow-Origin 响应头)
实现方式
- 网关支持(Nginx)
# 示例
server {
listen 80; # 或者其他端口,如443(HTTPS)
server_name yourdomain.com; # 替换为您的实际域名
# 允许特定源跨域访问
location /api/ { # 跨域请求的目标路径
add_header 'Access-Control-Allow-Origin' 'http://example.com'; # 允许http://example.com跨域访问
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; # 允许的HTTP方法
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With'; # 允许的自定义请求头
add_header 'Access-Control-Allow-Credentials' 'true'; # 允许携带Cookie(如果需要)
# 如果仅针对OPTIONS预检请求,则可在此处结束处理,避免进一步转发至后端
if ($request_method = 'OPTIONS') {
return 204;
}
# 其他配置,如代理、静态文件处理等
...
}
- 修改后端服务
(1). 配置 @CrossOrigin 注解
(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://localhost:9527", "http://127.0.0.1:9527", "http://127.0.0.1:8082", "http://127.0.0.1:8083")
//是否允许证书 不再默认开启
.allowCredentials(true)
//设置允许的方法
.allowedMethods("*")
//跨域允许时间
.maxAge(3600);
}
}
(3). 定义新的 corsFilter Bean,参考文章:https://www.jianshu.com/p/b02099a435bd
持续更新中...