目录
数据库
创建Maven项目
创建Maven项目,名字与地方自己选择。
创建完成后,检查maven的配置与jdk的版本是否错误。进入pom.xml文件进行依赖配置,首先进行配置的就是一个父子模板,我们写的是一个子模块,要加入父模块的相关配置
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
选择Java的version
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
进行依赖导入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
</plugin>
</plugins>
</build>
加入相应的配置信息
server:
port: 8080
spring:
application:
name: reggie_take_out
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/reggig?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
这个只负责让我们熟悉后端,所以前端的页面是写好的 。将他们拉到resources里面。一般我们前端的都要在resources里面的static或者templates里面,这次我们在直接拉进去不在这两个文件夹里面。所以正常访问的话是访问不到我们的网页的,我们要添加一个配置文件,用来访问我们的镜像文件
@Slf4j
@Configuration
public class WebMVCConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
*
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始config.WebMVCConfig静态资源映射");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
}
登录模块
实体层
@Data
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String name;
private String password;
private String phone;
private String sex;
//身份证,这里与数据库里面的字段不一样,数据库里面的字段为id_number,这个写没问题的前提是在配置类里面加入了
// #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
// map-underscore-to-camel-case: true
private String idNumber;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
}
//身份证,这里与数据库里面的字段不一样,数据库里面的字段为id_number,这个写没问题的前提是在配置类里面加入了
// 在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
// map-underscore-to-camel-case: true
private String idNumber;
接口
写入mybatis-plus的mapper类,service类、service实现类以及controller类
Mapper
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
Service
public interface EmployeeService extends IService<Employee> {
}
ServiceImpl
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}
Controller
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeServiceImpl employeeService;
}
统一返回值
@Data
public class R<T> {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
private Map 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, value);
return this;
}
}
用户登录模块
登录的判断逻辑,还有图解以及代码解释
- 将页面提交的密码进行MD5加密处理
- 根据页面提交的用户名查询数据库
- 如果没有查询到则返回登录失败结果
- 密码比对,如果不一致则返回登录失败结果
- 查看员工状态,如果已禁用状态。则返回员工已禁用结果
- 登录成功,将员工id存入Session并返回成功结果
代码步骤
1. 将页面提交的密码进行MD5加密处理
String password = employee.getPassword();
String password1 = DigestUtils.md5DigestAsHex(password.getBytes());
2.根据页面提交的用户名查询数据库
这个有两种方法 ,一种是QueryWrapper,另外一种LambdaQueryWrapper
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername,employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
QueryWrapper<Employee> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
QueryWrapper 的列名匹配使用的是 “数据库中的字段名(一般是下划线规则)”
LambdaQueryWrapper 的列名匹配使用的是“ Lambda的语法,偏向于对象”
使用LambdaQueryWrapper的优势
不同写“列名”,而是使用纯java的方式,避免了拼写错误(LambdaQueryWrapper的写法如果有错,则在编译期就会报错,而QueryWrapper需要运行的时候调用该方法才会报错)
3.如果没有查询到则返回登录失败结果
if(!emp.getUsername().equals(employee.getUsername())){
log.info("没有找到账户");
return R.error("登录失败");
}
4.密码比对,如果不一致则返回登录失败结果
if(!emp.getPassword().equals(employee.getPassword())) {
log.info("密码不正确");
return R.error("登录失败");
}
5.查看员工状态,如果已禁用状态。则返回员工已禁用结果
if(emp.getStatus()==0){
log.info("状态已禁用");
return R.error("登录失败");
}
6.登录成功,将员工id存入Session并返回成功结果
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
用户退出模块
清楚储存在session里面的用户信息。
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
//清理Session中保存的当前登录员工的ID
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
完善员工登录模块——过滤器
完善逻辑
前面我们已经完成了后台系统的员工登录功能开发,但是还存在一个问题:用户如果不登陆,照样可以正常访问。这种设计并不合理,我们希望看到的效果应该是只有登录成功后才可以访问系统中的页面,如果没有登录则跳转到登录页面
具体流程:
1.获取本次请求的URI
2.判断本次请求是否需要处理
3.如果不需要处理,直接放行
4.判断登录状态,如果以登录,直接放行
5.如果未登录返回到登录界面
1.获取本次请求的URI
String requestURI = request.getRequestURI();
log.info("拦截到的请求{}",requestURI);
2.判断本次请求是否需要处理
首先要判断那些请求不需要拦截
String[] uris = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/frond/**"
};
将拦截到的请求与不需要拦截到的请求进行比对处理,如果相同返回true
这里用到了路径匹配器
//路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
public boolean Check(String[] uris,String requestURI){
for (String uri : uris) {
boolean match = PATH_MATCHER.match(uri, requestURI);
if (match){
//匹配上了返回true
return true;
}
}
//for遍历结束都没有匹配上,则说明没有需要放行的请求
return false;
}
判断请求是否需要处理,当请求方法返回ture的时候就是不需要拦截的请求
boolean check = Check(uris, requestURI);
3.如果不需要处理,直接放行
if (check){
filterChain.doFilter(request, reponse);
log.info("放行的请求{}",requestURI);
return;
}
4.判断登录状态,如果以登录,直接放行
Object employee = request.getSession().getAttribute("employee");
if (employee!=null){
log.info("{}用户已登录",employee);
filterChain.doFilter(request, reponse);
return;
}
5.如果未登录返回到登录界面
log.info("用户未登录");
reponse.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
员工模块
员工添加模块
@PostMapping
public R<String> save(@RequestBody Employee employee,HttpServletRequest request){
log.info("员工信息{}",employee);
//设置初始密码12456.使用MD5进行加密
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获取登录的员工
Long empID = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empID);
employee.setUpdateUser(empID);
employeeService.save(employee);
log.info("新增员工成功");
return R.success("新增员工成功");
}
数据库中用户的name是唯一。当添加两次相同的name就会报错,这时两种办法
法一:try{}catch{}的方法把错误抛出来
法二:全局异常处理
全局异常处理
/**
* 全局异常处理
*/
@ResponseBody
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@Slf4j
public class GlobExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.info(ex.getMessage());
if(ex.getMessage().contains("Duplicate entry")){
String[] split=ex.getMessage().split(" ");
String msg=split[2]+"已存在";
log.info("{}已存在",split[2]);
return R.error(msg);
}
return R.error("未知错误 ");
}
}
if(ex.getMessage().contains("Duplicate entry")){
String[] split=ex.getMessage().split(" ");
String msg=split[2]+"已存在";
log.info("{}已存在",split[2]);
return R.error(msg);
}
Duplicate entry :这个就是数据库数据已存在报的错误
员工信息分页查询
代码开发步骤的执行流程
1.页面发送ajax请求,将分页查询参数(page、pagesize、name)提交到服务端
2.服务端Controller接收页面提交的数据并调用Service查询数据
3.Service调用Mapper操作数据库,查询分页数据
4.Controller将查询到的分页数据相应给页面
5.页面接受到分页数据并通过ElementUI的Table组件展示到页面上
具体实现:
建立分页插件的配置:
@Configuration
public class MybatisPlusConfig {
// 分页配置工具类
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
在controller里面的步骤
1.构造分页构造器
2.构造条件构造器
3.执行查询
1.构造分页构造器
Page pageInfo = new Page(page, pageSize);
2.构造条件构造器
LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper();
条件构造器构造好后,我们要加入一个查询条件以及一个排序条件
查询条件是用来查询的时候的条件
lambdaQueryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
lambdaQueryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
StringUtils.isNotEmpty(name):意思是当name不为空的时候进行查询
排序条件是用来完成查询后的如何进行排序
lambdaQueryWrapper.orderByDesc(Employee::getUpdateTime);
3.执行查询
employeeService.page(pageInfo,lambdaQueryWrapper);
返回一个R.Success
return R.success(pageInfo);
整体的代码
@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> lambdaQueryWrapper = new LambdaQueryWrapper();
// 添加过滤条件
lambdaQueryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
// 添加排序条件
lambdaQueryWrapper.orderByDesc(Employee::getUpdateTime);
// 执行查询
employeeService.page(pageInfo,lambdaQueryWrapper);
return R.success(pageInfo);
}
员工账号启用、禁用
对员工账号状态进行修改,当状态为0的时候为禁用,1为启用
@PutMapping
public R<String> update(@RequestBody Employee employee, HttpServletRequest request) {
log.info(employee.toString());
Long employeeID = (Long) request.getSession().getAttribute("employee");
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(employeeID);
employeeService.updateById(employee);
return R.success("更新成功");
}
进行测试发现能正常运行
比对后端下面的数据库语句发现 发现数据都传输正常,但是在最后的update的行数为零
通过视频讲解发现是前端js的问题,js无法获取Long的全部位数,只能获取16位造成了精度丢失。
员工账号禁用代码修复
我们可以在服务端给页面相应json数据时进行处理,将long型数据统一转为String字符串
具体实现步骤
1.提供对象转换器,基于jackson进行Java对象到json数据的转换
2.在webMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换
1.对象转换器
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
2.在WebMvcConfig配置类中扩展Spring mvc的消息转换器
/**
* 扩展mvc框架的消息转换器
*
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建消息转换器对象
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转换为json
mappingJackson2HttpMessageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器容器
converters.add(0, mappingJackson2HttpMessageConverter);
super.extendMessageConverters(converters);
}
/**
* 扩展mvc框架的消息转换器
*
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建消息转换器对象新建一个消息转换器
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();setObjectMapper:将消息转换set为我们配置的消息转换器
new JacksonObjectMapper():我们刚才自己配置的消息转换器
//设置对象转换器,底层使用Jackson将Java对象转换为json
mappingJackson2HttpMessageConverter.setObjectMapper(new JacksonObjectMapper());0, mappingJackson2HttpMessageConverter:将我们的消息转换器加入mvc框架的转换器容器里面,并设置执行顺序为0表示第一执行顺序
//将上面的消息转换器对象追加到mvc框架的转换器容器
converters.add(0, mappingJackson2HttpMessageConverter);
super.extendMessageConverters(converters);
}
编辑员工信息
代码开发
开发代码之前需要梳理一下操作过程和对应的程序的执行流程
- 点击编辑按钮时,页面跳转到add.html,并在url中携带参数【员工id】
- 在add.html页面中获取url中的参数【员工的id】
- 发送ajax请求,请求服务端,同时提交员工id参数
- 服务端接收请求,根据员工id查询员工信息,将员工信息以json形式相应给页面
- 页面接收服务端响应的json数据,通过vue的数据绑定进行员工信息回显
- 点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
- 服务端接收员工信息,并进行处理,完成后给页面响应
- 页面接收到服务端响应信息后进行相应处理
具体代码执行
/**
* 根据id查询员工信息
*
* @param id
* @return
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id) {
log.info("根据id查询员工信息。。。。。。。。。。。");
Employee emp = employeeService.getById(id);
if (emp != null) {
return R.success(emp);
}
return R.error("没有查询到该用户");
}
分类管理模块
Mybatis Plus公共字段自动填充
什么是公共字段
在新增员工时,要加入最后操作时间,最后操作人,修改时间。在编辑员工时,也要加入最后操作时间,最后操作人,修改时间
这些字段属于公共字段,我们可以对这些公共字段统一处理,这时候就可以使用Mybatis Plus提供的公共字段自动填充功能
Mybatis Plus公共字段自动填充的好处:对公共字段进行处理,避免重复代码
实现步骤:
- 实体类的属性加上@TableField,指定自动填充的策略
- 按照框架要求编写元数据对象处理器,在此类中统一公共字段赋值。此类需要实现MetaObjectHandler接口
具体操作
1. 实体类的属性加上@TableField,指定自动填充的策略,这只是加了注解的部分实体类
@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;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;fill:就是加载的时候要做什么
FieldFill是一个枚举类,是mybatis-plus提供的枚举
2.按照框架要求编写元数据对象处理器
建立一个自定义的类继承MetaObjectHandler 并实现insertFill与updateFill方法
/**
* 自定义元数据对象处理器
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充【insert】");
log.info(metaObject.toString());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充【update】");
log.info(metaObject.toString());
}
}
现在我们在进行新增或者更新的时候会自动调用我们的自定义元数据对象处理器,然后在进行我们正常的新增以及更新。
这是我们新增员工的controller,这里面的一些公共字段就可以由我们自定义元数据对象处理器来进行处理。
自定义元数据对象处理器接管时间的新增
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充【insert】");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());}
当自定义元数据对象处理器接管创建人的时候发现没有办法调用HttpServletRequest来获取session里面已经登录的用户id,使用ThreadLocal线程来获取用户id
使用ThreadLocal线程来获取用户id实现步骤
1、编写BaseContext工具类,基于ThreadLocal封装的工具类
2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id3、在MyMetaobjectHandler的方法中调用BaseContext获取登录用户的id
1、编写BaseContext工具类,基于ThreadLocal封装的工具类
/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
}
2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
在判断用户登录成功后用BaseContext设置当前登录用户的id
//4.判断登录状态,如果已登录,直接放行
//获取session里面的用户登录情况
Object employee = request.getSession().getAttribute("employee");
if (employee!=null){
log.info("{}用户已登录",employee);
filterChain.doFilter(request, reponse);
Long emoId = (Long)request.getSession().getAttribute("employee");
BaseContext.setCurrentId(emoId);
return;
}
3、在MyMetaobjectHandler的方法中调用BaseContext获取登录用户的id
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充【insert】");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser",BaseContext.getCurrentId());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
新增分类
框架搭建
新建实体类
/**
* 分类
*/
@Data
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//类型 1 菜品分类 2 套餐分类
private Integer type;
//分类名称
private String name;
//顺序
private Integer sort;
//创建时间
@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;
//是否删除
private Integer isDeleted;
}
新建mapper
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}
新建service
public interface CategoryService extends IService<Category> {
}
新建service实现类
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}
新建Controller
/**
* 分类管理
*/
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
}
新增菜品分类以及新增套餐分类执行流程
新增分类
//新增分类
@PostMapping
public R<String> save(@RequestBody Category category) {
categoryService.save(category);
return R.success("新增分类成功");
}
分类信息分页
/**
* 分类查询
* @param page
* @param pageSize
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize) {
log.info("page:{},pagesize:{}",page,pageSize);
//构造分页处理器
Page<Category> pageInfo = new Page(page, pageSize);
//构造条件构造器
LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper();
//添加排序条件
lambdaQueryWrapper.orderByAsc(Category::getSort);
//执行查询
categoryService.page(pageInfo, lambdaQueryWrapper);
return R.success(pageInfo);
}
删除分类
@DeleteMapping
public R<String> delete(@RequestParam Long ids) {
log.info("删除的分类id:{}", ids);
categoryService.removeById(ids);
return R.success("分类删除成功");
}
删除优化
在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除
要完善功能就要跟菜品以及套餐相关,加入相关实体类,并构建Mapper、Service以及实现类
我们要自定义一个remove方法来删除分类,就要在categoryService里面重写remove 方法
public interface CategoryService extends IService<Category> {
// 根据id删除分类
void remove(Long id);
}
categoryServiceImpl实现重写的remove 方法
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
/**
* 根据id删除分类,删除没有关联菜品以及套餐的分类
*
* @param id
*/
@Override
public void remove(Long id) {
}
}
加入判断,判断是否有关联菜品或者套餐,如果有关联抛出业务异常,没有就正常删除
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired
private DishService dishService;
@Autowired
private SetmealService setmealService;
@Autowired
private CategoryService categoryService;
/**
* 根据id删除分类,删除没有关联菜品以及套餐的分类
*
* @param id
*/
@Override
public void remove(Long ids) {
// 先判断是否有菜品关联,如果有抛出业务异常
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper();
dishLambdaQueryWrapper.eq(Dish::getCategoryId, ids);
int dishCount = dishService.count(dishLambdaQueryWrapper);
if (dishCount > 0) {
//抛出业务异常
}
// 先判断是否有套餐关联,如果有抛出业务异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper();
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId, id);
int setmealCount = setmealService.count(setmealLambdaQueryWrapper);
if (setmealCount > 0) {
//抛出业务异常
}
// 删除分类
categoryService.removeById(ids);
}
}
自定义业务异常
当关联有菜品或者套餐时,抛出业务异常,我们写一个自定义的业务异常类
/**
* 自定义业务异常
*/
public class CustomException extends RuntimeException{
public CustomException(String message) {
super(message);
}
}
自定义业务异常加入GlobExceptionHandler全局异常处理
@ExceptionHandler(CustomException.class)
public R<String> customExceptionHandler(CustomException ex){
log.info(ex.getMessage());
return R.error(ex.getMessage());
}
在遇到业务时抛出异常
/** * 根据id删除分类,删除没有关联菜品以及套餐的分类 * * @param id */ @Override public void remove(Long id) { // 先判断是否有菜品关联,如果有抛出业务异常 LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper(); dishLambdaQueryWrapper.eq(Dish::getCategoryId, id); int dishCount = dishService.count(dishLambdaQueryWrapper); if (dishCount > 0) { //抛出业务异常 throw new CustomException("关联了菜品,此分类无法删除"); } // 先判断是否有套餐关联,如果有抛出业务异常 LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper(); setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId, id); int setmealCount = setmealService.count(setmealLambdaQueryWrapper); if (setmealCount > 0) { //抛出业务异常 throw new CustomException("关联了套餐,此分类无法删除"); } // 删除分类 categoryService.removeById(ids); } }
categoryController删除时来执行自定义的remove 方法
@DeleteMapping
public R<String> delete(@RequestParam Long ids) {
log.info("删除的分类id:{}", ids);
categoryService.remove(ids);
return R.success("分类删除成功");
}
修改分类信息
/**
* 修改分类信息
* @param category
* @return
*/
@PutMapping
public R<String> update(@RequestBody Category category){
log.info("分类{}修改成功",category);
categoryService.updateById(category);
return R.success("分类修改成功");
}
菜品模块
文件上传下载
文件上传介绍
文件下载介绍
代码具体实现
@RestController @Slf4j @RequestMapping("/common") public class CommonController { @GetMapping("/upload") public R<String> upload(MultipartFile file) { } }
file这里不能随便起名字的要跟浏览器里面的文件上传的name保持一致
file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件就会被删除
转存文件
try { file.transferTo(new File("F:\\hello.jpg")); } catch (IOException e) { e.printStackTrace(); }
使用配置文件来规定路径
配置文件加入 wzx: path: F:\调用位置
@Value("${wzx.path}") private String basePath;转存文件
try {
file.transferTo(new File(basePath+"hello.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
转存的时候使用原来文件的名字
获取原文件的名字
String originalFilename = file.getOriginalFilename();转存文件
try { file.transferTo(new File(basePath+originalFilename)); } catch (IOException e) { e.printStackTrace(); }
UUID转存文件
不过一般都不建议使用上述的名字方法,相同的名字时会覆盖原来的文件,会丢失数据,采用uuid的方式命名会避免这种情况
String originalFilename = file.getOriginalFilename(); 获取原文件名称 String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); 获取原文件名称的后缀从最后一个.开始的后缀 String fileName = UUID.randomUUID().toString() + suffix; 用uuid的随机id加上原文件的后缀
当转存文件的时候路径中的文件夹本次不存在,自动创建文件夹
//创建一个目录对象
File dir = new File(basePath);
//判断当前目录是否存在
if(!dir.exists()){
//目录不存在需要创建
dir.mkdirs();
}
文件上传
配置信息里面的文件路径
wzx: path: F:\wzxw\前后都要有不然会导致新建问价但是文件转存的时候不会进入指定文件夹
@RestController
@Slf4j
@RequestMapping("/common")
public class CommonController {
@Value("${wzx.path}")
private String basePath;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file) {
log.info(file.toString());
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileName = UUID.randomUUID().toString() + suffix;
//创建一个目录对象
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);
}
文件下载
用来将上传的文件回显在浏览器上
@Value("${wzx.path}")
private String basePath;
/**
* 文件下载
* @param name
* @param response
*/
@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) {
//写入bytes从0写到len len是文件的总长度
outputStream.write(bytes, 0, len);
//刷新输出流
outputStream.flush();
}
//关闭资源
outputStream.close();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
新增菜品
加入实体类DishFlavor以及实体类相关的mapper、service、serviceimpl
新增菜品里面下拉菜单里面选择分类
/**
* 根据条件查询分类数据
*
* @param category
* @return
*/
@GetMapping("/list")
public R<List<Category>> list(Category category) {
//条件构造器
LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper();
//添加条件 查询type的值判断是菜品还是套餐 1是菜品 2是套餐
lambdaQueryWrapper.eq(category.getType()!=null,Category::getType, category.getType());
//添加排序条件 先根据sort进行升序排序 如果有sort相同根据更新时间降序排序
lambdaQueryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
//执行查询
List<Category> list = categoryService.list(lambdaQueryWrapper);
return R.success(list);
}
新增菜品,点击保存的时候,查看左边的请求发现有很多数据但是其中有一条数据在我们的实体类中发现没有办法接收
双表保存
点击保存菜品的时候不能跟以往一样save就可以了,涉及到了两张表,一张表为菜品信息,一张表为口味
我们自己写一个service 方法,在DishService里面写
//新增菜品,同时插入菜品对应的口味数据,需要操作两张表,dish dish_flavor
void dishWithFlavor(DishDto dishDto);
在DishServiceImpl实现
@Override
public void dishWithFlavor(DishDto dishDto) {
}
步骤:
- 保存菜品的基本信息到dish表
- 获取保存到dish表的菜品id
- 获取dishDto里面的口味信息
- 遍历菜品口味将菜品id逐个加入菜品口味
- 将菜品口味(内含菜品口味以及对应的菜品id)加入dish_flavor表里
- 涉及到了两张表,为了保证一致性加入事务
1.保存菜品的基本信息到dish表
this.save(dishDto);
2.获取保存到dish表的菜品id
Long id = dishDto.getId();
3.获取dishDto里面的口味信息
List<DishFlavor> flavors = dishDto.getFlavors();
4.遍历菜品口味将菜品id逐个加入菜品口味
flavors = flavors.stream().map((item) -> {
item.setDishId(id);
return item;
}).collect(Collectors.toList());
方法一:使用流的方式 flavors = flavors.stream().map((item) -> { item.setDishId(id); return item; }).collect(Collectors.toList());
Collectors.toList() :将map转换为List数组
方法二:使用forEach
5.将菜品口味(内含菜品口味以及对应的菜品id)加入dish_flavor表里
@Autowired
private DishFlavorService dishFlavorService;
dishFlavorService.saveBatch(flavors);
6.涉及到了两张表,为了保证一致性加入事务
在这个实现类加入注解@Transactional
启动类加入注解@EnableTransactionManagement
如果报Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.
启动类加入注解@EnableAsync(proxyTargetClass=true)
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品,同时保存口味数据
*
* @param dishDto
*/
@Transactional
@Override
public void dishWithFlavor(DishDto dishDto) {
//保存菜品的基本信息到dish表
this.save(dishDto);
//获取保存菜品的id
Long id = dishDto.getId();
//菜品口味
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item) -> {
item.setDishId(id);
return item;
}).collect(Collectors.toList());
//保存菜品口味到口味表dish_flavor 这里用到了fishflavorservice
dishFlavorService.saveBatch(flavors);
}
}
启动类
@Slf4j
@SpringBootApplication
@ServletComponentScan
@EnableTransactionManagement
@EnableAsync(proxyTargetClass=true)
public class ReggigApplication {
public static void main(String[] args) {
SpringApplication.run(ReggigApplication.class);
log.info("启动成功 ");
}
}
菜品分页
代码正常运行但是少了分类名称
/**
* 菜品分页查询
*
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
log.info("分页查询");
//分页构造器
Page<Dish> pageInfo = new Page<>(page, pageSize);
//条件构造器
LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//过滤条件
lambdaQueryWrapper.like(name != null, Dish::getName, name);
//添加排序条件
lambdaQueryWrapper.orderByDesc(Dish::getUpdateTime);
//执行查询
dishService.page(pageInfo, lambdaQueryWrapper);
return R.success(pageInfo);
}
加入下面的更新records里面的信息加入categoryName
//新建一个page 里面含有cagegoryName
Page<DishDto> dishDtoInfo = new Page<>(page, pageSize);
//对象拷贝 使pageInfo里面的属性全部拷贝到dishDtoInfo除了records
BeanUtils.copyProperties(pageInfo, dishDtoInfo, "records");
List<Dish> records = pageInfo.getRecords();
List<DishDto> list = records.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item, dishDto);
//获取categoryId
Long categoryId = item.getCategoryId();
//根据categoryId查询category
Category category = categoryService.getById(categoryId);
//如果category的name是null直接set的话会报错我们加入一个判断,当不为空时才set进去name防止报错
if(category!=null){
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
return dishDto;
}).collect(Collectors.toList());
dishDtoInfo.setRecords(list);
完整的菜品分页代码
/**
* 菜品分页查询
*
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
log.info("分页查询");
//分页构造器
Page<Dish> pageInfo = new Page<>(page, pageSize);
//条件构造器
LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//过滤条件
lambdaQueryWrapper.like(name != null, Dish::getName, name);
//添加排序条件
lambdaQueryWrapper.orderByDesc(Dish::getUpdateTime);
//执行查询
dishService.page(pageInfo, lambdaQueryWrapper);
//新建一个page 里面含有cagegoryName
Page<DishDto> dishDtoInfo = new Page<>(page, pageSize);
//对象拷贝 使pageInfo里面的属性全部拷贝到dishDtoInfo除了records
BeanUtils.copyProperties(pageInfo, dishDtoInfo, "records");
List<Dish> records = pageInfo.getRecords();
List<DishDto> list = records.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item, dishDto);
//获取categoryId
Long categoryId = item.getCategoryId();
//根据categoryId查询category
Category category = categoryService.getById(categoryId);
//如果category的name是null直接set的话会报错我们加入一个判断,当不为空时才set进去name防止报错
if(category!=null){
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
return dishDto;
}).collect(Collectors.toList());
dishDtoInfo.setRecords(list);
return R.success(dishDtoInfo);
}
修改菜品信息
回显信息,有口味以及商品是两张表,在service里面新建一个方法
//根据id查询菜品信息和对应的口味信息
public DishDto getByIdWithFlavor(Long id);
实现类
/**
* 根据id查询菜品信息和对应的口味信息
*
* @param id
* @return
*/
@Override
public DishDto getByIdWithFlavor(Long id) {
//查询商品信息
Dish dish = this.getById(id);
//铲鲟当前菜品对应的口味信息,从dish_flavor表查询
DishDto dishDto = new DishDto();
//拷贝dish到dishDto
BeanUtils.copyProperties(dish, dishDto);
LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(DishFlavor::getDishId, dish.getId());
List<DishFlavor> flavors = dishFlavorService.list(lambdaQueryWrapper);
dishDto.setFlavors(flavors);
return dishDto;
}
controller
/**
* 根据id查询菜品信息和对应的口味信息
*
* @param id
* @return
*/
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id) {
DishDto dishDto = dishService.getByIdWithFlavor(id);
return R.success(dishDto);
}
修改菜品
- 更新dish基本信息
- 根据dishId删除dish_flavor表里面的口味信息
- 根据dishId插入新的口味信息
同时操作两张表新建一个更新方法
//更新菜品 同时更新菜品对应的口味数据
void updateDishWithFlavor(DishDto dishDto);
实现类实现
1.更新dish基本信息
this.updateById(dishDto);
2.根据dishId删除dish_flavor表里面的口味信息
/根据当前菜品id清除DishFlavor已存在的口味信息
ambdaQueryWrapper<DishFlavor> DishFlavorQueryWrapper = new LambdaQueryWrapper<>();
/查询条件
ishFlavorQueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
/删除口味信息
ishFlavorService.remove(DishFlavorQueryWrapper);
3.根据dishId插入新的口味信息
//添加口味信息
List<DishFlavor> flavors = dishDto.getFlavors();
flavors= flavors.stream().map((item)->{
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
完整代码
/**
* 新增菜品,同时更新口味数据
*
* @param dishDto
*/
@Transactional
@Override
public void updateDishWithFlavor( DishDto dishDto) {
//更新dish基本信息
this.updateById(dishDto);
//更新口味信息->删除以前存在的口味信息,重新添加已更新的口味信息
//根据当前菜品id清除DishFlavor已存在的口味信息
LambdaQueryWrapper<DishFlavor> DishFlavorQueryWrapper = new LambdaQueryWrapper<>();
//查询条件
DishFlavorQueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
//删除口味信息
dishFlavorService.remove(DishFlavorQueryWrapper);
//添加口味信息
List<DishFlavor> flavors = dishDto.getFlavors();
flavors= flavors.stream().map((item)->{
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
controller调用
/**
* 更新菜品
*
* @param dishDto
* @return
*/
@PutMapping
public R<String> update(@RequestBody DishDto dishDto) {
log.info("更新菜品");
dishService.updateDishWithFlavor(dishDto);
return R.success("更新成功");
}
套餐模块
新增实体SetmealDish——套餐关系表,构建相关maper、service、实现类、Controller
新增套餐
添加菜品
/**
* 根据条件查询相关菜品
* @param dish
* @return
*/
@GetMapping("/list")
public R<List<Dish>> list(Dish dish) {
//构造条件查询器
LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//查询条件
lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
//查询起售状态的菜品
lambdaQueryWrapper.eq(Dish::getStatus,1);
//排序条件
lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
//执行查询
List<Dish> list = dishService.list(lambdaQueryWrapper);
return R.success(list);
}
新增套餐
新建service方法
/**
* 新增套餐,同时需要保存套餐喝菜品的关联关系
* @param setmealDto
*/
public void saveWithDish(SetmealDto setmealDto);
实现新建方法
@Autowired
SetmealDishService setmealDishService;
/**
* 新增套餐,同时需要保存套餐喝菜品的关联关系
*
* @param setmealDto
*/
@Override
@Transactional
public void saveWithDish(SetmealDto setmealDto) {
//保存套餐的基本信息,操作setmeal,执行insert操作
this.save(setmealDto);
//获取setmealId
Long id = setmealDto.getId();
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
//将setmealId放入对应的List<SetmealDish> setmealDishes
setmealDishes=setmealDishes.stream().map((item)->{
item.setSetmealId(id);
return item;
}).collect(Collectors.toList());
//保存套餐和菜品的关联信息,操作setmeal_dish,执行Insert操作
setmealDishService.saveBatch(setmealDishes);
}
controller调用
/**
* 新增套餐
* @param setmealDto
* @return
*/
@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto){
log.info("新增套餐");
log.info("套餐信息{}",setmealDto);
setmealService.saveWithDish(setmealDto);
return R.success("新增套餐成功");
}
套餐分页
步骤:
- 正常获取分页信息——setmealpageInfo,只是这样获取的信息没有分类名称信息
- 新建一个套餐Dto的分页构造器,将第一步获取的分页信息除records外的属性拷贝给第二部的分页构造器
- 获取第一步分页信息里面的records属性
- 通过流的方式遍历records属性
- 新建套餐Dto对象——setmealDto,将遍历的records属性拷贝给新建的setmealDto对象
- 获取records属性里面的categoryId
- 通过categoryId获取category对象
- set对象setmealDto的CategoryName
- 返回setmealDto
- set对象setmealDtoPageInfo的records
/**
* 套餐分页查询
*
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
//构建分页构造器
Page<Setmeal> setmealPage = new Page(page, pageSize);
//条件条件构造器
LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//查询条件
lambdaQueryWrapper.like(name != null, Setmeal::getName, name);
//添加排序条件
lambdaQueryWrapper.orderByDesc(Setmeal::getUpdateTime);
//执行查询
Page<Setmeal> pageInfo = setmealService.page(setmealPage, lambdaQueryWrapper);
Page<SetmealDto> setmealDtoPageInfo = new Page(page, pageSize);
BeanUtils.copyProperties(pageInfo, setmealDtoPageInfo, "records");
List<Setmeal> records = pageInfo.getRecords();
List<SetmealDto> records1 = records.stream().map((item) -> {
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(item, setmealDto);
Long categoryId = item.getCategoryId();
Category category = categoryService.getById(categoryId);
setmealDto.setCategoryName(category.getName());
return setmealDto;
}).collect(Collectors.toList());
setmealDtoPageInfo.setRecords(records1);
return R.success(setmealDtoPageInfo);
}
套餐删除
/**
* 删除套餐,同时需要删除套餐和菜品的关联数据
*
* @param ids
*/
@Override
@Transactional
public void removeWithDish(List<Long> ids) {
//删除套餐状态,确定是否可以删除
LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(Setmeal::getId, ids);
lambdaQueryWrapper.eq(Setmeal::getStatus, 1);
int count = this.count(lambdaQueryWrapper);
if (count > 0) {
//如果不能删除抛出业务异常
throw new CustomException("套餐正在售卖中,无法删除");
}
//如果可以删除,先删除套餐中的数据
this.removeByIds(ids);
//删除关系表中的数据
LambdaQueryWrapper<SetmealDish> setmealDishLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealDishLambdaQueryWrapper.in(SetmealDish::getSetmealId, ids);
setmealDishService.remove(setmealDishLambdaQueryWrapper);
}
手机验证码登录
短信发送
阿里云短信依赖
<!--阿里云短信服务-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.1.0</version>
</dependency>
阿里云短信工具类
/**
* 短信发送工具类
*/
public class SMSUtils {
/**
* 发送短信
* @param signName 签名
* @param templateCode 模板
* @param phoneNumbers 手机号
* @param param 参数
*/
public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setSysRegionId("cn-hangzhou");
request.setPhoneNumbers(phoneNumbers);
request.setSignName(signName);
request.setTemplateCode(templateCode);
request.setTemplateParam("{\"code\":\""+param+"\"}");
try {
SendSmsResponse response = client.getAcsResponse(request);
System.out.println("短信发送成功");
}catch (ClientException e) {
e.printStackTrace();
}
}
}
随机生成验证码
/**
* 随机生成验证码工具类
*/
public class ValidateCodeUtils {
/**
* 随机生成验证码
* @param length 长度为4位或者6位
* @return
*/
public static Integer generateValidateCode(int length){
Integer code =null;
if(length == 4){
code = new Random().nextInt(9999);//生成随机数,最大为9999
if(code < 1000){
code = code + 1000;//保证随机数为4位数字
}
}else if(length == 6){
code = new Random().nextInt(999999);//生成随机数,最大为999999
if(code < 100000){
code = code + 100000;//保证随机数为6位数字
}
}else{
throw new RuntimeException("只能生成4位或6位数字验证码");
}
return code;
}
/**
* 随机生成指定长度字符串验证码
* @param length 长度
* @return
*/
public static String generateValidateCode4String(int length){
Random rdm = new Random();
String hash1 = Integer.toHexString(rdm.nextInt());
String capstr = hash1.substring(0, length);
return capstr;
}
}
手机验证码登录
加入实体类User,加入mapper层、service层、实现层、controller层
加入移动端用户登录判断
//4-2.判断移动端登录状态,如果已登录,直接放行
//获取session里面的用户登录情况
Object user = request.getSession().getAttribute("user");
if (user != null) {
log.info("{}用户已登录", user);
filterChain.doFilter(request, reponse);
Long userId = (Long) request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);
return;
}
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
/**
* 发送手机短信验证码
* @param user
* @return
*/
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
//获取手机号
String phone = user.getPhone();
if(StringUtils.isNotEmpty(phone)){
//生成随机的4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}",code);
//调用阿里云提供的短信服务API完成发送短信
//SMSUtils.sendMessage("瑞吉外卖","",phone,code);
//需要将生成的验证码保存到Session
session.setAttribute(phone,code);
return R.success("手机验证码短信发送成功");
}
return R.error("短信发送失败");
}
/**
* 移动端用户登录
* @param map
* @param session
* @return
*/
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){
log.info(map.toString());
//获取手机号
String phone = map.get("phone").toString();
//获取验证码
String code = map.get("code").toString();
//从Session中获取保存的验证码
Object codeInSession = session.getAttribute(phone);
//进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
if(codeInSession != null && codeInSession.equals(code)){
//如果能够比对成功,说明登录成功
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone,phone);
User user = userService.getOne(queryWrapper);
if(user == null){
//判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
user = new User();
user.setPhone(phone);
user.setStatus(1);
userService.save(user);
}
session.setAttribute("user",user.getId());
return R.success(user);
}
return R.error("登录失败");
}
}
用户收货地址
导入AddressBook实体,建立mapper、service、实现类以及controller
/**
* 地址簿管理
*/
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {
@Autowired
private AddressBookService addressBookService;
/**
* 新增
*/
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook) {
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBook:{}", addressBook);
addressBookService.save(addressBook);
return R.success(addressBook);
}
/**
* 设置默认地址
*/
@PutMapping("default")
public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
log.info("addressBook:{}", addressBook);
LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
wrapper.set(AddressBook::getIsDefault, 0);
//SQL:update address_book set is_default = 0 where user_id = ?
addressBookService.update(wrapper);
addressBook.setIsDefault(1);
//SQL:update address_book set is_default = 1 where id = ?
addressBookService.updateById(addressBook);
return R.success(addressBook);
}
/**
* 根据id查询地址
*/
@GetMapping("/{id}")
public R get(@PathVariable Long id) {
AddressBook addressBook = addressBookService.getById(id);
if (addressBook != null) {
return R.success(addressBook);
} else {
return R.error("没有找到该对象");
}
}
/**
* 查询默认地址
*/
@GetMapping("default")
public R<AddressBook> getDefault() {
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
queryWrapper.eq(AddressBook::getIsDefault, 1);
//SQL:select * from address_book where user_id = ? and is_default = 1
AddressBook addressBook = addressBookService.getOne(queryWrapper);
if (null == addressBook) {
return R.error("没有找到该对象");
} else {
return R.success(addressBook);
}
}
/**
* 查询指定用户的全部地址
*/
@GetMapping("/list")
public R<List<AddressBook>> list(AddressBook addressBook) {
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBook:{}", addressBook);
//条件构造器
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());
queryWrapper.orderByDesc(AddressBook::getUpdateTime);
//SQL:select * from address_book where user_id = ? order by update_time desc
return R.success(addressBookService.list(queryWrapper));
}
}