苍穹外卖项目练习总结

news2025/2/23 18:26:55

做这个练习项目已经接近两年之久,现在拿出来复习一遍,主要就是里面处理问题的流程,以及整体思考的逻辑需要重新回顾一遍,后续会逐渐总结这一段时间以来学习到的知识。

项目整体包含两部分,一个是管理端,一个是用户端,管理端是一个网页,用户端是微信小程序的形式,技术栈用的比较全,主要练习增删改查以及redis的基础使用,了解Nginx的一些配置,并且了解小程序和web网页搭配后端的开发练习。本次,先总结前半部分。

目录

一、功能架构和技术栈 

二、准备知识

2.1 VO与DTO:

2.2 Nginx的使用

反向代理配置方式:

负载均衡配置:

2.3密码加密

2.4测试工具Swagger

三、员工管理实现

3.1员工登录

3.2新增员工存在的问题

 3.3条件分页查询员工

3.4 启用禁用员工账号

 3.5编辑员工操作

四、菜品管理实现

4.1公共字段填充(重点)

应用AOP+反射+自定义注解实现


一、功能架构和技术栈 

二、准备知识

2.1 VO与DTO:

2.2 Nginx的使用

前端请求地址与后端接口地址都不同,前端发送的请求如何到后端的? 

使用Nginx反向代理,相当于一个墙挡在各个服务器的前面,浏览器发送请求先到达nginx,然后再由nginx发送到tomcat服务器,为什么浏览器不直接发送到tomcat呢?这是因为采用Nginx有很多优点,可以进行负载均衡,由Nginx均分到各个服务器;还可以保证后端服务的安全性,因为前端无法直接访问到。

反向代理配置方式:

负载均衡配置:

2.3密码加密

为了安全,一般存储到数据库中的密码需要加密,此处采用md5加密,是单向的过程,只能将一个明文加密成密文,无法从密文解密成明文。使用spring自带的工具类完成,后面代码中演示。

2.4测试工具Swagger

使用Swagger测试工具,原先使用的postman,但是参数过多,需要生成多个参数,测试效率就有点低。

使用knife4j对swagger进行封装,简化了操作,需要导入依赖:

配置相应信息:

 /**
     * 通过knife4j生成接口文档
     * @return
     */
    @Bean
    public Docket docket1() {
        log.info("正在生成接口文档...");
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .groupName("管理端接口")
                .apiInfo(apiInfo)
                .select()
                //指定生成接口需要扫描的包,扫描之后会对所有的类进行扫描,之后使用反射解析类以及里面的方法,来生成接口文档。
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
    @Bean
    public Docket docket2() {
        log.info("正在生成接口文档...");
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .groupName("用户端接口")
                .apiInfo(apiInfo)
                .select()
                //指定生成接口需要扫描的包,扫描之后会对所有的类进行扫描,之后使用反射解析类以及里面的方法,来生成接口文档。
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
                .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/");
    }

三、员工管理实现

3.1员工登录

管理员登录部分中需要注意的地方:

1.使用md5加密,可以使用spring提供的工具类:DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes())

2.生成jwt令牌,指定签名的时候使用的签名算法,也就是header那部分,设置加密密钥、名称和过期时间,body中的私有声明claim部分可以去自己设置,此处设置成employeeId,后面将jwt令牌封装到了一个VO中,返回给前端,保存到请求头中,后面从请求头中获取token,然后后续拦截器进行jwt令牌校验根据加密密钥看是否可以解析成功。

3.使用@Builder注解,是Lombok库提供的一个功能,用于简化对象的创建过程,尤其是在需要设置多个属性时。@Builder注解会在编译时自动生成一个内部静态的Builder类,该类包含了所有字段的链式setter方法和一个build()方法用于创建对象实例。

Controller层:

@ApiOperation(value="员工登录")//添加说明
    @PostMapping("/login")
    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层:

 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);
        }

        //密码比对
        // 前端传来的明文密码进行md5加密处理:
        password= DigestUtils.md5DigestAsHex(password.getBytes());
        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;
    }

 jwt生成:此处设置了私有声明为employeeId,后续可以通过获取jwt令牌的名称然后获取到这个用户id。

public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

}

在yml文件中配置jwt的配置信息,密钥、前端传递过来的令牌名称,过期时间等。然后使用@ConfigurationProperties注解映射到配置类中。

sky:
  jwt:
    # 设置jwt签名加密时使用的秘钥
    admin-secret-key: itcast
    # 设置jwt过期时间
    admin-ttl: 7200000
    # 设置前端传递过来的令牌名称
    admin-token-name: token
    user-secret-key: nihao
    user-token-name: authentication
    user-ttl: 72000000
  wechat:
    appid: ${sky.wechat.appid}
    secret: ${sky.wechat.secret}

@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {

    /**
     * 管理端员工生成jwt令牌相关配置
     */
    private String adminSecretKey;
    private long adminTtl;
    private String adminTokenName;

    /**
     * 用户端微信用户生成jwt令牌相关配置
     */
    private String userSecretKey;
    private long userTtl;
    private String userTokenName;

}

3.2新增员工存在的问题

第一个需要完善的地方:

新增用户的时候如果用户名重复,没有处理这个异常,可以用全局异常处理器进行处理。复制这个异常,然后在全局异常处理器中单独处理。


/**
 * 全局异常处理器,处理项目中抛出的业务异常
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 捕获业务异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(BaseException ex){
        log.error("异常信息:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }
    //处理username重复的异常
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        //Duplicate entry 'xiaoyan' for key 'idx_username'
        String message=ex.getMessage();
        if(message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String username = split[2];
            String msg=username+MessageConstant.ALREADY_EXIST;
            return Result.error(msg);
        }else{
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }

}

第二个需要完善的地方:

新增员工的时候在service中需要指定哪个员工新增了员工,需要获取创建者的id,因此需要思考如何获取创建者的id。

可以想到之前结合jwt令牌来实现,将empId存到jwt令牌中,返回给前端,每次可以通过从请求头中来获取jwt令牌,然后解析得到empId即可,但是需要注意的是如何能够保证在本次请求中拿到empId,可以使用ThreadLocal:

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

ThreadLocal一般都会进行封装,封装为一个类,包含使用的方法:

package com.sky.context;

public class BaseContext {

    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    public static Long getCurrentId() {
        return threadLocal.get();
    }

    public static void removeCurrentId() {
        threadLocal.remove();
    }

}

实现方式:在用户登陆的时候将用户id存到jwt令牌中的claim中,然后当后面发起新增用户请求的时候,会经过拦截器,在服务端中拦截器会从请求头中解析出jwt令牌,从而获取empId,然后保存到该线程的ThreadLocal中,后面到达service层的时候可以从ThreadLocal中获取到id,然后设置创建人属性为该empId。


/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getUserTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
            //获取用户的id:
            Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
            //存储到ThreadLocal中:
            BaseContext.setCurrentId(userId);
            log.info("当前用户id:", userId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

 3.3条件分页查询员工

此处使用的是mybatis,并不是plus,使用plus会方便很多(但是容易遗忘sql /(ㄒoㄒ)/~~)

按照姓名条件模糊查询,如果没有条件则默认查询全部,有条件按照条件来查询:

具体实现过程:

前端传过来有三个元素,用户姓名(条件),页数,每页数据量,将其封装为一个DTO接收。返回的是该页的数据以及总数,也封装为一个VO接收,使用PageHelper分页插件来实现,在service中使用PageHelper.startPage(),然后再封装到page中,之后用page.getTotal方法查询方法获取到的总数以及记录。最后封装到PageResult中。

代码如下:

//Controller
 @ApiOperation("员工条件分页查询")
    @GetMapping("/page")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){//这里不用加@RequestBody了,前端是Query格式,不是json格式
        log.info("分页查询,参数为"+employeePageQueryDTO);
        PageResult pageResult=employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);

    }
//Service
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
        Page<Employee>page= employeeMapper.pageQuery(employeePageQueryDTO);
        long total = page.getTotal();
        List<Employee> records = page.getResult();

        return new PageResult(total,records);
    }
//Dao
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
//xml映射文件
<select id="pageQuery" resultType="com.sky.entity.Employee">
    select * from employee
    <where>
        <if test="name!=null and name!=''">
            and name like concat('%',#{name},'%')
        </if>
    </where>
    </select>

注意:经过测试后发现有些问题, 更新时间和创建时间返回到前端是以数组的形式,前端展示的也会有问题,所以需要修改一下:

有两种解决方案: 

1.在属性上添加注解但是这种方式只能针对一个个属性进行转换,所以不推荐。

2.在WebMvcConfiguration配置类(继承WebMvcConfigurationSupport类)中扩展SpringMvc消息转换器,统一对日期格式进行处理:

/**
     * 扩展SpringMVC框架的消息转换器,对后端传递给前端的数据进行统一的管理,
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters){
        log.info("扩展消息转换器...");
        //创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
        //需要为消息转换器设置一个对象转换器,对象转换器可以将java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());//这个JacksonObjectMapper类在common中的json包中,可以看到里面定义好了序列化方法和反序列化方法,将LocalDateTime类型的转化为规定的格式。
        //此时定义好了一个消息转换器但是并没有交给框架,所以框架也不会去使用这个转换器,需要再将这个转换器交给框架,也就是交给converters这个容器中:
        converters.add(0,converter);//注意容器中的消息转换器是有顺序的,加进去之后会默认排到最后,是没法用的,所以前面加个0代表索引。

    }

3.4 启用禁用员工账号

去改变员工账号的状态,如果状态为禁用,则无法登陆系统。这本质上是一个更新操作,但是单独为了这个操作写一个单独更新员工帐号状态属性的方法非常冗余,所以直接写成条件动态sql,根据传入的参数来对相应的属性进行更新,同时登陆操作也需要进行更改,需要增加一步判断,帐号状态是否可用:

接口设计如下:前端传两个参数,一个是员工id,一个是禁用或者启用

 整体代码如下:

//登录接口修改:添加判断操作
 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);
        }

        //密码比对
        // 前端传来的明文密码进行md5加密处理:
        password= DigestUtils.md5DigestAsHex(password.getBytes());
        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;
    }
//Controller:
/**
     * 启用和禁用员工账号
     * @param status
     * @param id
     * @return
     */
    @ApiOperation("启用和禁用员工账号")
    @PostMapping("/status/{status}")
    public Result startAndStop(@PathVariable Integer status,Long id){
        log.info("启用和禁用员工账号,员工账号为:"+status+", "+id);
        employeeService.startAndStop(status,id);
        return Result.success();

    }
//Service:
//启用和禁用员工账号:可以发现这个其实本质上是一个更新操作,但是只更新这两个的话,复用性太差了,所以可以写一个动态sql更新,这样复用性更高。
    @Override
    public void startAndStop(Integer status, Long id) {
        //需要写动态sql,所以要构建一个对象,封装这两个值:
//        Employee employee=new Employee();
//        employee.setStatus(status);
//        employee.setId(id);
        //上面那种构建方法是传统的方法,比较复杂,可以看到Employee实体类的前面添加了Builder注解,可以用这种新的方法:
        Employee employee = Employee.builder()
                .status(status)
                .id(id).
                build();
        employeeMapper.update(employee);
    }
//Mapper动态sql:
    <update id="update">
    update employee
    <set>
        <if test="name!=null">
            name=#{name},
        </if>
        <if test="username!=null">
            username=#{username},
        </if>
        <if test="password!=null">
            password=#{password},
        </if>
        <if test="phone!=null">
            phone=#{phone},
        </if>
        <if test="sex!=null">
            sex=#{sex},
        </if>
        <if test="idNumber!=null">
            id_Number=#{idNumber},
        </if>
        <if test="updateTime!=null">
            update_Time=#{updateTime},
        </if>
        <if test="updateUser!=null">
            update_user=#{updateUser},
        </if>
        <if test="status!=null">
            status=#{status},
        </if>
    </set>
        where id=#{id}
    </update>

 3.5编辑员工操作

这个地方需要注意的就是,点击编辑按钮后,需要先展示出来对应的员工信息,所以需要先进性查询回显操作,然后再进行更新操作,更新复用上面写的启用禁用员工账号的动态更新sql。其他没有特别需要注意的地方,整体代码如下:

//Controller
/**
     * 根据id查询员工
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询员工")
    public Result<Employee> getById(@PathVariable Long id){
        Employee employee;
        employee=employeeService.getById(id);
        return Result.success(employee);
    }
    /**
     * 编辑员工信息
     * @param employeeDTO
     * @return
     */
    @PutMapping
    @ApiOperation("编辑员工操作")
    public Result update(@RequestBody EmployeeDTO employeeDTO){//此处传过来的属性值,还是和这个属性对应,所以依然可以用这个DTO
        log.info("编辑员工操作"+employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();

    }
//Service
//编辑员工操作——先根据id查询:
    public Employee getById(Long id) {
        return employeeMapper.getById(id);
    }
    //编辑员工操作
    public void update(EmployeeDTO employeeDTO) {
        Employee employee=new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);
//        employee.setUpdateUser(BaseContext.getCurrentId());
//        employee.setUpdateTime(LocalDateTime.now());
        employeeMapper.update(employee);
    }
}
//DAO
@AutoFill(value = OperationType.UPDATE)
    void update(Employee employee);
    //编辑员工操作——先根绝id查询
    @Select("select *from employee where id=#{id}")
    Employee getById(Long id);

以上是员工管理部分,下面是菜品管理,其中最重要的就是AOP的使用,使用aop实现公共字段填充,可以当作aop以及自定义注解的练习,公共字段填充目前可以直接使用mybatis-plus来完成,简化了很多操作,底层也是aop实现的,无需手动去实现。

四、菜品管理实现

4.1公共字段填充(重点)

应用AOP+反射+自定义注解实现

问题分析:业务表中基本都存在着创建时间、更新时间、创建人、修改人等公共字段,在员工管理以及分类管理中存在许多这样的字段,每当增和改操作的时候都需要在Service层中手动赋值或修改这些字段,重复性很高,非常冗余,所以可以使用一个统一的方法去解决,这就很容易联想到AOP的使用:

只有insert和update操作需要对公共字段修改,所以要提取出这些操作,然后使用AOP对这些进行统一的赋值。

步骤如下:

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

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

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

 首先自定义注解AutoFill,然后在注解中指定数据库操作的类型,此处使用枚举实现:

(@Retention(RetentionPolicy.RUNTIME)是Java注解中的一个元注解,用于指定被注解的元素在什么时候有效。具体来说,它定义了被注解的元素的保留策略,即注解在运行时仍然有效。)

/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */
@Target({ElementType.METHOD})//表示添加的目标,此处指定加在方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //指定数据库操作类型:UPDATE、INSERT 更新和插入操作;在common模块中的enumeration包中存在这个OperationType枚举;
    OperationType value();
}

枚举类: 

/**
 * 数据库操作类型
 */
public enum OperationType {

    /**
     * 更新操作
     */
    UPDATE,

    /**
     * 插入操作
     */
    INSERT

}

然后自定义一个切面类:在里面完善好切入点和通知:

切入点:

/**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..))&& @annotation(com.sky.annotation.AutoFill)")//注意此处需要指定用于哪些方法,
    // 不仅要满足execution指定的包下的方法还得满足有这个注解才可以,因为只有execution内的指的是所有的方法,此处只需要对insert和update方法
    public void autoFillPointCut(){}

通知:首先应该使用前置通知,并指定需要拦截的方法,明确操作数据库的方法类型是insert还是update,确定需要增强的方法获取该方法的参数对象,便于为后面填充赋值,准备好赋值数据,之后通过反射来得到set方法对其赋值。

(其中getSignature():这个方法返回当前连接点的签名信息,即Signature对象。签名对象包含了方法或构造函数的基本信息,比如名称、参数等。)

@Before("autoFillPointCut()")//指定哪个方法
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始进行公共字段自动填充...");
        //开始对自动填充进行完善:
        //首先要获取到是insert方法还是update方法,如果是update方法只需要对update_user/update_time公共字段进行填充,
        // 如果是insert方法需要对四个公共字段都要进行填充,因此要获取到当前被拦截方法上的数据库操作类型:是insert还是update
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//(方法签名对象)本来是Signature类型,因为拦截到的是一个方法所以进行向下转型,变为MethodSignature
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
        OperationType operationType = autoFill.value();//获取数据库操作类型
        //获取到当前被拦截的方法的参数--实体对象(为这个对象的属性(公共参数)赋值)
        Object[]args=joinPoint.getArgs();//获取到所有的参数:args,此处有一个约定就是将对象放到insert方法或者update方法的第一个参数上
        if(args==null||args.length==0) {
            return;
        }
        Object entity=args[0];
        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();
        //根据获取到的不同操作类型,为对应的属性通过反射进行赋值
        if(operationType==OperationType.INSERT){
            //为四个公共字段进行赋值
            //获取set方法:
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);//第一个参数是方法名,第二个参数是传入值的类型
                Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
                setCreateUser.invoke(entity,currentId);
                setCreateTime.invoke(entity,now);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);

            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }else if(operationType==OperationType.UPDATE){
            //为两个公共字段进行赋值
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);

            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }





    }

整体流程就是:首先自定义注解,然后表明包含的数据库操作类型方法(Insert和Update),然后自定义切面类,表明切入点(在响应包下并且带有AutoFill注解的方法才会被增强),接下来通过前置通知,首先 获取Signature签名对象,然后通过反射得到方法上面的注解,并获取其中的value也就是数据库操作的方法类型,根据方法类型接下来通过反射获取对应的set方法,为相应的公共字段赋值即可完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2255670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数学拯救世界(三)———破魔

题目一&#xff1a; 还记不记得&#xff0c;出现带分数的话可以怎么办&#xff1f; 题目二&#xff1a; 还记不记得&#xff0c;昨天讲的重叠数 题目三&#xff1a; 提公因数&#xff0c;抓住问题本质 题目四&#xff1a;

JAVA |日常开发中连接Sqlite数据库详解

JAVA &#xff5c;日常开发中连接Sqlite数据库详解 前言一、SQLite 数据库概述1.1 定义与特点1.2 适用场景 二、Java 连接 SQLite 数据库的准备工作2.1 添加 SQLite JDBC 驱动依赖2.2 了解 JDBC 基础概念 三、建立数据库连接3.1 代码示例3.2 步骤解析 四、执行 SQL 语句4.1 创建…

对 JavaScript 说“不”

JavaScript编程语言历史悠久&#xff0c;但它是在 1995 年大约一周内创建的。 它最初被称为 LiveScript&#xff0c;但后来更名为 JavaScript&#xff0c;以赶上 Java 的潮流&#xff0c;尽管它与 Java 毫无关系。 它很快就变得非常流行&#xff0c;推动了 Web 应用程序革命&…

解谜类游戏《迷失岛2》等如何抽象出一套通用高效开发框架?

解谜类游戏以精妙的谜题设计和引人入胜的故事叙述为特点&#xff0c;考验着玩家的智慧与观察力。《迷失岛2》与《南瓜先生2九龙城寨》正是这一领域的佳作。游戏以独特的艺术风格和玩法设计吸引了大量玩家&#xff0c;而它们背后隐藏着一套强大的框架。 上海胖布丁游戏的技术总…

CID引流电商

ClickID技术是基于多家媒体平台开发的电商引流服务&#xff0c;通过媒体提供的宏参数&#xff0c;间接解决电商平台订单数据的回传问题&#xff0c;帮助账户收集到极致精准的数据模型&#xff0c;搭建不同媒体往各平台引流的桥梁。简单来说就是通过ClickID数据监测到另外一个平…

保姆级教程Docker部署Redis镜像

目录 1、创建挂载目录和配置文件 2、运行Redis镜像 3、查看redis运行状态 1、创建挂载目录和配置文件 # 创建宿主机Redis配置文件存放目录 sudo mkdir -p /data/docker/redis/conf# 创建Redis配置文件 cd /data/docker/redis/conf sudo touch redis.conf 到Github上找到Redi…

如何将快捷指令添加到启动台

如何将快捷指令添加到启动台/Finder/访达&#xff08;Mac&#xff09; 1. 打开快捷指令创建快捷指令 示例创建了一个文件操作测试的快捷指令。 2. 右键选择添加到程序坞 鼠标放在待添加的快捷指令上。 3. 右键添加到访达 鼠标放在待添加的快捷指令上。 之后就可以在启…

【AI学习】Mamba学习(二十):Mamba是一种线性注意力

论文《Demystify Mamba in Vision: A Linear Attention Perspective 》从线性注意力的视角&#xff0c;对Mamba 进行了阐释。 论文名称&#xff1a;Demystify Mamba in Vision: A Linear Attention Perspective (Arxiv 2024.05) 论文地址&#xff1a;https://arxiv.org/pdf/24…

Bootstrap-HTML(三)Bootstrap5列表组全解析

Bootstrap-HTML&#xff08;三&#xff09;Bootstrap5列表组全解析 前言&#xff08;一&#xff09;HTML 列表基础回顾1.无序列表2.有序列表3.定义列表 二、无样式的有序列表和无序列表内联列表 三、Bootstrap5 列表组1.基础的列表组2.设置禁用和活动项3.链接项的列表组4.移除列…

【CSP CCF记录】202212-2第28次认证 训练计划

题目 样例1输入 10 5 0 0 0 0 0 1 2 3 2 10 样例1输出 1 1 1 1 1 10 9 8 9 1 样例1解释 五项科目间没有依赖关系&#xff0c;都可以从第 1 天就开始训练。 10天时间恰好可以完成所有科目的训练。其中科目 1 耗时仅 1天&#xff0c;所以最晚可以拖延到第 10 天再开始训练&…

java 18 异常处理

一&#xff0c;异常概念 基本概念Java语言中&#xff0c;将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常) 执行过程中所发生的异常事件可分为两大类&#xff1a; 1&#xff09;Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部…

【Spark】 groupByKey与reduceByKey的区别

groupByKey 操作&#xff1a;将相同键的所有值收集到一个集合中。实现&#xff1a;不会在map端进行局部聚合&#xff0c;而是直接将所有相同键的数据传输到reduce端进行聚合。缺点&#xff1a;由于没有本地聚合&#xff0c;groupByKey会导致大量的数据传输和shuffle&#xff0c…

房屋租赁系统源码 SpringBoot + Vue 实现全功能解析

这是一套使用 SpringBoot 与 Vue 开发的房屋租赁系统源码&#xff0c;站长分析过这套源码&#xff0c; 推测其原始版本可能是一个员工管理系统&#xff0c;经过二次开发后&#xff0c;功能被拓展和调整&#xff0c;现已完全适用于房屋租赁业务。 该系统功能完善&#xff0c;涉…

【ArcGISPro】训练自己的深度学习模型并使用

本教程主要训练的是识别汽车的对象检测模型 所使用的工具如下(导出训练数据进行深度学习、训练深度学习模型、使用深度学习检测对象) 1.准备训练数据 1.1新建面矢量,构建检测对象 右键地理数据库->新建->要素类 选择面类型 1.2点击编辑窗口进行勾画汽车检测对象…

鸿蒙特色实战3共享单车案例

1 案例截图 2 开发准备 本项目需要一台NEXT真机&#xff0c;并完成以下准备工作。 在AppGallery Connect&#xff08;简称AGC&#xff09;上&#xff0c;参考创建项目和创建应用完成HarmonyOS应用的创建&#xff0c;从而使用各类服务。 添加应用包名要注意&#xff1a;与新建…

Thonny IDE + MicroPython + ESP32 + 0.96寸OLED(IIC) 显示任意字符

四针脚0.96英寸OLED显示屏模块的具体参数如下表所示。 参数名称 参数特性 分辨率 128x64像素 通信方式 IIC 驱动芯片 SSD1306 屏幕颜色 白色、蓝色或黄蓝双色 元件&#xff1a; 四针脚0.96英寸OLED显示屏模块 ESP32 DEVKIT_C开发板 杜邦线USB Type-C 接线&#xf…

监控易助力IT运维再升级

在当今数字化时代&#xff0c;企业对于IT系统的依赖程度日益加深&#xff0c;IT运维管理的重要性也随之凸显。作为首都的一家知名企业&#xff0c;北京某公司在业务快速发展的同时&#xff0c;也面临着IT系统规模不断扩大、运维管理复杂度不断提升的挑战。为了更好地应对这些挑…

线程(二)——线程安全

如何理解线程安全&#xff1a; 多线程并发执行的时候&#xff0c;有时候会触发一些“bug”&#xff0c;虽然代码能够执行&#xff0c;线程也在工作&#xff0c;但是过程和结果都不符合我们的开发时的预期&#xff0c;所以我们将此类线程称之为“线程安全问题”。 例如&#xff…

prometheusgrafana实现监控告警

Prometheus负责集群数据的监控和采集&#xff0c;然后传递给grafana进行可视化&#xff0c;集成睿象云可实现监控报警&#xff0c;为了方便操作&#xff0c;可以通过iframe嵌套grafana到指定的页面。 文章目录 1.Grafana集成Prometheus2.iframe内嵌grafana3.监控告警 1.Grafana…