1.| constant | 存放相关常量类 |
| context | 存放上下文类 |
| enumeration | 项目的枚举类存储 |
| exception | 存放自定义异常类 |
| json | 处理json转换的类 |
| properties | 存放SpringBoot相关的配置属性类 |
| result | 返回结果类的封装 |
| utils | 常用工具类 |
2.| Entity | 实体,通常和数据库中的表对应 |
| DTO | 数据传输对象,通常用于程序中各层之间传递数据 |
| VO | 视图对象,为前端展示数据提供的对象 |
| POJO | 普通Java对象,只有属性和对应的getter和setter |
3.| config | 存放配置类 |
| controller | 存放controller类 |
| interceptor | 存放拦截器类 |
| mapper | 存放mapper接口 |
| service | 存放service类 |
| SkyApplication | 启动类 |
3.先编译一下,然后正常运行
前后端联调:
nginx反向代理,就是将前端发送的动态请求由nginx转发到后端服务器
好处:
前端发送的请求,是如何请求到后端服务的?
在使用Nginx作为反向代理的Java项目中,前端发送的请求会被Nginx接收并处理,然后转发给后端服务。以下是一些基本步骤:
客户端(前端)发送请求到Nginx服务器。
Nginx服务器接收请求,并根据配置的规则进行处理。
Nginx根据配置的规则将请求转发给后端服务。
后端服务接收到请求并处理,然后返回响应。
Nginx接收后端服务的响应,并将其返回给客户端。
在这个过程中,Nginx扮演了反向代理的角色,接收客户端的请求并转发给后端服务,同时也将后端服务的响应返回给客户端。通过Nginx,可以实现对请求的负载均衡(把大量的请求按照我们指定的方式均衡的分配给集中的每台服务器)、缓存、安全过滤等处理,提高系统的性能和安全性。
配置:nginx.conf
server{
listen 80;
server_name localhost;
location /api/ {
proxy_pass http://localhost:8080/admin/; #反向代理 } }
nginx 负载均衡的配置方式: nginx.conf
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/; #负载均衡 } }
完善登录功能
问题:员工表中的密码是明文存储,安全性太低。
1.将密码加密后存储,提高安全性
2.使用MD5加密方式对明文密码加密
1.修改数据库中明文密码,改为MD5加密后的密文
2.修改Java代码,前端提交的密码进行MD5加密后再跟数据库中密码比对
在EmployeeServiceImpl中
//进行md5加密,然后再进行比对
password = DigestUtils.md5DigestAsHex(password.getBytes());
if (!password.equals(employee.getPassword())) {
//密码错误
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR); }
前后端 分离开发流程:
导入接口文档
json导入YApi
Yapi 是设计阶段使用的工具,管理和维护接口
Swagger 在开发阶段使用的框架,帮助后端开发人员做后端的接口测试
Swagger
使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。
Knife4j 是为Java MVC框架集成Swagger生成Api文档的增强解决方案。
1.导入maven坐标
2.在配置类中加入knnife4j相关配置
3.设置静态资源映射,否则接口文档页面无法访问
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
WebMvcConfiguration中:
@Bean
public Docket docket(){
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
//指定生成接口需要扫描的包 .apis(RequestHandlerSelectors.basePackage("com.sky.controller")) .paths(PathSelectors.any())
.build();
return docket;
}
/* **设置静态资源映射 ,主要是访问接口文档
* @param registry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始设置静态资源映射...");
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
常用注解:
通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:
@Api(tags ="员工相关接口")
@ApiOperation(value = "员工登录") 方法上
@ApiOperation("员工退出")
DTO:专注于做数据传输
当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据
VO
新增员工
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}",employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}
void save(EmployeeDTO employeeDTO);
@Overridepublic void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
//employee.setName(employeeDTO.getName());
//对象属性拷贝
BeanUtils.copyProperties(employeeDTO,employee);
//设置账号的状态,默认正常状态1表示正常,0表示锁定
employee.setStatus(StatusConstant.ENABLE);
//设置密码,默认123456
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.
DEFAULT_PASSWORD.getBytes()));
//设置当前记录的创建时间和修改时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//设置当前记录创建人id和修改人id
//TODO 后期需要改为当前登录用户的id
employee.setCreateUser(10L);
employee.setUpdateUser(10L);
employeeMapper.insert(employee);
}
@Insert("insert into employee (name,username,password,phone,sex,id_number,create_time,update_time," +
"create_user,update_user)" +"values "+
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser})")
void insert(Employee employee);
存在问题
1.录入的用户名已存在,抛出异常后没有处理
2.新增员工时,创建人id和修改人id设置为了固定值
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
log.error("异常信息:{}", ex.getMessage());
Duplicate entry 'zhangsan' for key 'employee.idx username
String message = ex.getMessage(); //获得异常信息
if (message.contains("Duplicate entry")) { //判断异常信息里面有没有关键字Duplicate entry
String[] split = message.split(" "); //动态把字符串提取出来,根据空格分割,得到数组对象
String username = split[2]; //第三个,下标为2
String msg = username + MessageConstant.ALREADY_EXISTS; //用户名已存在
return Result.error(msg);
} else {
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
解析出登录员工id后,如何传递给Service的save方法?
ThreadLocal 并不是一个Thread,而是Thread的局部变量。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:
public void set(T value) 设置当前线程的线程局部变量的值
public T get() 返回当前线程所对应的线程局部变量的值
public void remove() 移除当前线程的线程局部变量
注意:客户端发送的每次请求,后端的Tomcat服务器都会分配一个单独的线程来处理请求
员工分页查询
业务规则:
根据页码展示员工信息
每页展示10条数据
分页查询时可以根据需要,输入员工姓名进行查询
返回数据data:
total:总记录数 records:代表当前这一页要展示的数据集合,数据多个,为数组
根据分页查询接口设计对应的DTO:
@Data
public class EmployeePageQueryDTO implements Serializable {
//员工姓名
private String name;
//页码
private int page;
//每页显示记录数 private int pageSize;
}
后面所有的分页查询,统一都封装成PageResult对象:
/** * 封装分页查询结果 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
private long total; //总记录数
private List records; //当前页数据集合 }
员工信息分页查询后端返回的对象类型为:Result<PageResult>
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
log.info("员工分页查询,参数为:{}",employeePageQueryDTO); //输出
PageResult pageResult =employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}
PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
//select * from employee limit 0,10
//开始分页查询
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
Page<Employee> page =employeeMapper.pageQuery(employeePageQueryDTO);
long total = page.getTotal(); //总记录数
List<Employee> records = page.getResult(); //records
return new PageResult(total,records);
}
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.EmployeeMapper">
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from employee
<where>
<if test="name !=null and name !=''">
and name like concat('%',#{name},'%')
</if>
</where>
order by create_time desc
</select>
</mapper>
日期格式 错误:
解决方式: 方式一:在属性上加入注解,对日期进行格式化
方式二:在 WebMvcConfiguration 中扩展Spring MVC的消息转换器,统一对日期类型进行格式化处理
配置类上
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器,对象转换器可以将java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器加入容器中
converters.add(0,converter); //优先使用
}
启用禁用员工账号
业务规则: 可以对状态为"启用" 的员工账号进行"禁用"操作 可以对状态为"禁用"的员工账号进行"启用"操作 状态为"禁用"的员工账号不能登录系统
根据接口设计中的请求参数形式对应的在 EmployeeController 中创建启用禁用员工账号的方法:
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startStop(@PathVariable Integer status, Long id) {
log.info("启用禁用员工账号:{},{}", status, id);
employeeService.startOrStop(status, id);
return Result.success();
}
在 EmployeeService 接口中声明启用禁用员工账号的业务方法:
void startOrStop(Integer status, Long id);
在 EmployeeServiceImpl 中实现启用禁用员工账号的业务方法:
@Override
public void startOrStop(Integer status, Long id) {
// Employee employee = new Employee();
// employee.setStatus(status);
// employee.setId(id);
Employee employee = Employee.builder()
.status(status)
.id(id)
.build();
//Update employee set status =?where id = ?
employeeMapper.update(employee);
}
在 EmployeeMapper 接口中声明 update 方法:
void updae(Employee employee);
在 EmployeeMapper.xml 中编写SQL:
<update id="update" parameterType="Employee">
update employee
<set>
<if test="name != null">name = #{name},</if>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="sex != null">sex = #{sex},</if>
<if test="idNumber != null">id_Number = #{idNumber},</if>
<if test="updateTime != null">update_Time = #{updateTime},</if>
<if test="updateUser != null">update_User = #{updateUser},</if>
<if test="status != null">status = #{status},</if>
</set>
where id = #{id}
</update>
编辑员工
编辑员工功能涉及到两个接口: 根据id查询员工信息 编辑员工信息
在 EmployeeController 中创建 getById 方法:
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<Employee> getById(@PathVariable Long id) {
Employee employee = employeeService.getById(id);
return Result.success(employee);
}
在 EmployeeService 接口中声明 getById 方法:
Employee getById(Long id);
在 EmployeeServiceImpl 中实现 getById 方法:
@Override
public Employee getById(Long id) {
Employee employee=employeeMapper.getById(id);
employee.setPassword("****");
return employee;
}
在 EmployeeMapper 接口中声明 getById 方法:
@Select("select *from employee where id = #{id}")
Employee getById(Long id);
在 EmployeeController 中创建 update 方法:
@PutMapping
@ApiOperation("编辑员工信息")
public Result update(@RequestBody EmployeeDTO employeeDTO) {
log.info("编辑员工信息:{}", employeeDTO);
employeeService.update(employeeDTO);
return Result.success();
}
在 EmployeeService 接口中声明 update 方法:
void update(EmployeeDTO employeeDTO);
在 EmployeeServiceImpl 中实现 update 方法:
public void update(EmployeeDTO employeeDTO) {//通过DTO来接收前端提交过来的数据
//对象属性拷贝,给employee赋值
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.update(employee); //调用service方法
}
导入分类模块功能代码
业务规则:
分类名称必须是唯一的
分类按照类型可以分为菜品分类和套餐分类
新添加的分类状态默认为"禁用"
接口设计: 新增分类 分类分页查询 根据id删除分类 修改分类 启用禁用分类 根据类型查询分类