ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

web基础-前后端交互(不涉及数据库访问)

web基础-前后端交互(不涉及数据库访问) 1、web的程序架构2、后端响应前端的响应状态码3、分层解耦-IOC控制反转、DI依赖注入IOC详解DI详解4、web 程序案例不涉及数据交互1user.html 前端代码!DOCTYPE html html langzh-CN head !-- 设置字符编码为UTF-8支持中文显示 -- meta charsetUTF-8 !-- 设置视口适配移动端设备 -- meta nameviewport contentwidthdevice-width, initial-scale1.0 !-- 页面标题 -- title智能学习辅助系统/title style /* 全局样式重置 */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Microsoft YaHei, Arial, sans-serif; background-color: #f5f5f5; display: flex; flex-direction: column; min-height: 100vh; } /* 第一部分顶部导航栏 */ .header { display: flex; justify-content: space-between; align-items: center; background-color: #d0d0d0; padding: 15px 20px; } .header .title { font-size: 28px; font-weight: bold; color: #333; } .header .logout { color: #555; text-decoration: none; font-size: 16px; padding: 8px 16px; border-radius: 4px; transition: all 0.3s ease; } .header .logout:hover { background-color: #ff4d4f; color: #fff; box-shadow: 0 2px 6px rgba(255, 77, 79, 0.4); } /* 第二部分搜索表单区域 */ .search-form { padding: 20px; background-color: #fff; border-bottom: 1px solid #e0e0e0; } .search-form form { display: flex; align-items: center; gap: 15px; flex-wrap: wrap; } .search-form label { font-size: 14px; color: #555; margin-right: 5px; } .search-form input[typetext], .search-form select { height: 32px; padding: 0 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; min-width: 120px; } .search-form input[typetext]:focus, .search-form select:focus { border-color: #4a90d9; outline: none; } .search-form button { height: 32px; padding: 0 20px; font-size: 14px; border-radius: 4px; border: 1px solid #ccc; cursor: pointer; transition: all 0.3s; } .btn-primary { background-color: #1890ff; color: #fff; border-color: #1890ff; } .btn-primary:hover { background-color: #40a9ff; box-shadow: 0 2px 8px rgba(24, 144, 255, 0.4); } .btn-default { background-color: #fff; color: #666; border: 1px solid #d9d9d9; } .btn-default:hover { color: #1890ff; border-color: #1890ff; } /* 第三部分表格展示区 */ .main-content { flex: 1; } .table-container { margin: 20px; background-color: #fff; border-radius: 6px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); padding: 20px; } .table-container table { width: 100%; border-collapse: collapse; font-size: 14px; } .table-container thead { background-color: #f0f5fa; } .table-container thead th { padding: 12px 15px; text-align: left; font-weight: bold; color: #333; border-bottom: 2px solid #4a90d9; white-space: nowrap; } .table-container tbody td { padding: 12px 15px; border-bottom: 1px solid #e8e8e8; color: #555; vertical-align: middle; } /* 偶数行浅灰色背景 */ .table-container tbody tr:nth-child(even) { background-color: #f5f7fa; } .table-container tbody tr:hover { background-color: #e6f7ff; } .table-container .avatar { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; border: 2px solid #e0e0e0; } /* 操作按钮通用样式 */ .table-container .btn { border: none; padding: 5px 12px; font-size: 12px; border-radius: 4px; cursor: pointer; transition: all 0.3s; margin-right: 8px; } .table-container .btn-edit { background-color: #52c41a; color: #fff; } .table-container .btn-edit:hover { background-color: #73d13d; box-shadow: 0 2px 6px rgba(82, 196, 26, 0.4); } .table-container .btn-delete { background-color: #ff4d4f; color: #fff; } .table-container .btn-delete:hover { background-color: #ff7875; box-shadow: 0 2px 6px rgba(255, 77, 79, 0.4); } /* 性别标签样式 */ .gender-tag { display: inline-block; padding: 2px 10px; border-radius: 10px; font-size: 12px; } .gender-male { background-color: #e6f3ff; color: #4a90d9; } .gender-female { background-color: #fff0f0; color: #e74c3c; } /* 职位标签样式 */ .position-tag { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 12px; background-color: #f0f5ff; color: #2f54eb; } /* 空数据提示样式 */ .empty-tip { text-align: center; padding: 40px; color: #999; font-size: 16px; } /* 第四部分页脚版权区域 */ .footer { background-color: #555; color: #fff; text-align: center; padding: 20px 0; line-height: 1.8; font-size: 13px; } .footer .company-name { font-size: 14px; letter-spacing: 1px; } .footer .copyright { font-size: 12px; opacity: 0.8; margin-top: 5px; } /style /head body !-- 第一部分顶部导航栏 -- div classheader h1 classtitle智能学习辅助系统/h1 a href# classlogout退出登录/a /div !-- 主内容区域 -- !-- ⚠️ 将 idapp 提升到 main-content使搜索表单和表格都在 Vue 管理范围内 -- div classmain-content idapp !-- 第二部分搜索表单区域 -- div classsearch-form !-- submit.prevent 阻止表单默认提交行为改为调用 handleSearch -- form submit.preventhandleSearch !-- 使用 v-model 双向绑定搜索关键字双向绑定即数据模型与视图同步更新 -- label forname姓名:/label input typetext idname namename v-modelsearchName placeholder请输入姓名 label forgender性别:/label select idgender namegender v-modelsearchGender option value请选择/option option value1男/option option value2女/option /select label forposition职位:/label select idposition nameposition v-modelsearchPosition option value请选择/option option value1班主任/option option value2讲师/option option value3学工主管/option option value4教研主管/option option value5咨询师/option /select !-- 查询按钮typesubmit 触发表单提交由 submit.prevent 拦截并调用 handleSearch -- button typesubmit classbtn-primary查询/button !-- 清空按钮typebutton 避免触发表单提交调用 handleReset -- button typereset classbtn-default clickhandleReset清空/button /form /div !-- 第三部分表格展示区Vue挂载区域 -- div classtable-container table !-- 表头 -- thead tr th序号/th th姓名/th th性别/th th头像/th th职位/th th入职日期/th th最后操作时间/th th操作/th /tr /thead !-- 表体使用 v-for 循环渲染响应式数据 -- tbody !-- 空数据提示当列表为空时显示 -- tr v-ifempList.length 0 td colspan8 classempty-tip暂无数据/td /tr tr v-for(item, index) in empList :keyitem.id !-- 序号索引1 -- td{{ index 1 }}/td !-- 姓名 -- td{{ item.name }}/td !-- 头像使用 :src 动态绑定 -- td img :srcitem.image :altitem.image classavatar /td !-- 性别动态绑定class和文字 -- td span :class[gender-tag, item.gender 1 ? gender-male : gender-female] {{ item.gender 1 ? 男 : 女 }} /span /td !-- 职位映射为文字 -- td span classposition-tag{{ getPositionText(item.job) }}/span /td !-- 入职日期 -- td{{ item.entrydate }}/td !-- 最后操作时间 -- td{{ item.updatetime }}/td !-- 操作按钮 -- td button classbtn btn-edit clickhandleEdit(item)编辑/button button classbtn btn-delete clickhandleDelete(item)删除/button /td /tr /tbody /table /div /div !-- 第四部分页脚版权区域 -- div classfooter p classcompany-name科技股份有限公司/p p classcopyright版权所有 Copyright 2006-2024 All Rights Reserved/p /div !-- 引入 axios -- script srchttps://unpkg.com/axios/dist/axios.min.js/script !-- Vue 3 脚本使用 ref 实现响应式 -- script typemodule // 引入 createApp 和 ref //ref 是 Vue 3 组合式 API 的核心它让数据具备响应式追踪能力任何对 .value 的修改都会触发视图重新渲染。 import { createApp, ref, onMounted } from https://unpkg.com/vue3/dist/vue.esm-browser.js createApp({ setup() { // 职位映射表 const positionMap { 1: 班主任, 2: 讲师, 3: 学工主管, 4: 教研主管, 5: 咨询师 } // 原始完整数据不会被修改作为查询的数据源 // 使用 ref 创建响应式数组 // ref() 将数据包裹为响应式引用 // 在 JS 中通过 .value 访问在模板中自动解包无需 .value // 表格展示的数据查询后会被筛选赋值 const empList ref([]) // 搜索条件姓名v-model 双向绑定 const searchName ref() // 搜索条件性别v-model 双向绑定 const searchGender ref() // 搜索条件职位v-model 双向绑定 const searchPosition ref() /** * 查询功能 * 查询员工列表 * param {*} params 查询参数 * returns {Promise} 包含员工列表的 Promise * throws {Error} 如果查询失败 * 标记为异步函数只有加了它函数内部才能使用 await它和传统写法完全等价 * async function getEmpList() { ... } * * 发网络请求是需要时间的几十到几百毫秒而 JS 不会原地停下来等它是异步的。 * await 的作用就是等这个请求完成、拿到结果后再往下执行而语法规定 * await 只能写在 async 标记的函数里所以必须给函数加上 async。 * * const getEmpList async () {} 声明一个常量 getEmpList给它赋值一个异步箭头函数。 * 这样就把向后端请求数据 → 处理数据 → 放进表格这套逻辑封装成了一个可重复调用的函数 * 查询按钮、清空按钮、页面加载时都能调用它async 则是为了在函数内部用 await 等待 axios 的请求结果。 * * 如果不用 async/await就得改成 .then() 回调的写法效果相同但没那么直观 * const getEmpList () { axios.get(...).then(res { empList.value res.data.data }) } */ const getEmpList async () { // 组装查询参数只有填了值的条件才传给后端 const params {} if (searchName.value) params.name searchName.value if (searchGender.value) params.gender searchGender.value if (searchPosition.value) params.job searchPosition.value try { //发送查询请求需要等待这个请求执行完成才能继续往下执行 const res await axios.get(/list, { params }) if (res.data.code 1) { //作用是防御性编程万一后端没返回 data 字段比如 data: null 或字段缺失 // res.data.data 就是 undefined接下来执行 list.filter(...) 会直接报错 let list res.data.data || [] // 兜底若后端未过滤则在前端再过滤一次如果能确认前端已做条件过滤这里可以注释掉提高性能 list list.filter(item { const nameMatch !searchName.value || (item.name item.name.includes(searchName.value)) const genderMatch !searchGender.value || String(item.gender) String(searchGender.value) const jobMatch !searchPosition.value || String(item.job) String(searchPosition.value) return nameMatch genderMatch jobMatch }) empList.value list } else { alert(查询失败 res.data.msg) empList.value [] } } catch (e) { console.error(e) alert(请求失败请检查网络或接口地址) empList.value [] } } // 页面加载时查询一次全部数据 onMounted(() { getEmpList() }) // 查询功能 const handleSearch () { getEmpList() } // 清空/重置功能 const handleReset () { searchName.value searchGender.value searchPosition.value getEmpList() // 重新查询全部数据 } const getPositionText (code) { return positionMap[String(code)] || 未知 } const handleEdit (item) { alert(正在编辑${item.name}) } const handleDelete (item) { if (confirm(确定要删除「${item.name}」吗)) { const index empList.value.findIndex(emp emp.id item.id) if (index ! -1) { empList.value.splice(index, 1) } } } return { empList, searchName, searchGender, searchPosition, handleSearch, handleReset, getPositionText, handleEdit, handleDelete } } }).mount(#app) /script /body /html2user.txt 业务数据写死1,大乔,2,https://api.dicebear.com/7.x/adventurer/svg?seedrenyingying,1,2026-07-15,2026-07-15 15:05:45 2,小乔,2,https://api.dicebear.com/7.x/adventurer/svg?seedrenyingying,2,2026-07-18,2026-07-18 15:05:45 3,貂蝉,2,https://api.dicebear.com/7.x/adventurer/svg?seedrenyingying,3,2026-07-13,2026-07-13 15:05:45 4,吕布,1,https://api.dicebear.com/7.x/adventurer/svg?seedlinghu,4,2026-07-14,2026-07-14 15:05:45 5,赵云,1,https://api.dicebear.com/7.x/adventurer/svg?seedlinghu,5,2026-07-15,2026-07-15 15:05:45 6,张飞,1,https://api.dicebear.com/7.x/adventurer/svg?seedlinghu,1,2026-07-11,2026-07-11 15:05:45 7,关羽,1,https://api.dicebear.com/7.x/adventurer/svg?seedlinghu,2,2026-07-14,2026-07-14 15:05:45 8,刘备,1,https://api.dicebear.com/7.x/adventurer/svg?seedlinghu,3,2026-07-18,2026-07-18 15:05:453后端交互pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version4.1.0/version relativePath/ !-- lookup parent from repository -- /parent groupIdcom/groupId artifactIdspringboot-web-01/artifactId version0.0.1-SNAPSHOT/version namespringboot-web-01/name descriptionspringboot-web-01/description url/ licenses license/ /licenses developers developer/ /developers scm connection/ developerConnection/ tag/ url/ /scm properties java.version17/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webmvc/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webmvc-test/artifactId scopetest/scope /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.40/version /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId executions execution iddefault-compile/id phasecompile/phase goals goalcompile/goal /goals configuration annotationProcessorPaths path groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /path /annotationProcessorPaths /configuration /execution execution iddefault-testCompile/id phasetest-compile/phase goals goaltestCompile/goal /goals configuration annotationProcessorPaths path groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /path /annotationProcessorPaths /configuration /execution /executions /plugin /plugins /build /projectpojo层统一返回类Result和 User对象package com.springbootweb01.pojo; import lombok.Data; /** * 统一返回类用于返回前端的 JSON 数据 * 前端期望的是 * json * { * code: 1, * msg: success, * data: [...] * } */ Data public class Result { private Integer code; // 1成功, 0失败 private String msg; private Object data; public static Result success(Object data) { Result result new Result(); result.setCode(1); result.setMsg(success); result.setData(data); return result; } public static Result error(String msg) { Result result new Result(); result.setCode(0); result.setMsg(msg); return result; } }package com.springbootweb01.pojo; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDate; import java.time.LocalDateTime; Data NoArgsConstructor AllArgsConstructor public class User { private Integer id; private String name; private Integer gender; private String image; private Integer job; JsonFormat(pattern yyyy-MM-dd) private LocalDate entrydate; JsonFormat(pattern yyyy-MM-dd HH:mm:ss) private LocalDateTime updatetime; }controller层UserControllerpackage com.springbootweb01.controller; import cn.hutool.core.io.IoUtil; import com.springbootweb01.pojo.Result; import com.springbootweb01.pojo.User; import com.springbootweb01.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * 1. 前端页面是 user.html访问这个页面时页面会加载并执行 Vue 的代码。 * 2. 在 onMounted 中会调用 getEmpList()发送 axios.get(/list, { params }) 请求。 * 3. 后端 /list 接口会读取 user.txt 文件并解析。 * * 前端输入: name乔, gender1, job2 * ↓ * axios.get(/list, { params }) * ↓ * 实际请求: GET /list?name乔gender1job2 * ↓ * 后端 RequestParam 接收参数 * ↓ * 后端 stream().filter() 逐个条件过滤 * ↓ * 返回 Result { code:1, data: [...] } * ↓ * 前端直接渲染 res.data.data * * 4、RestController注解的作用 * RestController Controller ResponseBody * 作用将Controller方法的返回值直接写入HTTP响应体中而不是渲染视图。如果是对象或集合会自动转换为 JSON 格式。 */ RestController public class UserController { //方式1不分层解耦 RequestMapping(/list1) //前端有查询操作所以后端也要支持条件查询有两个要注意的 //注意1前端传过来的是什么查询字段后端接收的参数名要一致否则会报错。 //注意2RequestParam 的 required false 很关键否则用户不传某个参数时会直接报 400 错误。 public Result user1(RequestParam(required false) String name, RequestParam(required false) Integer gender, RequestParam(required false) Integer job) { //1、加载并读取user.txt文件获取用户数据 InputStream in this.getClass().getClassLoader().getResourceAsStream(user.txt); ArrayListString lines IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList()); System.out.println(lines); //2、将用户数据转换为User对象 ListUser empList lines.stream() .filter(line - line ! null !line.trim().isEmpty()) // 过滤空行防止user.txt 文件中存在空行 .map(line - { String[] split line.split(,); Integer id Integer.parseInt(split[0]); String nameStr split[1]; Integer genderVal Integer.parseInt(split[2]); String image split[3]; Integer jobVal Integer.parseInt(split[4]); LocalDate entrydate LocalDate.parse(split[5], DateTimeFormatter.ofPattern(yyyy-MM-dd)); LocalDateTime updatetime LocalDateTime.parse(split[6], DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)); return new User(id, nameStr, genderVal, image, jobVal, entrydate, updatetime); }).collect(Collectors.toList()); // 3. 条件过滤核心逻辑 if (name ! null !name.isEmpty()) { // 姓名模糊匹配包含 empList empList.stream() .filter(u - u.getName().contains(name)) .collect(Collectors.toList()); } if (gender ! null) { // 性别精确匹配 empList empList.stream() .filter(u - u.getGender().equals(gender)) .collect(Collectors.toList()); } if (job ! null) { // 职位精确匹配 empList empList.stream() .filter(u - u.getJob().equals(job)) .collect(Collectors.toList()); } //4、返回User对象JSON格式 return Result.success(empList); } //方式2分层解耦 Autowired // 程序运行时会自动查询该类型的bean对象并赋值给改成员变量自动注入UserService的实现类 private UserService userService; RequestMapping(/list) public Result user(RequestParam(required false) String name, RequestParam(required false) Integer gender, RequestParam(required false) Integer job) { //1、调用service获取数据 ListUser empList userService.findAll(); // 3. 条件过滤核心逻辑 if (name ! null !name.isEmpty()) { // 姓名模糊匹配包含 empList empList.stream() .filter(u - u.getName().contains(name)) .collect(Collectors.toList()); } if (gender ! null) { // 性别精确匹配 empList empList.stream() .filter(u - u.getGender().equals(gender)) .collect(Collectors.toList()); } if (job ! null) { // 职位精确匹配 empList empList.stream() .filter(u - u.getJob().equals(job)) .collect(Collectors.toList()); } //4、返回User对象JSON格式 return Result.success(empList); } }dao和service层package com.springbootweb01.dao; import java.util.List; public interface UserDao { //1、加载并读取user.txt文件获取用户数据 public ListString findAll(); }package com.springbootweb01.dao.impl; import cn.hutool.core.io.IoUtil; import com.springbootweb01.dao.UserDao; import org.springframework.stereotype.Repository; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; Repository public class UserDaoImpl implements UserDao { Override public ListString findAll() { //1、加载并读取user.txt文件获取用户数据 InputStream in this.getClass().getClassLoader().getResourceAsStream(user.txt); ArrayListString lines IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList()); return lines; } }package com.springbootweb01.service; import com.springbootweb01.pojo.User; import java.util.List; public interface UserService { //查询所有用户 public ListUser findAll(); }package com.springbootweb01.service.impl; import com.springbootweb01.dao.UserDao; import com.springbootweb01.pojo.User; import com.springbootweb01.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.stream.Collectors; Service public class UserServiceImpl implements UserService { Autowired // 程序运行时会自动查询该类型的bean对象并赋值给改成员变量 private UserDao userDao; Override public ListUser findAll() { //1、调用dao获取数据 ListString lines userDao.findAll(); //2、将用户数据转换为User对象 ListUser empList lines.stream() .filter(line - line ! null !line.trim().isEmpty()) // 过滤空行防止user.txt 文件中存在空行 .map(line - { String[] split line.split(,); Integer id Integer.parseInt(split[0]); String nameStr split[1]; Integer genderVal Integer.parseInt(split[2]); String image split[3]; Integer jobVal Integer.parseInt(split[4]); LocalDate entrydate LocalDate.parse(split[5], DateTimeFormatter.ofPattern(yyyy-MM-dd)); LocalDateTime updatetime LocalDateTime.parse(split[6], DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)); return new User(id, nameStr, genderVal, image, jobVal, entrydate, updatetime); }).collect(Collectors.toList()); return empList; } }执行效果http://localhost:8080/user.html
返回列表