瑞吉外卖业务开发

2023-11-14

一, 软件开发整体介绍

软件开发流程

  • 需求分析
    • 产品原型,需求规格说明书
  • 设计
    • 产品文档,UI界面设计,概要设计,详细设计,数据库设计
  • 编码
    • 项目代码,单元测试
  • 测试
    • 测试用例,测试报告
  • 上线运维
    • 软件环境安装,配置

角色分工

  • 项目经理:对整个项目负责,任务分配、把控进度
  • 产品经理:进行需求调研,输出需求调研文档、产品原型等
  • UI设计师:根据产品原型输出界面效果图
  • 架构师:项目整体架构设计、技术选型等
  • 开发工程师:代码实现
  • 测试工程师:编写测试用例,输出测试报告
  • 运维工程师:软件环境搭建、项目上线

环境

软件环境

  • 开发环境
  • 测试环境:专门给测试人员使用的环境,用于测试项目,一般外部用户无法访问
  • 生产环境(production):即线上环境,正式提供对外服务的环境

二,瑞吉外卖项目介绍

展示

38x76

技术选型

g2sl1

功能架构

uy3vd

角色

vojix

三,开发环境搭建

数据库环境搭建

  • 建立reggie库
    29190
  • 导入表结构
    lkzbu
  • 数据表

xlfft

maven环境配置

导入pom文件

  • 导入父工程
      <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>2.4.5</version>
          <relativePath/> <!-- lookup parent from repository -->
      </parent>
    
- 指定jdk版本
  ```xml
  <properties>  
  
    <java.version>1.8</java.version>  
     
</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>
    
- 导入配置文件
```yml
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
  • 导入静态资源

1

[!danger]
默认情况下是访问不到这些文件夹里面的静态资源的,所以需要配置资源映射

@Configuration  
public class WebMvcConfig extends WebMvcConfigurationSupport{  
//    设置静态资源映射  
  @Override  
  protected void addResourceHandlers(ResourceHandlerRegistry registry) {  
      registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");  
      registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");  

  }  
}

默认配置的资源映射 [[springboot-web#资源处理的默认规则]] (可以参考)

四,后台登录功能开发

需求分析

vuj5w
前端的登录按键和 handleLogin这个方法绑定,这个方法调用loginApi上产loginForm中的信息也就是账号密码,如果返回1就代表登陆成功,然后把数据转成JSON的格式保存到主页面,如果返回的不是1的话就设置一个错误信息,并且把登陆中改成登录

function loginApi(data) {  
  return $axios({  
    'url': '/employee/login',  
    'method': 'post',  
    data  
  })  
}  
  
function logoutApi(){  
  return $axios({  
    'url': '/employee/logout',  
    'method': 'post',  
  })  
}

代码开发

  • 导入实体类
    mapper
@Mapper  
public interface EmployeeMapper extends BaseMapper<Employee> {  
}
  • service
@Service  
public interface EmployeeService extends IService<Employee> {  
  
}
@Service  
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {  
  
}
  • 导入返回结果类,服务端响应的所有结果都会包装成此类型返回给前端页面
public class R<T> {  
  
    private Integer code; //编码:1成功,0和其它数字为失败  
  
    private String msg; //错误信息  
  
    private T data; //数据  
  
    private Map map = new HashMap(); //动态数据  
  第一个<T>是为了标注此方法是泛型方法
    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;  
    }

在Controller中创建登录方法
处理逻辑如下;

  1. 将页面提示的密码password进行md5加密
  2. 根据页面提交的username查询数据库
  3. 如果没有查询到返回登陆失败结果
  4. 密码比对,比对不一致返回登陆失败
  5. 查看员工状态,如果为已禁用状态,则返回已禁用结果
  6. 登陆成功,将员工的id存入session并返回登陆成功结果
    hy7e4
  • Controller ^3yec7p
@Slf4j  
@RestController  
@RequestMapping("/employee")  
public class EmployeeController {  
    @Autowired  
    private EmployeeService employeeService;  
  public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee ){  
    String password = employee.getPassword();  
    password= DigestUtils.md5DigestAsHex(password.getBytes());  
    LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();  
    queryWrapper.eq(Employee::getUsername,employee.getUsername());  
    Employee emp = employeeService.getOne(queryWrapper);  
    if(emp==null){  
        return R.error("登陆失败");  
    }  
    if(!emp.getPassword().equals((password))){  
        return R.error("登陆失败");  
    }  
    if(emp.getStatus()==0){  
        return R.error("账号已禁用");  
    }  
  
    request.getSession().setAttribute("emp",emp.getId());  
    return R.success(emp);  
}
}

完善登录功能-问题分析并创建过滤器

实现步骤:

1、创建自定义过滤器LoginCheckFilter
2、在启动类上加入注解@ServletComponentScan==(开启主件扫描去扫描过滤器)==
3、完善过滤器的处理逻辑

代码实现

过滤器具体的处理逻辑:

  1. 获取本次请求的url
  2. 判断本次请求是否需要处理
  3. 如果不需要处理,则直接放行
  4. 判断登录状态,如果已经登录,则直接放行
  5. 如果未登录则返回未登录结果
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A7lJIMV4-1686059761210)(null)]
    前端的逻辑是:如果msg是NOTLOGIN那么久自动跳转到登录页面
    8baby
@Slf4j  
@WebFilter( filterName="LoginCheckFilter",urlPatterns = "/*")  
public class LoginCheckFilter implements Filter {  
    public static final AntPathMatcher  PATH_MATHER  = new AntPathMatcher();  
    @Override  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
        HttpServletRequest request = (HttpServletRequest) servletRequest;  
        HttpServletResponse response = (HttpServletResponse) servletResponse;  
  
        String  uri = request.getRequestURI();  
        String[] urls= new String[] {  
                "/employee/login",  
                "/employee/logout",  
                "/backend/**",  
                "front/**"  
        };  
        if(check(urls,uri)){  
            filterChain.doFilter(request,response);  
            return;  
        }  
        if (request.getSession().getAttribute("employee")!=null){  
            filterChain.doFilter(request,response);  
            return;  
        }  
//controller里面直接返回R对象是因为框架自动帮我们转换成Json了,这里是filter需要我们手动转成Json数据传回前端  
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));  
       log.info("拦截到请求",uri);  
  
    }  
    public boolean check(String[] urls,String requesturl){  
        for (String url : urls) {  
            boolean match = PATH_MATHER.match(url,requesturl);  
            if (match) {  
                return true;  
            }  
        }  
        return false;  
  
    }  
}

五, 后台系统退出功能

需求分析

员工登录成功后,页面跳转到后台系统首页面(backend/index.html),此时会显示当前登录用户的姓名:
如果员工需要退出系统,直接点击右侧的退出按钮即可退出系统,退出系统后页面应跳转回登录页面

代码开发

用户点击页面中退出按钮,发送请求,请求地址为/employee/logout,请求方式为POST。我们只需要在Controller中创建对应的处理方法即可,具体的处理逻辑:
1、清理Session中的用户id
2、返回结果

/**  
 * 员工退出  
 * @param request  
 * @return  
 */  
@PostMapping("/logout")  
public R<String> logout(HttpServletRequest request) {  
    request.getSession().removeAttribute("employee");  
    return R.success("退出成功");  
  
}

前端判断code为1进行页面跳转

uu83z

前端进行页面切换部分
ns9l8
调用的是menuHandle方法
tnuh5
用iframe的方式显示一个页面
jjjsa

页面上有个iframe相当月页面上有一个坑,用来展示页面的,外面的来源那就是:src里面的url
wxz1v

六,新增员工

需求分析

后台系统中可以查看管理员工信息,通过新增员工来添加后台系统用户,点击[添加员工]跳转到新增页面,如下
iani3

代码开发

执行过程

  1. 页面发送ajax请求,将新增员工中输入的数据信息以json格式发送到服务端
  2. 服务端Controller接收页面提交的数据并调用Service将数据进行保存
  3. Service调用Mapper操作数据库,保存数据
    pvq07
    8csof

前端:员工管理页面点击添加员工按钮,会为我们切换到添加员工那个页面(iframe的方式)

@PostMapping  
public R<String> save(@RequestBody Employee employee,HttpServletRequest request){  
    log.info("新增员工.员工信息{}" ,employee.toString());  
    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("新增员工成功");  
  
}

异常处理

前面的程序还存在一个问题,就是当我们在新增员工时输入的账号已经存在,由于employee表中对该字段加入了唯一约束,此时程序会抛出异常:
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'zhangsan’for key ‘idx_username’

此时需要我们的程序进行异常捕获,通常有两种处理方式:
1、在Controller方法中加入try、catch进行异常捕获
2、使用异常处理器进行全局异常捕获

pw94v
一般来说对每个异常都进行单独捕获未免太麻烦,所以用异常处理器进行全局异常捕获
代码

  
/**  
 * 全局异常捕获  
 */  
@ControllerAdvice(annotations = {RestController.class, Controller.class})  
//@ResponseBody的作用其实是将java对象转为json格式的数据。  
@ResponseBody  
@Slf4j  
public class GlobalExceptionHandler {  
    /**  
     * 进行异常处理  
     * @return  
     */  
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)  
    public R<String> exceptionHander(SQLIntegrityConstraintViolationException ex){  
        log.error(ex.getMessage());  
        return R.error("失败了");  
  
    }  
  
}

测试
vx626
完善异常处理器

使异常处理器在捕获字段名重复这个异常的时候传回的错误信息可以包含重复的字段值

public R<String> exceptionHander(SQLIntegrityConstraintViolationException ex){  
    log.error(ex.getMessage());  
    if (ex.getMessage().contains("Duplicate entry")){  
        String[] split = ex.getMessage().split(" ");  
        return R.error(split[2]+"已存在");  
  
    }  
    return R.error("失败了");  
  
}

总结

1、根据产品原型明确业务需求
2、重点分析数据的流转过程和数据格式
3、通过debug断点调试跟踪程序执行过程
gzwl4

七,员工信息分页查询

需求分析

系统中的员工很多时,如果在一个页面全部显示会很乱,不便于查看,所以一般系统中都会以分页的形式来展示列表数据

代码开发

在开发代码之前,需要梳理一下整个程序的执行过程:

  1. 页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端
  2. 服务端Controller接收页面提交的数据并调用Service查询数据
  3. Service调用Mapper操作数据库,查询分页数据
  4. Controller将查询到的分页数据响应给页面
  5. 页面接收到分页数据并通过ElementUI的Table组件展示到页面上
    前端:
    lcvhb
    created是系统刚开始就执行的方法
    yrh6j
    l23cd
    设置MabitsPlus的配置
@Configuration  
public class MybatisPlusConfig {  
    public MybatisPlusInterceptor mybatisPlusInterceptor() {  
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();  
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());  
        return mybatisPlusInterceptor;  
    }  
}

Controller

    /**  
     * 分页查询  
     * @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);  
//自动给我们的page封装好了  
  
        return R.success(pageInfo);  
  
    }

分页请求发送的几个地方

  • 刚初始化页面
  • 查询
  • 点击分页插件

[!important]
返回的R里面的data是一种名叫Page的数据结构,这个数据结构里面自动封装了EmpLoyee数组

八, 禁用员工账号

代码开发

  1. 页面发送ajax请求,将参数(id,status)提交到服务端
  2. 服务端Controller接收页面提交的数据,并将调用Service更新数据
  3. Service调用Mapper操控服务器
    mgw2z
    初步代码
@PutMapping  
public R<String> update(@RequestBody Employee employee ){  
    log.info(employee.toString());  
    return null;  
  
  
}

当点击禁用,传送来的信息
qm9zi

功能测试

没有报错,但是功能没有实现
观察控制台输出sql
eldua
和数据库里面的id并不一致

y72mc

代码修复

原因是js对数据处理的时候丢失了精度,导致提交结果和数据库中的id不一致
如何解决这个问题
我们可以在服务端给页面响应JSON数据时进行处理,将long型统一转换成String字符串
具体实现步骤:
1)提供对象转换器Jackson0bjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)
2)在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

/**  
 * 扩展mvc消息转换器  
 * @param converters  
 */  
@Override  
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {  
    //创建消息转换器对象  
    MappingJackson2CborHttpMessageConverter converter = new MappingJackson2CborHttpMessageConverter();  
    //创建对象转换器底层使用Jackson将 java对象转换成json  
    converter.setObjectMapper(new JacksonObjectMapper());  
    //将上面的消息转换器将对象追加到mvc框架的转换器集合中  
    converters.add(0,converter);  
  
}

九,编辑员工信息

需求分析

管理员可对于用户信息进行编辑

代码开发

1、点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]
2、在add.html页面获取url中的参数[员工id]
3、发送ajax请求,请求服务端,同时提交员工id参数
4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
5、页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显
6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端7、服务端接收员工信息,并进行处理,完成后给页面响应
8、页面接收到服务端响应信息后进行相应处理

j0naa

@GetMapping("/{id}")  
public R<Employee> getByid(@PathVariable Long id){  
    Employee employee = employeeService.getById(id);  
    if (employee!=null){  
        return R.success(employee);  
  
    }  
    return R.error("没有查询到对应员工信息");  
}

[!note]
之所以可以完成修改,其实点击完成调用了我们之前写的通用的update方法,只要是根据id修改的表里某个字段都可以进行修改

公共字段自动填充

问题分析

前面我们完成了后台管理系统的员工管理功能开发,在新增员工时需要设置创建时间,创建人,修改时间,修改人等信息,在编辑员工时需要设置修改时间和修改人等信息.这些字段属于公共字段,也就是表中很多元素都有这些字段,如下:
29su2
能不能对于这些公共字段在某个地方统一处理,来简化开发呢答案就是使用Mybatis Plus提供的公共字段自动填充功能。

代码实现

MybatisPlus公共字段自动填充,也就是插入或者更新的时候为指定字段设置指定的值,使用它的好处是可以统一为这些字段进行处理,避免了代码重复.
实现步骤

  1. 在实体类的属性上加入@TableField的注解,指定自动填充的策略
  2. 按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHander接口
@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;
public class MyMetaObjecthandler implements MetaObjectHandler {  
    /**  
     * 插入操作自动填充  
     * @param metaObject  
     */  
 @Override  
 public void insertFill(MetaObject metaObject) {  
  metaObject.setValue("createTime", LocalDateTime.now());  
  metaObject.setValue("updateTime",LocalDateTime.now());  
  metaObject.setValue("createUser",new Long(1));  
  metaObject.setValue("updateUser",new Long(1));  
 }  
  
 /**  
  * 更新操作自动填充  
  * @param metaObject  
  */  
 @Override  
 public void updateFill(MetaObject metaObject) {  
  metaObject.setValue("updateTime",LocalDateTime.now());  
  metaObject.setValue("updateUser",new Long(1));  
 }  
 }

功能完善

前面我们已经完成了公共字段自动填充功能的代码开发,但是还有一个问题没有解决,就是我们在自动填充createUser和updateUser时设置的用户id是固定值,现在我们需要改造成动态获取当前登录用户的id。
有的同学可能想到,用户登录成功后我们将用户id存入了HttpSession中,现在我从HttpSession中获取不就行了?
注意,我们在MyMetaObjectHandler类中是不能获得HttpSession对象的,所以我们需要通过其他方式来获取登录用户id
可以使用ThreadLocal来解决此问题,它是JDK中提供的一个类。

在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
1、LoginCheckFilter的doFilter方法
2、EmployeeController的update方法
3、MyMetaObjectHandler的updateFill方法
可以在上面的三个方法中分别加入下面代码(获取当前线程id) :

long id = Thread.currentThread().getId() ;
log.info("线程id:{0}",id);

执行员工编辑功能进行验证,通过观察控制太输出可以发现,一次请求对应的线程id是相同24728
什么是ThreadLocal?

ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:

  • public void set(T value) 设置当前线程的线程局部变量的值
  • public T get() 返回当前线程所对应的线程局部变量的值
    我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id )。

实现步骤

  1. 编写BaseContext工具类,基于ThreadLocal封装的工具类
  2. 在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
  3. 在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id

[!note]
用设置静态方法的方式把ThreadLocal封装到工具类,然后这样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();  
    }  
  
}

在Filter中把用户id放入当前线程的线程局部变量的值(用户id)以便于之后使用
bobyh

同时修改之前公共字段自动填充的内容

metaObject.setValue("createUser",BaseContext.getCurrentId());  
metaObject.setValue("updateUser",BaseContext.getCurrentId());

新增分类

需求分析

后台管理系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类,当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台中添加一个套餐时需要选择一个餐品分类,在移动端也会按照菜品分类或者餐品分类来展示
gwkab

我们可以在后台分别添加菜品分类和套餐分类
c30nx

数据模型

256os

需要注意分类的名称是唯一的
cmr2z

需求分析

  • 员工列表页面可以对员工账号进行启用和禁用处理,账号禁用的员工不能登录系统,启用后方可登录系统
  • 需要注意的是,只有管理员(admin用户)可以对其他用户进行启用,禁用操作,所以普通用户登录系统后启用,禁用按钮不显示.
    93awo
    onqrl
    前端有一个名字为user的模型数据来之前存储的用户信息,如果user是admin也就是登录的用户是管理与,那么将显示禁用与启用信息.

代码开发

在开发业务之前需要把用到的类和接口基本结构准备好

  • 实体类category
  • Mapper接口CategoryMapper
  • 业务层接口CategoryService
  • 业务层实现类CategoryServiceImpl
  • 控制层CategoryController

操作流程
1、页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
3、Service调用Mapper操作数据库,保存数据

可以发现新增菜品分类和新增套餐分类的请求服务端地址和JSon格式相同,所以服务端只要提供一个方法处理即可
a318n

@RestController  
@RequestMapping("/category")  
public class CategoryController {  
    @Autowired  
    private CategoryService categoryService;  
   @PostMapping  
    public R<String > save(@RequestBody Category category){  
    log.info("category{}",category);  
    categoryService.save(category);  
    return R.success("新增分类成功");  
}  
}

删除分类

需求分析

在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。

代码开发

在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将参数(id)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service删除数据
3、Service调用Mapper操作数据库

hpqjb

@DeleteMapping  
public R<String> delete(Long id){  
    categoryService.removeById(id);  
    return R.success("删除成功")  
}

完善

和菜品和套餐已经连接的分类不能进行删除,因为我们没有设置外键,所以要用代码实现

@DeleteMapping  
public R<String> delete(Long ids){  
   categoryService.remove(ids);  
   return R.success("删除成功");  
}

[!note]
要在service中自己定义方法,而且可以调用别的service的方法

public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {  
    @Autowired  
    private DishService dishService;  
    @Autowired  
    private SetmealService setmealService;  
    @Override  
    public void remove(Long id) {  
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();  
        queryWrapper.eq(Dish::getCategoryId,id);  
        int count = dishService.count(queryWrapper);  
        if (count>0){  
            throw new CustomException("当期分类下关联了菜品,不能删除");  
        }  
        LambdaQueryWrapper<Setmeal> queryWrapper1 = new LambdaQueryWrapper<>();  
        queryWrapper1.eq(Setmeal::getCategoryId,id);  
        int count1 = setmealService.count(queryWrapper1);  
  
        if (count1>0){  
            throw new CustomException("当期分类下关联了套餐,不能删除");  
  
        }  
        //查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常  
        //查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常  
//正常删除分类  
        super.removeById(id);  
  
    }  
}

vnwag

文件上传与下载

文件上传介绍

文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
form表单的要求

  • method=“post” 采用post的方式提交数据
  • enctype=multpart/form-data" 采用multipart格式上传文件
  • type=“file” 使用input的file控件上传

举例

<form method="post" action="/common/upload" enctype="multipart/form-data"><input name="myFile" type="file" />
<input type="submit" value="提交" /></form>

服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:

  • commons-fileuplolad
  • commons-io
    Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如:
    17ave
@Slf4j  
@RestController  
@RequestMapping("/common")  
public class CommonController {  
    @Value("/${reggie.path}")  
    private String basePath;  
    @PostMapping("/upload")  
    public R<String> upload(MultipartFile file) throws IOException {  
  
        String originalFilename = file.getOriginalFilename();  
        //获取后缀  
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));  
        //使用UUID重新生成文件名,防止文件名称重复导致覆盖  
        String fileName = UUID.randomUUID().toString()+suffix;  
        //创建一个目录对象  
        File dir = new File(basePath);//判断当前目录是否存在  
        if(!dir.exists()){  
        //目录不存在,需要创建  
            dir.mkdirs();  
        }  
        log.info(basePath+fileName);  
        //file是一个临时文件需要指定保存的位置,否则本次请求完成后文件会被自动删除  
        file.transferTo(new File(basePath+ fileName));  
        return R.success(fileName);  
  
    }  
}

文件下载

s1xc8

@GetMapping("/download")  
public void download(String  name, HttpServletResponse response){  
  
    try {  
        //输入流,通过输入流读取文件内容  
        FileInputStream fileInputStream = new FileInputStream(basePath+name);  
        //输出流,通过输出流把文件写会浏览器,在浏览器展示图片  
        ServletOutputStream outputStream = response.getOutputStream();  
        int len=0;  
        byte[] bytes = new byte[1024];  
        while ((len=fileInputStream.read(bytes))!=-1){  
            outputStream.write(bytes,0,len);  
            outputStream.flush();  
        }  
        response.setContentType("image/jpeg");  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}

新增菜品

需求分析

后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。
1maor

数据模型

新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:

  • dish菜品表
  • dish_flavor菜品口味表

代码开发

准备工作
实体类DishFlavor(直接从课程资料中导入即可,Dish实体前面课程中已经导入过了)
Mapper接口DishFlavorMapper
业务层接口DishFlavorService
业务层实现类DishFlavorServicelmpl
控制层DishController

在开发代码之前,需要梳理一下新增菜品时前端页面和服务端的交互过程:

1、页面(badkend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中[
2、页面发送请求进行图片上传,请求服务端将图片保存到服务器
3、页面发送请求进行图片下载,将上传的图片进行回显
4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端
开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

导入dto

@Data  
public class DishDto extends Dish {  
  
    private List<DishFlavor> flavors = new ArrayList<>();  
  
    private String categoryName;  
  
    private Integer copies;  
}

[!danger]
DTO,全称为Data Transfer object,即数据传输对象,一般用于展示层与服务层之间的数据传输.

service


@Autowired  
private DishFlavorService dishFlavorService;  
//添加事务控制  
@Transactional  
@Override  
public void saveWithFlavor(DishDto dishDto) {  
//使用本身的save保存菜品
    this.save(dishDto);  
    Long id = dishDto.getId();  
    List<DishFlavor> flavors = dishDto.getFlavors();  
   flavors=  flavors.stream().map((item)->{  
        item.setDishId(id);  
        return item;  
    }).collect(Collectors.toList());  
  //保存口味信息
    dishFlavorService.saveBatch(flavors);  
}

controller
[[springmvc#9.1、@RequestBody]]

  @Autowired  
    private DishService dishService;  
    @Autowired  
    private DishFlavorService dishFlavorService;  
@PostMapping  
    public R<String> save(@RequestBody DishDto dishDto){  
        dishService.saveWithFlavor(dishDto);  
        return R.success("新增菜品成功");  
    }

菜品信息分页查询

系统中的菜品数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
xcz0q

代码开发

梳理交互流程
在开发代码之前,需要梳理一下菜品分页查询时前端页面和服务端的交互过程:
1、页面(backend/page/food/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据
2、页面发送请求,请求服务端进行图片下载,用于页面图片展示
开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

controller

@GetMapping("/page")  
public R<Page> page(int page,int pageSize,String name){  
Page<Dish> pageInfo = new Page<>(page,pageSize);  
 LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();  
 queryWrapper.like(name!=null,Dish::getName,name);  
 queryWrapper.orderByDesc(Dish::getCreateTime);  
 dishService.page(pageInfo,queryWrapper);  
 return R.success(pageInfo);  

}

但是有个问题
菜品表中的菜品分类字段是分类的id,前端要求的是菜品分类名称,这样才能展示
7m8zq
dtdze
所以我们要去处理一下

[!note]
因为原本的Dish里面没有categoryName这个属性,所以要借助DishDto,但是如何根据条件把Dto中的categoryName设置成我们需要的值,并且其余Dish有的我们都要有呢,需要先拷贝pageinfo中除了records的数据,(records)s是查询出来的数据列表,并不是我们需要的,需要把records处理好然后放回去,最后返回的是DishDto类型的Page

#工具类/对象拷贝
BeanUtils.copyProperties

controller

 @GetMapping("/page")  
    public R<Page> page(int page,int pageSize,String name){  
    Page<Dish> pageInfo = new Page<>(page,pageSize);  
    Page<DishDto> dishDtoPage= new Page<>();  
     LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();  
     queryWrapper.like(name!=null,Dish::getName,name);  
     queryWrapper.orderByDesc(Dish::getCreateTime);  
     dishService.page(pageInfo,queryWrapper);  
  
        BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");  
        List<Dish> records = pageInfo.getRecords();  
        List<DishDto> list= records.stream().map((item)->{  
            DishDto dishDto = new DishDto();  
            //拷贝Dish里面的属性到DishDto  
            BeanUtils.copyProperties(item,dishDto);  
            //查询到分类id  
            Long categoryId = item.getCategoryId();  
            Category category = categoryService.getById(categoryId);  
            //通过分类id查询到分类名称  
            String categoryName = category.getName();  
            dishDto.setCategoryName(categoryName);  
            return  dishDto;  
//最后通过collect收集成集合  
        }).collect(Collectors.toList());  
  
        dishDtoPage.setRecords(list);  
        //最后传回去的DishDto类型的分页信息  
        return R.success(dishDtoPage);  
  
    }

修改菜品

需求分析

在菜品管理列表页面,点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点金确定按钮完成菜品修改
rynwr

代码开发

1、页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示
2、页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显
3、页面发送请求,请求服务端进行图片下载,用于页图片回显
4、点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端
开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

service中自定义查询来完成查询的功能

public DishDto getByIdWithFlavor(Long id) {  
  
    //查询菜品基本信息  
    Dish dish = this.getById(id);  
  
    DishDto dishDto = new DishDto();  
    BeanUtils.copyProperties(dish,dishDto);  
    //查询当前菜品的口味信息  
    LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();  
    queryWrapper.eq(DishFlavor::getDishId,dish.getId());  
      
    List<DishFlavor> list = dishFlavorService.list(queryWrapper);  
    dishDto.setFlavors(list);  
    return dishDto;  
}

controller
因为我们要同时返回菜品信息和口味信息,但是这两个表是单独的,我们的dto中包含了我们需要的属性,完成dto的封装需要查询到dish信息和flavor信息然后封装进去返回

@GetMapping("/{id}")  
public R<DishDto> get(@PathVariable Long id){  
    DishDto byIdWithFlavor = dishService.getByIdWithFlavor(id);  
    return R.success(byIdWithFlavor);  
}

新增套餐

需求分析

套餐就是菜品的集合。
后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。
ddy2t
数据模型

新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal_dish表插入套餐和菜品关联数据所以在新增套餐时,涉及到两个表:

  • setmeal 套餐表
  • setmeal_dish套餐菜品关系表
    733yl
    o2ybb

代码开发

准备工作
需要准备好的类和接口

  • 实体类SetmealDish,直接导入即可
  • DTO SetmealDto 直接导入即可
  • Mapper接口SetmealDishMapper
  • 业务层接口SetmealDishService
  • 业务层实现类SetmealDishServiceImpl
  • 控制层SetmealController

梳理交互过程
在开发代码之前,需要梳理一下新增套餐时前端页面和服务端的交互过程:
1、页面(backend/page/combo/add.html)发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中
2、页面发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中
3、页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中
4、页面发送请求进行图片上传,请求服务端将图片保存到服务器
5、页面发送请求进行图片下载,将上传的图片进行回显
6、点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端
开发新增套餐功能,其实就是在服务端编写代码去处理前端页面发送的这6次请求即可。

/**  
 * 根据条件查询对应的菜品数据  
 * @param dish  
 * @return  
 */  
@GetMapping("/list")  
public R<List<Dish>> list(Dish dish){  
    LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();  
    queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
    //设置查询条件为状态为启用的  
queryWrapper.eq(Dish::getStatus,1);  
    queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);  
    List<Dish> list = dishService.list(queryWrapper);  
    return R.success(list);  
}
@Autowired  
private SetmealDishService setmealDishService;  
@Transactional  
@Override  
public void saveWithDish(SetmealDto setmealDto) {  
    //保存套餐的基本信息  
    this.save(setmealDto);  
    //保存套餐和菜品的关联信息  
    List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();  
setmealDishes.stream().map((item)->{  
        item.setSetmealId(setmealDto.getId());  
        return item;  
    }).collect(Collectors.toList());  
   setmealDishService.saveBatch(setmealDishes);  
  
  
}

controller

/**  
 * 新增套餐  
 * @param setmealDto  
 * @return  
 */  
@PostMapping  
public R<String> save(@RequestBody SetmealDto setmealDto){  
    setmealService.saveWithDish(setmealDto);  
    return R.success("新建套餐成功");  
}

套餐列表分页信息

在开发代码之前,需要梳理一下套餐分页查询时前端页面和服务端的交互过程:
1、页面(backend/page/combo/list.html)发送ajax请求,将分页查询参数(page.pageSize,name)提交到服务端,获取分页数据
2、页面发送请求,请求服务端进行图片下载,用于页面图片展示
开发套餐信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

@GetMapping("/page")  
public R<Page> page(int page,int pageSize,String name){  
     Page<Setmeal> pageInfo = new Page<>();  
     Page<SetmealDto> setmealDtoPage=new Page<>();  
  
    LambdaQueryWrapper<Setmeal> queryWrapper=new LambdaQueryWrapper<>();  
    queryWrapper.like(name!=null,Setmeal::getName,name);  
    queryWrapper.orderByDesc(Setmeal::getUpdateTime);  
    setmealService.page(pageInfo,queryWrapper);  
    BeanUtils.copyProperties(pageInfo,setmealDtoPage,"records");  
    List<Setmeal> records = pageInfo.getRecords();  
  
  
   List<SetmealDto> list=  records.stream().map((item)->{  
        SetmealDto setmealDto = new SetmealDto();  
       BeanUtils.copyProperties(item,setmealDto);  
        Long categoryId = item.getCategoryId();  
        Category category = categoryService.getById(categoryId);  
       if(category!=null){  
           String name1 = category.getName();  
           setmealDto.setCategoryName(name1);  
  
       }  
       return setmealDto;  
    }).collect(Collectors.toList());  
   setmealDtoPage.setRecords(list);  
    return R.success(setmealDtoPage);

删除套餐

需求分析

删除套餐有批量删除和单个删除,我们可以用数组接收传过来的id,这样就可以使用一个来实现批量删除和单个删除的功能,还要注意的是,我们在删除套餐的时候还要删除与之对应的套餐,菜品关系表的相关信息,(我套餐都没了还要套餐菜品关系干啥)

手机验证码登录

阿里云短信服务-介绍

阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。
应用场景:

  • 验证码
  • 短信通知
  • 推广短信

步骤

  1. 导入maven坐标
  2. 调用api
<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>

xrn7b

需求分析

为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。

手机验证码登录的优点:

  • 方便快捷,无需注册,直接登录
  • 使用短信验证码作为登录凭证,无需记忆密码
  • 安全
    登录流程:
    输入手机号>获取验证码>输入验证码>点击登录>登录成功
    注意:通过手机验证码登录,手机号是区分不同用户的标识。

数据模型

vg5r7

代码开发

流程梳理

在开发代码之前,需要梳理一下登录时前端页面和服务端的交互过程:
1、在登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信
2、在登录页面输入验证码,点击【登录】按钮,发送ajax请求,在服务端处理登录请求
开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

地址簿

需求分析

地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址。
sy0v8

数据模型
slkv2

导入功能代码
功能代码清单

  • 实体类AddressBook(直接从课程资料中导入即可)
  • Mapper接口AddressBookMapper
  • 业务层接口AddressBookService
  • 业务层实现类AddressBookServicelmpl
  • 控制层AddressBookController (直接从课程资料中导入即可)

菜品展示

用户登录成功后跳转到系统首页,在首页需要根据分类来展示菜品和套餐。如果菜品设置了口味信息,需要展示
选择规格按钮,否则显示按钮。
rsp19

代码开发

在开发代码之前,需要梳理一下前端页面和服务端的交互过程:
1、页面(front/index.html)发送ajax请求,获取分类数据(菜品分类和套餐分类)
2、页面发送ajax请求,获取第一个分类下的菜品或者套餐
开发菜品展示功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。
注意:首页加载完成后,还发送了一次ajax请求用于加载购物车数据,此处可以将这次请求的地址暂时修改一下,从静态json文件获取数据,等后续开发购物车功能时再修改回来,如下:
r79hc

菜品展示的同时获得菜品的口味信息

  @GetMapping("/list")  
    public R<List<DishDto>> list(Dish dish){  
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();  
        queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());  
        //设置查询条件为状态为启用的  
        queryWrapper.eq(Dish::getStatus,1);  
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);  
        List<Dish> list = dishService.list(queryWrapper);  
  
  
  
        List<DishDto> dishDtoList  = list.stream().map((item) -> {  
            DishDto dishDto = new DishDto();  
            //拷贝Dish里面的属性到DishDto  
            BeanUtils.copyProperties(item, dishDto);  
            //查询到分类id  
            Long categoryId = item.getCategoryId();  
            Category category = categoryService.getById(categoryId);  
            if (category != null) {  
                //通过分类id查询到分类名称  
                String categoryName = category.getName();  
                dishDto.setCategoryName(categoryName);  
            }  
  
            Long dishid = item.getId();//获取dishid  
            //获取口味表  
            LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();  
            dishFlavorLambdaQueryWrapper.eq(DishFlavor::getDishId,dishid);  
  
            List<DishFlavor> dishFlavors = dishFlavorService.list(dishFlavorLambdaQueryWrapper);  
            dishDto.setFlavors(dishFlavors);  
  
            return dishDto;  
//最后通过collect收集成集合  
        }).collect(Collectors.toList());  
        return R.success(dishDtoList);  
    }

查询套餐信息

@GetMapping("/list")  
public R<List<Setmeal>> listR(@RequestBody 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());  
    List<Setmeal> list = setmealService.list(queryWrapper);  
    return R.success(list);  
  
}

购物车

需求分析

移动端用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息
则需要选择规格后才能
加入购物车;对于套餐来说,可以直接点击将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。
a1q63

代码实现

1、点击[加入购物车]
或者[+]按钮,页面发送ajax请求,请求服务端,将菜品或者套餐添加到购物车
2、点击购物车图标,页面发送ajax请求,请求服务端查询购物车中的菜品和套餐
3、点击清空购物车按钮,页面发送ajax请求,请求服务端来执行清空购物车操作
开发购物车功能,其实就是在服务端编写代码去处理前端页面发送的这3次请求即可。

准备工作
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
●实体类ShoppingCart(直接从课程资料中导入即可)
●Mapper接口ShoppingCartMapper
●业务层接口ShoppingCartService
●业务层实现类ShoppingCartServicelmpl
●控制层ShoppingCartController

controller

@PostMapping("/add")  
public R<ShoppingCart> add(@RequestBody Shopping  
    //设置用户id  
    Long currentId = BaseContext.getCurrentId();  
    shoppingCart.setUserId(currentId);  
  
    //查询当前商品是否在购物车里面  
    Long dishId = shoppingCart.getDishId();   //  
    LambdaQueryWrapper<ShoppingCart> queryWrappe  
  
    queryWrapper.eq(ShoppingCart::getUserId, sho  
  
    if (dishId != null) {  
        //参加到购物车的是菜品  
        queryWrapper.eq(ShoppingCart::getDishId,  
    } else {  
        queryWrapper.eq(ShoppingCart::getSetmeal  
    }  
  
    ShoppingCart one = shoppingCartService.getOn  
    if (one != null) {  
        //如果已经存在只需要商品数量增加即可  
        Integer number = one.getNumber();  
        one.setNumber(number+1);  
        shoppingCartService.updateById(one);  
    } else {  
        //如果不存在,则添加购物车,数量默认就是1  
        shoppingCart.setNumber(1);  
        shoppingCartService.save(shoppingCart);  
        one=shoppingCart;  
    }  
  
  
    //不存在则添加到购物车  
    return R.success(one);  
  
}

查看购物车

 * 
 @GetMapping("/list")  
 public R<List<ShoppingCart>> listR() {  
     LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();  
     queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());  
     queryWrapper.orderByAsc(ShoppingCart::getCreateTime);  
     List<ShoppingCart> list = shoppingCartService.list(queryWrapper);  
     return R.success(list);  
 }

用户下单

需求分析

dtpbm
数据模型
用户下单业务对应的数据表为orders表和order_detail表:

  • orders:订单表
  • order_detail:订单明细表

rn18i

301ot

在开发代码之前,需要梳理一下用户下单操作时前端页面和服务端的交互过程:1、在购物车中点击
去结算按钮,页面跳转到订单确认页面
2、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址
3、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据
4、在订单确认页面点击去支付去支付按钮,发送ajax请求,请求服务端完成下单操作
开发用户下单功能,其实就是在服务端编写代码去处理前端页面发送的请求即可。

准备工作

在开发业务功能前,先将需要用到的类和接口基本结构创建好:
实体类Orders、OrderDetail (直接从课程资料中导入即可)
Mapper接口OrderMapper、OrderDetailMapper
业务层接口 OrderService、OrderDetailService
业务层实现类OrderServicelmpl、OrderDetailServicelmpl
控制层OrderController、OrderDetailController

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

瑞吉外卖业务开发 的相关文章

  • 从txt文件中读取数据而不下载它?

    我想从提供的文本文件中解析信息 有没有一种方法可以在应用程序中执行此操作 而无需先下载文件 以某种方式传输文本内容 打开到 URL 的 Http 连接 使用内置 HttpURLConnection 或使用 commons httpclien
  • 在Java Servlet中获取通过jquery ajax发送的参数[重复]

    这个问题在这里已经有答案了 我在网上搜索这个主题 但找不到有效的示例 我会很高兴有人能给我帮助 这就是我测试的 ajax url GetJson type POST dataType json contentType application
  • 在java代码中创建postgresql表

    我有一个与 postgreSQL 数据库连接的 java 代码 现在 我希望当它连接到数据库时 我还将创建数据库表 但我的问题是 它不会创建数据库 我不知道问题是什么 这是我的代码 Statement st null ResultSet r
  • Java:为什么.class文件中的方法类型包含返回类型,而不仅仅是签名?

    class 文件的常量池中有一个 NameAndType 结构 它用于动态绑定 该类可以 导出 的所有方法都被描述为 签名 返回类型 喜欢 getVector Ljava util Vector 当某些 jar 中方法的返回类型发生更改时
  • Spring 从 JBoss 上下文加载 PropertySourcesPlaceholderConfigurer

    我有一个使用 PropertySourcesPlaceholderConfigurer 的 spring 3 1 应用程序加载设置 我想管理测试和生产环境 只需从服务器上下文加载设置覆盖本地文件属性中指定的设置 下一个示例在 Tomcat
  • java:为什么主线程等待子线程完成

    我有一个简单的java程序 主线程 main 创建并启动另一个线程t class T extends Thread Override public void run while true System out println Inside
  • 我的 Kafka 流应用程序刚刚退出,代码为 0,什么也不做

    为了尝试 Kafka 流 我这样做了 public static void main String args final StreamsBuilder builder new StreamsBuilder final Properties
  • 始终将双精度舍入

    我怎么总是能把一个double to an int 并且永远不要将其四舍五入 我知道Math round double 但我希望它始终向上舍入 所以如果是的话3 2 四舍五入为 4 您可以使用Math ceil method 请参阅Java
  • BigDecimal汇总统计

    我有一个 BigDecimal 列表 List
  • React Native v0.71.8 React-native-vector-icons 你看不到的图标

    我在用react native版本v0 71 8 我安装了react native vector icons库 但图标未显示 似乎链接在最新版本的 React Native 中不再起作用 所以我按照说明进行操作 但它不再编译 出现以下错误
  • 在 Kotlin 中声明静态属性?

    My Java code public class Common public static ModelPengguna currentModelPengguna public class Common companion object v
  • 添加 char 和 int

    据我了解 字符是一个字符 即一个字母 一个digit 标点符号 制表符 空格或类似的东西 因此 当我这样做时 char c 1 System out println c 输出 1 正是我所期望的 那么为什么当我这样做时 int a 1 ch
  • 如何在一次操作中使用 Thymeleaf 检查 null 和空条件?

    有什么方法可以检查 Thymeleaf 中的 null 和empty 条件吗 方法一 1 variable1 variable2 variable3 2 variable null 3 variable 如果我们结合两个条件 例如 vari
  • 线程数组?

    所以我在理解如何避免线程的顺序执行时遇到了问题 我试图创建一个线程数组并在单独的循环中执行 start 和 join 函数 这是我现在拥有的代码示例 private static int w static class wThreads im
  • 在 Tensorflow-lite Android 中将位图转换为 ByteBuffer(浮点)

    在用于图像分类的tensorflow lite android演示代码中 图像首先转换为ByteBuffer格式以获得更好的性能 这种从位图到浮点格式的转换以及随后到字节缓冲区的转换似乎是一个昂贵的操作 循环 按位运算符 float mem
  • 如果 Modelmapper 中的整个属性为空,如何排除它们

    ModelMapper 是否 http modelmapper org http modelmapper org 支持什么排除属性 如果该值为空 我刚刚找到了 PropertyMap 但这对我来说是一种限制 因为我必须描述我想要的特定属性
  • 为什么/何时应该使用泛型方法?

    学习Java的时候遇到过通用方法 public
  • Spring MVC:通用 DAO 和服务类

    我正在 Spring MVC 中编写网页 我使用 Generic DAO 编写了所有 DAO 现在我想重写我的服务类 我该如何写 通用服务 我的 DAO 如下 DAO package net example com dao import j
  • ebean 映射到 BYTEA 的数据类型是什么?

    我有一个游戏 2 0 2 需要在数据库中存储一些文件的应用程序 我们使用 Ebean 作为 ORM 我相信我的数据库中需要一个 BYTEA 列来存储该文件 但我不确定在我的模型中使用什么数据类型 我应该使用某种Blob 或者只是一个byte
  • MyBatis 枚举的使用

    我知道以前有人问过这个问题 但我无法根据迄今为止找到的信息实施解决方案 所以也许有人可以向我解释一下 我有一个表 状态 它有两列 id 和 name id是PK 我不想使用 POJO Status 而是使用枚举 我创建了这样一个枚举 如下所

随机推荐

  • Django 从零开始实现简单的restful应用

    安装Django 我是在windows上安装的 之后在linux上试一下再补充上来 可以通过pip直接安装 pip install django 需要使用到mysql 数据库 pip install mysqlclient 新建项目 安装以
  • Node =》 nodemon

    在编写调试Node js的时候 如果修改了项目的代码 则需要频繁的手动close掉 然后再重新启动 非常频繁 现在可通过nodemon这个工具 能够监听项目文件的变动 当代码被修改后 nodemon会帮我们重新启动项目 极大方便了开发和调试
  • 在CSDN学Golang云原生(git)

    一 git的工作流程 Golang的Git工作流程与其他语言的Git工作流程类似 通常包括以下步骤 创建分支 在本地代码库中创建一个新的分支 该分支用于开发新功能或修复错误 编写代码 在创建的分支上进行编码 并将更改提交到本地版本控制库中
  • 【Egg从基础到进阶】一:Egg项目初始化及基础入门

    目录 Egg js 为企业级框架和应用而生 我们希望由 Egg js 孕育出更多上层框架 帮助开发团队和开发人员降低开发和维护成本 使用Egg 脚手架创建一个Egg项目 node gt 14 20 0 app router app cont
  • Java架构师技术进阶路线图详解

    在企业里 一名架构师已经可以算是高端人物了 但架构师也是需要学习的 任何人与事物都需要学习 下面我们就来了解一下Java架构师到底该如何进阶 请看如下 一 阅读源码 深入的Java学习 经典源码阅读不可少 常见的设计模式 编码必备 Spri
  • 如何给elementui中表格的行设置样式

  • 【C++ 存储和读取二进制文件以及类的实例】

    很多时候程序运行时的数据都以类的对象的形式存储 那么如何将这些对象保存使得下一次运行程序的时候可以直接使用呢 Step 1 gt 首先我们需要知道如何读写二进制文件 inline size t readBinaryFile const ch
  • Elasticsearch基础1——搜索引擎发展史和工作流程、es/es-head/kibana的基础安装

    文章目录 一 搜索引擎 1 1 搜索引擎的发展背景 1 2 Lucene和Elasticsearch 1 3 Solr和Elasticsearch对比 1 4 数据搜索方式 1 5 搜索引擎 1 5 1 搜索引擎工作流程 1 5 2 网络爬
  • SVN:This client is too old to work with working copy…解决办法

    svn This client is too old to work with working copy 由于svn工作拷贝目录下都有个 svn目录 里面保存着svn需要的一些版本信息等 当客户端软件升级后 里面的信息也相应增加以支持更多的
  • 如何查看共享服务器文件夹权限设置,如何共享服务器文件夹权限设置

    如何共享服务器文件夹权限设置 内容精选 换一换 Linux操作系统的弹性云服务器默认只能通过root帐号使用mount命令进行挂载文件系统 但可通过赋予其他普通用户root权限 达到使非root的普通用户能够在弹性云服务器上使用mount命
  • 双线macd指标参数最佳设置_15分钟macd参数设置方法 15分钟k线macd指标如何判断买卖点...

    炒股投资过程中 我们通常会借助多种指标来判断股票买卖时机 今天为大家分享判断股票买卖点的组合是15分钟k线与macd指标 那么15分钟macd参数设置方法是什么 15分钟k线macd指标如何判断买卖点 下面小编为大家来详细介绍下15分钟线m
  • 网络穿透代理局域网

    网址 穿透网址 开启通道 开始代理
  • ARM中的---汇编指令

    一 带点的 一般都是ARM GNU伪汇编指令 1 text data bss 依次表示的是 以下是代码段 以下是初始化数据段 以下是未初始化数据段 2 global 定义一个全局符号 通常是为ld使用 比如经常看到的 global star
  • 利用树莓派搭建简易服务器

    读研以来笔者一直负责实验室的网络维护 可以说是实验室名副其实的首席大网管 整个实验室是从学校网络中心购买了一个教育网的公网IP地址和带宽 公网IP绑定了实验室的主路由器 而主路由器就在笔者卡位的旁边 有一天笔者突发奇想 拿了手头的树莓派3结
  • Micropython——报错解决:TypeError: object with buffer protocol required

    报错 检查报错处代码 仔细检查可以发现 是括号放错位置 导致函数无法正常执行 故报错 一般情况下 Micropython除硬件如定时器中断内存溢出等硬件本身报错外 其他均为语法错误
  • 统计学习方法学习笔记(一)————统计学习方法概论

    1 统计学习 1 统计学习概念 统计学习 statistical learning 是关于计算机基于数据构建概率统计模型并运用模型对数据进行预测与分析的一门学科 统计学习也称为统计机器学习 statistical machine learn
  • MLIR入门系列系列学习笔记

    目录 1 名字解释 这一定义包含3个关键元素 2 代码演示 2 1 环境准备 2 2 编译llvm project 2 3 测试解析 2 3 1 源程序 2 3 2 将源程序生成抽象语法树 AST 3 MLIR三要素 3 1 MLIRGen
  • 为什么在组件内部data是一个函数而不是一个对象?

    为什么在组件内部data是一个函数而不是一个对象 因为在组件复用的时候会重新生成一个对象 而data是一个对象的话 因为对象是引用数据类型 data数据会被复用 而当data是一个函数的时候每次调用的时候就会返回一个新的data对象 vue
  • 安装--centos7上使用kubeadm安装三节点的k8s集群

    安装文档 https kubernetes io zh cn docs setup production environment tools kubeadm install kubeadm 参考 https blog csdn net qq
  • 瑞吉外卖业务开发

    一 软件开发整体介绍 软件开发流程 需求分析 产品原型 需求规格说明书 设计 产品文档 UI界面设计 概要设计 详细设计 数据库设计 编码 项目代码 单元测试 测试 测试用例 测试报告 上线运维 软件环境安装 配置 角色分工 项目经理 对整