From 01ac5551d8d0142f300cb6795fad6932502baa5f Mon Sep 17 00:00:00 2001 From: zhu-mingye <934230207@qq.com> Date: Wed, 23 Aug 2023 10:22:45 +0800 Subject: [PATCH] [Feature] Added Login Page (#30) --- paimon-web-api/pom.xml | 1 + paimon-web-server/pom.xml | 5 + .../server/controller/LoginController.java | 6 +- .../web/server/data/result/enums/Status.java | 1 + .../user/UserNotBindTenantException.java} | 20 ++- .../paimon/web/server/data/vo/UserInfoVo.java | 47 +++++++ .../web/server/service/RoleMenuService.java | 27 ++++ .../web/server/service/SysMenuService.java | 4 +- .../web/server/service/TenantService.java | 27 ++++ .../web/server/service/UserRoleService.java | 33 +++++ .../web/server/service/UserService.java | 5 +- .../web/server/service/UserTenantService.java | 27 ++++ .../service/impl/RoleMenuServiceImpl.java | 32 +++++ .../service/impl/TenantServiceImpl.java | 31 ++++ .../service/impl/UserRoleServiceImpl.java | 42 ++++++ .../server/service/impl/UserServiceImpl.java | 75 +++++++++- .../service/impl/UserTenantServiceImpl.java | 32 +++++ .../src/main/resources/application-prod.yml | 6 +- .../src/main/resources/application.yml | 9 +- .../main/resources/i18n/messages.properties | 1 + .../resources/i18n/messages_en_US.properties | 1 + .../resources/i18n/messages_zh_CN.properties | 37 ++--- paimon-web-ui/package.json | 7 +- paimon-web-ui/src/api/endpoints.ts | 6 + paimon-web-ui/src/api/http.ts | 49 +++++-- paimon-web-ui/src/pages/Other/Login/index.tsx | 133 ++++++++++++++++++ paimon-web-ui/src/router/index.tsx | 9 ++ paimon-web-ui/src/types/Public/data.d.ts | 48 +++++++ paimon-web-ui/src/types/User/data.d.ts | 104 ++++++++++++++ pom.xml | 7 + 30 files changed, 778 insertions(+), 54 deletions(-) rename paimon-web-server/src/main/java/org/apache/paimon/web/server/data/{dto/UserDto.java => result/exception/user/UserNotBindTenantException.java} (64%) create mode 100644 paimon-web-server/src/main/java/org/apache/paimon/web/server/data/vo/UserInfoVo.java create mode 100644 paimon-web-server/src/main/java/org/apache/paimon/web/server/service/RoleMenuService.java create mode 100644 paimon-web-server/src/main/java/org/apache/paimon/web/server/service/TenantService.java create mode 100644 paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserRoleService.java create mode 100644 paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserTenantService.java create mode 100644 paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/RoleMenuServiceImpl.java create mode 100644 paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/TenantServiceImpl.java create mode 100644 paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserRoleServiceImpl.java create mode 100644 paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserTenantServiceImpl.java create mode 100644 paimon-web-ui/src/pages/Other/Login/index.tsx create mode 100644 paimon-web-ui/src/types/Public/data.d.ts create mode 100644 paimon-web-ui/src/types/User/data.d.ts diff --git a/paimon-web-api/pom.xml b/paimon-web-api/pom.xml index 24f8c1ce2..121820a69 100644 --- a/paimon-web-api/pom.xml +++ b/paimon-web-api/pom.xml @@ -28,6 +28,7 @@ under the License. paimon-web-api + Paimon : Web : Api 8 diff --git a/paimon-web-server/pom.xml b/paimon-web-server/pom.xml index ca9b0acad..3c4fa57d1 100644 --- a/paimon-web-server/pom.xml +++ b/paimon-web-server/pom.xml @@ -51,6 +51,11 @@ under the License. ${project.version} + + cn.hutool + hutool-all + + org.springframework.boot spring-boot-configuration-processor diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/LoginController.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/LoginController.java index b209464e2..c9ff1286c 100644 --- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/LoginController.java +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/LoginController.java @@ -20,13 +20,13 @@ import org.apache.paimon.web.server.data.dto.LoginDto; import org.apache.paimon.web.server.data.result.R; +import org.apache.paimon.web.server.data.vo.UserInfoVo; import org.apache.paimon.web.server.service.UserService; import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -48,7 +48,7 @@ public class LoginController { * @return token string */ @PostMapping("/login") - public R login(@Validated @RequestBody LoginDto loginDto) { + public R login(@RequestBody LoginDto loginDto) { return R.succeed(userService.login(loginDto)); } @@ -65,7 +65,7 @@ public R tokenInfo() { /** logout. */ @PostMapping("/logout") public R logout() { - StpUtil.logout(); + StpUtil.logout(StpUtil.getLoginIdAsInt()); return R.succeed(); } } diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java index 27283bae4..9f1209fab 100644 --- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java @@ -43,6 +43,7 @@ public enum Status { USER_NOT_EXIST(10001, "user.not.exist"), USER_PASSWORD_ERROR(10002, "user.password.error"), USER_DISABLED_ERROR(10003, "user.is.disabled"), + USER_NOT_BING_TENANT(10004, "user.not.bing.tenant"), /** ------------role-----------------. */ ROLE_IN_USED(10101, "role.in.used"), ROLE_NAME_IS_EXIST(10102, "role.name.exist"), diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/dto/UserDto.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/exception/user/UserNotBindTenantException.java similarity index 64% rename from paimon-web-server/src/main/java/org/apache/paimon/web/server/data/dto/UserDto.java rename to paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/exception/user/UserNotBindTenantException.java index fdcd279e7..8cc858d32 100644 --- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/dto/UserDto.java +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/exception/user/UserNotBindTenantException.java @@ -16,18 +16,16 @@ * limitations under the License. */ -package org.apache.paimon.web.server.data.dto; +package org.apache.paimon.web.server.data.result.exception.user; -import org.apache.paimon.web.server.data.model.Tenant; -import org.apache.paimon.web.server.data.model.User; +import org.apache.paimon.web.server.data.result.enums.Status; +import org.apache.paimon.web.server.data.result.exception.BaseException; -import lombok.Data; +/** Exception to user not bind tenant. */ +public class UserNotBindTenantException extends BaseException { + private static final long serialVersionUID = 1L; -import java.util.List; - -/** user data transfer object. */ -@Data -public class UserDto { - private User user; - private List tenantList; + public UserNotBindTenantException() { + super(Status.USER_DISABLED_ERROR); + } } diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/vo/UserInfoVo.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/vo/UserInfoVo.java new file mode 100644 index 000000000..fdd08e20d --- /dev/null +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/vo/UserInfoVo.java @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.paimon.web.server.data.vo; + +import org.apache.paimon.web.server.data.model.SysMenu; +import org.apache.paimon.web.server.data.model.SysRole; +import org.apache.paimon.web.server.data.model.Tenant; +import org.apache.paimon.web.server.data.model.User; + +import cn.dev33.satoken.stp.SaTokenInfo; +import lombok.Data; + +import java.util.List; + +/** user data transfer object. */ +@Data +public class UserInfoVo { + /** current user info. */ + private User user; + /** current user's tenant list. */ + private List tenantList; + /** current user's role list. */ + private List roleList; + /** current user's token info. */ + private SaTokenInfo saTokenInfo; + /** current user's menu list. */ + private List sysMenuList; + /** current user's tenant. */ + private Tenant currentTenant; +} diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/RoleMenuService.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/RoleMenuService.java new file mode 100644 index 000000000..93cd92d24 --- /dev/null +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/RoleMenuService.java @@ -0,0 +1,27 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.paimon.web.server.service; + +import org.apache.paimon.web.server.data.model.RoleMenu; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** RoleMenu Service. */ +public interface RoleMenuService extends IService {} diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/SysMenuService.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/SysMenuService.java index c75590b5c..4bc9072ae 100644 --- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/SysMenuService.java +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/SysMenuService.java @@ -22,11 +22,13 @@ import org.apache.paimon.web.server.data.tree.TreeSelect; import org.apache.paimon.web.server.data.vo.RouterVo; +import com.baomidou.mybatisplus.extension.service.IService; + import java.util.List; import java.util.Set; /** Menu service. */ -public interface SysMenuService { +public interface SysMenuService extends IService { /** * Query menu list by user. * diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/TenantService.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/TenantService.java new file mode 100644 index 000000000..8fe26dbde --- /dev/null +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/TenantService.java @@ -0,0 +1,27 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.paimon.web.server.service; + +import org.apache.paimon.web.server.data.model.Tenant; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** Tenant Service. */ +public interface TenantService extends IService {} diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserRoleService.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserRoleService.java new file mode 100644 index 000000000..f51bd628a --- /dev/null +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserRoleService.java @@ -0,0 +1,33 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.paimon.web.server.service; + +import org.apache.paimon.web.server.data.model.User; +import org.apache.paimon.web.server.data.model.UserRole; + +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** UserRole Service. */ +public interface UserRoleService extends IService { + + List selectUserRoleListByUserId(User user); +} diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserService.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserService.java index c6ed35e7b..04ef546b7 100644 --- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserService.java +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserService.java @@ -21,6 +21,7 @@ import org.apache.paimon.web.server.data.dto.LoginDto; import org.apache.paimon.web.server.data.model.User; import org.apache.paimon.web.server.data.result.exception.BaseException; +import org.apache.paimon.web.server.data.vo.UserInfoVo; import com.baomidou.mybatisplus.extension.service.IService; @@ -32,10 +33,10 @@ public interface UserService extends IService { /** * login by username and password. * - * @param loginDto login info + * @param loginDto login params * @return {@link String} */ - String login(LoginDto loginDto) throws BaseException; + UserInfoVo login(LoginDto loginDto) throws BaseException; /** * Query the list of assigned user roles. diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserTenantService.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserTenantService.java new file mode 100644 index 000000000..f55bbaa00 --- /dev/null +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/UserTenantService.java @@ -0,0 +1,27 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.paimon.web.server.service; + +import org.apache.paimon.web.server.data.model.UserTenant; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** Tenant Service. */ +public interface UserTenantService extends IService {} diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/RoleMenuServiceImpl.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/RoleMenuServiceImpl.java new file mode 100644 index 000000000..0ead0955c --- /dev/null +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/RoleMenuServiceImpl.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.paimon.web.server.service.impl; + +import org.apache.paimon.web.server.data.model.RoleMenu; +import org.apache.paimon.web.server.mapper.RoleMenuMapper; +import org.apache.paimon.web.server.service.RoleMenuService; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** RoleMenuServiceImpl. */ +@Service +public class RoleMenuServiceImpl extends ServiceImpl + implements RoleMenuService {} diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/TenantServiceImpl.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/TenantServiceImpl.java new file mode 100644 index 000000000..a2e06bdca --- /dev/null +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/TenantServiceImpl.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.paimon.web.server.service.impl; + +import org.apache.paimon.web.server.data.model.Tenant; +import org.apache.paimon.web.server.mapper.TenantMapper; +import org.apache.paimon.web.server.service.TenantService; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** TenantServiceImpl. */ +@Service +public class TenantServiceImpl extends ServiceImpl implements TenantService {} diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserRoleServiceImpl.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserRoleServiceImpl.java new file mode 100644 index 000000000..6f6ca1f04 --- /dev/null +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserRoleServiceImpl.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.paimon.web.server.service.impl; + +import org.apache.paimon.web.server.data.model.User; +import org.apache.paimon.web.server.data.model.UserRole; +import org.apache.paimon.web.server.mapper.UserRoleMapper; +import org.apache.paimon.web.server.service.UserRoleService; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** UserRoleServiceImpl. */ +@Service +public class UserRoleServiceImpl extends ServiceImpl + implements UserRoleService { + + @Override + public List selectUserRoleListByUserId(User user) { + return list(new LambdaQueryWrapper().eq(UserRole::getUserId, user.getId())); + } +} diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserServiceImpl.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserServiceImpl.java index 5d66c7f33..6cae55790 100644 --- a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserServiceImpl.java +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserServiceImpl.java @@ -20,20 +20,35 @@ import org.apache.paimon.web.server.data.dto.LoginDto; import org.apache.paimon.web.server.data.enums.UserType; +import org.apache.paimon.web.server.data.model.RoleMenu; +import org.apache.paimon.web.server.data.model.SysMenu; +import org.apache.paimon.web.server.data.model.SysRole; import org.apache.paimon.web.server.data.model.User; +import org.apache.paimon.web.server.data.model.UserRole; import org.apache.paimon.web.server.data.result.exception.BaseException; import org.apache.paimon.web.server.data.result.exception.user.UserDisabledException; +import org.apache.paimon.web.server.data.result.exception.user.UserNotBindTenantException; import org.apache.paimon.web.server.data.result.exception.user.UserNotExistsException; import org.apache.paimon.web.server.data.result.exception.user.UserPasswordNotMatchException; +import org.apache.paimon.web.server.data.vo.UserInfoVo; import org.apache.paimon.web.server.mapper.UserMapper; import org.apache.paimon.web.server.service.LdapService; +import org.apache.paimon.web.server.service.RoleMenuService; +import org.apache.paimon.web.server.service.SysMenuService; +import org.apache.paimon.web.server.service.SysRoleService; +import org.apache.paimon.web.server.service.TenantService; +import org.apache.paimon.web.server.service.UserRoleService; import org.apache.paimon.web.server.service.UserService; +import cn.dev33.satoken.secure.SaSecureUtil; import cn.dev33.satoken.stp.StpUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -43,6 +58,11 @@ public class UserServiceImpl extends ServiceImpl implements Us @Autowired private LdapService ldapService; @Autowired private UserMapper userMapper; + @Autowired private UserRoleService userRoleService; + @Autowired private SysRoleService sysRoleService; + @Autowired private RoleMenuService roleMenuService; + @Autowired private SysMenuService sysMenuService; + @Autowired private TenantService tenantService; /** * login by username and password. @@ -51,7 +71,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * @return {@link String} */ @Override - public String login(LoginDto loginDto) throws BaseException { + public UserInfoVo login(LoginDto loginDto) throws BaseException { String username = loginDto.getUsername(); String password = loginDto.getPassword(); @@ -62,10 +82,55 @@ public String login(LoginDto loginDto) throws BaseException { if (!user.getEnabled()) { throw new UserDisabledException(); } + // query user info + UserInfoVo userInfoVo = getUserInfoVo(user); + if (CollectionUtils.isEmpty(userInfoVo.getTenantList())) { + throw new UserNotBindTenantException(); + } StpUtil.login(user.getId(), loginDto.isRememberMe()); - return StpUtil.getTokenValue(); + return userInfoVo; + } + + /** + * get user info. include user, role, menu. tenant. + * + * @param user user + * @return {@link UserInfoVo} + */ + private UserInfoVo getUserInfoVo(User user) { + UserInfoVo userInfoVo = new UserInfoVo(); + userInfoVo.setUser(user); + userInfoVo.setSaTokenInfo(StpUtil.getTokenInfo()); + + // get user role list + List sysRoles = new ArrayList<>(); + List userRoleList = userRoleService.selectUserRoleListByUserId(user); + + // get role list + userRoleList.forEach( + userRole -> { + sysRoles.add(sysRoleService.getById(userRole.getRoleId())); + }); + userInfoVo.setRoleList(sysRoles); + // get menu list + List sysMenus = new ArrayList<>(); + userRoleList.forEach( + userRole -> { + roleMenuService + .list( + new LambdaQueryWrapper() + .eq(RoleMenu::getRoleId, userRole.getRoleId())) + .forEach( + roleMenu -> { + sysMenus.add(sysMenuService.getById(roleMenu.getMenuId())); + }); + }); + userInfoVo.setSysMenuList(sysMenus); + + userInfoVo.setCurrentTenant(tenantService.getById(1)); + return userInfoVo; } private User localLogin(String username, String password) throws BaseException { @@ -77,10 +142,12 @@ private User localLogin(String username, String password) throws BaseException { if (user == null) { throw new UserNotExistsException(); } - if (!user.getPassword().equals(password)) { + + if (SaSecureUtil.md5(password).equals(user.getPassword())) { + return user; + } else { throw new UserPasswordNotMatchException(); } - return user; } private User ldapLogin(String username, String password) throws BaseException { diff --git a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserTenantServiceImpl.java b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserTenantServiceImpl.java new file mode 100644 index 000000000..ccf7c0c72 --- /dev/null +++ b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/UserTenantServiceImpl.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.paimon.web.server.service.impl; + +import org.apache.paimon.web.server.data.model.UserTenant; +import org.apache.paimon.web.server.mapper.UserTenantMapper; +import org.apache.paimon.web.server.service.UserTenantService; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** UserTenantServiceImpl. */ +@Service +public class UserTenantServiceImpl extends ServiceImpl + implements UserTenantService {} diff --git a/paimon-web-server/src/main/resources/application-prod.yml b/paimon-web-server/src/main/resources/application-prod.yml index 9e87e233b..133797283 100644 --- a/paimon-web-server/src/main/resources/application-prod.yml +++ b/paimon-web-server/src/main/resources/application-prod.yml @@ -15,7 +15,7 @@ spring: datasource: - url: jdbc:mysql://${MYSQL_ADDR:localhost:3306}/${MYSQL_DATABASE:paimon}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true - username: ${MYSQL_USERNAME:paimon} - password: ${MYSQL_PASSWORD:paimon@123} + url: jdbc:mysql://${MYSQL_ADDR:124.221.249.188:3887}/${MYSQL_DATABASE:paimon}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: ${MYSQL_USERNAME:root} + password: ${MYSQL_PASSWORD:Zhumingye520!@#.} driver-class-name: com.mysql.cj.jdbc.Driver \ No newline at end of file diff --git a/paimon-web-server/src/main/resources/application.yml b/paimon-web-server/src/main/resources/application.yml index 8bd64dc37..688ffc3ff 100644 --- a/paimon-web-server/src/main/resources/application.yml +++ b/paimon-web-server/src/main/resources/application.yml @@ -22,7 +22,7 @@ spring: application: name: Paimon Web UI profiles: - active: dev + active: dev messages: basename: i18n/messages encoding: UTF-8 @@ -59,4 +59,9 @@ sa-token: is-concurrent: true is-share: true token-style: uuid - is-log: false \ No newline at end of file + is-log: false + is-print: false + is-read-cookie: true + is-write-header: true + is-read-header: true + is-read-body: true \ No newline at end of file diff --git a/paimon-web-server/src/main/resources/i18n/messages.properties b/paimon-web-server/src/main/resources/i18n/messages.properties index 957a9fc2d..7c16a949f 100644 --- a/paimon-web-server/src/main/resources/i18n/messages.properties +++ b/paimon-web-server/src/main/resources/i18n/messages.properties @@ -26,6 +26,7 @@ internal.server.error=Internal Server Error:{0} user.not.exist=User Not Exist user.password.error=User Password Error user.is.disabled=User Is Disabled +user.not.bing.tenant=User Not Bing Tenant role.in.used=This role {0} is in used role.name.exist=This role name {0} is exist role.key.exist=This role key {0} is exist diff --git a/paimon-web-server/src/main/resources/i18n/messages_en_US.properties b/paimon-web-server/src/main/resources/i18n/messages_en_US.properties index df1cb11b1..f48cf1829 100644 --- a/paimon-web-server/src/main/resources/i18n/messages_en_US.properties +++ b/paimon-web-server/src/main/resources/i18n/messages_en_US.properties @@ -26,6 +26,7 @@ internal.server.error=Internal Server Error:{0} user.not.exist=User Not Exist user.password.error=User Password Error user.is.disabled=User Is Disabled +user.not.bing.tenant=User Not Bing Tenant role.in.used=This role {0} is in used role.name.exist=This role name {0} is exist role.key.exist=This role key {0} is exist diff --git a/paimon-web-server/src/main/resources/i18n/messages_zh_CN.properties b/paimon-web-server/src/main/resources/i18n/messages_zh_CN.properties index 98aa25531..6ceb2d805 100644 --- a/paimon-web-server/src/main/resources/i18n/messages_zh_CN.properties +++ b/paimon-web-server/src/main/resources/i18n/messages_zh_CN.properties @@ -14,21 +14,22 @@ # See the License for the specific language governing permissions and # limitations under the License. # -successfully=请求成功 -failed=请求失败 -unauthorized=用户未登陆 -forbidden=用户无权限 -unknown.error=未知错误:{0} -method.not.allowed=请求方法不支持 -invalid.request.parameter=请求参数 {0} 无效 -request.parameter.error=请求参数错误:{0} -internal.server.error=服务器内部错误:{0} -user.not.exist=用户不存在 -user.password.error=密码错误 -user.is.disabled=用户已禁用 -role.in.used=此角色{0}正在使用中 -role.name.exist=此角色名称{0}已使用 -role.key.exist=此角色关键字{0}已使用 -menu.in.used=此菜单正在使用中 -menu.name.exist=此菜单名称已存在:{0} -menu.path.invalid=此菜单路径无效:{0} +successfully=\u8BF7\u6C42\u6210\u529F +failed=\u8BF7\u6C42\u5931\u8D25 +unauthorized=\u7528\u6237\u672A\u767B\u9646 +forbidden=\u7528\u6237\u65E0\u6743\u9650 +unknown.error=\u672A\u77E5\u9519\u8BEF:{0} +method.not.allowed=\u8BF7\u6C42\u65B9\u6CD5\u4E0D\u652F\u6301 +invalid.request.parameter=\u8BF7\u6C42\u53C2\u6570 {0} \u65E0\u6548 +request.parameter.error=\u8BF7\u6C42\u53C2\u6570\u9519\u8BEF:{0} +internal.server.error=\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF:{0} +user.not.exist=\u7528\u6237\u4E0D\u5B58\u5728 +user.password.error=\u5BC6\u7801\u9519\u8BEF +user.is.disabled=\u7528\u6237\u5DF2\u7981\u7528 +user.not.bing.tenant=\u7528\u6237\u672A\u7ED1\u5B9A\u79DF\u6237 +role.in.used=\u6B64\u89D2\u8272{0}\u6B63\u5728\u4F7F\u7528\u4E2D +role.name.exist=\u6B64\u89D2\u8272\u540D\u79F0{0}\u5DF2\u4F7F\u7528 +role.key.exist=\u6B64\u89D2\u8272\u5173\u952E\u5B57{0}\u5DF2\u4F7F\u7528 +menu.in.used=\u6B64\u83DC\u5355\u6B63\u5728\u4F7F\u7528\u4E2D +menu.name.exist=\u6B64\u83DC\u5355\u540D\u79F0\u5DF2\u5B58\u5728:{0} +menu.path.invalid=\u6B64\u83DC\u5355\u8DEF\u5F84\u65E0\u6548:{0} diff --git a/paimon-web-ui/package.json b/paimon-web-ui/package.json index 5916f82f2..2faecbbb6 100644 --- a/paimon-web-ui/package.json +++ b/paimon-web-ui/package.json @@ -21,6 +21,8 @@ "i18next": "^23.4.4", "i18next-browser-languagedetector": "^7.1.0", "i18next-http-backend": "^2.2.1", + "antd": "^5.8.3", + "@ant-design/pro-components": "^2.6.3", "less": "^4.1.3", "monaco-editor": "^0.40.0", "react": "^18.2.0", @@ -28,7 +30,10 @@ "react-dom": "^18.2.0", "react-icons": "^4.10.1", "react-i18next": "^13.1.0", - "zustand": "^4.4.1" + "zustand": "^4.4.1", + "styled-components": "^6.0.0-rc.3", + "vite-plugin-semi-theme": "^0.5.0", + "screenfull": "^6.0.2" }, "devDependencies": { "@types/node": "^20.3.2", diff --git a/paimon-web-ui/src/api/endpoints.ts b/paimon-web-ui/src/api/endpoints.ts index dc7a79f3c..15675811a 100644 --- a/paimon-web-ui/src/api/endpoints.ts +++ b/paimon-web-ui/src/api/endpoints.ts @@ -16,8 +16,14 @@ specific language governing permissions and limitations under the License. */ export const API_ENDPOINTS = { + + // auth && login + GET_LDAP_ENABLE: '/ldap/enable', + LOGIN: '/login', + // catalog CREATE_FILE_SYSTEM_CATALOG: '/catalog/createFilesystemCatalog', CREATE_HIVE_CATALOG: '/catalog/createHiveCatalog', GET_ALL_CATALOGS: '/catalog/getAllCatalogs', + }; \ No newline at end of file diff --git a/paimon-web-ui/src/api/http.ts b/paimon-web-ui/src/api/http.ts index e1364c6e2..e46d6634d 100644 --- a/paimon-web-ui/src/api/http.ts +++ b/paimon-web-ui/src/api/http.ts @@ -16,6 +16,9 @@ specific language governing permissions and limitations under the License. */ import axios, { AxiosResponse, AxiosError } from 'axios'; +import { Notification } from '@douyinfe/semi-ui'; +import {UserState} from "@src/types/User/data"; +import {Result} from "@src/types/Public/data"; const httpClient = axios.create({ baseURL: '/api', @@ -29,10 +32,13 @@ const httpClient = axios.create({ // request interceptor. httpClient.interceptors.request.use( (config: any) => { + + console.log(localStorage.getItem('token')) + // Here you can set request headers like:config.headers['Token'] = localStorage.getItem('Token'). - /* config.headers = { - "Authorization": store.getState().user.token - }*/ + config.headers = { + "Authorization": localStorage.getItem('token') + } return config; }, (error: AxiosError) => { @@ -45,7 +51,24 @@ httpClient.interceptors.request.use( // response interceptor. httpClient.interceptors.response.use( (response: AxiosResponse) => { + // Here you can process the response data. + const { data: reponseData , config: reponseConfig} = response; + + // 处理 config 获取 baseUrl 和 url 拼接 判断是登录接口 , 如果是 拿到相应的数据 存储到 localStorage 中 并设置到请求头中 + if (reponseConfig.url === '/login') { + // todo: has a bug + const {data} = reponseData as Result + console.log('登录接口', data) + //todo: use store + localStorage.setItem('token', data.tokenInfo.tokenValue) + } + + // Handle response data + Notification.error({ + title: 'Error', + content: `${reponseData.msg}` + }); return response; }, (error: AxiosError) => { @@ -55,27 +78,35 @@ httpClient.interceptors.response.use( } ); -const httpGet = async (url: string, param?: E): Promise => { +const httpGet = async (url: string, param?: E, beforeCallBack? : () => void , afterCallBack? : () => void): Promise => { + beforeCallBack && beforeCallBack() const {data} = await httpClient.get(url, {params: param}) + afterCallBack && afterCallBack() return data } -const httpPost = async (url: string, param: E): Promise => { - const {data} = await httpClient.post(url, param) +const httpPost = async (url: string, body: E, beforeCallBack? : () => void , afterCallBack? : () => void): Promise => { + beforeCallBack && beforeCallBack() + const {data} = await httpClient.post(url, body) + afterCallBack && afterCallBack() return data } -const httpDelete = async (url: string, param: E): Promise => { +const httpDelete = async (url: string, param: E ,beforeCallBack? : () => void , afterCallBack? : () => void): Promise => { + beforeCallBack && beforeCallBack() const {data} = await httpClient.delete(url, {params: param}) + afterCallBack && afterCallBack() return data } -const httpFormPost = async (url: string, params?: E): Promise => { - const {data} = await httpClient.post(url, params, { +const httpFormPost = async (url: string, body?: E , beforeCallBack? : () => void , afterCallBack? : () => void): Promise => { + beforeCallBack && beforeCallBack() + const {data} = await httpClient.post(url, body, { headers: { 'Content-Type': 'multipart/form-data' } }) + afterCallBack && afterCallBack() return data } diff --git a/paimon-web-ui/src/pages/Other/Login/index.tsx b/paimon-web-ui/src/pages/Other/Login/index.tsx new file mode 100644 index 000000000..306d116db --- /dev/null +++ b/paimon-web-ui/src/pages/Other/Login/index.tsx @@ -0,0 +1,133 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +import {useEffect, useState} from "react"; +import {Card,} from '@douyinfe/semi-ui'; +import styled from "styled-components"; +import {useForm} from "antd/es/form/Form"; +import {ProForm, ProFormCheckbox, ProFormText, SubmitterProps} from "@ant-design/pro-components"; +import http from "@api/http.ts"; +import {API_ENDPOINTS} from "@api/endpoints.ts"; +import {Col, Row} from "antd"; +import "@douyinfe/semi-foundation/lib/es/button/button.css?inline" +import {LoginParams} from "@src/types/User/data"; + + +const Login = () => { + // const dispatch = useDispatch(); + // const navigate = useNavigate(); + + const [ldapEnabled, setLdapEnabled] = useState(false); + const [submitting, setSubmitting] = useState(false); + + const [form] = useForm(); + + + useEffect(() => { + // todo: get ldap enabled status , the backend code is not ready yet + // http.httpGet(API_ENDPOINTS.GET_LDAP_ENABLE).then(res => { + // setLdapEnabled(res.data); + // form.setFieldValue("ldapLogin",res.data) + // }, err => console.error(err)) + }, []); + + + const handleClickLogin = async () => { + setSubmitting(true); + const values = await form.validateFields(); + await http.httpPost(API_ENDPOINTS.LOGIN, {...values}, () => setSubmitting(true), () => setSubmitting(false)) + }; + + const proFormSubmitter: SubmitterProps = { + searchConfig: {submitText: 'Login'}, + resetButtonProps: false, + submitButtonProps: { + className: "semi-button-primary", + type: 'primary', + loading: submitting, + autoFocus: true, + htmlType: 'submit', + size: "large", + shape: "round", + style: { + width: '100%', + } + }, + }; + + + return + +

Paimon Manager

+ + + + + + + {/*{l("login.rememberMe")}*/} Remember Me + + + + + + + +
+
+} + +export default Login; + +const Container = styled.div` + background: url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr') no-repeat; + background-size: 100% 100%; + height: 100vh; + overflow: hidden; + width: 100vw; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-content: center; + justify-content: center; + align-items: center; + h3 { + font-size: 24px; + font-weight: 600; + text-align: center; + margin: 10px auto 20px; + } +` \ No newline at end of file diff --git a/paimon-web-ui/src/router/index.tsx b/paimon-web-ui/src/router/index.tsx index f48163883..3a9af4765 100644 --- a/paimon-web-ui/src/router/index.tsx +++ b/paimon-web-ui/src/router/index.tsx @@ -22,6 +22,7 @@ import LayoutPage from '@src/pages/Layout'; import PlaygroundPage from '@src/pages/Playground'; import MetaDataPage from '@pages/Metadata'; import DevStatus from "@pages/Abnormal/Dev"; +import Login from "@pages/Other/Login"; /*const Editor = lazy(() => import('@src/pages/Playground')) const Studio = lazy(() => import('@src/pages/Metadata'))*/ @@ -29,6 +30,10 @@ const Studio = lazy(() => import('@src/pages/Metadata'))*/ const routeList: RouteObject[] = [ { path: '/', + element: , + }, + { + path: '/layout', element: , children: [ { @@ -44,6 +49,10 @@ const routeList: RouteObject[] = [ element: } ] + }, + { + path: '*', + element: } ] diff --git a/paimon-web-ui/src/types/Public/data.d.ts b/paimon-web-ui/src/types/Public/data.d.ts new file mode 100644 index 000000000..accfa5330 --- /dev/null +++ b/paimon-web-ui/src/types/Public/data.d.ts @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +export interface Result { + code: number; + msg: string; + data: T; +} + +export interface PageData { + records: T[]; + page: number; + size: number; + total: number; +} + +// with page +export type PageResult = Result> + + + + +export interface BaseBeanColumns extends ExcludeNameAndEnableColumns { + name: string, + enabled: boolean, +} + +export interface ExcludeNameAndEnableColumns { + id: number, + createTime: Date, + updateTime: Date, +} diff --git a/paimon-web-ui/src/types/User/data.d.ts b/paimon-web-ui/src/types/User/data.d.ts new file mode 100644 index 000000000..1debcc2f2 --- /dev/null +++ b/paimon-web-ui/src/types/User/data.d.ts @@ -0,0 +1,104 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +import {BaseBeanColumns, ExcludeNameAndEnableColumns} from "@src/types/Public/data"; + +/** + * user login params + */ +interface LoginParams { + username: string; + password: string; + rememberMe: boolean; + ldapLogin: boolean; +} + +/** + * if login success, return user info and token + */ +export interface UserState { + status: boolean; + tokenInfo: SaTokenInfo; + roleList: Role[]; + tenantList: Tenant[]; + menuList: SysMenu[]; + user: User; +} + + +/** + * user info + */ +export interface User extends ExcludeNameAndEnableColumns { + username: string; + nickname?: string; + password?: string; + avatar?: string; + worknum?: string; + userType: number; + mobile?: string; + enabled: boolean; + isDelete: boolean; +} + + +export interface SaTokenInfo { + tokenName: string; + tokenValue: string; + isLogin: boolean; + loginId: number; + loginType: string; + tokenTimeout: number; + sessionTimeout: number; + tokenSessionTimeout: number; + tokenActivityTimeout: number; + loginDevice: string; + tag: string; +} + + +export interface Tenant extends ExcludeNameAndEnableColumns { + tenantCode: string; + isDelete: boolean; + note?: string; +} + +export interface Role extends ExcludeNameAndEnableColumns { + tenantId: number; + tenant: Tenant; + roleCode?: string; + roleName?: string; + isDelete: boolean; + note?: string; +} + + +export interface SysMenu extends BaseBeanColumns { + parentId: number, // 父级 + orderNum: number, // 排序 + path: string, // 路由 + component: string, // 组件 + type: string,// C菜单 F按钮 M目录 + display: boolean, // 菜单状态(0显示 1隐藏) + perms: string, // 权限标识 + icon: string, // 图标 + rootMenu: boolean, + note: string, + children: SysMenu[], +} diff --git a/pom.xml b/pom.xml index 028091e6f..93f1464c4 100644 --- a/pom.xml +++ b/pom.xml @@ -112,6 +112,7 @@ under the License. 3.5.3.1 1.35.0.RC 3.12.0 + 5.8.11
@@ -143,6 +144,12 @@ under the License. ${guava.version} + + cn.hutool + hutool-all + ${hutool.version} + + org.slf4j slf4j-api