82 Commits

Author SHA1 Message Date
xxx
a1d5a9c648 后台学员列表返回每个部门的学员数量 2023-09-23 21:11:23 +08:00
xxx
11fbf34b65 docker 时区 2023-09-22 13:37:41 +08:00
xxx
1103a89e71 系统配置接口返回部门和分类 2023-09-21 13:51:28 +08:00
xxx
baf4d50b33 ldap配置优化 2023-09-21 13:44:36 +08:00
xxx
700da4c468 新增资源和分类左侧菜单权限 2023-09-20 17:17:32 +08:00
xxx
e8c08f9c09 课程列表权限 2023-09-20 17:10:49 +08:00
xxx
99830cb707 新增文件上传权限 2023-09-20 17:02:29 +08:00
xxx
3fa8a047d0 补齐资源分类的权限 2023-09-20 16:56:47 +08:00
xxx
ef87c684c6 ldap注释 2023-09-20 16:51:17 +08:00
xxx
acb2ce83db playedu-api finally-name 2023-09-20 16:48:50 +08:00
xxx
f69f19bdb3 docker version 2023-09-20 16:45:32 +08:00
xxx
9fef487b13 fixed: LDAP的登录部门解析需要包含上级组织作用域 2023-09-20 16:41:32 +08:00
xxx
b685a21717 新增LDAP的部门同步 2023-09-20 16:33:58 +08:00
xxx
a82e2992b4 兼容window ad域 2023-09-20 14:41:13 +08:00
xxx
ad6151ab39 update 2023-09-06 16:54:05 +08:00
xxx
9b621e4e62 后台系统配置接口返回是否启用ldap 2023-09-06 11:15:58 +08:00
xxx
46b0bb7555 fixed: 系统private配置为空也返回* 2023-09-05 20:16:01 +08:00
xxx
657d418e7a fixed: 密码登录 2023-09-05 20:14:13 +08:00
xxx
8cf5bc0a6e 优化redis的连接失败的错误提示 2023-09-05 16:28:13 +08:00
xxx
06da295d58 增加管理员日志详情接口 2023-09-05 16:23:53 +08:00
xxx
61eb5be2ee fixed: 管理员日志报错 2023-09-05 16:16:25 +08:00
xxx
e1519f49cb dockerfile 2023-09-05 11:32:56 +08:00
xxx
6952f8679d 删除冗余代码 2023-09-04 16:44:07 +08:00
xxx
4fd23b0417 优化异常信息 2023-09-04 16:41:58 +08:00
xxx
4897401d32 ldap注释help 2023-09-01 13:56:13 +08:00
白书科技
d5e410cb1f !7 优化
ldap登录
2023-09-01 03:48:39 +00:00
xxx
069c3e4cc9 优化http拦截器 2023-08-29 15:02:21 +08:00
xxx
76dceaa44d 优化 2023-08-29 14:21:43 +08:00
xxx
4387471d76 课程上架时间 2023-08-29 14:19:08 +08:00
xxx
5a93eb9423 课程新增published_at字段 2023-08-29 13:54:17 +08:00
xxx
828f3e08b9 ppt文件存储路径 2023-08-27 13:55:38 +08:00
xxx
95e2652615 移除Minio的pre-sign-url的限流 2023-08-27 13:41:21 +08:00
xxx
1d31045807 数据表自动迁移 2023-08-27 13:28:13 +08:00
xxx
2e0801fb57 文件上传最大尺寸设置为10mb 2023-08-27 10:47:24 +08:00
xxx
828f6446a5 Merge branch 'main' into dev 2023-08-27 10:39:19 +08:00
xxx
b34320350d docker build 2023-08-27 10:37:58 +08:00
xxx
30806bfdcf update 2023-08-27 10:05:18 +08:00
xxx
54abd4ae6c sql 2023-08-27 10:02:32 +08:00
白书科技
acffce65ba !6 优化
Merge pull request !6 from 白书科技/feat-splitmodule20230804
2023-08-07 00:49:30 +00:00
wsw
e72d351d25 拆分模块框架初始化 2023-08-04 13:49:13 +08:00
none
26074efea4 Merge branch 'dev' 2023-07-31 14:42:40 +08:00
白书科技
86cd51a0d6 !5 优化
Merge pull request !5 from 白书科技/feat管理员日志-2023-07-30
2023-07-30 09:07:47 +00:00
wsw
bdd1b1bbb9 学员端--课程附件列表查询--增加ext参数 2023-07-30 15:36:24 +08:00
wsw
b7c4410028 课程附件列表查询--增加ext参数 2023-07-30 15:09:51 +08:00
wsw
db4c92e23e 管理员日志-增加管理员名称 2023-07-30 11:21:38 +08:00
白书科技
5c1346e22c !4 优化
Merge pull request !4 from 白书科技/feat课程附件功能-2023-07-22
2023-07-29 10:39:13 +00:00
none
8d18d5423b adminName -> admin_name 2023-07-29 17:28:26 +08:00
none
099642675b fixed: 默认的限流次数 2023-07-29 17:22:11 +08:00
wsw
f3103559d5 日志解析json报错 2023-07-29 17:18:58 +08:00
wsw
935ee6a500 管理员日志条件查询 2023-07-29 17:10:45 +08:00
none
c6eef3477e added: 管理员日志权限 2023-07-29 16:51:46 +08:00
wsw
67c3578609 管理员日志查询 2023-07-29 16:19:59 +08:00
none
c65013f266 后台课件操作日志c 2023-07-29 15:12:16 +08:00
wsw
ae0b3ab9e0 Merge branch 'feat课程附件功能-2023-07-22' of https://gitee.com/playeduxyz/playedu into feat课程附件功能-2023-07-22 2023-07-29 15:04:41 +08:00
wsw
bc194e6be2 管理员日志-入参出差脱敏处理 2023-07-29 15:04:20 +08:00
none
0aefff9aaf 课程附件下载api地址修改 2023-07-29 15:04:14 +08:00
none
e28e2e30af 课程附件下载重构 2023-07-29 15:01:39 +08:00
none
d697ded1ec admin_logs表机构调整 2023-07-29 13:55:11 +08:00
wsw
277332d410 学员课程附件列表查询、下载日志记录 2023-07-29 13:41:47 +08:00
none
ae13fa0201 resolve conflcit 2023-07-29 09:39:10 +08:00
wsw
b9623983c7 课程与附件关联 2023-07-29 09:28:28 +08:00
wsw
cd475f080e 课程附件表 2023-07-28 18:01:35 +08:00
none
e0f807909f 管理员日志 2023-07-28 16:18:09 +08:00
none
c88fc11c7e v1.2数据库变动sqlc 2023-07-28 16:14:42 +08:00
none
39519db020 代码优化 2023-07-28 16:00:46 +08:00
wsw
46015593e9 课程附件列表查询 2023-07-27 21:59:16 +08:00
wsw
3aa2ea9990 课程附件列表查询 2023-07-27 17:46:15 +08:00
wsw
0da1c9d0d2 管理员日志--入参做脱敏操作 2023-07-26 22:11:00 +08:00
wsw
b58ee9dbc6 管理员日志重构--修改入参获取不到问题 2023-07-26 18:33:58 +08:00
wsw
93752a9ca7 管理员日志重构--新增管理员日志注解 2023-07-25 18:46:35 +08:00
wsw
3eb7864582 课程附件查询接口,返回值增加已有附件类型列表 2023-07-24 11:12:40 +08:00
wsw
295992e4d4 课程附件-资源上传及查询改造 2023-07-23 23:02:46 +08:00
none
1edc205e9f 增加testing配置 2023-07-06 17:50:21 +08:00
Teng
f9c6a6ad74 Merge pull request #14 from PlayEdu/dev
Dev
2023-07-04 20:01:12 +08:00
none
d1d7c43a3a 默认的api限流次数 2023-07-03 20:51:26 +08:00
none
e554c01c7b fixed: conflict 2023-07-03 17:53:51 +08:00
none
a23155cb27 优化登录限制的提示 2023-07-03 17:41:44 +08:00
none
c987b34b9b 文案修改 2023-07-03 17:37:03 +08:00
none
4c085d4836 fixed: 前后台的账号体系的冲突 2023-07-03 17:28:45 +08:00
none
ce1cf5b475 文案修改 2023-07-03 17:26:24 +08:00
none
da3a6eff37 dockerfile 2023-07-03 14:32:31 +08:00
白书科技
192326bf7e !2 移除图形验证码 && api限流 && 账户限流
* 账户登录限流
* added: api限流
* 移除图形验证码
2023-07-03 06:31:53 +00:00
347 changed files with 7403 additions and 3602 deletions

36
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: EstablishDockerImage
on:
push:
branches:
- main
- dev
- 'feat/**'
env:
IMAGE_FQDN: registry.cn-hangzhou.aliyuncs.com/playedu/api
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALI_REGISTRY_EMAIL }}
password: ${{ secrets.ALI_REGISTRY_PASS }}
- name: Build
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ env.IMAGE_FQDN }}:1.4

1
.gitignore vendored
View File

@@ -33,4 +33,5 @@ build/
.vscode/
/src/main/resources/application-dev.yml
/playedu-api/src/main/resources/application-dev.yml
/logs

View File

@@ -1,23 +1,25 @@
FROM eclipse-temurin:17-alpine as builder
WORKDIR /app
FROM eclipse-temurin:17 as builder
COPY . /app
RUN /app/docker-build.sh
WORKDIR /app
FROM eclipse-temurin:17-alpine
RUN /app/mvnw -Dmaven.test.skip=true clean package
FROM eclipse-temurin:17
WORKDIR /app
# 使用东八区时间环境
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
# 将指定目录下的jar包复制到docker容器的/目录下
COPY --from=builder /app/target/playedu-api-*.jar /app/app.jar
COPY --from=builder /app/playedu-api/target/playedu-api.jar /app/app.jar
RUN chmod +x /app/app.jar
# 声明服务运行在8080端口
EXPOSE 9898
# 指定docker容器启动时运行jar包
ENTRYPOINT ["java", "-jar", "app.jar"]
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

View File

@@ -33,4 +33,10 @@ PlayEdu 是由白书科技团队经营多年线上教育系统打造出的一款
### 使用协议
本开源项目中所有代码基于 Apache-2.0 许可协议。
● 要求
- 保留页脚处版权信息。
- 保留源代码中的协议。
- 如果修改了代码,则必须在文件中进行说明。
● 允许
- 私用、商用、修改。

View File

@@ -1,468 +0,0 @@
# ************************************************************
# Sequel Pro SQL dump
# Version 4541
#
# http://www.sequelpro.com/
# https://github.com/sequelpro/sequelpro
#
# Host: 127.0.0.1 (MySQL 5.6.51)
# Database: playedu
# Generation Time: 2023-04-06 03:00:20 +0000
# ************************************************************
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# Dump of table admin_logs
# ------------------------------------------------------------
CREATE TABLE `admin_logs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`admin_id` int(11) NOT NULL DEFAULT '0' COMMENT '管理员ID',
`module` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '模块',
`opt` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '操作指令',
`remark` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '备注',
`ip` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'ip',
`ip_area` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '地址',
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a_m_o` (`admin_id`,`module`,`opt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
# Dump of table admin_permissions
# ------------------------------------------------------------
CREATE TABLE `admin_permissions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '类型[行为:action,数据:data]',
`group_name` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '分组',
`sort` int(11) NOT NULL COMMENT '升序',
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '权限名',
`slug` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'slug',
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
# Dump of table admin_role_permission
# ------------------------------------------------------------
CREATE TABLE `admin_role_permission` (
`role_id` int(11) unsigned NOT NULL DEFAULT '0',
`perm_id` int(10) unsigned NOT NULL DEFAULT '0',
KEY `role_id` (`role_id`),
KEY `perm_id` (`perm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table admin_roles
# ------------------------------------------------------------
CREATE TABLE `admin_roles` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '角色名',
`slug` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'slug',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `admin_roles` (`name`, `slug`, `created_at`, `updated_at`)
VALUES
('超级管理角色', 'super-role', '2023-02-24 06:19:15', '2023-02-24 06:19:15');
# Dump of table admin_user_role
# ------------------------------------------------------------
CREATE TABLE `admin_user_role` (
`admin_id` int(11) unsigned NOT NULL DEFAULT '0',
`role_id` int(10) unsigned NOT NULL DEFAULT '0',
KEY `admin_id` (`admin_id`),
KEY `role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `admin_user_role` (`admin_id`, `role_id`)
VALUES
(1, 1);
# Dump of table admin_users
# ------------------------------------------------------------
CREATE TABLE `admin_users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名',
`email` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱',
`password` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码',
`salt` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Salt',
`login_ip` varchar(15) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '登录IP',
`login_at` timestamp NULL DEFAULT NULL COMMENT '登录时间',
`is_ban_login` tinyint(4) NOT NULL DEFAULT '0' COMMENT '1禁止登录,0否',
`login_times` int(11) NOT NULL DEFAULT '0' COMMENT '登录次数',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `administrators_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `admin_users` (`name`, `email`, `password`, `salt`, `login_ip`, `login_at`, `is_ban_login`, `login_times`, `created_at`, `updated_at`)
VALUES
('超级管理员', 'admin@playedu.xyz', 'd771587aa711961304fa8c1a5273f491', 'VROkTh', '', '2023-04-06 16:51:17', 0, 0, '2023-02-19 18:10:12', '2023-04-06 16:51:17');
# Dump of table app_config
# ------------------------------------------------------------
CREATE TABLE `app_config` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`group_name` varchar(24) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '分组',
`name` varchar(24) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '升序',
`field_type` varchar(24) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '字段类型',
`key_name` varchar(188) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '',
`key_value` longtext COLLATE utf8mb4_unicode_ci COMMENT '',
`option_value` text COLLATE utf8mb4_unicode_ci COMMENT '可选值',
`is_private` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否私密信息',
`help` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '帮助信息',
`created_at` timestamp NULL DEFAULT NULL,
`is_hidden` tinyint(4) NOT NULL DEFAULT '0' COMMENT '1显示,0否',
PRIMARY KEY (`id`),
UNIQUE KEY `app_config_key_unique` (`key_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
# Dump of table course_chapters
# ------------------------------------------------------------
CREATE TABLE `course_chapters` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`course_id` int(11) NOT NULL DEFAULT '0' COMMENT '课程ID',
`name` varchar(64) NOT NULL DEFAULT '' COMMENT '章节名',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '升序',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table course_department
# ------------------------------------------------------------
CREATE TABLE `course_department` (
`course_id` int(11) NOT NULL DEFAULT '0',
`dep_id` int(11) NOT NULL DEFAULT '0',
KEY `course_id` (`course_id`),
KEY `dep_id` (`dep_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table course_hour
# ------------------------------------------------------------
CREATE TABLE `course_hour` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`course_id` int(11) NOT NULL DEFAULT '0' COMMENT '课程ID',
`chapter_id` int(11) NOT NULL DEFAULT '0' COMMENT '章节ID',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '升序',
`title` varchar(255) NOT NULL DEFAULT '' COMMENT '课时名',
`type` varchar(20) NOT NULL DEFAULT '' COMMENT '课时类型',
`rid` int(11) NOT NULL DEFAULT '0' COMMENT '资源id',
`duration` int(11) NOT NULL COMMENT '时长[s]',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `course_id` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table courses
# ------------------------------------------------------------
CREATE TABLE `courses` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL DEFAULT '' COMMENT '课程标题',
`thumb` varchar(255) NOT NULL DEFAULT '' COMMENT '课程封面',
`charge` int(11) NOT NULL DEFAULT '0' COMMENT '课程价格(分)',
`short_desc` varchar(255) NOT NULL DEFAULT '' COMMENT '简介',
`class_hour` int(11) NOT NULL DEFAULT '0' COMMENT '课时数',
`is_show` tinyint(4) NOT NULL DEFAULT '0' COMMENT '显示[1:是,0:否]',
`is_required` tinyint(4) NOT NULL DEFAULT '0' COMMENT '1:必修,0:选修',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` timestamp NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table departments
# ------------------------------------------------------------
CREATE TABLE `departments` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL DEFAULT '' COMMENT '部门名',
`parent_id` int(11) NOT NULL COMMENT '父id',
`parent_chain` varchar(255) NOT NULL DEFAULT '' COMMENT '父链',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '升序',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table resource_categories
# ------------------------------------------------------------
CREATE TABLE `resource_categories` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL DEFAULT '0',
`parent_chain` varchar(2550) NOT NULL DEFAULT '',
`name` varchar(64) NOT NULL DEFAULT '' COMMENT '分类名',
`sort` int(11) NOT NULL COMMENT '升序',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table resource_category
# ------------------------------------------------------------
CREATE TABLE `resource_category` (
`cid` int(11) NOT NULL DEFAULT '0',
`rid` int(11) NOT NULL,
KEY `cid` (`cid`),
KEY `rid` (`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table resource_course_category
# ------------------------------------------------------------
CREATE TABLE `resource_course_category` (
`course_id` int(11) NOT NULL DEFAULT '0',
`category_id` int(11) NOT NULL DEFAULT '0',
KEY `course_id` (`course_id`),
KEY `category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table resource_videos
# ------------------------------------------------------------
CREATE TABLE `resource_videos` (
`rid` int(11) unsigned NOT NULL,
`poster` varchar(255) NOT NULL DEFAULT '' COMMENT '封面',
`duration` int(10) unsigned NOT NULL COMMENT '视频时长[s]',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE KEY `rid` (`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table resources
# ------------------------------------------------------------
CREATE TABLE `resources` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`admin_id` int(11) NOT NULL DEFAULT '0',
`type` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '类型',
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '资源名',
`extension` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '文件类型',
`size` bigint(20) DEFAULT '0' COMMENT '大小[字节]',
`disk` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '存储磁盘',
`file_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '文件id',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '相对地址',
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'URL地址',
`created_at` timestamp NULL DEFAULT NULL,
`parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '所属素材',
`is_hidden` tinyint(4) NOT NULL DEFAULT '0' COMMENT '隐藏[0:否,1:是]',
PRIMARY KEY (`id`),
KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
# Dump of table user_course_hour_records
# ------------------------------------------------------------
CREATE TABLE `user_course_hour_records` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL DEFAULT '0',
`course_id` int(11) NOT NULL DEFAULT '0',
`hour_id` int(11) NOT NULL DEFAULT '0',
`total_duration` int(11) NOT NULL DEFAULT '0' COMMENT '总时长',
`finished_duration` int(11) NOT NULL DEFAULT '0' COMMENT '已完成时长',
`real_duration` int(11) NOT NULL DEFAULT '0' COMMENT '实际观看时长',
`is_finished` tinyint(4) DEFAULT NULL COMMENT '是否看完[1:是,0:否]',
`finished_at` timestamp NULL DEFAULT NULL COMMENT '看完时间',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `u_h_c_id` (`user_id`,`hour_id`,`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table user_course_records
# ------------------------------------------------------------
CREATE TABLE `user_course_records` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL DEFAULT '0',
`course_id` int(11) NOT NULL DEFAULT '0',
`hour_count` int(11) NOT NULL DEFAULT '0' COMMENT '课时数量',
`finished_count` int(11) NOT NULL DEFAULT '0' COMMENT '已完成课时数',
`progress` int(11) NOT NULL DEFAULT '0' COMMENT '进度',
`is_finished` tinyint(4) NOT NULL DEFAULT '0' COMMENT '看完[1:是,0:否]',
`finished_at` timestamp NULL DEFAULT NULL COMMENT '看完时间',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table user_department
# ------------------------------------------------------------
CREATE TABLE `user_department` (
`user_id` int(11) unsigned NOT NULL DEFAULT '0',
`dep_id` int(11) unsigned NOT NULL DEFAULT '0',
KEY `user_id` (`user_id`),
KEY `dep_id` (`dep_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table user_learn_duration_records
# ------------------------------------------------------------
CREATE TABLE `user_learn_duration_records` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL DEFAULT '0',
`created_date` date NOT NULL,
`duration` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '已学习时长[微秒]',
`start_at` timestamp NULL DEFAULT NULL COMMENT '开始时间',
`end_at` timestamp NULL DEFAULT NULL COMMENT '结束时间',
`course_id` int(11) NOT NULL DEFAULT '0',
`hour_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `u_d` (`user_id`,`created_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table user_learn_duration_stats
# ------------------------------------------------------------
CREATE TABLE `user_learn_duration_stats` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL DEFAULT '0',
`duration` bigint(20) NOT NULL DEFAULT '0',
`created_date` date NOT NULL,
PRIMARY KEY (`id`),
KEY `u_d` (`user_id`,`created_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table user_login_records
# ------------------------------------------------------------
CREATE TABLE `user_login_records` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`jti` varchar(64) NOT NULL DEFAULT '' COMMENT 'JTI',
`ip` varchar(15) NOT NULL DEFAULT '' COMMENT '登录ip',
`ip_area` varchar(64) NOT NULL DEFAULT '' COMMENT 'Ip解析区域',
`browser` varchar(64) NOT NULL DEFAULT '' COMMENT '浏览器',
`browser_version` varchar(64) NOT NULL DEFAULT '' COMMENT '浏览器版本',
`os` varchar(128) NOT NULL DEFAULT '' COMMENT '操作系统',
`expired` bigint(20) NOT NULL DEFAULT '0' COMMENT '过期时间',
`is_logout` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否注销',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `jti` (`jti`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table user_upload_image_logs
# ------------------------------------------------------------
CREATE TABLE `user_upload_image_logs` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL DEFAULT '0',
`typed` varchar(32) NOT NULL DEFAULT '' COMMENT '图片类型',
`scene` varchar(24) NOT NULL DEFAULT '' COMMENT '上传场景',
`driver` varchar(32) NOT NULL DEFAULT '' COMMENT '驱动',
`path` varchar(255) NOT NULL DEFAULT '' COMMENT '相对路径',
`url` varchar(255) NOT NULL DEFAULT '' COMMENT '访问地址',
`size` bigint(20) NOT NULL COMMENT '大小,单位:字节',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT '文件名',
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# Dump of table users
# ------------------------------------------------------------
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(32) NOT NULL DEFAULT '' COMMENT '邮件',
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '真实姓名',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
`password` varchar(128) NOT NULL DEFAULT '' COMMENT '密码',
`salt` varchar(12) NOT NULL DEFAULT '' COMMENT 'salt',
`id_card` varchar(64) NOT NULL DEFAULT '' COMMENT '身份证号',
`credit1` int(11) NOT NULL DEFAULT '0' COMMENT '学分',
`create_ip` varchar(15) NOT NULL DEFAULT '' COMMENT '注册Ip',
`create_city` varchar(32) NOT NULL DEFAULT '' COMMENT '注册城市',
`is_active` tinyint(4) NOT NULL DEFAULT '0' COMMENT '激活[1:是,0:否]',
`is_lock` tinyint(4) NOT NULL DEFAULT '0' COMMENT '锁定[1:是,0:否]',
`is_verify` tinyint(4) NOT NULL DEFAULT '0' COMMENT '实名认证[1:是,0:否]',
`verify_at` timestamp NULL DEFAULT NULL COMMENT '实名认证时间',
`is_set_password` tinyint(4) NOT NULL DEFAULT '0' COMMENT '设置密码[1:是,0:否]',
`login_at` timestamp NULL DEFAULT NULL COMMENT '登录时间',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

View File

@@ -1,13 +0,0 @@
#!/bin/sh
echo '设置M2_HOME...'
cp -r docker/.m2 /root
ls /root/.m2
echo '开始打包...'
export MAVEN_OPTS=-Dmaven.test.skip=true
/app/mvnw clean package

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<mirrors>
<mirror>
<id>Ali</id>
<name>Ali Maven</name>
<mirrorOf>*</mirrorOf>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
</mirrors>
</settings>

58
playedu-api/pom.xml Normal file
View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>xyz.playedu</groupId>
<artifactId>playedu</artifactId>
<version>1.2</version>
</parent>
<artifactId>playedu-api</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>xyz.playedu</groupId>
<artifactId>playedu-common</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>xyz.playedu</groupId>
<artifactId>playedu-system</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>xyz.playedu</groupId>
<artifactId>playedu-course</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>xyz.playedu</groupId>
<artifactId>playedu-resource</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<finalName>playedu-api</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -15,16 +15,22 @@
*/
package xyz.playedu.api;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import xyz.playedu.api.config.UniqueNameGenerator;
import xyz.playedu.common.config.UniqueNameGeneratorConfig;
@SpringBootApplication
@EnableAsync
@ComponentScan(nameGenerator = UniqueNameGenerator.class)
@EnableTransactionManagement
@ComponentScan(
basePackages = {"xyz.playedu"},
nameGenerator = UniqueNameGeneratorConfig.class)
@MapperScan("xyz.playedu.**.mapper")
public class PlayeduApiApplication {
public static void main(String[] args) {

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.bus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import xyz.playedu.api.event.UserLoginEvent;
import xyz.playedu.common.domain.LdapUser;
import xyz.playedu.common.domain.User;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.service.*;
import xyz.playedu.common.util.HelperUtil;
import xyz.playedu.common.util.IpUtil;
import xyz.playedu.common.util.RequestUtil;
import xyz.playedu.common.util.StringUtil;
import xyz.playedu.common.util.ldap.LdapTransformUser;
import java.util.HashMap;
@Component
@Slf4j
public class LoginBus {
@Autowired private FrontendAuthService authService;
@Autowired private DepartmentService departmentService;
@Autowired private LdapUserService ldapUserService;
@Autowired private UserService userService;
@Autowired private AppConfigService appConfigService;
@Autowired private ApplicationContext ctx;
public HashMap<String, Object> tokenByUser(User user) {
String token = authService.loginUsingId(user.getId(), RequestUtil.url());
HashMap<String, Object> data = new HashMap<>();
data.put("token", token);
ctx.publishEvent(
new UserLoginEvent(
this,
user.getId(),
user.getEmail(),
token,
IpUtil.getIpAddress(),
RequestUtil.ua()));
return data;
}
@Transactional
public HashMap<String, Object> tokenByLdapTransformUser(LdapTransformUser ldapTransformUser)
throws ServiceException {
// LDAP用户的名字
String ldapUserName = ldapTransformUser.getCn();
// 将LDAP用户所属的部门同步到本地
Integer depId = departmentService.createWithChainList(ldapTransformUser.getOu());
Integer[] depIds = depId == 0 ? null : new Integer[] {depId};
// LDAP用户在本地的缓存记录
LdapUser ldapUser = ldapUserService.findByUUID(ldapTransformUser.getId());
User user;
// 计算将LDAP用户关联到本地users表的email字段值
String localUserEmail = ldapTransformUser.getUid();
if (StringUtil.isNotEmpty(ldapTransformUser.getEmail())) {
localUserEmail = ldapTransformUser.getEmail();
}
if (ldapUser == null) {
// 检测localUserEmail是否存在
if (userService.find(localUserEmail) != null) {
throw new ServiceException(String.format("已有其它账号在使用:%s", localUserEmail));
}
// LDAP用户数据缓存到本地
ldapUser = ldapUserService.store(ldapTransformUser);
// 创建本地user
user =
userService.createWithDepIds(
localUserEmail,
ldapUserName,
appConfigService.defaultAvatar(),
HelperUtil.randomString(20),
"",
depIds);
// 将LDAP缓存数据与本地user关联
ldapUserService.updateUserId(ldapUser.getId(), user.getId());
} else {
user = userService.find(ldapUser.getUserId());
// 账号修改[账号有可能是email也有可能是uid]
if (!localUserEmail.equals(user.getEmail())) {
// 检测localUserEmail是否存在
if (userService.find(localUserEmail) != null) {
throw new ServiceException(String.format("已有其它账号在使用:%s", localUserEmail));
}
userService.updateEmail(user.getId(), localUserEmail);
}
// ldap-email的变化
if (!ldapUser.getEmail().equals(ldapTransformUser.getEmail())) {
ldapUserService.updateEmail(ldapUser.getId(), ldapTransformUser.getEmail());
}
// ldap-uid的变化
if (!ldapUser.getUid().equals(ldapTransformUser.getUid())) {
ldapUserService.updateUid(ldapUser.getId(), ldapTransformUser.getUid());
}
// 名字同步修改
if (!ldapUserName.equals(ldapUser.getCn())) {
userService.updateName(user.getId(), ldapUserName);
ldapUserService.updateCN(ldapUser.getId(), ldapUserName);
}
// 部门修改同步
String newOU = String.join(",", ldapTransformUser.getOu());
if (!newOU.equals(ldapUser.getOu())) {
userService.updateDepId(user.getId(), depIds);
ldapUserService.updateOU(ldapUser.getId(), newOU);
}
}
return tokenByUser(user);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import xyz.playedu.common.config.PlayEduConfig;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.service.RateLimiterService;
import xyz.playedu.common.util.RedisUtil;
@Component
public class LoginLimitCache {
@Autowired private RateLimiterService rateLimiterService;
@Autowired private PlayEduConfig playEduConfig;
public void check(String email) throws ServiceException {
String limitKey = cacheKey(email);
Long reqCount = rateLimiterService.current(limitKey, 600L);
if (reqCount >= 10 && !playEduConfig.getTesting()) {
Long exp = RedisUtil.ttlWithoutPrefix(limitKey);
String msg = String.format("您的账号已被锁定,请%s后重试", exp > 60 ? exp / 60 + "分钟" : exp + "");
throw new ServiceException(msg);
}
}
public void destroy(String email) {
RedisUtil.del(cacheKey(email));
}
private String cacheKey(String email) {
return "login-limit:" + email;
}
}

View File

@@ -13,25 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.bus;
package xyz.playedu.api.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import xyz.playedu.api.config.PlayEduConfig;
import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.common.util.RedisDistributedLock;
import java.util.concurrent.TimeUnit;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/2/19 12:06
*/
@Component
public class AppBus {
public class LoginLockCache {
@Autowired private PlayEduConfig playEduConfig;
@Autowired private RedisDistributedLock redisDistributedLock;
public boolean isDev() {
return !playEduConfig.getEnv().equals(SystemConstant.ENV_PROD);
public boolean apply(String username) {
String key = cacheKey(username);
return redisDistributedLock.tryLock(key, 10L, TimeUnit.SECONDS);
}
public void release(String username) {
redisDistributedLock.releaseLock(cacheKey(username));
}
private String cacheKey(String username) {
return "login-lock:" + username;
}
}

View File

@@ -17,6 +17,7 @@ package xyz.playedu.api.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -26,10 +27,10 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import xyz.playedu.api.exception.LimitException;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.exception.ServiceException;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.common.exception.LimitException;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.types.JsonResponse;
import java.util.List;
@@ -37,20 +38,26 @@ import java.util.List;
@Slf4j
public class ExceptionController {
// @ExceptionHandler(Exception.class)
// public JsonResponse exceptionHandler(Exception e) {
// log.error(e.getMessage());
// return JsonResponse.error("系统错误", 500);
// }
@ExceptionHandler(Exception.class)
public JsonResponse exceptionHandler(Exception e) {
log.error(e.getMessage());
return JsonResponse.error("系统错误", 500);
}
@ExceptionHandler(ServiceException.class)
public JsonResponse serviceExceptionHandler(ServiceException e) {
return JsonResponse.error(e.getMessage(), 1);
}
@ExceptionHandler(RedisConnectionFailureException.class)
public JsonResponse serviceExceptionHandler(RedisConnectionFailureException e) {
return JsonResponse.error("redis服务连接失败", 500);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public JsonResponse serviceExceptionHandler(HttpMessageNotReadableException e) {
return JsonResponse.error("参数为空", 406);
log.error("error", e);
return JsonResponse.error("前端提交参数解析失败", 406);
}
@ExceptionHandler(MethodArgumentNotValidException.class)

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.controller.backend;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.bus.BackendBus;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.domain.AdminLog;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.service.AdminLogService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.paginate.AdminLogPaginateFiler;
import xyz.playedu.common.types.paginate.PaginationResult;
import java.util.HashMap;
@RestController
@Slf4j
@RequestMapping("/backend/v1/admin/log")
public class AdminLogController {
@Autowired private AdminLogService adminLogService;
@Autowired private BackendBus backendBus;
@BackendPermission(slug = BPermissionConstant.ADMIN_LOG)
@GetMapping("/index")
@Log(title = "管理员日志-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
Integer page = MapUtils.getInteger(params, "page", 1);
Integer size = MapUtils.getInteger(params, "size", 10);
String sortField = MapUtils.getString(params, "sort_field");
String sortAlgo = MapUtils.getString(params, "sort_algo");
Integer adminId = MapUtils.getInteger(params, "admin_id");
String adminName = MapUtils.getString(params, "admin_name");
String module = MapUtils.getString(params, "module");
String title = MapUtils.getString(params, "title");
Integer opt = MapUtils.getInteger(params, "opt");
String startTime = MapUtils.getString(params, "start_time");
String endTime = MapUtils.getString(params, "end_time");
AdminLogPaginateFiler filter = new AdminLogPaginateFiler();
if (backendBus.isSuperAdmin()) {
filter.setAdminId(adminId);
} else {
filter.setAdminId(BCtx.getId());
}
filter.setAdminName(adminName);
filter.setModule(module);
filter.setTitle(title);
filter.setOpt(opt);
filter.setStartTime(startTime);
filter.setEndTime(endTime);
filter.setSortField(sortField);
filter.setSortAlgo(sortAlgo);
PaginationResult<AdminLog> result = adminLogService.paginate(page, size, filter);
HashMap<String, Object> data = new HashMap<>();
data.put("data", result.getData());
data.put("total", result.getTotal());
return JsonResponse.data(data);
}
@BackendPermission(slug = BPermissionConstant.ADMIN_LOG)
@GetMapping("/detail/{id}")
public JsonResponse detail(@PathVariable(name = "id") Integer id) {
Integer adminId = 0;
if (!backendBus.isSuperAdmin()) {
adminId = BCtx.getId();
}
AdminLog log = adminLogService.find(id, adminId);
if (log == null) {
throw new ServiceException("日志不存在");
}
return JsonResponse.data(log);
}
}

View File

@@ -21,16 +21,18 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.api.constant.BPermissionConstant;
import xyz.playedu.api.constant.BackendConstant;
import xyz.playedu.api.domain.AdminPermission;
import xyz.playedu.api.domain.AdminRole;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.AdminRoleRequest;
import xyz.playedu.api.service.AdminPermissionService;
import xyz.playedu.api.service.AdminRoleService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BackendConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.domain.AdminPermission;
import xyz.playedu.common.domain.AdminRole;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.service.AdminPermissionService;
import xyz.playedu.common.service.AdminRoleService;
import xyz.playedu.common.types.JsonResponse;
import java.util.ArrayList;
import java.util.HashMap;
@@ -38,11 +40,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/2/21 15:56
*/
@RestController
@RequestMapping("/backend/v1/admin-role")
@Slf4j
@@ -53,13 +50,15 @@ public class AdminRoleController {
@Autowired private AdminPermissionService permissionService;
@GetMapping("/index")
@Log(title = "管理员角色-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index() {
List<AdminRole> data = roleService.list();
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_ROLE)
@BackendPermission(slug = BPermissionConstant.ADMIN_ROLE)
@GetMapping("/create")
@Log(title = "管理员角色-新建", businessType = BusinessTypeConstant.GET)
public JsonResponse create() {
List<AdminPermission> permissions = permissionService.listOrderBySortAsc();
@@ -71,15 +70,17 @@ public class AdminRoleController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_ROLE)
@BackendPermission(slug = BPermissionConstant.ADMIN_ROLE)
@PostMapping("/create")
@Log(title = "管理员角色-新建", businessType = BusinessTypeConstant.INSERT)
public JsonResponse store(@RequestBody @Validated AdminRoleRequest request) {
roleService.createWithPermissionIds(request.getName(), request.getPermissionIds());
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_ROLE)
@BackendPermission(slug = BPermissionConstant.ADMIN_ROLE)
@GetMapping("/{id}")
@Log(title = "管理员角色-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(@PathVariable(name = "id") Integer id) throws NotFoundException {
AdminRole role = roleService.findOrFail(id);
@@ -111,8 +112,9 @@ public class AdminRoleController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_ROLE)
@BackendPermission(slug = BPermissionConstant.ADMIN_ROLE)
@PutMapping("/{id}")
@Log(title = "管理员角色-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(
@PathVariable(name = "id") Integer id, @RequestBody @Validated AdminRoleRequest request)
throws NotFoundException {
@@ -126,8 +128,9 @@ public class AdminRoleController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_ROLE)
@BackendPermission(slug = BPermissionConstant.ADMIN_ROLE)
@DeleteMapping("/{id}")
@Log(title = "管理员角色-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(@PathVariable(name = "id") Integer id) throws NotFoundException {
AdminRole role = roleService.findOrFail(id);

View File

@@ -22,18 +22,20 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.api.constant.BPermissionConstant;
import xyz.playedu.api.domain.AdminRole;
import xyz.playedu.api.domain.AdminUser;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.exception.ServiceException;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.AdminUserRequest;
import xyz.playedu.api.service.AdminRoleService;
import xyz.playedu.api.service.AdminUserService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.types.paginate.AdminUserPaginateFilter;
import xyz.playedu.api.types.paginate.PaginationResult;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.domain.AdminRole;
import xyz.playedu.common.domain.AdminUser;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.service.AdminRoleService;
import xyz.playedu.common.service.AdminUserService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.paginate.AdminUserPaginateFilter;
import xyz.playedu.common.types.paginate.PaginationResult;
import java.util.HashMap;
import java.util.List;
@@ -49,8 +51,9 @@ public class AdminUserController {
@Autowired private AdminRoleService roleService;
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_USER_INDEX)
@BackendPermission(slug = BPermissionConstant.ADMIN_USER_INDEX)
@GetMapping("/index")
@Log(title = "管理员-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse Index(@RequestParam HashMap<String, Object> params) {
Integer page = MapUtils.getInteger(params, "page", 1);
Integer size = MapUtils.getInteger(params, "size", 10);
@@ -81,8 +84,9 @@ public class AdminUserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_USER_CUD)
@BackendPermission(slug = BPermissionConstant.ADMIN_USER_CUD)
@GetMapping("/create")
@Log(title = "管理员-新建", businessType = BusinessTypeConstant.GET)
public JsonResponse create() {
List<AdminRole> roles = roleService.list();
@@ -92,11 +96,12 @@ public class AdminUserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_USER_CUD)
@BackendPermission(slug = BPermissionConstant.ADMIN_USER_CUD)
@PostMapping("/create")
@Log(title = "管理员-新建", businessType = BusinessTypeConstant.INSERT)
public JsonResponse store(@RequestBody @Validated AdminUserRequest req)
throws ServiceException {
if (req.getPassword() == null || req.getPassword().length() == 0) {
if (req.getPassword().length() == 0) {
return JsonResponse.error("请输入密码");
}
@@ -110,8 +115,9 @@ public class AdminUserController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_USER_CUD)
@BackendPermission(slug = BPermissionConstant.ADMIN_USER_CUD)
@GetMapping("/{id}")
@Log(title = "管理员-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(@PathVariable Integer id) throws NotFoundException {
AdminUser adminUser = adminUserService.findOrFail(id);
List<Integer> roleIds = adminUserService.getRoleIdsByUserId(adminUser.getId());
@@ -123,8 +129,9 @@ public class AdminUserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_USER_CUD)
@BackendPermission(slug = BPermissionConstant.ADMIN_USER_CUD)
@PutMapping("/{id}")
@Log(title = "管理员-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(
@PathVariable Integer id, @RequestBody @Validated AdminUserRequest req)
throws NotFoundException, ServiceException {
@@ -139,8 +146,9 @@ public class AdminUserController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.ADMIN_USER_CUD)
@BackendPermission(slug = BPermissionConstant.ADMIN_USER_CUD)
@DeleteMapping("/{id}")
@Log(title = "管理员-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(@PathVariable Integer id) {
adminUserService.removeWithRoleIds(id);
return JsonResponse.success();

View File

@@ -18,36 +18,36 @@ package xyz.playedu.api.controller.backend;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.api.constant.BPermissionConstant;
import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.domain.AppConfig;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.AppConfigRequest;
import xyz.playedu.api.service.AppConfigService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.constant.ConfigConstant;
import xyz.playedu.common.constant.SystemConstant;
import xyz.playedu.common.domain.AppConfig;
import xyz.playedu.common.service.AppConfigService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.StringUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/3/9 11:14
*/
@RestController
@RequestMapping("/backend/v1/app-config")
public class AppConfigController {
@Autowired private AppConfigService configService;
@BackendPermissionMiddleware(slug = BPermissionConstant.SYSTEM_CONFIG)
@BackendPermission(slug = BPermissionConstant.SYSTEM_CONFIG)
@GetMapping("")
@Log(title = "系统配置-读取", businessType = BusinessTypeConstant.GET)
public JsonResponse index() {
List<AppConfig> configs = configService.allShow();
List<AppConfig> data = new ArrayList<>();
for (AppConfig item : configs) {
if (item.getIsPrivate() == 1) {
if (item.getIsPrivate() == 1 && StringUtil.isNotEmpty(item.getKeyValue())) {
item.setKeyValue(SystemConstant.CONFIG_MASK);
}
data.add(item);
@@ -55,17 +55,28 @@ public class AppConfigController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.SYSTEM_CONFIG)
@BackendPermission(slug = BPermissionConstant.SYSTEM_CONFIG)
@PutMapping("")
@Log(title = "系统配置-保存", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse save(@RequestBody AppConfigRequest req) {
HashMap<String, String> data = new HashMap<>();
req.getData()
.forEach(
(key, value) -> {
// 过滤掉未变动的private配置
if (SystemConstant.CONFIG_MASK.equals(value)) {
return;
}
data.put(key, value);
String saveValue = value;
// LDAP的url配置自动加ldap://处理
if (ConfigConstant.LDAP_URL.equals(key)
&& StringUtil.isNotEmpty(value)
&& !StringUtil.startsWithIgnoreCase(value, "ldap://")) {
saveValue = "ldap://" + saveValue;
}
data.put(key, saveValue);
});
configService.saveFromMap(data);
return JsonResponse.data(null);

View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.controller.backend;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.api.request.backend.CourseAttachmentMultiRequest;
import xyz.playedu.api.request.backend.CourseAttachmentRequest;
import xyz.playedu.api.request.backend.CourseAttachmentSortRequest;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BackendConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.course.domain.CourseAttachment;
import xyz.playedu.course.service.CourseAttachmentService;
import java.util.*;
@RestController
@Slf4j
@RequestMapping("/backend/v1/course/{courseId}/attachment")
public class CourseAttachmentController {
@Autowired private CourseAttachmentService attachmentService;
@BackendPermission(slug = BPermissionConstant.COURSE)
@PostMapping("/create")
@Log(title = "线上课-附件-新建", businessType = BusinessTypeConstant.INSERT)
public JsonResponse store(
@PathVariable(name = "courseId") Integer courseId,
@RequestBody @Validated CourseAttachmentRequest req)
throws NotFoundException {
// 附件类型校验
String type = req.getType();
if (!BackendConstant.RESOURCE_TYPE_ATTACHMENT.contains(type)) {
return JsonResponse.error("附件类型不支持");
}
// 课时重复添加校验
List<Integer> existsRids = attachmentService.getRidsByCourseId(courseId);
if (existsRids != null) {
if (existsRids.contains(req.getRid())) {
return JsonResponse.error("附件已存在");
}
}
CourseAttachment courseAttachment =
attachmentService.create(
courseId, req.getSort(), req.getTitle(), type, req.getRid());
return JsonResponse.success();
}
@BackendPermission(slug = BPermissionConstant.COURSE)
@PostMapping("/create-batch")
@Transactional
@Log(title = "线上课-附件-批量新建", businessType = BusinessTypeConstant.INSERT)
public JsonResponse storeMulti(
@PathVariable(name = "courseId") Integer courseId,
@RequestBody @Validated CourseAttachmentMultiRequest req) {
if (req.getAttachments().size() == 0) {
return JsonResponse.error("参数为空");
}
List<Integer> existsRids = attachmentService.getRidsByCourseId(courseId);
List<CourseAttachment> attachments = new ArrayList<>();
Date now = new Date();
for (CourseAttachmentMultiRequest.AttachmentItem item : req.getAttachments()) {
if (existsRids.contains(item.getRid())) {
return JsonResponse.error("附件《" + item.getTitle() + "》已存在");
}
attachments.add(
new CourseAttachment() {
{
setCourseId(courseId);
setSort(item.getSort());
setType(item.getType());
setRid(item.getRid());
setTitle(item.getTitle());
setCreatedAt(now);
}
});
}
attachmentService.saveBatch(attachments);
return JsonResponse.success();
}
@BackendPermission(slug = BPermissionConstant.COURSE)
@GetMapping("/{id}")
@Log(title = "线上课-附件-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id)
throws NotFoundException {
CourseAttachment courseAttachment = attachmentService.findOrFail(id, courseId);
return JsonResponse.data(courseAttachment);
}
@BackendPermission(slug = BPermissionConstant.COURSE)
@PutMapping("/{id}")
@Log(title = "线上课-附件-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id,
@RequestBody @Validated CourseAttachmentRequest req)
throws NotFoundException {
CourseAttachment courseAttachment = attachmentService.findOrFail(id, courseId);
attachmentService.update(courseAttachment, req.getSort(), req.getTitle());
return JsonResponse.success();
}
@BackendPermission(slug = BPermissionConstant.COURSE)
@DeleteMapping("/{id}")
@Log(title = "线上课-附件-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id)
throws NotFoundException {
CourseAttachment courseAttachment = attachmentService.findOrFail(id, courseId);
attachmentService.removeById(courseAttachment.getId());
return JsonResponse.success();
}
@PutMapping("/update/sort")
@Log(title = "线上课-附件-排序调整", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse updateSort(
@PathVariable(name = "courseId") Integer courseId,
@RequestBody @Validated CourseAttachmentSortRequest req) {
attachmentService.updateSort(req.getIds(), courseId);
return JsonResponse.success();
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.controller.backend;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.paginate.CourseAttachmentDownloadLogPaginateFiler;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.course.domain.CourseAttachmentDownloadLog;
import xyz.playedu.course.service.CourseAttachmentDownloadLogService;
import java.util.*;
@RestController
@Slf4j
@RequestMapping("/backend/v1/course/attachment/download/log")
public class CourseAttachmentDownloadLogController {
@Autowired private CourseAttachmentDownloadLogService courseAttachmentDownloadLogService;
@GetMapping("/index")
@Log(title = "学员下载课件记录-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
Integer page = MapUtils.getInteger(params, "page", 1);
Integer size = MapUtils.getInteger(params, "size", 10);
String sortField = MapUtils.getString(params, "sort_field");
String sortAlgo = MapUtils.getString(params, "sort_algo");
Integer userId = MapUtils.getInteger(params, "user_id");
Integer courseId = MapUtils.getInteger(params, "course_id");
String title = MapUtils.getString(params, "title");
Integer courserAttachmentId = MapUtils.getInteger(params, "courser_attachment_id");
Integer rid = MapUtils.getInteger(params, "rid");
CourseAttachmentDownloadLogPaginateFiler filter =
new CourseAttachmentDownloadLogPaginateFiler();
filter.setUserId(userId);
filter.setCourseId(courseId);
filter.setTitle(title);
filter.setCourserAttachmentId(courserAttachmentId);
filter.setRid(rid);
filter.setSortField(sortField);
filter.setSortAlgo(sortAlgo);
PaginationResult<CourseAttachmentDownloadLog> result =
courseAttachmentDownloadLogService.paginate(page, size, filter);
HashMap<String, Object> data = new HashMap<>();
data.put("data", result.getData());
data.put("total", result.getTotal());
return JsonResponse.data(data);
}
}

View File

@@ -20,23 +20,20 @@ import org.springframework.context.ApplicationContext;
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.domain.CourseChapter;
import xyz.playedu.api.event.CourseChapterDestroyEvent;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.CourseChapterRequest;
import xyz.playedu.api.request.backend.CourseChapterSortRequest;
import xyz.playedu.api.service.CourseChapterService;
import xyz.playedu.api.service.CourseHourService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.course.domain.CourseChapter;
import xyz.playedu.course.service.CourseChapterService;
import xyz.playedu.course.service.CourseHourService;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/2/26 17:28
*/
@RestController
@RequestMapping("/backend/v1/course/{courseId}/chapter")
public class CourseChapterController {
@@ -47,8 +44,9 @@ public class CourseChapterController {
@Autowired private ApplicationContext ctx;
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@PostMapping("/create")
@Log(title = "线上课-章节-新建", businessType = BusinessTypeConstant.GET)
public JsonResponse store(
@PathVariable(name = "courseId") Integer courseId,
@RequestBody @Validated CourseChapterRequest req) {
@@ -56,8 +54,9 @@ public class CourseChapterController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@GetMapping("/{id}")
@Log(title = "线上课-章节-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id)
@@ -66,8 +65,9 @@ public class CourseChapterController {
return JsonResponse.data(chapter);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@PutMapping("/{id}")
@Log(title = "线上课-章节-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id,
@@ -78,8 +78,9 @@ public class CourseChapterController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@DeleteMapping("/{id}")
@Log(title = "线上课-章节-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id)
@@ -96,6 +97,7 @@ public class CourseChapterController {
}
@PutMapping("/update/sort")
@Log(title = "线上课-章节-更新排序", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse updateSort(
@PathVariable(name = "courseId") Integer courseId,
@RequestBody @Validated CourseChapterSortRequest req) {

View File

@@ -24,27 +24,34 @@ 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.domain.*;
import xyz.playedu.api.event.CourseDestroyEvent;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.CourseRequest;
import xyz.playedu.api.service.*;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.types.paginate.CoursePaginateFiler;
import xyz.playedu.api.types.paginate.PaginationResult;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.service.*;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.paginate.CoursePaginateFiler;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.course.domain.Course;
import xyz.playedu.course.domain.CourseAttachment;
import xyz.playedu.course.domain.CourseChapter;
import xyz.playedu.course.domain.CourseHour;
import xyz.playedu.course.service.CourseAttachmentService;
import xyz.playedu.course.service.CourseChapterService;
import xyz.playedu.course.service.CourseHourService;
import xyz.playedu.course.service.CourseService;
import xyz.playedu.resource.domain.Resource;
import xyz.playedu.resource.service.ResourceService;
import java.text.ParseException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/2/24 14:16
*/
@RestController
@Slf4j
@RequestMapping("/backend/v1/course")
@@ -52,17 +59,23 @@ public class CourseController {
@Autowired private CourseService courseService;
@Autowired private ResourceCategoryService categoryService;
@Autowired private CategoryService categoryService;
@Autowired private CourseChapterService chapterService;
@Autowired private CourseHourService hourService;
@Autowired private CourseAttachmentService attachmentService;
@Autowired private ResourceService resourceService;
@Autowired private DepartmentService departmentService;
@Autowired private ApplicationContext ctx;
@BackendPermission(slug = BPermissionConstant.COURSE)
@GetMapping("/index")
@Log(title = "线上课-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
Integer page = MapUtils.getInteger(params, "page", 1);
Integer size = MapUtils.getInteger(params, "size", 10);
@@ -97,7 +110,7 @@ public class CourseController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@GetMapping("/create")
public JsonResponse create() {
HashMap<String, Object> data = new HashMap<>();
@@ -105,11 +118,12 @@ public class CourseController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@PostMapping("/create")
@Transactional
@Log(title = "线上课-新建", businessType = BusinessTypeConstant.INSERT)
public JsonResponse store(@RequestBody @Validated CourseRequest req) throws ParseException {
if (req.getShortDesc() != null && req.getShortDesc().length() > 200) {
if (req.getShortDesc().length() > 200) {
return JsonResponse.error("课程简短介绍不能超过200字");
}
Course course =
@@ -125,7 +139,7 @@ public class CourseController {
Date now = new Date();
int classHourCount = 0;
if (req.getHours().size() > 0) { // 无章节课时配置
if (!req.getHours().isEmpty()) { // 无章节课时配置
List<CourseHour> insertHours = new ArrayList<>();
final Integer[] chapterSort = {0};
for (CourseRequest.HourItem hourItem : req.getHours()) {
@@ -143,12 +157,12 @@ public class CourseController {
}
});
}
if (insertHours.size() > 0) {
if (!insertHours.isEmpty()) {
hourService.saveBatch(insertHours);
classHourCount = insertHours.size();
}
} else {
if (req.getChapters() == null || req.getChapters().size() == 0) {
if (req.getChapters().isEmpty()) {
return JsonResponse.error("请配置课时");
}
@@ -186,7 +200,7 @@ public class CourseController {
});
}
}
if (insertHours.size() > 0) {
if (!insertHours.isEmpty()) {
hourService.saveBatch(insertHours);
classHourCount = insertHours.size();
}
@@ -196,17 +210,57 @@ public class CourseController {
courseService.updateClassHour(course.getId(), classHourCount);
}
// 课程附件
if (null != req.getAttachments() && !req.getAttachments().isEmpty()) {
List<CourseAttachment> insertAttachments = new ArrayList<>();
final Integer[] sort = {0};
for (CourseRequest.AttachmentItem attachmentItem : req.getAttachments()) {
insertAttachments.add(
new CourseAttachment() {
{
setCourseId(course.getId());
setSort(sort[0]++);
setTitle(attachmentItem.getName());
setType(attachmentItem.getType());
setRid(attachmentItem.getRid());
setCreatedAt(now);
}
});
}
if (!insertAttachments.isEmpty()) {
attachmentService.saveBatch(insertAttachments);
}
}
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@GetMapping("/{id}")
@Log(title = "线上课-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(@PathVariable(name = "id") Integer id) throws NotFoundException {
Course course = courseService.findOrFail(id);
List<Integer> depIds = courseService.getDepIdsByCourseId(course.getId());
List<Integer> categoryIds = courseService.getCategoryIdsByCourseId(course.getId());
List<CourseChapter> chapters = chapterService.getChaptersByCourseId(course.getId());
List<CourseHour> hours = hourService.getHoursByCourseId(course.getId());
List<CourseAttachment> attachments =
attachmentService.getAttachmentsByCourseId(course.getId());
if (null != attachments && !attachments.isEmpty()) {
Map<Integer, Resource> resourceMap =
resourceService
.chunks(attachments.stream().map(CourseAttachment::getRid).toList())
.stream()
.collect(Collectors.toMap(Resource::getId, Function.identity()));
attachments.forEach(
courseAttachment -> {
Resource resource = resourceMap.get(courseAttachment.getRid());
if (null != resource) {
courseAttachment.setUrl(resource.getUrl());
courseAttachment.setExt(resource.getExtension());
}
});
}
HashMap<String, Object> data = new HashMap<>();
data.put("course", course);
@@ -214,13 +268,14 @@ public class CourseController {
data.put("category_ids", categoryIds); // 已关联的分类
data.put("chapters", chapters);
data.put("hours", hours.stream().collect(Collectors.groupingBy(CourseHour::getChapterId)));
data.put("attachments", attachments);
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@PutMapping("/{id}")
@Transactional
@Log(title = "线上课-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(
@PathVariable(name = "id") Integer id, @RequestBody @Validated CourseRequest req)
throws NotFoundException {
@@ -232,13 +287,15 @@ public class CourseController {
req.getShortDesc(),
req.getIsRequired(),
req.getIsShow(),
req.getPublishedAt(),
req.getCategoryIds(),
req.getDepIds());
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@DeleteMapping("/{id}")
@Log(title = "线上课-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(@PathVariable(name = "id") Integer id) {
courseService.removeById(id);
ctx.publishEvent(new CourseDestroyEvent(this, BCtx.getId(), id));

View File

@@ -23,22 +23,24 @@ 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.BackendConstant;
import xyz.playedu.api.domain.CourseChapter;
import xyz.playedu.api.domain.CourseHour;
import xyz.playedu.api.event.CourseHourCreatedEvent;
import xyz.playedu.api.event.CourseHourDestroyEvent;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.CourseHourMultiRequest;
import xyz.playedu.api.request.backend.CourseHourRequest;
import xyz.playedu.api.request.backend.CourseHourSortRequest;
import xyz.playedu.api.service.CourseChapterService;
import xyz.playedu.api.service.CourseHourService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.types.SelectOption;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BackendConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.SelectOption;
import xyz.playedu.course.domain.CourseChapter;
import xyz.playedu.course.domain.CourseHour;
import xyz.playedu.course.service.CourseChapterService;
import xyz.playedu.course.service.CourseHourService;
import java.util.*;
@@ -58,8 +60,9 @@ public class CourseHourController {
@Autowired private ApplicationContext ctx;
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@GetMapping("/create")
@Log(title = "线上课-课时-新建", businessType = BusinessTypeConstant.GET)
public JsonResponse create(@PathVariable(name = "courseId") Integer courseId) {
// 课时类型
List<SelectOption<String>> typeItems = new ArrayList<>();
@@ -81,8 +84,9 @@ public class CourseHourController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@PostMapping("/create")
@Log(title = "线上课-课时-新建", businessType = BusinessTypeConstant.INSERT)
public JsonResponse store(
@PathVariable(name = "courseId") Integer courseId,
@RequestBody @Validated CourseHourRequest req)
@@ -124,9 +128,10 @@ public class CourseHourController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@PostMapping("/create-batch")
@Transactional
@Log(title = "线上课-课时-批量导入", businessType = BusinessTypeConstant.INSERT)
public JsonResponse storeMulti(
@PathVariable(name = "courseId") Integer courseId,
@RequestBody @Validated CourseHourMultiRequest req) {
@@ -175,8 +180,9 @@ public class CourseHourController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@GetMapping("/{id}")
@Log(title = "线上课-课时-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id)
@@ -185,8 +191,9 @@ public class CourseHourController {
return JsonResponse.data(courseHour);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@PutMapping("/{id}")
@Log(title = "线上课-课时-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id,
@@ -201,8 +208,9 @@ public class CourseHourController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE)
@BackendPermission(slug = BPermissionConstant.COURSE)
@DeleteMapping("/{id}")
@Log(title = "线上课-课时-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id)
@@ -220,6 +228,7 @@ public class CourseHourController {
}
@PutMapping("/update/sort")
@Log(title = "线上课-课时-更新排序", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse updateSort(
@PathVariable(name = "courseId") Integer courseId,
@RequestBody @Validated CourseHourSortRequest req) {

View File

@@ -24,17 +24,22 @@ import org.springframework.context.ApplicationContext;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.api.constant.BPermissionConstant;
import xyz.playedu.api.domain.User;
import xyz.playedu.api.domain.UserCourseRecord;
import xyz.playedu.api.event.UserCourseRecordDestroyEvent;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.CourseUserDestroyRequest;
import xyz.playedu.api.service.*;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.types.mapper.UserCourseHourRecordUserFirstCreatedAtMapper;
import xyz.playedu.api.types.paginate.PaginationResult;
import xyz.playedu.api.types.paginate.UserPaginateFilter;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.domain.User;
import xyz.playedu.common.service.*;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.mapper.UserCourseHourRecordUserFirstCreatedAtMapper;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.common.types.paginate.UserPaginateFilter;
import xyz.playedu.course.domain.UserCourseRecord;
import xyz.playedu.course.service.CourseService;
import xyz.playedu.course.service.UserCourseHourRecordService;
import xyz.playedu.course.service.UserCourseRecordService;
import java.util.ArrayList;
import java.util.HashMap;
@@ -63,9 +68,10 @@ public class CourseUserController {
@Autowired private ApplicationContext ctx;
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE_USER)
@BackendPermission(slug = BPermissionConstant.COURSE_USER)
@GetMapping("/index")
@SneakyThrows
@Log(title = "线上课-学习记录-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index(
@PathVariable(name = "courseId") Integer courseId,
@RequestParam HashMap<String, Object> params) {
@@ -138,8 +144,9 @@ public class CourseUserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.COURSE_USER_DESTROY)
@BackendPermission(slug = BPermissionConstant.COURSE_USER_DESTROY)
@PostMapping("/destroy")
@Log(title = "线上课-学习记录-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(
@PathVariable(name = "courseId") Integer courseId,
@RequestBody @Validated CourseUserDestroyRequest req) {

View File

@@ -20,12 +20,17 @@ 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.BackendConstant;
import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.domain.User;
import xyz.playedu.api.domain.UserLearnDurationStats;
import xyz.playedu.api.service.*;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BackendConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.constant.SystemConstant;
import xyz.playedu.common.domain.User;
import xyz.playedu.common.service.*;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.course.domain.UserLearnDurationStats;
import xyz.playedu.course.service.CourseService;
import xyz.playedu.course.service.UserLearnDurationStatsService;
import xyz.playedu.resource.service.ResourceService;
import java.util.ArrayList;
import java.util.HashMap;
@@ -44,7 +49,7 @@ public class DashboardController {
@Autowired private AdminUserService adminUserService;
@Autowired private ResourceCategoryService resourceCategoryService;
@Autowired private CategoryService categoryService;
@Autowired private UserService userService;
@@ -57,6 +62,7 @@ public class DashboardController {
@Autowired private UserLearnDurationStatsService userLearnDurationStatsService;
@GetMapping("/index")
@Log(title = "主面板", businessType = BusinessTypeConstant.GET)
public JsonResponse index() {
HashMap<String, Object> data = new HashMap<>();
data.put("version", SystemConstant.VERSION);
@@ -68,7 +74,7 @@ public class DashboardController {
data.put("course_total", courseService.total()); // 线上课数量
data.put("department_total", departmentService.total());
data.put("resource_category_total", resourceCategoryService.total());
data.put("resource_category_total", categoryService.total());
data.put("admin_user_total", adminUserService.total());
data.put(

View File

@@ -15,6 +15,7 @@
*/
package xyz.playedu.api.controller.backend;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
@@ -23,32 +24,35 @@ import org.springframework.context.ApplicationContext;
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.domain.Course;
import xyz.playedu.api.domain.Department;
import xyz.playedu.api.domain.User;
import xyz.playedu.api.domain.UserCourseRecord;
import xyz.playedu.api.event.DepartmentDestroyEvent;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.*;
import xyz.playedu.api.service.CourseService;
import xyz.playedu.api.service.DepartmentService;
import xyz.playedu.api.service.UserCourseRecordService;
import xyz.playedu.api.service.UserService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.types.paginate.PaginationResult;
import xyz.playedu.api.types.paginate.UserPaginateFilter;
import xyz.playedu.api.request.backend.DepartmentParentRequest;
import xyz.playedu.api.request.backend.DepartmentRequest;
import xyz.playedu.api.request.backend.DepartmentSortRequest;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.domain.Department;
import xyz.playedu.common.domain.User;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.service.AppConfigService;
import xyz.playedu.common.service.DepartmentService;
import xyz.playedu.common.service.UserService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.LdapConfig;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.common.types.paginate.UserPaginateFilter;
import xyz.playedu.common.util.ldap.LdapUtil;
import xyz.playedu.course.domain.Course;
import xyz.playedu.course.domain.UserCourseRecord;
import xyz.playedu.course.service.CourseDepartmentService;
import xyz.playedu.course.service.CourseService;
import xyz.playedu.course.service.UserCourseRecordService;
import java.util.*;
import java.util.stream.Collectors;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/2/19 10:33
*/
@RestController
@Slf4j
@RequestMapping("/backend/v1/department")
@@ -56,6 +60,8 @@ public class DepartmentController {
@Autowired private DepartmentService departmentService;
@Autowired private CourseDepartmentService courseDepartmentService;
@Autowired private UserService userService;
@Autowired private CourseService courseService;
@@ -64,7 +70,10 @@ public class DepartmentController {
@Autowired private ApplicationContext ctx;
@Autowired private AppConfigService appConfigService;
@GetMapping("/index")
@Log(title = "部门-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index() {
HashMap<String, Object> data = new HashMap<>();
data.put("departments", departmentService.groupByParent());
@@ -74,48 +83,63 @@ public class DepartmentController {
}
@GetMapping("/departments")
@Log(title = "部门-全部部门", businessType = BusinessTypeConstant.GET)
public JsonResponse index(
@RequestParam(name = "parent_id", defaultValue = "0") Integer parentId) {
List<Department> departments = departmentService.listByParentId(parentId);
return JsonResponse.data(departments);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.DEPARTMENT_CUD)
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
@GetMapping("/create")
@Log(title = "部门-新建", businessType = BusinessTypeConstant.GET)
public JsonResponse create() {
HashMap<String, Object> data = new HashMap<>();
data.put("departments", departmentService.groupByParent());
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.DEPARTMENT_CUD)
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
@PostMapping("/create")
@Log(title = "部门-新建", businessType = BusinessTypeConstant.INSERT)
public JsonResponse store(@RequestBody @Validated DepartmentRequest req)
throws NotFoundException {
if (appConfigService.enabledLdapLogin()) {
return JsonResponse.error("已启用LDAP服务禁止添加部门");
}
departmentService.create(req.getName(), req.getParentId(), req.getSort());
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.DEPARTMENT_CUD)
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
@GetMapping("/{id}")
@Log(title = "部门-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(@PathVariable Integer id) throws NotFoundException {
Department department = departmentService.findOrFail(id);
return JsonResponse.data(department);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.DEPARTMENT_CUD)
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
@PutMapping("/{id}")
@Log(title = "部门-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(@PathVariable Integer id, @RequestBody DepartmentRequest req)
throws NotFoundException {
if (appConfigService.enabledLdapLogin()) {
return JsonResponse.error("已启用LDAP服务禁止添加部门");
}
Department department = departmentService.findOrFail(id);
departmentService.update(department, req.getName(), req.getParentId(), req.getSort());
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.DEPARTMENT_CUD)
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
@GetMapping("/{id}/destroy")
@Log(title = "部门-批量删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse preDestroy(@PathVariable Integer id) {
List<Integer> courseIds = departmentService.getCourseIdsByDepId(id);
if (appConfigService.enabledLdapLogin()) {
return JsonResponse.error("已启用LDAP服务禁止添加部门");
}
List<Integer> courseIds = courseDepartmentService.getCourseIdsByDepId(id);
List<Integer> userIds = departmentService.getUserIdsByDepId(id);
HashMap<String, Object> data = new HashMap<>();
@@ -123,7 +147,7 @@ public class DepartmentController {
data.put("users", new ArrayList<>());
data.put("children", departmentService.listByParentId(id));
if (courseIds != null && courseIds.size() > 0) {
if (courseIds != null && !courseIds.isEmpty()) {
data.put(
"courses",
courseService.chunks(
@@ -135,7 +159,7 @@ public class DepartmentController {
}
}));
}
if (userIds != null && userIds.size() > 0) {
if (userIds != null && !userIds.isEmpty()) {
data.put(
"users",
userService.chunks(
@@ -152,32 +176,42 @@ public class DepartmentController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.DEPARTMENT_CUD)
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
@DeleteMapping("/{id}")
@Log(title = "部门-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(@PathVariable Integer id) throws NotFoundException {
if (appConfigService.enabledLdapLogin()) {
return JsonResponse.error("已启用LDAP服务禁止添加部门");
}
Department department = departmentService.findOrFail(id);
departmentService.destroy(department.getId());
ctx.publishEvent(new DepartmentDestroyEvent(this, BCtx.getId(), department.getId()));
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.DEPARTMENT_CUD)
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
@PutMapping("/update/sort")
@Log(title = "部门-更新排序", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse resort(@RequestBody @Validated DepartmentSortRequest req) {
departmentService.resetSort(req.getIds());
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.DEPARTMENT_CUD)
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
@PutMapping("/update/parent")
@Log(title = "部门-更新父级", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse updateParent(@RequestBody @Validated DepartmentParentRequest req)
throws NotFoundException {
if (appConfigService.enabledLdapLogin()) {
return JsonResponse.error("已启用LDAP服务禁止添加部门");
}
departmentService.changeParent(req.getId(), req.getParentId(), req.getIds());
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.DEPARTMENT_USER_LEARN)
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_USER_LEARN)
@GetMapping("/{id}/users")
@Log(title = "部门-学员", businessType = BusinessTypeConstant.GET)
public JsonResponse users(
@PathVariable(name = "id") Integer id, @RequestParam HashMap<String, Object> params) {
Integer page = MapUtils.getInteger(params, "page", 1);
@@ -213,7 +247,7 @@ public class DepartmentController {
PaginationResult<User> users = userService.paginate(page, size, filter);
List<Course> courses;
if (courseIdsStr != null && courseIdsStr.trim().length() > 0) {
if (courseIdsStr != null && !courseIdsStr.trim().isEmpty()) {
// 指定了需要显示的线上课
courses =
courseService.chunks(
@@ -275,4 +309,63 @@ public class DepartmentController {
return JsonResponse.data(data);
}
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
@PostMapping("/ldap-sync")
@Log(title = "部门-LDAP同步", businessType = BusinessTypeConstant.INSERT)
@SneakyThrows
public JsonResponse ldapSync() {
LdapConfig ldapConfig = appConfigService.ldapConfig();
List<String> ouList =
LdapUtil.departments(
ldapConfig.getUrl(),
ldapConfig.getAdminUser(),
ldapConfig.getAdminPass(),
ldapConfig.getBaseDN());
if (ouList == null || ouList.isEmpty()) {
return JsonResponse.error("部门为空");
}
HashMap<String, Integer> depIdKeyByName = new HashMap<>();
Integer sort = 0;
for (String department : ouList) {
String[] tmp = department.toLowerCase().split(",");
String prevName = "";
for (String s : tmp) {
// 控制部门排序
sort++;
// 当前的子部门名
String tmpName = s.replace("ou=", "");
// 父部门id
Integer parentId = 0;
// 部门的链名=>父部门1,父部门2,子部门
String fullName = tmpName;
if (!prevName.isEmpty()) {
fullName = prevName + "," + tmpName;
parentId = depIdKeyByName.get(prevName);
}
// 检查是否已经创建
Integer depId = depIdKeyByName.get(tmpName);
if (depId == null) {
// 检查是否已经创建
Department tmpDep = departmentService.findByName(tmpName, parentId);
if (tmpDep == null) {
// 创建部门
Integer tmpDepId = departmentService.create(tmpName, parentId, sort);
depIdKeyByName.put(fullName, tmpDepId);
} else {
depIdKeyByName.put(fullName, tmpDep.getId());
}
}
prevName = fullName;
}
}
return JsonResponse.success();
}
}

View File

@@ -20,21 +20,25 @@ import org.springframework.context.ApplicationContext;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.api.BCtx;
import xyz.playedu.api.bus.BackendBus;
import xyz.playedu.api.constant.BPermissionConstant;
import xyz.playedu.api.domain.AdminUser;
import xyz.playedu.api.event.AdminUserLoginEvent;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.middleware.ImageCaptchaCheckMiddleware;
import xyz.playedu.api.request.backend.LoginRequest;
import xyz.playedu.api.request.backend.PasswordChangeRequest;
import xyz.playedu.api.service.AdminUserService;
import xyz.playedu.api.service.BackendAuthService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.HelperUtil;
import xyz.playedu.api.util.IpUtil;
import xyz.playedu.api.util.RequestUtil;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.bus.BackendBus;
import xyz.playedu.common.config.PlayEduConfig;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.domain.AdminUser;
import xyz.playedu.common.service.AdminUserService;
import xyz.playedu.common.service.BackendAuthService;
import xyz.playedu.common.service.RateLimiterService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.HelperUtil;
import xyz.playedu.common.util.IpUtil;
import xyz.playedu.common.util.RedisUtil;
import xyz.playedu.common.util.RequestUtil;
import java.util.HashMap;
@@ -50,20 +54,36 @@ public class LoginController {
@Autowired private ApplicationContext ctx;
@Autowired private RateLimiterService rateLimiterService;
@Autowired private PlayEduConfig playEduConfig;
@PostMapping("/login")
@ImageCaptchaCheckMiddleware
@Log(title = "管理员-登录", businessType = BusinessTypeConstant.LOGIN)
public JsonResponse login(@RequestBody @Validated LoginRequest loginRequest) {
AdminUser adminUser = adminUserService.findByEmail(loginRequest.email);
if (adminUser == null) {
return JsonResponse.error("邮箱或密码错误");
}
String limitKey = "admin-login-limit:" + loginRequest.getEmail();
Long reqCount = rateLimiterService.current(limitKey, 3600L);
if (reqCount > 5 && !playEduConfig.getTesting()) {
Long exp = RedisUtil.ttlWithoutPrefix(limitKey);
return JsonResponse.error(
String.format("您的账号已被锁定,请%s后重试", exp > 60 ? exp / 60 + "分钟" : exp + ""));
}
String password =
HelperUtil.MD5(loginRequest.getPassword() + adminUser.getSalt()).toLowerCase();
if (!adminUser.getPassword().equals(password)) {
return JsonResponse.error("邮箱或密码错误");
}
RedisUtil.del(limitKey);
if (adminUser.getIsBanLogin().equals(1)) {
return JsonResponse.error("当前用户已禁止登录");
return JsonResponse.error("当前管理员已禁止登录");
}
String token = authService.loginUsingId(adminUser.getId(), RequestUtil.url());
@@ -83,12 +103,14 @@ public class LoginController {
}
@PostMapping("/logout")
@Log(title = "管理员-登出", businessType = BusinessTypeConstant.LOGOUT)
public JsonResponse logout() {
authService.logout();
return JsonResponse.success("success");
}
@GetMapping("/detail")
@Log(title = "管理员-详情", businessType = BusinessTypeConstant.GET)
public JsonResponse detail() {
AdminUser user = BCtx.getAdminUser();
HashMap<String, Boolean> permissions = backendBus.adminUserPermissions(user.getId());
@@ -100,8 +122,9 @@ public class LoginController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.PASSWORD_CHANGE)
@BackendPermission(slug = BPermissionConstant.PASSWORD_CHANGE)
@PutMapping("/password")
@Log(title = "管理员-密码修改", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse changePassword(@RequestBody @Validated PasswordChangeRequest req) {
AdminUser user = BCtx.getAdminUser();
String password = HelperUtil.MD5(req.getOldPassword() + user.getSalt());

View File

@@ -20,21 +20,25 @@ import org.springframework.context.ApplicationContext;
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.BackendConstant;
import xyz.playedu.api.domain.Resource;
import xyz.playedu.api.domain.ResourceCategory;
import xyz.playedu.api.event.ResourceCategoryDestroyEvent;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.ResourceCategoryParentRequest;
import xyz.playedu.api.request.backend.ResourceCategoryRequest;
import xyz.playedu.api.request.backend.ResourceCategorySortRequest;
import xyz.playedu.api.service.CourseService;
import xyz.playedu.api.service.ResourceCategoryService;
import xyz.playedu.api.service.ResourceService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BackendConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.domain.Category;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.service.CategoryService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.course.service.CourseCategoryService;
import xyz.playedu.course.service.CourseService;
import xyz.playedu.resource.domain.Resource;
import xyz.playedu.resource.service.ResourceCategoryService;
import xyz.playedu.resource.service.ResourceService;
import java.util.*;
import java.util.stream.Collectors;
@@ -48,15 +52,20 @@ import java.util.stream.Collectors;
@RequestMapping("/backend/v1/resource-category")
public class ResourceCategoryController {
@Autowired private ResourceCategoryService categoryService;
@Autowired private CategoryService categoryService;
@Autowired private CourseService courseService;
@Autowired private ResourceService resourceService;
@Autowired private ResourceCategoryService resourceCategoryService;
@Autowired private CourseCategoryService courseCategoryService;
@Autowired private ApplicationContext ctx;
@GetMapping("/index")
@Log(title = "资源-分类-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index() {
HashMap<String, Object> data = new HashMap<>();
data.put("categories", categoryService.groupByParent());
@@ -64,48 +73,54 @@ public class ResourceCategoryController {
}
@GetMapping("/categories")
@Log(title = "资源-分类-全部分类", businessType = BusinessTypeConstant.GET)
public JsonResponse index(
@RequestParam(name = "parent_id", defaultValue = "0") Integer parentId) {
List<ResourceCategory> categories = categoryService.listByParentId(parentId);
List<Category> categories = categoryService.listByParentId(parentId);
return JsonResponse.data(categories);
}
@GetMapping("/create")
@Log(title = "资源-分类-新建", businessType = BusinessTypeConstant.GET)
public JsonResponse create() {
HashMap<String, Object> data = new HashMap<>();
data.put("categories", categoryService.groupByParent());
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.RESOURCE_CATEGORY)
@BackendPermission(slug = BPermissionConstant.RESOURCE_CATEGORY)
@PostMapping("/create")
@Log(title = "资源-分类-新建", businessType = BusinessTypeConstant.INSERT)
public JsonResponse store(@RequestBody @Validated ResourceCategoryRequest req)
throws NotFoundException {
categoryService.create(req.getName(), req.getParentId(), req.getSort());
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.RESOURCE_CATEGORY)
@BackendPermission(slug = BPermissionConstant.RESOURCE_CATEGORY)
@GetMapping("/{id}")
@Log(title = "资源-分类-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(@PathVariable Integer id) throws NotFoundException {
ResourceCategory category = categoryService.findOrFail(id);
Category category = categoryService.findOrFail(id);
return JsonResponse.data(category);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.RESOURCE_CATEGORY)
@BackendPermission(slug = BPermissionConstant.RESOURCE_CATEGORY)
@PutMapping("/{id}")
@Log(title = "资源-分类-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(@PathVariable Integer id, @RequestBody ResourceCategoryRequest req)
throws NotFoundException {
ResourceCategory category = categoryService.findOrFail(id);
Category category = categoryService.findOrFail(id);
categoryService.update(category, req.getName(), req.getParentId(), req.getSort());
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.RESOURCE_CATEGORY)
@BackendPermission(slug = BPermissionConstant.RESOURCE_CATEGORY)
@GetMapping("/{id}/destroy")
@Log(title = "资源-分类-批量删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse preDestroy(@PathVariable Integer id) {
List<Integer> courseIds = categoryService.getCourseIdsById(id);
List<Integer> rids = categoryService.getRidsById(id);
List<Integer> courseIds = courseCategoryService.getCourseIdsByCategoryId(id);
List<Integer> rids = resourceCategoryService.getRidsByCategoryId(id);
HashMap<String, Object> data = new HashMap<>();
data.put("children", categoryService.listByParentId(id));
@@ -149,22 +164,27 @@ public class ResourceCategoryController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.RESOURCE_CATEGORY)
@BackendPermission(slug = BPermissionConstant.RESOURCE_CATEGORY)
@DeleteMapping("/{id}")
@Log(title = "资源-分类-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(@PathVariable Integer id) throws NotFoundException {
ResourceCategory category = categoryService.findOrFail(id);
Category category = categoryService.findOrFail(id);
categoryService.deleteById(category.getId());
ctx.publishEvent(new ResourceCategoryDestroyEvent(this, BCtx.getId(), category.getId()));
return JsonResponse.success();
}
@BackendPermission(slug = BPermissionConstant.RESOURCE_CATEGORY)
@PutMapping("/update/sort")
@Log(title = "资源-分类-更新排序", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse resort(@RequestBody @Validated ResourceCategorySortRequest req) {
categoryService.resetSort(req.getIds());
return JsonResponse.success();
}
@BackendPermission(slug = BPermissionConstant.RESOURCE_CATEGORY)
@PutMapping("/update/parent")
@Log(title = "资源-分类-更新父级", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse updateParent(@RequestBody @Validated ResourceCategoryParentRequest req)
throws NotFoundException {
categoryService.changeParent(req.getId(), req.getParentId(), req.getIds());

View File

@@ -23,23 +23,25 @@ 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.bus.BackendBus;
import xyz.playedu.api.constant.BackendConstant;
import xyz.playedu.api.domain.AdminUser;
import xyz.playedu.api.domain.Resource;
import xyz.playedu.api.domain.ResourceVideo;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.exception.ServiceException;
import xyz.playedu.api.request.backend.ResourceDestroyMultiRequest;
import xyz.playedu.api.request.backend.ResourceUpdateRequest;
import xyz.playedu.api.service.AdminUserService;
import xyz.playedu.api.service.MinioService;
import xyz.playedu.api.service.ResourceService;
import xyz.playedu.api.service.ResourceVideoService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.types.paginate.PaginationResult;
import xyz.playedu.api.types.paginate.ResourcePaginateFilter;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.bus.BackendBus;
import xyz.playedu.common.constant.BackendConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.domain.AdminUser;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.service.AdminUserService;
import xyz.playedu.common.service.MinioService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.common.types.paginate.ResourcePaginateFilter;
import xyz.playedu.resource.domain.Resource;
import xyz.playedu.resource.domain.ResourceVideo;
import xyz.playedu.resource.service.ResourceService;
import xyz.playedu.resource.service.ResourceVideoService;
import java.util.*;
import java.util.stream.Collectors;
@@ -59,6 +61,7 @@ public class ResourceController {
@Autowired private BackendBus backendBus;
@GetMapping("/index")
@Log(title = "资源-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
Integer page = MapUtils.getInteger(params, "page", 1);
Integer size = MapUtils.getInteger(params, "size", 10);
@@ -68,7 +71,7 @@ public class ResourceController {
String type = MapUtils.getString(params, "type");
String categoryIds = MapUtils.getString(params, "category_ids");
if (type == null || type.trim().length() == 0) {
if (type == null || type.trim().isEmpty()) {
return JsonResponse.error("请选择资源类型");
}
@@ -100,7 +103,7 @@ public class ResourceController {
// 操作人
data.put("admin_users", new HashMap<>());
if (result.getData().size() > 0) {
if (!result.getData().isEmpty()) {
Map<Integer, String> adminUsers =
adminUserService
.chunks(result.getData().stream().map(Resource::getAdminId).toList())
@@ -109,12 +112,18 @@ public class ResourceController {
data.put("admin_users", adminUsers);
}
if (!type.equals(BackendConstant.RESOURCE_TYPE_VIDEO)
&& !type.equals(BackendConstant.RESOURCE_TYPE_IMAGE)) {
filter.setType(BackendConstant.RESOURCE_TYPE_ATTACHMENT);
data.put("existing_types", resourceService.paginateType(filter));
}
return JsonResponse.data(data);
}
@DeleteMapping("/{id}")
@Transactional
@SneakyThrows
@Log(title = "资源-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(@PathVariable(name = "id") Integer id) throws NotFoundException {
Resource resource = resourceService.findOrFail(id);
@@ -137,13 +146,14 @@ public class ResourceController {
@PostMapping("/destroy-multi")
@SneakyThrows
@Log(title = "资源-批量列表", businessType = BusinessTypeConstant.DELETE)
public JsonResponse multiDestroy(@RequestBody ResourceDestroyMultiRequest req) {
if (req.getIds() == null || req.getIds().size() == 0) {
if (req.getIds() == null || req.getIds().isEmpty()) {
return JsonResponse.error("请选择需要删除的资源");
}
List<Resource> resources = resourceService.chunks(req.getIds());
if (resources == null || resources.size() == 0) {
if (resources == null || resources.isEmpty()) {
return JsonResponse.success();
}
@@ -169,6 +179,7 @@ public class ResourceController {
@GetMapping("/{id}")
@SneakyThrows
@Log(title = "资源-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(@PathVariable(name = "id") Integer id) {
Resource resource = resourceService.findOrFail(id);
@@ -186,6 +197,7 @@ public class ResourceController {
@PutMapping("/{id}")
@SneakyThrows
@Log(title = "资源-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(
@RequestBody @Validated ResourceUpdateRequest req,
@PathVariable(name = "id") Integer id) {

View File

@@ -22,14 +22,15 @@ 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 xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.constant.ConfigConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.service.CategoryService;
import xyz.playedu.common.service.DepartmentService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.RequestUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -40,25 +41,17 @@ import java.util.Map;
@Slf4j
public class SystemController {
@Autowired private ImageCaptchaService imageCaptchaService;
@Autowired private DepartmentService departmentService;
@GetMapping("/image-captcha")
public JsonResponse imageCaptcha() throws IOException {
ImageCaptchaResult imageCaptchaResult = imageCaptchaService.generate();
HashMap<String, String> data = new HashMap<>();
data.put("key", imageCaptchaResult.getKey());
data.put("image", imageCaptchaResult.getImage());
return JsonResponse.data(data);
}
@Autowired private CategoryService categoryService;
@GetMapping("/config")
@Log(title = "其它-系统配置", businessType = BusinessTypeConstant.GET)
public JsonResponse config() {
Map<String, String> configData = BCtx.getConfig();
String apiUrl = configData.get(CConfig.SYSTEM_API_URL);
if (apiUrl == null || apiUrl.trim().length() == 0) {
String apiUrl = configData.get(ConfigConstant.SYSTEM_API_URL);
if (apiUrl == null || apiUrl.trim().isEmpty()) {
apiUrl = RequestUtil.uriWithProtocol();
} else {
if (apiUrl.endsWith("/")) {
@@ -68,18 +61,18 @@ public class SystemController {
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));
data.put(ConfigConstant.SYSTEM_NAME, configData.get(ConfigConstant.SYSTEM_NAME));
data.put(ConfigConstant.SYSTEM_LOGO, configData.get(ConfigConstant.SYSTEM_LOGO));
data.put(ConfigConstant.SYSTEM_API_URL, apiUrl);
data.put(ConfigConstant.SYSTEM_PC_URL, configData.get(ConfigConstant.SYSTEM_PC_URL));
data.put(ConfigConstant.SYSTEM_H5_URL, configData.get(ConfigConstant.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");
String memberDefaultAvatar = configData.get(ConfigConstant.MEMBER_DEFAULT_AVATAR);
if (memberDefaultAvatar == null || memberDefaultAvatar.trim().isEmpty()) {
data.put(ConfigConstant.MEMBER_DEFAULT_AVATAR, apiUrl + "/images/default_avatar.png");
} else {
data.put(CConfig.MEMBER_DEFAULT_AVATAR, memberDefaultAvatar);
data.put(ConfigConstant.MEMBER_DEFAULT_AVATAR, memberDefaultAvatar);
}
// 内置的三个线上课封面
@@ -89,6 +82,15 @@ public class SystemController {
defaultCourseThumbs.add(apiUrl + "/images/courses/thumb3.png");
data.put("default.course_thumbs", defaultCourseThumbs);
// LDAP登录
data.put("ldap-enabled", "1".equals(configData.get(ConfigConstant.LDAP_ENABLED)));
// 全部部门
data.put("departments", departmentService.groupByParent());
// 全部资源分类
data.put("resource_categories", categoryService.groupByParent());
return JsonResponse.data(data);
}
}

View File

@@ -23,16 +23,20 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import xyz.playedu.api.BCtx;
import xyz.playedu.api.constant.BackendConstant;
import xyz.playedu.api.domain.Resource;
import xyz.playedu.api.exception.ServiceException;
import xyz.playedu.api.request.backend.UploadVideoMergeRequest;
import xyz.playedu.api.service.MinioService;
import xyz.playedu.api.service.ResourceService;
import xyz.playedu.api.service.UploadService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.HelperUtil;
import xyz.playedu.api.request.backend.UploadFileMergeRequest;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BackendConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.service.MinioService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.HelperUtil;
import xyz.playedu.resource.domain.Resource;
import xyz.playedu.resource.service.ResourceService;
import xyz.playedu.resource.service.UploadService;
import java.util.HashMap;
@@ -46,7 +50,9 @@ public class UploadController {
@Autowired private ResourceService resourceService;
@BackendPermission(slug = BPermissionConstant.UPLOAD)
@PostMapping("/minio")
@Log(title = "上传-MinIO", businessType = BusinessTypeConstant.UPLOAD)
public JsonResponse uploadMinio(
@RequestParam HashMap<String, Object> params, MultipartFile file)
throws ServiceException {
@@ -55,10 +61,12 @@ public class UploadController {
return JsonResponse.data(res);
}
@BackendPermission(slug = BPermissionConstant.UPLOAD)
@GetMapping("/minio/upload-id")
@Log(title = "上传-MinIO-uploadId", businessType = BusinessTypeConstant.UPLOAD)
public JsonResponse minioUploadId(@RequestParam HashMap<String, Object> params) {
String extension = MapUtils.getString(params, "extension");
if (extension == null || extension.trim().length() == 0) {
if (extension == null || extension.trim().isEmpty()) {
return JsonResponse.error("extension参数为空");
}
String type = BackendConstant.RESOURCE_EXT_2_TYPE.get(extension.toLowerCase());
@@ -78,7 +86,9 @@ public class UploadController {
return JsonResponse.data(data);
}
@BackendPermission(slug = BPermissionConstant.UPLOAD)
@GetMapping("/minio/pre-sign-url")
@Log(title = "上传-MinIO-签名URL", businessType = BusinessTypeConstant.UPLOAD)
public JsonResponse minioPreSignUrl(@RequestParam HashMap<String, Object> params) {
String uploadId = MapUtils.getString(params, "upload_id");
Integer partNumber = MapUtils.getInteger(params, "part_number");
@@ -92,8 +102,10 @@ public class UploadController {
return JsonResponse.data(data);
}
@PostMapping("/minio/merge-video")
public JsonResponse minioMergeVideo(@RequestBody @Validated UploadVideoMergeRequest req)
@BackendPermission(slug = BPermissionConstant.UPLOAD)
@PostMapping("/minio/merge-file")
@Log(title = "上传-MinIO-文件合并", businessType = BusinessTypeConstant.UPLOAD)
public JsonResponse minioMergeFile(@RequestBody @Validated UploadFileMergeRequest req)
throws ServiceException {
String type = BackendConstant.RESOURCE_EXT_2_TYPE.get(req.getExtension());
if (type == null) {
@@ -102,10 +114,10 @@ public class UploadController {
String extension = req.getExtension();
String originalFilename = req.getOriginalFilename().replaceAll("(?i)." + extension, "");
// 合并视频文件
// 合并资源文件
String url = minioService.merge(req.getFilename(), req.getUploadId());
// 视频素材保存
// 资源素材保存
Resource videoResource =
resourceService.create(
BCtx.getId(),
@@ -118,14 +130,18 @@ public class UploadController {
"",
req.getFilename(),
url);
// 视频封面素材保存
Resource posterResource =
uploadService.storeBase64Image(BCtx.getId(), req.getPoster(), null);
// 视频封面素材改为[隐藏 && 属于视频的子素材]
resourceService.changeParentId(posterResource.getId(), videoResource.getId());
// 视频信息
resourceService.storeResourceVideo(
videoResource.getId(), req.getDuration(), posterResource.getUrl());
// 视频资源特殊处理--视频封面资源
if (BackendConstant.RESOURCE_TYPE_VIDEO.equals(type)) {
// 视频封面素材保存
Resource posterResource =
uploadService.storeBase64Image(BCtx.getId(), req.getPoster(), null);
// 视频的封面素材改为[隐藏 && 属于视频的子素材]
resourceService.changeParentId(posterResource.getId(), videoResource.getId());
// 视频信息
resourceService.storeResourceVideo(
videoResource.getId(), req.getDuration(), posterResource.getUrl());
}
HashMap<String, Object> data = new HashMap<>();
data.put("url", url);
@@ -133,14 +149,16 @@ public class UploadController {
return JsonResponse.data(data);
}
@BackendPermission(slug = BPermissionConstant.UPLOAD)
@GetMapping("/minio/merge")
@Log(title = "上传-MinIO-文件合并", businessType = BusinessTypeConstant.UPLOAD)
public JsonResponse minioMerge(@RequestParam HashMap<String, Object> params) {
String filename = MapUtils.getString(params, "filename");
String uploadId = MapUtils.getString(params, "upload_id");
if (filename == null || filename.trim().length() == 0) {
if (filename == null || filename.trim().isEmpty()) {
return JsonResponse.error("filename必填");
}
if (uploadId == null || uploadId.trim().length() == 0) {
if (uploadId == null || uploadId.trim().isEmpty()) {
return JsonResponse.error("uploadId必填");
}

View File

@@ -28,27 +28,31 @@ 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.*;
import xyz.playedu.api.event.UserCourseHourRecordDestroyEvent;
import xyz.playedu.api.event.UserCourseRecordDestroyEvent;
import xyz.playedu.api.event.UserDestroyEvent;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.middleware.BackendPermissionMiddleware;
import xyz.playedu.api.request.backend.UserImportRequest;
import xyz.playedu.api.request.backend.UserRequest;
import xyz.playedu.api.service.*;
import xyz.playedu.api.service.internal.UserDepartmentService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.types.mapper.UserCourseHourRecordCourseCountMapper;
import xyz.playedu.api.types.paginate.PaginationResult;
import xyz.playedu.api.types.paginate.UserCourseHourRecordPaginateFilter;
import xyz.playedu.api.types.paginate.UserCourseRecordPaginateFilter;
import xyz.playedu.api.types.paginate.UserPaginateFilter;
import xyz.playedu.api.util.HelperUtil;
import xyz.playedu.common.annotation.BackendPermission;
import xyz.playedu.common.annotation.Log;
import xyz.playedu.common.constant.BPermissionConstant;
import xyz.playedu.common.constant.BusinessTypeConstant;
import xyz.playedu.common.constant.ConfigConstant;
import xyz.playedu.common.constant.SystemConstant;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.domain.*;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.service.*;
import xyz.playedu.common.service.UserDepartmentService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.mapper.UserCourseHourRecordCourseCountMapper;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.common.types.paginate.UserCourseHourRecordPaginateFilter;
import xyz.playedu.common.types.paginate.UserCourseRecordPaginateFilter;
import xyz.playedu.common.types.paginate.UserPaginateFilter;
import xyz.playedu.common.util.HelperUtil;
import xyz.playedu.course.domain.*;
import xyz.playedu.course.service.*;
import java.util.*;
import java.util.stream.Collectors;
@@ -83,8 +87,9 @@ public class UserController {
@Autowired private ApplicationContext ctx;
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_INDEX)
@BackendPermission(slug = BPermissionConstant.USER_INDEX)
@GetMapping("/index")
@Log(title = "学员-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index(@RequestParam HashMap<String, Object> params) {
Integer page = MapUtils.getInteger(params, "page", 1);
Integer size = MapUtils.getInteger(params, "size", 10);
@@ -101,7 +106,7 @@ public class UserController {
String createdAt = MapUtils.getString(params, "created_at");
String depIdsStr = MapUtils.getString(params, "dep_ids");
List<Integer> depIds = null;
if (depIdsStr != null && depIdsStr.trim().length() > 0) {
if (depIdsStr != null && !depIdsStr.trim().isEmpty()) {
if ("0".equals(depIdsStr)) {
depIds = new ArrayList<>();
} else {
@@ -126,7 +131,7 @@ public class UserController {
}
};
if (createdAt != null && createdAt.trim().length() > 0) {
if (createdAt != null && !createdAt.trim().isEmpty()) {
filter.setCreatedAt(createdAt.split(","));
}
@@ -140,25 +145,28 @@ public class UserController {
userService.getDepIdsGroup(result.getData().stream().map(User::getId).toList()));
data.put("departments", departmentService.id2name());
data.put("pure_total", userService.total());
data.put("dep_user_count", departmentService.getDepartmentsUserCount());
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_STORE)
@BackendPermission(slug = BPermissionConstant.USER_STORE)
@GetMapping("/create")
@Log(title = "学员-新建", businessType = BusinessTypeConstant.GET)
public JsonResponse create() {
return JsonResponse.data(null);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_STORE)
@BackendPermission(slug = BPermissionConstant.USER_STORE)
@PostMapping("/create")
@Log(title = "学员-新建", businessType = BusinessTypeConstant.INSERT)
public JsonResponse store(@RequestBody @Validated UserRequest req) {
String email = req.getEmail();
if (userService.emailIsExists(email)) {
return JsonResponse.error("邮箱已存在");
}
String password = req.getPassword();
if (password.length() == 0) {
if (password.isEmpty()) {
return JsonResponse.error("请输入密码");
}
userService.createWithDepIds(
@@ -171,8 +179,9 @@ public class UserController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_UPDATE)
@BackendPermission(slug = BPermissionConstant.USER_UPDATE)
@GetMapping("/{id}")
@Log(title = "学员-编辑", businessType = BusinessTypeConstant.GET)
public JsonResponse edit(@PathVariable(name = "id") Integer id) throws NotFoundException {
User user = userService.findOrFail(id);
@@ -185,9 +194,10 @@ public class UserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_UPDATE)
@BackendPermission(slug = BPermissionConstant.USER_UPDATE)
@PutMapping("/{id}")
@Transactional
@Log(title = "学员-编辑", businessType = BusinessTypeConstant.UPDATE)
public JsonResponse update(
@PathVariable(name = "id") Integer id, @RequestBody @Validated UserRequest req)
throws NotFoundException {
@@ -209,8 +219,9 @@ public class UserController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_DESTROY)
@BackendPermission(slug = BPermissionConstant.USER_DESTROY)
@DeleteMapping("/{id}")
@Log(title = "学员-删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroy(@PathVariable(name = "id") Integer id) throws NotFoundException {
User user = userService.findOrFail(id);
userService.removeById(user.getId());
@@ -220,9 +231,10 @@ public class UserController {
@PostMapping("/store-batch")
@Transactional
@Log(title = "学员-批量导入", businessType = BusinessTypeConstant.INSERT)
public JsonResponse batchStore(@RequestBody @Validated UserImportRequest req) {
List<UserImportRequest.UserItem> users = req.getUsers();
if (users.size() == 0) {
if (users.isEmpty()) {
return JsonResponse.error("数据为空");
}
if (users.size() > 1000) {
@@ -233,7 +245,7 @@ public class UserController {
Integer startLine = req.getStartLine();
// 默认的学员头像
String defaultAvatar = BCtx.getConfig().get(CConfig.MEMBER_DEFAULT_AVATAR);
String defaultAvatar = BCtx.getConfig().get(ConfigConstant.MEMBER_DEFAULT_AVATAR);
List<String[]> errorLines = new ArrayList<>();
errorLines.add(new String[] {"错误行", "错误信息"}); // 错误表-表头
@@ -246,7 +258,7 @@ public class UserController {
HashMap<String, Integer> depChainNameMap = new HashMap<>();
for (Department tmpDepItem : departments) {
// 一级部门
if (tmpDepItem.getParentChain() == null || tmpDepItem.getParentChain().length() == 0) {
if (tmpDepItem.getParentChain() == null || tmpDepItem.getParentChain().isEmpty()) {
depChainNameMap.put(tmpDepItem.getName(), tmpDepItem.getId());
continue;
}
@@ -275,7 +287,7 @@ public class UserController {
for (UserImportRequest.UserItem userItem : users) {
i++; // 索引值
if (userItem.getEmail() == null || userItem.getEmail().trim().length() == 0) {
if (userItem.getEmail() == null || userItem.getEmail().trim().isEmpty()) {
errorLines.add(new String[] {"" + (i + startLine) + "", "未输入邮箱账号"});
} else {
// 邮箱重复判断
@@ -292,7 +304,7 @@ public class UserController {
}
// 部门数据检测
if (userItem.getDeps() == null || userItem.getDeps().trim().length() == 0) {
if (userItem.getDeps() == null || userItem.getDeps().trim().isEmpty()) {
errorLines.add(new String[] {"" + (i + startLine) + "", "未选择部门"});
} else {
String[] tmpDepList = userItem.getDeps().trim().split("\\|");
@@ -315,13 +327,13 @@ public class UserController {
// 姓名为空检测
String tmpName = userItem.getName();
if (tmpName == null || tmpName.trim().length() == 0) {
if (tmpName == null || tmpName.trim().isEmpty()) {
errorLines.add(new String[] {"" + (i + startLine) + "", "昵称为空"});
}
// 密码为空检测
String tmpPassword = userItem.getPassword();
if (tmpPassword == null || tmpPassword.trim().length() == 0) {
if (tmpPassword == null || tmpPassword.trim().isEmpty()) {
errorLines.add(new String[] {"" + (i + startLine) + "", "密码为空"});
}
@@ -348,7 +360,7 @@ public class UserController {
// 邮箱是否注册检测
List<String> existsEmails = userService.existsEmailsByEmails(emails);
if (existsEmails.size() > 0) {
if (!existsEmails.isEmpty()) {
for (String tmpEmail : existsEmails) {
errorLines.add(new String[] {"" + emailRepeat.get(tmpEmail) + "", "邮箱已注册"});
}
@@ -381,9 +393,10 @@ public class UserController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_LEARN)
@BackendPermission(slug = BPermissionConstant.USER_LEARN)
@GetMapping("/{id}/learn-hours")
@SneakyThrows
@Log(title = "学员-已学习课时列表", businessType = BusinessTypeConstant.GET)
public JsonResponse learnHours(
@PathVariable(name = "id") Integer id, @RequestParam HashMap<String, Object> params) {
Integer page = MapUtils.getInteger(params, "page", 1);
@@ -417,8 +430,9 @@ public class UserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_LEARN)
@BackendPermission(slug = BPermissionConstant.USER_LEARN)
@GetMapping("/{id}/learn-courses")
@Log(title = "学员-已学习课程列表", businessType = BusinessTypeConstant.GET)
public JsonResponse latestLearnCourses(
@PathVariable(name = "id") Integer id, @RequestParam HashMap<String, Object> params) {
Integer page = MapUtils.getInteger(params, "page", 1);
@@ -452,8 +466,9 @@ public class UserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_LEARN)
@BackendPermission(slug = BPermissionConstant.USER_LEARN)
@GetMapping("/{id}/all-courses")
@Log(title = "学员-课程", businessType = BusinessTypeConstant.GET)
public JsonResponse allCourses(@PathVariable(name = "id") Integer id) {
// 读取学员关联的部门
List<Integer> depIds = userService.getDepIdsByUserId(id);
@@ -461,7 +476,7 @@ public class UserController {
HashMap<Integer, List<Course>> depCourses = new HashMap<>();
List<Integer> courseIds = new ArrayList<>();
if (depIds != null && depIds.size() > 0) {
if (depIds != null && !depIds.isEmpty()) {
departments = departmentService.chunk(depIds);
depIds.forEach(
(depId) -> {
@@ -474,7 +489,7 @@ public class UserController {
});
depCourses.put(depId, tmpCourses);
if (tmpCourses != null && tmpCourses.size() > 0) {
if (tmpCourses != null && !tmpCourses.isEmpty()) {
courseIds.addAll(tmpCourses.stream().map(Course::getId).toList());
}
});
@@ -482,13 +497,13 @@ public class UserController {
// 未关联部门课程
List<Course> openCourses = courseService.getOpenCoursesAndShow(1000);
if (openCourses != null && openCourses.size() > 0) {
if (openCourses != null && !openCourses.isEmpty()) {
courseIds.addAll(openCourses.stream().map(Course::getId).toList());
}
// 读取学员的线上课学习记录
List<UserCourseRecord> userCourseRecords = new ArrayList<>();
if (courseIds.size() > 0) {
if (!courseIds.isEmpty()) {
userCourseRecords = userCourseRecordService.chunk(id, courseIds);
}
@@ -513,9 +528,10 @@ public class UserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_LEARN)
@BackendPermission(slug = BPermissionConstant.USER_LEARN)
@GetMapping("/{id}/learn-course/{courseId}")
@SneakyThrows
@Log(title = "学员-单个课程的学习记录", businessType = BusinessTypeConstant.GET)
public JsonResponse learnCourseDetail(
@PathVariable(name = "id") Integer id,
@PathVariable(name = "courseId") Integer courseId) {
@@ -534,9 +550,10 @@ public class UserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_LEARN)
@BackendPermission(slug = BPermissionConstant.USER_LEARN)
@GetMapping("/{id}/learn-stats")
@SneakyThrows
@Log(title = "学员-学习统计", businessType = BusinessTypeConstant.GET)
public JsonResponse learn(@PathVariable(name = "id") Integer id) {
// 最近一个月的每天学习时长
String todayStr = DateTime.now().toDateStr();
@@ -581,9 +598,10 @@ public class UserController {
return JsonResponse.data(data);
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_LEARN_DESTROY)
@BackendPermission(slug = BPermissionConstant.USER_LEARN_DESTROY)
@DeleteMapping("/{id}/learn-course/{courseId}")
@SneakyThrows
@Log(title = "学员-线上课学习记录删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroyUserCourse(
@PathVariable(name = "id") Integer id,
@PathVariable(name = "courseId") Integer courseId) {
@@ -592,9 +610,10 @@ public class UserController {
return JsonResponse.success();
}
@BackendPermissionMiddleware(slug = BPermissionConstant.USER_LEARN_DESTROY)
@BackendPermission(slug = BPermissionConstant.USER_LEARN_DESTROY)
@DeleteMapping("/{id}/learn-course/{courseId}/hour/{hourId}")
@SneakyThrows
@Log(title = "学员-线上课课时学习记录删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse destroyUserHour(
@PathVariable(name = "id") Integer id,
@PathVariable(name = "courseId") Integer courseId,

View File

@@ -20,9 +20,9 @@ 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.domain.ResourceCategory;
import xyz.playedu.api.service.ResourceCategoryService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.common.domain.Category;
import xyz.playedu.common.service.CategoryService;
import xyz.playedu.common.types.JsonResponse;
import java.util.HashMap;
import java.util.List;
@@ -32,15 +32,15 @@ import java.util.stream.Collectors;
@RequestMapping("/api/v1/category")
public class CategoryController {
@Autowired private ResourceCategoryService resourceCategoryService;
@Autowired private CategoryService categoryService;
@GetMapping("/all")
public JsonResponse all() {
List<ResourceCategory> categories = resourceCategoryService.all();
List<Category> categories = categoryService.all();
HashMap<String, Object> data = new HashMap<>();
data.put(
"categories",
categories.stream().collect(Collectors.groupingBy(ResourceCategory::getParentId)));
categories.stream().collect(Collectors.groupingBy(Category::getParentId)));
return JsonResponse.data(data);
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.controller.frontend;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.common.context.FCtx;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.IpUtil;
import xyz.playedu.course.domain.*;
import xyz.playedu.course.service.*;
import xyz.playedu.resource.domain.Resource;
import xyz.playedu.resource.service.ResourceService;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/3/13 16:25
*/
@RestController
@RequestMapping("/api/v1/course")
public class CourseController {
@Autowired private CourseService courseService;
@Autowired private CourseChapterService chapterService;
@Autowired private CourseHourService hourService;
@Autowired private CourseAttachmentService attachmentService;
@Autowired private ResourceService resourceService;
@Autowired private UserCourseRecordService userCourseRecordService;
@Autowired private UserCourseHourRecordService userCourseHourRecordService;
@Autowired private CourseAttachmentDownloadLogService courseAttachmentDownloadLogService;
@GetMapping("/{id}")
@SneakyThrows
public JsonResponse detail(@PathVariable(name = "id") Integer id) {
Course course = courseService.findOrFail(id);
List<CourseHour> courseHours = hourService.getHoursByCourseId(course.getId());
List<CourseAttachment> attachments =
attachmentService.getAttachmentsByCourseId(course.getId());
if (null != attachments && !attachments.isEmpty()) {
Map<Integer, Resource> resourceMap =
resourceService
.chunks(attachments.stream().map(CourseAttachment::getRid).toList())
.stream()
.collect(Collectors.toMap(Resource::getId, Function.identity()));
attachments.forEach(
courseAttachment -> {
Resource resource = resourceMap.get(courseAttachment.getRid());
if (null != resource) {
courseAttachment.setExt(resource.getExtension());
}
});
}
HashMap<String, Object> data = new HashMap<>();
data.put("course", course);
data.put("chapters", chapterService.getChaptersByCourseId(course.getId()));
data.put(
"hours",
courseHours.stream().collect(Collectors.groupingBy(CourseHour::getChapterId)));
data.put("learn_record", userCourseRecordService.find(FCtx.getId(), course.getId()));
data.put(
"learn_hour_records",
userCourseHourRecordService.getRecords(FCtx.getId(), course.getId()).stream()
.collect(Collectors.toMap(UserCourseHourRecord::getHourId, e -> e)));
data.put("attachments", attachments);
return JsonResponse.data(data);
}
@GetMapping("/{courseId}/attach/{id}/download")
@SneakyThrows
public JsonResponse attachmentDownload(
@PathVariable(name = "courseId") Integer courseId,
@PathVariable(name = "id") Integer id) {
CourseAttachment attachment = attachmentService.findOrFail(id, courseId);
Resource resource = resourceService.findOrFail(attachment.getRid());
HashMap<String, Object> data = new HashMap<>();
data.put("download_url", resource.getUrl());
courseAttachmentDownloadLogService.save(
new CourseAttachmentDownloadLog() {
{
setUserId(FCtx.getId());
setCourseId(attachment.getCourseId());
setCourserAttachmentId(attachment.getId());
setRid(resource.getId());
setTitle(attachment.getTitle());
setIp(IpUtil.getIpAddress());
setCreatedAt(new Date());
}
});
return JsonResponse.data(data);
}
}

View File

@@ -19,14 +19,14 @@ import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.api.domain.Course;
import xyz.playedu.api.domain.Department;
import xyz.playedu.api.exception.NotFoundException;
import xyz.playedu.api.service.CourseService;
import xyz.playedu.api.service.DepartmentService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.types.paginate.CoursePaginateFiler;
import xyz.playedu.api.types.paginate.PaginationResult;
import xyz.playedu.common.domain.Department;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.service.DepartmentService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.paginate.CoursePaginateFiler;
import xyz.playedu.common.types.paginate.PaginationResult;
import xyz.playedu.course.domain.Course;
import xyz.playedu.course.service.CourseService;
import java.util.HashMap;
import java.util.stream.Collectors;

View File

@@ -18,21 +18,27 @@ package xyz.playedu.api.controller.frontend;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import xyz.playedu.api.FCtx;
import xyz.playedu.api.bus.UserBus;
import xyz.playedu.api.caches.CourseCache;
import xyz.playedu.api.caches.UserCanSeeCourseCache;
import xyz.playedu.api.domain.*;
import xyz.playedu.api.event.UserCourseHourFinishedEvent;
import xyz.playedu.api.event.UserLearnCourseUpdateEvent;
import xyz.playedu.api.request.frontend.CourseHourRecordRequest;
import xyz.playedu.api.service.CourseHourService;
import xyz.playedu.api.service.CourseService;
import xyz.playedu.api.service.ResourceService;
import xyz.playedu.api.service.UserCourseHourRecordService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.RedisDistributedLock;
import xyz.playedu.common.context.FCtx;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.RedisDistributedLock;
import xyz.playedu.course.caches.CourseCache;
import xyz.playedu.course.caches.UserCanSeeCourseCache;
import xyz.playedu.course.caches.UserLastLearnTimeCache;
import xyz.playedu.course.domain.Course;
import xyz.playedu.course.domain.CourseHour;
import xyz.playedu.course.domain.UserCourseHourRecord;
import xyz.playedu.course.service.CourseHourService;
import xyz.playedu.course.service.CourseService;
import xyz.playedu.course.service.UserCourseHourRecordService;
import xyz.playedu.resource.domain.Resource;
import xyz.playedu.resource.service.ResourceService;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
@@ -54,14 +60,16 @@ public class HourController {
@Autowired private UserCourseHourRecordService userCourseHourRecordService;
@Autowired private UserBus userBus;
// ------- CACHE ----------
@Autowired private UserCanSeeCourseCache userCanSeeCourseCache;
@Autowired private CourseCache courseCache;
@Autowired private RedisDistributedLock redisDistributedLock;
@Autowired private UserLastLearnTimeCache userLastLearnTimeCache;
@Autowired private ApplicationContext ctx;
@GetMapping("/{id}")
@SneakyThrows
public JsonResponse detail(
@@ -124,11 +132,23 @@ public class HourController {
return JsonResponse.error("请稍后再试");
}
userCourseHourRecordService.storeOrUpdate(
FCtx.getId(), course.getId(), hour.getId(), duration, hour.getDuration());
// 此处未考虑上面代码执行失败释放锁
redisDistributedLock.releaseLock(lockKey);
try {
boolean isFinished =
userCourseHourRecordService.storeOrUpdate(
FCtx.getId(),
course.getId(),
hour.getId(),
duration,
hour.getDuration());
if (isFinished) {
ctx.publishEvent(
new UserCourseHourFinishedEvent(
this, FCtx.getId(), courseId, hour.getId()));
}
} finally {
// 此处未考虑上面代码执行失败释放锁
redisDistributedLock.releaseLock(lockKey);
}
return JsonResponse.success();
}
@@ -149,10 +169,25 @@ public class HourController {
return JsonResponse.error("请稍后再试");
}
userBus.userLearnDurationRecord(FCtx.getUser(), course, hour);
try {
Long curTime = System.currentTimeMillis();
// 此处未考虑上面代码执行失败释放锁
redisDistributedLock.releaseLock(lockKey);
// 最近一次学习时间
Long lastTime = userLastLearnTimeCache.get(FCtx.getId());
// 最大周期为10s+0.5s的网络延迟
if (lastTime == null || curTime - lastTime > 10500) {
lastTime = curTime - 10000;
}
userLastLearnTimeCache.put(FCtx.getId(), curTime);
ctx.publishEvent(
new UserLearnCourseUpdateEvent(
this, FCtx.getId(), course.getId(), hour.getId(), lastTime, curTime));
} finally {
// 此处未考虑上面代码执行失败释放锁
redisDistributedLock.releaseLock(lockKey);
}
return JsonResponse.success();
}

View File

@@ -18,17 +18,10 @@ package xyz.playedu.api.controller.frontend;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.playedu.api.types.JsonResponse;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/3/24 17:42
*/
@RestController
public class IndexController {
@GetMapping("/")
public JsonResponse index() {
return JsonResponse.success();
public String index() {
return "系统正在运行中...";
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.controller.frontend;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.validation.annotation.Validated;
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 xyz.playedu.api.bus.LoginBus;
import xyz.playedu.api.cache.LoginLimitCache;
import xyz.playedu.api.cache.LoginLockCache;
import xyz.playedu.api.event.UserLogoutEvent;
import xyz.playedu.api.request.frontend.LoginLdapRequest;
import xyz.playedu.api.request.frontend.LoginPasswordRequest;
import xyz.playedu.common.constant.ConfigConstant;
import xyz.playedu.common.context.FCtx;
import xyz.playedu.common.domain.User;
import xyz.playedu.common.exception.LimitException;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.service.*;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.*;
import xyz.playedu.common.util.ldap.LdapTransformUser;
import xyz.playedu.common.util.ldap.LdapUtil;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/auth/login")
@Slf4j
public class LoginController {
@Autowired private UserService userService;
@Autowired private FrontendAuthService authService;
@Autowired private ApplicationContext ctx;
@Autowired private AppConfigService appConfigService;
@Autowired private LoginBus loginBus;
@Autowired private LoginLimitCache loginLimitCache;
@Autowired private LoginLockCache loginLockCache;
@PostMapping("/password")
@SneakyThrows
public JsonResponse password(@RequestBody @Validated LoginPasswordRequest req)
throws LimitException {
if (appConfigService.enabledLdapLogin()) {
return JsonResponse.error("请使用LDAP登录");
}
String email = req.getEmail();
User user = userService.find(email);
if (user == null) {
return JsonResponse.error("邮箱或密码错误");
}
loginLimitCache.check(email);
if (!HelperUtil.MD5(req.getPassword() + user.getSalt()).equals(user.getPassword())) {
return JsonResponse.error("邮箱或密码错误");
}
if (user.getIsLock() == 1) {
return JsonResponse.error("当前学员已锁定无法登录");
}
loginLimitCache.destroy(email);
return JsonResponse.data(loginBus.tokenByUser(user));
}
@PostMapping("/ldap")
@SneakyThrows
public JsonResponse ldap(@RequestBody @Validated LoginLdapRequest req) {
String username = req.getUsername();
// 系统配置
Map<String, String> config = appConfigService.keyValues();
String url = config.get(ConfigConstant.LDAP_URL);
String adminUser = config.get(ConfigConstant.LDAP_ADMIN_USER);
String adminPass = config.get(ConfigConstant.LDAP_ADMIN_PASS);
String baseDN = config.get(ConfigConstant.LDAP_BASE_DN);
if (url.isEmpty() || adminUser.isEmpty() || adminPass.isEmpty() || baseDN.isEmpty()) {
return JsonResponse.error("LDAP服务未配置");
}
String mail = null;
String uid = null;
if (StringUtil.contains(username, "@")) {
mail = username;
} else {
uid = username;
}
// 限流控制
loginLimitCache.check(username);
// 锁控制-防止并发登录重复写入数据
if (!loginLockCache.apply(username)) {
return JsonResponse.error("请稍候再试");
}
try {
LdapTransformUser ldapTransformUser =
LdapUtil.loginByMailOrUid(
url, adminUser, adminPass, baseDN, mail, uid, req.getPassword());
if (ldapTransformUser == null) {
return JsonResponse.error("登录失败.请检查账号和密码");
}
HashMap<String, Object> data = loginBus.tokenByLdapTransformUser(ldapTransformUser);
// 删除限流控制
loginLimitCache.destroy(username);
return JsonResponse.data(data);
} catch (ServiceException e) {
return JsonResponse.error(e.getMessage());
} catch (Exception e) {
log.error("LDAP登录失败", e);
return JsonResponse.error("系统错误");
} finally {
loginLockCache.release(username);
}
}
@PostMapping("/logout")
public JsonResponse logout() {
authService.logout();
ctx.publishEvent(new UserLogoutEvent(this, FCtx.getId(), FCtx.getJwtJti()));
return JsonResponse.success();
}
}

View File

@@ -20,40 +20,30 @@ 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;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.common.constant.ConfigConstant;
import xyz.playedu.common.service.AppConfigService;
import xyz.playedu.common.types.JsonResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/3/13 11:26
*/
@RestController
@RequestMapping("/api/v1/system")
public class SystemController {
@Autowired private AppConfigService appConfigService;
@Autowired private ImageCaptchaService imageCaptchaService;
@GetMapping("/config")
public JsonResponse config() {
Map<String, String> configs = appConfigService.keyValues();
HashMap<String, String> data = new HashMap<>();
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-name", configs.get(ConfigConstant.SYSTEM_NAME));
data.put("system-logo", configs.get(ConfigConstant.SYSTEM_LOGO));
data.put("system-api-url", configs.get(ConfigConstant.SYSTEM_API_URL));
data.put("system-pc-url", configs.get(ConfigConstant.SYSTEM_PC_URL));
data.put("system-h5-url", configs.get(ConfigConstant.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"));
@@ -63,16 +53,7 @@ public class SystemController {
data.put("player-bullet-secret-opacity", configs.get("player.bullet_secret_opacity"));
data.put("player-disabled-drag", configs.get("player.disabled_drag"));
return JsonResponse.data(data);
}
@GetMapping("/image-captcha")
public JsonResponse imageCaptcha() throws IOException {
ImageCaptchaResult imageCaptchaResult = imageCaptchaService.generate();
HashMap<String, String> data = new HashMap<>();
data.put("key", imageCaptchaResult.getKey());
data.put("image", imageCaptchaResult.getImage());
data.put("ldap-enabled", configs.get(ConfigConstant.LDAP_ENABLED));
return JsonResponse.data(data);
}

View File

@@ -23,25 +23,25 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import xyz.playedu.api.FCtx;
import xyz.playedu.api.constant.FrontendConstant;
import xyz.playedu.api.domain.*;
import xyz.playedu.api.exception.ServiceException;
import xyz.playedu.api.request.frontend.ChangePasswordRequest;
import xyz.playedu.api.service.*;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.types.mapper.UserCourseHourRecordCourseCountMapper;
import xyz.playedu.api.types.response.UserLatestLearn;
import xyz.playedu.api.util.PrivacyUtil;
import xyz.playedu.common.constant.FrontendConstant;
import xyz.playedu.common.context.FCtx;
import xyz.playedu.common.domain.Department;
import xyz.playedu.common.domain.User;
import xyz.playedu.common.domain.UserUploadImageLog;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.service.DepartmentService;
import xyz.playedu.common.service.UserService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.types.mapper.UserCourseHourRecordCourseCountMapper;
import xyz.playedu.common.util.PrivacyUtil;
import xyz.playedu.course.domain.*;
import xyz.playedu.course.service.*;
import xyz.playedu.resource.service.UploadService;
import java.util.*;
import java.util.stream.Collectors;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/3/13 09:21
*/
@RestController
@RequestMapping("/api/v1/user")
@Slf4j
@@ -68,7 +68,7 @@ public class UserController {
User user = FCtx.getUser();
List<Department> departments = new ArrayList<>();
List<Integer> depIds = userService.getDepIdsByUserId(user.getId());
if (depIds != null && depIds.size() > 0) {
if (depIds != null && !depIds.isEmpty()) {
departments = departmentService.listByIds(depIds);
}
@@ -134,17 +134,21 @@ public class UserController {
// 全部部门课
List<Course> openCourses = courseService.getOpenCoursesAndShow(500, categoryId);
// 汇总到一个list中
if (depCourses != null && depCourses.size() > 0) {
if (depCourses != null && !depCourses.isEmpty()) {
courses.addAll(depCourses);
}
if (openCourses != null && openCourses.size() > 0) {
if (openCourses != null && !openCourses.isEmpty()) {
courses.addAll(openCourses);
}
// 对结果进行排序->按照课程id倒序
if (courses.size() > 0) {
if (!courses.isEmpty()) {
courses =
courses.stream()
.sorted(Comparator.comparing(Course::getId).reversed())
.sorted(
Comparator.comparing(
Course::getPublishedAt,
Comparator.nullsFirst(Date::compareTo))
.reversed())
.toList();
}
@@ -154,7 +158,7 @@ public class UserController {
// -------- 读取学习进度 ----------
Map<Integer, UserCourseRecord> learnCourseRecords = new HashMap<>();
if (courses.size() > 0) {
if (!courses.isEmpty()) {
learnCourseRecords =
userCourseRecordService.chunk(FCtx.getId(), courseIds).stream()
.collect(Collectors.toMap(UserCourseRecord::getCourseId, e -> e));
@@ -174,7 +178,7 @@ public class UserController {
Long learnDuration = userLearnDurationStatsService.userDuration(FCtx.getId()); // 学习总时长
// -------- 学习数据统计 ----------
if (courses.size() > 0) {
if (!courses.isEmpty()) {
for (Course courseItem : courses) {
if (courseItem.getIsRequired() == 1) {
requiredHourCount += courseItem.getClassHour();
@@ -233,7 +237,7 @@ public class UserController {
// 读取当前学员最近100条学习的线上课
List<UserCourseHourRecord> userCourseHourRecords =
userCourseHourRecordService.getLatestCourseIds(FCtx.getId(), 100);
if (userCourseHourRecords == null || userCourseHourRecords.size() == 0) {
if (userCourseHourRecords == null || userCourseHourRecords.isEmpty()) {
return JsonResponse.data(new ArrayList<>());
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.middleware;
package xyz.playedu.api.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -21,32 +21,31 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import xyz.playedu.api.BCtx;
import xyz.playedu.api.bus.AppBus;
import xyz.playedu.api.bus.BackendBus;
import xyz.playedu.api.domain.AdminUser;
import xyz.playedu.api.service.AdminUserService;
import xyz.playedu.api.service.AppConfigService;
import xyz.playedu.api.service.BackendAuthService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.HelperUtil;
import xyz.playedu.common.bus.BackendBus;
import xyz.playedu.common.context.BCtx;
import xyz.playedu.common.domain.AdminUser;
import xyz.playedu.common.service.AdminUserService;
import xyz.playedu.common.service.AppConfigService;
import xyz.playedu.common.service.BackendAuthService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.HelperUtil;
import java.io.IOException;
import java.util.Map;
@Component
@Slf4j
public class AdminMiddleware implements HandlerInterceptor {
@Order(20)
public class AdminInterceptor implements HandlerInterceptor {
@Autowired private BackendAuthService authService;
@Autowired private AdminUserService adminUserService;
@Autowired private AppBus appBus;
@Autowired private BackendBus backendBus;
@Autowired private AppConfigService configService;
@@ -55,10 +54,6 @@ public class AdminMiddleware implements HandlerInterceptor {
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if ("OPTIONS".equals(request.getMethod())) {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
// 读取全局配置
Map<String, String> systemConfig = configService.keyValues();
BCtx.setConfig(systemConfig);

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import xyz.playedu.common.config.PlayEduConfig;
import xyz.playedu.common.constant.BackendConstant;
import xyz.playedu.common.service.RateLimiterService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.HelperUtil;
import xyz.playedu.common.util.IpUtil;
@Component
@Slf4j
@Order(10)
public class ApiInterceptor implements HandlerInterceptor {
@Autowired private RateLimiterService rateLimiterService;
@Autowired private PlayEduConfig playEduConfig;
@Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "86400");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(204);
// 返回false意味着整个请求执行到这里结束不会继续乡下执行了
return false;
}
// 当前api的请求路径
String path = request.getRequestURI();
// 白名单过滤 || OPTIONS请求
if (BackendConstant.API_LIMIT_WHITELIST.contains(path)) {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
// 限流判断
String reqCountKey = "api-limiter:" + IpUtil.getIpAddress();
Long reqCount = rateLimiterService.current(reqCountKey, playEduConfig.getLimiterDuration());
long limitCount = playEduConfig.getLimiterLimit();
long limitRemaining = limitCount - reqCount;
response.setHeader("X-RateLimit-Limit", String.valueOf(limitCount));
response.setHeader("X-RateLimit-Remaining", String.valueOf(limitRemaining));
if (limitRemaining <= 0) {
response.setStatus(429);
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(HelperUtil.toJsonStr(JsonResponse.error("太多请求")));
return false;
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.middleware;
package xyz.playedu.api.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -21,22 +21,24 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import xyz.playedu.api.FCtx;
import xyz.playedu.api.constant.FrontendConstant;
import xyz.playedu.api.domain.User;
import xyz.playedu.api.service.FrontendAuthService;
import xyz.playedu.api.service.UserService;
import xyz.playedu.api.types.JsonResponse;
import xyz.playedu.api.util.HelperUtil;
import xyz.playedu.common.constant.FrontendConstant;
import xyz.playedu.common.context.FCtx;
import xyz.playedu.common.domain.User;
import xyz.playedu.common.service.FrontendAuthService;
import xyz.playedu.common.service.UserService;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.HelperUtil;
import java.io.IOException;
@Component
@Slf4j
public class FrontMiddleware implements HandlerInterceptor {
@Order(20)
public class FrontInterceptor implements HandlerInterceptor {
@Autowired private FrontendAuthService authService;
@@ -46,10 +48,6 @@ public class FrontMiddleware implements HandlerInterceptor {
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if ("OPTIONS".equals(request.getMethod())) {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
if (FrontendConstant.UN_AUTH_URI_WHITELIST.contains(request.getRequestURI())) {
return HandlerInterceptor.super.preHandle(request, response, handler);
}

View File

@@ -13,42 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.config;
import jakarta.annotation.Resource;
package xyz.playedu.api.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import xyz.playedu.api.middleware.AdminMiddleware;
import xyz.playedu.api.middleware.FrontMiddleware;
@Configuration
@Slf4j
public class WebMvcConfig implements WebMvcConfigurer {
@Resource private AdminMiddleware adminMiddleware;
@Autowired private AdminInterceptor adminInterceptor;
@Autowired private FrontMiddleware frontMiddleware;
@Autowired private FrontInterceptor frontInterceptor;
@Autowired private ApiInterceptor apiInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminMiddleware).addPathPatterns("/backend/**");
registry.addInterceptor(frontMiddleware).addPathPatterns("/api/v1/**");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(false)
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("GET", "PUT", "POST", "DELETE")
.exposedHeaders("*");
registry.addInterceptor(apiInterceptor).addPathPatterns("/**");
registry.addInterceptor(adminInterceptor).addPathPatterns("/backend/**");
registry.addInterceptor(frontInterceptor).addPathPatterns("/api/v1/**");
}
}

View File

@@ -19,18 +19,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import xyz.playedu.api.constant.BackendLogConstant;
import xyz.playedu.api.domain.AdminLog;
import xyz.playedu.api.domain.AdminUser;
import xyz.playedu.api.event.AdminUserLoginEvent;
import xyz.playedu.api.service.AdminLogService;
import xyz.playedu.api.service.AdminUserService;
import xyz.playedu.api.util.IpUtil;
import java.util.Date;
import xyz.playedu.common.domain.AdminUser;
import xyz.playedu.common.service.AdminUserService;
@Component
@Slf4j
@@ -38,8 +31,6 @@ public class AdminUserLoginListener {
@Autowired private AdminUserService adminUserService;
@Autowired private AdminLogService adminLogService;
@EventListener
public void updateLoginInfo(AdminUserLoginEvent event) {
AdminUser adminUser = new AdminUser();
@@ -51,20 +42,4 @@ public class AdminUserLoginListener {
adminUserService.updateById(adminUser);
}
@Async
@EventListener
public void log(AdminUserLoginEvent event) {
String area = IpUtil.getRealAddressByIP(event.getIp());
AdminLog adminLog = new AdminLog();
adminLog.setAdminId(event.getAdminId());
adminLog.setModule(BackendLogConstant.MODULE_LOGIN);
adminLog.setOpt(BackendLogConstant.OPT_LOGIN);
adminLog.setIp(event.getIp());
adminLog.setIpArea(area);
adminLog.setCreatedAt(new Date());
adminLogService.save(adminLog);
}
}

View File

@@ -20,7 +20,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.CourseCategoryDestroyEvent;
import xyz.playedu.api.service.CourseService;
import xyz.playedu.course.service.CourseService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -20,7 +20,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.CourseChapterDestroyEvent;
import xyz.playedu.api.service.CourseHourService;
import xyz.playedu.course.service.CourseHourService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -20,10 +20,11 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.CourseDestroyEvent;
import xyz.playedu.api.service.CourseDepartmentService;
import xyz.playedu.api.service.UserCourseHourRecordService;
import xyz.playedu.api.service.UserCourseRecordService;
import xyz.playedu.api.service.internal.ResourceCourseCategoryService;
import xyz.playedu.course.service.CourseAttachmentService;
import xyz.playedu.course.service.CourseCategoryService;
import xyz.playedu.course.service.CourseDepartmentService;
import xyz.playedu.course.service.UserCourseHourRecordService;
import xyz.playedu.course.service.UserCourseRecordService;
/**
* @Author 杭州白书科技有限公司
@@ -35,12 +36,14 @@ public class CourseDestroyListener {
@Autowired private CourseDepartmentService courseDepartmentService;
@Autowired private ResourceCourseCategoryService courseCategoryService;
@Autowired private CourseCategoryService courseCategoryService;
@Autowired private UserCourseRecordService userCourseRecordService;
@Autowired private UserCourseHourRecordService userCourseHourRecordService;
@Autowired private CourseAttachmentService courseAttachmentService;
@EventListener
public void departmentRelateRemove(CourseDestroyEvent event) {
courseDepartmentService.removeByCourseId(event.getCourseId());
@@ -51,6 +54,11 @@ public class CourseDestroyListener {
courseCategoryService.removeByCourseId(event.getCourseId());
}
@EventListener
public void attachmentRelateRemove(CourseDestroyEvent event) {
courseAttachmentService.remove(event.getCourseId());
}
@EventListener
public void removeUserRecords(CourseDestroyEvent event) {
userCourseRecordService.removeByCourseId(event.getCourseId());

View File

@@ -20,8 +20,8 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.CourseHourCreatedEvent;
import xyz.playedu.api.service.CourseHourService;
import xyz.playedu.api.service.CourseService;
import xyz.playedu.course.service.CourseHourService;
import xyz.playedu.course.service.CourseService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -20,8 +20,8 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.CourseHourDestroyEvent;
import xyz.playedu.api.service.CourseHourService;
import xyz.playedu.api.service.CourseService;
import xyz.playedu.course.service.CourseHourService;
import xyz.playedu.course.service.CourseService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -20,7 +20,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.DepartmentDestroyEvent;
import xyz.playedu.api.service.DepartmentService;
import xyz.playedu.common.service.DepartmentService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -20,9 +20,9 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.UserCourseHourFinishedEvent;
import xyz.playedu.api.service.CourseHourService;
import xyz.playedu.api.service.UserCourseHourRecordService;
import xyz.playedu.api.service.UserCourseRecordService;
import xyz.playedu.course.service.CourseHourService;
import xyz.playedu.course.service.UserCourseHourRecordService;
import xyz.playedu.course.service.UserCourseRecordService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -22,7 +22,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.UserCourseHourRecordDestroyEvent;
import xyz.playedu.api.service.UserCourseRecordService;
import xyz.playedu.course.service.UserCourseRecordService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -20,7 +20,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.UserCourseRecordDestroyEvent;
import xyz.playedu.api.service.UserCourseHourRecordService;
import xyz.playedu.course.service.UserCourseHourRecordService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -22,7 +22,12 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.UserDestroyEvent;
import xyz.playedu.api.service.*;
import xyz.playedu.common.service.UserLoginRecordService;
import xyz.playedu.common.service.UserService;
import xyz.playedu.course.service.UserCourseHourRecordService;
import xyz.playedu.course.service.UserCourseRecordService;
import xyz.playedu.course.service.UserLearnDurationRecordService;
import xyz.playedu.course.service.UserLearnDurationStatsService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -22,8 +22,8 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.UserLearnCourseUpdateEvent;
import xyz.playedu.api.service.UserLearnDurationRecordService;
import xyz.playedu.api.service.UserLearnDurationStatsService;
import xyz.playedu.course.service.UserLearnDurationRecordService;
import xyz.playedu.course.service.UserLearnDurationStatsService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -23,9 +23,9 @@ import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.UserLoginEvent;
import xyz.playedu.api.service.FrontendAuthService;
import xyz.playedu.api.service.UserLoginRecordService;
import xyz.playedu.api.util.IpUtil;
import xyz.playedu.common.service.FrontendAuthService;
import xyz.playedu.common.service.UserLoginRecordService;
import xyz.playedu.common.util.IpUtil;
import java.util.HashMap;

View File

@@ -23,7 +23,7 @@ import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.UserLogoutEvent;
import xyz.playedu.api.service.UserLoginRecordService;
import xyz.playedu.common.service.UserLoginRecordService;
/**
* @Author 杭州白书科技有限公司

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.request.backend;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Data
public class CourseAttachmentMultiRequest {
@Data
public static class AttachmentItem {
private String title;
private Integer sort;
private String type;
private Integer rid;
}
@NotNull(message = "attachments参数不存在")
private List<AttachmentItem> attachments;
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.request.backend;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class CourseAttachmentRequest {
@NotBlank(message = "请输入附件名称")
private String title;
@NotNull(message = "sort参数不存在")
private Integer sort;
@NotBlank(message = "请选择附件类型")
private String type;
@NotNull(message = "rid参数不存在")
private Integer rid;
}

View File

@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.service;
package xyz.playedu.api.request.backend;
import xyz.playedu.api.types.ImageCaptchaResult;
import lombok.Data;
import java.io.IOException;
import java.util.List;
public interface ImageCaptchaService {
ImageCaptchaResult generate() throws IOException;
boolean verify(String key, String code);
@Data
public class CourseAttachmentSortRequest {
private List<Integer> ids;
}

View File

@@ -22,6 +22,7 @@ import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
@@ -58,6 +59,9 @@ public class CourseRequest {
@JsonProperty("category_ids")
private Integer[] categoryIds;
@JsonProperty("published_at")
private Date publishedAt;
@Data
public static class HourItem {
private String name;
@@ -72,6 +76,13 @@ public class CourseRequest {
private List<HourItem> hours;
}
@Data
public static class AttachmentItem {
private String name;
private String type;
private Integer rid;
}
// 格式
// [
// {
@@ -100,4 +111,14 @@ public class CourseRequest {
// ]
@NotNull(message = "hours参数不存在")
private List<HourItem> hours;
// 格式
// [
// {
// 'name' => '附件名',
// 'type' => '附件类型',
// 'rid' => '资源id',
// }...
// ]
private List<AttachmentItem> attachments;
}

View File

@@ -15,19 +15,15 @@
*/
package xyz.playedu.api.request.backend;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import xyz.playedu.api.request.backend.types.ImageCaptchaRequestInterface;
import java.io.Serial;
import java.io.Serializable;
@Data
public class LoginRequest implements Serializable, ImageCaptchaRequestInterface {
public class LoginRequest implements Serializable {
@Serial private static final long serialVersionUID = 1L;
@@ -36,12 +32,4 @@ public class LoginRequest implements Serializable, ImageCaptchaRequestInterface
@NotNull(message = "请输入密码")
public String password;
@NotNull(message = "请输入图形验证码")
@JsonProperty("captcha_value")
public String captchaValue;
@NotNull(message = "captchaKey参数为空")
@JsonProperty("captcha_key")
public String captchaKey;
}

View File

@@ -28,7 +28,7 @@ import lombok.Data;
* @create 2023/3/8 14:49
*/
@Data
public class UploadVideoMergeRequest {
public class UploadFileMergeRequest {
@NotBlank(message = "请输入课程标题")
private String filename;

Some files were not shown because too many files have changed in this diff Show More