From 178a1ea507ca5fea746a9fbb060486bf51fa61d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A5=9E=E7=A0=81-=E6=96=B9=E6=99=93=E8=BE=89?= Date: Sat, 31 Jan 2026 23:31:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E6=89=80=E6=9C=89?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=BE=85=E5=AE=8C=E6=88=90=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=92=8C=E6=A8=A1=E5=9D=97=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 应用管理模块后端开发 - 创建pg_application、pg_app_api、pg_api_dict三张表 - 实现Application、AppApi、ApiDict实体类 - 实现ApplicationMapper及XML映射 - 实现IApplicationService及实现类 - 实现ApplicationController(7个API接口) - 应用编码生成:YY + 6位序号 - 密钥生成:32位随机字符串 - 接口授权保存(事务处理) ## 学生会员模块集成 - IStudentService新增5个方法: - isStudentInSchool:检查学生是否在指定学校 - updateStudentMember:更新学生会员关联 - unbindStudent:解绑学生 - countByMemberId:统计会员绑定学生数 - selectStudentVOsByMemberId:查询会员绑定学生列表 - StudentServiceImpl实现5个方法 - StudentMapper新增2个SQL查询 - MemberServiceImpl完成5个TODO: - 学生绑定校验(教师只能绑定本校学生) - 学生绑定更新 - 学生解绑 - 删除前检查(有学生不可删) - 获取绑定学生列表 ## 学生批量导入完善 - IRegionService新增getRegionIdByPath方法 - ISchoolService新增3个方法: - getSchoolIdByName:根据学校名称查询ID - getSchoolGradeId:根据年级名称查询ID - getSchoolClassId:根据班级名称查询ID - IMemberService新增getOrCreateMemberByPhone方法 - StudentImportListener完整实现: - 区域ID查询 - 学校ID查询 - 年级ID查询 - 班级ID查询 - 会员查询或创建 - 学生信息保存 - 性别和出生日期解析 ## 导入模板下载 - StudentController实现downloadTemplate方法 - 使用EasyExcel生成标准Excel模板 - 包含示例数据 --- .../controller/ApplicationController.java | 43 +-- .../domain/dto/ApplicationDTO.java | 40 ++- .../application/domain/entity/ApiDict.java | 34 +- .../application/domain/entity/AppApi.java | 31 +- .../domain/entity/Application.java | 16 +- .../application/domain/vo/ApplicationVO.java | 47 ++- .../application/mapper/ApiDictMapper.java | 16 +- .../application/mapper/AppApiMapper.java | 25 +- .../application/mapper/ApplicationMapper.java | 40 ++- .../service/IApplicationService.java | 59 ++-- .../service/impl/ApplicationServiceImpl.java | 310 ++++++++---------- .../pangu/base/service/IRegionService.java | 7 + .../base/service/impl/RegionServiceImpl.java | 25 ++ .../pangu/member/service/IMemberService.java | 7 + .../service/impl/MemberServiceImpl.java | 59 +++- .../pangu/school/service/ISchoolService.java | 24 ++ .../service/impl/SchoolServiceImpl.java | 31 ++ .../student/controller/StudentController.java | 34 +- .../listener/StudentImportListener.java | 107 +++++- .../pangu/student/mapper/StudentMapper.java | 16 + .../student/service/IStudentService.java | 42 +++ .../service/impl/StudentServiceImpl.java | 46 ++- .../mapper/application/AppApiMapper.xml | 21 ++ .../mapper/application/ApplicationMapper.xml | 108 ++++++ .../mapper/student/StudentMapper.xml | 43 +++ sql/pangu_application.sql | 100 +++--- 26 files changed, 939 insertions(+), 392 deletions(-) create mode 100644 pangu-system/src/main/resources/mapper/application/AppApiMapper.xml create mode 100644 pangu-system/src/main/resources/mapper/application/ApplicationMapper.xml diff --git a/pangu-system/src/main/java/com/pangu/application/controller/ApplicationController.java b/pangu-system/src/main/java/com/pangu/application/controller/ApplicationController.java index 59d7607..f910246 100644 --- a/pangu-system/src/main/java/com/pangu/application/controller/ApplicationController.java +++ b/pangu-system/src/main/java/com/pangu/application/controller/ApplicationController.java @@ -7,32 +7,32 @@ import com.pangu.application.service.IApplicationService; import com.pangu.common.core.controller.BaseController; import com.pangu.common.core.domain.AjaxResult; import com.pangu.common.core.page.TableDataInfo; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; import java.util.List; -import java.util.Map; /** - * 应用管理控制器 + * 应用管理Controller + * * @author pangu */ @RestController -@RequestMapping("/api/app") +@RequestMapping("/api/application") +@RequiredArgsConstructor +@PreAuthorize("hasRole('admin')") public class ApplicationController extends BaseController { - @Resource - private IApplicationService applicationService; + private final IApplicationService applicationService; /** * 查询应用列表 */ @GetMapping("/list") - public TableDataInfo list(ApplicationDTO dto) { - startPage(); - List list = applicationService.selectApplicationList(dto); - return getDataTable(list); + public TableDataInfo list(ApplicationDTO applicationDTO) { + return applicationService.selectApplicationList(applicationDTO); } /** @@ -40,15 +40,16 @@ public class ApplicationController extends BaseController { */ @GetMapping("/{appId}") public AjaxResult getInfo(@PathVariable Long appId) { - return success(applicationService.selectApplicationById(appId)); + ApplicationVO applicationVO = applicationService.getApplicationById(appId); + return success(applicationVO); } /** * 新增应用 */ @PostMapping - public AjaxResult add(@Validated @RequestBody ApplicationDTO dto) { - Map result = applicationService.insertApplication(dto); + public AjaxResult add(@Validated @RequestBody ApplicationDTO applicationDTO) { + ApplicationVO result = applicationService.insertApplication(applicationDTO); return success(result); } @@ -56,8 +57,8 @@ public class ApplicationController extends BaseController { * 修改应用 */ @PutMapping - public AjaxResult edit(@Validated @RequestBody ApplicationDTO dto) { - return toAjax(applicationService.updateApplication(dto)); + public AjaxResult edit(@Validated @RequestBody ApplicationDTO applicationDTO) { + return toAjax(applicationService.updateApplication(applicationDTO)); } /** @@ -65,7 +66,7 @@ public class ApplicationController extends BaseController { */ @DeleteMapping("/{appId}") public AjaxResult remove(@PathVariable Long appId) { - return toAjax(applicationService.deleteApplicationById(appId)); + return toAjax(applicationService.deleteApplication(appId)); } /** @@ -73,16 +74,16 @@ public class ApplicationController extends BaseController { */ @PutMapping("/resetSecret/{appId}") public AjaxResult resetSecret(@PathVariable Long appId) { - String newSecret = applicationService.resetAppSecret(appId); - return success(Map.of("appSecret", newSecret)); + String newSecret = applicationService.resetSecret(appId); + return success(newSecret); } /** - * 获取API接口列表(用于授权选择) + * 获取API接口列表 */ @GetMapping("/apiList") - public AjaxResult apiList() { - List list = applicationService.selectApiDictList(); + public AjaxResult getApiList() { + List list = applicationService.getApiList(); return success(list); } } diff --git a/pangu-system/src/main/java/com/pangu/application/domain/dto/ApplicationDTO.java b/pangu-system/src/main/java/com/pangu/application/domain/dto/ApplicationDTO.java index 5a97233..0f3621b 100644 --- a/pangu-system/src/main/java/com/pangu/application/domain/dto/ApplicationDTO.java +++ b/pangu-system/src/main/java/com/pangu/application/domain/dto/ApplicationDTO.java @@ -3,51 +3,57 @@ package com.pangu.application.domain.dto; import lombok.Data; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Size; import java.io.Serializable; import java.util.List; /** - * 应用传输对象 + * 应用数据传输对象 + * * @author pangu */ @Data public class ApplicationDTO implements Serializable { - + private static final long serialVersionUID = 1L; - /** 应用ID(编辑时必填) */ + /** 应用ID */ private Long appId; + /** 应用编码 */ + private String appCode; + /** 应用名称 */ @NotBlank(message = "应用名称不能为空") - @Size(max = 100, message = "应用名称不能超过100个字符") private String appName; - /** 应用编码(查询条件) */ - private String appCode; - - /** 应用描述 */ - @Size(max = 500, message = "应用描述不能超过500个字符") - private String appDesc; + /** 应用密钥 */ + private String appSecret; /** 联系人 */ - @Size(max = 50, message = "联系人不能超过50个字符") private String contactPerson; /** 联系电话 */ - @Size(max = 20, message = "联系电话不能超过20个字符") private String contactPhone; /** 状态(0正常 1停用) */ private String status; - /** 授权的接口编码列表 */ - private List apiCodes; + /** 备注 */ + private String remark; - /** 分页页码 */ + /** 授权接口ID列表 */ + private List apiIds; + + // ========== 查询条件 ========== + /** 开始时间(查询条件) */ + private String beginTime; + + /** 结束时间(查询条件) */ + private String endTime; + + /** 页码 */ private Integer pageNum; - /** 分页大小 */ + /** 每页数量 */ private Integer pageSize; } diff --git a/pangu-system/src/main/java/com/pangu/application/domain/entity/ApiDict.java b/pangu-system/src/main/java/com/pangu/application/domain/entity/ApiDict.java index 6e985f4..b9786b2 100644 --- a/pangu-system/src/main/java/com/pangu/application/domain/entity/ApiDict.java +++ b/pangu-system/src/main/java/com/pangu/application/domain/entity/ApiDict.java @@ -1,22 +1,20 @@ package com.pangu.application.domain.entity; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.io.Serializable; -import java.util.Date; +import java.time.LocalDateTime; /** - * API接口字典实体 + * API接口字典实体类 + * * @author pangu */ @Data @TableName("pg_api_dict") public class ApiDict implements Serializable { - + private static final long serialVersionUID = 1L; /** 接口ID */ @@ -35,26 +33,16 @@ public class ApiDict implements Serializable { /** 请求方法 */ private String apiMethod; - /** 接口描述 */ - private String apiDesc; - - /** 显示顺序 */ - private Integer orderNum; + /** 排序 */ + private Integer sortOrder; /** 状态(0正常 1停用) */ private String status; - /** 创建者 */ - private String createBy; - /** 创建时间 */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date createTime; + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; - /** 更新者 */ - private String updateBy; - - /** 更新时间 */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date updateTime; + /** 备注 */ + private String remark; } diff --git a/pangu-system/src/main/java/com/pangu/application/domain/entity/AppApi.java b/pangu-system/src/main/java/com/pangu/application/domain/entity/AppApi.java index 55a499a..66bb718 100644 --- a/pangu-system/src/main/java/com/pangu/application/domain/entity/AppApi.java +++ b/pangu-system/src/main/java/com/pangu/application/domain/entity/AppApi.java @@ -1,22 +1,20 @@ package com.pangu.application.domain.entity; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.fasterxml.jackson.annotation.JsonFormat; +import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.io.Serializable; -import java.util.Date; +import java.time.LocalDateTime; /** - * 应用接口授权实体 + * 应用接口授权实体类 + * * @author pangu */ @Data -@TableName("pg_app_grant") +@TableName("pg_app_api") public class AppApi implements Serializable { - + private static final long serialVersionUID = 1L; /** 主键ID */ @@ -26,19 +24,10 @@ public class AppApi implements Serializable { /** 应用ID */ private Long appId; - /** 接口编码 */ - private String apiCode; - - /** 接口名称 */ - private String apiName; - - /** 接口路径 */ - private String apiPath; - - /** 创建者 */ - private String createBy; + /** 接口ID */ + private Long apiId; /** 创建时间 */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date createTime; + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; } diff --git a/pangu-system/src/main/java/com/pangu/application/domain/entity/Application.java b/pangu-system/src/main/java/com/pangu/application/domain/entity/Application.java index 53af959..b17a6a4 100644 --- a/pangu-system/src/main/java/com/pangu/application/domain/entity/Application.java +++ b/pangu-system/src/main/java/com/pangu/application/domain/entity/Application.java @@ -1,39 +1,35 @@ package com.pangu.application.domain.entity; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.*; import com.pangu.common.core.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; /** - * 应用实体 + * 应用实体类 + * * @author pangu */ @Data @EqualsAndHashCode(callSuper = true) @TableName("pg_application") public class Application extends BaseEntity { - + private static final long serialVersionUID = 1L; /** 应用ID */ @TableId(type = IdType.AUTO) private Long appId; - /** 应用编码(格式:YY + 6位序号) */ + /** 应用编码 */ private String appCode; /** 应用名称 */ private String appName; - /** 应用密钥(32位随机字符串) */ + /** 应用密钥 */ private String appSecret; - /** 应用描述 */ - private String appDesc; - /** 联系人 */ private String contactPerson; diff --git a/pangu-system/src/main/java/com/pangu/application/domain/vo/ApplicationVO.java b/pangu-system/src/main/java/com/pangu/application/domain/vo/ApplicationVO.java index 711d3c0..9a4ba16 100644 --- a/pangu-system/src/main/java/com/pangu/application/domain/vo/ApplicationVO.java +++ b/pangu-system/src/main/java/com/pangu/application/domain/vo/ApplicationVO.java @@ -4,41 +4,70 @@ import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.io.Serializable; -import java.util.Date; +import java.time.LocalDateTime; import java.util.List; /** * 应用视图对象 + * * @author pangu */ @Data public class ApplicationVO implements Serializable { - + private static final long serialVersionUID = 1L; + /** 应用ID */ private Long appId; + + /** 应用编码 */ private String appCode; + + /** 应用名称 */ private String appName; - private String appDesc; + + /** 应用密钥 */ + private String appSecret; + + /** 联系人 */ private String contactPerson; + + /** 联系电话 */ private String contactPhone; + + /** 状态(0正常 1停用) */ private String status; - private String createBy; + /** 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date createTime; + private LocalDateTime createTime; - /** 授权的接口列表 */ - private List apis; + /** 备注 */ + private String remark; + + /** 授权接口列表 */ + private List apiList; /** - * 接口信息 + * API视图对象 */ @Data - public static class ApiInfo implements Serializable { + public static class ApiVO implements Serializable { private static final long serialVersionUID = 1L; + + /** 接口ID */ + private Long apiId; + + /** 接口编码 */ private String apiCode; + + /** 接口名称 */ private String apiName; + + /** 接口路径 */ private String apiPath; + + /** 请求方法 */ + private String apiMethod; } } diff --git a/pangu-system/src/main/java/com/pangu/application/mapper/ApiDictMapper.java b/pangu-system/src/main/java/com/pangu/application/mapper/ApiDictMapper.java index 891a1db..08defe1 100644 --- a/pangu-system/src/main/java/com/pangu/application/mapper/ApiDictMapper.java +++ b/pangu-system/src/main/java/com/pangu/application/mapper/ApiDictMapper.java @@ -3,26 +3,12 @@ package com.pangu.application.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pangu.application.domain.entity.ApiDict; import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; - -import java.util.List; /** * API接口字典Mapper接口 + * * @author pangu */ @Mapper public interface ApiDictMapper extends BaseMapper { - - /** - * 查询启用的接口列表 - */ - @Select("SELECT * FROM pg_api_dict WHERE status = '0' ORDER BY order_num") - List selectEnabledList(); - - /** - * 根据接口编码列表查询 - */ - List selectByCodes(@Param("codes") List codes); } diff --git a/pangu-system/src/main/java/com/pangu/application/mapper/AppApiMapper.java b/pangu-system/src/main/java/com/pangu/application/mapper/AppApiMapper.java index 02e2f5e..4bac40a 100644 --- a/pangu-system/src/main/java/com/pangu/application/mapper/AppApiMapper.java +++ b/pangu-system/src/main/java/com/pangu/application/mapper/AppApiMapper.java @@ -2,37 +2,32 @@ package com.pangu.application.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pangu.application.domain.entity.AppApi; -import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; import java.util.List; /** * 应用接口授权Mapper接口 + * * @author pangu */ @Mapper public interface AppApiMapper extends BaseMapper { /** - * 根据应用ID查询授权接口 + * 批量插入应用接口授权 + * + * @param list 授权列表 + * @return 影响行数 */ - @Select("SELECT * FROM pg_app_grant WHERE app_id = #{appId}") - List selectByAppId(@Param("appId") Long appId); + int batchInsert(@Param("list") List list); /** - * 根据应用ID删除授权接口 + * 根据应用ID删除授权 + * + * @param appId 应用ID + * @return 影响行数 */ - @Delete("DELETE FROM pg_app_grant WHERE app_id = #{appId}") int deleteByAppId(@Param("appId") Long appId); - - /** - * 根据应用编码查询授权的接口路径列表 - */ - @Select("SELECT aa.api_path FROM pg_app_grant aa " - + "INNER JOIN pg_application a ON aa.app_id = a.app_id " - + "WHERE a.app_code = #{appCode} AND a.del_flag = '0'") - List selectApiPathsByAppCode(@Param("appCode") String appCode); } diff --git a/pangu-system/src/main/java/com/pangu/application/mapper/ApplicationMapper.java b/pangu-system/src/main/java/com/pangu/application/mapper/ApplicationMapper.java index 531abf9..46937d5 100644 --- a/pangu-system/src/main/java/com/pangu/application/mapper/ApplicationMapper.java +++ b/pangu-system/src/main/java/com/pangu/application/mapper/ApplicationMapper.java @@ -1,33 +1,53 @@ package com.pangu.application.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.pangu.application.domain.dto.ApplicationDTO; import com.pangu.application.domain.entity.Application; +import com.pangu.application.domain.vo.ApplicationVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; + +import java.util.List; /** * 应用Mapper接口 + * * @author pangu */ @Mapper public interface ApplicationMapper extends BaseMapper { /** - * 查询最大应用编码 + * 查询应用列表 + * + * @param page 分页对象 + * @param dto 查询条件 + * @return 应用列表 */ - @Select("SELECT MAX(app_code) FROM pg_application WHERE del_flag = '0'") - String selectMaxAppCode(); + List selectApplicationVOList(Page page, @Param("dto") ApplicationDTO dto); /** - * 根据应用编码查询应用 + * 根据ID查询应用详情 + * + * @param appId 应用ID + * @return 应用详情 */ - @Select("SELECT * FROM pg_application WHERE app_code = #{appCode} AND del_flag = '0'") - Application selectByAppCode(@Param("appCode") String appCode); + ApplicationVO selectApplicationVOById(@Param("appId") Long appId); /** - * 检查应用名称是否存在(排除指定ID) + * 检查应用编码是否唯一 + * + * @param appCode 应用编码 + * @param appId 应用ID(编辑时排除自己) + * @return 数量 */ - @Select("SELECT COUNT(*) FROM pg_application WHERE app_name = #{appName} AND del_flag = '0' AND ( #{excludeId} IS NULL OR app_id != #{excludeId} )") - int checkAppNameExists(@Param("appName") String appName, @Param("excludeId") Long excludeId); + int countByAppCode(@Param("appCode") String appCode, @Param("appId") Long appId); + + /** + * 获取下一个应用编码序号 + * + * @return 序号 + */ + int getNextCodeSeq(); } diff --git a/pangu-system/src/main/java/com/pangu/application/service/IApplicationService.java b/pangu-system/src/main/java/com/pangu/application/service/IApplicationService.java index 916b27a..00674e4 100644 --- a/pangu-system/src/main/java/com/pangu/application/service/IApplicationService.java +++ b/pangu-system/src/main/java/com/pangu/application/service/IApplicationService.java @@ -1,63 +1,82 @@ package com.pangu.application.service; +import com.baomidou.mybatisplus.extension.service.IService; import com.pangu.application.domain.dto.ApplicationDTO; import com.pangu.application.domain.entity.ApiDict; import com.pangu.application.domain.entity.Application; import com.pangu.application.domain.vo.ApplicationVO; +import com.pangu.common.core.page.TableDataInfo; import java.util.List; -import java.util.Map; /** - * 应用管理服务接口 + * 应用服务接口 + * * @author pangu */ -public interface IApplicationService { +public interface IApplicationService extends IService { /** * 查询应用列表 + * + * @param applicationDTO 查询条件 + * @return 应用列表 */ - List selectApplicationList(ApplicationDTO dto); + TableDataInfo selectApplicationList(ApplicationDTO applicationDTO); /** - * 查询应用详情 + * 根据ID查询应用详情 + * + * @param appId 应用ID + * @return 应用详情 */ - ApplicationVO selectApplicationById(Long appId); + ApplicationVO getApplicationById(Long appId); /** * 新增应用 - * @return 包含 appCode 和 appSecret 的 Map + * + * @param applicationDTO 应用信息 + * @return 结果(包含新生成的密钥) */ - Map insertApplication(ApplicationDTO dto); + ApplicationVO insertApplication(ApplicationDTO applicationDTO); /** * 修改应用 + * + * @param applicationDTO 应用信息 + * @return 结果 */ - int updateApplication(ApplicationDTO dto); + int updateApplication(ApplicationDTO applicationDTO); /** * 删除应用 + * + * @param appId 应用ID + * @return 结果 */ - int deleteApplicationById(Long appId); + int deleteApplication(Long appId); /** * 重置应用密钥 + * + * @param appId 应用ID * @return 新密钥 */ - String resetAppSecret(Long appId); + String resetSecret(Long appId); /** - * 获取API接口字典列表(用于授权选择) + * 获取API接口列表 + * + * @return API接口列表 */ - List selectApiDictList(); + List getApiList(); /** - * 根据应用编码查询应用(用于开放API认证) + * 检查应用编码是否唯一 + * + * @param appCode 应用编码 + * @param appId 应用ID + * @return true唯一 false不唯一 */ - Application selectByAppCode(String appCode); - - /** - * 检查应用是否有接口权限 - */ - boolean checkApiPermission(String appCode, String apiPath); + boolean checkAppCodeUnique(String appCode, Long appId); } diff --git a/pangu-system/src/main/java/com/pangu/application/service/impl/ApplicationServiceImpl.java b/pangu-system/src/main/java/com/pangu/application/service/impl/ApplicationServiceImpl.java index 56cca60..8e6330d 100644 --- a/pangu-system/src/main/java/com/pangu/application/service/impl/ApplicationServiceImpl.java +++ b/pangu-system/src/main/java/com/pangu/application/service/impl/ApplicationServiceImpl.java @@ -1,8 +1,10 @@ package com.pangu.application.service.impl; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pangu.application.domain.dto.ApplicationDTO; import com.pangu.application.domain.entity.ApiDict; import com.pangu.application.domain.entity.AppApi; @@ -13,222 +15,198 @@ import com.pangu.application.mapper.AppApiMapper; import com.pangu.application.mapper.ApplicationMapper; import com.pangu.application.service.IApplicationService; import com.pangu.common.core.exception.ServiceException; -import com.pangu.common.utils.DateUtils; +import com.pangu.common.core.page.TableDataInfo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; +import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; /** - * 应用管理服务实现 + * 应用服务实现 + * * @author pangu */ +@Slf4j @Service -public class ApplicationServiceImpl implements IApplicationService { +@RequiredArgsConstructor +public class ApplicationServiceImpl extends ServiceImpl implements IApplicationService { - @Resource - private ApplicationMapper applicationMapper; - @Resource - private AppApiMapper appApiMapper; - @Resource - private ApiDictMapper apiDictMapper; + private final ApplicationMapper applicationMapper; + private final AppApiMapper appApiMapper; + private final ApiDictMapper apiDictMapper; @Override - public List selectApplicationList(ApplicationDTO dto) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - if (dto != null && dto.getAppName() != null && !dto.getAppName().isEmpty()) { - wrapper.like(Application::getAppName, dto.getAppName()); - } - if (dto != null && dto.getAppCode() != null && !dto.getAppCode().isEmpty()) { - wrapper.like(Application::getAppCode, dto.getAppCode()); - } - if (dto != null && dto.getStatus() != null && !dto.getStatus().isEmpty()) { - wrapper.eq(Application::getStatus, dto.getStatus()); - } - wrapper.orderByDesc(Application::getCreateTime); - List list = applicationMapper.selectList(wrapper); - List result = new ArrayList<>(); - for (Application app : list) { - result.add(toVO(app)); - } - return result; + public TableDataInfo selectApplicationList(ApplicationDTO applicationDTO) { + Page page = new Page<>( + applicationDTO.getPageNum() != null ? applicationDTO.getPageNum() : 1, + applicationDTO.getPageSize() != null ? applicationDTO.getPageSize() : 10 + ); + List list = applicationMapper.selectApplicationVOList(page, applicationDTO); + return new TableDataInfo(list, page.getTotal()); } @Override - public ApplicationVO selectApplicationById(Long appId) { - Application app = applicationMapper.selectById(appId); - if (app == null) { - throw new ServiceException("应用不存在"); - } - return toVO(app); + public ApplicationVO getApplicationById(Long appId) { + return applicationMapper.selectApplicationVOById(appId); } @Override @Transactional(rollbackFor = Exception.class) - public Map insertApplication(ApplicationDTO dto) { - if (applicationMapper.checkAppNameExists(dto.getAppName(), null) > 0) { - throw new ServiceException("应用名称已存在"); - } + public ApplicationVO insertApplication(ApplicationDTO applicationDTO) { + // 生成应用编码 String appCode = generateAppCode(); + + // 生成应用密钥 String appSecret = generateAppSecret(); - Application app = new Application(); - app.setAppCode(appCode); - app.setAppName(dto.getAppName()); - app.setAppSecret(appSecret); - app.setAppDesc(dto.getAppDesc()); - app.setContactPerson(dto.getContactPerson()); - app.setContactPhone(dto.getContactPhone()); - app.setStatus(dto.getStatus() != null ? dto.getStatus() : "0"); - app.setCreateBy("admin"); - app.setCreateTime(DateUtils.getNowDate()); - app.setDelFlag("0"); - applicationMapper.insert(app); - saveAppApis(app.getAppId(), dto.getApiCodes()); - Map result = new HashMap<>(); - result.put("appCode", appCode); - result.put("appSecret", appSecret); + + // 构建应用对象 + Application application = buildApplication(applicationDTO); + application.setAppCode(appCode); + application.setAppSecret(appSecret); + application.setStatus("0"); + + // 保存应用 + int result = applicationMapper.insert(application); + if (result <= 0) { + throw new ServiceException("新增应用失败"); + } + + // 保存接口授权 + saveAppApis(application.getAppId(), applicationDTO.getApiIds()); + + log.info("新增应用成功, appId={}, appCode={}", application.getAppId(), appCode); + + // 返回应用信息(包含密钥) + ApplicationVO vo = new ApplicationVO(); + vo.setAppId(application.getAppId()); + vo.setAppCode(appCode); + vo.setAppName(application.getAppName()); + vo.setAppSecret(appSecret); + return vo; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int updateApplication(ApplicationDTO applicationDTO) { + // 构建应用对象 + Application application = buildApplication(applicationDTO); + application.setAppId(applicationDTO.getAppId()); + + // 更新应用 + int result = applicationMapper.updateById(application); + if (result <= 0) { + throw new ServiceException("修改应用失败"); + } + + // 更新接口授权 + appApiMapper.deleteByAppId(application.getAppId()); + saveAppApis(application.getAppId(), applicationDTO.getApiIds()); + + log.info("修改应用成功, appId={}, appName={}", application.getAppId(), application.getAppName()); return result; } @Override @Transactional(rollbackFor = Exception.class) - public int updateApplication(ApplicationDTO dto) { - if (dto.getAppId() == null) { - throw new ServiceException("应用ID不能为空"); - } - Application existApp = applicationMapper.selectById(dto.getAppId()); - if (existApp == null) { - throw new ServiceException("应用不存在"); - } - if (applicationMapper.checkAppNameExists(dto.getAppName(), dto.getAppId()) > 0) { - throw new ServiceException("应用名称已存在"); - } - Application app = new Application(); - app.setAppId(dto.getAppId()); - app.setAppName(dto.getAppName()); - app.setAppDesc(dto.getAppDesc()); - app.setContactPerson(dto.getContactPerson()); - app.setContactPhone(dto.getContactPhone()); - app.setStatus(dto.getStatus()); - app.setUpdateBy("admin"); - app.setUpdateTime(DateUtils.getNowDate()); - int rows = applicationMapper.updateById(app); - appApiMapper.deleteByAppId(dto.getAppId()); - saveAppApis(dto.getAppId(), dto.getApiCodes()); - return rows; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public int deleteApplicationById(Long appId) { - Application app = applicationMapper.selectById(appId); - if (app == null) { - throw new ServiceException("应用不存在"); - } - int rows = applicationMapper.deleteById(appId); + public int deleteApplication(Long appId) { + // 软删除应用 + Application application = new Application(); + application.setAppId(appId); + application.setDelFlag("1"); + int result = applicationMapper.updateById(application); + + // 删除接口授权 appApiMapper.deleteByAppId(appId); - return rows; + + log.info("删除应用成功, appId={}", appId); + return result; } @Override @Transactional(rollbackFor = Exception.class) - public String resetAppSecret(Long appId) { - Application app = applicationMapper.selectById(appId); - if (app == null) { - throw new ServiceException("应用不存在"); - } + public String resetSecret(Long appId) { + // 生成新密钥 String newSecret = generateAppSecret(); - Application updateApp = new Application(); - updateApp.setAppId(appId); - updateApp.setAppSecret(newSecret); - updateApp.setUpdateBy("admin"); - updateApp.setUpdateTime(DateUtils.getNowDate()); - applicationMapper.updateById(updateApp); + + // 更新密钥 + Application application = new Application(); + application.setAppId(appId); + application.setAppSecret(newSecret); + int result = applicationMapper.updateById(application); + + if (result <= 0) { + throw new ServiceException("重置密钥失败"); + } + + log.info("重置应用密钥成功, appId={}", appId); return newSecret; } @Override - public List selectApiDictList() { - return apiDictMapper.selectEnabledList(); + public List getApiList() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ApiDict::getStatus, "0"); + wrapper.orderByAsc(ApiDict::getSortOrder); + return apiDictMapper.selectList(wrapper); } @Override - public Application selectByAppCode(String appCode) { - return applicationMapper.selectByAppCode(appCode); - } - - @Override - public boolean checkApiPermission(String appCode, String apiPath) { - List paths = appApiMapper.selectApiPathsByAppCode(appCode); - return paths != null && paths.contains(apiPath); + public boolean checkAppCodeUnique(String appCode, Long appId) { + int count = applicationMapper.countByAppCode(appCode, appId); + return count == 0; } + /** + * 生成应用编码 + * 格式:YY + 6位序号 + */ private String generateAppCode() { - String maxCode = applicationMapper.selectMaxAppCode(); - int nextSeq = 1; - if (maxCode != null && maxCode.length() > 2) { - try { - nextSeq = Integer.parseInt(maxCode.substring(2)) + 1; - } catch (NumberFormatException ignored) { - } - } - return String.format("YY%06d", nextSeq); + int seq = applicationMapper.getNextCodeSeq(); + return String.format("YY%06d", seq); } + /** + * 生成应用密钥 + * 32位随机字符串 + */ private String generateAppSecret() { return RandomUtil.randomString(32); } - private void saveAppApis(Long appId, List apiCodes) { - if (CollUtil.isEmpty(apiCodes)) { - return; - } - List apiDicts = apiDictMapper.selectByCodes(apiCodes); - Map dictMap = apiDicts.stream().collect(Collectors.toMap(ApiDict::getApiCode, d -> d)); - String username = "admin"; - java.util.Date now = DateUtils.getNowDate(); - for (String apiCode : apiCodes) { - ApiDict dict = dictMap.get(apiCode); - if (dict != null) { - AppApi appApi = new AppApi(); - appApi.setAppId(appId); - appApi.setApiCode(apiCode); - appApi.setApiName(dict.getApiName()); - appApi.setApiPath(dict.getApiPath()); - appApi.setCreateBy(username); - appApi.setCreateTime(now); - appApiMapper.insert(appApi); - } - } + /** + * 构建应用对象 + */ + private Application buildApplication(ApplicationDTO dto) { + Application application = new Application(); + application.setAppName(dto.getAppName()); + application.setContactPerson(dto.getContactPerson()); + application.setContactPhone(dto.getContactPhone()); + application.setRemark(dto.getRemark()); + return application; } - private ApplicationVO toVO(Application app) { - ApplicationVO vo = new ApplicationVO(); - vo.setAppId(app.getAppId()); - vo.setAppCode(app.getAppCode()); - vo.setAppName(app.getAppName()); - vo.setAppDesc(app.getAppDesc()); - vo.setContactPerson(app.getContactPerson()); - vo.setContactPhone(app.getContactPhone()); - vo.setStatus(app.getStatus()); - vo.setCreateBy(app.getCreateBy()); - vo.setCreateTime(app.getCreateTime()); - List appApis = appApiMapper.selectByAppId(app.getAppId()); - if (CollUtil.isNotEmpty(appApis)) { - List apis = appApis.stream().map(api -> { - ApplicationVO.ApiInfo info = new ApplicationVO.ApiInfo(); - info.setApiCode(api.getApiCode()); - info.setApiName(api.getApiName()); - info.setApiPath(api.getApiPath()); - return info; - }).collect(Collectors.toList()); - vo.setApis(apis); + /** + * 保存接口授权 + */ + private void saveAppApis(Long appId, List apiIds) { + if (apiIds == null || apiIds.isEmpty()) { + return; } - return vo; + + List list = new ArrayList<>(); + LocalDateTime now = LocalDateTime.now(); + for (Long apiId : apiIds) { + AppApi appApi = new AppApi(); + appApi.setAppId(appId); + appApi.setApiId(apiId); + appApi.setCreateTime(now); + list.add(appApi); + } + + appApiMapper.batchInsert(list); } } diff --git a/pangu-system/src/main/java/com/pangu/base/service/IRegionService.java b/pangu-system/src/main/java/com/pangu/base/service/IRegionService.java index b685fa4..b064e1e 100644 --- a/pangu-system/src/main/java/com/pangu/base/service/IRegionService.java +++ b/pangu-system/src/main/java/com/pangu/base/service/IRegionService.java @@ -44,4 +44,11 @@ public interface IRegionService { * 是否存在子区域 */ boolean hasChildRegion(Long regionId); + + /** + * 根据区域路径查询区域ID + * @param regionPath 区域路径(如:湖北省-武汉市-武昌区) + * @return 区域ID + */ + Long getRegionIdByPath(String regionPath); } diff --git a/pangu-system/src/main/java/com/pangu/base/service/impl/RegionServiceImpl.java b/pangu-system/src/main/java/com/pangu/base/service/impl/RegionServiceImpl.java index 051b171..c414ef5 100644 --- a/pangu-system/src/main/java/com/pangu/base/service/impl/RegionServiceImpl.java +++ b/pangu-system/src/main/java/com/pangu/base/service/impl/RegionServiceImpl.java @@ -127,4 +127,29 @@ public class RegionServiceImpl implements IRegionService { int count = regionMapper.countChildByParentId(regionId); return count > 0; } + + @Override + public Long getRegionIdByPath(String regionPath) { + if (StringUtils.isEmpty(regionPath)) { + return null; + } + + // 解析区域路径:湖北省-武汉市-武昌区 + String[] parts = regionPath.split("-"); + if (parts.length == 0) { + return null; + } + + // 从最后一级开始查询(区 -> 市 -> 省) + String regionName = parts[parts.length - 1]; + List regions = regionMapper.selectRegionList(new Region()); + + for (Region region : regions) { + if (regionName.equals(region.getRegionName())) { + return region.getRegionId(); + } + } + + return null; + } } diff --git a/pangu-system/src/main/java/com/pangu/member/service/IMemberService.java b/pangu-system/src/main/java/com/pangu/member/service/IMemberService.java index 10cd7d3..0c78761 100644 --- a/pangu-system/src/main/java/com/pangu/member/service/IMemberService.java +++ b/pangu-system/src/main/java/com/pangu/member/service/IMemberService.java @@ -106,4 +106,11 @@ public interface IMemberService extends IService { * @return 会员信息 */ Member getMemberByOpenId(String openId); + + /** + * 根据手机号查询或创建会员 + * @param phone 手机号 + * @return 会员ID + */ + Long getOrCreateMemberByPhone(String phone); } diff --git a/pangu-system/src/main/java/com/pangu/member/service/impl/MemberServiceImpl.java b/pangu-system/src/main/java/com/pangu/member/service/impl/MemberServiceImpl.java index 6f3a759..ea6ceef 100644 --- a/pangu-system/src/main/java/com/pangu/member/service/impl/MemberServiceImpl.java +++ b/pangu-system/src/main/java/com/pangu/member/service/impl/MemberServiceImpl.java @@ -34,6 +34,7 @@ public class MemberServiceImpl extends ServiceImpl impleme private final MemberMapper memberMapper; private final BCryptPasswordEncoder passwordEncoder; + private final com.pangu.student.service.IStudentService studentService; /** 默认密码 */ private static final String DEFAULT_PASSWORD = "123456"; @@ -68,8 +69,9 @@ public class MemberServiceImpl extends ServiceImpl impleme // 填充名称 fillMemberVONames(memberVO); - // 查询绑定的学生(暂时返回空列表,等学生模块完成后再实现) - memberVO.setStudents(new ArrayList<>()); + // 查询绑定的学生 + List students = studentService.selectStudentVOsByMemberId(memberId); + memberVO.setStudents(students); return memberVO; } @@ -194,24 +196,26 @@ public class MemberServiceImpl extends ServiceImpl impleme throw new ServiceException("会员不存在"); } - // TODO: 教师只能绑定本校学生(等学生模块完成后实现) - // if (IdentityTypeEnum.isTeacher(member.getIdentityType())) { - // if (!studentService.isStudentInSchool(studentId, member.getSchoolId())) { - // throw new ServiceException("教师只能绑定本校学生"); - // } - // } + // 教师只能绑定本校学生 + if (IdentityTypeEnum.isTeacher(member.getIdentityType())) { + if (!studentService.isStudentInSchool(studentId, member.getSchoolId())) { + throw new ServiceException("教师只能绑定本校学生"); + } + } - // TODO: 调用学生模块的接口更新学生的memberId(等学生模块完成后实现) + // 更新学生的memberId + int result = studentService.updateStudentMember(studentId, memberId); log.info("绑定学生成功, memberId={}, studentId={}", memberId, studentId); - return 1; + return result; } @Override @Transactional(rollbackFor = Exception.class) public int unbindStudent(Long memberId, Long studentId) { - // TODO: 调用学生模块的接口解绑(等学生模块完成后实现) + // 解绑学生 + int result = studentService.unbindStudent(studentId); log.info("解绑学生成功, memberId={}, studentId={}", memberId, studentId); - return 1; + return result; } @Override @@ -221,9 +225,8 @@ public class MemberServiceImpl extends ServiceImpl impleme @Override public boolean checkCanDelete(Long memberId) { - // TODO: 检查是否有绑定的学生(等学生模块完成后实现) - // return studentService.countByMemberId(memberId) == 0; - return true; + // 检查是否有绑定的学生 + return studentService.countByMemberId(memberId) == 0; } @Override @@ -312,4 +315,30 @@ public class MemberServiceImpl extends ServiceImpl impleme default -> "未知"; }; } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long getOrCreateMemberByPhone(String phone) { + // 先查询是否存在 + Member existMember = getMemberByPhone(phone); + if (existMember != null) { + return existMember.getMemberId(); + } + + // 不存在则创建 + Member newMember = new Member(); + newMember.setMemberCode(generateMemberCode()); + newMember.setPhone(phone); + newMember.setNickname(generateNickname(phone)); + newMember.setPassword(passwordEncoder.encode(DEFAULT_PASSWORD)); + newMember.setIdentityType(IdentityTypeEnum.PARENT.getCode()); + newMember.setRegisterSource(RegisterSourceEnum.BACKEND.getCode()); + newMember.setStatus("0"); + newMember.setCreateTime(LocalDateTime.now()); + + memberMapper.insert(newMember); + log.info("批量导入自动创建会员, memberId={}, phone={}", newMember.getMemberId(), phone); + + return newMember.getMemberId(); + } } diff --git a/pangu-system/src/main/java/com/pangu/school/service/ISchoolService.java b/pangu-system/src/main/java/com/pangu/school/service/ISchoolService.java index 915091f..356f893 100644 --- a/pangu-system/src/main/java/com/pangu/school/service/ISchoolService.java +++ b/pangu-system/src/main/java/com/pangu/school/service/ISchoolService.java @@ -85,4 +85,28 @@ public interface ISchoolService { * @return 影响行数 */ int deleteSchoolClass(Long schoolClassId); + + /** + * 根据学校名称和区域ID查询学校ID + * @param schoolName 学校名称 + * @param regionId 区域ID + * @return 学校ID + */ + Long getSchoolIdByName(String schoolName, Long regionId); + + /** + * 根据学校ID和年级名称查询学校年级ID + * @param schoolId 学校ID + * @param gradeName 年级名称 + * @return 学校年级关联ID + */ + Long getSchoolGradeId(Long schoolId, String gradeName); + + /** + * 根据学校年级ID和班级名称查询学校班级ID + * @param schoolGradeId 学校年级ID + * @param className 班级名称 + * @return 学校班级关联ID + */ + Long getSchoolClassId(Long schoolGradeId, String className); } diff --git a/pangu-system/src/main/java/com/pangu/school/service/impl/SchoolServiceImpl.java b/pangu-system/src/main/java/com/pangu/school/service/impl/SchoolServiceImpl.java index e305c0e..1cda526 100644 --- a/pangu-system/src/main/java/com/pangu/school/service/impl/SchoolServiceImpl.java +++ b/pangu-system/src/main/java/com/pangu/school/service/impl/SchoolServiceImpl.java @@ -313,4 +313,35 @@ public class SchoolServiceImpl implements ISchoolService { return vo; }).collect(Collectors.toList()); } + + @Override + public Long getSchoolIdByName(String schoolName, Long regionId) { + SchoolQueryDTO query = new SchoolQueryDTO(); + query.setSchoolName(schoolName); + query.setRegionId(regionId); + List schools = schoolMapper.selectSchoolList(query); + return schools.isEmpty() ? null : schools.get(0).getSchoolId(); + } + + @Override + public Long getSchoolGradeId(Long schoolId, String gradeName) { + List grades = schoolGradeMapper.selectBySchoolIds(Collections.singletonList(schoolId)); + for (SchoolGrade grade : grades) { + if (gradeName.equals(grade.getGradeName())) { + return grade.getId(); + } + } + return null; + } + + @Override + public Long getSchoolClassId(Long schoolGradeId, String className) { + List classes = schoolClassMapper.selectBySchoolGradeIds(Collections.singletonList(schoolGradeId)); + for (SchoolClass clazz : classes) { + if (className.equals(clazz.getClassName())) { + return clazz.getId(); + } + } + return null; + } } diff --git a/pangu-system/src/main/java/com/pangu/student/controller/StudentController.java b/pangu-system/src/main/java/com/pangu/student/controller/StudentController.java index 1746cfe..88bbe20 100644 --- a/pangu-system/src/main/java/com/pangu/student/controller/StudentController.java +++ b/pangu-system/src/main/java/com/pangu/student/controller/StudentController.java @@ -1,9 +1,11 @@ package com.pangu.student.controller; +import com.alibaba.excel.EasyExcel; import com.pangu.common.core.controller.BaseController; import com.pangu.common.core.domain.AjaxResult; import com.pangu.common.core.page.TableDataInfo; import com.pangu.student.domain.dto.StudentDTO; +import com.pangu.student.domain.dto.StudentImportDTO; import com.pangu.student.domain.vo.ImportResultVO; import com.pangu.student.domain.vo.StudentVO; import com.pangu.student.service.IStudentService; @@ -12,6 +14,13 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + /** * 学生管理Controller * @@ -78,8 +87,29 @@ public class StudentController extends BaseController { * 下载导入模板 */ @GetMapping("/template") - public void downloadTemplate() { - // TODO: 实现模板下载 + public void downloadTemplate(HttpServletResponse response) throws IOException { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + String fileName = URLEncoder.encode("学生导入模板", StandardCharsets.UTF_8).replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); + + // 创建模板数据 + List templateData = new ArrayList<>(); + StudentImportDTO example = new StudentImportDTO(); + example.setStudentName("张小明"); + example.setStudentNo("STU20260001"); + example.setMemberPhone("13812345678"); + example.setRegionPath("湖北省-武汉市-武昌区"); + example.setSchoolName("武汉市第一中学"); + example.setGradeName("七年级"); + example.setClassName("1班"); + example.setGender("男"); + example.setBirthday("2015-03"); + templateData.add(example); + + EasyExcel.write(response.getOutputStream(), StudentImportDTO.class) + .sheet("学生信息") + .doWrite(templateData); } /** diff --git a/pangu-system/src/main/java/com/pangu/student/listener/StudentImportListener.java b/pangu-system/src/main/java/com/pangu/student/listener/StudentImportListener.java index feb29d6..db675d5 100644 --- a/pangu-system/src/main/java/com/pangu/student/listener/StudentImportListener.java +++ b/pangu-system/src/main/java/com/pangu/student/listener/StudentImportListener.java @@ -3,11 +3,18 @@ package com.pangu.student.listener; import cn.hutool.core.util.StrUtil; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; +import com.pangu.base.service.IRegionService; +import com.pangu.member.service.IMemberService; +import com.pangu.school.service.ISchoolService; +import com.pangu.student.domain.dto.StudentDTO; import com.pangu.student.domain.dto.StudentImportDTO; import com.pangu.student.domain.vo.ImportResultVO; import com.pangu.student.service.IStudentService; import lombok.extern.slf4j.Slf4j; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + /** * 学生导入监听器 * @@ -17,11 +24,20 @@ import lombok.extern.slf4j.Slf4j; public class StudentImportListener extends AnalysisEventListener { private final IStudentService studentService; + private final IRegionService regionService; + private final ISchoolService schoolService; + private final IMemberService memberService; private final ImportResultVO result = new ImportResultVO(); private int rowNum = 1; // 从第2行开始(第1行是表头) - public StudentImportListener(IStudentService studentService) { + public StudentImportListener(IStudentService studentService, + IRegionService regionService, + ISchoolService schoolService, + IMemberService memberService) { this.studentService = studentService; + this.regionService = regionService; + this.schoolService = schoolService; + this.memberService = memberService; result.setTotal(0); result.setSuccessCount(0); result.setFailCount(0); @@ -41,15 +57,60 @@ public class StudentImportListener extends AnalysisEventListener "1"; + case "女" -> "2"; + default -> "0"; + }; + } + + /** + * 解析出生年月 + */ + private LocalDate parseBirthday(String birthday) { + if (StrUtil.isBlank(birthday)) { + return null; + } + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM"); + return LocalDate.parse(birthday + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } catch (Exception e) { + log.warn("解析出生年月失败:{}", birthday); + return null; + } + } } diff --git a/pangu-system/src/main/java/com/pangu/student/mapper/StudentMapper.java b/pangu-system/src/main/java/com/pangu/student/mapper/StudentMapper.java index ec8645e..a594536 100644 --- a/pangu-system/src/main/java/com/pangu/student/mapper/StudentMapper.java +++ b/pangu-system/src/main/java/com/pangu/student/mapper/StudentMapper.java @@ -43,4 +43,20 @@ public interface StudentMapper extends BaseMapper { * @return 数量 */ int countByStudentNo(@Param("studentNo") String studentNo, @Param("studentId") Long studentId); + + /** + * 根据会员ID统计学生数量 + * + * @param memberId 会员ID + * @return 学生数量 + */ + int countByMemberId(@Param("memberId") Long memberId); + + /** + * 根据会员ID查询学生列表 + * + * @param memberId 会员ID + * @return 学生列表 + */ + List selectStudentVOsByMemberId(@Param("memberId") Long memberId); } diff --git a/pangu-system/src/main/java/com/pangu/student/service/IStudentService.java b/pangu-system/src/main/java/com/pangu/student/service/IStudentService.java index 094004c..f6886f9 100644 --- a/pangu-system/src/main/java/com/pangu/student/service/IStudentService.java +++ b/pangu-system/src/main/java/com/pangu/student/service/IStudentService.java @@ -71,4 +71,46 @@ public interface IStudentService extends IService { * @return true唯一 false不唯一 */ boolean checkStudentNoUnique(String studentNo, Long studentId); + + /** + * 检查学生是否在指定学校 + * + * @param studentId 学生ID + * @param schoolId 学校ID + * @return true在该学校 false不在 + */ + boolean isStudentInSchool(Long studentId, Long schoolId); + + /** + * 更新学生的会员ID + * + * @param studentId 学生ID + * @param memberId 会员ID + * @return 结果 + */ + int updateStudentMember(Long studentId, Long memberId); + + /** + * 解绑学生(清空会员ID) + * + * @param studentId 学生ID + * @return 结果 + */ + int unbindStudent(Long studentId); + + /** + * 统计会员绑定的学生数量 + * + * @param memberId 会员ID + * @return 学生数量 + */ + int countByMemberId(Long memberId); + + /** + * 根据会员ID查询学生列表 + * + * @param memberId 会员ID + * @return 学生列表 + */ + List selectStudentVOsByMemberId(Long memberId); } diff --git a/pangu-system/src/main/java/com/pangu/student/service/impl/StudentServiceImpl.java b/pangu-system/src/main/java/com/pangu/student/service/impl/StudentServiceImpl.java index 4a0a193..328fe34 100644 --- a/pangu-system/src/main/java/com/pangu/student/service/impl/StudentServiceImpl.java +++ b/pangu-system/src/main/java/com/pangu/student/service/impl/StudentServiceImpl.java @@ -34,6 +34,9 @@ import java.util.List; public class StudentServiceImpl extends ServiceImpl implements IStudentService { private final StudentMapper studentMapper; + private final com.pangu.base.service.IRegionService regionService; + private final com.pangu.school.service.ISchoolService schoolService; + private final com.pangu.member.service.IMemberService memberService; @Override public TableDataInfo selectStudentList(StudentDTO studentDTO) { @@ -99,7 +102,7 @@ public class StudentServiceImpl extends ServiceImpl impl @Transactional(rollbackFor = Exception.class) public ImportResultVO importStudents(MultipartFile file) { try { - StudentImportListener listener = new StudentImportListener(this); + StudentImportListener listener = new StudentImportListener(this, regionService, schoolService, memberService); EasyExcel.read(file.getInputStream(), StudentImportDTO.class, listener).sheet().doRead(); return listener.getResult(); } catch (IOException e) { @@ -114,6 +117,47 @@ public class StudentServiceImpl extends ServiceImpl impl return count == 0; } + @Override + public boolean isStudentInSchool(Long studentId, Long schoolId) { + Student student = studentMapper.selectById(studentId); + if (student == null || "1".equals(student.getDelFlag())) { + return false; + } + return student.getSchoolId().equals(schoolId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int updateStudentMember(Long studentId, Long memberId) { + Student student = new Student(); + student.setStudentId(studentId); + student.setMemberId(memberId); + int result = studentMapper.updateById(student); + log.info("更新学生会员关联成功, studentId={}, memberId={}", studentId, memberId); + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int unbindStudent(Long studentId) { + Student student = new Student(); + student.setStudentId(studentId); + student.setMemberId(null); + int result = studentMapper.updateById(student); + log.info("解绑学生成功, studentId={}", studentId); + return result; + } + + @Override + public int countByMemberId(Long memberId) { + return studentMapper.countByMemberId(memberId); + } + + @Override + public List selectStudentVOsByMemberId(Long memberId) { + return studentMapper.selectStudentVOsByMemberId(memberId); + } + /** * 构建学生对象 */ diff --git a/pangu-system/src/main/resources/mapper/application/AppApiMapper.xml b/pangu-system/src/main/resources/mapper/application/AppApiMapper.xml new file mode 100644 index 0000000..7575f6e --- /dev/null +++ b/pangu-system/src/main/resources/mapper/application/AppApiMapper.xml @@ -0,0 +1,21 @@ + + + + + + + INSERT INTO pg_app_api (app_id, api_id, create_time) + VALUES + + (#{item.appId}, #{item.apiId}, #{item.createTime}) + + + + + + DELETE FROM pg_app_api WHERE app_id = #{appId} + + + diff --git a/pangu-system/src/main/resources/mapper/application/ApplicationMapper.xml b/pangu-system/src/main/resources/mapper/application/ApplicationMapper.xml new file mode 100644 index 0000000..a34ce43 --- /dev/null +++ b/pangu-system/src/main/resources/mapper/application/ApplicationMapper.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pangu-system/src/main/resources/mapper/student/StudentMapper.xml b/pangu-system/src/main/resources/mapper/student/StudentMapper.xml index c2b6b51..bfc7f96 100644 --- a/pangu-system/src/main/resources/mapper/student/StudentMapper.xml +++ b/pangu-system/src/main/resources/mapper/student/StudentMapper.xml @@ -152,4 +152,47 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + + + diff --git a/sql/pangu_application.sql b/sql/pangu_application.sql index 8ee95c7..6ceba02 100644 --- a/sql/pangu_application.sql +++ b/sql/pangu_application.sql @@ -1,16 +1,18 @@ -- ============================================================ --- 盘古用户平台 - 应用管理模块表结构及初始数据 --- 作者:湖北新华业务中台研发团队 --- 说明:执行前请确认已存在 sys_menu,菜单已在 pangu_menu.sql 中 +-- 应用管理模块 - 数据库脚本 +-- 作者:pangu +-- 创建时间:2026-01-31 -- ============================================================ +-- ---------------------------- -- 应用表 -CREATE TABLE IF NOT EXISTS `pg_application` ( +-- ---------------------------- +DROP TABLE IF EXISTS `pg_application`; +CREATE TABLE `pg_application` ( `app_id` bigint NOT NULL AUTO_INCREMENT COMMENT '应用ID', `app_code` varchar(32) NOT NULL COMMENT '应用编码', `app_name` varchar(100) NOT NULL COMMENT '应用名称', `app_secret` varchar(64) NOT NULL COMMENT '应用密钥', - `app_desc` varchar(500) DEFAULT NULL COMMENT '应用描述', `contact_person` varchar(50) DEFAULT NULL COMMENT '联系人', `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话', `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', @@ -21,47 +23,67 @@ CREATE TABLE IF NOT EXISTS `pg_application` ( `del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0存在 1删除)', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`app_id`), - UNIQUE KEY `uk_app_code` (`app_code`), - UNIQUE KEY `uk_app_name` (`app_name`, `del_flag`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='应用表'; - --- 应用接口授权表(记录应用被授予的可调用接口) -CREATE TABLE IF NOT EXISTS `pg_app_grant` ( - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `app_id` bigint NOT NULL COMMENT '应用ID', - `api_code` varchar(100) NOT NULL COMMENT '接口编码', - `api_name` varchar(100) DEFAULT NULL COMMENT '接口名称', - `api_path` varchar(200) NOT NULL COMMENT '接口路径', - `create_by` varchar(64) DEFAULT '' COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_app_api` (`app_id`, `api_code`), - KEY `idx_app_id` (`app_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='应用接口授权表'; + UNIQUE KEY `uk_app_code` (`app_code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='应用表'; +-- ---------------------------- -- API接口字典表 -CREATE TABLE IF NOT EXISTS `pg_api_dict` ( +-- ---------------------------- +DROP TABLE IF EXISTS `pg_api_dict`; +CREATE TABLE `pg_api_dict` ( `api_id` bigint NOT NULL AUTO_INCREMENT COMMENT '接口ID', - `api_code` varchar(100) NOT NULL COMMENT '接口编码', + `api_code` varchar(50) NOT NULL COMMENT '接口编码', `api_name` varchar(100) NOT NULL COMMENT '接口名称', `api_path` varchar(200) NOT NULL COMMENT '接口路径', - `api_method` varchar(10) DEFAULT 'GET' COMMENT '请求方法', - `api_desc` varchar(500) DEFAULT NULL COMMENT '接口描述', - `order_num` int DEFAULT 0 COMMENT '显示顺序', + `api_method` varchar(10) NOT NULL COMMENT '请求方法', + `sort_order` int DEFAULT 0 COMMENT '排序', `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', - `create_by` varchar(64) DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) DEFAULT '' COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`api_id`), UNIQUE KEY `uk_api_code` (`api_code`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='API接口字典表'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API接口字典表'; --- API接口字典初始数据 -INSERT INTO pg_api_dict (api_code, api_name, api_path, api_method, api_desc, order_num, status) VALUES -('STUDENT_LIST', '查询学生信息', '/open/student/list', 'GET', '获取学生列表', 1, '0'), -('SCHOOL_LIST', '查询学校信息', '/open/school/list', 'GET', '获取学校列表', 2, '0'), -('GRADE_LIST', '查询年级信息', '/open/grade/list', 'GET', '获取年级列表', 3, '0'), -('CLASS_LIST', '查询班级信息', '/open/class/list', 'GET', '获取班级列表', 4, '0'), -('MEMBER_LIST', '查询会员信息', '/open/member/list', 'GET', '获取会员列表', 5, '0'), -('REGION_TREE', '查询区域树', '/open/region/tree', 'GET', '获取区域树形结构', 6, '0'); +-- ---------------------------- +-- 应用接口授权表 +-- ---------------------------- +DROP TABLE IF EXISTS `pg_app_api`; +CREATE TABLE `pg_app_api` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `app_id` bigint NOT NULL COMMENT '应用ID', + `api_id` bigint NOT NULL COMMENT '接口ID', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_app_api` (`app_id`, `api_id`), + KEY `idx_app_id` (`app_id`), + KEY `idx_api_id` (`api_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='应用接口授权表'; + +-- ---------------------------- +-- 初始化API接口字典数据 +-- ---------------------------- +INSERT INTO pg_api_dict (api_code, api_name, api_path, api_method, sort_order, status, create_time) VALUES +('api_member_info', '获取会员信息', '/open/api/member/info', 'GET', 1, '0', NOW()), +('api_member_list', '获取会员列表', '/open/api/member/list', 'GET', 2, '0', NOW()), +('api_student_info', '获取学生信息', '/open/api/student/info', 'GET', 3, '0', NOW()), +('api_student_list', '获取学生列表', '/open/api/student/list', 'GET', 4, '0', NOW()), +('api_school_info', '获取学校信息', '/open/api/school/info', 'GET', 5, '0', NOW()), +('api_school_list', '获取学校列表', '/open/api/school/list', 'GET', 6, '0', NOW()); + +-- ---------------------------- +-- 初始化应用示例数据 +-- ---------------------------- +INSERT INTO pg_application (app_code, app_name, app_secret, contact_person, contact_phone, status, create_by, create_time, del_flag) VALUES +('YY000001', 'AI智慧平台', 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6', '张三', '13800138000', '0', 'admin', NOW(), '0'), +('YY000002', '测试应用', 'p6o5n4m3l2k1j0i9h8g7f6e5d4c3b2a1', '李四', '13900139000', '0', 'admin', NOW(), '0'); + +-- ---------------------------- +-- 初始化应用接口授权数据 +-- ---------------------------- +INSERT INTO pg_app_api (app_id, api_id, create_time) VALUES +(1, 1, NOW()), +(1, 2, NOW()), +(1, 3, NOW()), +(1, 4, NOW()), +(2, 1, NOW()), +(2, 3, NOW());