概览:用户注册,登录,登出模块,控制台日志输出,json||表单接收区别,返回类型构造,异常返回模块,参数校验模块,session模块,优化:登录拦截器实现
开发顺序推荐:Dao->Service->controller
请求头类型选择
请求使用一般Content-Type:application/json类型传输,少数使用form格式。
两种接收方式区别:
/**
* form表单方式接收数据
* @param username
*/
@PostMapping("/register")
public void register(@RequestParam(value = "username") String username){}
@PostMapping("/register2")
public void register2(User user){}
/**
* json方式接收数据
*/
@PostMapping("/register3")
public void register3(@RequestBody User user){}
返回类型构建
返回对象vo
@Data
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class ResponseVo<T> {
private Integer status;
private String msg;
private T data;
public ResponseVo(Integer status, String msg) {
this.status = status;
this.msg = msg;
}
public ResponseVo(Integer status, T data) {
this.status = status;
this.data = data;
}
/**
* 构建常用返回类型的静态方法以供直接调用
*
* @return
*/
public static <T> ResponseVo<T> success() {
return new ResponseVo<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getDesc());
}
public static <T> ResponseVo<T> success(String msg) {
return new ResponseVo<>(ResponseEnum.SUCCESS.getCode(), msg);
}
public static <T> ResponseVo<T> successByCommonData(T data) {
return new ResponseVo<>(ResponseEnum.SUCCESS.getCode(), data);
}
public static <T> ResponseVo<T> error(ResponseEnum responseEnum) {
return new ResponseVo<>(responseEnum.getCode(), responseEnum.getDesc());
}
public static <T> ResponseVo<T> error(ResponseEnum responseEnum,String msg) {
return new ResponseVo<>(responseEnum.getCode(), msg);
}
public static <T> ResponseVo<T> error(ResponseEnum responseEnum, BindingResult bindingResult) {
return new ResponseVo<>(responseEnum.getCode(), bindingResult.getFieldError().getField() + bindingResult.getFieldError().getDefaultMessage());
}
}
返回类型enum
@Getter
public enum ResponseEnum {
SUCCESS(0, "成功"),
FAIL_PSWERROR(1, "密码错误"),
FAIL_USER_EXIST(2, "用户已存在"),
FAIL_NON_LOGIN(10, "用户为未登录"),
MASHINE_ERROR(-1, "服务器错误"),
;
int code;
String desc;
ResponseEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
}
构建异常返回类并设置状态码:
@ControllerAdvice
public class RunTimeExceptionJandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
@ResponseStatus(HttpStatus.FORBIDDEN) //设置相应状态码
public ResponseVo handle(RuntimeException e) {
return ResponseVo.error(ResponseEnum.MASHINE_ERROR, e.getMessage());
}
}
参数校验模块:
@Data
public class UserForm {
@NotBlank(message = "用户名不能为空") //用于字符String,会判断空格
//@NotNull 判断是否为null
//@NotEmpty 用于集合,数组,List判空
private String username;
@NotBlank
private String password;
@NotBlank
private String email;
}
/**
* json方式接收数据
* @Valid & BindingResult: 用于判断参数合法性校验
*/
@PostMapping("/register3")
public ResponseVo register3(@Valid @RequestBody UserForm userForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
System.out.println("注册参数有无,{}"+bindingResult.getFieldError().getField()+bindingResult.getFieldError().getDefaultMessage());
return ResponseVo.error(ResponseEnum.PARAMS_ERROR,bindingResult.getFieldError().getField()+bindingResult.getFieldError().getDefaultMessage());
}
return ResponseVo.success("注册成功");
}
控制台日志输出
mybatis:
configuration:
map-underscore-to-camel-case: true
#控制台日志配置
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
logging: #输出日志格式
pattern:
console: "[%thread] %-5level %logger{36} - %msg%n"
用户注册模块:
用户实体类
@Data
public class User {
private Integer id;
private String username;
private String password;
private String email;
private String phone;
private String question;
private String answer;
private Integer role;
private Date createTime;
private Date updateTime;
}
用户注册功能开发
@Service
public class IUserService {
@Autowired
private UserMapper userMapper;
/**
注册
**/
void register(User user){
//用户名不能重复
if(userMapper.countByUsername(user.getUsername())>0){
throw new RuntimeException("该username已注册");
}
//email不能重复
if(userMapper.countByUsername(user.getUsername())>0){
throw new RuntimeException("该邮箱已注册");
}
//MD5加密摘要算法(Spring自带)
user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes(StandardCharsets.UTF_8)));
//用户类别设置
user.setRole(0);
//写入数据库
int result = userMapper.insertSelective(user);
if(result == 0){
throw new RuntimeException("注册失败");
}
}
}
数据库操作
@Service
public interface UserMapper {
@Select("select count(1) from mall_user where username = #{username}")
int countByUsername(String username);
@Select("select count(1) from mall_user where email = #{email}")
int countByEmail(String email);
@Update("insert into mall_user * values (user.)")
int insertSelective(User user);
}
用户登录并设置session模块:
@PostMapping("/login")
public ResponseVo<User> login(@Valid @RequestBody UserLoginForm userLoginForm, BindingResult bindingResult, HttpServletRequest httpServletRequest) {
if (bindingResult.hasErrors()) {
return ResponseVo.error(ResponseEnum.PARAMS_ERROR, bindingResult);
}
ResponseVo<User> userResponseVo = userService.login(userLoginForm.getUsername(), userLoginForm.getPassword());
//设置Session
HttpSession session = httpServletRequest.getSession();
session.setAttribute(UserConst.CURRENT_USER, userResponseVo);
return userResponseVo;
}
@GetMapping("/getUser")
public ResponseVo<User> getUserInfo(HttpServletRequest request){
User user = (User) request.getSession().getAttribute(UserConst.CURRENT_USER);
if(user == null){
return ResponseVo.error(ResponseEnum.FAIL_NON_LOGIN);
}
return ResponseVo.successByCommonData(user);
}
public class UserConst {
public static final String CURRENT_USER = "currentUser";
}
/**
登录
**/
public ResponseVo<User> login(String username,String password){
//推荐只使用用户名去查,因为有索引,密码没有
User user = userMapper.selectByUsername(username, password);
if(user == null){
return ResponseVo.error(ResponseEnum.ERROR_USER_PSW);
}
if(!user.getPassword().equalsIgnoreCase(DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8)))){
return ResponseVo.error(ResponseEnum.ERROR_USER_PSW);
}
return ResponseVo.success();
}
注意:推荐只使用用户名去查,因为有索引,密码没有。
Session和cookie的区别:
session保存在内存,重启会丢失。分布式改进版本:token+redis
cookie保存在客户端浏览器中。
注意:cookie跨域问题(localhost和127.0.0.1是不同的域名和地址,也算跨域),只有同一个域名下,cookie才能保存session的生效。但是如果篡改成已登录的的sessionId,任意机器也能登录成功。
详解:分布式会话和基于TOKEN的分布式会话_xy294636185的博客-CSDN博客
登出功能实现:
@PostMapping("/logout")
public ResponseVo logout(HttpServletRequest request){
User user = (User) request.getSession().getAttribute(UserConst.CURRENT_USER);
if(user == null){
return ResponseVo.error(ResponseEnum.FAIL_NON_LOGIN);
}
request.getSession().removeAttribute(UserConst.CURRENT_USER);
return ResponseVo.success();
}
登录失效原因:
1.cookie中SessionId变化
2.服务器重启
3.session过期(如果不过期需要redis+token实现无限延期)
#配置session过期时间
server:
servlet:
session:
timeout: 60 #单位s,底层实现设置了最低限制,不能低于1min
优化:登录状态的统一判断-拦截器功能
两种方式实现:
1.Interceptor-Url(从http层面进行拦截)
public class UserLoginInterceptor implements HandlerInterceptor {
/**
* 在方法前拦截
* true表示继续流程,false表示中断
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
User user = (User) request.getSession().getAttribute(UserConst.CURRENT_USER);
if(user == null){
//返回方式1,不推荐
//response.getWriter().print("error");
//返回方式2,统一异常处理
throw new UserLoginException();
// return false;
}
return true;
}
// public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
// }
//
// public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
// }
}
//开启拦截
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
//这里可以写进配置类中统一配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserLoginInterceptor())
.addPathPatterns("/**") //默认拦截所有请求
.excludePathPatterns("/error","user/login","user/register"); //排除登录和注册接口
}
}
注意:不要拦截错误页面:"/error"
特殊异常处理
public class UserLoginException extends RuntimeException{
//这里什么都不用写,只需要类名,抛完一异常会被统一异常处理拦截
}
@ControllerAdvice
public class RunTimeExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
@ResponseStatus(HttpStatus.FORBIDDEN) //设置相应状态码
public ResponseVo handle(RuntimeException e) {
return ResponseVo.error(ResponseEnum.MASHINE_ERROR, e.getMessage());
}
//捕获异常
@ExceptionHandler(UserLoginException.class)
@ResponseBody
public ResponseVo userLoginHandle(){
return ResponseVo.error(ResponseEnum.FAIL_NON_LOGIN);
}
}
2.AOP-包名
带特殊用户版本:https://blog.csdn.net/xy294636185/article/details/119331342
简单版:
@Aspect
@Component
public class AuthVerifyAop {
@Value("${oauth.user_cache_key}")
private String userCacheKey;
@Pointcut("@annotation(com.fii.eodc.intellect.decistionbrain.config.auth.AuthApis)")
public void doOperation() {
}
@Before("doOperation()&&@annotation(authApis)")
public void before(JoinPoint point, AuthApis authApis) {
//获取请求路由
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
assert requestAttributes != null;
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
String requestURI = request.getRequestURI();
//获取用户信息
List<PermissionVO> permission = (List<PermissionVO>) request.getSession().getAttribute("permission");
if (permission != null && permission.size() > 0) {
//通过注解获取被访问接口可允许的权限ID
String[] value = authApis.value();
if (value.length == 0) {
//通过路由获取被访问接口可允许的权限ID
value = ApiAuthVerify.getResourceIds(requestURI);
}
if (value == null || value.length == 0) {
throw new GlobalException(Result.message(403, "unauthorized"));
}
}
throw new GlobalException(Result.message(403, "unauthorized"));
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface AuthApis {
String[] value() default {};
String desc() default "";
}
public enum ApiAuthVerify {
/*Demo*/
SYS_DEMO("/demo/getDemo",new String[]{"SYS_DAMO"}),
SYS_DEMO_SECOND("/demo/getDemo2",new String[]{"SYS_DEMO_SECOND"})
;
private String uri;
private String[] resourceIds;
ApiAuthVerify(String uri, String[] resourceIds){
this.uri = uri;
this.resourceIds = resourceIds;
}
public static String[] getResourceIds(String uri){
for (ApiAuthVerify verify : ApiAuthVerify.values()) {
if(Objects.equals(verify.uri,uri))
return verify.resourceIds;
}
return null;
}
public String getUri(){
return uri;
}
public String[] getResourceIds(){
return resourceIds;
}
}
@Api(tags = "demo")
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private DemoService demoService;
@ApiOperation(value = "demo",httpMethod = "GET",notes = "demo")
@GetMapping("/getDemo")
@AuthApis("SYS_DAMO_SECOND")
public Result<List<DemoVO>> getDemo(){
return Result.success(demoService.getDemo());
}
@ApiOperation(value = "demo2",httpMethod = "GET",notes = "demo2")
@GetMapping("/getDemo2")
@AuthApis("SYS_DAMO")
public Result<List<DemoVO>> getDemo2(){
return Result.success(demoService.getDemo2());
}
}