课程结构

基础数据模块

  • 项目概述:系统架构全景
  • 环境搭建:开发基石
  • 员工管理:权限体系构建
  • 分类管理:业务脉络梳理
  • 菜品管理:核心数据建模
  • 套餐管理实战:组合艺术

点餐业务模块

  • 店铺营业状态设置 → 业务开关
  • 微信登录 → 无缝认证
  • 缓存商品 → 性能优化
  • 购物车 → 临时存储区
  • 用户下单 → 交易核心
  • 订单支付与管理 → 资金流
  • 历史订单 → 数据沉淀
  • 订单状态定时处理 → 自动化
  • 来电提醒和催单 → 即时通讯

统计报表模块

  • 图形报表:数据可视化
  • Excel报表:结构化输出

软件开发整体介绍

  • 软件开发流程
    • 需求分析
      • 需求规格说明书
      • 产品原型
    • 设计阶段
      • ui设计
      • 数据库设计
      • 接口设计
    • 编码阶段
      • 编写项目代码
      • 单元测试
    • 测试
      • 测试用例
      • 测试报告
    • 上线运维
      • 软件环境安装
      • 配置
  • 角色分工
    • 项目经理 对整个项目负责 人物分配 把控进度
    • 产品经理 根据需求调研 输出调研文档 产品原型
    • ui设计师 根据产品原型输出界面效果图
    • 架构师 项目整体架构实现 技术选型等
    • 开发工程师 代码实现
    • 测试工程师 编写软件测试用例 输出测试报告
    • 运维工程师 软件环境搭建 项目上线
  • 软件环境
    • 开发环境(developement) 开发人员在开发阶段使用的环境 一般外部用户无法访问
    • 测试环境(testing) 专门给测试人员使用的环境 用于测试项目 一般外部用户无法访问
    • 生产环境(production) 正式对外提供服务的环境

项目介绍

本项目(苍穹外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括 系统管理后台 和 小程序端应用 两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的分类、菜品、套餐、订单、员工等进行管理维护,对餐厅的各类数据进行统计,同时也可进行来单语音播报功能。小程序端主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单、支付、催单等。

  • 管理端
    餐饮企业内部员工使用
    员工登录/退出 , 员工信息管理 , 分类管理 , 菜品管理 , 套餐管理 , 菜品口味管理 , 订单管理 ,数据统计,来单提醒。
模块 描述
登录/退出 内部员工必须登录后,才可以访问系统管理后台
员工管理 管理员可以在系统后台对员工信息进行管理,包含查询、新增、编辑、禁用等功能
分类管理 主要对当前餐厅经营的 菜品分类 或 套餐分类 进行管理维护, 包含查询、新增、修改、删除等功能
菜品管理 主要维护各个分类下的菜品信息,包含查询、新增、修改、删除、启售、停售等功能
套餐管理 主要维护当前餐厅中的套餐信息,包含查询、新增、修改、删除、启售、停售等功能
订单管理 主要维护用户在移动端下的订单信息,包含查询、取消、派送、完成,以及订单报表下载等功能
数据统计 主要完成对餐厅的各类数据统计,如营业额、用户数量、订单等
  • 用户端
    移动端应用主要提供给消费者使用。
    微信登录 , 收件人地址管理 , 用户历史订单查询 , 菜品规格查询 , 购物车功能 , 下单 , 支付、分类及菜品浏览。
模块 描述
登录/退出 用户需要通过微信授权后登录使用小程序进行点餐
点餐-菜单 在点餐界面需要展示出菜品分类/套餐分类, 并根据当前选择的分类加载其中的菜品信息, 供用户查询选择
点餐-购物车 用户选中的菜品就会加入用户的购物车, 主要包含 查询购物车、加入购物车、删除购物车、清空购物车等功能
订单支付 用户选完菜品/套餐后, 可以对购物车菜品进行结算支付, 这时就需要进行订单的支付
个人信息 在个人中心页面中会展示当前用户的基本信息, 用户可以管理收货地址, 也可以查询历史订单数据

技术栈全景

分层架构

层级 技术组件
用户层 Vue.js, 微信小程序, ElementUI
网关层 Nginx(反向代理+负载均衡)
应用层 SpringBoot, JWT, WebSocket
数据层 MySQL, Redis, MyBatis

关键技术点

  • 安全认证:JWT令牌机制
  • 性能优化:Redis缓存+Spring Cache
  • 异步处理:Spring Task定时任务
  • 文件存储:阿里云OSS对象存储
  • 文档管理:Swagger自动化接口文档
  • 数据导出:POI Excel操作

开发环境搭建

前端环境搭建

  • 基于nginx
  • 从黑马提供的资料中找到nginx保存到非中文目录即可
    • debug日志
      • 启动nginx服务器后 访问官方提供的localhost:80无法正常进入前端页面
      • 查看error.log错误日志文件得到报错信息
      • bind() to 0.0.0.0:80 failed
      • 了解到端口80被占用
      • 在nginx.conf文件中将监听端口修改为82
      • 重启nginx服务器 使用http://localhost:82正常访问到了前端页面

后端环境搭建

  • 基于maven进行项目构建 并且进行分模块开发
  • 了解项目结构
序号 名称 说明
1 sky-take-out maven父工程,统一管理依赖版本,聚合其他子模块
2 sky-common 子模块,存放公共类,例如:工具类、常量类、异常类等
3 sky-pojo 子模块,存放实体类、VO、DTO等
4 sky-server 子模块,后端服务,存放配置文件、Controller、Service、Mapper等
分析sky-common模块的每个包的作用:
名称 说明
constant 存放相关常量类
context 存放上下文类
enumeration 项目的枚举类存储
exception 存放自定义异常类
json 处理json转换的类
properties 存放SpringBoot相关的配置属性类
result 返回结果类的封装
utils 常用工具类
分析sky-pojo模块的每个包的作用:
名称 说明
Entity 实体,通常和数据库中的表对应
DTO 数据传输对象,通常用于程序中各层之间传递数据
VO 视图对象,为前端展示数据提供的对象
POJO 普通Java对象,只有属性和对应的getter和setter

分析sky-server模块的每个包的作用:

名称 说明
config 存放配置类
controller 存放controller类
interceptor 存放拦截器类
mapper 存放mapper接口
service 存放service类
SkyApplication 启动类

使用git进行版本控制

- 创建本地仓库
	- 在gitignore中配置我们不希望看见的文件
	- ```
  
**/target/  
*.idea  
*.iml  
*.class  
*.Test.java  
**/test/
  • 创建本地git项目
  • 将git项目同步至github
  • debug日志
    • 尝试将代码push至github时报错
    • SSL 证书问题:无法获取本地颁发者证书
    • 使用以下代码更新 Git 的证书捆绑包 成功解决
# 重置 Git 的 SSL 验证设置
git config --global http.sslBackend schannel  # Windows 系统专用
git config --global http.sslVerify true

# 或者使用 OpenSSL 后端
git config --global http.sslBackend openssl

数据库环境搭建

  • 通过数据库表语句创建数据库表结构
  • 使用提供的sky.sql脚本
  • 在datagrip中执行
  • 创建sky-take-out架构 共11张表

前后端联调

  • 在maven中先进行编译
  • debug日志
    • 第一次启动项目报错`java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field ‘com.sun.tools.javac.tree.JCTree qualid’
    • 应该是lombok版本不兼容
    • 在pom.xml中添加最新的lombok依赖
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version> <!-- 使用最新版本 -->
    <scope>provided</scope>
</dependency>
  • 重新编译运行 项目正常启动

  • debug日志

    • 启动再次报错`Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port.
    • 端口8080已经被其他进程占用。我们需要解决端口冲突问题。
    • 很好解决 修改端口即可
    • 在application.yml中修改端口号即可
    • 注:对应前端配置文件也需要修改端口号
    • 同时记得修改数据库在yml中的配置

登录逻辑三层架构

Controller层:

在sky-server模块中,com.sky.controller.admin.EmployeeController

/**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);
		//调用service方法查询数据库
        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }

Service层:

在sky-server模块中,com.sky.service.impl.EmployeeServiceImpl

/**
     * 员工登录
     *
     * @param employeeLoginDTO
     * @return
     */
    public Employee login(EmployeeLoginDTO employeeLoginDTO) {
        String username = employeeLoginDTO.getUsername();
        String password = employeeLoginDTO.getPassword();

        //1、根据用户名查询数据库中的数据
        Employee employee = employeeMapper.getByUsername(username);

        //2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
        if (employee == null) {
            //账号不存在
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        //密码比对
        if (!password.equals(employee.getPassword())) {
            //密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }

        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }

        //3、返回实体对象
        return employee;
    }

Mapper层:

在sky-server模块中,com.sky.mapper.EmployeeMapper

package com.sky.mapper;

import com.sky.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface EmployeeMapper {

    /**
     * 根据用户名查询员工
     * @param username
     * @return
     */
    @Select("select * from employee where username = #{username}")
    Employee getByUsername(String username);

}


前后端如何联通?

使用nginx反向代理 将前端发送的动态请求由nginx转发到后端服务器

使用反向代理的好处

  • 提高访问速度
    • nginx可以进行缓存 相同接口情况下不需要访问服务端即可返回数据
  • 进行负载均衡
    • 可以将大量的请求按照我们指定的方式均衡的分配给每台服务器
  • 保证后端服务安全
    • 后台服务地址不会直接暴露 而是通过nginx转发

如何配置反向代理?

server{
    listen 80;
    server_name localhost;
    
    location /api/{
        proxy_pass http://localhost:8080/admin/; #反向代理
    }
}

如上代码的含义是:监听80端口号, 然后当我们访问 http://localhost:80/api/../…这样的接口的时候,它会通过 location /api/ {} 这样的反向代理到 http://localhost:8080/admin/上来。

同样 在nginx.conf中

# 反向代理,处理管理端发送的请求
location /api/ {
	proxy_pass   http://localhost:8080/admin/;
    #proxy_pass   http://webservers/admin/;
}

负载均衡配置

upstream webservers{
    server 192.168.100.128:8080;
    server 192.168.100.129:8080;
}
server{
    listen 80;
    server_name localhost;
    
    location /api/{
        proxy_pass http://webservers/admin;#负载均衡
    }
}

**upstream:**如果代理服务器是一组服务器的话,我们可以使用upstream指令配置后端服务器组。

如上代码的含义是:监听80端口号, 然后当我们访问 http://localhost:80/api/../…这样的接口的时候,它会通过 location /api/ {} 这样的反向代理到 http://webservers/admin,根据webservers名称找到一组服务器,根据设置的负载均衡策略(默认是轮询)转发到具体的服务器。

**注:**upstream后面的名称可自定义,但要上下保持一致。

nginx 负载均衡策略:

名称 说明
轮询 默认方式
weight 权重方式,默认为1,权重越高,被分配的客户端请求就越多
ip_hash 依据ip分配方式,这样每个访客可以固定访问一个后端服务
least_conn 依据最少连接方式,把请求优先分配给连接数少的后端服务
url_hash 依据url分配方式,这样相同的url会被分配到同一个后端服务
fair 依据响应时间方式,响应时间短的服务将会被优先分配

具体配置方式:

轮询:

upstream webservers{
    server 192.168.100.128:8080;
    server 192.168.100.129:8080;
}

weight:

upstream webservers{
    server 192.168.100.128:8080 weight=90;
    server 192.168.100.129:8080 weight=10;
}

ip_hash:

upstream webservers{
    ip_hash;
    server 192.168.100.128:8080;
    server 192.168.100.129:8080;
}

least_conn:

upstream webservers{
    least_conn;
    server 192.168.100.128:8080;
    server 192.168.100.129:8080;
}

url_hash:

upstream webservers{
    hash &request_uri;
    server 192.168.100.128:8080;
    server 192.168.100.129:8080;
}

fair:

upstream webservers{
    server 192.168.100.128:8080;
    server 192.168.100.129:8080;
    fair;
}

完善登录功能

  • 问题: 员工表中密码是明文存储 安全性太低
  • 解决方案 使用md5加密方式对明文密码加密
  • 实现步骤
    • 在employee表中修改秘密
    • 修改Java代码,前端提交的密码进行MD5加密后再跟数据库中密码比对
    • 打开EmployeeServiceImpl.java,修改比对密码

导入接口文档

  • 前后端分离开发流程

    • 定义接口 确定接口的路径 请求方式 传入参数 返回参数
    • 前端开发人员和后端开发人员并行开发 同时也可以自测
    • 前后端人员进行联调测试
    • 提交给测试人员进行最终测试
  • 导入步骤

    • 黑马官方教程使用yapi
    • 笔者使用自己更为熟悉的apifox
    • 考虑到黑马提供的接口文档格式为yapi独有格式 apifox无法识别
    • 写了一个脚本将接口文档转换为openAPI 3.0 格式
import json
import re
from datetime import datetime
def convert_yapi_to_openapi(yapi_data, title="Converted API", version="1.0.0"):
    """
    将 YAPI 格式转换为 OpenAPI 3.0 格式
    """
    # 创建 OpenAPI 基本结构
    openapi = {
        "openapi": "3.0.0",
        "info": {
            "title": title,
            "version": version,
            "description": f"Converted from YAPI on {datetime.now().strftime('%Y-%m-%d')}"
        },
        "servers": [{"url": "http://localhost:8989"}],
        "paths": {},
        "components": {
            "schemas": {},
            "securitySchemes": {
                "BearerAuth": {
                    "type": "http",
                    "scheme": "bearer"
                }
            }
        },
        "security": [{"BearerAuth": []}]
    }
    # 提取所有组件定
    schema_definitions = {}
    # 遍历每个分类
    for category in yapi_data:
        category_name = category.get("name", "Uncategorized")
        print(f"Processing category: {category_name}")
        # 遍历分类中的接口
        for api in category.get("list", []):
            try:
                path = api.get("path", "")
                method = api.get("method", "get").lower()
                if not path or not method:
                    print(f"  Skipping invalid API: missing path or method")
                    continue
                # 创建路径对象
                if path not in openapi["paths"]:
                    openapi["paths"][path] = {}
                # 处理参数
                parameters = []
                # 1. 处理查询参数
                for query_param in api.get("req_query", []):
                    parameters.append({
                        "name": query_param.get("name", ""),
                        "in": "query",
                        "description": query_param.get("desc", ""),
                        "required": query_param.get("required") == "1",
                        "schema": {
                            "type": _infer_type(query_param.get("type", "string")),
                            "example": query_param.get("value", "")
                        }
                    })
                # 2. 处理路径参数 (从路径中提取)
                path_params = re.findall(r'\{(\w+)\}', path)
                for param_name in path_params:
                    parameters.append({
                        "name": param_name,
                        "in": "path",
                        "required": True,
                        "schema": {"type": "string"}
                    })
                # 3. 处理请求头
                for header in api.get("req_headers", []):
                    if header.get("name", "").lower() == "authorization":
                        # 跳过授权头,使用全局安全方案
                        continue
                    parameters.append({
                        "name": header.get("name", ""),
                        "in": "header",
                        "description": header.get("desc", ""),
                        "required": header.get("required") == "1",
                        "schema": {"type": "string"}
                    })
                # 处理请求体
                request_body = None
                req_body_type = api.get("req_body_type", "")
                if req_body_type == "json" and api.get("req_body_other"):
                    try:
                        # 解析 JSON Schema
                        body_schema = json.loads(api["req_body_other"])
                        # 提取并存储组件定义
                        if "$$ref" in body_schema:
                            ref_name = body_schema["$$ref"].split('/')[-1]
                            schema_definitions[ref_name] = body_schema
                            body_schema = {"$ref": f"#/components/schemas/{ref_name}"}
                        request_body = {
                            "description": "Request body",
                            "required": True,
                            "content": {
                                "application/json": {
                                    "schema": body_schema
                                }
                            }
                        }
                    except json.JSONDecodeError:
                        print(f"  Error parsing request body for {path}: {api['req_body_other']}")
                # 处理响应体
                responses = {}
                if api.get("res_body"):
                    try:
                        res_body = json.loads(api["res_body"])
                        # 提取并存储组件定义
                        if "$$ref" in res_body:
                            ref_name = res_body["$$ref"].split('/')[-1]
                            schema_definitions[ref_name] = res_body
                            res_body = {"$ref": f"#/components/schemas/{ref_name}"}
                        responses["200"] = {
                            "description": "Successful response",
                            "content": {
                                "application/json": {
                                    "schema": res_body
                                }
                            }
                        }
                    except json.JSONDecodeError:
                        print(f"  Error parsing response body for {path}: {api['res_body']}")
                # 如果没有响应体,添加默认响应
                if not responses:
                    responses["200"] = {
                        "description": "Successful response",
                        "content": {}
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "code": {"type": "integer"},
                                        "msg": {"type": "string"},
                                        "data": {}
                                    }
                                }
                            }
                        }
                    }
                # 创建接口对象
                openapi["paths"][path][method] = {
                    "summary": api.get("title", "Untitled"),
                    "description": api.get("desc", ""),
                    "tags": [category_name],
                    "parameters": parameters,
                    "responses": responses
                }
                if request_body:
                    openapi["paths"][path][method]["requestBody"] = request_body
                print(f"  Added {method.upper()} {path}")
            except Exception as e:
                print(f"  Error processing API {api.get('title')}: {str(e)}")
    # 添加组件定义
    openapi["components"]["schemas"] = schema_definitions
    return openapi
def _infer_type(type_str):
    """推断参数类型"""
    type_str = type_str.lower()
    if "int" in type_str:
        return "integer"
    if "bool" in type_str:
        return "boolean"
    if "file" in type_str:
        return "string"
    return "string"
def main():
    # 输入和输出文件路径
    input_file = "苍穹外卖-用户端接口.json"
    output_file = "苍穹外卖-用户端接口open.json"
    print(f"Starting conversion from {input_file} to {output_file}")
    try:
        # 读取 YAPI 导出的 JSON
        with open(input_file, "r", encoding="utf-8") as f:
            yapi_data = json.load(f)
        # 执行转换
        openapi_data = convert_yapi_to_openapi(
            yapi_data,
            title="苍穹外卖API",
            version="1.0.0"
        )
        # 保存转换结果
        with open(output_file, "w", encoding="utf-8") as f:
            json.dump(openapi_data, f, ensure_ascii=False, indent=2)
        print(f"Conversion completed successfully! Saved to {output_file}")
    except Exception as e:
        print(f"Error during conversion: {str(e)}")
if __name__ == "__main__":
    main()

Swagger

使用他的规范定义接口以及接口相关的信息
就可以做到生成接口文档 以及在线调试接口页面
Knife4j 是为java MVC框架集成Swagger生成api文档的增强解决方案

  • 使用步骤
    • 导入坐标
<dependency>
      <groupId>com.github.xiaoymin</groupId>
      <artifactId>knife4j-spring-boot-starter</artifactId>
   </dependency>
  • 在配置类总加入knife4j的相关配置
    • 在WebMvcConfiguration.java中添加
/**
        * 通过knife4j生成接口文档
        * @return
   */
       @Bean
       public Docket docket() {
           ApiInfo apiInfo = new ApiInfoBuilder()
                   .title("苍穹外卖项目接口文档")
                   .version("2.0")
                   .description("苍穹外卖项目接口文档")
                   .build();
           Docket docket = new Docket(DocumentationType.SWAGGER_2)
                   .apiInfo(apiInfo)
                   .select()
                   .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                   .paths(PathSelectors.any())
                   .build();
           return docket;
       }

  • 设置静态资源映射 否则接口文档无法访问
    • 在相同类中添加
   /**
        * 设置静态资源映射
        * @param registry
   */
   protected void addResourceHandlers(ResourceHandlerRegistry registry) {
           registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
           registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
   }
  • Apifox / Yapi 主要是设计阶段使用到的工具 用于管理和维护接口
  • Swagger是在开发阶段使用的框架 帮助后端开发人眼做后端的接口测试

常用注解

通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:

注解 说明
@Api 用在类上,例如Controller,表示对类的说明
@ApiModel 用在类上,例如entity、DTO、VO
@ApiModelProperty 用在属性上,描述属性信息
@ApiOperation 用在方法上,例如Controller的方法,说明方法的用途、作用

接下来,使用上述注解,生成可读性更好的接口文档

在sky-pojo模块中

EmployeeLoginDTO.java

package com.sky.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;

@Data
@ApiModel(description = "员工登录时传递的数据模型")
public class EmployeeLoginDTO implements Serializable {

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("密码")
    private String password;

}

EmployeeLoginVo.java

package com.sky.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(description = "员工登录返回的数据格式")
public class EmployeeLoginVO implements Serializable {

    @ApiModelProperty("主键值")
    private Long id;

    @ApiModelProperty("用户名")
    private String userName;

    @ApiModelProperty("姓名")
    private String name;

    @ApiModelProperty("jwt令牌")
    private String token;

}

在sky-server模块中

EmployeeController.java

package com.sky.controller.admin;

import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.entity.Employee;
import com.sky.properties.JwtProperties;
import com.sky.result.Result;
import com.sky.service.EmployeeService;
import com.sky.utils.JwtUtil;
import com.sky.vo.EmployeeLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 员工管理
 */
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")
    @ApiOperation(value = "员工登录")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) 	{
        //..............

        
    }

    /**
     * 退出
     *
     * @return
     */
    @PostMapping("/logout")
    @ApiOperation("员工退出")
    public Result<String> logout() {
        return Result.success();
    }

}

想温柔的对待这个世界