《苍穹外卖》电商实战项目(java)知识点整理(P1~P65)【上】

这篇具有很好参考价值的文章主要介绍了《苍穹外卖》电商实战项目(java)知识点整理(P1~P65)【上】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

史上最完整的《苍穹外卖》项目实操笔记,跟视频的每一P对应,全系列10万字,涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳,参考这篇,相信会带给你极大启发。

《苍穹外卖》项目实操笔记【中】:P66~P122《苍穹外卖》项目实操笔记【中】

一、重要知识点精讲

1.1 nginx反向代理P11

1. nginx反向代理好处:

1. 提高访问速度(可以进行缓存,如果访问相同资源可以直接响应数据)

2. 可以进行负载均衡(如果没有nginx前端只能固定地访问后端某一台服务器,加入nginx则可以将请求分发给后端不同的服务器)

负载均衡:把大量的请求按照、我们指定的指定的方式均衡的分配给集群中的每台服务器。

3. 保证后端服务安全(前端不能直接请求到后端服务器,需要通过Nginx转发)

2. nginx反向代理的搭建:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

location /api/ 的意思是如果请求能匹配上/api/这个字符串。

proxy_pass 该指令的作用是设定转发的目的地,其后跟的是转发的目的地址。

3. nginx负载均衡的配置:

在webservers里面定义一组服务器,用于承接访问负载:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4. nginx负载均衡的策略:

服务器不一定需要平均承接请求,可以通过更改参数赋以不同的权重:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

技巧:

1. 按F12可以打开浏览器的调试工具  

3. 备注写上TODO可以在IDEA下方的TODO列表看到待做的操作

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

重要知识点:

1.用Git进行版本控制 P7 **

2.JWT令牌 P10 *

3.nginx反向代理 P11 **

4.Swagger P15 *

5.ThreadLocal P20 **

6.分页查询 P22 *

7.AOP P32 *

8.上传文件 图片 P36 P37 *

9.批量删除 P43 *

二、搭建开发环境 P3~P14

2.1软件开发整体介绍P3

软件开发流程

1. 需求分析:需求规格说明书(word文档)、产品原型(静态网页展示功能图片)。

2. 设计:UI设计(用户界面,小到按钮,大到页面布局,人机交互)、数据库设计(表结构、字段、类型等)、接口设计。

3. 编码:项目代码、单元测试。

4. 测试:测试用例、测试报告。

5. 上线运维:软件环境安装、配置。

角色分工

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

软件环境

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

2.2 苍穹外卖项目介绍P4

项目介绍

为餐饮企业(餐厅、饭店)定制的一款软件产品。

功能架构:体现项目中的业务功能模块。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

产品原型(产品经理)

产品原型:用于展示项目的业务功能(一般用静态的HTML页面+适当的说明文字进行展示),一般由产品经理进行设计。

技术选型(架构师)

技术选型:展示项目中使用到的技术框架和中间件等。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

2.3 前端环境搭建P5

先确保将nginx.exe放在无中文的目录下:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

将监听的端口号更改为81,因为80端口时常被占用,如果用80端口可能会因为端口占用而无法打开!!

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

注意:这里配置的是nginx的监听端口,nginx在81号端口上监听网页端,最后是将数据传入8080端口的服务器端。

2.4 后端环境搭建P6

1. 熟悉项目框架+2.5+2.6

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

common存放的是公共类:constant常量类,context项目上下文,enumeration枚举类,exception异常类,json处理json转换的类,properties是Springboot中的一些配置属性类,会把配置文件中的配置项封装成对象,result后端的返回结果,utils工具类。

注意下面对象职责的说明:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

Entity就是实体类,实体类一般与数据库表对应。(数据库字段一般是下划线命名,实体类属性一般是驼峰命名)

DTO数据传输对象,DTO一般是作为方法传入的参数在使用,不局限于前端给controller层传参,也可以是controller层给service层传参。

VO是视图对象,用于前端数据的展示,所以一般是controller层把VO传给前端,然后前端展示。

server子模块存放的是配置文件、配置类、拦截器、controller、service、mapper、启动类等。

2.5 使用Git进行版本控制P7

.gitignore中存放的是git不需要管理的文件:比如编译后生成的targit文件,以及测试类、测试包还有idea自带的一些文件。

先创建Git本地仓库

VCS - Create Git Repository创建远程仓库,选中根目录即可,若右上角出现标志说明成功:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

打钩是提交按钮,点击后勾选所有文件,编写版本文字,点击Commit,这步是将项目提交到本地仓库:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

PS:如果已有本地仓库,若想移除重新添加,下面是移除本地仓库的方法,首先在settings中移除本地仓库,然后关闭idea,把仓库地址下的.git、.idea、.gitignore文件删除,重新启动idea打开项目即可:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

然后创建Git远程仓库

在gitee上创建远程仓库:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

点击复制按钮,在IDEA中点击向上的按钮:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

点击下面的链接,定义远程仓库,然后将刚刚复制的链接粘贴进来点击OK,即可将本地仓库与远程仓库关联:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

最后将本地文件推送到Git远程仓库

然后直接点击Push即可,然后刷新一下gitee页面,会发现同步成功:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

2.6 搭建数据库P8

一共11张表如下:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

将已经提供的建表语句粘贴到查询处,点击运行,左边建立成功11张表:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

2.7 前后端联调P9

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

要先将连接数据库的密码改为自己的密码:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在右端Maven处选中compile进行编译,若显示BUILD SUCCESS则说明编译通过:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在sky-server目录下的SkyApplication类中启动项目:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

输入localhost:81可以打开登录页面:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

2.8 调试方法+JWT令牌P10

点击小虫(进入断点调试),打上断点,然后前端点击登录(此时前端的数据会作为参数传入):

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

光标放在字段上还会显示接收到的数据:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

若想程序在所希望的地方停止,可以添加断点,然后点击左下角的右箭头,意思是放行;点击一折的箭头,意思是前进一步:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

执行之后会在其中标明注入的数据:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

jwt令牌是调用了一个工具类,JwtProperties是一个配置属性类,这里讲一个小技巧,ctri+鼠标左键点进去后,可以通过点击左上角的地球来锁定当前类所在的目录路径位置:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

@ConfigurationProperties注解代表当前类是一个配置属性类,作用是:封装配置文件中的一些配置项。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在注解内的参数指示了配置类中的参数,比如sky.jwt,就去application.yml文件中找sky jwt的配置项,这些配置项就对应了相应的属性。

原理就是:通过配置属性类,将配置文件中的配置项,封装成一个类,然后通过@Autowired注解注入到要使用的地方。

如下图使用builder方式来建造对象,前提是要在EmployeeLoginVo类上面加上@Builder注解。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

注意后端给前端响应的数据一律都是封装为Result:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

按f12进入到开发者工具,点击登录,可以看到请求的路径:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

2.9 nginx反向代理P11

但出现问题,前端请求的地址和后端接口的地址不一致是如何请求成功的呢?

下图是前端请求地址,端口为81:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

后端的地址如下,应该是http://localhost:8080/admin/employee/login

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

原理:nginx反向代理,将前端发送的请求由nginx转发到后端服务器。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

 下图是nginx的配置文件,它监听的是81端口,服务器名是本地(http://localhost:81)。如果匹配到api字符串(http://localhost:81/api),就转发到proxy_pass对应的地址(http://localhost:8080/admin)。如果后面还有字符串就拼接到目标地址后面(http://localhost:8080/admin/employee/login)。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

2.10 完善登录功能P12

目前登录存在的问题:密码是明文存储(如123456),安全性太低。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

思路:将密码采用MD5方式加密后进行存储,提高安全性(存储入数据库的密码是加密后的数据,并且加密的过程是不可逆的,无法通过加密结果算出明文)。

比对思路是:将前端输入的密码,经过转换,看能否比对上数据库中存储的密文。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

将下面字符串粘贴到数据库:e10adc3949ba59abbe56e057f20f883e记得要Ctrl+S保存更改结果。

在service类中只需调用Spring提供的DigestUtils类中的md5DigestAsHex方法对密码进行加密转换,提供给后面比对即可,代码如下:

password = DigestUtils.md5DigestAsHex(password.getBytes());

2.11 导入接口文档P13

在开发之前需要先将接口定义好,然后前后端人员并行开发。

前后端分离开发流程

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

操作步骤

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

2.12 SwaggerP14

Swagger介绍

使用Swagger只需要按照规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。Swagger可以帮助后端生成接口文档、进行在线接口测试。

Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。

Swagger使用步骤

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

导入下面坐标:

<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;
    }

它会扫描controller里面的所有方法,然后通过反射去解析,最终生成接口文档。

设置静态资源映射:

protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}

启动后访问localhost:8080/doc.html即可: 

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

有login和logout这些都是controller里的方法: 

 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

可以直接在页面的测试栏中对方法进行测试:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

Swagger常用注解P15

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

@Api注解使用样例(记得要加tags=):

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

@ApiModel注解使用样例:

@ApiModelProperty注解使用样例:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

@ApiOperation注解使用样例:

 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

注解影响如下图:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

三、模块开发

3.1 (新增员工)分析设计P16

一般是对产品原型(静态HTML页面)分析,因为比较直观。思考录入项有没有什么限制。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

密码采用默认密码,登录后可以进行修改。

接口定义如下(data一般是查询是会用到,msg一般是出错时会返回消息):

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

数据库设计如下:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.2 (新增员工)代码开发P17

注意:当前端提交的数据和实体类中对应的属性差别较大时(也就是实体类中会有多余的属性),建议使用DTO来封装(DTO里的数据字段和前端提交的数据字段都能对应上)。

1.在EmployeeController中新建一个方法save,传入的参数是employeeDTO

1. 首先编写如下代码,是网页端读入的字段数据,在这里传入employeeService对象。有2点注意事项:①前端传入的数据是json格式,要用@RequestBody注解转换为对象。②为了方便调试加一个log.info,花括号{}的内容在后面会被替换为employeeDTO的值

@PostMapping//post方式请求
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){ 
      log.info("新增员工:{}",employeeDTO);
      employeeService.save(employeeDTO);
      return Result.success();
}

2. 在EmployeeService中编写如下代码,思路是:先创建一个emloyee实体类,然后把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);
}

有2个注意事项:①可以用BeanUtils工具类中的copyProperties方法来对对象进行拷贝,前提是对象的属性有一部分是相同的。②不应该直接用数字数字,否则会是硬编码,应该使用StatusConstant常量类。 

3. 在EmployeeMapper中编写SQL语句,来将数据插入数据库:

@Insert("insert into employee(name,username,password,phone,sex,id_number,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);

下面是开启驼峰命名:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.3 (新增员工)功能测试P18

在开发阶段,前端界面可能没有开发好,所以不能进行前后端联调测试,只能用接口文档进行测试。

下面进行测试出现401,是因为有拦截器进行了拦截,原因是缺少token令牌:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

所以我们先在员工登录页面获取一个令牌:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

全局参数设置-输入参数名称+参数值,然后关闭页面: 

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

然后带着参数值发送:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

成功在数据库中添加记录:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

下面是前后端联调成功:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.4 (新增员工)完善P19-20

问题1:录入的用户名已存在,抛出异常后没有处理,没处理的话,控制台会抛出错误P19。

用全局的异常处理器sky-server/handler/GlobalExceptionHandler,创建exceptionHandler方法,在方法里添加如下代码:

@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);
        }
}

目的是输出:xxx已经存在的提示。核心思想是:提取错误那段话的第3个词,然后拼接后输出。

 电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

问题2:新增员工时,创建人id和修改人id设置为了固定值P20。

程序中将创建者和更新者的id写死为10:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

JWT认证机制:用户发起请求发送用户名和密码,后端进行校验,如果验证通过就生成JWT Token,将Token返回给客户端,客户端会保存Token,在后续请求的请求头中都会携带JWT Token,请求会被拦截器拦截到,会检查Token,如果通过就会展示数据,如果没有通过就会返回错误信息。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在拦截请求验证的时候可以获得JWT令牌

问题是:在解析出登录员工id后如何传递给Service的save方法?

答:通过ThreadLocal,它是Thread的局部变量,为每个线程提供单独一份的存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,在线程外则不能访问。

可以通过在controller、service和拦截器中输出线程的id来看是否单次请求是同一个线程,经实验验证是同一个线程。 

System.out.println("当前线程的id:"+Thread.currentThread().getId());

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在sky-common/src/main/java/context/BaseContext下封装了ThreadLocal的操作。 

先在拦截器JwtTokenAdminInterceptor里将ID存到存储空间里(set),因为每次请求线程不变,所以存储空间的值不会被更改,因此可以在EmployeeServiceImpl类中取到该值(get),进而输出,很妙!

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

小技巧:选中要计算的表达式,然后右键,选择Evaluate Expression,然后点击Evaluate即可。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.5 (分页查询)分析设计P21

分页展示,每页展示10条数据,可以输入员工姓名进行查询。total是总的数目,records是一页的条目数。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.6 (分页查询)代码开发P22

下面是PageResult和EmployeePageQueryDTO的实体类定义:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

Result<PageResult>是在PageResult的基础上加上code和msg,作为返回给前端的对象。

在EmployeeController添加一个方法:

@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
       log.info("员工分页查询,参数为:{}",employeePageQueryDTO);
       PageResult pageResult = employeeService.PageQuery(employeePageQueryDTO);
       return Result.success(pageResult);
}

在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);
    }

PageHelper的startPage方法可以通过传入的参数自动设置Limit,传入的是页码和每页的记录数,好处是:字符串的拼接不用自己做。底层实现是:它会给ThreadLocal设置上述参数,然后在执行SQL语句时会自动被取出,然后拼接成Limit。

Page是PageHelper插件定义的一个泛型类,是一个固定的返回类型。

pagehelper可以简化分页代码的编写:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在EmployeeMapper中编写方法:

Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

在application.yml配置文件中扫描了EmployeeMapper.xml配置文件: 

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在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>

如果有传入name,代表是员工姓名查询,它只会返回带有相关词(字也可以,因为是模糊查询)的员工信息。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

如果没有传入name,那么name就为空,<if>判断内容不执行,<where>默认返回1,所以它会查询所有employee元素。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在这里是模糊查询,用concat将name与%进行拼接,%的意思是匹配任意字符串/字符。

<select>标签的id是mapper中的对应方法名。resultType是传入的参数类型。

3.7 (分页查询)功能测试P23

返回401,说明JWT校验时出现问题:Token的有效时间大约是2小时(此时重新登录获得Token,然后设置全局)。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

发现日期显示的格式有问题(数字挤在一起):

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.8 (分页查询)代码完善P24

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

方法一:在Employee实体类中的LocalDateTime属性上加上@JsonFormat注解,格式化时间。

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;

方法二:拓展Spring MVC的消息转换器,统一对后端返回给前端的数据进行转换处理:

在sky-server下的com/sky/config/WebMvcConfiguration下创建:

//托转Spring MVC框架的消息转换器
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //先创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());
        //消息转换器还没交给框架,需要把消息转换器加到容器里
        converters.add(0,converter); //容器自带消息转换器,默认新加的排在末尾,0表示是首位,自己加的消息转换器排在首位
    }

在JacksonObjectMapper里面有关于日期时间的序列化和反序列化器。 

按下面方式推送一下:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.9 (启禁账号)分析设计P25

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

{status}是路径参数,1为启用,0为禁用。地址栏传参传入员工id。

3.10 (启禁账号)代码开发P26

操作:传入status和id,将某一id的status从0改为1或从1改为0。

在EmployeeController中编写如下代码:

@PostMapping("/status/{status}")
    @ApiOperation("启用禁用员工账号")
    public Result startOrStop(@PathVariable Integer status,Long id){
        log.info("启用禁用员工账号:{},{}",status,id);
        employeeService.startOrStop(status,id);
        return Result.success();
}

取的是路径参数,加注解@PathVariable,如果和路径参数不同名,就要加括号双引号指明取的是哪个路径参数@PathVariable("status") ;如果同名,就不用加。

在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中写入如下代码,下面这个代码对全字段都可以进行修改,所以不仅仅适用于对status的修改

</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>

3.11 (编辑员工)分析设计P27

可以修改员工的姓名、手机号和身份证号等。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

首先根据id查询到员工信息,然后编辑员工信息。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.12 (编辑员工)代码开发P28

①回显数据操作:用查询语句把Employee对象查出来,然后显示。

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);

②接收提交的数据:调用之前mapper的update方法进行更新。

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);

3.13 (编辑员工)功能测试P29

修改原有的一条数据成功。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.14 导入分类管理代码P30

视频首先介绍了一下该模块的主要功能。

然后就是导入类,Java的Mapper层是3个类,resources的Mapper层是1个类,Service层有1个接口1个实现类(Impl结尾),Controller层有1个类。

 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

然后进行前后端联调测试。

3.15 (字段填充)分析设计P31

在多个业务表中都有公共字段,如create_time、create_user(insert时用到);update_time,update_user(insert和update时用到)这些。

插入数据的时候需要为这些字段赋值,会有大量重复的冗余set方法代码,后期如果表结构发生变化,代码需要跟着修改,此时就不方便修改(如果后期进行修改要重复一个个进行修改)。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

实现思路:自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法。然后自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值。在Mapper的方法上加入AutoFill注解。

技术点:枚举,注解,AOP,反射。

3.16 (字段填充)代码开发P32

1. 在com.sky下创建annotation包,创造一个AutoFill的Annotation注解。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

写入如下代码:

//自定义注解,用于标识某个方法需要进行功能字段自动填充处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

Target注解指定加上什么上面,Retention注解指定什么时候用,

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

2. 在com.sky下创建aspect包,创建类AutoFillAspect,写入如下代码:

//自定义切面,实现公共字段自动填充处理逻辑
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    //切入点
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") 
    public void autoFillPointCut(){}
    //前置通知,在通知中进行公共字段的赋值
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充...");
    }
}

切入点:对哪些类的哪些方法进行拦截。@Pointcut里面写的是对哪些方法进行拦截,要满足2点:①必须是mapper下的所有类的方法,②还要有AutoFill这个注解。

通知:前置通知,后置通知,环绕通知,异常通知。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

然后在sky-server下的mapper中的EmployeeMapper类里,insert上加入@AutoFill(value= OperationType.INSERT)注解,update上加入@AutoFill(value= OperationType.UPDATE)注解。

3.测试:在aspect/AutoFillAspect上的log.info("开始进行公共字段自动填充...")处打上断点,然后点击小虫(断点调试),正常登录,到员工管理界面,点击修改,看是否会运行到log语句。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.17 (字段填充)代码开发P33

在sky-server的com.sky下的aspect的AutoFillAspect里的log.info("开始进行公共字段自动填充...");下添加如下代码:

1.获取到当前被拦截的方法上的数据库操作类型(比如是Insert还是Update,不同的类型需要给不同的参数赋值)

MethodSignature signature = (MethodSignature) joinPoint.getSignature();//通过连接点对象来获取签名,向下转型为MethodSignature
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型(Insert or Update)

2.获取到当前被拦截的方法的参数--实体对象(比如传入的参数是员工还是菜品还是其它的)

Object[] args = joinPoint.getArgs(); //获得了方法所有的参数
if(args == null || args.length==0 ){ //没有参数
     return;
}
Object entity = args[0];//现在约定实体放在第1个位置,传入实体可能不同所以用Object

3.准备赋值的数据(给公共字段赋值的数据,比如时间就是系统时间,用户ID是从ThreadLocal获取)

4.根据当前不同的操作类型,为对应的属性通过反射来赋值

LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
if(operationType == OperationType.INSERT){
    //为4个公共字段赋值
    try {
        Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); //把方法名全部换成常量类,防止写错
        Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
        Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
        Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
        //4.根据当前不同的操作类型,为对应的属性通过反射来赋值
        setCreateTime.invoke(entity,now);
        setCreateUser.invoke(entity,currentId);
        setUpdateTime.invoke(entity,now);
        setUpdateUser.invoke(entity,currentId);
    }catch (Exception e){
        e.printStackTrace();
    }
}else if(operationType == OperationType.UPDATE){
    try {
        //为2个公共字段赋值
        Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
        Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
        //4.根据当前不同的操作类型,为对应的属性通过反射来赋值
        setUpdateTime.invoke(entity, now);
        setUpdateUser.invoke(entity, currentId);
    }catch (Exception e){
        e.printStackTrace();
    }
}
}

然后要在mapper层的CategoryMapper和EmployeeMapper中的Insert和Update方法上加上@AutoFill注解,注解内容用OperationType.INSERT或OperationType.Update。

最后把service层的那些手动赋值删除掉或者注释掉。

3.18 (字段填充)功能测试P34

在log.info("开始进行公共字段自动填充")上打一个断点。逐步放行看signature的值,看autoFill的值,确定是UPDATE。然后看args数组传入的参数,employee的4个公共字段为空。看准备的数据是否正确,比如当前系统时间,和用户ID这里是1。最后看反射是否为具体的参数赋值,也就是entity。执行完Update语句看控制台是否输出了正确的sql。最后提交一下代码:公共字段自动填充代码实现。

3.19 (新增菜品)设计分析P35

需求分析+接口设计+数据库设计。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

根据类型查询分类;文件上传;新增菜品。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.20 (新增菜品)代码开发+OSS P36

在controller下创建一个CommonController,写入如下代码(只是一个原始版本,改进版本在P37):

@RestController
@RequestMapping("/admin/common")
@Api(tags="通用接口")
@Slf4j
public class CommonController {
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file){
        log.info("文件上传:{}",file);
        return null;
    }
}

然后需要在阿里云上申请一个oss(Object Storage Service) 即对象存储服务,阿里云上有7天试用期。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

下面是获取endpoint、accessKeyId、accessKeySecret和bucketName的方法:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

下面是操作后得到的accessKeyId、accessKeySecret:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

endpoint:oss-cn-shenzhen.aliyuncs.com

accessKeyId:LTAI5tHzX4fMjySKcCoCvYci

accessKeySecret:vPKhVa4Kux8jUP6fU4614CQ3FW0wiC

bucketName:cangqiongwaimaipbj

在application.yml中是使用了引用的方式,引用了application-dev.yml中设置的参数值:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

 配置属性类,读取配置文件的配置项,然后封装为一个Java对象(所以在配置类中会有输出的提示;在配置类中是用横线分割,在类中是用驼峰命名法,框架会自动转换)。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在sky-common下面的utils包里有一个AliOssUtil是一个上传图片的包。

在sky-server下面的config包里添加一个OssConfiguration类。

@Configuration
@Slf4j
public class OssConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
        return new AliOssUtil(aliOssProperties.getEndpoint(),
                aliOssProperties.getAccessKeyId(),
                aliOssProperties.getAccessKeySecret(),
                aliOssProperties.getBucketName());
    }
}

@ConditionalOnMissingBean注解的意思:当没有这个对象的时候再去创建。注意要return的是这个新创建的对象,不然后面自动注入会失败,这里的主要目的就是创建Bean对象,但具体的原理还没有搞清楚。 

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

注意读写权限要像上图一样设置为公共读,这是为了后面前端能正确访问OSS中的图片并显示。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

设置为公共读之后可以看到前端的图片能够正常展示。

3.21 (新增菜品)代码开发P37 

下面是新的commonController代码):

@RestController
@RequestMapping("/admin/common")
@Api(tags="通用接口")
@Slf4j
public class CommonController {
    @Autowired
    private AliOssUtil aliOssUtil;
    //文件上传
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file){
        log.info("文件上传:{}",file);
        try {
            String originalFilename = file.getOriginalFilename();//原始文件名
            //截取原始文件名的的后缀
            String extention = originalFilename.substring(originalFilename.lastIndexOf("."));
            //构造新文件名称
            String objectName = UUID.randomUUID().toString()+extention;
            //文件的请求路径
            String filePath = aliOssUtil.upload(file.getBytes(), objectName);
            return Result.success(filePath);
        }catch(IOException e){
            log.error("文件上传失败:{}",e);
        }
        return null;
    }
}

简单测试一下:看图片是否能在前端显示。 

3.22 (新增菜品)代码开发P38

在sky-server的controller下创建DishController:

@RestController
@RequestMapping("/admin/dish")
@Api(tags="菜品相关接口")
@Slf4j
public class DishController {
    @Autowired
    private DishService dishService;
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}",dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }
}

 在sky-server的service下创建DishService:

public interface DishService {
    //新增菜品和对应的口味
    public void saveWithFlavor(DishDTO dishDTO);
}

在sky-server的service的impl下创建DishServiceImpl:

@Service
@Slf4j
public class DishServiceImpl implements DishService {
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;
    //新增菜品对应的口味
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);
        //想菜品表插入1条数据
        dishMapper.insert(dish);
        //获取insert语句生成的主键值
        Long dishId = dish.getId();
        //向口味表插入n条数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if(flavors != null && flavors.size()>0){
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            //向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
        }
    }
}

一个菜品有多个口味数据,向菜品表插入1条数据,向口味表插入n条数据。

因为涉及到多个表,所以添加@Transactional的注解(需要在启动类上添加@EnableTransactionManagement注解):

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

在sky-server的mapper创建DishMapper类,写入insert方法的代码:

@Mapper
public interface DishMapper {
    //根据分类id查询菜品数量
    @Select("select count(id) from dish where category_id = #{categoryId}")
    Integer countByCategoryId(Long categoryId);
    void insert(Dish dish);
}

 在sky-server的resources下的mapper下创建DishMapper.xml文件,写入如下代码:

<?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.DishMapper">
    <insert id="insert">
        insert into dish(name,category_id,price,image,description,create_time,update_time,create_user,update_user,status)
        values (#{name},#{categoryId},#{price},#{image},#{description},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
    </insert>
</mapper>

在sky-server的mapper创建DishFlavorMapper类,写入insert方法的代码。

@Mapper
public interface DishFlavorMapper {
    @AutoFill(value= OperationType.INSERT)
    void insertBatch(List<DishFlavor> flavors);
}

 在sky-server的resources下的mapper下创建DishFlavorMapper.xml文件,写入如下代码。

<?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.DishFlavorMapper">
    <insert id="insertBatch">
        insert into dish_flavor (dish_id,name,value) VALUES
        <foreach collection="flavors" item="df" separator=",">
            (#{df.dishId},#{df.name},#{df.value})
        </foreach>
    </insert>
</mapper>

在DishMapper和DishFlavorMapper中的insert方法上添加@AutoFill(value= OperationType.INSERT)注解。

3.23 (新增菜品)功能测试P39

采用前后端联调的方式。在DishServiceImpl的saveWithFlavor方法中,首先看一下传入参数dishDTO是否被封装好。然后看拷贝后的dish对象的值是否完整。最后看insert语句后的2个公共字段是否被赋值好。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

没啥问题之后提交代码:新增菜品业务代码开发。

3.24 (分页查询)设计分析P40

请求的参数会被封装成DTO

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

因为categoryName是不存在菜品表里的,现在前端页面要展示分类名称,所以要定义VO,将VO转为JSON数据然后展示。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.25 (分页查询)代码开发P41

在sky-server的controller中已有的DishController类中添加如下代码:

@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){
    log.info("菜品分页查询:{}",dishPageQueryDTO);
    PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
    return Result.success(pageResult);
}

在sky-server的service中已有的DishService类中添加如下代码:

public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);

在sky-server的service中已有的DishServiceImpl类中添加如下代码

public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO){
    PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
    Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
    return new PageResult(page.getTotal(),page.getResult());
}

在sky-server的mapper中已有的DishMapper类中添加如下代码:

Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);

 在sky-server的resources下的mapper下已有的DishMapper.xml中写入如下代码:

<select id="pageQuery" resultType="com.sky.vo.DishVO">
    select d.* , c.name as categoryName from dish d left outer join category c on d.category_id = c.id
    <where>
        <if test="name != null">
            and d.name like concat('%',#{name},'%')
        </if>
        <if test="categoryId != null">
            and d.category_id = #{categoryId}
        </if>
        <if test="status != null">
            and d.status = #{status}
        </if>
    </where>
</select>

可以先在Navicat中编写SQL语句进行查询再写到配置文件中。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

出现问题,有2列都叫name,于是给category表起别名。

select d.* , c.name as categoryName from dish d left outer join category c on d.category_id = c.id

简单测试一下发现没有问题:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.26 (删除菜品)设计分析P42

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

3.27 (删除菜品)代码实现P43

批量删除是在地址栏写入要删除菜品的集合。

在sky-server的controller中已有的DishController类中添加如下代码:

@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids){
        log.info("菜品批量删除:{}",ids);
        dishService.deleteBatch(ids);
        return Result.success();
}

需要加一个注解@RequestParam,可以将地址栏中多个数字参数提取出来然后变成List集合。 

在sky-server的service中已有的DishService类中添加如下代码:

void deleteBatch(List<Long> ids);

在sky-server的service的Impl中已有的DishServiceImpl类中添加如下代码:

@Transactional
public void deleteBatch(List<Long> ids){
    //不能删除:存在起售中的菜品
    for (Long id : ids) {
        Dish dish = dishMapper.getById(id);
        if(dish.getStatus()== StatusConstant.ENABLE){ //状态为1起售中
            //当前菜品处于起售中,不能删除
            throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
        }
    }
    //不能删除:菜品被套餐关联
    List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
    if(setmealIds != null && setmealIds.size()>0){
        //当前菜品被套餐关联了,不能删除
        throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
    }
    //删除菜品表中的菜品数据
    for (Long id : ids) {
        dishMapper.deleteById(id);
        //删除口味数据
        dishFlavorMapper.deleteByDishId(id);
    }

}

批量删除用foreach循环来遍历,删除被套餐关联的SQL语句比较复杂。 

删除菜品表中的菜品数据这里,每次循环需要执行2次SQL,可能会出现性能问题。应该采用如下的SQL形式:delete from dish where id in (?,?,?)。

在sky-server的mapper中已有的DishMapper类中添加如下代码(负责删除菜品):

//根据主键删除菜品
@Delete("delete from dish where id = #{id}")
void deleteById(Long id);

在sky-server的mapper中创建DishFlavorMapper类中添加如下代码(负责删除关联的口味数据):

//根据菜品id删除对应的口味数据
@Delete("delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);

在sky-server的mapper中创建SetmealDishMapper类中添加如下代码(负责查看是否有关联的套餐):

@Mapper
public interface SetmealDishMapper {
    //根据菜品id查询对应的套餐id
    //select setmeal_id from setmeal_dish where dish_id in (1,2,3,4)
    List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
}

(上步SQL具体实现)在sky-server的resources的mapper中创建SetmealDishMapper类中添加如下代码(思路是去查询套餐表,看套餐菜品id是否和当前传入的id相同):

<?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.SetmealDishMapper">
    <select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
        select setmeal_id from setmeal_dish where dish_id in
        <foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
            #{dishId}
        </foreach>
    </select>
</mapper>

这里比较巧妙的是foreach循环,collection是集合,item是一个个项,separator是分割符号,open是开始符号,close是结束符号。每个元素用逗号分割,然后用大括号括起来。 

3.28 (删除菜品)功能测试P44

先测试一下删除单个,再测试批量删除。要注意起售中和被套餐关联的菜品不能被删除。最后提交一下代码:删除菜品业务代码开发。

 在sky-server的service的Impl中已有的DishServiceImpl类中修改代码如下(只是修改最后一部分):

@Transactional
public void deleteBatch(List<Long> ids){
    //不能删除:存在起售中的菜品
    for (Long id : ids) {
        Dish dish = dishMapper.getById(id);
        if(dish.getStatus()== StatusConstant.ENABLE){ //状态为1起售中
            //当前菜品处于起售中,不能删除
            throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
        }
    }
    //不能删除:菜品被套餐关联
    List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
    if(setmealIds != null && setmealIds.size()>0){
        //当前菜品被套餐关联了,不能删除
        throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
    }
    //删除菜品表中的菜品数据
    dishMapper.deleteByIds(ids);
    //删除口味数据
    dishFlavorMapper.deleteByDishIds(ids);
}

在sky-server的mapper中已有的DishMapper类中添加如下代码:

void deleteByIds(List<Long> ids);

在sky-server的resources的mapper中已有的DishMapper.xml配置文件中添加如下代码:

<delete id="deleteByIds">
    delete from dish where id in
    <foreach collection="ids" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

在sky-server的mapper中已有的DishFlavorMapper类中添加如下代码:

void deleteByDishIds(List<Long> dishIds);

在sky-server的resources的mapper中已有的DishFlavorMapper.xml配置文件中添加如下代码:

<delete id="deleteByDishIds">
    delete from dish_flavor where dish_id
    <foreach collection="dishIds" separator="," open="(" close=")">
        #{dishId}
    </foreach>
</delete>

3.29 (修改菜品)分析设计P45

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

根据id查询菜品和口味,回显返回数据。然后修改菜品(用PUT)。

3.30 (修改菜品)代码开发P46

 在sky-server的controller中已有的DishController类中添加代码如下:

@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable Long id){
    log.info("根据id查询菜品:{}",id);
    DishVO dishVO = dishService.getByIdWithFlavor(id);
    return Result.success(dishVO);
}

 在sky-server的service中已有的DishService类中添加代码如下:

//根据id查询菜品
DishVO getByIdWithFlavor(Long id);

 在sky-server的service的Impl中已有的DishServiceImpl类中添加代码如下:

//根据id查询菜品和对应的口味数据
public DishVO getByIdWithFlavor(Long id){
    //根据id查询菜品数据
    Dish dish = dishMapper.getById(id);
    //根据菜品id查询口味数据
    List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);
    //将查询到的数据封装到VO
    DishVO dishVO = new DishVO() ;
    BeanUtils.copyProperties(dish,dishVO);
    dishVO.setFlavors(dishFlavors);
    return dishVO;
}

  在sky-server的mapper中已有的dishFlavorMapper类中添加代码如下:

@Select("select * from dish_flavor where dish_id=#{dishId}")
List<DishFlavor> getByDishId(Long dishId);

3.31 (修改菜品)代码开发P47

 在sky-server的controller中已有的DishController类中添加代码如下:

//修改菜品
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO){
    log.info("修改菜品;{}",dishDTO);
    dishService.updateWithFlavor(dishDTO);
    return Result.success();
}

 在sky-server的service中已有的DishService类中添加代码如下:

//根据id修改菜品基本信息和对应的口味信息
void updateWithFlavor(DishDTO dishDTO);

 在sky-server的service的Impl中已有的DishServiceImpl类中添加代码如下:

//根据id修改菜品基本信息和对应的口味信息
public void updateWithFlavor(DishDTO dishDTO){
    Dish dish = new Dish();
    BeanUtils.copyProperties(dishDTO,dish);
    //修改菜品表基本信息
    dishMapper.update(dish);
    //删除原有的口味数据
    dishFlavorMapper.deleteByDishId(dishDTO.getId());
    //重新插入口味数据
    List<DishFlavor> flavors = dishDTO.getFlavors();
    if(flavors != null && flavors.size()>0){
        flavors.forEach(dishFlavor ->{
            dishFlavor.setDishId(dishDTO.getId());
        });
    }
    dishFlavorMapper.insertBatch(flavors);
}

口味的修改比较麻烦,有可能是删除了再新增,有可能不删除,有可能没删除直接新增。

处理方法:直接把菜品原先关联的口味数据删掉,然后再按照当前传来的口味重新插入数据。

传入DTO不合适,因为DTO里有口味数据,而修改菜品不应该包含口味,所以应该只传入一个Dish数据。

  在sky-server的mapper中已有的dishFlavorMapper类中添加代码如下:

//根据id动态修改菜品
@AutoFill(value=OperationType.UPDATE)
void update(Dish dish);

  在sky-server的resources下的mapper中已有的dishFlavorMapper.xml类中添加代码如下:

<update id="update">
    update dish
    <set>
        <if test="name != null"> name = #{name},</if>
        <if test="categoryId != null">category_id = #{categoryId},</if>
        <if test="price != null">price = #{price},</if>
        <if test="image != null">image = #{image},</if>
        <if test="description != null">description = #{description},</if>
        <if test="status != null">status = #{status},</if>
        <if test="updateTime != null">update_Time = #{updateTime},</if>
        <if test="updateUser != null">update_User = #{updateUser},</if>
    </set>
    where id = #{id}
</update>

这里用的是动态SQL。

3.32 (修改菜品)功能测试P48

前后端联调测试,尝试修改一道菜品,能成功修改即可。

四、Redis系列

本辑讲的是:Redis入门 -> Redis数据类型 -> Redis常用命令 -> 在Java中操作Redis -> 店铺营业状态设置。

4.1 Redis入门 P50

Redis是一个基于内存的key-value结构数据库。读写性能高。因为内存有限所以不能存储所有数据。Redis只存储热点数据。

下载方式:直接在苍穹外卖资料包中第5天的资料中,选择windows版本x64版的压缩包解压。

启动方式:在redis-server.exe和redis.windows.conf所在的目录,点击路径栏,输入cmd,然后输入下面的启动命令:

redis-server.exe redis.windows.conf

下面就是启动成功: 

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

输入ctrl+c就是停止服务。

在server服务端启动的基础上,去启动客户端cli,同样进入当前路径下的cmd,然后输入:

redis-cli.exe

如果出现下面就是成功进入: 

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

输入exit即可退出。

cli客户端可以通过-h指定连接的ip,-p制定连接的端口:

redis-cli.exe -h localhost -p 6379

redis默认没有密码,如果需要密码则修改redis.windows.conf配置文件。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

此时重新登录也并不会提示输入密码,但如果输入keys *则会报错,身份验证被要求。

通过-a的方式来输入密码:

redis-cli.exe -h localhost -p 6379 -a 密码

也可以通过图形化界面来操作服务。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

填入Host和Port和密码即可图形化:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4.2 Redis常用数据类型 P51

Redis存储是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型。

字符串string,哈希hash,列表list,集合set,有序集合zset(sorted set)。

哈希:在value里面又分为field和value。比较适合存储对象,包括属性和值。

列表:类似于一个队列,有顺序,按照插入顺序排序,可以有重复元素。可以存储跟顺序有关系的。

集合:无序集合,没有重复元素。可以运算交集或者并集。

有序集合:集合中每个元素关联一个分数,根据分数升序排序,没有重复元素。适用场景排行榜,或者投票排名、

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4.3 Redis字符串操作P52

可以通过可视化工具来练习Redis语法:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

SET key value                设置指定key的值。

GET key                         获取指定key的值。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

SETEX key seconds value                 设置指定key的值,并将key的过期时间设为seconds秒。

(seconds是时间,时间以秒为单位,下图想说明的是如果过期了,将直接删除key和value)

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

SETNX key value                只有在key不存在时设置key的值。

(下图想说明的是如果已经存在了某个键将无法再重新设置,设置成功返回1,设置失败返回0)

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4.4 Redis哈希操作P53

HSET key field value                将哈希表key中的字段field的值设为value。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

效果展示如下图:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

HGET key field                获取存储在哈希表中指定field的值。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

HDEL key field                删除存储在哈希表中的指定field。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

HKEYS key                获取哈希表中所有field。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

HVALS key                获取哈希表中所有value。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4.5 Redis列表操作P54

列表是简单的字符串列表,跟插入顺序有关,最先插入的会排在最后。

LPUSH key value1 [value2]                将一个或多个值插入到列表头部

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

LRANGE key start stop                获取列表指定范围内的元素

(如果stop为-1则表示获取全部元素)

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

RPOP key                移除并获取列表最后一个元素

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

LLEN key                获取列表长度

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4.6 Redis集合操作P55

集合中没有重复元素。而且没有顺序。

SADD key member1 [member2]                向集合添加一个或多个成员。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

SMEMBERS key                返回集合中的所有成员。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

SCARD key                获取集合的成员数

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

SINTER key1 [key2]                返回给定所有集合的交集

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

SUNION key1 [key2]                返回所有给定集合的并集

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

SREM key member1 [member2]                删除集合中一个或多个成员

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4.7 Redis有序集合操作P56

ZADD key score1 member1 [score2 member2]                向有序集合添加一个或多个成员

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

ZRANGE key start stop [WITHSCORES]                通过索引区间返回有序集合中指定区间内的成员。(stop为-1表示范围为全部,withscores是展示member的同时展示score)

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

ZINCRBY key increment member                有序集合中对指定成员的分数加上增量increment。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

ZREM key member [member ...]                移除有序集合中的一个或多个成员。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4.8 Redis 通用命令P57

KEYS pattern                查找所有符合给定模式(pattern)的key

(*代表全部,剩下的可以是半匹配)

电商项目csdn,前端,数据库,java,mysql,python,项目,实战电商项目csdn,前端,数据库,java,mysql,python,项目,实战

EXISTS key                检查给定key是否存在

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

TYPE key                返回key所储存的值的类型

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

DEL key                该命令用于在key存在时删除key

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

Java中操作Redis

4.9 操作步骤说明 P58

Redis的Java客户端很多,包括:Jedis(所有的方法和Redis是一一对应的)、Lettuce、Spring Data Redis。

Spring Data Redis是Spring的一部分,对Redis底层开发包进行了高度封装。在Spring项目中,可以使用Spring Data Redis来简化操作。

①导入Spring Data Redis的maven坐标

②配置Redis数据源

③编写配置类,创建RedisTemplate对象

④通过RedisTemplate对象操作Redis

4.10 环境搭建P59

①导入Spring Data Redis的maven坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

②配置Redis数据源

方法1:是直接在application.yml中配置,但不推荐,最好使用引用。

spring:
  redis:
    host: localhost
    port: 6379
    database: 0

方法2:是在application-dev.yml中配置具体值

sky:
  redis:
    host: localhost
    port: 6379
    database: 0

在application.yml中配置引用

spring:
  redis:
    host: ${sky.redis.host}
    port: ${sky.redis.port}
    database: ${sky.redis.database} 

 数据库默认是0号数据库(DB0),一直到15,总共16个。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

③编写配置类,创建RedisTemplate对象

 在sky-server下的config包下创建RedisConfiguration类,写入如下代码:

@Configuration
@Slf4j
public class RedisConfiguration {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        log.info("开始创建redis模板对象...");
        RedisTemplate redisTemplate = new RedisTemplate();
        //设置redis的连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置redis key的序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

 在sky-server/src/test/java/com/sky/test下创建Java类SpringDataRedisTest写入如下代码:

@SpringBootTest
public class SpringDataRedisTest {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void testRedisTemplate(){
       System.out.println(redisTemplate);
        ValueOperations valueOperations = redisTemplate.opsForValue();
        HashOperations hashOperations = redisTemplate.opsForHash();
        ListOperations listOperations = redisTemplate.opsForList();
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    }
}

可以先运行当前文件输出一下看看是否生成了对象。 

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

④通过RedisTemplate对象操作Redis

 4.11 操作字符串数据 P60

Redis的String和Java的String不是完全相同。Java中的任何对象都会转为Redis的字符串进行存储。

在sky-server/src/test/java/com/sky/test下已有的Java类SpringDataRedisTest中写入如下代码:

//操作字符串类型的数据
@Test
public void testString(){
    //set get setex setnx
    redisTemplate.opsForValue().set("city","北京");
    String city = (String)redisTemplate.opsForValue().get("city");
    System.out.println(city);
    redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES);//第3个参数是时间,第4个参数是时间单位
    redisTemplate.opsForValue().setIfAbsent("lock","1");
    redisTemplate.opsForValue().setIfAbsent("lock","2");

}

 value会有乱码的现象,key不会有问题,是因为序列化器不同,所以结果不同。

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4.12 操作哈希数据P61

在sky-server/src/test/java/com/sky/test下已有的Java类SpringDataRedisTest中写入如下代码:

@Test
public void testHash(){
    //hset hget hkeys hvals
    HashOperations hashOperations = redisTemplate.opsForHash();
    hashOperations.put("100","name","tom"); //相当于hset
    hashOperations.put("100","age","20");
    String name = (String)hashOperations.get("100", "name"); //相当于hget
    System.out.println(name);
    Set keys = hashOperations.keys("100"); //相当于hkeys
    System.out.println(keys);
    List values = hashOperations.values("100"); //相当于hvals
    System.out.println(values);
    hashOperations.delete("100","age");//相当于hdel
    
}

控制台输出结果如下:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战

4.13 操作其它类型数据P62

列表(list)类数据:

在sky-server/src/test/java/com/sky/test下已有的Java类SpringDataRedisTest中写入如下代码:

@Test
public void testList(){
    //Lpush lrange rpop llen
    ListOperations listOperations = redisTemplate.opsForList();
    listOperations.leftPushAll("mylist","a","b","c"); //lpush多个value
    listOperations.leftPush("mylist","d"); //lpush单个value
    List mylist = listOperations.range("mylist",0,-1); //lrange
    System.out.println(mylist);
    listOperations.rightPop("mylist"); //rpop
    Long size = listOperations.size("mylist"); //llen
    System.out.println(size);
}

电商项目csdn,前端,数据库,java,mysql,python,项目,实战 电商项目csdn,前端,数据库,java,mysql,python,项目,实战

集合类数据:

在sky-server/src/test/java/com/sky/test下已有的Java类SpringDataRedisTest中写入如下代码:

@Test
public void testSet(){
    //sadd smembers scard sinter sunion srem
    SetOperations setOperations = redisTemplate.opsForSet();
    setOperations.add("set1","a","b","c","d"); //sadd
    setOperations.add("set2","a","b","x","y");
    Set members = setOperations.members("set1"); //smembers
    System.out.println(members);
    Long size = setOperations.size("set1"); //scard
    System.out.println(size);
    Set intersect = setOperations.intersect("set1","set2"); //sinter取交集
    System.out.println(intersect);
    Set union = setOperations.union("set1","set2"); //sunion取并集
    System.out.println(union);
    setOperations.remove("set1","a","b"); //srem
}

有序集合类数据:

在sky-server/src/test/java/com/sky/test下已有的Java类SpringDataRedisTest中写入如下代码:

@Test
public void testZset(){
    //zadd zrange zincrby zrem
    ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    zSetOperations.add("zset1","a",10); //zadd
    zSetOperations.add("zset2","b",12);
    zSetOperations.add("zset1","c",9);
    Set zset1 = zSetOperations.range("zset1",0,-1); //zrange
    System.out.println(zset1);
    zSetOperations.incrementScore("zset1","c",10); //zincrby
    zSetOperations.remove("zset1","a","b"); //zrem
}

通用命令操作:

在sky-server/src/test/java/com/sky/test下已有的Java类SpringDataRedisTest中写入如下代码:

@Test
public void testCommon(){
    //keys exists type del
    Set keys = redisTemplate.keys("*"); //keys
    System.out.println(keys);
    Boolean name = redisTemplate.hasKey("name"); //exists
    Boolean set1 = redisTemplate.hasKey("set1");
    for(Object key : keys){
        DataType type = redisTemplate.type(key); //type
        System.out.println(type.name());
    }
    redisTemplate.delete("mylist"); //del
}

4.14(营业状态设置)分析设计P63

设置营业状态;管理端查询营业状态,用户端查询营业状态(管理端和用户端查询路径不同)。

营业状态存储在Redis中,不用在Mysql中单独创建一张表。

4.15(营业状态设置)代码开发P64

先把test中的SpringDataRedisTest里的@SpringBootTest注释掉。

这里要注意一点:营业状态是存储在Redis中的,SHOP_STATUS这个key不能够通过redis直接修改,这里必须通过前端页面(管理端电脑)进行修改,也就是调用下面的setStatus,因为redis和SpringDataRedis的序列化器是不同的。

在controller/admin下创建ShopController这个是管理端的,写入如下代码:

@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Api(tags="店铺相关接口")
@Slf4j
public class ShopController {
    public static final String KEY="SHOP_STATUS";
    @Autowired
    private RedisTemplate redisTemplate;
    //设置店铺营业状态
    @PutMapping("/{status}")
    @ApiOperation("设置店铺的营业状态")
    public Result setStatus( @PathVariable Integer status){
        log.info("设置店铺的营业状态为:{}",status==1 ?"营业中":"打烊中");
        redisTemplate.opsForValue().set("SHOP_STATUS",status);
        return Result.success();
    }
    //获取店铺的营业状态
    @GetMapping("/status")
    @ApiOperation("获取店铺的营业状态")
    public Result<Integer> getStatus(){
        Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
        log.info("获取到店铺的营业状态为:{}",status==1?"营业中":"打烊中");
        return Result.success(status);
    }
}

在controller下创建user包,然后把amin的ShopController复制到这个下面,然后进行简单修改,只保留获取状态的。因为有2个类类名相同,会导致Bean冲突,所以我们要在@RestController中指定Bean的名称。

@RestController("userShopController")
@RequestMapping("/user/shop")
@Api(tags="店铺相关接口")
@Slf4j
public class ShopController {
    public static final String KEY="SHOP_STATUS";
    @Autowired
    private RedisTemplate redisTemplate;
    //获取店铺的营业状态
    @GetMapping("/status")
    @ApiOperation("获取店铺的营业状态")
    public Result<Integer> getStatus(){
        Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
        log.info("获取到店铺的营业状态为:{}",status==1?"营业中":"打烊中");
        return Result.success(status);
    }
}

4.16(营业状态设置)功能测试P65

直接通过前后端联调的方式,去修改营业的状态,然后退出看状态是否保持,在Redis可视化界面中是否有相关的key-value记录。

因为现在接口用户端和管理端没有区分开,很不好看,所以现在在sky-server下的config的WebMvcConfiguration中写入如下代码:

@Bean
public Docket docket() {
    ApiInfo apiInfo = new ApiInfoBuilder()
            .title("苍穹外卖项目接口文档")
            .version("2.0")
            .description("苍穹外卖项目接口文档")
            .build();
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
            .groupName("管理端接口")
            .apiInfo(apiInfo)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
            .paths(PathSelectors.any())
            .build();
    return docket;
}
@Bean
public Docket docket1() {
    ApiInfo apiInfo = new ApiInfoBuilder()
            .title("苍穹外卖项目接口文档")
            .version("2.0")
            .description("苍穹外卖项目接口文档")
            .build();
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
            .groupName("用户端接口")
            .apiInfo(apiInfo)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
            .paths(PathSelectors.any())
            .build();
    return docket;
}

可以分为用户端和管理端的接口:

电商项目csdn,前端,数据库,java,mysql,python,项目,实战文章来源地址https://www.toymoban.com/news/detail-820704.html

到了这里,关于《苍穹外卖》电商实战项目(java)知识点整理(P1~P65)【上】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java与es8实战之三:Java API Client有关的知识点串讲

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇是《java与es8实战》系列的第三篇,将一些重要的知识点在这里梳理清楚,为后面的实践奠定基础 一共有七个与Java API Client有关的重要知识点 关于namespace:每个feature都有自己的package 命名规则:

    2024年02月11日
    浏览(44)
  • SpringBoot+SSM项目实战 苍穹外卖(12) Apache POI

    继续上一节的内容,本节是苍穹外卖后端开发的最后一节,本节学习Apache POI,完成工作台、数据导出功能。 工作台是系统运营的数据看板,并提供快捷操作入口,可以有效提高商家的工作效率。 工作台展示的数据:今日数据、订单管理、菜品总览、套餐总览、订单信息 营业

    2024年01月16日
    浏览(54)
  • SpringBoot+SSM项目实战 苍穹外卖(11) Apache ECharts

    继续上一节的内容,本节学习Apache ECharts,实现营业额统计、用户统计、订单统计和销量排名Top10功能。 数据统计效果图: Apache ECharts 是一款基于 Javascript 的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。 常见效果:柱形图、饼形图、折线图

    2024年01月17日
    浏览(41)
  • 最适合新手的SpringBoot+SSM项目《苍穹外卖》实战—(五)员工管理

    黑马程序员最新Java项目实战《苍穹外卖》,最适合新手的SpringBoot+SSM的企业级Java项目实战。 设计 DTO 类 我们需要根据新增员工接口设计对应的 DTO 类去接收前端传递的参数,前端传递参数列表如下: 注意: 当前端提交的数据和实体类中对应的属性差别比较大时,建议使用

    2024年02月15日
    浏览(45)
  • Vue项目实战——【基于 Vue3.x + Vant UI】实现一个多功能记账本(项目演示、涉及知识点、源码分享)

    1、前言 如果你对 vue3.x 的基础知识还很陌生,推荐先去学习一下 vue 基础 内容 参考链接 Vue2.x全家桶 Vue2.x全家桶参考链接 Vue3.x知识一览 Vue3.x重点知识参考链接 如果你 刚学完 vue3 , 想检查一下自己的学习成果 如果你 已学完 vue3 , 想快速回顾复习所学知识 如果你 已精通

    2024年01月18日
    浏览(53)
  • 最适合新手的SpringBoot+SSM项目《苍穹外卖》实战—(二)开发环境搭建

    黑马程序员最新Java项目实战《苍穹外卖》,最适合新手的SpringBoot+SSM的企业级Java项目实战。 前端工程基于 nginx 运行,因为《苍穹外卖》项目侧重于后端开发,所以黑马程序员给我们直接提供了前端的代码部分,我们只需要在本地搭建好前端环境,并运行起来,专注于后端开

    2024年02月10日
    浏览(41)
  • 【学习笔记】java项目—苍穹外卖day11

    Apache ECharts 营业额统计 用户统计 订单统计 销量排名Top10 功能实现: 数据统计 数据统计效果图: 1.1 介绍 Apache ECharts 是一款基于 Javascript 的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。 官网地址:https://echarts.apache.org/zh/index.html 常见效果

    2024年04月09日
    浏览(47)
  • Java项目-苍穹外卖-Day10-SpirngTask及WebSocket

    本章实现的业务功能 超时未支付订单自动取消,配送中订单商家忘点完成自动再固定时间检查且修改成完成状态 来单提醒功能 催单提醒功能 一般的话周几和第几日是不能同时出现的 因为比如 4月15日 周四 可能4月15日不是周四 可能冲突的 所以周和日一般只能有一个 现在有

    2024年02月09日
    浏览(43)
  • Java项目-苍穹外卖-Day11-Apache ECharts数据统计

    主要是以下四项的统计,以不同形式的图形进行展示 自己去网站上看一哈,我不太懂前端 com.sky.controller.admin.ReportController com.sky.service.impl.ReportServiceImpl.java orderMapper orderMapper.xml Reportcontroller ReportServiceImpl orderMapper.xml reportController ReportServiceImpl orderMapper.xml

    2024年02月09日
    浏览(46)
  • 项目知识点记录

    Java程序使用 JDBC 接口访问关系数据库的时候,需要以下几步: 创建全局 DataSource 实例,表示数据库连接池; 在需要读写数据库的方法内部,按如下步骤访问数据库: 从全局 DataSource 实例获取 Connection 实例; 通过 Connection 实例创建 PreparedStatement 实例; 执行 SQL 语句,如果是

    2024年02月13日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包