当前位置: 首页 > article >正文

交大智邦后端Java笔试题

交大智邦后端Java笔试题

简答题

  1. 只要一个类加上了@Component注解,就一定能成为一个Spring Bean吗?如果不是,请举出反例。

    不一定

    1. 扫描范围不包括 com.code.lab.web.component 或者被 @ComponentScan 显式排除 通过 excludeFilters 手动排除特定类
    2. 被条件注解排除 @ConditionalOnProperty(name = “feature.enabled”, havingValue = “false”)
    3. 被@Profile(“test”)限制环境
    package com.code.lab.web.component;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @author Cai
     */
    @Component
    @ConditionalOnProperty(name = "feature.enabled", havingValue = "false")
    @Profile("test")
    public class MyComponent {
    
    }
    
    
    package com.code.lab.web;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.scheduling.annotation.EnableAsync;
    
    /**
     * @author Cai
     */
    @EnableAsync
    @SpringBootApplication
    @ComponentScan("com.code.lab.web.config",excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyComponent.class))
    public class SpringbootCommonsPool2DemoApplication {
      public static void main(String[] args) {
        SpringApplication.run(SpringbootCommonsPool2DemoApplication.class, args);
      }
    }
    
  2. 如果有两个相同类型,但不重名的String Bean,在引入的时候应如何处理?针对@Autowired@Resource分别作答。

package com.code.lab.web.config.thrad;

import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @author Cai
 */
@Configuration
@Slf4j
public class EnhancedThreadPoolConfig {
  /**
   * 订单处理线程池(高优先级)
   *
   * @return 线程池
   */
  @Bean("orderThreadPool")
  @Primary
  public ThreadPoolTaskExecutor orderThreadPool() {
    // 新增关键配置
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(4);
    executor.setMaxPoolSize(8);
    executor.setKeepAliveSeconds(60);
    executor.setQueueCapacity(2000);
    executor.setThreadFactory(new CustomThreadFactory("Order-Thread-"));
    executor.setRejectedExecutionHandler(new CustomRejectPolicy());
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setAwaitTerminationSeconds(30);
//    优雅停机
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setTaskDecorator(new ContextCopyDecorator());
    executor.initialize();
    return executor;
  }

  /**
   * 日志记录线程(低优先级)
   *
   * @return 线程池
   */
  @Bean("logThreadPool")
  public ThreadPoolTaskExecutor logThreadPool() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(2);
    executor.setQueueCapacity(1000);
    executor.setThreadNamePrefix("Log-Thread-");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
    executor.initialize();
    return executor;
  }
}
1. 使用 @Autowired 注入 结合 @Qualifier 指定 Bean 名称
显式指定要注入的 Bean 名称:
@Qualifier("orderThreadPool") 
@Autowired
ThreadPoolTaskExecutor orderExecutor

2.使用 @Primary 标记默认 Bean
3.使用 @Resource 注入
@Resource(name="orderThreadPool")
ThreadPoolTaskExecutor orderExecutor

3.如果需要修改默认的时间输出格式,需要如何处理?(单个字段、全局)

局部
1.第一种使用 @JsonFormat(Jackson 序列化)
 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
  private LocalDateTime createTime;
2:使用 @DateTimeFormat(Spring MVC 参数绑定)
 @DateTimeFormat(pattern = "yyyy/MM/dd")
 private Date startDate;
全局时间格式修改
修改 application.yml
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
通过代码自定义全局序列化规则:    
@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        // 注册 JavaTime 模块(处理 LocalDateTime 等类型)
        mapper.registerModule(new JavaTimeModule());
        // 全局日期格式
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        // 禁用时间戳格式
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }
}

4.如何保证接口消息的幂等性。

同一操作多次执行的结果与一次执行的结果一致:

1.唯一标识符(ID 幂等)

2.Token 机制

3.数据库唯一约束

4.锁

5.在请求接口中开启一个线程执行高CPU操作,是否会导致接口线程阻塞?

当请求到达接口时,主线程会执行以下操作

1.创建并启动一个新线程用于执行高 CPU 操作

2.继续执行后续代码

在启动新线程后不会被阻塞,会立即释放并处理其他请求


能耗统计

在项目现场需要统计用电量,因此拟使用一块电表进行电量的采集。与家用的电表一样,此表的采集值永远是累计值。采集到的JSON数据如下:

{
    "timestamp": "采集时的时间戳,数字格式",
    "meterId": "电表唯一号",
    "value": "采集到的结果,数字格式"
}

由于现场的不稳定,可能存在以下的特殊情况:

  1. 可能由于网络问题,导致采集数据失败,得到的value为0。
  2. 可能由于设备更换(不考虑更换期间的耗电),此累积值归零,但下一次采集的结果中,meterId会发生变化。(存在可能设备修复后继续使用)

请编写一段伪代码,以完成对此电表的采集,并按天给出使用电量。

package com.code.lab.pool;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Cai
 */
public class ElectricityMeterProcessor {
  // 记录每个电表的上次有效读数:meterId -> (lastTimestamp, lastValue)
  private final Map<String, MeterRecord> meterDb = new HashMap<>();
  // 按天统计总用电量:dateStr -> totalUsage
  private final Map<String, Long> dailyUsage = new HashMap<>();
  private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

  // 处理电表数据
  public void processMeterData(MeterData data) {
    // 过滤无效数据(value=0)
    if (data.value == 0) {
      return;
    }

    String meterId = data.meterId;
    long currentTimestamp = data.timestamp;
    long currentValue = data.value;

    // 转换时间戳为日期字符串(UTC时区)
    LocalDate date = Instant.ofEpochSecond(currentTimestamp).atZone(ZoneId.of("UTC")).toLocalDate();
    String dateStr = date.format(DATE_FORMATTER);

    // 新电表或设备更换后首次读数
    if (!meterDb.containsKey(meterId)) {
      meterDb.put(meterId, new MeterRecord(currentTimestamp, currentValue));
      return;
    }

    // 获取上次记录
    MeterRecord lastRecord = meterDb.get(meterId);
    long lastValue = lastRecord.value;

    // 计算增量
    long increment;
    if (currentValue < lastValue) {
      // 设备重置,视为新设备开始累计
      increment = currentValue;
    } else {
      // 正常增量
      increment = currentValue - lastValue;
    }

    // 更新电表记录
    meterDb.put(meterId, new MeterRecord(currentTimestamp, currentValue));

    // 累加当日用电量
    dailyUsage.merge(dateStr, increment, Long::sum);
  }

  // 获取按天统计结果
  public Map<String, Long> getDailyUsage() {
    return new HashMap<>(dailyUsage);
  }

  // 电表数据类
  public static class MeterData {
    long timestamp; // 时间戳(秒级)
    String meterId; // 电表ID
    long value; // 电表读数

    public MeterData(long timestamp, String meterId, long value) {
      this.timestamp = timestamp;
      this.meterId = meterId;
      this.value = value;
    }
  }

  // 电表记录类(保存上次读数和时间)
  private static class MeterRecord {
    long timestamp;
    long value;

    public MeterRecord(long timestamp, long value) {
      this.timestamp = timestamp;
      this.value = value;
    }
  }

  // 测试示例
  public static void main(String[] args) {
    ElectricityMeterProcessor processor = new ElectricityMeterProcessor();

    // 示例数据流(时间戳为秒级)
      // 2023-10-01,初始值
    processor.processMeterData(new MeterData(1696118400L, "A", 100));
      // 2023-10-02,+200
    processor.processMeterData(new MeterData(1696204800L, "A", 300));
      // 无效数据(过滤)
    processor.processMeterData(new MeterData(1696291200L, "A", 0));
      // 2023-10-03,设备重置 +50
    processor.processMeterData(new MeterData(1696291200L, "A", 50));
      // 无效数据(过滤)
    processor.processMeterData(new MeterData(1696377600L, "B", 0));
      // 2023-10-05,新电表初始值
    processor.processMeterData(new MeterData(1696464000L, "B", 200));

    // 输出结果
    System.out.println(processor.getDailyUsage());
  }
}

权限管理

有一套系统,可以对用户、权限、菜单进行管理,即通过配置用户的权限、权限可访问的菜单,完成对用户操作区域的管理。

  1. 请设计上述内容的表结构,并完成service部分的伪代码,以及所使用的SQL语句(或使用JPA表达)。
  2. 请完成接口鉴权的伪代码。(假设同一菜单的URL前缀一致)
#用户表 (user)
CREATE TABLE user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    password VARCHAR(100) NOT NULL
);
#权限表 (permission)
CREATE TABLE permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,      -- 如 "用户管理-查询"
    code VARCHAR(50) UNIQUE NOT NULL -- 如 "user:read"
);
#菜单表 (menu)
CREATE TABLE menu (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,      -- 如 "用户管理"
    url_prefix VARCHAR(100) NOT NULL, -- 如 "/api/users"
    parent_id BIGINT,               -- 支持多级菜单
    FOREIGN KEY (parent_id) REFERENCES menu(id)
);
#用户权限关联表 (user_permission)
CREATE TABLE user_permission (
    user_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    PRIMARY KEY (user_id, permission_id),
    FOREIGN KEY (user_id) REFERENCES user(id),
    FOREIGN KEY (permission_id) REFERENCES permission(id)
);
#权限菜单关联表 (permission_menu)
CREATE TABLE permission_menu (
    permission_id BIGINT NOT NULL,
    menu_id BIGINT NOT NULL,
    PRIMARY KEY (permission_id, menu_id),
    FOREIGN KEY (permission_id) REFERENCES permission(id),
    FOREIGN KEY (menu_id) REFERENCES menu(id)
);

Service 伪代码与 SQL/JPA 实现

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    // 为用户分配权限
    public void assignPermission(Long userId, Long permissionId) {
        userRepository.addUserPermission(userId, permissionId);
    }

    // 查询用户拥有的所有菜单
    public List<Menu> getUserMenus(Long userId) {
        return userRepository.findMenusByUserId(userId);
    }
}

// PermissionService.java
@Service
public class PermissionService {
    @Autowired
    private PermissionRepository permissionRepository;
    
    // 为权限关联菜单
    public void linkMenuToPermission(Long permissionId, Long menuId) {
        permissionRepository.addPermissionMenu(permissionId, menuId);
    }
}

// MenuService.java
@Service
public class MenuService {
    @Autowired
    private MenuRepository menuRepository;
    
    // 根据 URL 前缀查找菜单
    public Menu findMenuByUrlPrefix(String urlPrefix) {
        return menuRepository.findByUrlPrefix(urlPrefix);
    }
}
// User.java(实体类)
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    
    @ManyToMany
    @JoinTable(
        name = "user_permission",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id")
    )
    private Set<Permission> permissions;
}

// Permission.java(实体类)
@Entity
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String code;
    
    @ManyToMany
    @JoinTable(
        name = "permission_menu",
        joinColumns = @JoinColumn(name = "permission_id"),
        inverseJoinColumns = @JoinColumn(name = "menu_id")
    )
    private Set<Menu> menus;
}

// Menu.java(实体类)
@Entity
public class Menu {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String urlPrefix;
    
    @ManyToOne
    @JoinColumn(name = "parent_id")
    private Menu parent;
}

SQL 查询示例

-- 查询用户的所有菜单(JPA 会自动生成类似 SQL)
SELECT m.* FROM menu m
JOIN permission_menu pm ON m.id = pm.menu_id
JOIN permission p ON pm.permission_id = p.id
JOIN user_permission up ON p.id = up.permission_id
WHERE up.user_id = 1;

接口鉴权伪代码

  1. 用户请求到达接口时,解析请求 URL。
  2. 根据 URL 前缀匹配对应的菜单。
  3. 检查用户是否有权限访问该菜单关联的权限。
// AuthInterceptor.java(拦截器伪代码)
public class AuthInterceptor implements HandlerInterceptor {
    @Autowired
    private MenuService menuService;
    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String requestUrl = request.getRequestURI();
        
        // 1. 根据 URL 前缀匹配菜单
        Menu menu = menuService.findMenuByUrlPrefix(requestUrl);
        if (menu == null) {
            // 无菜单配置,默认放行或拒绝
            return true; 
        }
        
        // 2. 获取当前用户 ID(假设已通过登录验证)
        Long userId = (Long) request.getAttribute("currentUserId");
        
        // 3. 检查用户是否有权限访问该菜单
        List<Menu> userMenus = userService.getUserMenus(userId);
        if (!userMenus.contains(menu)) {
            response.sendError(403, "无权限访问");
            return false;
        }
        return true;
    }
}

分布式锁

基于spring data redis中提供的redisTempalte,实现分布式锁的功能,要求满足:

  1. 能在持有锁的线程终止后,自动释放锁。

    @Component
    public class RedisDistributedLock {
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
    
        private static final String LOCK_PREFIX = "LOCK:";
        private static final String LOCK_VALUE = UUID.randomUUID().toString(); // 确保唯一性
        private static final long DEFAULT_EXPIRE = 30000; // 默认锁过期时间 30 秒
    
        // 获取锁
        public boolean tryLock(String lockKey, long expireMillis) {
            String key = LOCK_PREFIX + lockKey;
            return Boolean.TRUE.equals(
                redisTemplate.opsForValue().setIfAbsent(
                    key, 
                    LOCK_VALUE, 
                    expireMillis > 0 ? expireMillis : DEFAULT_EXPIRE, 
                    TimeUnit.MILLISECONDS
                )
            );
        }
    
        // 释放锁(Lua 脚本保证原子性)
        public void unlock(String lockKey) {
            String key = LOCK_PREFIX + lockKey;
            String script = 
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
            redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class), 
                Collections.singletonList(key), 
                LOCK_VALUE
            );
        }
    }
    
  2. 能强行抢占锁,被强的线程能暂停执行,并等待锁的恢复。

    public class LockInterruptHandler {
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
        private static final String CHANNEL_PREFIX = "LOCK_INTERRUPT:";
    
        // 发送抢占信号
        public void sendInterruptSignal(String lockKey) {
            String channel = CHANNEL_PREFIX + lockKey;
            redisTemplate.convertAndSend(channel, "INTERRUPT");
        }
    
        // 订阅抢占信号
        public void subscribe(String lockKey, Runnable onInterrupt) {
            String channel = CHANNEL_PREFIX + lockKey;
            redisTemplate.getConnectionFactory().getConnection().subscribe(
                (message, pattern) -> onInterrupt.run(), 
                channel.getBytes(StandardCharsets.UTF_8)
            );
        }
    }
    
  3. 实现公平锁,即锁的持有是有序的。(扩展题,可不作答)

​ 使用 Redis List 维护等待队列,确保按请求顺序获取锁。

public class FairRedisLock extends RedisDistributedLock {
    private static final String QUEUE_PREFIX = "LOCK_QUEUE:";
    private static final String QUEUE_VALUE_PREFIX = "REQUEST:";

    // 获取公平锁
    public boolean tryFairLock(String lockKey, long timeout) {
        String queueKey = QUEUE_PREFIX + lockKey;
        String requestId = QUEUE_VALUE_PREFIX + UUID.randomUUID();

        // 加入等待队列
        redisTemplate.opsForList().rightPush(queueKey, requestId);

        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start < timeout) {
                // 检查是否轮到当前请求
                String head = redisTemplate.opsForList().index(queueKey, 0);
                if (requestId.equals(head) && tryLock(lockKey, DEFAULT_EXPIRE)) {
                    redisTemplate.opsForList().remove(queueKey, 0, requestId);
                    return true;
                }
                TimeUnit.MILLISECONDS.sleep(100);
            }
            return false;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        } finally {
            redisTemplate.opsForList().remove(queueKey, 0, requestId);
        }
    }
}

http://www.kler.cn/a/576551.html

相关文章:

  • Linux驱动学习笔记之I2C通信(观b站讯为电子有感)
  • HOW - React 如何在在浏览器绘制之前同步执行 - useLayoutEffect
  • 2025-03-07 electron无法打包的小问题
  • 20242817李臻《Linux⾼级编程实践》第二周
  • WordPress报502错误问题解决-php-fpm-84.service loaded failed failed LSB: starts php-fpm
  • 为AI聊天工具添加一个知识系统 之133 详细设计之74通用编程语言 之4 架构及其核心
  • 【SegRNN 源码理解】self.revIN可逆实例标准化
  • Elasticsearch:“Your trial license is expired”
  • 乐鑫打造全球首款 PSA Certified Level 2 RISC-V 芯片
  • Qt 实现绘图板(支持橡皮擦与 Ctrl+Z 撤销功能)[特殊字符]
  • React Native 0.76 升级后 APK 体积增大的原因及优化方案
  • linux的文件系统及文件类型
  • 护照阅读器在旅游景区流程中的应用
  • 深度学习网格搜索实战
  • GPO 配置的 4 种常见安全错误及安全优化策略
  • 机器学习(李宏毅)——Life-Long Learning
  • 在 Docker 中安装并配置 Nginx
  • Service 无法访问后端 Pod,如何逐步定位问题
  • C++ `bitset` 入门指南
  • 蓝桥杯P17153-班级活动 题解