ldap登录
This commit is contained in:
白书科技 2023-09-01 03:48:39 +00:00
parent 069c3e4cc9
commit d5e410cb1f
30 changed files with 1292 additions and 89 deletions

View File

@ -4,6 +4,8 @@ on:
push:
branches:
- main
- dev
- 'feat/**'
env:
IMAGE_FQDN: registry.cn-hangzhou.aliyuncs.com/playedu/api

View File

@ -4,7 +4,7 @@ WORKDIR /app
COPY . /app
RUN /app/docker-build.sh
RUN /app/mvnw -Dmaven.test.skip=true clean package
FROM eclipse-temurin:17
@ -14,7 +14,7 @@ WORKDIR /app
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 将指定目录下的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
# 声明服务运行在8080端口
EXPOSE 9898

View File

@ -1,7 +0,0 @@
#!/bin/sh
echo '开始打包...'
export MAVEN_OPTS=-Dmaven.test.skip=true
/app/mvnw clean package

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

@ -0,0 +1,42 @@
/*
* 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.util.RedisDistributedLock;
import java.util.concurrent.TimeUnit;
@Component
public class LoginLockCache {
@Autowired private RedisDistributedLock redisDistributedLock;
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

@ -35,6 +35,7 @@ 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;
@ -66,6 +67,8 @@ public class DepartmentController {
@Autowired private ApplicationContext ctx;
@Autowired private AppConfigService appConfigService;
@GetMapping("/index")
@Log(title = "部门-列表", businessType = BusinessTypeConstant.GET)
public JsonResponse index() {
@ -98,6 +101,9 @@ public class DepartmentController {
@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();
}
@ -115,6 +121,9 @@ public class DepartmentController {
@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();
@ -124,6 +133,9 @@ public class DepartmentController {
@GetMapping("/{id}/destroy")
@Log(title = "部门-批量删除", businessType = BusinessTypeConstant.DELETE)
public JsonResponse preDestroy(@PathVariable Integer id) {
if (appConfigService.enabledLdapLogin()) {
return JsonResponse.error("已启用LDAP服务禁止添加部门");
}
List<Integer> courseIds = courseDepartmentService.getCourseIdsByDepId(id);
List<Integer> userIds = departmentService.getUserIdsByDepId(id);
@ -165,6 +177,9 @@ public class DepartmentController {
@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()));
@ -184,6 +199,9 @@ public class DepartmentController {
@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();
}

View File

@ -28,7 +28,6 @@ import xyz.playedu.api.request.frontend.CourseHourRecordRequest;
import xyz.playedu.common.context.FCtx;
import xyz.playedu.common.types.JsonResponse;
import xyz.playedu.common.util.RedisDistributedLock;
import xyz.playedu.course.bus.UserBus;
import xyz.playedu.course.caches.CourseCache;
import xyz.playedu.course.caches.UserCanSeeCourseCache;
import xyz.playedu.course.caches.UserLastLearnTimeCache;
@ -61,8 +60,6 @@ public class HourController {
@Autowired private UserCourseHourRecordService userCourseHourRecordService;
@Autowired private UserBus userBus;
// ------- CACHE ----------
@Autowired private UserCanSeeCourseCache userCanSeeCourseCache;
@Autowired private CourseCache courseCache;

View File

@ -15,6 +15,9 @@
*/
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;
@ -23,26 +26,29 @@ 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.event.UserLoginEvent;
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.config.PlayEduConfig;
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.service.FrontendAuthService;
import xyz.playedu.common.service.RateLimiterService;
import xyz.playedu.common.service.UserService;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.service.*;
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 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;
@ -51,13 +57,27 @@ public class LoginController {
@Autowired private ApplicationContext ctx;
@Autowired private RateLimiterService rateLimiterService;
@Autowired private AppConfigService appConfigService;
@Autowired private PlayEduConfig playEduConfig;
@Autowired private LdapUserService ldapUserService;
@Autowired private DepartmentService departmentService;
@Autowired private LoginBus loginBus;
@Autowired private LoginLimitCache loginLimitCache;
@Autowired private LoginLockCache loginLockCache;
@PostMapping("/password")
public JsonResponse password(@RequestBody @Validated LoginPasswordRequest req)
@SneakyThrows
public JsonResponse password(
@RequestBody @Validated LoginPasswordRequest req, LoginBus loginBus)
throws LimitException {
if (appConfigService.enabledLdapLogin()) {
return JsonResponse.error("请使用LDAP登录");
}
String email = req.getEmail();
User user = userService.find(email);
@ -65,39 +85,74 @@ public class LoginController {
return JsonResponse.error("邮箱或密码错误");
}
String limitKey = "login-limit:" + req.getEmail();
Long reqCount = rateLimiterService.current(limitKey, 600L);
if (reqCount >= 10 && !playEduConfig.getTesting()) {
Long exp = RedisUtil.ttlWithoutPrefix(limitKey);
return JsonResponse.error(
String.format("您的账号已被锁定,请%s后重试", exp > 60 ? exp / 60 + "分钟" : exp + ""));
}
loginLimitCache.check(email);
if (!HelperUtil.MD5(req.getPassword() + user.getSalt()).equals(user.getPassword())) {
return JsonResponse.error("邮箱或密码错误");
}
RedisUtil.del(limitKey);
if (user.getIsLock() == 1) {
return JsonResponse.error("当前学员已锁定无法登录");
}
String token = authService.loginUsingId(user.getId(), RequestUtil.url());
loginLimitCache.destroy(email);
HashMap<String, Object> data = new HashMap<>();
data.put("token", token);
return JsonResponse.data(loginBus.tokenByUser(user));
}
ctx.publishEvent(
new UserLoginEvent(
this,
user.getId(),
user.getEmail(),
token,
IpUtil.getIpAddress(),
RequestUtil.ua()));
@PostMapping("/ldap")
@SneakyThrows
public JsonResponse ldap(@RequestBody @Validated LoginLdapRequest req) {
String username = req.getUsername();
return JsonResponse.data(data);
// 系统配置
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")

View File

@ -53,6 +53,8 @@ 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"));
data.put("ldap-enabled", configs.get(ConfigConstant.LDAP_ENABLED));
return JsonResponse.data(data);
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.frontend;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class LoginLdapRequest {
@NotBlank(message = "请输入账户名")
private String username;
@NotBlank(message = "请输入密码")
private String password;
}

View File

@ -35,4 +35,11 @@ public class ConfigConstant {
public static final String MINIO_BUCKET = "minio.bucket";
public static final String MINIO_ENDPOINT = "minio.endpoint";
public static final String MINIO_DOMAIN = "minio.domain";
public static final String LDAP_ENABLED = "ldap.enabled";
public static final String LDAP_URL = "ldap.url";
public static final String LDAP_ADMIN_USER = "ldap.admin_user";
public static final String LDAP_ADMIN_PASS = "ldap.admin_pass";
public static final String LDAP_BASE_DN = "ldap.base_dn";
public static final String LDAP_USER_DN_PREFIX = "ldap.user_dn_prefix";
}

View File

@ -31,6 +31,7 @@ public class FrontendConstant {
add("/api/v1/system/config");
add("/api/v1/system/image-captcha");
add("/api/v1/auth/login/password");
add("/api/v1/auth/login/ldap");
}
};

View File

@ -0,0 +1,244 @@
/*
* 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.common.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
/**
* @TableName ldap_user
*/
@TableName(value = "ldap_user")
public class LdapUser implements Serializable {
/** */
@TableId(type = IdType.AUTO)
private Integer id;
/** 唯一特征值 */
private String uuid;
/** 用户ID */
private Integer userId;
/** cn */
private String cn;
/** dn */
private String dn;
/** ou */
private String ou;
/** uid */
private String uid;
/** 邮箱 */
private String email;
/** */
private Date createdAt;
/** */
private Date updatedAt;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** */
public Integer getId() {
return id;
}
/** */
public void setId(Integer id) {
this.id = id;
}
/** 唯一特征值 */
public String getUuid() {
return uuid;
}
/** 唯一特征值 */
public void setUuid(String uuid) {
this.uuid = uuid;
}
/** 用户ID */
public Integer getUserId() {
return userId;
}
/** 用户ID */
public void setUserId(Integer userId) {
this.userId = userId;
}
/** cn */
public String getCn() {
return cn;
}
/** cn */
public void setCn(String cn) {
this.cn = cn;
}
/** dn */
public String getDn() {
return dn;
}
/** dn */
public void setDn(String dn) {
this.dn = dn;
}
/** ou */
public String getOu() {
return ou;
}
/** ou */
public void setOu(String ou) {
this.ou = ou;
}
/** uid */
public String getUid() {
return uid;
}
/** uid */
public void setUid(String uid) {
this.uid = uid;
}
/** 邮箱 */
public String getEmail() {
return email;
}
/** 邮箱 */
public void setEmail(String email) {
this.email = email;
}
/** */
public Date getCreatedAt() {
return createdAt;
}
/** */
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
/** */
public Date getUpdatedAt() {
return updatedAt;
}
/** */
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
LdapUser other = (LdapUser) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
&& (this.getUuid() == null
? other.getUuid() == null
: this.getUuid().equals(other.getUuid()))
&& (this.getUserId() == null
? other.getUserId() == null
: this.getUserId().equals(other.getUserId()))
&& (this.getCn() == null
? other.getCn() == null
: this.getCn().equals(other.getCn()))
&& (this.getDn() == null
? other.getDn() == null
: this.getDn().equals(other.getDn()))
&& (this.getOu() == null
? other.getOu() == null
: this.getOu().equals(other.getOu()))
&& (this.getUid() == null
? other.getUid() == null
: this.getUid().equals(other.getUid()))
&& (this.getEmail() == null
? other.getEmail() == null
: this.getEmail().equals(other.getEmail()))
&& (this.getCreatedAt() == null
? other.getCreatedAt() == null
: this.getCreatedAt().equals(other.getCreatedAt()))
&& (this.getUpdatedAt() == null
? other.getUpdatedAt() == null
: this.getUpdatedAt().equals(other.getUpdatedAt()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getUuid() == null) ? 0 : getUuid().hashCode());
result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
result = prime * result + ((getCn() == null) ? 0 : getCn().hashCode());
result = prime * result + ((getDn() == null) ? 0 : getDn().hashCode());
result = prime * result + ((getOu() == null) ? 0 : getOu().hashCode());
result = prime * result + ((getUid() == null) ? 0 : getUid().hashCode());
result = prime * result + ((getEmail() == null) ? 0 : getEmail().hashCode());
result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
result = prime * result + ((getUpdatedAt() == null) ? 0 : getUpdatedAt().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", uuid=").append(uuid);
sb.append(", userId=").append(userId);
sb.append(", cn=").append(cn);
sb.append(", dn=").append(dn);
sb.append(", ou=").append(ou);
sb.append(", uid=").append(uid);
sb.append(", email=").append(email);
sb.append(", createdAt=").append(createdAt);
sb.append(", updatedAt=").append(updatedAt);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}

View File

@ -15,7 +15,7 @@
*/
package xyz.playedu.common.exception;
public class ServiceException extends Exception {
public class ServiceException extends RuntimeException {
public ServiceException() {}
public ServiceException(String message) {

View File

@ -0,0 +1,27 @@
/*
* 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.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import xyz.playedu.common.domain.LdapUser;
/**
* @author tengyongzhi
* @description 针对表ldap_user的数据库操作Mapper
* @createDate 2023-08-31 14:33:19 @Entity xyz.playedu.common.domain.LdapUser
*/
public interface LdapUserMapper extends BaseMapper<LdapUser> {}

View File

@ -35,4 +35,8 @@ public interface AppConfigService extends IService<AppConfig> {
Map<String, String> keyValues();
MinioConfig getMinioConfig();
boolean enabledLdapLogin();
String defaultAvatar();
}

View File

@ -45,7 +45,7 @@ public interface DepartmentService extends IService<Department> {
String childrenParentChain(Department department);
void create(String name, Integer parentId, Integer sort) throws NotFoundException;
Integer create(String name, Integer parentId, Integer sort) throws NotFoundException;
void remoteRelateUsersByDepId(Integer depId);
@ -64,4 +64,6 @@ public interface DepartmentService extends IService<Department> {
Map<Integer, Integer> getDepartmentsUserCount();
List<Department> chunk(List<Integer> ids);
Integer createWithChainList(List<String> ou);
}

View File

@ -0,0 +1,42 @@
/*
* 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.common.service;
import com.baomidou.mybatisplus.extension.service.IService;
import xyz.playedu.common.domain.LdapUser;
import xyz.playedu.common.util.ldap.LdapTransformUser;
/**
* @author tengyongzhi
* @description 针对表ldap_user的数据库操作Service
* @createDate 2023-08-31 11:59:27
*/
public interface LdapUserService extends IService<LdapUser> {
LdapUser findByUUID(String id);
LdapUser store(LdapTransformUser ldapTransformUser);
void updateUserId(Integer id, Integer userId);
void updateCN(Integer id, String cn);
void updateOU(Integer id, String newOU);
void updateEmail(Integer id, String email);
void updateUid(Integer id, String uid);
}

View File

@ -80,4 +80,10 @@ public interface UserService extends IService<User> {
Map<Integer, List<Integer>> getDepIdsGroup(List<Integer> userIds);
void changeAvatar(Integer userId, String avatar);
void updateName(Integer id, String cn);
void updateDepId(Integer id, Integer[] depIds);
void updateEmail(Integer id, String email);
}

View File

@ -81,7 +81,7 @@ public class AppConfigServiceImpl extends ServiceImpl<AppConfigMapper, AppConfig
});
});
if (list.size() > 0) {
if (!list.isEmpty()) {
updateBatchById(list);
}
}
@ -103,4 +103,18 @@ public class AppConfigServiceImpl extends ServiceImpl<AppConfigMapper, AppConfig
minioConfig.setDomain(config.get(ConfigConstant.MINIO_DOMAIN));
return minioConfig;
}
@Override
public boolean enabledLdapLogin() {
AppConfig appConfig =
getOne(query().getWrapper().eq("key_name", ConfigConstant.LDAP_ENABLED));
return "1".equals(appConfig.getKeyValue());
}
@Override
public String defaultAvatar() {
AppConfig appConfig =
getOne(query().getWrapper().eq("key_name", ConfigConstant.MEMBER_DEFAULT_AVATAR));
return appConfig.getKeyValue();
}
}

View File

@ -18,6 +18,7 @@ package xyz.playedu.common.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -108,7 +109,7 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
private void updateParentChain(String newChildrenPC, String oldChildrenPC) {
List<Department> children =
list(query().getWrapper().like("parent_chain", oldChildrenPC + "%"));
if (children.size() == 0) {
if (children.isEmpty()) {
return;
}
@ -125,7 +126,7 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
.getParentChain()
.replaceFirst(
oldChildrenPC + ",",
newChildrenPC.length() == 0
newChildrenPC.isEmpty()
? newChildrenPC
: newChildrenPC + ',');
}
@ -133,7 +134,7 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
// parentId计算
int parentId = 0;
if (pc != null && pc.length() > 0) {
if (pc != null && !pc.isEmpty()) {
String[] parentIds = pc.split(",");
parentId = Integer.parseInt(parentIds[parentIds.length - 1]);
}
@ -153,7 +154,7 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
throw new NotFoundException("父级部门不存在");
}
String pc = parentDepartment.getParentChain();
parentChain = pc == null || pc.length() == 0 ? parentId + "" : pc + "," + parentId;
parentChain = pc == null || pc.isEmpty() ? parentId + "" : pc + "," + parentId;
}
return parentChain;
}
@ -161,14 +162,14 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
@Override
public String childrenParentChain(Department department) {
String prefix = department.getId() + "";
if (department.getParentChain() != null && department.getParentChain().length() > 0) {
if (department.getParentChain() != null && !department.getParentChain().isEmpty()) {
prefix = department.getParentChain() + "," + prefix;
}
return prefix;
}
@Override
public void create(String name, Integer parentId, Integer sort) throws NotFoundException {
public Integer create(String name, Integer parentId, Integer sort) throws NotFoundException {
String parentChain = "";
if (parentId != 0) {
parentChain = compParentChain(parentId);
@ -183,6 +184,8 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
department.setUpdatedAt(new Date());
save(department);
return department.getId();
}
@Override
@ -258,9 +261,31 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
@Override
public List<Department> chunk(List<Integer> ids) {
if (ids == null || ids.size() == 0) {
if (ids == null || ids.isEmpty()) {
return new ArrayList<>();
}
return list(query().getWrapper().in("id", ids));
}
@Override
@SneakyThrows
public Integer createWithChainList(List<String> ou) {
if (ou == null || ou.isEmpty()) {
return 0;
}
Department department = null;
for (int i = 0; i < ou.size(); i++) {
String name = ou.get(i);
Integer parentId = department == null ? 0 : department.getId();
department = getOne(query().getWrapper().eq("name", name).eq("parent_id", parentId));
if (department == null) {
Integer depId = create(name, parentId, i);
// refresh
department = new Department();
department.setId(depId);
}
}
return department.getId();
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.common.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import xyz.playedu.common.domain.LdapUser;
import xyz.playedu.common.mapper.LdapUserMapper;
import xyz.playedu.common.service.LdapUserService;
import xyz.playedu.common.util.StringUtil;
import xyz.playedu.common.util.ldap.LdapTransformUser;
import java.util.Date;
/**
* @author tengyongzhi
* @description 针对表ldap_user的数据库操作Service实现
* @createDate 2023-08-31 11:59:27
*/
@Service
public class LdapUserServiceImpl extends ServiceImpl<LdapUserMapper, LdapUser>
implements LdapUserService {
@Override
public LdapUser findByUUID(String id) {
return getOne(query().getWrapper().eq("uuid", id));
}
@Override
public LdapUser store(LdapTransformUser ldapTransformUser) {
LdapUser user = new LdapUser();
user.setUuid(ldapTransformUser.getId());
user.setCn(ldapTransformUser.getCn());
user.setDn(ldapTransformUser.getDn());
user.setUid(ldapTransformUser.getUid());
// ou
user.setOu(String.join(",", ldapTransformUser.getOu()));
// 邮箱可能不存在
if (StringUtil.isNotEmpty(ldapTransformUser.getEmail())) {
user.setEmail(ldapTransformUser.getEmail());
}
user.setCreatedAt(new Date());
user.setUpdatedAt(new Date());
save(user);
return user;
}
@Override
public void updateUserId(Integer id, Integer userId) {
LdapUser user = new LdapUser();
user.setId(id);
user.setUserId(userId);
updateById(user);
}
@Override
public void updateCN(Integer id, String cn) {
LdapUser user = new LdapUser();
user.setId(id);
user.setCn(cn == null ? "" : cn);
updateById(user);
}
@Override
public void updateOU(Integer id, String newOU) {
LdapUser user = new LdapUser();
user.setId(id);
user.setOu(newOU == null ? "" : newOU);
updateById(user);
}
@Override
public void updateEmail(Integer id, String email) {
LdapUser user = new LdapUser();
user.setId(id);
user.setEmail(email == null ? "" : email);
updateById(user);
}
@Override
public void updateUid(Integer id, String uid) {
LdapUser user = new LdapUser();
user.setId(id);
user.setUid(uid);
updateById(user);
}
}

View File

@ -117,7 +117,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
user.setCreatedAt(new Date());
user.setUpdatedAt(new Date());
if (idCard != null && idCard.length() > 0) {
if (idCard != null && !idCard.isEmpty()) {
user.setVerifyAt(new Date());
user.setIsVerify(1);
}
@ -144,14 +144,14 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
newUser.setAvatar(avatar);
newUser.setIdCard(idCard);
if (password != null && password.length() > 0) {
if (password != null && !password.isEmpty()) {
newUser.setPassword(HelperUtil.MD5(password + user.getSalt()));
}
if (newUser.getName() != null
&& newUser.getName().length() > 0
&& !newUser.getName().isEmpty()
&& newUser.getIdCard() != null
&& newUser.getIdCard().length() > 0) {
&& !newUser.getIdCard().isEmpty()) {
if (user.getVerifyAt() == null) {
newUser.setIsVerify(1);
newUser.setVerifyAt(new Date());
@ -204,7 +204,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Override
public List<User> chunks(List<Integer> ids, List<String> fields) {
if (ids == null || ids.size() == 0) {
if (ids == null || ids.isEmpty()) {
return new ArrayList<>();
}
return list(query().getWrapper().in("id", ids).select(fields));
@ -212,7 +212,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Override
public List<User> chunks(List<Integer> ids) {
if (ids == null || ids.size() == 0) {
if (ids == null || ids.isEmpty()) {
return new ArrayList<>();
}
return list(query().getWrapper().in("id", ids));
@ -250,7 +250,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Override
public Map<Integer, List<Integer>> getDepIdsGroup(List<Integer> userIds) {
if (userIds == null || userIds.size() == 0) {
if (userIds == null || userIds.isEmpty()) {
return null;
}
Map<Integer, List<UserDepartment>> data =
@ -273,4 +273,25 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
user.setAvatar(avatar);
updateById(user);
}
@Override
public void updateName(Integer id, String cn) {
User user = new User();
user.setId(id);
user.setName(cn);
updateById(user);
}
@Override
public void updateDepId(Integer id, Integer[] depIds) {
userDepartmentService.resetStoreDepIds(id, depIds);
}
@Override
public void updateEmail(Integer id, String email) {
User user = new User();
user.setId(id);
user.setEmail(email);
updateById(user);
}
}

View File

@ -0,0 +1,35 @@
/*
* 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.common.util.ldap;
import lombok.Data;
import java.util.List;
@Data
public class LdapTransformUser {
private String id;
private String dn;
private String cn;
private List<String> ou;
private String email;
private String uid;
}

View File

@ -0,0 +1,229 @@
/*
* 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.common.util.ldap;
import lombok.extern.slf4j.Slf4j;
import xyz.playedu.common.exception.ServiceException;
import xyz.playedu.common.util.StringUtil;
import java.util.*;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
@Slf4j
public class LdapUtil {
private static final String USER_OBJECT_CLASS =
"(|(objectClass=person)(objectClass=posixAccount)(objectClass=inetOrgPerson)(objectClass=organizationalPerson))";
private static final String[] USER_RETURN_ATTRS =
new String[] {
"uid", // 用户的唯一识别符号全局唯一可以看做用户表的手机号此字段可用于配合密码直接登录
"cn", // CommonName -> 可以认作为人的名字比如张三在LDAP中此字段是可以重复的,但是同一ou下不可重复
"mail", // 邮箱此值不一定存在全局唯一可配合密码直接登录
"email", // 邮箱同上
"entryUUID",
};
private static final String[] OU_RETURN_ATTRS = new String[] {"ou"};
public static LdapContext initContext(String url, String adminUser, String adminPass)
throws NamingException {
Hashtable<String, String> context = new Hashtable<>();
context.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
context.put(Context.SECURITY_AUTHENTICATION, "simple");
// 服务地址
context.put(Context.PROVIDER_URL, url);
// 管理员账户和密码
context.put(Context.SECURITY_PRINCIPAL, adminUser);
context.put(Context.SECURITY_CREDENTIALS, adminPass);
return new InitialLdapContext(context, null);
}
public static List<HashMap<String, String>> users(LdapContext ldapContext, String baseDN)
throws NamingException {
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
controls.setReturningAttributes(USER_RETURN_ATTRS);
controls.setReturningObjFlag(true);
NamingEnumeration<SearchResult> result = null;
try {
result = ldapContext.search(baseDN, USER_OBJECT_CLASS, controls);
} catch (NamingException e) {
log.error("LDAP用户查询失败", e);
} finally {
closeContext(ldapContext);
}
if (result == null || !result.hasMoreElements()) {
log.info("LDAP服务中没有用户");
return null;
}
List<HashMap<String, String>> users = new ArrayList<>();
while (result.hasMoreElements()) {
SearchResult item = result.nextElement();
if (item == null) {
continue;
}
Attributes attributes = item.getAttributes();
log.info("name={},attributes={}", item.getName(), attributes);
}
return users;
}
public static List<String> departments(LdapContext ldapContext, String baseDN)
throws NamingException {
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
controls.setReturningAttributes(OU_RETURN_ATTRS);
controls.setReturningObjFlag(true);
String filter = "(objectClass=organizationalUnit)";
NamingEnumeration<SearchResult> result = null;
try {
result = ldapContext.search(baseDN, filter, controls);
} catch (NamingException e) {
log.error("LDAP部门查询失败", e);
} finally {
closeContext(ldapContext);
}
if (result == null || !result.hasMoreElements()) {
log.info("LDAP部门为空");
return null;
}
List<String> units = new ArrayList<>();
while (result.hasMoreElements()) {
SearchResult item = result.nextElement();
if (item == null) {
continue;
}
units.add(item.getName());
}
return units;
}
public static LdapTransformUser loginByMailOrUid(
String url,
String adminUser,
String adminPass,
String baseDN,
String mail,
String uid,
String password)
throws ServiceException, NamingException {
if (StringUtil.isEmpty(mail) && StringUtil.isEmpty(uid)) {
throw new ServiceException("mail和Uid不能同时为空");
}
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
controls.setReturningAttributes(USER_RETURN_ATTRS);
controls.setReturningObjFlag(true);
controls.setCountLimit(1);
String userFilter = "";
if (StringUtil.isNotEmpty(mail)) {
userFilter = String.format("(|(mail=%s)(email=%s))", mail, mail);
} else if (StringUtil.isNotEmpty(uid)) {
userFilter = String.format("(uid=%s)", uid);
}
String filter = String.format("(&%s%s)", userFilter, USER_OBJECT_CLASS);
LdapContext ldapContext = initContext(url, adminUser, adminPass);
NamingEnumeration<SearchResult> result = null;
try {
result = ldapContext.search(baseDN, filter, controls);
} catch (NamingException e) {
log.error("通过mail或uid登录失败", e);
} finally {
closeContext(ldapContext);
}
if (result == null || !result.hasMoreElements()) {
log.info("用户不存在");
return null;
}
// 根据mail或uid查询出来的用户
SearchResult item = result.nextElement();
Attributes attributes = item.getAttributes();
String email =
attributes.get("mail") == null ? null : (String) attributes.get("mail").get();
if (email == null) {
email = attributes.get("email") == null ? null : (String) attributes.get("email").get();
}
LdapTransformUser ldapUser = new LdapTransformUser();
ldapUser.setDn(item.getName());
ldapUser.setId((String) attributes.get("entryUUID").get());
ldapUser.setCn((String) attributes.get("cn").get());
ldapUser.setUid((String) attributes.get("uid").get());
ldapUser.setEmail(email);
// 使用用户dn+提交的密码去登录ldap系统
// 登录成功则意味着密码正确
// 登录失败则意味着密码错误
try {
ldapContext = initContext(url, ldapUser.getDn() + "," + baseDN, password);
log.info("LDAP登录成功");
} catch (Exception e) {
// 无法登录->密码错误
log.info("LDAP用户提交的密码错误");
return null;
} finally {
ldapContext.close();
}
// ou计算
String[] rdnList = ldapUser.getDn().split(",");
List<String> ou = new ArrayList<>();
for (String s : rdnList) {
if (StringUtil.startsWith(s, "ou=")) {
ou.add(s.replace("ou=", ""));
}
}
Collections.reverse(ou);
ldapUser.setOu(ou);
return ldapUser;
}
public static void closeContext(LdapContext ldapCtx) {
if (ldapCtx == null) {
return;
}
try {
ldapCtx.close();
} catch (NamingException e) {
log.error("Failed to close ldap context", e);
}
}
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.playedu.common.mapper.LdapUserMapper">
<resultMap id="BaseResultMap" type="xyz.playedu.common.domain.LdapUser">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="uuid" column="uuid" jdbcType="VARCHAR"/>
<result property="userId" column="user_id" jdbcType="INTEGER"/>
<result property="cn" column="cn" jdbcType="VARCHAR"/>
<result property="dn" column="dn" jdbcType="VARCHAR"/>
<result property="ou" column="ou" jdbcType="VARCHAR"/>
<result property="uid" column="uid" jdbcType="VARCHAR"/>
<result property="email" column="email" jdbcType="VARCHAR"/>
<result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
<result property="updatedAt" column="updated_at" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,uuid,user_id,
cn,dn,ou,
uid,email,created_at,
updated_at
</sql>
</mapper>

View File

@ -223,6 +223,70 @@ public class AppConfigCheck implements CommandLineRunner {
}
},
});
put(
"LDAP配置",
new AppConfig[] {
new AppConfig() {
{
setName("启用");
setSort(10);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_SWITCH);
setKeyName(ConfigConstant.LDAP_ENABLED);
setKeyValue("0");
setHelp("注意目前仅支持OpenLDAP服务");
}
},
new AppConfig() {
{
setName("服务地址");
setSort(20);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(ConfigConstant.LDAP_URL);
setKeyValue("");
setHelp("LDAP的对外服务地址。例如ldap.example.com");
}
},
new AppConfig() {
{
setName("用户名");
setSort(40);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(ConfigConstant.LDAP_ADMIN_USER);
setKeyValue("");
setHelp("用户登录到LDAP。例子cn=admin,dc=playedu,dc=xyz");
}
},
new AppConfig() {
{
setName("密码");
setSort(50);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(ConfigConstant.LDAP_ADMIN_PASS);
setKeyValue("");
setIsPrivate(1);
}
},
new AppConfig() {
{
setName("基本DN");
setSort(60);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(ConfigConstant.LDAP_BASE_DN);
setKeyValue("");
setHelp("从LDAP根节点搜索用户");
}
},
new AppConfig() {
{
setName("附件用户DN");
setSort(70);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(ConfigConstant.LDAP_USER_DN_PREFIX);
setKeyValue("");
setHelp("搜索用户时基于基础DN的搜索范围限制");
}
},
});
}
};

View File

@ -88,7 +88,7 @@ public class MigrationCheck implements CommandLineRunner {
`perm_id` int(10) unsigned NOT NULL DEFAULT '0',
KEY `role_id` (`role_id`),
KEY `perm_id` (`perm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -125,7 +125,7 @@ public class MigrationCheck implements CommandLineRunner {
`role_id` int(10) unsigned NOT NULL DEFAULT '0',
KEY `admin_id` (`admin_id`),
KEY `role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -198,7 +198,7 @@ public class MigrationCheck implements CommandLineRunner {
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -215,7 +215,7 @@ public class MigrationCheck implements CommandLineRunner {
`dep_id` int(11) NOT NULL DEFAULT '0',
KEY `course_id` (`course_id`),
KEY `dep_id` (`dep_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -239,7 +239,7 @@ public class MigrationCheck implements CommandLineRunner {
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `course_id` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -264,7 +264,7 @@ public class MigrationCheck implements CommandLineRunner {
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -285,7 +285,7 @@ public class MigrationCheck implements CommandLineRunner {
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -306,7 +306,7 @@ public class MigrationCheck implements CommandLineRunner {
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -323,7 +323,7 @@ public class MigrationCheck implements CommandLineRunner {
`rid` int(11) NOT NULL,
KEY `cid` (`cid`),
KEY `rid` (`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -340,7 +340,7 @@ public class MigrationCheck implements CommandLineRunner {
`category_id` int(11) NOT NULL DEFAULT '0',
KEY `course_id` (`course_id`),
KEY `category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -358,7 +358,7 @@ public class MigrationCheck implements CommandLineRunner {
`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;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -412,7 +412,7 @@ public class MigrationCheck implements CommandLineRunner {
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `u_h_c_id` (`user_id`,`hour_id`,`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -436,7 +436,7 @@ public class MigrationCheck implements CommandLineRunner {
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -453,7 +453,7 @@ public class MigrationCheck implements CommandLineRunner {
`dep_id` int(11) unsigned NOT NULL DEFAULT '0',
KEY `user_id` (`user_id`),
KEY `dep_id` (`dep_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -478,7 +478,7 @@ public class MigrationCheck implements CommandLineRunner {
`hour_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `u_d` (`user_id`,`created_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -497,7 +497,7 @@ public class MigrationCheck implements CommandLineRunner {
`created_date` date NOT NULL,
PRIMARY KEY (`id`),
KEY `u_d` (`user_id`,`created_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -524,7 +524,7 @@ public class MigrationCheck implements CommandLineRunner {
PRIMARY KEY (`id`),
UNIQUE KEY `jti` (`jti`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -548,7 +548,7 @@ public class MigrationCheck implements CommandLineRunner {
`name` varchar(255) NOT NULL DEFAULT '' COMMENT '文件名',
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -581,7 +581,7 @@ public class MigrationCheck implements CommandLineRunner {
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -635,7 +635,7 @@ public class MigrationCheck implements CommandLineRunner {
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `course_id` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -660,7 +660,7 @@ public class MigrationCheck implements CommandLineRunner {
`ip` varchar(45) NOT NULL DEFAULT '' COMMENT '下载ip',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
@ -676,6 +676,31 @@ public class MigrationCheck implements CommandLineRunner {
""");
}
});
add(
new HashMap<>() {
{
put("table", "ldap_user");
put("name", "20230831_11_51_17_ldap_user");
put(
"sql",
"""
CREATE TABLE `ldap_user` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(64) NOT NULL DEFAULT '' COMMENT '唯一特征值',
`user_id` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
`cn` varchar(120) NOT NULL DEFAULT '' COMMENT 'cn',
`dn` varchar(120) NOT NULL DEFAULT '' COMMENT 'dn',
`ou` varchar(255) NOT NULL DEFAULT '' COMMENT 'ou',
`uid` varchar(120) NOT NULL DEFAULT '' COMMENT 'uid',
`email` varchar(120) NOT NULL DEFAULT '' COMMENT '邮箱',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_uuid` (`uuid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
""");
}
});
}
};

View File

@ -49,12 +49,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@ -170,6 +164,7 @@
</dependencies>
<build>
<finalName>playedu-api</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>