瑞吉外卖 对象转换器 公共字段自动填充 文件上传/下载 阿里云短信
https://blog.csdn.net/weixin_43715214/category_12022798.html
大佬记录
项目介绍day01
功能架构
(1)用户层
本项目中在构建系统管理后台的前端页面,我们会用到H5、Vue.js、ElementUI等技术。而在构建移动端应用时,我们会使用到微信小程序。
(2)网关层
Nginx是一个服务器,主要用来作为Http服务器,部署静态资源,访问性能高。在Nginx中还有两个比较重要的作用: 反向代理和负载均衡, 在进行项目部署时,要实现Tomcat的负载均衡,就可以通过Nginx来实现。
(3)应用层
SpringBoot: 快速构建Spring项目, 采用 "约定优于配置" 的思想, 简化Spring项目的配置开发。
Spring: 统一管理项目中的各种资源(bean), 在web开发的各层中都会用到。
SpringMVC:SpringMVC是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合,可以无缝集成。
SpringSession: 主要解决在集群环境下的Session共享问题。
lombok:能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法。
Swagger: 可以自动的帮助开发人员生成接口文档,并对接口进行测试。
(4)数据层
MySQL: 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。
MybatisPlus: 本项目持久层将会使用MybatisPlus来简化开发, 基本的单表增删改查直接调用框架提供的方法即可。
Redis: 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存(降低数据库访问压力, 提供访问效率), 在后面的性能优化中会使用。
(5)工具
git: 版本控制工具, 在团队协作中, 使用该工具对项目中的代码进行管理。
maven: 项目构建工具。
junit:单元测试工具,开发人员功能实现完毕后,需要通过junit对功能进行单元测试。
软件开发介绍
第1阶段: 需求分析
完成产品原型、需求规格说明书的编写。
产品原型,一般是通过网页(html)的形式展示当前的页面展示什么样的数据, 页面的布局是什么样子的,点击某个菜单,打开什么页面,点击某个按钮,出现什么效果,都可以通过产品原型看到。
需求规格说明书, 一般来说就是使用 Word 文档来描述当前项目有哪些功能,每一项功能的需求及业务流程是什么样的,都会在文档中描述。
第2阶段: 设计
设计的内容包含 产品设计、UI界面设计、概要设计、详细设计、数据库设计。
在设计阶段,会出具相关的UI界面、及相关的设计文档。比如数据库设计,需要设计当前项目中涉及到哪些数据库,每一个数据库里面包含哪些表,这些表结构之间的关系是什么样的,表结构中包含哪些字段,字段类型都会在文档中描述清楚。
第3阶段: 编码
编写项目代码、并完成单元测试。
作为软件开发工程师,我们主要的工作就是在该阶段, 对分配给我们的模块功能,进行编码实现。编码实现完毕后,进行单元测试,单元测试通过后再进入到下一阶段。
第4阶段: 测试
在该阶段中主要由测试人员, 对部署在测试环境的项目进行功能测试, 并出具测试报告。
第5阶段: 上线运维
在项目上线之前, 会由运维人员准备服务器上的软件环境安装、配置, 配置完毕后, 再将我们开发好的项目,部署在服务器上运行。
我们作为软件开发工程师, 我们主要的任务是在编码阶段, 但是在一些小的项目组当中, 也会涉及到数据库的设计、测试等方面的工作。
角色分工
开发环境搭建
需求
后台系统
菜品管理(批量删除、起售停售)
• 套餐管理(修改、起售停售)
• 订单明细
移动端
个人中心(退出登录、最新订单查询、历史订单、地址管理-修改地址、地址管理-删除地址)
• 购物车(删除购物车中的商品)
https://blog.csdn.net/weixin_43715214/article/details/126920640
精力全用在写注释上了,笔记黑马没给,懒得截图,就直接看别人的吧
前面主要是做了:导数据库、Maven,静态页面拦截器
员工登录
//EmployeeController
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request,@RequestBody Employee employee){
// 通过request获取Session,把登录成功的信息传进去;@RequestBody用于接受JSON数据
//1、将页面提交的密码password进行md5加密处理
String password = employee.getPassword();//拿到密码
password = DigestUtils.md5DigestAsHex(password.getBytes());//工具类md5加密
//2、根据页面提交的用户名username查询数据库
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername,employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);//数据库有唯一索引,用getOne
//3、如果没有查询到则返回登录失败结果
if(emp == null){
return R.error("该账号未注册,登录失败");
}
//4、密码比对,如果不一致则返回登录失败结果
if(!emp.getPassword().equals(password)){
return R.error("账号或密码输入错误,登录失败");
}
//5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
if(emp.getStatus() == 0){
return R.error("账号已禁用");
}
//6、登录成功,将员工id存入Session并返回登录成功结果
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}
退出后台
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
//清理Session中保存的当前登录员工的id
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
Day02员工管理业务开发
2.1完善登录功能
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
//要过滤的东西(过滤器名字,拦截哪些路径)
@Slf4j
public class LoginCheckFilter implements Filter{
//AntPathMatcher路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;//两个转型
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1、获取本次请求的URI
String requestURI = request.getRequestURI();// /backend/index.html
log.info("拦截到请求:{}",requestURI);//{}表示占位符 代表,后面的东西
String[] urls = new String[]{ //定义不需要处理的请求路径
"/employee/login",//登录请求路径
"/employee/logout",//员工退出系统
"/backend/**",//后端页面静态资源
"/front/**",//移动端页面静态资源
"/common/**", "/user/sendMsg", "/user/login"
};
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
if(check){
log.info("本次请求{}不需要处理,直接放行",requestURI);
filterChain.doFilter(request,response);
return;
}
//4-1、需要处理,判断 员工 登录状态,如果已登录,则直接放行filterChain.doFilter
if(request.getSession().getAttribute("employee") != null){
log.info("员工已登录,员工id为:{}",request.getSession().getAttribute("employee"));
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request,response);
return;
}
//4-2、判断 用户 登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("user") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));
Long userId = (Long) request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);
filterChain.doFilter(request,response);
return;
}
log.info("用户未登录");
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
//把R转成JSON 输出’NOTLOGIN‘和request.js里面响应拦截器的条件匹配
return;
}
/**
* 路径匹配,检查本次请求是否需要放行
*/
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
}
2.2新增员工
@ControllerAdvice(annotations = {RestController.class, Controller.class})
//AOP通知(annotations={拦截加了 XX.class的注解的Controller} )
@ResponseBody//返回JSON数据的方法需要加这个注解
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)//(要处理的异常类)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if(ex.getMessage().contains("Duplicate entry")){//异常信息包含这个字段就是重复错误
String[] split = ex.getMessage().split(" ");
String msg = split[2] + "已存在";//根据字段输出得知name在[2]
return R.error(msg);
}
return R.error("未知错误");
}
}
@PostMapping
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
log.info("新增员工,员工信息:{}",employee.toString());
//设置初始密码123456,需要进行md5加密处理
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//employee.setCreateTime(LocalDateTime.now());//LocalDateTime.now():获取系统当前time
//employee.setUpdateTime(LocalDateTime.now());
//获得当前登录用户的id
//Long empId = (Long) request.getSession().getAttribute("employee");
//employee.setCreateUser(empId);
//employee.setUpdateUser(empId);
employeeService.save(employee);
//继承了MabatisPlus的IService接口写的save
return R.success("新增员工成功");
}
2.3员工信息分页查询
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
//构造分页构造器
Page pageInfo = new Page(page,pageSize);
//构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
//添加过滤条件
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
//执行查询
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
2.4启用/禁用员工账号
对象转换器
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON] */
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()//添加序列化器
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
MVC框架消息转换器
@Slf4j
@Configuration//说明是个配置类
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 扩展mvc框架的消息转换器*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);//0代表使用转换器的顺序
}
}
2.5编辑员工信息
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
log.info("根据id查询员工信息...");
Employee employee = employeeService.getById(id);
if(employee != null){
return R.success(employee);
}
return R.error("没有查询到对应员工信息");
}
Day03分类管理业务开发
3.1公共字段自动填充
ThreadLocal
//基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
//存id泛型用Long
/**
* 设置值 工具方法用静态static
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
3.2新增分类
3.3分类信息分页查询
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
//分页构造器
Page<Category> pageInfo = new Page<>(page,pageSize);
//条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加排序条件,根据sort进行排序
queryWrapper.orderByAsc(Category::getSort);
//分页查询
categoryService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
3.4删除分类
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper,Category> implements CategoryService{
@Autowired
private DishService dishService;
@Autowired
private SetmealService setmealService;
/**
* 根据id删除分类,删除之前需要进行判断
* @param id
*/
@Override
public void remove(Long id) {
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据分类id进行查询
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count1 = dishService.count(dishLambdaQueryWrapper);
//查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
if(count1 > 0){//当前分类 已经关联菜品,抛出一个业务异常
throw new CustomException("当前分类下关联了菜品,不能删除");
}
//查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据分类id进行查询
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count();
if(count2 > 0){//已经关联套餐,抛出一个业务异常
throw new CustomException("当前分类下关联了套餐,不能删除");
}
//什么都不关联,可以正常删除分类
super.removeById(id);
} }
3.5修改分类
@PutMapping
public R<String> update(@RequestBody Category category){
log.info("修改分类信息:{}",category);
categoryService.updateById(category);
return R.success("修改分类信息成功");
}
Day04菜品管理业务开发
4.1文件上传下载
文件上传介绍
文件下载介绍
文件上传代码实现
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {
@Value("${reggie.path}")//通过配置文件application.yml注入
private String basePath;
/**
* 文件上传
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file){//这个file要和前端的保持一致
//前端上传的file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
log.info(file.toString());
String originalFilename = file.getOriginalFilename();//获得上传时的原始文件名
//截取之前文件的类型后缀(.jpg)
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//使用UUID重新生成文件名,防止之前上传的原始文件名称重复造成文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;
File dir = new File(basePath);//创建一个目录对象
//判断当前目录是否存在
if(!dir.exists()){//如果上面配置的目录basePath不存在,需要创建
dir.mkdirs();}
try {//将临时文件转存(transferTo())到指定位置
file.transferTo(new File(basePath + fileName));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(fileName);
}
文件下载代码实现
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
try {
//输入流,通过输入流读取文件内容
FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
//输出流,通过输出流将文件写回浏览器
ServletOutputStream outputStream = response.getOutputStream();
response.setContentType("image/jpeg");//设置回去的是“”类型的文件
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes)) != -1){//循环输出写会浏览器
outputStream.write(bytes,0,len);
outputStream.flush();//刷新
}
//关闭资源
outputStream.close();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
4.2新增菜品
DTO(展示层与后端服务层间数据传输)
public class DishServiceImpl extends ServiceImpl<DishMapper,Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/* * 新增菜品,同时保存对应的口味数据 */
@Transactional//多表操作 需要事务控制注释同时启动类加入@EnableTran..
public void saveWithFlavor(DishDto dishDto) {
//保存菜品的基本信息到菜品表dish
this.save(dishDto);
Long dishId = dishDto.getId();//菜品id
//菜品口味
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item) -> {
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
//保存菜品口味数据到菜品口味表dish_flavor
dishFlavorService.saveBatch(flavors);
}
4.3菜品信息分页查询
//DishController菜品信息分页查询
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//构造分页构造器对象
Page<Dish> pageInfo = new Page<>(page,pageSize);
Page<DishDto> dishDtoPage = new Page<>();
//条件构造器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//添加过滤条件
queryWrapper.like(name != null,Dish::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Dish::getUpdateTime);
//执行分页查询
dishService.page(pageInfo,queryWrapper);
//对象属性(想拷贝的东西,目的对象,ignore属性)拷贝
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
List<Dish> records = pageInfo.getRecords();
List<DishDto> list = records.stream().map((item) -> {
//item代表stream遍历的每个Dish菜品
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto);//其他属性的拷贝赋值
Long categoryId = item.getCategoryId();//每个菜品分类id
//根据id查询分类对象
Category category = categoryService.getById(categoryId);
if(category != null){
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
return dishDto;
}).collect(Collectors.toList());//收集遍历返回的dishDto对象 转成集合
dishDtoPage.setRecords(list);
return R.success(dishDtoPage);
}
4.4修改菜品
/** DishServiceImpl
* 根据id查询菜品信息和对应的口味信息
* @param id
* @return
*/
public DishDto getByIdWithFlavor(Long id) {
//查询菜品基本信息,从dish表查询
Dish dish = this.getById(id);
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish,dishDto);//对象属性拷贝
//查询当前菜品对应的口味信息,从dish_flavor表查询
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dish.getId());
List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);//查到的口味封装到集合里
dishDto.setFlavors(flavors);
return dishDto;
}
@Transactional// DishServiceImpl
public void updateWithFlavor(DishDto dishDto) {
//更新dish表基本信息
this.updateById(dishDto);//传递子类对象直接更新父类的属性
//清理当前菜品对应口味数据---dish_flavor表的delete操作
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());//构造条件
dishFlavorService.remove(queryWrapper);
//添加当前提交过来的口味数据---dish_flavor表的insert操作
List<DishFlavor> flavors = dishDto.getFlavors();//没封装dishDto
flavors = flavors.stream().map((item) -> {//set dishId
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
4.5起售停售删除等功能作为实战作业
https://blog.csdn.net/weixin_43715214/article/details/127739000
Day05套餐管理业务开发
新增套餐
//代码开发3、根据条件查询对应的菜品数据
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish){
//构造查询条件
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());
//添加条件,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus,1);
//添加排序条件 排序顺序,创建时间倒序
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
List<Dish> list = dishService.list(queryWrapper);
List<DishDto> dishDtoList = list.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto);
Long categoryId = item.getCategoryId();//分类id
//根据id查询分类对象
Category category = categoryService.getById(categoryId);
if(category != null){
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
//当前菜品的id
Long dishId = item.getId();
LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
//SQL:select * from dish_flavor where dish_id = ?
List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
dishDto.setFlavors(dishFlavorList);
return dishDto;
}).collect(Collectors.toList());
return R.success(dishDtoList);
}
//SetmealServiceImpl 新增套餐,同时需要保存套餐和菜品的关联关系
@Transactional
public void saveWithDish(SetmealDto setmealDto) {
//保存套餐的基本信息,操作setmeal,执行insert操作
this.save(setmealDto);
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
setmealDishes.stream().map((item) -> {
item.setSetmealId(setmealDto.getId());
return item;
}).collect(Collectors.toList());
//保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
setmealDishService.saveBatch(setmealDishes);
}
//新增套餐
@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto){
log.info("套餐信息:{}",setmealDto);
setmealService.saveWithDish(setmealDto);
return R.success("新增套餐成功");
}
套餐信息分页查询
// * 套餐分页查询
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//分页构造器对象
Page<Setmeal> pageInfo = new Page<>(page,pageSize);
Page<SetmealDto> dtoPage = new Page<>();
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据name进行like模糊查询
queryWrapper.like(name != null,Setmeal::getName,name);
//添加排序条件,根据更新时间降序排列
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
setmealService.page(pageInfo,queryWrapper);
//对象拷贝 records 泛型不一样要ignore
BeanUtils.copyProperties(pageInfo,dtoPage,"records");
List<Setmeal> records = pageInfo.getRecords();
List<SetmealDto> list = records.stream().map((item) -> {
SetmealDto setmealDto = new SetmealDto();
//上面对象是new的需要对象拷贝
BeanUtils.copyProperties(item,setmealDto);
//分类id
Long categoryId = item.getCategoryId();
//根据分类id查询分类对象
Category category = categoryService.getById(categoryId);
if(category != null){
//分类名称
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
dtoPage.setRecords(list);
return R.success(dtoPage);
}
删除套餐
//* 删除套餐,同时需要删除套餐和菜品的关联数据
@Transactional
public void removeWithDish(List<Long> ids) {
//select count(*) from setmeal where id in (1,2,3) and status = 1
//查询套餐状态,确定是否可用删除
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
queryWrapper.in(Setmeal::getId,ids);
queryWrapper.eq(Setmeal::getStatus,1);
int count = this.count(queryWrapper);
if(count > 0){// 不能删除,抛出一个业务异常
throw new CustomException("套餐正在售卖中,不能删除");
}
//如果可以删除,先删除套餐表中的数据---setmeal
this.removeByIds(ids);
//delete from setmeal_dish where setmeal_id in (1,2,3)
LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
//删除关系表中的数据----setmeal_dish
setmealDishService.remove(lambdaQueryWrapper);
}
修改 停售 起售功能实战作业
第六章手机验证码登录
下一步要企业资质 只能看 在Day5-14
做手机验证码的时候,可以去用腾讯云,首次认证赠送100条短信,超好玩,模板在这,自己根据自己的信息改,超级好玩,真的 模板在这https://blog.csdn.net/qq_56233219/article/details/118015291
短信发送(阿里云SMS)
https://help.aliyun.com/product/44282.html
手机验证码登录
h5页面手机调试模式
//发送手机短信验证码
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
String phone = user.getPhone();//获取手机号
if(StringUtils.isNotEmpty(phone)){//手机号非空
//工具类 生成随机的4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}",code);
//调用阿里云提供的短信服务API("签名","模板",手机号,动态验证码)完成发送短信
//SMSUtils.sendMessage("瑞吉外卖","",phone,code);
//需要将生成的验证码保存到Session
session.setAttribute(phone,code);
return R.success("手机验证码短信发送成功");
}
return R.error("短信发送失败");
}
//* 移动端用户登录
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){
log.info(map.toString());
//获取手机号
String phone = map.get("phone").toString();
//获取验证码
String code = map.get("code").toString();
//从Session中获取保存的验证码
Object codeInSession = session.getAttribute(phone);
//进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
if(codeInSession != null && codeInSession.equals(code)){
//如果能够比对成功,说明登录成功
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone,phone);
User user = userService.getOne(queryWrapper);
if(user == null){
//判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
user = new User();
user.setPhone(phone);
user.setStatus(1); //默认
userService.save(user);
}
session.setAttribute("user",user.getId());
return R.success(user);
}
return R.error("登录失败");
}
Day06菜品展示、购物车、下单
导入用户地址簿相关功能代码
//新增
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook) {
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBook:{}", addressBook);
addressBookService.save(addressBook);
return R.success(addressBook);
}
/**
* 设置默认地址
*/
@PutMapping("default")
public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
log.info("addressBook:{}", addressBook);
LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
wrapper.set(AddressBook::getIsDefault, 0);
//SQL:update address_book set is_default = 0 where user_id = ?
addressBookService.update(wrapper);
addressBook.setIsDefault(1);
//SQL:update address_book set is_default = 1 where id = ?
addressBookService.updateById(addressBook);
return R.success(addressBook);
}
菜品展示
p93感觉表结构太多,ssm掌握的还比较浅先看后面吧
购物车
下单
Git版本控制
Linux从安装到实战&瑞吉外卖项目部署
本文来自博客园,作者:软工菜鸡,转载请注明原文链接:https://www.cnblogs.com/SElearner/p/17676684.html