8 Commits

Author SHA1 Message Date
none
9344f81772 部门列表api返回部门的学员数量 2023-04-12 13:55:22 +08:00
none
3fda4b6926 update 2023-04-11 17:09:14 +08:00
none
af205cd939 update 2023-04-11 17:07:29 +08:00
none
cd746f6240 update 2023-04-11 16:44:05 +08:00
none
2f49a9bdaf 学员的默认头像 2023-04-11 11:12:25 +08:00
none
7251c68e83 优化系统配置返回 2023-04-11 10:36:05 +08:00
none
8dbe266132 update 2023-04-11 09:20:52 +08:00
none
474baab4c1 update 2023-04-11 09:19:29 +08:00
17 changed files with 182 additions and 40 deletions

View File

@@ -1,26 +1,43 @@
<p align="center">
<img src="https://playedu.xyz/images/index/logo-big.png?v=2023032901" width="150"/>
<img src="https://www.playedu.xyz/docs/github-header.jpg"/>
</p>
<h1 align="center">PlayEdu开源培训系统</h1>
<p align="center">一款开源的培训系统,您可以使用它快速搭建私有化内部培训平台</p>
### 演示站点
| 站点 | 账号 | 密码 |
|--------------------------------------------------------|---------------------|----------|
| [https://demo.playedu.xyz](https://demo.playedu.xyz) | `1@playedu.xyz` | `123123` |
| [https://admin.playedu.xyz](https://admin.playedu.xyz) | `admin@playedu.xyz` | `123123` |
### 功能明细
+ **学员端:** PC端、移动端(开发中)
+ **线上课:** 关联分类、关联部门、章节课/无章节课
+ **学员:** 批量导入、关联部门、学习记录、学员信息
+ **后台管理:** 分类管理(多级)、部门管理(多级)、视频资源管理、图片资源管理、课程管理
+ **数据统计:** 学习进度明细(任务进度/课程进度/课时进度)、资源明细统计、每日学习排名统计、学员每日学习时长统计、学员总学习时长统计
+ **播放管理:** 记忆续播、防录屏跑马灯
+ **其他:** 权限管理、网站管理
### 常用链接
+ [官网](https://playedu.xyz)
+ [文档](https://playedu.xyz/docs/docs/intro/)
+ [快速上手](https://playedu.xyz/docs/docs/install/quick)
+ [功能列表](https://playedu.xyz/docs/docs/function)
- [官网](https://playedu.xyz)
- [文档](https://playedu.xyz/docs/docs/intro/)
- [快速上手](https://playedu.xyz/docs/docs/install/quick)
- [功能列表](https://playedu.xyz/docs/docs/function)
### 依赖项目
+ [PC 界面程序](https://github.com/PlayEdu/frontend)
+ [后台界面程序](https://github.com/PlayEdu/backend)
- [PC 界面程序](https://github.com/PlayEdu/frontend)
- [后台界面程序](https://github.com/PlayEdu/backend)
### 技术栈
+ Java17 + SpringBoot3 + MySQL
+ React18 + Vite
- Java17 + SpringBoot3 + MySQL
- React18 + Vite
### 开发团队
@@ -30,5 +47,5 @@
欢迎使用杭州白书科技有限公司提供的开源培训解决方案!请您仔细阅读以下条款。通过使用 PlayEdu ,您表示同意接受以下所有条款。
+ 本开源项目中所有代码基于 Apache-2.0 许可协议,您默认遵守许可协议中约定的义务。
+ 您默认授权我们将您使用 PlayEdu 所在业务的 Logo 放置在本官网展示。
- 本开源项目中所有代码基于 Apache-2.0 许可协议,您默认遵守许可协议中约定的义务。
- 您默认授权我们将您使用 PlayEdu 所在业务的 Logo 放置在本官网展示。

View File

@@ -10,6 +10,7 @@ import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import xyz.playedu.api.constant.BackendConstant;
import xyz.playedu.api.constant.CConfig;
import xyz.playedu.api.domain.AppConfig;
import xyz.playedu.api.service.AppConfigService;
@@ -35,7 +36,7 @@ public class AppConfigCheck implements ApplicationRunner {
setName("网站名");
setSort(10);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_INPUT);
setKeyName("system.name");
setKeyName(CConfig.SYSTEM_NAME);
setKeyValue("");
setHelp("请输入网站名");
}
@@ -45,7 +46,7 @@ public class AppConfigCheck implements ApplicationRunner {
setName("Logo");
setSort(20);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_IMAGE);
setKeyName("system.logo");
setKeyName(CConfig.SYSTEM_LOGO);
setKeyValue("");
}
},
@@ -54,7 +55,7 @@ public class AppConfigCheck implements ApplicationRunner {
setName("API访问地址");
setSort(30);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_INPUT);
setKeyName("system.api_url");
setKeyName(CConfig.SYSTEM_API_URL);
setKeyValue("");
setHelp("请输入API访问地址");
}
@@ -64,7 +65,7 @@ public class AppConfigCheck implements ApplicationRunner {
setName("PC端口访问地址");
setSort(40);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_INPUT);
setKeyName("system.pc_url");
setKeyName(CConfig.SYSTEM_PC_URL);
setKeyValue("");
setHelp("请输入PC端访问地址");
}
@@ -74,7 +75,7 @@ public class AppConfigCheck implements ApplicationRunner {
setName("H5端口访问地址");
setSort(50);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_INPUT);
setKeyName("system.h5_url");
setKeyName(CConfig.SYSTEM_H5_URL);
setKeyValue("");
setHelp("请输入H5端访问地址");
}
@@ -143,6 +144,19 @@ public class AppConfigCheck implements ApplicationRunner {
}
},
});
put(
"学员配置",
new AppConfig[] {
new AppConfig() {
{
setName("默认头像");
setSort(10);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_IMAGE);
setKeyName(CConfig.MEMBER_DEFAULT_AVATAR);
setKeyValue("");
}
},
});
}
};

View File

@@ -0,0 +1,21 @@
/**
* This file is part of the PlayEdu.
* (c) 杭州白书科技有限公司
*/
package xyz.playedu.api.constant;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/4/11 10:12
*/
public class CConfig {
public static final String SYSTEM_NAME = "system.name";
public static final String SYSTEM_LOGO = "system.logo";
public static final String SYSTEM_API_URL = "system.api_url";
public static final String SYSTEM_PC_URL = "system.pc_url";
public static final String SYSTEM_H5_URL = "system.h5_url";
public static final String MEMBER_DEFAULT_AVATAR = "member.default_avatar";
}

View File

@@ -47,6 +47,7 @@ public class DepartmentController {
public JsonResponse index() {
HashMap<String, Object> data = new HashMap<>();
data.put("departments", departmentService.groupByParent());
data.put("dep_user_count", departmentService.getDepartmentsUserCount());
return JsonResponse.data(data);
}
@@ -133,7 +134,7 @@ public class DepartmentController {
@DeleteMapping("/{id}")
public JsonResponse destroy(@PathVariable Integer id) throws NotFoundException {
Department department = departmentService.findOrFail(id);
departmentService.deleteById(department.getId());
departmentService.destroy(department.getId());
ctx.publishEvent(new DepartmentDestroyEvent(this, BCtx.getId(), department.getId()));
return JsonResponse.success();
}

View File

@@ -4,22 +4,29 @@
*/
package xyz.playedu.api.controller.backend;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.playedu.api.BCtx;
import xyz.playedu.api.constant.CConfig;
import xyz.playedu.api.service.ImageCaptchaService;
import xyz.playedu.api.types.ImageCaptchaResult;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.RequestUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/backend/v1/system")
@Slf4j
public class SystemController {
@Autowired private ImageCaptchaService imageCaptchaService;
@@ -37,7 +44,34 @@ public class SystemController {
@GetMapping("/config")
public JsonResponse config() {
Map<String, String> data = BCtx.getConfig();
Map<String, String> configData = BCtx.getConfig();
String apiUrl = configData.get(CConfig.SYSTEM_API_URL);
if (apiUrl == null || apiUrl.trim().length() == 0) {
apiUrl = RequestUtil.uriWithProtocol();
}
HashMap<String, Object> data = new HashMap<>();
data.put(CConfig.SYSTEM_NAME, configData.get(CConfig.SYSTEM_NAME));
data.put(CConfig.SYSTEM_LOGO, configData.get(CConfig.SYSTEM_LOGO));
data.put(CConfig.SYSTEM_API_URL, apiUrl);
data.put(CConfig.SYSTEM_PC_URL, configData.get(CConfig.SYSTEM_PC_URL));
data.put(CConfig.SYSTEM_H5_URL, configData.get(CConfig.SYSTEM_H5_URL));
// 学员的默认头像
String memberDefaultAvatar = configData.get(CConfig.MEMBER_DEFAULT_AVATAR);
if (memberDefaultAvatar == null || memberDefaultAvatar.trim().length() == 0) {
data.put(CConfig.MEMBER_DEFAULT_AVATAR, apiUrl + "/images/default_avatar.png");
}
// 内置的三个线上课封面
List<String> defaultCourseThumbs = new ArrayList<>();
defaultCourseThumbs.add(apiUrl + "/images/courses/thumb1.png");
defaultCourseThumbs.add(apiUrl + "/images/courses/thumb2.png");
defaultCourseThumbs.add(apiUrl + "/images/courses/thumb3.png");
data.put("default.course_thumbs", defaultCourseThumbs);
return JsonResponse.data(data);
}
}

View File

@@ -13,7 +13,9 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.api.BCtx;
import xyz.playedu.api.constant.BPermissionConstant;
import xyz.playedu.api.constant.CConfig;
import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.domain.Department;
import xyz.playedu.api.domain.User;
@@ -188,8 +190,12 @@ public class UserController {
return JsonResponse.error("一次最多导入1000条数据");
}
// 导入表格的有效数据起始行-用于错误提醒
Integer startLine = req.getStartLine();
// 默认的学员头像
String defaultAvatar = BCtx.getConfig().get(CConfig.MEMBER_DEFAULT_AVATAR);
List<String[]> errorLines = new ArrayList<>();
errorLines.add(new String[] {"错误行", "错误信息"}); // 错误表-表头
@@ -287,6 +293,7 @@ public class UserController {
tmpInsertUser.setPassword(HelperUtil.MD5(tmpPassword + tmpSalt));
tmpInsertUser.setSalt(tmpSalt);
tmpInsertUser.setName(tmpName);
tmpInsertUser.setAvatar(defaultAvatar);
tmpInsertUser.setIdCard(userItem.getIdCard());
tmpInsertUser.setCreateIp(SystemConstant.INTERNAL_IP);
tmpInsertUser.setCreateCity(SystemConstant.INTERNAL_IP_AREA);

View File

@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.playedu.api.constant.CConfig;
import xyz.playedu.api.service.AppConfigService;
import xyz.playedu.api.service.ImageCaptchaService;
import xyz.playedu.api.types.ImageCaptchaResult;
@@ -37,11 +38,11 @@ public class SystemController {
HashMap<String, String> data = new HashMap<>();
data.put("system-name", configs.get("system.name"));
data.put("system-logo", configs.get("system.logo"));
data.put("system-api-url", configs.get("system.api_url"));
data.put("system-pc-url", configs.get("system.pc_url"));
data.put("system-h5-url", configs.get("system.h5_url"));
data.put("system-name", configs.get(CConfig.SYSTEM_NAME));
data.put("system-logo", configs.get(CConfig.SYSTEM_LOGO));
data.put("system-api-url", configs.get(CConfig.SYSTEM_API_URL));
data.put("system-pc-url", configs.get(CConfig.SYSTEM_PC_URL));
data.put("system-h5-url", configs.get(CConfig.SYSTEM_H5_URL));
data.put("system-pc-index-footer-msg", configs.get("system.pc_index_footer_msg"));
data.put("player-poster", configs.get("player.poster"));

View File

@@ -9,6 +9,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import xyz.playedu.api.domain.Department;
import xyz.playedu.api.types.mapper.DepartmentsUserCountMapRes;
import java.util.List;
/**
* @author tengteng
@@ -16,4 +19,6 @@ import xyz.playedu.api.domain.Department;
* @createDate 2023-02-19 12:19:45 @Entity xyz.playedu.api.domain.Department
*/
@Mapper
public interface DepartmentMapper extends BaseMapper<Department> {}
public interface DepartmentMapper extends BaseMapper<Department> {
List<DepartmentsUserCountMapRes> getDepartmentsUserCount();
}

View File

@@ -25,13 +25,11 @@ public interface DepartmentService extends IService<Department> {
Department findOrFail(Integer id) throws NotFoundException;
void deleteById(Integer id) throws NotFoundException;
void destroy(Integer id) throws NotFoundException;
void update(Department department, String name, Integer parentId, Integer sort)
throws NotFoundException;
List<Integer> allIds();
String compParentChain(Integer parentId) throws NotFoundException;
String childrenParentChain(Department department);
@@ -53,4 +51,6 @@ public interface DepartmentService extends IService<Department> {
Map<Integer, String> id2name();
Long total();
Map<Integer, Integer> getDepartmentsUserCount();
}

View File

@@ -21,6 +21,7 @@ import xyz.playedu.api.mapper.DepartmentMapper;
import xyz.playedu.api.service.CourseDepartmentService;
import xyz.playedu.api.service.DepartmentService;
import xyz.playedu.api.service.internal.UserDepartmentService;
import xyz.playedu.api.types.mapper.DepartmentsUserCountMapRes;
import java.util.*;
import java.util.stream.Collectors;
@@ -60,7 +61,7 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
@Override
@Transactional
public void deleteById(Integer id) throws NotFoundException {
public void destroy(Integer id) throws NotFoundException {
Department department = findOrFail(id);
updateParentChain(department.getParentChain(), childrenParentChain(department));
removeById(department.getId());
@@ -136,16 +137,6 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
updateBatchById(updateRows);
}
@Override
public List<Integer> allIds() {
List<Department> departments = list(query().getWrapper().eq("1", "1").select("id"));
List<Integer> ids = new ArrayList<>();
for (Department department : departments) {
ids.add(department.getId());
}
return ids;
}
@Override
public String compParentChain(Integer parentId) throws NotFoundException {
String parentChain = "";
@@ -257,4 +248,13 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
public Long total() {
return count();
}
@Override
public Map<Integer, Integer> getDepartmentsUserCount() {
return getBaseMapper().getDepartmentsUserCount().stream()
.collect(
Collectors.toMap(
DepartmentsUserCountMapRes::getDepId,
DepartmentsUserCountMapRes::getTotal));
}
}

View File

@@ -0,0 +1,19 @@
/**
* This file is part of the PlayEdu.
* (c) 杭州白书科技有限公司
*/
package xyz.playedu.api.types.mapper;
import lombok.Data;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/4/12 13:46
*/
@Data
public class DepartmentsUserCountMapRes {
private Integer depId;
private Integer total;
}

View File

@@ -54,6 +54,13 @@ public class RequestUtil {
+ (Arrays.asList(443, 80, 0).contains(portNumber) ? "" : ":" + portNumber);
}
public static String uriWithProtocol() {
Integer portNumber = port();
return RequestUtil.protocol()
+ RequestUtil.domain()
+ (Arrays.asList(443, 80, 0).contains(portNumber) ? "" : ":" + portNumber);
}
public static String pathname() {
HttpServletRequest request = RequestUtil.handler();
return request == null ? "" : request.getRequestURI();
@@ -74,4 +81,15 @@ public class RequestUtil {
}
return null;
}
public static String protocol() {
HttpServletRequest request = RequestUtil.handler();
if (request != null) {
String requestUrl = request.getRequestURL().toString();
List<String> urls = Arrays.asList(requestUrl.split("//"));
return urls.get(0) + "//";
}
return null;
}
}

View File

@@ -15,8 +15,13 @@
</resultMap>
<sql id="Base_Column_List">
id,name,parent_id,
parent_chain,sort,created_at,
id,name,parent_id,
parent_chain,sort,created_at,
updated_at
</sql>
<select id="getDepartmentsUserCount" resultType="xyz.playedu.api.types.mapper.DepartmentsUserCountMapRes">
SELECT `dep_id`, count(1) AS `total`
FROM user_department
GROUP BY dep_id;
</select>
</mapper>

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB