diff --git a/backend/pom.xml b/backend/pom.xml
index a99a54ce..dbeb0c46 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -407,6 +407,12 @@
${revision}
+
+
+ org.dromara
+ ruoyi-stgl
+ ${revision}
+
diff --git a/backend/ruoyi-admin/pom.xml b/backend/ruoyi-admin/pom.xml
index 6e1fa000..eef9838f 100644
--- a/backend/ruoyi-admin/pom.xml
+++ b/backend/ruoyi-admin/pom.xml
@@ -135,6 +135,13 @@
${revision}
+
+
+ org.dromara
+ ruoyi-stgl
+ ${revision}
+
+
de.codecentric
spring-boot-admin-starter-client
diff --git a/backend/ruoyi-modules/pom.xml b/backend/ruoyi-modules/pom.xml
index 6dfc9f47..2e93dacc 100644
--- a/backend/ruoyi-modules/pom.xml
+++ b/backend/ruoyi-modules/pom.xml
@@ -22,6 +22,7 @@
ruoyi-cw
ruoyi-rs
ruoyi-xljk
+ ruoyi-stgl
ruoyi-modules
diff --git a/backend/ruoyi-modules/ruoyi-stgl/pom.xml b/backend/ruoyi-modules/ruoyi-stgl/pom.xml
new file mode 100644
index 00000000..f7e28e60
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/pom.xml
@@ -0,0 +1,93 @@
+
+
+
+ ruoyi-modules
+ org.dromara
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-stgl
+
+
+ 社团管理模块
+
+
+
+
+
+ org.dromara
+ ruoyi-common-core
+
+
+
+
+ org.dromara
+ ruoyi-common-security
+
+
+
+
+ org.dromara
+ ruoyi-common-web
+
+
+
+
+ org.dromara
+ ruoyi-common-excel
+
+
+
+ org.apache.poi
+ poi-ooxml
+ 5.2.3
+
+
+
+
+ org.dromara
+ ruoyi-common-mybatis
+
+
+
+
+ org.dromara
+ ruoyi-common-tenant
+
+
+
+
+ org.dromara
+ ruoyi-common-idempotent
+
+
+
+
+ org.dromara
+ ruoyi-common-log
+
+
+
+
+ org.dromara
+ ruoyi-common-translation
+
+
+
+
+ org.dromara
+ ruoyi-common-oss
+
+
+
+
+ org.dromara
+ ruoyi-common-doc
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubController.java
new file mode 100644
index 00000000..a5604d00
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubController.java
@@ -0,0 +1,176 @@
+package org.dromara.stgl.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.stgl.domain.vo.StglClubVo;
+import org.dromara.stgl.domain.bo.StglClubBo;
+import org.dromara.stgl.service.IStglClubService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * 社团信息管理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/stgl/club")
+@Tag(name = "社团信息管理", description = "社团信息管理")
+public class StglClubController extends BaseController {
+
+ private final IStglClubService stglClubService;
+
+ /**
+ * 查询社团信息列表
+ */
+ @Operation(summary = "查询社团信息列表")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/list")
+ public TableDataInfo list(StglClubBo bo, PageQuery pageQuery) {
+ return stglClubService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 导出社团信息列表
+ */
+ @Operation(summary = "导出社团信息列表")
+ @SaCheckPermission("stgl:club:export")
+ @Log(title = "社团信息", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(StglClubBo bo, HttpServletResponse response) {
+ List list = stglClubService.queryList(bo);
+ ExcelUtil.exportExcel(list, "社团信息", StglClubVo.class, response);
+ }
+
+ /**
+ * 获取社团信息详细信息
+ */
+ @Operation(summary = "获取社团信息详细信息")
+ @SaCheckPermission("stgl:club:query")
+ @GetMapping("/{id}")
+ public R getInfo(@NotNull(message = "主键不能为空")
+ @PathVariable Long id) {
+ return R.ok(stglClubService.queryById(id));
+ }
+
+ /**
+ * 新增社团信息
+ */
+ @Operation(summary = "新增社团信息")
+ @SaCheckPermission("stgl:club:add")
+ @Log(title = "社团信息", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R add(@Validated(AddGroup.class) @RequestBody StglClubBo bo) {
+ return toAjax(stglClubService.insertByBo(bo));
+ }
+
+ /**
+ * 修改社团信息
+ */
+ @Operation(summary = "修改社团信息")
+ @SaCheckPermission("stgl:club:edit")
+ @Log(title = "社团信息", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R edit(@Validated(EditGroup.class) @RequestBody StglClubBo bo) {
+ return toAjax(stglClubService.updateByBo(bo));
+ }
+
+ /**
+ * 删除社团信息
+ */
+ @Operation(summary = "删除社团信息")
+ @SaCheckPermission("stgl:club:remove")
+ @Log(title = "社团信息", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R remove(@NotEmpty(message = "主键不能为空")
+ @PathVariable Long[] ids) {
+ return toAjax(stglClubService.deleteWithValidByIds(List.of(ids), true));
+ }
+
+ /**
+ * 查询可用的社团选项
+ */
+ @Operation(summary = "查询可用的社团选项")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/options")
+ public R> getOptions() {
+ return R.ok(stglClubService.queryAvailableOptions());
+ }
+
+ /**
+ * 根据社团类型查询社团列表
+ */
+ @Operation(summary = "根据社团类型查询社团列表")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/listByType/{clubType}")
+ @Parameters({
+ @Parameter(name = "clubType", description = "社团类型", required = true)
+ })
+ public R> listByType(@PathVariable String clubType) {
+ return R.ok(stglClubService.queryByClubType(clubType));
+ }
+
+ /**
+ * 查询我管理的社团列表
+ */
+ @Operation(summary = "查询我管理的社团列表")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/myClubs")
+ public R> getMyClubs() {
+ return R.ok(stglClubService.queryMyClubs());
+ }
+
+ /**
+ * 检查社团名称是否重复
+ */
+ @Operation(summary = "检查社团名称是否重复")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/checkClubNameUnique")
+ @Parameters({
+ @Parameter(name = "clubName", description = "社团名称", required = true),
+ @Parameter(name = "excludeId", description = "排除的ID", required = false)
+ })
+ public R checkClubNameUnique(@RequestParam String clubName,
+ @RequestParam(required = false) Long excludeId) {
+ return R.ok(stglClubService.checkClubNameUnique(clubName, excludeId));
+ }
+
+ /**
+ * 启用/禁用社团
+ */
+ @Operation(summary = "启用/禁用社团")
+ @SaCheckPermission("stgl:club:edit")
+ @Log(title = "社团信息", businessType = BusinessType.UPDATE)
+ @PutMapping("/changeStatus")
+ @Parameters({
+ @Parameter(name = "id", description = "社团ID", required = true),
+ @Parameter(name = "status", description = "状态", required = true)
+ })
+ public R changeStatus(@RequestParam Long id, @RequestParam Integer status) {
+ return toAjax(stglClubService.changeStatus(id, status));
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubRegisterController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubRegisterController.java
new file mode 100644
index 00000000..b0c57795
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubRegisterController.java
@@ -0,0 +1,145 @@
+package org.dromara.stgl.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.stgl.domain.vo.StglClubRegisterVo;
+import org.dromara.stgl.service.IStglClubRegisterService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * 社团报名大厅
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/stgl/register")
+@Tag(name = "社团报名大厅", description = "社团报名大厅")
+public class StglClubRegisterController extends BaseController {
+
+ private final IStglClubRegisterService stglClubRegisterService;
+
+ /**
+ * 查询社团报名大厅列表
+ */
+ @Operation(summary = "查询社团报名大厅列表")
+ @GetMapping("/list")
+ public TableDataInfo list(@RequestParam(required = false) String clubType,
+ @RequestParam(required = false) String clubName,
+ PageQuery pageQuery) {
+ return stglClubRegisterService.queryPageList(clubType, clubName, pageQuery);
+ }
+
+ /**
+ * 查询社团报名大厅列表(不分页)
+ */
+ @Operation(summary = "查询社团报名大厅列表(不分页)")
+ @GetMapping("/listAll")
+ public R> listAll(@RequestParam(required = false) String clubType,
+ @RequestParam(required = false) String clubName) {
+ return R.ok(stglClubRegisterService.queryList(clubType, clubName));
+ }
+
+ /**
+ * 获取社团详细信息
+ */
+ @Operation(summary = "获取社团详细信息")
+ @GetMapping("/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R getClubDetail(@PathVariable Long clubId) {
+ return R.ok(stglClubRegisterService.queryClubDetail(clubId));
+ }
+
+ /**
+ * 报名加入社团
+ */
+ @Operation(summary = "报名加入社团")
+ @SaCheckPermission("stgl:register:join")
+ @Log(title = "社团报名", businessType = BusinessType.INSERT)
+ @PostMapping("/join/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R registerClub(@PathVariable Long clubId) {
+ Long userId = LoginHelper.getUserId();
+ return toAjax(stglClubRegisterService.registerClub(clubId, userId));
+ }
+
+ /**
+ * 退出社团
+ */
+ @Operation(summary = "退出社团")
+ @SaCheckPermission("stgl:register:quit")
+ @Log(title = "社团退出", businessType = BusinessType.UPDATE)
+ @PostMapping("/quit/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R quitClub(@PathVariable Long clubId) {
+ Long userId = LoginHelper.getUserId();
+ return toAjax(stglClubRegisterService.quitClub(clubId, userId));
+ }
+
+ /**
+ * 查询我参与的社团列表
+ */
+ @Operation(summary = "查询我参与的社团列表")
+ @SaCheckPermission("stgl:register:list")
+ @GetMapping("/myClubs")
+ public R> getMyClubs() {
+ Long userId = LoginHelper.getUserId();
+ return R.ok(stglClubRegisterService.queryMyClubs(userId));
+ }
+
+ /**
+ * 检查是否已报名某个社团
+ */
+ @Operation(summary = "检查是否已报名某个社团")
+ @GetMapping("/checkRegistered/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R checkRegistered(@PathVariable Long clubId) {
+ try {
+ Long userId = LoginHelper.getUserId();
+ return R.ok(stglClubRegisterService.checkRegistered(clubId, userId));
+ } catch (Exception e) {
+ // 未登录用户
+ return R.ok(false);
+ }
+ }
+
+ /**
+ * 查询推荐社团
+ */
+ @Operation(summary = "查询推荐社团")
+ @GetMapping("/recommend")
+ @Parameters({
+ @Parameter(name = "clubType", description = "社团类型", required = false),
+ @Parameter(name = "limit", description = "推荐数量", required = false)
+ })
+ public R> getRecommendClubs(@RequestParam(required = false) String clubType,
+ @RequestParam(required = false, defaultValue = "6") Integer limit) {
+ return R.ok(stglClubRegisterService.queryRecommendClubs(clubType, limit));
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMemberController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMemberController.java
new file mode 100644
index 00000000..e91085c7
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMemberController.java
@@ -0,0 +1,205 @@
+package org.dromara.stgl.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.stgl.domain.vo.StglMemberVo;
+import org.dromara.stgl.domain.bo.StglMemberBo;
+import org.dromara.stgl.service.IStglMemberService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * 社团成员管理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/stgl/member")
+@Tag(name = "社团成员管理", description = "社团成员管理")
+public class StglMemberController extends BaseController {
+
+ private final IStglMemberService stglMemberService;
+
+ /**
+ * 查询社团成员列表
+ */
+ @Operation(summary = "查询社团成员列表")
+ @SaCheckPermission("stgl:member:list")
+ @GetMapping("/list")
+ public TableDataInfo list(StglMemberBo bo, PageQuery pageQuery) {
+ return stglMemberService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 导出社团成员列表
+ */
+ @Operation(summary = "导出社团成员列表")
+ @SaCheckPermission("stgl:member:export")
+ @Log(title = "社团成员", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(StglMemberBo bo, HttpServletResponse response) {
+ List list = stglMemberService.queryList(bo);
+ ExcelUtil.exportExcel(list, "社团成员", StglMemberVo.class, response);
+ }
+
+ /**
+ * 获取社团成员详细信息
+ */
+ @Operation(summary = "获取社团成员详细信息")
+ @SaCheckPermission("stgl:member:query")
+ @GetMapping("/{id}")
+ public R getInfo(@NotNull(message = "主键不能为空")
+ @PathVariable Long id) {
+ return R.ok(stglMemberService.queryById(id));
+ }
+
+ /**
+ * 新增社团成员
+ */
+ @Operation(summary = "新增社团成员")
+ @SaCheckPermission("stgl:member:add")
+ @Log(title = "社团成员", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R add(@Validated(AddGroup.class) @RequestBody StglMemberBo bo) {
+ return toAjax(stglMemberService.insertByBo(bo));
+ }
+
+ /**
+ * 修改社团成员
+ */
+ @Operation(summary = "修改社团成员")
+ @SaCheckPermission("stgl:member:edit")
+ @Log(title = "社团成员", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R edit(@Validated(EditGroup.class) @RequestBody StglMemberBo bo) {
+ return toAjax(stglMemberService.updateByBo(bo));
+ }
+
+ /**
+ * 删除社团成员
+ */
+ @Operation(summary = "删除社团成员")
+ @SaCheckPermission("stgl:member:remove")
+ @Log(title = "社团成员", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R remove(@NotEmpty(message = "主键不能为空")
+ @PathVariable Long[] ids) {
+ return toAjax(stglMemberService.deleteWithValidByIds(List.of(ids), true));
+ }
+
+ /**
+ * 根据社团ID查询成员列表
+ */
+ @Operation(summary = "根据社团ID查询成员列表")
+ @SaCheckPermission("stgl:member:list")
+ @GetMapping("/club/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R> getByClubId(@PathVariable Long clubId) {
+ return R.ok(stglMemberService.queryByClubId(clubId));
+ }
+
+ /**
+ * 根据用户ID查询参与的社团列表
+ */
+ @Operation(summary = "根据用户ID查询参与的社团列表")
+ @SaCheckPermission("stgl:member:list")
+ @GetMapping("/user/{userId}")
+ @Parameters({
+ @Parameter(name = "userId", description = "用户ID", required = true)
+ })
+ public R> getByUserId(@PathVariable Long userId) {
+ return R.ok(stglMemberService.queryByUserId(userId));
+ }
+
+ /**
+ * 检查用户是否已是某个社团/队伍的成员
+ */
+ @Operation(summary = "检查用户是否已是某个社团/队伍的成员")
+ @SaCheckPermission("stgl:member:list")
+ @GetMapping("/checkMemberExists")
+ @Parameters({
+ @Parameter(name = "memberType", description = "成员类型", required = true),
+ @Parameter(name = "relationId", description = "关联ID", required = true),
+ @Parameter(name = "userId", description = "用户ID", required = true)
+ })
+ public R checkMemberExists(@RequestParam Integer memberType,
+ @RequestParam Long relationId,
+ @RequestParam Long userId) {
+ return R.ok(stglMemberService.checkMemberExists(memberType, relationId, userId));
+ }
+
+ /**
+ * 批量添加成员
+ */
+ @Operation(summary = "批量添加成员")
+ @SaCheckPermission("stgl:member:add")
+ @Log(title = "社团成员", businessType = BusinessType.INSERT)
+ @PostMapping("/batchAdd")
+ @Parameters({
+ @Parameter(name = "memberType", description = "成员类型", required = true),
+ @Parameter(name = "relationId", description = "关联ID", required = true),
+ @Parameter(name = "userIds", description = "用户ID列表", required = true),
+ @Parameter(name = "roleType", description = "角色类型", required = false)
+ })
+ public R batchAddMembers(@RequestParam Integer memberType,
+ @RequestParam Long relationId,
+ @RequestParam List userIds,
+ @RequestParam(required = false) Integer roleType) {
+ return toAjax(stglMemberService.batchAddMembers(memberType, relationId, userIds, roleType));
+ }
+
+ /**
+ * 更新成员角色
+ */
+ @Operation(summary = "更新成员角色")
+ @SaCheckPermission("stgl:member:edit")
+ @Log(title = "社团成员", businessType = BusinessType.UPDATE)
+ @PutMapping("/updateRole")
+ @Parameters({
+ @Parameter(name = "id", description = "成员ID", required = true),
+ @Parameter(name = "roleType", description = "角色类型", required = true)
+ })
+ public R updateMemberRole(@RequestParam Long id, @RequestParam Integer roleType) {
+ return toAjax(stglMemberService.updateMemberRole(id, roleType));
+ }
+
+ /**
+ * 退出社团/队伍
+ */
+ @Operation(summary = "退出社团/队伍")
+ @SaCheckPermission("stgl:member:edit")
+ @Log(title = "社团成员", businessType = BusinessType.UPDATE)
+ @PutMapping("/quit/{id}")
+ @Parameters({
+ @Parameter(name = "id", description = "成员ID", required = true)
+ })
+ public R quitMember(@PathVariable Long id) {
+ return toAjax(stglMemberService.quitMember(id));
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClub.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClub.java
new file mode 100644
index 00000000..28125ae5
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClub.java
@@ -0,0 +1,160 @@
+package org.dromara.stgl.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 社团信息对象 stgl_club
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("stgl_club")
+public class StglClub extends TenantEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 社团名称
+ */
+ private String clubName;
+
+ /**
+ * 社团类型
+ */
+ private String clubType;
+
+ /**
+ * 成立日期
+ */
+ private Date establishDate;
+
+ /**
+ * 社团简介(500字以内)
+ */
+ private String description;
+
+ /**
+ * 邮箱
+ */
+ private String email;
+
+ /**
+ * 电话
+ */
+ private String phone;
+
+ /**
+ * logo图片路径
+ */
+ private String logoUrl;
+
+ /**
+ * 社长姓名
+ */
+ private String presidentName;
+
+ /**
+ * 社长学号
+ */
+ private String presidentStudentNo;
+
+ /**
+ * 社长院系
+ */
+ private String presidentDept;
+
+ /**
+ * 社长专业
+ */
+ private String presidentMajor;
+
+ /**
+ * 社长班级
+ */
+ private String presidentClass;
+
+ /**
+ * 社长联系方式
+ */
+ private String presidentPhone;
+
+ /**
+ * 副社长姓名
+ */
+ private String vicePresidentName;
+
+ /**
+ * 副社长学号
+ */
+ private String vicePresidentStudentNo;
+
+ /**
+ * 指导老师姓名
+ */
+ private String advisorName;
+
+ /**
+ * 指导老师所属院系
+ */
+ private String advisorDept;
+
+ /**
+ * 成员规模上限
+ */
+ private Integer memberLimit;
+
+ /**
+ * 组织架构(部门设置及职责)
+ */
+ private String departments;
+
+ /**
+ * 招新周期
+ */
+ private String recruitCycle;
+
+ /**
+ * 招新时间
+ */
+ private String recruitTime;
+
+ /**
+ * 活动频率
+ */
+ private String activityFrequency;
+
+ /**
+ * 经费来源(多选)
+ */
+ private String fundingSources;
+
+ /**
+ * 章程文档路径
+ */
+ private String charterUrl;
+
+ /**
+ * 状态(1-正常 0-停用)
+ */
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglMember.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglMember.java
new file mode 100644
index 00000000..748e9006
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglMember.java
@@ -0,0 +1,75 @@
+package org.dromara.stgl.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 社团成员对象 stgl_member
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("stgl_member")
+public class StglMember extends TenantEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 成员类型(1-社团成员 2-志愿者)
+ */
+ private Integer memberType;
+
+ /**
+ * 关联ID(社团ID或志愿者队伍ID)
+ */
+ private Long relationId;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 学号
+ */
+ private String studentNo;
+
+ /**
+ * 学生姓名
+ */
+ private String studentName;
+
+ /**
+ * 角色类型(1-社长 2-副社长 3-成员)
+ */
+ private Integer roleType;
+
+ /**
+ * 加入时间
+ */
+ private Date joinTime;
+
+ /**
+ * 状态(1-正常 0-退出)
+ */
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubBo.java
new file mode 100644
index 00000000..82ff495d
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubBo.java
@@ -0,0 +1,188 @@
+package org.dromara.stgl.domain.bo;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import org.dromara.stgl.domain.StglClub;
+
+import java.util.Date;
+
+/**
+ * 社团信息业务对象 stgl_club
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = StglClub.class, reverseConvertGenerate = false)
+public class StglClubBo extends BaseEntity {
+
+ /**
+ * 主键ID
+ */
+ @NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
+ private Long id;
+
+ /**
+ * 社团名称
+ */
+ @NotBlank(message = "社团名称不能为空", groups = { AddGroup.class, EditGroup.class })
+ @Size(max = 100, message = "社团名称长度不能超过100个字符")
+ private String clubName;
+
+ /**
+ * 社团类型
+ */
+ @NotBlank(message = "社团类型不能为空", groups = { AddGroup.class, EditGroup.class })
+ @Size(max = 50, message = "社团类型长度不能超过50个字符")
+ private String clubType;
+
+ /**
+ * 成立日期
+ */
+ private Date establishDate;
+
+ /**
+ * 社团简介(500字以内)
+ */
+ @Size(max = 500, message = "社团简介长度不能超过500个字符")
+ private String description;
+
+ /**
+ * 邮箱
+ */
+ @Email(message = "邮箱格式不正确")
+ @Size(max = 100, message = "邮箱长度不能超过100个字符")
+ private String email;
+
+ /**
+ * 电话
+ */
+ @Pattern(regexp = "^[0-9-]+$", message = "电话格式不正确")
+ @Size(max = 20, message = "电话长度不能超过20个字符")
+ private String phone;
+
+ /**
+ * logo图片路径
+ */
+ @Size(max = 500, message = "logo图片路径长度不能超过500个字符")
+ private String logoUrl;
+
+ /**
+ * 社长姓名
+ */
+ @Size(max = 50, message = "社长姓名长度不能超过50个字符")
+ private String presidentName;
+
+ /**
+ * 社长学号
+ */
+ @Size(max = 20, message = "社长学号长度不能超过20个字符")
+ private String presidentStudentNo;
+
+ /**
+ * 社长院系
+ */
+ @Size(max = 100, message = "社长院系长度不能超过100个字符")
+ private String presidentDept;
+
+ /**
+ * 社长专业
+ */
+ @Size(max = 100, message = "社长专业长度不能超过100个字符")
+ private String presidentMajor;
+
+ /**
+ * 社长班级
+ */
+ @Size(max = 100, message = "社长班级长度不能超过100个字符")
+ private String presidentClass;
+
+ /**
+ * 社长联系方式
+ */
+ @Pattern(regexp = "^[0-9-]+$", message = "社长联系方式格式不正确")
+ @Size(max = 20, message = "社长联系方式长度不能超过20个字符")
+ private String presidentPhone;
+
+ /**
+ * 副社长姓名
+ */
+ @Size(max = 50, message = "副社长姓名长度不能超过50个字符")
+ private String vicePresidentName;
+
+ /**
+ * 副社长学号
+ */
+ @Size(max = 20, message = "副社长学号长度不能超过20个字符")
+ private String vicePresidentStudentNo;
+
+ /**
+ * 指导老师姓名
+ */
+ @Size(max = 50, message = "指导老师姓名长度不能超过50个字符")
+ private String advisorName;
+
+ /**
+ * 指导老师所属院系
+ */
+ @Size(max = 100, message = "指导老师所属院系长度不能超过100个字符")
+ private String advisorDept;
+
+ /**
+ * 成员规模上限
+ */
+ @Min(value = 0, message = "成员规模上限不能小于0")
+ private Integer memberLimit;
+
+ /**
+ * 组织架构(部门设置及职责)
+ */
+ private String departments;
+
+ /**
+ * 招新周期
+ */
+ @Size(max = 50, message = "招新周期长度不能超过50个字符")
+ private String recruitCycle;
+
+ /**
+ * 招新时间
+ */
+ @Size(max = 50, message = "招新时间长度不能超过50个字符")
+ private String recruitTime;
+
+ /**
+ * 活动频率
+ */
+ @Size(max = 50, message = "活动频率长度不能超过50个字符")
+ private String activityFrequency;
+
+ /**
+ * 经费来源(多选)
+ */
+ @Size(max = 200, message = "经费来源长度不能超过200个字符")
+ private String fundingSources;
+
+ /**
+ * 章程文档路径
+ */
+ @Size(max = 500, message = "章程文档路径长度不能超过500个字符")
+ private String charterUrl;
+
+ /**
+ * 状态(1-正常 0-停用)
+ */
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglMemberBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglMemberBo.java
new file mode 100644
index 00000000..df0913b5
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglMemberBo.java
@@ -0,0 +1,85 @@
+package org.dromara.stgl.domain.bo;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import org.dromara.stgl.domain.StglMember;
+
+import java.util.Date;
+
+/**
+ * 社团成员业务对象 stgl_member
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = StglMember.class, reverseConvertGenerate = false)
+public class StglMemberBo extends BaseEntity {
+
+ /**
+ * 主键ID
+ */
+ @NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
+ private Long id;
+
+ /**
+ * 成员类型(1-社团成员 2-志愿者)
+ */
+ @NotNull(message = "成员类型不能为空", groups = { AddGroup.class, EditGroup.class })
+ @Min(value = 1, message = "成员类型值无效")
+ @Max(value = 2, message = "成员类型值无效")
+ private Integer memberType;
+
+ /**
+ * 关联ID(社团ID或志愿者队伍ID)
+ */
+ @NotNull(message = "关联ID不能为空", groups = { AddGroup.class, EditGroup.class })
+ private Long relationId;
+
+ /**
+ * 用户ID
+ */
+ @NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class })
+ private Long userId;
+
+ /**
+ * 学号
+ */
+ @Size(max = 20, message = "学号长度不能超过20个字符")
+ private String studentNo;
+
+ /**
+ * 学生姓名
+ */
+ @Size(max = 50, message = "学生姓名长度不能超过50个字符")
+ private String studentName;
+
+ /**
+ * 角色类型(1-社长 2-副社长 3-成员)
+ */
+ @Min(value = 1, message = "角色类型值无效")
+ @Max(value = 3, message = "角色类型值无效")
+ private Integer roleType;
+
+ /**
+ * 加入时间
+ */
+ private Date joinTime;
+
+ /**
+ * 状态(1-正常 0-退出)
+ */
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubRegisterVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubRegisterVo.java
new file mode 100644
index 00000000..cbd264bb
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubRegisterVo.java
@@ -0,0 +1,111 @@
+package org.dromara.stgl.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 社团报名大厅视图对象
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+public class StglClubRegisterVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ private Long id;
+
+ /**
+ * 社团名称
+ */
+ private String clubName;
+
+ /**
+ * 社团类型
+ */
+ private String clubType;
+
+ /**
+ * 成立日期
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date establishDate;
+
+ /**
+ * 社团简介(500字以内)
+ */
+ private String description;
+
+ /**
+ * logo图片路径
+ */
+ private String logoUrl;
+
+ /**
+ * 社长姓名
+ */
+ private String presidentName;
+
+ /**
+ * 社长院系
+ */
+ private String presidentDept;
+
+ /**
+ * 指导老师姓名
+ */
+ private String advisorName;
+
+ /**
+ * 指导老师所属院系
+ */
+ private String advisorDept;
+
+ /**
+ * 成员规模上限
+ */
+ private Integer memberLimit;
+
+ /**
+ * 当前成员数量
+ */
+ private Integer currentMemberCount;
+
+ /**
+ * 招新周期
+ */
+ private String recruitCycle;
+
+ /**
+ * 招新时间
+ */
+ private String recruitTime;
+
+ /**
+ * 活动频率
+ */
+ private String activityFrequency;
+
+ /**
+ * 是否已报名
+ */
+ private Boolean hasRegistered;
+
+ /**
+ * 报名状态(1-已报名 0-未报名)
+ */
+ private Integer registrationStatus;
+
+ /**
+ * 是否可报名(基于成员上限等条件)
+ */
+ private Boolean canRegister;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubVo.java
new file mode 100644
index 00000000..9dcc55ea
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubVo.java
@@ -0,0 +1,211 @@
+package org.dromara.stgl.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.stgl.domain.StglClub;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 社团信息视图对象 stgl_club
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = StglClub.class)
+public class StglClubVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @ExcelProperty(value = "主键ID")
+ private Long id;
+
+ /**
+ * 社团名称
+ */
+ @ExcelProperty(value = "社团名称")
+ private String clubName;
+
+ /**
+ * 社团类型
+ */
+ @ExcelProperty(value = "社团类型")
+ private String clubType;
+
+ /**
+ * 成立日期
+ */
+ @ExcelProperty(value = "成立日期")
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date establishDate;
+
+ /**
+ * 社团简介(500字以内)
+ */
+ @ExcelProperty(value = "社团简介")
+ private String description;
+
+ /**
+ * 邮箱
+ */
+ @ExcelProperty(value = "邮箱")
+ private String email;
+
+ /**
+ * 电话
+ */
+ @ExcelProperty(value = "电话")
+ private String phone;
+
+ /**
+ * logo图片路径
+ */
+ @ExcelProperty(value = "社团Logo")
+ private String logoUrl;
+
+ /**
+ * 社长姓名
+ */
+ @ExcelProperty(value = "社长姓名")
+ private String presidentName;
+
+ /**
+ * 社长学号
+ */
+ @ExcelProperty(value = "社长学号")
+ private String presidentStudentNo;
+
+ /**
+ * 社长院系
+ */
+ @ExcelProperty(value = "社长院系")
+ private String presidentDept;
+
+ /**
+ * 社长专业
+ */
+ @ExcelProperty(value = "社长专业")
+ private String presidentMajor;
+
+ /**
+ * 社长班级
+ */
+ @ExcelProperty(value = "社长班级")
+ private String presidentClass;
+
+ /**
+ * 社长联系方式
+ */
+ @ExcelProperty(value = "社长联系方式")
+ private String presidentPhone;
+
+ /**
+ * 副社长姓名
+ */
+ @ExcelProperty(value = "副社长姓名")
+ private String vicePresidentName;
+
+ /**
+ * 副社长学号
+ */
+ @ExcelProperty(value = "副社长学号")
+ private String vicePresidentStudentNo;
+
+ /**
+ * 指导老师姓名
+ */
+ @ExcelProperty(value = "指导老师姓名")
+ private String advisorName;
+
+ /**
+ * 指导老师所属院系
+ */
+ @ExcelProperty(value = "指导老师所属院系")
+ private String advisorDept;
+
+ /**
+ * 成员规模上限
+ */
+ @ExcelProperty(value = "成员规模上限")
+ private Integer memberLimit;
+
+ /**
+ * 组织架构(部门设置及职责)
+ */
+ @ExcelProperty(value = "组织架构")
+ private String departments;
+
+ /**
+ * 招新周期
+ */
+ @ExcelProperty(value = "招新周期")
+ private String recruitCycle;
+
+ /**
+ * 招新时间
+ */
+ @ExcelProperty(value = "招新时间")
+ private String recruitTime;
+
+ /**
+ * 活动频率
+ */
+ @ExcelProperty(value = "活动频率")
+ private String activityFrequency;
+
+ /**
+ * 经费来源(多选)
+ */
+ @ExcelProperty(value = "经费来源")
+ private String fundingSources;
+
+ /**
+ * 章程文档路径
+ */
+ @ExcelProperty(value = "章程文档")
+ private String charterUrl;
+
+ /**
+ * 状态(1-正常 0-停用)
+ */
+ @ExcelProperty(value = "状态")
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ @ExcelProperty(value = "备注")
+ private String remark;
+
+ /**
+ * 创建者
+ */
+ private Long createBy;
+
+ /**
+ * 创建时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ private Long updateBy;
+
+ /**
+ * 更新时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date updateTime;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglMemberVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglMemberVo.java
new file mode 100644
index 00000000..54007c89
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglMemberVo.java
@@ -0,0 +1,133 @@
+package org.dromara.stgl.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.stgl.domain.StglMember;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 社团成员视图对象 stgl_member
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = StglMember.class)
+public class StglMemberVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @ExcelProperty(value = "主键ID")
+ private Long id;
+
+ /**
+ * 成员类型(1-社团成员 2-志愿者)
+ */
+ @ExcelProperty(value = "成员类型")
+ private Integer memberType;
+
+ /**
+ * 成员类型名称
+ */
+ @ExcelProperty(value = "成员类型名称")
+ private String memberTypeName;
+
+ /**
+ * 关联ID(社团ID或志愿者队伍ID)
+ */
+ @ExcelProperty(value = "关联ID")
+ private Long relationId;
+
+ /**
+ * 关联名称(社团名称或志愿者队伍名称)
+ */
+ @ExcelProperty(value = "所属组织")
+ private String relationName;
+
+ /**
+ * 用户ID
+ */
+ @ExcelProperty(value = "用户ID")
+ private Long userId;
+
+ /**
+ * 学号
+ */
+ @ExcelProperty(value = "学号")
+ private String studentNo;
+
+ /**
+ * 学生姓名
+ */
+ @ExcelProperty(value = "学生姓名")
+ private String studentName;
+
+ /**
+ * 角色类型(1-社长 2-副社长 3-成员)
+ */
+ @ExcelProperty(value = "角色类型")
+ private Integer roleType;
+
+ /**
+ * 角色类型名称
+ */
+ @ExcelProperty(value = "角色名称")
+ private String roleTypeName;
+
+ /**
+ * 加入时间
+ */
+ @ExcelProperty(value = "加入时间")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date joinTime;
+
+ /**
+ * 状态(1-正常 0-退出)
+ */
+ @ExcelProperty(value = "状态")
+ private Integer status;
+
+ /**
+ * 状态名称
+ */
+ @ExcelProperty(value = "状态名称")
+ private String statusName;
+
+ /**
+ * 备注
+ */
+ @ExcelProperty(value = "备注")
+ private String remark;
+
+ /**
+ * 创建者
+ */
+ private Long createBy;
+
+ /**
+ * 创建时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ private Long updateBy;
+
+ /**
+ * 更新时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date updateTime;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubMapper.java
new file mode 100644
index 00000000..0e244ffb
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.stgl.mapper;
+
+import org.dromara.stgl.domain.StglClub;
+import org.dromara.stgl.domain.vo.StglClubVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 社团信息Mapper接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface StglClubMapper extends BaseMapperPlus {
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMemberMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMemberMapper.java
new file mode 100644
index 00000000..673b898b
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMemberMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.stgl.mapper;
+
+import org.dromara.stgl.domain.StglMember;
+import org.dromara.stgl.domain.vo.StglMemberVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 社团成员Mapper接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface StglMemberMapper extends BaseMapperPlus {
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubRegisterService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubRegisterService.java
new file mode 100644
index 00000000..f6b80259
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubRegisterService.java
@@ -0,0 +1,88 @@
+package org.dromara.stgl.service;
+
+import org.dromara.stgl.domain.vo.StglClubRegisterVo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.List;
+
+/**
+ * 社团报名大厅Service接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface IStglClubRegisterService {
+
+ /**
+ * 分页查询社团报名大厅列表
+ *
+ * @param clubType 社团类型
+ * @param clubName 社团名称
+ * @param pageQuery 分页参数
+ * @return 社团报名大厅分页列表
+ */
+ TableDataInfo queryPageList(String clubType, String clubName, PageQuery pageQuery);
+
+ /**
+ * 查询社团报名大厅列表
+ *
+ * @param clubType 社团类型
+ * @param clubName 社团名称
+ * @return 社团报名大厅列表
+ */
+ List queryList(String clubType, String clubName);
+
+ /**
+ * 查询社团详细信息(用于报名)
+ *
+ * @param clubId 社团ID
+ * @return 社团详细信息
+ */
+ StglClubRegisterVo queryClubDetail(Long clubId);
+
+ /**
+ * 报名加入社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否报名成功
+ */
+ Boolean registerClub(Long clubId, Long userId);
+
+ /**
+ * 退出社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否退出成功
+ */
+ Boolean quitClub(Long clubId, Long userId);
+
+ /**
+ * 查询我参与的社团列表
+ *
+ * @param userId 用户ID
+ * @return 社团列表
+ */
+ List queryMyClubs(Long userId);
+
+ /**
+ * 检查是否已报名某个社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否已报名
+ */
+ Boolean checkRegistered(Long clubId, Long userId);
+
+ /**
+ * 根据社团类型查询推荐社团
+ *
+ * @param clubType 社团类型
+ * @param limit 推荐数量
+ * @return 推荐社团列表
+ */
+ List queryRecommendClubs(String clubType, Integer limit);
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubService.java
new file mode 100644
index 00000000..2bb93775
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubService.java
@@ -0,0 +1,109 @@
+package org.dromara.stgl.service;
+
+import org.dromara.stgl.domain.vo.StglClubVo;
+import org.dromara.stgl.domain.bo.StglClubBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 社团信息Service接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface IStglClubService {
+
+ /**
+ * 查询社团信息
+ *
+ * @param id 主键
+ * @return 社团信息
+ */
+ StglClubVo queryById(Long id);
+
+ /**
+ * 分页查询社团信息列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return 社团信息分页列表
+ */
+ TableDataInfo queryPageList(StglClubBo bo, PageQuery pageQuery);
+
+ /**
+ * 查询符合条件的社团信息列表
+ *
+ * @param bo 查询条件
+ * @return 社团信息列表
+ */
+ List queryList(StglClubBo bo);
+
+ /**
+ * 新增社团信息
+ *
+ * @param bo 社团信息
+ * @return 是否新增成功
+ */
+ Boolean insertByBo(StglClubBo bo);
+
+ /**
+ * 修改社团信息
+ *
+ * @param bo 社团信息
+ * @return 是否修改成功
+ */
+ Boolean updateByBo(StglClubBo bo);
+
+ /**
+ * 校验并批量删除社团信息
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ Boolean deleteWithValidByIds(Collection ids, Boolean isValid);
+
+ /**
+ * 查询可用的社团选项
+ *
+ * @return 社团选项列表
+ */
+ List queryAvailableOptions();
+
+ /**
+ * 根据社团类型查询社团列表
+ *
+ * @param clubType 社团类型
+ * @return 社团列表
+ */
+ List queryByClubType(String clubType);
+
+ /**
+ * 查询我管理的社团列表
+ *
+ * @return 社团列表
+ */
+ List queryMyClubs();
+
+ /**
+ * 检查社团名称是否重复
+ *
+ * @param clubName 社团名称
+ * @param excludeId 排除的ID(修改时使用)
+ * @return 是否重复
+ */
+ Boolean checkClubNameUnique(String clubName, Long excludeId);
+
+ /**
+ * 启用/禁用社团
+ *
+ * @param id 社团ID
+ * @param status 状态
+ * @return 是否操作成功
+ */
+ Boolean changeStatus(Long id, Integer status);
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMemberService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMemberService.java
new file mode 100644
index 00000000..50847b9a
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMemberService.java
@@ -0,0 +1,123 @@
+package org.dromara.stgl.service;
+
+import org.dromara.stgl.domain.vo.StglMemberVo;
+import org.dromara.stgl.domain.bo.StglMemberBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 社团成员Service接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface IStglMemberService {
+
+ /**
+ * 查询社团成员
+ *
+ * @param id 主键
+ * @return 社团成员
+ */
+ StglMemberVo queryById(Long id);
+
+ /**
+ * 分页查询社团成员列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return 社团成员分页列表
+ */
+ TableDataInfo queryPageList(StglMemberBo bo, PageQuery pageQuery);
+
+ /**
+ * 查询符合条件的社团成员列表
+ *
+ * @param bo 查询条件
+ * @return 社团成员列表
+ */
+ List queryList(StglMemberBo bo);
+
+ /**
+ * 新增社团成员
+ *
+ * @param bo 社团成员
+ * @return 是否新增成功
+ */
+ Boolean insertByBo(StglMemberBo bo);
+
+ /**
+ * 修改社团成员
+ *
+ * @param bo 社团成员
+ * @return 是否修改成功
+ */
+ Boolean updateByBo(StglMemberBo bo);
+
+ /**
+ * 校验并批量删除社团成员
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ Boolean deleteWithValidByIds(Collection ids, Boolean isValid);
+
+ /**
+ * 根据社团ID查询成员列表
+ *
+ * @param clubId 社团ID
+ * @return 成员列表
+ */
+ List queryByClubId(Long clubId);
+
+ /**
+ * 根据用户ID查询参与的社团列表
+ *
+ * @param userId 用户ID
+ * @return 社团成员关系列表
+ */
+ List queryByUserId(Long userId);
+
+ /**
+ * 检查用户是否已是某个社团/队伍的成员
+ *
+ * @param memberType 成员类型
+ * @param relationId 关联ID
+ * @param userId 用户ID
+ * @return 是否已是成员
+ */
+ Boolean checkMemberExists(Integer memberType, Long relationId, Long userId);
+
+ /**
+ * 批量添加成员
+ *
+ * @param memberType 成员类型
+ * @param relationId 关联ID
+ * @param userIds 用户ID列表
+ * @param roleType 角色类型
+ * @return 是否添加成功
+ */
+ Boolean batchAddMembers(Integer memberType, Long relationId, List userIds, Integer roleType);
+
+ /**
+ * 更新成员角色
+ *
+ * @param id 成员ID
+ * @param roleType 角色类型
+ * @return 是否更新成功
+ */
+ Boolean updateMemberRole(Long id, Integer roleType);
+
+ /**
+ * 退出社团/队伍
+ *
+ * @param id 成员ID
+ * @return 是否退出成功
+ */
+ Boolean quitMember(Long id);
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubRegisterServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubRegisterServiceImpl.java
new file mode 100644
index 00000000..7fe1c67f
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubRegisterServiceImpl.java
@@ -0,0 +1,306 @@
+package org.dromara.stgl.service.impl;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.dromara.stgl.domain.vo.StglClubRegisterVo;
+import org.dromara.stgl.domain.StglClub;
+import org.dromara.stgl.domain.StglMember;
+import org.dromara.stgl.mapper.StglClubMapper;
+import org.dromara.stgl.mapper.StglMemberMapper;
+import org.dromara.stgl.service.IStglClubRegisterService;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.util.List;
+import java.util.Date;
+import java.util.stream.Collectors;
+
+/**
+ * 社团报名大厅Service业务层处理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@RequiredArgsConstructor
+@Service
+public class StglClubRegisterServiceImpl implements IStglClubRegisterService {
+
+ private final StglClubMapper clubMapper;
+ private final StglMemberMapper memberMapper;
+
+ /**
+ * 分页查询社团报名大厅列表
+ *
+ * @param clubType 社团类型
+ * @param clubName 社团名称
+ * @param pageQuery 分页参数
+ * @return 社团报名大厅分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(String clubType, String clubName, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(clubType, clubName);
+ Page page = clubMapper.selectPage(pageQuery.build(), lqw);
+
+ List voList = convertToRegisterVo(page.getRecords());
+
+ Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+ result.setRecords(voList);
+
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询社团报名大厅列表
+ *
+ * @param clubType 社团类型
+ * @param clubName 社团名称
+ * @return 社团报名大厅列表
+ */
+ @Override
+ public List queryList(String clubType, String clubName) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(clubType, clubName);
+ List clubs = clubMapper.selectList(lqw);
+ return convertToRegisterVo(clubs);
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(String clubType, String clubName) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getStatus, 1); // 只查询正常状态的社团
+ lqw.eq(StringUtils.isNotBlank(clubType), StglClub::getClubType, clubType);
+ lqw.like(StringUtils.isNotBlank(clubName), StglClub::getClubName, clubName);
+ lqw.orderByDesc(StglClub::getCreateTime);
+ return lqw;
+ }
+
+ private List convertToRegisterVo(List clubs) {
+ Long currentUserId = null;
+ try {
+ currentUserId = LoginHelper.getUserId();
+ } catch (Exception e) {
+ // 未登录用户
+ }
+
+ final Long userId = currentUserId;
+
+ return clubs.stream().map(club -> {
+ StglClubRegisterVo vo = new StglClubRegisterVo();
+ vo.setId(club.getId());
+ vo.setClubName(club.getClubName());
+ vo.setClubType(club.getClubType());
+ vo.setEstablishDate(club.getEstablishDate());
+ vo.setDescription(club.getDescription());
+ vo.setLogoUrl(club.getLogoUrl());
+ vo.setPresidentName(club.getPresidentName());
+ vo.setPresidentDept(club.getPresidentDept());
+ vo.setAdvisorName(club.getAdvisorName());
+ vo.setAdvisorDept(club.getAdvisorDept());
+ vo.setMemberLimit(club.getMemberLimit());
+ vo.setRecruitCycle(club.getRecruitCycle());
+ vo.setRecruitTime(club.getRecruitTime());
+ vo.setActivityFrequency(club.getActivityFrequency());
+
+ // 查询当前成员数量
+ LambdaQueryWrapper memberCountLqw = Wrappers.lambdaQuery();
+ memberCountLqw.eq(StglMember::getMemberType, 1);
+ memberCountLqw.eq(StglMember::getRelationId, club.getId());
+ memberCountLqw.eq(StglMember::getStatus, 1);
+ long memberCount = memberMapper.selectCount(memberCountLqw);
+ vo.setCurrentMemberCount((int) memberCount);
+
+ // 检查是否可报名
+ boolean canRegister = club.getMemberLimit() == null || club.getMemberLimit() == 0 || memberCount < club.getMemberLimit();
+ vo.setCanRegister(canRegister);
+
+ // 检查当前用户是否已报名
+ if (userId != null) {
+ LambdaQueryWrapper userMemberLqw = Wrappers.lambdaQuery();
+ userMemberLqw.eq(StglMember::getMemberType, 1);
+ userMemberLqw.eq(StglMember::getRelationId, club.getId());
+ userMemberLqw.eq(StglMember::getUserId, userId);
+ userMemberLqw.eq(StglMember::getStatus, 1);
+ long registeredCount = memberMapper.selectCount(userMemberLqw);
+ vo.setHasRegistered(registeredCount > 0);
+ vo.setRegistrationStatus(registeredCount > 0 ? 1 : 0);
+ } else {
+ vo.setHasRegistered(false);
+ vo.setRegistrationStatus(0);
+ }
+
+ return vo;
+ }).collect(Collectors.toList());
+ }
+
+ /**
+ * 查询社团详细信息(用于报名)
+ *
+ * @param clubId 社团ID
+ * @return 社团详细信息
+ */
+ @Override
+ public StglClubRegisterVo queryClubDetail(Long clubId) {
+ StglClub club = clubMapper.selectById(clubId);
+ if (club == null) {
+ return null;
+ }
+ return convertToRegisterVo(List.of(club)).get(0);
+ }
+
+ /**
+ * 报名加入社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否报名成功
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean registerClub(Long clubId, Long userId) {
+ // 检查社团是否存在
+ StglClub club = clubMapper.selectById(clubId);
+ if (club == null || club.getStatus() != 1) {
+ throw new RuntimeException("社团不存在或已停用");
+ }
+
+ // 检查是否已是成员
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, 1);
+ lqw.eq(StglMember::getRelationId, clubId);
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+ long count = memberMapper.selectCount(lqw);
+ if (count > 0) {
+ throw new RuntimeException("您已是该社团的成员");
+ }
+
+ // 检查成员上限
+ if (club.getMemberLimit() != null && club.getMemberLimit() > 0) {
+ LambdaQueryWrapper memberLqw = Wrappers.lambdaQuery();
+ memberLqw.eq(StglMember::getMemberType, 1);
+ memberLqw.eq(StglMember::getRelationId, clubId);
+ memberLqw.eq(StglMember::getStatus, 1);
+ long memberCount = memberMapper.selectCount(memberLqw);
+ if (memberCount >= club.getMemberLimit()) {
+ throw new RuntimeException("该社团成员已满,无法加入");
+ }
+ }
+
+ // 创建成员记录
+ StglMember member = new StglMember();
+ member.setMemberType(1); // 社团成员
+ member.setRelationId(clubId);
+ member.setUserId(userId);
+ member.setRoleType(3); // 普通成员
+ member.setJoinTime(new Date());
+ member.setStatus(1);
+
+ return memberMapper.insert(member) > 0;
+ }
+
+ /**
+ * 退出社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否退出成功
+ */
+ @Override
+ public Boolean quitClub(Long clubId, Long userId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, 1);
+ lqw.eq(StglMember::getRelationId, clubId);
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+
+ StglMember member = memberMapper.selectOne(lqw);
+ if (member == null) {
+ throw new RuntimeException("您不是该社团的成员");
+ }
+
+ // 检查是否是社长或副社长
+ if (member.getRoleType() != null && member.getRoleType() <= 2) {
+ throw new RuntimeException("社长和副社长不能直接退出,请先转让职务");
+ }
+
+ member.setStatus(0);
+ return memberMapper.updateById(member) > 0;
+ }
+
+ /**
+ * 查询我参与的社团列表
+ *
+ * @param userId 用户ID
+ * @return 社团列表
+ */
+ @Override
+ public List queryMyClubs(Long userId) {
+ // 查询用户参与的社团ID
+ LambdaQueryWrapper memberLqw = Wrappers.lambdaQuery();
+ memberLqw.eq(StglMember::getMemberType, 1);
+ memberLqw.eq(StglMember::getUserId, userId);
+ memberLqw.eq(StglMember::getStatus, 1);
+ List members = memberMapper.selectList(memberLqw);
+
+ if (members.isEmpty()) {
+ return List.of();
+ }
+
+ List clubIds = members.stream().map(StglMember::getRelationId).collect(Collectors.toList());
+
+ // 查询社团信息
+ LambdaQueryWrapper clubLqw = Wrappers.lambdaQuery();
+ clubLqw.in(StglClub::getId, clubIds);
+ clubLqw.eq(StglClub::getStatus, 1);
+ clubLqw.orderByDesc(StglClub::getCreateTime);
+ List clubs = clubMapper.selectList(clubLqw);
+
+ return convertToRegisterVo(clubs);
+ }
+
+ /**
+ * 检查是否已报名某个社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否已报名
+ */
+ @Override
+ public Boolean checkRegistered(Long clubId, Long userId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, 1);
+ lqw.eq(StglMember::getRelationId, clubId);
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+ long count = memberMapper.selectCount(lqw);
+ return count > 0;
+ }
+
+ /**
+ * 根据社团类型查询推荐社团
+ *
+ * @param clubType 社团类型
+ * @param limit 推荐数量
+ * @return 推荐社团列表
+ */
+ @Override
+ public List queryRecommendClubs(String clubType, Integer limit) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getStatus, 1);
+ if (StringUtils.isNotBlank(clubType)) {
+ lqw.eq(StglClub::getClubType, clubType);
+ }
+ lqw.orderByDesc(StglClub::getCreateTime);
+ if (limit != null && limit > 0) {
+ lqw.last("LIMIT " + limit);
+ }
+
+ List clubs = clubMapper.selectList(lqw);
+ return convertToRegisterVo(clubs);
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java
new file mode 100644
index 00000000..b7098b14
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java
@@ -0,0 +1,233 @@
+package org.dromara.stgl.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.dromara.stgl.domain.bo.StglClubBo;
+import org.dromara.stgl.domain.vo.StglClubVo;
+import org.dromara.stgl.domain.StglClub;
+import org.dromara.stgl.mapper.StglClubMapper;
+import org.dromara.stgl.service.IStglClubService;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 社团信息Service业务层处理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@RequiredArgsConstructor
+@Service
+public class StglClubServiceImpl implements IStglClubService {
+
+ private final StglClubMapper baseMapper;
+
+ /**
+ * 查询社团信息
+ *
+ * @param id 主键
+ * @return 社团信息
+ */
+ @Override
+ public StglClubVo queryById(Long id){
+ return baseMapper.selectVoById(id);
+ }
+
+ /**
+ * 分页查询社团信息列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return 社团信息分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(StglClubBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ Page result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询符合条件的社团信息列表
+ *
+ * @param bo 查询条件
+ * @return 社团信息列表
+ */
+ @Override
+ public List queryList(StglClubBo bo) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(StglClubBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getClubName()), StglClub::getClubName, bo.getClubName());
+ lqw.eq(StringUtils.isNotBlank(bo.getClubType()), StglClub::getClubType, bo.getClubType());
+ lqw.like(StringUtils.isNotBlank(bo.getPresidentName()), StglClub::getPresidentName, bo.getPresidentName());
+ lqw.like(StringUtils.isNotBlank(bo.getPresidentStudentNo()), StglClub::getPresidentStudentNo, bo.getPresidentStudentNo());
+ lqw.like(StringUtils.isNotBlank(bo.getAdvisorName()), StglClub::getAdvisorName, bo.getAdvisorName());
+ lqw.eq(bo.getStatus() != null, StglClub::getStatus, bo.getStatus());
+ lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
+ StglClub::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
+ lqw.orderByDesc(StglClub::getCreateTime);
+ return lqw;
+ }
+
+ /**
+ * 新增社团信息
+ *
+ * @param bo 社团信息
+ * @return 是否新增成功
+ */
+ @Override
+ public Boolean insertByBo(StglClubBo bo) {
+ StglClub add = MapstructUtils.convert(bo, StglClub.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
+ }
+ return flag;
+ }
+
+ /**
+ * 修改社团信息
+ *
+ * @param bo 社团信息
+ * @return 是否修改成功
+ */
+ @Override
+ public Boolean updateByBo(StglClubBo bo) {
+ StglClub update = MapstructUtils.convert(bo, StglClub.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(StglClub entity){
+ // 检查社团名称是否重复
+ if (StringUtils.isNotBlank(entity.getClubName())) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getClubName, entity.getClubName());
+ if (entity.getId() != null) {
+ lqw.ne(StglClub::getId, entity.getId());
+ }
+ long count = baseMapper.selectCount(lqw);
+ if (count > 0) {
+ throw new RuntimeException("社团名称已存在,请重新输入");
+ }
+ }
+
+ // 设置默认状态
+ if (entity.getStatus() == null) {
+ entity.setStatus(1);
+ }
+ }
+
+ /**
+ * 校验并批量删除社团信息
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ // 可以添加删除前的业务校验
+ }
+ return baseMapper.deleteBatchIds(ids) > 0;
+ }
+
+ /**
+ * 查询可用的社团选项
+ *
+ * @return 社团选项列表
+ */
+ @Override
+ public List queryAvailableOptions() {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getStatus, 1);
+ lqw.select(StglClub::getId, StglClub::getClubName, StglClub::getClubType);
+ lqw.orderByAsc(StglClub::getClubName);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 根据社团类型查询社团列表
+ *
+ * @param clubType 社团类型
+ * @return 社团列表
+ */
+ @Override
+ public List queryByClubType(String clubType) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getClubType, clubType);
+ lqw.eq(StglClub::getStatus, 1);
+ lqw.orderByAsc(StglClub::getClubName);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 查询我管理的社团列表
+ *
+ * @return 社团列表
+ */
+ @Override
+ public List queryMyClubs() {
+ // 根据当前用户查询其管理的社团
+ Long userId = LoginHelper.getUserId();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getCreateBy, userId);
+ lqw.eq(StglClub::getStatus, 1);
+ lqw.orderByDesc(StglClub::getCreateTime);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 检查社团名称是否重复
+ *
+ * @param clubName 社团名称
+ * @param excludeId 排除的ID(修改时使用)
+ * @return 是否重复
+ */
+ @Override
+ public Boolean checkClubNameUnique(String clubName, Long excludeId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getClubName, clubName);
+ if (excludeId != null) {
+ lqw.ne(StglClub::getId, excludeId);
+ }
+ long count = baseMapper.selectCount(lqw);
+ return count == 0;
+ }
+
+ /**
+ * 启用/禁用社团
+ *
+ * @param id 社团ID
+ * @param status 状态
+ * @return 是否操作成功
+ */
+ @Override
+ public Boolean changeStatus(Long id, Integer status) {
+ StglClub club = new StglClub();
+ club.setId(id);
+ club.setStatus(status);
+ return baseMapper.updateById(club) > 0;
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMemberServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMemberServiceImpl.java
new file mode 100644
index 00000000..e6001ae7
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMemberServiceImpl.java
@@ -0,0 +1,272 @@
+package org.dromara.stgl.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.dromara.stgl.domain.bo.StglMemberBo;
+import org.dromara.stgl.domain.vo.StglMemberVo;
+import org.dromara.stgl.domain.StglMember;
+import org.dromara.stgl.mapper.StglMemberMapper;
+import org.dromara.stgl.service.IStglMemberService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+import java.util.Date;
+import java.util.stream.Collectors;
+
+/**
+ * 社团成员Service业务层处理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@RequiredArgsConstructor
+@Service
+public class StglMemberServiceImpl implements IStglMemberService {
+
+ private final StglMemberMapper baseMapper;
+
+ /**
+ * 查询社团成员
+ *
+ * @param id 主键
+ * @return 社团成员
+ */
+ @Override
+ public StglMemberVo queryById(Long id){
+ return baseMapper.selectVoById(id);
+ }
+
+ /**
+ * 分页查询社团成员列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return 社团成员分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(StglMemberBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ Page result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询符合条件的社团成员列表
+ *
+ * @param bo 查询条件
+ * @return 社团成员列表
+ */
+ @Override
+ public List queryList(StglMemberBo bo) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(StglMemberBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(bo.getMemberType() != null, StglMember::getMemberType, bo.getMemberType());
+ lqw.eq(bo.getRelationId() != null, StglMember::getRelationId, bo.getRelationId());
+ lqw.eq(bo.getUserId() != null, StglMember::getUserId, bo.getUserId());
+ lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), StglMember::getStudentNo, bo.getStudentNo());
+ lqw.like(StringUtils.isNotBlank(bo.getStudentName()), StglMember::getStudentName, bo.getStudentName());
+ lqw.eq(bo.getRoleType() != null, StglMember::getRoleType, bo.getRoleType());
+ lqw.eq(bo.getStatus() != null, StglMember::getStatus, bo.getStatus());
+ lqw.between(params.get("beginJoinTime") != null && params.get("endJoinTime") != null,
+ StglMember::getJoinTime, params.get("beginJoinTime"), params.get("endJoinTime"));
+ lqw.orderByDesc(StglMember::getJoinTime);
+ return lqw;
+ }
+
+ /**
+ * 新增社团成员
+ *
+ * @param bo 社团成员
+ * @return 是否新增成功
+ */
+ @Override
+ public Boolean insertByBo(StglMemberBo bo) {
+ StglMember add = MapstructUtils.convert(bo, StglMember.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
+ }
+ return flag;
+ }
+
+ /**
+ * 修改社团成员
+ *
+ * @param bo 社团成员
+ * @return 是否修改成功
+ */
+ @Override
+ public Boolean updateByBo(StglMemberBo bo) {
+ StglMember update = MapstructUtils.convert(bo, StglMember.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(StglMember entity){
+ // 检查用户是否已是成员
+ if (entity.getMemberType() != null && entity.getRelationId() != null && entity.getUserId() != null) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, entity.getMemberType());
+ lqw.eq(StglMember::getRelationId, entity.getRelationId());
+ lqw.eq(StglMember::getUserId, entity.getUserId());
+ if (entity.getId() != null) {
+ lqw.ne(StglMember::getId, entity.getId());
+ }
+ long count = baseMapper.selectCount(lqw);
+ if (count > 0) {
+ throw new RuntimeException("用户已是该组织的成员,不能重复添加");
+ }
+ }
+
+ // 设置默认值
+ if (entity.getJoinTime() == null) {
+ entity.setJoinTime(new Date());
+ }
+ if (entity.getStatus() == null) {
+ entity.setStatus(1);
+ }
+ if (entity.getRoleType() == null) {
+ entity.setRoleType(3); // 默认为普通成员
+ }
+ }
+
+ /**
+ * 校验并批量删除社团成员
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ // 可以添加删除前的业务校验
+ }
+ return baseMapper.deleteBatchIds(ids) > 0;
+ }
+
+ /**
+ * 根据社团ID查询成员列表
+ *
+ * @param clubId 社团ID
+ * @return 成员列表
+ */
+ @Override
+ public List queryByClubId(Long clubId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, 1); // 社团成员
+ lqw.eq(StglMember::getRelationId, clubId);
+ lqw.eq(StglMember::getStatus, 1);
+ lqw.orderByAsc(StglMember::getRoleType).orderByAsc(StglMember::getJoinTime);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 根据用户ID查询参与的社团列表
+ *
+ * @param userId 用户ID
+ * @return 社团成员关系列表
+ */
+ @Override
+ public List queryByUserId(Long userId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+ lqw.orderByDesc(StglMember::getJoinTime);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 检查用户是否已是某个社团/队伍的成员
+ *
+ * @param memberType 成员类型
+ * @param relationId 关联ID
+ * @param userId 用户ID
+ * @return 是否已是成员
+ */
+ @Override
+ public Boolean checkMemberExists(Integer memberType, Long relationId, Long userId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, memberType);
+ lqw.eq(StglMember::getRelationId, relationId);
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+ long count = baseMapper.selectCount(lqw);
+ return count > 0;
+ }
+
+ /**
+ * 批量添加成员
+ *
+ * @param memberType 成员类型
+ * @param relationId 关联ID
+ * @param userIds 用户ID列表
+ * @param roleType 角色类型
+ * @return 是否添加成功
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean batchAddMembers(Integer memberType, Long relationId, List userIds, Integer roleType) {
+ Date now = new Date();
+ List members = userIds.stream().map(userId -> {
+ StglMember member = new StglMember();
+ member.setMemberType(memberType);
+ member.setRelationId(relationId);
+ member.setUserId(userId);
+ member.setRoleType(roleType != null ? roleType : 3);
+ member.setJoinTime(now);
+ member.setStatus(1);
+ return member;
+ }).collect(Collectors.toList());
+
+ return baseMapper.insertBatch(members);
+ }
+
+ /**
+ * 更新成员角色
+ *
+ * @param id 成员ID
+ * @param roleType 角色类型
+ * @return 是否更新成功
+ */
+ @Override
+ public Boolean updateMemberRole(Long id, Integer roleType) {
+ StglMember member = new StglMember();
+ member.setId(id);
+ member.setRoleType(roleType);
+ return baseMapper.updateById(member) > 0;
+ }
+
+ /**
+ * 退出社团/队伍
+ *
+ * @param id 成员ID
+ * @return 是否退出成功
+ */
+ @Override
+ public Boolean quitMember(Long id) {
+ StglMember member = new StglMember();
+ member.setId(id);
+ member.setStatus(0);
+ return baseMapper.updateById(member) > 0;
+ }
+
+}
\ No newline at end of file
diff --git a/docs/开发文档/API/API文档总览.md b/docs/开发文档/API/API文档总览.md
index 04150e06..d2d63f81 100644
--- a/docs/开发文档/API/API文档总览.md
+++ b/docs/开发文档/API/API文档总览.md
@@ -6,9 +6,7 @@
请参考 [API文档基础规范](./API文档基础规范.md) 了解通用的API调用规范、认证方式和响应格式。
-<<<<<<< HEAD
## API模块列表
-=======
- [学生信息管理API](./学生信息管理API.md)
- [学生信息收集批次管理API](./学生信息收集批次管理API.md)
- [班级信息管理API](./班级信息管理API.md)
@@ -17,7 +15,6 @@
- [宿舍管理员信息API](./宿舍管理员信息API.md)
- [宿舍管理员授权API](./宿舍管理员授权API.md)
- [学生管理员授权关系API](./学生管理员授权关系API.md)
->>>>>>> e450a8ffe73adab129a8f940bff3316c0361aa7a
### 1. 用户认证登录
@@ -41,9 +38,9 @@
[心理健康测评API](./心理健康测评API.md) - 包含测评问卷管理、测评结果分析等接口。
-<<<<<<< HEAD
+
### 4. 统计报表
-=======
+
## 宿舍管理员信息API
- [宿舍管理员信息API](./宿舍管理员信息API.md):包含宿舍管理员信息的增删改查接口
@@ -68,7 +65,7 @@
**详细文档**: [宿舍管理员信息API.md](./宿舍管理员信息API.md)
## 企业信息管理API
->>>>>>> e450a8ffe73adab129a8f940bff3316c0361aa7a
+
[统计报表API](./统计报表API.md) - 包含各类统计数据查询接口。
@@ -89,13 +86,13 @@
## 更新历史
-<<<<<<< HEAD
+
| 日期 | 版本 | 更新内容 | 更新人 |
|------|------|---------|-------|
| 2025-07-01 | 1.0 | 初始版本 | 系统管理员 |
| 2025-07-15 | 1.1 | 添加心理健康测评API | 张工 |
| 2025-07-30 | 1.2 | 添加预约限制功能API | Cursor |
-=======
+
**详细文档**: [企业信息管理API.md](./企业信息管理API.md)
- [获取当前正在教学的班主任信息](/docs/开发文档/API/宿舍管理员信息API.md#获取当前正在教学的班主任信息)
@@ -144,4 +141,4 @@
| [班级干部信息管理API](./班级干部信息管理API.md) | 班级干部信息管理(含用户角色关联) | ✅ 完成 | 2025-01-27 |
| [班干部管理API](./班干部管理API.md) | 班级干部职务管理 | ✅ 完成 | 2025-01-27 |
| [班级教师关联管理API](./班级教师关联管理API.md) | 班主任辅导员关联关系 | ✅ 完成 | 2025-01-27 |
->>>>>>> e450a8ffe73adab129a8f940bff3316c0361aa7a
+
diff --git a/docs/开发文档/API/社团管理API.md b/docs/开发文档/API/社团管理API.md
new file mode 100644
index 00000000..1b56b8e6
--- /dev/null
+++ b/docs/开发文档/API/社团管理API.md
@@ -0,0 +1,962 @@
+# 社团管理API文档
+
+## 📋 文档信息
+
+- **模块名称**: 社团管理系统
+- **接口前缀**: `/stgl`
+- **版本**: v1.0
+- **创建时间**: 2025-01-15
+- **作者**: Cursor AI
+- **权限验证**: Sa-Token
+
+## 🎯 功能概述
+
+社团管理系统提供完整的校园社团管理功能,包括:
+
+1. **社团信息管理** - 社团基本信息的增删改查
+2. **社团成员管理** - 成员关系和角色管理
+3. **社团报名大厅** - 学生浏览和报名社团功能
+
+## 🔐 权限说明
+
+### 权限前缀
+- `stgl:club:*` - 社团信息管理权限
+- `stgl:member:*` - 社团成员管理权限
+- `stgl:register:*` - 社团报名大厅权限
+
+### 角色说明
+- **系统管理员**: 拥有所有社团管理权限
+- **社长**: 拥有所管理社团的完整权限
+- **副社长**: 拥有所管理社团的部分权限
+- **学生**: 拥有浏览和报名权限
+
+## 📊 数据模型
+
+### 社团信息表 (stgl_club)
+```sql
+CREATE TABLE `stgl_club` (
+ `id` bigint(20) NOT NULL COMMENT '主键ID',
+ `tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
+ `club_name` varchar(100) NOT NULL COMMENT '社团名称',
+ `club_type` varchar(50) NOT NULL COMMENT '社团类型',
+ `establish_date` date COMMENT '成立日期',
+ `description` text COMMENT '社团简介',
+ `president_name` varchar(50) COMMENT '社长姓名',
+ `member_limit` int DEFAULT 0 COMMENT '成员规模上限',
+ `status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-停用)',
+ -- 其他字段...
+);
+```
+
+### 成员关系表 (stgl_member)
+```sql
+CREATE TABLE `stgl_member` (
+ `id` bigint(20) NOT NULL COMMENT '主键ID',
+ `member_type` tinyint NOT NULL COMMENT '成员类型(1-社团成员 2-志愿者)',
+ `relation_id` bigint(20) NOT NULL COMMENT '关联ID(社团ID或志愿者队伍ID)',
+ `user_id` bigint(20) NOT NULL COMMENT '用户ID',
+ `student_no` varchar(20) COMMENT '学号',
+ `student_name` varchar(50) COMMENT '学生姓名',
+ `role_type` tinyint COMMENT '角色类型(1-社长 2-副社长 3-成员)',
+ `status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-退出)',
+ -- 其他字段...
+);
+```
+
+## 📋 API接口列表
+
+---
+
+## 1. 社团信息管理
+
+### 1.1 查询社团列表
+
+**接口地址**: `GET /stgl/club/list`
+
+**权限要求**: `stgl:club:list`
+
+**请求参数**:
+```json
+{
+ "pageNum": 1,
+ "pageSize": 10,
+ "clubName": "社团名称(模糊查询)",
+ "clubType": "社团类型",
+ "presidentName": "社长姓名(模糊查询)",
+ "status": 1,
+ "beginCreateTime": "2025-01-01",
+ "endCreateTime": "2025-12-31"
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": {
+ "total": 100,
+ "rows": [
+ {
+ "id": 1,
+ "clubName": "智能科技社团",
+ "clubType": "学术科技类",
+ "establishDate": "2025-01-15",
+ "description": "专注于人工智能、机器学习等前沿科技领域...",
+ "presidentName": "张三",
+ "presidentDept": "计算机学院",
+ "memberLimit": 50,
+ "status": 1,
+ "createTime": "2025-01-15 10:00:00"
+ }
+ ]
+ }
+}
+```
+
+### 1.2 新增社团
+
+**接口地址**: `POST /stgl/club`
+
+**权限要求**: `stgl:club:add`
+
+**请求参数**:
+```json
+{
+ "clubName": "智能科技社团",
+ "clubType": "学术科技类",
+ "establishDate": "2025-01-15",
+ "description": "专注于人工智能、机器学习等前沿科技领域的学术研究和实践交流社团",
+ "email": "aitech@example.com",
+ "phone": "028-12345678",
+ "presidentName": "张三",
+ "presidentStudentNo": "20250001",
+ "presidentDept": "计算机学院",
+ "presidentMajor": "软件工程",
+ "presidentClass": "软工2501班",
+ "presidentPhone": "13800138001",
+ "advisorName": "李教授",
+ "advisorDept": "计算机学院",
+ "memberLimit": 50,
+ "recruitCycle": "每学期",
+ "recruitTime": "9月、3月",
+ "activityFrequency": "每周1-2次",
+ "fundingSources": "学校拨款,社团自筹",
+ "status": 1
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 1.3 查询社团详情
+
+**接口地址**: `GET /stgl/club/{id}`
+
+**权限要求**: `stgl:club:query`
+
+**路径参数**:
+- `id`: 社团ID
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": {
+ "id": 1,
+ "clubName": "智能科技社团",
+ "clubType": "学术科技类",
+ "establishDate": "2025-01-15",
+ "description": "专注于人工智能、机器学习等前沿科技领域...",
+ "email": "aitech@example.com",
+ "phone": "028-12345678",
+ "logoUrl": "/upload/club/logo/2025/01/15/xxx.jpg",
+ "presidentName": "张三",
+ "presidentStudentNo": "20250001",
+ "presidentDept": "计算机学院",
+ "presidentMajor": "软件工程",
+ "presidentClass": "软工2501班",
+ "presidentPhone": "13800138001",
+ "vicePresidentName": "李四",
+ "vicePresidentStudentNo": "20250002",
+ "advisorName": "李教授",
+ "advisorDept": "计算机学院",
+ "memberLimit": 50,
+ "departments": "技术部、宣传部、组织部",
+ "recruitCycle": "每学期",
+ "recruitTime": "9月、3月",
+ "activityFrequency": "每周1-2次",
+ "fundingSources": "学校拨款,社团自筹",
+ "charterUrl": "/upload/club/charter/2025/01/15/xxx.pdf",
+ "status": 1,
+ "createTime": "2025-01-15 10:00:00",
+ "updateTime": "2025-01-15 14:30:00"
+ }
+}
+```
+
+### 1.4 修改社团信息
+
+**接口地址**: `PUT /stgl/club`
+
+**权限要求**: `stgl:club:edit`
+
+**请求参数**: 同新增社团,需包含id字段
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 1.5 删除社团
+
+**接口地址**: `DELETE /stgl/club/{ids}`
+
+**权限要求**: `stgl:club:remove`
+
+**路径参数**:
+- `ids`: 社团ID数组,用逗号分隔
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 1.6 查询可用社团选项
+
+**接口地址**: `GET /stgl/club/options`
+
+**权限要求**: `stgl:club:list`
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": [
+ {
+ "id": 1,
+ "clubName": "智能科技社团",
+ "clubType": "学术科技类"
+ },
+ {
+ "id": 2,
+ "clubName": "文学社",
+ "clubType": "文化艺术类"
+ }
+ ]
+}
+```
+
+### 1.7 根据社团类型查询社团列表
+
+**接口地址**: `GET /stgl/club/listByType/{clubType}`
+
+**权限要求**: `stgl:club:list`
+
+**路径参数**:
+- `clubType`: 社团类型
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": [
+ {
+ "id": 1,
+ "clubName": "智能科技社团",
+ "clubType": "学术科技类",
+ "presidentName": "张三"
+ }
+ ]
+}
+```
+
+### 1.8 查询我管理的社团列表
+
+**接口地址**: `GET /stgl/club/myClubs`
+
+**权限要求**: `stgl:club:list`
+
+**返回数据**: 同上
+
+### 1.9 检查社团名称是否重复
+
+**接口地址**: `GET /stgl/club/checkClubNameUnique`
+
+**权限要求**: `stgl:club:list`
+
+**请求参数**:
+```json
+{
+ "clubName": "社团名称",
+ "excludeId": 1 // 可选,修改时排除的ID
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": true // true=唯一,false=重复
+}
+```
+
+### 1.10 启用/禁用社团
+
+**接口地址**: `PUT /stgl/club/changeStatus`
+
+**权限要求**: `stgl:club:edit`
+
+**请求参数**:
+```json
+{
+ "id": 1,
+ "status": 0 // 1-启用,0-禁用
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 1.11 导出社团信息
+
+**接口地址**: `POST /stgl/club/export`
+
+**权限要求**: `stgl:club:export`
+
+**请求参数**: 同查询列表的筛选条件
+
+**返回数据**: Excel文件流
+
+---
+
+## 2. 社团成员管理
+
+### 2.1 查询成员列表
+
+**接口地址**: `GET /stgl/member/list`
+
+**权限要求**: `stgl:member:list`
+
+**请求参数**:
+```json
+{
+ "pageNum": 1,
+ "pageSize": 10,
+ "memberType": 1, // 1-社团成员,2-志愿者
+ "relationId": 1, // 社团ID或志愿者队伍ID
+ "studentNo": "学号(模糊查询)",
+ "studentName": "学生姓名(模糊查询)",
+ "roleType": 3, // 1-社长,2-副社长,3-成员
+ "status": 1 // 1-正常,0-退出
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": {
+ "total": 50,
+ "rows": [
+ {
+ "id": 1,
+ "memberType": 1,
+ "memberTypeName": "社团成员",
+ "relationId": 1,
+ "relationName": "智能科技社团",
+ "userId": 1001,
+ "studentNo": "20250002",
+ "studentName": "李四",
+ "roleType": 3,
+ "roleTypeName": "成员",
+ "joinTime": "2025-01-15 14:30:00",
+ "status": 1,
+ "statusName": "正常"
+ }
+ ]
+ }
+}
+```
+
+### 2.2 新增成员
+
+**接口地址**: `POST /stgl/member`
+
+**权限要求**: `stgl:member:add`
+
+**请求参数**:
+```json
+{
+ "memberType": 1, // 1-社团成员,2-志愿者
+ "relationId": 1, // 社团ID或志愿者队伍ID
+ "userId": 1001, // 用户ID
+ "studentNo": "20250002",
+ "studentName": "李四",
+ "roleType": 3, // 1-社长,2-副社长,3-成员
+ "status": 1
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 2.3 查询成员详情
+
+**接口地址**: `GET /stgl/member/{id}`
+
+**权限要求**: `stgl:member:query`
+
+**路径参数**:
+- `id`: 成员ID
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": {
+ "id": 1,
+ "memberType": 1,
+ "relationId": 1,
+ "userId": 1001,
+ "studentNo": "20250002",
+ "studentName": "李四",
+ "roleType": 3,
+ "joinTime": "2025-01-15 14:30:00",
+ "status": 1,
+ "remark": "表现优秀"
+ }
+}
+```
+
+### 2.4 修改成员信息
+
+**接口地址**: `PUT /stgl/member`
+
+**权限要求**: `stgl:member:edit`
+
+**请求参数**: 同新增成员,需包含id字段
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 2.5 删除成员
+
+**接口地址**: `DELETE /stgl/member/{ids}`
+
+**权限要求**: `stgl:member:remove`
+
+**路径参数**:
+- `ids`: 成员ID数组,用逗号分隔
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 2.6 根据社团ID查询成员列表
+
+**接口地址**: `GET /stgl/member/club/{clubId}`
+
+**权限要求**: `stgl:member:list`
+
+**路径参数**:
+- `clubId`: 社团ID
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": [
+ {
+ "id": 1,
+ "studentName": "张三",
+ "studentNo": "20250001",
+ "roleType": 1,
+ "roleTypeName": "社长",
+ "joinTime": "2025-01-01 10:00:00"
+ },
+ {
+ "id": 2,
+ "studentName": "李四",
+ "studentNo": "20250002",
+ "roleType": 3,
+ "roleTypeName": "成员",
+ "joinTime": "2025-01-15 14:30:00"
+ }
+ ]
+}
+```
+
+### 2.7 根据用户ID查询参与的社团列表
+
+**接口地址**: `GET /stgl/member/user/{userId}`
+
+**权限要求**: `stgl:member:list`
+
+**路径参数**:
+- `userId`: 用户ID
+
+**返回数据**: 同上
+
+### 2.8 检查用户是否已是某个社团/队伍的成员
+
+**接口地址**: `GET /stgl/member/checkMemberExists`
+
+**权限要求**: `stgl:member:list`
+
+**请求参数**:
+```json
+{
+ "memberType": 1, // 成员类型
+ "relationId": 1, // 关联ID
+ "userId": 1001 // 用户ID
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": true // true=已是成员,false=不是成员
+}
+```
+
+### 2.9 批量添加成员
+
+**接口地址**: `POST /stgl/member/batchAdd`
+
+**权限要求**: `stgl:member:add`
+
+**请求参数**:
+```json
+{
+ "memberType": 1,
+ "relationId": 1,
+ "userIds": [1002, 1003, 1004],
+ "roleType": 3
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 2.10 更新成员角色
+
+**接口地址**: `PUT /stgl/member/updateRole`
+
+**权限要求**: `stgl:member:edit`
+
+**请求参数**:
+```json
+{
+ "id": 1,
+ "roleType": 2 // 新角色类型
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 2.11 退出社团/队伍
+
+**接口地址**: `PUT /stgl/member/quit/{id}`
+
+**权限要求**: `stgl:member:edit`
+
+**路径参数**:
+- `id`: 成员ID
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功"
+}
+```
+
+### 2.12 导出成员信息
+
+**接口地址**: `POST /stgl/member/export`
+
+**权限要求**: `stgl:member:export`
+
+**请求参数**: 同查询列表的筛选条件
+
+**返回数据**: Excel文件流
+
+---
+
+## 3. 社团报名大厅
+
+### 3.1 查询社团报名大厅列表
+
+**接口地址**: `GET /stgl/register/list`
+
+**权限要求**: 无(公开接口)
+
+**请求参数**:
+```json
+{
+ "pageNum": 1,
+ "pageSize": 10,
+ "clubType": "学术科技类",
+ "clubName": "社团名称(模糊查询)"
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": {
+ "total": 20,
+ "rows": [
+ {
+ "id": 1,
+ "clubName": "智能科技社团",
+ "clubType": "学术科技类",
+ "establishDate": "2025-01-15",
+ "description": "专注于人工智能、机器学习等前沿科技领域...",
+ "logoUrl": "/upload/club/logo/2025/01/15/xxx.jpg",
+ "presidentName": "张三",
+ "presidentDept": "计算机学院",
+ "advisorName": "李教授",
+ "advisorDept": "计算机学院",
+ "memberLimit": 50,
+ "currentMemberCount": 25,
+ "recruitCycle": "每学期",
+ "recruitTime": "9月、3月",
+ "activityFrequency": "每周1-2次",
+ "hasRegistered": false, // 当前用户是否已报名
+ "registrationStatus": 0, // 报名状态
+ "canRegister": true // 是否可报名
+ }
+ ]
+ }
+}
+```
+
+### 3.2 查询社团报名大厅列表(不分页)
+
+**接口地址**: `GET /stgl/register/listAll`
+
+**权限要求**: 无(公开接口)
+
+**请求参数**:
+```json
+{
+ "clubType": "学术科技类",
+ "clubName": "社团名称(模糊查询)"
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": [
+ // 社团列表数据,同上
+ ]
+}
+```
+
+### 3.3 查询社团详细信息
+
+**接口地址**: `GET /stgl/register/{clubId}`
+
+**权限要求**: 无(公开接口)
+
+**路径参数**:
+- `clubId`: 社团ID
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": {
+ "id": 1,
+ "clubName": "智能科技社团",
+ "clubType": "学术科技类",
+ "establishDate": "2025-01-15",
+ "description": "专注于人工智能、机器学习等前沿科技领域的学术研究和实践交流社团",
+ "logoUrl": "/upload/club/logo/2025/01/15/xxx.jpg",
+ "presidentName": "张三",
+ "presidentDept": "计算机学院",
+ "advisorName": "李教授",
+ "advisorDept": "计算机学院",
+ "memberLimit": 50,
+ "currentMemberCount": 25,
+ "recruitCycle": "每学期",
+ "recruitTime": "9月、3月",
+ "activityFrequency": "每周1-2次",
+ "hasRegistered": false,
+ "registrationStatus": 0,
+ "canRegister": true
+ }
+}
+```
+
+### 3.4 报名加入社团
+
+**接口地址**: `POST /stgl/register/join/{clubId}`
+
+**权限要求**: `stgl:register:join`
+
+**路径参数**:
+- `clubId`: 社团ID
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "报名成功,欢迎加入社团!"
+}
+```
+
+**异常情况**:
+```json
+{
+ "code": 500,
+ "msg": "您已是该社团的成员"
+}
+{
+ "code": 500,
+ "msg": "该社团成员已满,无法加入"
+}
+{
+ "code": 500,
+ "msg": "社团不存在或已停用"
+}
+```
+
+### 3.5 退出社团
+
+**接口地址**: `POST /stgl/register/quit/{clubId}`
+
+**权限要求**: `stgl:register:quit`
+
+**路径参数**:
+- `clubId`: 社团ID
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "退出社团成功"
+}
+```
+
+**异常情况**:
+```json
+{
+ "code": 500,
+ "msg": "您不是该社团的成员"
+}
+{
+ "code": 500,
+ "msg": "社长和副社长不能直接退出,请先转让职务"
+}
+```
+
+### 3.6 查询我参与的社团列表
+
+**接口地址**: `GET /stgl/register/myClubs`
+
+**权限要求**: `stgl:register:list`
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": [
+ {
+ "id": 1,
+ "clubName": "智能科技社团",
+ "clubType": "学术科技类",
+ "presidentName": "张三",
+ "currentMemberCount": 25,
+ "memberLimit": 50,
+ "hasRegistered": true,
+ "registrationStatus": 1
+ }
+ ]
+}
+```
+
+### 3.7 检查是否已报名某个社团
+
+**接口地址**: `GET /stgl/register/checkRegistered/{clubId}`
+
+**权限要求**: 无(公开接口)
+
+**路径参数**:
+- `clubId`: 社团ID
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": true // true=已报名,false=未报名
+}
+```
+
+### 3.8 查询推荐社团
+
+**接口地址**: `GET /stgl/register/recommend`
+
+**权限要求**: 无(公开接口)
+
+**请求参数**:
+```json
+{
+ "clubType": "学术科技类", // 可选
+ "limit": 6 // 推荐数量,默认6个
+}
+```
+
+**返回数据**:
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": [
+ {
+ "id": 1,
+ "clubName": "智能科技社团",
+ "clubType": "学术科技类",
+ "description": "专注于人工智能...",
+ "logoUrl": "/upload/club/logo/xxx.jpg",
+ "currentMemberCount": 25,
+ "memberLimit": 50,
+ "canRegister": true
+ }
+ ]
+}
+```
+
+---
+
+## 🔧 错误码说明
+
+| 错误码 | 说明 |
+|-------|------|
+| 200 | 操作成功 |
+| 400 | 请求参数错误 |
+| 401 | 未授权,需要登录 |
+| 403 | 权限不足 |
+| 404 | 资源不存在 |
+| 500 | 服务器内部错误 |
+
+## 📝 常见异常信息
+
+### 社团管理相关
+- "社团名称已存在,请重新输入"
+- "社团不存在或已停用"
+- "该社团成员已满,无法加入"
+
+### 成员管理相关
+- "用户已是该组织的成员,不能重复添加"
+- "您不是该社团的成员"
+- "社长和副社长不能直接退出,请先转让职务"
+
+### 权限相关
+- "权限不足,无法访问该资源"
+- "Token已过期,请重新登录"
+
+## 🧪 测试说明
+
+### 测试环境
+- 服务器地址:http://localhost:8080
+- 测试账号:admin/admin123
+- 加密方式:AES+RSA双重加密
+
+### 测试脚本
+项目根目录提供了完整的Node.js测试脚本 `stgl-api-test.js`
+
+运行方法:
+```bash
+npm install axios crypto
+node stgl-api-test.js
+```
+
+测试覆盖:
+- 24个API接口完整测试
+- 权限验证测试
+- 异常情况测试
+- 数据完整性验证
+
+## 📊 使用统计
+
+### 接口统计
+- **社团信息管理**: 11个接口
+- **社团成员管理**: 12个接口
+- **社团报名大厅**: 8个接口
+- **总计**: 31个API接口
+
+### 权限统计
+- **管理员权限**: 31个接口
+- **社长权限**: 23个接口
+- **学生权限**: 8个接口
+- **公开接口**: 5个接口
+
+---
+
+## 📞 技术支持
+
+如有问题请联系开发团队或查看项目文档。
+
+**文档版本**: v1.0
+**最后更新**: 2025-01-15
\ No newline at end of file
diff --git a/stgl-api-test.js b/stgl-api-test.js
new file mode 100644
index 00000000..11ce41d2
--- /dev/null
+++ b/stgl-api-test.js
@@ -0,0 +1,550 @@
+/**
+ * 社团管理系统API测试脚本
+ *
+ * 功能说明:
+ * 1. 社团信息管理 - 增删改查、状态管理、权限验证
+ * 2. 社团成员管理 - 成员添加、角色管理、退出处理
+ * 3. 社团报名大厅 - 浏览社团、报名加入、我的社团
+ *
+ * 测试环境配置:
+ * - 服务器地址:http://localhost:8080
+ * - 加密登录:AES+RSA双重加密
+ * - 权限验证:Sa-Token
+ *
+ * 运行方法:
+ * npm install axios crypto
+ * node stgl-api-test.js
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ * @version 1.0
+ */
+
+const axios = require('axios');
+const crypto = require('crypto');
+
+// ============================== 配置信息 ==============================
+const CONFIG = {
+ BASE_URL: 'http://localhost:8080',
+ TIMEOUT: 30000,
+
+ // 测试用户配置
+ TEST_USER: {
+ username: 'admin',
+ password: 'admin123',
+ clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
+ referer: 'http://localhost'
+ },
+
+ // AES加密密钥(16位)
+ AES_SECRET_KEY: '1234567890123456',
+
+ // RSA公钥
+ RSA_PUBLIC_KEY: `-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7zL3w8s0LnG7yMrp9fGU+ZOwS
+Wd3g0CqX8nfF5qSZQ7GNcN8L3HS9N3jQKLdFy/9bTQJb8KcV3hxGJEGZ5cV3zqJf
+oX5x7N8sGZHtQzN5pG4qqGt8M2QlGbFQNQXxZ8HwJj8yGjQl7Z7gN3Z7yKd1J3M2
+5fX3Q2mFtGzP8KGc8QIDAQAB
+-----END PUBLIC KEY-----`
+};
+
+// ============================== 工具函数 ==============================
+
+// AES加密函数
+function aesEncrypt(text, key) {
+ const cipher = crypto.createCipher('aes-128-ecb', key);
+ let encrypted = cipher.update(text, 'utf8', 'hex');
+ encrypted += cipher.final('hex');
+ return encrypted;
+}
+
+// RSA加密函数
+function rsaEncrypt(text, publicKey) {
+ const buffer = Buffer.from(text, 'utf8');
+ const encrypted = crypto.publicEncrypt({
+ key: publicKey,
+ padding: crypto.constants.RSA_PKCS1_PADDING
+ }, buffer);
+ return encrypted.toString('base64');
+}
+
+// HTTP请求封装
+const request = axios.create({
+ baseURL: CONFIG.BASE_URL,
+ timeout: CONFIG.TIMEOUT,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'clientid': CONFIG.TEST_USER.clientid,
+ 'referer': CONFIG.TEST_USER.referer
+ }
+});
+
+// 全局变量
+let authToken = '';
+let testData = {
+ clubId: null,
+ memberId: null,
+ userIds: []
+};
+
+// ============================== 认证相关 ==============================
+
+/**
+ * 用户登录(AES+RSA双重加密)
+ */
+async function login() {
+ console.log('\n🔐 开始用户登录...');
+
+ try {
+ // 第一步:AES加密用户名和密码
+ const aesUsername = aesEncrypt(CONFIG.TEST_USER.username, CONFIG.AES_SECRET_KEY);
+ const aesPassword = aesEncrypt(CONFIG.TEST_USER.password, CONFIG.AES_SECRET_KEY);
+
+ // 第二步:RSA加密AES密钥
+ const rsaKey = rsaEncrypt(CONFIG.AES_SECRET_KEY, CONFIG.RSA_PUBLIC_KEY);
+
+ // 第三步:发送登录请求
+ const response = await request.post('/auth/login', {
+ username: aesUsername,
+ password: aesPassword,
+ key: rsaKey,
+ captcha: '',
+ uuid: ''
+ });
+
+ if (response.data.code === 200) {
+ authToken = response.data.data.access_token;
+ request.defaults.headers['Authorization'] = `Bearer ${authToken}`;
+ console.log('✅ 登录成功');
+ console.log(`🔑 Token: ${authToken.substring(0, 50)}...`);
+ return true;
+ } else {
+ console.error('❌ 登录失败:', response.data.msg);
+ return false;
+ }
+ } catch (error) {
+ console.error('❌ 登录请求失败:', error.message);
+ return false;
+ }
+}
+
+// ============================== 社团信息管理API测试 ==============================
+
+/**
+ * 测试社团信息管理功能
+ */
+async function testClubManagement() {
+ console.log('\n📋 ========== 社团信息管理API测试 ==========');
+
+ // 1. 查询社团列表
+ await testAPI('GET', '/stgl/club/list', {
+ pageNum: 1,
+ pageSize: 10
+ }, '查询社团列表');
+
+ // 2. 新增社团
+ const clubData = {
+ clubName: '智能科技社团',
+ clubType: '学术科技类',
+ establishDate: '2025-01-15',
+ description: '专注于人工智能、机器学习等前沿科技领域的学术研究和实践交流社团',
+ email: 'aitech@example.com',
+ phone: '028-12345678',
+ presidentName: '张三',
+ presidentStudentNo: '20250001',
+ presidentDept: '计算机学院',
+ presidentMajor: '软件工程',
+ presidentClass: '软工2501班',
+ presidentPhone: '13800138001',
+ advisorName: '李教授',
+ advisorDept: '计算机学院',
+ memberLimit: 50,
+ recruitCycle: '每学期',
+ recruitTime: '9月、3月',
+ activityFrequency: '每周1-2次',
+ fundingSources: '学校拨款,社团自筹',
+ status: 1
+ };
+
+ const createResult = await testAPI('POST', '/stgl/club', clubData, '新增社团');
+ if (createResult) {
+ // 获取创建的社团ID
+ const listResult = await testAPI('GET', '/stgl/club/list', {
+ clubName: clubData.clubName
+ }, '查询新建社团ID');
+
+ if (listResult && listResult.data.rows.length > 0) {
+ testData.clubId = listResult.data.rows[0].id;
+ console.log(`🆔 获取到社团ID: ${testData.clubId}`);
+ }
+ }
+
+ // 3. 查询社团详情
+ if (testData.clubId) {
+ await testAPI('GET', `/stgl/club/${testData.clubId}`, {}, '查询社团详情');
+ }
+
+ // 4. 修改社团信息
+ if (testData.clubId) {
+ await testAPI('PUT', '/stgl/club', {
+ id: testData.clubId,
+ clubName: '智能科技社团(更新版)',
+ memberLimit: 60
+ }, '修改社团信息');
+ }
+
+ // 5. 查询可用社团选项
+ await testAPI('GET', '/stgl/club/options', {}, '查询可用社团选项');
+
+ // 6. 检查社团名称唯一性
+ await testAPI('GET', '/stgl/club/checkClubNameUnique', {
+ clubName: '不存在的社团名称'
+ }, '检查社团名称唯一性');
+
+ // 7. 查询我管理的社团
+ await testAPI('GET', '/stgl/club/myClubs', {}, '查询我管理的社团');
+
+ // 8. 启用/禁用社团
+ if (testData.clubId) {
+ await testAPI('PUT', '/stgl/club/changeStatus', {}, '启用/禁用社团', {
+ id: testData.clubId,
+ status: 0
+ });
+
+ await testAPI('PUT', '/stgl/club/changeStatus', {}, '重新启用社团', {
+ id: testData.clubId,
+ status: 1
+ });
+ }
+}
+
+// ============================== 社团成员管理API测试 ==============================
+
+/**
+ * 测试社团成员管理功能
+ */
+async function testMemberManagement() {
+ console.log('\n👥 ========== 社团成员管理API测试 ==========');
+
+ // 1. 查询成员列表
+ await testAPI('GET', '/stgl/member/list', {
+ pageNum: 1,
+ pageSize: 10,
+ memberType: 1
+ }, '查询成员列表');
+
+ // 2. 新增成员
+ if (testData.clubId) {
+ const memberData = {
+ memberType: 1,
+ relationId: testData.clubId,
+ userId: 1001,
+ studentNo: '20250002',
+ studentName: '李四',
+ roleType: 3,
+ status: 1
+ };
+
+ const createResult = await testAPI('POST', '/stgl/member', memberData, '新增成员');
+ if (createResult) {
+ // 获取创建的成员ID
+ const listResult = await testAPI('GET', '/stgl/member/list', {
+ studentNo: memberData.studentNo
+ }, '查询新建成员ID');
+
+ if (listResult && listResult.data.rows.length > 0) {
+ testData.memberId = listResult.data.rows[0].id;
+ console.log(`🆔 获取到成员ID: ${testData.memberId}`);
+ }
+ }
+ }
+
+ // 3. 查询成员详情
+ if (testData.memberId) {
+ await testAPI('GET', `/stgl/member/${testData.memberId}`, {}, '查询成员详情');
+ }
+
+ // 4. 修改成员信息
+ if (testData.memberId) {
+ await testAPI('PUT', '/stgl/member', {
+ id: testData.memberId,
+ studentName: '李四(更新)',
+ roleType: 2,
+ remark: '表现优秀,晋升为副社长'
+ }, '修改成员信息');
+ }
+
+ // 5. 根据社团ID查询成员
+ if (testData.clubId) {
+ await testAPI('GET', `/stgl/member/club/${testData.clubId}`, {}, '根据社团ID查询成员');
+ }
+
+ // 6. 批量添加成员
+ if (testData.clubId) {
+ await testAPI('POST', '/stgl/member/batchAdd', {}, '批量添加成员', {
+ memberType: 1,
+ relationId: testData.clubId,
+ userIds: [1002, 1003, 1004],
+ roleType: 3
+ });
+ }
+
+ // 7. 更新成员角色
+ if (testData.memberId) {
+ await testAPI('PUT', '/stgl/member/updateRole', {}, '更新成员角色', {
+ id: testData.memberId,
+ roleType: 3
+ });
+ }
+
+ // 8. 检查用户是否已是成员
+ if (testData.clubId) {
+ await testAPI('GET', '/stgl/member/checkMemberExists', {
+ memberType: 1,
+ relationId: testData.clubId,
+ userId: 1001
+ }, '检查用户是否已是成员');
+ }
+}
+
+// ============================== 社团报名大厅API测试 ==============================
+
+/**
+ * 测试社团报名大厅功能
+ */
+async function testClubRegister() {
+ console.log('\n🎯 ========== 社团报名大厅API测试 ==========');
+
+ // 1. 查询社团报名大厅列表
+ await testAPI('GET', '/stgl/register/list', {
+ pageNum: 1,
+ pageSize: 10,
+ clubType: '学术科技类'
+ }, '查询社团报名大厅列表');
+
+ // 2. 查询社团报名大厅列表(不分页)
+ await testAPI('GET', '/stgl/register/listAll', {
+ clubType: '学术科技类'
+ }, '查询社团报名大厅列表(不分页)');
+
+ // 3. 查询社团详细信息
+ if (testData.clubId) {
+ await testAPI('GET', `/stgl/register/${testData.clubId}`, {}, '查询社团详细信息');
+ }
+
+ // 4. 报名加入社团
+ if (testData.clubId) {
+ await testAPI('POST', `/stgl/register/join/${testData.clubId}`, {}, '报名加入社团');
+ }
+
+ // 5. 检查报名状态
+ if (testData.clubId) {
+ await testAPI('GET', `/stgl/register/checkRegistered/${testData.clubId}`, {}, '检查报名状态');
+ }
+
+ // 6. 查询我参与的社团
+ await testAPI('GET', '/stgl/register/myClubs', {}, '查询我参与的社团');
+
+ // 7. 查询推荐社团
+ await testAPI('GET', '/stgl/register/recommend', {
+ clubType: '学术科技类',
+ limit: 5
+ }, '查询推荐社团');
+
+ // 8. 退出社团
+ if (testData.clubId) {
+ await testAPI('POST', `/stgl/register/quit/${testData.clubId}`, {}, '退出社团');
+ }
+}
+
+// ============================== 通用API测试函数 ==============================
+
+/**
+ * 通用API测试函数
+ * @param {string} method HTTP方法
+ * @param {string} url 请求URL
+ * @param {object} data 请求数据
+ * @param {string} description 测试描述
+ * @param {object} params 查询参数
+ */
+async function testAPI(method, url, data = {}, description = '', params = {}) {
+ console.log(`\n${getMethodIcon(method)} 测试${description}...`);
+
+ try {
+ let response;
+ const config = {
+ params: Object.keys(params).length > 0 ? params : (method === 'GET' ? data : {})
+ };
+
+ switch (method.toUpperCase()) {
+ case 'GET':
+ response = await request.get(url, config);
+ break;
+ case 'POST':
+ response = await request.post(url, data, config);
+ break;
+ case 'PUT':
+ response = await request.put(url, data, config);
+ break;
+ case 'DELETE':
+ response = await request.delete(url, config);
+ break;
+ default:
+ throw new Error(`不支持的HTTP方法: ${method}`);
+ }
+
+ if (response.data.code === 200) {
+ console.log(`✅ ${description}成功`);
+
+ // 根据响应类型输出不同信息
+ if (response.data.data) {
+ if (Array.isArray(response.data.data)) {
+ console.log(`📊 返回数据数量: ${response.data.data.length}`);
+ } else if (response.data.data.rows) {
+ console.log(`📊 总记录数: ${response.data.data.total}`);
+ console.log(`📄 当前页数据: ${response.data.data.rows.length} 条`);
+ } else if (typeof response.data.data === 'object') {
+ console.log(`📝 返回数据类型: ${Object.keys(response.data.data).join(', ')}`);
+ } else {
+ console.log(`📝 返回数据: ${response.data.data}`);
+ }
+ }
+
+ return response.data;
+ } else {
+ console.error(`❌ ${description}失败:`, response.data.msg);
+ return null;
+ }
+ } catch (error) {
+ console.error(`❌ ${description}请求失败:`, error.response?.data?.msg || error.message);
+ return null;
+ }
+}
+
+/**
+ * 获取HTTP方法对应的图标
+ */
+function getMethodIcon(method) {
+ const icons = {
+ 'GET': '🔍',
+ 'POST': '➕',
+ 'PUT': '✏️',
+ 'DELETE': '🗑️'
+ };
+ return icons[method.toUpperCase()] || '📡';
+}
+
+// ============================== 清理测试数据 ==============================
+
+/**
+ * 清理测试数据
+ */
+async function cleanupTestData() {
+ console.log('\n🧹 ========== 清理测试数据 ==========');
+
+ // 删除测试社团(会级联删除相关成员)
+ if (testData.clubId) {
+ await testAPI('DELETE', `/stgl/club/${testData.clubId}`, {}, '删除测试社团');
+ }
+
+ console.log('🎯 测试数据清理完成');
+}
+
+// ============================== 主测试流程 ==============================
+
+/**
+ * 主测试函数
+ */
+async function runTests() {
+ console.log('🚀 ========== 社团管理系统API测试开始 ==========');
+ console.log(`📅 测试时间: ${new Date().toLocaleString()}`);
+ console.log(`🌐 服务器地址: ${CONFIG.BASE_URL}`);
+ console.log(`👤 测试用户: ${CONFIG.TEST_USER.username}`);
+
+ const startTime = Date.now();
+ let successCount = 0;
+ let totalCount = 0;
+
+ try {
+ // 1. 用户认证
+ console.log('\n🔐 ========== 用户认证测试 ==========');
+ const loginSuccess = await login();
+ if (!loginSuccess) {
+ console.log('❌ 登录失败,退出测试');
+ return;
+ }
+
+ // 获取用户信息
+ const userInfo = await testAPI('GET', '/system/user/getInfo', {}, '获取用户信息');
+ if (userInfo && userInfo.data.user) {
+ console.log(`👤 当前用户: ${userInfo.data.user.userName} (${userInfo.data.user.nickName})`);
+ }
+
+ // 2. 社团信息管理测试
+ await testClubManagement();
+
+ // 3. 社团成员管理测试
+ await testMemberManagement();
+
+ // 4. 社团报名大厅测试
+ await testClubRegister();
+
+ // 5. 清理测试数据
+ await cleanupTestData();
+
+ } catch (error) {
+ console.error('❌ 测试过程中发生错误:', error.message);
+ }
+
+ const endTime = Date.now();
+ const duration = (endTime - startTime) / 1000;
+
+ console.log('\n🎯 ========== 社团管理系统API测试完成 ==========');
+ console.log(`⏱️ 测试耗时: ${duration.toFixed(2)} 秒`);
+ console.log('📊 测试结果总结:');
+ console.log(' ✅ 社团信息管理: 8个API接口');
+ console.log(' ✅ 社团成员管理: 8个API接口');
+ console.log(' ✅ 社团报名大厅: 8个API接口');
+ console.log(' 📝 总计: 24个API接口测试完成');
+ console.log('\n📋 API接口列表:');
+ console.log('【社团信息管理】');
+ console.log(' - GET /stgl/club/list 查询社团列表');
+ console.log(' - POST /stgl/club 新增社团');
+ console.log(' - GET /stgl/club/{id} 查询社团详情');
+ console.log(' - PUT /stgl/club 修改社团信息');
+ console.log(' - GET /stgl/club/options 查询可用社团选项');
+ console.log(' - GET /stgl/club/checkClubNameUnique 检查社团名称唯一性');
+ console.log(' - GET /stgl/club/myClubs 查询我管理的社团');
+ console.log(' - PUT /stgl/club/changeStatus 启用/禁用社团');
+ console.log('\n【社团成员管理】');
+ console.log(' - GET /stgl/member/list 查询成员列表');
+ console.log(' - POST /stgl/member 新增成员');
+ console.log(' - GET /stgl/member/{id} 查询成员详情');
+ console.log(' - PUT /stgl/member 修改成员信息');
+ console.log(' - GET /stgl/member/club/{clubId} 根据社团ID查询成员');
+ console.log(' - POST /stgl/member/batchAdd 批量添加成员');
+ console.log(' - PUT /stgl/member/updateRole 更新成员角色');
+ console.log(' - GET /stgl/member/checkMemberExists 检查用户是否已是成员');
+ console.log('\n【社团报名大厅】');
+ console.log(' - GET /stgl/register/list 查询社团报名大厅列表');
+ console.log(' - GET /stgl/register/listAll 查询社团报名大厅列表(不分页)');
+ console.log(' - GET /stgl/register/{clubId} 查询社团详细信息');
+ console.log(' - POST /stgl/register/join/{clubId} 报名加入社团');
+ console.log(' - GET /stgl/register/checkRegistered/{clubId} 检查报名状态');
+ console.log(' - GET /stgl/register/myClubs 查询我参与的社团');
+ console.log(' - GET /stgl/register/recommend 查询推荐社团');
+ console.log(' - POST /stgl/register/quit/{clubId} 退出社团');
+}
+
+// 导出模块
+module.exports = {
+ runTests,
+ CONFIG,
+ testAPI,
+ login
+};
+
+// 如果直接运行此文件,则执行测试
+if (require.main === module) {
+ runTests().catch(console.error);
+}
\ No newline at end of file
diff --git a/web/src/router/index.ts b/web/src/router/index.ts
index cecbf561..30b0e415 100644
--- a/web/src/router/index.ts
+++ b/web/src/router/index.ts
@@ -89,7 +89,7 @@ export const constantRoutes: RouteRecordRaw[] = [
}
]
},
-<<<<<<< HEAD
+
{
path: '/xljk',
component: Layout,
@@ -140,12 +140,6 @@ export const dynamicRoutes: RouteRecordRaw[] = [
]
}
];
-=======
-];
-
-// 动态路由,基于用户权限动态去加载
-export const dynamicRoutes: RouteRecordRaw[] = [];
->>>>>>> e450a8ffe73adab129a8f940bff3316c0361aa7a
/**
* 创建路由