diff --git a/.idea/GitCommitMessageStorage.xml b/.idea/GitCommitMessageStorage.xml new file mode 100644 index 0000000..e4fd56a --- /dev/null +++ b/.idea/GitCommitMessageStorage.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 0cc553f..4f81532 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -13,5 +13,17 @@ $ProjectFileDir$ + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://frp-air.top:43458 + + + + + + $ProjectFileDir$ + \ No newline at end of file diff --git a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserManagementController.java b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserManagementController.java new file mode 100644 index 0000000..fd400de --- /dev/null +++ b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserManagementController.java @@ -0,0 +1,46 @@ +package com.eshore.gringotts.web.domain.user.controller; + +import com.eshore.gringotts.web.configuration.amis.AmisListResponse; +import com.eshore.gringotts.web.configuration.amis.AmisResponse; +import com.eshore.gringotts.web.domain.user.service.UserService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 用户管理 + * + * @author lanyuanxiaoyao + * @date 2024-11-15 + */ +@RestController +@RequestMapping("/user_management") +public class UserManagementController { + private static final Logger logger = LoggerFactory.getLogger(UserManagementController.class); + + private final UserService userService; + + public UserManagementController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/list") + public AmisListResponse list() { + return AmisListResponse.responseListData(userService.list()); + } + + @GetMapping("/disable/{username}") + public AmisResponse disable(@PathVariable String username) { + userService.disable(username); + return AmisResponse.responseSuccess(); + } + + @GetMapping("/enable/{username}") + public AmisResponse enable(@PathVariable String username) { + userService.enable(username); + return AmisResponse.responseSuccess(); + } +} diff --git a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/entity/User.java b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/entity/User.java index 263a839..eaca485 100644 --- a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/entity/User.java +++ b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/entity/User.java @@ -79,5 +79,9 @@ public class User { * 数据监管方 */ CHECKER, + /** + * 系统管理员 + */ + ADMINISTRATOR, } } diff --git a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/repository/UserRepository.java b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/repository/UserRepository.java index dd89daa..811ab47 100644 --- a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/repository/UserRepository.java +++ b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/repository/UserRepository.java @@ -4,7 +4,10 @@ import com.blinkfox.fenix.jpa.FenixJpaRepository; import com.blinkfox.fenix.specification.FenixJpaSpecificationExecutor; import com.eshore.gringotts.web.domain.user.entity.User; import java.util.Optional; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; /** * User操作 @@ -16,4 +19,9 @@ import org.springframework.stereotype.Repository; public interface UserRepository extends FenixJpaRepository, FenixJpaSpecificationExecutor { Boolean existsByUsername(String username); Optional findByUsername(String username); + + @Transactional + @Modifying + @Query("update User u set u.state = ?2 where u.username = ?1") + void updateStateByUsername(String username, User.State state); } diff --git a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/service/UserService.java b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/service/UserService.java index 92bd833..b46384e 100644 --- a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/service/UserService.java +++ b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/service/UserService.java @@ -8,7 +8,11 @@ import cn.hutool.crypto.SecureUtil; import com.eshore.gringotts.web.domain.user.controller.UserController; import com.eshore.gringotts.web.domain.user.entity.User; import com.eshore.gringotts.web.domain.user.repository.UserRepository; +import java.time.LocalDateTime; +import java.util.List; import lombok.Data; +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.list.ImmutableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -68,6 +72,20 @@ public class UserService { return userRepository.existsByUsername(username); } + public void disable(String username) { + userRepository.updateStateByUsername(username, User.State.DISABLED); + } + + public void enable(String username) { + userRepository.updateStateByUsername(username, User.State.NORMAL); + } + + public ImmutableList list() { + List users = userRepository.findAll(); + return Lists.immutable.ofAll(users) + .collect(UserListItem::new); + } + private String encryptPassword(String password) { String salt = "tY2gNdkt7x%%HcCAFc"; return SecureUtil.sha256(StrUtil.format("{}{}", salt, password)); @@ -127,4 +145,23 @@ public class UserService { this.token = token.getTokenValue(); } } + + @Data + public static class UserListItem { + private Long id; + private String username; + private User.Role role; + private User.State state; + private LocalDateTime createTime; + private LocalDateTime updateTime; + + public UserListItem(User user) { + this.id = user.getId(); + this.username = user.getUsername(); + this.role = user.getRole(); + this.state = user.getState(); + this.createTime = user.getCreateTime(); + this.updateTime = user.getUpdateTime(); + } + } } diff --git a/gringotts-web/src/main/resources/static/assets/component/common.js b/gringotts-web/src/main/resources/static/assets/component/common.js index ca8a1cb..01b7c35 100644 --- a/gringotts-web/src/main/resources/static/assets/component/common.js +++ b/gringotts-web/src/main/resources/static/assets/component/common.js @@ -47,3 +47,120 @@ function paginationTemplate(perPage = 20, maxButtons = 5) { ], } } + +function mappingField(field, mapping) { + let mapData = { + '*': `\${${field}}`, + } + mapping.forEach(item => { + mapData[item['value']] = `${item['label']}` + }) + return { + type: 'mapping', + value: `\${${field}}`, + map: mapData, + } +} + +function mappingItem(label, value, color = 'bg-info') { + return { + label: label, + value: value, + color: color, + } +} + +const userRoleMapping = [ + mappingItem('数据提供方', 'PROVIDER', 'bg-blue-500'), + mappingItem('数据使用方', 'CUSTOMER', 'bg-purple-500'), + mappingItem('审核监管方', 'CHECKER', 'bg-cyan-500'), + mappingItem('系统管理员', 'ADMINISTRATOR', 'bg-green-500'), +] + +const userStateMapping = [ + mappingItem('审查中', 'CHECKING', 'bg-warning'), + mappingItem('正常', 'NORMAL', 'bg-success'), + mappingItem('禁用', 'DISABLED', 'bg-danger'), +] + +function userAddDialog() { + return { + actionType: 'dialog', + dialog: { + title: '新用户注册', + actions: [ + { + type: 'reset', + label: '清空', + }, + { + type: 'submit', + label: '注册', + level: 'primary', + } + ], + body: { + type: 'form', + api: '${base}/user/register', + mode: 'horizontal', + canAccessSuperData: false, + horizontal: { + left: 2, + }, + body: [ + { + type: 'input-email', + name: 'username', + label: '邮箱', + placeholder: '请输入邮箱', + required: true, + clearable: true, + clearValueOnEmpty: true, + validateApi: '${base}/user/exists_username/${username}' + }, + { + type: 'input-password', + name: 'password', + label: '密码', + placeholder: '请输入密码', + required: true, + clearable: true, + clearValueOnEmpty: true, + validations: { + matchRegexp: /^(?=.*\d)(?!.*(\d)\1{2})(?!.*(012|123|234|345|456|567|678|789|987|876|765|654|543|432|321|210))(?=.*[a-zA-Z])(?=.*[^\da-zA-Z\s]).{8,16}$/ + }, + validationErrors: { + matchRegexp: '密码至少包含字母、数字、特殊字符,8-16位,并且不能连续出现3个大小连续或相同的数字', + } + }, + { + type: 'input-password', + name: 'confirm-password', + label: '确认密码', + placeholder: '请再次输入密码', + required: true, + clearable: true, + validations: { + equalsField: 'password' + }, + validationErrors: { + equalsField: '两次输入密码不一致', + } + }, + { + type: 'radios', + name: 'role', + label: '角色', + required: true, + selectFirst: true, + options: [ + {label: '数据提供方', value: 'PROVIDER'}, + {label: '数据使用方', value: 'CUSTOMER'}, + {label: '审查监管方', value: 'CHECKER'}, + ] + }, + ] + } + } + } +} diff --git a/gringotts-web/src/main/resources/static/assets/component/pages/overview-tab.js b/gringotts-web/src/main/resources/static/assets/component/pages/overview-tab.js new file mode 100644 index 0000000..b6d6d74 --- /dev/null +++ b/gringotts-web/src/main/resources/static/assets/component/pages/overview-tab.js @@ -0,0 +1,9 @@ +function overviewTab() { + return { + title: '概览', + icon: 'fa fa-house', + body: [ + 'hello world' + ] + } +} \ No newline at end of file diff --git a/gringotts-web/src/main/resources/static/assets/component/pages/user-tab.js b/gringotts-web/src/main/resources/static/assets/component/pages/user-tab.js new file mode 100644 index 0000000..6ea0efd --- /dev/null +++ b/gringotts-web/src/main/resources/static/assets/component/pages/user-tab.js @@ -0,0 +1,92 @@ +function userTab() { + return { + title: '用户管理', + icon: 'fa fa-user', + body: [ + { + type: 'crud', + api: { + method: 'get', + url: '${base}/user_management/list' + }, + ...crudCommonOptions(), + headerToolbar: [ + 'reload', + { + type: 'action', + icon: 'fa fa-plus', + tooltip: '新增账号', + ...userAddDialog(), + } + ], + columns: [ + { + name: 'username', + label: '邮箱', + }, + { + label: '角色', + width: 120, + ...mappingField('role', userRoleMapping) + }, + { + label: '账号状态', + width: 80, + ...mappingField('state', userStateMapping) + }, + { + label: '创建时间', + width: 150, + type: 'tpl', + tpl: '${DATETOSTR(createTime)}' + }, + { + label: '更新时间', + width: 150, + type: 'tpl', + tpl: '${DATETOSTR(updateTime)}' + }, + { + label: '操作', + width: 100, + type: 'operation', + fixed: 'right', + className: 'nowrap', + buttons: [ + { + visibleOn: "${state === 'CHECKING'}", + label: '审核', + icon: 'fa fa-fingerprint', + level: 'primary', + size: 'xs', + }, + { + visibleOn: "${state === 'NORMAL'}", + label: '禁用', + icon: 'fa fa-ban', + level: 'danger', + size: 'xs', + confirmText: '确认禁用账号${username}?', + confirmTitle: '禁用账号', + actionType: 'ajax', + api: 'get:${base}/user_management/disable/${username}', + }, + + { + visibleOn: "${state === 'DISABLED'}", + label: '启用', + icon: 'fa fa-check', + level: 'success', + size: 'xs', + confirmText: '确认启用账号${username}?', + confirmTitle: '启用账号', + actionType: 'ajax', + api: 'get:${base}/user_management/enable/${username}', + } + ] + }, + ] + } + ] + } +} \ No newline at end of file diff --git a/gringotts-web/src/main/resources/static/index.html b/gringotts-web/src/main/resources/static/index.html index 82092f6..70cae04 100644 --- a/gringotts-web/src/main/resources/static/index.html +++ b/gringotts-web/src/main/resources/static/index.html @@ -25,43 +25,67 @@ + +