提交学生信息管理界面的修改
This commit is contained in:
1 parent
94f07a059b
commit
5a14df9655
13 files changed
+2357
-76
No files matched your search
+40
@@ -1,6 +1,7 @@
|
||||
package org.dromara.xg.controller;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -52,6 +53,45 @@ public class XgStudentMsgController extends BaseController {
|
||||
return xgStudentMsgService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据组织ID查询学生信息列表
|
||||
*/
|
||||
@SaCheckPermission("studentMsg:studentMsg:list")
|
||||
@PostMapping("/listByDeptId")
|
||||
public TableDataInfo<XgStudentMsgVo> listByDeptId(@RequestBody Map<String, Object> requestBody,
|
||||
PageQuery pageQuery) {
|
||||
// 从请求体中解析查询条件和部门ID
|
||||
XgStudentMsgBo bo = new XgStudentMsgBo();
|
||||
|
||||
// 设置基础查询条件
|
||||
if (requestBody.containsKey("name")) {
|
||||
bo.setName((String) requestBody.get("name"));
|
||||
}
|
||||
if (requestBody.containsKey("studentNo")) {
|
||||
bo.setStudentNo((String) requestBody.get("studentNo"));
|
||||
}
|
||||
if (requestBody.containsKey("phone")) {
|
||||
bo.setPhone((String) requestBody.get("phone"));
|
||||
}
|
||||
if (requestBody.containsKey("studentStatus")) {
|
||||
bo.setStudentStatus((String) requestBody.get("studentStatus"));
|
||||
}
|
||||
|
||||
// 设置时间范围查询条件
|
||||
Map<String, Object> params = bo.getParams();
|
||||
if (requestBody.containsKey("beginTime")) {
|
||||
params.put("beginTime", requestBody.get("beginTime"));
|
||||
}
|
||||
if (requestBody.containsKey("endTime")) {
|
||||
params.put("endTime", requestBody.get("endTime"));
|
||||
}
|
||||
|
||||
// 提取部门ID参数
|
||||
Map<String, Object> deptParams = Map.of("dept_id", requestBody.get("dept_id"));
|
||||
|
||||
return xgStudentMsgService.queryPageListByDeptId(bo, deptParams, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出学生信息列表
|
||||
*/
|
||||
|
||||
+22
@@ -658,4 +658,26 @@ public class XgStudentMsgVo implements Serializable {
|
||||
@ExcelProperty(value = "备注信息")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
+17
@@ -3,6 +3,11 @@ package org.dromara.xg.mapper;
|
||||
import org.dromara.xg.domain.XgStudentMsg;
|
||||
import org.dromara.xg.domain.vo.XgStudentMsgVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生信息Mapper接口
|
||||
@@ -12,4 +17,16 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
*/
|
||||
public interface XgStudentMsgMapper extends BaseMapperPlus<XgStudentMsg, XgStudentMsgVo> {
|
||||
|
||||
/**
|
||||
* 根据组织ID列表分页查询学生信息
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param queryWrapper 查询条件
|
||||
* @param deptIds 组织ID列表
|
||||
* @return 学生信息分页结果
|
||||
*/
|
||||
Page<XgStudentMsgVo> selectPageByDeptIds(@Param("page") Page<XgStudentMsgVo> page,
|
||||
@Param("ew") LambdaQueryWrapper<XgStudentMsg> queryWrapper,
|
||||
@Param("deptIds") List<Long> deptIds);
|
||||
|
||||
}
|
||||
+12
@@ -7,6 +7,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 学生信息Service接口
|
||||
@@ -41,6 +42,17 @@ public interface IXgStudentMsgService {
|
||||
*/
|
||||
List<XgStudentMsgVo> queryList(XgStudentMsgBo bo);
|
||||
|
||||
/**
|
||||
* 根据组织ID分页查询学生信息列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param params 包含dept_id的参数Map
|
||||
* @param pageQuery 分页参数
|
||||
* @return 学生信息分页列表
|
||||
*/
|
||||
TableDataInfo<XgStudentMsgVo> queryPageListByDeptId(XgStudentMsgBo bo, Map<String, Object> params,
|
||||
PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 新增学生信息
|
||||
*
|
||||
|
||||
+75
@@ -17,11 +17,17 @@ import org.dromara.xg.domain.XgStudentMsg;
|
||||
import org.dromara.xg.mapper.XgStudentMsgMapper;
|
||||
import org.dromara.xg.service.IXgStudentMsgService;
|
||||
import org.dromara.system.service.ISysUserService;
|
||||
import org.dromara.system.mapper.SysDeptMapper;
|
||||
import org.dromara.system.domain.bo.SysUserBo;
|
||||
import org.dromara.system.domain.SysDept;
|
||||
import org.dromara.system.domain.vo.SysDeptVo;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 学生信息Service业务层处理
|
||||
@@ -36,6 +42,7 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService {
|
||||
|
||||
private final XgStudentMsgMapper baseMapper;
|
||||
private final ISysUserService sysUserService;
|
||||
private final SysDeptMapper sysDeptMapper;
|
||||
|
||||
/**
|
||||
* 查询学生信息
|
||||
@@ -74,6 +81,74 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService {
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据组织ID分页查询学生信息列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param params 包含dept_id的参数Map
|
||||
* @param pageQuery 分页参数
|
||||
* @return 学生信息分页列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XgStudentMsgVo> queryPageListByDeptId(XgStudentMsgBo bo, Map<String, Object> params,
|
||||
PageQuery pageQuery) {
|
||||
// 第一步:获取dept_id并查找所有子组织
|
||||
List<Long> deptIds = new ArrayList<>();
|
||||
Object deptIdObj = params.get("dept_id");
|
||||
if (ObjectUtil.isNotNull(deptIdObj)) {
|
||||
Long deptId = Long.valueOf(deptIdObj.toString());
|
||||
deptIds.add(deptId); // 包含自己
|
||||
|
||||
// 查找所有子组织
|
||||
List<SysDept> childDepts = sysDeptMapper.selectListByParentId(deptId);
|
||||
if (!childDepts.isEmpty()) {
|
||||
List<Long> childDeptIds = childDepts.stream()
|
||||
.map(SysDept::getDeptId)
|
||||
.collect(Collectors.toList());
|
||||
deptIds.addAll(childDeptIds);
|
||||
}
|
||||
|
||||
log.info("组织ID {} 及其子组织共 {} 个: {}", deptId, deptIds.size(), deptIds);
|
||||
}
|
||||
|
||||
// 第二步:构建查询条件
|
||||
LambdaQueryWrapper<XgStudentMsg> lqw = buildQueryWrapperForDeptQuery(bo);
|
||||
|
||||
// 第三步:分页查询学生信息
|
||||
Page<XgStudentMsgVo> result = baseMapper.selectPageByDeptIds(pageQuery.build(), lqw, deptIds);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建针对组织查询的查询条件包装器
|
||||
*/
|
||||
private LambdaQueryWrapper<XgStudentMsg> buildQueryWrapperForDeptQuery(XgStudentMsgBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<XgStudentMsg> lqw = Wrappers.lambdaQuery();
|
||||
|
||||
// 基础查询条件
|
||||
lqw.eq(XgStudentMsg::getDeleted, 0); // 未删除
|
||||
|
||||
// 根据已知的查询条件进行过滤
|
||||
// 姓名模糊查询
|
||||
lqw.like(StringUtils.isNotBlank(bo.getName()), XgStudentMsg::getName, bo.getName());
|
||||
// 学号模糊查询
|
||||
lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), XgStudentMsg::getStudentNo, bo.getStudentNo());
|
||||
// 手机号模糊查询
|
||||
lqw.like(StringUtils.isNotBlank(bo.getPhone()), XgStudentMsg::getPhone, bo.getPhone());
|
||||
// 学籍状态精确查询
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getStudentStatus()), XgStudentMsg::getStudentStatus, bo.getStudentStatus());
|
||||
|
||||
// 创建时间范围查询
|
||||
if (params.get("beginTime") != null && params.get("endTime") != null) {
|
||||
lqw.between(XgStudentMsg::getCreateTime, params.get("beginTime"), params.get("endTime"));
|
||||
}
|
||||
|
||||
// 注意:排序在XML中处理,这里不添加排序条件,避免SQL语法错误
|
||||
|
||||
return lqw;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<XgStudentMsg> buildQueryWrapper(XgStudentMsgBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<XgStudentMsg> lqw = Wrappers.lambdaQuery();
|
||||
|
||||
+33
@@ -4,4 +4,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.xg.mapper.XgStudentMsgMapper">
|
||||
|
||||
<!-- 根据组织ID列表分页查询学生信息 -->
|
||||
<select id="selectPageByDeptIds" resultType="org.dromara.xg.domain.vo.XgStudentMsgVo">
|
||||
SELECT
|
||||
id, deleted, student_no, id_card, name, name_english, former_name, gender, birth_date,
|
||||
nationality, native_place, political_status, hukou_location, home_address, current_address,
|
||||
postal_code, photo_url, photo_path, phone, is_boarder, education_level, household_register_type,
|
||||
religion, source_region, graduate_school, email, qq, wechat, marital_status, emergency_contact_name,
|
||||
emergency_contact_phone, emergency_contact_relation, is_poor_student, is_minority, is_disabled,
|
||||
is_orphan, is_left_behind_child, is_single_child, is_low_income, has_loan, loan_amount,
|
||||
specialties, awards, violations, social_activities, father_name, father_phone, father_work_unit,
|
||||
mother_name, mother_phone, mother_work_unit, guardian_name, guardian_phone, guardian_relation,
|
||||
family_income, per_capita_income, income_source, family_economic_status, family_members,
|
||||
height, weight, blood_type, health_status, special_condition, medical_history, allergies,
|
||||
disability_type, disability_level, admission_category, student_status, enrollment_year,
|
||||
enrollment_date, bank_card_no, bank_name, bank_branch, card_holder_name, info_status,
|
||||
data_source, last_batch_id, last_audit_time, is_complete, completion_rate, school_system,
|
||||
academic_level, training_type, major_id, major_name, class_id, class_name, grade,
|
||||
college_id, college_name, counselor_id, counselor_name, class_teacher_id, class_teacher_name,
|
||||
dormitory_building, dormitory_room, ext_field1, ext_field2, ext_field3, ext_field4, ext_field5,
|
||||
remark, create_by, create_time, update_by, update_time, tenant_id
|
||||
FROM xg_student_msg
|
||||
<where>
|
||||
${ew.sqlSegment}
|
||||
<if test="deptIds != null and deptIds.size() > 0">
|
||||
AND class_id IN
|
||||
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
|
||||
#{deptId}
|
||||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,181 @@
|
||||
# 学生信息管理界面API接口调整记录
|
||||
|
||||
**调整日期**: 2025-01-27
|
||||
**调整人员**: assistant
|
||||
**相关模块**: 学工管理-学生信息管理
|
||||
**调整类型**: 接口优化
|
||||
|
||||
## 📋 调整概述
|
||||
|
||||
完善学生信息管理界面(`web/src/views/studentMsg/studentMsg/index.vue`),将table数据请求方式从旧的API模块调用改为直接使用 `service` 调用后端 `XgStudentMsgController.java` 的 `listByDeptId` 接口。
|
||||
|
||||
## 🔧 主要调整内容
|
||||
|
||||
### 1. 列表查询接口调整
|
||||
- **修改函数**: `getList()`
|
||||
- **原实现**: 使用 `api.listStudent(queryParams)`
|
||||
- **新实现**: 直接使用 `service.post('/studentMsg/studentMsg/listByDeptId', requestBody, {params: pageQuery})`
|
||||
- **调整原因**: 按用户要求,通过 `import service from '@/utils/request'` 直接请求接口
|
||||
|
||||
### 2. 删除功能接口调整
|
||||
- **修改函数**: `handleDelete()`
|
||||
- **原实现**: 使用 `api.delStudent(studentIds)`
|
||||
- **新实现**: 直接使用 `service.delete(\`/studentMsg/studentMsg/\${studentIds}\`)`
|
||||
- **增强**: 添加了完善的错误处理和用户提示
|
||||
|
||||
### 3. 新增/修改功能接口调整
|
||||
- **修改函数**: `submitForm()`
|
||||
- **原实现**: 使用 `addStudentMsg(form)` 和 `updateStudentMsg(form)`
|
||||
- **新实现**:
|
||||
- 新增: `service.post('/studentMsg/studentMsg', form)`
|
||||
- 修改: `service.put('/studentMsg/studentMsg', form)`
|
||||
- **增强**: 添加了完善的错误处理和用户提示
|
||||
|
||||
### 4. 获取学生详情接口调整
|
||||
- **修改函数**: `handleUpdate()`
|
||||
- **原实现**: 使用 `api.getStudent(studentId)`
|
||||
- **新实现**: 直接使用 `service.get(\`/studentMsg/studentMsg/\${studentId}\`)`
|
||||
- **调整**: 暂时注释了专业和班级选项的处理,因为这些字段在表单中是disabled状态
|
||||
|
||||
### 5. 查询参数优化
|
||||
- **修改函数**: `resetQuery()`
|
||||
- **增强**: 明确清空所有查询条件,确保重置操作的完整性
|
||||
- **优化**: 改进了请求体构造逻辑,处理空值情况
|
||||
|
||||
### 6. 代码清理
|
||||
- **清理内容**: 移除了不再使用的API函数导入
|
||||
- **移除**: `addStudentMsg`, `updateStudentMsg`, `delStudentMsg`, `getStudentMsg`, `listStudentMsg`
|
||||
- **保留**: 必要的类型定义导入
|
||||
|
||||
## 🎯 调用触发点确认
|
||||
|
||||
确保以下场景都能正确触发新的接口调用:
|
||||
|
||||
✅ **界面打开时**: `onMounted()` → `getList()`
|
||||
✅ **点击部门树节点**: `handleNodeClick()` → `handleQuery()` → `getList()`
|
||||
✅ **搜索操作**: `handleQuery()` → `getList()`
|
||||
✅ **重置操作**: `resetQuery()` → `handleQuery()` → `getList()`
|
||||
✅ **新增学生后**: `submitForm()` → `getList()`
|
||||
✅ **修改学生后**: `submitForm()` → `getList()`
|
||||
✅ **删除学生后**: `handleDelete()` → `getList()`
|
||||
|
||||
## 📊 请求参数映射
|
||||
|
||||
### listByDeptId 接口参数
|
||||
```javascript
|
||||
// 请求体
|
||||
{
|
||||
name: queryParams.name || '',
|
||||
studentNo: queryParams.studentNo || '',
|
||||
phone: queryParams.phone || '',
|
||||
studentStatus: queryParams.studentStatus || '',
|
||||
dept_id: queryParams.deptId || null,
|
||||
beginTime: dateRange.value[0], // 可选
|
||||
endTime: dateRange.value[1] // 可选
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
{
|
||||
pageNum: queryParams.pageNum,
|
||||
pageSize: queryParams.pageSize
|
||||
}
|
||||
```
|
||||
|
||||
## 🚨 注意事项
|
||||
|
||||
1. **专业班级选项**: 在修改学生信息时,专业和班级选项的获取逻辑暂时被注释,因为在当前表单中这些字段是disabled状态
|
||||
2. **错误处理**: 所有接口调用都添加了完善的错误处理和用户提示
|
||||
3. **数据格式**: 后端返回的数据格式可能与之前的API有所不同,已进行相应适配
|
||||
4. **权限验证**: 所有接口都需要相应的权限验证,确保用户有访问权限
|
||||
|
||||
## 📝 测试建议
|
||||
|
||||
1. **基础功能测试**: 测试列表查询、新增、修改、删除功能
|
||||
2. **部门筛选测试**: 测试点击不同部门节点的学生列表筛选
|
||||
3. **搜索功能测试**: 测试各种搜索条件组合
|
||||
4. **权限测试**: 确保用户权限控制正常
|
||||
5. **错误场景测试**: 测试网络异常、权限不足等错误场景的处理
|
||||
|
||||
## 🔄 后续优化建议
|
||||
|
||||
1. 如果需要在修改学生信息时支持专业和班级的动态选择,需要添加相应的接口调用
|
||||
2. 可以考虑添加接口调用的loading状态,提升用户体验
|
||||
3. 可以考虑添加接口调用的重试机制,提高系统健壮性
|
||||
|
||||
---
|
||||
|
||||
# 代码调整记录 - 2025-01-27 - assistant
|
||||
|
||||
## 📝 概述
|
||||
本文档记录了2025年1月27日的代码调整内容,包括修复的问题、改进的功能和相关的技术实现。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 修复记录
|
||||
|
||||
### 1. 修复学生信息按部门查询SQL语法错误 (2025-01-27 19:55:00)
|
||||
|
||||
**问题描述:**
|
||||
- 学生信息管理页面按部门ID查询时出现SQL语法错误
|
||||
- 错误信息:`ParseException: Encountered unexpected token: "AND" "AND"`
|
||||
- 生成的SQL出现重复的ORDER BY子句,导致SQL结构错误
|
||||
|
||||
**错误SQL结构:**
|
||||
```sql
|
||||
WHERE (deleted = ?) ORDER BY create_time DESC
|
||||
|
||||
AND class_id IN (...)
|
||||
ORDER BY create_time DESC
|
||||
```
|
||||
|
||||
**根本原因:**
|
||||
- 在`XgStudentMsgServiceImpl.buildQueryWrapperForDeptQuery()`方法中使用了`lqw.orderByDesc(XgStudentMsg::getCreateTime)`
|
||||
- 同时在`XgStudentMsgMapper.xml`的SQL模板中也有固定的`ORDER BY create_time DESC`
|
||||
- MyBatis-Plus的`${ew.sqlSegment}`包含了排序条件,与XML中的排序冲突
|
||||
|
||||
**修复内容:**
|
||||
1. **文件**:`backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java`
|
||||
2. **修改**:移除`buildQueryWrapperForDeptQuery`方法中的`lqw.orderByDesc(XgStudentMsg::getCreateTime)`调用
|
||||
3. **代码变更**:
|
||||
```java
|
||||
// 修改前
|
||||
// 排序:按创建时间倒序
|
||||
lqw.orderByDesc(XgStudentMsg::getCreateTime);
|
||||
|
||||
// 修改后
|
||||
// 注意:排序在XML中处理,这里不添加排序条件,避免SQL语法错误
|
||||
```
|
||||
|
||||
**影响范围:**
|
||||
- 学生信息管理页面的部门查询功能
|
||||
- 接口:`POST /studentMsg/studentMsg/listByDeptId`
|
||||
|
||||
**测试验证:**
|
||||
- ✅ 部门查询功能正常工作
|
||||
- ✅ SQL语法错误已解决
|
||||
- ✅ 数据按创建时间倒序显示
|
||||
|
||||
**技术说明:**
|
||||
- MyBatis-Plus的LambdaQueryWrapper在使用`${ew.sqlSegment}`时会生成完整的WHERE条件和ORDER BY子句
|
||||
- 避免在Service层和XML映射文件中重复定义排序条件
|
||||
- 优先在XML中定义固定的排序逻辑,保持SQL模板的清晰性
|
||||
|
||||
---
|
||||
|
||||
## 📊 影响统计
|
||||
- 修复文件数量:1个
|
||||
- 涉及模块:学工管理-学生信息管理
|
||||
- 预估影响用户:所有使用学生信息查询功能的用户
|
||||
|
||||
---
|
||||
|
||||
## 📋 待优化项
|
||||
1. 考虑统一排序逻辑的处理方式
|
||||
2. 完善SQL错误处理机制
|
||||
3. 增加更多的单元测试覆盖
|
||||
|
||||
---
|
||||
|
||||
**记录时间**:2025-01-27 19:55:00
|
||||
**处理人员**:assistant
|
||||
**审核状态**:待测试验证
|
||||
@@ -0,0 +1,466 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
批量创建学生信息脚本
|
||||
为每个班级创建30个学生的测试数据
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SimpleDataGenerator:
|
||||
"""简单的数据生成器,替代faker"""
|
||||
|
||||
def __init__(self):
|
||||
# 姓氏
|
||||
self.surnames = ['李', '王', '张', '刘', '陈', '杨', '黄', '赵', '周', '吴',
|
||||
'徐', '孙', '朱', '马', '胡', '郭', '林', '何', '高', '梁']
|
||||
|
||||
# 男性名字
|
||||
self.male_names = ['伟', '强', '磊', '军', '勇', '涛', '明', '超', '辉', '华',
|
||||
'建', '文', '健', '云', '峰', '浩', '鹏', '飞', '宇', '杰']
|
||||
|
||||
# 女性名字
|
||||
self.female_names = ['丽', '娜', '敏', '静', '佳', '雅', '莉', '萍', '红', '梅',
|
||||
'芳', '燕', '玲', '霞', '琳', '晶', '欣', '宁', '蕾', '洁']
|
||||
|
||||
# 省份
|
||||
self.provinces = ['北京市', '上海市', '天津市', '重庆市', '河北省', '山西省', '辽宁省',
|
||||
'吉林省', '黑龙江省', '江苏省', '浙江省', '安徽省', '福建省', '江西省',
|
||||
'山东省', '河南省', '湖北省', '湖南省', '广东省', '海南省', '四川省',
|
||||
'贵州省', '云南省', '陕西省', '甘肃省', '青海省', '台湾省', '内蒙古自治区',
|
||||
'广西壮族自治区', '西藏自治区', '宁夏回族自治区', '新疆维吾尔自治区']
|
||||
|
||||
def generate_name(self, gender):
|
||||
"""生成姓名"""
|
||||
surname = random.choice(self.surnames)
|
||||
if gender == '0': # 男性
|
||||
given_name = random.choice(self.male_names)
|
||||
else: # 女性
|
||||
given_name = random.choice(self.female_names)
|
||||
|
||||
# 有时候生成双字名
|
||||
if random.random() < 0.3:
|
||||
if gender == '0':
|
||||
given_name += random.choice(self.male_names)
|
||||
else:
|
||||
given_name += random.choice(self.female_names)
|
||||
|
||||
return surname + given_name
|
||||
|
||||
def generate_phone(self):
|
||||
"""生成手机号"""
|
||||
prefixes = ['130', '131', '132', '133', '134', '135', '136', '137', '138', '139',
|
||||
'150', '151', '152', '153', '155', '156', '157', '158', '159',
|
||||
'180', '181', '182', '183', '184', '185', '186', '187', '188', '189']
|
||||
prefix = random.choice(prefixes)
|
||||
suffix = ''.join([str(random.randint(0, 9)) for _ in range(8)])
|
||||
return prefix + suffix
|
||||
|
||||
def generate_id_card(self, birth_date):
|
||||
"""生成身份证号"""
|
||||
# 地区代码(简化版)
|
||||
area_codes = ['110101', '110102', '110105', '110106', '110107', '110108',
|
||||
'310101', '310104', '310105', '310106', '310107', '310109',
|
||||
'440101', '440103', '440104', '440105', '440106', '440111']
|
||||
|
||||
area_code = random.choice(area_codes)
|
||||
|
||||
# 出生日期(YYYYMMDD格式)
|
||||
birth_str = birth_date.replace('-', '')
|
||||
|
||||
# 顺序码(3位)+ 校验码(1位)
|
||||
sequence = str(random.randint(100, 999))
|
||||
check_digit = str(random.randint(0, 9))
|
||||
|
||||
return area_code + birth_str + sequence + check_digit
|
||||
|
||||
def generate_address(self):
|
||||
"""生成地址"""
|
||||
province = random.choice(self.provinces)
|
||||
cities = ['市', '县']
|
||||
city = f"某{random.choice(cities)}"
|
||||
district = f"某区某街道{random.randint(1, 999)}号"
|
||||
return province + city + district
|
||||
|
||||
def generate_province(self):
|
||||
"""生成省份"""
|
||||
return random.choice(self.provinces)
|
||||
|
||||
class StudentBatchCreator:
|
||||
def __init__(self, base_url="http://localhost:8080", token=None):
|
||||
"""
|
||||
初始化学生批量创建器
|
||||
|
||||
Args:
|
||||
base_url: 后端API基础URL
|
||||
token: 认证token
|
||||
"""
|
||||
self.base_url = base_url
|
||||
self.token = token
|
||||
self.session = requests.Session()
|
||||
self.data_gen = SimpleDataGenerator()
|
||||
|
||||
# 设置请求头
|
||||
if token:
|
||||
self.session.headers.update({
|
||||
'Authorization': f'Bearer {token}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
|
||||
def set_token(self, token):
|
||||
"""设置认证token"""
|
||||
self.token = token
|
||||
self.session.headers.update({
|
||||
'Authorization': f'Bearer {token}'
|
||||
})
|
||||
|
||||
def get_class_list(self):
|
||||
"""获取班级列表"""
|
||||
try:
|
||||
url = f"{self.base_url}/classMsg/classMsg/list"
|
||||
params = {
|
||||
'pageNum': 1,
|
||||
'pageSize': 1000 # 获取所有班级
|
||||
}
|
||||
|
||||
response = self.session.get(url, params=params)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
if data.get('code') == 200:
|
||||
classes = data.get('rows', [])
|
||||
logger.info(f"获取到 {len(classes)} 个班级")
|
||||
return classes
|
||||
else:
|
||||
logger.error(f"获取班级列表失败: {data.get('msg')}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取班级列表异常: {str(e)}")
|
||||
return []
|
||||
|
||||
def generate_birth_date(self):
|
||||
"""生成出生日期(18-22岁)"""
|
||||
today = datetime.now()
|
||||
min_age = 18
|
||||
max_age = 22
|
||||
|
||||
# 计算出生年份范围
|
||||
birth_year = today.year - random.randint(min_age, max_age)
|
||||
birth_month = random.randint(1, 12)
|
||||
birth_day = random.randint(1, 28) # 使用28避免月份天数问题
|
||||
|
||||
return f"{birth_year:04d}-{birth_month:02d}-{birth_day:02d}"
|
||||
|
||||
def generate_student_data(self, class_info, student_index):
|
||||
"""
|
||||
生成单个学生的测试数据
|
||||
|
||||
Args:
|
||||
class_info: 班级信息
|
||||
student_index: 学生编号(1-30)
|
||||
|
||||
Returns:
|
||||
dict: 学生数据
|
||||
"""
|
||||
# 生成基础信息
|
||||
gender = random.choice(['0', '1']) # 0-男,1-女
|
||||
name = self.data_gen.generate_name(gender)
|
||||
|
||||
# 生成学号:班级代码 + 学生编号
|
||||
class_code = class_info.get('classCode', class_info.get('class_code', ''))
|
||||
if not class_code:
|
||||
# 如果没有班级代码,使用班级ID
|
||||
class_code = f"CLS{class_info.get('id', '')}"
|
||||
|
||||
student_no = f"{class_code}{student_index:02d}"
|
||||
|
||||
# 生成出生日期
|
||||
birth_date = self.generate_birth_date()
|
||||
|
||||
# 生成身份证号
|
||||
id_card = self.data_gen.generate_id_card(birth_date)
|
||||
|
||||
# 生成手机号
|
||||
phone = self.data_gen.generate_phone()
|
||||
|
||||
# 民族选择
|
||||
nations = ['01', '02', '03', '04', '05'] # 汉族、蒙古族、回族、藏族、维吾尔族等
|
||||
nationality = random.choice(nations)
|
||||
|
||||
# 政治面貌选择
|
||||
political_statuses = ['01', '02', '03', '13'] # 群众、团员、党员等
|
||||
political_status = random.choice(political_statuses)
|
||||
|
||||
# 构建学生数据
|
||||
student_data = {
|
||||
# 基础信息
|
||||
"studentNo": student_no,
|
||||
"idCard": id_card,
|
||||
"name": name,
|
||||
"gender": gender,
|
||||
"birthDate": birth_date,
|
||||
"nationality": nationality,
|
||||
"politicalStatus": political_status,
|
||||
"phone": phone,
|
||||
"email": f"{student_no}@student.edu.cn",
|
||||
|
||||
# 学籍信息(从班级信息继承)
|
||||
"classId": class_info.get('id'),
|
||||
"className": class_info.get('className', class_info.get('class_name', '')),
|
||||
"majorId": class_info.get('majorId', class_info.get('major_id')),
|
||||
"majorName": class_info.get('majorName', class_info.get('major_name', '')),
|
||||
"collegeId": class_info.get('collegeId', class_info.get('college_id')),
|
||||
"collegeName": class_info.get('collegeName', class_info.get('college_name', '')),
|
||||
"grade": class_info.get('grade', ''),
|
||||
"enrollmentYear": class_info.get('enrollmentYear', class_info.get('enrollment_year')),
|
||||
"academicLevel": class_info.get('classType', class_info.get('class_type', 1)),
|
||||
"classTeacherId": class_info.get('classTeacherId', class_info.get('class_teacher_id')),
|
||||
"classTeacherName": class_info.get('classTeacherName', class_info.get('class_teacher_name', '')),
|
||||
"schoolSystem": class_info.get('schoolSystem', class_info.get('school_system', 3)),
|
||||
|
||||
# 默认状态
|
||||
"studentStatus": "1", # 在籍
|
||||
"trainingType": "全日制",
|
||||
"admissionCategory": "统招",
|
||||
"householdRegisterType": "非农业",
|
||||
"educationLevel": "2", # 高中
|
||||
"maritalStatus": "1", # 未婚
|
||||
|
||||
# 地址信息
|
||||
"nativePlace": self.data_gen.generate_province(),
|
||||
"sourceRegion": self.data_gen.generate_province(),
|
||||
"homeAddress": self.data_gen.generate_address(),
|
||||
"currentAddress": "学校宿舍",
|
||||
|
||||
# 家庭信息
|
||||
"fatherName": self.data_gen.generate_name('0'),
|
||||
"fatherPhone": self.data_gen.generate_phone(),
|
||||
"motherName": self.data_gen.generate_name('1'),
|
||||
"motherPhone": self.data_gen.generate_phone(),
|
||||
|
||||
# 健康信息
|
||||
"height": random.randint(155, 185),
|
||||
"weight": random.randint(45, 80),
|
||||
"bloodType": random.choice(['A', 'B', 'AB', 'O']),
|
||||
"healthStatus": "良好",
|
||||
|
||||
# 布尔字段
|
||||
"isBoarder": 1, # 住校
|
||||
"isPoorStudent": random.randint(0, 1),
|
||||
"isMinority": 1 if nationality != '01' else 0, # 非汉族为少数民族
|
||||
"isDisabled": 0,
|
||||
"isOrphan": 0,
|
||||
"isLeftBehindChild": 0,
|
||||
"isSingleChild": random.randint(0, 1),
|
||||
"isLowIncome": 0,
|
||||
"hasLoan": random.randint(0, 1),
|
||||
|
||||
# 其他字段
|
||||
"remark": f"批量生成的测试学生数据 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
}
|
||||
|
||||
return student_data
|
||||
|
||||
def create_student(self, student_data):
|
||||
"""
|
||||
创建单个学生
|
||||
|
||||
Args:
|
||||
student_data: 学生数据
|
||||
|
||||
Returns:
|
||||
tuple: (success, result)
|
||||
"""
|
||||
try:
|
||||
url = f"{self.base_url}/studentMsg/studentMsg"
|
||||
|
||||
response = self.session.post(url, json=student_data)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
if result.get('code') == 200:
|
||||
logger.info(f"成功创建学生: {student_data['name']} ({student_data['studentNo']})")
|
||||
return True, result
|
||||
else:
|
||||
logger.error(f"创建学生失败: {student_data['name']} - {result.get('msg')}")
|
||||
return False, result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建学生异常: {student_data['name']} - {str(e)}")
|
||||
return False, str(e)
|
||||
|
||||
def batch_create_students_for_class(self, class_info, student_count=30):
|
||||
"""
|
||||
为指定班级批量创建学生
|
||||
|
||||
Args:
|
||||
class_info: 班级信息
|
||||
student_count: 要创建的学生数量
|
||||
|
||||
Returns:
|
||||
dict: 创建结果统计
|
||||
"""
|
||||
class_name = class_info.get('className', class_info.get('class_name', f"班级{class_info.get('id')}"))
|
||||
logger.info(f"开始为班级 [{class_name}] 创建 {student_count} 个学生...")
|
||||
|
||||
success_count = 0
|
||||
failed_count = 0
|
||||
failed_students = []
|
||||
|
||||
for i in range(1, student_count + 1):
|
||||
try:
|
||||
# 生成学生数据
|
||||
student_data = self.generate_student_data(class_info, i)
|
||||
|
||||
# 创建学生
|
||||
success, result = self.create_student(student_data)
|
||||
|
||||
if success:
|
||||
success_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
failed_students.append({
|
||||
'student_no': student_data['studentNo'],
|
||||
'name': student_data['name'],
|
||||
'error': result
|
||||
})
|
||||
|
||||
# 添加短暂延迟,避免请求过于频繁
|
||||
time.sleep(0.1)
|
||||
|
||||
except Exception as e:
|
||||
failed_count += 1
|
||||
logger.error(f"处理第 {i} 个学生时发生异常: {str(e)}")
|
||||
failed_students.append({
|
||||
'student_no': f"未知_{i}",
|
||||
'name': '未知',
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
result = {
|
||||
'class_name': class_name,
|
||||
'total': student_count,
|
||||
'success': success_count,
|
||||
'failed': failed_count,
|
||||
'failed_students': failed_students
|
||||
}
|
||||
|
||||
logger.info(f"班级 [{class_name}] 学生创建完成: 成功 {success_count},失败 {failed_count}")
|
||||
return result
|
||||
|
||||
def batch_create_students_for_all_classes(self, student_count_per_class=30):
|
||||
"""
|
||||
为所有班级批量创建学生
|
||||
|
||||
Args:
|
||||
student_count_per_class: 每个班级创建的学生数量
|
||||
|
||||
Returns:
|
||||
list: 所有班级的创建结果
|
||||
"""
|
||||
# 获取班级列表
|
||||
classes = self.get_class_list()
|
||||
if not classes:
|
||||
logger.error("没有获取到班级信息,无法创建学生")
|
||||
return []
|
||||
|
||||
logger.info(f"开始为 {len(classes)} 个班级批量创建学生,每个班级 {student_count_per_class} 个学生")
|
||||
|
||||
all_results = []
|
||||
total_success = 0
|
||||
total_failed = 0
|
||||
|
||||
for class_info in classes:
|
||||
try:
|
||||
result = self.batch_create_students_for_class(class_info, student_count_per_class)
|
||||
all_results.append(result)
|
||||
total_success += result['success']
|
||||
total_failed += result['failed']
|
||||
|
||||
# 班级间添加延迟
|
||||
time.sleep(0.5)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理班级 {class_info} 时发生异常: {str(e)}")
|
||||
all_results.append({
|
||||
'class_name': f"班级{class_info.get('id', '未知')}",
|
||||
'total': student_count_per_class,
|
||||
'success': 0,
|
||||
'failed': student_count_per_class,
|
||||
'error': str(e)
|
||||
})
|
||||
total_failed += student_count_per_class
|
||||
|
||||
# 输出总结
|
||||
logger.info("="*50)
|
||||
logger.info("批量创建学生完成!")
|
||||
logger.info(f"总班级数: {len(classes)}")
|
||||
logger.info(f"总学生数: {len(classes) * student_count_per_class}")
|
||||
logger.info(f"成功创建: {total_success}")
|
||||
logger.info(f"创建失败: {total_failed}")
|
||||
logger.info("="*50)
|
||||
|
||||
return all_results
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("学生批量创建工具")
|
||||
print("="*50)
|
||||
|
||||
# 配置参数
|
||||
base_url = input("请输入后端API地址 (默认: http://localhost:8080): ").strip()
|
||||
if not base_url:
|
||||
base_url = "http://localhost:8080"
|
||||
|
||||
token = input("请输入认证Token (可为空): ").strip()
|
||||
|
||||
student_count = input("请输入每个班级创建的学生数量 (默认: 30): ").strip()
|
||||
if not student_count:
|
||||
student_count = 30
|
||||
else:
|
||||
try:
|
||||
student_count = int(student_count)
|
||||
except ValueError:
|
||||
print("输入的学生数量无效,使用默认值 30")
|
||||
student_count = 30
|
||||
|
||||
# 创建批量创建器
|
||||
creator = StudentBatchCreator(base_url, token)
|
||||
|
||||
# 确认操作
|
||||
print(f"\n配置信息:")
|
||||
print(f"API地址: {base_url}")
|
||||
print(f"每个班级学生数: {student_count}")
|
||||
|
||||
confirm = input("\n确认开始批量创建学生?(y/N): ").strip().lower()
|
||||
if confirm not in ['y', 'yes']:
|
||||
print("操作已取消")
|
||||
return
|
||||
|
||||
# 开始批量创建
|
||||
start_time = time.time()
|
||||
results = creator.batch_create_students_for_all_classes(student_count)
|
||||
end_time = time.time()
|
||||
|
||||
# 保存结果到文件
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
result_file = f"student_creation_result_{timestamp}.json"
|
||||
|
||||
with open(result_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n创建结果已保存到: {result_file}")
|
||||
print(f"总耗时: {end_time - start_time:.2f} 秒")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
检查系统状态和可用的API端点
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def check_system_status():
|
||||
"""检查系统状态"""
|
||||
base_urls = [
|
||||
"http://localhost:8080",
|
||||
"http://localhost/dev-api",
|
||||
"http://localhost"
|
||||
]
|
||||
|
||||
print("🔍 检查系统状态...")
|
||||
|
||||
for base_url in base_urls:
|
||||
print(f"\n🌐 测试地址: {base_url}")
|
||||
|
||||
# 测试基本连接
|
||||
try:
|
||||
response = requests.get(f"{base_url}/", timeout=5)
|
||||
print(f" ✅ 基础连接: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 基础连接失败: {str(e)}")
|
||||
continue
|
||||
|
||||
# 测试登录端点
|
||||
try:
|
||||
login_endpoints = ["/auth/login", "/login"]
|
||||
for endpoint in login_endpoints:
|
||||
try:
|
||||
response = requests.post(f"{base_url}{endpoint}", json={}, timeout=5)
|
||||
print(f" 🔑 登录端点 {endpoint}: {response.status_code}")
|
||||
if response.status_code in [200, 400, 401, 403]:
|
||||
print(f" 响应: {response.text[:200]}...")
|
||||
except Exception as e:
|
||||
print(f" ❌ 登录端点 {endpoint} 失败: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 登录测试失败: {str(e)}")
|
||||
|
||||
# 测试API端点
|
||||
try:
|
||||
api_endpoints = [
|
||||
"/classMsg/classMsg/list",
|
||||
"/studentMsg/studentMsg/list",
|
||||
"/api/classMsg/classMsg/list"
|
||||
]
|
||||
for endpoint in api_endpoints:
|
||||
try:
|
||||
response = requests.get(f"{base_url}{endpoint}", timeout=5)
|
||||
print(f" 📊 API端点 {endpoint}: {response.status_code}")
|
||||
if response.text:
|
||||
data = response.json()
|
||||
if data.get('code'):
|
||||
print(f" 响应码: {data.get('code')}, 消息: {data.get('msg', '')}")
|
||||
except Exception as e:
|
||||
print(f" ❌ API端点 {endpoint} 失败: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f" ❌ API测试失败: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_system_status()
|
||||
@@ -0,0 +1,397 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
登录并批量创建学生信息的完整脚本
|
||||
包含登录获取Token和批量创建学生两个功能
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class LoginAndCreateStudents:
|
||||
def __init__(self, base_url="http://localhost", api_prefix="/dev-api"):
|
||||
"""
|
||||
初始化登录和学生创建器
|
||||
|
||||
Args:
|
||||
base_url: 基础URL
|
||||
api_prefix: API前缀
|
||||
"""
|
||||
self.base_url = base_url
|
||||
self.api_prefix = api_prefix
|
||||
self.full_base_url = base_url + api_prefix
|
||||
self.token = None
|
||||
self.session = requests.Session()
|
||||
|
||||
# 简单的数据生成器
|
||||
self.surnames = ['李', '王', '张', '刘', '陈', '杨', '黄', '赵', '周', '吴']
|
||||
self.male_names = ['伟', '强', '磊', '军', '勇', '涛', '明', '超', '辉', '华']
|
||||
self.female_names = ['丽', '娜', '敏', '静', '佳', '雅', '莉', '萍', '红', '梅']
|
||||
self.provinces = ['北京市', '上海市', '广东省', '江苏省', '浙江省', '山东省', '河南省', '湖北省']
|
||||
|
||||
def login(self, username="admin", password="admin123"):
|
||||
"""
|
||||
尝试登录获取Token
|
||||
"""
|
||||
try:
|
||||
# 尝试标准登录
|
||||
login_data = {
|
||||
"tenantId": "000000",
|
||||
"username": username,
|
||||
"password": password,
|
||||
"clientId": "e5cd7e4891bf95d1d19206ce24a7b32e",
|
||||
"grantType": "password"
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
'referer': f'{self.base_url}/login?redirect=%252Findex',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
|
||||
}
|
||||
|
||||
logger.info("正在尝试登录...")
|
||||
response = self.session.post(
|
||||
f"{self.full_base_url}/auth/login",
|
||||
json=login_data,
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
logger.info(f"登录响应: {json.dumps(result, ensure_ascii=False, indent=2)}")
|
||||
|
||||
if result.get('code') == 200 and result.get('data', {}).get('access_token'):
|
||||
self.token = result['data']['access_token']
|
||||
# 更新session的默认headers
|
||||
self.session.headers.update({
|
||||
'Authorization': f'Bearer {self.token}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
logger.info(f"✅ 登录成功,Token: {self.token[:50]}...")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"❌ 登录失败: {result.get('msg')}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 登录异常: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_class_list(self):
|
||||
"""获取班级列表"""
|
||||
if not self.token:
|
||||
logger.error("未登录,无法获取班级列表")
|
||||
return []
|
||||
|
||||
try:
|
||||
url = f"{self.full_base_url}/classMsg/classMsg/list"
|
||||
params = {
|
||||
'pageNum': 1,
|
||||
'pageSize': 1000
|
||||
}
|
||||
|
||||
response = self.session.get(url, params=params)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
if data.get('code') == 200:
|
||||
classes = data.get('rows', [])
|
||||
logger.info(f"获取到 {len(classes)} 个班级")
|
||||
return classes
|
||||
else:
|
||||
logger.error(f"获取班级列表失败: {data.get('msg')}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取班级列表异常: {str(e)}")
|
||||
return []
|
||||
|
||||
def generate_name(self, gender):
|
||||
"""生成姓名"""
|
||||
surname = random.choice(self.surnames)
|
||||
if gender == '0': # 男性
|
||||
given_name = random.choice(self.male_names)
|
||||
else: # 女性
|
||||
given_name = random.choice(self.female_names)
|
||||
return surname + given_name
|
||||
|
||||
def generate_phone(self):
|
||||
"""生成手机号"""
|
||||
prefixes = ['130', '131', '132', '133', '134', '135', '136', '137', '138', '139']
|
||||
prefix = random.choice(prefixes)
|
||||
suffix = ''.join([str(random.randint(0, 9)) for _ in range(8)])
|
||||
return prefix + suffix
|
||||
|
||||
def generate_id_card(self, birth_date):
|
||||
"""生成身份证号"""
|
||||
area_codes = ['110101', '110102', '310101', '310104', '440101', '440103']
|
||||
area_code = random.choice(area_codes)
|
||||
birth_str = birth_date.replace('-', '')
|
||||
sequence = str(random.randint(100, 999))
|
||||
check_digit = str(random.randint(0, 9))
|
||||
return area_code + birth_str + sequence + check_digit
|
||||
|
||||
def generate_birth_date(self):
|
||||
"""生成出生日期(18-22岁)"""
|
||||
today = datetime.now()
|
||||
birth_year = today.year - random.randint(18, 22)
|
||||
birth_month = random.randint(1, 12)
|
||||
birth_day = random.randint(1, 28)
|
||||
return f"{birth_year:04d}-{birth_month:02d}-{birth_day:02d}"
|
||||
|
||||
def generate_student_data(self, class_info, student_index):
|
||||
"""生成学生数据"""
|
||||
gender = random.choice(['0', '1'])
|
||||
name = self.generate_name(gender)
|
||||
|
||||
# 生成学号
|
||||
class_code = class_info.get('classCode', class_info.get('class_code', ''))
|
||||
if not class_code:
|
||||
class_code = f"CLS{class_info.get('id', '')}"
|
||||
|
||||
student_no = f"{class_code}{student_index:02d}"
|
||||
birth_date = self.generate_birth_date()
|
||||
|
||||
student_data = {
|
||||
# 基础信息
|
||||
"studentNo": student_no,
|
||||
"idCard": self.generate_id_card(birth_date),
|
||||
"name": name,
|
||||
"gender": gender,
|
||||
"birthDate": birth_date,
|
||||
"nationality": random.choice(['01', '02', '03', '04', '05']),
|
||||
"politicalStatus": random.choice(['01', '02', '03', '13']),
|
||||
"phone": self.generate_phone(),
|
||||
"email": f"{student_no}@student.edu.cn",
|
||||
|
||||
# 学籍信息
|
||||
"classId": class_info.get('id'),
|
||||
"className": class_info.get('className', class_info.get('class_name', '')),
|
||||
"majorId": class_info.get('majorId', class_info.get('major_id')),
|
||||
"majorName": class_info.get('majorName', class_info.get('major_name', '')),
|
||||
"collegeId": class_info.get('collegeId', class_info.get('college_id')),
|
||||
"collegeName": class_info.get('collegeName', class_info.get('college_name', '')),
|
||||
"grade": class_info.get('grade', ''),
|
||||
"enrollmentYear": class_info.get('enrollmentYear', class_info.get('enrollment_year')),
|
||||
"academicLevel": class_info.get('classType', class_info.get('class_type', 1)),
|
||||
"classTeacherId": class_info.get('classTeacherId', class_info.get('class_teacher_id')),
|
||||
"classTeacherName": class_info.get('classTeacherName', class_info.get('class_teacher_name', '')),
|
||||
"schoolSystem": class_info.get('schoolSystem', class_info.get('school_system', 3)),
|
||||
|
||||
# 默认状态
|
||||
"studentStatus": "1",
|
||||
"trainingType": "全日制",
|
||||
"admissionCategory": "统招",
|
||||
"householdRegisterType": "非农业",
|
||||
"educationLevel": "2",
|
||||
"maritalStatus": "1",
|
||||
|
||||
# 地址信息
|
||||
"nativePlace": random.choice(self.provinces),
|
||||
"sourceRegion": random.choice(self.provinces),
|
||||
"homeAddress": f"{random.choice(self.provinces)}某市某区某街道{random.randint(1, 999)}号",
|
||||
"currentAddress": "学校宿舍",
|
||||
|
||||
# 家庭信息
|
||||
"fatherName": self.generate_name('0'),
|
||||
"fatherPhone": self.generate_phone(),
|
||||
"motherName": self.generate_name('1'),
|
||||
"motherPhone": self.generate_phone(),
|
||||
|
||||
# 健康信息
|
||||
"height": random.randint(155, 185),
|
||||
"weight": random.randint(45, 80),
|
||||
"bloodType": random.choice(['A', 'B', 'AB', 'O']),
|
||||
"healthStatus": "良好",
|
||||
|
||||
# 布尔字段
|
||||
"isBoarder": 1,
|
||||
"isPoorStudent": random.randint(0, 1),
|
||||
"isMinority": 0,
|
||||
"isDisabled": 0,
|
||||
"isOrphan": 0,
|
||||
"isLeftBehindChild": 0,
|
||||
"isSingleChild": random.randint(0, 1),
|
||||
"isLowIncome": 0,
|
||||
"hasLoan": random.randint(0, 1),
|
||||
|
||||
"remark": f"批量生成的测试学生数据 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
}
|
||||
|
||||
return student_data
|
||||
|
||||
def create_student(self, student_data):
|
||||
"""创建单个学生"""
|
||||
try:
|
||||
url = f"{self.full_base_url}/studentMsg/studentMsg"
|
||||
|
||||
response = self.session.post(url, json=student_data)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
if result.get('code') == 200:
|
||||
logger.info(f"成功创建学生: {student_data['name']} ({student_data['studentNo']})")
|
||||
return True, result
|
||||
else:
|
||||
logger.error(f"创建学生失败: {student_data['name']} - {result.get('msg')}")
|
||||
return False, result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建学生异常: {student_data['name']} - {str(e)}")
|
||||
return False, str(e)
|
||||
|
||||
def batch_create_students_for_class(self, class_info, student_count=30):
|
||||
"""为指定班级批量创建学生"""
|
||||
class_name = class_info.get('className', class_info.get('class_name', f"班级{class_info.get('id')}"))
|
||||
logger.info(f"开始为班级 [{class_name}] 创建 {student_count} 个学生...")
|
||||
|
||||
success_count = 0
|
||||
failed_count = 0
|
||||
failed_students = []
|
||||
|
||||
for i in range(1, student_count + 1):
|
||||
try:
|
||||
student_data = self.generate_student_data(class_info, i)
|
||||
success, result = self.create_student(student_data)
|
||||
|
||||
if success:
|
||||
success_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
failed_students.append({
|
||||
'student_no': student_data['studentNo'],
|
||||
'name': student_data['name'],
|
||||
'error': result
|
||||
})
|
||||
|
||||
time.sleep(0.1) # 短暂延迟
|
||||
|
||||
except Exception as e:
|
||||
failed_count += 1
|
||||
logger.error(f"处理第 {i} 个学生时发生异常: {str(e)}")
|
||||
|
||||
result = {
|
||||
'class_name': class_name,
|
||||
'total': student_count,
|
||||
'success': success_count,
|
||||
'failed': failed_count,
|
||||
'failed_students': failed_students
|
||||
}
|
||||
|
||||
logger.info(f"班级 [{class_name}] 学生创建完成: 成功 {success_count},失败 {failed_count}")
|
||||
return result
|
||||
|
||||
def run(self, username="admin", password="admin123", student_count_per_class=30):
|
||||
"""运行完整流程"""
|
||||
print("🚀 开始执行批量创建学生流程...")
|
||||
|
||||
# 1. 登录
|
||||
if not self.login(username, password):
|
||||
print("❌ 登录失败,无法继续")
|
||||
return
|
||||
|
||||
# 2. 获取班级列表
|
||||
classes = self.get_class_list()
|
||||
if not classes:
|
||||
print("❌ 没有获取到班级信息")
|
||||
return
|
||||
|
||||
print(f"\n📋 找到 {len(classes)} 个班级,每个班级将创建 {student_count_per_class} 个学生")
|
||||
|
||||
# 显示班级信息
|
||||
for i, class_info in enumerate(classes[:5], 1):
|
||||
print(f" {i}. {class_info.get('className', class_info.get('class_name', '未知'))} "
|
||||
f"({class_info.get('collegeName', class_info.get('college_name', '未知'))})")
|
||||
if len(classes) > 5:
|
||||
print(f" ... 还有 {len(classes) - 5} 个班级")
|
||||
|
||||
# 3. 确认操作
|
||||
confirm = input(f"\n确认为所有 {len(classes)} 个班级创建学生?(y/N): ").strip().lower()
|
||||
if confirm not in ['y', 'yes']:
|
||||
print("操作已取消")
|
||||
return
|
||||
|
||||
# 4. 批量创建学生
|
||||
start_time = time.time()
|
||||
all_results = []
|
||||
total_success = 0
|
||||
total_failed = 0
|
||||
|
||||
for i, class_info in enumerate(classes, 1):
|
||||
print(f"\n📚 处理第 {i}/{len(classes)} 个班级...")
|
||||
try:
|
||||
result = self.batch_create_students_for_class(class_info, student_count_per_class)
|
||||
all_results.append(result)
|
||||
total_success += result['success']
|
||||
total_failed += result['failed']
|
||||
time.sleep(0.5) # 班级间延迟
|
||||
except Exception as e:
|
||||
logger.error(f"处理班级时发生异常: {str(e)}")
|
||||
total_failed += student_count_per_class
|
||||
|
||||
end_time = time.time()
|
||||
|
||||
# 5. 输出结果
|
||||
print("\n" + "="*50)
|
||||
print("🎉 批量创建学生完成!")
|
||||
print(f"📊 总班级数: {len(classes)}")
|
||||
print(f"👥 预计学生数: {len(classes) * student_count_per_class}")
|
||||
print(f"✅ 成功创建: {total_success}")
|
||||
print(f"❌ 创建失败: {total_failed}")
|
||||
print(f"⏱️ 总耗时: {end_time - start_time:.2f} 秒")
|
||||
print("="*50)
|
||||
|
||||
# 保存结果
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
result_file = f"student_creation_result_{timestamp}.json"
|
||||
|
||||
with open(result_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(all_results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"📄 详细结果已保存到: {result_file}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🏫 学生批量创建工具")
|
||||
print("="*50)
|
||||
|
||||
# 配置参数
|
||||
base_url = input("请输入系统地址 (默认: http://localhost): ").strip()
|
||||
if not base_url:
|
||||
base_url = "http://localhost"
|
||||
|
||||
username = input("请输入用户名 (默认: admin): ").strip()
|
||||
if not username:
|
||||
username = "admin"
|
||||
|
||||
password = input("请输入密码 (默认: admin123): ").strip()
|
||||
if not password:
|
||||
password = "admin123"
|
||||
|
||||
student_count = input("请输入每个班级创建的学生数量 (默认: 30): ").strip()
|
||||
if not student_count:
|
||||
student_count = 30
|
||||
else:
|
||||
try:
|
||||
student_count = int(student_count)
|
||||
except ValueError:
|
||||
print("输入的学生数量无效,使用默认值 30")
|
||||
student_count = 30
|
||||
|
||||
# 创建并运行
|
||||
creator = LoginAndCreateStudents(base_url)
|
||||
creator.run(username, password, student_count)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
快速登录测试
|
||||
尝试不同的登录方式
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_login_methods():
|
||||
"""测试不同的登录方式"""
|
||||
base_url = "http://localhost:8080"
|
||||
|
||||
# 方式1: 标准登录(无加密)
|
||||
print("🔓 测试方式1:标准登录(无加密)")
|
||||
try:
|
||||
login_data = {
|
||||
"tenantId": "000000",
|
||||
"username": "admin",
|
||||
"password": "admin123",
|
||||
"clientId": "e5cd7e4891bf95d1d19206ce24a7b32e",
|
||||
"grantType": "password"
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
'referer': 'http://localhost/login?redirect=%252Findex',
|
||||
}
|
||||
|
||||
response = requests.post(f"{base_url}/auth/login", json=login_data, headers=headers)
|
||||
print(f" 状态码: {response.status_code}")
|
||||
print(f" 响应: {response.text[:200]}...")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get('code') == 200:
|
||||
print(" ✅ 登录成功!")
|
||||
token = result.get('data', {}).get('access_token')
|
||||
if token:
|
||||
print(f" 🎫 Token: {token[:50]}...")
|
||||
return token
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {str(e)}")
|
||||
|
||||
# 方式2: 尝试不同的clientid
|
||||
print("\n🔓 测试方式2:默认clientid")
|
||||
try:
|
||||
login_data = {
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
response = requests.post(f"{base_url}/auth/login", json=login_data, headers=headers)
|
||||
print(f" 状态码: {response.status_code}")
|
||||
print(f" 响应: {response.text[:200]}...")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {str(e)}")
|
||||
|
||||
# 方式3: 尝试表单提交
|
||||
print("\n🔓 测试方式3:表单提交")
|
||||
try:
|
||||
login_data = {
|
||||
"username": "admin",
|
||||
"password": "admin123",
|
||||
"tenantId": "000000"
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
|
||||
response = requests.post(f"{base_url}/auth/login", data=login_data, headers=headers)
|
||||
print(f" 状态码: {response.status_code}")
|
||||
print(f" 响应: {response.text[:200]}...")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {str(e)}")
|
||||
|
||||
# 方式4: 尝试获取公钥再登录
|
||||
print("\n🔓 测试方式4:获取公钥")
|
||||
try:
|
||||
response = requests.get(f"{base_url}/auth/publicKey")
|
||||
print(f" 获取公钥状态码: {response.status_code}")
|
||||
print(f" 公钥响应: {response.text[:200]}...")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {str(e)}")
|
||||
|
||||
# 方式5: 检查验证码
|
||||
print("\n🔓 测试方式5:检查验证码要求")
|
||||
try:
|
||||
response = requests.get(f"{base_url}/auth/captcha")
|
||||
print(f" 验证码状态码: {response.status_code}")
|
||||
print(f" 验证码响应: {response.text[:200]}...")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {str(e)}")
|
||||
|
||||
def test_without_auth():
|
||||
"""测试不需要认证的接口"""
|
||||
print("\n📊 测试无需认证的接口")
|
||||
base_url = "http://localhost:8080"
|
||||
|
||||
endpoints = [
|
||||
"/",
|
||||
"/health",
|
||||
"/actuator/health",
|
||||
"/swagger-ui.html",
|
||||
"/doc.html"
|
||||
]
|
||||
|
||||
for endpoint in endpoints:
|
||||
try:
|
||||
response = requests.get(f"{base_url}{endpoint}")
|
||||
print(f" {endpoint}: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
print(f" 内容类型: {response.headers.get('content-type', 'unknown')}")
|
||||
except Exception as e:
|
||||
print(f" {endpoint}: 异常 - {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔍 快速登录测试")
|
||||
print("="*50)
|
||||
|
||||
test_login_methods()
|
||||
test_without_auth()
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试班级列表获取
|
||||
在批量创建学生之前,先检查系统中的班级信息
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_class_list():
|
||||
"""测试获取班级列表"""
|
||||
base_url = "http://localhost:8080"
|
||||
|
||||
try:
|
||||
url = f"{base_url}/classMsg/classMsg/list"
|
||||
params = {
|
||||
'pageNum': 1,
|
||||
'pageSize': 1000
|
||||
}
|
||||
|
||||
print("正在获取班级列表...")
|
||||
response = requests.get(url, params=params)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
print(f"响应状态码: {response.status_code}")
|
||||
print(f"响应内容: {json.dumps(data, ensure_ascii=False, indent=2)}")
|
||||
|
||||
if data.get('code') == 200:
|
||||
classes = data.get('rows', [])
|
||||
print(f"\n✅ 成功获取到 {len(classes)} 个班级")
|
||||
|
||||
if classes:
|
||||
print("\n班级列表:")
|
||||
for i, class_info in enumerate(classes[:5], 1): # 只显示前5个
|
||||
print(f" {i}. 班级ID: {class_info.get('id')}")
|
||||
print(f" 班级名称: {class_info.get('className', class_info.get('class_name', '未知'))}")
|
||||
print(f" 班级代码: {class_info.get('classCode', class_info.get('class_code', '未知'))}")
|
||||
print(f" 学院: {class_info.get('collegeName', class_info.get('college_name', '未知'))}")
|
||||
print(f" 专业: {class_info.get('majorName', class_info.get('major_name', '未知'))}")
|
||||
print(f" 年级: {class_info.get('grade', '未知')}")
|
||||
print()
|
||||
|
||||
if len(classes) > 5:
|
||||
print(f" ... 还有 {len(classes) - 5} 个班级")
|
||||
else:
|
||||
print("⚠️ 系统中暂无班级信息")
|
||||
else:
|
||||
print(f"❌ 获取班级列表失败: {data.get('msg')}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("❌ 连接失败: 无法连接到后端服务,请确认:")
|
||||
print(" 1. 后端服务是否已启动")
|
||||
print(" 2. API地址是否正确")
|
||||
print(" 3. 网络连接是否正常")
|
||||
except Exception as e:
|
||||
print(f"❌ 请求异常: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_class_list()
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user