feat: add user management workflows

This commit is contained in:
Codex
2026-03-19 10:06:43 +08:00
parent 64e61454ab
commit 897cee385d
14 changed files with 547 additions and 39 deletions

View File

@@ -5,6 +5,7 @@ Innovation Platform is a Spring Boot based demo for university innovation and en
## What is included ## What is included
- Authentication endpoints powered by Sa-Token - Authentication endpoints powered by Sa-Token
- User management APIs for admin and profile workflows
- Project CRUD APIs with pagination - Project CRUD APIs with pagination
- Project dashboard statistics endpoint - Project dashboard statistics endpoint
- Static admin dashboard prototype at `backend/src/main/resources/static/index.html` - Static admin dashboard prototype at `backend/src/main/resources/static/index.html`
@@ -21,6 +22,14 @@ Innovation Platform is a Spring Boot based demo for university innovation and en
- `POST /api/auth/login` - `POST /api/auth/login`
- `POST /api/auth/register` - `POST /api/auth/register`
- `POST /api/auth/logout` - `POST /api/auth/logout`
- `GET /api/users`
- `GET /api/users/{id}`
- `POST /api/users`
- `PUT /api/users/{id}`
- `DELETE /api/users/{id}`
- `GET /api/users/teachers`
- `POST /api/users/change-password`
- `PUT /api/users/profile`
- `GET /api/projects` - `GET /api/projects`
- `GET /api/projects/stats` - `GET /api/projects/stats`
- `GET /api/projects/{id}` - `GET /api/projects/{id}`
@@ -43,3 +52,4 @@ When the backend is running, open `/index.html` to use the lightweight dashboard
- This repository snapshot was recovered from an archive, so local build and runtime validation depend on the target machine having Java and Maven available. - This repository snapshot was recovered from an archive, so local build and runtime validation depend on the target machine having Java and Maven available.
- The current environment used for editing did not include a working Maven installation, so changes were verified statically only. - The current environment used for editing did not include a working Maven installation, so changes were verified statically only.
- The SQL bootstrap files were refreshed into a clean UTF-8 seed set and made idempotent with `ON DUPLICATE KEY UPDATE`. - The SQL bootstrap files were refreshed into a clean UTF-8 seed set and made idempotent with `ON DUPLICATE KEY UPDATE`.
- Upstream user-management changes were reconciled into this branch with DTO-based responses so password hashes are not exposed from `/api/users`.

View File

@@ -0,0 +1,85 @@
package com.innovation.platform.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.innovation.platform.common.PageResult;
import com.innovation.platform.common.Result;
import com.innovation.platform.dto.ChangePasswordRequest;
import com.innovation.platform.dto.UserCreateRequest;
import com.innovation.platform.dto.UserProfileUpdateRequest;
import com.innovation.platform.dto.UserQueryRequest;
import com.innovation.platform.dto.UserResponse;
import com.innovation.platform.dto.UserUpdateRequest;
import com.innovation.platform.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "User management", description = "User listing, maintenance, teacher lookup, and profile operations")
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@SaCheckLogin
public class UserController {
private final SysUserService sysUserService;
@Operation(summary = "List users with pagination and filters")
@GetMapping
public Result<PageResult<UserResponse>> page(UserQueryRequest request) {
IPage<UserResponse> page = sysUserService.getUserList(request);
return Result.success(PageResult.of(page));
}
@Operation(summary = "Get user detail by id")
@GetMapping("/{id}")
public Result<UserResponse> getById(@Parameter(description = "User id") @PathVariable Long id) {
return Result.success(sysUserService.getUserById(id));
}
@Operation(summary = "Create a user")
@PostMapping
public Result<Long> create(@Valid @RequestBody UserCreateRequest request) {
return Result.success("User created successfully.", sysUserService.createUser(request));
}
@Operation(summary = "Update a user")
@PutMapping("/{id}")
public Result<UserResponse> update(
@Parameter(description = "User id") @PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request
) {
return Result.success("User updated successfully.", sysUserService.updateUser(id, request));
}
@Operation(summary = "Delete a user")
@DeleteMapping("/{id}")
public Result<Void> delete(@Parameter(description = "User id") @PathVariable Long id) {
sysUserService.deleteUser(id);
return Result.success("User deleted successfully.", null);
}
@Operation(summary = "List active teachers")
@GetMapping("/teachers")
public Result<List<UserResponse>> getTeacherList() {
return Result.success(sysUserService.getTeacherList());
}
@Operation(summary = "Change the current user's password")
@PostMapping("/change-password")
public Result<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request) {
sysUserService.changePassword(request);
return Result.success("Password changed successfully.", null);
}
@Operation(summary = "Update the current user's profile")
@PutMapping("/profile")
public Result<UserResponse> updateProfile(@Valid @RequestBody UserProfileUpdateRequest request) {
return Result.success("Profile updated successfully.", sysUserService.updateProfile(request));
}
}

View File

@@ -0,0 +1,15 @@
package com.innovation.platform.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class ChangePasswordRequest {
@NotBlank(message = "Current password is required.")
private String oldPassword;
@NotBlank(message = "New password is required.")
@Size(min = 6, max = 100, message = "New password must be between 6 and 100 characters.")
private String newPassword;
}

View File

@@ -0,0 +1,37 @@
package com.innovation.platform.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UserCreateRequest {
@NotBlank(message = "Username is required.")
@Size(max = 50, message = "Username must stay within 50 characters.")
private String username;
@Size(max = 100, message = "Password must stay within 100 characters.")
private String password;
@NotBlank(message = "Real name is required.")
@Size(max = 50, message = "Real name must stay within 50 characters.")
private String realName;
private Integer gender;
@Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "Phone number format is invalid.")
private String phone;
@Pattern(
regexp = "^$|^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
message = "Email format is invalid."
)
private String email;
@Size(max = 255, message = "Avatar URL must stay within 255 characters.")
private String avatar;
private Integer roleType;
private Integer status;
}

View File

@@ -0,0 +1,23 @@
package com.innovation.platform.dto;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UserProfileUpdateRequest {
@Size(max = 50, message = "Real name must stay within 50 characters.")
private String realName;
@Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "Phone number format is invalid.")
private String phone;
@Pattern(
regexp = "^$|^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
message = "Email format is invalid."
)
private String email;
@Size(max = 255, message = "Avatar URL must stay within 255 characters.")
private String avatar;
}

View File

@@ -0,0 +1,23 @@
package com.innovation.platform.dto;
import lombok.Data;
@Data
public class UserQueryRequest {
private String keyword;
private Integer roleType;
private Integer status;
private Integer current = 1;
private Integer size = 10;
public Integer getCurrent() {
return current == null || current < 1 ? 1 : current;
}
public Integer getSize() {
if (size == null || size < 1) {
return 10;
}
return Math.min(size, 100);
}
}

View File

@@ -0,0 +1,66 @@
package com.innovation.platform.dto;
import com.innovation.platform.entity.SysUser;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class UserResponse {
private Long id;
private String username;
private String realName;
private Integer gender;
private String phone;
private String email;
private String avatar;
private Integer status;
private String statusLabel;
private Integer roleType;
private String roleTypeLabel;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public static UserResponse fromEntity(SysUser user) {
return UserResponse.builder()
.id(user.getId())
.username(user.getUsername())
.realName(user.getRealName())
.gender(user.getGender())
.phone(user.getPhone())
.email(user.getEmail())
.avatar(user.getAvatar())
.status(user.getStatus())
.statusLabel(statusLabel(user.getStatus()))
.roleType(user.getRoleType())
.roleTypeLabel(roleTypeLabel(user.getRoleType()))
.createTime(user.getCreateTime())
.updateTime(user.getUpdateTime())
.build();
}
private static String statusLabel(Integer value) {
if (value == null) {
return "Unknown";
}
return switch (value) {
case 0 -> "Disabled";
case 1 -> "Active";
default -> "Other";
};
}
private static String roleTypeLabel(Integer value) {
if (value == null) {
return "Unknown";
}
return switch (value) {
case 1 -> "Student";
case 2 -> "Teacher";
case 3 -> "Administrator";
default -> "Other";
};
}
}

View File

@@ -0,0 +1,28 @@
package com.innovation.platform.dto;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UserUpdateRequest {
@Size(max = 50, message = "Real name must stay within 50 characters.")
private String realName;
private Integer gender;
@Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "Phone number format is invalid.")
private String phone;
@Pattern(
regexp = "^$|^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
message = "Email format is invalid."
)
private String email;
@Size(max = 255, message = "Avatar URL must stay within 255 characters.")
private String avatar;
private Integer roleType;
private Integer status;
}

View File

@@ -1,37 +1,44 @@
package com.innovation.platform.service; package com.innovation.platform.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.innovation.platform.dto.ChangePasswordRequest;
import com.innovation.platform.dto.LoginRequest; import com.innovation.platform.dto.LoginRequest;
import com.innovation.platform.dto.LoginResponse; import com.innovation.platform.dto.LoginResponse;
import com.innovation.platform.dto.RegisterRequest; import com.innovation.platform.dto.RegisterRequest;
import com.innovation.platform.dto.UserCreateRequest;
import com.innovation.platform.dto.UserProfileUpdateRequest;
import com.innovation.platform.dto.UserQueryRequest;
import com.innovation.platform.dto.UserResponse;
import com.innovation.platform.dto.UserUpdateRequest;
import com.innovation.platform.entity.SysUser; import com.innovation.platform.entity.SysUser;
/** import java.util.List;
* 用户服务接口
*/
public interface SysUserService { public interface SysUserService {
/**
* 用户登录
*/
LoginResponse login(LoginRequest request); LoginResponse login(LoginRequest request);
/**
* 用户注册
*/
void register(RegisterRequest request); void register(RegisterRequest request);
/**
* 用户登出
*/
void logout(); void logout();
/**
* 根据用户名查询用户
*/
SysUser getByUsername(String username); SysUser getByUsername(String username);
/**
* 根据ID查询用户
*/
SysUser getById(Long id); SysUser getById(Long id);
IPage<UserResponse> getUserList(UserQueryRequest request);
UserResponse getUserById(Long id);
Long createUser(UserCreateRequest request);
UserResponse updateUser(Long id, UserUpdateRequest request);
void deleteUser(Long id);
List<UserResponse> getTeacherList();
void changePassword(ChangePasswordRequest request);
UserResponse updateProfile(UserProfileUpdateRequest request);
} }

View File

@@ -3,15 +3,26 @@ package com.innovation.platform.service.impl;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.crypto.digest.BCrypt; import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.innovation.platform.dto.ChangePasswordRequest;
import com.innovation.platform.dto.LoginRequest; import com.innovation.platform.dto.LoginRequest;
import com.innovation.platform.dto.LoginResponse; import com.innovation.platform.dto.LoginResponse;
import com.innovation.platform.dto.RegisterRequest; import com.innovation.platform.dto.RegisterRequest;
import com.innovation.platform.dto.UserCreateRequest;
import com.innovation.platform.dto.UserProfileUpdateRequest;
import com.innovation.platform.dto.UserQueryRequest;
import com.innovation.platform.dto.UserResponse;
import com.innovation.platform.dto.UserUpdateRequest;
import com.innovation.platform.entity.SysUser; import com.innovation.platform.entity.SysUser;
import com.innovation.platform.mapper.SysUserMapper; import com.innovation.platform.mapper.SysUserMapper;
import com.innovation.platform.service.SysUserService; import com.innovation.platform.service.SysUserService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -53,14 +64,16 @@ public class SysUserServiceImpl implements SysUserService {
throw new RuntimeException("Username already exists."); throw new RuntimeException("Username already exists.");
} }
ensureEmailAvailable(request.getEmail(), null);
SysUser user = new SysUser(); SysUser user = new SysUser();
user.setUsername(request.getUsername()); user.setUsername(clean(request.getUsername()));
user.setPassword(BCrypt.hashpw(request.getPassword())); user.setPassword(BCrypt.hashpw(request.getPassword()));
user.setRealName(request.getRealName()); user.setRealName(clean(request.getRealName()));
user.setPhone(request.getPhone()); user.setPhone(clean(request.getPhone()));
user.setEmail(request.getEmail()); user.setEmail(clean(request.getEmail()));
user.setGender(request.getGender()); user.setGender(request.getGender());
user.setRoleType(request.getRoleType() != null ? request.getRoleType() : 0); user.setRoleType(request.getRoleType() != null ? request.getRoleType() : 1);
user.setStatus(1); user.setStatus(1);
sysUserMapper.insert(user); sysUserMapper.insert(user);
@@ -83,4 +96,192 @@ public class SysUserServiceImpl implements SysUserService {
public SysUser getById(Long id) { public SysUser getById(Long id) {
return sysUserMapper.selectById(id); return sysUserMapper.selectById(id);
} }
@Override
public IPage<UserResponse> getUserList(UserQueryRequest request) {
Page<SysUser> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
String keyword = clean(request.getKeyword());
wrapper.and(StringUtils.hasText(keyword), nested -> nested
.like(SysUser::getUsername, keyword)
.or()
.like(SysUser::getRealName, keyword)
.or()
.like(SysUser::getPhone, keyword)
.or()
.like(SysUser::getEmail, keyword))
.eq(request.getRoleType() != null, SysUser::getRoleType, request.getRoleType())
.eq(request.getStatus() != null, SysUser::getStatus, request.getStatus())
.orderByDesc(SysUser::getCreateTime);
return sysUserMapper.selectPage(page, wrapper).convert(UserResponse::fromEntity);
}
@Override
public UserResponse getUserById(Long id) {
return UserResponse.fromEntity(requireUser(id));
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createUser(UserCreateRequest request) {
if (getByUsername(request.getUsername()) != null) {
throw new RuntimeException("Username already exists.");
}
ensureEmailAvailable(request.getEmail(), null);
SysUser user = new SysUser();
user.setUsername(clean(request.getUsername()));
user.setPassword(BCrypt.hashpw(resolvePassword(request.getPassword())));
user.setRealName(clean(request.getRealName()));
user.setGender(request.getGender());
user.setPhone(clean(request.getPhone()));
user.setEmail(clean(request.getEmail()));
user.setAvatar(clean(request.getAvatar()));
user.setRoleType(request.getRoleType() != null ? request.getRoleType() : 1);
user.setStatus(request.getStatus() != null ? request.getStatus() : 1);
applyAuditFields(user, true);
sysUserMapper.insert(user);
return user.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public UserResponse updateUser(Long id, UserUpdateRequest request) {
SysUser user = requireUser(id);
ensureEmailAvailable(request.getEmail(), id);
user.setRealName(clean(request.getRealName()));
user.setGender(request.getGender());
user.setPhone(clean(request.getPhone()));
user.setEmail(clean(request.getEmail()));
user.setAvatar(clean(request.getAvatar()));
user.setRoleType(request.getRoleType() != null ? request.getRoleType() : user.getRoleType());
user.setStatus(request.getStatus() != null ? request.getStatus() : user.getStatus());
applyAuditFields(user, false);
sysUserMapper.updateById(user);
return UserResponse.fromEntity(user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteUser(Long id) {
SysUser user = requireUser(id);
Long currentUserId = currentUserId();
if (currentUserId != null && currentUserId.equals(id)) {
throw new RuntimeException("You cannot delete the current account.");
}
sysUserMapper.deleteById(user.getId());
}
@Override
public List<UserResponse> getTeacherList() {
return sysUserMapper.selectList(
new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getRoleType, 2)
.eq(SysUser::getStatus, 1)
.orderByAsc(SysUser::getRealName)
).stream().map(UserResponse::fromEntity).toList();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void changePassword(ChangePasswordRequest request) {
Long userId = currentUserId();
if (userId == null) {
throw new RuntimeException("Current login session is invalid.");
}
SysUser user = requireUser(userId);
if (!BCrypt.checkpw(request.getOldPassword(), user.getPassword())) {
throw new RuntimeException("Current password is incorrect.");
}
if (request.getOldPassword().equals(request.getNewPassword())) {
throw new RuntimeException("New password must be different from the current password.");
}
user.setPassword(BCrypt.hashpw(request.getNewPassword()));
applyAuditFields(user, false);
sysUserMapper.updateById(user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public UserResponse updateProfile(UserProfileUpdateRequest request) {
Long userId = currentUserId();
if (userId == null) {
throw new RuntimeException("Current login session is invalid.");
}
SysUser user = requireUser(userId);
ensureEmailAvailable(request.getEmail(), userId);
user.setRealName(clean(request.getRealName()));
user.setPhone(clean(request.getPhone()));
user.setEmail(clean(request.getEmail()));
user.setAvatar(clean(request.getAvatar()));
applyAuditFields(user, false);
sysUserMapper.updateById(user);
return UserResponse.fromEntity(user);
}
private SysUser requireUser(Long id) {
SysUser user = sysUserMapper.selectById(id);
if (user == null) {
throw new RuntimeException("User does not exist.");
}
return user;
}
private void ensureEmailAvailable(String email, Long currentUserId) {
String cleanEmail = clean(email);
if (!StringUtils.hasText(cleanEmail)) {
return;
}
SysUser existing = getByEmail(cleanEmail);
if (existing != null && (currentUserId == null || !existing.getId().equals(currentUserId))) {
throw new RuntimeException("Email is already in use.");
}
}
private SysUser getByEmail(String email) {
String cleanEmail = clean(email);
if (!StringUtils.hasText(cleanEmail)) {
return null;
}
return sysUserMapper.selectOne(
new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getEmail, cleanEmail)
);
}
private void applyAuditFields(SysUser user, boolean create) {
Long userId = currentUserId();
if (userId == null) {
return;
}
if (create) {
user.setCreateBy(userId);
}
user.setUpdateBy(userId);
}
private Long currentUserId() {
return StpUtil.isLogin() ? StpUtil.getLoginIdAsLong() : null;
}
private String resolvePassword(String password) {
String cleanPassword = clean(password);
return StringUtils.hasText(cleanPassword) ? cleanPassword : "123456";
}
private String clean(String value) {
return value == null ? null : value.trim();
}
} }

View File

@@ -3,9 +3,9 @@ server:
spring: spring:
datasource: datasource:
url: jdbc:mysql://mysql:3306/innovation_platform?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/innovation_platform?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai}
username: innovation username: ${SPRING_DATASOURCE_USERNAME:innovation}
password: innovation123 password: ${SPRING_DATASOURCE_PASSWORD:innovation123}
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
sql: sql:
init: init:

View File

@@ -1,15 +1,17 @@
-- Seed users -- Seed users
INSERT INTO sys_user (id, username, password, real_name, gender, role_type, status) VALUES INSERT INTO sys_user (id, username, password, real_name, gender, phone, email, role_type, status) VALUES
(1, 'admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'System Admin', 1, 3, 1), (1, 'admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'System Admin', 1, '13900000001', 'admin@innovation.local', 3, 1),
(2, 'teacher001', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Alice Chen', 2, 2, 1), (2, 'teacher001', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Alice Chen', 2, '13900000002', 'alice.chen@innovation.local', 2, 1),
(3, 'teacher002', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Michael Zhao', 1, 2, 1), (3, 'teacher002', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Michael Zhao', 1, '13900000003', 'michael.zhao@innovation.local', 2, 1),
(4, 'student001', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Olivia Lin', 2, 1, 1), (4, 'student001', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Olivia Lin', 2, '13900000004', 'olivia.lin@innovation.local', 1, 1),
(5, 'student002', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Ethan Wu', 1, 1, 1), (5, 'student002', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Ethan Wu', 1, '13900000005', 'ethan.wu@innovation.local', 1, 1),
(6, 'student003', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Sophia Xu', 2, 1, 1) (6, 'student003', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt9hQIu', 'Sophia Xu', 2, '13900000006', 'sophia.xu@innovation.local', 1, 1)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
password = VALUES(password), password = VALUES(password),
real_name = VALUES(real_name), real_name = VALUES(real_name),
gender = VALUES(gender), gender = VALUES(gender),
phone = VALUES(phone),
email = VALUES(email),
role_type = VALUES(role_type), role_type = VALUES(role_type),
status = VALUES(status); status = VALUES(status);

View File

@@ -0,0 +1,7 @@
-- Add user uniqueness and lookup indexes.
ALTER TABLE sys_user
ADD CONSTRAINT uk_email UNIQUE (email);
CREATE INDEX idx_phone ON sys_user(phone);
CREATE INDEX idx_role_type ON sys_user(role_type);
CREATE INDEX idx_status ON sys_user(status);

View File

@@ -16,7 +16,11 @@ CREATE TABLE IF NOT EXISTS sys_user (
update_by BIGINT DEFAULT NULL, update_by BIGINT DEFAULT NULL,
deleted TINYINT DEFAULT 0, deleted TINYINT DEFAULT 0,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE KEY uk_username (username) UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email),
KEY idx_phone (phone),
KEY idx_role_type (role_type),
KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Student profile table -- Student profile table