苍穹外卖
本文最后更新于3 天前,其中的信息可能已经过时,如有错误请发送邮件到1169063119@qq.com

软件开发流程

需求分析:需求规格说明书、产品原型

设计:ui设计、数据库设计、接口设计

编码:项目代码、单元测试

测试:测试用例、测试报告

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

角色分工

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

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

ui设计师:根据产品原型输出界面效果图

架构师:项目整体架构设计、技术选型等

开发工程师:代码实现

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

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

软件环境

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

测试环境:专门给测试人员使用的环境,用于测试项目,一般外部用户无法访问

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

苍穹外卖项目介绍

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

管理端:外卖商家使用

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

用户端:点餐用户使用

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

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

产品原型:用于展示项目的业务功能

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

用户层:node.js、VUE.js、ElementUI、微信小程序、apache echart

网关层:nginx

应用层:Spring Boot、Spring MVC、Spring Task、httpclient、Spring Cache、JWT、阿里云OSS、Swagger、POI、WebSocket

数据层:MySQL、Redis、mybatis、pagehelper、spring data redis

工具:Git、maven、Junit、postman

完善登录功能-密码加密

修改数据库中明文密码,改为MD5加密后的密文

修改Java代码,前端提交的密码进行MD5加密后再跟数据库中密码比对

Swagger

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

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

使用方式:

  • 导入knife4j的maven坐标
  • 在配置类中加入knife4j相关配置
  • 设置静态资源映射,否则接口文档页面无法访问

功能测试

1.通过接口文档测试

2.通过前后端联调测试

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

新增员工部分代码

    @PostMapping()
    @ApiOperation("新增员工") //添加swagger的接口描述
    public Result save(@RequestBody EmployeeDTO employeeDTO){ //如果是json格式的数据,需要加上@RequestBody注解
        log.info("新增员工:{}",employeeDTO);  //{}是占位符
        employeeService.save(employeeDTO);
        return Result.success();
    }
    public void save(EmployeeDTO employeeDTO) {
        Employee  employee = new Employee();

        //通过set方法设置属性过于繁琐
//        employee.setName(employeeDTO.getName());

        //对象属性拷贝
        BeanUtils.copyProperties(employeeDTO,employee);

        //设置账号状态,默认正常状态,1表示正常,0表示锁定
        employee.setStatus(StatusConstant.ENABLE);

        //设置密码,对密码进行md5加密处理
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));

        //设置创建时间、更新时间
//        employee.setCreateTime(LocalDateTime.now());
//        employee.setUpdateTime(LocalDateTime.now());

        //设置创建人、更新人
//        employee.setCreateUser(BaseContext.getCurrentId());
//        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.insert(employee);
    }

员工分页查询部分代码

    @PostMapping("/page")
    @ApiOperation("员工分页查询")
    public Result<PageResult> page(EmployeePageQueryDTO  employeePageQueryDTO){
    log.info("员工分页查询,参数:{}",employeePageQueryDTO);
    PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO); //调用emloyeeService,返回PageResult对象
        return Result.success(pageResult); //返回结果并封装到Result中
    }
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
//select * from employee limit 0,10 底层是基于limit关键字来查询
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO); //底层基于MyBatis的分页插件进行分页查询
//Page对象包含了分页查询的所有数据

long total = page.getTotal();
List<Employee> records = page.getResult();

return new PageResult(total,records);
}

启用禁用员工账号部分代码

@PostMapping("/status/{status}")
    @ApiOperation("启用禁用员工账号")
    public Result startOrStop(@PathVariable Integer status, Long id){
        log.info("启用或禁用员工账号{},{}",status,id);
        employeeService.startOrstop(status,id);
        return Result.success();
    }
    @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();

//update语句设置为动态的,根据传进来的参数的不同,可以修改多个字段
employeeMapper.update(employee);
}

根据id查询员工信息

    @GetMapping("/{id}")
    @ApiOperation("根据id查询员工信息")
    public Result<Employee> getById(@PathVariable Long id){
        Employee employee = employeeService.getById(id);
        return Result.success(employee);
    }
public Employee getById(Long id) {
Employee employee = employeeMapper.getById(id);
employee.setPassword("****");
return employee;
}

编辑员工信息

    @PutMapping
    @ApiOperation("编辑员工信息")
    public Result update(@RequestBody EmployeeDTO employeeDTO){
        log.info("编辑员工信息:{}",employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();A
    }
    public void update(EmployeeDTO employeeDTO) {
Employee employee = new Employee(); // 接受到的是emploeeDTO,我们要的是employee,所以要对数据进行转换
BeanUtils.copyProperties(employeeDTO,employee); //属性拷贝

//DTO中没有修改时间及修改者,所以要单独设置
// employee.setUpdateTime(LocalDateTime.now());
// employee.setUpdateUser(BaseContext.getCurrentId());

employeeMapper.update(employee);
}

自定义注解

用于标识某个方法需要进行功能字段自动填充处理

AutoFill

@Target(ElementType.METHOD) //指定当前注解会加在什么位置,指定我们的方法只能加在方法上面
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:update insert两种,查询操作不需要设置
    OperationType value();
}

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("开始进行公共字段的填充");

            //获取到当前被拦截的方法上的数据库操作类型
            MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //方法签名对象
            AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); //获取方法上的注解对象
            OperationType operationType = autoFill.value(); //获取数据库操作类型

            //获取到当前被拦截的方法的参数--实体对象
            Object[] args = joinPoint.getArgs();
            if (args == null || args.length == 0){
                return;
            }
            Object entity = args[0];

            //准备赋值的数据
            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);

                    //通过反射为对象属性赋值
                    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){
                //为2个公共字段赋值
                try{
                    Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                    Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                    //通过反射为对象属性赋值
                    setUpdateTime.invoke(entity,now);
                    setUpdateUser.invoke(entity,currentId);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }

}

新增菜品

DishController.java

@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {

    @Autowired
    private DishService dishService;

    /**
     * 新增菜品
     * @param dishDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }

}

DishService.java

 public void saveWithFlavor(DishDTO dishDTO); //除了菜品信息外,还要保存菜品口味数据

DishServiceImpl.java

@Service
@Slf4j
public class DishServiceImpl implements DishService {

    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;

    /**
     * 新增菜品及对应的口味
     * @param dishDTO
     */
    @Override
    public void saveWithFlavor(DishDTO dishDTO) {

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);

        //向菜品表插入1跳数据
        dishMapper.insert(dish);

        //获取insert语句生成的主键值
        Long dishId = dish.getId();

        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0){
            flavors.forEach(dishFlavor -> dishFlavor.setDishId(dishId)); // 设置菜品id
            //向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors); //  批量插入

        }

    }
}

DishDao.java

@Data
public class DishDTO implements Serializable {

    private Long id;
    //菜品名称
    private String name;
    //菜品分类id
    private Long categoryId;
    //菜品价格
    private BigDecimal price;
    //图片
    private String image;
    //描述信息
    private String description;
    //0 停售 1 起售
    private Integer status;
    //口味
    private List<DishFlavor> flavors = new ArrayList<>();

}

DishMapper.java

@Mapper
public interface DishMapper {

    /**
     * 根据分类id查询菜品数量
     * @param categoryId
     * @return
     */
    @Select("select count(id) from dish where category_id = #{categoryId}")
    Integer countByCategoryId(Long categoryId);

    /**
     * 插入菜品数据
     * @param dish
     */
    @AutoFill(value = OperationType.INSERT)
    void insert(Dish dish);
}

DishFlavorMapper.java

@Mapper
public interface DishFlavorMapper {

    /**
     * 批量插入口味数据
     * @param flavors
     */
    void insertBatch(List<DishFlavor> flavors);
}

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" useGeneratedKeys="true" keyProperty="id">
/* useGeneratedKeys 添加后,mybatis 会自动填充主键
   keyProperty 添加后,mybatis 会将主键填充到实体类中*/
        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.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,status,create_time,update_time,create_user,update_user)
        values(#{name},#{categoryId},#{price},#{image},#{description},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})
    </insert>
</mapper>

菜品分页查询部分代码

    @GetMapping("/page")
    @ApiOperation("菜品分页查询")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){ //菜品查询需要返回值
        log.info("菜品分页查询:{}", dishPageQueryDTO);
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
        return Result.success(pageResult);
    }
@Override
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
return new PageResult(page.getTotal(),page.getResult());
}

删除菜品部分代码

    @DeleteMapping()
    @ApiOperation("批量删除菜品")
    public Result delete(@RequestParam List<Long> ids){ //@RequestParam是告诉SpringMVC,这个参数的值是请求参数中的ids
        log.info("批量删除菜品:{}",ids);
        dishService.deleteBatch(ids); //批量删除
        return Result.success();
    }
 @Transactional //涉及多个表操作,要使用事务注解
 public void deleteBatch(List<Long> ids) {
        //判断当前菜品是否能够删除--是否存在起售中的菜品
        for (Long id : ids){
            Dish dish = dishMapper.getById(id);
            if (dish.getStatus() == StatusConstant.ENABLE){
                //当前菜品处于起售中,不能删除
                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);
        }


    }

修改菜品部分代码

    @PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO){
        log.info("修改菜品:{}", dishDTO);
        dishService.updateWithFlavor(dishDTO);
        return Result.success();
    }
    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())); // 设置菜品id
            //向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors); //  批量插入

        }
    }
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇