Reggie

接口


一.每日功能更新

22-05-22 day01

  1. 創(chuàng)建項(xiàng)目,完成后端員工登陸與登出功能
  2. 創(chuàng)建遠(yuǎn)程倉(cāng)庫(kù),push代碼到遠(yuǎn)程develop分支

22-05-23 day02
1.基于filter實(shí)現(xiàn)權(quán)限控制
2.新增添加員工功能
3.全局異常處理器,解決員工username注冊(cè)時(shí)重復(fù)問題
4.新增員工條件分頁(yè)查詢功能
5.新增修改員工狀態(tài)/修改員工信息(前端long id問題解決)
6.新增修改頁(yè)面員工信息回顯


22-05-25 day03
1.利用MybatisPlus的公共字段填充功能 解決各個(gè)表的冗余字段
2.利用ThreadLocal解決獨(dú)立線程數(shù)據(jù)共享問題
3.菜品/套餐分類增刪改查
5.菜品/套餐分類刪除操作的邏輯外鍵


22-05-26 day04
1.文件上傳與預(yù)覽接口
2.添加菜品功能(dish+dish_flavor)
3.前端映射對(duì)象Dto類的創(chuàng)建
4.dish的分頁(yè)條件查詢(BeanUtils的使用,dishPage對(duì)象轉(zhuǎn)dishDtoPage對(duì)象)


22-05-28 day05
1.套餐新增功能(套餐+套餐菜品關(guān)系)
2.套餐分頁(yè)展示功能
3.套餐修改功能(套餐+套餐菜品關(guān)系)
4.套餐刪除功能(套餐+套餐菜品關(guān)系)
5.LocalDateTime類型導(dǎo)致前端時(shí)間帶T


22-05-29 day06
1.前臺(tái)用戶的登陸與權(quán)限校驗(yàn)
2.前臺(tái)主頁(yè)面的菜品分類與級(jí)聯(lián)顯示
3.購(gòu)物車的增刪改查


22-05-31 day07
1.前臺(tái)新增訂單/訂單項(xiàng)
2.前臺(tái)新增用戶個(gè)人訂單列表展示
3.后臺(tái)新增所有訂單展示
4.用redis代替session完成驗(yàn)證碼功能
5.用redis完成根據(jù)categoryId查詢菜品緩存的功能
6.用redis+spring cache 完成categoryId查詢套餐緩存的功能


22-06-01 day08
1.使用swagger整理所有接口
2.使用swagger注解描述接口

二.使用技術(shù)點(diǎn)

靜態(tài)資源無(wú)法直接訪問?

解決方案:添加靜態(tài)資源映射
創(chuàng)建SpringMvcConfig類繼承WebMvcConfigurationSupport,重寫addResourceHandlers方法

@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {

    @Override
    //設(shè)置靜態(tài)資源文件映射
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // /backend/** 這些文件需要使用靜態(tài)資源映射
        registry.addResourceHandler("/backend/**")
                //靜態(tài)資源文件映射的位置在/backend/下
                .addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**")
                .addResourceLocations("classpath:/front/");
    }


密碼的MD5加密

密碼都是經(jīng)MD5加密后存于數(shù)據(jù)庫(kù),所以不論是登陸校驗(yàn),還是用戶注冊(cè)都需要將密碼MD5加密后進(jìn)行存儲(chǔ) 比對(duì)...

import org.springframework.util.DigestUtils;
//將字節(jié)轉(zhuǎn)換為16進(jìn)制MD5密碼
String password = DigestUtils.md5DigestAsHex(password.getBytes());

響應(yīng)格式R類

定義了標(biāo)準(zhǔn)的響應(yīng)格式類R<T>,該類提供了返回成功和失敗的靜態(tài)方法
并沒有將有參構(gòu)造對(duì)外暴露,保證了傳遞給前段數(shù)據(jù)的一致性

package com.itheima.reggie.common;

import lombok.Data;
import java.util.HashMap;
import java.util.Map;

@Data
public class R<T> {

    private Integer code; //編碼:1成功,0和其它數(shù)字為失敗

    private String msg; //錯(cuò)誤信息

    private T data; //數(shù)據(jù)

    private Map map = new HashMap(); //動(dòng)態(tài)數(shù)據(jù)

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}


MybatisPlus簡(jiǎn)化了單表的操作,

mapper接口繼承BaseMapper<T>

@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}

Service接口繼承Iservce<T>

public interface EmployeeService extends IService<Employee> {
}

ServiceImpl繼承ServiceImpl<mapper,T>實(shí)現(xiàn)Service接口

@Service
public class EmployeeServiceImpl 
extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}

controller層直接使用ServiceImpl進(jìn)行查詢
使用lambdaQueryWrapper代替硬編碼的column字段參數(shù)

       //根據(jù)用戶名查詢到員工
        LambdaQueryWrapper<Employee> queryWrapper =new LambdaQueryWrapper();
        queryWrapper.eq(Employee::getUsername,loginEmployee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);

增加權(quán)限過濾器
  • 如何在springboot中使用過濾器
    1.創(chuàng)建MyFilter實(shí)現(xiàn)Filter接口,并用@WebFilter標(biāo)注
    2.在啟動(dòng)類打開Servlet注解掃描@ServletComponentScan
  • 如何設(shè)置過濾器放行靜態(tài)資源/登錄頁(yè)面?
  1. 創(chuàng)建數(shù)組 將放行路徑存于數(shù)組
  2. 在doFilter方法中獲取通過request的getRequestUri的方法獲取請(qǐng)求路徑
    將uri與數(shù)組中可以放行的路徑進(jìn)行遍歷
    遍歷過程中使用AntPathMatcher類的match方法進(jìn)行路徑匹配確認(rèn)
  3. 被攔截頁(yè)面進(jìn)行權(quán)限校驗(yàn),校驗(yàn)不通過傳遞json字符串給前端,由前端進(jìn)行頁(yè)面跳轉(zhuǎn)
@WebFilter
@Slf4j
public class LoginFilter implements Filter {
    //創(chuàng)建uri放行路徑數(shù)組
    private String[] allowUriArr ={
           "/backend/**",
           "/front/**",
            "/employee/login"
    };

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //獲取請(qǐng)求路徑
        String requestURI = request.getRequestURI();
        log.info("攔截請(qǐng)求:{}",requestURI);
        //判斷uri是否屬于放行數(shù)組中的路徑
        Boolean result = isAllowUri(requestURI);
        if (result){
            filterChain.doFilter(request,response);
            return;
        }
        //從session拿到用戶信息,判斷是否為空
        HttpSession session = request.getSession();
        Object employeeId = session.getAttribute("employeeId");
        if (employeeId != null){
            filterChain.doFilter(request,response);
            return;
        }
        //如果為空,則沒登錄,返回給前端固定的R.error
        log.info("用戶未登錄");
        R notlogin = R.error("NOTLOGIN");
        String notloginJson = JSON.toJSONString(notlogin);
        response.getWriter().write(notloginJson);
        return;
    }

    /**
     * 判斷請(qǐng)求路徑是否在放行路徑數(shù)組中
     * @param requestURI
     * @return
     */
    private Boolean isAllowUri(String requestURI) {
        //遍歷allowUriArr數(shù)組,挨個(gè)比較是否與requestURI匹配
        AntPathMatcher apm =new AntPathMatcher();
        for (String allowUri : allowUriArr) {
            if (apm.match(allowUri,requestURI)){
                return true;
            }
        }
        return false;
    }

    @Override
    public void destroy() {

    }
}

全局異常處理器

在進(jìn)行用戶注冊(cè)的時(shí)候,若用戶名已被占用,我們需要捕捉異常
1.創(chuàng)建異臣楸海控制類,并用@RestControllerAdvice標(biāo)識(shí)
2.類中方法用@ExceptionHandler(SQLIntegrityConstraintViolationException.class)標(biāo)識(shí),代表我們要捕捉此異常.
3.我們將異常信息截取,判斷是否是用戶名重復(fù)異常,截取其中的重復(fù)用戶名
4.將重復(fù)用戶名作為msg傳遞給前端
5.若不是我們識(shí)別到的異常,就做統(tǒng)一返回.

@RestControllerAdvice
@Slf4j
public class ReggieException {

    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException e){
          log.info("異常信息:{}",e.getMessage());
          String msg = e.getMessage();
          if(msg.contains("Duplicate entry")){
              String[] s = msg.split(" ");
              return R.error("用戶名"+s[2]+"已被使用");
          }
          return R.error("未知錯(cuò)誤!!!");
    }
}

利用mybatisPlus進(jìn)行分頁(yè)+條件查詢

分頁(yè)查詢:
1.在mybatisPlusConfig中注冊(cè)分頁(yè)插件攔截器
2.以前端傳遞的page和pageSize作為參數(shù)創(chuàng)建page對(duì)象
條件查詢:
3.創(chuàng)建條件查詢對(duì)象LambdaQueryWrapper<T>
4.使用方法添加條件
5.將分頁(yè)對(duì)象與條件對(duì)象作為參數(shù)傳入service方法,結(jié)果返回一個(gè)page對(duì)象
6.將page對(duì)象作為data返回給前端

@GetMapping("/page")
    public R<Page> getMemberList(@RequestParam("page")String pageStr,
                                 @RequestParam("pageSize")String pageSizeStr,
                                 String name){
        log.info("前端傳來(lái)的頁(yè)面查詢信息:第{}頁(yè),{}條數(shù)據(jù),name是{}",pageStr,pageSizeStr,name);
        if (pageStr == null || pageStr == ""){
            pageStr="1";
        }
        if (pageSizeStr == null || pageSizeStr == ""){
            pageSizeStr="10";
        }
        int page = Integer.parseInt(pageStr);
        int pageSize = Integer.parseInt(pageSizeStr);
        //page對(duì)象
        Page<Employee> employeePage = new Page<>();
        employeePage.setPages(page);
        employeePage.setSize(pageSize);
        //條件查詢對(duì)象
        LambdaQueryWrapper<Employee> queryWrapper =new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.hasLength(name),Employee::getName,name);
        //針對(duì)page對(duì)象調(diào)用service查詢
        Page<Employee> pageResult = employeeService.page(employeePage,queryWrapper);
        return R.success(pageResult);

    }

前端對(duì)于Long id精度失真的解決

讓分頁(yè)查詢返回的json格式數(shù)據(jù)庫(kù)中, long類型的屬性, 不直接轉(zhuǎn)換為數(shù)字類型, 轉(zhuǎn)換為字符串類型就可以解決這個(gè)問題了
1.在啟動(dòng)類中


@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        Jackson2ObjectMapperBuilderCustomizer customizer = jacksonObjectMapperBuilder ->
                jacksonObjectMapperBuilder
                        .serializerByType(Long.class, ToStringSerializer.instance)
                        .serializerByType(Long.TYPE, ToStringSerializer.instance);
        return customizer;
    }
}

在SpringMvcConfig中

package com.itheima.reggie.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;

@Configuration
@Slf4j
public class SpringMvcConfig extends WebMvcConfigurationSupport {
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 擴(kuò)展mvc框架的消息轉(zhuǎn)換器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("擴(kuò)展消息轉(zhuǎn)換器...");
        //創(chuàng)建消息轉(zhuǎn)換器對(duì)象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //設(shè)置對(duì)象轉(zhuǎn)換器陕悬,底層使用Jackson將Java對(duì)象轉(zhuǎn)為json
        //messageConverter.setObjectMapper(new JacksonObjectMapper());
        messageConverter.setObjectMapper(objectMapper);
        //將上面的消息轉(zhuǎn)換器對(duì)象追加到mvc框架的轉(zhuǎn)換器集合中
        converters.add(0,messageConverter);
    }
}


Mybatis-Plus的公共字段填充功能

本項(xiàng)目中多數(shù)表存在公共字段:createTime/updateTime/createUser/updateUser
我們借助Mybatis-Plus的公共字段填充功能,在進(jìn)行sql查詢前,將在元數(shù)據(jù)對(duì)象處理器中配置好的屬性值添加到響應(yīng)字段
1.在公共字段上添加@TableField(fil = FieldFill.INSERT)或者@TableField(fil = FieldFill.INSERT_UPDATE)代表此公共字段需要被填充

@TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
}

2.創(chuàng)建元數(shù)據(jù)異常處理器實(shí)現(xiàn)MetaObjectHandler,并重寫insertFill和updateFill

@Component
@Slf4j
//元數(shù)據(jù)對(duì)象處理器  為@TbaleField(fill = "xxx")字段賦值
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        long threadId= Thread.currentThread().getId();
        log.info("當(dāng)前線程id:{}"+threadId);
        log.info("metaObject:"+metaObject);
        //從ThreadLocal中拿到員工id
        Long empId = BaseContext.getEmpId();
        metaObject.setValue("createTime",LocalDateTime.now());
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("createUser",new Long(empId));
        metaObject.setValue("updateUser",new Long(empId));
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        long threadId= Thread.currentThread().getId();
        log.info("當(dāng)前線程id:{}"+threadId);
        //從ThreadLocal中拿到員工id
        Long empId = BaseContext.getEmpId();
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",new Long(empId));

    }
}

信息修改人是通過session中數(shù)據(jù)獲取的,那么作為dao層的MyMetaObjectHandler在使用框架方法時(shí)無(wú)法獲取request對(duì)象或者session對(duì)象,這是怎么做?看下面這個(gè)解決方案...↓

使用ThreadLocal在一個(gè)線程中共享數(shù)據(jù)

通過打印我們發(fā)現(xiàn),瀏覽器的一次請(qǐng)求與響應(yīng)都存在于一個(gè)線程中,從filter的控制層到model層,都存在于一個(gè)線程中,那么我們就可以使用TheadLocal進(jìn)行線程數(shù)據(jù)共享

9d0daece70953be0a233c795efa737a.png

地址:https://www.processon.com/view/link/628da5171e08532319e9182a
1.創(chuàng)建BaseContext 存放 ThreadLocal對(duì)象
ThreadLocal私有化,不被其他線程使用,封裝成方法進(jìn)行使用
靜態(tài) 隨著類的加載而加載,需要被靜態(tài)方法調(diào)用
final ThreadLocal對(duì)象地址值不被修改,但對(duì)象屬性可以修改

public class BaseContext {
    //ThreadLocal私有化,不被其他線程使用,封裝成方法進(jìn)行使用
    //靜態(tài)   隨著類的加載而加載,需要被靜態(tài)方法調(diào)用
    //final ThreadLocal對(duì)象地址值不被修改,但對(duì)象屬性可以修改
    private static final ThreadLocal<Long> THREAD_LOCAL_EMP_ID =new ThreadLocal<>();

    //提供公共暴露方法,只能存放數(shù)據(jù),不能修改對(duì)象
    public static void setEmpId(Long id){
        THREAD_LOCAL_EMP_ID.set(id);
    }
    public static Long getEmpId(){
        return THREAD_LOCAL_EMP_ID.get();
    }
}

2.在filter中(控制層)將session數(shù)據(jù)存到本線程的ThreadLocal中

 HttpSession session = request.getSession();
        Object employeeId = session.getAttribute("employeeId");
        if (employeeId != null){
            long threadId= Thread.currentThread().getId();
            log.info("當(dāng)前線程id:"+threadId);
            //在線程中的ThreadLocal存放一個(gè)emp id的值;在MyMetaObjectHandler中讀取
            BaseContext.setEmpId((Long) employeeId);
            //放行
            filterChain.doFilter(request,response);

3.在元數(shù)據(jù)處理器(模型層)獲取當(dāng)前線程中的ThreadLocal

 //從ThreadLocal中拿到員工id
        Long empId = BaseContext.getEmpId();

So,ThreadLocal用于線程中存儲(chǔ)數(shù)據(jù)的對(duì)象,一個(gè)線程中有一個(gè)ThreadLocalMap,
ThreadLocalMap是以ThreadLocal對(duì)象為key,ThreadLocal的值為值的map集合,
所以一個(gè)線程可以有多個(gè)ThreadLocal對(duì)象存儲(chǔ)多份數(shù)據(jù)
ThreadLocal<Long> threadLocal =new ThreadLocal<>();
void threadLocal.set(obj);
Object threadLocal.get();

在業(yè)務(wù)邏輯中添加邏輯外鍵

由于外鍵等特性需要數(shù)據(jù)庫(kù)執(zhí)行額外的工作,而這些操作會(huì)占用數(shù)據(jù)庫(kù)的計(jì)算資源,所以我們可以將大部分的需求都遷移到無(wú)狀態(tài)的服務(wù)中完成以降低數(shù)據(jù)庫(kù)的工作負(fù)載.
當(dāng)我們想刪除分類時(shí),就要考慮是否有dish或者setmeal引用了分類的id作為字段值.
因?yàn)槲覀円贑ategoryService中去手動(dòng)定義刪除方法,刪除方法中添加邏輯外鍵,判斷當(dāng)前被刪除id是否被另外兩張表引用
如果被引用,那么我們拋出自定義用戶異常,全局異常處理器捕獲異常并返回給前端

刪除業(yè)務(wù)

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    @Autowired
    private DishService dishService;
    @Autowired
    private SetmealService setmealService;

    //根據(jù)分類id刪除,進(jìn)行邏輯外鍵判斷
    @Override
    public void deleteCategoryById(Long id) {
        //判斷此id是否被dish表引用
        LambdaQueryWrapper<Dish> dishWrapper = new LambdaQueryWrapper<>();
        dishWrapper.eq(Dish::getCategoryId,id);
        int count1 = dishService.count(dishWrapper);
        if (count1 > 0){
            throw new CustomException("該分類下包含菜品,無(wú)法刪除!!!");
        }
        //判斷此id是否被setmeal表引用
        LambdaQueryWrapper<Setmeal> setmealWrapper = new LambdaQueryWrapper<>();
        setmealWrapper.eq(Setmeal::getCategoryId,id);
        int count2 = setmealService.count(setmealWrapper);
        if (count2 > 0){
            throw new CustomException("該分類下包含菜品,無(wú)法刪除!!!");
        }
        //都沒被引用允許刪除
        removeById(id);
    }
}

自定義用戶異常

public class CustomException extends RuntimeException {
    public CustomException() {
    }

    public CustomException(String message) {
        super(message);
    }
}

攔截用戶異常

 //攔截自定義用戶異常
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler2(CustomException e) {
        String message = e.getMessage();
        return R.error(message);
    }

文件上傳 與 文件預(yù)覽

文件上傳就是將瀏覽器上傳文件保存到服務(wù)器固定位置
預(yù)覽就是從固定位置讀取文件再寫出
文件上傳的前端要求是

  • post提交
  • enctype=multippart/form-data
  • input中type=file
    本項(xiàng)目中使用element ui封裝好的文件上傳組件,后端接收到前端post請(qǐng)求及請(qǐng)求體中參數(shù)名為file的字節(jié)文件
  1. 創(chuàng)建公共的controller及方法進(jìn)行接收文件,后續(xù)都使用此控制器方法進(jìn)行文件上傳處理
  2. 編寫upload方法接收用戶的文件,并保存到固定位置
    2.1 我們?cè)趛ml中定義了文件存放路徑,屬性上使用@Value("${reggie.basepath}")進(jìn)行引用
    2.2 接收文件參數(shù)類型固定為MultipartFile,參數(shù)名與前端傳的參數(shù)名一致.這里是file
    2.3解析文件名,截取文件格式,例如.jpg
    2.4為了避免本地文件名重復(fù)導(dǎo)致文件覆蓋,使用uuid.random.toString作為文件名
    2.5使用file.transferTo(newFile)方法,將內(nèi)存中文件copy到目標(biāo)路徑
    2.6將新生成的文件名作為返回值回傳給前端
public class CommonController {
    @Value("${reggie.basepath}")
    private String basepath;

    @PostMapping("upload")
    public R<String> upload(MultipartFile file){
        String originalFilename = file.getOriginalFilename();
        log.info("上傳的文件名:"+originalFilename);
        //截取文件格式
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        String prefix = UUID.randomUUID().toString();

        //判斷文件夾是否存在 不存在創(chuàng)建
        File dir = new File(basepath);
        if (!dir.exists()){
            dir.mkdirs();
        }
        //目標(biāo)文件
        File goalFile = new File(basepath,prefix+suffix);
        try {
            file.transferTo(goalFile);

        } catch (IOException e) {
            e.printStackTrace();
            return  R.error("文件上傳失敗");
        }
        return R.success(prefix+suffix);
    }
}
  1. 前端接收到新的文件名后,申請(qǐng)圖片預(yù)覽
    3.1 接收前端要預(yù)覽的文件名
    3.2 讀取服務(wù)器本地文件
    3.3 寫到response的字節(jié)輸出流中
 @GetMapping("/download")
    public void download(String name, HttpServletResponse response){
        //下載文件路徑
        File file =new File(basepath,name);

        BufferedInputStream bis = null;
        ServletOutputStream os= null;
        try {
            bis = new BufferedInputStream(new FileInputStream(file));
            os = response.getOutputStream();
            response.setContentType("image/jpeg");

            int len;
            byte[]  buffer = new byte[8*1024];
            while((len = bis.read(buffer))!=-1){
                os.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

Dto類對(duì)象 接收前端的對(duì)象

拿新建菜品為例,前端傳來(lái)的json數(shù)據(jù)中包含了dish對(duì)象沒有的flavor屬性,為了不破壞ORM對(duì)應(yīng)關(guān)系,我們創(chuàng)建DishDto類繼承Dish類,來(lái)接收前端的數(shù)據(jù),其中就包含了dish類對(duì)象接收不了的flavors屬性


page<Dish>轉(zhuǎn)page<DishDto>

在進(jìn)行dish分頁(yè)查詢時(shí),發(fā)現(xiàn)前端需要這個(gè)表以外的其他數(shù)據(jù),顯然我們返回的page<Dish>不能滿足
1.在DishDto中新增前端需要的屬性:categoryName
2.通過BeanUtils的xxx方法將page<Dish>的屬性復(fù)制到page<DishDto>,但不復(fù)制records,因?yàn)閞ecords的泛型是Dish,Dish沒有我們需要的額外categoryName屬性,所以我們需要將records進(jìn)行加工
3.遍歷records數(shù)組的每一條dish數(shù)據(jù),將dish對(duì)象的屬性值再?gòu)?fù)制給新的dishDto對(duì)象,
新的dto對(duì)象再通過原有的category_id屬性去查詢category對(duì)象,再將category對(duì)象的categoryName賦值到dto對(duì)象的categoryName屬性中,這樣我們一個(gè)dto對(duì)象就完整了.
4.我們把每一個(gè)新的dto對(duì)象添加到集合中
5.這個(gè)新生成的集合就代替了原有的records,將新生成的集合添加到page<DishDto>的records屬性
6.我們返回page<DishDto>對(duì)象給前端

@GetMapping("/page")
    public R<Page> getAllDishes(Integer page,Integer pageSize,String name){
        //創(chuàng)建分頁(yè)對(duì)象
        Page<Dish> dishPage = new Page<>(page,pageSize);
        //創(chuàng)建條件查詢對(duì)象
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.like(StringUtils.hasLength(name),Dish::getName,name);
        //執(zhí)行查詢sql
        dishService.page(dishPage,queryWrapper);
        //此時(shí)的dishPage并沒有前端要的categoryName屬性 所以需要轉(zhuǎn)換成dishDtoPage
        Page<DishDto> dishDtoPage = new Page<>();
        //dishPage的屬性復(fù)制給dishDtoPage,records屬性除外,因?yàn)槲覀円獙?duì)records進(jìn)行補(bǔ)充
        BeanUtils.copyProperties(dishPage,dishDtoPage,"records");
        List<Dish> dishRecords = dishPage.getRecords();
        //創(chuàng)建一個(gè)新的dishDtoPage的Records
        List<DishDto> dishDtoRecords =new ArrayList<>();
        for (Dish dish : dishRecords) {
            DishDto dishDto = new DishDto();
            //將每一個(gè)dish對(duì)象的屬性 賦值給dishDto對(duì)象
            BeanUtils.copyProperties(dish,dishDto);
            //此時(shí) dishDto對(duì)象的categoryName屬性還是空,但是dish對(duì)象的caegory_id屬性是有的
            Category category = categoryService.getById(dish.getCategoryId());
            //將dishDto對(duì)象的categoryName屬性賦值
            dishDto.setCategoryName(category.getName());
            //將這個(gè)dishDto加入到dishDtoPage的Records中去
            dishDtoRecords.add(dishDto);
        }
        dishDtoPage.setRecords(dishDtoRecords);
        return R.success(dishDtoPage);


    }

LocalDateTime類型導(dǎo)致前端時(shí)間帶T

在這個(gè)時(shí)間屬性上加上注解 把T可以去掉


    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;

前端傳遞的時(shí)間字符串后端參數(shù)如何接收?

使用@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 注解標(biāo)識(shí)時(shí)間類型參數(shù)

@GetMapping("/page")
    // @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 轉(zhuǎn)換前端字符串類型
    public R<Page> page(Integer page, Integer pageSize, String number,@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")  Date beginTime,@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime){
        Page<OrdersDto> ordersDtoPage= ordersService.getAllOrders(page,pageSize,number,beginTime,endTime);
        return R.success(ordersDtoPage);
    }

AtomicInteger類的使用

AtomicInteger.addAndGet方法具有原子性,不會(huì)再累加過程中被其他線程操作數(shù)據(jù)

AtomicInteger amount = new AtomicInteger(0);
amount.addAndGet();
使用redis緩存實(shí)現(xiàn)驗(yàn)證碼功能

1.導(dǎo)入redis場(chǎng)景啟動(dòng)器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.創(chuàng)建redisTmplate對(duì)象

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        //默認(rèn)的Key序列化器為:JdkSerializationRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 值不要隨便字符串格式钧椰,否則存入的都是字符串格式的
        //redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

3.在yml中配置redis
spring:
redis:
host: 192.168.188.128
port: 6379
database: 0
4.在存儲(chǔ)驗(yàn)證碼的方法中,將驗(yàn)證碼存到redis中,以電話號(hào)為key

redisTemplate.opsForValue().set(phone,code+"",120, TimeUnit.SECONDS);

5.用戶登陸校驗(yàn)
接收前端的phone和code
根據(jù)phone從redis從get redisCode
比對(duì)code和redisCode是否一致
一致就刪除redis中redisCode,放行
不一致就報(bào)錯(cuò)


用redis完成根據(jù)categoryId查詢菜品緩存的功能

1.判斷是否存在緩存
2.存在緩存調(diào)用緩存,不存在就執(zhí)行sql
3.執(zhí)行sql后將結(jié)果存到redis中
4.更新數(shù)據(jù)的操作刪除對(duì)應(yīng)的redis中的緩存

@GetMapping("/list")
    //根據(jù)dish對(duì)象中的CategoryId屬性 查詢到對(duì)應(yīng)的dish list
    public R<List<DishDto>> getDishListByCatogoryId(Dish dish){
        //查看redis中是否有緩存,沒有的話再去執(zhí)行數(shù)據(jù)庫(kù)
        //以dish_CategoryId作為key 查詢結(jié)果作為value
        String dishKey = "dish" + dish.getCategoryId().toString();
        List<DishDto> redisList = (List<DishDto>) redisTemplate.opsForValue().get(dishKey);
        if (redisList != null){
            //沒走數(shù)據(jù)庫(kù) ,走了緩存查詢dishes
            return R.success(redisList);
        }
        //如果緩存中沒有,那就查詢數(shù)據(jù)庫(kù)
        LambdaQueryWrapper<Dish> queryWrapper =new LambdaQueryWrapper<>();
        queryWrapper.eq(Dish::getCategoryId,dish.getCategoryId())
                .eq(Dish::getStatus,1)
                .orderByAsc(Dish::getSort);
        List<Dish> list = dishService.list(queryWrapper);
        //為了迎合前端需求,將dishList轉(zhuǎn)換為dishDtoList
        List<DishDto> dtoList =new ArrayList<>();
        for (Dish dish1 : list) {
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(dish1,dishDto);
            //dto填充 flavors屬性
            LambdaQueryWrapper<DishFlavor> queryWrapper1 = new LambdaQueryWrapper<>();
            queryWrapper1.eq(DishFlavor::getDishId,dish1.getId());
            List<DishFlavor> flavors = dishFlavorService.list(queryWrapper1);
            dishDto.setFlavors(flavors);
            //dto填充categoryname屬性
            Category category = categoryService.getById(dish1.getCategoryId());
            dishDto.setCategoryName(category.getName());
            dtoList.add(dishDto);
        }
        //查詢數(shù)據(jù)庫(kù)后,將查詢結(jié)果存到redis內(nèi)存中
        redisTemplate.opsForValue().set(dishKey,dtoList);
        return R.success(dtoList);
    }
用redis+spring cache 完成categoryId查詢套餐緩存的功能

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.啟動(dòng)類開啟緩存注解
@EnableCaching
2.yml配置緩存設(shè)置

設(shè)置緩存數(shù)據(jù)的過期時(shí)間

Spring
cache:
redis:
time-to-live: 1800000
3.使用注解
@Cacheable : 有緩存走緩存,沒有就執(zhí)行方法
value:redis中的緩存組件名字
key:緩存組件的key
unless:什么條件下不去走緩存

@GetMapping("/list")
    @Cacheable(value ="setmealCahce",key="#setmeal.categoryId",unless = "#result == null")
    public R<List<Setmeal>> list(Setmeal setmeal){
        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);
    }

@CacheEvict
allEntries :刪除當(dāng)前緩存組的所有內(nèi)容

 @PutMapping
    @CacheEvict(value = "setmealCahce",allEntries = true)
    public R<String> updateSetmeal(@RequestBody SetmealDto setmealDto) {
        //更新setmeal兩步驟,先更新setmeal表,在刪除setmealdish表再新增
        setmealService.updateSetmeal(setmealDto);
        return R.success("更新成功!!!");
    }

@CachePost 將方法返回值添加到緩存

三.遇到的問題

SpringMvcConfig沒有寫@Configruation導(dǎo)致靜態(tài)資源映射失效


LambdaQueryWrapper<T>創(chuàng)建時(shí)沒有添加泛型,使用方法時(shí)無(wú)法使用lambda表達(dá)式


新建用戶未MD5加密密碼就保存到數(shù)據(jù)庫(kù)


分頁(yè)查詢未創(chuàng)建mybatisPlus分頁(yè)插件interceptor


前端get請(qǐng)求誤以為有請(qǐng)求體,實(shí)際是url拼接


在繼承了ServiceImpl的serviceImpl中去調(diào)用了mapper,其實(shí)只要用本身的方法執(zhí)行sql操作就行了


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末羹幸,一起剝皮案震驚了整個(gè)濱河市催束,隨后出現(xiàn)的幾起案子澎现,更是在濱河造成了極大的恐慌铅匹,老刑警劉巖押赊,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異包斑,居然都是意外死亡流礁,警方通過查閱死者的電腦和手機(jī)涕俗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)神帅,“玉大人再姑,你說我怎么就攤上這事≌矣” “怎么了元镀?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)萎坷。 經(jīng)常有香客問我凹联,道長(zhǎng),這世上最難降的妖魔是什么哆档? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任蔽挠,我火速辦了婚禮,結(jié)果婚禮上瓜浸,老公的妹妹穿的比我還像新娘澳淑。我一直安慰自己,他們只是感情好插佛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布杠巡。 她就那樣靜靜地躺著,像睡著了一般雇寇。 火紅的嫁衣襯著肌膚如雪氢拥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天锨侯,我揣著相機(jī)與錄音嫩海,去河邊找鬼。 笑死囚痴,一個(gè)胖子當(dāng)著我的面吹牛叁怪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播深滚,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼奕谭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了痴荐?” 一聲冷哼從身側(cè)響起血柳,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹬昌,沒想到半個(gè)月后混驰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年栖榨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昆汹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡婴栽,死狀恐怖满粗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情愚争,我是刑警寧澤映皆,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站轰枝,受9級(jí)特大地震影響捅彻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鞍陨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一步淹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诚撵,春花似錦缭裆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至筛武,卻和暖如春缝其,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背徘六。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工氏淑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人硕噩。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像缭贡,于是被迫代替她去往敵國(guó)和親炉擅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容