苍穹外卖项目学习总结

声明:参考资料来源黑马程序员苍穹外卖项目

目录

一、苍穹外卖项目

1、项目介绍

2、完善登陆功能

3、员工管理、分类管理

4、菜品管理

5、套餐管理

6、店铺营业状态设置

一、苍穹外卖项目

1、项目介绍

1)定位:专门为餐饮企业定制的一款软件产品;

功能架构:体现项目中的业务功能模块,分为管理端、用户端;

管理端:员工管理、分类管理、菜品管理、套餐管理、订单管理、工作台、数据统计、来单提醒;

用户端:微信登录、商品浏览、购物车、用户下单、微信支付、历史订单、地址管理、用户催单;

产品原型:用于展示项目的业务功能,一般由产品经理进行设计;

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

2)整体结构

前端工程基于nignx运行;

后端工程基于maven进行项目构建,并进行分模块开发;

项目结构

序号

名称

说明

1

sky-take-out

maven父工程,统一管理依赖版本,聚合其他子模块

2

sky-common

子模块,存放公共类,例如:工具类、常量类、异常类等

3

sky-pojo

子模块,存放实体类、VODTO

4

sky-server

子模块,后端服务,存放配置文件、ControllerServiceMapper

 

3)使用Git进行项目代码的版本控制;

创建Git本地仓库;

创建Git远程仓库;

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

4)通过数据库建表语句创建数据库表结构;

序号

表名

中文名

1

employee

员工表

2

category

分类表

3

dish

菜品表

4

dish_flavor

菜品口味表

5

setmeal

套餐表

6

setmeal_dish

套餐菜品关系表

7

user

用户表

8

address_book

地址表

9

shopping_cart

购物车表

10

orders

订单表

11

order_detail

订单明细表

5)nginx反向代理,就是将前端发送动态请求由nginx转发到后端服务器;

nginx反向代理的好处:

提高访问速度;

进行负载均衡(把大量请求按照我们指定的方式均衡的分配给集群中的每台服务器);

保证后端服务安全;

nginx负载均衡策略;

 

2、完善登陆功能

1)问题:员工表中的密码是明文存储,安全性太低;

解决办法:将密码加密后存储,提高安全性,使用MD5加密方式对明文密码加密;

MD5算法:可以产生出一个128位(16字节)的散列值。

2)将资料中的项目接口导入YApi。

3)Swagger

使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面;

 官网:https://swagger.io/;

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

使用方式

导入 knife4j 的maven坐标;

在配置类中加入 knife4j 相关配置;

设置静态资源映射,否则接口文档页面无法访问;

接口文档访问路径为 http://ip:port/doc.html;

Yapi 是设计阶段使用的工具,管理和维护接口;

Swagger 在开发阶段使用的框架,帮助后端开发人员做后端的接口测试。

通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:

3、员工管理、分类管理

1)该部分具体实现功能:

新增员工、员工分页查询、启用禁用员工账号、编辑员工、导入分类模块功能代码;

2)步骤:需求分析和设计、代码开发、功能测试、代码完善;

注意:当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据;

3)代码开发

在EmployeeController中创建方法,接收前端提交的参数;

在EmployeeService接口中声明方法;

在EmployeeServiceImpl中实现方法;

在EmployeeMapper中声明方法,通过注解或在xml文件中实现;

4)功能测试

通过接口文档测试;

通过前后端联调测试;

注意:由于开发阶段前端和后端是并行开发的,后端完成某个功能后,此时前端对应的功能可能还没有开发完成, 导致无法进行前后端联调测试。所以在开发阶段,后端测试主要以接口文档测试为主。

5)代码完善

新增员工存在的问题

①、录入的用户名已存在,抛出异常后没有处理;

②、新增员工时,创建人id和修改人id设置为了固定值;

解决办法

①、通过全局异常处理器处理;

②、动态获取当前登录员工的id,员工登录成功后会生成JWT令牌并响应给前端,后续请求中,前端会携带JWT令牌,通过JWT令牌可以解析出当前登录员工id,在拦截器中解析出当前登录员工id,并放入线程局部变量中,在Service中获取线程局部变量中的值;

ThreadLocal 并不是一个Thread,而是Thread的局部变量。

ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。 ThreadLocal常用方法:

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

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

public void remove()        移除当前线程的线程局部变量;

员工分页查询存在的问题

操作时间字段展示有问题

解决办法

方式一:在属性上加入注解,对日期进行格式化;

方式二:在 WebMvcConfiguration 中扩展Spring MVC的消息转换器,统一对日期类型进行格式化处理

4、菜品管理

1)公共字段自动填充,公共字段如下:

问题:代码不便于后期维护;

解决办法:

自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法;

自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值;

在 Mapper 的方法上加入 AutoFill 注解。

2)新增菜品

步骤:需求分析和设计、代码开发、功能测试;

业务规则:

菜品名称必须是唯一的;

菜品必须属于某个分类下,不能单独存在;

新增菜品时可以根据情况选择菜品的口味;

每个菜品必须对应一张图片;

接口设计:根据类型查询分类(已完成)、文件上传、新增菜品;

开发文件上传接口:

在yml文件中配置阿里云endpoint、access-key-id、access-key-secret、bucket-name;

创建阿里云Oss工具类;

创建CommonController类,实现文件上传功能;

开发新增菜品接口:

在DishController中创建方法,接收前端提交的参数;

在DishService接口中声明方法;

在DishServiceImpl中实现方法;

在DishMapper中声明方法,在xml中实现批量插入菜品数据和口味数据;

功能测试:

可以通过接口文档进行测试,也可以进行前后端联调测试。

3)菜品分页查询

业务规则: 根据页码展示菜品信息;

每页展示10条数据;

分页查询时可以根据需要输入菜品名称、菜品分类、菜品状态进行查询;

根据菜品分页查询接口定义设计对应的DTO来接收前端传的数据;

根据菜品分页查询接口定义设计对应的VO来接收后端返回的数据;

根据接口定义创建DishController的page分页查询方法;

在 DishService 中扩展分页查询方法;

在 DishServiceImpl 中实现分页查询方法;

在 DishMapper 接口中声明 pageQuery 方法;

在 DishMapper.xml 中编写SQL;

功能测试:

可以通过接口文档进行测试,也可以进行前后端联调测试。

4)删除菜品

业务规则: 可以一次删除一个菜品,也可以批量删除菜品;

起售中的菜品不能删除;

被套餐关联的菜品不能删除;

删除菜品后,关联的口味数据也需要删除掉;

代码开发

根据删除菜品的接口定义在DishController中创建方法;

在DishService接口中声明deleteBatch方法;

在DishServiceImpl中实现deleteBatch方法,注意需要判断起售状态和套餐是否关联来决定是否抛出异常,其中异常信息由常量类来提供;

在DishMapper中声明getById方法,并配置SQL;

创建SetmealDishMapper,声明getSetmealIdsByDishIds方法,并在xml文件中编写SQL;

在DishMapper.xml中声明deleteById方法并配置SQL;

功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可。

5)修改菜品

接口设计:

根据id查询菜品;

根据类型查询分类(已实现);

文件上传(已实现);

修改菜品;

代码开发 

先查询后修改

在DishController中创建方法,接收前端提交的参数;

在DishService接口中声明方法;

在DishServiceImpl中实现方法;

在DishFlavorMapper中声明方法,通过注解或在xml文件中实现,查询菜品,在DishMapper中声明方法,在xml文件中实现;

功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可。

5、套餐管理

1)新增套餐、根据分类id查询菜品

业务规则:

  • 套餐名称唯一

  • 套餐必须属于某个分类

  • 套餐必须包含菜品

  • 名称、分类、价格、图片为必填项

  • 添加菜品窗口需要根据分类类型来展示菜品

  • 新增的套餐默认为停售状态

接口设计(共涉及到4个接口):

  • 根据类型查询分类(已完成)

  • 根据分类id查询菜品

  • 图片上传(已完成)

  • 新增套餐

代码实现

根据分类查询id接口设计

在DishController中创建方法,接收前端提交的参数;

在DishService接口中声明方法;

在DishServiceImpl中实现方法;

在DishMapper中声明方法,在xml文件中实现;

新增套餐接口设计

在SetmealController中创建方法,接收前端提交的参数;

在SetmealService接口中声明方法;

在SetmealServiceImpl中实现方法;

在SetmealMapper中声明方法,在xml文件中实现;

功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可。

2)套餐分页查询

业务规则:

  • 根据页码进行分页展示

  • 每页展示10条数据

  • 可以根据需要,按照套餐名称、分类、售卖状态进行查询

在SetmealController中创建方法,接收前端提交的参数;

在SetmealService接口中声明方法;

在SetmealServiceImpl中实现方法;

在SetmealMapper中声明方法,在xml文件中实现;

功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可。

3)删除套餐

业务规则:

  • 可以一次删除一个套餐,也可以批量删除套餐

  • 起售中的套餐不能删除

在SetmealController中创建方法,接收前端提交的参数;

在SetmealService接口中声明方法;

在SetmealServiceImpl中实现方法;

在SetmealMapper中声明方法,通过注解实现;

功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可。

4)修改套餐

接口设计(共涉及到5个接口):

  • 根据id查询套餐

  • 根据类型查询分类(已完成)

  • 根据分类id查询菜品(已完成)

  • 图片上传(已完成)

  • 修改套餐

根据id查询套餐和修改套餐接口设计

在SetmealController中创建方法,接收前端提交的参数;

在SetmealService接口中声明方法;

在SetmealServiceImpl中实现方法;

在SetmealMapper中声明方法,通过注解实现;

功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可。

5)起售停售套餐

业务规则:

  • 可以对状态为起售的套餐进行停售操作,可以对状态为停售的套餐进行起售操作

  • 起售的套餐可以展示在用户端,停售的套餐不能展示在用户端

  • 起售套餐时,如果套餐内包含停售的菜品,则不能起售

在SetmealController中创建方法,接收前端提交的参数;

在SetmealService接口中声明方法;

在SetmealServiceImpl中实现方法;

在SetmealMapper中声明方法,通过注解实现;

功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可。

6、店铺营业状态设置

1)Redis:是一个基于内存的 key-value 结构数据库。

基于内存存储,读写性能高;

适合存储热点数据(热点商品、资讯、新闻);

企业应用广泛;

服务启动命令:redis-server.exe redis.windows.conf;

Redis服务默认端口号为 6379 ,通过快捷键Ctrl + C 即可停止Redis服务;

客户端连接命令:redis-cli.exe;

通过redis-cli.exe命令默认连接的是本地的redis服务,并且使用默认6379端口。也可以通过指定如下参数连接: -h ip地址 -p 端口号 -a 密码(如果需要);

设置Redis服务密码,修改redis.windows.conf;

Redis客户端图形工具:

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

字符串 string;

哈希 hash;

列表 list;

集合 set;

有序集合 sorted set / zset;

字符串(string):普通字符串,Redis中最简单的数据类型;

哈希(hash):也叫散列,类似于Java中的HashMap结构;

列表(list):按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList ;

集合(set):无序集合,没有重复元素,类似于Java中的HashSet;

有序集合(sorted set / zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素;

Redis 字符串类型常用命令:

SET key value            设置指定key的值;

GET key            获取指定key的值;

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

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

Redis hash 是一个string类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:

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

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

HDEL key field        删除存储在哈希表中的指定字段;

HKEYS key         获取哈希表中所有字段;

HVALS key         获取哈希表中所有值;

Redis 列表是简单的字符串列表,按照插入顺序排序,常用命令:

LPUSH key value1 [value2]     将一个或多个值插入到列表头部(左边);

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

RPOP key             移除并获取列表最后一个元素(右边);

LLEN key             获取列表长度;

Redis set 是string类型的无序集合。集合成员是唯一的,集合中不能出现重复的数据,常用命令: SADD key member1 [member2]     向集合添加一个或多个成员

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

SCARD key             获取集合的成员数;

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

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

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

Redis有序集合是string类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数。常用命令:

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

ZRANGE key start stop [WITHSCORES]         通过索引区间返回有序集合中指定区间内的成员; ZINCRBY key increment member             有序集合中对指定成员的分数加上增量 increment; ZREM key member [member ...]             移除有序集合中的一个或多个成员;

Redis的通用命令是不分数据类型的,都可以使用的命令:

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

EXISTS key         检查给定 key 是否存在;

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

DEL key         该命令用于在 key 存在是删除 key;

2)在java中操作Redis

Redis 的 Java 客户端很多,常用的几种: Jedis、Lettuce、Spring Data Redis;

Spring Data Redis使用方式:

操作步骤: 导入Spring Data Redis 的maven坐标;

配置Redis数据源;

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

通过RedisTemplate对象操作Redis;

//导入坐标
<dependency>
    <groupId>org.springframework.boot</groupId>       
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
//配置数据源
spring:
    redis:
        host: localhost
        port: 6379
        password: 123456
//编写配置类
@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;
    }

}

RedisTemplate 针对大量api进行了归类封装,将同一数据类型的操作封装为对应的Operation接口,具体分类如下:

ValueOperations:string数据操作;

SetOperations:set类型数据操作 ;

ZSetOperations:zset类型数据操作;

HashOperations:hash类型的数据操作;

ListOperations:list类型的数据操作;

@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();
        SetOperations setOperations = redisTemplate.opsForSet();
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();

    }

    /**
     * 操作字符串类型的数据
     */
    @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);
        redisTemplate.opsForValue().setIfAbsent("lock","1");
        redisTemplate.opsForValue().setIfAbsent("lock","2");

    }

    /**
     * 操作哈希类型数据
     */
    @Test
    public void testHash(){
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.put("100","name","tom");
        hashOperations.put("100","age","20");

        String name = (String) hashOperations.get("100", "name");
        System.out.println(name);

        Set keys = hashOperations.keys("100");
        System.out.println(keys);

        List values = hashOperations.values("100");
        System.out.println(values);

        hashOperations.delete("100","age");
    }

    /**
     * 操作列表类型数据
     */
    @Test
    public void testList(){
        //lpush lrange rpop llen
        ListOperations listOperations = redisTemplate.opsForList();

        listOperations.leftPushAll("mylist","a","b","c");
        listOperations.leftPush("mylist","d");

        List mylist = listOperations.range("mylist", 0, -1);
        System.out.println(mylist);

        listOperations.rightPop("mylist");

        Long size = listOperations.size("mylist");
        System.out.println(size);
    }
    /**
     * 操作集合类型数据
     */
    @Test
    public void testSet(){
        SetOperations setOperations = redisTemplate.opsForSet();

        setOperations.add("set1","a","b","c","d");
        setOperations.add("set2","a","b","x","y");

        Set members = setOperations.members("set1");
        System.out.println(members);

        Long size = setOperations.size("set1");
        System.out.println(size);

        Set intersect = setOperations.intersect("set1", "set2");
        System.out.println(intersect);

        Set union = setOperations.union("set1", "set2");
        System.out.println(union);

        setOperations.remove("set1","a","b");
    }
    /**
     * 操作有序集合类型的数据
     */
    @Test
    public void testZset(){
        //zadd zrange zincrby zrem
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();

        zSetOperations.add("zset1","a",10);
        zSetOperations.add("zset1","b",12);
        zSetOperations.add("zset1","c",9);

        Set zset1 = zSetOperations.range("zset1",0,-1);
        System.out.println(zset1);

        zSetOperations.incrementScore("zset1","c",10);

        zSetOperations.remove("zset1","a","b");
    }
    /**
     * 通用命令操作
     */
    @Test
    public void testCommon(){
        //keys exists type del
        Set keys = redisTemplate.keys("*");
        System.out.println(keys);

        Boolean name = redisTemplate.hasKey("name");
        Boolean set1 = redisTemplate.hasKey("set1");

        for (Object key : keys) {
            DataType type = redisTemplate.type(key);
            System.out.println(type.name());
        }
        redisTemplate.delete("mylist");
    }
}

3)店铺营业状态设置

接口设计: 设置营业状态、管理端查询营业状态、用户端查询营业状态;

营业状态数据存储方式:基于Redis的字符串来进行存储,约定:1表示营业 0表示打烊;

在shopController中接收前端参数,通过redisTemlate实现;

在用户端和管理端分别设计查询店铺营业状态接口;

修改代码使接口文档中管理端和用户端接口分开。

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
苍穹项目可以使用Postman进行API接口的测试和调试。Postman是一款常用的API开发工具,它可以帮助开发人员发送HTTP请求并查看响应结果,方便进行接口的测试和调试。 在苍穹项目中,可以使用Postman发送各种类型的HTTP请求,比如GET、POST、PUT、DELETE等,来模拟用户操作和测试接口功能。通过Postman,可以验证接口的正确性、查看接口返回的数据、调试接口的参数等。 为了使用Postman进行苍穹项目的接口测试,您需要以下步骤: 1. 下载并安装Postman:您可以从Postman官网(https://www.postman.com/)上下载并安装适合您的操作系统的版本。 2. 打开Postman并创建一个新的请求:打开Postman应用,在界面上选择"New"来创建一个新的请求。 3. 输入接口URL和选择请求方法:在新建请求的界面中,输入苍穹项目的接口URL,并选择适当的请求方法,比如GET或POST。 4. 添加请求参数和请求头:根据需要,您可以添加请求参数和请求头,以便于模拟不同的请求情况。 5. 发送请求并查看响应:点击发送按钮,Postman会向服务器发送请求,并在界面上显示响应结果。您可以查看接口返回的数据、响应状态码等信息。 通过以上步骤,您可以使用Postman进行苍穹项目的接口测试。这样可以帮助您确保接口的正确性和稳定性,提高项目的质量和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值