build(backend): switch to thin-jar layout (split libs into target/lib); chore: remove test-* files; misc updates
| | |
| | | |
| | | <build> |
| | | <plugins> |
| | | <!-- 禁用 Spring Boot 胖包重打包,改为瘦包 + 外置依赖 --> |
| | | <plugin> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-maven-plugin</artifactId> |
| | | <configuration> |
| | | <skip>true</skip> |
| | | </configuration> |
| | | </plugin> |
| | | |
| | | <!-- 在打包阶段复制所有依赖到 target/lib --> |
| | | <plugin> |
| | | <groupId>org.apache.maven.plugins</groupId> |
| | | <artifactId>maven-dependency-plugin</artifactId> |
| | | <version>3.6.1</version> |
| | | <executions> |
| | | <execution> |
| | | <id>copy-dependencies</id> |
| | | <phase>package</phase> |
| | | <goals> |
| | | <goal>copy-dependencies</goal> |
| | | </goals> |
| | | <configuration> |
| | | <outputDirectory>${project.build.directory}/lib</outputDirectory> |
| | | <includeScope>runtime</includeScope> |
| | | <overWriteReleases>false</overWriteReleases> |
| | | <overWriteSnapshots>false</overWriteSnapshots> |
| | | <overWriteIfNewer>true</overWriteIfNewer> |
| | | </configuration> |
| | | </execution> |
| | | </executions> |
| | | </plugin> |
| | | |
| | | <!-- 生成可执行瘦 JAR:写入 Main-Class 与 Class-Path 指向 lib/ --> |
| | | <plugin> |
| | | <groupId>org.apache.maven.plugins</groupId> |
| | | <artifactId>maven-jar-plugin</artifactId> |
| | | <version>3.4.2</version> |
| | | <configuration> |
| | | <archive> |
| | | <manifest> |
| | | <addClasspath>true</addClasspath> |
| | | <classpathPrefix>lib/</classpathPrefix> |
| | | <mainClass>com.rongyichuang.RycBackendApplication</mainClass> |
| | | </manifest> |
| | | </archive> |
| | | </configuration> |
| | | </plugin> |
| | | </plugins> |
| | | </build> |
| | |
| | | * Web端用户登录 |
| | | */ |
| | | @PostMapping("/web-login") |
| | | public ResponseEntity<LoginResponse> webLogin(@Valid @RequestBody LoginRequest request) { |
| | | public ResponseEntity<?> webLogin(@Valid @RequestBody LoginRequest request) { |
| | | logger.info("收到Web登录请求,手机号: {}", request.getPhone()); |
| | | try { |
| | | LoginResponse response = authService.login(request); |
| | |
| | | return ResponseEntity.ok(response); |
| | | } catch (Exception e) { |
| | | logger.error("Web登录失败,手机号: {}, 错误: {}", request.getPhone(), e.getMessage()); |
| | | return ResponseEntity.badRequest().build(); |
| | | // 返回包含错误信息的JSON响应 |
| | | return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); |
| | | } |
| | | } |
| | | |
| | | // 错误响应类 |
| | | public static class ErrorResponse { |
| | | private String message; |
| | | |
| | | public ErrorResponse(String message) { |
| | | this.message = message; |
| | | } |
| | | |
| | | public String getMessage() { |
| | | return message; |
| | | } |
| | | |
| | | public void setMessage(String message) { |
| | | this.message = message; |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | @PostMapping("/wx-login") |
| | | public ResponseEntity<WxLoginResponse> wxLogin(@RequestBody WxLoginRequest request) { |
| | | logger.info("收到微信登录请求,openid: {}", request.getWxOpenid()); |
| | | try { |
| | | WxLoginResponse response = authService.wxLogin(request); |
| | | logger.info("微信登录成功,openid: {}", request.getWxOpenid()); |
| | | return ResponseEntity.ok(response); |
| | | } catch (JsonProcessingException e) { |
| | | logger.error("微信登录JSON处理异常,openid: {}, 错误: {}", request.getWxOpenid(), e.getMessage()); |
| | | return ResponseEntity.badRequest().build(); |
| | | } catch (Exception e) { |
| | | logger.error("微信登录失败,openid: {}, 错误: {}", request.getWxOpenid(), e.getMessage(), e); |
| | | return ResponseEntity.status(500).build(); |
| | | } |
| | | } |
| | | |
| | |
| | | this.id = activityPlayer.getId(); |
| | | this.playerName = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getName() : ""; |
| | | this.projectName = activityPlayer.getProjectName(); |
| | | // 从User实体获取phone,而不是从Player实体(Player.phone已废弃) |
| | | this.phone = activityPlayer.getPlayer() != null && activityPlayer.getPlayer().getUser() != null ? |
| | | activityPlayer.getPlayer().getUser().getPhone() : ""; |
| | | // 临时使用废弃的Player.phone字段,后续需要从服务层传入User的phone |
| | | this.phone = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getPhone() : ""; |
| | | this.averageScore = activityPlayer.getTotalScore(); |
| | | this.ratingCount = 0; // 需要从评分表中统计 |
| | | this.applyTime = activityPlayer.getCreateTime() != null ? |
| | | activityPlayer.getCreateTime().format(FORMATTER) : null; |
| | | this.state = activityPlayer.getState(); |
| | | } |
| | | |
| | | public CompetitionParticipantResponse(ActivityPlayer activityPlayer, String userPhone) { |
| | | this.id = activityPlayer.getId(); |
| | | this.playerName = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getName() : ""; |
| | | this.projectName = activityPlayer.getProjectName(); |
| | | // 从参数获取User的phone |
| | | this.phone = userPhone != null ? userPhone : ""; |
| | | this.averageScore = activityPlayer.getTotalScore(); |
| | | this.ratingCount = 0; // 需要从评分表中统计 |
| | | this.applyTime = activityPlayer.getCreateTime() != null ? |
| | |
| | | this.id = activityPlayer.getId(); |
| | | this.playerName = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getName() : ""; |
| | | this.projectName = activityPlayer.getProjectName(); |
| | | // 从User实体获取phone,而不是从Player实体(Player.phone已废弃) |
| | | this.phone = activityPlayer.getPlayer() != null && activityPlayer.getPlayer().getUser() != null ? |
| | | activityPlayer.getPlayer().getUser().getPhone() : ""; |
| | | // 临时使用废弃的Player.phone字段,后续需要从服务层传入User的phone |
| | | this.phone = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getPhone() : ""; |
| | | this.averageScore = averageScore; |
| | | this.ratingCount = ratingCount != null ? ratingCount : 0; |
| | | this.applyTime = activityPlayer.getCreateTime() != null ? |
| | | activityPlayer.getCreateTime().format(FORMATTER) : null; |
| | | this.state = activityPlayer.getState(); |
| | | this.playerId = activityPlayer.getPlayerId(); |
| | | } |
| | | |
| | | public PromotableParticipantResponse(ActivityPlayer activityPlayer, BigDecimal averageScore, Integer ratingCount, String userPhone) { |
| | | this.id = activityPlayer.getId(); |
| | | this.playerName = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getName() : ""; |
| | | this.projectName = activityPlayer.getProjectName(); |
| | | // 从参数获取User的phone |
| | | this.phone = userPhone != null ? userPhone : ""; |
| | | this.averageScore = averageScore; |
| | | this.ratingCount = ratingCount != null ? ratingCount : 0; |
| | | this.applyTime = activityPlayer.getCreateTime() != null ? |
| | |
| | | * @deprecated 此字段已废弃,请使用关联User实体的phone字段 |
| | | */ |
| | | @Deprecated |
| | | @Column(name = "phone", length = 32, nullable = false, unique = true) |
| | | @Column(name = "phone", length = 32) |
| | | private String phone; |
| | | |
| | | /** |
| | |
| | | import com.rongyichuang.media.service.MediaV2Service; |
| | | import com.rongyichuang.media.dto.MediaSaveInput; |
| | | import com.rongyichuang.message.service.MessageService; |
| | | import com.rongyichuang.auth.util.JwtUtil; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | |
| | | @Autowired |
| | | private MessageService messageService; |
| | | |
| | | @Autowired |
| | | private JwtUtil jwtUtil; |
| | | |
| | | |
| | | public ActivityPlayer getMyActivityPlayer(Long activityId) { |
| | |
| | | String phone = input.getPlayerInfo().getPhone(); |
| | | String name = input.getPlayerInfo().getName(); |
| | | |
| | | // 获取当前用户的wxopenid(从JWT token中) |
| | | String currentWxOpenid = null; |
| | | try { |
| | | String token = userContextUtil.getTokenFromRequest(); |
| | | if (token != null && jwtUtil.validateToken(token)) { |
| | | currentWxOpenid = jwtUtil.getWxOpenidFromToken(token); |
| | | log.debug("从JWT token中获取到wxopenid: {}", currentWxOpenid); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("获取当前用户wxopenid时发生异常: {}", e.getMessage()); |
| | | } |
| | | |
| | | // 先查找现有用户 |
| | | Optional<User> existingUserOpt = userService.findByPhone(phone); |
| | | |
| | |
| | | // 更新用户的生日信息 |
| | | if (input.getPlayerInfo().getBirthDate() != null) { |
| | | user.setBirthday(input.getPlayerInfo().getBirthDate()); |
| | | } |
| | | |
| | | // 更新wxopenid(如果当前token中包含且不为空) |
| | | if (currentWxOpenid != null && !currentWxOpenid.trim().isEmpty()) { |
| | | // 检查这个openid是否已经被其他用户使用 |
| | | Optional<User> existingUserWithOpenid = userService.findByWxOpenid(currentWxOpenid); |
| | | if (existingUserWithOpenid.isEmpty() || existingUserWithOpenid.get().getId().equals(user.getId())) { |
| | | user.setWxOpenid(currentWxOpenid); |
| | | log.info("更新用户wxopenid: {}", currentWxOpenid); |
| | | } else { |
| | | log.warn("wxopenid {} 已被其他用户使用,用户ID: {}", currentWxOpenid, existingUserWithOpenid.get().getId()); |
| | | } |
| | | } |
| | | |
| | | user = userService.save(user); |
| | |
| | | newUser.setBirthday(input.getPlayerInfo().getBirthDate()); |
| | | } |
| | | |
| | | // 设置wxopenid(如果当前token中包含且不为空) |
| | | if (currentWxOpenid != null && !currentWxOpenid.trim().isEmpty()) { |
| | | // 检查这个openid是否已经被其他用户使用 |
| | | Optional<User> existingUserWithOpenid = userService.findByWxOpenid(currentWxOpenid); |
| | | if (existingUserWithOpenid.isEmpty()) { |
| | | newUser.setWxOpenid(currentWxOpenid); |
| | | log.info("为新用户设置wxopenid: {}", currentWxOpenid); |
| | | } else { |
| | | log.warn("wxopenid {} 已被其他用户使用,用户ID: {}", currentWxOpenid, existingUserWithOpenid.get().getId()); |
| | | } |
| | | } |
| | | |
| | | newUser = userService.save(newUser); |
| | | log.info("为小程序报名成功创建新用户,用户ID: {}", newUser.getId()); |
| | | return newUser; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 根据微信openid查找用户 |
| | | */ |
| | | public Optional<User> findByWxOpenid(String wxOpenid) { |
| | | return userRepository.findByWxOpenid(wxOpenid); |
| | | } |
| | | |
| | | /** |
| | | * 保存用户 |
| | | */ |
| | | public User save(User user) { |
| | |
| | | level: |
| | | com.rongyichuang: DEBUG |
| | | org.springframework.security: DEBUG |
| | | org.springframework.transaction: DEBUG |
| | | org.springframework.orm.jpa: DEBUG |
| | | org.hibernate.SQL: DEBUG |
| | | org.hibernate.type.descriptor.sql.BasicBinder: TRACE |
| | | org.hibernate.engine.transaction: DEBUG |
| | | |
| | | # 应用配置 |
| | | app: |
| | |
| | | error: '', |
| | | statusText: '', |
| | | genderText: '', |
| | | educationText: '' |
| | | educationText: '', |
| | | // 时间轴相关数据 |
| | | timeline: [], |
| | | timelineLoading: false, |
| | | timelineError: '', |
| | | showRatingDetail: false, |
| | | ratingDetail: null |
| | | }, |
| | | |
| | | onLoad(options) { |
| | |
| | | educationText: this.getEducationText(projectDetail.playerInfo?.education), |
| | | loading: false |
| | | }) |
| | | |
| | | // 加载时间轴数据 |
| | | this.loadProjectTimeline() |
| | | } else { |
| | | throw new Error('项目详情获取失败') |
| | | } |
| | |
| | | |
| | | // 从API获取项目详情 |
| | | async getProjectDetailFromAPI(projectId) { |
| | | // 构建GraphQL查询 |
| | | // 构建GraphQL查询 - 与后端schema匹配 |
| | | const query = ` |
| | | query GetProjectDetail($id: ID!) { |
| | | activityPlayerDetail(id: $id) { |
| | | id |
| | | activityId |
| | | playerId |
| | | playerName |
| | | playerGender |
| | | playerPhone |
| | | playerEducation |
| | | playerBirthDate |
| | | playerIdCard |
| | | playerAddress |
| | | activityName |
| | | projectName |
| | | projectDescription |
| | | projectCategory |
| | | projectTags |
| | | projectFiles { |
| | | id |
| | | fileName |
| | | fileUrl |
| | | fileSize |
| | | fileType |
| | | uploadTime |
| | | } |
| | | submitTime |
| | | reviewTime |
| | | reviewerId |
| | | reviewerName |
| | | score |
| | | rating { |
| | | id |
| | | judgeId |
| | | judgeName |
| | | score |
| | | feedback |
| | | ratingTime |
| | | } |
| | | state |
| | | description |
| | | feedback |
| | | state |
| | | stageId |
| | | playerInfo { |
| | | id |
| | | name |
| | | phone |
| | | gender |
| | | birthday |
| | | education |
| | | introduction |
| | | description |
| | | avatarUrl |
| | | avatar { |
| | | id |
| | | name |
| | | path |
| | | } |
| | | userInfo { |
| | | userId |
| | | name |
| | | phone |
| | | avatarUrl |
| | | avatar { |
| | | id |
| | | name |
| | | path |
| | | } |
| | | } |
| | | } |
| | | regionInfo { |
| | | id |
| | | name |
| | | fullPath |
| | | } |
| | | submissionFiles { |
| | | id |
| | | name |
| | | path |
| | | url |
| | | fullUrl |
| | | fullThumbUrl |
| | | fileExt |
| | | fileSize |
| | | thumbUrl |
| | | } |
| | | ratingForm { |
| | | schemeId |
| | | schemeName |
| | | totalMaxScore |
| | | items { |
| | | id |
| | | name |
| | | maxScore |
| | | weight |
| | | } |
| | | } |
| | | } |
| | | } |
| | | ` |
| | |
| | | async getRatingStatsFromAPI(projectId) { |
| | | const query = ` |
| | | query GetRatingStats($activityPlayerId: ID!) { |
| | | ratingStats(activityPlayerId: $activityPlayerId) { |
| | | averageScore |
| | | totalRatings |
| | | scoreDistribution { |
| | | score |
| | | count |
| | | } |
| | | judgeRatingsForPlayer(activityPlayerId: $activityPlayerId) { |
| | | judgeId |
| | | judgeName |
| | | hasRated |
| | | totalScore |
| | | ratingTime |
| | | } |
| | | averageScoreForPlayer(activityPlayerId: $activityPlayerId) |
| | | } |
| | | ` |
| | | |
| | | try { |
| | | const result = await app.graphqlRequest(query, { activityPlayerId: projectId }) |
| | | return result.ratingStats |
| | | const ratings = result.judgeRatingsForPlayer || [] |
| | | const averageScore = result.averageScoreForPlayer || 0 |
| | | |
| | | // 只计算已评分的评委 |
| | | const ratedJudges = ratings.filter(rating => rating.hasRated) |
| | | |
| | | return { |
| | | averageScore: averageScore, |
| | | totalRatings: ratedJudges.length, |
| | | ratings: ratings |
| | | } |
| | | } catch (error) { |
| | | throw error |
| | | } |
| | |
| | | } catch (error) { |
| | | throw error |
| | | } |
| | | }, |
| | | |
| | | // 加载项目时间轴 |
| | | async loadProjectTimeline() { |
| | | try { |
| | | this.setData({ |
| | | timelineLoading: true, |
| | | timelineError: '' |
| | | }) |
| | | |
| | | const timeline = await this.getProjectTimelineFromAPI(this.data.projectId) |
| | | |
| | | if (timeline && timeline.stages) { |
| | | // 处理时间轴数据 |
| | | const processedTimeline = timeline.stages.map(stage => { |
| | | return { |
| | | stageId: stage.stageId, |
| | | stageName: stage.stageName, |
| | | matchTime: stage.matchTime, |
| | | matchTimeText: stage.matchTime ? this.formatDateTime(stage.matchTime) : '时间待定', |
| | | participated: stage.participated, |
| | | activityPlayerId: stage.activityPlayerId, |
| | | averageScore: stage.averageScore, |
| | | ratingCount: stage.ratingCount, |
| | | hasRating: stage.hasRating, |
| | | scoreText: stage.averageScore ? `${stage.averageScore.toFixed(1)}分` : '', |
| | | isClickable: stage.hasRating && stage.activityPlayerId |
| | | } |
| | | }) |
| | | |
| | | this.setData({ |
| | | timeline: processedTimeline, |
| | | timelineLoading: false |
| | | }) |
| | | } else { |
| | | this.setData({ |
| | | timeline: [], |
| | | timelineLoading: false |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.error('加载时间轴失败:', error) |
| | | this.setData({ |
| | | timelineError: error.message || '时间轴加载失败', |
| | | timelineLoading: false |
| | | }) |
| | | } |
| | | }, |
| | | |
| | | // 从API获取项目时间轴 |
| | | async getProjectTimelineFromAPI(activityPlayerId) { |
| | | const query = ` |
| | | query GetProjectTimeline($activityPlayerId: ID!) { |
| | | projectStageTimeline(activityPlayerId: $activityPlayerId) { |
| | | activityId |
| | | activityName |
| | | stages { |
| | | stageId |
| | | stageName |
| | | matchTime |
| | | sortOrder |
| | | participated |
| | | activityPlayerId |
| | | averageScore |
| | | ratingCount |
| | | hasRating |
| | | latestRatingTime |
| | | } |
| | | } |
| | | } |
| | | ` |
| | | |
| | | try { |
| | | const result = await app.graphqlRequest(query, { activityPlayerId }) |
| | | return result.projectStageTimeline |
| | | } catch (error) { |
| | | throw error |
| | | } |
| | | }, |
| | | |
| | | // 打开阶段详情 |
| | | openStageDetail(e) { |
| | | const { playerId, clickable, participated } = e.currentTarget.dataset |
| | | |
| | | if (!clickable || !participated || !playerId) { |
| | | wx.showToast({ |
| | | title: '暂无评分详情', |
| | | icon: 'none' |
| | | }) |
| | | return |
| | | } |
| | | |
| | | // 跳转到评分详情页面或显示详情弹窗 |
| | | wx.navigateTo({ |
| | | url: `/pages/review/stage-detail?playerId=${playerId}` |
| | | }) |
| | | }, |
| | | |
| | | // 关闭阶段详情 |
| | | closeStageDetail() { |
| | | this.setData({ |
| | | showRatingDetail: false, |
| | | ratingDetail: null |
| | | }) |
| | | }, |
| | | |
| | | // 预览文件 |
| | |
| | | // 获取性别文本 |
| | | getGenderText(gender) { |
| | | const genderMap = { |
| | | 1: '男', |
| | | 2: '女', |
| | | 'MALE': '男', |
| | | 'FEMALE': '女' |
| | | } |
| | |
| | | 'COLLEGE': '大专', |
| | | 'BACHELOR': '本科', |
| | | 'MASTER': '硕士', |
| | | 'DOCTOR': '博士' |
| | | 'DOCTOR': '博士', |
| | | '高中及以下': '高中及以下', |
| | | '大专': '大专', |
| | | '本科': '本科', |
| | | '硕士': '硕士', |
| | | '博士': '博士' |
| | | } |
| | | return educationMap[education] || '' |
| | | return educationMap[education] || education || '' |
| | | }, |
| | | |
| | | // 分享功能 |
| | |
| | | |
| | | } catch (error) { |
| | | console.error('提交失败:', error) |
| | | wx.showToast({ |
| | | title: error.message || '提交失败,请重试', |
| | | icon: 'none' |
| | | wx.showModal({ |
| | | title: '提交失败', |
| | | content: error.message || '提交失败,请重试', |
| | | showCancel: false, |
| | | confirmText: '我知道了' |
| | | }) |
| | | } finally { |
| | | this.setData({ isSubmitting: false }) |