feat(admin-server): 添加管理员相关功能

- 新增 Admin 实体类
- 添加 AdminMapper 接口及 XML 配置
- 实现 AdminService 接口及其实现类
- 创建 AdminController 控制器
- 增加全局异常处理器和统一返回结果封装
- 集成 MyBatis-Plus 和 Swagger
This commit is contained in:
FalingCliff 2025-05-24 15:20:57 +08:00
parent 3b12e9ece2
commit 8e3e8fc8b8
11 changed files with 364 additions and 0 deletions

10
pom.xml
View File

@ -44,6 +44,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
@ -54,6 +59,11 @@
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.12</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>

View File

@ -1,9 +1,11 @@
package com.example.admin_server;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.admin_server.mapper")
public class AdminServerApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,79 @@
package com.example.admin_server.common;
import com.example.admin_server.enums.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
/**
* 全局异常处理器统一将异常转换为 Result 返回
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 参数校验异常@Valid
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.findFirst()
.map(err -> err.getField() + " " + err.getDefaultMessage())
.orElse(ResultCode.VALIDATE_ERROR.getMsg());
log.warn("@Valid参数校验失败: {}", message);
return Result.of(ResultCode.VALIDATE_ERROR, message);
}
/**
* 参数绑定异常普通对象绑定
*/
@ExceptionHandler(BindException.class)
public Result<?> handleBindException(BindException ex) {
String message = ex.getFieldErrors().stream()
.findFirst()
.map(err -> err.getField() + " " + err.getDefaultMessage())
.orElse(ResultCode.VALIDATE_ERROR.getMsg());
log.warn("参数绑定失败: {}", message);
return Result.of(ResultCode.VALIDATE_ERROR, message);
}
/**
* 单个参数校验@Validated
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {
String message = ex.getConstraintViolations().stream()
.findFirst()
.map(ConstraintViolation::getMessage)
.orElse(ResultCode.VALIDATE_ERROR.getMsg());
log.warn("@Validated参数校验失败: {}", message);
return Result.of(ResultCode.VALIDATE_ERROR, message);
}
/**
* 请求体不可读
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result<?> handleHttpMessageNotReadable(HttpMessageNotReadableException ex) {
log.warn("消息不可读: {}", ex.getMessage());
return Result.of(ResultCode.VALIDATE_ERROR);
}
/**
* 兜底异常
*/
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception ex) {
log.error("系统异常", ex);
return Result.of(ResultCode.SERVER_ERROR);
}
}

View File

@ -0,0 +1,73 @@
package com.example.admin_server.common;
import com.example.admin_server.enums.ResultCode;
import lombok.Data;
@Data
public class Result<T> {
private Integer code;
private String msg;
private T data;
private Result() {}
/** 成功:无数据 */
public static <T> Result<T> ok() {
return restResult(null, ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg());
}
/** 成功:有数据 */
public static <T> Result<T> ok(T data) {
return restResult(data, ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg());
}
/** 成功:指定 msg 和 data */
public static <T> Result<T> ok(String msg, T data) {
return restResult(data, ResultCode.SUCCESS.getCode(), msg);
}
/** 失败:默认错误码与信息 */
public static <T> Result<T> fail() {
return restResult(null, ResultCode.FAIL.getCode(), ResultCode.FAIL.getMsg());
}
/** 失败:指定信息 */
public static <T> Result<T> fail(String msg) {
return restResult(null, ResultCode.FAIL.getCode(), msg);
}
/** 失败:指定错误码与信息 */
public static <T> Result<T> fail(Integer code, String msg) {
return restResult(null, code, msg);
}
/** 通用:使用枚举返回 */
public static <T> Result<T> of(ResultCode resultCode) {
return restResult(null, resultCode.getCode(), resultCode.getMsg());
}
/** 通用:使用枚举 + 自定义提示 */
public static <T> Result<T> of(ResultCode resultCode, String msg) {
return restResult(null, resultCode.getCode(), msg);
}
/** 通用:使用枚举 + 返回数据 */
public static <T> Result<T> of(ResultCode resultCode, T data) {
return restResult(data, resultCode.getCode(), resultCode.getMsg());
}
/** 统一创建返回结构 */
private static <T> Result<T> restResult(T data, int code, String msg) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
/** 是否成功 */
public boolean isSuccess() {
return this.code != null && this.code.equals(ResultCode.SUCCESS.getCode());
}
}

View File

@ -0,0 +1,19 @@
package com.example.admin_server.controller.admin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 后台管理员表 前端控制器
* </p>
*
* @author FallingCliff
* @since 2025-05-24
*/
@RestController
@RequestMapping("/admin")
public class AdminController {
}

View File

@ -0,0 +1,20 @@
package com.example.admin_server.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ResultCode {
SUCCESS(200, "成功"),
FAIL(500, "失败"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "资源不存在"),
VALIDATE_ERROR(400, "参数校验失败"),
SERVER_ERROR(503, "服务器内部错误");
private final int code;
private final String msg;
}

View File

@ -0,0 +1,16 @@
package com.example.admin_server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.admin_server.model.entity.Admin;
/**
* <p>
* 后台管理员表 Mapper 接口
* </p>
*
* @author FallingCliff
* @since 2025-05-24
*/
public interface AdminMapper extends BaseMapper<Admin> {
}

View File

@ -0,0 +1,86 @@
package com.example.admin_server.model.entity;
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 io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 后台管理员表
* </p>
*
* @author FallingCliff
* @since 2025-05-24
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("admin")
@ApiModel(value="Admin对象", description="后台管理员表")
public class Admin implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "用户名")
@TableField("username")
private String username;
@ApiModelProperty(value = "密码")
@TableField("password")
private String password;
@ApiModelProperty(value = "姓名")
@TableField("real_name")
private String realName;
@ApiModelProperty(value = "邮箱")
@TableField("email")
private String email;
@ApiModelProperty(value = "手机号")
@TableField("phone")
private String phone;
@ApiModelProperty(value = "头像")
@TableField("avatar")
private String avatar;
@ApiModelProperty(value = "状态0禁用1正常")
@TableField("status")
private Integer status;
@ApiModelProperty(value = "是否为超级管理员1是0否")
@TableField("is_super")
private Integer isSuper;
@ApiModelProperty(value = "上次登录IP")
@TableField("last_login_ip")
private String lastLoginIp;
@ApiModelProperty(value = "上次登录时间")
@TableField("last_login_time")
private LocalDateTime lastLoginTime;
@ApiModelProperty(value = "创建时间")
@TableField("create_time")
private LocalDateTime createTime;
@ApiModelProperty(value = "更新时间")
@TableField("update_time")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,16 @@
package com.example.admin_server.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.admin_server.model.entity.Admin;
/**
* <p>
* 后台管理员表 服务类
* </p>
*
* @author FallingCliff
* @since 2025-05-24
*/
public interface IAdminService extends IService<Admin> {
}

View File

@ -0,0 +1,21 @@
package com.example.admin_server.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.admin_server.mapper.AdminMapper;
import com.example.admin_server.model.entity.Admin;
import com.example.admin_server.service.IAdminService;
import org.springframework.stereotype.Service;
/**
* <p>
* 后台管理员表 服务实现类
* </p>
*
* @author FallingCliff
* @since 2025-05-24
*/
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService {
}

View File

@ -0,0 +1,22 @@
<?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="com.example.admin_server.mapper.AdminMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.example.admin_server.model.entity.Admin">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
<result column="real_name" property="realName" />
<result column="email" property="email" />
<result column="phone" property="phone" />
<result column="avatar" property="avatar" />
<result column="status" property="status" />
<result column="is_super" property="isSuper" />
<result column="last_login_ip" property="lastLoginIp" />
<result column="last_login_time" property="lastLoginTime" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
</resultMap>
</mapper>