当前位置:   article > 正文

Day_1

Day_1

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

反向代理的配置方式:

  1. server{
  2. listen 80;
  3. server_name localhost;
  4. location /api/ {
  5. proxy_pass http://localhost:8080/admin/; #反向代理
  6. }
  7. }

nginx 负载均衡的配置方式:

  1. upstream webservers{
  2. server 192.168.100.128:8080;
  3. server 192.168.100.129:8080;
  4. }
  5. server{
  6. listen 80;
  7. server_name localhost;
  8. location /api/ {
  9. proxy_pass http://webservers/admin/; #负载均衡 默认为轮询
  10. }
  11. }

 2. 登录功能

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

拦截器配置

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

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

登录Controller

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

  1. @PostMapping("/login")
  2. @ApiOperation(value = "员工登录")
  3. public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
  4. log.info("员工登录:{}", employeeLoginDTO);
  5. Employee employee = employeeService.login(employeeLoginDTO);
  6. //登录成功后,生成jwt令牌
  7. Map<String, Object> claims = new HashMap<>();
  8. claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
  9. String token = JwtUtil.createJWT(
  10. jwtProperties.getAdminSecretKey(),
  11. jwtProperties.getAdminTtl(),
  12. claims);
  13. EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
  14. .id(employee.getId())
  15. .userName(employee.getUsername())
  16. .name(employee.getName())
  17. .token(token)
  18. .build();
  19. return Result.success(employeeLoginVO);
  20. }

登录service

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

 3. Swagger

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

  1. <dependency>
  2. <groupId>com.github.xiaoymin</groupId>
  3. <artifactId>knife4j-spring-boot-starter</artifactId>
  4. <version>3.0.2</version>
  5. </dependency>

使用方式

1. 导入 knife4j maven坐标

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

WebMvcConfiguration.java

  1. @Bean
  2. public Docket docket() {
  3. log.info("准备生产接口文档");
  4. ApiInfo apiInfo = new ApiInfoBuilder()
  5. .title("苍穹外卖项目接口文档")
  6. .version("2.0")
  7. .description("苍穹外卖项目接口文档")
  8. .build();
  9. Docket docket = new Docket(DocumentationType.SWAGGER_2)
  10. .apiInfo(apiInfo)
  11. .select()
  12. .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
  13. .paths(PathSelectors.any())
  14. .build();
  15. return docket;
  16. }

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

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

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 返回前端需要的数据类型

新增员工

正常采用三层结构实现

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

程序存在问题

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

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

  1. @ExceptionHandler
  2. public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
  3. //错误内容: Duplicate entry 'zhangsan' for key 'idx_username'
  4. final String message = ex.getMessage();
  5. if(message.contains("Duplicate entry")) {
  6. String[] split = message.split(" ");
  7. String username = split[2];
  8. String msg = username + MessageConstant.ALREADY_EXISTS;
  9. return Result.error(msg);
  10. }else {
  11. return Result.error(MessageConstant.UNKNOWN_ERROR);
  12. }
  13. }

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

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

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

员工分页查询

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

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

代码完善

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

解决方法:

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

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

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

  1. protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
  2. //自己创建一个消息转换器
  3. MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
  4. // 为消息转换器 设置一个对象转换器,可以将java对象序列化为json数据
  5. converter.setObjectMapper(new JacksonObjectMapper());
  6. //将自己的消息转换器加入到容器里, 默认是放在最后一个
  7. // 0 -> 就是把这个消息转换器放在前面
  8. converters.add(0, converter);
  9. }

启用禁用员工账号

需要一个 update 数据库的方法

在 EmployeeMapper.xml 编写 SQL

  1. <update id="update" parameterType="Employee">
  2. update employee
  3. <set>
  4. <if test="name != null and name != ''" >name = #{name},</if>
  5. <if test="username != null and username != ''" >username = #{username},</if>
  6. <if test="sex != null and sex != ''" >sex = #{sex},</if>
  7. <if test="password != null and password != ''" >password = #{password},</if>
  8. <if test="phone != null and phone != ''" >phone = #{phone},</if>
  9. <if test="idNumber != null and idNumber != ''" >id_number = #{idNumber},</if>
  10. <if test="status != null">status = #{status},</if>
  11. <if test="updateTime != null">update_time = #{updateTime},</if>
  12. <if test="updateUser != null">update_user = #{updateUser},</if>
  13. </set>
  14. where id = #{id}
  15. </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

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

自定义切面类 AutoFillAspect

  1. @Aspect
  2. @Component
  3. @Slf4j
  4. public class AutoFillAspect {
  5. /**
  6. * 指定切入点
  7. */
  8. @Pointcut("execution(* com.sky.mapper.*.*(..)) &&
  9. @annotation(com.sky.annotation.AutoFill)")
  10. public void autoFillPointCut(){}
  11. /**
  12. * 前置通知,在通知中进行公共字段的赋值
  13. */
  14. @Before("autoFillPointCut()")
  15. public void autoFill(JoinPoint joinPoint) {
  16. log.info("开始进行公共字段的填充");
  17. // 在这里实现逻辑
  18. }
  19. }

实现逻辑:

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

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

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

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

3. 获取赋值的数据

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

4. 通过反射来赋值

  1. if(operationType == OperationType.INSERT) {
  2. //给4个公共字段赋值
  3. try {
  4. // 获取创建时间方法
  5. Method setCreateTime =
  6. entity.getClass()
  7. .getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class);
  8. // 获取创建人方法
  9. Method setCreateUser =
  10. entity.getClass()
  11. .getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
  12. // 获取修改时间方法
  13. Method setUpdateTime =
  14. entity.getClass()
  15. .getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
  16. // 获取修改人方法
  17. Method setUpdateUser =
  18. entity.getClass()
  19. .getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
  20. // 赋值操作
  21. // 创建时间赋值
  22. setCreateTime.invoke(entity, now);
  23. // 创建人赋值
  24. setCreateUser.invoke(entity, currentId);
  25. // 修改时间赋值
  26. setUpdateTime.invoke(entity, now);
  27. // 修改人赋值
  28. setUpdateUser.invoke(entity, currentId);
  29. } catch (Exception e) {
  30. e.printStackTrace();
  31. }
  32. }else if(operationType == OperationType.UPDATE) {
  33. //给2个公共字段赋值
  34. try {
  35. // 获取修改时间方法
  36. Method setUpdateTime =
  37. entity.getClass()
  38. .getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
  39. // 获取修改人方法
  40. Method setUpdateUser =
  41. entity.getClass()
  42. .getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
  43. // 赋值操作
  44. // 修改时间赋值
  45. setUpdateTime.invoke(entity, now);
  46. // 修改人赋值
  47. setUpdateUser.invoke(entity, currentId);
  48. } catch (Exception e) {
  49. throw new RuntimeException(e);
  50. }
  51. }else {
  52. // 既不是新增又不是修改
  53. throw new RuntimeException(MessageConstant.UNKNOWN_ERROR);
  54. }

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

  1. @AutoFill(OperationType.INSERT
  2. @AutoFill(OperationType.UPDATE)

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

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号