本内容来自码云开源内容,同时部分优化,有需要的可以去看看原本内容
README.md · Ndelible/瑞吉外卖 - 码云 - 开源中国 (gitee.com)
项目介绍
本项目(瑞吉外卖)是一款专门为餐饮企业定制的软件产品,其中包括系统管理后台和移动端应用两部分。系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等操作。
环境搭建
使用工具
技术选型
功能架构
适用人群
- 后台系统管理员:登录后台管理系统,拥有后台系统中的所有操作权限。
- 后台系统普通员工:登录后台管理系统,对菜品、套餐、订单等进行管理。
- C端用户:登录移动端应用,可以浏览菜品、添加购物车、设置地址、在线下单等。
项目使用
�使用说明
- 本项目自带前端代码,无需前端代码的编写。
- 本项目是通过虚拟机创建服务器进行项目部署,实现前后端分离管理。
- 使用前需要安装虚拟机及Linux系统搭建服务器。
- 项目运行前,需要更改相对应的端口号及主机地址,也可通过配置文件修改。
⚠ 注意事项
项目开发
开发环境搭建
-
-
完整pom.xml文件如下:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>reggie_take_out</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
</project>
server:
port: 8080
spring:
application:
name: reggie_take_out
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
@Slf4j
@SpringBootApplication
public class ReggieApplication {
public static void main(String[] args){
SpringApplication.run(ReggieApplication.class,args);
log.info("项目启动成功!");
}
}
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射,将静态资源映射到/backend和/front下,可以直接访问
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//log.info("开始进行静态资源映射...");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
* @author PangPAi
* @date 2024/7/12
* 自定义异常类
* 常量类
*/
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
public static class AddressBook {
public static final String ADDRESS_BOOK_ERROR = "没有找到该对象";
}
public static class Category {
public static final String SUCCESS_CATEGORY_MESSAGE = "新增分类成功";
public static final String DELETE_CATEGORY_MESSAGE = "分类信息删除成功";
public static final String DELETE_CATEGORY_ID = "删除分类,id为:{}";
public static final String UPDATE_CATEGORY_MESSAGE = "修改分类信息成功";
public static final String UPDATE_CATEGORY = "修改分类信息:{}";
public static final String FIND_CATEGORY = "查询所有分类信息";
public static final String FIND_CATEGORY_LIST = "查询结果:{}";
}
public static class Dish {
public static final String SUCCESS_DISH_MESSAGE = "新增菜品成功";
public static final String UPDATE_DISH_MESSAGE = "修改菜品成功";
public static final String DELETE_DISH_MESSAGE = "删除成功";
public static final String status_success = "售卖状态修改成功";
public static final String status_error = "设置失败!状态异常";
}
public static class Employee {
public static final String LOGIN_ERROR = "登录失败";
public static final String LOGIN_ERROR_USER_STATUS = "账号已禁用";
public static final String LOGOUT_SUCCESS = "退出成功";
public static final String SAVE_EMPLOYEE = "修改密码成功";
public static final String SAVE_SUCCESS = "新增员工成功";
public static final String UPDATE_SUCCESS = "员工信息修改成功";
public static final String UPDATE_Thread_ID = "线程id为:{}";
public static final String getById_ID = "根据id查询员工信息...";
public static final String getById_ERROR = "没有查询到对应员工信息";
}
public static class Order {
public static final String SUBMIT_ORDER = "订单数据:{}";
public static final String SUBMIT_ORDER_SUCCESS = "下单成功";
public static final String UPDATE_ORDER_STATUS = "派送成功";
public static final String LOGOUT_SUCCESS = "派送失败";
}
public static class Setmeal {
public static final String SAVE_SetMal_DTO = "套餐信息:{}";
public static final String SAVE_SUCCESS = "新增套餐成功";
public static final String DELETE_SUCCESS = "套餐数据删除成功";
public static final String startOrStop_success = "起售停售";
}
public static class ShoppingCart {
public static final String ADD_shoppingCart = "购物车数据:{}";
public static final String LOOK_shoppingCart = "查看购物车...";
public static final String CLEAR_shoppingCart = "清空购物车成功";
}
public static class Impl{
public static final String Category_remove_dishService = "当前分类下关联了菜品,不能删除";
public static final String Category_remove_setmealService = "当前分类下关联了套餐,不能删除";
public static final String Dish_deleteByIds_status = "套餐正在售卖中,不能删除";
public static final String Order_submit_shoppingCarts = "购物车为空,不能下单";
public static final String Order_submit_addressBook = "用户地址信息有误,不能下单";
public static final String Setmeal_removeWithDish_status = "套餐正在售卖中,不能删除";
}
public static final String Application = "项目启动成功!"; //这里是启动项的日志输出
}
这里还要注意一点,自定义异常类需要手动引入,比如才EmployeeController类中,我们可以使用
import static com.pangpai.ruiji.common.CustomException.Employee.*;
管理端
后台登录功能开发
- @ApiModelProperty("编码")
private Integer code; //编码:1成功,0和其它数字为失败
@ApiModelProperty("错误信息")
private String msg; //错误信息
@ApiModelProperty("数据")
private T data; //数据
@ApiModelProperty("动态数据")
//private Map map = new HashMap(); //动态数据
#这里需要注意的是,我个人的问题,就是在部署的时候出现了问题,最后把这里改了就好了哦。具体原理可以去查查
private Map<String, String> map = new HashMap<>();//动态数据
后台退出功能开发
@Override
public R<String> logout(HttpSession session){
//1、清除session中用户信息
session.invalidate();
//2.返回登录成功信息
return R.success("登出成功了");
}
@Override
public R<String> logout(HttpSession session){
//1、清除session中用户信息
session.removeAttribute("employee");
//2.返回登录成功信息
return R.success("登出成功了");
}
完善登录功能
新增员工
- /**
* 新增员工
* @param employee
* @return
*/
@PostMapping
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
log.info(SAVE_EMPLOYEE,employee.toString());
//设置初始密码123456,需要进行md5加密处理
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获得当前登录用户的id
//Long empId = (Long) request.getSession().getAttribute("employee");
//employee.setCreateUser(empId);
//employee.setUpdateUser(empId);
employeeService.save(employee);
return R.success(SAVE_SUCCESS);
* 全局异常处理
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if(ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg = split[2] + "已存在";
return R.error(msg);
}
return R.error("未知错误");
}
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
log.error(ex.getMessage());
return R.error(ex.getMessage());
}
}
分页查询
- /**
* 员工信息分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
//构造分页构造器
Page pageInfo = new Page(page,pageSize);
//构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
//添加过滤条件
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
//执行查询
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
启用/禁用员工账号
- /**
* 根据id修改员工状态
* @param employee
* @return
*/
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
log.info(employee.toString());
long id = Thread.currentThread().getId();
log.info(UPDATE_Thread_ID,id);
//Long empId = (Long)request.getSession().getAttribute("employee");
//employee.setUpdateTime(LocalDateTime.now());
//employee.setUpdateUser(empId);
employeeService.updateById(employee);
return R.success(UPDATE_SUCCESS);
}
- js对long型数据进行处理时丢失精度,导致提交的id和数据库中的id不一致。
- long型数据统一转为String字符串。
- /**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}
查询员工信息
- /**
* 根据id查询员工信息
* @param id
* @return
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
log.info(getById_ID);
Employee employee = employeeService.getById(id);
if(employee != null){
return R.success(employee);
}
return R.error(getById_ERROR);
}
公共字段自动填充
实现步骤:
1. 在实体类的属性上加入@TableField注解,指定自动填充的策略。
//mybaits-plus自动填充
@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. 编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。
-
* @author PangPAi
* @date 2024/7/12
* 基于ThreadLocal封装工具类,用于存储和获取上下文信息,当前用户登录ID等。
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 设置值
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
-
-
- /**
* 更新操作,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]...");
log.info(metaObject.toString());
long id = Thread.currentThread().getId();
log.info("线程id为:{}",id);
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
- /**
新增分类或菜品
- @PostMapping
public R<String> save(@RequestBody Category category) {
log.info("category:{}", category);
categoryService.save(category);
return R.success(SUCCESS_CATEGORY_MESSAGE);
}
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper,Category> implements CategoryService{
/**
* 根据id删除分类,删除之前需要进行判断
* @param id
*/
@Override
public void remove(Long id) {
}
}
删除分类或菜品
- @DeleteMapping
public R<String> delete(Long id) {
log.info(DELETE_CATEGORY_ID, id);
categoryService.removeById(id);
//categoryService.remove(id);
return R.success(SUCCESS_CATEGORY_MESSAGE);
}
- interface CategoryService extends IService<Category> {
void remove(Long id);
}
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper,Category> implements CategoryService{
private DishService dishService;
private SetmealService setmealService;
* 根据id删除分类,删除之前需要进行判断
* @param id
*/
@Override
public void remove(Long id) {
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据分类id进行查询
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count1 = dishService.count(dishLambdaQueryWrapper);
if(count1 > 0){
//已经关联菜品,抛出一个业务异常
throw new CustomException(Category_remove_dishService);
}
//查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据分类id进行查询
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count();
if(count2 > 0){
//已经关联套餐,抛出一个业务异常
throw new CustomException(Category_remove_setmealService);
}
//正常删除分类
super.removeById(id);
}
}
修改分类或菜品
/**
* 根据id修改分类信息
* @param category
* @return
*/
@PutMapping
public R<String> update(@RequestBody Category category) {
log.info(UPDATE_CATEGORY, category);
categoryService.updateById(category);
return R.success(SUCCESS_CATEGORY_MESSAGE);
}
文件上传下载
- 代码如下:
@Slf4j
@RequestMapping("/common")
ublic class CommonController {
@Value("${reggie.path}")
private String basePath;
/**
* 文件上传
* @param file
* @return
*/
@ApiOperation(value = "文件上传", notes = "上传文件",httpMethod = "POST")
@PostMapping("/upload")
public R<String> upload(MultipartFile file){
//file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
log.info(file.toString());
//原始文件名
String originalFilename = file.getOriginalFilename();//abc.jpg
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;//dfsdfdfd.jpg
//创建一个目录对象
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);
}
/**
* 文件下载
* @param name
* @param response
*/
@ApiOperation(value = "文件下载", notes = "下载文件",httpMethod = "GET")
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
try {
//输入流,通过输入流读取文件内容
FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
//输出流,通过输出流将文件写回浏览器
ServletOutputStream outputStream = response.getOutputStream();
//设置响应头信息,告诉浏览器这是个图片
response.setContentType("image/jpeg");
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes,0,len);
outputStream.flush();
}
//关闭资源
outputStream.close();
fileInputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
套餐和菜品分类
套餐分类
- /**
* 根据条件查询数据
* @param category
* @return
*/
@ApiOperation(value = "根据条件查询分类数据",notes = "根据条件查询分类数据",httpMethod = "GET")
@GetMapping("/list")
public R<List<Category>> list(Category category) {
log.info(FIND_CATEGORY);
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); //条件构造器
queryWrapper.eq(category.getType() != null, Category::getType, category.getType()); //添加查询条件
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); //排序条件
List<Category> list = categoryService.list(queryWrapper);
log.info(FIND_CATEGORY_LIST, list);
return R.success(list);
}
菜品分类
保存数据
- /**
* 新增套餐
* @param setmealDto
* @return
*/
@ApiOperation(value = "新增套餐",notes = "新增套餐",httpMethod = "POST")
@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto){
log.info(SAVE_SetMal_DTO,setmealDto);
setmealService.saveWithDish(setmealDto);
return R.success(SAVE_SUCCESS);
}
- /**
* 新增套餐,同时需要保存套餐和菜品的关联关系
* @param setmealDto
*/
public void saveWithDish(SetmealDto setmealDto);
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper,Setmeal> implements SetmealService {
@Autowired
private SetmealDishService setmealDishService;
/**
* 新增套餐,同时需要保存套餐和菜品的关联关系
* @param setmealDto
*/
@Transactional
public void saveWithDish(SetmealDto setmealDto) {
//保存套餐的基本信息,操作setmeal,执行insert操作
this.save(setmealDto);
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
setmealDishes.stream().map((item) -> {
item.setSetmealId(setmealDto.getId());
return item;
}).collect(Collectors.toList());
//保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
setmealDishService.saveBatch(setmealDishes);
}
批量删除
- /**
* 删除套餐
* @param ids
* @return
*/
@ApiOperation(value = "删除套餐",notes = "删除套餐",httpMethod = "DELETE")
@DeleteMapping
public R<String> delete(@RequestParam List<Long> ids){
log.info("ids:{}",ids);
setmealService.removeWithDish(ids);
return R.success(DELETE_SUCCESS);
}
- /**
* 删除套餐,同时需要删除套餐和菜品的关联数据
* @param ids
*/
public void removeWithDish(List<Long> ids);
- /**
* 删除套餐,同时需要删除套餐和菜品的关联数据
* @param ids
*/
@Transactional
public void removeWithDish(List<Long> ids) {
//select count(*) from setmeal where id in (1,2,3) and status = 1
//查询套餐状态,确定是否可用删除
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
queryWrapper.in(Setmeal::getId,ids);
queryWrapper.eq(Setmeal::getStatus,1);
int count = this.count(queryWrapper);
if(count > 0){
//如果不能删除,抛出一个业务异常
throw new CustomException(Setmeal_removeWithDish_status);
}
//如果可以删除,先删除套餐表中的数据---setmeal
this.removeByIds(ids);
//delete from setmeal_dish where setmeal_id in (1,2,3)
LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
//删除关系表中的数据----setmeal_dish
setmealDishService.remove(lambdaQueryWrapper);
}
批量启售/停售
- /**
* 修改菜品状态
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
//这个参数这里一定记得加注解才能获取到参数,否则这里非常容易出问题
public R<String> status(@PathVariable("status") Integer status, @RequestParam List<Long> ids) {
//log.info("status:{}",status);
//log.info("ids:{}",ids);
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper();
queryWrapper.in(ids != null, Dish::getId, ids);
//根据数据进行批量查询
List<Dish> list = dishService.list(queryWrapper);
if (list != null) {
for (Dish dish : list) {
if (dish != null) {
dish.setStatus(status);
dishService.updateById(dish);
}
}
return R.success(status_success);
}
return R.error(status_error);
}
移动端
邮箱短信发送
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<!-- Spring Boot的核心启动器,提供基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 用于测试Spring Boot应用的依赖项,如JUnit、Spring Test等 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 阿里云短信服务SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
<!-- 阿里云短信服务SDK,用于发送短信 -->
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.1.0</version>
<!-- 阿里云短信服务API -->
</dependency>
<!-- 邮件发送功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- 构建RESTful Web服务所需的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>compile</scope>
</dependency>
<!-- MyBatis增强工具,简化CRUD操作 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- Java工具类库,自动处理boilerplate代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<!-- JSON处理库,用于Java对象与JSON格式转换 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<!-- Apache常用工具类库 -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- MySQL数据库JDBC驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>8.0.33</scope>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
<!-- Redis的Java客户端库 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
<!-- Spring Data Redis集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Cache集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Spring Security集成-->
<!-- <dependency>-->
<!-- <groupId>org.apache.shardingsphere</groupId>-->
<!-- <artifactId>sharding-jdbc-spring-boot-starter</artifactId>-->
<!-- <version>4.0.0-RC1</version>-->
<!-- </dependency>-->
<!-- <!– 日志记录功能 –>-->
<!-- <dependency>-->
<!-- <groupId>io.springfox</groupId>-->
<!-- <artifactId>springfox-boot-starter</artifactId>-->
<!-- <version>3.0.0</version>-->
<!-- </dependency>-->
<!-- Swaggger2 API文档生成工具 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- <!– Knife4j文档生成工具 –>-->
<!-- <dependency>-->
<!-- <groupId>com.github.xiaoymin</groupId>-->
<!-- <artifactId>knife4j-spring-boot-starter</artifactId>-->
<!-- <version>3.0.2</version>-->
<!-- </dependency>-->
<!-- Google核心库,提供额外的集合类型和工具 -->
<!-- 避免版本冲突 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
<!-- 避免版本冲突 -->
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.83</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.pangpai.ruiji.RuijiApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
手机验证码登录
* 检查用户是否已经完成登录
*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
//路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1、获取本次请求的URI
String requestURI = request.getRequestURI();// /backend/index.html
log.info("拦截到请求:{}",requestURI);
//定义不需要处理的请求路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**",
"/user/sendMsg",
"/user/login",
"/swagger-ui.html",
"/doc.html",
"/webjars/**",
"/swagger-resources",
"/v2/**",
"/test/exception"
};
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
if(check){
log.info("本次请求{}不需要处理",requestURI);
filterChain.doFilter(request,response);
return;
}
//4-1、判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("employee") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request,response);
return;
}
//4-2、判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("user") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));
Long userId = (Long) request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);
filterChain.doFilter(request,response);
return;
}
log.info("用户未登录");
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
}
/**
* 路径匹配,检查本次请求是否需要放行
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
}
@Slf4j
@RequestMapping("/user")
@Api(tags = "用户模块", description = "用户模块相关接口,短信邮箱服务")
public class UserController {
@Resource
UserService userService;
@Resource
JavaMailSender javaMailSender;
@Autowired
private RedisTemplate redisTemplate;
@Value("${spring.mail.username}")
String from;
@ApiOperation(value = "发送验证码", notes = "发送验证码",httpMethod = "POST")
@PostMapping("/sendMsg")
private R<String> sendMsg(@RequestBody User user, HttpSession session) {
//这里用qq邮箱去发送验证码
//获取到前端提交过来的qq号
String phone = user.getPhone();
//这里工具类判是否为空
if (StringUtils.isNotEmpty(phone)) {
//这里用到生成验证码的工具类
String code = ValidateCodeUtils.generateValidateCode(4).toString();//生成四位的验证码
log.info("code={}", code);
SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); // 构建一个邮件的对象
simpleMailMessage.setFrom(from); //设置邮件发件者
simpleMailMessage.setTo(phone); //设置邮件接受者
simpleMailMessage.setSubject("登录验证码"); //设置有纪念的主题
String text = "[PangPAi] 您的瑞吉外卖登录验证码为" + code + ",请勿泄露"; //设置邮件的征文
simpleMailMessage.setText(text);
//将生成的验证码保存到Session
//将我们生成的手机号和验证码放到session里面,我们后面用户填入验证码之后,我们验证的时候就从这里去取然后进行比对
//这里我们需要一个异常捕获
//ession.setAttribute(phone, code);
redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);//将验证码保存到redis中,设置过期时间为5分钟
try {
javaMailSender.send(simpleMailMessage);//发送邮件
return R.success("验证码短信发送成功");
} catch (MailException e) {
e.printStackTrace();
}
}
return R.error("验证码发送失败");
}
//移动应用登录端
@ApiOperation(value = "移动端登录", notes = "移动端登录",httpMethod = "POST")
@PostMapping("/login")
//这里使用map来接收前端传过来的值
private R<User> login(@RequestBody Map map, HttpSession session) {
log.info(map.toString());
//使用map来接收参数,接收键值参数、
//编写处理逻辑
//获取到手机号
//获取到验证码
//从Session中获取到保存的验证码
//将session中获取到的验证码和前端提交过来的验证码进行比较,这样就可以实现一个验证的方式
//比对页面提交的验证码和session中
//判断当前的手机号在数据库查询是否有记录,如果没有记录,说明是一个新的用户,然后自动将这个手机号进行注册
String phone = map.get("phone").toString();
String code = map.get("code").toString();
//获取session中phone字段对应的验证码
//Object codeInSession = session.getAttribute(phone);
//从redis中获取验证码
Object codeInSession = redisTemplate.opsForValue().get(phone);
//下面进行比对
if (codeInSession != null && codeInSession.equals(code)) {
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
//在表中根据号码来查询是否存在该邮箱用户
userLambdaQueryWrapper.eq(User::getPhone, phone);
User user = userService.getOne(userLambdaQueryWrapper);
if (user == null) {
//判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
user = new User();
user.setPhone(phone);
user.setStatus(1);
userService.save(user);
}
//这里我们将user存储进去,后面各项操作,我们会用,其中拦截器那边会判断用户是否登录,所以我们将这个存储进去,
session.setAttribute("user", user.getId());
//如果用户登录成功,删除redis中缓存的验证码
redisTemplate.delete(phone);
return R.success(user);
}
return R.error("验证失败");
}
/**
* 退出登录
*
* @param session
* @return
*/
@ApiOperation(value = "退出登录", notes = "退出登录",httpMethod = "POST")
@PostMapping("/loginout")
public R<String> loginout(HttpSession session) {
//清除session中的id。
session.removeAttribute("user");
return R.success("退出登录成功");
}
}
导入用户地址簿
菜品展示
- /**
* 根据条件查询对应的菜品数据
* @param dish
* @return
*/
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish){
List<DishDto> dishDtoList = null;
//动态构造key值
String key = "dish_" + dish.getCategoryId() +"_" + dish.getStatus();//缓存的key值
//从redis中获取缓存数据
dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
if(dishDtoList != null){
//如果存在,直接返回,无需查询
return R.success(dishDtoList);
}
//构造查询条件
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());
//添加条件,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus,1);
//添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
List<Dish> list = dishService.list(queryWrapper);
dishDtoList = list.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto);
Long categoryId = item.getCategoryId();//分类id
//根据id查询分类对象
Category category = categoryService.getById(categoryId);
if(category != null){
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
//当前菜品的id
Long dishId = item.getId();
LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId); //SQL:select * from dish_flavor where dish_id = ?
List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
dishDto.setFlavors(dishFlavorList);
return dishDto;
}).collect(Collectors.toList());
//如果不存在,查询数据库,并将数据存入redis缓存
redisTemplate.opsForValue().set(key, dishDtoList, 60, TimeUnit.MINUTES);
return R.success(dishDtoList);
}
* 根据条件查询套餐数据
* @param setmeal
* @return
*/
@ApiOperation(value = "根据条件查询套餐数据",notes = "根据条件查询套餐数据",httpMethod = "GET")
@GetMapping("/list")
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);
}
购物车
- /**
* 添加购物车
*
* @param shoppingCart
* @return
*/
@ApiOperation(value = "添加购物车",notes = "添加购物车",httpMethod = "POST")
@PostMapping("/add")
public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart) {
log.info(ADD_shoppingCart, shoppingCart);
//设置用户id,指定当前是哪个用户的购物车数据
Long currentId = BaseContext.getCurrentId();
shoppingCart.setUserId(currentId);
Long dishId = shoppingCart.getDishId();
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId, currentId);
if (dishId != null) {
//添加到购物车的是菜品
queryWrapper.eq(ShoppingCart::getDishId, dishId);
} else {
//添加到购物车的是套餐
queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId());
}
//查询当前菜品或者套餐是否在购物车中
//SQL:select * from shopping_cart where user_id = ? and dish_id/setmeal_id = ?
ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
if (cartServiceOne != null) {
//如果已经存在,就在原来数量基础上加一
Integer number = cartServiceOne.getNumber();
cartServiceOne.setNumber(number + 1);
shoppingCartService.updateById(cartServiceOne);
} else {
//如果不存在,则添加到购物车,数量默认就是一
shoppingCart.setNumber(1);
shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCartService.save(shoppingCart);
cartServiceOne = shoppingCart;
}
return R.success(cartServiceOne);
}
- /**
* 查看购物车
* @return
*/
@ApiOperation(value = "查看购物车",notes = "查看购物车",httpMethod = "GET")
@GetMapping("/list")
public R<List<ShoppingCart>> list() {
log.info(LOOK_shoppingCart);
// 获取当前用户的ID
Long currentId = BaseContext.getCurrentId();
// 创建查询条件,查询当前用户的购物车记录
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());//指定当前用户的ID
queryWrapper.orderByAsc(ShoppingCart::getCreateTime);//按照创建时间排序
// 获取购物车列表
List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
// 遍历列表,检查并删除数量小于1的记录
for (ShoppingCart cartItem : list) {
if (cartItem.getNumber() <= 0) {
// 如果数量小于或等于0,则删除该记录
shoppingCartService.removeById(cartItem.getId());
}
}
// 重新获取更新后的购物车列表
list = shoppingCartService.list(queryWrapper);
return R.success(list);
}
- /**
* 清空购物车
*
* @return
*/
@ApiOperation(value = "清空购物车",notes = "清空购物车",httpMethod = "DELETE")
@DeleteMapping("/clean")
public R<String> clean() {
//SQL:delete from shopping_cart where user_id = ?
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
shoppingCartService.remove(queryWrapper);
return R.success(CLEAR_shoppingCart);
}
用户下单
- @ApiOperation(value = "用户下单", notes = "用户下单接口", httpMethod = "POST")
@PostMapping("/submit")
public R<String> submit(@RequestBody Orders orders) {
log.info(SUBMIT_ORDER, orders);
orderService.submit(orders);
return R.success(SUBMIT_ORDER_SUCCESS);
}
在OrderServiceImpl中实现submit方法
@Transactional
public void submit(Orders orders) {
//获得当前用户id
Long userId = BaseContext.getCurrentId();
//查询当前用户的购物车数据
LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ShoppingCart::getUserId,userId);
List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);
if(shoppingCarts == null || shoppingCarts.size() == 0){
throw new CustomException(Order_submit_shoppingCarts);
}
//查询用户数据
User user = userService.getById(userId);
//查询地址数据
Long addressBookId = orders.getAddressBookId();
AddressBook addressBook = addressBookService.getById(addressBookId);
if(addressBook == null){
throw new CustomException(Order_submit_addressBook);
}
long orderId = IdWorker.getId();//订单号
AtomicInteger amount = new AtomicInteger(0);
List<OrderDetail> orderDetails = shoppingCarts.stream().map((item) -> {
OrderDetail orderDetail = new OrderDetail();
orderDetail.setOrderId(orderId);
orderDetail.setNumber(item.getNumber());
orderDetail.setDishFlavor(item.getDishFlavor());
orderDetail.setDishId(item.getDishId());
orderDetail.setSetmealId(item.getSetmealId());
orderDetail.setName(item.getName());
orderDetail.setImage(item.getImage());
orderDetail.setAmount(item.getAmount());
amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());
return orderDetail;
}).collect(Collectors.toList());
orders.setId(orderId);
orders.setOrderTime(LocalDateTime.now());
orders.setCheckoutTime(LocalDateTime.now());
orders.setStatus(2);
orders.setAmount(new BigDecimal(amount.get()));//总金额
orders.setUserId(userId);
orders.setNumber(String.valueOf(orderId));
orders.setUserName(user.getName());
orders.setConsignee(addressBook.getConsignee());
orders.setPhone(addressBook.getPhone());
orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())
+ (addressBook.getCityName() == null ? "" : addressBook.getCityName())
+ (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())
+ (addressBook.getDetail() == null ? "" : addressBook.getDetail()));
//向订单表插入数据,一条数据
this.save(orders);
//向订单明细表插入数据,多条数据
orderDetailService.saveBatch(orderDetails);
//清空购物车数据
shoppingCartService.remove(wrapper);
}
项目优化
缓存优化
环境搭建
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
port: 8080
spring:
application:
# 应用的名称
name: reggie_take_out
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root # 请填写你的数据库的密码
redis:
# 使用本地 redis 服务就用本地的 Ip 地址;使用Linux 上的 edis 服务就用 Linux 上的 Ip 地址
host: 172.168.2.94 # 请填写你自己的 Ip 地址
port: 6379
password: 123456 # 填写你自己设置的 redis 的密码;没设置密码请注释该行
database: 0
cache:
redis:
time-to-live: 1800000 # 设置缓存数据的过期时间
mybatis-plus:
configuration:
# 在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
reggie:
path: D:\IDEA\ptg\ # 请填写你自己所存储的图片的位置
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
//默认的Key序列化器为:JdkSerializationRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer()); // key序列化
//redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // value序列化
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
缓存短信验证码
- @Autowired
private RedisTemplate redisTemplate; - redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);//将验证码保存到redis中,设置过期时间为5分钟
- session.setAttribute("user", user.getId());
//如果用户登录成功,删除redis中缓存的验证码
redisTemplate.delete(phone);
return R.success(user);
缓存菜品数据
/**
* 根据条件查询对应的菜品数据
* @param dish
* @return
*/
@ApiOperation(value = "根据条件查询对应的菜品数据", notes = "根据条件查询对应的菜品数据",httpMethod = "GET")
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish){
List<DishDto> dishDtoList = null;
//动态构造key值
String key = "dish_" + dish.getCategoryId() +"_" + dish.getStatus();//缓存的key值
//从redis中获取缓存数据
dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
if(dishDtoList != null){
//如果存在,直接返回,无需查询
return R.success(dishDtoList);
}
//构造查询条件
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());
//添加条件,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus,1);
//添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
List<Dish> list = dishService.list(queryWrapper);
dishDtoList = list.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto);
Long categoryId = item.getCategoryId();//分类id
//根据id查询分类对象
Category category = categoryService.getById(categoryId);
if(category != null){
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
//当前菜品的id
Long dishId = item.getId();
LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId); //SQL:select * from dish_flavor where dish_id = ?
List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
dishDto.setFlavors(dishFlavorList);
return dishDto;
}).collect(Collectors.toList());
//如果不存在,查询数据库,并将数据存入redis缓存
redisTemplate.opsForValue().set(key, dishDtoList, 60, TimeUnit.MINUTES);
return R.success(dishDtoList);
}
/**
* 新增菜品
* @param dishDto
* @return
*/
@ApiOperation(value = "新增菜品", notes = "新增菜品",httpMethod = "POST")
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.saveWithFlavor(dishDto);
return R.success(SUCCESS_DISH_MESSAGE);
}
/**
* 修改菜品
* @param dishDto
* @return
*/
@ApiOperation(value = "修改菜品", notes = "修改菜品",httpMethod = "PUT")
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.updateWithFlavor(dishDto);
return R.success(UPDATE_DISH_MESSAGE);
}
SpringCache
Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,大大简化我们在业务中操作缓存的代码。
Spring Cache只是提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓存技术。CacheManager是Spring提供的各种缓存技术抽象接口。
@Slf4j
@SpringBootApplication
@ServletComponentScan
@EnableTransactionManagement
@EnableSwagger2//开启swagger2
public class RuijiApplication {
public static void main(String[] args) {
SpringApplication.run(RuijiApplication.class, args);
log.info(Application);
}
}
1.pom.xml
<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>
2.application.yml
spring:
redis:
host: 192.168.200.200
port: 6379
password: root@123456
database: 0
cache:
redis:
time-to-live: 1800000 #设置缓存过期时间,可选
缓存套餐数据
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
spring:
cache:
redis:
time-to-live: 1800000 #设置缓存数据的过期时间
@Slf4j
@SpringBootApplication
@ServletComponentScan
@EnableTransactionManagement
@EnableSwagger2//开启swagger2
public class RuijiApplication {
public static void main(String[] args) {
SpringApplication.run(RuijiApplication.class, args);
log.info(Application);
}
}
/**
* 通用返回结果,服务端响应的数据最终都会封装成此对象
* @param <T>
*/
@Data
@ApiModel("返回结果")
public class R<T> implements Serializable{
@ApiModelProperty("编码")
private Integer code; //编码:1成功,0和其它数字为失败
@ApiModelProperty("错误信息")
private String msg; //错误信息
@ApiModelProperty("数据")
private T data; //数据
@ApiModelProperty("动态数据")
// private Map map = new HashMap(); //动态数据
private Map<String, String> map = new HashMap<>();//动态数据
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, (String) value);
// this.map.put(key, value);
return this;
}
}
清理套餐数据
这里忘记了,不知道怎么写,可以去温习一下内容
读写分离
Mysql主从复制
这个我单独写了一份:mysql主从复制-CSDN博客
项目实现读写分离
- reggie,运行SQL
- Sharding-JDBC实现读写分离的步骤:
- <dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://180.76.54.3:3306/reggie?characterEncoding=utf-8
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.81.128:3306/reggie?characterEncoding=utf-8
username: root
password: root
masterslave:
# 读写分离配置
load-balance-algorithm-type: round_robin #轮询
# 最终的数据源名称
name: dataSource
# 主库数据源名称
master-data-source-name: master
# 从库数据源名称列表,多个逗号分隔
slave-data-source-names: slave
props:
sql:
show: true #开启SQL显示,默认false
main:
allow-bean-definition-overriding: true
main:
allow-bean-definition-overriding: true
项目部署
listen 80;
server_name localhost;
location /{
root html/dist;
index index.html;
}
# 反向代理配置
location ^~ /api/{
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://192.168.138.101:8080;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html{
root html;
}
}
-
- chmod 777 reggieStart.sh 命令设置执行权限
同时根据实际情况来更改 .sh文件:vim reggieStart.sh。
对于需要加载图片的位置需要自己手动导入 Linux,并更改相关配置文件的路径 - img目录用于存放图片
- path改为你自己服务器中图片所放的位置,并且需要保存,提交并推送到git仓库中
- chmod 777 reggieStart.sh 命令设置执行权限
最后,如果想要部署到云服务器上,也是可以的,但是量力而行。