系统管理后端开发-02
UserService
package com.it6666.service;
import com.it6666.domain.User;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author BNTang
*/
public interface UserService {
/**
* 根据手机号查询用户
*
* @param phone 手机号
* @return 根据手机号查询到的用户对象
*/
User queryUserByPhone(String phone);
/**
* 根据用户ID查询用户
*
* @param userId 用户编号
* @return 根据用户ID查询到的用户对象
*/
User getOne(Long userId);
}
UserServiceImpl
package com.it6666.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.it6666.mapper.UserMapper;
import com.it6666.domain.User;
import com.it6666.service.UserService;
/**
* @author BNTang
*/
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public User queryUserByPhone(String phone) {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq(User.COL_PHONE, phone);
return this.userMapper.selectOne(qw);
}
@Override
public User getOne(Long userId) {
return this.userMapper.selectById(userId);
}
}
最终结构图
生成Menu菜单相关实体以及接口和持久层,注意选择生成到那个项目中, 前面生成过了User在生成Menu就很快了,基本上动的很少
修改MenuService
package com.it6666.service;
import com.it6666.domain.Menu;
import com.it6666.domain.SimpleUser;
import java.util.List;
/**
* description: MenuService
* date: 2020-09-03 21:39
* author: 30315
* version: 1.0
*/
public interface MenuService {
/**
* 查询菜单信息
* 如查用户是超级管理员,那么查询所以菜单和权限
* 如果用户是普通用户,那么根据用户ID关联角色和权限
*
* @param isAdmin 是否是超级管理员
* @param simpleUser 如果 isAdmin=true, simpleUser可以为空
*/
public List<Menu> selectMenuTree(boolean isAdmin, SimpleUser simpleUser);
}
修改MenuServiceImpl
package com.it6666.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.it6666.constants.Constants;
import com.it6666.domain.SimpleUser;
import org.springframework.stereotype.Service;
import java.util.List;
import com.it6666.domain.Menu;
import com.it6666.mapper.MenuMapper;
import com.it6666.service.MenuService;
/**
* description: MenuServiceImpl
* date: 2020-09-03 21:39
* author: 30315
* version: 1.0
*/
@Service
public class MenuServiceImpl implements MenuService {
private final MenuMapper menuMapper;
public MenuServiceImpl(MenuMapper menuMapper) {
this.menuMapper = menuMapper;
}
@Override
public List<Menu> selectMenuTree(boolean isAdmin, SimpleUser simpleUser) {
QueryWrapper<Menu> qw = new QueryWrapper<>();
qw.eq(Menu.COL_STATUS, Constants.STATUS_TRUE);
qw.in(Menu.COL_MENU_TYPE, Constants.MENU_TYPE_M, Constants.MENU_TYPE_C);
qw.orderByAsc(Menu.COL_PARENT_ID);
if (isAdmin) {
return menuMapper.selectList(qw);
} else {
// 根据用户id查询用户拥有的菜单信息
return menuMapper.selectList(qw);
}
}
}
添加Shiro的配置
添加在 his-system/system-web/ 中的 src/main/java/com.it6666/config/shiro 文件夹中
package com.it6666.config.shiro;
import com.alibaba.fastjson.JSON;
import com.it6666.constants.HttpStatus;
import com.it6666.vo.AjaxResult;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
/**
* description: 判断用户是否进行了登录
* date: 2020-09-03 21:56
* author: 30315
* version: 1.0
*/
public class ShiroLoginFilter extends FormAuthenticationFilter {
/**
* 在访问controller前,判断是否登录,返回Json,不进行重定向
*
* @param request
* @param response
* @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
AjaxResult ajaxResult = AjaxResult.fail();
ajaxResult.put("code", HttpStatus.UNAUTHORIZED);
ajaxResult.put("msg", "登录认证失效,请重新登录!");
httpServletResponse.getWriter().write(JSON.toJSON(ajaxResult).toString());
return false;
}
}
创建TokenWebSessionManager
添加在 his-system/system-web/ 中的 src/main/java/com.it6666/config/shiro 文件夹中
package com.it6666.config.shiro;
import com.it6666.constants.Constants;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
import java.util.UUID;
/**
* description: 生成token, 如果没有 token 生成一个返回到前台,如果有就从请求头里面取出来
* date: 2020-09-03 22:05
* author: 30315
* version: 1.0
*/
@Configuration
public class TokenWebSessionManager extends DefaultWebSessionManager {
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
// 从头里面得到请求TOKEN, 如果不存在就生成一个返回
String header = WebUtils.toHttp(request).getHeader(Constants.TOKEN);
if (StringUtils.hasText(header)) {
return header;
}
return UUID.randomUUID().toString();
}
}
创建ActiverUser
添加在 his-system/system-web/ 中的 src/main/java/com.it6666/vo 文件夹中
package com.it6666.vo;
import com.it6666.domain.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
/**
* description: ActiverUser
* date: 2020-09-03 22:11
* author: 30315
* version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ActiverUser implements Serializable {
private User user;
// 角色
private List<String> roles = Collections.EMPTY_LIST;
// 权限
private List<String> permissions = Collections.EMPTY_LIST;
}
创建UserRealm
添加在 his-system/system-web/ 中的 src/main/java/com.it6666/config/shiro 文件夹中
package com.it6666.config.shiro;
import com.it6666.domain.User;
import com.it6666.service.UserService;
import com.it6666.vo.ActiverUser;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
/**
* description: 自定义 realm 去匹配输入的用户名和密码
* date: 2020-09-03 22:15
* author: 30315
* version: 1.0
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
@Lazy
private UserService userService;
@Override
public String getName() {
return this.getClass().getSimpleName();
}
/**
* 做认证, 就是登陆
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 得到用户登陆名
String phone = token.getPrincipal().toString();
// 根据电话查询用户是否存在
User user = userService.queryUserByPhone(phone);
// 查询到了说明用户存在,但是密码可能不正确
if (null != user) {
// 组装ActiverUser, 存放到 Reids 里面的对象
ActiverUser activerUser = new ActiverUser();
activerUser.setUser(user);
// 匹配密码
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(activerUser, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
return info;
} else {
// 返回null, 代表用户不存在
return null;
}
}
/**
* 做授权, 登陆成功之后判断用户是否有某个菜单或按钮的权限
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 身份得到的就是上一个方法(SimpleAuthenticationInfo), 第一个参数 activerUser
ActiverUser activerUser = (ActiverUser) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
return info;
}
}
创建ShiroProperties
添加在 his-system/system-web/ 中的 src/main/java/com.it6666/config/shiro 文件夹中
package com.it6666.config.shiro;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* description: ShiroProperties
* date: 2020-09-03 22:19
* author: 30315
* version: 1.0
*/
@ConfigurationProperties(prefix = "shiro")
@Data
public class ShiroProperties {
/**
* 密码加密方式
*/
private String hashAlgorithmName = "md5";
/**
* 密码散列次数
*/
private Integer hashIterations = 2;
/**
* 不拦击的路径
*/
private String[] anonUrls;
/**
* 拦截的路径
*/
private String[] authcUrls;
}
创建ShiroAutoConfiguration
添加在 his-system/system-web/ 中的 src/main/java/com.it6666/config/shiro 文件夹中
package com.it6666.config.shiro;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* description: ShiroAutoConfiguration
* date: 2020-09-03 22:20
* author: 30315
* version: 1.0
*/
@Configuration
@EnableConfigurationProperties(value = {ShiroProperties.class, RedisProperties.class})
public class ShiroAutoConfiguration {
private ShiroProperties shiroProperties;
private RedisProperties redisProperties;
public static final String SHIRO_FILTER_NAME = "shiroFilter";
public ShiroAutoConfiguration(ShiroProperties shiroProperties, RedisProperties redisProperties) {
this.shiroProperties = shiroProperties;
this.redisProperties = redisProperties;
}
/**
* 创建凭证匹配器
*/
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 注入散列算法名
matcher.setHashAlgorithmName(shiroProperties.getHashAlgorithmName());
// 注入散列次数
matcher.setHashIterations(shiroProperties.getHashIterations());
return matcher;
}
/**
* 创建自定义, realm
* 并注入, 凭证匹配器
*/
@Bean
@ConditionalOnClass(value = {UserRealm.class})
public UserRealm getUserRealm(HashedCredentialsMatcher matcher) {
UserRealm userRealm = new UserRealm();
// 注入, 凭证匹配器
userRealm.setCredentialsMatcher(matcher);
return userRealm;
}
/**
* 创建安全管理器
*/
@Bean
@ConditionalOnClass(value = DefaultWebSecurityManager.class)
public DefaultWebSecurityManager getSecurityManager(DefaultWebSessionManager defaultWebSessionManager, UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 注入, realm
securityManager.setRealm(userRealm);
securityManager.setSessionManager(defaultWebSessionManager);
return securityManager;
}
/**
* 声明过滤器
* Shiro 的Web过滤器, id必须和 web.xml 里面的 shiroFilter的 targetBeanName 的值一样
*/
@Bean(value = SHIRO_FILTER_NAME)
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 注入安全管理器
bean.setSecurityManager(securityManager);
// 处理用户未认证访问要认证的(资源)地址的跳转问题, 默认是跳转到, shiroProperties.getLoginUrl() 现在改成以 Json 串形式返回
Map<String, Filter> filters = new HashMap<>();
filters.put("authc", new ShiroLoginFilter());
bean.setFilters(filters);
Map<String, String> map = new HashMap<>();
// 配置不拦截的路径
String[] anonUrls = shiroProperties.getAnonUrls();
if (anonUrls != null && anonUrls.length > 0) {
for (String anonUrl : anonUrls) {
map.put(anonUrl, "anon");
}
}
// 配置拦截的路径
String[] authcUrls = this.shiroProperties.getAuthcUrls();
if (authcUrls != null && authcUrls.length > 0) {
for (String authcUrl : authcUrls) {
map.put(authcUrl, "authc");
}
}
bean.setFilterChainDefinitionMap(map);
return bean;
}
/**
* 注册DelegatingFilterProxy
*/
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> registDelegatingFilterProxy() {
// 创建注册器
FilterRegistrationBean<DelegatingFilterProxy> bean = new FilterRegistrationBean<>();
// 创建过滤器
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
// 注入过滤器
bean.setFilter(proxy);
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName(SHIRO_FILTER_NAME);
Collection<String> servleNames = new ArrayList<>();
servleNames.add(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
bean.setServletNames(servleNames);
return bean;
}
/**
* sessionManager 里面可以决定 sessionDAO
*
* @param redisSessionDao defaultWebSessionManager 来源 com.it6666.system.config.TokenWebSessionManager
* @return
*/
@Bean
public DefaultWebSessionManager defaultWebSessionManager(RedisSessionDAO redisSessionDao) {
DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
defaultWebSessionManager.setSessionDAO(redisSessionDao);
return defaultWebSessionManager;
}
/**
* 使用Redis, 来存储登录的信息
* sessionDao 还需要设置给, sessionManager
*/
@Bean
public RedisSessionDAO redisSessionDAO(IRedisManager redisManager) {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
// 操作那个 Redis
redisSessionDAO.setRedisManager(redisManager);
// 用户的登录信息保存多久, 7天
redisSessionDAO.setExpire(7 * 24 * 3600);
return redisSessionDAO;
}
/**
* 因为分步式项目,所以使用 Redis 去存我们的登陆 Session
*
* @return
*/
@Bean
public IRedisManager redisManager() {
// 因为 RedisManager 要操作 Redis 所以必须把Redis的客户端给RedisManager
RedisManager redisManager = new RedisManager();
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
JedisPool jedisPool = new JedisPool(jedisPoolConfig, redisProperties.getHost(), redisProperties.getPort(), 5000, redisProperties.getPassword());
redisManager.setJedisPool(jedisPool);
return redisManager;
}
/**
* 加入注解的使用,不加入这个注解不生效,开始
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
// 加入注解的使用,不加入这个注解不生效,结束
}
LoginController
写登陆、退出、查询用户信息、接口
添加在 his-system/system-web/ 中的 src/main/java/com.it6666/controller/system 文件夹中
package com.it6666.controller.system;
import com.it6666.constants.Constants;
import com.it6666.constants.HttpStatus;
import com.it6666.domain.Menu;
import com.it6666.domain.SimpleUser;
import com.it6666.dto.LoginBodyDto;
import com.it6666.service.MenuService;
import com.it6666.vo.ActiverUser;
import com.it6666.vo.AjaxResult;
import com.it6666.vo.MenuTreeVo;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* description: LoginController
* date: 2020-09-03 22:25
* author: 30315
* version: 1.0
*/
@RestController
@Log4j2
public class LoginController {
private final MenuService menuService;
public LoginController(MenuService menuService) {
this.menuService = menuService;
}
/**
* 登录方法
*
* @return 结果
*/
@PostMapping("login/doLogin")
public AjaxResult login(@RequestBody LoginBodyDto loginBodyDto, HttpServletRequest request) {
AjaxResult ajax = AjaxResult.success();
String username = loginBodyDto.getUsername();
String password = loginBodyDto.getPassword();
// 构造用户名和密码的 UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
// 得到会话的token, 也就是 Redis里面存的
Serializable webToken = subject.getSession().getId();
ajax.put(Constants.TOKEN, webToken);
} catch (Exception e) {
log.error("用户名或密码不正确", e);
ajax = AjaxResult.error(HttpStatus.ERROR, "用户名或密码不正确");
}
return ajax;
}
/**
* 获取用户信息
*
* @return 用户信息
*/
@GetMapping("login/getInfo")
public AjaxResult getInfo() {
Subject subject = SecurityUtils.getSubject();
ActiverUser activerUser = (ActiverUser) subject.getPrincipal();
AjaxResult ajax = AjaxResult.success();
ajax.put("username", activerUser.getUser().getUserName());
ajax.put("picture", activerUser.getUser().getPicture());
ajax.put("roles", activerUser.getRoles());
ajax.put("permissions", activerUser.getPermissions());
return ajax;
}
/**
* 用户退出
*
*/
@GetMapping("login/logout")
public AjaxResult logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return AjaxResult.success("用户退出成功");
}
/**
* 获取应该显示的菜单信息
*
* @return 菜单信息
*/
@GetMapping("login/getMenus")
public AjaxResult getMeuns() {
Subject subject = SecurityUtils.getSubject();
ActiverUser activerUser = (ActiverUser) subject.getPrincipal();
boolean isAdmin = activerUser.getUser().getUserType().equals(Constants.USER_ADMIN);
SimpleUser simpleUser = null;
if (!isAdmin) {
simpleUser = new SimpleUser(activerUser.getUser().getUserId(), activerUser.getUser().getUserName());
}
List<Menu> menus = menuService.selectMenuTree(isAdmin, simpleUser);
List<MenuTreeVo> menuVos = new ArrayList<>();
for (Menu menu : menus) {
menuVos.add(new MenuTreeVo(menu.getMenuId().toString(), menu.getPath()));
}
return AjaxResult.success(menuVos);
}
}
可以进行postman测试接口了,打开postman,我这里以yapi进行测试
接口数据校验
修改his-commons项目的pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
修改his-commons src/main/java/com/it6666/dto 中的 LoginBodyDto
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginBodyDto implements Serializable {
// 用户名
@NotNull(message = "用户名不能为空")
private String username;
// 密码
@NotNull(message = "用户密码不能为空")
private String password;
// 验证码
private String code;
}
修改system-web的LoginController
在system-web com.it6666.exception 里创建GlobalExceptionHandler全局异常处理
package com.it6666.exception;
import com.it6666.vo.AjaxResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* description: GlobalExceptionHandler
* date: 2020-09-03 23:54
* author: 30315
* version: 1.0
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 当系统出现MethodArgumentNotValidException这个异常时,会调用下面的方法
*
* @param e
* @return
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public AjaxResult jsonErrorHandler(MethodArgumentNotValidException e) {
return AjaxResult.error(e.getMessage());
}
}
在system-web测试测试登陆方法
接口数据校验返回数据优化
system-web com.it6666.exception 里 GlobalExceptionHandler
package com.it6666.exception;
import com.it6666.vo.AjaxResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* description: 全局异常处理
* date: 2020-09-03 23:54
* author: 30315
* version: 1.0
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 当系统出现MethodArgumentNotValidException这个异常时,会调用下面的方法
*
* @param e
* @return
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public AjaxResult jsonErrorHandler(MethodArgumentNotValidException e) {
List<Map<String, Object>> list = new ArrayList<>();
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
for (ObjectError allError : allErrors) {
Map<String, Object> map = new HashMap<>();
map.put("defaultMessage", allError.getDefaultMessage());
map.put("objectName", allError.getObjectName());
// 注意,这里面拿到的是具体的某一个属性
FieldError fieldError = (FieldError) allError;
map.put("field", fieldError.getField());
list.add(map);
}
return AjaxResult.fail("后端数据校验异常", list);
}
}
测试
全部通过,现在愉快的去写前端,注意一下,domain里面的实体必须实现序列化,不然会出现 Caused by: java.io.NotSerializableException 异常
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具