Forráskód Böngészése

Merge branch 'feature/0902_courseware' into test

# Conflicts:
#	cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/TenantAlbumMusicServiceImpl.java
Eric 2 hete
szülő
commit
480dcd8505
16 módosított fájl, 257 hozzáadás és 10 törlés
  1. 1 0
      cooleshow-app/src/main/java/com/yonge/cooleshow/config/ResourceServerConfig.java
  2. 0 1
      cooleshow-app/src/main/java/com/yonge/cooleshow/teacher/controller/TenantAlbumSheetController.java
  3. 15 0
      cooleshow-auth/auth-api/src/main/java/com/yonge/cooleshow/auth/api/client/SysUserFeignService.java
  4. 32 0
      cooleshow-auth/auth-api/src/main/java/com/yonge/cooleshow/auth/api/client/fallback/SysUserFeignServiceFallback.java
  5. 7 0
      cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/core/provider/PhoneAuthenticationProvider.java
  6. 1 1
      cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/core/provider/service/DefaultUserDetailsService.java
  7. 8 1
      cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/core/service/CustomAuthenticationKeyGenerator.java
  8. 88 0
      cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/service/impl/CbsQrCodeScanServiceImpl.java
  9. 33 0
      cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/web/controller/OpenQrLoginController.java
  10. 0 2
      cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/web/controller/TokenController.java
  11. 8 0
      cooleshow-auth/pom.xml
  12. 7 1
      cooleshow-common/src/main/java/com/yonge/cooleshow/common/service/IdGeneratorService.java
  13. 13 0
      cooleshow-common/src/main/java/com/yonge/cooleshow/common/service/impl/RedisIdGeneratorService.java
  14. 25 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/cbs/impl/CbsQrCodeScanServiceImpl.java
  15. 4 4
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/TenantAlbumMusicServiceImpl.java
  16. 15 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/wrapper/TenantAlbumMusicWrapper.java

+ 1 - 0
cooleshow-app/src/main/java/com/yonge/cooleshow/config/ResourceServerConfig.java

@@ -38,6 +38,7 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
 						"/*/appVersionInfo/queryByPlatform",
 						"/*/uploadFile",
 						"/*/open/**",
+						"/open/**",
 						"/*/room/statusSync",
 						"/*/room/tencentRtcCallback",
 						"/*/wechat/*",

+ 0 - 1
cooleshow-app/src/main/java/com/yonge/cooleshow/teacher/controller/TenantAlbumSheetController.java

@@ -3,7 +3,6 @@ package com.yonge.cooleshow.teacher.controller;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.dayaedu.cbs.openfeign.client.CoursewareFeignService;
-import com.dayaedu.cbs.openfeign.wrapper.courseware.CbsLessonCoursewareDetailWrapper;
 import com.dayaedu.cbs.openfeign.wrapper.courseware.CbsLessonCoursewareWrapper;
 import com.microsvc.toolkit.common.response.paging.QueryInfo;
 import com.yonge.cooleshow.auth.api.client.SysUserFeignService;

+ 15 - 0
cooleshow-auth/auth-api/src/main/java/com/yonge/cooleshow/auth/api/client/SysUserFeignService.java

@@ -1,5 +1,6 @@
 package com.yonge.cooleshow.auth.api.client;
 
+import com.dayaedu.cbs.openfeign.wrapper.qrcode.CbsQrCodeScanWrapper;
 import com.yonge.cooleshow.auth.api.client.fallback.SysUserFeignServiceFallback;
 import com.yonge.cooleshow.auth.api.dto.RealnameAuthReq;
 import com.yonge.cooleshow.auth.api.dto.SysUserQueryInfo;
@@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestParam;
 
 import java.util.List;
+import java.util.Map;
 
 @FeignClient(contextId = "sysUserFeignService", name = "${app-config.open-feign.auth-server.name:auth-server}", url = "${app-config.open-feign.auth-server.url:}",
 		configuration = { FeignConfiguration.class }, fallback = SysUserFeignServiceFallback.class)
@@ -83,4 +85,17 @@ public interface SysUserFeignService {
 	@ApiOperation(value = "获取登录用户client")
 	HttpResponseResult<String> client();
 
+	@PostMapping(value = "open/qrcode/userInfo")
+	CbsQrCodeScanWrapper.UserInfo userInfo(@RequestBody CbsQrCodeScanWrapper.QrCodeScanUserInfoReq req);
+
+	@PostMapping(value = "open/qrcode/login")
+	CbsQrCodeScanWrapper.QrCodeScanToken login(@RequestBody CbsQrCodeScanWrapper.QrCodeScanReq req);
+
+	@PostMapping(value = "smsLogin")
+	HttpResponseResult<Map<String,Object>> smsLogin(@RequestParam("phone")String phone,
+													@RequestParam("smsCode")String smsCode,
+													@RequestParam("loginType")String loginType,
+													@RequestParam("clientId")String clientId,
+													@RequestParam("clientSecret")String clientSecret);
+
 }

+ 32 - 0
cooleshow-auth/auth-api/src/main/java/com/yonge/cooleshow/auth/api/client/fallback/SysUserFeignServiceFallback.java

@@ -1,7 +1,9 @@
 package com.yonge.cooleshow.auth.api.client.fallback;
 
 import java.util.List;
+import java.util.Map;
 
+import com.dayaedu.cbs.openfeign.wrapper.qrcode.CbsQrCodeScanWrapper;
 import com.yonge.cooleshow.auth.api.dto.RealnameAuthReq;
 import com.yonge.cooleshow.auth.api.dto.SysUserQueryInfo;
 import com.yonge.toolset.utils.idcard.IdcardInfoExtractor;
@@ -97,4 +99,34 @@ public class SysUserFeignServiceFallback implements SysUserFeignService {
 		return null;
 	}
 
+	/**
+	 * @param req
+	 * @return
+	 */
+	@Override
+	public CbsQrCodeScanWrapper.UserInfo userInfo(CbsQrCodeScanWrapper.QrCodeScanUserInfoReq req) {
+		return null;
+	}
+
+	/**
+	 * @param req
+	 * @return
+	 */
+	@Override
+	public CbsQrCodeScanWrapper.QrCodeScanToken login(CbsQrCodeScanWrapper.QrCodeScanReq req) {
+		return null;
+	}
+
+	/**
+	 * @param phone
+	 * @param smsCode
+	 * @param clientId
+	 * @param loginType
+	 * @param clientSecret
+	 * @return
+	 */
+	@Override
+	public HttpResponseResult<Map<String, Object>> smsLogin(String phone, String smsCode, String loginType, String clientId, String clientSecret) {
+		return null;
+	}
 }

+ 7 - 0
cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/core/provider/PhoneAuthenticationProvider.java

@@ -28,6 +28,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.jwt.Jwt;
 import org.springframework.security.jwt.JwtHelper;
 import org.springframework.security.jwt.crypto.sign.RsaVerifier;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.security.interfaces.RSAPublicKey;
@@ -147,6 +148,12 @@ public class PhoneAuthenticationProvider extends AbstractAuthenticationProvider
                 if (!b) {
                     throw new BadCredentialsException("验证码校验失败");
                 }
+
+                // 老师扫码登陆
+                if (loginEntity.getClientId().toLowerCase().startsWith("qr_")) {
+                    // 重置登录账号信息
+                    loginEntity.setClientId(loginEntity.getClientId().toLowerCase().replace("qr_", "").toUpperCase());
+                }
             }
 
             String clientId = loginEntity.getClientId();

+ 1 - 1
cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/core/provider/service/DefaultUserDetailsService.java

@@ -79,7 +79,7 @@ public class DefaultUserDetailsService implements UserDetailsService {
             if (UserLockFlag.LOCKED.equals(data.getLockFlag())) {
                 throw new LockedException("账户被锁定");
             }
-        }else if(SysUserType.TEACHER.getCode().equals(clientId)) {
+        }else if(SysUserType.TEACHER.getCode().equals(clientId) || "qr_teacher".equalsIgnoreCase(clientId)) {
             TeacherApi data = adminFeignService.getTeacher(sysUser.getId()).getData();
             if (data == null) {
                 throw new UsernameNotFoundException("账户不存在");

+ 8 - 1
cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/core/service/CustomAuthenticationKeyGenerator.java

@@ -4,6 +4,7 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.TreeSet;
 
+import cn.hutool.core.util.RandomUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.security.oauth2.common.util.OAuth2Utils;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
@@ -25,7 +26,13 @@ public class CustomAuthenticationKeyGenerator extends DefaultAuthenticationKeyGe
 		if (!authentication.isClientOnly()) {
 			values.put(USERNAME, StringUtils.substringAfter(authentication.getName(), ":"));
 		}
-		values.put(CLIENT_ID, authorizationRequest.getClientId());
+
+		String clientId = authorizationRequest.getClientId();
+		if (StringUtils.isNotEmpty(clientId) && "qr_teacher".equalsIgnoreCase(clientId)) {
+			// 酷乐秀老师端扫码登录,允许多端登录,同时生成多个token
+			clientId += ":" + RandomUtil.randomNumbers(6);
+		}
+		values.put(CLIENT_ID, clientId);
 		if (authorizationRequest.getScope() != null) {
 			values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope())));
 		}

+ 88 - 0
cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/service/impl/CbsQrCodeScanServiceImpl.java

@@ -0,0 +1,88 @@
+package com.yonge.cooleshow.auth.service.impl;
+
+import cn.hutool.core.util.RandomUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.dayaedu.cbs.openfeign.wrapper.qrcode.CbsQrCodeScanWrapper;
+import com.yonge.cooleshow.auth.api.client.SysUserFeignService;
+import com.yonge.cooleshow.auth.api.entity.SysUser;
+import com.yonge.cooleshow.auth.core.service.CustomTokenServices;
+import com.yonge.cooleshow.auth.dal.dao.SysConfigDao;
+import com.yonge.cooleshow.auth.service.SysUserService;
+import com.yonge.cooleshow.common.entity.HttpResponseResult;
+import com.yonge.cooleshow.common.service.IdGeneratorService;
+import com.yonge.toolset.base.exception.BizException;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Map;
+import java.util.Objects;
+
+@Service
+public class CbsQrCodeScanServiceImpl {
+
+    @Resource
+    private CustomTokenServices customTokenServices;
+    @Resource
+    private SysUserService sysUserService;
+    @Resource
+    private SysUserFeignService sysUserFeignService;
+    @Resource
+    private SysConfigDao sysConfigDao;
+
+    @Autowired
+    private IdGeneratorService smsCodeService;
+
+    public CbsQrCodeScanWrapper.UserInfo userInfo(CbsQrCodeScanWrapper.QrCodeScanUserInfoReq req) {
+        CbsQrCodeScanWrapper.UserInfo userInfo = new CbsQrCodeScanWrapper.UserInfo();
+        userInfo.setUsername(customTokenServices.loadAuthentication(req.getToken()).getName().split(":")[1]);
+        userInfo.setClientTypes("TEACHER");
+
+        SysUser sysUser = sysUserService.queryByPhone(userInfo.getUsername());
+        if (sysUser == null) {
+            throw new BizException(HttpStatus.UNAUTHORIZED.value(), "用户不存在");
+        }
+        // 查询老师机构ID
+        Long tenantId = sysUserService.getTenantByClient(sysUser.getId(), "TEACHER");
+        if (Objects.nonNull(tenantId) && tenantId == -1L) {
+            // 平台老师不允许扫码登录乐教通,返回机构ID
+            userInfo.setClientTypes("-1");
+        }
+        return userInfo;
+    }
+
+    public CbsQrCodeScanWrapper.QrCodeScanToken login(CbsQrCodeScanWrapper.QrCodeScanReq req) {
+        //校验是否过期
+        OAuth2Authentication auth2Authentication = customTokenServices.loadAuthentication(req.getPassword());
+        String phone = auth2Authentication.getName().split(":")[1];
+        SysUser sysUser = sysUserService.queryByPhone(phone);
+        if (sysUser == null) {
+            throw new BizException(HttpStatus.UNAUTHORIZED.value(), "用户不存在");
+        }
+        //获取需要排除的用户编号
+        String excludeUserIds = sysConfigDao.findConfigValue("exclude_user_ids");
+        if(StringUtils.isNotEmpty(excludeUserIds) && excludeUserIds.contains(sysUser.getId().toString())){
+            throw new BizException("扫码登陆失败: 用户已锁定");
+        }
+
+        // 生成6位随机数验证码
+        String code = RandomUtil.randomNumbers(6);
+        // 保存验证码
+        smsCodeService.saveVerifyValidCode(phone, code, "SMS_VERIFY_CODE_LOGIN");
+        //调用登陆接口
+        HttpResponseResult<Map<String,Object>> result = sysUserFeignService.smsLogin(phone, code,"SMS", req.getClientId(), req.getClientSecret());
+        if (result != null){
+            if(result.getCode() != 200){
+                throw new BizException("扫码登陆失败", result.getMsg());
+            }
+            CbsQrCodeScanWrapper.QrCodeScanToken qrCodeScanToken = new CbsQrCodeScanWrapper.QrCodeScanToken();
+            JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(result.getData().get("authentication")));
+            qrCodeScanToken.setTokenData(jsonObject.getString("access_token"));
+            return qrCodeScanToken;
+        }
+        throw new BizException("扫码登陆失败", "调用登陆接口失败");
+    }
+}

+ 33 - 0
cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/web/controller/OpenQrLoginController.java

@@ -0,0 +1,33 @@
+package com.yonge.cooleshow.auth.web.controller;
+
+import com.dayaedu.cbs.openfeign.wrapper.qrcode.CbsQrCodeScanWrapper;
+import com.yonge.cooleshow.auth.service.impl.CbsQrCodeScanServiceImpl;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+@RestController
+@Api(tags = "扫码登陆")
+@RequestMapping(value = "/open")
+public class OpenQrLoginController {
+
+	@Resource
+	private CbsQrCodeScanServiceImpl cbsQrCodeScanService;
+
+	@ApiOperation(value = "获取用户信息")
+	@PostMapping(value = "/qrcode/userInfo")
+	public CbsQrCodeScanWrapper.UserInfo userInfo(@RequestBody CbsQrCodeScanWrapper.QrCodeScanUserInfoReq req) {
+		return cbsQrCodeScanService.userInfo(req);
+	}
+
+	@ApiOperation(value = "生成token")
+	@PostMapping(value = "/qrcode/login")
+	public CbsQrCodeScanWrapper.QrCodeScanToken login(@RequestBody CbsQrCodeScanWrapper.QrCodeScanReq req) {
+		return cbsQrCodeScanService.login(req);
+	}
+}

+ 0 - 2
cooleshow-auth/auth-server/src/main/java/com/yonge/cooleshow/auth/web/controller/TokenController.java

@@ -1,6 +1,5 @@
 package com.yonge.cooleshow.auth.web.controller;
 
-import cn.hutool.core.net.URLEncodeUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -46,7 +45,6 @@ import org.springframework.web.client.RestTemplate;
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
-import java.text.MessageFormat;
 import java.util.Base64;
 import java.util.Calendar;
 import java.util.Locale;

+ 8 - 0
cooleshow-auth/pom.xml

@@ -25,4 +25,12 @@
         <module>auth-api</module>
         <module>auth-server</module>
     </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.dayaedu.cbs.openfeign</groupId>
+            <artifactId>dayaedu-openfeign-api</artifactId>
+            <version>${cbs.version}</version>
+        </dependency>
+    </dependencies>
 </project>

+ 7 - 1
cooleshow-common/src/main/java/com/yonge/cooleshow/common/service/IdGeneratorService.java

@@ -23,5 +23,11 @@ public interface IdGeneratorService {
 	 */
 	boolean verifyValidCode(String mobile, String authCode,String keyEnum);
 
-
+	/**
+	 * 保存验证码
+	 * @param mobile 手机号
+	 * @param authCode 验证码
+	 * @param keyEnum 验证码类型
+	 */
+	void saveVerifyValidCode(String mobile, String authCode,String keyEnum);
 }

+ 13 - 0
cooleshow-common/src/main/java/com/yonge/cooleshow/common/service/impl/RedisIdGeneratorService.java

@@ -113,6 +113,19 @@ public class RedisIdGeneratorService implements IdGeneratorService {
 		}
     }
 
+	/**
+	 * 保存验证码
+	 *
+	 * @param mobile   手机号
+	 * @param authCode 验证码
+	 * @param keyEnum  验证码类型
+	 */
+	@Override
+	public void saveVerifyValidCode(String mobile, String authCode, String keyEnum) {
+		String key = "verificationCode" + keyEnum + mobile;
+		// 验证码有效性:10分钟
+		redisCache.put(key, authCode, 600);
+	}
 
 
 }

+ 25 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/cbs/impl/CbsQrCodeScanServiceImpl.java

@@ -0,0 +1,25 @@
+package com.yonge.cooleshow.biz.dal.service.cbs.impl;
+
+import com.dayaedu.cbs.openfeign.service.CbsQrCodeScanService;
+import com.dayaedu.cbs.openfeign.wrapper.qrcode.CbsQrCodeScanWrapper;
+import com.yonge.cooleshow.auth.api.client.SysUserFeignService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+@Service
+public class CbsQrCodeScanServiceImpl implements CbsQrCodeScanService {
+
+    @Resource
+    private SysUserFeignService sysUserFeignService;
+
+    @Override
+    public CbsQrCodeScanWrapper.UserInfo userInfo(CbsQrCodeScanWrapper.QrCodeScanUserInfoReq req) {
+        return sysUserFeignService.userInfo(req);
+    }
+
+    @Override
+    public CbsQrCodeScanWrapper.QrCodeScanToken login(CbsQrCodeScanWrapper.QrCodeScanReq req) {
+        return sysUserFeignService.login(req);
+    }
+}

+ 4 - 4
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/TenantAlbumMusicServiceImpl.java

@@ -113,8 +113,8 @@ public class TenantAlbumMusicServiceImpl extends ServiceImpl<TenantAlbumMusicMap
             if (CollectionUtils.isEmpty(musicSheets)) {
                 return page.setRecords(musicSheets);
             }
-            CbsMusicSheetWrapper.MusicSheetApplicationQuery cbsQuery = musicSheetService.getMusicSheetApplicationQuery(SourceTypeEnum.TENANT);
-            List<Long> cbsMusicSheetIds = musicSheets.stream().map(e -> e.getCbsMusicSheetId()).collect(Collectors.toList());
+            CbsMusicSheetWrapper.MusicSheetApplicationQuery cbsQuery = musicSheetService.getMusicSheetApplicationQuery();
+            List<Long> cbsMusicSheetIds = musicSheets.stream().map(TenantAlbumMusicWrapper.StudentTenantAlbumMusic::getCbsMusicSheetId).collect(Collectors.toList());
             cbsQuery.setMusicSheetIds(cbsMusicSheetIds);
             cbsQuery.setRows(cbsMusicSheetIds.size());
             List<CbsMusicSheetWrapper.MusicSheetApplication> rows = musicSheetService.queryCbsMusicSheetApplication(cbsQuery);
@@ -200,7 +200,7 @@ public class TenantAlbumMusicServiceImpl extends ServiceImpl<TenantAlbumMusicMap
                     studentTenantAlbumMusic.setTitleImg(row.getCoverImg());
                     studentTenantAlbumMusic.setMusicSubject(row.getCourseTypeCode());
                     studentTenantAlbumMusic.setMusicSubjectName(ECourseType.valueOf(row.getCourseTypeCode()).getName());
-                    musicSheets.add(studentTenantAlbumMusic);
+                    musicSheets.add(studentTenantAlbumMusic.courseNum(row.getCourseNum()).unitTestNum(row.getUnitTestNum()));
 
                 }
 
@@ -421,7 +421,7 @@ public class TenantAlbumMusicServiceImpl extends ServiceImpl<TenantAlbumMusicMap
             } else {
                 List<Long> useAlbumIdsByUserId = userTenantAlbumRecordService.getUseAlbumIdsByUserId(query.getUserId(), query.getClient());
                 if (CollectionUtils.isNotEmpty(useAlbumIdsByUserId)) {
-                    List<Long> list = tenantAlbumMusicService.getTenantAlbumMusicIdsByIds(useAlbumIdsByUserId, Arrays.asList(SubjectTypeEnum.COURSEWARE));
+                    List<Long> list = tenantAlbumMusicService.getTenantAlbumMusicIdsByIds(useAlbumIdsByUserId, Collections.singletonList(SubjectTypeEnum.COURSEWARE));
                     if (list.contains(query.getLessonCoursewareId())) {
                         lessonCoursewareDto.setPlay(true);
                     }

+ 15 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/wrapper/TenantAlbumMusicWrapper.java

@@ -253,6 +253,12 @@ public class TenantAlbumMusicWrapper {
         @ApiModelProperty("声谱类型,SINGLE:单曲,CONCERT:合奏")
         private String musicSheetType;
 
+        @ApiModelProperty("课时数")
+        private Integer courseNum;
+
+        @ApiModelProperty("单元测验数量")
+        private Integer unitTestNum;
+
         public String jsonString() {
             return JSON.toJSONString(this);
         }
@@ -261,6 +267,15 @@ public class TenantAlbumMusicWrapper {
             return JSON.parseObject(json, TenantAlbumMusic.class);
         }
 
+        public StudentTenantAlbumMusic unitTestNum(Integer unitTestNum) {
+            this.unitTestNum = unitTestNum;
+            return this;
+        }
+
+        public StudentTenantAlbumMusic courseNum(Integer courseNum) {
+            this.courseNum = courseNum;
+            return this;
+        }
     }
 
 }