qnjy's blog

方向不对,努力白费

0%

1 文件的上传下载

客户端:在页面上文件上传,对页面的form表单有要求

1.必须post请求

2.enctype=“multipart/form-data”

3.type=”file”(input的file控件)

举例:

image-20230313194307672

服务端:要接受客户端页面上传的文件,通常会使用apache的两个组件

  • commons-fileupload
  • commons-io

文件下载:通过浏览器文件下载有两种表现形式

  • 以附件形式下载,弹出保存对话框,将文件保存到某个位置
  • 直接在浏览器打开

2 代码运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@RestController
@Slf4j
public class CommonController {

@Value("${reggie.path}")
private String basePath;

/**
* 文件上传
*
* @param file
* @return
*/
@PostMapping("upload")
public R<String> upload(MultipartFile file) {
log.info(file.toString());

//获得文件的原始名字
String originalFilename = file.getOriginalFilename();
//截取拓展名
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

//拼接保存的文件名
String fileName = UUID.randomUUID().toString()+suffix;

File dir = new File(basePath);

if(!dir.exists()){
dir.mkdirs();
}

try {
file.transferTo(new File(basePath + fileName));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(fileName);
}
}

3 文件的下载

通过流的方式写入写出图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@GetMapping("download")
public void download(String name, HttpServletResponse response) {
try {
//输入流,通过输入流读取文件内容
FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));

//输出流,通过输出流将文件写回浏览器(在浏览器展示)
ServletOutputStream outputStream = response.getOutputStream();

int len = 0;
byte[] bytes = new byte[1024];

while ((len = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
outputStream.flush();
}

fileInputStream.close();
outputStream.close();

} catch (IOException e) {
e.printStackTrace();
}
}

4 新增菜品

需求分析:

后台系统中可以管理菜品信息,再添加菜品时需要选择当前菜品的所属分类,并上传菜品图片,并在移动端按照菜品分类来展示对应的菜品信息

交互流程:

1.页面发送ajax请求,请求服务端获取菜品分类数据并展示下拉框

2.页面发送请求进行图片上传,请求服务端保存

3.页面发送请求进行图片下载,浏览器预览图片

4.点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端

再写save方法的时候,因为口味是集合存储的,所以我们需要DTO来解决传参问题。

1 公共字段自动填充

问题分析:

数据库表的设计都包含create_time、update_time、create_user、update_user,当我们需要进行修改操作的时候,很多表中都有这样的字段,这些字段可以归列为公共字段:能不能让这些字段在某个地方统一处理,来简化开发?

答案就是使用mybatisplus提供的公共字段自动填充

mybatisplus公共字段自动填充:就是在插入或者更新的时候指定字段赋予指定的值,避免代码重复

实现步骤:

1、在实体类的属性上加入@TableField,指定自动填充策略

2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjecHandler接口

2 改造员工信息修改方法

删除原有赋值语句,创建公共类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyMetaObjectHandler implements MetaObjectHandler {

//插入操作填充
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充Insert...");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("createUser",new Long(1));
metaObject.setValue("updateUser",new Long(1));
}

//更新操作填充
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充update...");
log.info(metaObject.toString());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser",new Long(1));
}
}

但是这样有一个问题:创建者和修改者信息是写死的,正常应该是谁去修改就填充谁的信息,有一种解决思路是:获取http请求中的session来赋值,但是metaObjectHandler中是不能获得httpsession对象的,所以只能使用以下方法来获取登陆用户id。

可以使用ThreadLocal来解决,这是JDK中的一个类

看完3之后,可以这样解决:

在LoginCheckFilter中的doFilter方法中获取当前用户登录的id,然后调用ThreadLocal的set方法将用户id赋值,然后在MyMetaObjectHandler中的updateFill方法中调用ThreadLocal的get方法取值。

实现步骤:

1、编写BaseContext工具类,基于ThreadLocal封装的工具类

2、在LoginCheckFilter的doFilter中调用赋值

3、在MyMetaObjectHandler中调用取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 用于保存和获取当前用户登录的id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

public static void setCurrentId(Long id){
threadLocal.set(id);
}

public static Long getCurrentId(){
return threadLocal.get();
}
}

3 ThreadLocal学习

客户端每次发送http请求,服务端会分配一个新的线程来处理,在修改员工信息的功能中,经过以下类的是同一线程:

1、LoginCheckFilter的doFilter方法

2、EmployeeController的update方法

3、MyMetaObjectHandler的updateFill方法

我们如何证明这一点?

可以在以上方法中使用thread.currentThread().getid();来获取当前线程id

1、什么是ThreadLocal

ThreadLocal并不是一个线程,而是线程的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立的更改自己的副本,并不会影响其他线程副本。

ThreadLocal为每个线程提供单独的存储空间,具有线程隔离的效果,只能在线程内取值

2、常用方法

1
2
3
public void set(T value)		设置当前线程的线程局部变量值

public T get() 返回当前线程对应的局部变量值

4 新增分类功能

需求分析:

后台分类,包括两种类型,分别是菜品分类和套餐分类。当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台徐彤中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐

代码开发:

需要先将需要用到的类和接口基本结构创建好:

  • 实体类:Category
  • Mapper接口CategoryMapper
  • 业务层接口CategoryService
  • 业务层实现类CategoryServiceImpl
  • 控制层CategoryController

分析执行过程:

1.在页面上发送ajax请求,将新增的数据以json的数据返回给服务端

2.在服务端的Controller当中接受提交的数据,并调用Service进行保存

3.Service调用Mapper操作数据库,保存数据

5 分类信息的分页查询

代码开发:

1.页面发送ajax请求,将Page,PageSize以json的形式传入服务端

2.Controller层接受数据,并调用service层进行处理

3.service层操作mapper,保存到数据库当中

4.controller将数据会返回给页面

5.页面通过ElementUI的table组件显示分页效果

6 删除分类

需求分析:在分类管理列表页面,可以对某个分类进行删除操作,主要问题是当分类关联了菜品或者套餐时,此分类不能删除

代码开发:

1.页面发送ajax请求,将参数{id}提交到服务端

2.controller接受页面数据并调用service删除数据

4.servic调用mapper操作数据库

功能完善:

上面已经完成了删除功能,但是没有检查数据的分类是否关联了菜品或者套餐,所以需要以下操作:

菜品和套餐基本代码写好

7 修改分类

需求分析:在分类管理页面有修改按钮,点击之后会回显原来的数据,修改成功之后,还会要自动更新修改人、修改时间。

1 新增员工和异常处理

需求分析:

后台管理系统可以管理员工信息,通过新增员工来添加员工信息

代码开发:

1、页面发送ajax请求,将输入的数据以json形式提交到服务器

2、服务端Controller接受页面提交的数据并调用Service将数据进行保存

3、Service调用mapper操作数据库,保存数据

功能测试正常,但是这时候如果用户在输入相同用户名,因为用户名是在mysql中唯一属性,这时候会出现异常

image-20230216174051224

这里建议使用全局异常捕获,在common包下创建异常处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//捕获加XX注解的异常
@ResponseBody
@Slf4j
@ControllerAdvice(annotations = {RestController.class})
public class GlobalExceptionHandler {

@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {
log.info(ex.getMessage());

if (ex.getMessage().contains("Duplicate entry")) {
String[] str = ex.getMessage().split(" ");
return R.error("用户名" + str[2] + "已存在");
}
return R.error("未知错误");
}
}

这样很容易能够定位到出错的位置,其中把异常信息用String的split方法截取出来

2 员工分页查询

需求分析:

系统中有几百个员工,如果在一个页面中显示出来很显示很乱,所以一般需要通过分页的方式展示

代码开发:

1、页面发送ajax请求,将分页查询的参数(page、PageSize、name)提交到服务器

2、服务端Controller接受页面提交的数据并调用Service查询数据

3、Service调用Mapper操作数据库,查询分页数据

4、Controller将查询的分页数据响应给页面

5、页面接受到分页数据并通过ElementUI的Table组件展示到页面上

在这个项目中并没有使用RestFul风格设计接口,前端接口是写死的。在这个项目中使用mybatisplus提供的分页插件

1
2
3
4
5
6
7
8
9
10
@Configuration
public class MybatisPlusConfig {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}

Controller中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public R<Page> page(int page, int pageSize, String name) {
log.info("page={},pageSize={},name={}", page, pageSize, name);

//构造分页构造器
Page pageInfo = new Page();

LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();

//构造根据用户名查询条件语句
queryWrapper.like(StringUtils.isNotEmpty(name), Employee::getUsername, name);
//添加排序条件
queryWrapper.orderByDesc(Employee::getCreateTime);

//执行查询
employeeService.page(pageInfo, queryWrapper);
return R.success(pageInfo);
}

这里根据name查询并且降序排序,中间使用queryWrapper条件构造器构造sql语句;因为前端需要page类的信息,所以这里返回pageinfo

功能测试一切正常

3 启用/禁用员工账号

需求分析:

在员工管理页面,可以对某个员工进行账号禁用/启用,只有管理员(admin用户)可以对其他普通用户操作,所以普通用户是看不到启用/禁用按钮的

代码开发:

1、页面发送ajax请求,将参数{id、status}提交到服务端

2、服务端Controller接受页面提交的数据并调用service更新数据

3、service调用mapper操作数据库

1
2
3
4
5
6
7
8
9
10
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee) {
log.info("员工信息:{}",employee.toString());

Long empId = (Long)request.getSession().getAttribute("employee");
employee.setUpdateUser(empId);
employee.setUpdateTime(LocalDateTime.now());
employeeService.updateById(employee);
return R.success("员工信息修改成功");
}

这里操作失败,是因为js对long型数据操作处理时丢失精度

解决:

在服务端给页面相应json数据时,将long型转换成String字符串

代码修复步骤:

1、拷贝JacksonObjectMapper工具类

2、扩展MVC框架的消息转换器

1
2
3
4
5
6
7
8
9
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建消息转换器
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置消息转换器
messageConverter.setObjectMapper(new JacksonObjectMapper());
//优先使用
converters.add(0,messageConverter);
}

4 编辑员工信息

需求分析:在页面点击编辑,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最好点击保存

代码开发:

1、点击编辑按钮,跳转到add.html,并在url中携带参数员工id

2、在add.html页面中获取url中的参数[员工id]

3、发送ajax请求,提交到服务端,同时提交员工id参数

4、服务端接受请求,根据员工id查询员工信息,将员工信息以json形式响应给页面

5、页面接受服务端响应的json数据,通过vue的数据绑定进行员工信息的回显

6、点击保存按钮,发送ajax请求,将页面中的员工信息以json形式提交给服务端

7、服务端接受员工信息,并进行处理,完成后响应给页面

8、页面接受到服务端响应信息后进行相应处理

1 软件开发的流程

1.需求分析:(产品原型、需求规格说明书)

2.设计(产品文档、UI界面设计、概要设计、详细设计、数据库设计)

3.编码(项目代码、单元测试)

4.测试(测试用例、测试报告)

5.上线部署(运行维护)

2 角色分工

项目经理:对整个项目负责,任务分配、把控进度

产品经理:进行需求调研,输出需求调研文档、产品原型等

UI设计师:根据产品经理输出界面效果图

架构师:从技术层面,设计整体架构,技术选型

开发工程师:代码实现

测试工程师:编写测试用例,输出测试报告

运维工程师:软件环境搭建、项目上线

3 软件环境

开发环境:开发人员在开发阶段使用的环境,一般外部用户无法访问

测试环境:专门给测试人员进行测试项目的环境,外部无法访问

生产环境:就是线上环境,正式提供对外服务的环境

1 项目介绍

本项目(瑞吉外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。

分为3期开发:

第一期:实现基本需求,移动端通过H5实现

第二期:针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便。

第三期:针对系统进行优化升级,提高系统的访问性能

1.1 技术选型

image-20230215205213239

1.2 功能架构

image-20230215210522707

1.3 角色

后台系统管理员:登录后台管理系统,用友后台系统中的所有操作权限

后台系统普通员工:登录后台管理系统,对菜品、套餐、订单等进行管理

C端用户:登陆移动端应用,可以浏览菜品、添加购物车、设置地址、在线下单等

2 开发环境搭建

image-20230215212633948

3 后台登陆功能开发

需求分析:用户输入账号密码->controller写接口->Service写方法->Mapper操作数据库

处理逻辑:

1、将页面提交的密码password进行md5加密

2、根据页面提交的用户名username查询数据库

3、如果没有查到则返回登录失败结果

4、密码比对,如果不一致则返回登录失败结果

5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果

6、登陆成功,将员工id存入session并返回登录成功结果

4 退出功能开发

需求分析:员工登陆成功后跳转到首页,此时显示用户姓名,点击退出登录,跳转到重新登录页面

5 完善登录功能过滤器

目前如果用户不登录登录也能访问首页,我们需要看的效果是只有登录成功后才能看到系统首页。完善需要三步:

1.编写LoginCheckFilter过滤器

2.在启动类上加入注解@ServletComponentScan

3.编写过滤器处理逻辑

过滤器编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//创建LoginFilter实现Filter类,然后重写doFilter方法

public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

//1.获取本次请求uri
String request = request.getRequestURI();

//定义不需要处理的请求:
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
//2.判断本次请求是否需要处理
...
//3.如果不需要处理,放行
...
//4.判断登陆状态,如果已登录,放行
...
//5.如果未登录,返回登录页面,通过流向客户端页面响应数据
...
//url匹配逻辑
public Boolean check(String[] urls, String requestURI) {
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match)
return true;
}
return false;

6 条件构造器的使用方法

在这里插入图片描述

之前写的项目并没有认真学习微服务理论体系,现在复习一下

1 什么是微服务

将一个大型的互联网项目,根据业务功能对项目拆分,每个业务模块作为独立项目开发,形成服务集群,实现一个业务功能,需要各个模块之间调用实现。微服务架构是为了实现高内聚,低耦合

image-20230215191634609

微服务需要考虑的问题:

服务之间如何远程调用?

服务健康状态如何感知?

服务集群地址如何维护?

服务拆分粒度如何?

2 服务拆分和远程调用

1.不同微服务,不要重复开发相同功能

2.微服务数据独立,不要访问其他微服务的数据库

3.微服务将自己的业务暴露为接口,供其他微服务调用

3 案例分析

需求:根据订单id查询订单的同时,把订单所属的用户信息一起返回

远程调用分析:

一 Vue简介

一套用于构建用户界面的渐进式JavaScript框架

1.1 vue特点

  1. 采用组件化模式,提高代码复用率、且让代码更好维护。
  2. 声明式编码,让编码人员无需直接操作DOM,提高开发效率
  3. 使用虚拟DOM+优秀的Diff算法,尽量服用DOM节点

1.2 HELLO小案例

image-20221228195528964

1.3 模板语法

1.差值语法:

​ 功能:用于解析标签体内容

​ 写法:,xxx是js表达式,且可以直接读取到data中的所有属性

2.指令语法:

​ 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)

​ 举例:v-bind:href=”xxx”或简写为:href=”xxx”,xxx同样要写js表达式且可以直接读取到data中的所有属性。

1.4 数据绑定

1.单向绑定:数据只能从data流向页面

2.双向绑定:data和页面双向流通

1.5 数据代理

1.通过vm对象来代理data对象中属性的操作

1.6 事件的基本使用

image-20230102170134043

事件修饰符:

image-20230102170421561

Mybatis框架的核心理解:映射和ORM

SpringMVC框架的核心理解:拦截+分发 DispatcherServlet

基础Servlet时:一个路径url——>一个servlet—->一个类

SpringMVC:

只有一个Servlet(DispatcherServlet)—->匹配请求路径【拦截】

具体的请求url【分发】—->一个controller中的方法—->方法

阅读全文 »

任务

1 SpringMVC框架引入项目,学习使用

2 维修模块和收费模块完成


mybatis—>持久层(数据模型层)M 优化与数据库的交互

SpringMVC—>控制层C 优化【请求—响应】操作

之前的控制层使用Servlet实现,有很多繁琐的地方

SpringMVC解决路径映射、请求接受、数据响应

阅读全文 »