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

news2024/11/14 6:35:36

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

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

《苍穹外卖》项目实操笔记【下】:P123~P189《苍穹外卖》项目实操笔记【下】

(PS:如在做项目的过程中遇到问题,建议先自行解决,如实在解决不了,可添加文末微信咨询答疑,收费标准按8.88元/人次,避免改BUG的痛苦;如需源码可联系博主暂售5.88元。建议慎重选择。毕设需求定制、网站搭建均欢迎咨询,专业实惠。)

一、重要知识点精讲

1.1 nginx反向代理P11

1. nginx反向代理好处:

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

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

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

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

2. nginx反向代理的搭建:

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

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

3. nginx负载均衡的配置:

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

4. nginx负载均衡的策略:

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

技巧:

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

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

重要知识点:

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. 上线运维:软件环境安装、配置。

角色分工

软件环境

2.2 苍穹外卖项目介绍P4

项目介绍

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

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

产品原型(产品经理)

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

技术选型(架构师)

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

2.3 前端环境搭建P5

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

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

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

2.4 后端环境搭建P6

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

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

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

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创建远程仓库,选中根目录即可,若右上角出现标志说明成功:

 

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

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

然后创建Git远程仓库

在gitee上创建远程仓库:

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

 

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

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

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

2.6 搭建数据库P8

一共11张表如下:

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

2.7 前后端联调P9

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

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

 

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

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

2.8 调试方法+JWT令牌P10

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

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

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

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

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

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

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

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

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

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

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

2.9 nginx反向代理P11

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

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

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

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

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

2.10 完善登录功能P12

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

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

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

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

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

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

2.11 导入接口文档P13

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

前后端分离开发流程

操作步骤

2.12 SwaggerP14

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

它会扫描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即可: 

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

 

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

 

Swagger常用注解P15

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

@ApiModel注解使用样例:

@ApiModelProperty注解使用样例:

@ApiOperation注解使用样例:

 

注解影响如下图:

三、模块开发

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

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

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

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

数据库设计如下:

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

下面是开启驼峰命名:

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

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

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

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

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

然后带着参数值发送:

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

下面是前后端联调成功:

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个词,然后拼接后输出。

 

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

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

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

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

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

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

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

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

 

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

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

 

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

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

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

 

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

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

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可以简化分页代码的编写:

在EmployeeMapper中编写方法:

Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

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

在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,代表是员工姓名查询,它只会返回带有相关词(字也可以,因为是模糊查询)的员工信息。

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

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

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

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

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

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

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

方法一:在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里面有关于日期时间的序列化和反序列化器。 

按下面方式推送一下:

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

 

{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

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

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

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

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

3.14 导入分类管理代码P30

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

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

 

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

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

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

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

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

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

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

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

写入如下代码:

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

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

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这个注解。

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

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

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

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

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

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

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天试用期。

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

 

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

endpoint:oss-cn-shenzhen.aliyuncs.com

accessKeyId:LTAI5tHzX4fMjySKcCoCvYci

accessKeySecret:vPKhVa4Kux8jUP6fU4614CQ3FW0wiC

bucketName:cangqiongwaimaipbj

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

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

在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对象,但具体的原理还没有搞清楚。 

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

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

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注解):

在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个公共字段是否被赋值好。

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

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

请求的参数会被封装成DTO

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

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语句进行查询再写到配置文件中。

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

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

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

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

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

根据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

下面就是启动成功: 

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

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

redis-cli.exe

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

输入exit即可退出。

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

redis-cli.exe -h localhost -p 6379

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

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

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

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

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

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

4.2 Redis常用数据类型 P51

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

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

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

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

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

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

4.3 Redis字符串操作P52

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

SET key value                设置指定key的值。

GET key                         获取指定key的值。

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

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

 

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

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

4.4 Redis哈希操作P53

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

效果展示如下图:

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

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

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

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

4.5 Redis列表操作P54

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

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

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

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

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

LLEN key                获取列表长度

4.6 Redis集合操作P55

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

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

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

SCARD key                获取集合的成员数

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

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

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

4.7 Redis有序集合操作P56

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

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

 

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

 

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

 

4.8 Redis 通用命令P57

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

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

EXISTS key                检查给定key是否存在

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

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

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个。

③编写配置类,创建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();
    }
}

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

④通过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不会有问题,是因为序列化器不同,所以结果不同。

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
    
}

控制台输出结果如下:

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

 

集合类数据:

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2089958.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

phpstorm远程连接阿里云项目

1.安全组 打开端口3306&#xff0c;22 一、.远程连接mysql 1.1新建一个远程连接的用户&#xff08;root用户默认不能远程&#xff0c;不用修改&#xff09; -- 登录到MySQL mysql -u root -p -- 授权远程用户 GRANT ALL PRIVILEGES ON *.* TO 远程用户名% IDENTIFIED BY 远…

ffmpeg视频转切片m3u8并加密videojs播放hls.js播放dplayer播放(弹幕效果)

文章目录 学习链接步骤安装openssl生成一个enc.key文件生成加密串创建enc.keyinfo文件视频切分m3u8文件 web前端查看m3u8视频后台返回enc.key的接口videojs播放m3u8视频 其它videojs切换播放视频hls.js切换播放视频dplayer切换播放视频(弹幕) 学习链接 Java实现视频加密及播放…

redis主从同步

redis 相比于memcached可以永久缓存。 首先停掉mysql源码安装redis 1. 安装redis cd /root/redis-6.2.4 make # 直接make&#xff0c;里面已经配置过了 make install# 安装 cd utils/ ./install_server.sh需要在文件里面注释掉&#xff0c;systemd判断语句 vim install_s…

【GPT】Coze使用开放平台接口-【2】创建工作流-语音伪造检测工作流

在Coze使用开放平台接口-【1】创建插件&#xff0c;我们已经成功创建了开放平台的插件&#xff0c;也创建了对应的工具。本文档就根据创建好的插件&#xff0c;来创建对应的工作流&#xff0c;来让接口能够用起来。 下面直接用现成的插件快商通AI开放平台&#xff0c;来创建语音…

202408830使用python3给BGR3的裸图加上BMP图的文件头

202408830使用python3给BGR3的裸图加上文件头 2024/8/30 20:20 缘起&#xff0c;获取SONY索尼的8530的机芯的4K的BGR3的裸图。可以使用7-yuv打开了。 如果直接可以给这张YUV图加上BMP格式的文件头&#xff0c;就可以直接使用标准的图像处理软件/ACDSee5.0打开了&#xff01; 这…

RISC-V单片机智能落地扇方案

在众多产品中&#xff0c;智能落地扇产品凭借其出色的性能和质量优势&#xff0c;备受消费者青睐。智能落地扇有着卓越的性能和智能化的操作。 RAMSUN提供的智能落地扇方案主控单片机芯片采用RISC-V微处理器&#xff0c;内置高速存储器&#xff0c;最高工作频率可达144MHz&…

【智能算法改进】多策略融合的改进黑猩猩搜索算法及其应用

目录 1.算法原理2.改进点3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】黑猩猩优化算法&#xff08;ChOA&#xff09;原理及实现 2.改进点 改进的 Sine 混沌映射初始化种群 ChoA 种群随机初始化的方法导致种群多样性、均匀性差、容易出现边界聚集现象&#xff0c…

E30.【C语言】练习:sizeof和strlen的习题集(B)

Exercise 4:sizeof 字符串 求下列代码打印结果 #define CRT_NO_WARNINGNESS 1 #include <stdio.h> #include <string.h> int main() {char arr[] "abcdef";printf("%zd\n", sizeof(arr));printf("%zd\n", sizeof(arr 0));printf…

gitee 简单使用

目录 1. github 介绍 2. gitee 介绍 2.1 下载工具 2.2 创建仓库 2.3 clone 远程仓库到本地 2.4 上传代码 2.4.1 add:预添加到本地仓库 2.4.2 commit:提交代码到仓库 2.4.3 push:推送添加操作到远程仓库 3. 问题解决 3.1 文件推送成功&#xff0c;gitee没有绿点 3.2…

UR5e Gazebo仿真

一、环境准备 安装ROS Noetic 首先&#xff0c;确保你的Ubuntu 20.04系统已经安装了ROS Noetic。如果未安装&#xff0c;可以通过ROS的官方仓库进行安装。创建工作空间 创建一个新的ROS工作空间&#xff0c;用于存放UR5e相关的代码和配置文件。 bash复制代码 mkdir -p ~/ur5e…

Qt:玩转QPainter序列六(图形)

前言 继续看源码。 正文 剩下的大部分都是画各种图形的函数&#xff0c;它们一般都有多个重载版本&#xff0c;我就不一 一介绍使用了&#xff0c;只挑其中的一部分使用一下。 在 QPainter 类中&#xff0c;这些方法涉及到绘图的各种功能&#xff0c;主要用于设置视图变换、…

AI大模型日报#0830:智谱AI推出多款升级模型、阿里开源多模态Qwen2-VL

导读&#xff1a;AI大模型日报&#xff0c;爬虫LLM自动生成&#xff0c;一文览尽每日AI大模型要点资讯&#xff01;目前采用“文心一言”&#xff08;ERNIE-4.0-8K-latest&#xff09;、“智谱AI”&#xff08;glm-4-0520&#xff09;生成了今日要点以及每条资讯的摘要。欢迎阅…

Vue前端开发必备工具——json-server模拟后端服务器

文章目录 1、简介2、安装3、快速使用(1)创建JSON文件&#xff1a;(2) 启动json-server(3) 使用API(4) 以其它端口号启动(5) 启动多个API 当我们用Vue开发前端时&#xff0c;如果想调用后端接口&#xff0c;又没有真实的后端接口服务器怎么办&#xff1f;答案是&#xff1a;可以…

数据结构(邓俊辉)学习笔记】串 05——KMP算法:理解next[]表

文章目录 1.快速移动2.避免回溯3.通配哨兵 1.快速移动 在接下来这节&#xff0c;就让我们从严格的意义上来理解 next 表的具体含义及其原理。 我们已经切实地看到&#xff0c; KMP 算法的优化效果首先体现在它可以使模式串得以快速地后移&#xff0c;而不是如蛮力算法那样只…

jmeter连接mysql数据库以及常规用法

1、在jmeter中新建一个测试计划&#xff0c;在测试计划界面中点击浏览&#xff0c;选择连接mysql数据库的jar包 如果没有jar包可以去网上下载&#xff0c;也可以通过如下链接进行下载 链接: https://pan.baidu.com/s/1BI6f19KSzXGlkSOwbnequw 提取码: gn8e 2、然后创建线程组&a…

CentOS7安装docker小记

首先你得需要有一个虚拟机&#xff0c;我的配置如图&#xff1a; 安装docker的工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 指定阿里云的仓库 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.re…

生信机器学习入门3 - Scikit-Learn训练机器学习分类感知器

1. 在线读取iris数据集 import os import pandas as pd# 下载 try:s https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.dataprint(From URL:, s)df pd.read_csv(s,headerNone,encodingutf-8)except HTTPError:s iris.data# 读取.data文件&#xff0c;…

使用python导出Excel表格中的lua配置

背景&#xff1a;游戏开发中&#xff0c; 策划使用Excel配置游戏中的参数数据&#xff0c;写一个工具用于导出这些配置 工具选择使用 python来开发&#xff0c;这样Windows、macOS、Linux平台都可以使用&#xff0c;而且有丰富的第三方模块。 本机先安装python&#xff0c;我…

nvidia-cuda-tensorrt-cudnn下载网站

tensorrt:https://developer.nvidia.com/tensorrt/download cudnn:https://developer.nvidia.com/rdp/cudnn-archive cuda:https://developer.nvidia.com/cuda-toolkit-archive

Python编码系列—Python微服务架构:构建可扩展的云原生应用

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…