Compare commits
2 Commits
6365dd8dd0
...
c1113c937c
| Author | SHA1 | Date | |
|---|---|---|---|
| c1113c937c | |||
| 5bb159358f |
@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Tag(name = "赛事附件")
|
@Tag(name = "活动附件")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/contests/attachments")
|
@RequestMapping("/contests/attachments")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -30,7 +30,7 @@ public class ContestAttachmentController {
|
|||||||
|
|
||||||
@GetMapping("/contest/{contestId}")
|
@GetMapping("/contest/{contestId}")
|
||||||
@RequirePermission("contest:read")
|
@RequirePermission("contest:read")
|
||||||
@Operation(summary = "查询赛事下的附件列表")
|
@Operation(summary = "查询活动下的附件列表")
|
||||||
public Result<List<BizContestAttachment>> findByContest(@PathVariable Long contestId) {
|
public Result<List<BizContestAttachment>> findByContest(@PathVariable Long contestId) {
|
||||||
List<BizContestAttachment> list = attachmentService.list(
|
List<BizContestAttachment> list = attachmentService.list(
|
||||||
new LambdaQueryWrapper<BizContestAttachment>()
|
new LambdaQueryWrapper<BizContestAttachment>()
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Tag(name = "赛事管理")
|
@Tag(name = "活动管理")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/contests")
|
@RequestMapping("/contests")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -28,14 +28,14 @@ public class ContestController {
|
|||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@RequirePermission("contest:create")
|
@RequirePermission("contest:create")
|
||||||
@Operation(summary = "创建赛事")
|
@Operation(summary = "创建活动")
|
||||||
public Result<BizContest> create(@Valid @RequestBody CreateContestDto dto) {
|
public Result<BizContest> create(@Valid @RequestBody CreateContestDto dto) {
|
||||||
return Result.success(contestService.createContest(dto, SecurityUtil.getCurrentUserId()));
|
return Result.success(contestService.createContest(dto, SecurityUtil.getCurrentUserId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/stats")
|
@GetMapping("/stats")
|
||||||
@RequirePermission("contest:read")
|
@RequirePermission("contest:read")
|
||||||
@Operation(summary = "获取赛事统计")
|
@Operation(summary = "获取活动统计")
|
||||||
public Result<Map<String, Object>> getStats() {
|
public Result<Map<String, Object>> getStats() {
|
||||||
Long tenantId = SecurityUtil.getCurrentTenantId();
|
Long tenantId = SecurityUtil.getCurrentTenantId();
|
||||||
boolean isSuperTenant = tenantService.isSuperTenant(tenantId);
|
boolean isSuperTenant = tenantService.isSuperTenant(tenantId);
|
||||||
@ -44,14 +44,14 @@ public class ContestController {
|
|||||||
|
|
||||||
@GetMapping("/dashboard")
|
@GetMapping("/dashboard")
|
||||||
@RequirePermission("contest:read")
|
@RequirePermission("contest:read")
|
||||||
@Operation(summary = "获取赛事看板")
|
@Operation(summary = "获取活动看板")
|
||||||
public Result<Map<String, Object>> getDashboard() {
|
public Result<Map<String, Object>> getDashboard() {
|
||||||
return Result.success(contestService.getDashboard(SecurityUtil.getCurrentTenantId()));
|
return Result.success(contestService.getDashboard(SecurityUtil.getCurrentTenantId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@RequirePermission("contest:read")
|
@RequirePermission("contest:read")
|
||||||
@Operation(summary = "查询赛事列表")
|
@Operation(summary = "查询活动列表")
|
||||||
public Result<PageResult<Map<String, Object>>> findAll(QueryContestDto dto) {
|
public Result<PageResult<Map<String, Object>>> findAll(QueryContestDto dto) {
|
||||||
Long tenantId = SecurityUtil.getCurrentTenantId();
|
Long tenantId = SecurityUtil.getCurrentTenantId();
|
||||||
boolean isSuperTenant = tenantService.isSuperTenant(tenantId);
|
boolean isSuperTenant = tenantService.isSuperTenant(tenantId);
|
||||||
@ -60,7 +60,7 @@ public class ContestController {
|
|||||||
|
|
||||||
@GetMapping("/my-contests")
|
@GetMapping("/my-contests")
|
||||||
@RequirePermission({"contest:read", "contest:activity:read"})
|
@RequirePermission({"contest:read", "contest:activity:read"})
|
||||||
@Operation(summary = "获取我的赛事")
|
@Operation(summary = "获取我的活动")
|
||||||
public Result<PageResult<Map<String, Object>>> getMyContests(QueryContestDto dto) {
|
public Result<PageResult<Map<String, Object>>> getMyContests(QueryContestDto dto) {
|
||||||
Long userId = SecurityUtil.getCurrentUserId();
|
Long userId = SecurityUtil.getCurrentUserId();
|
||||||
Long tenantId = SecurityUtil.getCurrentTenantId();
|
Long tenantId = SecurityUtil.getCurrentTenantId();
|
||||||
@ -69,21 +69,21 @@ public class ContestController {
|
|||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@RequirePermission("contest:read")
|
@RequirePermission("contest:read")
|
||||||
@Operation(summary = "查询赛事详情")
|
@Operation(summary = "查询活动详情")
|
||||||
public Result<Map<String, Object>> findDetail(@PathVariable Long id) {
|
public Result<Map<String, Object>> findDetail(@PathVariable Long id) {
|
||||||
return Result.success(contestService.findDetail(id));
|
return Result.success(contestService.findDetail(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PatchMapping("/{id}")
|
@PatchMapping("/{id}")
|
||||||
@RequirePermission("contest:update")
|
@RequirePermission("contest:update")
|
||||||
@Operation(summary = "更新赛事")
|
@Operation(summary = "更新活动")
|
||||||
public Result<BizContest> update(@PathVariable Long id, @RequestBody CreateContestDto dto) {
|
public Result<BizContest> update(@PathVariable Long id, @RequestBody CreateContestDto dto) {
|
||||||
return Result.success(contestService.updateContest(id, dto));
|
return Result.success(contestService.updateContest(id, dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PatchMapping("/{id}/publish")
|
@PatchMapping("/{id}/publish")
|
||||||
@RequirePermission("contest:publish")
|
@RequirePermission("contest:publish")
|
||||||
@Operation(summary = "发布/撤回赛事")
|
@Operation(summary = "发布/撤回活动")
|
||||||
public Result<Void> publish(@PathVariable Long id, @RequestBody Map<String, String> body) {
|
public Result<Void> publish(@PathVariable Long id, @RequestBody Map<String, String> body) {
|
||||||
contestService.publishContest(id, body.get("contestState"));
|
contestService.publishContest(id, body.get("contestState"));
|
||||||
return Result.success();
|
return Result.success();
|
||||||
@ -91,7 +91,7 @@ public class ContestController {
|
|||||||
|
|
||||||
@PatchMapping("/{id}/finish")
|
@PatchMapping("/{id}/finish")
|
||||||
@RequirePermission("contest:update")
|
@RequirePermission("contest:update")
|
||||||
@Operation(summary = "结束赛事")
|
@Operation(summary = "结束活动")
|
||||||
public Result<Void> finish(@PathVariable Long id) {
|
public Result<Void> finish(@PathVariable Long id) {
|
||||||
contestService.finishContest(id);
|
contestService.finishContest(id);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
@ -99,7 +99,7 @@ public class ContestController {
|
|||||||
|
|
||||||
@PatchMapping("/{id}/reopen")
|
@PatchMapping("/{id}/reopen")
|
||||||
@RequirePermission("contest:update")
|
@RequirePermission("contest:update")
|
||||||
@Operation(summary = "重新开放赛事")
|
@Operation(summary = "重新开放活动")
|
||||||
public Result<Void> reopen(@PathVariable Long id) {
|
public Result<Void> reopen(@PathVariable Long id) {
|
||||||
contestService.reopenContest(id);
|
contestService.reopenContest(id);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
@ -107,7 +107,7 @@ public class ContestController {
|
|||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@RequirePermission("contest:delete")
|
@RequirePermission("contest:delete")
|
||||||
@Operation(summary = "删除赛事")
|
@Operation(summary = "删除活动")
|
||||||
public Result<Void> remove(@PathVariable Long id) {
|
public Result<Void> remove(@PathVariable Long id) {
|
||||||
contestService.removeContest(id);
|
contestService.removeContest(id);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Tag(name = "赛事公告")
|
@Tag(name = "活动公告")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/contests/notices")
|
@RequestMapping("/contests/notices")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -78,7 +78,7 @@ public class ContestNoticeController {
|
|||||||
|
|
||||||
@GetMapping("/contest/{contestId}")
|
@GetMapping("/contest/{contestId}")
|
||||||
@RequirePermission("notice:read")
|
@RequirePermission("notice:read")
|
||||||
@Operation(summary = "查询赛事下的公告列表")
|
@Operation(summary = "查询活动下的公告列表")
|
||||||
public Result<List<BizContestNotice>> findByContest(@PathVariable Long contestId) {
|
public Result<List<BizContestNotice>> findByContest(@PathVariable Long contestId) {
|
||||||
Long tenantId = SecurityUtil.getCurrentTenantId();
|
Long tenantId = SecurityUtil.getCurrentTenantId();
|
||||||
List<BizContestNotice> list = noticeService.list(
|
List<BizContestNotice> list = noticeService.list(
|
||||||
|
|||||||
@ -34,7 +34,7 @@ public class ContestTeamController {
|
|||||||
|
|
||||||
@GetMapping("/contest/{contestId}")
|
@GetMapping("/contest/{contestId}")
|
||||||
@RequirePermission("team:read")
|
@RequirePermission("team:read")
|
||||||
@Operation(summary = "查询赛事下的团队列表")
|
@Operation(summary = "查询活动下的团队列表")
|
||||||
public Result<List<Map<String, Object>>> findByContest(@PathVariable Long contestId) {
|
public Result<List<Map<String, Object>>> findByContest(@PathVariable Long contestId) {
|
||||||
return Result.success(teamService.findByContest(contestId, SecurityUtil.getCurrentTenantId()));
|
return Result.success(teamService.findByContest(contestId, SecurityUtil.getCurrentTenantId()));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,15 +7,15 @@ import lombok.Data;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "创建赛事DTO")
|
@Schema(description = "创建活动DTO")
|
||||||
public class CreateContestDto {
|
public class CreateContestDto {
|
||||||
|
|
||||||
@NotBlank(message = "赛事名称不能为空")
|
@NotBlank(message = "活动名称不能为空")
|
||||||
@Schema(description = "赛事名称")
|
@Schema(description = "活动名称")
|
||||||
private String contestName;
|
private String contestName;
|
||||||
|
|
||||||
@NotBlank(message = "赛事类型不能为空")
|
@NotBlank(message = "活动类型不能为空")
|
||||||
@Schema(description = "赛事类型")
|
@Schema(description = "活动类型")
|
||||||
private String contestType;
|
private String contestType;
|
||||||
|
|
||||||
@Schema(description = "可见性")
|
@Schema(description = "可见性")
|
||||||
@ -41,10 +41,10 @@ public class CreateContestDto {
|
|||||||
@Schema(description = "地址")
|
@Schema(description = "地址")
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
@Schema(description = "赛事内容")
|
@Schema(description = "活动内容")
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
@Schema(description = "赛事关联租户ID列表")
|
@Schema(description = "活动关联租户ID列表")
|
||||||
private List<Integer> contestTenants;
|
private List<Integer> contestTenants;
|
||||||
|
|
||||||
@Schema(description = "封面图URL")
|
@Schema(description = "封面图URL")
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import lombok.Data;
|
|||||||
@Schema(description = "创建公告DTO")
|
@Schema(description = "创建公告DTO")
|
||||||
public class CreateNoticeDto {
|
public class CreateNoticeDto {
|
||||||
|
|
||||||
@NotNull(message = "赛事ID不能为空")
|
@NotNull(message = "活动ID不能为空")
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
@NotBlank(message = "公告标题不能为空")
|
@NotBlank(message = "公告标题不能为空")
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import lombok.Data;
|
|||||||
@Schema(description = "创建报名DTO")
|
@Schema(description = "创建报名DTO")
|
||||||
public class CreateRegistrationDto {
|
public class CreateRegistrationDto {
|
||||||
|
|
||||||
@NotNull(message = "赛事ID不能为空")
|
@NotNull(message = "活动ID不能为空")
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
@NotBlank(message = "报名类型不能为空")
|
@NotBlank(message = "报名类型不能为空")
|
||||||
|
|||||||
@ -11,8 +11,8 @@ import java.util.List;
|
|||||||
@Schema(description = "创建团队DTO")
|
@Schema(description = "创建团队DTO")
|
||||||
public class CreateTeamDto {
|
public class CreateTeamDto {
|
||||||
|
|
||||||
@NotNull(message = "赛事ID不能为空")
|
@NotNull(message = "活动ID不能为空")
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
@NotBlank(message = "团队名称不能为空")
|
@NotBlank(message = "团队名称不能为空")
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "查询赛事DTO")
|
@Schema(description = "查询活动DTO")
|
||||||
public class QueryContestDto {
|
public class QueryContestDto {
|
||||||
|
|
||||||
@Schema(description = "页码", defaultValue = "1")
|
@Schema(description = "页码", defaultValue = "1")
|
||||||
@ -13,22 +13,22 @@ public class QueryContestDto {
|
|||||||
@Schema(description = "每页条数", defaultValue = "10")
|
@Schema(description = "每页条数", defaultValue = "10")
|
||||||
private Long pageSize = 10L;
|
private Long pageSize = 10L;
|
||||||
|
|
||||||
@Schema(description = "赛事名称")
|
@Schema(description = "活动名称")
|
||||||
private String contestName;
|
private String contestName;
|
||||||
|
|
||||||
@Schema(description = "赛事状态")
|
@Schema(description = "活动状态")
|
||||||
private String contestState;
|
private String contestState;
|
||||||
|
|
||||||
@Schema(description = "状态")
|
@Schema(description = "状态")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@Schema(description = "赛事类型")
|
@Schema(description = "活动类型")
|
||||||
private String contestType;
|
private String contestType;
|
||||||
|
|
||||||
@Schema(description = "可见性")
|
@Schema(description = "可见性")
|
||||||
private String visibility;
|
private String visibility;
|
||||||
|
|
||||||
@Schema(description = "赛事阶段")
|
@Schema(description = "活动阶段")
|
||||||
private String stage;
|
private String stage;
|
||||||
|
|
||||||
@Schema(description = "创建者租户ID")
|
@Schema(description = "创建者租户ID")
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public class QueryRegistrationDto {
|
|||||||
@Schema(description = "每页条数", defaultValue = "10")
|
@Schema(description = "每页条数", defaultValue = "10")
|
||||||
private Long pageSize = 10L;
|
private Long pageSize = 10L;
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
@Schema(description = "报名状态")
|
@Schema(description = "报名状态")
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public class QueryWorkDto {
|
|||||||
@Schema(description = "每页条数", defaultValue = "10")
|
@Schema(description = "每页条数", defaultValue = "10")
|
||||||
private Long pageSize = 10L;
|
private Long pageSize = 10L;
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
@Schema(description = "报名ID")
|
@Schema(description = "报名ID")
|
||||||
|
|||||||
@ -13,27 +13,27 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 赛事实体(35+ 字段,7 个 JSON 列)
|
* 活动实体(35+ 字段,7 个 JSON 列)
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName(value = "t_biz_contest", autoResultMap = true)
|
@TableName(value = "t_biz_contest", autoResultMap = true)
|
||||||
@Schema(description = "赛事实体")
|
@Schema(description = "活动实体")
|
||||||
public class BizContest extends BaseEntity {
|
public class BizContest extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "赛事名称")
|
@Schema(description = "活动名称")
|
||||||
@TableField("contest_name")
|
@TableField("contest_name")
|
||||||
private String contestName;
|
private String contestName;
|
||||||
|
|
||||||
@Schema(description = "赛事类型:individual/team")
|
@Schema(description = "活动类型:individual/team")
|
||||||
@TableField("contest_type")
|
@TableField("contest_type")
|
||||||
private String contestType;
|
private String contestType;
|
||||||
|
|
||||||
@Schema(description = "赛事发布状态", allowableValues = {"published", "unpublished"})
|
@Schema(description = "活动发布状态", allowableValues = {"published", "unpublished"})
|
||||||
@TableField("contest_state")
|
@TableField("contest_state")
|
||||||
private String contestState;
|
private String contestState;
|
||||||
|
|
||||||
@Schema(description = "赛事进度状态:ongoing/finished")
|
@Schema(description = "活动进度状态:ongoing/finished")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@Schema(description = "开始时间")
|
@Schema(description = "开始时间")
|
||||||
@ -47,7 +47,7 @@ public class BizContest extends BaseEntity {
|
|||||||
@Schema(description = "线下地址")
|
@Schema(description = "线下地址")
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
@Schema(description = "赛事详情(富文本)")
|
@Schema(description = "活动详情(富文本)")
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
@Schema(description = "可见范围", allowableValues = {"public", "designated", "internal", "private"})
|
@Schema(description = "可见范围", allowableValues = {"public", "designated", "internal", "private"})
|
||||||
|
|||||||
@ -10,10 +10,10 @@ import lombok.EqualsAndHashCode;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName("t_biz_contest_attachment")
|
@TableName("t_biz_contest_attachment")
|
||||||
@Schema(description = "赛事附件实体")
|
@Schema(description = "活动附件实体")
|
||||||
public class BizContestAttachment extends BaseEntity {
|
public class BizContestAttachment extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
|
|||||||
@ -10,15 +10,15 @@ import lombok.EqualsAndHashCode;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 赛事公告实体
|
* 活动公告实体
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName("t_biz_contest_notice")
|
@TableName("t_biz_contest_notice")
|
||||||
@Schema(description = "赛事公告实体")
|
@Schema(description = "活动公告实体")
|
||||||
public class BizContestNotice extends BaseEntity {
|
public class BizContestNotice extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
|
|||||||
@ -12,10 +12,10 @@ import java.time.LocalDateTime;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName("t_biz_contest_registration")
|
@TableName("t_biz_contest_registration")
|
||||||
@Schema(description = "赛事报名实体")
|
@Schema(description = "活动报名实体")
|
||||||
public class BizContestRegistration extends BaseEntity {
|
public class BizContestRegistration extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import java.time.LocalDateTime;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
@TableName("t_biz_contest_registration_teacher")
|
@TableName("t_biz_contest_registration_teacher")
|
||||||
@Schema(description = "赛事报名老师关联实体")
|
@Schema(description = "活动报名老师关联实体")
|
||||||
public class BizContestRegistrationTeacher implements Serializable {
|
public class BizContestRegistrationTeacher implements Serializable {
|
||||||
|
|
||||||
@Schema(description = "主键ID")
|
@Schema(description = "主键ID")
|
||||||
|
|||||||
@ -10,14 +10,14 @@ import lombok.EqualsAndHashCode;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName("t_biz_contest_team")
|
@TableName("t_biz_contest_team")
|
||||||
@Schema(description = "赛事团队实体")
|
@Schema(description = "活动团队实体")
|
||||||
public class BizContestTeam extends BaseEntity {
|
public class BizContestTeam extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "租户ID")
|
@Schema(description = "租户ID")
|
||||||
@TableField("tenant_id")
|
@TableField("tenant_id")
|
||||||
private Long tenantId;
|
private Long tenantId;
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import java.time.LocalDateTime;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
@TableName("t_biz_contest_team_member")
|
@TableName("t_biz_contest_team_member")
|
||||||
@Schema(description = "赛事团队成员实体")
|
@Schema(description = "活动团队成员实体")
|
||||||
public class BizContestTeamMember implements Serializable {
|
public class BizContestTeamMember implements Serializable {
|
||||||
|
|
||||||
@Schema(description = "主键ID")
|
@Schema(description = "主键ID")
|
||||||
|
|||||||
@ -15,14 +15,14 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName(value = "t_biz_contest_work", autoResultMap = true)
|
@TableName(value = "t_biz_contest_work", autoResultMap = true)
|
||||||
@Schema(description = "赛事作品实体")
|
@Schema(description = "活动作品实体")
|
||||||
public class BizContestWork extends BaseEntity {
|
public class BizContestWork extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "租户ID")
|
@Schema(description = "租户ID")
|
||||||
@TableField("tenant_id")
|
@TableField("tenant_id")
|
||||||
private Long tenantId;
|
private Long tenantId;
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ public class BizContestWork extends BaseEntity {
|
|||||||
@TableField("user_work_id")
|
@TableField("user_work_id")
|
||||||
private Long userWorkId;
|
private Long userWorkId;
|
||||||
|
|
||||||
// ====== 赛果字段 ======
|
// ====== 成果字段 ======
|
||||||
@Schema(description = "最终得分")
|
@Schema(description = "最终得分")
|
||||||
@TableField("final_score")
|
@TableField("final_score")
|
||||||
private BigDecimal finalScore;
|
private BigDecimal finalScore;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import java.time.LocalDateTime;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
@TableName("t_biz_contest_work_attachment")
|
@TableName("t_biz_contest_work_attachment")
|
||||||
@Schema(description = "赛事作品附件实体")
|
@Schema(description = "活动作品附件实体")
|
||||||
public class BizContestWorkAttachment implements Serializable {
|
public class BizContestWorkAttachment implements Serializable {
|
||||||
|
|
||||||
@Schema(description = "主键ID")
|
@Schema(description = "主键ID")
|
||||||
@ -20,7 +20,7 @@ public class BizContestWorkAttachment implements Serializable {
|
|||||||
@TableField("tenant_id")
|
@TableField("tenant_id")
|
||||||
private Long tenantId;
|
private Long tenantId;
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import java.util.Map;
|
|||||||
public interface IContestWorkService extends IService<BizContestWork> {
|
public interface IContestWorkService extends IService<BizContestWork> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 为指定赛事生成下一个作品编号(与 {@link #submitWork} 所用规则一致:W{contestId}-{序号})。
|
* 为指定活动生成下一个作品编号(与 {@link #submitWork} 所用规则一致:W{contestId}-{序号})。
|
||||||
*/
|
*/
|
||||||
String nextContestWorkNo(Long contestId);
|
String nextContestWorkNo(Long contestId);
|
||||||
|
|
||||||
|
|||||||
@ -42,15 +42,15 @@ public class ContestRegistrationServiceImpl extends ServiceImpl<ContestRegistrat
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> createRegistration(CreateRegistrationDto dto, Long tenantId, Long creatorId) {
|
public Map<String, Object> createRegistration(CreateRegistrationDto dto, Long tenantId, Long creatorId) {
|
||||||
log.info("开始创建报名,赛事ID:{},用户ID:{}", dto.getContestId(), dto.getUserId());
|
log.info("开始创建报名,活动ID:{},用户ID:{}", dto.getContestId(), dto.getUserId());
|
||||||
|
|
||||||
// 验证赛事存在且已发布
|
// 验证活动存在且已发布
|
||||||
BizContest contest = contestMapper.selectById(dto.getContestId());
|
BizContest contest = contestMapper.selectById(dto.getContestId());
|
||||||
if (contest == null) {
|
if (contest == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
if (!PublishStatus.PUBLISHED.getValue().equals(contest.getContestState())) {
|
if (!PublishStatus.PUBLISHED.getValue().equals(contest.getContestState())) {
|
||||||
throw BusinessException.of(ErrorCode.BAD_REQUEST, "赛事未发布,无法报名");
|
throw BusinessException.of(ErrorCode.BAD_REQUEST, "活动未发布,无法报名");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
@ -85,7 +85,7 @@ public class ContestRegistrationServiceImpl extends ServiceImpl<ContestRegistrat
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<Map<String, Object>> findAll(QueryRegistrationDto dto, Long tenantId, boolean isSuperTenant) {
|
public PageResult<Map<String, Object>> findAll(QueryRegistrationDto dto, Long tenantId, boolean isSuperTenant) {
|
||||||
log.info("查询报名列表,赛事ID:{},页码:{}", dto.getContestId(), dto.getPage());
|
log.info("查询报名列表,活动ID:{},页码:{}", dto.getContestId(), dto.getPage());
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestRegistration> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestRegistration> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ public class ContestRegistrationServiceImpl extends ServiceImpl<ContestRegistrat
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getStats(Long contestId, Long tenantId, boolean isSuperAdmin) {
|
public Map<String, Object> getStats(Long contestId, Long tenantId, boolean isSuperAdmin) {
|
||||||
log.info("获取报名统计,赛事ID:{},租户ID:{},超管:{}", contestId, tenantId, isSuperAdmin);
|
log.info("获取报名统计,活动ID:{},租户ID:{},超管:{}", contestId, tenantId, isSuperAdmin);
|
||||||
|
|
||||||
// 非超管需要按租户过滤
|
// 非超管需要按租户过滤
|
||||||
boolean needTenantFilter = !isSuperAdmin && tenantId != null;
|
boolean needTenantFilter = !isSuperAdmin && tenantId != null;
|
||||||
@ -217,7 +217,7 @@ public class ContestRegistrationServiceImpl extends ServiceImpl<ContestRegistrat
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getMyRegistration(Long contestId, Long userId, Long tenantId) {
|
public Map<String, Object> getMyRegistration(Long contestId, Long userId, Long tenantId) {
|
||||||
log.info("查询我的报名,赛事ID:{},用户ID:{}", contestId, userId);
|
log.info("查询我的报名,活动ID:{},用户ID:{}", contestId, userId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestRegistration> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestRegistration> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizContestRegistration::getContestId, contestId);
|
wrapper.eq(BizContestRegistration::getContestId, contestId);
|
||||||
|
|||||||
@ -73,7 +73,7 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, BizContest> i
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BizContest createContest(CreateContestDto dto, Long creatorId) {
|
public BizContest createContest(CreateContestDto dto, Long creatorId) {
|
||||||
log.info("开始创建赛事,名称:{}", dto.getContestName());
|
log.info("开始创建活动,名称:{}", dto.getContestName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BizContest entity = new BizContest();
|
BizContest entity = new BizContest();
|
||||||
@ -97,17 +97,17 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, BizContest> i
|
|||||||
}
|
}
|
||||||
|
|
||||||
save(entity);
|
save(entity);
|
||||||
log.info("赛事创建成功,ID:{}, 名称:{}", entity.getId(), entity.getContestName());
|
log.info("活动创建成功,ID:{}, 名称:{}", entity.getId(), entity.getContestName());
|
||||||
return entity;
|
return entity;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("创建赛事失败,名称:{}", dto.getContestName(), e);
|
log.error("创建活动失败,名称:{}", dto.getContestName(), e);
|
||||||
throw new BusinessException(ErrorCode.INTERNAL_ERROR, "创建赛事失败:" + e.getMessage());
|
throw new BusinessException(ErrorCode.INTERNAL_ERROR, "创建活动失败:" + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<Map<String, Object>> findAll(QueryContestDto dto, Long tenantId, boolean isSuperTenant) {
|
public PageResult<Map<String, Object>> findAll(QueryContestDto dto, Long tenantId, boolean isSuperTenant) {
|
||||||
log.info("查询赛事列表,页码:{},每页:{}", dto.getPage(), dto.getPageSize());
|
log.info("查询活动列表,页码:{},每页:{}", dto.getPage(), dto.getPageSize());
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContest> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContest> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizContest::getValidState, 1);
|
wrapper.eq(BizContest::getValidState, 1);
|
||||||
@ -220,7 +220,7 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, BizContest> i
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<Map<String, Object>> getMyContests(QueryContestDto dto, Long userId, Long tenantId) {
|
public PageResult<Map<String, Object>> getMyContests(QueryContestDto dto, Long userId, Long tenantId) {
|
||||||
log.info("查询我的赛事,用户ID:{},租户ID:{}", userId, tenantId);
|
log.info("查询我的活动,用户ID:{},租户ID:{}", userId, tenantId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContest> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContest> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizContest::getValidState, 1);
|
wrapper.eq(BizContest::getValidState, 1);
|
||||||
@ -249,11 +249,11 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, BizContest> i
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> findDetail(Long id) {
|
public Map<String, Object> findDetail(Long id) {
|
||||||
log.info("查询赛事详情,ID:{}", id);
|
log.info("查询活动详情,ID:{}", id);
|
||||||
|
|
||||||
BizContest contest = getById(id);
|
BizContest contest = getById(id);
|
||||||
if (contest == null) {
|
if (contest == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> result = entityToMap(contest);
|
Map<String, Object> result = entityToMap(contest);
|
||||||
@ -287,72 +287,72 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, BizContest> i
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BizContest updateContest(Long id, CreateContestDto dto) {
|
public BizContest updateContest(Long id, CreateContestDto dto) {
|
||||||
log.info("更新赛事,ID:{}", id);
|
log.info("更新活动,ID:{}", id);
|
||||||
|
|
||||||
BizContest entity = getById(id);
|
BizContest entity = getById(id);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
mapDtoToEntity(dto, entity);
|
mapDtoToEntity(dto, entity);
|
||||||
updateById(entity);
|
updateById(entity);
|
||||||
|
|
||||||
log.info("赛事更新成功,ID:{}", id);
|
log.info("活动更新成功,ID:{}", id);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void publishContest(Long id, String contestState) {
|
public void publishContest(Long id, String contestState) {
|
||||||
log.info("发布/撤回赛事,ID:{},状态:{}", id, contestState);
|
log.info("发布/撤回活动,ID:{},状态:{}", id, contestState);
|
||||||
|
|
||||||
BizContest entity = getById(id);
|
BizContest entity = getById(id);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.setContestState(contestState);
|
entity.setContestState(contestState);
|
||||||
updateById(entity);
|
updateById(entity);
|
||||||
log.info("赛事状态更新成功,ID:{},新状态:{}", id, contestState);
|
log.info("活动状态更新成功,ID:{},新状态:{}", id, contestState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finishContest(Long id) {
|
public void finishContest(Long id) {
|
||||||
log.info("结束赛事,ID:{}", id);
|
log.info("结束活动,ID:{}", id);
|
||||||
|
|
||||||
BizContest entity = getById(id);
|
BizContest entity = getById(id);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.setStatus("finished");
|
entity.setStatus("finished");
|
||||||
updateById(entity);
|
updateById(entity);
|
||||||
log.info("赛事已结束,ID:{}", id);
|
log.info("活动已结束,ID:{}", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reopenContest(Long id) {
|
public void reopenContest(Long id) {
|
||||||
log.info("重新开启赛事,ID:{}", id);
|
log.info("重新开启活动,ID:{}", id);
|
||||||
|
|
||||||
BizContest entity = getById(id);
|
BizContest entity = getById(id);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.setStatus("ongoing");
|
entity.setStatus("ongoing");
|
||||||
updateById(entity);
|
updateById(entity);
|
||||||
log.info("赛事已重新开启,ID:{}", id);
|
log.info("活动已重新开启,ID:{}", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeContest(Long id) {
|
public void removeContest(Long id) {
|
||||||
log.info("删除赛事,ID:{}", id);
|
log.info("删除活动,ID:{}", id);
|
||||||
removeById(id);
|
removeById(id);
|
||||||
log.info("赛事删除成功,ID:{}", id);
|
log.info("活动删除成功,ID:{}", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getStats(Long tenantId, boolean isSuperTenant) {
|
public Map<String, Object> getStats(Long tenantId, boolean isSuperTenant) {
|
||||||
log.info("获取赛事统计,租户ID:{}", tenantId);
|
log.info("获取活动统计,租户ID:{}", tenantId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContest> baseWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContest> baseWrapper = new LambdaQueryWrapper<>();
|
||||||
baseWrapper.eq(BizContest::getValidState, 1);
|
baseWrapper.eq(BizContest::getValidState, 1);
|
||||||
|
|||||||
@ -32,7 +32,7 @@ public class ContestTeamServiceImpl extends ServiceImpl<ContestTeamMapper, BizCo
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Map<String, Object> createTeam(CreateTeamDto dto, Long tenantId, Long creatorId) {
|
public Map<String, Object> createTeam(CreateTeamDto dto, Long tenantId, Long creatorId) {
|
||||||
log.info("开始创建团队,赛事ID:{},团队名称:{}", dto.getContestId(), dto.getTeamName());
|
log.info("开始创建团队,活动ID:{},团队名称:{}", dto.getContestId(), dto.getTeamName());
|
||||||
|
|
||||||
BizContestTeam team = new BizContestTeam();
|
BizContestTeam team = new BizContestTeam();
|
||||||
team.setTenantId(tenantId);
|
team.setTenantId(tenantId);
|
||||||
@ -70,7 +70,7 @@ public class ContestTeamServiceImpl extends ServiceImpl<ContestTeamMapper, BizCo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> findByContest(Long contestId, Long tenantId) {
|
public List<Map<String, Object>> findByContest(Long contestId, Long tenantId) {
|
||||||
log.info("查询赛事团队列表,赛事ID:{}", contestId);
|
log.info("查询活动团队列表,活动ID:{}", contestId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestTeam> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestTeam> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizContestTeam::getContestId, contestId);
|
wrapper.eq(BizContestTeam::getContestId, contestId);
|
||||||
|
|||||||
@ -76,7 +76,7 @@ public class ContestWorkServiceImpl extends ServiceImpl<ContestWorkMapper, BizCo
|
|||||||
|
|
||||||
Long contestId = registration.getContestId();
|
Long contestId = registration.getContestId();
|
||||||
|
|
||||||
// 查询赛事提交规则
|
// 查询活动提交规则
|
||||||
BizContest contest = contestMapper.selectById(contestId);
|
BizContest contest = contestMapper.selectById(contestId);
|
||||||
String submitRule = contest != null ? contest.getSubmitRule() : "once";
|
String submitRule = contest != null ? contest.getSubmitRule() : "once";
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ public class ContestWorkServiceImpl extends ServiceImpl<ContestWorkMapper, BizCo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<Map<String, Object>> findAll(QueryWorkDto dto, Long tenantId, boolean isSuperTenant) {
|
public PageResult<Map<String, Object>> findAll(QueryWorkDto dto, Long tenantId, boolean isSuperTenant) {
|
||||||
log.info("查询作品列表,赛事ID:{},页码:{}", dto.getContestId(), dto.getPage());
|
log.info("查询作品列表,活动ID:{},页码:{}", dto.getContestId(), dto.getPage());
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
@ -310,7 +310,7 @@ public class ContestWorkServiceImpl extends ServiceImpl<ContestWorkMapper, BizCo
|
|||||||
Page<BizContestWork> page = new Page<>(dto.getPage(), dto.getPageSize());
|
Page<BizContestWork> page = new Page<>(dto.getPage(), dto.getPageSize());
|
||||||
Page<BizContestWork> result = contestWorkMapper.selectPage(page, wrapper);
|
Page<BizContestWork> result = contestWorkMapper.selectPage(page, wrapper);
|
||||||
|
|
||||||
// 批量查询报名/赛事信息
|
// 批量查询报名/活动信息
|
||||||
Set<Long> registrationIds = result.getRecords().stream()
|
Set<Long> registrationIds = result.getRecords().stream()
|
||||||
.map(BizContestWork::getRegistrationId)
|
.map(BizContestWork::getRegistrationId)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
@ -499,7 +499,7 @@ public class ContestWorkServiceImpl extends ServiceImpl<ContestWorkMapper, BizCo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getStats(Long contestId, Long tenantId, boolean isSuperTenant) {
|
public Map<String, Object> getStats(Long contestId, Long tenantId, boolean isSuperTenant) {
|
||||||
log.info("获取作品统计,赛事ID:{}", contestId);
|
log.info("获取作品统计,活动ID:{}", contestId);
|
||||||
|
|
||||||
// 租户过滤
|
// 租户过滤
|
||||||
boolean needTenantFilter = !isSuperTenant && tenantId != null;
|
boolean needTenantFilter = !isSuperTenant && tenantId != null;
|
||||||
@ -611,7 +611,7 @@ public class ContestWorkServiceImpl extends ServiceImpl<ContestWorkMapper, BizCo
|
|||||||
@Override
|
@Override
|
||||||
public PageResult<Map<String, Object>> getGuidedWorks(Long contestId, String workNo, String playerName,
|
public PageResult<Map<String, Object>> getGuidedWorks(Long contestId, String workNo, String playerName,
|
||||||
String accountNo, Long page, Long pageSize, Long userId) {
|
String accountNo, Long page, Long pageSize, Long userId) {
|
||||||
log.info("查询指导作品,赛事ID:{},教师用户ID:{}", contestId, userId);
|
log.info("查询指导作品,活动ID:{},教师用户ID:{}", contestId, userId);
|
||||||
|
|
||||||
// 简化实现:查询当前教师指导的报名ID列表,再查对应作品
|
// 简化实现:查询当前教师指导的报名ID列表,再查对应作品
|
||||||
// 完整实现需要关联 t_biz_contest_registration_teacher 表
|
// 完整实现需要关联 t_biz_contest_registration_teacher 表
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Tag(name = "赛事评委")
|
@Tag(name = "活动评委")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/contests/judges")
|
@RequestMapping("/contests/judges")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -23,7 +23,7 @@ public class ContestJudgeController {
|
|||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@RequirePermission("contest:update")
|
@RequirePermission("contest:update")
|
||||||
@Operation(summary = "添加赛事评委")
|
@Operation(summary = "添加活动评委")
|
||||||
public Result<BizContestJudge> createJudge(@RequestBody Map<String, Object> body) {
|
public Result<BizContestJudge> createJudge(@RequestBody Map<String, Object> body) {
|
||||||
Long contestId = Long.valueOf(body.get("contestId").toString());
|
Long contestId = Long.valueOf(body.get("contestId").toString());
|
||||||
Long judgeId = Long.valueOf(body.get("judgeId").toString());
|
Long judgeId = Long.valueOf(body.get("judgeId").toString());
|
||||||
@ -35,7 +35,7 @@ public class ContestJudgeController {
|
|||||||
|
|
||||||
@GetMapping("/contest/{contestId}")
|
@GetMapping("/contest/{contestId}")
|
||||||
@RequirePermission("contest:read")
|
@RequirePermission("contest:read")
|
||||||
@Operation(summary = "查询赛事评委列表",
|
@Operation(summary = "查询活动评委列表",
|
||||||
description = "返回 assigned(显式关联)与 implicitPool(平台隐式池)。添加评委抽屉仅用 assigned 回显;作品分配可选池为 assigned ∪ implicitPool(前端合并)。")
|
description = "返回 assigned(显式关联)与 implicitPool(平台隐式池)。添加评委抽屉仅用 assigned 回显;作品分配可选池为 assigned ∪ implicitPool(前端合并)。")
|
||||||
public Result<ContestJudgesForContestVo> findByContest(@PathVariable Long contestId) {
|
public Result<ContestJudgesForContestVo> findByContest(@PathVariable Long contestId) {
|
||||||
return Result.success(contestJudgeService.findByContest(contestId));
|
return Result.success(contestJudgeService.findByContest(contestId));
|
||||||
|
|||||||
@ -82,7 +82,7 @@ public class ContestReviewController {
|
|||||||
|
|
||||||
@GetMapping("/judge/contests")
|
@GetMapping("/judge/contests")
|
||||||
@RequirePermission("review:score")
|
@RequirePermission("review:score")
|
||||||
@Operation(summary = "获取评委参与的赛事列表")
|
@Operation(summary = "获取评委参与的活动列表")
|
||||||
public Result<List<Map<String, Object>>> getJudgeContests() {
|
public Result<List<Map<String, Object>>> getJudgeContests() {
|
||||||
Long judgeId = SecurityUtil.getCurrentUserId();
|
Long judgeId = SecurityUtil.getCurrentUserId();
|
||||||
return Result.success(contestReviewService.getJudgeContests(judgeId));
|
return Result.success(contestReviewService.getJudgeContests(judgeId));
|
||||||
@ -90,7 +90,7 @@ public class ContestReviewController {
|
|||||||
|
|
||||||
@GetMapping("/judge/contests/{contestId}/works")
|
@GetMapping("/judge/contests/{contestId}/works")
|
||||||
@RequirePermission("review:score")
|
@RequirePermission("review:score")
|
||||||
@Operation(summary = "获取评委赛事作品列表")
|
@Operation(summary = "获取评委活动作品列表")
|
||||||
public Result<PageResult<Map<String, Object>>> getJudgeContestWorks(
|
public Result<PageResult<Map<String, Object>>> getJudgeContestWorks(
|
||||||
@PathVariable Long contestId,
|
@PathVariable Long contestId,
|
||||||
@RequestParam(defaultValue = "1") Long page,
|
@RequestParam(defaultValue = "1") Long page,
|
||||||
@ -132,7 +132,7 @@ public class ContestReviewController {
|
|||||||
|
|
||||||
@GetMapping("/judge/contests/{contestId}/detail")
|
@GetMapping("/judge/contests/{contestId}/detail")
|
||||||
@RequirePermission("review:read")
|
@RequirePermission("review:read")
|
||||||
@Operation(summary = "获取评委视角的赛事详情(含评审规则)")
|
@Operation(summary = "获取评委视角的活动详情(含评审规则)")
|
||||||
public Result<Map<String, Object>> getJudgeContestDetail(@PathVariable Long contestId) {
|
public Result<Map<String, Object>> getJudgeContestDetail(@PathVariable Long contestId) {
|
||||||
Long judgeId = SecurityUtil.getCurrentUserId();
|
Long judgeId = SecurityUtil.getCurrentUserId();
|
||||||
return Result.success(contestReviewService.getJudgeContestDetail(judgeId, contestId));
|
return Result.success(contestReviewService.getJudgeContestDetail(judgeId, contestId));
|
||||||
|
|||||||
@ -38,7 +38,7 @@ public class PresetCommentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/judge/contests")
|
@GetMapping("/judge/contests")
|
||||||
@Operation(summary = "获取评委参与的赛事列表")
|
@Operation(summary = "获取评委参与的活动列表")
|
||||||
public Result<List<Map<String, Object>>> getJudgeContests() {
|
public Result<List<Map<String, Object>>> getJudgeContests() {
|
||||||
Long judgeId = SecurityUtil.getCurrentUserId();
|
Long judgeId = SecurityUtil.getCurrentUserId();
|
||||||
return Result.success(presetCommentService.getJudgeContests(judgeId));
|
return Result.success(presetCommentService.getJudgeContests(judgeId));
|
||||||
@ -76,7 +76,7 @@ public class PresetCommentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/sync")
|
@PostMapping("/sync")
|
||||||
@Operation(summary = "同步评语到其他赛事")
|
@Operation(summary = "同步评语到其他活动")
|
||||||
public Result<Map<String, Object>> syncComments(@Valid @RequestBody SyncPresetCommentsDto dto) {
|
public Result<Map<String, Object>> syncComments(@Valid @RequestBody SyncPresetCommentsDto dto) {
|
||||||
Long judgeId = SecurityUtil.getCurrentUserId();
|
Long judgeId = SecurityUtil.getCurrentUserId();
|
||||||
return Result.success(presetCommentService.syncComments(dto.getSourceContestId(), dto.getTargetContestIds(), judgeId));
|
return Result.success(presetCommentService.syncComments(dto.getSourceContestId(), dto.getTargetContestIds(), judgeId));
|
||||||
|
|||||||
@ -7,15 +7,15 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 某赛事下的评委数据:显式关联与平台隐式池分离,避免扁平列表语义混淆。
|
* 某活动下的评委数据:显式关联与平台隐式池分离,避免扁平列表语义混淆。
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "赛事评委查询结果")
|
@Schema(description = "活动评委查询结果")
|
||||||
public class ContestJudgesForContestVo {
|
public class ContestJudgesForContestVo {
|
||||||
|
|
||||||
@Schema(description = "机构为该赛事显式添加的评委(t_biz_contest_judge),每条必有 id、judgeId;添加评委抽屉回显与提交差集仅基于此列表")
|
@Schema(description = "机构为该活动显式添加的评委(t_biz_contest_judge),每条必有 id、judgeId;添加评委抽屉回显与提交差集仅基于此列表")
|
||||||
private List<Map<String, Object>> assigned;
|
private List<Map<String, Object>> assigned;
|
||||||
|
|
||||||
@Schema(description = "平台评委租户下对该赛事默认可用、未写入关联表的用户(id 为 null,isPlatform 为 true);可与 assigned 合并作为作品分配可选池")
|
@Schema(description = "平台评委租户下对该活动默认可用、未写入关联表的用户(id 为 null,isPlatform 为 true);可与 assigned 合并作为作品分配可选池")
|
||||||
private List<Map<String, Object>> implicitPool;
|
private List<Map<String, Object>> implicitPool;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,8 +11,8 @@ import java.math.BigDecimal;
|
|||||||
@Schema(description = "创建预设评语DTO")
|
@Schema(description = "创建预设评语DTO")
|
||||||
public class CreatePresetCommentDto {
|
public class CreatePresetCommentDto {
|
||||||
|
|
||||||
@NotNull(message = "赛事ID不能为空")
|
@NotNull(message = "活动ID不能为空")
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
@NotBlank(message = "评语内容不能为空")
|
@NotBlank(message = "评语内容不能为空")
|
||||||
|
|||||||
@ -11,11 +11,11 @@ import java.util.List;
|
|||||||
@Schema(description = "同步预设评语DTO")
|
@Schema(description = "同步预设评语DTO")
|
||||||
public class SyncPresetCommentsDto {
|
public class SyncPresetCommentsDto {
|
||||||
|
|
||||||
@NotNull(message = "源赛事ID不能为空")
|
@NotNull(message = "源活动ID不能为空")
|
||||||
@Schema(description = "源赛事ID")
|
@Schema(description = "源活动ID")
|
||||||
private Long sourceContestId;
|
private Long sourceContestId;
|
||||||
|
|
||||||
@NotEmpty(message = "目标赛事列表不能为空")
|
@NotEmpty(message = "目标活动列表不能为空")
|
||||||
@Schema(description = "目标赛事ID列表")
|
@Schema(description = "目标活动ID列表")
|
||||||
private List<Long> targetContestIds;
|
private List<Long> targetContestIds;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,10 +12,10 @@ import java.math.BigDecimal;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName("t_biz_contest_judge")
|
@TableName("t_biz_contest_judge")
|
||||||
@Schema(description = "赛事评委实体")
|
@Schema(description = "活动评委实体")
|
||||||
public class BizContestJudge extends BaseEntity {
|
public class BizContestJudge extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import lombok.EqualsAndHashCode;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName(value = "t_biz_contest_review_rule", autoResultMap = true)
|
@TableName(value = "t_biz_contest_review_rule", autoResultMap = true)
|
||||||
@Schema(description = "赛事评审规则实体")
|
@Schema(description = "活动评审规则实体")
|
||||||
public class BizContestReviewRule extends BaseEntity {
|
public class BizContestReviewRule extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "租户ID")
|
@Schema(description = "租户ID")
|
||||||
|
|||||||
@ -12,14 +12,14 @@ import java.time.LocalDateTime;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
@TableName("t_biz_contest_work_judge_assignment")
|
@TableName("t_biz_contest_work_judge_assignment")
|
||||||
@Schema(description = "赛事作品评委分配实体")
|
@Schema(description = "活动作品评委分配实体")
|
||||||
public class BizContestWorkJudgeAssignment implements Serializable {
|
public class BizContestWorkJudgeAssignment implements Serializable {
|
||||||
|
|
||||||
@Schema(description = "主键ID")
|
@Schema(description = "主键ID")
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
|
|||||||
@ -14,14 +14,14 @@ import java.time.LocalDateTime;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName(value = "t_biz_contest_work_score", autoResultMap = true)
|
@TableName(value = "t_biz_contest_work_score", autoResultMap = true)
|
||||||
@Schema(description = "赛事作品评分实体")
|
@Schema(description = "活动作品评分实体")
|
||||||
public class BizContestWorkScore extends BaseEntity {
|
public class BizContestWorkScore extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "租户ID")
|
@Schema(description = "租户ID")
|
||||||
@TableField("tenant_id")
|
@TableField("tenant_id")
|
||||||
private Long tenantId;
|
private Long tenantId;
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import java.math.BigDecimal;
|
|||||||
@Schema(description = "预设评语实体")
|
@Schema(description = "预设评语实体")
|
||||||
public class BizPresetComment extends BaseEntity {
|
public class BizPresetComment extends BaseEntity {
|
||||||
|
|
||||||
@Schema(description = "赛事ID")
|
@Schema(description = "活动ID")
|
||||||
@TableField("contest_id")
|
@TableField("contest_id")
|
||||||
private Long contestId;
|
private Long contestId;
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ public interface IContestJudgeService extends IService<BizContestJudge> {
|
|||||||
BizContestJudge createJudge(Long contestId, Long judgeId, String specialty, BigDecimal weight, String description);
|
BizContestJudge createJudge(Long contestId, Long judgeId, String specialty, BigDecimal weight, String description);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询某赛事评委:{@link ContestJudgesForContestVo#getAssigned()} 为显式关联;
|
* 查询某活动评委:{@link ContestJudgesForContestVo#getAssigned()} 为显式关联;
|
||||||
* {@link ContestJudgesForContestVo#getImplicitPool()} 为平台默认可用未落库项。
|
* {@link ContestJudgesForContestVo#getImplicitPool()} 为平台默认可用未落库项。
|
||||||
*/
|
*/
|
||||||
ContestJudgesForContestVo findByContest(Long contestId);
|
ContestJudgesForContestVo findByContest(Long contestId);
|
||||||
|
|||||||
@ -36,7 +36,7 @@ public class ContestJudgeServiceImpl extends ServiceImpl<ContestJudgeMapper, Biz
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BizContestJudge createJudge(Long contestId, Long judgeId, String specialty, BigDecimal weight, String description) {
|
public BizContestJudge createJudge(Long contestId, Long judgeId, String specialty, BigDecimal weight, String description) {
|
||||||
log.info("添加评委,赛事ID:{},评委用户ID:{}", contestId, judgeId);
|
log.info("添加评委,活动ID:{},评委用户ID:{}", contestId, judgeId);
|
||||||
|
|
||||||
// 校验是否重复
|
// 校验是否重复
|
||||||
LambdaQueryWrapper<BizContestJudge> dupWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestJudge> dupWrapper = new LambdaQueryWrapper<>();
|
||||||
@ -44,7 +44,7 @@ public class ContestJudgeServiceImpl extends ServiceImpl<ContestJudgeMapper, Biz
|
|||||||
dupWrapper.eq(BizContestJudge::getJudgeId, judgeId);
|
dupWrapper.eq(BizContestJudge::getJudgeId, judgeId);
|
||||||
dupWrapper.eq(BizContestJudge::getValidState, 1);
|
dupWrapper.eq(BizContestJudge::getValidState, 1);
|
||||||
if (count(dupWrapper) > 0) {
|
if (count(dupWrapper) > 0) {
|
||||||
throw BusinessException.of(ErrorCode.BAD_REQUEST, "该评委已添加到此赛事");
|
throw BusinessException.of(ErrorCode.BAD_REQUEST, "该评委已添加到此活动");
|
||||||
}
|
}
|
||||||
|
|
||||||
BizContestJudge entity = new BizContestJudge();
|
BizContestJudge entity = new BizContestJudge();
|
||||||
@ -61,7 +61,7 @@ public class ContestJudgeServiceImpl extends ServiceImpl<ContestJudgeMapper, Biz
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ContestJudgesForContestVo findByContest(Long contestId) {
|
public ContestJudgesForContestVo findByContest(Long contestId) {
|
||||||
log.info("查询赛事评委列表,赛事ID:{}", contestId);
|
log.info("查询活动评委列表,活动ID:{}", contestId);
|
||||||
|
|
||||||
// 获取平台评委租户 ID
|
// 获取平台评委租户 ID
|
||||||
Long judgeTenantId = getJudgeTenantId();
|
Long judgeTenantId = getJudgeTenantId();
|
||||||
@ -77,7 +77,7 @@ public class ContestJudgeServiceImpl extends ServiceImpl<ContestJudgeMapper, Biz
|
|||||||
// 收集所有需要查询的用户 ID(已分配评委)
|
// 收集所有需要查询的用户 ID(已分配评委)
|
||||||
Set<Long> assignedJudgeIds = judges.stream().map(BizContestJudge::getJudgeId).collect(Collectors.toSet());
|
Set<Long> assignedJudgeIds = judges.stream().map(BizContestJudge::getJudgeId).collect(Collectors.toSet());
|
||||||
|
|
||||||
// 2. 查询平台评委(自动对所有赛事可用)
|
// 2. 查询平台评委(自动对所有活动可用)
|
||||||
List<SysUser> platformJudges = new ArrayList<>();
|
List<SysUser> platformJudges = new ArrayList<>();
|
||||||
if (judgeTenantId != null) {
|
if (judgeTenantId != null) {
|
||||||
LambdaQueryWrapper<SysUser> platformWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<SysUser> platformWrapper = new LambdaQueryWrapper<>();
|
||||||
@ -105,7 +105,7 @@ public class ContestJudgeServiceImpl extends ServiceImpl<ContestJudgeMapper, Biz
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量查询每个评委在该赛事下的已分配作品数
|
// 批量查询每个评委在该活动下的已分配作品数
|
||||||
Map<Long, Long> assignedCountMap = new HashMap<>();
|
Map<Long, Long> assignedCountMap = new HashMap<>();
|
||||||
for (Long judgeId : allUserIds) {
|
for (Long judgeId : allUserIds) {
|
||||||
LambdaQueryWrapper<BizContestWorkJudgeAssignment> assignWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWorkJudgeAssignment> assignWrapper = new LambdaQueryWrapper<>();
|
||||||
|
|||||||
@ -48,11 +48,11 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> calculateAllFinalScores(Long contestId) {
|
public Map<String, Object> calculateAllFinalScores(Long contestId) {
|
||||||
log.info("批量计算终分,赛事ID:{}", contestId);
|
log.info("批量计算终分,活动ID:{}", contestId);
|
||||||
|
|
||||||
BizContest contest = contestMapper.selectById(contestId);
|
BizContest contest = contestMapper.selectById(contestId);
|
||||||
if (contest == null) {
|
if (contest == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取计算规则
|
// 获取计算规则
|
||||||
@ -107,7 +107,7 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("批量计算终分完成,赛事ID:{},计算数量:{}", contestId, calculatedCount);
|
log.info("批量计算终分完成,活动ID:{},计算数量:{}", contestId, calculatedCount);
|
||||||
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
result.put("calculatedCount", calculatedCount);
|
result.put("calculatedCount", calculatedCount);
|
||||||
@ -117,7 +117,7 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> calculateRankings(Long contestId) {
|
public Map<String, Object> calculateRankings(Long contestId) {
|
||||||
log.info("计算排名,赛事ID:{}", contestId);
|
log.info("计算排名,活动ID:{}", contestId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizContestWork::getContestId, contestId);
|
wrapper.eq(BizContestWork::getContestId, contestId);
|
||||||
@ -145,7 +145,7 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
workMapper.updateById(work);
|
workMapper.updateById(work);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("排名计算完成,赛事ID:{},排名数量:{}", contestId, works.size());
|
log.info("排名计算完成,活动ID:{},排名数量:{}", contestId, works.size());
|
||||||
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
result.put("rankedCount", works.size());
|
result.put("rankedCount", works.size());
|
||||||
@ -171,7 +171,7 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> batchSetAwards(Long contestId, BatchSetAwardsDto dto) {
|
public Map<String, Object> batchSetAwards(Long contestId, BatchSetAwardsDto dto) {
|
||||||
log.info("批量设置奖项,赛事ID:{},数量:{}", contestId, dto.getAwards().size());
|
log.info("批量设置奖项,活动ID:{},数量:{}", contestId, dto.getAwards().size());
|
||||||
|
|
||||||
int updated = 0;
|
int updated = 0;
|
||||||
for (BatchSetAwardsDto.AwardItem item : dto.getAwards()) {
|
for (BatchSetAwardsDto.AwardItem item : dto.getAwards()) {
|
||||||
@ -193,7 +193,7 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> autoSetAwards(Long contestId, AutoSetAwardsDto dto) {
|
public Map<String, Object> autoSetAwards(Long contestId, AutoSetAwardsDto dto) {
|
||||||
log.info("自动设置奖项,赛事ID:{}", contestId);
|
log.info("自动设置奖项,活动ID:{}", contestId);
|
||||||
|
|
||||||
// 1. 获取已排名作品,按排名排序
|
// 1. 获取已排名作品,按排名排序
|
||||||
LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
|
||||||
@ -252,11 +252,11 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void publishResults(Long contestId) {
|
public void publishResults(Long contestId) {
|
||||||
log.info("发布赛果,赛事ID:{}", contestId);
|
log.info("发布成果,活动ID:{}", contestId);
|
||||||
|
|
||||||
BizContest contest = contestMapper.selectById(contestId);
|
BizContest contest = contestMapper.selectById(contestId);
|
||||||
if (contest == null) {
|
if (contest == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证有排名作品
|
// 验证有排名作品
|
||||||
@ -276,32 +276,32 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
contest.setStatus("finished");
|
contest.setStatus("finished");
|
||||||
contestMapper.updateById(contest);
|
contestMapper.updateById(contest);
|
||||||
|
|
||||||
log.info("赛果发布成功,赛事ID:{}", contestId);
|
log.info("成果发布成功,活动ID:{}", contestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unpublishResults(Long contestId) {
|
public void unpublishResults(Long contestId) {
|
||||||
log.info("撤回赛果,赛事ID:{}", contestId);
|
log.info("撤回成果,活动ID:{}", contestId);
|
||||||
|
|
||||||
BizContest contest = contestMapper.selectById(contestId);
|
BizContest contest = contestMapper.selectById(contestId);
|
||||||
if (contest == null) {
|
if (contest == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PublishStatus.PUBLISHED.getValue().equals(contest.getResultState())) {
|
if (!PublishStatus.PUBLISHED.getValue().equals(contest.getResultState())) {
|
||||||
throw BusinessException.of(ErrorCode.BAD_REQUEST, "赛果未发布,无法撤回");
|
throw BusinessException.of(ErrorCode.BAD_REQUEST, "成果未发布,无法撤回");
|
||||||
}
|
}
|
||||||
|
|
||||||
contest.setResultState(PublishStatus.UNPUBLISHED.getValue());
|
contest.setResultState(PublishStatus.UNPUBLISHED.getValue());
|
||||||
contest.setResultPublishTime(null);
|
contest.setResultPublishTime(null);
|
||||||
contestMapper.updateById(contest);
|
contestMapper.updateById(contest);
|
||||||
|
|
||||||
log.info("赛果撤回成功,赛事ID:{}", contestId);
|
log.info("成果撤回成功,活动ID:{}", contestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<Map<String, Object>> getResults(Long contestId, Long page, Long pageSize, String workNo, String accountNo) {
|
public PageResult<Map<String, Object>> getResults(Long contestId, Long page, Long pageSize, String workNo, String accountNo) {
|
||||||
log.info("查询赛果列表,赛事ID:{},页码:{}", contestId, page);
|
log.info("查询成果列表,活动ID:{},页码:{}", contestId, page);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizContestWork::getContestId, contestId);
|
wrapper.eq(BizContestWork::getContestId, contestId);
|
||||||
@ -446,7 +446,7 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getResultsSummary(Long contestId) {
|
public Map<String, Object> getResultsSummary(Long contestId) {
|
||||||
log.info("查询赛果概览,赛事ID:{}", contestId);
|
log.info("查询成果概览,活动ID:{}", contestId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestWork> baseWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWork> baseWrapper = new LambdaQueryWrapper<>();
|
||||||
baseWrapper.eq(BizContestWork::getContestId, contestId);
|
baseWrapper.eq(BizContestWork::getContestId, contestId);
|
||||||
@ -507,7 +507,7 @@ public class ContestResultServiceImpl implements IContestResultService {
|
|||||||
|
|
||||||
BizContest contest = contestMapper.selectById(contestId);
|
BizContest contest = contestMapper.selectById(contestId);
|
||||||
if (contest == null) {
|
if (contest == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> contestMap = new LinkedHashMap<>();
|
Map<String, Object> contestMap = new LinkedHashMap<>();
|
||||||
|
|||||||
@ -62,7 +62,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
List<Long> desiredOrder = new ArrayList<>(new LinkedHashSet<>(judgeIds));
|
List<Long> desiredOrder = new ArrayList<>(new LinkedHashSet<>(judgeIds));
|
||||||
Set<Long> desired = new HashSet<>(desiredOrder);
|
Set<Long> desired = new HashSet<>(desiredOrder);
|
||||||
|
|
||||||
log.info("分配作品(同步),赛事ID:{},作品ID:{},期望评委数:{}", contestId, workId, desired.size());
|
log.info("分配作品(同步),活动ID:{},作品ID:{},期望评委数:{}", contestId, workId, desired.size());
|
||||||
|
|
||||||
// 1. 取消已分配但不在本次列表中的评委(仅删除未评分的分配行)
|
// 1. 取消已分配但不在本次列表中的评委(仅删除未评分的分配行)
|
||||||
LambdaQueryWrapper<BizContestWorkJudgeAssignment> existWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWorkJudgeAssignment> existWrapper = new LambdaQueryWrapper<>();
|
||||||
@ -124,7 +124,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> batchAssignWorks(Long contestId, List<Long> workIds, List<Long> judgeIds, Long creatorId) {
|
public Map<String, Object> batchAssignWorks(Long contestId, List<Long> workIds, List<Long> judgeIds, Long creatorId) {
|
||||||
log.info("批量分配作品,赛事ID:{},作品数:{},评委数:{}", contestId, workIds.size(), judgeIds.size());
|
log.info("批量分配作品,活动ID:{},作品数:{},评委数:{}", contestId, workIds.size(), judgeIds.size());
|
||||||
|
|
||||||
int totalCreated = 0;
|
int totalCreated = 0;
|
||||||
int totalSkipped = 0;
|
int totalSkipped = 0;
|
||||||
@ -144,7 +144,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> autoAssignWorks(Long contestId, Long creatorId) {
|
public Map<String, Object> autoAssignWorks(Long contestId, Long creatorId) {
|
||||||
log.info("自动分配作品,赛事ID:{}", contestId);
|
log.info("自动分配作品,活动ID:{}", contestId);
|
||||||
|
|
||||||
// 1. 获取所有未分配的作品
|
// 1. 获取所有未分配的作品
|
||||||
LambdaQueryWrapper<BizContestWork> workWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWork> workWrapper = new LambdaQueryWrapper<>();
|
||||||
@ -159,14 +159,14 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
throw BusinessException.of(ErrorCode.BAD_REQUEST, "没有需要分配的作品");
|
throw BusinessException.of(ErrorCode.BAD_REQUEST, "没有需要分配的作品");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 获取赛事所有评委
|
// 2. 获取活动所有评委
|
||||||
LambdaQueryWrapper<BizContestJudge> judgeWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestJudge> judgeWrapper = new LambdaQueryWrapper<>();
|
||||||
judgeWrapper.eq(BizContestJudge::getContestId, contestId);
|
judgeWrapper.eq(BizContestJudge::getContestId, contestId);
|
||||||
judgeWrapper.eq(BizContestJudge::getValidState, 1);
|
judgeWrapper.eq(BizContestJudge::getValidState, 1);
|
||||||
List<BizContestJudge> judges = judgeMapper.selectList(judgeWrapper);
|
List<BizContestJudge> judges = judgeMapper.selectList(judgeWrapper);
|
||||||
|
|
||||||
if (judges.isEmpty()) {
|
if (judges.isEmpty()) {
|
||||||
throw BusinessException.of(ErrorCode.BAD_REQUEST, "赛事尚未配置评委");
|
throw BusinessException.of(ErrorCode.BAD_REQUEST, "活动尚未配置评委");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 轮询分配:每个作品分配给3个评委
|
// 3. 轮询分配:每个作品分配给3个评委
|
||||||
@ -303,7 +303,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> getAssignedWorks(Long judgeId, Long contestId) {
|
public List<Map<String, Object>> getAssignedWorks(Long judgeId, Long contestId) {
|
||||||
log.info("查询评委已分配作品,评委ID:{},赛事ID:{}", judgeId, contestId);
|
log.info("查询评委已分配作品,评委ID:{},活动ID:{}", judgeId, contestId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestWorkJudgeAssignment> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWorkJudgeAssignment> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizContestWorkJudgeAssignment::getJudgeId, judgeId);
|
wrapper.eq(BizContestWorkJudgeAssignment::getJudgeId, judgeId);
|
||||||
@ -343,7 +343,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> getJudgeContests(Long judgeId) {
|
public List<Map<String, Object>> getJudgeContests(Long judgeId) {
|
||||||
log.info("查询评委关联赛事,评委ID:{}", judgeId);
|
log.info("查询评委关联活动,评委ID:{}", judgeId);
|
||||||
|
|
||||||
Set<Long> contestIds = new LinkedHashSet<>();
|
Set<Long> contestIds = new LinkedHashSet<>();
|
||||||
|
|
||||||
@ -399,7 +399,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
@Override
|
@Override
|
||||||
public PageResult<Map<String, Object>> getJudgeContestWorks(Long judgeId, Long contestId, Long page, Long pageSize,
|
public PageResult<Map<String, Object>> getJudgeContestWorks(Long judgeId, Long contestId, Long page, Long pageSize,
|
||||||
String workNo, String accountNo, String reviewStatus) {
|
String workNo, String accountNo, String reviewStatus) {
|
||||||
log.info("查询评委赛事作品,评委ID:{},赛事ID:{},页码:{}", judgeId, contestId, page);
|
log.info("查询评委活动作品,评委ID:{},活动ID:{},页码:{}", judgeId, contestId, page);
|
||||||
|
|
||||||
// 分页查询分配记录
|
// 分页查询分配记录
|
||||||
LambdaQueryWrapper<BizContestWorkJudgeAssignment> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWorkJudgeAssignment> wrapper = new LambdaQueryWrapper<>();
|
||||||
@ -511,7 +511,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getReviewProgress(Long contestId) {
|
public Map<String, Object> getReviewProgress(Long contestId) {
|
||||||
log.info("查询评审进度,赛事ID:{}", contestId);
|
log.info("查询评审进度,活动ID:{}", contestId);
|
||||||
|
|
||||||
// 总作品数
|
// 总作品数
|
||||||
LambdaQueryWrapper<BizContestWork> workWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWork> workWrapper = new LambdaQueryWrapper<>();
|
||||||
@ -580,7 +580,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getWorkStatusStats(Long contestId) {
|
public Map<String, Object> getWorkStatusStats(Long contestId) {
|
||||||
log.info("查询作品状态统计,赛事ID:{}", contestId);
|
log.info("查询作品状态统计,活动ID:{}", contestId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestWork> baseWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestWork> baseWrapper = new LambdaQueryWrapper<>();
|
||||||
baseWrapper.eq(BizContestWork::getContestId, contestId);
|
baseWrapper.eq(BizContestWork::getContestId, contestId);
|
||||||
@ -669,7 +669,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
public Map<String, Object> calculateFinalScore(Long workId) {
|
public Map<String, Object> calculateFinalScore(Long workId) {
|
||||||
log.info("计算作品终分,作品ID:{}", workId);
|
log.info("计算作品终分,作品ID:{}", workId);
|
||||||
|
|
||||||
// 1. 获取作品、赛事、评审规则
|
// 1. 获取作品、活动、评审规则
|
||||||
BizContestWork work = workMapper.selectById(workId);
|
BizContestWork work = workMapper.selectById(workId);
|
||||||
if (work == null) {
|
if (work == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "作品不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "作品不存在");
|
||||||
@ -677,7 +677,7 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
|
|
||||||
BizContest contest = contestMapper.selectById(work.getContestId());
|
BizContest contest = contestMapper.selectById(work.getContestId());
|
||||||
if (contest == null) {
|
if (contest == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
String calculationRule = "average"; // 默认
|
String calculationRule = "average"; // 默认
|
||||||
@ -768,11 +768,11 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== 评委赛事详情 ======
|
// ====== 评委活动详情 ======
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getJudgeContestDetail(Long judgeId, Long contestId) {
|
public Map<String, Object> getJudgeContestDetail(Long judgeId, Long contestId) {
|
||||||
log.info("获取评委赛事详情,评委ID:{},赛事ID:{}", judgeId, contestId);
|
log.info("获取评委活动详情,评委ID:{},活动ID:{}", judgeId, contestId);
|
||||||
|
|
||||||
// 显式评委 或 已有作品分配记录
|
// 显式评委 或 已有作品分配记录
|
||||||
LambdaQueryWrapper<BizContestJudge> judgeWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestJudge> judgeWrapper = new LambdaQueryWrapper<>();
|
||||||
@ -785,13 +785,13 @@ public class ContestReviewServiceImpl implements IContestReviewService {
|
|||||||
assignCheck.eq(BizContestWorkJudgeAssignment::getContestId, contestId);
|
assignCheck.eq(BizContestWorkJudgeAssignment::getContestId, contestId);
|
||||||
assignCheck.eq(BizContestWorkJudgeAssignment::getJudgeId, judgeId);
|
assignCheck.eq(BizContestWorkJudgeAssignment::getJudgeId, judgeId);
|
||||||
if (assignmentMapper.selectCount(assignCheck) == 0) {
|
if (assignmentMapper.selectCount(assignCheck) == 0) {
|
||||||
throw BusinessException.of(ErrorCode.FORBIDDEN, "您不是该赛事的评委");
|
throw BusinessException.of(ErrorCode.FORBIDDEN, "您不是该活动的评委");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BizContest contest = contestMapper.selectById(contestId);
|
BizContest contest = contestMapper.selectById(contestId);
|
||||||
if (contest == null) {
|
if (contest == null) {
|
||||||
throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
|
throw BusinessException.of(ErrorCode.NOT_FOUND, "活动不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
|||||||
@ -31,7 +31,7 @@ public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, B
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BizPresetComment createComment(CreatePresetCommentDto dto, Long judgeId) {
|
public BizPresetComment createComment(CreatePresetCommentDto dto, Long judgeId) {
|
||||||
log.info("创建预设评语,评委ID:{},赛事ID:{}", judgeId, dto.getContestId());
|
log.info("创建预设评语,评委ID:{},活动ID:{}", judgeId, dto.getContestId());
|
||||||
|
|
||||||
BizPresetComment entity = new BizPresetComment();
|
BizPresetComment entity = new BizPresetComment();
|
||||||
entity.setContestId(dto.getContestId());
|
entity.setContestId(dto.getContestId());
|
||||||
@ -48,7 +48,7 @@ public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, B
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> findAll(Long contestId, Long judgeId) {
|
public List<Map<String, Object>> findAll(Long contestId, Long judgeId) {
|
||||||
log.info("查询预设评语列表,赛事ID:{},评委ID:{}", contestId, judgeId);
|
log.info("查询预设评语列表,活动ID:{},评委ID:{}", contestId, judgeId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizPresetComment> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizPresetComment> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizPresetComment::getValidState, 1);
|
wrapper.eq(BizPresetComment::getValidState, 1);
|
||||||
@ -155,7 +155,7 @@ public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, B
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> getJudgeContests(Long judgeId) {
|
public List<Map<String, Object>> getJudgeContests(Long judgeId) {
|
||||||
log.info("查询评委关联赛事(预设评语视角),评委ID:{}", judgeId);
|
log.info("查询评委关联活动(预设评语视角),评委ID:{}", judgeId);
|
||||||
|
|
||||||
LambdaQueryWrapper<BizContestJudge> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizContestJudge> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(BizContestJudge::getJudgeId, judgeId);
|
wrapper.eq(BizContestJudge::getJudgeId, judgeId);
|
||||||
@ -166,7 +166,7 @@ public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, B
|
|||||||
return judgeRecords.stream().map(j -> {
|
return judgeRecords.stream().map(j -> {
|
||||||
Map<String, Object> map = new LinkedHashMap<>();
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
map.put("id", j.getContestId());
|
map.put("id", j.getContestId());
|
||||||
// 查询赛事详情获取名称和状态
|
// 查询活动详情获取名称和状态
|
||||||
BizContest contest = contestMapper.selectById(j.getContestId());
|
BizContest contest = contestMapper.selectById(j.getContestId());
|
||||||
if (contest != null) {
|
if (contest != null) {
|
||||||
map.put("contestName", contest.getContestName());
|
map.put("contestName", contest.getContestName());
|
||||||
@ -179,9 +179,9 @@ public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, B
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> syncComments(Long sourceContestId, List<Long> targetContestIds, Long judgeId) {
|
public Map<String, Object> syncComments(Long sourceContestId, List<Long> targetContestIds, Long judgeId) {
|
||||||
log.info("同步预设评语,源赛事ID:{},目标赛事数:{},评委ID:{}", sourceContestId, targetContestIds.size(), judgeId);
|
log.info("同步预设评语,源活动ID:{},目标活动数:{},评委ID:{}", sourceContestId, targetContestIds.size(), judgeId);
|
||||||
|
|
||||||
// 查询源赛事的评语
|
// 查询源活动的评语
|
||||||
LambdaQueryWrapper<BizPresetComment> sourceWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BizPresetComment> sourceWrapper = new LambdaQueryWrapper<>();
|
||||||
sourceWrapper.eq(BizPresetComment::getContestId, sourceContestId);
|
sourceWrapper.eq(BizPresetComment::getContestId, sourceContestId);
|
||||||
sourceWrapper.eq(BizPresetComment::getJudgeId, judgeId);
|
sourceWrapper.eq(BizPresetComment::getJudgeId, judgeId);
|
||||||
@ -189,7 +189,7 @@ public class PresetCommentServiceImpl extends ServiceImpl<PresetCommentMapper, B
|
|||||||
List<BizPresetComment> sourceComments = presetCommentMapper.selectList(sourceWrapper);
|
List<BizPresetComment> sourceComments = presetCommentMapper.selectList(sourceWrapper);
|
||||||
|
|
||||||
if (sourceComments.isEmpty()) {
|
if (sourceComments.isEmpty()) {
|
||||||
throw BusinessException.of(ErrorCode.BAD_REQUEST, "源赛事没有预设评语");
|
throw BusinessException.of(ErrorCode.BAD_REQUEST, "源活动没有预设评语");
|
||||||
}
|
}
|
||||||
|
|
||||||
int created = 0;
|
int created = 0;
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.competition.common.enums.Visibility;
|
import com.competition.common.enums.Visibility;
|
||||||
import com.competition.modules.leai.util.LeaiUtil;
|
import com.competition.modules.leai.util.LeaiUtil;
|
||||||
|
import com.competition.modules.sys.entity.SysUser;
|
||||||
|
import com.competition.modules.sys.mapper.SysUserMapper;
|
||||||
import com.competition.modules.ugc.entity.UgcWork;
|
import com.competition.modules.ugc.entity.UgcWork;
|
||||||
import com.competition.modules.ugc.entity.UgcWorkPage;
|
import com.competition.modules.ugc.entity.UgcWorkPage;
|
||||||
import com.competition.modules.ugc.mapper.UgcWorkMapper;
|
import com.competition.modules.ugc.mapper.UgcWorkMapper;
|
||||||
@ -28,6 +30,7 @@ public class LeaiSyncService implements ILeaiSyncService {
|
|||||||
private final UgcWorkMapper ugcWorkMapper;
|
private final UgcWorkMapper ugcWorkMapper;
|
||||||
private final UgcWorkPageMapper ugcWorkPageMapper;
|
private final UgcWorkPageMapper ugcWorkPageMapper;
|
||||||
private final LeaiApiClient leaiApiClient;
|
private final LeaiApiClient leaiApiClient;
|
||||||
|
private final SysUserMapper sysUserMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* V4.0 核心同步逻辑
|
* V4.0 核心同步逻辑
|
||||||
@ -128,7 +131,11 @@ public class LeaiSyncService implements ILeaiSyncService {
|
|||||||
Long userId = findUserIdByPhone(phone);
|
Long userId = findUserIdByPhone(phone);
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
work.setUserId(userId);
|
work.setUserId(userId);
|
||||||
|
} else {
|
||||||
|
log.warn("通过手机号未找到对应用户,作品将无法关联用户: remoteWorkId={}, phone={}", remoteWorkId, phone);
|
||||||
}
|
}
|
||||||
|
} else if (phone == null) {
|
||||||
|
log.warn("Webhook回调中手机号为空,无法关联用户: remoteWorkId={}", remoteWorkId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ugcWorkMapper.insert(work);
|
ugcWorkMapper.insert(work);
|
||||||
@ -165,6 +172,12 @@ public class LeaiSyncService implements ILeaiSyncService {
|
|||||||
if (remoteData.containsKey("progressMessage")) {
|
if (remoteData.containsKey("progressMessage")) {
|
||||||
wrapper.set(UgcWork::getProgressMessage, LeaiUtil.toString(remoteData.get("progressMessage"), null));
|
wrapper.set(UgcWork::getProgressMessage, LeaiUtil.toString(remoteData.get("progressMessage"), null));
|
||||||
}
|
}
|
||||||
|
// 同步封面图(AI创作过程中可能推送 coverUrl)
|
||||||
|
Object coverUrl = remoteData.get("coverUrl");
|
||||||
|
if (coverUrl == null) coverUrl = remoteData.get("cover_url");
|
||||||
|
if (coverUrl != null) {
|
||||||
|
wrapper.set(UgcWork::getCoverUrl, coverUrl.toString());
|
||||||
|
}
|
||||||
wrapper.set(UgcWork::getModifyTime, LocalDateTime.now());
|
wrapper.set(UgcWork::getModifyTime, LocalDateTime.now());
|
||||||
|
|
||||||
ugcWorkMapper.update(null, wrapper);
|
ugcWorkMapper.update(null, wrapper);
|
||||||
@ -269,11 +282,12 @@ public class LeaiSyncService implements ILeaiSyncService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过手机号查找用户ID
|
* 通过手机号查找用户ID
|
||||||
* 多租户场景:需要确定租户后查找
|
|
||||||
*/
|
*/
|
||||||
private Long findUserIdByPhone(String phone) {
|
private Long findUserIdByPhone(String phone) {
|
||||||
// TODO: 如果需要按租户隔离,需要传入 orgId(即租户 code/tenant_code)查找租户再查找用户
|
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
|
||||||
// 当前简化处理:直接通过手机号查用户
|
wrapper.eq(SysUser::getPhone, phone)
|
||||||
return null; // 暂时不自动关联用户,后续通过 phone + orgId(租户 code)查询
|
.last("LIMIT 1");
|
||||||
|
SysUser user = sysUserMapper.selectOne(wrapper);
|
||||||
|
return user != null ? user.getId() : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,6 @@ public class PublicRegisterActivityDto {
|
|||||||
@Schema(description = "子女ID(participantType=child时必填)")
|
@Schema(description = "子女ID(participantType=child时必填)")
|
||||||
private Long childId;
|
private Long childId;
|
||||||
|
|
||||||
@Schema(description = "团队ID(团队赛事时填写)")
|
@Schema(description = "团队ID(团队活动时填写)")
|
||||||
private Long teamId;
|
private Long teamId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,6 @@ WHERE tenant_id IS NULL;
|
|||||||
ALTER TABLE t_biz_contest_notice
|
ALTER TABLE t_biz_contest_notice
|
||||||
ADD INDEX idx_tenant_id (tenant_id);
|
ADD INDEX idx_tenant_id (tenant_id);
|
||||||
|
|
||||||
-- 4. 添加联合索引优化按租户和赛事查询
|
-- 4. 添加联合索引优化按租户和活动查询
|
||||||
ALTER TABLE t_biz_contest_notice
|
ALTER TABLE t_biz_contest_notice
|
||||||
ADD INDEX idx_tenant_contest (tenant_id, contest_id);
|
ADD INDEX idx_tenant_contest (tenant_id, contest_id);
|
||||||
|
|||||||
@ -4,12 +4,12 @@
|
|||||||
-- 使用 DROP + CREATE 确保表结构完整,因为评审模块尚未有生产数据
|
-- 使用 DROP + CREATE 确保表结构完整,因为评审模块尚未有生产数据
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- 1. 赛事评委表(对应实体 BizContestJudge,继承 BaseEntity)
|
-- 1. 活动评委表(对应实体 BizContestJudge,继承 BaseEntity)
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
DROP TABLE IF EXISTS t_biz_contest_judge;
|
DROP TABLE IF EXISTS t_biz_contest_judge;
|
||||||
CREATE TABLE t_biz_contest_judge (
|
CREATE TABLE t_biz_contest_judge (
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
contest_id BIGINT NOT NULL COMMENT '赛事ID',
|
contest_id BIGINT NOT NULL COMMENT '活动ID',
|
||||||
judge_id BIGINT NOT NULL COMMENT '评委用户ID',
|
judge_id BIGINT NOT NULL COMMENT '评委用户ID',
|
||||||
specialty VARCHAR(100) DEFAULT NULL COMMENT '专业领域',
|
specialty VARCHAR(100) DEFAULT NULL COMMENT '专业领域',
|
||||||
weight DECIMAL(3,2) DEFAULT 1.00 COMMENT '评委权重 0-1',
|
weight DECIMAL(3,2) DEFAULT 1.00 COMMENT '评委权重 0-1',
|
||||||
@ -26,7 +26,7 @@ CREATE TABLE t_biz_contest_judge (
|
|||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
INDEX idx_contest_id (contest_id),
|
INDEX idx_contest_id (contest_id),
|
||||||
INDEX idx_judge_id (judge_id)
|
INDEX idx_judge_id (judge_id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='赛事评委表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动评委表';
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- 2. 评审规则表(对应实体 BizContestReviewRule,继承 BaseEntity)
|
-- 2. 评审规则表(对应实体 BizContestReviewRule,继承 BaseEntity)
|
||||||
@ -60,7 +60,7 @@ CREATE TABLE t_biz_contest_review_rule (
|
|||||||
DROP TABLE IF EXISTS t_biz_contest_work_judge_assignment;
|
DROP TABLE IF EXISTS t_biz_contest_work_judge_assignment;
|
||||||
CREATE TABLE t_biz_contest_work_judge_assignment (
|
CREATE TABLE t_biz_contest_work_judge_assignment (
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
contest_id BIGINT NOT NULL COMMENT '赛事ID',
|
contest_id BIGINT NOT NULL COMMENT '活动ID',
|
||||||
work_id BIGINT NOT NULL COMMENT '作品ID',
|
work_id BIGINT NOT NULL COMMENT '作品ID',
|
||||||
judge_id BIGINT NOT NULL COMMENT '评委用户ID',
|
judge_id BIGINT NOT NULL COMMENT '评委用户ID',
|
||||||
assignment_time DATETIME DEFAULT NULL COMMENT '分配时间',
|
assignment_time DATETIME DEFAULT NULL COMMENT '分配时间',
|
||||||
@ -82,7 +82,7 @@ DROP TABLE IF EXISTS t_biz_contest_work_score;
|
|||||||
CREATE TABLE t_biz_contest_work_score (
|
CREATE TABLE t_biz_contest_work_score (
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
tenant_id BIGINT DEFAULT NULL COMMENT '租户ID',
|
tenant_id BIGINT DEFAULT NULL COMMENT '租户ID',
|
||||||
contest_id BIGINT NOT NULL COMMENT '赛事ID',
|
contest_id BIGINT NOT NULL COMMENT '活动ID',
|
||||||
work_id BIGINT NOT NULL COMMENT '作品ID',
|
work_id BIGINT NOT NULL COMMENT '作品ID',
|
||||||
assignment_id BIGINT DEFAULT NULL COMMENT '分配记录ID',
|
assignment_id BIGINT DEFAULT NULL COMMENT '分配记录ID',
|
||||||
judge_id BIGINT NOT NULL COMMENT '评委用户ID',
|
judge_id BIGINT NOT NULL COMMENT '评委用户ID',
|
||||||
@ -113,7 +113,7 @@ CREATE TABLE t_biz_contest_work_score (
|
|||||||
DROP TABLE IF EXISTS t_biz_preset_comment;
|
DROP TABLE IF EXISTS t_biz_preset_comment;
|
||||||
CREATE TABLE t_biz_preset_comment (
|
CREATE TABLE t_biz_preset_comment (
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
contest_id BIGINT DEFAULT NULL COMMENT '赛事ID(null 表示全局)',
|
contest_id BIGINT DEFAULT NULL COMMENT '活动ID(null 表示全局)',
|
||||||
judge_id BIGINT DEFAULT NULL COMMENT '评委ID(null 表示通用)',
|
judge_id BIGINT DEFAULT NULL COMMENT '评委ID(null 表示通用)',
|
||||||
content VARCHAR(1000) NOT NULL COMMENT '评语文本',
|
content VARCHAR(1000) NOT NULL COMMENT '评语文本',
|
||||||
category VARCHAR(50) DEFAULT NULL COMMENT '评语分类',
|
category VARCHAR(50) DEFAULT NULL COMMENT '评语分类',
|
||||||
|
|||||||
@ -8,15 +8,15 @@
|
|||||||
-- 一、表级别注释
|
-- 一、表级别注释
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
ALTER TABLE t_biz_contest COMMENT='赛事表';
|
ALTER TABLE t_biz_contest COMMENT='活动表';
|
||||||
ALTER TABLE t_biz_contest_attachment COMMENT='赛事附件表';
|
ALTER TABLE t_biz_contest_attachment COMMENT='活动附件表';
|
||||||
ALTER TABLE t_biz_contest_notice COMMENT='赛事公告表';
|
ALTER TABLE t_biz_contest_notice COMMENT='活动公告表';
|
||||||
ALTER TABLE t_biz_contest_registration COMMENT='赛事报名表';
|
ALTER TABLE t_biz_contest_registration COMMENT='活动报名表';
|
||||||
ALTER TABLE t_biz_contest_registration_teacher COMMENT='赛事报名老师关联表';
|
ALTER TABLE t_biz_contest_registration_teacher COMMENT='活动报名老师关联表';
|
||||||
ALTER TABLE t_biz_contest_team COMMENT='赛事团队表';
|
ALTER TABLE t_biz_contest_team COMMENT='活动团队表';
|
||||||
ALTER TABLE t_biz_contest_team_member COMMENT='赛事团队成员表';
|
ALTER TABLE t_biz_contest_team_member COMMENT='活动团队成员表';
|
||||||
ALTER TABLE t_biz_contest_work COMMENT='赛事作品表';
|
ALTER TABLE t_biz_contest_work COMMENT='活动作品表';
|
||||||
ALTER TABLE t_biz_contest_work_attachment COMMENT='赛事作品附件表';
|
ALTER TABLE t_biz_contest_work_attachment COMMENT='活动作品附件表';
|
||||||
ALTER TABLE t_biz_homework COMMENT='作业表';
|
ALTER TABLE t_biz_homework COMMENT='作业表';
|
||||||
ALTER TABLE t_biz_homework_review_rule COMMENT='作业评审规则表';
|
ALTER TABLE t_biz_homework_review_rule COMMENT='作业评审规则表';
|
||||||
ALTER TABLE t_biz_homework_score COMMENT='作业评分表';
|
ALTER TABLE t_biz_homework_score COMMENT='作业评分表';
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
-- V9: 将数据库表/列 COMMENT 中的旧品牌用词统一改为"活动"
|
||||||
|
|
||||||
|
-- 表级 COMMENT 更新
|
||||||
|
ALTER TABLE t_biz_contest COMMENT='活动表';
|
||||||
|
ALTER TABLE t_biz_contest_attachment COMMENT='活动附件表';
|
||||||
|
ALTER TABLE t_biz_contest_notice COMMENT='活动公告表';
|
||||||
|
ALTER TABLE t_biz_contest_registration COMMENT='活动报名表';
|
||||||
|
ALTER TABLE t_biz_contest_registration_teacher COMMENT='活动报名老师关联表';
|
||||||
|
ALTER TABLE t_biz_contest_team COMMENT='活动团队表';
|
||||||
|
ALTER TABLE t_biz_contest_team_member COMMENT='活动团队成员表';
|
||||||
|
ALTER TABLE t_biz_contest_work COMMENT='活动作品表';
|
||||||
|
ALTER TABLE t_biz_contest_work_attachment COMMENT='活动作品附件表';
|
||||||
|
ALTER TABLE t_biz_contest_judge COMMENT='活动评委表';
|
||||||
|
ALTER TABLE t_biz_contest_review_rule COMMENT='活动评审规则表';
|
||||||
|
ALTER TABLE t_biz_contest_work_score COMMENT='活动作品评分表';
|
||||||
|
ALTER TABLE t_biz_contest_work_judge_assignment COMMENT='活动作品评委分配表';
|
||||||
|
ALTER TABLE t_biz_preset_comment COMMENT='活动预设评语表';
|
||||||
|
|
||||||
|
-- 列级 COMMENT 更新(V4 中创建的列)
|
||||||
|
ALTER TABLE t_biz_contest_judge MODIFY COLUMN contest_id BIGINT NOT NULL COMMENT '活动ID';
|
||||||
|
-- 注意:t_biz_contest_review_rule 表没有 contest_id 列(评审规则是基于 tenant_id 的全局规则),跳过
|
||||||
|
ALTER TABLE t_biz_contest_work_score MODIFY COLUMN contest_id BIGINT NOT NULL COMMENT '活动ID';
|
||||||
|
ALTER TABLE t_biz_contest_work_judge_assignment MODIFY COLUMN contest_id BIGINT NOT NULL COMMENT '活动ID';
|
||||||
|
ALTER TABLE t_biz_preset_comment MODIFY COLUMN contest_id BIGINT DEFAULT NULL COMMENT '活动ID(null 表示全局)';
|
||||||
@ -1,6 +1,4 @@
|
|||||||
# 开发环境
|
# 开发环境
|
||||||
VITE_API_BASE_URL=/api
|
VITE_API_BASE_URL=/api
|
||||||
# AI 绘本子项目本地 dev server(vite proxy /ai-web → 该地址)
|
# 乐读派 AI 创作后端地址(直连,不走 Vite 代理)
|
||||||
VITE_AI_CLIENT_DEV_URL=http://localhost:3001
|
VITE_LEAI_API_URL=http://192.168.1.120:8080
|
||||||
# 兼容旧名:与 VITE_AI_CLIENT_DEV_URL 二选一即可,优先前者
|
|
||||||
VITE_AI_POST_MESSAGE_URL=http://localhost:3001
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
# 生产环境
|
# 生产环境
|
||||||
VITE_API_BASE_URL=/api
|
VITE_API_BASE_URL=/api
|
||||||
# 如果后端部署在不同域名,可以改成完整地址:
|
# 乐读派 AI 创作后端地址(直连)
|
||||||
# VITE_API_BASE_URL=https://api.your-domain.com
|
VITE_LEAI_API_URL=http://192.168.1.120:8080
|
||||||
VITE_AI_POST_MESSAGE_URL=http://localhost:3001
|
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
# 测试环境
|
# 测试环境
|
||||||
VITE_BASE_URL=/web-test/
|
VITE_BASE_URL=/web-test/
|
||||||
VITE_API_BASE_URL=/api-test
|
VITE_API_BASE_URL=/api-test
|
||||||
|
# 乐读派 AI 创作后端地址(直连)
|
||||||
|
VITE_LEAI_API_URL=http://192.168.1.120:8080
|
||||||
|
|||||||
79
frontend/docs/nginx-deployment.md
Normal file
79
frontend/docs/nginx-deployment.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Nginx 部署配置指南 — AI 绘本创作模块整合
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
AI 绘本创作模块已整合进主前端。该模块通过 `VITE_LEAI_API_URL` 环境变量**直连乐读派后端**(跨域),不走 Nginx 代理。只需确保乐读派后端允许跨域即可。
|
||||||
|
|
||||||
|
## 关键配置
|
||||||
|
|
||||||
|
| 项目 | 配置方式 | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 乐读派 API 地址 | `.env.*` 中 `VITE_LEAI_API_URL` | 编译时注入,浏览器直连 |
|
||||||
|
| 乐读派 WebSocket | 自动从 `VITE_LEAI_API_URL` 推导 `ws://` | CreatingView 页面使用 |
|
||||||
|
| 主后端 API | Nginx 代理 `/api` → `8580` | 与之前相同 |
|
||||||
|
|
||||||
|
## 环境变量配置
|
||||||
|
|
||||||
|
在每个环境对应的 `.env.*` 文件中配置乐读派后端地址:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.development / .env.production / .env.test
|
||||||
|
VITE_LEAI_API_URL=http://192.168.1.120:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## 乐读派后端 CORS 配置
|
||||||
|
|
||||||
|
由于前端直连乐读派后端(跨域),乐读派后端需要配置 CORS:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 乐读派后端 application.yml 需要添加:
|
||||||
|
allowed-origins:
|
||||||
|
- http://your-domain.com # 生产域名
|
||||||
|
- http://localhost:3000 # 本地开发
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nginx 配置示例
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# ─── 主后端 API(与之前相同) ───
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:8580/api/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 前端静态资源 ───
|
||||||
|
location /web/ {
|
||||||
|
alias /path/to/frontend/dist/;
|
||||||
|
try_files $uri $uri/ /web/index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── SPA 回退 ───
|
||||||
|
location / {
|
||||||
|
return 301 /web/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 只需启动一个前端(不再需要 aicreate-client)
|
||||||
|
cd frontend
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Vite 代理只需配置主后端 `/api → 8580`,乐读派 API 由浏览器直接访问 `VITE_LEAI_API_URL`。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **CORS 必须配置**:乐读派后端必须允许前端域名的跨域请求
|
||||||
|
2. **WebSocket 跨域**:Creating 页面的进度推送使用 WebSocket,乐读派后端也需要允许 WS 跨域
|
||||||
|
3. **HTTPS 环境**:如果前端使用 HTTPS,`VITE_LEAI_API_URL` 建议也使用 `https://`,否则浏览器会阻止混合内容
|
||||||
|
4. **修改地址后需重新构建**:`VITE_LEAI_API_URL` 是编译时变量,修改后需要 `npm run build`
|
||||||
663
frontend/package-lock.json
generated
663
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,11 +14,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons-vue": "^7.0.1",
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
|
"@stomp/stompjs": "^7.3.0",
|
||||||
"@vee-validate/zod": "^4.12.4",
|
"@vee-validate/zod": "^4.12.4",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
|
"ali-oss": "^6.23.0",
|
||||||
"ant-design-vue": "^4.1.1",
|
"ant-design-vue": "^4.1.1",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
@ -31,6 +34,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.59.1",
|
"@playwright/test": "^1.59.1",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/multer": "^2.0.0",
|
"@types/multer": "^2.0.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue/eslint-config-typescript": "^13.0.0",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* AI 3D 模块 API 存根文件
|
* AI 3D 模块 API 存根文件
|
||||||
* 原模块已剥离至 competition-management-system-stripped-modules/
|
* 原模块已剥离至 competition-management-system-stripped-modules/
|
||||||
* 仅保留被赛事模块引用的类型和接口定义
|
* 仅保留被活动模块引用的类型和接口定义
|
||||||
*/
|
*/
|
||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
import type { PaginationParams } from "@/types/api";
|
import type { PaginationParams } from "@/types/api";
|
||||||
|
|||||||
@ -1302,7 +1302,7 @@ export const reviewsApi = {
|
|||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取评委视角的赛事详情(含评审规则)
|
// 获取评委视角的活动详情(含评审规则)
|
||||||
getJudgeContestDetail: async (contestId: number): Promise<any> => {
|
getJudgeContestDetail: async (contestId: number): Promise<any> => {
|
||||||
const response = await request.get<any, any>(
|
const response = await request.get<any, any>(
|
||||||
`/contests/reviews/judge/contests/${contestId}/detail`,
|
`/contests/reviews/judge/contests/${contestId}/detail`,
|
||||||
@ -1556,7 +1556,7 @@ export const resultsApi = {
|
|||||||
|
|
||||||
// 评委管理
|
// 评委管理
|
||||||
export const judgesApi = {
|
export const judgesApi = {
|
||||||
// 获取赛事评委(assigned + implicitPool)
|
// 获取活动评委(assigned + implicitPool)
|
||||||
getList: async (
|
getList: async (
|
||||||
contestId: number,
|
contestId: number,
|
||||||
): Promise<ContestJudgesForContestResponse> => {
|
): Promise<ContestJudgesForContestResponse> => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* 学生模块 API 存根文件
|
* 学生模块 API 存根文件
|
||||||
* 原模块已剥离至 competition-management-system-stripped-modules/
|
* 原模块已剥离至 competition-management-system-stripped-modules/
|
||||||
* 仅保留被赛事模块引用的类型和接口定义
|
* 仅保留被活动模块引用的类型和接口定义
|
||||||
*/
|
*/
|
||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* 教师模块 API 存根文件
|
* 教师模块 API 存根文件
|
||||||
* 原模块已剥离至 competition-management-system-stripped-modules/
|
* 原模块已剥离至 competition-management-system-stripped-modules/
|
||||||
* 仅保留被赛事模块引用的类型和接口定义
|
* 仅保留被活动模块引用的类型和接口定义
|
||||||
*/
|
*/
|
||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
import type { PaginationParams, PaginationResponse } from "@/types/api";
|
||||||
|
|||||||
@ -63,10 +63,11 @@
|
|||||||
|
|
||||||
<!-- 主内容 -->
|
<!-- 主内容 -->
|
||||||
<main class="public-main">
|
<main class="public-main">
|
||||||
<!-- 创作页始终挂载,用 v-show 控制显隐(不移动 DOM,iframe 状态保留) -->
|
<router-view v-slot="{ Component }">
|
||||||
<PublicCreate v-if="createMounted" v-show="isCreateRoute" />
|
<keep-alive :include="['AiCreateShell']">
|
||||||
<!-- 其他页面正常路由渲染 -->
|
<component :is="Component" />
|
||||||
<router-view v-if="!isCreateRoute" />
|
</keep-alive>
|
||||||
|
</router-view>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- 移动端底部导航 -->
|
<!-- 移动端底部导航 -->
|
||||||
@ -116,13 +117,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from "vue"
|
import { computed } from "vue"
|
||||||
import { useRouter, useRoute } from "vue-router"
|
import { useRouter, useRoute } from "vue-router"
|
||||||
import { HomeOutlined, UserOutlined, PlusCircleOutlined, AppstoreOutlined, TrophyOutlined } from "@ant-design/icons-vue"
|
import { HomeOutlined, UserOutlined, PlusCircleOutlined, AppstoreOutlined, TrophyOutlined } from "@ant-design/icons-vue"
|
||||||
import PublicCreate from "@/views/public/create/Index.vue"
|
import { useAicreateStore } from "@/stores/aicreate"
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const aicreateStore = useAicreateStore()
|
||||||
|
|
||||||
const isLoggedIn = computed(() => !!localStorage.getItem("public_token"))
|
const isLoggedIn = computed(() => !!localStorage.getItem("public_token"))
|
||||||
const user = computed(() => {
|
const user = computed(() => {
|
||||||
@ -131,26 +133,6 @@ const user = computed(() => {
|
|||||||
})
|
})
|
||||||
const userAvatar = computed(() => user.value?.avatar || undefined)
|
const userAvatar = computed(() => user.value?.avatar || undefined)
|
||||||
|
|
||||||
// 是否当前路由为创作页(精确匹配,不含 /create/generating/:id)
|
|
||||||
const isCreateRoute = computed(() => route.name === 'PublicCreate')
|
|
||||||
|
|
||||||
// 懒挂载:用户首次访问创作页时才创建组件,避免不必要资源加载
|
|
||||||
const createMounted = ref(false)
|
|
||||||
|
|
||||||
// 监听路由变化,首次进入创作页时标记挂载
|
|
||||||
watch(isCreateRoute, (val) => {
|
|
||||||
if (val && isLoggedIn.value) {
|
|
||||||
createMounted.value = true
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
|
|
||||||
// 登出时销毁创作页组件,避免下一个用户看到上一个用户的数据
|
|
||||||
watch(isLoggedIn, (val) => {
|
|
||||||
if (!val) {
|
|
||||||
createMounted.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentTab = computed(() => {
|
const currentTab = computed(() => {
|
||||||
const path = route.path
|
const path = route.path
|
||||||
if (path.includes("/mine")) return "mine"
|
if (path.includes("/mine")) return "mine"
|
||||||
@ -164,7 +146,13 @@ const goHome = () => router.push("/p/gallery")
|
|||||||
const goActivity = () => router.push("/p/activities")
|
const goActivity = () => router.push("/p/activities")
|
||||||
const goCreate = () => {
|
const goCreate = () => {
|
||||||
if (!isLoggedIn.value) { router.push("/p/login"); return }
|
if (!isLoggedIn.value) { router.push("/p/login"); return }
|
||||||
router.push("/p/create")
|
const saved = aicreateStore.lastCreateRoute
|
||||||
|
// 有保存的创作路由且不是默认欢迎页,恢复到该路由
|
||||||
|
if (saved && saved !== '/p/create') {
|
||||||
|
router.push(saved)
|
||||||
|
} else {
|
||||||
|
router.push("/p/create")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const goWorks = () => {
|
const goWorks = () => {
|
||||||
if (!isLoggedIn.value) { router.push("/p/login"); return }
|
if (!isLoggedIn.value) { router.push("/p/login"); return }
|
||||||
|
|||||||
@ -78,18 +78,70 @@ const baseRoutes: RouteRecordRaw[] = [
|
|||||||
component: () => import("@/views/public/mine/Children.vue"),
|
component: () => import("@/views/public/mine/Children.vue"),
|
||||||
meta: { title: "子女账号" },
|
meta: { title: "子女账号" },
|
||||||
},
|
},
|
||||||
// ========== 创作与作品库 ==========
|
// ========== AI 绘本创作(嵌套路由) ==========
|
||||||
{
|
{
|
||||||
path: "create",
|
path: "create",
|
||||||
name: "PublicCreate",
|
name: "PublicCreateShell",
|
||||||
component: () => import("@/views/public/create/Index.vue"),
|
component: () => import("@/views/public/create/Index.vue"),
|
||||||
meta: { title: "绘本创作" },
|
meta: { title: "绘本创作" },
|
||||||
},
|
children: [
|
||||||
{
|
{
|
||||||
path: "create/generating/:id",
|
path: "",
|
||||||
name: "PublicCreating",
|
name: "PublicCreateWelcome",
|
||||||
component: () => import("@/views/public/create/Generating.vue"),
|
component: () => import("@/views/public/create/views/WelcomeView.vue"),
|
||||||
meta: { title: "生成中" },
|
},
|
||||||
|
{
|
||||||
|
path: "upload",
|
||||||
|
name: "PublicCreateUpload",
|
||||||
|
component: () => import("@/views/public/create/views/UploadView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "characters",
|
||||||
|
name: "PublicCreateCharacters",
|
||||||
|
component: () => import("@/views/public/create/views/CharactersView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "style",
|
||||||
|
name: "PublicCreateStyle",
|
||||||
|
component: () => import("@/views/public/create/views/StyleSelectView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "story",
|
||||||
|
name: "PublicCreateStory",
|
||||||
|
component: () => import("@/views/public/create/views/StoryInputView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "creating",
|
||||||
|
name: "PublicCreateCreating",
|
||||||
|
component: () => import("@/views/public/create/views/CreatingView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "preview/:workId?",
|
||||||
|
name: "PublicCreatePreview",
|
||||||
|
component: () => import("@/views/public/create/views/PreviewView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "edit-info/:workId",
|
||||||
|
name: "PublicCreateEditInfo",
|
||||||
|
component: () => import("@/views/public/create/views/EditInfoView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "save-success/:workId",
|
||||||
|
name: "PublicCreateSaveSuccess",
|
||||||
|
component: () => import("@/views/public/create/views/SaveSuccessView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "dubbing/:workId",
|
||||||
|
name: "PublicCreateDubbing",
|
||||||
|
component: () => import("@/views/public/create/views/DubbingView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "read/:workId",
|
||||||
|
name: "PublicCreateRead",
|
||||||
|
component: () => import("@/views/public/create/views/BookReaderView.vue"),
|
||||||
|
meta: { noAuth: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "works",
|
path: "works",
|
||||||
|
|||||||
@ -25,6 +25,9 @@ export const useAicreateStore = defineStore('aicreate', () => {
|
|||||||
const workDetail = ref<any>(null)
|
const workDetail = ref<any>(null)
|
||||||
const authRedirectUrl = ref('')
|
const authRedirectUrl = ref('')
|
||||||
|
|
||||||
|
// ─── Tab 切换状态保存 ───
|
||||||
|
const lastCreateRoute = ref('')
|
||||||
|
|
||||||
// ─── 方法 ───
|
// ─── 方法 ───
|
||||||
function setPhone(val: string) {
|
function setPhone(val: string) {
|
||||||
phone.value = val
|
phone.value = val
|
||||||
@ -51,6 +54,14 @@ export const useAicreateStore = defineStore('aicreate', () => {
|
|||||||
sessionStorage.removeItem('le_sessionToken')
|
sessionStorage.removeItem('le_sessionToken')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLastCreateRoute(path: string) {
|
||||||
|
lastCreateRoute.value = path
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLastCreateRoute() {
|
||||||
|
lastCreateRoute.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
imageUrl.value = ''
|
imageUrl.value = ''
|
||||||
extractId.value = ''
|
extractId.value = ''
|
||||||
@ -60,6 +71,7 @@ export const useAicreateStore = defineStore('aicreate', () => {
|
|||||||
storyData.value = null
|
storyData.value = null
|
||||||
workId.value = ''
|
workId.value = ''
|
||||||
workDetail.value = null
|
workDetail.value = null
|
||||||
|
lastCreateRoute.value = ''
|
||||||
localStorage.removeItem('le_workId')
|
localStorage.removeItem('le_workId')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,5 +116,7 @@ export const useAicreateStore = defineStore('aicreate', () => {
|
|||||||
imageUrl, extractId, characters, selectedCharacter,
|
imageUrl, extractId, characters, selectedCharacter,
|
||||||
selectedStyle, storyData, workId, workDetail,
|
selectedStyle, storyData, workId, workDetail,
|
||||||
reset, saveRecoveryState, restoreRecoveryState,
|
reset, saveRecoveryState, restoreRecoveryState,
|
||||||
|
// Tab 切换状态
|
||||||
|
lastCreateRoute, setLastCreateRoute, clearLastCreateRoute,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default { name: 'AiCreateShell' }
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ai-create-shell">
|
<div class="ai-create-shell">
|
||||||
<!-- 初始化加载 -->
|
<!-- 初始化加载 -->
|
||||||
@ -12,22 +16,33 @@
|
|||||||
<!-- 子路由渲染 -->
|
<!-- 子路由渲染 -->
|
||||||
<router-view v-else v-slot="{ Component }">
|
<router-view v-else v-slot="{ Component }">
|
||||||
<transition name="ai-slide" mode="out-in">
|
<transition name="ai-slide" mode="out-in">
|
||||||
<component :is="Component" />
|
<keep-alive>
|
||||||
|
<component :is="Component" />
|
||||||
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { leaiApi } from '@/api/public'
|
import { leaiApi } from '@/api/public'
|
||||||
import { useAicreateStore } from '@/stores/aicreate'
|
import { useAicreateStore } from '@/stores/aicreate'
|
||||||
|
|
||||||
const store = useAicreateStore()
|
const store = useAicreateStore()
|
||||||
|
const route = useRoute()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const loadError = ref('')
|
const loadError = ref('')
|
||||||
|
|
||||||
|
// 监听路由变化,保存最后创作路由到 store
|
||||||
|
watch(() => route.path, (path) => {
|
||||||
|
if (path.startsWith('/p/create')) {
|
||||||
|
store.setLastCreateRoute(path)
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
/** 获取乐读派 Token 并存入 store */
|
/** 获取乐读派 Token 并存入 store */
|
||||||
const initToken = async () => {
|
const initToken = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@ -60,11 +75,15 @@ onMounted(() => {
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
// 壳组件样式不使用 scoped,因为 aicreate.scss 已通过 .ai-create-shell 隔离
|
// 壳组件样式不使用 scoped,因为 aicreate.scss 已通过 .ai-create-shell 隔离
|
||||||
// 覆盖 PublicLayout 的 public-main 样式,让创作页面全屏展示
|
// 覆盖 PublicLayout 的 public-main 样式,让创作页面全屏展示
|
||||||
|
// 只清除左右上的内边距,保留底部为 tabbar 占位
|
||||||
.public-main:has(> .ai-create-shell) {
|
.public-main:has(> .ai-create-shell) {
|
||||||
padding: 0 !important;
|
padding-left: 0 !important;
|
||||||
padding-bottom: 0 !important;
|
padding-right: 0 !important;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
// 保留 padding-bottom 为底部 tabbar 留空间(桌面 40px / 移动 80px)
|
||||||
max-width: 430px;
|
max-width: 430px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--ai-bg, #FFFDF7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-create-shell {
|
.ai-create-shell {
|
||||||
|
|||||||
@ -16,14 +16,6 @@ const getBase = (mode: string) => {
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
// vite.config 顶层无法从 .env 读取 VITE_*,需在回调内 loadEnv
|
|
||||||
const env = loadEnv(mode, process.cwd(), "");
|
|
||||||
// AI 绘本子项目 Vite 开发服务地址(主站 /ai-web 代理到此地址,需与 lesingle-aicreate-client 端口一致)
|
|
||||||
const aiProxyTarget =
|
|
||||||
env.VITE_AI_CLIENT_DEV_URL ||
|
|
||||||
env.VITE_AI_POST_MESSAGE_URL ||
|
|
||||||
"http://localhost:3001";
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base: getBase(mode),
|
base: getBase(mode),
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
@ -36,15 +28,10 @@ export default defineConfig(({ mode }) => {
|
|||||||
host: "0.0.0.0",
|
host: "0.0.0.0",
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
// 主后端
|
||||||
"/api": {
|
"/api": {
|
||||||
target: "http://localhost:8580",
|
target: "http://localhost:8580",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
// rewrite: (path) => path.replace(/^\/api/, ''),
|
|
||||||
},
|
|
||||||
"/ai-web": {
|
|
||||||
target: aiProxyTarget,
|
|
||||||
changeOrigin: true,
|
|
||||||
// 子项目 base 为 /ai-web/,不重写路径,直接转发到子 dev server
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -183,7 +183,7 @@ export function checkQuota() {
|
|||||||
return api.post('/query/validate', {
|
return api.post('/query/validate', {
|
||||||
orgId: store.orgId,
|
orgId: store.orgId,
|
||||||
phone: store.phone,
|
phone: store.phone,
|
||||||
type: 'A'
|
apiType: 'A3'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user