软件开发流程
需求分析:需求规格说明书、产品原型
设计: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); // 批量插入
}
}
起售停售菜品部分代码
@PostMapping("/status/{status}")
@ApiOperation("起售、停售菜品")
public Result startOrStop(@PathVariable Integer status, Long id){
log.info("起售、停售菜品:{} {}", status,id);
dishService.startOrStop(status,id);
return Result.success();
}
@Transactional
public void startOrStop(Integer status, Long id) {
Dish dish = Dish.builder()
.id(id)
.status(status)
.build();
dishMapper.update(dish);
if (status == StatusConstant.DISABLE){
//如果停售了菜品,需要清理当前菜品的关联数据( dish_flavor、setmeal_dish)
List<Long> dishIds = new ArrayList<>();
dishIds.add(id);
List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(dishIds);
if (setmealIds != null && setmealIds.size() > 0){
for (Long setmealId : setmealIds){
Setmeal setmeal = Setmeal.builder()
.id(setmealId)
.status(status)
.build();
setmealMapper.update(setmeal);
}
}
}
}
新增套餐部分代码
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<Dish>> list(Long categoryId){
log.info("根据分类id查询菜品:{}", categoryId.toString());
List<Dish> list = dishService.list(categoryId);
return Result.success(list);
}
public List<Dish> list(Long categoryId) {
Dish dish = Dish.builder() // dish 继承了dishDTO
.categoryId(categoryId) // 设置菜品分类id
.status(StatusConstant.ENABLE) // 设置状态为起售状态
.build(); // 构建查询条件
return dishMapper.list(dish);
}
@PostMapping()
@ApiOperation("新增套餐")
public Result save(@RequestBody SetmealDTO setmealDTO){
log.info("新增套餐:{}",setmealDTO);
setmealService.saveSetMeal(setmealDTO);
return Result.success();
}
@Transactional
public void saveSetMeal(SetmealDTO setmealDTO) {
//创建套餐对象
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO,setmeal);
//新增一份套餐
setmealMapper.insert(setmeal);
//获取生成的套餐id
Long setmealId = setmeal.getId();
//获取套餐中菜品信息
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> { //遍历套餐中的菜品
setmealDish.setSetmealId(setmealId); //设置套餐id
});
//保存套餐和菜品的关联关系
setmealDishMapper.insertBatch(setmealDishes); //批量保存
}
套餐分页查询部分代码
@GetMapping("/page")
@ApiOperation("分页查询")
public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {
log.info("套餐分页查询:{}", setmealPageQueryDTO);
PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
return Result.success(pageResult);
}
public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
int pageNum = setmealPageQueryDTO.getPage();
int pageSize = setmealPageQueryDTO.getPageSize();
PageHelper.startPage(pageNum, pageSize);
Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);
return new PageResult(page.getTotal(), page.getResult());
}
删除套餐部分代码
@DeleteMapping
@ApiOperation("批量删除套餐")
public Result delete(@RequestParam List<Long> ids){
log.info("批量删除套餐:{}", ids);
setmealService.deleteBatch(ids);
return Result.success();
}
@Transactional
public void deleteBatch(List<Long> ids) {
ids.forEach(id -> {
Setmeal setmeal = setmealMapper.getById(id);
if(StatusConstant.ENABLE == setmeal.getStatus()){
//起售中的套餐不能删除
throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
}
});
ids.forEach(setmealId -> {
//删除套餐表中的数据
setmealMapper.deleteById(setmealId);
//删除套餐菜品关系表中的数据
setmealDishMapper.deleteBySetmealId(setmealId);
});
}
修改套餐部分代码
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
public Result<List<Setmeal>> list(Long categoryId) {
Setmeal setmeal = new Setmeal();
setmeal.setCategoryId(categoryId);
setmeal.setStatus(StatusConstant.ENABLE);
List<Setmeal> list = setmealService.list(setmeal);
return Result.success(list);
}
@PutMapping
@ApiOperation("修改套餐")
public Result update(@RequestBody SetmealDTO setmealDTO){ //@RequestBody是告诉SpringBoot,这个参数是JSON数据,需要转换成对象
log.info("修改套餐:{}", setmealDTO);
setmealService.updatesetmeal(setmealDTO);
return Result.success();
}
@Transactional
public void updatesetmeal(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO,setmeal);
//修改套餐基本信息
setmealMapper.update(setmeal);
//获取套餐id
Long setmealId = setmealDTO.getId();
//删除原有的套餐数据
setmealDishMapper.delectBySetmealId(setmealId);
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//重新插入新的套餐数据
setmealDishMapper.insertBatch(setmealDishes);
}
public SetmealVO getByIdWithDish(Long id) {
Setmeal setmeal = setmealMapper.getById(id); // 查询套餐数据
List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id); // 查询套餐菜品关系
SetmealVO setmealVO = new SetmealVO(); // 封装套餐数据
BeanUtils.copyProperties(setmeal,setmealVO);
setmealVO.setSetmealDishes(setmealDishes);
return setmealVO;
}
起售停售套餐
@PostMapping("/status/{status}")
@ApiOperation("套餐起售停售")
public Result startOrStop(@PathVariable Integer status, Long id){ //@PathVariable是告诉SpringBoot,这个参数是路径参数,需要从路径中获取
log.info("套餐起售停售:{}", status);
setmealService.startOrStop(status,id);
return Result.success();
}
public void startOrStop(Integer status, Long id) {
//起售套餐时,判断套餐内是否有停售菜品,有停售菜品提示"套餐内包含未启售菜品,无法启售"
if (status == StatusConstant.ENABLE){
List<Dish> dishList = dishMapper.getBySetmealId(id);
if (dishList != null && dishList.size() > 0){ // 有菜品
dishList.forEach(dish -> {
if (StatusConstant.DISABLE == dish.getStatus()) {
throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);
}
});
}
}
Setmeal setmeal = Setmeal.builder()
.id(id)
.status(status)
.build();
setmealMapper.update(setmeal);
}
店铺的营业状态
管理端
@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
public static final String KEY = "SHOP_STATUS"; // Redis中存储店铺营业状态的key
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置店铺营业状态
* @param status
* @return
*/
@PutMapping ("/{status}")
@ApiOperation("设置店铺的营业状态")
public Result setStatus(@PathVariable Integer status){ //@PathVariable注解用于获取url中的动态参数
log.info("设置店铺营业状态为:{}",status == 1 ? "营业中" : "打烊中");
redisTemplate.opsForValue().set(KEY,status);
return Result.success();
}
/**
* 获取店铺营业状态
* @return
*/
@GetMapping ("/status") // @GetMapping注解用于获取url中的动态参数
@ApiOperation("获取店铺的营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer)redisTemplate.opsForValue().get(KEY);
log.info("获取到店铺营业状态为:{}",status == 1 ? "营业中" : "打烊中");
return Result.success(status);
}
}
用户端
@RestController("userShopController")
@RequestMapping("/admin/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
public static final String KEY = "SHOP_STATUS";
@Autowired
private RedisTemplate redisTemplate;
/**
* 获取店铺营业状态
* @return
*/
@GetMapping ("/status") // @GetMapping注解用于获取url中的动态参数
@ApiOperation("获取店铺的营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer)redisTemplate.opsForValue().get(KEY);
log.info("获取到店铺营业状态为:{}",status == 1 ? "营业中" : "打烊中");
return Result.success(status);
}
}
微信登录
@ApiOperation("微信登录")
@PostMapping ("/login")
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){
log.info("微信登录,参数:{}",userLoginDTO.getCode());
//微信登陆
User user = userService.wxLogin(userLoginDTO);
//为微信用户生成jwt令牌
HashMap<String, Object> claims = new HashMap<>(); // jwt令牌中存储的额外信息
claims.put(JwtClaimsConstant.USER_ID,user.getId());
String token =JwtUtil.createJWT(jwtProperties.getUserSecretKey(),jwtProperties.getUserTtl(),claims);
UserLoginVO userLoginVO = UserLoginVO.builder()
.id(user.getId())
.openid(user.getOpenid())
.token(token)
.build();
return Result.success(userLoginVO);
}
//微信服务接口地址
public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
private final WeChatProperties weChatProperties;
private final UserMapper userMapper;
public UserServiceImpl(WeChatProperties weChatProperties, UserMapper userMapper) {
this.weChatProperties = weChatProperties;
this.userMapper = userMapper;
}
@Override
public User wxLogin(UserLoginDTO userLoginDTO) {
String openid = getopenid(userLoginDTO.getCode()); // 获取openid
//判断openid是否为空,如果为空表示登陆失败,抛出业务异常
if (openid == null){
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
//判断当前用户是否为新用户
User user = userMapper.getByOpenid(openid);
//如果是新用户,自动完成注册
if(user == null){
user = User.builder()
.openid(openid)
.createTime(LocalDateTime.now())
.build();
userMapper.insert(user);
}
//但会这个用户对象,返回给前端
return user;
}
/**
*调用微信接口服务,获得当前微信用户的openid
* @param code
* @return
*/
private String getopenid(String code){
Map<String,String> map = new HashMap<>();
map.put("appid",weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code",code);
map.put("grant_type","authorization_code");
String json = HttpClientUtil.doGet(WX_LOGIN,map);
JSONObject jsonObject = JSON.parseObject(json);
String openid = jsonObject.getString("openid");
return openid;
};
商品浏览
CategoryController
@RestController("userCategoryController")
@RequestMapping("/user/category")
@Api(tags = "C端-分类接口")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 查询分类
* @param type
* @return
*/
@GetMapping("/list")
@ApiOperation("查询分类")
public Result<List<Category>> list(Integer type) {
List<Category> list = categoryService.list(type);
return Result.success(list);
}
}
DishController
@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
public class DishController {
@Autowired
private DishService dishService;
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
List<DishVO> list = dishService.listWithFlavor(dish);
return Result.success(list);
}
}
DishService
public interface DishService {
/**
* 条件查询菜品和口味
* @param dish
* @return
*/
List<DishVO> listWithFlavor(Dish dish);
}
DishServiceImpl
@Service
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
@Autowired
private SetmealDishMapper setmealDishMapper;
@Autowired
private SetmealMapper setmealMapper;
/**
* 条件查询菜品和口味
* @param dish
* @return
*/
public List<DishVO> listWithFlavor(Dish dish) {
List<Dish> dishList = dishMapper.list(dish);
List<DishVO> dishVOList = new ArrayList<>();
for (Dish d : dishList) {
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(d,dishVO);
//根据菜品id查询对应的口味
List<DishFlavor> flavors = dishFlavorMapper.getByDishId(d.getId());
dishVO.setFlavors(flavors);
dishVOList.add(dishVO);
}
return dishVOList;
}
}
SetmealController
@RestController("userSetmealController")
@RequestMapping("/user/setmeal")
@Api(tags = "C端-套餐浏览接口")
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 条件查询
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
public Result<List<Setmeal>> list(Long categoryId) {
Setmeal setmeal = new Setmeal();
setmeal.setCategoryId(categoryId);
setmeal.setStatus(StatusConstant.ENABLE);
List<Setmeal> list = setmealService.list(setmeal);
return Result.success(list);
}
/**
* 根据套餐id查询包含的菜品列表
*
* @param id
* @return
*/
@GetMapping("/dish/{id}")
@ApiOperation("根据套餐id查询包含的菜品列表")
public Result<List<DishItemVO>> dishList(@PathVariable("id") Long id) {
List<DishItemVO> list = setmealService.getDishItemById(id);
return Result.success(list);
}
SetmealService
public interface SetmealService {
/**
* 条件查询
* @param setmeal
* @return
*/
List<Setmeal> list(Setmeal setmeal);
/**
* 根据id查询菜品选项
* @param id
* @return
*/
List<DishItemVO> getDishItemById(Long id);
}
SetmealServiceImpl
@Service
@Slf4j
public class SetmealServiceImpl implements SetmealService {
@Autowired
private SetmealMapper setmealMapper;
@Autowired
private SetmealDishMapper setmealDishMapper;
@Autowired
private DishMapper dishMapper;
/**
* 条件查询
* @param setmeal
* @return
*/
public List<Setmeal> list(Setmeal setmeal) {
List<Setmeal> list = setmealMapper.list(setmeal);
return list;
}
/**
* 根据id查询菜品选项
* @param id
* @return
*/
public List<DishItemVO> getDishItemById(Long id) {
return setmealMapper.getDishItemBySetmealId(id);
}
}
SetmealMapper
@Mapper
public interface SetmealMapper {
/**
* 动态条件查询套餐
* @param setmeal
* @return
*/
List<Setmeal> list(Setmeal setmeal);
/**
* 根据套餐id查询菜品选项
* @param setmealId
* @return
*/
@Select("select sd.name, sd.copies, d.image, d.description " +
"from setmeal_dish sd left join dish d on sd.dish_id = d.id " +
"where sd.setmeal_id = #{setmealId}")
List<DishItemVO> getDishItemBySetmealId(Long setmealId);
}
SetmealMapper.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.SetmealMapper">
<select id="list" parameterType="Setmeal" resultType="Setmeal">
select * from setmeal
<where>
<if test="name != null">
and name like concat('%',#{name},'%')
</if>
<if test="categoryId != null">
and category_id = #{categoryId}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
</select>
</mapper>
清理缓存
private void cleanCache(String key){
Set keys = redisTemplate.keys(key);
redisTemplate.delete(keys);
}
cleanCache("dish_*");