国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

項目筆記-瑞吉外賣(全)

這篇具有很好參考價值的文章主要介紹了項目筆記-瑞吉外賣(全)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

1.業(yè)務開發(fā)

1.對后端返回請求值的分析

2.對不同種請求參數(shù)的分析

3.事務管理

day01

1.軟件開發(fā)整體介紹

項目筆記-瑞吉外賣(全)

2.項目整體介紹??

  • 后端:管理菜品和員工信息
  • 前臺:通過手機端,可以瀏覽菜品和添加客戶端

開發(fā)項目流程:

  1. 實現(xiàn)基本需求,用戶能在手機瀏覽器訪問
  2. 對移動端應用改進,使用微信小程序實現(xiàn)
  3. 對系統(tǒng)進行優(yōu)化升級,提高系統(tǒng)的使用性能

技術選型:

? 項目筆記-瑞吉外賣(全)

功能架構:

項目筆記-瑞吉外賣(全)

角色:

項目筆記-瑞吉外賣(全)

3.開發(fā)環(huán)境搭建

  • 涉及數(shù)據(jù)庫 + maven
  1. 數(shù)據(jù)庫表介紹:

    項目筆記-瑞吉外賣(全)

  2. Maven項目搭建

    • 第一步,先創(chuàng)建一個maven空項目,然后設置好pom.xml文件和application.yml文件
    • 第二步,配置springboot環(huán)境,啟動測試
    • 第三部,導入前端靜態(tài)資源,加入配置類來將瀏覽器路徑和本地項目文件路徑做匹配
    @Slf4j
    @Configuration
    public class WebMvcConfig extends WebMvcConfigurationSupport {
        /**
         * 設置靜態(tài)資源映射
         * @param registry
         */
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            log.info("開始進行靜態(tài)資源映射...");
            registry.addResourceHandler("/backend/**").//瀏覽器地址欄
                    //映射到真實的路徑(映射的真實路徑末尾必須添加斜杠`/`)
                    addResourceLocations("classPath:/backend/");//這里不要加空格符,貼著放
            registry.addResourceHandler("/front/**")
                    .addResourceLocations("classpath:/front/");
        }
    }
    

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

4.登錄功能??

查看項目代碼的一般邏輯:

? 前端html界面,找到響應的元素,找到對應的js動態(tài)方法,分析發(fā)送(Ajax)請求到后端的過程,處理好后端代碼,返回處理的R對象給前端來判斷使用(判斷運用是否正確),最后前端再決定跳轉到哪一個界面

  1. 需求分析

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    前端代碼

    項目筆記-瑞吉外賣(全)

  2. 功能結構

    項目筆記-瑞吉外賣(全)

4.1代碼實現(xiàn)
  1. 導入通用返回結果類R類

    ? 前端代碼與R類關系

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    R類

    項目筆記-瑞吉外賣(全)

  2. 梳理登錄方法邏輯

    項目筆記-瑞吉外賣(全)

  3. 代碼實現(xiàn)

    項目筆記-瑞吉外賣(全)

5.退出功能

  1. 功能邏輯

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

6.頁面效果出現(xiàn)

  • index.html

    menuList屬性值封裝了不同頁面的信息

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)


day02

完成功能:

項目筆記-瑞吉外賣(全)

1.完善登錄功能

  1. 問題分析

    使用過濾器或者攔截器實現(xiàn)

    項目筆記-瑞吉外賣(全)

  2. 代碼實現(xiàn)步驟

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

  3. 具體實現(xiàn)

    • 1.攔截器用原生的Servlet攔截,因此主加載類要加上@ServletComponentScan注解攔截

      2.加上日志注解,能夠使用日志輸出

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    • 2.具體邏輯

    前端處理部分

    項目筆記-瑞吉外賣(全)

    前端處理響應攔截器:如果是這個狀態(tài)那么自動跳轉回登錄頁面

    項目筆記-瑞吉外賣(全)

    后端部分:

    package com.itiheima.reggie.filter;
    
    import com.alibaba.fastjson.JSON;
    import com.itiheima.reggie.entity.R;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.util.AntPathMatcher;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @author qin start
     * @create 2023-04-24-11:28
     */
    @WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")//攔截所有路徑
    @Slf4j
    public class LoginCheckFilter implements Filter {
    
        //spring路徑匹配器
        public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            //轉成http格式的Servlet
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
    
    
    //        1、獲取本次請求的URI
            //定義不需要處理的請求路徑
            String url = request.getRequestURI();
            String[] urls = new String[]{
                    "/employee/login",
                    "/employee/logout",
                    "/backend/**",//靜態(tài)資源放行
                    "/front/**"
            };
    //        2、判斷本次請求是否需要處理
            boolean check = check(url, urls);
    //        3、如果不需要處理,則直接放行
            if(check){
                log.info("攔截到的請求:{}",url);
                filterChain.doFilter(request,response);
                return;
            }
    //        4、判斷登錄狀態(tài),如果已登錄,則直接放行
            //通過判斷session存儲的數(shù)據(jù)
            if(request.getSession().getAttribute("id") != null){
                log.info("登陸成功!用戶id為:{}",request.getSession().getAttribute("id"));
                filterChain.doFilter(request,response);
                return;
            }
    //        5、如果未登錄則返回未登錄結果
    //
            //這里要用輸出流,因為不是控制器自動返回json格式對象
            log.info("登陸失?。√D回登錄界面");
            response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));//不放行
            return;
        }
    
        /**
         * 判斷請求路徑是否在不需要處理的路徑里
         * @param url
         * @param urls
         * @return
         */
        public boolean check(String url,String[] urls){
            for (String pattern : urls) {
                //這里順序不能搞反,第一個參數(shù)為匹配模式
                if(PATH_MATCHER.match(pattern,url)){
                    return true;
                }
            }
            return false;
        }
    }
    
    

2.新增員工功能

  1. 功能
  • 數(shù)據(jù)模型中,employee字段要唯一

項目筆記-瑞吉外賣(全)

項目筆記-瑞吉外賣(全)

  1. 執(zhí)行流程

    項目筆記-瑞吉外賣(全)

  2. 代碼實現(xiàn)

    @PostMapping
    public R<String> addEmployee(HttpServletRequest request,@RequestBody Employee employee){
    
        //        log.info(employee.toString());
    
        //設置初始密碼
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes(StandardCharsets.UTF_8)));
    
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
    
        employee.setCreateUser((long)request.getSession().getAttribute("id"));
        employee.setUpdateUser((long)request.getSession().getAttribute("id"));
    
        employeeService.save(employee);
    
        return R.success("新增員工成功");
    }
    
    1. 處理數(shù)據(jù)庫插入重復名字異常

      項目筆記-瑞吉外賣(全)

      全局異常處理器來處理異常

      關鍵點在@ControllerAdvice和@ExceptionHandler,一個用來攔截方法,一個用來處理異常

      @ControllerAdvice捕獲方法后,有異常就處理

      @ControllerAdvice(annotations = {RestController.class, Controller.class})
      @ResponseBody//java對象轉為json格式的數(shù)據(jù)
      @Slf4j
      public class GlobalExceptionHandler {
      
          //用來捕獲插入重復數(shù)據(jù)異常
          @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
          public R<String> exceptionHandler (SQLIntegrityConstraintViolationException exception){
              log.error(exception.getMessage());
              return R.error("failed");
          }
      }
      
      //用來捕獲插入重復數(shù)據(jù)異常
      @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
      public R<String> exceptionHandler (SQLIntegrityConstraintViolationException exception){
          if (exception.getMessage().contains("Duplicate entry")){
              String[] split = exception.getMessage().split(" ");//根據(jù)空格符分割數(shù)組
              String msg = split[2] + "已存在";
              return R.error(msg);
          }
          return R.error("unknown error");
      }
      
    2. 小結:

      項目筆記-瑞吉外賣(全)

3.啟用禁用員工信息??(自定義消息轉換器使用)

  1. 需求分析

項目筆記-瑞吉外賣(全)

  1. 啟用、禁用員工賬號,本質(zhì)上就是一個更新操作,也就是對status狀態(tài)字段進行操作在Controller中創(chuàng)建update方法,此方法是一個通用的修改員工信息的方法

    @PutMapping
    public R<String> update(HttpServletRequest request, @RequestBody Employee employee){
        log.info(employee.toString());
    
        Long empID = (Long)request.getSession().getAttribute("employee");
        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(empID);
        employeeService.updateById(employee);
        return R.success("員工修改信息成功");
    }
    
    
  2. 出現(xiàn)問題:

    項目筆記-瑞吉外賣(全)

    原因:js對后端傳過來的數(shù)據(jù)long類型精度丟失,因為Java對象默認通過SpringMVC消息轉換器傳遞過來的數(shù)據(jù)默認是一般的json格式,"id"字段會被當做整型數(shù)據(jù)處理,而js中Long型精度和后端不匹配。

    **解決:**對SpringMVC配置自定義的消息處理器,將"id"對應的json格式數(shù)據(jù)轉為字符串值

  3. 具體解決步驟:

    ①提供對象轉換器Jackson0bjectMapper,基于Jackson進行Java對象到json數(shù)據(jù)的轉換
    ②在WebMcConfig配置類中擴展Spring mvc的消息轉換器,在此消息轉換器中使用提供的對象轉換器進行Java對象到json數(shù)據(jù)的轉換

    //JacksonObjectMapper
    package com.itzq.reggie.common;
    
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
    import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
    import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
    import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
    import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
    import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
    import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
    import java.math.BigInteger;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    import java.time.format.DateTimeFormatter;
    import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
    
    /**
     * 對象映射器:基于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框架的消息轉換器
         * @param converters
         */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    
        //創(chuàng)建消息轉換器對象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //設置對象轉化器,底層使用jackson將java對象轉為json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //將上面的消息轉換器對象追加到mvc框架的轉換器集合當中(index設置為0,表示設置在第一個位置,避免被其它轉換器接收,從而達不到想要的功能)
        converters.add(0,messageConverter);
    
    }
    
    

4.編輯員工信息

  1. 功能分析項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

  2. 后端功能代碼實現(xiàn)

    回顯數(shù)據(jù)

    @GetMapping("/{id}")
    public R<Employee> getById(@PathVariable Long id){
    
        log.info("根據(jù)id查詢員工信息。。。");
        Employee employee = employeeService.getById(id);
        if (employee != null){
            return R.success(employee);
        }
        return R.error("沒有查詢到該員工信息");
    }
    
    

    修改功能

    前面在禁用員工的時候使用到了修改數(shù)據(jù),所以這里不用配置

day03

1.公共字段自動填充

  • 問題分析:

    設置修改時間和修改人等字段在每張表中基本上都有,而且屬于多條記錄共有的具有相似功能的字段,因此可以每次修改或者插入的時候自動處理

  • 用到技術:①Mybatis Plus公共字段自動填充ThreadLocal線程內(nèi)部屬性

  • 技術詳解:

    ThreadLocal線程內(nèi)部屬性:客戶端發(fā)送的每次http請求,對應的在服務端都會分配一個新的線程來處理,在處理過程中涉及到下面類中的方法都屬于相同的一個線程,因此一次請求中ThreadLocal對象也應該相同

    因此,可以用ThreadLocal用于保存登錄用戶id,解決實現(xiàn)公共字段填充類無法在方法上自動裝配HttpServletRequest的困境,做到會話間數(shù)據(jù)共享

  1. 實現(xiàn)步驟

    項目筆記-瑞吉外賣(全)

  2. 具體代碼實現(xiàn)

    第一版

    package com.itzq.reggie.common;
    
    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;
    
    import java.time.LocalDateTime;
    
    @Component
    @Slf4j
    public class MyMetaObjectHandler implements MetaObjectHandler {
    
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("公共字段自動填充【insert】");
            log.info(metaObject.toString());
            metaObject.setValue("createTime", LocalDateTime.now());
            metaObject.setValue("updateTime", LocalDateTime.now());
            metaObject.setValue("createUser", new Long(1));
            metaObject.setValue("updateUser", new Long(1));
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("公共字段自動填充【update】");
            log.info(metaObject.toString());
        }
    }
    

    第二版

    @Component
    @Slf4j
    public class MyMetaObjectHandler implements MetaObjectHandler {
    
        /**
         * 插入時對公共字段賦值
         * @param metaObject
         */
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("公共字段自動填充【insert】");
            //1.直接給公共字段設置值
            metaObject.setValue("createTime", LocalDateTime.now());
            metaObject.setValue("updateTime", LocalDateTime.now());
            metaObject.setValue("createUser", BaseContext.get());
            metaObject.setValue("updateUser", BaseContext.get());
        }
    
        /**
         * 更新時對公共字段賦值
         * @param metaObject
         */
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("公共字段自動填充【update】");
    
            metaObject.setValue("updateTime", LocalDateTime.now());
            metaObject.setValue("updateUser", new Long(1));
    
    
        }
    }
    
    /**
     * 工具類,用來獲取當前登錄用戶的id
     * 這里設置為靜態(tài)的可以,因為每個線程的ThreadLocal值不同,這樣聲明成靜態(tài)的時候不同線程會賦予不同的ThreadLocal值
     * @author qin start
     * @create 2023-04-26-17:34
     */
    public class BaseContext {
    
        private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    
        public static void set(Long id){
            threadLocal.set(id);
        }
    
        public static Long get(){
            return threadLocal.get();
        }
    }
    

2.新增分類

  1. 功能分析

    項目筆記-瑞吉外賣(全)

  2. 代碼實現(xiàn)
    項目筆記-瑞吉外賣(全)

    @RestController
    @RequestMapping("/category")
    @Slf4j
    public class CategoryController {
    
        @Autowired
        private CategoryService categoryService;
        @PostMapping
        public R<String> save(@RequestBody Category category){
            log.info("新增菜品:{}",category);
            categoryService.save(category);
    
            return R.success("1");
        }
    }
    

3.分類的分頁查詢

  1. 項目筆記-瑞吉外賣(全)

  2. 代碼實現(xiàn)

    @GetMapping("/page")
    public R<Page> page(int page,int pageSize){
    
        //1.定義分頁構造器
        Page<Category> pageInfo = new Page<>(page,pageSize);
    
        //2.定義條件構造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Category::getSort);
    
        //3.進行查詢
        categoryService.page(pageInfo,queryWrapper);
    
        return R.success(pageInfo);
    }
    

4.分類刪除??

  1. 項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

  2. 簡單代碼開發(fā)(第一版)

    @DeleteMapping
    public R<String> delete(long ids){//這里一定要為long類型
        log.info("刪除菜品id:{}",ids);
    
        categoryService.removeById(ids);
    
        return R.success("1");
    }
    
  3. 第二版代碼開發(fā)思路+實現(xiàn)??

    因為菜品``和套餐都有關聯(lián)到分類的可能性,因此如果刪除分類時,分類里有相應的菜品和套餐,那么要判斷不能刪除

    ①頁面發(fā)送Ajax請求,傳過來要刪除菜品分類的id

    ②根據(jù)id去菜品表套餐表去查詢有幾條數(shù)據(jù),如果有數(shù)據(jù)的話拋出一個自定義業(yè)務異常,提示不能刪除

    ③沒有業(yè)務異常的話,進行正常的刪除操作

    1.增加一個業(yè)務方法

    @Service
    public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
        @Autowired
        private DishService dishService;
        @Autowired
        private SetMealService setMealService;
        @Override
        public void remove(long id) {
    
    
            //1.根據(jù)id去查詢菜品表
            LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Dish::getCategoryId,id);
    
            int count = dishService.count(queryWrapper);
            //拋出自定義業(yè)務處理異常
            if(count > 0){
                throw new CustomException("無法刪除分類,該分類下存在菜品信息");
            }
    
    
            //2.根據(jù)id去查詢套餐表
            LambdaQueryWrapper<Setmeal> queryWrapper1 = new LambdaQueryWrapper<>();
            queryWrapper1.eq(Setmeal::getCategoryId,id);
    
            int count1 = setMealService.count(queryWrapper1);
            //拋出自定義業(yè)務處理異常
            if(count1 > 0){
                throw new CustomException("無法刪除分類,該分類下存在套餐信息");
            }
            //3.如果沒有關聯(lián),那么調(diào)用父類ServiceImpl的方法刪除
            super.removeById(id);
        }
    }
    

    2.自定義異常類和全局異常類方法

    public class CustomException extends RuntimeException{
        public CustomException(String message){
            super(message);
        }
    }
    
    //用來處理刪除菜品分類信息
        @ExceptionHandler(CustomException.class)
        public R<String> exceptionHandler (CustomException ex){
    
            return R.error(ex.getMessage());
        }
    
    

3.修改分類

  1. 需求分析

  2. 代碼實現(xiàn)

    /**
         * 修改分類信息
         * @param category
         * @return
         */
    @PutMapping
    public R<String> update(@RequestBody Category category){
        log.info("修改分類信息為:{}",category);
        categoryService.updateById(category);
        return R.success("修改分類信息成功");
    }
    
    

day04

菜品管理相關內(nèi)容

項目筆記-瑞吉外賣(全)

1.文件上傳??

如果部署上去之后,無法打開頁面,先clean一下,再打開項目

  1. 需求分析

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    • 文件過濾器會先攔截下來請求,直接返回“未登錄”的信息

    項目筆記-瑞吉外賣(全)

  2. 具體實現(xiàn)

    • MutipartFile文件名必須為file,與前端標簽名保持一致

    • 文件上傳后轉存問題:如果文件上傳后不轉存到指定位置,那么默認存儲在一個本地的臨時文件中,程序運行后就會刪除,因此要文件轉存

    • 代碼:

      注意點:①讀取路徑用@Value注解從配置文件中讀取

      ? ②文件名的拼接

      ? ③文件路徑的判別合法問題

      ? ④返回文件名稱

      @RestController
      @RequestMapping("/common")
      @Slf4j
      public class CommonController {
      
      
          //@Value注解用來取值
          @Value("${reggie.path}")
          private String basePath;
      
          /**
           * 上傳文件
           * @param file  文件參數(shù)名必須為file
           * @return
           */
          @RequestMapping("/upload")
          public R<String> upload(@RequestBody MultipartFile file){
      
              log.info("上傳圖片:{}",file.toString());
      
              //1.生成文件名字,實現(xiàn)文件的保存功能,目前先保存到本地上
              //獲取文件類型
              String originalFilename = file.getOriginalFilename();
              String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
              //使用UUID生成自定義文件名
              String fileName = UUID.randomUUID().toString() + suffix;
      
              //創(chuàng)建存放的目錄對象
              File dir = new File(basePath);
              if(!dir.exists()){
                  dir.mkdirs();
              }
      
      
              try {
                  file.transferTo(new File(basePath + fileName));
              } catch (IOException e) {
                  e.printStackTrace();
              }
      
              //返回文件名稱,因為頁面之后要使用
              return R.success(fileName);
      
          }
      
      }
      

2.文件下載

  1. 前端分析

    項目筆記-瑞吉外賣(全)

  2. 代碼實現(xiàn)

    • 注意name字段由前端傳過來的參數(shù)/common/download?name=${response.data}自動賦值
    • 注意這里讀取是從服務端讀,發(fā)送是HttpServletResponse獲取的輸出去,傳送回去需要先設置響應頭數(shù)據(jù)格式
    @GetMapping("/download")
    //這的name由前端自動賦值
    public void downLoad(String name, HttpServletResponse response){
        //獲取文件名稱,從本地數(shù)據(jù)來創(chuàng)建一個input流進行讀取,output流進行輸出
        File file = new File(basePath + name);
    
        try {
            FileInputStream is = new FileInputStream(file);
    
            OutputStream outputStream = response.getOutputStream();
            response.setContentType("image/jpeg");
            //文件讀寫操作
            int len = 0;
            byte[] bytes = new byte[1024];
    
            while((len = is.read(bytes)) != -1){
                outputStream.write(bytes);
                //所儲存的數(shù)據(jù)全部清空
                outputStream.flush();
            }
            is.close();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

3.新增菜品??

  1. 需求分析

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

  2. 功能實現(xiàn)

    • 回顯菜品分類信息

      因為一進去發(fā)送地址在category下,因此要在category下編寫相應的請求

      項目筆記-瑞吉外賣(全)

      /**
           * 根據(jù)條件查詢分類信息,并返回json數(shù)據(jù)
           * @param category
           * @return
           */
      @GetMapping("/list")
      public R<List<Category>> list(Category category){
      
          LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
      
          queryWrapper.eq(category.getType() != null,Category::getType,category.getType());
      
          queryWrapper.orderByDesc(Category::getSort).orderByDesc(Category::getUpdateTime);
      
          List<Category> list = categoryService.list(queryWrapper);
      
          return R.success(list);
      }
      
    • 處理前端發(fā)過來的請求

      DTO繼承實體類,擴展實體類

    • 將DTO中的數(shù)據(jù)保存到兩張表中

      @Service
      @Transactional
      public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
      
          @Autowired
          DishFlavorService dishFlavorService;
      
          /**
           * 根據(jù)菜品信息,保存數(shù)據(jù)到菜品表和菜品口味表
           * @param dishDto
           */
          @Override
          public void saveWithFlavor(DishDto dishDto) {
              //保存菜品的基本信息到菜品的基本表中,如果有的菜品的屬性對不上,那么不保存
              super.save(dishDto);
      
              //這里保存之后,會將表中的數(shù)據(jù)重新填回到dishDto,因此也就獲取了此時的菜品id
      
              //獲取新增的菜品id,為口味表中的每一個口味增加相應的菜品id
              Long id = dishDto.getId();
              //獲取菜品口味
              List<DishFlavor> flavors = dishDto.getFlavors();
      
              for (DishFlavor flavor : flavors) {
                  flavor.setDishId(id);
              }
      
              //批量保存菜品口味數(shù)據(jù)到菜品口味表
              dishFlavorService.saveBatch(flavors);
              
          }
      }
      
  3. 總結分析

    • 處理前端請求
    • 后端返回值識前端需求的數(shù)據(jù)為準,后端定義了數(shù)據(jù)模型之后只要根據(jù)前端需求,將相應的需求放在R.data屬性中即可

4.菜品信息分頁查詢

  1. 需求分析

    • 處理分頁查詢中的難點在于:如何將dish表中的categoryId字段切換為categoryName,因為前端頁面需要菜品分類而不是菜品ID。所以我們就要用到dishDto這個類擴展dish類,① 先從dish表中查數(shù)據(jù)然后封裝到page類中 ② 將page類中的分頁信息數(shù)據(jù)數(shù)據(jù)拷貝到Page這個分頁信息類 ③ 將page中的records信息映射到page的分頁信息類

    封裝Dish數(shù)據(jù)

  2. 代碼實現(xiàn)

    • BeanUtils工具類屬于org.springframework.beans.BeanUtils,是spring框架提供的工具類,簡化數(shù)據(jù)封裝,用于封裝JavaBean,避免了大量的get,set方法進行拷貝賦值
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
    
        //1.根據(jù)名字查詢分頁信息
        //首先保證名字不為空
        Page<Dish> dishPageInfo = new Page<>(page,pageSize);
        Page<DishDto> dtoPageInfo = new Page<>();
    
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(name != null,Dish::getName,name);
        dishService.page(dishPageInfo,queryWrapper);
    
        //2.對dish分頁信息進行拷貝
        BeanUtils.copyProperties(dishPageInfo,dtoPageInfo,"records");
    
        List<Dish> records = dishPageInfo.getRecords();
    
        //3.返回頁面的新records數(shù)據(jù),就是data數(shù)據(jù)
        List<DishDto> list = records.stream().map((item) -> {
    
            //用來將每一個item變?yōu)閐ishDto,然后返回給Page<dishDto>
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(item,dishDto);
    
            Long categoryId = item.getCategoryId();
    
            Category category = categoryService.getById(categoryId);
            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
    
            return dishDto;
    
        }).collect(Collectors.toList());
    
        dtoPageInfo.setRecords(list);
    
        return R.success(dtoPageInfo);
    }
    

5.菜品修改信息??

  • 凡是涉及對數(shù)據(jù)庫數(shù)據(jù)的多次增刪改(>=2次),都需要事務控制,來防止一次修改出錯而接著照常執(zhí)行的錯誤
  1. 需求分析

    • 保存修改數(shù)據(jù)的時候發(fā)送的請求
  2. 代碼實現(xiàn)

    • DishServiceImpl —> 回顯功能

      進行簡單的查詢兩個表dish和Flavor,中間橋梁是dishID;查詢數(shù)據(jù)封裝到DishDto,返回給頁面

      dish中有dishID,而Flavor表中有dishID,可根據(jù)dishId查詢口味表

      /**
           * 根據(jù)id查詢查詢dishDto來賦值
           * 回顯信息
           * @param id
           * @return
           */
      @Override
      public DishDto getByIdWithFlavors(Long id) {
      
          //查詢dish表信息,獲取對應菜品
          Dish dish = this.getById(id);
          DishDto dishDto = new DishDto();
          //拷貝到拓展對象中
          BeanUtils.copyProperties(dish,dishDto);
      
          Long dishId = dish.getId();
          LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
          queryWrapper.eq(dishId != null,DishFlavor::getDishId,dishId);
          List<DishFlavor> list = dishFlavorService.list(queryWrapper);
      
          dishDto.setFlavors(list);
      
          //返回主角數(shù)據(jù)
          return dishDto;
      }
      
    • 保存修改數(shù)據(jù):

      操作:清理當前菜品口味信息,然后批量插入口味信息 —> 避免了還需要判斷是清楚還是增加的麻煩

      ①更新dish表②更新口味表(先清除之間口味信息,再批量插入當前口味信息)

      DishServiceImpl

      /**
           * 修改菜品信息
           * @param dishDto
           */
      @Override
      @Transactional//保證事務一致性
      public void updateWithFlavor(DishDto dishDto) {
          //1.更新dish表中的信息
          this.updateById(dishDto);
      
      
          //清理當前菜品,然后批量插入口味信息
          //2.根據(jù)id清除相關口味信息
          Long id = dishDto.getId();
          LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
          queryWrapper.eq(DishFlavor::getDishId,id);
      
          dishFlavorService.remove(queryWrapper);
      
          //3.批量插入口味信息,重新設置flavor信息,因為傳遞過來的flavor的dishId屬性沒賦上值
          List<DishFlavor> flavors = dishDto.getFlavors();
      
          flavors = flavors.stream().map((item) -> {
              item.setDishId(dishDto.getId());
              return item;
          }).collect(Collectors.toList());
      
          dishFlavorService.saveBatch(flavors);
      }
      

day05

1.概述

項目筆記-瑞吉外賣(全)

數(shù)據(jù)模型

注意套餐關系表中存儲的數(shù)據(jù),一個套餐對應多個菜品,分開存儲

項目筆記-瑞吉外賣(全)

項目筆記-瑞吉外賣(全)

項目筆記-瑞吉外賣(全)

項目筆記-瑞吉外賣(全)

2.新增套餐

  1. 分析??

    和上一個開發(fā)類似

    1. 前端提交過來的信息包含套餐基本信息套餐與菜品關聯(lián)信息

      因此需要設置一個setmealDto,Dto中包含套餐基本信息和套餐與菜品關聯(lián)信息

    2. 后端在setmealController中接收這個Dto,然后新增業(yè)務方法去處理Dto

    3. 業(yè)務方法:

      ①將dto基本信息傳入到套餐基本信息表

      ②將套餐id這個對象中的list集合中的數(shù)據(jù)添加到套餐菜品表

      ③涉及操作兩張表,需要加入@transactional注解,要么同時成功,要么同時失敗

    項目筆記-瑞吉外賣(全)

  2. 功能實現(xiàn)一:回顯添加菜品

    根據(jù)分類categoryId,來去相應dish表中查詢信息,進而回顯信息

    /**
         * 根據(jù)categoryId,回顯對應分類下的菜品信息
         * @param dish
         * @return
         */
    @GetMapping("/list")
    public R<List<Dish>> list(Dish dish){
    
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        //兩個eq信息
        queryWrapper.eq(dish != null,Dish::getCategoryId,dish.getCategoryId());
        queryWrapper.eq(Dish::getStatus,1);
    
        //添加排序條件
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
    
        List<Dish> list = dishService.list(queryWrapper);
    
        return R.success(list);
    }
    

    功能實現(xiàn)二:

    實現(xiàn)添加菜品功能

    • 這里要注意setMealId在前端傳過來的數(shù)據(jù)沒有,需要將前端基本信息添加到SetMeal表中,才能得到相應的Id,然后為套餐菜品對象賦值上值

    SetMealServiceImpl

    @Service
    public class SetMealServiceImpl extends ServiceImpl<SetMealMapper, Setmeal> implements SetMealService {
    
        @Autowired
        private SetMealDishService setMealDishService;
        /**
         * 新增菜品套餐
         * @param setmealDto
         */
        @Override
        public void saveWithDish(SetmealDto setmealDto) {
            //1.調(diào)用setMeal本有的功能,順便得到套餐id
            this.save(setmealDto);
    
            //2.將從數(shù)據(jù)庫得到的套餐id封裝回setmealDishes對象中
            List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
    
            setmealDishes.stream().map((item) -> {
                item.setSetmealId(setmealDto.getId());
                return item;
            }).collect(Collectors.toList());
    
    
            //3.
            setMealDishService.saveBatch(setmealDishes);
        }
    }
    

3.套餐分頁查詢

  • 功能與day04的菜品信息分類查詢相似
  • 在套餐管理界面,套餐分類字段顯示的是categoryId對應的中文,但在數(shù)據(jù)庫里查詢到的是categoryId,因此需要利用categoryId查詢到categoryName,并賦值給數(shù)據(jù)傳輸對象SetmealDto
/**
     * 套餐分頁查詢
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
@GetMapping("/page")
public R<Page> list(int page, int pageSize, String name){
    //分頁構造器對象
    Page<Setmeal> pageInfo = new Page<>(page, pageSize);
    Page<SetmealDto> dtoPage = new Page<>();

    //構造查詢條件對象
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(name != null, Setmeal::getName, name);

    //操作數(shù)據(jù)庫
    setMealService.page(pageInfo,queryWrapper);

    //對象拷貝
    BeanUtils.copyProperties(pageInfo,dtoPage,"records");

    List<Setmeal> records = pageInfo.getRecords();

    List<SetmealDto> list = records.stream().map((item) -> {
        SetmealDto setmealDto = new SetmealDto();
        BeanUtils.copyProperties(item, setmealDto);
        //獲取categoryId
        Long categoryId = item.getCategoryId();
        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);
}

4.刪除套餐信息

  1. 需求分析

    項目筆記-瑞吉外賣(全)

    提供一個方法處理刪除一個和刪除多個請求

    項目筆記-瑞吉外賣(全)

  2. 代碼開發(fā)

    注意點:

    ①接受前端ids數(shù)據(jù),傳過來的數(shù)據(jù)本身是數(shù)組形式,所以加不加注解無所謂,但是List是列表,所以要加注解@RequestParam

    ②根據(jù)id刪除套餐,不僅刪除套餐,也刪除關聯(lián)套餐表中的信息

    業(yè)務邏輯:(SetMealServiceImpl

    ? 1.查詢套餐狀態(tài),確定是否可用刪除
    ? 2.如果不能刪除,拋出一個業(yè)務異常,提示在售賣中
    ? 3.如果可以刪除,先刪除套餐表中的數(shù)據(jù)
    ? 4.刪除關系表中的數(shù)據(jù)

/**
     * 根據(jù)ids刪除套餐信息
     * @param ids
     */
@Override
@Transactional
public void removeWithDish(List<Long> ids) {

    //        1.查詢套餐狀態(tài),確定是否可用刪除
    //SQL語句:select count(*) from setMeal where id in ids and status = 1;
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.in(Setmeal::getId,ids);
    queryWrapper.eq(Setmeal::getStatus,1);
    int count = this.count(queryWrapper);
    //        2.如果不能刪除,拋出一個業(yè)務異常,提示**在售賣中**
    if(count > 0){
        throw new CustomException("商品還在銷售,不能刪除");
    }
    //        3.如果可以刪除,先刪除套餐表中的數(shù)據(jù)
    this.removeByIds(ids);
    //        4.刪除關系表中的數(shù)據(jù)
    //根據(jù)套餐id去關系表中去查數(shù)據(jù),然后匹配刪除
    //delete from setMealDish where setmealId in ids
    LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
    setMealDishService.remove(lambdaQueryWrapper);

}
  1. 起售,停售操作(SetMealServiceImpl)

    • 這里采用遍歷操作實現(xiàn)批量停售,起售不太好,應該用mp的具體更新方法操作,等學了mp之后再來補吧,希望還記得
/**
     * 更改售賣狀態(tài)
     * @param ids
     * @param status 1表示啟售,0表示停售
     */
@Override
public void changeStatus(List<Long> ids, int status) {

    //改變售賣狀態(tài)
    for (int i = 0; i < ids.size(); i++) {
        Long id = ids.get(i);
        //根據(jù)id得到每個dish菜品。
        Setmeal setmeal = this.getById(id);
        setmeal.setStatus(status);
        this.updateById(setmeal);
    }

}

5.短信發(fā)送

  1. 概述

    項目筆記-瑞吉外賣(全)

    項目筆記-瑞吉外賣(全)

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zEC2oEPD-1685267442440)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305082027150.png)]

  2. 代碼開發(fā)

6.手機驗證碼登錄

  1. 需求分析

  2. 代碼開發(fā)(前期準備)

    ①對短信服務進行放行,否則會自動跳回到登錄頁面

    ②改寫全局過濾器,對移動端用戶進行驗證碼訪問,進行放行

  3. 代碼開發(fā)(發(fā)送驗證碼)

    項目筆記-瑞吉外賣(全)

    避坑:

    這個測試的時候,前端頁面有問題,login.html不發(fā)送ajax請求,解決辦法:把day05代碼中的所有前端代碼替換到自己的項目中就行了

/**
     * 發(fā)送手機驗證碼
     * @param user
     */
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
    //1.獲取手機號,并進行檢驗
    String phone = user.getPhone();

    //2.生成驗證碼,采用短信發(fā)送服務進行發(fā)送短信
    if(phone != null){
        String code = String.valueOf(ValidateCodeUtils.generateValidateCode(4));
        log.info("驗證碼為:{}",code);

        //            SMSUtils.sendMessage("瑞吉外賣","SMS_460725810",phone,code);

        //3.將發(fā)送的驗證碼保存在session中,便于之后查驗
        session.setAttribute(phone,code);

        return R.success("發(fā)送驗證碼成功,等待查收");

    }

    //4.短信發(fā)送錯誤,返回錯誤信息;成功,返回成功信息
    return R.error("發(fā)送驗證碼失敗");
}
  1. 代碼開發(fā)(驗證碼登錄)

    1. 因前端傳過來的對象,后端沒有相應的實體類與其對應

      這時可以采取拓展實體類dto或者是map集合的方式接收

    2. 登錄方法返回值為User對象,這樣讓瀏覽器也保存一份用戶信息

/**
     * 發(fā)送手機驗證碼
     * @param map
     */
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){

    log.info("登錄操作:{}",map);
    //1.獲取手機號和驗證碼
    String phone = map.get("phone").toString();
    String code = map.get("code").toString();
    //2.進行手機號和驗證碼比對,如果成功進行登錄的邏輯
    Object codeInSession = session.getAttribute("phone");
    if(codeInSession != null && codeInSession.equals(code)){

        //3.匹配結果對上之后,如果手機號在表中不存在自動完成祖冊
        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("id",user.getId());

        return R.success(user);
    }

    //4.短信發(fā)送錯誤,返回錯誤信息;成功,返回成功信息
    return R.error("登陸失敗");
}

day06

這一天都是移動端開發(fā)

1.地址簿相關功能

  1. 需求分析
/**
 * 地址簿管理
 */
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {

    @Autowired
    private AddressBookService addressBookService;

    /**
     * 新增
     */
    @PostMapping
    public R<AddressBook> save(@RequestBody AddressBook addressBook) {
        addressBook.setUserId(BaseContext.get());
        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.get());
        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);
    }

    /**
     * 根據(jù)id查詢地址
     */
    @GetMapping("/{id}")
    public R get(@PathVariable Long id) {
        AddressBook addressBook = addressBookService.getById(id);
        if (addressBook != null) {
            return R.success(addressBook);
        } else {
            return R.error("沒有找到該對象");
        }
    }

    /**
     * 查詢默認地址
     */
    @GetMapping("default")
    public R<AddressBook> getDefault() {
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AddressBook::getUserId, BaseContext.get());
        queryWrapper.eq(AddressBook::getIsDefault, 1);

        //SQL:select * from address_book where user_id = ? and is_default = 1
        AddressBook addressBook = addressBookService.getOne(queryWrapper);

        if (null == addressBook) {
            return R.error("沒有找到該對象");
        } else {
            return R.success(addressBook);
        }
    }

    /**
     * 查詢指定用戶的全部地址
     */
    @GetMapping("/list")
    public R<List<AddressBook>> list(AddressBook addressBook) {
        addressBook.setUserId(BaseContext.get());
        log.info("addressBook:{}", addressBook);

        //條件構造器
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());
        queryWrapper.orderByDesc(AddressBook::getUpdateTime);

        //SQL:select * from address_book where user_id = ? order by update_time desc
        return R.success(addressBookService.list(queryWrapper));
    }
}

2.菜品展示

  1. 需求分析

    image-20230509151017659

    ①因為發(fā)送兩次請求,第二次失敗,所以展示信息有誤,這里將第二次的改用假數(shù)據(jù)

    ②存在問題:頁面發(fā)送的請求為http://localhost:8080/dish/list?categoryId=1397844263642378242&status=1,這個list請求在Controller中只設置了返回List類型,而Dish實體類中沒有相應的口味信息,因此在前端頁面上不會顯示口味信息,所以要拓展dish實體類,返回dishDto

    1. 修改list方法,返回dishDto的代碼
    
    /**
         * 根據(jù)categoryId,回顯對應分類以及菜品口味信息
         * @param dish
         * @return
         */
    @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish){
    
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        //兩個eq信息
        queryWrapper.eq(dish != null,Dish::getCategoryId,dish.getCategoryId());
        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();
    
            //1.對象拷貝(每一個list數(shù)據(jù))
            BeanUtils.copyProperties(item,dishDto);
            Long categoryId = item.getCategoryId();  //分類id
            //通過categoryId查詢到category內(nèi)容
            Category category = categoryService.getById(categoryId);
            //判空
            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
    
            //2.將菜品口味信息賦值給dto
    
            Long id = item.getId();
            LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
            dishFlavorLambdaQueryWrapper.eq(DishFlavor::getDishId,id);
    
            List<DishFlavor> dishFlavors = dishFlavorService.list(dishFlavorLambdaQueryWrapper);
    
            dishDto.setFlavors(dishFlavors);
            return dishDto;
        }).collect(Collectors.toList());
    
        return R.success(dishDtoList);
    }
    
    1. 設置setMeal方法,用于展示套餐
    /**
         * 用于移動端展示數(shù)據(jù)
         * @param setmeal
         * @return
         */
    @GetMapping("/list")
    public R<List<Setmeal>> list(Setmeal setmeal){
        //創(chuàng)建條件構造器
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        //添加條件
        queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
        queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
    
        //排序
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);
    
        List<Setmeal> list = setMealService.list(queryWrapper);
    
        return R.success(list);
    }
    

3.加入購物車

  1. 數(shù)據(jù)分析

    項目筆記-瑞吉外賣(全)

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-btcgqkdD-1685267442442)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305141908129.png)]

  2. 代碼開發(fā)

    項目筆記-瑞吉外賣(全)

    設置userId,確定哪個用戶點的,然后判斷number數(shù)據(jù)類型

    項目筆記-瑞吉外賣(全)

    @PostMapping("/add")
    public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
    
        log.info("購物車數(shù)據(jù):{}",shoppingCart);
        //1.將當前用戶的id設置進數(shù)據(jù)庫中
        Long currentId = BaseContext.get();
        shoppingCart.setUserId(currentId);
    
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,currentId);
    
        //2.判斷當前傳過來的是菜品信息還是套餐信息。用于后續(xù)判斷當前菜品或者套餐是否在購物車中
        if(shoppingCart.getDishId() != null){
            queryWrapper.eq(ShoppingCart::getDishId,shoppingCart.getDishId());
        }else{
            queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
        }
    
        //select * from shoppingCart where userId = ? and ...
    
        ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
        //3.進行查詢記錄,如果能查到記錄,那么說明數(shù)據(jù)庫中有,需要在原有基礎上+1
        if(cartServiceOne != null){
            Integer number = cartServiceOne.getNumber();
            number += 1;
            cartServiceOne.setNumber(number);
    
            shoppingCartService.updateById(cartServiceOne);
        }else{
            shoppingCart.setNumber(1);
            shoppingCartService.save(shoppingCart);
            cartServiceOne.setNumber(1);
        }
    
        return R.success(cartServiceOne);
    }
    
    1. 展示購物車信息 /list

      根據(jù)userId,去查購物車返回list集合就可

      按照登錄id去數(shù)據(jù)庫中查詢信息
      不同用戶的userid不同,所以購物車信息不同

4.用戶下單??

用到了很多張表,具有可學習性

  1. 訂單表插入數(shù)據(jù) — 從購物表中算出總金額 查詢用戶信息
  2. 訂單明細表中插入多條數(shù)據(jù) — 從購物表得出
  1. 需求分析

    項目筆記-瑞吉外賣(全)

    訂單明細表

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OMTzZb4b-1685267442445)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305142010371.png)]

  2. 代碼開發(fā)

    項目筆記-瑞吉外賣(全)

    用戶下單分析一:(下單之后,存入訂單表訂單明細表中)

    ? 傳遞參數(shù):

    ? 不需要傳遞過來購物車信息用戶id,因為在登陸過程中已經(jīng)知道用戶id且購物車信息可根據(jù)用戶id查詢出來

    用戶下單分析二:

    ? 獲得當前用戶id

    ? 查詢當前用戶的購物車數(shù)據(jù)
    ? 向訂單表插入數(shù)據(jù),一條數(shù)據(jù)
    ? 向訂單明細表插入數(shù)據(jù),多條數(shù)據(jù)
    ? 清空購物車數(shù)據(jù)

2.項目優(yōu)化

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-j3E0jDZj-1685267442447)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305262033884.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wz3p48bs-1685267442447)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305262034094.png)]

day01

1.引入版本控制

  1. 先創(chuàng)建一個遠程的空倉庫,復制鏈接
  2. 本地先創(chuàng)建倉庫,然后連接遠程倉庫推送
  3. 創(chuàng)建兩個分支,masterv1.0,v1.0用于開發(fā)緩存內(nèi)容,開發(fā)完成后,合并到master分支

2.環(huán)境搭建

  1. springboot-redis-starter

  2. redis文件配置

  3. 設置redis配置類,便于觀察

    項目筆記-瑞吉外賣(全)

3.短信驗證碼(Redis)

  1. 實現(xiàn)思路

    項目筆記-瑞吉外賣(全)

4.緩存菜品數(shù)據(jù)

  1. 實現(xiàn)思路

    主從一致

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TAWU5g3p-1685267442449)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271513707.png)]

  2. 實現(xiàn)代碼

    • 查詢菜品
    1. 先根據(jù)前端傳遞過來的categoryId和status構造一個key,從緩存數(shù)據(jù)庫中獲取,如果能獲取到,那么直接返回
    2. 獲取不到dishDto,進行1去數(shù)據(jù)庫查詢,然后放入緩存數(shù)據(jù)庫,然后返回
    //查詢方法
    @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish){
    
    
        List<DishDto> dishDtoList = null;
    
        //1.第一次訪問構造key,存入緩存數(shù)據(jù)庫中
        String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();
    
        //2.從緩存數(shù)據(jù)庫中拿數(shù)據(jù)
        dishDtoList = (List<DishDto>)redisTemplate.opsForValue().get(key);
    
        //3.判斷緩存數(shù)據(jù)庫中有沒有,有的話直接去拿數(shù)據(jù)
        if(dishDtoList != null){
            return R.success(dishDtoList);
        }
        //4.如果沒有的話,去數(shù)據(jù)庫查詢
        //.....
    }
    
    //修改方法
    @PostMapping
    public R<String> add(@RequestBody DishDto dishDto){
    
        log.info(dishDto.toString());
    
        dishService.saveWithFlavor(dishDto);
    
        //添加后后刪除數(shù)據(jù)庫中對應的key  刪除dish開頭+dishid
        String key = "dish_" + dishDto.getCategoryId() + "_1";
        redisTemplate.delete(key);
    
        return R.success("新增菜品成功");
    }
    

5.SpringCache

  1. 概述

    常用注解

    • 使用spring cache

      使用哪種緩存技術,就導入對應的包,然后開啟注解即可

    • 使用jar包

      使用基礎功能的緩存,導入web包即可

      使用基于redis等緩存技術,那么要導入spring-boot-starter-cache

    項目筆記-瑞吉外賣(全)

  2. 普通cache使用

    • 普通的緩存在加注解之后放在一個線程安全的map中,基于內(nèi)存

    • 初始化緩存方式

      ①注入cachemanager,加上注解緩存操作(獲取方法內(nèi)參數(shù),通過SPEL),緩存的對象的類要實現(xiàn)序列化接口

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FrJVSACv-1685267442450)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271636494.png)]

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-icyXONac-1685267442450)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271638756.png)]

    刪除緩存

    項目筆記-瑞吉外賣(全)

    查詢數(shù)據(jù)

    項目筆記-瑞吉外賣(全)

  3. redis使用

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gUFBCIaN-1685267442452)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271707236.png)]

6.使用springCache緩存套餐數(shù)據(jù)

項目筆記-瑞吉外賣(全)

緩存的數(shù)據(jù):分類 + 不同種類

項目筆記-瑞吉外賣(全)

day02

1.MySQL主從復制

  1. 介紹

    實現(xiàn)讀寫分離,減輕單臺數(shù)據(jù)庫壓力和防止主數(shù)據(jù)庫數(shù)據(jù)損毀

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lZYOYNoS-1685267442454)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271739718.png)]

  2. 原理—從庫和主庫通過日志做一樣的操作

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kgG6J8Kg-1685267442454)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271745822.png)]

  3. 操作步驟

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jLGSubZH-1685267442454)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271859784.png)]

    第二步:重啟MySQL

    項目筆記-瑞吉外賣(全)

    #mysql8第三步執(zhí)行
    create user xiaoming identified by 'Root@123456';
    grant replication slave on *.* to xiaoming;
    

    MySQL主從復制略過了。。。之后有時間再來補吧

2.Nginx

1.概述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gebXbH18-1685267442455)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271928408.png)]

項目筆記-瑞吉外賣(全)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0j5WVZVf-1685267442456)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271939160.png)]

2.基本命令

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-s0W8kKqq-1685267442457)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271942072.png)]

./nginx -v查看版本

./nginx -t檢查配置文件是否正確

3.配置文件

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KB1hPHBa-1685267442458)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281346785.png)]

4.niginx具體應用??
1.部署靜態(tài)資源
  1. 配置信息在config文件里
  2. 服務可開多個

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mk1fM9XZ-1685267442458)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281352592.png)]

2.反向代理

正向代理是在客戶端設置,反向代理是在服務端設置

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lGfUbUtV-1685267442459)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281401642.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KvR5Nqlq-1685267442459)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281404563.png)]

實現(xiàn)反向代理 ----轉發(fā)

3.負載均衡

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1ZuNYVVu-1685267442460)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281413916.png)]

實現(xiàn)

負載均衡算法

  • 默認輪詢算法

day03

1.前后端分離

1.概述

項目筆記-瑞吉外賣(全)

項目筆記-瑞吉外賣(全)

2.前后端分離開發(fā)

變化:前后端代碼不再混合在一個工程中

  1. 開發(fā)流程

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MgWOZ1cB-1685267442461)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281432025.png)]

3.YApi

提供API接口數(shù)據(jù)

4.Swagger(生成接口文檔)
  1. 介紹

    項目筆記-瑞吉外賣(全)

  2. 具體實現(xiàn)

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ehhnnKDk-1685267442463)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281443013.png)]

    項目筆記-瑞吉外賣(全)

  3. 功能

    因為包掃描,所以能真正地controller

  4. 常用注解

    目的是為了生成接口的時候,個性化定制

    項目筆記-瑞吉外賣(全)

2.項目部署

部署架構

  • 前端項目
  1. 部署靜態(tài)資源nginx上

    將前端靜態(tài)資源文件放在nginx的html文件夾下,然后修改nginx配置信息,映射靜態(tài)文件

  2. 配置反向代理

    項目筆記-瑞吉外賣(全)

  • 后端項目

    1. 項目筆記-瑞吉外賣(全)

    2. 打包項目 部署工程

      (如果訪問后端項目,訪問超時,可能是因為數(shù)據(jù)庫的問題,可能遠端沒有配置數(shù)據(jù)庫)

  • 總結

    之后再啟動項目,先啟動前端項目,再啟動后端項目
    .aliyuncs.com/img/202305281417603.png" alt=“image-20230528141758552” style=“zoom:33%;” />

day03

1.前后端分離

1.概述

[外鏈圖片轉存中…(img-RglKcxwc-1685267442460)]

[外鏈圖片轉存中…(img-gqfy4Vyd-1685267442461)]

2.前后端分離開發(fā)

變化:前后端代碼不再混合在一個工程中

  1. 開發(fā)流程

    [外鏈圖片轉存中…(img-MgWOZ1cB-1685267442461)]

3.YApi

提供API接口數(shù)據(jù)

4.Swagger(生成接口文檔)
  1. 介紹

    [外鏈圖片轉存中…(img-OcSGU8NN-1685267442462)]

  2. 具體實現(xiàn)

    [外鏈圖片轉存中…(img-ehhnnKDk-1685267442463)]

    [外鏈圖片轉存中…(img-qddnjUoD-1685267442464)]

  3. 功能

    因為包掃描,所以能真正地controller

  4. 常用注解

    目的是為了生成接口的時候,個性化定制

    [外鏈圖片轉存中…(img-OkPdI6g0-1685267442464)]

2.項目部署

部署架構

  • 前端項目
  1. 部署靜態(tài)資源nginx上

    將前端靜態(tài)資源文件放在nginx的html文件夾下,然后修改nginx配置信息,映射靜態(tài)文件

  2. 配置反向代理

    [外鏈圖片轉存中…(img-mFWI408T-1685267442465)]

  • 后端項目

    1. [外鏈圖片轉存中…(img-sitC0vrN-1685267442465)]

    2. 打包項目 部署工程

      (如果訪問后端項目,訪問超時,可能是因為數(shù)據(jù)庫的問題,可能遠端沒有配置數(shù)據(jù)庫)

  • 總結

    之后再啟動項目,先啟動前端項目,再啟動后端項目文章來源地址http://www.zghlxwxcb.cn/news/detail-466971.html

到了這里,關于項目筆記-瑞吉外賣(全)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 做完瑞吉外賣項目的一點筆記和源碼

    做完瑞吉外賣項目的一點筆記和源碼

    源碼在 https://gitee.com/pluto8/take-out 1、軟件開發(fā)流程 需求分析 :產(chǎn)品原型,需求規(guī)格說明書(文檔形式) 設計:產(chǎn)品文檔、UI界面設計、概要設計、詳細設計、數(shù)據(jù)庫設計 編碼:項目代碼,單元測試 測試:測試用例,測試報告 上線運維:軟件環(huán)境安裝,配置 2、角色分工:

    2024年02月08日
    瀏覽(21)
  • 【SpringBoot項目實戰(zhàn)+思維導圖】瑞吉外賣①(項目介紹、開發(fā)環(huán)境搭建、后臺登陸/退出功能開發(fā))

    【SpringBoot項目實戰(zhàn)+思維導圖】瑞吉外賣①(項目介紹、開發(fā)環(huán)境搭建、后臺登陸/退出功能開發(fā))

    全文主體框架來源于黑馬瑞吉外賣的項目資料,我在文中會嵌入如下五個方面的個人內(nèi)容: 項目中易發(fā)生錯誤的地方 項目中涉及的一些難理解知識點 一些遺忘知識點的回顧 業(yè)務的多種實現(xiàn)方法 我在做項目時的思考和一些踩坑 作為一名軟件開發(fā)工程師,我們需要了解在軟件開

    2024年02月05日
    瀏覽(36)
  • 瑞吉外賣項目——瑞吉外賣

    瑞吉外賣項目——瑞吉外賣

    需求分析:產(chǎn)品原型、需求規(guī)格說明書 設計:產(chǎn)品文檔、UI界面設計、概要設計、詳細設計、數(shù)據(jù)庫設計 編碼:項目代碼、單元測試 測試:測試用例、測試報告 上線運維:軟件環(huán)境安裝、配置 項目經(jīng)理:對整個項目負責,任務分配、把控進度 產(chǎn)品經(jīng)理:進行需求調(diào)研,輸

    2023年04月26日
    瀏覽(22)
  • 瑞吉外賣筆記

    瑞吉外賣筆記

    直接創(chuàng)建新工程 繼承父工程的形式來做這個,這里新建父工程 pom文件 創(chuàng)建測試類并啟動 導入 在默認頁面和前臺頁面的情況下,直接把這倆拖到resource目錄下直接訪問是訪問不到的,因為被mvc框架攔截了 所以我們要編寫一個映射類放行這些資源 創(chuàng)建配置映射類 訪問成功 用

    2024年01月19日
    瀏覽(18)
  • 瑞吉外賣項目記錄

    瑞吉外賣項目記錄

    本文為個人學習黑馬《瑞吉外賣》項目后進行的項目總結,更偏向于對自己編寫文本能力的鍛煉以及對項目知識點的簡短記錄。因為個人能力問題,其中可行性分析和測試部分只進行了小標題的陳列,并沒有進行編輯。對《瑞吉外賣》項目感興趣的朋友也可以瀏覽本文后再去

    2024年02月05日
    瀏覽(19)
  • 瑞吉外賣項目----(2)緩存優(yōu)化

    瑞吉外賣項目----(2)緩存優(yōu)化

    將項目推送到遠程倉庫里,教程在git 提交遠程倉庫前建議取消代碼檢查 創(chuàng)建新的分支v1.0(用于實現(xiàn)緩存優(yōu)化)并推送到遠程倉庫 1.1.1 maven坐標 導入spring-data-redis的maven坐標: 1.1.2 配置文件 在application.yml中加入redis相關配置: 1.1.3 配置類 在項目中加入RedisConfig 1.2.1 實現(xiàn)思路

    2024年02月14日
    瀏覽(16)
  • 瑞吉外賣項目——前后端分離

    瑞吉外賣項目——前后端分離

    前后端分離開發(fā),就是在項目開發(fā)過程中,對于前端代碼的開發(fā)由專門的 前端開發(fā)人員 負責,后端代碼則由 后端開發(fā)人員 負責,這樣可以做到分工明確、各司其職,提高開發(fā)效率,前后端代碼并行開發(fā),可以加快項目開發(fā)進度。 目前,前后端分離開發(fā)方式已經(jīng)被越來越多

    2023年04月20日
    瀏覽(17)
  • 【java】【項目實戰(zhàn)】[外賣五]菜品管理業(yè)務開發(fā)

    【java】【項目實戰(zhàn)】[外賣五]菜品管理業(yè)務開發(fā)

    目錄 一、文件上傳與下載 1.1 文件上傳介紹 1.2 文件下載介紹 1.3 文件上傳代碼實現(xiàn) 1.3.1 新增upload.html 1.3.2 修改application.yml ?1.3.3?CommonController 1.3.4? 功能測試 1.4 文件下載代碼實現(xiàn) ?1.4.1? CommonController 1.4.2? 功能測試 二、新增菜品 2.1 需求分析 2.2 數(shù)據(jù)模型 2.3 代碼實現(xiàn) 2.3.

    2024年02月11日
    瀏覽(23)
  • 【SpringBoot項目】SpringBoot項目-瑞吉外賣【day01】

    【SpringBoot項目】SpringBoot項目-瑞吉外賣【day01】

    ??博客x主頁:己不由心王道長??! ??文章說明:SpringBoot項目-瑞吉外賣【day01】?? ?系列專欄:SpringBoot項目 ??本篇內(nèi)容:對黑馬的瑞吉外賣項目的day01進行筆記和項目實現(xiàn)?? ??每日一語:人有退路,就有些許安全感。等到哪一天,你真沒了退路,你就發(fā)現(xiàn)眼前哪條路都

    2023年04月08日
    瀏覽(24)
  • 【SpringBoot項目】SpringBoot項目-瑞吉外賣【day03】分類管理

    【SpringBoot項目】SpringBoot項目-瑞吉外賣【day03】分類管理

    ??博客x主頁:己不由心王道長??! ??文章說明:SpringBoot項目-瑞吉外賣【day03】分類管理?? ?系列專欄:SpringBoot項目 ??本篇內(nèi)容:對黑馬的瑞吉外賣項目的day03進行筆記和項目實現(xiàn)?? ??每日一語:生活不可能像你想象得那么好,但也不會像你想象得那么糟。?? ??

    2024年02月22日
    瀏覽(17)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包