java不同版本特性示例

This commit is contained in:
maxf
2025-08-05 18:53:13 +08:00
parent 5c87319677
commit a4be814442
66 changed files with 1619 additions and 101 deletions

View File

@@ -0,0 +1,13 @@
package top.yexuejc.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Demo2Application {
public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}
}

View File

@@ -0,0 +1,43 @@
package top.yexuejc.demo.config;
import java.util.List;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
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 org.springframework.web.servlet.ModelAndView;
/**
* @author maxiaofeng
* @date 2025/7/3 18:27
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) {
log.error("参数校验异常: ", ex);
BindingResult result = ex.getBindingResult();
List<String> errorMessages = result.getAllErrors().stream().map(ObjectError::getDefaultMessage).toList();
if (request.getContentType() != null && request.getContentType().contains("application/json")) {
// 返回 JSON 错误信息
return new ResponseEntity<>(errorMessages, HttpStatus.BAD_REQUEST);
} else {
// 返回错误页面或重定向到原页面
ModelAndView modelAndView = new ModelAndView("login"); // 示例页面名
modelAndView.addObject("error", errorMessages);
return modelAndView;
}
}
}

View File

@@ -0,0 +1,39 @@
package top.yexuejc.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/home", "/login/**", "/register", "/css/**", "/js/**", "/images/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home")
.failureUrl("/login?error=true")
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout=true")
.permitAll()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,22 @@
package top.yexuejc.demo.config;
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
/**
* @author maxiaofeng
* @date 2025/7/4 18:28
*/
@Configuration
public class ThymeleafConfig {
@Bean
public SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.addDialect(new LayoutDialect());
engine.setTemplateResolver(templateResolver);
return engine;
}
}

View File

@@ -0,0 +1,22 @@
package top.yexuejc.demo.model;
import java.io.Serializable;
import jakarta.validation.constraints.NotBlank;
/**
* @author maxiaofeng
* @date 2025/7/3 16:39
*/
public record LoginVO(@NotBlank(message = "账号不能为空") String username,
@NotBlank(message = "密码不能为空") String password) implements Serializable {
}
//@Data
//public class LoginVO implements Serializable {
// @NotBlank
// private String username;
// private String password;
//}

View File

@@ -0,0 +1,18 @@
package top.yexuejc.demo.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* @author maxiaofeng
* @date 2025/7/3 18:43
*/
@Controller
public class IndexCtrl {
@RequestMapping("/")
public ModelAndView index() {
return new ModelAndView("index");
}
}

View File

@@ -0,0 +1,56 @@
package top.yexuejc.demo.web;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import top.yexuejc.demo.model.LoginVO;
/**
* @author maxiaofeng
* @date 2025/7/3 16:37
*/
@Controller
@RequestMapping("/login")
public class LoginCtrl {
private static final Logger log = LoggerFactory.getLogger(LoginCtrl.class);
@GetMapping("")
public ModelAndView page(ModelAndView mv, @RequestParam Map<String, Object> params) {
mv.addAllObjects(params);
mv.setViewName("login");
return mv;
}
@GetMapping("/get")
public String loginGet(@Valid LoginVO loginVO) throws JsonProcessingException {
log.info(new JsonMapper().writeValueAsString(loginVO));
return "redirect:/";
}
@PostMapping(value = "/postJson", consumes = MediaType.APPLICATION_JSON_VALUE)
public String loginPostJson(@RequestBody @Validated LoginVO loginVO) throws JsonProcessingException {
log.info(new JsonMapper().writeValueAsString(loginVO));
return "redirect:/";
}
@PostMapping(value = "/postFrom", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String loginPostFrom(@Validated LoginVO loginVO) throws JsonProcessingException {
log.info(new JsonMapper().writeValueAsString(loginVO));
return "redirect:/";
}
}

View File

@@ -0,0 +1,9 @@
spring.application.name=demo2
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html

View File

@@ -0,0 +1,127 @@
/**
*
* @author maxiaofeng
* @date 2025/7/4 17:33
*/
/* 移动设备样式 */
@media screen and (max-width: 760px) {
.ui-menu {
display: none;
}
.ui-main-container {
width: 100%;
display: flex;
flex-direction: column;
}
.ui-header {
height: 50px;
background-color: #14ccff50;
}
.ui-content {
flex: 1;
background: #d2fe8650;
}
}
/* 桌面设备样式 */
@media screen and (min-width: 760px) {
.ui-menu {
display: block;
width: 200px;
height: 100vh;
background-color: #834f4f50;
position: fixed; /* 固定位置 */
left: 0;
top: 0;
transition: width 0.3s ease-in-out;
overflow: hidden;
}
.ui-main-container {
margin-left: 200px; /* 给侧边栏留出空间 */
display: flex;
flex-direction: column;
height: 100vh; /* 占满整个视口高度 */
transition: width 0.3s ease-in-out;
}
.ui-content {
flex: 1;
background: #d2fe8650;
padding: 10px;
}
.ui-header {
height: 50px;
background-color: #14ccff50;
}
.h-container {
display: flex;
flex-direction: row; /* 横向排列 */
align-items: center; /* 垂直居中对齐 */
justify-content: space-between; /* 左右撑开 */
width: 100%; /* 确保占满宽度 */
height: 50px;
}
.h-icon-menu {
height: 20px;
margin: 0 10px;
}
.h-icon-menu-hide {
display: block;
}
.h-icon-menu-show {
display: none;
}
.h-sys-name {
font-size: 20px;
}
.h-right-container {
display: flex;
align-items: center;
margin-left: auto;
height: 100%;
}
.h-notice {
display: flex;
align-items: center;
}
.h-user-info {
display: flex;
align-items: center;
margin: 0 10px;
height: 100%;
}
.h-user-avatar {
height: 100%;
display: flex;
align-items: center;
}
.h-user-avatar img {
border-radius: 50%;
width: 40px;
height: 40px;
}
.h-user-name {
margin: 0 10px;
}
}
body {
margin: 0;
padding: 0;
overflow: hidden;
}

View File

@@ -0,0 +1,38 @@
body {
background-color: #f8f9fa;
}
.card {
border-radius: 10px;
border: none;
}
.card-title {
color: #495057;
font-weight: 600;
}
.btn-primary {
background-color: #0d6efd;
border: none;
padding: 10px;
}
.btn-primary:hover {
background-color: #0b5ed7;
}
.form-control {
padding: 12px;
border-radius: 5px;
}
.form-control:focus {
border-color: #86b7fe;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.alert {
border-radius: 5px;
padding: 10px 15px;
}

View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<body>
<div class="h-container">
<svg class="h-icon-menu h-icon-menu-show" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M11.776 899.072h1000.96v59.904H11.776zM12.8 65.024H1013.76v59.904H12.8zM10.24 473.088h636.416v59.904H10.24zM765.44 728.064l244.224-229.888-244.224-229.888z"></path>
</svg>
<svg class="h-icon-menu h-icon-menu-hide" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M11.264 64.512h1001.472v59.904H11.264zM10.24 899.072h1001.472v59.904H10.24zM376.32 490.496h636.928v59.904H376.32zM258.56 295.424l-244.736 230.4 244.736 229.888z"></path>
</svg>
<div class="h-sys-name">超级管理系统</div>
<div class="h-right-container">
<div class="h-notice">
<svg class="h-icon-menu" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M512 42.666667c63.637333 0 116.288 50.432 120.682667 114.005333 103.125333 46.058667 175.552 149.12 175.552 269.696v203.306667C867.477333 677.952 896 741.525333 896 820.458667c0 17.557333-15.36 32.896-32.917333 32.896H640l-0.085333 4.8A128 128 0 0 1 384 853.333333L160.917333 853.333333C143.36 853.333333 128 837.973333 128 820.437333c0-76.736 28.522667-142.506667 87.765333-190.741333v-203.306667c0-120.618667 72.426667-223.658667 175.552-269.717333C395.712 93.098667 448.362667 42.666667 512 42.666667z m64 810.688L448 853.333333l0.106667 3.754667A64 64 0 0 0 576 853.354667zM512 106.666667c-29.525333 0-54.741333 23.914667-56.832 54.421333l-2.645333 38.357333-35.114667 15.68c-83.072 37.077333-137.642667 119.104-137.642667 211.242667v233.749333l-23.594666 19.2c-35.136 28.608-55.744 65.173333-62.08 110.016h635.904l-1.066667-7.082666c-7.210667-41.962667-27.2-75.306667-61.098667-102.933334l-23.594666-19.2V426.368c0-92.16-54.570667-174.165333-137.642667-211.242667l-35.114667-15.68-2.645333-38.357333C566.741333 130.581333 541.525333 106.666667 512 106.666667z"></path>
</svg>
</div>
<div class="h-user-info">
<div class="h-user-name">maxiaofeng</div>
<div class="h-user-avatar">
<img src="https://picsum.photos/id/237/200/200" alt="">
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<title layout:title-pattern="${contentTitle} - 我的应用">首页</title>
<link rel="stylesheet" th:href="@{/css/common/main.css}" href="../static/css/common/main.css">
</head>
<body>
<div class="ui-menu" layout:insert="~{common/menu}">
</div>
<div class="ui-main-container">
<header class="ui-header">
<div layout:insert="~{common/header}">
<svg class="h-icon-menu h-icon-menu-hide" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M11.264 64.512h1001.472v59.904H11.264zM10.24 899.072h1001.472v59.904H10.24zM376.32 490.496h636.928v59.904H376.32zM258.56 295.424l-244.736 230.4 244.736 229.888z"></path>
</svg>
<svg class="h-icon-menu h-icon-menu-show" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M11.776 899.072h1000.96v59.904H11.776zM12.8 65.024H1013.76v59.904H12.8zM10.24 473.088h636.416v59.904H10.24zM765.44 728.064l244.224-229.888-244.224-229.888z"></path>
</svg>
</div>
</header>
<div class="ui-content" layout:fragment="content">
</div>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>$(document).ready(function () {
const $menu = $('.ui-menu');
const $mainContainer = $('.ui-main-container');
const $iconHide = $('.h-icon-menu-hide');
const $iconShow = $('.h-icon-menu-show');
// 初始状态:菜单显示
let isMenuVisible = true;
function updateLayout() {
if (isMenuVisible) {
$menu.css('width', '200px');
$mainContainer.css({
'margin-left': '200px',
'width': 'calc(100% - 200px)'
});
} else {
$menu.css('width', '0');
$mainContainer.css({
'margin-left': '0',
'width': '100%'
});
}
}
// 点击 hide 图标:隐藏菜单
$iconHide.on('click', function () {
isMenuVisible = false;
updateLayout();
$iconHide.hide();
$iconShow.show();
});
// 点击 show 图标:显示菜单
$iconShow.on('click', function () {
isMenuVisible = true;
updateLayout();
$iconShow.hide();
$iconHide.show();
});
});
</script>
</html>

View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - 我的应用</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link th:href="@{/css/login.css}" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center mt-5">
<div class="col-md-6 col-lg-4">
<div class="card shadow-sm">
<div class="card-body p-4">
<div class="text-center mb-4">
<h2 class="card-title">用户登录</h2>
</div>
<!-- 错误消息 -->
<div th:if="${error}" class="alert alert-danger" role="alert">
<span th:text="${error}"></span>
</div>
<!-- 注销消息 -->
<div th:if="${message}" class="alert alert-success" role="alert">
<span th:text="${message}"></span>
</div>
<!-- 登录表单 -->
<form th:action="@{/login/postFrom}" method="post">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username"
placeholder="请输入用户名" required autofocus>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password"
placeholder="请输入密码" required>
</div>
<div class="d-grid gap-2 mb-3">
<button type="submit" class="btn btn-primary">登录</button>
</div>
<div class="text-center">
<a th:href="@{/register}" class="text-decoration-none">注册新账户</a>
<span class="mx-2">|</span>
<a href="#" class="text-decoration-none">忘记密码?</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,13 @@
package top.yexuejc.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Demo2ApplicationTests {
@Test
void contextLoads() {
}
}