关于慕课网GIT说明:https://www.imooc.com/help/detail/111
使用下面的命令将源码从远程仓库拉取到本地,需要本地提前安装好git
git clone https://git.imooc.com/coding-474/jiawawiki.git
会配置ssh的,可以用ssh:
git clone ssh://git@git.imooc.com:80/coding-474/jiawawiki.git
cd web
npm install
@ResponseBody
用来返回字符串或JSON对象,使用此注解,表示接口支持所有请求接口(GET、POST、PUT、DELETE)@GetMapping
只接受GET方法,类似还有@PostMapping
、@PutMapping
、DeleteMapping
@RequestMapping
用法:@RequestMapping(value = "/hello", method = RequestMethod.GET)
@RestController
一般用来返回字符串
@Controller
一般用来返回页面
@Primary
注解,当有多个实现类是,首先注入此类
Alt
+ Enter
自动导包
Alt
+ Insert
键,打开Generate面板,包含getter and setter、constructor、toString等方法
如果Applicatiob和Controller类不在同一个包内,须要添加 @ComponentScan("Cotroller包全限定路径")
自动删除无用的引用file-->settings-->搜索 auto
在Auto Import中勾选Optimize import on the fly
bootstrap
配置,单个SpringBoot不会读取bootstrap配置,要SpringCloud架构下的SpringBoot应用才会读,一般用于动态配置,线上可以实时修改实时生效的配置,一般配合nacos使用。
<property name="url" value="jdbc:mysql://47.113.221.222:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8"/>
File > Invalidate Caches /Restart
即可清理缓存引入依赖,使用SpringBoot内置的依赖,不需要添加``version</mark> 版本号
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
file-->settings-->搜索 compiler,再在右侧勾选 Build project automatically
按两次Shift
键,切换至Action
(或者在help--> find Action...),搜索registr(注意:不是register
,也不是选 Registration Actions
`),勾选compiler.automake.allow.when.app.running
注意:只有保存配置文件(application.properties 等)Ctrl
+ S
, 才会触发热部署, 或者点击“小锤子” 图标编译;或者稍微改一下service层代码
当复制一个类,在复制的类里按 Ctrl
+ R
进行替换(注意勾选 match case 区分大小写)
复制一个类,如果在左边复制是复制一个新类,在右边复制是复制一个类名
new 类名 按下Ctrl
+ Alt
+ V
自动生成一个新类
后端接口很多,为了前端能够统一处理逻辑(登录校验、权限校验),需要统一后端的返回值
选中一个类,按 Shift
+ F6
进行类重命名
toString 一般用来打日志用
两种for循环模板:fori
、 iter
在cotroller层不要出现domain实体ebook,用ebookResp(或ebookVo)代替
Ctrl
+ Y
删掉当前行
有时idea提示程序包不存在,实际是有的。Maven可能出现编译缓存。maven—>clean即可.如果觉㧹代码没有错,但编译报错,就clean一下
- 测试结果跟代码不符
- 代码没错却编译出错
- 引入了jar包却没反应
养成习惯:改动再小,也要测试通过
Vuex 保存全局变量
main.ts是初始(配置)文件
Vue CLI 初始执行main.ts,将内容页App.vue渲染到index.html,完成页面显示
"dependencice":{
"vue": "^30.0.0",
"vue-router": "^4.0.0-.",
"vux": "^4.0.0-0"
}
^
表示如果有小版本,将自动更新。package-lock.json
文件进行版本锁定
<router-view/>
标签来填充路由内容setup()
Vue3新增的初始化方法Ctrl
+ Shift
+F
全局搜索setup(){
console.log("setup");
const ebooks = ref();
onMounted(() => {
axios.get("http://localhost:8880/ebook/list?name=Spring").then((response) => {
console.log(response);
const data = response.data;
ebooks.value = data.content;
console.log(response);
})
})
return {
ebooks
}
}
setup(){
console.log("setup");
const ebooks1 =reactive({books: []});
onMounted(() => {
axios.get("http://localhost:8880/ebook/list?name=Spring").then((response) => {
console.log(response);
const data = response.data;
ebooks.value = data.content;
ebooks1.books = data.content;
console.log(response);
})
})
return {
ebooks2: toRef(ebooks1,"books")
}
}
上述的两种方法都可以用,但是我们在项目中,尽量统一,要么全用ref,要么全用reactive
const pagination = {
onChange: (page: number) => {
console.log(page);
},
pageSize: 3,
};
npm install --save @ant-design/icons-vue
全局使用图标:
切换到main.js
添加import * as Icons from '@ant-design/icons-vue'
在下边继续添加
const icons: any = Icons;
for (const i in icons) {
app.component(i, icons[i]);
}
改造代码:
createApp(App).use(store).use(router).use(Antd).mount('#app');
为:
const app = createApp(App);
app.use(store).use(router).use(Antd).mount('#app');
createApp(App).use(store).use(router).use(Antd).mount('#app');
<style scoped>
.ant-avatar {
width: 50px;
height: 50px;
line-height: 50px;
border-radius: 8%;
margin: 5px 0;
</style>
自定义参数:VUE_APP_XXX必需以VUE_APP_开头,自定义文件放在web目录下
NODE_ENV=production
VUE_APP_SERVER=http://127.0.0.1:8080
三个通知:前置、后置、环绕。
Shift
+ F6
重命名
{
dataIndex: 'name',
key: 'name',
slots: { title: 'customTitle', customRender: 'name' },
}
PageHelper.startPage(1,3);
// 语句只对第一个select起作用,如果后边有两个select,第二个不执行
PageInfo<Ebook> pageInfo = new PageInfo<>(elookList);
LOG.info("总行数:" + pageInfor.getTotal());
LOG.info("总页数:" + pageInfor.getPages());
分页四个元素:
页码:(pageNume:1)
数量:(pageSize:3)
总行数:pageInfor.getTotal()
总页数:pageInfor.getPages()
后两个,建议返回total,前端还可以自己去计算总页数;但是如果返回总页数,前端不能确切的知道总条数。
Alt
+ Insert
键,生成get set方法onMounted() => {
handleQuery({
page:1,
size:pagination.value.pageSize
})
}
真正传到后端的是上述的page、size,这个必须和后端的PageReq的两个参数要一致才会将前端的参数自动映射到EbookReq req这个类里来;
/**
* 数据查询
**/
<!-- 下面params是传过来的变量-->
<!-- any 表示它是一个任何类型都可以,其实是一个json对象 -->
const handleQuery = (params: any) => {
loding.value = true;
axios.get("/ebook/list",{
<!--下面的params不是传过来的参数-->
params: {
page: params.page,
page: params.size
}
}).then((response) => {
loading.value = false;
const data = response.data;
ebooks.value = data.content.list;
}
另一种方式:
<!-- 下面params是传过来的变量-->
const handleQuery = (params: any) => {
loding.value = true;
axios.get("/ebook/list?page=" + params.page + "&size=" + params.size).then...
}
Ctrl
+ D
复制当前行并粘贴
edit
方法无参数,可用下边两种方法表示:
<a-button type="primary" @click="edit">编辑</a-button>
<a-button type="primary" @click="edit()">编辑</a-button>
edit
方法有参数表示方法
<a-button type="primary" @click="edit(record)">编辑</a-button>
Alt
+ Shift
+ 上/下:可将当前行上下移动
@RequestBody
注解对应的是json方式和(POST)提交,就像ebook用的是content-type:application/json 需要用@RequestBody才能接收到。同样是POST提交,以form方式提交(Content-Type: application/x-www-form-urlencoded)就不需要任何注解。
POST http://localhost:8880/ebook/save
Content-Type: application/json
{}
public CommonResp save(@RequestBody EbookSaveReq ebookSaveReq) {}
EbookSaveReq类和EbookQueryResp类内容相同,这种设计方法会使类变得很多,但是比较灵活,后面加参数校验时可以体现出来。在6-11中
如果定义的类带有泛型,new时也要带有泛型,否则会new失败且不会报错
public class CommonResp<T> {} //声明类
CommonResp resp = new CommonResp<>(); //new对象
Ctrl
+ D
对比修改内容Alt
+ Shift
+ 上 代码上移setup(){
const param = ref(); //定义一个响应式变量
param.value = {}; //初始给它一个空变量,否则会报错
}
Ctrl
+ Y
删除选中的内容
加上:
表示变量,不加表示属性
<a-table
:columns="columns"
:data-source="level1"
:row-key="record => record.id"
:pagination="false"
>
树形数据展示,查看Ant Design Vue—>Table表格—>树形数据展示
<a-selection-option v-for="c in level1" :key="c.id" :value="c.id" :disabled="category.id === c.id">
{{c.name}}
</a-selection-option>
如果是在属性里边用变量,我们直接用变量就可以了;
如果是在HTML,就是属性外面,在节点中间要去使用这个变量的话,应该用两个大括号
Ctrl
+ Shift
+ Z
:代码重做(就是回到Ctrl+Z回退之前)const
= constant 不变的、永恒的
let
表示变量
/**
* 将某节点及其子孙节点全部置为disabled
*/
const setDisable = (treeSelectData: any, id: any) => {
// console.log(treeSelectData, id);
// 遍历数组,即遍历某一层节点
for (let i = 0; i < treeSelectData.length; i++) {
const node = treeSelectData[i];
if (node.id === id) {
// 如果当前节点就是目标节点
console.log("disabled", node);
// 将目标节点设置为disabled
node.disabled = true;
// 遍历所有子节点,将所有子节点全部都加上disabled
const children = node.children;
if (Tool.isNotEmpty(children)) {
for (let j = 0; j < children.length; j++) {
setDisable(children, children[j].id)
}
}
} else {
// 如果当前节点不是目标节点,则到其子节点再找找看。
const children = node.children;
if (Tool.isNotEmpty(children)) {
setDisable(children, id);
}
}
}
};
/**
* 编辑
*/
const edit = (record: any) => {
modalVisible.value = true;
doc.value = Tool.copy(record);
// 不能选择当前节点及其所有子孙节点,作为父节点,会使树断开
treeSelectData.value = Tool.copy(level1.value);
setDisable(treeSelectData.value, record.id);
// 为选择树添加一个"无"
treeSelectData.value.unshift({id: 0, name: '无'});
};
/**
* 新增
*/
const add = () => {
modalVisible.value = true;
doc.value = {};
treeSelectData.value = Tool.copy(level1.value) || [];
// 为选择树添加一个"无"
treeSelectData.value.unshift({id: 0, name: '无'});
};
<router-link :to="'/admin/doc?ebookId=' + record.id">
tips:
npm iwangeditor@4.6.3 --save
import E from "wangeditor"
const editor = new E("#div1")
editor.create()
初始的时候,没有modal这块标签(即文档表单),放入到sutUp中渲染不出来;根本就没有这样一个元素,所以初始的时候我们写 #ID
去获取这个选择器的时候,选择不到任何元素根本原因:html上没有content元素,只要html有content元素,即使不可见,也能被渲染出来
加入到onMount中仍旧显示不出来,考虑到modal初始是隐藏的,会不会是因为隐藏而找不到content?
添加到add()和edit()中也不显示;考虑到加载modal是很耗时的,在modal还没加载出来的时候editor.reate()就已经执行了,所以需要异步执行;
需要异步执行时,要想到setTimeout
setTimeout(function(){
editor.create();
},100);
// 将富文本的层级调低
editor.config.zIndex = 0;
<a-table
v-if="level1.length > 0" // 只有level1的长度大于0, level1是动态的
:columns="columns"
:data-source="level1"
:row-key="record => record.id"
:pagination="false"
:defaultExpandAllRows="true" // 展开才生效, 这个属性不是动态监听数据的变化的,它是初始的时候执行一次,后边就不在执行。如果在初始的时候,level1还没有数据就没有展开效果了,所以加上上边v-if那一句话,是有数据了,采取展示这个表格,此时这个属性就又作用了
>
...
const level1 = ref(); // 初始化的时候为null
level1.value = []; // 初始化一个空数组,否则当为null时,上边的level1.length就会出错了
doc.value.content = editor.txt.html();报 Property 'content' does not exist on type '{}'.
错误,是因为在定义的时候定义错了定义成了 const doc= ref({});
这样是写了一个空对象;可以初始给它复制一个空对象,如:
const doc = ref();
doc.value = {};
这样就可以了
使用!!绕过类型检验
:disabled="user.id" // 会报Expected Boolean, got Number with ...
修改成
:disabled="!!user.id"
// 更新用户,是用户名不可修改
user.setLoginName(null); // 设置接收用户名为空,防止被人绕过前段
userMapper.updateByPremaryKeySelective(user);// 只修改有改动的值
tips:如果要用到第三方的js里边的一些变量,需要在使用的地方定义一下,告诉它这些变量是存在的。
declare let hexMd5: any;
v-if和i-show都可以用来显示或者隐藏某个元素;v-show只是不显示,适用于动态变化;而v-if会不加载(删掉),初始的时候就判断好是隐藏还是显示。
单点登录系统有可能只是提供接口,也可能包括页面
要使用JWT需要引入额外的依赖包
@NotNull
: 会校验null
NotEmpty
: 会校验null 和“”
使用Redis,需要先引入redisTemplate
redisTemplate.opsForValue().set(token, userLoginResp, 3600*24, TimeUnit.SECONDS)
token随着用户登录信息返回给前端
把一个类放入到rides里边,需要序列化(implements Serializable )或者直接转化成字符串JSONObject.toJSONString(userLoginResp)
;DUBBO是RPC框架,就用到了序列化技术,比如:从A应用的类传到B应用去执行
@Autowired自动注入注解 实际上调用Impl的具体实现 但是当一个接口的方法,对应多个实现的时候,怎么区分到底注入哪一个呢 答案是@Qualifier注解和@Resource注解
@Qualifier注解的用处:当一个接口有多个实现的时候,为了指名具体调用哪个类的实现
@Resource注解:可以通过 byName命名 和 byType类型的方式注入, 默认先按 byName的方式进行匹配,如果匹配不到,再按 byType的方式进行匹配。 可以为 @Service和@Resource 添加 name 这个属性来区分不同的实现
vuex :全局响应式变量
vuex封装sessionStorage,各页面/组件只知道vuex,不需要知道sessionStorage
index.ts
declare let SessionStorage: any;
const USER = "USER"
const store = createStore({
state:{
user: SessionStorage.get(USER) || {} // 避免空指针异常
},
// 同步
mutations: {
setUser(state, user){ // user为传进来的值
state.user = user
}
}
// 异步
actions: {
}
modules:{
}
});
export default store;
store.commit("setUser",user.value); // 会自动调用上边的mutations方法
更新方式:
实时更新
定时批量更新
定时框架Quartz
logback 增加自定义参数
可以给定时任务添加线程池
在线程开始的地方,给LOG_ID赋值
@Component
@ServerEndpoint("/ws/{token}")
publi class WebSocketServer{
// 注意上边的注解,练习时由于忘了写注解导致链接WebSocket出错
}
SpringBoot异步化使用
两个功能代码写在一条线上,会互相影响,可以使用异步线程让两个功能走两条线
易犯错误:
@Asyn
要加在方法上; @Asyn
注解的异步方法,要写到另一个类里边去,不能跟调用的地方在同一个类里边 很多新手认为加上注解,异步化就成功了,测试起来也没有发现什么异常WebSocketServer.java 单一职责原则
WsService类要能被注入到其他类,前提是一定要能被扫描到该类,记得添加@Service注解
同时对两张表有增删改的操作,就要考虑加事务,否则会造成数据不准确。当然也有不加事务的场景,不能一概而论
同一个类里A调用B,B加事务注解不生效
异步有可能方法内部耗时较长,容易出错
当线程的数量超过线程池的最大线程数量(容量),后边的请求就会变成同步,业务量很大的话,就会有可能让线程越来越多,一直到把我们整个服务器塞满,就会影响到原有的业务。所以就引入了MQ,和Redis一样,MQ是一个中间件,需要单独安装。常用的有RocketMQ、Kafka、RabbitMQ等
mqnamesrv.cmd
(相当于注册中心,管理mqbroker) @RocketMQMessageListener(consumerGroup = "default", topic = "VOTE_TOPIC")
,记得添加@Service注解,使能够被扫描到流程:点赞—>发送MQ—>消费MQ—>推送WS
本章内容都被注释掉了,只是学习,本应用可以用异步进行处理,没必要引入MQ
统计数据收集与Echarts报表
统计维度:
业务表统计: 所有报表数据都是从业务表直接获取
中间表统计:定时将业务表数据汇总到中间表,报表数据从中间表获取
概念: 快照
从业务表收集数据的SQL尽量简单,不要影响业务表性能
快照分成两部分:
凌晨1点,把所有的电子书都insert一条快照、后边每小时都是update后四个字段。
字段添加唯一键(unique)不影响java代码
mybatis默认一次只能执行一个sql , 我们可以通过修改链接,增加一个配置项,让它可以执行多个sql。即在application.properties中的数据库地址中添加
spring.datasource.url=jdbc:mysql://ip/datatable?...&allowMultiQueries=true
@JsonFormat(pattern="MM-dd", timezone = "GMT+8")
private Date date;
加载完index.html后,vue相关的内容还没初始化,所以界面会显示
ECS和RDS要在同一地域,这样可以用内网链接,否则只能用外网链接
多环境的启动
nohub可以让要执行的指令后台运行
deploy.sh 文件内的~,表示的是当前用户的目录
启动deploy.sh会报错,是因为windows下的换行和linux的换行符不一致,需要在linux下vim deploy.sh 设置,按Esc 输入: set ff=unix 回车,这样就转换了换行符了,这样再保存,就不会再报错,执行:sh deploy.sh
查看日志:tail -100f trace.log
如果日志较多,可以查看日志的跟踪号: grep "跟踪号" trace.log即可过滤出需要的信息
try_files $uri $uri/ /index.html;
解决页面刷新后空白的问题;server_name 修改成自己的ip include /etc/nginx/conf.d/*.conf
mysql 8版本 中数据导入mysql5版本
1.通过文本编辑器搜索“ utf8mb4_0900_ai_ci” 批量替换成 “ utf8_general_ci ”
2.将“ utf8mb4 ” 批量替换成 " utf8 "
3.保存后,再次进行导入sql文件即可。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。