一、软件开发整体介绍
- 软件开发流程

- 角色分工
- 项目经理:对整个项目负责,任务分配
- 产品经理:进行需求调研,输出需求调研文档,产品原型等
- UI设计师:根据产品原型输出界面效果图
- 架构师:项目整体架构设计,技术选型等
- 开发工程师:代码实现
- 测试工程师:编写测试用例,输出测试报告
- 运维工程师:软件选件搭建,项目上线
- 软件环境
- 开发环境(development):开发人员在开发阶段使用的环境,一般外部用户无法访问
- 测试环境(testing):专门给测试人员使用的环境,用于测试项目,一般外部用户无法访问
- 生产环境(production):即线上环境,正式提供对外服务的环境
二、苍穹外卖项目介绍
- 项目介绍
- 定位:专门为餐饮企业(餐厅、饭店)定制的一款软件产品,分为客户端(外卖商家使用)和用户端(点赞用户使用)
- 功能框架:体现项目中的业务功能模块

- 产品原型
- 产品原型:用于展示项目的业务功能,一般由产品经理进行
(产品原型是一种用来展示项目业务功能的工具,通常由产品经理负责创建。产品经理在项目的初期阶段,会基于市场调研、用户需求分析-和业务目标,构思和设计产品的基本功能和交互流程)
- 技术选型
技术选型:展现项目中使用到的技术框架和中间插件

三、开发环境搭建
整体结构

- 前端环境搭建
- 前端工程基于nginx运行(代码已经开发好,直接运行起来就可以 )
代码说明: - 苍穹外卖项目 -> 授课资料 -> day01 -> 资料 -> 前端运行环境 -> nginx-1.20.2 (nginx服务器标准的目录结构)-> html -> sky(前端项目)
- niginx目录必须放在没有中文的目录中才能正常运行
- 双击nginx.exe即可启动nginx服务
- nginx的默认端口号是80,所以浏览器地址栏直接输入:localhost 就可以访问前端页面
- 后端环境搭建
- 后端工程基于maven进行项目构建,并且进行分模块开发(代码已经搭建好,直接导入到idea即可)


- 数据库环境搭建
- 通过数据库建表语句创建数据库表结构
- 资料 ->day01 -> 数据库

- 创建数据库,直接执行sky.sql文件即可

- 启动项目
- 修改配置文件中的数据库相关信息
- 点击maven,找到父项目,点击compile编译一下,看一下是否有错误
- 然后启动前端、后端项目
- 浏览器输入:loacalhot 测试项目能否正常启动
- 前后端联调
- 后端的初始工程中已经实现了登录功能,直接进行前后端联测试即可
实现思路:

运行项目之前先编译,保证项目是必须编译通过才能运行

登录模块
- Controller层
/**
* 登录
*
* @param employeeLoginDTO
* @return
*/
@PostMapping("/login")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
log.info("员工登录:{}", employeeLoginDTO);
//调用service方法查询数据库
Employee employee = employeeService.login(employeeLoginDTO);
//登录成功后,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)
.build();
return Result.success(employeeLoginVO);
}
- Service层
/**
* 员工登录
*
* @param employeeLoginDTO
* @return
*/
public Employee login(EmployeeLoginDTO employeeLoginDTO) {
String username = employeeLoginDTO.getUsername();
String password = employeeLoginDTO.getPassword();
//1、根据用户名查询数据库中的数据
Employee employee = employeeMapper.getByUsername(username);
//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
if (employee == null) {
//账号不存在
throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
}
//密码比对
if (!password.equals(employee.getPassword())) {
//密码错误
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}
if (employee.getStatus() == StatusConstant.DISABLE) {
//账号被锁定
throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
}
//3、返回实体对象
return employee;
}
- Mapper层
package com.sky.mapper;
import com.sky.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface EmployeeMapper {
/**
* 根据用户名查询员工
* @param username
* @return
*/
@Select("select * from employee where username = #{username}")
Employee getByUsername(String username);
}
扩充知识点JWT令牌****

- 场景:登录认证
- 登录成功后,生成令牌
- 后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理

依赖:

思路:
生成令牌:登录成功后,生成JWT令牌,并返回给前端。
令牌效验:在请求到达服务器端后,对令牌进行统一拦截、效验。


四、重点知识点
nginx反向代理的好处:
- 提高访问速度
因为nginx本身可以进行缓存,如果访问的同一接口,并且做了数据缓存,nginx就直接可把数据返回,不需要真正地访问服务端,从而提高访问速度。 - 可以进行负载均衡
所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器 - 保证后端服务安全
因为一般后台服务地址不会暴露,所以使用浏览器不能直接访问,可以把nginx作为请求访问的入口,请求到达nginx后转发到具体的服务中,从而保证后端服务的安全。
- nginx反向代理搭建
server{
listen 80;
server_name localhost;
location /api/{
proxy_pass http://localhost:8080/admin/; #反向代理
}
}
location /api/ :的意思是如果请求能匹配上/api/这个字符串。
proxy_pass :该指令是用来设置代理服务器的地址,可以是主机名称,IP地址加端口号等形式。
如上代码的含义是:监听80端口号, 然后当我们访问 http://localhost:80/api/…/…这样的接口的时候,它会通过 location /api/ {} 这样的反向代理到 http://localhost:8080/admin/上来。2. nginx负载均衡的配置
接下来,进到nginx-1.20.2\conf,打开nginx配置
# 反向代理,处理管理端发送的请求
location /api/ {
proxy_pass http://localhost:8080/admin/;
#proxy_pass http://webservers/admin/;
}
当在访问http://localhost/api/employee/login,nginx接收到请求后转到http://localhost:8080/admin/,故最终的请求地址为http://localhost:8080/admin/employee/login,和后台服务的访问地址一致。
- 负载均衡
当如果服务以集群的方式进行部署时,那nginx在转发请求到服务器时就需要做相应的负载均衡。其实,负载均衡从本质上来说也是基于反向代理来实现的,最终都是转发请求。
nginx负载均的衡配置方式
upstream webservers{
server 192.168.100.128:8080;
server 192.168.100.129:8080;
}
server{
listen 80;
server_name localhost;
location /api/{
proxy_pass http://webservers/admin;#负载均衡
}
}
upstream:如果代理服务器是一组服务器的话,我们可以使用upstream指令配置后端服务器组。
如上代码的含义是:监听80端口号, 然后当我们访问 http://localhost:80/api/…/…这样的接口的时候,它会通过 location /api/ {} 这样的反向代理到 http://webservers/admin,根据webservers名称找到一组服务器,根据设置的负载均衡策略(默认是轮询)转发到具体的服务器。
注:upstream后面的名称可自定义,但要上下保持一致。
- nginx负载均衡的策略:
服务器不一定需要平均承接请求,可以通过更改参数赋以不同的权重:

五、完善登录功能
问题:员工表中的密码是明文存储,安全性太低

- 思路:
- 将密码加密后存储,提高安全性

- 使用MD5加密方式对明文密码加密

- 实现步骤
- 修改数据库中明文密码,改为MD5加密后的密文

- 修改Java代码,前端提交的密码进行MD5加密后再跟数据库中密码比对
打开EmployeeServiceImpl.java,修改比对密码
/**
* 员工登录
*
* @param employeeLoginDTO
* @return
*/
public Employee login(EmployeeLoginDTO employeeLoginDTO) {
String username = employeeLoginDTO.getUsername();
String password = employeeLoginDTO.getPassword();
//1、根据用户名查询数据库中的数据
Employee employee = employeeMapper.getByUsername(username);
//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
if (employee == null) {
//账号不存在
throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
}
//密码比对
**//对前端传过来的明文密码进行MD5加密处理**
**password = DigestUtils.md5DigestAsHex(password.getBytes());**
if (!password.equals(employee.getPassword())) {
//密码错误
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}
if (employee.getStatus() == StatusConstant.DISABLE) {
//账号被锁定
throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
}
//3、返回实体对象
return employee;
}
六、Swagger
- 介绍
使用swagger只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。
Knife4j是为java mvc框架继承Swagger生成api文档的增强解决方案。
在pom.xml中添加依赖
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
常用注解
通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:
| 注解 | 说明 |
|---|---|
| @Api | 用在类上,例如Controller,表示对类的说明 |
| @ApiModel | 用在类上,例如entity、DTO、VO |
| @ApiModelProperty | 用在属性上,描述属性信息 |
| @ApiOperation | 用在方法上,例如Controller的方法,说明方法的用途、作用 |
七、模块开发
7.1 新增员工
-
需求分析

密码为默认的,登录进去之后可以进行修改 -
设计接口

-
数据库设计

7.2 代码开发(新增员工)
根据新增员工接口设计对应的DTO

注意:当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据
- 在EmployeeController中新建一个save方法,传入参数employeeDTO
@PostMapping//post方式请求
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}",employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}
注:
- 前端传入的数据是json格式,需要用@RequestBoby注解转换为对象
- 为了方便调试加一个log.info,花括号{}表示占位符,代码启动时会替换成emploreeDTO的值
- 在EmployeeService中编写如下代码
思路:把DTO的数据拷贝到实体类中,剩下的属性再进行复制
public void save(EmployeeDTO employeeDTO){
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO,employee);//对象属性拷贝
employee.setStatus(StatusConstant.ENABLE);
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
employee.setCreateUser(10L); //TODO 后续需要改为当前登录用户的id
employee.setUpdateUser(10L);
employeeMapper.insert(employee);
}
- 在EmployeeMapper中编写SQL语句,将数据插入到数据库中
@Insert("insert into employee(name,username,password,phone,sex,id_nu‘mber,status,create_time,update_time,create_user,update_user)"+
"values"+
"(#(name),#(username),#(password),#(phone),#(sex),#(idNumber),#(status),#(createTime),#(updateTime),#(createUser),#(updateUser))")
void insert(Employee employee);
八、Redis
各种数据类型的特点
- 字符串(
1&spm=1001.2101.3001.5002&articleId=138168413&d=1&t=3&u=f89c3238ca2f46069639a6be43b8e09c)
576

被折叠的 条评论
为什么被折叠?



