一、重要知识点精讲
1.1 nginx反向代理
1. nginx反向代理好处:
1. 提高访问速度(可以进行缓存,如果访问相同资源可以直接响应数据)
2. 可以进行负载均衡(如果没有nginx前端只能固定地访问后端某一台服务器,加入nginx则可以将请求分发给后端不同的服务器)
3. 保证后端服务安全(前端不能直接请求到后端服务器,需要通过Nginx转发)
2. nginx反向代理的搭建:
location /api/ 的意思是如果请求能匹配上/api/这个字符串。
proxy_pass 该指令的作用是设定转发的目的地,其后跟的是转发的目的地址。
3. nginx负载均衡的配置:
在webservers里面定义一组服务器,用于承接访问负载:
4. nginx负载均衡的策略:
服务器不一定需要平均承接请求,可以通过更改参数赋以不同的权重:
1. 按F12可以打开浏览器的调试工具
2. 负载均衡:把大量的请求按照我们指定的指定的方式均衡的分配给集群中的每台服务器。
3. 备注写上TODO可以在IDEA下方的TODO列表看到待做的操作
二、项目开发介绍
2.1软件开发整体介绍
软件开发流程
1. 需求分析:需求规格说明书(word文档)、产品原型(静态网页展示功能图片)。
2. 设计:UI设计(用户界面,小到按钮,大到页面布局,人机交互)、数据库设计(表结构、字段、类型等)、接口设计。
3. 编码:项目代码、单元测试。
4. 测试:测试用例、测试报告。
5. 上线运维:软件环境安装、配置。
角色分工
软件环境
2.2 苍穹外卖项目介绍
项目介绍
为餐饮企业(餐厅、饭店)定制的一款软件产品。
功能架构:体现项目中的业务功能模块。
产品原型
产品原型:用于展示项目的业务功能(一般用静态的HTML页面+适当的说明文字进行展示),一般由产品经理进行设计。
技术选型
技术选型:展示项目中使用到的技术框架和中间件等。
三、环境搭建
3.1 开发环境搭建
前端环境搭建
先确保将nginx.exe放在无中文的目录下:
将监听的端口号更改为81,因为80端口时常被占用,如果用80端口可能会因为端口占用而无法打开!!
注意:这里配置的是nginx的监听端口,nginx在81号端口上监听网页端,最后是将数据传入8080端口的服务器端。
后端环境搭建
1. 熟悉项目框架
common存放的是公共类。constant常量类。context项目上下文。enumeration枚举类。exception异常类。json处理json转换的类。properties是Springboot中的一些配置属性类,会把配置文件中的配置项封装成对象。result后端的返回结果。utils工具类。
pojo存放的是一些entity、DTO、VO
server子模块存放的是配置文件、配置类、拦截器、controller、service、mapper、启动类等。
2. 使用Git进行版本控制
.gitignore中都是要忽略的文件:
VCS-Create Git Repository创建远程仓库,选中根目录即可,若右上角出现标志说明成功:
勾选所有文件,编写记录文字,点击Commit:
在gitee上创建远程仓库:
点击赋值按钮,在IDEA中点击向上的按钮:
点击下面的链接,然后将刚刚复制的粘贴进来点击OK:
然后直接点击Push即可,然后刷新一下gitee页面,会发现同步成功:
3. 搭建数据库
一共11张表如下:
将已经提供的建表语句粘贴到查询处,点击运行,左边建立成功11张表:
4. 前后端联调
要先将连接数据库的密码改为自己的密码:
在右端Maven处选中compile进行编译,若显示BUILD SUCCESS则说明编译通过:
在sky-server目录下的SkyApplication类中启动项目:
输入localhost:81可以打开登录页面:
调试方法
打上断点,点击小虫:
光标放在字段上还会显示接收到的数据:
若想程序在所希望的地方停止,可以添加断点,然后点击左下角的按钮,意思是放行:
执行之后会在其中标明注入的数据:
按f12进入到开发者工具,点击登录,可以看到请求的路径:
但出现问题,前端请求的地址和后端接口的地址不一致是如何请求成功的呢?
nginx反向代理
但为什么需要NGINX进行转发呢?有什么好处呢?详细见1.1
3.2 完善登录功能
思路:将密码加密后进行存储,提高安全性。加密方式采用MD5。存储入数据库的数据将会是加密后的数据。并且加密的过程是不可逆的,无法通过加密结果算出明文。
e10adc3949ba59abbe56e057f20f883e记得要Ctrl+S保存更改结果:
只需调用Spring提供的DigestUtils类中的md5DigestAsHex方法传入密码即可将密码变成密文,提供给后面比对。
password = DigestUtils.md5DigestAsHex(password.getBytes());
四、接口文档和Swagger
4.1 导入接口文档
在开发之前需要先将接口定义好,然后前后端人员并行开发。
前后端分离开发流程
操作步骤
4.2 Swagger
Swagger介绍
使用Swagger只需要按照规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。Swagger可以帮助后端生成接口文档、进行在线接口测试。
Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。
Swagger使用方式
导入下面坐标:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-starter</artifactId>
<version>3.0.2</version>
</dependency>
相关配置:
@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;
}
设置静态资源映射:
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
Swagger常用注解
@Api注解使用样例:
@ApiModel注解使用样例:
@ApiModelProperty注解使用样例:
@ApiOperation注解使用样例:
注解影响如下图:
五、员工功能模块制作
5.1 新增员工
1.需求分析和设计
一般是对产品原型(静态HTML页面)分析,因为比较直观。思考录入项有没有什么限制。
密码采用默认密码,登录后可以进行修改。
接口定义如下:
数据库设计如下:
2.代码开发
当前端提交的数据和实体类中对应的属性差别较大是,建议使用DTO来封装。
1. 首先在EmployeeController中编写如下代码,EmployeeDTO是网页端读入的字段数据,在这里传入employeeService中:
@PostMapping//post方式请求
@ApiOperation("新增员工")
//请求路径在类的头部已加过
public Result save(@RequestBody EmployeeDTO employeeDTO){ //声明参数接收前端数据,json数据要在前面加@RequestBody
log.info("新增员工:{}",employeeDTO);//方便调试
employeeService.save(employeeDTO);
return Result.success();//表示插入成功
}
2. 然后再EmployeeService中编写如下代码:
//新增员工
void save(EmployeeDTO employeeDTO);
3. 再在EmployeeServiceImpl中完成save()方法的编写,主要是添加除了employeeDTO以外字段的内容:
@Override
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee(); //创建Employee实体
//对象属性拷贝,可以一次性拷贝过来
BeanUtils.copyProperties(employeeDTO,employee); //employeeDTO是源,employee是目标,从源拷贝到目标
//调用copuProperties方法的前提:属性名必须先是一致的
//设置账号的状态,默认正常状态,1表示正常,0表示锁定
employee.setStatus(StatusConstant.ENABLE);
//设置密码,默认密码
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));//存入数据库要对密码进行md5加密
//PasswordConstant.DEFAULT_PASSWORD中存储密码”123456“
employee.setCreateTime(LocalDateTime.now());//设置当前记录的创建时间和修改时间
employee.setUpdateTime(LocalDateTime.now());
//设置当前记录创建人id和修改人id
//TODO 后期需要改为当前登录用户的id
employee.setCreateUser(10L);
employee.setUpdateUser(10L);
employeeMapper.insert(employee);//调用持久层插入数据
}
4. 最后呢调用了一个EmployeeMapper执行了SQL语句,来将数据插入数据库:
@Insert("insert into employee(name,username,password,phone,sex,id_number,create_time,update_time,create_user,update_user,status)" //数据库字段
+ "values" +
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})") //实体类字段
void insert(Employee employee); //单表新增操作,没必要写入xml,通过注解完成
下面是开启驼峰命名:
3.功能测试
下面进行测试出现401,是因为有拦截器进行了拦截,原因是缺少token令牌:
所以我们先在员工登录页面获取一个令牌:
全局参数设置-输入参数名称+参数值,然后关闭页面:
然后带着参数值发送:
成功在数据库中添加记录:
下面是前后端联调成功:
4.代码完善
问题1:录入的用户名已存在,抛出异常后没有处理
用全局的异常处理器sky-server-handler-GlobalExceptionHandler,在方法里添加如下代码:
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
String message = ex.getMessage();
if(message.contains("Duplicate entry")){
String[] split = message.split(" ");
String username = split[2];
String msg = username + MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
}else{
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
问题2:新增员工时,创建人id和修改人id设置为了固定值
程序中将id写死为10:
JWT认证机制:用户发起请求发送用户名和密码,后端进行校验,如果验证通过就生成JWT Token,将Token返回给客户端,客户端会保存Token,在后续请求的请求头中都会携带JWT Token,请求会被拦截器拦截到,会检查Token,如果通过就会展示数据,如果没有通过就会返回错误信息。
问题是:在解析处登录员工id后如何传递给Service的save方法?
答:通过ThreadLocal,它是Thread的局部变量,为每个线程提供单独一份的存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,在线程外则不能访问。
先在拦截器JwtTokenAdminInterceptor里将ID存到存储空间里,因为每次请求线程不变,所以存储空间的值不会被更改,因此可以在EmployeeServiceImpl类中取到该值,进而输出,很妙!
5.2 员工分页查询
1. 需求分析和设计
分页展示,每页展示10条数据,可以输入员工姓名进行查询。
2. 代码开发
在EmployeeController添加一个方法:
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
log.info("员工分页查询,参数为:{}",employeePageQueryDTO);
PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}
pagehelper可以简化分页代码的编写:
在EmployeeService接口中编写方法:
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
在EmployeeServiceImpl中实现方法:
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) { //DTO已将页码和每页记录数传入,因此可以算出
// select * from employee limit 0,10,通过Limit来控制
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize()); //页码和每页记录数传入
//Page是固定的,Employee是每个用户的信息
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);//
//要将page对象处理为PageResult对象
long total = page.getTotal();
List<Employee> result = page.getResult();
return new PageResult(total,result);
}
在EmployeeMapper中编写方法:
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
在EmployeeMapper.xml中编写SQL语句,limit不用我们手写,pagehelper会自动帮我们追加拼接,order by是排序条件:
<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>
3. 功能测试
返回401,说明JWT校验时出现问题:Token的有效时间大约是2小时。
发现日期显示的格式有问题:
//托转Spring MVC框架的消息转换器
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//先创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列话为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//消息转换器还没交给框架,需要把消息转换器加到容器里
converters.add(0,converter); //容器自带消息转换器,默认新加的排在末尾,0表示是首位
}
4. 代码完善
、
拓展Spring MVC的消息转换器,统一对后端返回给前端的数据进行转换处理:
在WebMvcConfiguration下创建:
//托转Spring MVC框架的消息转换器
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//先创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列话为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//消息转换器还没交给框架,需要把消息转换器加到容器里
converters.add(0,converter); //容器自带消息转换器,默认新加的排在末尾,0表示是首位
}
按下面方式推送一下:
5.3 启用禁用员工账号
1. 需求分析和设计
2. 代码开发
在EmployeeController中编写如下代码:
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status,Long id){
log.info("启用禁用员工账号:{},{}",status,id);
employeeService.startOrStop(status,id);
return Result.success();
}
在EmployeeService接口中编入如下代码:
//启用禁用员工账号
void startOrStop(Integer status, Long id);
在EmployeeServiceImpl类中写入如下代码,注意下面的第2种书写方式:
@Override
public void startOrStop(Integer status, Long id) {
//update employee set status = ? where id = ?
/* Employee employee = new Employee();
employee.setStatus(status);
employee.setId(id); */
Employee employee = Employee.builder()
.status(status)
.id(id)
.build();
employeeMapper.update(employee);
}
在EmployeeMapper中写入如下代码:
void update(Employee employee);
在EmployeeMapper.xml中写入如下代码:
</select>
<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>
5.4 编辑员工
回显数据:
EmployeeController编写如下代码:
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<Employee> getById(@PathVariable Long id){
Employee employee = employeeService.getById(id);
return Result.success(employee);
}
EmployeeService接口编写如下代码:
//根据id查询员工
Employee getById(Long id);
EmployeeServiceImpl实现类编写如下代码:
@Override
public Employee getById(Long id) {
Employee employee = employeeMapper.getById(id);
employee.setPassword("****");
return employee;
}
EmployeeMapper中编写如下代码:
//根据id查询员工信息
@Select("select * from employee where id = #{id}")
Employee getById(Long id);
接收提交的数据:
EmployeeController编写如下代码:
@PutMapping
@ApiOperation("编辑员工信息")
public Result update(@RequestBody EmployeeDTO employeeDTO){
log.info("编辑员工信息:{}",employeeDTO);
employeeService.update(employeeDTO);
return Result.success();
}
EmployeeService接口编写如下代码:
//编辑员工信息
void update(EmployeeDTO employeeDTO);
EmployeeServiceImpl实现类编写如下代码:
@Override
public void update(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO,employee); //属性拷贝
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.update(employee); //需要传入Employee参数
}
EmployeeMapper中编写如下代码:
void update(Employee employee);
5.5 导入分类模块功能代码
六、菜品功能模块制作
6.1 公共字段自动填充
1. 问题分析
存在冗余代码,后期不方便修改。需要写大量重复的代码,如果后期进行修改要重复一个个进行修改。
解决思路:通过切面,AOP来增强功能,加入注解,表名需要拦截,需要为公共字段进行赋值。
2. 实现思路
3. 代码开发
4. 功能测试
6.2 新增菜品
6.3 菜品分页查询
6.4 删除菜品
6.5 修改菜品
点击小地球可以找到页面位置:
版权声明:本文为博主作者:吾浴西风原创文章,版权归属原作者,如果侵权,请联系我们删除!
原文链接:https://blog.csdn.net/RuanFun/article/details/132655720