SPRINGBOOT+VUE项目实战

第一章=>
1、ElementUI         
2、布局与主体             
3、增删改查               
4、路由                       
5、播放组件

第二章=>
6、分页                 
7、代码生成                 
8、导入导出               
9、用户登录                 
10、注册与异常处理   

第三章=>
11、JWT               
12、文件上传               
13、权限管理           
14、Redis

第四章=>
15、Echats             
16、百度地图               
17、Markdwon       
18、WangEditor

第五章=>
19、前台页面         
20、视频播放               
21、多级评论           
22、支付宝

第六章=>
23、购物车             
24、借书管理               
25、百度地图           
26、聊天室

第七章=>
27、考试系统         
28、邮箱登录               
29、活动预约           
30、电商系统

****************************************************************************************************************************************************************************

1、ElementUI 
【1】软件安装,开发环境配置。
JDK1.8、mysql5.7、node、navicat、idea2021
*************************************************************************
【2】安装vue-cli
npm i -g @vue/cli
*************************************************************************
vue -V
*************************************************************************
vue create white
*************************************************************************
用white记录模板创建即可。
*************************************************************************
npm config set registry https://registry.npmmirror.com/
npm config get registry
*************************************************************************
用WebStorm打开
*************************************************************************
【3】项目结构
http://localhost:8080/
App.vue通过路由展示了Home与About页面
*************************************************************************
【4】安装element
npm i element-ui -S
*************************************************************************使用
<el-button type="primary">主要点击</el-button>

****************************************************************************************************************************************************************************

2、布局与主体
【1】安装less
npm i less-loader less -S
************************************************************************样式调整而已
链接:https://pan.baidu.com/s/15YwRICJKS7aCiBqWcYeAKw 
提取码:iwr9 
完成主页面样式设计

****************************************************************************************************************************************************************************

5、Springboot框架搭建
【1】配置Lombok、Spring web、MyBatis Framework、MySQL Driver
【2】配置阿里云下载
<!--配置阿里云仓库下载--><repositories><repository><id>nexus-aliyun</id><name>nexus-aliyun</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>public</id><name>nexus-aliyun</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></pluginRepository></pluginRepositories>
【3】启动报错
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
*****************************************************************************
#数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#东八区+数据库连接配置
spring.datasource.url=jdbc:mysql://wdfgdzx.top:3306/black?serverTimezone=GMT2b%8
spring.datasource.username=root
spring.datasource.password=s19911009!
*****************************************************************************
black utf8mb4  utf8mb4_unicode_ci两个配置
*****************************************************************************
#0代表黑,1代表白。前端是8001
server.port=8000

****************************************************************************************************************************************************************************

6、Mybatis实现数据增删改查
【1】动态sql
【2】File -> Settings -> Editor -> File encodings --> 设置properties的编码
【3】mybatis配置
#指定mybatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
*****************************************************************************properties
#0代表黑,1代表白,前端是8001
server.port=8000
#数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库配置
spring.datasource.url=jdbc:mysql://wdfgdzx.top:3306/black?serverTimezone=GMT%2b8&allowMultiQueries=true&useAffectedRows=true
spring.datasource.username=root
spring.datasource.password=s19911009!
#指定mybatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
【4】详细代码
链接:https://pan.baidu.com/s/1Jz80if48Z5pannN_TOAUTQ 
提取码:fq9j

****************************************************************************************************************************************************************************

8、分页查询实现
【1】控制器的路基@PostMapping("list_page")public HashMap list_page(@RequestBody User user) {Integer totalNum = userMapper.total();MyUtils.selectByPageManage(totalNum, user);HashMap hashMap = new HashMap();hashMap.put("total", totalNum);hashMap.put("data", userMapper.list_page(user));return hashMap;}
【2】跨域问题的处理
package com.black.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class Cross {// 当前跨域请求最大有效时长,默认1天private static final long MAX_DAY = 24 * 60 * 60;@Beanpublic CorsFilter buildCorsFilter() {CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*"); // 访问源地址corsConfiguration.addAllowedHeader("*");// 访问源请求头corsConfiguration.addAllowedMethod("*"); // 访问源请求方法corsConfiguration.setMaxAge(MAX_DAY);// 设置最大有效时长UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration); // 对接口配置跨域设置return new CorsFilter(urlBasedCorsConfigurationSource);}
}
【3】配置axios
npm i axios -S
************************************************************************
// main.js
import axios from "axios"; // 导入axiosVue.prototype.$http = axios //在Vue的原型上添加一个$http属性,该属性保存了axios
axios.defaults.baseURL = 'http://localhost:8000'

****************************************************************************************************************************************************************************

9、MybatisPlus与SwaggerUI
【1】pom配置
<!--mybatis-plus-->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version>
</dependency>
【2】MybaitsPlus配置
package com.black.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@MapperScan("com.black.mapper") // !!!!!!!!!!!!!!
public class MybatisPlus {@Beanpublic MybatisPlusInterceptor buildMybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return mybatisPlusInterceptor;}
}
【3】简化了UserMapper.interface与UserMapper.xml(只需要写复杂的即可,简单的MP代理了)
package com.black.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.black.pojo.User;
import org.apache.ibatis.annotations.Param;import java.util.List;public interface UserMapper extends BaseMapper<User> { // !!!!!!!!!!Integer total(@Param("user") User user); //总条数List<User> list_page(@Param("user") User user); //分页数据
}
******************************************************************************
MybatisPlus让数据库操作变得更简单...
******************************************************************************
@TableId(value = "id", type = IdType.AUTO) // 这个会影响是否按顺序增加!!!!!!!
@TableId(type = IdType.AUTO) // 当代码里的字段与数据库不同时,可以通过value="xxx"对应数据库字段
type = IdType.AUTO// 这个影响自增,所以很重要
【4】SwaggerUI简化postman的测试
<!-- swagger接口文档 -->
<dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version>
</dependency>
******************************************************************************
# properties
# 引入swagger3.0时加入的配置 http://localhost:8000/swagger-ui/index.html
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
******************************************************************************
package com.black.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration
@EnableSwagger2
public class Swagger {@Beanpublic Docket restAPI() {return new Docket(DocumentationType.SWAGGER_2).groupName("HIT的接口").apiInfo(APIInfo()).useDefaultResponseMessages(true).forCodeGeneration(false).select().apis(RequestHandlerSelectors.basePackage("com.black.controller")) // !!!!!!!!!!!!.paths(PathSelectors.any()).build();}private ApiInfo APIInfo() {return new ApiInfoBuilder().title("RESTful").description("http://wdfgdzx.top:8001/").termsOfServiceUrl("http://wdfgdzx.top:8001/").contact(new Contact("HIT", "http://wdfgdzx.top:8001/", "wdfgdzx@163.com")).version("V1.0").build();}
}
******************************************************************************看自己收藏的CSDN一样的
http://localhost:8000/swagger-ui/index.html

****************************************************************************************************************************************************************************

10、VUE实现增删改查
【1】配置xml SQL查询
Editor->Inspections->SQL->No data sources configured 和 SQL dialect detection
看收藏的CSDN帖子
***************************************************************************
【2】安装配置axios
npm i axios -S
***************************************************************************
import axios from "axios";// 导入axios/*1、配置后台请求接口*/
const $http = axios.create({  //在Vue的原型上添加一个$http属性,该属性保存了axiosbaseURL: "http://localhost:8000",timeout: 5000
})/*2、请求拦截器,对发送请求钱做一些处理,比如统一加token/对请求参数统一加密等...*/
$http.interceptors.request.use(config => {config.headers['Content-Type'] = "application/json;charset=utf-8"  // 定义网络文件的类型和网页的编码,决定文件接收方将以什么形式、什么编码读取这个文件return config
})/*3、响应拦截器,在接口响应后统一处理结果*/
$http.interceptors.response.use(res => {return res;
})export default $http
***************************************************************************
import $http from './util/axios.js' // 引入定义的axios工具
Vue.prototype.$http = $http //引入util/axios.js暴露的$http来使用
***************************************************************************
【3】行数据脏改
/*修改窗口*/updateWindow(row) {this.userFormFlag = truethis.userForm = JSON.parse(JSON.stringify(row)) // !!!!!!!!!!!!},

****************************************************************************************************************************************************************************

11、SpringBoot代码生成
【1】引入依赖
<!--代码生产-->
<dependency><groupId>org.apache.velocity</groupId><artifactId>velocity</artifactId><version>1.7</version>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.1</version>
</dependency>
**********************************************************************我只想说打扰了,哥哥
这么简单的代码,不至于生成,还覆盖了我原有的代码,过!!!!
不如直接复制+替换关键字不香吗???

****************************************************************************************************************************************************************************

12、VUE使用路由展示左侧栏
【1】props: ['fff_top_foldData', 'fff_top_foldClick'],/*说明props还可以接受函数,我日尼玛哦*/
【2】路由守卫
*************************************************************************使用vuex解决导航路径问题
npm i vuex -S --force
*************************************************************************myVuex.js
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)const myVuex = new Vuex.Store({state: {currentPathName: '' // 当前路径名称},mutations: {setStateCurrentPathName(state) {state.currentPathName = localStorage.getItem("currentPathName")}}
})export default myVuex
*************************************************************************main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router/router.js'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import './assets/main.css' // 全局样式表
import $http from './util/axios.js' // 引入定义的axios工具
import myVuex from "@/store/myVuex.js"; // 引入vuex管理全局存储Vue.prototype.$http = $http //引入util/axios.js暴露的$http来使用Vue.config.productionTip = false
Vue.use(ElementUI, {size: 'mini'}) // 挂载elementUInew Vue({router,myVuex,render: h => h(App)
}).$mount('#app')
*************************************************************************router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Manage from "@/views/Manage.vue";
import User from "@/views/User.vue";
import Home from "@/views/Home.vue";
import myVuex from "@/store/myVuex";Vue.use(VueRouter)const routes = [{path: '/',name: 'manage',component: Manage,redirect: "/home",children: [{path: 'user', name: '用户管理', component: User},{path: 'home', name: '首页', component: Home}]},{path: '/about',name: 'about',// route level code-splitting// this generates a separate chunk (about.[hash].js) for this route// which is lazy-loaded when the route is visited.component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')}
]const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes
})/*路由守卫*/
router.beforeEach((to, from, next) => {// console.log(to)localStorage.setItem("setStateCurrentPathName", to.name) // 设置即将访问的路由名称,为了在Header中使用myVuex.commit("setPath") // 触发myVuex里的数据更新next()
})export default router
*************************************************************************Top.vue

****************************************************************************************************************************************************************************

13、SpringBoot实现导入导出
【1】说明学只能是学。但是和军队管理的用还差的很多,还是要多用,做项目来成长。
@GetMapping("/list_export")public String list_export(HttpServletResponse httpServletResponse) throws Exception {//1、从数据查询List<User> userList = userMapper.selectList(null);ExcelWriter excelWriter = ExcelUtil.getWriter(true);//2、自定义标题excelWriter.addHeaderAlias("id", "序号");excelWriter.addHeaderAlias("name", "姓名");excelWriter.addHeaderAlias("nick", "昵称");excelWriter.addHeaderAlias("email", "邮箱");excelWriter.addHeaderAlias("phone", "手机");excelWriter.addHeaderAlias("address", "地址");excelWriter.setOnlyAlias(true); // 仅写出指定字段excelWriter.write(userList, true);//3、设置浏览器响应的格式httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");String fileName = URLEncoder.encode("用户信息", "UTF-8");httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");//4、拿到输出流并刷新ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();excelWriter.flush(servletOutputStream, true);servletOutputStream.close();excelWriter.close();return "导出成功";}
【2】学用需要结合,这样才真正掌握某一项技能。军队管理!!!!!
【3】批量插入的mybatis的写法,重要!!!!!!!!!!!!!!!!!!!!!
<insert id="list_insert" parameterType="java.util.List">
【4】导入的第二种方式
********************************************************************忽略表头中文
List<CsvRow> rowList = data.getRows();  // 忽略表头文件,直接读取内容
rowList.remove(0);
row.get(5)
********************************************************************
写死了,唯一的优点是不用管表头名字
【5】前端调用
********************************************************************
/*导出*/
list_export() {window.open("http://localhost:8000/user/list_export")
},
********************************************************************
<el-upload style="display: inline-block"action="http://localhost:8000/user/list_import"name="multipartFile" // !!!!!!!!!!!!!多看文档:show-file-list="false"accept="xlsx":on-success="onImportSuccess"><el-button type="primary" class="ml-5">导入<i class="el-icon-upload2"></i></el-button>
</el-upload>

****************************************************************************************************************************************************************************

14、Springboot+VUE实现登录
【1】登录页的路由+布局
【2】后台写登录接口
@RestController
@RequestMapping("/big")
public class BigController {@ResourceUserMapper userMapper;@PostMapping("login")public String login(@RequestBody User user) {// 拿到用户输入的账号密码User existUser = userMapper.selectUserByNamePassword(user);if (existUser == null) {return "账号或密码错误"; // 好简单!!!!!!!!!!!}return "登录成功";}
}
*************************************************************************
400报错,如果后端要求的传参没有传递也会报错400。比如big/login要求传user,如果没传则报错400
*************************************************************************
<el-form :rules="ruleList" :model="user"><!--用来校验表单-->
*************************************************************************
<el-form-item prop="name"><!--必须el-form-item prop="xxx"才能生效-->
再次说明学以致用最重要!!!!!!!!!!!!!!!!!!!!!!!!
*************************************************************************校验规则先通过才发送请求
ref="userForm"
*************************************************************************用到了引用ref
loginClick() {
this.$refs["userForm"].validate(valid => {if (valid) { // 表单校验合法this.$http.post("/big/login", this.user).then(res => {if (res.data !== "登录成功") {this.$message.error("用户名或密码错误")} else {this.$router.push("/")}})} else {return false}
})
}
【3】假如数据库存在脏数据,就是有重复的用户名+密码,怎么处理?
用List判断数据是否存在
*************************************************************************
用try catch,捕获到异常然后再处理

****************************************************************************************************************************************************************************

15、Springboot+Vue实现注册与异常处理
【1】登录信息存储
package com.black.util;// 常量类
public class Constants {public static final String CODE_200 = "200"; // 成功public static final String CODE_400 = "400"; // 参数错误public static final String CODE_401 = "401"; // 权限不足public static final String CODE_500 = "500"; // 系统错误public static final String CODE_600 = "600"; // 其他错误
}
*******************************************************************************
package com.black.util;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;// 返回结果包装类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Res {private String code;private String message;private Object object;// 成功的两个方法public static Res success() {return new Res(Constants.CODE_200, "", null);}public static Res success(Object object) {return new Res(Constants.CODE_200, "", object);}// 不成功的方法public static Res error() {return new Res(Constants.CODE_500, "系统错误", null);}public static Res error(String code, String message) {return new Res(code, message, null);}
}
【2】全局异常处理,最终还是用到了Res,这个先保留了解下...
【3】前端的处理
localStorage.setItem("user", JSON.stringify(res.data.object)) // 存储用户信息到浏览器
*******************************************************************************
user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}
*******************************************************************************
logoutClick() {this.$router.push("/login")localStorage.removeItem("user") // 移出浏览器用户信息this.$message.success("退出成功")}
【4】注册页面与接口的改造,统一返回封装的Res

****************************************************************************************************************************************************************************

16、Springboot使用JWT
【1】JWT全称是json web token
它将用户信息加密到token里,服务器不保存任何用户信息。
服务器通过使用保存的密匙验证token的正确性,只要正确即可通过验证。
***************************************************************************
优点:
简洁:通过URL POST参数或者在http header发送,数据量小,传输速度也快。
***************************************************************************
自包含,避免了多次的数据库查询。
***************************************************************************
因为token是以json加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
***************************************************************************
不需要在服务端保存会话信息,特别适用于分布式的---微服务---。
***************************************************************************
缺点:
无法作废已颁布的令牌;
不易于应对数据过期;
【2】组成,一个token是三个组成部分。
header头部;载荷payload;签证singature。用.分割
***************************************************************************
<!--JWT-->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.10.3</version>
</dependency>
***************************************************************************
package com.black.util;import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.black.pojo.User;import java.util.Date;public class Token {public static String productToken(User user) {return JWT.create().withAudience(user.getId() + "") // user.id作为载荷.withExpiresAt(DateUtil.offsetHour(new Date(), 2)) //2小时后token过期.sign(Algorithm.HMAC256(user.getPassword())); // 以 password 作为 token 的密钥}
}
【3】token的重要作用
/*2、请求拦截器,对发送请求钱做一些处理,比如统一加token/对请求参数统一加密等...*/
$http.interceptors.request.use(config => {config.headers['Content-Type'] = "application/json;charset=utf-8"  // 定义网络文件的类型和网页的编码,决定文件接收方将以什么形式、什么编码读取这个文件// 从本地存储拿,设置请求头!!!!!!!!!!!!let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}if (user) {config.headers['token'] = user.token; // 设置}return config
})
***************************************************************************后台拦截器
权限验证不同过,前端提示
/*3、响应拦截器,在接口响应后统一处理结果*/
$http.interceptors.response.use(res => {//alert(JSON.stringify(res))if (res.data.code === "401") {elementUI.Message({message: res.data.message,type: "error"})}return res;
})
【4】后台获取当前用户
private static UserMapper staticUserMapper; // 因为@Resource不能定义为静态,这里是为了转存
@Resource
private UserMapper userMapper; // 因为有个静态方法引用了@PostConstruct
public void setUserMapper() {staticUserMapper = userMapper;
}
***************************************************************************
// 获取当前用户
public static User getNowUser() {try {HttpServletRequest httpServletRequest = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); //拿到httpServletRequestString token = httpServletRequest.getHeader("token");//拿到tokenif (StrUtil.isNotBlank(token)) {String id = JWT.decode(token).getAudience().get(0); //拿到user.idreturn staticUserMapper.selectById(id);//拿到user}} catch (Exception e) {e.printStackTrace();return null;}return null;
}
***************************************************************************
System.err.println(JWTInterceptor.getNowUser()); // 全局可以获取
任何方法,任何Controller去使用

****************************************************************************************************************************************************************************

17、文件上传
【1】swagger-ui的配置,在放行代码指定这些,JWT拦截里放行。牛批!!!!!
"/swagger**/**",
"/webjars/**",
"/v2/**",
"/doc.html"
**************************************************************************文件操作都放行
"/document/**"
**************************************************************************上传不要@RequestBody
public String upload(Document document) throws Exception 
**************************************************************************
#上传文件大小指定(单个)
spring.servlet.multipart.max-file-size=100MB
**************************************************************************
File uploadFile = new File(uploadDir + "/" + IdUtil.fastSimpleUUID() + "." + type);
**************************************************************************
【2】同样的图片去重43分钟,利用md5值是否相同来比较,这个是自己想的解决办法,必须赞!!!!!!!!!!!!
@PostMapping("/upload")
public String upload(Document document) throws Exception {String originName = document.getMultipartFile().getOriginalFilename();String type = FileUtil.extName(originName);long size = document.getMultipartFile().getSize();// 1、存到磁盘、存储数据库File uploadDir = new File(MyUtils.getFinalPath());System.err.println(uploadDir);if (!uploadDir.exists()) {System.out.println(uploadDir.mkdir()); //不存在创建新目录}// 2、最终文件路径String fileUUID = IdUtil.fastSimpleUUID() + "." + type;String url = "http://localhost:8000/document/" + fileUUID;File uploadFile = new File(uploadDir + "/" + fileUUID);//3、获取md5if (!uploadFile.exists()) {document.getMultipartFile().transferTo(uploadFile); // 这时候才存磁盘!!!!!}String md5 = SecureUtil.md5(uploadFile);// 4、判断数据库里md5是否存在document.setMd5(md5);List<Document> documentList = documentMapper.md5_list(document); // 数据库查询到相同md5的对象System.out.println(documentList);if (documentList.size() != 0) {url = documentList.get(0).getUrl(); // 存在的话,url直接从已存在数据库信息拿到System.out.println(uploadFile.delete());// 删除本地存储的文件}// 5、存数据库Document insertDocument = new Document();insertDocument.setUrl(url);insertDocument.setName(originName);insertDocument.setType(type);insertDocument.setSize(size / 1024);insertDocument.setMd5(md5);documentMapper.insert(insertDocument);return url;
}
**************************************************************************
这里青戈处理了很久....我尼玛!!!!!!!!
而且青戈处理的不对....浪费良久后青戈处理对了...
还是谦虚点,还是有比我牛批的地方...
【3】上传图像,这个name很重要的!!!!!!!!!!!
<!--头像-->
<el-uploadname="multipartFile"class="avatar-uploader"action="http://localhost:8000/document/upload":show-file-list="false":on-success="onSuccess"><img v-if="userForm.avatar" :src="userForm.avatar" class="avatar"><i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
【4】处理保存后,左上角头像的更新问题(初步处理,没有一键触发!!!!)
子传父、父传子
**************************************************************************Person.vue
// 触发父级更新user方法
this.$emit("person_fff_user", this.user)
**************************************************************************Composite.vue
data() {return {foldData: 'foldClass el-icon-s-fold',// 折叠类collapseFlag: false, // 是否折叠sideWidth: 200, // 左侧边栏宽度logoTextShowFlag: true,user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}// 这个不取,第一次展示就是空了!!!!!!!!!}
},
methods: {/*子传父*/async person_fff_user(val) { //这里val就是person传过来的userconst {data: res} = await this.$http.post("user/select", val) // 从后台获取数据// console.log(res.object)this.user = res.object},<Top :fff_top_fold-data="foldData":fff_top_foldClick="foldClick":fff_top_user="user"> <!--向person传递user-->
</Top>**************************************************************************Top.vue
<Top :fff_top_fold-data="foldData":fff_top_foldClick="foldClick":fff_top_user="user"> <!--向person传递user-->
</Top>props: ['fff_top_foldData', 'fff_top_foldClick', 'fff_top_pathName', 'fff_top_user'],/*说明props还可以接受函数,我日尼玛哦*/<div style="display: inline-block;"><img :src="fff_top_user.avatar"style="width: 30px;border-radius: 10%;position: relative;top:5px;right: 5px;"><span>{{fff_top_user.name}}</span><i class="el-icon-arrow-down" style="margin-left: 5px"></i>
</div>
**************************************************************************
Person----Composite---Top
子----------父------------子

****************************************************************************************************************************************************************************

18、整合Echarts
【1】配置
npm i echarts -S
************************************************************************
import * as echarts from 'echarts'
折线图 柱状图 饼图
************************************************************************
this.$http.post("/echarts/select").then(res => {// this.$http.post("/echarts/vip").then(res => {// console.log(res)// alert(JSON.stringify(res.data.object.x))// alert(JSON.stringify(res.data.object.y))option.xAxis.data = res.data.object.x;// alert(JSON.stringify(res))// option.xAxis.data = ["第一季度", "第二季度", "第三季度", "第四季度",]; // 演示从数据库查询的option.series[0].data = res.data.object.y;option && myChart.setOption(option);
})
请求后台更改数据,主要是属性的更改,与后台json结合,加上option的位置要在更改后放置!!!
【2】概览部分
<!--头部-->
<el-row :gutter="10"><el-col :span="6"><el-card style="color: #409eff"><div><i class="el-icon-user-solid"></i> 用户总数</div><div style="text-align: center;padding: 10px 0;font-weight: bold;font-size: 20px">100</div></el-card></el-col><el-col :span="6"><el-card style="color: #67C23A"><div><i class="el-icon-money"></i> 销售总量</div><div style="text-align: center;padding: 10px 0;font-weight: bold;font-size: 20px">100</div></el-card></el-col><el-col :span="6"><el-card style="color: #E6A23C"><div><i class="el-icon-bank-card"></i> 收益总额</div><div style="text-align: center;padding: 10px 0;font-weight: bold;font-size: 20px">¥100000,00</div></el-card></el-col><el-col :span="6"><el-card style="color: #F56C6C"><div><i class="el-icon-s-shop"></i> 门店总数</div><div style="text-align: center;padding: 10px 0;font-weight: bold;font-size: 20px">20</div></el-card></el-col>
</el-row>
************************************************************************
option = {title: {left: 'center' // 标题属性},tooltip: { // 鼠标移入显示数据标签trigger: 'item'},legend: { // 显示说明左侧orient: 'vertical',left: 'left'},xAxis: {type: 'category',data: []},yAxis: {type: 'value'},series: [{name: 'member', // 和legend是绑定的data: [],type: 'line'}]
};

****************************************************************************************************************************************************************************

20、权限管理
【1】Role
复制前端的Role,全局替换。修改下细节字段即可
*****************************************************************************
复制后端的Role、RoleMapper.interface、RoleMapper.xml、RoleController
*****************************************************************************
修改下细节字段即可
【2】菜单分配页实现新功能 32分钟
<!--树形控件-->
<el-tree
:data="menu.data"
show-checkbox
node-key="id"
:default-expanded-keys="[2]"
:default-checked-keys="[3]"
@check-change="onCheckChange">
</el-tree>
*****************************************************************************
如果是PostMapping,用post必须指定body里一个{}空对象查询
如果要求登录注意header参数的token设置
*****************************************************************************关键代码
/*找到所有*/
@PostMapping("/list_all")
public Res list_all(@RequestBody Menu menu) {
List<Menu> menuList = menuMapper.selectList(null); // 查询所有
List<Menu> fatherMenuList = menuList.stream().filter(temp -> temp.getFather() == null).collect(Collectors.toList());// 找出father为null的一级菜单
for (Menu item : fatherMenuList) { // 找出每一个一级菜单的子菜单,并设置给item(父菜单)
List<Menu> childrenList = menuList.stream().filter(temp -> item.getId().equals(temp.getFather())).collect(Collectors.toList());
item.setChildren(childrenList);
}
return Res.success(fatherMenuList);
}
【2】角色拥有的菜单,一个字段搞定的事情,青戈非要再用一个表+实体类....
private String menuIds; // 拥有的菜单集合
@TableField(exist = false)
private List<Integer> menuIdList;
*****************************************************************************这个问题处理
确实有点难度,就在于如果传参与获取的类型不一致,尽量前后端不要绑定,
绑定就很难处理,调用会报错400。分开后就不存在这个问题,特别是参与获取的类型不一致
menuIdList: [],  前端需要的是数组!!!!!!!!!
this.roleForm.menuIds ,后端需要的参数是字符串!!!!!!!!!!!!!!!!!!!
【3】动态路由
给用户一个角色role
*****************************************************************************
role.name不允许重复设置。
索引-角色名称/name/UNIQUE/BTREE---后端捕获异常,返回给前端提示...
*****************************************************************************多测试,空指针bug
@PostMapping("/select")
public Res select(@RequestBody Role role) {
Role existRole = roleMapper.selectById(role.getId());
// 处理成前端需要的格式menuIds ->menuIdList  提供前端需要的格式!!!!!!!!!!!!!!!!!!!
List<Integer> menuIdList = new ArrayList<>();
if (existRole.getMenuIds() != null) {
String menuIds = existRole.getMenuIds().replace("[", "").replace("]", "");
String[] tempArray = menuIds.split(",");
for (String temp : tempArray) {
menuIdList.add(Integer.parseInt(temp));
}
}List<Menu> menuList = menuMapper.selectBatchIds(menuIdList);
existUser.setMenuList(menuList);// !!!!!!!!!都是为了这个
return Res.success(existRole);// 需要返回对象
}
*****************************************************************************
我的方法节省了很大的功夫,同时也有很多细节BUG需要耐心排查。
*****************************************************************************30分钟
removeIf()等,我都不需要写这些东西,因为存的好,表建的好!!!!!!!!!
*****************************************************************************都浓缩成这个方法了!!!!!!!
@PostMapping("/login")
public Res login(@RequestBody User user) {
// 拿到用户输入的账号密码
User existUser;
try {
existUser = userMapper.selectUserByNamePassword(user);
} catch (Exception e) { // 如果系统中存在多条等异常情况
e.printStackTrace();
return Res.error(Constants.CODE_500, "系统错误");
}
if (existUser == null) {
return Res.error(Constants.CODE_400, "用户名或密码错误");
}
// 设置token,保证existUser要用id与password
String token = Token.productToken(existUser);
existUser.setToken(token);
existUser.setPassword("xxx"); // 拿到token后,保护用户密码Role role = new Role();
role.setName(existUser.getRole());
//根据用户角色,查到角色完整信息
Role existRole = roleMapper.selectRoleByName(role);
//根据角色的菜单,生成菜单List,设置到existUser中
List<Integer> menuIdList = new ArrayList<>();
if (existRole.getMenuIds() != null) { // 存在菜单
String menuIds = existRole.getMenuIds().replace("[", "").replace("]", "");
String[] tempArray = menuIds.split(",");
for (String temp : tempArray) {
menuIdList.add(Integer.parseInt(temp));
}
}
// 不存在菜单,设置为空
List<Menu> menuList = menuMapper.selectBatchIds(menuIdList);
List<Menu> fatherMenuList = menuList.stream().filter(temp -> temp.getFather() == null).collect(Collectors.toList());// 找出father为null的一级菜单
for (Menu item : fatherMenuList) { // 找出每一个一级菜单的子菜单,并设置给item(父菜单)
List<Menu> childrenList = menuList.stream().filter(temp -> item.getId().equals(temp.getFather())).collect(Collectors.toList());
item.setChildren(childrenList);
}
existUser.setMenuList(fatherMenuList);// !!!!!!!!!都是为了这个return Res.success(existUser);// 这里返回existUser 是因为虽然用户输入的是名+密码,但是实际拿到还有图标等信息
}
*****************************************************************************实现动态菜单
确实有省功夫的地方,但是青戈的这个思路还是很值得学习的!!!!!!
<div v-for="item in menuList" :key="item.id">
<!--有path一级菜单,全靠login Controller返回的父子关系数据-->
<div v-if="item.path">
<el-menu-item :index="item.path">
<i :class="item.icon"></i><!--这里如果不放在上面,收缩时会看不到-->
<template slot="title">
<span>{{item.name}}</span>
</template>
</el-menu-item>
</div>
<!--二级菜单-->
<div v-else>
<el-submenu :index="item.id+''">
<template slot="title">
<i :class="item.icon"></i>
<span>{{item.name}}</span>
</template>
<div v-for="subItem in item.children" :key="subItem.id">
<el-menu-item :index="subItem.path">
<i :class="subItem.icon"></i>
<span>{{subItem.name}}</span>
</el-menu-item>
</div>
</el-submenu>
</div>
</div>
*****************************************************************************
<el-menu :default-openeds="openList"background-color="rgb(48,65,86)"text-color="white"active-text-color="yellow":collapse-transition="false":collapse="fff_left_collapseFlag"router>
*****************************************************************************
openList: localStorage.getItem("menuList") ? JSON.parse(localStorage.getItem("menuList")).map(v => v.id + '') : []
【4】动态路由终版起飞
export const setRoutes = () => {const menuList = JSON.parse(localStorage.getItem("menuList"));if (menuList) {const finalRoute = { // 动态的添加进去path: '/', component: Composite,redirect: "/login",children: []}menuList.forEach(item => {if (item.path) { // 一级路由let menu = {path: item.path.replace("/", ""),name: item.name,component: () => import('../views/' + item.vue + '.vue')}finalRoute.children.push(menu)} else if (item.children.length) {item.children.forEach(subItem => { // 二级路由if (subItem.path) {let subMenu = {path: subItem.path.replace("/", ""),name: subItem.name,component: () => import('../views/' + subItem.vue + '.vue')}finalRoute.children.push(subMenu)}})}})// 动态添加到现有路由router.addRoute(finalRoute)}
}
*****************************************************************************Login.vue
import {setRoutes} from '../router/router.js'
setRoutes() // 动态设置当前用户路由
*****************************************************************************处理其他细节问题
router.js里调下自己!!!!!!!!!!!!!!!
setRoutes();// 刷新页面的时候刷新路由
*****************************************************************************报错重复路由...
vue-router.esm.js?3423:16 [vue-router] Duplicate named routes definition: { name: "主页", path: "/home" }
*****************************************************************************解决,没有的时候才添加
// 动态添加到现有路由
const routeNameList = router.getRoutes().map(v => v.name)
if (!routeNameList.includes('composite')) {router.addRoute(finalRoute)
}
*****************************************************************************访问其他页面提示404
{path: '*', name: '404', component: NotFound},
*****************************************************************************
<template><div style="text-align: center;margin-top: 300px;"><h1>404 NotFound 请您检查路由地址是否正确</h1><br><h2>您也可以联系微信:15921291928</h2></div>
</template><script>export default {name: "NotFound"}
</script><style scoped></style>
*****************************************************************************自己的逻辑又解决了一个问题!!!!
//后台处理下,选择主页1 选择用户管理3,但是没传过来2系统管理的问题。就是记录了子菜单,但是没有父菜单,动态路由的时候有问题
List<Integer> tempList = new ArrayList<>(); // 用另一个容器存储,防止遍历时出现问题
for (int i = 0; i < menuIdList.size(); i++) {
tempList.add(menuIdList.get(i));
}
for (Integer menuId : menuIdList) {
Menu menu = menuMapper.selectById(menuId);
if (menu.getFather() != null && !tempList.contains(menu.getFather())) {// 这个就是有孩子,但是集合id里面没有对应父级id
tempList.add(menu.getFather()); // 临时容器存储
}
}
// 不存在菜单,设置为空
List<Menu> menuList = menuMapper.selectBatchIds(tempList);List<Menu> fatherMenuList = menuList.stream().filter(temp -> temp.getFather() == null).collect(Collectors.toList());// 找出father为null的一级菜单
for (Menu item : fatherMenuList) { // 找出每一个一级菜单的子菜单,并设置给item(父菜单)
List<Menu> childrenList = menuList.stream().filter(temp -> item.getId().equals(temp.getFather())).collect(Collectors.toList());
item.setChildren(childrenList);
}
existUser.setMenuList(fatherMenuList);// !!!!!!!!!都是为了这个return Res.success(existUser);// 这里返回existUser 是因为虽然用户输入的是名+密码,但是实际拿到还有图标等信息
*****************************************************************************分配完毕的重新登录
setRoutes()// 动态设置当前用户路由
localStorage.setItem("user", JSON.stringify(res.data.object)) // 存储用户信息到浏览器
localStorage.setItem("menuList", JSON.stringify(res.data.object.menuList)) // 存储菜单信息
*****************************************************************************
if (!localStorage.getItem("user")) { // 首次登录必须刷新路由,否则存在权限不对应问题router.addRoute(finalRoute)
}
*****************************************************************************这个可以处理,也有弊端
created() {
if (window.location.href.indexOf("#reloaded") == -1) {
window.location.href = window.location.href + "#reloaded";
window.location.reload();
}
},
*****************************************************************************全局搜索
Ctrl + Shift + R
myVuex.commit("userLogout")  全局退出登录
*****************************************************************************操作完管理员角色需要重新登录
giveMenu(row) {
this.roleForm.id = row.id; // 为了分配菜单用的
this.roleForm.name = row.name
*****************************************************************************
if (this.$refs.tree !== undefined && this.roleForm.id !== undefined) {if (this.roleForm.name === "管理员") {
myVuex.commit("userLogout")}
}
*****************************************************************************
父级菜单的处理,还真不能存到数据库。就后台动态返回就完事了...
【5】解决一些系统BUG
访问http://localhost:8080/  提示404问题解决
*****************************************************************************404与登录页面处理
if (to.matched.length === 0) { // 未找到路由的情况
const storeMenuList = localStorage.getItem("menuList")
if (storeMenuList) {next("/404")
} else {next("/login") // 如果没有菜单信息跳转到登录页面
}
}
next() // 放行
*****************************************************************************牛批 
网友水友...发现了我发现的问题,青戈说的好像也对
知识的学习,重要的在于运用,我日TM
404BUG的处理。根源还是没有添加路由,还是上次的路由。如何处理呢?
重置路由就解决了
// 重置路由的方法
export const clearRouter = () => {router.matcher = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes})
}
*****************************************************************************
userLogout(state) { // 全局退出方法// 清空缓存localStorage.removeItem("user")localStorage.removeItem("menuList")router.push("/login")// 重置清空路由clearRouter()
}
*****************************************************************************
终于终于搞定了 牛批的版本

****************************************************************************************************************************************************************************

24、服务器部署
【1】自己动手解决,系统菜单收缩后仍展示问题
买阿里云、开端口号
******************************************************************************
firewall-cmd --zone=public --list-ports #查看历史开启端口
systemctl status firewalld #查看防火墙状态
systemctl start firewalld #开启防火墙
firewall-cmd --zone=public --add-port=端口号/tcp --permanent #开启新的端口号
firewall-cmd --reload #重启防火墙
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 端口号 #将80映射到某个端口
firewall-cmd --zone=public --remove-port=端口号/tcp --permanent # 关闭端口号
【2】nginx的配置
#把默认的80端口改成8002端口。代理dist目录/root/site/white/dist
cd /usr/local/nginx/conf
vim nginx.conf               
******************************************************************************               
cd /usr/local/nginx/sbin
./nginx
ps -ef | grep nginx
******************************************************************************
netstat -lnp|grep 8002
kill -9 21458
******************************************************************************
访问http://wdfgdzx.top:8002/
******************************************************************************代理如下目录
location / {root /root/site/white/dist;index index.html index.htm;try_files $uri $uri/ /index.html;
}
******************************************************************************重启
cd /usr/local/nginx/sbin
./nginx -s reload
【3】配置动态whiteIp  blackIp地址  25-36分钟
// 线上前端IP
export const whiteIp = "wdfgdzx.top:8000"
******************************************************************************前端
localhost.js white.js
******************************************************************************后端
localhost.properties    black.properties
【4】使用的技术
前端:VUE2 Vue-Router Vuex ElementUI Axios
后端:SpringBoot Hutool Poi Lombok MybatisPlus

****************************************************************************************************************************************************************************

26、1V1、1对多、多V多查询
【1】以老师、学生教学为示例
*****************************************************************************41
一个表四个复制
【2】主要是数据库的查询练习,看着学习吧。1V1查询
*****************************************************************************
<!--1.2查询分页数据-->
*****************************************************************************每次select的时候
用了left join
用teacher_id去left join user.id,从而获得user.nick as 当做teacherName去使用。在前端展示
!!!!!!!!!!!!!!!牛批,悟透了!!!!!!! 哈哈哈哈哈
*****************************************************************************
从科目到讲课老师:是1V1
从讲课老师到多个科目:是1V多  老师是1    课程是多
【3】老师查看自己教授的课程。1V多
【4】学生选课 多V多
<!--设置学生选课信息,先删除后设置-->

****************************************************************************************************************************************************************************

32、个人中心、修改头像、数据联动/修改密码
【1】个人中心还需要讲吗?.............
一个个人中心讲解了30分钟....
【2】修改密码,autocomplete="new-password"这个好用,不会自动填充密码
<el-form-item label="用户名"><el-input v-model="user.name" disabled autocomplete="off"></el-input>
</el-form-item><el-form-item label="新密码" prop="newPassword"><el-input v-model="passwordForm.newPassword" autocomplete="new-password" show-password></el-input>
</el-form-item><el-form-item label="确认新密码" prop="confirmPassword"><el-input v-model="passwordForm.confirmPassword" autocomplete="new-password"show-password></el-input>
</el-form-item>

****************************************************************************************************************************************************************************

33、集成wangeditor
【1】安装
npm i wangeditor -S --force
********************************************************************************
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date recordTime;
private String modifyPerson;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date modifyTime;
********************************************************************************
Ctrl+R   区分大小写后,批量替换,直接起飞!!!!!!!!!!!!!
********************************************************************************
Error in v-on handler: "Error: 无效的节点选择器:content"
nextTick解决这个问题
********************************************************************************
// 新增的时候开始操作wangEditor
this.$nextTick(() => {
const editor = new E('#content')
editor.create()
})
********************************************************************************23分
对于wangEditor的打开窗口显示,我解决了一个历史性难题,牛批!!!!!!!!!!!
********************************************************************************
@PostMapping("/editor")
public JSON editor(MultipartFile multipartFile) throws IOException { // 这里是三方WangEditor发起的请求,所以不能用包装对象,emmm!!!!!!String originName = multipartFile.getOriginalFilename();String type = FileUtil.extName(originName);long size = multipartFile.getSize();// 1、存到磁盘、存储数据库File uploadDir = new File(MyUtils.getFinalPath());System.err.println(uploadDir);if (!uploadDir.exists()) {System.out.println(uploadDir.mkdir()); //不存在创建新目录}// 2、最终文件路径String fileUUID = IdUtil.fastSimpleUUID() + "." + type;String url = blackIp + "/document/" + fileUUID;File uploadFile = new File(uploadDir + "/" + fileUUID);//3、获取md5if (!uploadFile.exists()) {multipartFile.transferTo(uploadFile); // 这时候才存磁盘!!!!!}String md5 = SecureUtil.md5(uploadFile);Document document = new Document();// 4、判断数据库里md5是否存在document.setMd5(md5);List<Document> documentList = documentMapper.md5_list(document); // 数据库查询到相同md5的对象System.out.println(documentList);if (documentList.size() != 0) {url = documentList.get(0).getUrl(); // 存在的话,url直接从已存在数据库信息拿到System.out.println(uploadFile.delete());// 删除本地存储的文件}// 5、存数据库Document insertDocument = new Document();insertDocument.setUrl(url);insertDocument.setName(originName);insertDocument.setType(type);insertDocument.setSize(size / 1024);insertDocument.setMd5(md5);documentMapper.insert(insertDocument);JSONObject jsonObject = new JSONObject();// 第一个设置jsonObject.set("errno", 0);JSONObject data = new JSONObject();data.set("url", url);JSONArray jsonArray = new JSONArray();jsonArray.add(data);// 第二个设置jsonObject.set("data", jsonArray);return jsonObject;
}

****************************************************************************************************************************************************************************

19、前台页面
【1】先跟着老师实现吧,整合后面自己思考下!!!!
还是很多收获的...
*************************************************************************************
不管是否前台后台一起写,组件名最好不要重名,懂吧!!!!!!!!!!!!// 配置前台页面路由{path: '/white',name: 'White',component: White, // 这是综合管理页面,访问每个子页面的时候它都会展示children: [{path: '/main', name: 'Main', component: Main},]},
*************************************************************************************
注意在这里加了判断:控制路由跳转
if (res.data.object.role === "学生") {this.$router.push("/main") // 跳转前台首页
} else { // 跳到后台首页this.$router.push("/home")this.$message.success("登录成功,欢迎您")
}
*************************************************************************************
登录什么的可以复用原有的管理端....
*************************************************************************************
<el-menu :default-active="'1'" class="el-menu-demo" mode="horizontal" router>
这个router让我排查了一会!!!!!!!!!!!!!!!!!!!!!!!!!!
最后记得带router,日尼玛
*************************************************************************************
青戈真是哥,1小时的视频,我学习捉摸了很久才get到精髓所在。
*************************************************************************************
个人信息页面、修改密码加回来 emmm,卧槽了!!!!!!!!!!!

****************************************************************************************************************************************************************************

14、实现Redis缓存效果
【1】缓存数据,让用户体验更好,速度更快,避免对数据库的多次请求。
spring自带的也有cache缓存,但是我们重点学习redis
****************************************************************************************
单机测试:发现,没有缓存,接口会频繁请求数据库,会有打满风险。
【2】主角:Springboot集成Redis
Redis的学习安装什么的自己在学习一遍吧!!!!!!!!!!!!!!
<!--Redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
****************************************************************************************
// 刷新Redis  其实是直接清空!!!!!!!!!!!! 
// 如果用到了,在增删改的时候都需要调用下flushRedis(Constants.LIST_ALL_KEY);
private void flushRedis(String key) {stringRedisTemplate.delete(key);
}
****************************************************************************************
实现了减轻数据库压力的作用。
当然这样比较粗放,可以更精细化,比如删除那一条,重新从数据库查询,重新set
感觉也差不多!!!!!!!!!!!!!!!!!!
****************************************************************************************
更优的方法,是去除之前的缓存json--->变成List--->操作List的增删改(和数据库保持一致的
的同时,不用二次查询数据库...)!!!!!!!!!!!!!!!!!!!
--->NoSQL???好像也还行......

****************************************************************************************************************************************************************************

20、视频播放
【1】先实现后台文件的预览功能,配置使用kkfileview(不好用 算了)
写个Video页面,然后再写Detail页面
*************************************************************************************
npm i vue-video-player -S --force
*************************************************************************************
npm i video.js -S --force
*************************************************************************************
<template><div class="Detail-container" style="margin-top: 5px;"><div id="player"><video-player class="video-player vjs-big-play-centered"ref="videoPlayer":playsinline="false":options="playerOptions"></video-player></div></div>
</template><script>// 在组件中引入import {videoPlayer} from 'vue-video-player'import 'vue-video-player/src/custom-theme.css'import 'video.js/dist/video-js.css'export default {name: "Detail",// 注册components: {videoPlayer},data() {return {// video: {},// 视频控制设置playerOptions: {playbackRates: [0.5, 1.0, 1.5, 2.0], // 可选的播放速度autoplay: false, // 如果为true,浏览器准备好时开始回放。muted: false, // 默认情况下将会消除任何音频。loop: false, // 是否视频一结束就重新开始。preload: "auto", // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)language: "zh-CN",aspectRatio: "16:9", // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。sources: [{type: "video/mp4", // 类型src: "", // url地址},],poster: "", // 封面地址notSupportedMessage: "此视频暂无法播放,请稍后再试", // 允许覆盖Video.js无法播放媒体源时显示的默认信息。controlBar: {timeDivider: true, // 当前时间和持续时间的分隔符durationDisplay: true, // 显示持续时间remainingTimeDisplay: true, // 是否显示剩余时间功能fullscreenToggle: true, // 是否显示全屏按钮}}// -------}},created() {let id = this.$route.query.id // 从上个路径拿到的idthis.$http.post("/document/select_id", {id: id}).then(res => {// console.log(res)if (res.data.code = "200") {this.video = res.data.object;this.playerOptions.sources[0].src = res.data.object.url  // 赋值视频地址} else {this.$message.error(res.data.code)}})}}
</script><style scoped lang="less"></style>

****************************************************************************************************************************************************************************

21、多级评论
【1】新建novel表和写对应的后端代码!!!!!!!!!!!
【2】处理前端和安装相关插件
npm i mavon-editor@2.10.4 -S --force
***************************************************************************************
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'Vue.use(mavonEditor) // 挂载
***************************************************************************************
import axios from "axios";
***************************************************************************************
imgAdd(pos, $file) {let $vm = this.$refs.md// 第一步.将图片上传到服务器.const formData = new FormData();formData.append('file', $file);axios({url: `http://${whiteIp}/document/upload`, // 这是自己后台的接口method: 'post',data: formData,headers: {'Content-Type': 'multipart/form-data'},}).then((res) => {// 第二步.将返回的url替换到文本原位置![...](./0) -> ![...](url)/*** $vm 指为mavonEditor实例,可以通过如下两种方式获取* 1. 通过引入对象获取: `import {mavonEditor} from ...` 等方式引入后,* `$vm`为`mavonEditor`* 2. 通过$refs获取: html声明ref : `<mavon-editor ref=md ></mavon-editor>,* `$vm`为 `this.$refs.md`*/$vm.$img2Url(pos, res.data);})},
***************************************************************************************
处理小说内容查看的方式 30分钟
【3】最最中重点:小说评论功能的实现
@PostMapping("/select_tree_by_novel_id/{novelId}")
public Res select_tree_by_novel_id(@PathVariable Integer novelId) {List<Discuss> discussList = discussMapper.selectListByNovelId(novelId);List<Discuss> rootList = discussList.stream().filter(discuss -> discuss.getRootId() == null).collect(Collectors.toList()); // 所有没有父级的for (Discuss discuss : rootList) {List<Discuss> childrenList = discussList.stream().filter(temp -> temp.getRootId() != null && temp.getRootId().equals(discuss.getId())).collect(Collectors.toList());// 设置父级评论的用户昵称+用户IDchildrenList.forEach(children -> {Discuss tempDisCuss = discussMapper.selectById(children.getFatherId()); // 根据父id查到对应的评论!!!!注意父id和根id的区别!!!!!!!!User tempUser = userMapper.selectById(tempDisCuss.getUserId()); // 用用户ID查询到用户// 设置父级用户ID+昵称children.setFatherUserId(tempUser.getId());children.setFatherUserNick(tempUser.getNick());});discuss.setDiscussChildrenList(childrenList);}return Res.success(rootList); // 注意返回的是rootList!!!!!!!!!!!!!!!!
}
***************************************************************************************
@PostMapping("/insertOrUpdate")
public Res insertOrUpdate(@RequestBody Discuss discuss) { // @RequestBody很重要if (discuss.getId() != null) {discussMapper.updateById(discuss);} else {if (discuss.getFatherId() != null) {Discuss fatherDiscuss = discussMapper.selectById(discuss.getFatherId()); // 找到父节点if (fatherDiscuss.getFatherId() != null) { // 说明父节点,也有父节点,这时候要操作了!!!!!!!!!!!!discuss.setRootId(fatherDiscuss.getRootId());  // 设置和父节点同样的祖宗ID!!!!!!!!!!!!!!!!!// 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!// 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!// 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!// 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!// 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!// 这里和childrenList用rootId筛选就形成了妙处!!!!!!!!不然就陷入死循环了,秒秒秒呀!!!!!!!!!!!!!!!}}discussMapper.insert(discuss);}return Res.success(null);
}

****************************************************************************************************************************************************************************

22、支付宝
【1】生成公钥私钥的工具
https://opendocs.alipay.com/common/02kipk?pathHash=0d20b438
【2】沙箱环境
https://openhome.alipay.com/develop/sandbox/app
27分钟,orderMapper内容看不到!!!!!!!!!!!!!!!
【3】图书管理、订单管理页面...25分钟
package com.black.controller;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.black.mapper.OrdersMapper;
import com.black.pojo.Alipay;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/alipay")
public class AlipayController {@Resourceprivate OrdersMapper ordersMapper;@GetMapping("/pay")public String pay(Alipay aliPay) {AlipayTradePagePayResponse alipayTradePagePayResponse = null;try {// 发起API调用请求,以创建首付款二维码为例alipayTradePagePayResponse = Factory.Payment.Page().pay(aliPay.getSubject(),aliPay.getTraceNo(),aliPay.getTotalAmount(),"");} catch (Exception e) {e.printStackTrace();}return alipayTradePagePayResponse.getBody();}@PostMapping("/notify")public String notify(HttpServletRequest httpServletRequest) throws Exception {if (httpServletRequest.getParameter("trade_status").equals("TRADE_SUCCESS")) {System.out.println("---支付宝异步回调---");Map<String, String> paramsMap = new HashMap<>();Map<String, String[]> requestParamsMap = httpServletRequest.getParameterMap();for (String name : requestParamsMap.keySet()) {paramsMap.put(name, httpServletRequest.getParameter(name));}String tradeNo = paramsMap.get("out_trade_no");String gmtPayment = paramsMap.get("gmt_payment");String alipayNo = paramsMap.get("trade_no");// 支付宝验签if (Factory.Payment.Common().verifyNotify(paramsMap)) {System.out.println("交易名称" + paramsMap.get("subject"));System.out.println("交易状态" + paramsMap.get("trade_status"));System.out.println("支付宝交易凭证" + paramsMap.get("trade_no"));System.out.println("商户订单号" + paramsMap.get("out_trade_no"));System.out.println("交易金额" + paramsMap.get("total_amount"));System.out.println("买家在支付宝唯一id" + paramsMap.get("buyer_id"));System.out.println("买家付款时间" + paramsMap.get("gmt_payment"));System.out.println("买家付款金额" + paramsMap.get("gmt_pay_amount"));// 更新订单为已支付ordersMapper.updateState(tradeNo, "已支付", gmtPayment, alipayNo);}}return "success";}
}
************************************************************************************
<update id="updateState">update ordersset state=#{state},payment_time=#{gmtPayment},alipay_no=#{alipayNo}where no =#{tradeNo}
</update>
【4】退款功能
<!--支付宝-->
<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-easysdk -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.22.110.ALL</version>
</dependency>
************************************************************************************
用这个包
@GetMapping("/return_pay")
public Res return_pay(Alipay alipay) throws Exception {// 7天无理由退款String now = DateUtil.now();Orders orders = ordersMapper.getByNo(alipay.getTraceNo());if (orders != null) {// hutool工具类,判断时间间隔long between = DateUtil.between(DateUtil.parseDateTime(orders.getPaymentTime()), DateUtil.parseDateTime(now), DateUnit.DAY);if (between > 7) {return Res.error("-1", "该订单已超过7天,不支持退款");}}// 1. 创建Client,通用SDK提供的Client,负责调用支付宝的APIAlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL,alipayConfig.getAppId(), alipayConfig.getAppPrivateKey(), FORMAT, CHARSET,alipayConfig.getAlipayPublicKey(), SIGN_TYPE);// 2. 创建 Request,设置参数AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();JSONObject bizContent = new JSONObject();bizContent.set("trade_no", alipay.getAlipayTraceNo());  // 支付宝回调的订单流水号bizContent.set("refund_amount", alipay.getTotalAmount());  // 订单的总金额bizContent.set("out_request_no", alipay.getTraceNo());   //  我的订单编号request.setBizContent(bizContent.toString());// 3. 执行请求AlipayTradeRefundResponse response = alipayClient.execute(request);if (response.isSuccess()) {  // 退款成功,isSuccess 为trueSystem.out.println("调用成功");// 4. 更新数据库状态ordersMapper.updatePayState(alipay.getTraceNo(), "已退款", now);return Res.success();} else {   // 退款失败,isSuccess 为falseSystem.out.println(response.getBody());return Res.error(response.getCode(), response.getBody());}
}

****************************************************************************************************************************************************************************

23、购物车  1小时10分钟
【1】商品
【2】购物车
【3】订单中心

****************************************************************************************************************************************************************************

24、在线考试

****************************************************************************************************************************************************************************

·

****************************************************************************************************************************************************************************

26、聊天室
 【1】两个浏览器,两个账号!!!WsConfigWsUtil***************************************************************************************设置拦截器:public void addInterceptors(InterceptorRegistry interceptorRegistry) {interceptorRegistry.addInterceptor(jwtInterceptor()) // 调用@Bean返回的.addPathPatterns("/**")// 拦截的所有请求,判断token是否合法来决定是否需要登录。.excludePathPatterns("/big/login","/big/register","/document/**","/**/list_export","/**/list_import","/swagger**/**","/webjars/**","/v2/**","/doc.html","/alipay/**","ws_server/**"); // 需要配置4个swagger放行}
***************************************************************************************
学习之,改进之!!!!!!!!!!!!!!!!!!
<template><div class="Chat-container" style="padding: 10px;margin-bottom:50px; "><el-row><!--在线人数区域--><el-col :span="4"><el-card style="width: 300px;height: 300px;color: #333333"><div style="padding-bottom: 10px;border-bottom: 1px solid #cccccc">在线用户<span style="font-size: 12px;">(点击聊天气泡开始聊天)</span></div><div style="padding: 10px 0;" v-for="item in userList" :key="item.name"><span>{{item.name}}</span><i class="el-icon-chat-dot-round" style="margin-left: 10px;font-size: 16px;cursor: pointer"@click="clickChatUser(item)"></i><span style="font-size: 12px;color: limegreen;margin-left: 5px;" v-if="item.name===chatUser">chatting...</span></div></el-card></el-col><!--聊天窗口--><el-col :span="20"><div style="width: 800px;margin: 0 auto;background-color: white;border-radius: 5px;box-shadow: 0 0 10px #cccccc"><div style="text-align: center;line-height: 50px;">WsWeb聊天室<span style="color: limegreen" v-if="chatUser">(与{{chatUser}}聊天中...)</span></div><div style="height: 350px;overflow: auto;border-top:1px solid #ccc" v-html="content"></div><div style="height: 200px;"><textarea v-model="text"style="height: 160px;width: 100%;padding: 20px; border: none;border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;outline: none"></textarea><div style="text-align: right;padding-right: 10px"><el-button type="primary" size="mini" @click="send">发送</el-button></div></div></div></el-col></el-row></div>
</template><script>import {whiteIp} from "../../public/config";let socket;export default {name: "Chat",data() {return {user: {},userList: [],chatUser: '',chatUserAvatar: '',text: '',messageList: [],content: ''}},created() {this.wsInit()},methods: {clickChatUser(item) {this.chatUser = item.name;this.$http.post("/user/select", item).then(res => {if (res.data.code = "200") {// console.log(res)this.chatUserAvatar = res.data.object.avatar;}})},wsInit() {this.user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {} // 获取本地存储用户let userName = this.user.namelet _this = this;if (typeof (WebSocket) == 'undefined') {console.log('您的浏览器不支持ws...')} else {console.log('您的浏览器支持ws!!!')let socketUrl = "ws://" + whiteIp + "/ws_server/" + userNameif (socket != null) {socket.close()socket = null}socket = new WebSocket(socketUrl);// ws的几个事件,在vue中定义socket.onopen = function () {console.log('ws已经打开...')}socket.onmessage = function (message) {console.log('收到数据===' + message.data)let data = JSON.parse(message.data)if (data.userList) {_this.userList = data.userList.filter(item => item.name !== userName)} else {// if (data.from === _this.chatUser)if (data.from) {// console.log(data.from)_this.chatUser = data.from;_this.$http.post("/user/select", {name: data.from}).then(res => {if (res.data.code = "200") {// console.log(res)_this.chatUserAvatar = res.data.object.avatar;_this.messageList.push(data)// 构建发送的消息_this.createContent(data.from, null, data.text)}})}}}// 关闭事件socket.onclose = function () {console.log('ws已关闭...')}socket.onerror = function () {console.log('发生错误...')}}},send() {if (!this.chatUser) {this.$message({type: 'warning', message: "请选择聊天对象"})return;}if (!this.text) {this.$message({type: 'warning', message: "请输入内容"})} else {if (typeof (WebSocket) == "undefined") {console.log("您的浏览器不支持WebSocket");} else {console.log("您的浏览器支持WebSocket");// 组装待发送的消息 json// {"from": "zhang", "to": "admin", "text": "聊天文本"}let message = {from: this.user.name, to: this.chatUser, text: this.text}socket.send(JSON.stringify(message));  // 将组装好的json发送给服务端,由服务端进行转发this.messageList.push({user: this.user.name, text: this.text})// 构建消息内容,本人消息this.createContent(null, this.user.name, this.text)this.text = '';}}},createContent(remoteUser, nowUser, text) {  // 这个方法是用来将 json的聊天消息数据转换成 html的。let html// 当前用户消息if (nowUser) { // nowUser 表示是否显示当前用户发送的聊天消息,绿色气泡html = "<div class=\"el-row\" style=\"padding: 5px 0\">\n" +"  <div class=\"el-col el-col-22\" style=\"text-align: right; padding-right: 10px\">\n" +"    <div class=\"tip left\">" + text + "</div>\n" +"  </div>\n" +"  <div class=\"el-col el-col-2\">\n" +"  <span class=\"el-avatar el-avatar--circle\" style=\"height: 40px; width: 40px; line-height: 40px;\">\n" +"    <img src=\"" + this.user.avatar + "\" style=\"object-fit: cover;\">\n" +"  </span>\n" +"  </div>\n" +"</div>";} else if (remoteUser) {   // remoteUser表示远程用户聊天消息,蓝色的气泡html = "<div class=\"el-row\" style=\"padding: 5px 0\">\n" +"  <div class=\"el-col el-col-2\" style=\"text-align: right\">\n" +"  <span class=\"el-avatar el-avatar--circle\" style=\"height: 40px; width: 40px; line-height: 40px;\">\n" +"    <img src=\"" + this.chatUserAvatar + "\" style=\"object-fit: cover;\">\n" +"  </span>\n" +"  </div>\n" +"  <div class=\"el-col el-col-22\" style=\"text-align: left; padding-left: 10px\">\n" +"    <div class=\"tip right\">" + text + "</div>\n" +"  </div>\n" +"</div>";}console.log(html)this.content += html;}}}
</script><style>.tip {color: white;text-align: center;border-radius: 10px;font-family: sans-serif;padding: 10px;width: auto;display: inline-block !important;display: inline;}.right {background-color: deepskyblue;}.left {background-color: forestgreen;}
</style>

****************************************************************************************************************************************************************************

30、电商系统与主流
【1】电商系统:
在终端执行这两个就能运行起来了:
set NODE_OPTIONS=--openssl-legacy-provider
npm run serve
【2】博客系统:
set NODE_OPTIONS=--openssl-legacy-provider
npm run serve
****************************************************************************************
npm i --force
set NODE_OPTIONS=--openssl-legacy-provider
npm run dev
【3】电影系统
Solution1:
npm install -g npm-check-updates
ncu -u
npm install

****************************************************************************************************************************************************************************

方式一:安装python解决(正确配置系统环境变量),python(v2.7 recommended, 
v3.x.x is not supported) - 推荐
下载:http://www.python.org/ftp/python/2.7.3/python-2.7.3.msi
自行下载
npm install --python=python2.7 #先下载
npm config set python python2.7 #再设置环境
**********************************************************************************************
# npm install node-sass@latest
npm install node-sass@4.12 --force 好使  卧槽
**********************************************************************************************
删除node_modules文件 
卸载webpack npm remove webpack-dev-server 
重装指定版本webpack npm install webpack-dev-server@2.9.1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/831168.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第61题旋转链表

题目&#xff1a; 题解&#xff1a; func rotateRight(head *ListNode, k int) *ListNode {if k 0 || head nil || head.Next nil {return head}n : 1iter : headfor iter.Next ! nil {iter iter.Nextn}add : n - k%nif add n {return head}iter.Next headfor add > …

leaflet加载wms服务实现wms交互

leaflet地图与wms服务的交互&#xff0c;点击wms服务获取地理区域信息以及后续操作 加载wms服务 给地图添加监听点击事件 构造GetFeatureInfo发送请求&#xff0c;需要包含WMS服务的URL、请求的类型&#xff08;GetFeatureInfo&#xff09;、返回信息的格式&#xff08;通常是…

golang判断通道chan是否关闭的2种方式

chan通道在go语言的办法编程中使用频繁&#xff0c;我们可以通过以下2种方式来判断channel通道是否已经关闭&#xff0c;1是使用 for range循环&#xff0c;另外是通过 for循环中if 简短语句的 逗号 ok 模式来判断。 示例代码如下&#xff1a; //方式1 通过for range形式判断…

进销存单机版和excel进销存那个好用

进销存单机版和EXCEL进销存哪个好用&#xff1f;单机版是安装在单台电脑上使用的&#xff0c;它不能像网络版一样可以多台电脑同时共享数据&#xff0c;所以进销存单机版有一个优势就是不需要连接网络也可以使用。 进销存单机版 进销存软件单机版是经过开发人员设计好的一种信…

es环境安装及php对接使用

Elasticsearch Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c;基于RESTful web接口。Elasticsearch是用Java语言开发的&#xff0c;并作为Apache许可条款下的开放源码发布&#xff0c;是一种流行的…

postman一直转圈圈,无法启动

解决 地址栏输入%appdata%进入此目录&#xff0c;删除%appdata%目录下的postman文件可以解决问题。

贪心算法 Greedy Algorithm

1) 贪心例子 称之为贪心算法或贪婪算法&#xff0c;核心思想是 将寻找最优解的问题分为若干个步骤 每一步骤都采用贪心原则&#xff0c;选取当前最优解 因为没有考虑所有可能&#xff0c;局部最优的堆叠不一定让最终解最优 v2已经不会更新v3因为v3更新过了 贪心算法是一种在…

react路由路径兼容.html

react路由路径兼容.html 背景根文件代码路由代码nginx配置 背景 因为react-router 的路径是没有.html后缀的&#xff0c;这样对于和其他系统有交互的时候&#xff0c;让别人改url地址不是很方便&#xff0c;所以想办法进行了老系统的兼容操作。 根文件代码 import React fro…

Spring Boot系列之条件注解

概述 想要搞懂Spring Boot自动配置&#xff0c;绕不过条件注解&#xff0c;即Conditional&#xff0c;可用于根据某个特定的条件来判断是否需要创建某个特定的Bean。本文分析基于spring-boot-autoconfigure-3.2.4版本。 Conditional注解可以添加在被Configuration、Component…

Ps 滤镜:智能锐化

Ps菜单&#xff1a;滤镜/锐化/智能锐化 Filter/Sharpen/Smart Sharpen 智能锐化 Smart Sharpen滤镜可以用来提高图像的视觉清晰度和边缘细节&#xff0c;同时最大限度地减少常见的锐化问题如噪点和光晕等。 “智能锐化”滤镜通过自适应算法分析图像内容&#xff0c;针对不同的细…

省级财政收入、支出、第一、二、三产业增加值、工业增加值、金融业增加值占GDP比重数据(1978-2022年)

01、数据介绍 财政收支作为国家治理的基础&#xff0c;越来越受到社会各界的关注。同时&#xff0c;产业结构的优化与升级也是中国经济持续增长的关键因素。本数据对中国省级财政收入、支出占GDP的比重以及第一、二、三产业的增加值占GDP的比重和工业增加值占GDP的比重、金融业…

Pandas入门篇(二)-------Dataframe篇5(进阶)(Dataframe的时间序列Dataframe最终篇!!)(机器学习前置技术栈)

目录 概述一、pandas的日期类型&#xff08;一&#xff09;datetime64类型的特点&#xff08;二&#xff09; 时间序列的创建1.从字符串创建datetime64类型2. 整数&#xff08;Unix时间戳&#xff09;创建datetime64类型3.导入数据时直接转换 &#xff08;三&#xff09;dateti…

力扣经典150题第四十九题:插入区间

目录 题目描述和要求示例解释解题思路算法实现复杂度分析测试和验证总结和拓展参考资料 题目描述和要求 给定一个无重叠的、按照区间起始端点排序的区间列表 intervals&#xff0c;其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束。同样给定一个区间 newInter…

开源库交叉编译(七)--- FAAD2

官网&#xff1a;https://www.linuxfromscratch.org/blfs/view/svn/multimedia/faad2.html 源码&#xff1a;faad2-2.10.1.tar.gz Step 1&#xff1a;解压 tar -xvzf faad2-2.10.1.tar.gzcd faad2-2.10.1/ Step 2&#xff1a;创建安装目录 mkdir arm_faad2 Step 3&#…

MyBatis:mybatis入门

MyBatis 持久层框架,用于简化JDBC开发,是对原始JDBC程序的封装 持久层 数据访问层(dao),用来操作数据库 框架 一个半成品软件,一套可重用的通用软件代码模型. JDBC (Java DataBase Connectivity) 使用java语言操作关系型数据库的一套API 本质是sun公司官方定义的一套操…

打印机-STM32版本 硬件部分

最终PCB EDA工程: 一、确定芯片型号 根据项目需求&#xff0c;梳理需要用到的功能&#xff0c; 电量检测&#xff1a;ADC 按键&#xff1a;IO input外部中断 LED&#xff1a;IO output 温度检测&#xff1a;ADC 电机控制&#xff1a;IO output 打印通讯&#xff1a;SPI …

C++string类使用大全

目录 温馨提示&#xff1a;这篇文章有约两万字 什么是string类&#xff1f; 一. 定义和初始化string对象 1.string的构造函数的形式&#xff1a; 2.拷贝赋值运算符 3.assign函数 二.string对象上的操作 1.读写string对象 2.读取未知数量的string对象 3.使用getline …

windows ubuntu sed,awk,grep篇:10.awk 变量的操作符

目录 62. 变量 64. 算术操作符 65. 字符串操作符 66. 赋值操作符 67. 比较操作符 68. 正则表达式操作符 62. 变量 Awk 变量以字母开头&#xff0c;后续字符可以是数字、字母、或下划线。关键字不能用作 awk 变量。 不像其他编程语言&#xff0c; awk 变量可以直接使…

实习面试之算法准备:数学题

目录 1 技巧2 例题2.1 Nim 游戏2.2 石子游戏2.3 灯泡开关 1 技巧 稍加思考&#xff0c;找到规律 2 例题 2.1 Nim 游戏 你和你的朋友&#xff0c;两个人一起玩 Nim 游戏&#xff1a; 桌子上有一堆石头。 你们轮流进行自己的回合&#xff0c; 你作为先手 。 每一回合&#xf…

vue修饰符有哪些

Vue中常用的修饰符主要有以下几类&#xff1a; 事件修饰符&#xff1a; .stop&#xff1a;阻止事件的冒泡&#xff0c;相当于调用了event.stopPropagation()方法。 .prevent&#xff1a;阻止事件的默认行为&#xff0c;相当于调用了event.preventDefault()方法。 .capture&a…