maven项目
xxxxxxxxxx
61<!---所有springboot项目都必须继承自spring-boot-starter-parent -->
2<parent>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-parent</artifactId>
5 <version>3.0.5</version>
6</parent>
web场景依赖
xxxxxxxxxx
61<dependencies>
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-web</artifactId>
5 </dependency>
6</dependencies>
xxxxxxxxxx
61
2public class demo01 {
3 public static void main(String[] args) {
4 SpringApplication.run(demo01.class, args);
5 }
6}
xxxxxxxxxx
121package com.siyueqi.controller;
2
3import org.springframework.web.bind.annotation.GetMapping;
4import org.springframework.web.bind.annotation.RestController;
5
6
7public class HelloController {
8 "/hello") (
9 public String hello(){
10 return "<h1>hello,im springboot3</h1>";
11 }
12}
默认访问localhost:8080
SpringBoot打包插件加载:
xxxxxxxxxx
81<build>
2 <plugins>
3 <plugin>
4 <groupId>org.springframework.boot</groupId>
5 <artifactId>spring-boot-maven-plugin</artifactId>
6 </plugin>
7 </plugins>
8</build>
打包命令:mvn clean package
把项目打包为一个可执行jar包
启动命令:java -jar xxx.jar
启动项目
在实际的Spring项目中,通常会采用分层架构来组织代码,这种架构将代码分为Mapper
、Service
和Controller
三层,每一层都有明确的职责。
作用:负责与数据库进行交互,执行SQL语句,完成数据的增删改查操作。
示例:在Spring项目中,通常使用MyBatis作为ORM框架,Mapper
接口定义了数据库操作的方法。
作用:负责处理业务逻辑,调用Mapper
层完成数据操作,并将结果返回给Controller
层。
示例:Service
层通常会注入Mapper
层的Bean。
作用:负责接收用户请求,调用Service
层处理业务逻辑,并将结果返回给客户端(可以是HTML页面、JSON数据等)。
示例:Controller
层通常会注入Service
层的Bean。
导入相关的场景,拥有相关的功能。即拥有场景启动器:
官方提供的场景:命名为:spring-boot-starter-*
第三方提供的场景:命名为:thirdpartyproject*-spring-boot-starter
无需编写任何配置,可以直接开发业务
properties优先级大于yml
application.properties
:集中式管理配置文件
xxxxxxxxxx
21server.port = 8080; #端口号
2server.path = /index #路径
yaml
:树形集中式管理配置文件(缩进隔离)
xxxxxxxxxx
41server
2 port:8080
3 servlet
4 context-path:/index
yml
获取配置信息例如邮箱发送的yml配置信息如下:
xxxxxxxxxx
91email
2 user 2312855581@qq.com
3 code jfesdjawdksa
4 host stmp.qq.com
5 auth ture
6 #下面是数组类型
7 time
8 -2025年
9 -3月
java中获取配置信息:
@Value("${键名}")
实现方式:
xxxxxxxxxx
81public class EmailProperties{
2 ("${email.user}")
3 public String user;
4 ("${email.code}")
5 public String code;
6 ("${email.host}")
7 public String host;
8}
@ConfigurationProperties(prefix="前缀")
实体类的成员变量名需与配置文件中的键名保持一致
实现方式:
xxxxxxxxxx
61prefix='email') (
2public class EmailProperties{
3 public String user;
4 public String code;
5 public String host;
6}
linux服务器上只需要有java环境即可
修改配置(外部配置application.properties)、监控、健康检查。
一键创建整个项目结构
思考: 1、为什么导入 starter-web 所有相关依赖都导入进来? 开发什么场景,导入什么场景启动器。 maven依赖传递原则。A-B-C:A就拥有B和C 导入 场景启动器。 场景启动器 自动把这个场景的所有核心依赖全部导入进来 2、为什么版本号都不用写? 每个boot项目都有一个父项目 spring-boot-starter-parent parent的父项目是spring-boot-dependencies 父项目 版本仲裁中心,把所有常见的jar的依赖版本都声明好了。 比如:mysql-connector-j 3、自定义版本号
利用maven的就近原则
直接在当前项目 properties
标签中声明父项目用的版本属性的 key
直接在dependcy中设置version
标签版本号
自动配置好了
Boot工程默认扫描启动类的包及其子包下的内容
额外扫描:@ComponentScan(base-package="come.siyueqi")
spring中用:
标签:<context:component-scan base-package="com.siyeuqi">
注解:@ComponentScan(base-package="come.siyueqi")
注解 | 说明 | 位置 |
---|---|---|
@Component | 声明bean的基础注解 | 不属于以下三类时,用此注解 |
@Controller | @Component的衍生注解 | 标注在控制器类上 |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(用于与mybatis整合,用的少) |
若注册的bean对象来自于第三方,则无法使用以上的注解声明bean:
用@Bean
或@Import
@Configuration
Spring配置类:
@SpringBootConfiguration
SpringBoot配置类
@Bean
替代Bean标签,组件在Ioc中的名字即为方法名或@Bean("name")
@Scope
替代Scope标签,设置实例为单实例或多实例
@Conditional
-->@ConditionalOnProperty@
配置文件中有指定信息则注入,否则不注入
-->@ConditionalOnMissingBean@
Ioc容器中有xxx.class则不注入,否则注入
-->@ConditionnalOnClass@
当前启动器环境中有某个类则注入,否则不注入
明确需求-->阅读接口文档-->思路分析-->开发-->测试
起步依赖:
xxxxxxxxxx
41<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-validation</artifactId>
4</dependency>
在需要进行校验的参数前添加@Pattern
,在Controller类添加@Validated
xxxxxxxxxx
61
2public class UserController {
3
4 private UserService userService;
5 "/register") (
6 public Result register( (regexp = "^\\S{5,16}$")String username, (regexp = "^\\S{5,16}$") String password){}
xxxxxxxxxx
81
2public class GlobalExceptionHandler{
3 Exception.class) (
4 public Result handleException(Exception e){
5 e.printStackTrace();
6 return Result.error(StringUtils.hasLength(e.getMessage())?e.getMessage():"操作失败");
7 }
8}
其中SpringBoot
拥有一个StringUtils.hasLength
方法判断e.getMessage()是否有返回值,若无则会返回设定的操作失败.
StringUtils.hasLength(e.getMessage())
?e.getMessage():"操作失败"
<--操作符
登录令牌-Jwt/Session/JId
承载业务数据,减少后续请求查询数据库的次数
防止篡改,保证信息的合法性和有效性
组成:
第一部分:Header 记录令牌类型,签名算法
第二部分:Payload 携带自定义信息,默认信息
第三部分:Signature 防止Token被篡改,确保安全性
xxxxxxxxxx
51<dependency>
2 <groupld>com.auth0</groupld>
3 <artifactld>java-jwt</artifactld>
4 <version>4.4.0</version>
5</dependency>
生成测试:
xxxxxxxxxx
101public void testGen(){
2 Map<String,Object> claims = new HashMap<>();
3 claims.put("id",1);
4 claims.put("username","张三");
5 String token = JWT.create()
6 .withClaim("user",claims)//添加载荷
7 .withExpiresAt(new Date(System.currentTimeMillis()+1000*60*60*24))//添加过期时间
8 .sign(Algorithm.HMAC256("siyueqi"));//指定算法配置密钥
9 System.out.println(token);
10 }
验证测试:
xxxxxxxxxx
111public void testParse(){
2 //模拟传输token
3 String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
4 "eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6IuW8oOS4iSJ9LCJleHAiOjE3NDM1Nzk0NTR9." +
5 "ayIvs8LfvbvlO4cAEVODfJUJlYmfWZZ7CKEoYg7riBc";
6
7 JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("siyueqi")).build();
8 DecodedJWT decodedJWT = jwtVerifier.verify(token);//验证token返回解析jwt对象
9 Map<String,Claim> claims = decodedJWT.getClaims();
10 System.out.println(claims.get("user"));
11 }
实现:
xxxxxxxxxx
151
2public class LoginInterceptor implements HandlerInterceptor {
3
4 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
5 //令牌验证
6 String token = request.getHeader("Authorization");
7 try{
8 Map<String,Object> claims = JwtUtil.parseToken(token);
9 return true;
10 } catch (Exception e) {
11 response.setStatus(401);
12 return false;
13 }
14 }
15}
拦截器会对token进行认证,随后再放行给各个servlet
提供线程局部变量,有set(),get()方法.ThreadLocal存储数据线程安全(非手动锁)
Lambda表达式可以实现一个匿名接口函数式类方法,简化new过程
xxxxxxxxxx
161public class ThreadLocalUtil {
2 //提供ThreadLocal对象,
3 private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
4 //根据键获取值
5 public static <T> T get(){
6 return (T) THREAD_LOCAL.get();
7 }
8 //存储键值对
9 public static void set(Object value){
10 THREAD_LOCAL.set(value);
11 }
12 //清除ThreadLocal 防止内存泄漏
13 public static void remove(){
14 THREAD_LOCAL.remove();
15 }
16}
remove可以放在拦截器中的aftercompletion
方法中,避免内存泄漏
@RequestBody的作用是将前端返回的json类型数据解析为java实体类
Validation的分组校验接口
定义分组:在实体类内部定义接口
校验项分组方式:通过groups属性指定
校验时指定分组:@Validated注解中的value属性赋值
校验项默认属于default分组,支持default默认分组,支持校验项接口继承
自定义注解state
自定义校验数据的类StateValidation实现ConstraintValidation接口
State.java(anno)
xxxxxxxxxx
121//元注解
2FIELD)//元注解 (
3RetentionPolicy.RUNTIME)//元注解 (
4validatedBy = {StateValidation.class})//指定注解校验规则 (
5
6public @interface State {
7 String message() default "state参数的值只能是已发布或草稿";//提供校验失败信息
8
9 Class<?>[] groups() default {};//指定分组
10
11 Class<? extends Payload>[] payload() default {};
12}
StateValidation.java
xxxxxxxxxx
171public class StateValidation implements ConstraintValidator<State, String>{
2 /*
3 * @param s 要校验的数据
4 * @param context
5 * @return 返回false校验不通过
6 */
7
8 public boolean isValid(String s, ConstraintValidatorContext context) { //给哪个注解提供校验规则 |校验的数据类型
9 if (s == null) {
10 return false;
11 }
12 if(s.equals("已发布")||s.equals("草稿")){
13 return true;
14 }
15 return false;
16 }
17}
article.java
xxxxxxxxxx
21
2 private String state;//发布状态 已发布|草稿
ArticleController.java
xxxxxxxxxx
41 public Result add( Article article) {
2 articleService.add(article);
3 return Result.success();
4 }
Springboot约定大于配置
xml文件项目配置:
重点 | 要求 | 举例 |
---|---|---|
1. namespace | 必须和接口全限定名 一字不差(大小写、包名、单词顺序) | <mapper namespace="com.siyueqi.bigevents.mapper.ArticleMapper"> |
2. id | 必须和接口方法名 完全对应 | <select id="getList" ...> |
3. parameterType / resultType | 要么省略,要么写对;动态 SQL 通常省略 | resultType="com.siyueqi.bigevents.pojo.Article" |
ArticleServiceimpl.java
xxxxxxxxxx
161 public PageBean<Article> getList(Integer pageNum, Integer pageSize, String categroyId, String state) {
2 //创建PageBean对象 --> 封装查询好的数据
3 PageBean<Article> pb = new PageBean<>();
4 //开启分页查询 --> PageHelper(Mybatis库)
5 PageHelper.startPage(pageNum, pageSize);
6 //调用mapper查询
7 Map<String,Object> map = ThreadLocalUtil.get();
8 Integer userId = (Integer) map.get("id");
9 List<Article> ls = articleMapper.getList(userId,categroyId,state);
10 //Page中包含了获取PageHelper分页查询后得到的总记录和当前页数据
11 Page<Article> p = (Page<Article>) ls;
12 //把数据填充到PageBean对象中
13 pb.setTotal(p.getTotal());
14 pb.setItems(p.getResult());
15 return pb;
16 }
ArticleController.java
xxxxxxxxxx
91
2 public Result<PageBean<Article>> list(Integer pageNum ,
3 Integer pageSize,
4 required = false) String categroyId, (
5 required = false) String state) (
6 {
7 PageBean<Article> pb = articleService.getList(pageNum, pageSize, categroyId, state);
8 return Result.success(pb);
9 }
ArticleMapper.xml
xxxxxxxxxx
151<mapper namespace="com.siyueqi.bigevents.mapper.ArticleMapper">
2<!--动态sql-->
3 <select id="getList" resultType="com.siyueqi.bigevents.pojo.Article">
4 SELECT * FROM article
5 <where>
6 <if test="categoryId != null">
7 category_id = #{categoryId}
8 </if>
9 <if test="state != null">
10 AND state = #{state}
11 </if>
12 AND create_user = #{userId}
13 </where>
14 </select>
15</mapper>
ArticleController.java
xxxxxxxxxx
61//获取文章详情
2"/detail") (
3public Result<Article> detail(Integer id){
4Result<Article> cont = articleService.getDetail(id);
5return cont;
6}
ArticleService.java
xxxxxxxxxx
11Result<Article> getDetail(Integer id);
ArticleServiceimpl.java
xxxxxxxxxx
81public Result<Article> getDetail(Integer id) {
2//查询文章内容mapper
3Article cont = articleMapper.getDetail(id);
4if(cont == null){
5return Result.error("404,文章不存在");
6}
7return Result.success(cont);
8}
ArticleMapper.java
xxxxxxxxxx
31//获取文章详情
2"SELECT * FROM article WHERE id = #{id}") (
3Article getDetail(Integer id);
Article Controller.java
xxxxxxxxxx
61//更新文章
2
3public Result update( Article article) {
4articleService.updateArticle(article);
5return Result.success();
6}
注意:@RequestBody只能绑定一个对象。
@RequestBody
的作用是:将整个请求体(JSON、XML 等)反序列化为一个 Java 对象。
✅ 你可以用一个类来接收多个字段(推荐方式)。
✅ 也可以用一个Map<String, Object> 来接收任意字段。
ArticleService.java
xxxxxxxxxx
11void updateArticle(Article article);
ArticleServiceimpl.java
xxxxxxxxxx
61
2public void updateArticle(Article dto) {
3Article article = new Article();
4BeanUtils.copyProperties(dto, article); // 或手动 set
5articleMapper.updateById(article);
6}
xxxxxxxxxx
31public void updateArticle(Article article) {
2articleMapper.updateById(article);
3}
差异点 第一段 第二段 1. 调用对象 直接把入参 article
传给updateById
先 new Article()
,再把属性拷贝过去,用新的arti
去更新2. 实际效果 与第二段等价,因为两变量指向同一个对象 与第一段等价,因为 arti
与article
内容相同3. 额外开销 无 多一次 new
与BeanUtils.copyProperties
的反射/拷贝4. 潜在风险 若调用方后续继续使用 article
对象,可能误把未设置的字段当成 null 去处理业务拷贝后原对象与持久化对象隔离,后续对 article
的修改不会影响数据库5. 代码意图 简洁、直接,暗示调用方已准备好完整实体 显式“防御式拷贝”,防止调用方误改或隐藏字段覆盖
1.新建一个只包含可修改字段的 DTO:
xxxxxxxxxx
71
2public class ArticleUpdateDTO {
3 private Long id; // 主键必须有
4 private String title;
5 private String summary;
6 // 只放调用方允许改的字段
7}
2.Controller 直接接收这个 DTO,再调用 Service:
xxxxxxxxxx
41"/article/update") (
2public void update( ArticleUpdateDTO dto) {
3 articleService.updateArticle(dto);
4}
3.Service 里用 MyBatis-Plus 的 updateById
(字段策略 not_null
时最安全):
xxxxxxxxxx
81public void updateArticle(ArticleUpdateDTO dto) {
2 Article article = new Article();
3 article.setId(dto.getId());
4 article.setTitle(dto.getTitle());
5 article.setSummary(dto.getSummary());
6 // 其余字段不 set,就不会更新
7 articleMapper.updateById(article);
8}
优点:
不需要 BeanUtils;
前端/调用方只能传 DTO 里声明的字段,天然防误传;
代码清晰、易维护。
ArticleController.java
xxxxxxxxxx
512public Result delete( Integer id) {
3articleService.deleteArticle(id);
4return Result.success();
5}
ArticleService.java
xxxxxxxxxx
11void deleteArticle(Integer id);
ArticleServiceimpl.java
xxxxxxxxxx
41
2public void deleteArticle(Integer id) {
3articleMapper.deleteArticle(id);
4}
ArticleMapper.java
xxxxxxxxxx
31//删除文章
2"DELETE FROM article WHERE id = #{id}") (
3void deleteArticle(Integer id);
FileUploadController.java
xxxxxxxxxx
1112public class FileUpLoadController {
3"/upload") (
4public Result<String> upload(MultipartFile file) throws IOException {
5//文件内容存储在本地
6String Ofilename = file.getOriginalFilename();
7String filename = UUID.randomUUID().toString()+Ofilename.substring(Ofilename.lastIndexOf("."));
8file.transferTo(new File("D:\\JavaCode\\files\\"+Ofilename+filename));
9return Result.success("url地址");
10}
11}
通过UUID将图片唯一化,不容易被覆盖/重写
AK:
xxxxxxxxxx
11jSbo2ro5bDbKF13uBJWv3dUUbcyrlnIQdi56H5K6
SK:
xxxxxxxxxx
11x7K9IClIq-sR_VOBDgEdtatY70EA2xyJRLcFHEpB
登录成功获得token->服务器redis储存token->拦截器验证客户端发送的token是否与服务器redis储存的token相同
Vue3+Element
必须在script标签中且属性为module
导入方式:import {模块name} from './.js文件路径'
export {函数名1,函数名2,...,函数名n}
函数名 as 别名
例如 simplename as sn
export default {函数1,函数2,...,函数n}
导出后不需要特别指出函数名
列表渲染,遍历容器的元素或者对象的属性
v-for = "(item,index) in items"
itmes为遍历的数组容器
item为遍历出的元素
index为索引
遍历的数组需要定义在data中,v-for在需要的标签中进行属性引用
xxxxxxxxxx
101<tr v-for="(article,index) in articleList">
2 <td>{{article.title}}</td>
3 <td>{{article.category}}</td>
4 <td>{{article.time}}</td>
5 <td>{{article.state}}</td>
6 <td>
7 <button>编辑</button>
8 <button>删除</button>
9 </td>
10 </tr>
动态为Html标签绑定属性值
v-bind:属性名="属性值"
--> :属性名="属性值"
xxxxxxxxxx
31<div id="app">
2 <a :href="url">官网</a>
3</div>
绑定的属性值需要定义在data中,v-bind在需要的标签中进行属性引用
用来控制元素的显示与隐藏
v-if/show="表达式" true时显示,false时隐藏
特殊点:
v-if基于条件判断来控制创建或移除元素节点(条件渲染)
v-show基于css样式display来控制与隐藏
v-if:要么显示,要么不显示,不需要频繁切换的场景
v-show:频繁切换显示隐藏的场景
为Html标签绑定事件
v-on:事件名="函数名"
-->@事件名="函数名"
xxxxxxxxxx
151 <div id="app">
2 <button @click="money">点我有惊喜</button>
3 <button @click="m0ney">再点更惊喜</button>
4 </div>
5 createApp({
6 methods:{
7 money: function(){
8 alert("b1")
9 },
10 m0ney: function(){
11 alert("b2")@
12 }
13 }
14 }).mount("#app");//控制html元素
15
在表单元素上使用,双向数据绑定,获取或设置表单项数据
v-model="变量名"
绑定的属性值需要定义在data中,v-bind在需要的标签中进行属性引用
xxxxxxxxxx
61文章分类: <input type="text" v-model="searchConditions.category"/>
2发布状态: <input type="text" v-model="searchConditions.state"/>
3 searchConditions:{
4 category:"",
5 state:""
6 }
生命周期:指一个对象从创建到销毁的整个过程
八个阶段:每个阶段会自动执行一个生命周期方法(钩子),让开发者可以在特定阶段执行代码(面向切面
)
状态 | 阶段周期 |
---|---|
beforeCreate | 创建前 |
created | 创建后 |
beforeMount | 载入前 |
mounted | 挂载完成 |
beforeUpdate | 数据更新前 |
updated | 数据更新后 |
beforeUnmount | 组件销毁前 |
unmounted | 组件销毁后 |
重点:mounted
axios.请求方式(url[],data[],config[])
xxxxxxxxxx
151<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
2<script>
3let article = {
4 title:'123',
5 category:'1',
6 time:'2000-1-1',
7 state:'已发布'
8 }
9 axios.post('http://localhost:8080/article/add',article).then(result=>{
10 console.log(result.data);
11 }).catch(err=>{
12 console.log(err)
13 })
14</script>
15//.then()成功回调 .catch()失败回调
完整写法:
xxxxxxxxxx
71axios({
2 method:'post',
3 url:'http://localhost:8080/article/add',
4 data:article
5 }).then(result=>{
6 console.log(result.data)
7 })
xxxxxxxxxx
791
2<html lang="zh">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8</head>
9
10<body>
11 <div id="app">
12
13 文章分类: <input type="text" v-model="searchConditions.category">
14 发布状态: <input type="text" v-model="searchConditions.state">
15
16 <button @click="search">搜索</button>
17
18 <br />
19 <br />
20 <table border="1 solid" colspa="0" cellspacing="0">
21 <tr>
22 <th>文章标题</th>
23 <th>分类</th>
24 <th>发表时间</th>
25 <th>状态</th>
26 <th>操作</th>
27 </tr>
28 <tr v-for="(article,index) in articleList">
29 <td>{{article.title}}</td>
30 <td>{{article.category}}</td>
31 <td>{{article.time}}</td>
32 <td>{{article.state}}</td>
33 <td>
34 <button>编辑</button>
35 <button>删除</button>
36 </td>
37 </tr>
38 </table>
39 </div>
40
41 <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
42 <script type="module">
43 import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
44 createApp({
45 data(){
46 return{
47 articleList:[],
48 searchConditions:{
49 category:'',
50 state:''
51 }
52 }
53 },
54 methods:{
55 search:function(){
56 //发送请求完成搜索 携带搜索条件
57 axios.get('http://localhost:8080/article/search?category='+this.searchConditions.category+'&state='+this.searchConditions.state).then(
58 result=>{
59 this.articleList = result.data
60 }
61 ).catch(
62 err=>{
63 console.log(err)
64 }
65 )
66 }
67 },
68 mounted:function(){
69 axios.get('http://localhost:8080/article/getAll').then(result=>{
70 this.articleList=result.data
71 }).catch(err=>{
72 console.log(err)
73 })
74 }
75
76 }).mount("#app")
77 </script>
78</body>
79</html>