管理后台:

1. 提交获取教师二维码的功能(前端+后端)
2. 学生实体类加一些字段
This commit is contained in:
mirage 2026-02-24 15:26:34 +08:00
parent 653987d860
commit 9b48594479
16 changed files with 612 additions and 102 deletions

View File

@ -49,3 +49,21 @@ export function teacherConfigRole(params: Record<string, any>) {
export function teacherGetConfig() {
return request.get({ url: '/teacher/get.config' })
}
// 生成邀请码和二维码
export function teacherGenerateInvitationCode(params: Record<string, any>) {
return request.post({ url: '/teacher/generateInvitationCode', params })
}
// 获取教师二维码图片URL
export function teacherGetQrCodeUrl(params: Record<string, any>) {
return request.get({ url: '/teacher/qrcode', params })
}
// 获取教师二维码图片流(带鉴权,用于展示与下载)
export function teacherGetQrCodeImage(params: Record<string, any>) {
return request.get(
{ url: '/teacher/qrcode/image', params, responseType: 'blob' },
{ isReturnDefaultResponse: true }
)
}

View File

@ -149,8 +149,9 @@ export class Axios {
requestOptions: opt
}
const { urlPrefix } = opt
// 拼接请求前缀如api
if (urlPrefix) {
// 绝对 URL如图片地址不拼接前缀直接使用
const isAbsoluteUrl = typeof config.url === 'string' && /^https?:\/\//i.test(config.url)
if (urlPrefix && !isAbsoluteUrl) {
// 确保路径格式正确:/adminapi/xxx
const url = config.url?.startsWith('/') ? config.url : `/${config.url}`
axioxConfig.url = `/${urlPrefix}${url}`

View File

@ -15,6 +15,8 @@ import com.mdd.common.entity.system.SystemRole;
import com.mdd.common.validator.annotation.IDMust;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -30,15 +32,15 @@ public class TeacherController {
ITeacherService iTeacherService;
@GetMapping("/list")
@ApiOperation(value="教师信息扩展列表")
@ApiOperation(value = "教师信息扩展列表")
public AjaxResult<PageResult<TeacherListedVo>> list(@Validated PageValidate pageValidate,
@Validated TeacherSearchValidate searchValidate) {
@Validated TeacherSearchValidate searchValidate) {
PageResult<TeacherListedVo> list = iTeacherService.list(pageValidate, searchValidate);
return AjaxResult.success(list);
}
@GetMapping("/detail")
@ApiOperation(value="教师信息扩展详情")
@ApiOperation(value = "教师信息扩展详情")
public AjaxResult<TeacherDetailVo> detail(@Validated @IDMust() @RequestParam("id") Integer id) {
TeacherDetailVo detail = iTeacherService.detail(id);
return AjaxResult.success(detail);
@ -46,14 +48,14 @@ public class TeacherController {
@Log(title = "添加教师账号")
@PostMapping("/add")
@ApiOperation(value="添加教师账号", notes = "根据提供的角色id列表为添加的教师账号选择角色")
@ApiOperation(value = "添加教师账号", notes = "支持传入invitationCode(邀请码/二维码标识)根据提供的角色id列表为添加的教师账号选择角色")
public AjaxResult<Object> add(@Validated @RequestBody TeacherCreateValidate createValidate) {
return iTeacherService.add(createValidate);
}
@Log(title = "教师信息扩展编辑")
@PostMapping("/edit")
@ApiOperation(value="教师信息扩展编辑")
@ApiOperation(value = "教师信息扩展编辑", notes = "支持更新invitationCode(邀请码/二维码标识)")
public AjaxResult<Object> edit(@Validated @RequestBody TeacherUpdateValidate updateValidate) {
iTeacherService.edit(updateValidate);
return AjaxResult.success();
@ -61,7 +63,7 @@ public class TeacherController {
@Log(title = "教师信息扩展删除")
@PostMapping("/del")
@ApiOperation(value="教师信息扩展删除")
@ApiOperation(value = "教师信息扩展删除")
public AjaxResult<Object> del(@Validated @RequestBody IdValidate idValidate) {
iTeacherService.del(idValidate.getId());
return AjaxResult.success();
@ -81,4 +83,29 @@ public class TeacherController {
iTeacherService.roleConfig(idValidate.getId());
return AjaxResult.success();
}
@Log(title = "生成邀请码")
@PostMapping("/generateInvitationCode")
@ApiOperation(value = "生成邀请码和二维码", notes = "为指定教师生成唯一的邀请码和二维码图片")
public AjaxResult<Object> generateInvitationCode(@Validated @RequestBody IdValidate idValidate) {
return iTeacherService.generateInvitationCode(idValidate.getId());
}
@GetMapping("/qrcode")
@ApiOperation(value = "获取二维码图片URL", notes = "获取指定教师的二维码图片访问地址")
public AjaxResult<Object> getQrCodeUrl(@Validated @IDMust() @RequestParam("id") Integer id) {
return iTeacherService.getQrCodeUrl(id);
}
@GetMapping("/qrcode/image")
@ApiOperation(value = "获取二维码图片流", notes = "带鉴权返回教师二维码图片,用于前端展示与下载")
public ResponseEntity<byte[]> getQrCodeImage(@Validated @IDMust() @RequestParam("id") Integer id) {
byte[] bytes = iTeacherService.getQrCodeImageBytes(id);
if (bytes == null || bytes.length == 0) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_PNG)
.body(bytes);
}
}

View File

@ -10,8 +10,6 @@ import com.mdd.common.core.AjaxResult;
import com.mdd.common.core.PageResult;
import com.mdd.common.entity.system.SystemRole;
import javax.management.relation.Role;
/**
* 教师信息扩展服务接口类
* @author gyp
@ -64,4 +62,30 @@ public interface ITeacherService {
void roleConfig(Integer id);
AjaxResult<SystemRole> getRoleConfig();
/**
* 生成邀请码和二维码
*
* @param teacherId 教师ID
* @return AjaxResult<String> 返回生成的邀请码
* @author gyp
*/
AjaxResult<Object> generateInvitationCode(Integer teacherId);
/**
* 获取二维码图片URL
*
* @author gyp
* @param teacherId 教师ID
* @return AjaxResult<String> 返回二维码图片的绝对URL
*/
AjaxResult<Object> getQrCodeUrl(Integer teacherId);
/**
* 获取二维码图片字节流用于带鉴权的图片展示与下载
*
* @param teacherId 教师ID
* @return 图片字节数组不存在时返回 null
*/
byte[] getQrCodeImageBytes(Integer teacherId);
}

View File

@ -29,6 +29,7 @@ import com.mdd.common.mapper.user.UserMapper;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.text.SimpleDateFormat;
import com.mdd.common.util.*;
import io.netty.util.internal.ThreadLocalRandom;
@ -92,6 +93,8 @@ public class StudentInfoServiceImpl implements IStudentInfoService {
"=:academicWarnings@academic_warnings:int",
"=:isVerified@is_verified:int",
"=:verifiedBy@verified_by:int",
"=:recruitmentTeacherId@recruitment_teacher_id:int",
"=:receptionTeacherId@reception_teacher_id:int",
});
IPage<StudentInfo> iPage = studentInfoMapper.selectPage(new Page<>(page, limit), queryWrapper);
@ -102,13 +105,27 @@ public class StudentInfoServiceImpl implements IStudentInfoService {
BeanUtils.copyProperties(item, vo);
vo.setVerifiedTime(TimeUtils.timestampToDate(item.getVerifiedTime()));
// 通过 student_id 关联查询基本信息获取 name
// 通过 student_id 关联查询基本信息获取姓名及基础信息
StudentBaseInfo baseInfo = studentBaseInfoMapper.selectOne(
new QueryWrapper<StudentBaseInfo>()
.eq("student_id", item.getStudentId())
.last("limit 1"));
if (baseInfo != null) {
vo.setName(baseInfo.getName());
vo.setGender(baseInfo.getGender());
vo.setIdCard(baseInfo.getIdCard());
vo.setPreviousSchool(baseInfo.getPreviousSchool());
vo.setHeight(baseInfo.getHeight());
vo.setWeight(baseInfo.getWeight());
vo.setShoeSize(baseInfo.getShoeSize());
}
// 转换创建时间和更新时间为字符串格式
if (item.getCreateTime() != null) {
vo.setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item.getCreateTime()));
}
if (item.getUpdateTime() != null) {
vo.setUpdateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item.getUpdateTime()));
}
College college = collegeMapper.selectById(item.getCollegeId());

View File

@ -16,36 +16,43 @@ import com.mdd.admin.vo.teacher.TeacherDetailVo;
import com.mdd.common.core.AjaxResult;
import com.mdd.common.core.PageResult;
import com.mdd.common.entity.College;
import com.mdd.common.entity.Config;
import com.mdd.common.entity.Teacher;
import com.mdd.common.entity.admin.Admin;
import com.mdd.common.entity.admin.AdminRole;
import com.mdd.common.entity.system.SystemRole;
import com.mdd.common.entity.user.User;
import com.mdd.common.mapper.CollegeMapper;
import com.mdd.common.mapper.TeacherMapper;
import com.mdd.common.mapper.admin.AdminMapper;
import com.mdd.common.mapper.admin.AdminRoleMapper;
import com.mdd.common.mapper.system.SystemRoleMapper;
import com.mdd.common.exception.OperateException;
import com.mdd.common.util.ConfigUtils;
import com.mdd.common.util.QrCodeUtil;
import com.mdd.common.util.RandomUtil;
import com.mdd.common.util.TimeUtils;
import com.mdd.common.util.ToolUtils;
import com.mdd.common.util.UrlUtils;
import com.mdd.common.util.YmlUtils;
import com.google.zxing.WriterException;
import java.io.File;
import java.io.IOException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import javax.management.relation.Role;
import java.util.*;
import java.util.stream.Collectors;
/**
* 教师信息扩展实现类
*
* @author gyp
*/
@Service
public class TeacherServiceImpl implements ITeacherService {
@Resource
TeacherMapper teacherMapper;
@Autowired
@ -55,8 +62,6 @@ public class TeacherServiceImpl implements ITeacherService {
@Autowired
private AdminServiceImpl adminServiceImpl;
@Autowired
private AdminRoleMapper adminRoleMapper;
@Autowired
private AdminRoleServiceImpl adminRoleServiceImpl;
@Autowired
private SystemRoleMapper systemRoleMapper;
@ -64,42 +69,48 @@ public class TeacherServiceImpl implements ITeacherService {
/**
* 教师信息扩展列表
*
* @author gyp
* @param pageValidate 分页参数
* @param pageValidate 分页参数
* @param searchValidate 搜索参数
* @return PageResult<TeacherListedVo>
* @author gyp
*/
@Override
public PageResult<TeacherListedVo> list(PageValidate pageValidate, TeacherSearchValidate searchValidate) {
Integer page = pageValidate.getPage_no();
Integer page = pageValidate.getPage_no();
Integer limit = pageValidate.getPage_size();
QueryWrapper<Teacher> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
queryWrapper.orderByDesc("teacher_id");
teacherMapper.setSearch(queryWrapper, searchValidate, new String[]{
"=:adminId@admin_id:int",
"like:name:str",
"like:name@teacher_name:str",
"=:teacherCode@teacher_code:str",
"=:collegeId@college_id:int",
"=:teacherStatus@teacher_status:int",
"=:invitationCode@invitation_code:str",
});
IPage<Teacher> iPage = teacherMapper.selectPage(new Page<>(page, limit), queryWrapper);
List<TeacherListedVo> list = new LinkedList<>();
for(Teacher item : iPage.getRecords()) {
for (Teacher item : iPage.getRecords()) {
TeacherListedVo vo = new TeacherListedVo();
BeanUtils.copyProperties(item, vo);
// 映射主键实体是teacherIdVO也是teacherIdBeanUtils会自动处理
vo.setTeacherId(item.getTeacherId());
// 显式设置教师工号确保字段正确映射
vo.setTeacherCode(item.getTeacherCode());
// 兼容前端列表使用name字段展示教师姓名但教师表字段为teacher_name
vo.setName(item.getTeacherName());
// 设置二维码URL转换为绝对URL
if (item.getQrcodeUrl() != null && !item.getQrcodeUrl().isEmpty()) {
// 后台端静态资源映射/adminapi/uploads/**因此这里需走 admin 端的绝对路径转换
vo.setQrcodeUrl(UrlUtils.toAdminAbsoluteUrl(item.getQrcodeUrl()));
}
vo.setCreateTime(TimeUtils.timestampToDate(item.getCreateTime()));
vo.setUpdateTime(TimeUtils.timestampToDate(item.getUpdateTime()));
College college = collegeMapper.selectById(vo.getCollegeId());
if(college != null) vo.setCollegeName(college.getCollegeName());
Admin admin = adminMapper.selectById(vo.getAdminId());
if(admin != null) {
vo.setName(admin.getName());
vo.setTeacherCode(admin.getAccount());
}
if (college != null) vo.setCollegeName(college.getCollegeName());
list.add(vo);
}
@ -110,53 +121,59 @@ public class TeacherServiceImpl implements ITeacherService {
/**
* 教师信息扩展详情
*
* @author gyp
* @param id 主键参数
* @return Teacher
* @author gyp
*/
@Override
public TeacherDetailVo detail(Integer id) {
Teacher model = teacherMapper.selectOne(
new QueryWrapper<Teacher>()
.eq("id", id)
.last("limit 1"));
.eq("teacher_id", id)
.last("limit 1"));
Assert.notNull(model, "数据不存在");
TeacherDetailVo vo = new TeacherDetailVo();
BeanUtils.copyProperties(model, vo);
Admin admin = adminMapper.selectById(vo.getAdminId());
if(admin != null) {
vo.setName(admin.getName());
vo.setTeacherCode(admin.getAccount());
// 映射主键实体是teacherIdVO也是teacherIdBeanUtils会自动处理
vo.setTeacherId(model.getTeacherId());
// 显式设置教师工号确保字段正确映射
vo.setTeacherCode(model.getTeacherCode());
// 兼容前端详情使用name字段展示教师姓名但教师表字段为teacher_name
vo.setName(model.getTeacherName());
// 设置二维码URL转换为绝对URL
if (model.getQrcodeUrl() != null && !model.getQrcodeUrl().isEmpty()) {
// 后台端静态资源映射/adminapi/uploads/**因此这里需走 admin 端的绝对路径转换
vo.setQrcodeUrl(UrlUtils.toAdminAbsoluteUrl(model.getQrcodeUrl()));
}
College college = collegeMapper.selectById(vo.getCollegeId());
if(college != null) vo.setCollegeName(college.getCollegeName());
if (college != null) vo.setCollegeName(college.getCollegeName());
return vo;
}
/**
* 教师信息扩展新增
*
* @author gyp
* @param createValidate 参数
* @author gyp
*/
@Override
public AjaxResult<Object> add(TeacherCreateValidate createValidate) {
// 自动生成唯一教师工号作为管理员账号 & 教师工号
String teacherCode = generateTeacherCode();
// 创建admin账号
SystemAdminCreateValidate admin = new SystemAdminCreateValidate();
admin.setAccount(createValidate.getTeacherCode());
admin.setAccount(teacherCode);
admin.setName(createValidate.getName());
admin.setDisable(1);
// 密码默认是电话号码后8位
admin.setPassword(createValidate.getContactPhone().substring(3));
int teacherRoleId;
try{
try {
teacherRoleId = Integer.parseInt(ConfigUtils.get("teacher", "teacher_role_id"));
}catch (NullPointerException e){
} catch (NullPointerException e) {
return AjaxResult.failed("未配置教师角色");
}
admin.setRoleId(Collections.singletonList(teacherRoleId));
@ -169,12 +186,19 @@ public class TeacherServiceImpl implements ITeacherService {
queryWrapper.eq("account", admin.getAccount());
queryWrapper.isNull("delete_time");
Admin admin1 = adminMapper.selectOne(queryWrapper);
// 创建附属教师信息
// 创建教师信息注意新表结构没有admin_id字段所以不存储adminId
Teacher model = new Teacher();
BeanUtils.copyProperties(createValidate, model);
model.setAdminId(admin1.getId());
model.setCreateTime(System.currentTimeMillis()/1000);
model.setUpdateTime(System.currentTimeMillis()/1000);
model.setTeacherName(createValidate.getName());
model.setTeacherCode(teacherCode);
model.setCollegeId(createValidate.getCollegeId());
model.setContactPhone(createValidate.getContactPhone());
model.setContactEmail(createValidate.getContactEmail());
if (createValidate.getInvitationCode() != null && !createValidate.getInvitationCode().isEmpty()) {
model.setInvitationCode(createValidate.getInvitationCode());
}
model.setTeacherStatus(1); // 默认在职
model.setCreateTime(System.currentTimeMillis() / 1000);
model.setUpdateTime(System.currentTimeMillis() / 1000);
teacherMapper.insert(model);
return AjaxResult.success("添加信息成功");
@ -183,24 +207,27 @@ public class TeacherServiceImpl implements ITeacherService {
/**
* 教师信息扩展编辑
*
* @author gyp
* @param updateValidate 参数
* @author gyp
*/
@Override
public void edit(TeacherUpdateValidate updateValidate) {
Teacher model = teacherMapper.selectOne(
new QueryWrapper<Teacher>()
.eq("id", updateValidate.getId())
.last("limit 1"));
.eq("teacher_id", updateValidate.getTeacherId())
.last("limit 1"));
Assert.notNull(model, "数据不存在!");
model.setId(updateValidate.getId());
model.setAdminId(updateValidate.getAdminId());
model.setTeacherId(updateValidate.getTeacherId());
model.setTeacherName(updateValidate.getName());
model.setCollegeId(updateValidate.getCollegeId());
model.setContactPhone(updateValidate.getContactPhone());
model.setContactEmail(updateValidate.getContactEmail());
model.setTeacherStatus(updateValidate.getTeacherStatus());
if (updateValidate.getInvitationCode() != null) {
model.setInvitationCode(updateValidate.getInvitationCode());
}
model.setUpdateTime(System.currentTimeMillis() / 1000);
teacherMapper.updateById(model);
}
@ -208,52 +235,53 @@ public class TeacherServiceImpl implements ITeacherService {
/**
* 教师信息扩展删除
*
* @author gyp
* @param id 主键ID
* @author gyp
*/
@Override
public void del(Integer id) {
Teacher model = teacherMapper.selectOne(
new QueryWrapper<Teacher>()
.eq("id", id)
.last("limit 1"));
.eq("teacher_id", id)
.last("limit 1"));
Assert.notNull(model, "数据不存在!");
teacherMapper.delete(new QueryWrapper<Teacher>().eq("id", id));
teacherMapper.delete(new QueryWrapper<Teacher>().eq("teacher_id", id));
}
/**
* 配置教师的角色的ID
* */
*/
@Override
public void roleConfig(Integer id) {
QueryWrapper<AdminRole> queryWrapper = null;
// 存放教师列表
List<AdminRole> isTeacher = new ArrayList<>();
int originalRoleId = -1; // 初始ID为-1应该不会出现-1的ID
try{
try {
// 查找原教师配置的所有教师
originalRoleId = Integer.parseInt(ConfigUtils.get("teacher", "teacher_role_id"));
List<AdminRole> adminRoleList = adminRoleServiceImpl.getAdminIdByRoleId(originalRoleId);
isTeacher.addAll(adminRoleList);
}catch (NullPointerException ignored){} // 忽视此处因为查找不到的报错
} catch (NullPointerException ignored) {
} // 忽视此处因为查找不到的报错
// 若配置不存在会新建配置
ConfigUtils.set("teacher","teacher_role_id",id.toString());
ConfigUtils.set("teacher", "teacher_role_id", id.toString());
// 将原本的所有教师的角色ID更新
if(!isTeacher.isEmpty()) {
if (!isTeacher.isEmpty()) {
// 对每个教师的角色重新分配
for (AdminRole adminRole : isTeacher) {
List<Integer> roleIds = adminRoleServiceImpl.getRoleIdAttr(adminRole.getAdminId());
for(int i = 0; i < roleIds.size(); i++){
for (int i = 0; i < roleIds.size(); i++) {
// 替换原教师角色
if(roleIds.get(i) == originalRoleId){
if (roleIds.get(i) == originalRoleId) {
roleIds.set(i, id);
}
// 删除重复分配
else if(roleIds.get(i).equals(id)){
else if (roleIds.get(i).equals(id)) {
roleIds.remove(i);
i--;
}
@ -275,15 +303,269 @@ public class TeacherServiceImpl implements ITeacherService {
return AjaxResult.success(role);
}
/**
* 生成唯一教师工号T + 时间戳秒 + 3位随机数
*/
private String generateTeacherCode() {
String code;
do {
code = "T" + (System.currentTimeMillis() / 1000) + RandomUtil.randomNumber(3);
} while (teacherMapper.selectCount(new QueryWrapper<Teacher>().eq("teacher_code", code)) > 0);
return code;
}
public String getNameById(Integer teacherId) {
Teacher teacher = teacherMapper.selectById(teacherId);
if(teacher == null){
if (teacher == null) {
return null;
}
Admin admin = adminMapper.selectById(teacher.getAdminId());
if(admin == null){
return teacher.getTeacherName();
}
/**
* 生成邀请码和二维码
*
* @param teacherId 教师ID
* @return AjaxResult<String> 返回生成的邀请码
*/
@Override
public AjaxResult<Object> generateInvitationCode(Integer teacherId) {
Teacher teacher = teacherMapper.selectOne(
new QueryWrapper<Teacher>()
.eq("teacher_id", teacherId)
.last("limit 1"));
Assert.notNull(teacher, "教师不存在!");
// 生成唯一邀请码INV + 时间戳() + 6位随机字符串
String invitationCode;
do {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String randomStr = ToolUtils.randomString(6).toUpperCase();
invitationCode = "INV" + timestamp + randomStr;
} while (teacherMapper.selectCount(
new QueryWrapper<Teacher>()
.eq("invitation_code", invitationCode)
.ne("teacher_id", teacherId)) > 0);
// 生成二维码内容URL预报名页面地址
String qrCodeContent = buildQrCodeUrl(invitationCode);
// 生成二维码图片并保存
String qrcodeUrl = generateAndSaveQrCode(invitationCode, qrCodeContent, teacherId);
// 更新教师表的邀请码和二维码地址
teacher.setInvitationCode(invitationCode);
teacher.setQrcodeUrl(qrcodeUrl);
teacher.setUpdateTime(System.currentTimeMillis() / 1000);
teacherMapper.updateById(teacher);
return AjaxResult.success(invitationCode);
}
/**
* 构建二维码内容URL预报名页面地址
*
* @param invitationCode 邀请码
* @return 完整的预报名页面URL
*/
private String buildQrCodeUrl(String invitationCode) {
// 优先从配置中读取预报名H5地址建议配置为 uniapp H5 https://xx.com/h5
String h5BaseUrl = ConfigUtils.get("pre_registration", "front_url", "");
if (h5BaseUrl == null || h5BaseUrl.isEmpty()) {
// 如果没有单独配置尝试从 yml 中读取 like.front-url
String frontUrl = YmlUtils.get("like.front-url");
if (frontUrl != null && !frontUrl.isEmpty()) {
h5BaseUrl = frontUrl;
} else {
// 再退一步使用当前请求域名
h5BaseUrl = UrlUtils.getRequestUrl();
}
}
if (h5BaseUrl == null) {
h5BaseUrl = "";
}
// 移除末尾的斜杠方便统一拼接
while (h5BaseUrl.endsWith("/")) {
h5BaseUrl = h5BaseUrl.substring(0, h5BaseUrl.length() - 1);
}
String fullUrl;
if (h5BaseUrl.contains("#/")) {
// 如果已经配置了完整的 hash 路径例如https://xx.com/h5/#/pages/pre_registration/pre_registration
// 这里只负责追加或合并 invitationCode 参数
if (h5BaseUrl.contains("?")) {
fullUrl = h5BaseUrl + "&invitationCode=" + invitationCode;
} else {
fullUrl = h5BaseUrl + "?invitationCode=" + invitationCode;
}
} else {
// 默认拼接为小程序路径去除H5的hash模式以便微信扫码直接进入小程序
// {h5BaseUrl}/pages/pre_registration/pre_registration?invitationCode=xxx
fullUrl = h5BaseUrl
/*+ "/pages/pre_registration/pre_registration.html"
+ "?invitationCode=" + invitationCode*/;
}
return fullUrl;
}
/**
* 生成二维码图片并保存方案B本地存储预留方案AOSS上传
*
* @param invitationCode 邀请码
* @param qrCodeContent 二维码内容
* @param teacherId 教师ID
* @return 二维码图片的相对路径URL
*/
private String generateAndSaveQrCode(String invitationCode, String qrCodeContent, Integer teacherId) {
try {
// 二维码图片尺寸
int qrCodeWidth = 300;
int qrCodeHeight = 300;
// 获取存储引擎配置
String engine = ConfigUtils.get("storage", "default", "local");
engine = engine == null || engine.isEmpty() ? "local" : engine;
String qrcodeUrl;
String date = TimeUtils.timestampToDate(TimeUtils.timestamp(), "yyyyMMdd");
String fileName = "teacher_" + teacherId + "_" + invitationCode + ".png";
String fileKey = date + "/" + fileName;
if ("local".equals(engine)) {
// ========== 方案B本地存储 ==========
String directory = YmlUtils.get("like.upload-directory");
if (directory == null || directory.isEmpty()) {
throw new OperateException("请配置上传目录like.upload-directory");
}
String folder = "qrcode/teacher";
String savePath = (directory + folder + "/" + date).replace("\\", "/");
File saveDir = new File(savePath);
if (!saveDir.exists()) {
if (!saveDir.mkdirs()) {
throw new OperateException("创建二维码保存目录失败");
}
}
File qrCodeFile = new File(savePath, fileName);
QrCodeUtil.generateQrCodeToFile(qrCodeContent, qrCodeWidth, qrCodeHeight, qrCodeFile.getAbsolutePath());
// 返回相对路径
qrcodeUrl = folder + "/" + fileKey;
} else {
// ========== 方案AOSS上传预留代码 ==========
// 注意OSS上传需要MultipartFile这里需要将byte[]转换为临时文件再上传
// 或者直接使用OSS的putObject方法上传字节流
// 方案A-1使用临时文件上传适用于所有OSS
File tempFile = File.createTempFile("qrcode_", ".png");
try {
QrCodeUtil.generateQrCodeToFile(qrCodeContent, qrCodeWidth, qrCodeHeight, tempFile.getAbsolutePath());
String folder = "qrcode/teacher";
String ossKey = folder + "/" + fileKey;
if ("aliyun".equals(engine)) {
// 阿里云OSS上传
/*
Map<String, String> config = ConfigUtils.getMap("storage", "aliyun");
AliyunStorage aliyunStorage = new AliyunStorage(config);
// 需要将File转换为MultipartFile或使用OSS的putObject方法
// 这里使用OSS直接上传字节流的方式
com.aliyun.oss.OSS ossClient = aliyunStorage.ossClient();
try {
ossClient.putObject(config.get("bucket"), ossKey, new FileInputStream(tempFile));
qrcodeUrl = ossKey;
} finally {
ossClient.shutdown();
}
*/
// TODO: 实现阿里云OSS上传逻辑
qrcodeUrl = ossKey;
} else if ("qiniu".equals(engine)) {
// 七牛云上传
// TODO: 实现七牛云上传逻辑
qrcodeUrl = ossKey;
} else if ("qcloud".equals(engine)) {
// 腾讯云COS上传
// TODO: 实现腾讯云COS上传逻辑
qrcodeUrl = ossKey;
} else {
throw new OperateException("不支持的存储引擎: " + engine);
}
} finally {
// 删除临时文件
if (tempFile.exists()) {
tempFile.delete();
}
}
}
return qrcodeUrl;
} catch (WriterException | IOException e) {
throw new OperateException("生成二维码失败: " + e.getMessage());
}
}
/**
* 获取二维码图片URL
*
* @param teacherId 教师ID
* @return AjaxResult<String> 返回二维码图片的绝对URL
*/
@Override
public AjaxResult<Object> getQrCodeUrl(Integer teacherId) {
Teacher teacher = teacherMapper.selectOne(
new QueryWrapper<Teacher>()
.eq("teacher_id", teacherId)
.last("limit 1"));
Assert.notNull(teacher, "教师不存在!");
if (teacher.getQrcodeUrl() == null || teacher.getQrcodeUrl().isEmpty()) {
return AjaxResult.failed("该教师尚未生成二维码,请先生成邀请码");
}
// 转换为后台端可访问的绝对URL/adminapi/uploads/**
String absoluteUrl = UrlUtils.toAdminAbsoluteUrl(teacher.getQrcodeUrl());
return AjaxResult.success(absoluteUrl);
}
@Override
public byte[] getQrCodeImageBytes(Integer teacherId) {
Teacher teacher = teacherMapper.selectOne(
new QueryWrapper<Teacher>()
.eq("teacher_id", teacherId)
.last("limit 1"));
if (teacher == null || teacher.getQrcodeUrl() == null || teacher.getQrcodeUrl().isEmpty()) {
return null;
}
String engine = ConfigUtils.get("storage", "default", "local");
engine = engine == null || engine.isEmpty() ? "local" : engine;
if (!"local".equals(engine)) {
return null;
}
String directory = YmlUtils.get("like.upload-directory");
if (directory == null || directory.isEmpty()) {
return null;
}
String path = teacher.getQrcodeUrl().startsWith("/") ? teacher.getQrcodeUrl() : "/" + teacher.getQrcodeUrl();
String fullPath = (directory + path).replace("\\", "/");
java.io.File file = new java.io.File(fullPath);
if (!file.exists() || !file.isFile()) {
return null;
}
try {
return java.nio.file.Files.readAllBytes(file.toPath());
} catch (java.io.IOException e) {
return null;
}
return admin.getName();
}
}

View File

@ -90,7 +90,9 @@ public class TeachingTaskServiceImpl implements ITeachingTaskService {
Course course = courseMapper.selectById(courseId);
Teacher teacher = teacherMapper.selectById(teacherId);
Class clazz = classMapper.selectById(classId);
Admin adminTeacher = adminMapper.selectById(teacher.getAdminId());
Admin adminTeacher = adminMapper.selectOne(
new QueryWrapper<Admin>()
.eq("account", teacher.getTeacherCode()));
Admin adminCreator = adminMapper.selectById(createdBy);
Admin adminUpdater = adminMapper.selectById(updatedBy);
vo.setSemesterName(semester.getSemesterName());
@ -131,7 +133,9 @@ public class TeachingTaskServiceImpl implements ITeachingTaskService {
Course course = courseMapper.selectById(courseId);
Teacher teacher = teacherMapper.selectById(teacherId);
Class clazz = classMapper.selectById(classId);
Admin adminTeacher = adminMapper.selectById(teacher.getAdminId());
Admin adminTeacher = adminMapper.selectOne(
new QueryWrapper<Admin>()
.eq("account", teacher.getTeacherCode()));
vo.setSemesterName(semester.getSemesterName());
vo.setCourseName(course.getCourseName());
vo.setClassName(clazz.getClassName());
@ -248,7 +252,7 @@ public class TeachingTaskServiceImpl implements ITeachingTaskService {
for(Teacher teacher : teachers){
// 将在名单的教师记录
for(TeacherCourse teacherCourse : teacherCourses){
if(teacher.getId().equals(teacherCourse.getTeacherId())){
if(teacher.getTeacherId().equals(teacherCourse.getTeacherId())){
teacherIds.add(teacherCourse.getTeacherId());
}
}
@ -257,7 +261,7 @@ public class TeachingTaskServiceImpl implements ITeachingTaskService {
}
List<Integer> teacherIds = new ArrayList<>();
for (Teacher teacher : teachers) {
teacherIds.add(teacher.getId());
teacherIds.add(teacher.getTeacherId());
}
return AjaxResult.success(teacherIds);
}

View File

@ -50,4 +50,10 @@ public class StudentInfoSearchValidate implements Serializable {
@ApiModelProperty(value = "认证人ID")
private Integer verifiedBy;
@ApiModelProperty(value = "招生老师ID")
private Integer recruitmentTeacherId;
@ApiModelProperty(value = "接待老师ID")
private Integer receptionTeacherId;
}

View File

@ -15,8 +15,7 @@ public class TeacherCreateValidate implements Serializable {
private static final long serialVersionUID = 1L;
@NotEmpty(message = "账号不能为空")
@ApiModelProperty(value = "教师工号", required = true)
@ApiModelProperty(value = "教师工号(系统自动生成)")
private String teacherCode;
@NotEmpty(message = "姓名不能为空")
@ -31,4 +30,7 @@ public class TeacherCreateValidate implements Serializable {
@ApiModelProperty(value = "联系邮箱")
private String contactEmail;
@ApiModelProperty(value = "邀请码(二维码地址标识)")
private String invitationCode;
}

View File

@ -14,9 +14,6 @@ public class TeacherSearchValidate implements Serializable {
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "关联管理员ID")
private Integer adminId;
@ApiModelProperty(value = "教师工号")
private String teacherCode;
@ -26,4 +23,7 @@ public class TeacherSearchValidate implements Serializable {
@ApiModelProperty(value = "教师状态1-在职2-休假3-离职")
private Integer teacherStatus;
@ApiModelProperty(value = "邀请码(二维码地址标识)")
private String invitationCode;
}

View File

@ -17,12 +17,12 @@ public class TeacherUpdateValidate implements Serializable {
private static final long serialVersionUID = 1L;
@IDMust(message = "id参数必传且需大于0")
@ApiModelProperty(value = "")
private Integer id;
@IDMust(message = "teacherId参数必传且需大于0")
@ApiModelProperty(value = "教师ID")
private Integer teacherId;
@ApiModelProperty(value = "关联管理员ID")
private Integer adminId;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "教师工号")
private String teacherCode;
@ -42,4 +42,7 @@ public class TeacherUpdateValidate implements Serializable {
@ApiModelProperty(value = "教师状态1-在职2-休假3-离职")
private Integer teacherStatus;
@ApiModelProperty(value = "邀请码(二维码地址标识)")
private String invitationCode;
}

View File

@ -7,6 +7,7 @@ import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigDecimal;
import java.util.Date;
@Data
@ApiModel("学生信息列表Vo")
@ -71,6 +72,18 @@ public class StudentInfoListedVo implements Serializable {
@ApiModelProperty(value = "学业预警: [0=无, 1=一级预警, 2=二级预警, 3=三级预警]")
private Integer academicWarnings;
@ApiModelProperty(value = "中考成绩")
private BigDecimal highSchoolScore;
@ApiModelProperty(value = "预报名金额")
private BigDecimal preRegistrationAmount;
@ApiModelProperty(value = "招生老师ID")
private Integer recruitmentTeacherId;
@ApiModelProperty(value = "接待老师ID")
private Integer receptionTeacherId;
@ApiModelProperty(value = "是否认证: [0=未认证, 1=已认证]")
private Integer isVerified;

View File

@ -12,15 +12,12 @@ public class TeacherDetailVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "")
private Integer id;
@ApiModelProperty(value = "教师ID")
private Integer teacherId;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "关联管理员ID")
private Integer adminId;
@ApiModelProperty(value = "教师工号")
private String teacherCode;
@ -42,5 +39,10 @@ public class TeacherDetailVo implements Serializable {
@ApiModelProperty(value = "教师状态1-在职2-休假3-离职")
private Integer teacherStatus;
@ApiModelProperty(value = "邀请码(二维码地址标识)")
private String invitationCode;
@ApiModelProperty(value = "二维码图片地址")
private String qrcodeUrl;
}

View File

@ -12,15 +12,12 @@ public class TeacherListedVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "")
private Integer id;
@ApiModelProperty(value = "教师ID")
private Integer teacherId;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "关联管理员ID")
private Integer adminId;
@ApiModelProperty(value = "学院名")
private String collegeName;
@ -42,6 +39,12 @@ public class TeacherListedVo implements Serializable {
@ApiModelProperty(value = "教师状态1-在职2-休假3-离职")
private Integer teacherStatus;
@ApiModelProperty(value = "邀请码(二维码地址标识)")
private String invitationCode;
@ApiModelProperty(value = "二维码图片地址")
private String qrcodeUrl;
@ApiModelProperty(value = "创建时间")
private String createTime;

View File

@ -15,13 +15,17 @@ public class Teacher implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value="id", type= IdType.AUTO)
@ApiModelProperty(value = "")
private Integer id;
@TableId(value="teacher_id", type= IdType.AUTO)
@ApiModelProperty(value = "教师ID")
private Integer teacherId;
@TableField("admin_id")
@ApiModelProperty(value = "关联管理员ID")
private Integer adminId;
@TableField("teacher_name")
@ApiModelProperty(value = "教师姓名")
private String teacherName;
@TableField("teacher_code")
@ApiModelProperty(value = "教师工号")
private String teacherCode;
@ApiModelProperty(value = "所属院系ID")
private Integer collegeId;
@ -35,6 +39,14 @@ public class Teacher implements Serializable {
@ApiModelProperty(value = "教师状态1-在职2-休假3-离职")
private Integer teacherStatus;
@TableField("invitation_code")
@ApiModelProperty(value = "邀请码(二维码地址标识)")
private String invitationCode;
@TableField("qrcode_url")
@ApiModelProperty(value = "二维码图片地址")
private String qrcodeUrl;
@ApiModelProperty(value = "创建时间")
private Long createTime;

View File

@ -0,0 +1,96 @@
package com.mdd.common.util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 二维码生成工具类
*/
public class QrCodeUtil {
/**
* 生成二维码图片并保存到文件
*
* @param content 二维码内容
* @param width 图片宽度
* @param height 图片高度
* @param filePath 保存文件路径
* @throws WriterException 写入异常
* @throws IOException IO异常
*/
public static void generateQrCodeToFile(String content, int width, int height, String filePath) throws WriterException, IOException {
BufferedImage image = generateQrCodeImage(content, width, height);
File file = new File(filePath);
// 确保父目录存在
File parentDir = file.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
}
ImageIO.write(image, "png", file);
}
/**
* 生成二维码图片BufferedImage
*
* @param content 二维码内容
* @param width 图片宽度
* @param height 图片高度
* @return BufferedImage
* @throws WriterException 写入异常
*/
public static BufferedImage generateQrCodeImage(String content, int width, int height) throws WriterException {
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.MARGIN, 1);
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
graphics.setColor(Color.BLACK);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
if (bitMatrix.get(x, y)) {
graphics.fillRect(x, y, 1, 1);
}
}
}
graphics.dispose();
return image;
}
/**
* 生成二维码图片字节数组
*
* @param content 二维码内容
* @param width 图片宽度
* @param height 图片高度
* @return 图片字节数组
* @throws WriterException 写入异常
* @throws IOException IO异常
*/
public static byte[] generateQrCodeBytes(String content, int width, int height) throws WriterException, IOException {
BufferedImage image = generateQrCodeImage(content, width, height);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, "png", outputStream);
return outputStream.toByteArray();
}
}