接口
一.每日功能更新
22-05-22 day01
- 創(chuàng)建項(xiàng)目,完成后端員工登陸與登出功能
- 創(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è)面?
- 創(chuàng)建數(shù)組 將放行路徑存于數(shù)組
- 在doFilter方法中獲取通過request的getRequestUri的方法獲取請(qǐng)求路徑
將uri與數(shù)組中可以放行的路徑進(jìn)行遍歷
遍歷過程中使用AntPathMatcher類的match方法進(jìn)行路徑匹配確認(rèn) - 被攔截頁(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ù)共享
地址: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é)文件
- 創(chuàng)建公共的controller及方法進(jìn)行接收文件,后續(xù)都使用此控制器方法進(jìn)行文件上傳處理
- 編寫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);
}
}
- 前端接收到新的文件名后,申請(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操作就行了