Day_1

本文详细介绍了苍穹外卖后端开发过程,包括环境搭建、技术选型(如Maven和nginx反向代理)、数据库操作(MD5加密和员工管理)、API设计(Swagger集成和Knife4j)、以及公共字段自动填充的AOP实现。
摘要由CSDN通过智能技术生成

源码下载链接:xxlinx/sky-take-out

1. 环境搭建

技术选型

后端项目结构

sky-take-out

maven父工程,统一管理依赖版本,聚合其他子模块

sky-common

子模块,存放公共类,例如:工具类、常量类、异常类等

sky-pojo

子模块,存放实体类、VODTO

sky-server

子模块,配置文件、ControllerServiceMapper

sky-common

存放的是一些公共类,可以供其他模块使用

sky-pojo

存放的是一些 entityDTOVO

sky-server

存放的是 配置文件、配置类、拦截器、controllerservicemapper、启动类等

数据库

参考数据库设计文档

前后端联调

前端发送的请求,是如何请求到后端服务的?

前端请求地址:http://localhost/api/employee/login

后端接口地址:http://localhost:8080/admin/employee/login

nginx 反向代理

就是将前端发送的动态请求由 nginx 转发到后端服务器

nginx 反向代理的好处:

  1. 提高访问速度,nginx可以进行缓存
  2. 进行负载均衡,针对分布式系统
  3. 保证后端服务安全,不会对外公开自己的服务调用接口

配置方式

在文件 nginx.conf

反向代理的配置方式:

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

}

nginx 负载均衡的配置方式:

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/;  #负载均衡 默认为轮询
	}

}

 2. 登录功能

员工表中的密码是明文存储,安全性太低,采用 MD5 加密格式

拦截器配置

需求:在调用用户登录接口的时候不需要进行 jwtToken 认证,其他接口都需要进行认证

拦截器对动态方法进行拦截

登录Controller

对于新登录的用户,生成一个 jwt 令牌

@PostMapping("/login")
@ApiOperation(value = "员工登录")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
    log.info("员工登录:{}", employeeLoginDTO);

    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

因为数据库里存的是进行 md5 加密后的信息,在进行密码对比的时候,需要将前端传入的明文密码转为 md5 后再进行对比

 3. Swagger

Knife4j 是为Java MVC框架集成Swagger生成Api文档的增强解决方案

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>

使用方式

1. 导入 knife4j maven坐标

2. 在配置类中加入 knife4j 相关配置

WebMvcConfiguration.java

@Bean
public Docket docket() {
    log.info("准备生产接口文档");
    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;
}

3. 设置静态资源映射,否则接口文档页面无法访问

protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    log.info("开始设置静态资源映射");
    registry.addResourceHandler("/doc.html").
        addResourceLocations("classpath:/META-INF/resources/");
    registry.addResourceHandler("/webjars/**").
        addResourceLocations("classpath:/META-INF/resources/webjars/");
}

4. 访问

接口文档访问路径为 http://ip:port/doc.html

常用注解

注解

说明

@Api

用在类上,例如Controller,表示对类的说明

@ApiModel

用在类上,例如entityDTOVO

@ApiModelProperty

用在属性上,描述属性信息

@ApiOperation

用在方法上,例如Controller的方法,说明方法的用途、作用


4. 员工管理 

开发都是采用三层结构(MVC模式),具体查看源码

代码开发:

1. 设计接受前端传入的 DTO 

2. 在 Controller 定义执行方法

3. 在 Service,ServiceImpl 中进行实现方法逻辑

4. 在 Mapper 层进行对数据库的调用查询

5. Controller 返回前端需要的数据类型

新增员工

正常采用三层结构实现

@PostMapping
@ApiOperation("员工新增")
public Result save(@RequestBody EmployeeDTO employeeDTO) {
    log.info("新增员工:{}", employeeDTO);
    // 新增员工业务方法
    employeeService.save(employeeDTO);
    return Result.success();
}

程序存在问题

1. 在出现同样的 username 的时候,系统会报错;

该异常应被全局异常处理器处理

@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
    //错误内容: Duplicate entry 'zhangsan' for key 'idx_username'
    final String message = ex.getMessage();
    if(message.contains("Duplicate entry")) {
        String[] split = message.split(" ");
        String username = split[2];
        String msg = username + MessageConstant.ALREADY_EXISTS;

        return Result.error(msg);
    }else {
        return Result.error(MessageConstant.UNKNOWN_ERROR);
    }
}

2. 当前登录用户的 id 如何存储

使用 ThreadLocal,是一个线程的局部变量,为每个线程单独提供一份存储空间,具有线程隔离效果,只有在线程内才能获取到对应的值,线程外则不能访问

在 sky-common 中已经封装为 BaseContext 类

员工分页查询

分页查询使用使用 mybatis 的分页插件 PageHelper 来简化分页代码的开发。

底层基于 mybatis 的拦截器实现,在 sql 语句后面进行拼接 limit

代码完善

日期时间在前端的显示结果不是我们想要的

解决方法:

1. 在属性上加入注解,对日期进行格式化

可以实现日期的序列化,但是只能实现这一个属性的序列化。 不推荐

2.  WebMvcConfiguration 中扩展Spring MVC的消息转换器,统一对日期类型进行格式化

protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    //自己创建一个消息转换器
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    // 为消息转换器 设置一个对象转换器,可以将java对象序列化为json数据
    converter.setObjectMapper(new JacksonObjectMapper());

    //将自己的消息转换器加入到容器里, 默认是放在最后一个
    // 0 -> 就是把这个消息转换器放在前面
    converters.add(0, converter);
}

启用禁用员工账号

需要一个 update 数据库的方法

在 EmployeeMapper.xml 编写 SQL

<update id="update" parameterType="Employee">
    update employee
    <set>
        <if test="name != null and name != ''" >name = #{name},</if>
        <if test="username != null and username != ''" >username = #{username},</if>
        <if test="sex != null and sex != ''" >sex = #{sex},</if>
        <if test="password != null and password != ''" >password = #{password},</if>
        <if test="phone != null and phone != ''" >phone = #{phone},</if>
        <if test="idNumber != null and idNumber != ''" >id_number = #{idNumber},</if>
        <if test="status != null">status = #{status},</if>
        <if test="updateTime != null">update_time = #{updateTime},</if>
        <if test="updateUser != null">update_user = #{updateUser},</if>
    </set>
    where id = #{id}
</update>

编辑员工信息

需要一个查询员工信息的接口,和上一个的修改员工信息的接口


5. 分类模块功能

思路与员工管理的一致,做着基本的crud

注:

在删除分类的时候,要求其下面没有挂载任何内容才可以


6. 公共字段自动填充

针对业务表里的公共字段进行维护

序号

字段名

含义

数据类型

1

create_time

创建时间

datetime

2

create_user

创建人id

bigint

3

update_time

修改时间

datetime

4

update_user

修改人id

bigint

实现思路

技术:注解、AOP、反射

思路:

1.自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2.自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

3.在 Mapper 的方法上加入 AutoFill 注解

开发

自定义注解 AutoFill

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型: update insert
    OperationType value();

}

自定义切面类 AutoFillAspect

@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    /**
     * 指定切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) &&             
    @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知,在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始进行公共字段的填充");
        // 在这里实现逻辑
    }

}

实现逻辑:

1.  获取到当前被拦截到的方法的数据库操作类型

//获取到方法签名对象
MethodSignature signature = (MethodSignature)joinPoint.getSignature(); 
//获取方法上的注解对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); 
// 获取到数据库的操作类型
OperationType operationType = autoFill.value();  

2. 获取到被拦截的方法的参数(实体对象)  这里约定: 参数里面的实体对象为第一个参数

Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0) {
    return;
}
Object entity = args[0];

3. 获取赋值的数据

LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();

4. 通过反射来赋值

if(operationType == OperationType.INSERT) {
    //给4个公共字段赋值
    try {
        // 获取创建时间方法
        Method setCreateTime = 
             entity.getClass()
            .getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class);
        // 获取创建人方法
        Method setCreateUser =     
             entity.getClass()
             .getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
        // 获取修改时间方法
        Method setUpdateTime = 
             entity.getClass()
            .getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
        // 获取修改人方法
        Method setUpdateUser = 
             entity.getClass()
             .getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

        // 赋值操作
        // 创建时间赋值
        setCreateTime.invoke(entity, now);
        // 创建人赋值
        setCreateUser.invoke(entity, currentId);
        // 修改时间赋值
        setUpdateTime.invoke(entity, now);
        // 修改人赋值
        setUpdateUser.invoke(entity, currentId);
    } catch (Exception e) {
        e.printStackTrace();
    }
}else if(operationType == OperationType.UPDATE) {
    //给2个公共字段赋值
    try {
        // 获取修改时间方法
        Method setUpdateTime =     
            entity.getClass()
            .getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
        // 获取修改人方法
        Method setUpdateUser = 
            entity.getClass()
            .getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

        // 赋值操作
        // 修改时间赋值
        setUpdateTime.invoke(entity, now);
        // 修改人赋值
        setUpdateUser.invoke(entity, currentId);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}else {
    // 既不是新增又不是修改
    throw new RuntimeException(MessageConstant.UNKNOWN_ERROR);
}

5. Mapper接口的方法上加入 AutoFill 注解

@AutoFill(OperationType.INSERT

@AutoFill(OperationType.UPDATE)

6. 去掉业务层这个重复的代码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值