第一章=>
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;
@Bean
public 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"; // 导入axios
Vue.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 {
@Bean
public 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 {
@Bean
public 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属性,该属性保存了axios
baseURL: "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 = true
this.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'}) // 挂载elementUI
new 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 {
@Resource
UserMapper 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(); //拿到httpServletRequest
String token = httpServletRequest.getHeader("token");//拿到token
if (StrUtil.isNotBlank(token)) {
String id = JWT.decode(token).getAudience().get(0); //拿到user.id
return 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、获取md5
if (!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-upload
name="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传过来的user
const {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、获取md5
if (!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 // 从上个路径拿到的id
this.$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());
// 设置父级评论的用户昵称+用户ID
childrenList.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 {
@Resource
private 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 orders
set 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,负责调用支付宝的API
AlipayClient 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 为true
System.out.println("调用成功");
// 4. 更新数据库状态
ordersMapper.updatePayState(alipay.getTraceNo(), "已退款", now);
return Res.success();
} else { // 退款失败,isSuccess 为false
System.out.println(response.getBody());
return Res.error(response.getCode(), response.getBody());
}
}
****************************************************************************************************************************************************************************
23、购物车 1小时10分钟
【1】商品
【2】购物车
【3】订单中心
****************************************************************************************************************************************************************************
24、在线考试
****************************************************************************************************************************************************************************
·
****************************************************************************************************************************************************************************
26、聊天室
【1】两个浏览器,两个账号!!! WsConfig WsUtil *************************************************************************************** 设置拦截器: 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.name let _this = this; if (typeof (WebSocket) == 'undefined') { console.log('您的浏览器不支持ws...') } else { console.log('您的浏览器支持ws!!!') let socketUrl = "ws://" + whiteIp + "/ws_server/" + userName if (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