Java全栈项目 - 校园招聘信息平台

news2024/12/20 9:47:21

项目介绍

校园招聘信息平台是一个面向高校学生和企业的双向服务平台。该系统帮助企业发布招聘信息,方便学生查询职位并投递简历,同时为学校就业部门提供就业数据分析功能。

技术栈

后端

  • Spring Boot 2.x
  • Spring Security
  • MyBatis Plus
  • MySQL 8.0
  • Redis
  • RabbitMQ

前端

  • Vue.js 2.x
  • Element UI
  • Axios
  • Echarts

核心功能

1. 用户模块

  • 学生注册/登录
  • 企业注册/登录
  • 管理员登录
  • 基于JWT的认证授权

2. 企业模块

  • 企业信息管理
  • 职位发布/管理
  • 简历筛选
  • 面试安排

3. 学生模块

  • 个人信息维护
  • 简历制作
  • 职位搜索/投递
  • 投递状态跟踪

4. 数据分析模块

  • 就业率统计
  • 薪资分布分析
  • 行业/地区分布
  • 就业趋势分析

系统架构

├── recruitment-system
│   ├── recruitment-common    -- 公共模块
│   ├── recruitment-admin     -- 管理后台
│   ├── recruitment-api       -- 接口服务
│   ├── recruitment-service   -- 业务逻辑
│   ├── recruitment-dao       -- 数据访问
│   └── recruitment-web       -- 前端页面

数据库设计

主要包含以下核心表:

-- 用户表
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50),
    password VARCHAR(100),
    role VARCHAR(20),
    create_time DATETIME
);

-- 企业表
CREATE TABLE company (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100),
    industry VARCHAR(50),
    scale VARCHAR(50),
    description TEXT
);

-- 职位表
CREATE TABLE position (
    id BIGINT PRIMARY KEY,
    company_id BIGINT,
    title VARCHAR(100),
    salary VARCHAR(50),
    education VARCHAR(20),
    description TEXT
);

-- 简历表
CREATE TABLE resume (
    id BIGINT PRIMARY KEY,
    student_id BIGINT,
    education TEXT,
    experience TEXT,
    skills TEXT
);

项目亮点

1. 分布式Session

使用Redis实现分布式Session,支持集群部署

2. 消息队列

使用RabbitMQ处理异步任务:

  • 简历投递通知
  • 面试通知
  • 系统消息推送

3. 全文检索

整合Elasticsearch实现职位搜索功能:

  • 多条件组合查询
  • 结果高亮显示
  • 智能推荐

4. 性能优化

  • Redis缓存热点数据
  • MyBatis SQL优化
  • 前端资源压缩
  • 图片CDN加速

项目总结

通过这个项目的开发,不仅巩固了Java全栈技术,还学习了:

  1. 大型项目的架构设计
  2. 分布式系统开发经验
  3. 性能优化最佳实践
  4. 项目开发完整流程

后续优化方向

  1. 引入Spring Cloud实现微服务架构
  2. 添加大数据分析功能
  3. 开发移动端应用
  4. 优化系统性能和用户体验

Java全栈项目 - 校园招聘信息平台(一):用户模块与企业模块详解

一、用户模块详细设计

1. 数据库设计

-- 用户基础信息表
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL COMMENT '用户名',
    password VARCHAR(100) NOT NULL COMMENT '密码',
    role VARCHAR(20) NOT NULL COMMENT '角色:STUDENT/COMPANY/ADMIN',
    email VARCHAR(100) COMMENT '邮箱',
    phone VARCHAR(20) COMMENT '手机号',
    status TINYINT DEFAULT 1 COMMENT '状态:0禁用,1启用',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 学生信息表
CREATE TABLE student_info (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL COMMENT '关联用户ID',
    real_name VARCHAR(50) COMMENT '真实姓名',
    gender TINYINT COMMENT '性别:0女,1男',
    birthday DATE COMMENT '出生日期',
    school VARCHAR(100) COMMENT '学校',
    major VARCHAR(100) COMMENT '专业',
    education VARCHAR(20) COMMENT '学历',
    graduation_year INT COMMENT '毕业年份'
);

-- 企业信息表
CREATE TABLE company_info (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL COMMENT '关联用户ID',
    company_name VARCHAR(100) NOT NULL COMMENT '企业名称',
    industry VARCHAR(50) COMMENT '所属行业',
    scale VARCHAR(50) COMMENT '企业规模',
    location VARCHAR(200) COMMENT '企业地址',
    website VARCHAR(200) COMMENT '企业官网',
    license_no VARCHAR(100) COMMENT '营业执照号',
    description TEXT COMMENT '企业简介',
    verify_status TINYINT DEFAULT 0 COMMENT '认证状态:0未认证,1已认证'
);

2. 核心功能实现

2.1 注册功能
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Transactional
    public void registerStudent(StudentRegisterDTO dto) {
        // 1. 参数校验
        validateRegisterParams(dto);
        
        // 2. 检查用户名是否存在
        checkUsernameExist(dto.getUsername());
        
        // 3. 保存用户基本信息
        SysUser user = new SysUser();
        user.setUsername(dto.getUsername());
        user.setPassword(passwordEncoder.encode(dto.getPassword()));
        user.setRole("STUDENT");
        userMapper.insert(user);
        
        // 4. 保存学生详细信息
        StudentInfo studentInfo = new StudentInfo();
        BeanUtils.copyProperties(dto, studentInfo);
        studentInfo.setUserId(user.getId());
        studentInfoMapper.insert(studentInfo);
        
        // 5. 发送注册成功邮件
        sendRegisterEmail(dto.getEmail());
    }
    
    @Transactional
    public void registerCompany(CompanyRegisterDTO dto) {
        // 类似学生注册流程
        // 额外增加企业认证材料上传
        // ...
    }
}
2.2 登录认证
@Service
public class AuthServiceImpl implements AuthService {

    @Autowired
    private JwtTokenProvider tokenProvider;
    
    public LoginVO login(LoginDTO dto) {
        // 1. 认证用户
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword())
        );
        
        // 2. 生成JWT Token
        String token = tokenProvider.generateToken(authentication);
        
        // 3. 获取用户信息
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        
        // 4. 返回登录信息
        return LoginVO.builder()
                .token(token)
                .userInfo(buildUserInfo(userDetails))
                .build();
    }
}
2.3 JWT认证授权
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain chain) {
        try {
            String jwt = getJwtFromRequest(request);
            
            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                Long userId = tokenProvider.getUserIdFromJWT(jwt);
                UserDetails userDetails = userService.loadUserById(userId);
                
                UsernamePasswordAuthenticationToken authentication = 
                    new UsernamePasswordAuthenticationToken(userDetails, null, 
                        userDetails.getAuthorities());
                        
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("JWT认证失败", ex);
        }
        
        chain.doFilter(request, response);
    }
}

二、企业模块详细设计

1. 数据库设计

-- 职位表
CREATE TABLE position (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    company_id BIGINT NOT NULL COMMENT '企业ID',
    title VARCHAR(100) NOT NULL COMMENT '职位名称',
    department VARCHAR(50) COMMENT '所属部门',
    salary_min INT COMMENT '最低薪资',
    salary_max INT COMMENT '最高薪资',
    education VARCHAR(20) COMMENT '学历要求',
    experience VARCHAR(50) COMMENT '经验要求',
    description TEXT COMMENT '职位描述',
    requirement TEXT COMMENT '任职要求',
    status TINYINT DEFAULT 1 COMMENT '状态:0下线,1上线',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 简历投递表
CREATE TABLE resume_delivery (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    position_id BIGINT NOT NULL COMMENT '职位ID',
    student_id BIGINT NOT NULL COMMENT '学生ID',
    resume_id BIGINT NOT NULL COMMENT '简历ID',
    status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态:PENDING/PASSED/REJECTED',
    hr_notes TEXT COMMENT 'HR备注',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 面试安排表
CREATE TABLE interview (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    delivery_id BIGINT NOT NULL COMMENT '投递ID',
    interview_time DATETIME NOT NULL COMMENT '面试时间',
    location VARCHAR(200) COMMENT '面试地点',
    type VARCHAR(20) COMMENT '面试方式:ONLINE/OFFLINE',
    round INT COMMENT '面试轮次',
    status VARCHAR(20) COMMENT '状态:PENDING/COMPLETED/CANCELED',
    feedback TEXT COMMENT '面试反馈',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

2. 核心功能实现

2.1 企业信息管理
@Service
public class CompanyServiceImpl implements CompanyService {

    @Transactional
    public void updateCompanyInfo(CompanyUpdateDTO dto) {
        // 1. 验证操作权限
        checkCompanyPermission(dto.getCompanyId());
        
        // 2. 更新基本信息
        CompanyInfo companyInfo = new CompanyInfo();
        BeanUtils.copyProperties(dto, companyInfo);
        companyInfoMapper.updateById(companyInfo);
        
        // 3. 处理企业Logo上传
        if (dto.getLogoFile() != null) {
            String logoUrl = fileService.uploadImage(dto.getLogoFile());
            companyInfoMapper.updateLogo(dto.getCompanyId(), logoUrl);
        }
    }
    
    public CompanyDetailVO getCompanyDetail(Long companyId) {
        // 1. 获取企业基本信息
        CompanyInfo info = companyInfoMapper.selectById(companyId);
        
        // 2. 获取在招职位数
        Integer positionCount = positionMapper.countByCompanyId(companyId);
        
        // 3. 构建返回数据
        return CompanyDetailVO.builder()
                .basicInfo(info)
                .positionCount(positionCount)
                .build();
    }
}
2.2 职位管理
@Service
public class PositionServiceImpl implements PositionService {

    @Transactional
    public void publishPosition(PositionPublishDTO dto) {
        // 1. 验证企业权限
        checkCompanyPermission(dto.getCompanyId());
        
        // 2. 保存职位信息
        Position position = new Position();
        BeanUtils.copyProperties(dto, position);
        positionMapper.insert(position);
        
        // 3. 发送职位消息到消息队列
        sendPositionMessage(position);
        
        // 4. 更新搜索索引
        updateSearchIndex(position);
    }
    
    public PageResult<PositionVO> queryPositions(PositionQueryDTO query) {
        // 1. 构建查询条件
        LambdaQueryWrapper<Position> wrapper = new QueryWrapper<Position>()
            .lambda()
            .eq(query.getCompanyId() != null, Position::getCompanyId, query.getCompanyId())
            .like(StringUtils.isNotBlank(query.getKeyword()), Position::getTitle, query.getKeyword())
            .eq(Position::getStatus, 1);
            
        // 2. 分页查询
        Page<Position> page = positionMapper.selectPage(
            new Page<>(query.getPageNum(), query.getPageSize()), 
            wrapper
        );
        
        // 3. 转换返回结果
        return PageResult.build(page, this::convertToVO);
    }
}
2.3 简历筛选
@Service
public class ResumeDeliveryServiceImpl implements ResumeDeliveryService {

    public PageResult<DeliveryVO> queryDeliveryList(DeliveryQueryDTO query) {
        // 1. 分页查询投递记录
        Page<ResumeDelivery> page = deliveryMapper.queryDeliveryList(
            new Page<>(query.getPageNum(), query.getPageSize()),
            query
        );
        
        // 2. 批量查询简历信息
        List<Long> resumeIds = page.getRecords().stream()
            .map(ResumeDelivery::getResumeId)
            .collect(Collectors.toList());
        Map<Long, ResumeVO> resumeMap = resumeService.batchGetResumeVO(resumeIds);
        
        // 3. 构建返回数据
        return PageResult.build(page, delivery -> {
            DeliveryVO vo = new DeliveryVO();
            BeanUtils.copyProperties(delivery, vo);
            vo.setResume(resumeMap.get(delivery.getResumeId()));
            return vo;
        });
    }
    
    @Transactional
    public void updateDeliveryStatus(DeliveryStatusDTO dto) {
        // 1. 更新投递状态
        ResumeDelivery delivery = new ResumeDelivery();
        delivery.setId(dto.getDeliveryId());
        delivery.setStatus(dto.getStatus());
        delivery.setHrNotes(dto.getNotes());
        deliveryMapper.updateById(delivery);
        
        // 2. 发送状态变更通知
        messageService.sendDeliveryStatusMessage(dto);
    }
}
2.4 面试安排
@Service
public class InterviewServiceImpl implements InterviewService {

    @Transactional
    public void arrangeInterview(InterviewArrangeDTO dto) {
        // 1. 创建面试记录
        Interview interview = new Interview();
        BeanUtils.copyProperties(dto, interview);
        interviewMapper.insert(interview);
        
        // 2. 更新投递状态
        deliveryService.updateStatus(dto.getDeliveryId(), "INTERVIEWING");
        
        // 3. 发送面试通知
        ResumeDelivery delivery = deliveryMapper.selectById(dto.getDeliveryId());
        sendInterviewNotification(delivery, interview);
    }
    
    private void sendInterviewNotification(ResumeDelivery delivery, Interview interview) {
        // 1. 发送邮件通知
        EmailMessage emailMessage = buildInterviewEmailMessage(delivery, interview);
        messageService.sendEmail(emailMessage);
        
        // 2. 发送短信通知
        SmsMessage smsMessage = buildInterviewSmsMessage(delivery, interview);
        messageService.sendSms(smsMessage);
        
        // 3. 发送系统站内信
        SystemMessage systemMessage = buildInterviewSystemMessage(delivery, interview);
        messageService.sendSystemMessage(systemMessage);
    }
}

3. 接口文档示例

/api/company/info:
  put:
    summary: 更新企业信息
    parameters:
      - name: dto
        in: body
        required: true
        schema:
          type: object
          properties:
            companyId:
              type: integer
              description: 企业ID
            companyName:
              type: string
              description: 企业名称
            industry:
              type: string
              description: 所属行业
    responses:
      200:
        description: 更新成功
        
/api/position/publish:
  post:
    summary: 发布职位
    parameters:
      - name: dto
        in: body
        required: true
        schema:
          type: object
          properties:
            companyId:
              type: integer
            title:
              type: string
            salary:
              type: string
    responses:
      200:
        description: 发布成功

以上是用户模块和企业模块的详细设计。包含了:

  1. 完整的数据库表设计
  2. 核心业务逻辑实现
  3. 关键功能的代码示例
  4. 接口文档规范

这些模块的实现考虑了:

  • 数据安全性(密码加密、JWT认证)
  • 业务完整性(事务管理)
  • 异步处理(消息队列)
  • 通知提醒(邮件、短信、站内信)

Java全栈项目 - 校园招聘信息平台(二):学生模块与数据分析模块详解

一、学生模块详细设计

1. 数据库设计

-- 简历表
CREATE TABLE resume (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    student_id BIGINT NOT NULL COMMENT '学生ID',
    title VARCHAR(100) COMMENT '简历标题',
    avatar_url VARCHAR(200) COMMENT '头像URL',
    expect_salary VARCHAR(50) COMMENT '期望薪资',
    expect_city VARCHAR(50) COMMENT '期望城市',
    expect_position VARCHAR(100) COMMENT '期望职位',
    self_evaluation TEXT COMMENT '自我评价',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 教育经历表
CREATE TABLE resume_education (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    resume_id BIGINT NOT NULL COMMENT '简历ID',
    school VARCHAR(100) NOT NULL COMMENT '学校名称',
    major VARCHAR(100) COMMENT '专业',
    degree VARCHAR(50) COMMENT '学历',
    start_date DATE COMMENT '开始时间',
    end_date DATE COMMENT '结束时间',
    gpa VARCHAR(20) COMMENT '学分绩点',
    description TEXT COMMENT '在校经历'
);

-- 项目经历表
CREATE TABLE resume_project (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    resume_id BIGINT NOT NULL,
    name VARCHAR(100) NOT NULL COMMENT '项目名称',
    role VARCHAR(50) COMMENT '担任角色',
    start_date DATE COMMENT '开始时间',
    end_date DATE COMMENT '结束时间',
    description TEXT COMMENT '项目描述',
    achievement TEXT COMMENT '项目成就'
);

-- 求职进度表
CREATE TABLE job_progress (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    student_id BIGINT NOT NULL,
    position_id BIGINT NOT NULL,
    company_id BIGINT NOT NULL,
    status VARCHAR(20) COMMENT '状态:待处理/已查看/面试/通过/拒绝',
    latest_time DATETIME COMMENT '最新进展时间',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

2. 核心功能实现

2.1 简历管理
@Service
public class ResumeServiceImpl implements ResumeService {
    
    @Transactional
    public Long createResume(ResumeCreateDTO dto) {
        // 1. 保存基本信息
        Resume resume = new Resume();
        BeanUtils.copyProperties(dto, resume);
        resumeMapper.insert(resume);
        
        // 2. 保存教育经历
        saveEducationList(resume.getId(), dto.getEducationList());
        
        // 3. 保存项目经历
        saveProjectList(resume.getId(), dto.getProjectList());
        
        // 4. 处理头像上传
        if (dto.getAvatarFile() != null) {
            String avatarUrl = fileService.uploadImage(dto.getAvatarFile());
            resumeMapper.updateAvatar(resume.getId(), avatarUrl);
        }
        
        return resume.getId();
    }
    
    public ResumeDetailVO getResumeDetail(Long resumeId) {
        // 1. 获取基本信息
        Resume resume = resumeMapper.selectById(resumeId);
        
        // 2. 获取教育经历
        List<ResumeEducation> educationList = educationMapper.selectByResumeId(resumeId);
        
        // 3. 获取项目经历
        List<ResumeProject> projectList = projectMapper.selectByResumeId(resumeId);
        
        // 4. 构建返回数据
        return ResumeDetailVO.builder()
                .basicInfo(resume)
                .educationList(educationList)
                .projectList(projectList)
                .build();
    }
}
2.2 职位搜索
@Service
public class PositionSearchServiceImpl implements PositionSearchService {

    @Autowired
    private ElasticsearchRestTemplate esTemplate;

    public PageResult<PositionVO> searchPositions(PositionSearchDTO dto) {
        // 1. 构建搜索条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        
        // 关键词搜索
        if (StringUtils.isNotBlank(dto.getKeyword())) {
            boolQuery.must(QueryBuilders.multiMatchQuery(dto.getKeyword())
                .field("title", 3.0f)
                .field("description")
                .field("requirement"));
        }
        
        // 城市筛选
        if (StringUtils.isNotBlank(dto.getCity())) {
            boolQuery.filter(QueryBuilders.termQuery("city", dto.getCity()));
        }
        
        // 薪资范围
        if (dto.getSalaryMin() != null && dto.getSalaryMax() != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("salaryMin")
                .gte(dto.getSalaryMin())
                .lte(dto.getSalaryMax()));
        }
        
        // 2. 执行搜索
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(boolQuery)
            .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
            .withPageable(PageRequest.of(dto.getPageNum(), dto.getPageSize()))
            .build();
            
        SearchHits<PositionES> searchHits = esTemplate.search(searchQuery, PositionES.class);
        
        // 3. 处理高亮显示
        return PageResult.build(searchHits, this::convertToVO);
    }
}
2.3 职位投递
@Service
public class JobApplicationServiceImpl implements JobApplicationService {

    @Transactional
    public void applyPosition(JobApplyDTO dto) {
        // 1. 检查是否重复投递
        checkDuplicateApply(dto.getStudentId(), dto.getPositionId());
        
        // 2. 创建投递记录
        ResumeDelivery delivery = new ResumeDelivery();
        delivery.setStudentId(dto.getStudentId());
        delivery.setPositionId(dto.getPositionId());
        delivery.setResumeId(dto.getResumeId());
        delivery.setStatus("PENDING");
        deliveryMapper.insert(delivery);
        
        // 3. 创建求职进度
        JobProgress progress = new JobProgress();
        progress.setStudentId(dto.getStudentId());
        progress.setPositionId(dto.getPositionId());
        progress.setCompanyId(dto.getCompanyId());
        progress.setStatus("待处理");
        progressMapper.insert(progress);
        
        // 4. 发送投递通知
        sendDeliveryNotification(delivery);
    }
    
    public List<JobProgressVO> getProgressList(Long studentId) {
        // 1. 查询进度列表
        List<JobProgress> progressList = progressMapper.selectByStudentId(studentId);
        
        // 2. 批量查询职位和公司信息
        Set<Long> positionIds = progressList.stream()
            .map(JobProgress::getPositionId)
            .collect(Collectors.toSet());
        Map<Long, PositionVO> positionMap = positionService.batchGetPositionVO(positionIds);
        
        // 3. 构建返回数据
        return progressList.stream()
            .map(progress -> {
                JobProgressVO vo = new JobProgressVO();
                BeanUtils.copyProperties(progress, vo);
                vo.setPosition(positionMap.get(progress.getPositionId()));
                return vo;
            })
            .collect(Collectors.toList());
    }
}

二、数据分析模块详细设计

1. 数据库设计

-- 就业统计表
CREATE TABLE employment_stats (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    year INT NOT NULL COMMENT '统计年份',
    month INT NOT NULL COMMENT '统计月份',
    school_id BIGINT COMMENT '学校ID',
    major_id BIGINT COMMENT '专业ID',
    total_students INT COMMENT '毕业生总数',
    employed_count INT COMMENT '已就业人数',
    employment_rate DECIMAL(5,2) COMMENT '就业率',
    avg_salary DECIMAL(10,2) COMMENT '平均薪资',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 行业分布表
CREATE TABLE industry_distribution (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    year INT NOT NULL,
    industry VARCHAR(50) NOT NULL COMMENT '行业',
    student_count INT COMMENT '就业人数',
    salary_avg DECIMAL(10,2) COMMENT '平均薪资',
    proportion DECIMAL(5,2) COMMENT '占比'
);

-- 地区分布表
CREATE TABLE region_distribution (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    year INT NOT NULL,
    province VARCHAR(50) NOT NULL COMMENT '省份',
    city VARCHAR(50) NOT NULL COMMENT '城市',
    student_count INT COMMENT '就业人数',
    salary_avg DECIMAL(10,2) COMMENT '平均薪资',
    proportion DECIMAL(5,2) COMMENT '占比'
);

2. 核心功能实现

2.1 就业率统计
@Service
public class EmploymentStatsServiceImpl implements EmploymentStatsService {

    public EmploymentOverviewVO calculateEmploymentStats(StatsQueryDTO query) {
        // 1. 查询基础数据
        List<EmploymentStats> statsList = statsMapper.queryEmploymentStats(query);
        
        // 2. 计算总体就业率
        BigDecimal totalRate = calculateTotalRate(statsList);
        
        // 3. 计算同比/环比
        Map<String, BigDecimal> compareData = calculateCompareData(query);
        
        // 4. 构建趋势数据
        List<TrendPointVO> trendData = buildTrendData(query);
        
        return EmploymentOverviewVO.builder()
                .employmentRate(totalRate)
                .yearOnYear(compareData.get("yearOnYear"))
                .monthOnMonth(compareData.get("monthOnMonth"))
                .trendData(trendData)
                .build();
    }
    
    private List<TrendPointVO> buildTrendData(StatsQueryDTO query) {
        // 1. 获取时间范围
        Date startDate = DateUtils.addMonths(new Date(), -12);
        Date endDate = new Date();
        
        // 2. 查询趋势数据
        List<EmploymentStats> trendList = statsMapper.queryTrendData(
            query.getSchoolId(), 
            startDate, 
            endDate
        );
        
        // 3. 按月份聚合数据
        return trendList.stream()
            .collect(Collectors.groupingBy(
                stats -> stats.getYear() + "-" + stats.getMonth(),
                Collectors.averagingDouble(EmploymentStats::getEmploymentRate)
            ))
            .entrySet().stream()
            .map(entry -> new TrendPointVO(entry.getKey(), entry.getValue()))
            .collect(Collectors.toList());
    }
}
2.2 薪资分析
@Service
public class SalaryAnalysisServiceImpl implements SalaryAnalysisService {

    public SalaryAnalysisVO analyzeSalary(AnalysisQueryDTO query) {
        // 1. 计算薪资区间分布
        List<SalaryRangeVO> rangeList = calculateSalaryRange(query);
        
        // 2. 计算专业薪资对比
        List<MajorSalaryVO> majorList = compareMajorSalary(query);
        
        // 3. 计算行业薪资水平
        List<IndustrySalaryVO> industryList = calculateIndustrySalary(query);
        
        return SalaryAnalysisVO.builder()
                .rangeDistribution(rangeList)
                .majorComparison(majorList)
                .industryLevel(industryList)
                .build();
    }
    
    private List<SalaryRangeVO> calculateSalaryRange(AnalysisQueryDTO query) {
        // 1. 定义薪资区间
        List<SalaryRange> ranges = Arrays.asList(
            new SalaryRange(0, 5000),
            new SalaryRange(5000, 10000),
            new SalaryRange(10000, 15000),
            new SalaryRange(15000, 20000),
            new SalaryRange(20000, Integer.MAX_VALUE)
        );
        
        // 2. 统计各区间人数
        List<Map<String, Object>> stats = deliveryMapper.statSalaryRange(
            query.getYear(),
            query.getSchoolId()
        );
        
        // 3. 计算占比
        return calculateProportions(stats, ranges);
    }
}
2.3 行业分布分析
@Service
public class IndustryAnalysisServiceImpl implements IndustryAnalysisService {

    public IndustryAnalysisVO analyzeIndustryDistribution(AnalysisQueryDTO query) {
        // 1. 查询行业分布数据
        List<IndustryDistribution> distributionList = 
            distributionMapper.queryIndustryDistribution(query);
            
        // 2. 计算TOP10行业
        List<IndustryProportionVO> top10 = calculateTop10Industries(distributionList);
        
        // 3. 计算行业发展趋势
        List<IndustryTrendVO> trendList = calculateIndustryTrend(query);
        
        return IndustryAnalysisVO.builder()
                .industryTop10(top10)
                .industryTrend(trendList)
                .build();
    }
    
    @Cacheable(value = "industryTrend", key = "#query.year")
    private List<IndustryTrendVO> calculateIndustryTrend(AnalysisQueryDTO query) {
        // 1. 获取近5年数据
        List<IndustryDistribution> historyData = 
            distributionMapper.queryHistoryDistribution(query.getYear() - 5, query.getYear());
            
        // 2. 按行业分组
        Map<String, List<IndustryDistribution>> industryMap = 
            historyData.stream().collect(Collectors.groupingBy(IndustryDistribution::getIndustry));
            
        // 3. 计算增长率
        return industryMap.entrySet().stream()
            .map(entry -> {
                String industry = entry.getKey();
                List<IndustryDistribution> industryData = entry.getValue();
                BigDecimal growthRate = calculateGrowthRate(industryData);
                return new IndustryTrendVO(industry, growthRate);
            })
            .collect(Collectors.toList());
    }
}

3. 数据可视化实现

// 使用ECharts实现可视化图表
const employmentChart = {
    initEmploymentRate(data) {
        const chart = echarts.init(document.getElementById('employment-rate'));
        const option = {
            title: {
                text: '就业率趋势分析'
            },
            tooltip: {
                trigger: 'axis'
            },
            xAxis: {
                type: 'category',
                data: data.map(item => item.month)
            },
            yAxis: {
                type: 'value',
                axisLabel: {
                    formatter: '{value}%'
                }
            },
            series: [{
                name: '就业率',
                type: 'line',
                data: data.map(item => item.rate),
                markPoint: {
                    data: [
                        {type: 'max', name: '最大值'},
                        {type: 'min', name: '最小值'}
                    ]
                }
            }]
        };
        chart.setOption(option);
    },
    
    initSalaryDistribution(data) {
        const chart = echarts.init(document.getElementById('salary-dist'));
        const option = {
            title: {
                text: '薪资分布'
            },
            tooltip: {
                trigger: 'item',
                formatter: '{b}: {c} ({d}%)'
            },
            series: [{
                type: 'pie',
                radius: ['50%', '70%'],
                data: data.map(item => ({
                    name: item.range,
                    value: item.count
                }))
            }]
        };
        chart.setOption(option);
    }
};

4. 数据导出功能

@Service
public class DataExportServiceImpl implements DataExportService {

    public void exportEmploymentStats(ExportQueryDTO query, HttpServletResponse response) {
        // 1. 查询数据
        List<EmploymentStats> statsList = statsMapper.queryForExport(query);
        
        // 2. 创建Excel工作簿
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("就业统计");
        
        // 3. 创建表头
        Row headerRow = sheet.createRow(0);
        String[] headers = {"统计年月", "专业", "毕业人数", "就业人数", "就业率", "平均薪资"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
        }
        
        // 4. 填充数据
        int rowNum = 1;
        for (EmploymentStats stats : statsList) {
            Row row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(stats.getYear() + "-" + stats.getMonth());
            row.createCell(1).setCellValue(stats.getMajorName());
            row.createCell(2).setCellValue(stats.getTotalStudents());
            row.createCell(3).setCellValue(stats.getEmployedCount());
            row.createCell(4).setCellValue(stats.getEmploymentRate() + "%");
            row.createCell(5).setCellValue(stats.getAvgSalary());
        }
        
        // 5. 导出文件
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment; filename=employment_stats.xlsx");
        workbook.write(response.getOutputStream());
    }
}

以上是学生模块和数据分析模块的详细设计。主要特点包括:

  1. 学生模块:
  • 完整的简历管理功能
  • 基于Elasticsearch的职位搜索
  • 完善的投递跟踪机制
  1. 数据分析模块:
  • 多维度的统计分析
  • 可视化图表展示
  • 数据导出功能

技术亮点:

  • 使用Redis缓存热点数据
  • 使用ECharts实现数据可视化
  • 采用POI处理Excel导出
  • 实现数据实时统计和离线分析

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

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

相关文章

c语言----顺序结构

顺序结构的基本概念 定义&#xff1a;顺序结构是C语言程序中最基本的结构&#xff0c;它按照语句的先后顺序依次执行。就像我们日常做事一样&#xff0c;一步一步地按照顺序来完成任务。在C语言程序中&#xff0c;从程序的第一条语句开始&#xff0c;逐句向下执行&#xff0c;…

基于base32的兑换码算法(思路)

base32编码指的是基于32个可打印字符对任意字节数据进行编码&#xff1a;大写字母A-Z以及数字2-7。 兑换码要求:长度为10个字符 如果将这32个字符依次放到一个base数组中&#xff0c;那么最大的下标就是31。我们将要编码的任意字节数据按照五个bit为一组进行划分&#xff0c;…

python学opencv|读取图像(十六)修改HSV图像HSV值

【1】引言 前序学习进程中&#xff0c;我们已经掌握了对HSV通道和BGR通道的拆分和合并&#xff0c;并通过自由组合的形式&#xff0c;获得了和初始图像完全不一样的新图像&#xff0c;相关文章可以参考下述链接&#xff1a; python学opencv|读取图像&#xff08;十四&#xf…

用QT制作的倒计时软件

一、pro代码 RC_ICONS countdown.ico 二、mainwindow.cpp代码 #include "mainwindow.h" #include "ui_mainwindow.h"#include <QDateTime> #include <QMessageBox> #include <QSettings>MainWindow::MainWindow(QWidget *parent): QM…

VScode MAC按任意键关闭终端 想要访问桌面文件

说明 最近配置MAC上CPP的运行环境&#xff0c;在安装必要的CPP插件后&#xff0c;配置launch和task等json文件后&#xff0c;点击运行三角形&#xff0c;每次都会跳出main想要访问桌面上的文件。并且输出也是在调试控制台&#xff0c;非常逆天。 尝试 尝试1:尽管我尝试将ta…

【一本通】线段

【一本通】线段 C语言代码Java代码C代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 数轴上有 n条线段&#xff0c;选取其中 k条线段使得这 k条线段两两没有重合部分&#xff0c;问最大的k为多少? 输入 输入文件segment.in的第一行为…

学习日志024--opencv中处理轮廓的函数

目录 前言​​​​​​​ 一、 梯度处理的sobel算子函数 功能 参数 返回值 代码演示 二、梯度处理拉普拉斯算子 功能 参数 返回值 代码演示 三、Canny算子 功能 参数 返回值 代码演示 四、findContours函数与drawContours函数 功能 参数 返回值 代码演示 …

关于分页的样式问题

在最近写网页的时候遇到了一个关于样式的问题&#xff0c;今天我来跟大家来说一下。像是分页中的颜色效果&#xff0c;斑马纹颜色要注意颜色不要过于深。 这种的颜色就有一点深看着很不舒服&#xff0c;应将当前的颜色改为淡一点的&#xff0c;也可以利用rgba调整透明度&#x…

机器学习周报(12.9-12.15)

文章目录 摘要Abstract 1 Swin Transformer1.1 输入1.2 Patch Partition1.3 Linear Embedding1.4 Patch Merging1.5 Swin Transformer Block1.6 代码总结 摘要 本篇博客介绍了采用类似于卷积核的移动窗口进行图像特征提取的Swin Transformer网络模型&#xff0c;这是一种基于T…

基于Spring Boot的校园车辆管理系统

一、系统背景与意义 随着校园规模的不断扩大和车辆数量的增加&#xff0c;传统的车辆管理方式已经难以满足高效、准确管理车辆的需求。因此&#xff0c;开发一个基于Spring Boot的校园车辆管理系统具有重要的现实意义。该系统可以实现对校园车辆的信息化管理&#xff0c;提高车…

SpringBoot3整合FastJSON2如何配置configureMessageConverters

在 Spring Boot 3 中整合 FastJSON 2 主要涉及到以下几个步骤&#xff0c;包括添加依赖、配置 FastJSON 作为 JSON 处理器等。下面是详细的步骤&#xff1a; 1. 添加依赖 首先&#xff0c;你需要在你的 pom.xml 文件中添加 FastJSON 2 的依赖。以下是 Maven 依赖的示例&#…

隐私清理工具Goversoft Privazer

PrivaZer 是一款专为隐私保护而生的 Windows 系统清理工具&#xff0c;支持深度扫描、清除无用文件和隐私痕迹。 PrivaZer - 深度扫描磁盘&#xff0c;自动清理上网痕迹&#xff0c;全面保护 Windows 的网络隐私 释放磁盘空间 硬盘空间告急&#xff0c;想清理却又无从下手&…

UDP网络编程套接

目录 本文核心 预备知识 1.端口号 认识TCP协议 认识UDP协议 网络字节序 socket编程接口 sockaddr结构 UDP套接字编程 服务端 客户端 TCP与UDP传输的区别 可靠性&#xff1a; 传输方式&#xff1a; 用途&#xff1a; 头部开销&#xff1a; 速度&#xff1a; li…

3.zabbix中文设置

1、zabbix中文设置 2、中文乱码的原因 zabbix使用DejaVuSan.ttf字体&#xff0c;不支持中文&#xff0c;导致中文出现乱码。解决方法很简单&#xff0c;把我们电脑里面字体文件传到zabbix服务器上。 3、解决zabbix乱码方法 3.1、从Window服务器找到相应的字休复制到zabbix S…

【Vue3学习】setup语法糖中的ref,reactive,toRef,toRefs

在 Vue 3 的组合式 API&#xff08;Composition API&#xff09;中&#xff0c;ref、reactive、toRef 和 toRefs 是四个非常重要的工具函数&#xff0c;用于创建和管理响应式数据。 一、ref 用ref()包裹数据,返回的响应式引用对象&#xff0c;包含一个 .value 属性&#xff0…

图书馆管理系统(三)基于jquery、ajax

任务3.4 借书还书页面 任务描述 这部分主要是制作借书还书的界面&#xff0c;这里我分别制作了两个网页分别用来借书和还书。此页面&#xff0c;也是通过获取books.txt内容然后添加到表格中&#xff0c;但是借还的操作没有添加到后端中去&#xff0c;只是一个简单的前端操作。…

springboot450房屋租赁管理系统(论文+源码)_kaic

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统房屋租赁管理系统信息管理难度大&#xff0c;容错率低&am…

vue框架的搭建

1什么是Node.js&#xff1b; Node.js 是一个免费、开源、跨平台的 JavaScript 运行时环境, 它让开发人员能够创建服务器 Web 应用、命令行工具和脚本 Node.js下载&#xff1a; 下载Node 16.20.2 安装Node.js 安装Node.js 测试安装 运行命令行 win键R 查看node版本 输入&am…

TCP基础了解

什么是 TCP &#xff1f; TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。 面向连接&#xff1a;一定是「一对一」才能连接&#xff0c;不能像 UDP 协议可以一个主机同时向多个主机发送消息&#xff0c;也就是一对多是无法做到的&#xff1b; 可靠的&#xff1a;无论…

电商数据采集电商,行业数据分析,平台数据获取|稳定的API接口数据

电商数据采集可以通过多种方式完成&#xff0c;其中包括人工采集、使用电商平台提供的API接口、以及利用爬虫技术等自动化工具。以下是一些常用的电商数据采集方法&#xff1a; 人工采集&#xff1a;人工采集主要是通过基本的“复制粘贴”的方式在电商平台上进行数据的收集&am…