赞
踩
sky-take-out | maven父工程,统一管理依赖版本,聚合其他子模块 |
sky-common | 子模块,存放公共类,例如:工具类、常量类、异常类等 |
sky-pojo | 子模块,存放实体类、VO、DTO等 |
sky-server | 子模块,配置文件、Controller、Service、Mapper等 |
存放的是一些公共类,可以供其他模块使用
存放的是一些 entity、DTO、VO
存放的是 配置文件、配置类、拦截器、controller、service、mapper、启动类等
参考数据库设计文档
前端发送的请求,是如何请求到后端服务的?
前端请求地址:http://localhost/api/employee/login
后端接口地址:http://localhost:8080/admin/employee/login
nginx 反向代理的好处:
在文件 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/; #负载均衡 默认为轮询
- }
-
- }
员工表中的密码是明文存储,安全性太低,采用 MD5 加密格式
需求:在调用用户登录接口的时候不需要进行 jwtToken 认证,其他接口都需要进行认证
拦截器对动态方法进行拦截
对于新登录的用户,生成一个 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);
- }
因为数据库里存的是进行 md5 加密后的信息,在进行密码对比的时候,需要将前端传入的明文密码转为 md5 后再进行对比
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 | 用在类上,例如entity、DTO、VO |
@ApiModelProperty | 用在属性上,描述属性信息 |
@ApiOperation | 用在方法上,例如Controller的方法,说明方法的用途、作用 |
开发都是采用三层结构(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>
需要一个查询员工信息的接口,和上一个的修改员工信息的接口
思路与员工管理的一致,做着基本的crud
注:
在删除分类的时候,要求其下面没有挂载任何内容才可以
针对业务表里的公共字段进行维护
序号 | 字段名 | 含义 | 数据类型 |
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 注解
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface AutoFill {
- //数据库操作类型: update insert
- OperationType value();
-
- }
- @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. 去掉业务层这个重复的代码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。