-
Spring Validation:表单数据验证
-
依赖:
<dependency> <groupId>jakarta.validation</groupId> <artifactId>jakarta.validation-api</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
-
注解
-
@NotNull
不允许为 null 对象
-
@AssertTrue
是否为 true
-
@Size
约定字符串的长度
-
@Min
字符串的最小长度
-
@Max
字符串的最大长度
-
@Email
是否是邮箱格式
-
@NotEmpty
不允许为null或者为空,可以用于判断字符串、集合,比如 Map、数组、List
-
@NotBlank
不允许为 null 和 空格
-
@NotEmpty(message = "名称不能为 null") private String name; @Min(value = 18, message = "你的年龄必须大于等于18岁") @Max(value = 150, message = "你的年龄必须小于等于150岁") private int age; @NotEmpty(message = "邮箱必须输入") @Email(message = "邮箱不正确") private String email;
-
执行校验
@PostMapping("/user/save") public String saveUser(@Valid User user, BindingResult errors) { if (errors.hasErrors()) { // 如果校验不通过,返回用户编辑页面 return "user/addUser"; } // 校验通过,返回成功页面 return "redirect:/user/list.html"; //redirect: 这个用于跳转到某一个页面网址,如果是同一个域名,你可以省略域名,直接写 path }
-
<form action="/user/save" th:object="${user}" method="POST"> <div th:classappend="${#fields.hasErrors('age')} ? 'error' : ''"> <label>年龄:</label> <input type="text" th:field="*{age}" /> <p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p> </div> </form> //th:object 用于替换对象,使用了这个就不需要每次都编写 user.xxx,可以直接操作 xxx 了 //th:classappend 用于给标签添加class,支持我们动态的管理样式 //th:errors 显示出错误信息(注解中的message属性值) //th:field 显示上一次输入的内容
-
-
Thymeleaf 布局(Layout):合并渲染HTML
-
片段
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="layout"> <div th:fragment="content"> //内容 </div> </html>
-
模板
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>布局</title> </head> <body> <header class="header"> //模板内容 </header> <div class="container" th:include="::content">页面正文内容</div>//插入片段的地方 <footer class="footer"> //模板内容 </footer> </body> </html>
-
-
迭代器:用于遍历集合对象
Iterator<Book> itr = books.iterator(); while (itr.hasNext()) { Book book = itr.next(); if (book.getId() == bookId) { itr.remove(); } }
-
Spring Boot ComponentScan
-
加了
@SpringBootApplication
注解的类是启动类,是整个系统的启动入口,Spring Boot
框架会默认扫描fm.douban.app
包(启动类所在的包)及其所有子包(fm.douban.app.*
、fm.douban.app.*.*
)进行解析。但fm.douban.service
、fm.douban.service.impl
不是fm.douban.app
的子包(平级),所以不会自动扫描,也不会自动实例化Bean
,自然不会实例化SongServiceImpl
。所以报错了 -
解决办法:为启动类的注解
@SpringBootApplication
加一个参数,告知系统需要额外扫描的包@SpringBootApplication(scanBasePackages={"fm.douban.app", "fm.douban.service"}) public class AppApplication { public static void main(String[] args) { SpringApplication.run(AppApplication.class, args); } }
-
如果不是
Spring Boot
启动类,可以使用独立的注解@ComponentScan
,作用也是一样的,用于指定多个需要额外自动扫描的包@ComponentScan({"fm.service", "fm.app"}) public class SpringConfiguration { ... ... }
-
使用
@RestController
的类,所有方法都不会渲染Thymeleaf
页面,而是都返回数据。等同于使用@Controller
的类的方法上添加@ResponseBody
注解,效果是一样的。
-
-
Spring Boot Logger
-
配置:修改
Spring Boot
系统的标准配置文件:application.properties
(在项目的src/main/resources/
目录下),增加日志级别配置:logging.level.root=info //表示所有日志(root)都为 info 级别。
logging.level.fm.douban.app=info //也可以为不同的的包定义不同的级别
logging.level.root=error意味着不输出更低优先级的 WARN、INFO、DEBUG 日志, 只输出ERROR 日志。
-
编码:配置完成后,编码很简单,只需要实例化日志对象即可打印日志了
public class SongListControl { private static final Logger LOG = LoggerFactory.getLogger(SongListControl.class); @PostConstruct public void init(){ LOG.info("SongListControl 启动啦"); } } //定义类变量 LOG 的语句属于固定写法,只需要在不同的类中,修改 getLogger() 方法参数为当前的类名即可。这样就能识别每段日志的来源。
-
-
Spring Boot Properties
-
配置文件格式:每一行是一条配置项:配置项名称=配置项值。
-
自定义配置项:
我们可以在 application.properties 配置文件中加入自定义的配置项。如: song.name=God is a girl 使用配置项 @Value("${song.name}") private String songName; 代码中使用配置项,application.properties 文件必须有配置,缺少了就会报错;但 application.properties 文件中的配置没有被代码使用,则没关系。就是说,多了没事,少了就报错 。
-
-
Cookie
-
读Cookie:
public Map index(HttpServletRequest request) { Map returnData = new HashMap(); returnData.put("result", "this is song list"); returnData.put("author", songAuthor); Cookie[] cookies = request.getCookies(); returnData.put("cookies", cookies); return returnData; }
-
使用注解读取 cookie:如果知道
cookie
的名字,就可以通过注解的方式读取,不需要再遍历cookie
数组了,更加方便。为
control
类的方法增加一个@CookieValue("xxxx") String xxxx
参数即可,注意使用时要填入正确的cookie
名字。 -
写Cookie
public Map index(HttpServletResponse response) { Map returnData = new HashMap(); returnData.put("result", "this is song list"); returnData.put("name", songName); Cookie cookie = new Cookie("sessionId","CookieTestInfo"); // 设置的是 cookie 的域名,就是会在哪个域名下生成 cookie 值 cookie.setDomain("youkeda.com"); // 是 cookie 的路径,一般就是写到 / ,不会写其他路径的 cookie.setPath("/"); // 设置cookie 的最大存活时间,-1 代表随浏览器的有效期,也就是浏览器关闭掉,这个 cookie 就失效了。 cookie.setMaxAge(-1); // 设置是否只能服务器修改,浏览器端不能修改,安全有保障 cookie.setHttpOnly(false); response.addCookie(cookie); returnData.put("message", "add cookie successfule"); return returnData; }
-
-
Spring Session
-
读取session
public Map index(HttpServletRequest request, HttpServletResponse response) { Map returnData = new HashMap(); returnData.put("result", "this is song list"); // 取得 HttpSession 对象 HttpSession session = request.getSession(); // 读取登录信息,因为getAttribute返回的是object对象所以要转化 UserLoginInfo userLoginInfo = (UserLoginInfo)session.getAttribute("userLoginInfo"); if (userLoginInfo == null) { // 未登录 returnData.put("loginInfo", "not login"); } else { // 已登录 returnData.put("loginInfo", "already login"); } return returnData; } //登录信息实例对象UserLoginInfo因为要在网络上传输,就必须实现序列化接口 Serializable ,否则不实现的话会报错。
-
写入session
public Map loginMock(HttpServletRequest request, HttpServletResponse response) { Map returnData = new HashMap(); // 假设对比用户名和密码成功 // 仅演示的登录信息对象 UserLoginInfo userLoginInfo = new UserLoginInfo(); userLoginInfo.setUserId("12334445576788"); userLoginInfo.setUserName("ZhangSan"); // 取得 HttpSession 对象 HttpSession session = request.getSession(); // 写入登录信息 session.setAttribute("userLoginInfo", userLoginInfo); returnData.put("message", "login successfule"); return returnData; }
-
Spring Session 配置
-
SpringBoot提供了编程式的配置方式,主要用于配置 Bean,方法是在类上添加
@Configuration
注解,就表示这是一个配置类,系统会自动扫描并处理。在方法上添加
@Bean
注解,表示把此方法返回的对象实例注册成Bean
。 -
session配置
-
先增加依赖
<!-- spring session 支持 --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-core</artifactId> </dependency>
-
在类上额外添加一个注解:
@EnableSpringHttpSession
,开启session
。然后,注册两个bean
:CookieSerializer
:读写 Cookies 中的 SessionId 信息MapSessionRepository
:Session 信息在服务器上的存储仓库。
-
@Configuration @EnableSpringHttpSession public class SpringHttpSessionConfig { @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setCookieName("JSESSIONID"); // 用正则表达式配置匹配的域名,可以兼容 localhost、127.0.0.1 等各种场景 serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); serializer.setCookiePath("/"); serializer.setUseHttpOnlyCookie(false); // 最大生命周期的单位是秒 serializer.setCookieMaxAge(24 * 60 * 60); return serializer; } // 当前存在内存中 @Bean public MapSessionRepository sessionRepository() { return new MapSessionRepository(new ConcurrentHashMap<>()); } }
-
-
-
-
Spring Request 拦截器
-
用处:用于统一处理相同逻辑。如在实际的项目中,会有大量的页面功能是需要判断用户是否登录的。例如电商的网站,订单、购物车、管理收货地址等等,都需要登录。那么让每一个页面都判断是否登录、未登录跳转到登录页,就太繁琐了,也不利于维护。
-
创建拦截器
public class Interceptor implements HandlerInterceptor { // Controller方法执行之前 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 只有返回true才会继续向下执行,返回false取消当前请求 return true; } //Controller方法执行之后 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } // 整个请求完成后(包括Thymeleaf渲染完毕) @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
-
配置拦截器
@Configuration public class WebAppConfigurer implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 多个拦截器组成一个拦截器链 // 仅演示,设置所有 url 都拦截 registry.addInterceptor(new Interceptor()).addPathPatterns("/**"); } } //也可以调用 excludePathPatterns() 方法排除某些 URL //常用 addPathPatterns("/**") 表示拦截所有的 URL
-
-
HttpServletRequest
提供了setAttribute()
、getAttribute()
方法,可以在当前的这次请求中设置和读取额外的属性数据 -
@RequestMapping
注解放在control
类上,表示为整个类的所有方法统一加上一个前缀如:
@RequestMapping(path="app")
-
页面跳转
String loginPageUrl = "/login"; response.sendRedirect(loginPageUrl);
-
MongoDB
-
安装:先安装Docker再安装MongoDB
sudo docker run -it --name mongo -p 27017:27017 -d mongo//安装 sudo docker exec -it mongo mongo admin//启动 db.createUser({ user:'admin',pwd:'123456',roles:[ { role:'root', db: 'admin'}]});//创建管理员账户 db.createUser({ user:'xxxx',pwd:'xxxxxx',roles:[{ role:'root', db: 'admin'},{ role:'dbAdmin', db: 'practice'}]});//创建读写账户 db.auth('admin', '123456')//登录账户 use 名字//创建并进入数据库 exit//退出 sudo docker ps//查看容器
-
依赖:可在创建工程时选中Spring Data MongoDB,如果是已经创建的工程,可以手动增加依赖库
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>
-
配置
# 购买的云服务器的公网 IP spring.data.mongodb.host= # MongoDB 服务的端口号 spring.data.mongodb.port=27017 # 创建的数据库及用户名和密码 spring.data.mongodb.database=practice spring.data.mongodb.username= spring.data.mongodb.password=
-
数据增删改查
-
对数据库的操作一定要放在
@Service
类中,而不是放在@Controller
类中;且@Controller
类可以调用@Service
类的方法 -
新增数据
@Autowired private MongoTemplate mongoTemplate; public void test() { Song song = new Song(); song.setId("001");//设置主键 song.setSubjectId("s001"); song.setLyrics("..."); song.setName("成都"); mongoTemplate.insert(song);
-
查询数据
//findById() 方法第 1 个参数就是主键 id,第 2 个参数是具体的类,写法是 类名.class mongoTemplate.findById(songId, Song.class) //多条件查询 //组合条件,根据或(or)、且(and)的关系进行组合,多个子条件对象组合成一个总条件对象: Criteria criteria = new Criteria(); criteria.orOperator(criteria1, criteria2);//criteria.andOperator(criteria1, criteria2); Query query = new Query(criteria); query.limit(10);//限定查询量 List<Song> songs = mongoTemplate.find(query, Song.class); /*orOperator()和andOperator()的参数,都可以输入多个子条件,也可以输入子条件数组 List<Criteria> subCris = new ArrayList(); criteria.andOperator(subCris.toArray(new Criteria[]{}));*/
-
修改数据
// 修改 id=1 的数据 //单一条件查询 Query query = new Query(Criteria.where("条件字段名").is("条件值")); // 把歌名修改为 “new name” Update updateData = new Update(); updateData.set("name", "new name"); // 执行修改,修改返回结果的是一个对象 UpdateResult result = mongoTemplate.updateFirst(query, updateData, Song.class); // 修改的记录数大于 0 ,表示修改成功 System.out.println("修改的数据记录数量:" + result.getModifiedCount());
-
删除数据
Song song = new Song(); song.setId(songId); // 执行删除 DeleteResult result = mongoTemplate.remove(song);//用对象来表示查询条件。作为服务接口,使用对象做参数,具备比较好的扩展性和兼容性。 // 删除的记录数大于 0 ,表示删除成功 System.out.println("删除的数据记录数量:" + result.getDeletedCount()); //创建一个对象并设置好属性值,作为删除的条件,符合条件的数据都将被删除。可以设置更多的属性值来提高精确性,但通过主键来删除数据,是保证不误删的一个比较好的办法。
-
分页查询
Pageable pageable = PageRequest.of(0 , 20);//PageRequest.of() 方法第一个参数是页码,注意从 0 开始计数,第一页的值是 0 ;第二个参数是每页的数量。 query.with(pageable); // 总数 long count = mongoTemplate.count(query, Song.class); // 根据结果、分页条件、总数三个数据,构建分页器对象。 Page<Song> pageResult = PageableExecutionUtils.getPage(songs, pageable, new LongSupplier() { @Override public long getAsLong() { return count; } });
-
-