XieJava's blog

记录最好的自己


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于

网络安全之国际主流网络安全架构模型

发表于 2024-11-18 | 更新于: 2024-11-18 | 分类于 技术 , 网络安全 | | 阅读次数:
字数统计: 2.1k | 阅读时长 ≈ 7

目前,国际主流的网络安全架构模型主要有:
● 信息技术咨询公司Gartner的ASA(Adaptive Security Architecture自适应安全架构)
● 美国政府资助的非营利研究机构MITRE的ATT&CK(Adversarial Tactics Techniques & Common Knowledge 对抗战术技术和常识)
● 信息技术咨询公司Gartner的CSMA(Cybersecurity Mesh Architecture网络安全网格架构)

1. Gartner提出的ASA(Adaptive Security Architecture自适应安全架构)

2014年,针对于当时业界安全产品主要重在防御和边界,安全态势形成严重挑战的问题,Gartner公司首次提出ASA(Adaptive Security Architecture自适应安全架构),希望业界能从单纯被动防御和应急响应的思路中解放出来,通过加强持续监测和分析预测提升主动防御能力,1.0架构主要包括以下四个象限:

1) 预防Pretect,通过系统加固和隔离等手段来减少系统暴漏面积,比如采用黑名单;
2) 检测Detect,持续监视/检测事故的发生并对其进行评估,对检出的事故进行隔离,以防止其造成进一步的破坏;
3) 响应Response,事故发生之后需要对事故进行补救和溯源;
4) 预测Predict,根据以往已经存在的威胁,来预测潜在的威胁。

2017年,针对于高级攻击的防御架构,Gartner公司在1.0架构的基础上进行了相关的理论丰富,将自适应安全架构的外延扩大,自适应安全架构进入2.0时期:

1) “持续监控分析”改成“持续可视化和评估”,同时加入UEBA相关的内容(User& Entity behavior analytics用户和实体的行为分析);
2) 引入每个象限的小循环体系,不仅仅是四个象限大循环;
3) 在大循环中加入了策略和合规要求,同时对大循环的每个步骤说明了循环的目的,到保护象限是实施动作、到检测象限是监测动作、到响应和预测象限都是调整动作。

2018年,Gartner公司正式确认了CARTA(Continuous Adaptive Risk and Trust Assessment持续自适应风险与信任评估)的安全趋势,即自适应安全架构3.0。相比2.0架构,3.0架构最大的变化是把2.0架构作为攻击的保护外环,增加关于访问的保护内环,原因在于:

1) 2.0架构未考虑认证问题,导致完整性有缺失,如黑客获取有效认证内容,如用户名密码,自适应架构对于此类事件是“可信”的,威胁无法感知;
2) 为加强云时代下的云服务的发现、访问、监控和管理,3.0架构将CASB(Cloud Access Security Broker云访问安全代理)作为原型挪到了这个总体架构中,解决了部分认证问题;
3) 如果认证体系只是一次性认证并没有持续的监控和审计,必须要有被窃取认证信息的心理预期,所以要持续的进行监控和分析以及响应,形成闭环。

3.0架构的适用场景变得更为广泛,包括了安全响应、数据保护、安全运营中心、开发安全运维、物联网、供应链安全、业务持续和灾难恢复等领域。

ASA自适应安全架构

2. MITRE提出ATT&CK(Adversarial Tactics Techniques & Common Knowledge 对抗战术技术和常识)

ATT&CK(Adversarial Tactics Techniques & Common Knowledge 对抗战术技术和常识)是一个知识库,它从攻击者的角度看待问题,理顺了攻击者渗透网络、入侵主机、提升权限、隐秘移动和渗漏数据的攻击链,建立了网络攻击中使用的战术和技术的详尽列表,呈现了攻击者在攻击网络时所采用的行为,并且详细介绍了每一种技术的利用方式。

该模型被CISA(美国网络安全与基础设施安全局)和FBI以及超过80%的企业用于威胁调查,它对于政府或企业组织来说都非常有用,因为组织需要建立一个基于威胁的防御体系。

ATT&CK模型可以帮助网络事件响应团队(CERT)、安全运营中心(SOC)、红蓝队、威胁猎手、IT部门等安全团队,更好地测试、开发和排序其检测和响应机制,对公司的业务、行业和知识产权提供高效安全保障,具体可分为:

1) Detections and Analytics(检测和分析):帮助网络防御者开发分析程序,以检测对手使用的技术。
2) Threat Intelligence(威胁情报):为分析人员提供了一种通用语言来构造,比较和分析威胁情报。
3) Adversary Emulation and Red Teaming(攻击模拟):提供了一种通用语言和框架,攻击模拟团队可以使用该语言和框架来模拟特定威胁并计划其行动。
4) Assessment and Engineering(评估与工程化):可用于评估组织的能力并推动工程决策。

但是,安全公司McAfee与加州大学伯克利分校长期网络安全中心的联合调研项目发现,很多网络安全团队在框架应用方面仍然面临诸多挑战:

1) 大多数采用ATT&CK框架的安全团队都没有实现自动化。虽然91%的企业使用ATT&CK框架来标记网络安全事件,但只有不到一半的企业可以自动更改部分安全策略;
2) ATT&CK框架和安全产品之间的互操作性存在困难;
3) 难以将网络安全事件映射到安全策略更改,以及无法关联来自云、网络和端点的事件;
4) 企业使用的安全产品可能无法检测到ATT&CK矩阵中存在的所有技术。

2021年,MITRE发布了ATT&CK的第十个版本,该版本最大的变化是在企业矩阵(Enterprise ATT&CK)中添加了一组新的数据源和数据组件对象,以补充ATT&CK v9中发布的数据源名称更改。新版ATT&CK企业矩阵包含14个战术、188个技术、379个子技术、129个组和638个软件。

ATT&CK架构

3. Gartner提出的CSMA(Cybersecurity Mesh Architecture网络安全网格架构)

随着更多的应用和数据迁移到云服务,传统网络边界已经消失,网络安全威胁形势日益复杂。同时,网络攻击手段也在快速演进,AI和大规模自动化技术驱动的新型威胁层出不穷,带来了快速增长的网络攻击数量。在这种形势下,传统安全手段已经无以为继,组织普遍存在网络安全技能短缺的困境。因此,Gartner在2021年一种全新的安全架构模式CSMA(Cybersecurity Mesh Architecture网络安全网格架构)。

CSMA架构是一种现代安全方法,包括在最需要的地方部署控制措施、构建身份化的零信任网络并以身份作为安全边界,通过提供基础安全服务以及集中式策略管理和编排功能,使诸多工具能够协同操作,而不是每个安全工具都在孤岛环境中使用。
通过CSMA架构,可以获得所有边缘的深度可见性、集中管理分布式解决方案、策略的一致执行、利用威胁情报、通过集成第三方能力以更好地防卫已知和未知攻击、跨混合环境自动执行可操作响应等优势。

CSMA网络安全网格架构

CSMA架构希望通过搭建一个全面覆盖、深度集成、动态协同的“网络安全网格平台”,将不同厂商的安全工具整合为一个协同生态系统,让组织能够灵活地进行方案部署,同时从集成和融合的运营、可视化和安全性中受益,对于在当前不断扩展的网络中,降低复杂度和提高整体安全有效性至关重要。


博客文章:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!

vue3实现登录获取token并自动刷新token进行JWT认证

发表于 2024-10-10 | 更新于: 2024-10-10 | 分类于 技术 | | 阅读次数:
字数统计: 1.6k | 阅读时长 ≈ 8

在《django应用JWT(JSON Web Token)实战》介绍了如何通过django实现JWT,并以一个具体API接口实例的调用来说明JWT如何使用。本文介绍如何通过vue3的前端应用来使用JWT认证调用后端的API接口,实现一下的登录认证获取JWT进行接口认证。

账号密码验证jwt流程

一、账号密码登录获取JWT

通过Login.vue实现登录的用户名、密码表单信息收集,调用getToken()方法进行鉴权验证并获取jwt的token。
Login.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<template>
<div class="body">
<el-form :model="form" :rules="rules" ref="loginForm" class="loginContainer">
<h3 class="loginTitle">
欢迎登录
</h3>
<el-form-item label="用户名" prop="username" :label-width="formLabelWidth">
<el-input type="text" v-model="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password" :label-width="formLabelWidth">
<el-input type="password" v-model="form.password" placeholder="请输入密码"></el-input>
</el-form-item>
<el-form-item :label-width="formLabelWidth">
<el-button type="primary" :plain="true" @click="submitForm('form')">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>

<script>
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus';
import {getToken} from '../api/user'
export default {
data() {
return {
form: {
username: '',
password: '',
err_username: "",
err_password: "",
},
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, min:6, message: '请输入密码', trigger: 'blur' }
]
},
formLabelWidth: '120px'
};
},
methods: {
submitForm(formName) {
if (this.$refs.loginForm) {
this.$refs.loginForm.validate(valid => {
if (valid) {
// 提交表单逻辑
console.log('提交成功:', this.form);
this.login();
} else {
console.log('验证失败');
ElMessage.error('验证失败,请检查您的输入!');
}
});
} else {
console.error('表单未找到');
}
},
login() {
var that = this;
this.message = "";
// 用户名密码鉴权获取jwt的token
getToken({
'username': this.form.username,
'password': this.form.password,
}).then((Response) => {
console.log(Response);
if (Response && Response.access) {
// //保存数据到本地存储
this.username= that.form.username;
useUserStore().login(this.username,Response.access,Response.refresh)
this.username = "";
this.password = "";
this.$router.push({name:"home"}); //跳转到首页
}
})
.catch(function (error) {
console.log(error);
if ("username" in error) {
that.err_username = error.username[0];
} else if ("password" in error) {
that.err_password = error.password[0];
} else {
ElMessage.error('登录失败!');
}
});
},

}
};
</script>

<style scoped>
.loginContainer{
border-radius: 15px;
background-clip: padding-box;
text-align: left;
margin: auto;
margin-top: 180px;
width: 450px;
padding: 15px 35px 15px 35px;
background: aliceblue;
border:1px solid blueviolet;
box-shadow: 0 0 25px #f885ff;
}
.loginTitle{
margin: 0px auto 48px auto;
text-align: center;
font-size: 40px;
}
.loginRemember{
text-align: left;
margin: 0px 0px 15px 0px;
}
.loginbody{
width: 100vw;
height: 100vh;
background-size:100%;
overflow: hidden;
}
</style>

在登录时调用getToken()方法获取jwt的token
getToken的封装方法如下:

1
2
3
4
5
6
7
8
9
import request from '@/utils/request'

export function getToken(data) {
return request({
url: 'token/',
method: 'post',
data
})
}

通过用户名和密码鉴权可以获得JWT的token,接口会返回access的token和refresh的token,需要将这两个token保存下来,access的token用来进行API接口的jwt认证,refresh的token用来刷新失效的access的token。

二、将JWT保存至本地

通过pinia将token保存至浏览器的本地存储,以便于后面请求API时带上访问的token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { defineStore } from 'pinia'
import { refreshToken } from '../api/user'

export const useUserStore = defineStore('user', {
persist: {
enabled: true, //开启数据持久化
strategies: [
{
key: "userState", //给一个要保存的名称
storage: localStorage, //sessionStorage / localStorage 存储方式
},
],
},
state: () => ({
isLoggedIn: false,
username: '',
jwtAccessToken: null,
jwtRefreshToken: null,
}),
actions: {
login(username, accessToken,refreshToken) {
this.username = username
this.isLoggedIn = true
this.setToken(accessToken, refreshToken)
},
logout() {
this.username = ''
this.jwtAccessToken = null
this.isLoggedIn = false
},
setToken(accessToken, refreshToken) {
this.jwtAccessToken = accessToken
this.jwtRefreshToken = refreshToken
},
refreshToken() {
return new Promise((resolve, reject) => {
refreshToken({"refresh":this.jwtRefreshToken}).then((response) => {
this.setToken(response.access, this.jwtRefreshToken)
resolve(response.access)
console.log('return refreshToken-----------'+response.access)
}).catch((error) => {
reject(error)
})
})
}

},
getters: {
getIsLoggedIn: (state) => state.isLoggedIn,
getUsername: (state) => state.username,
getUserAccessToken: (state) => state.jwtAccessToken,
getRefreshToken: (state) => state.jwtRefreshToken,
}
})

在登录的Login.vue组件中调用useUserStore().login(this.username,Response.access,Response.refresh)将用户名、access的token、refresh的token保存至浏览器的本地存储。

三、请求API带上JWT

将axios的调用封装成request.js在调用API接口时带上JWT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import axios from 'axios'
import Router from '@/components/tools/Router'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import { refreshToken } from '../api/user'

const api_rul = import.meta.env.VITE_APP_API_URL

// create an axios instance
const service = axios.create({
baseURL: api_rul,
timeout: 5000, // request timeout
})

// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
const { url } = config
// 指定页面访问需要JWT认证。
if (url.indexOf('/login')!== -1) {
return config
}
let jwt = useUserStore().getUserAccessToken
config.headers.Authorization = `Bearer ${jwt}`
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)

export default service

主要是在请求头重带着jwt的信息

1
2
let jwt = useUserStore().getUserAccessToken
config.headers.Authorization = `Bearer ${jwt}`

四、在token失效时自动重新获取token

前面提到JWT基于安全考虑有两个token,一个是access token ,一个是refresh token 。access token的失效时间较短,可以有效降低泄露而造成的影响,两个token的区别和作用如下:

access token refresh token
有效时间 较短(如半小时) 较长(如一天)
作用 鉴权验证 重新获取access token
什么时候使用 每次接口鉴权验证时 access token失效时使用

使用refresh token的逻辑如下:
重新获取token流程

以下通过拦截器实现token失效后重新获取access token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import axios from 'axios'
import Router from '@/components/tools/Router'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import { refreshToken } from '../api/user'

const api_rul = import.meta.env.VITE_APP_API_URL

// create an axios instance
const service = axios.create({
baseURL: api_rul,
timeout: 5000, // request timeout
})

// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
const { url } = config
// 指定页面访问需要JWT认证。
if (url.indexOf('/login')!== -1) {
return config
}
let jwt = useUserStore().getUserAccessToken
config.headers.Authorization = `Bearer ${jwt}`
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)

// response interceptor
service.interceptors.response.use(

response => {
const res = response.data
return res
},
async error => {
console.log('err' + error) // for debug
const originalRequest = error.config;
// 授权验证失败
if (error.response.status === 401 && originalRequest._retry!== true) {
originalRequest._retry = true;
// 刷新token
let jwtRefreshToken=useUserStore().getRefreshToken
await refreshToken({"refresh":jwtRefreshToken}).then((response) => {
// 刷新token成功,重新请求
let jwtToken=response.access
useUserStore().setToken(jwtToken, jwtRefreshToken)
console.log('return refreshToken-----------'+response.access)
originalRequest.headers.Authorization = `Bearer ${jwtToken}`
return service(originalRequest)
}).catch((error) => {
// 刷新token失败,跳转到登录页面
ElMessage.error('请重新登录!')
Router.push({name:'login'})
})

}
// 内部错误
if (error.response.status === 500) {
let errormsg=error.response.data.msg
ElMessage.error('服务器内部错误!'+errormsg)
}
if (error.response.status === 400)
{
ElMessage.error('错误的请求!')
}
return Promise.reject(error)
}
)

export default service

在判断error.response.status === 401时调用refreshToken重新获取jwttoken进行接口的调用。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注微信公众号,一起学习、成长!

django应用JWT(JsonWebToken)实战

发表于 2024-09-22 | 更新于: 2024-09-22 | 分类于 技术 | | 阅读次数:
字数统计: 1.5k | 阅读时长 ≈ 5

在前后端分离的项目中,前后端进行身份验证通常用JWT来进行,JWT 提供了一个理想的认证解决方案,用来保护 RESTful API,确保只有经过认证的用户才能访问受保护的资源。基于前端框架(如React, Angular, Vue.js)的单页面应用 (SPA),开发者通过使用 JWT可以获得一种简单、安全、高效的方式来处理用户认证和授权的问题。本文通过django项目的实战来说明如何应用和使用JWT。

一、什么是JWT

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络各方之间以安全且紧凑的形式传输信息。JWT 是一个小型的凭证,通常用于身份验证和授权场景。JWT 由三部分组成:头部 (Header)、负载 (Payload) 和签名 (Signature)。
JWT信息由3段构成,它们之间用圆点“.”连接,格式如下:

1
aaaaaa.bbbbbb.cccccc

一个典型的JWT如下所示:

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI2OTcyMDc2LCJpYXQiOjE3MjY5NzAyNzYsImp0aSI6IjMyMTFiZjdmZDlhZTRmNTBhMDNmOGM2NjcwNDM2NjFiIiwidXNlcl9pZCI6Mn0.Ej6US4Uk-sSNm9P8kTU_cDAzBpO4I-BLhPstp5sG00Q
  • 头部 (Header):包含了关于 JWT 类型的信息以及所使用的签名算法。
  • 负载 (Payload):是 JWT 的主体部分,包含了实际需要声明的数据。这些数据通常包括用户ID、用户名、角色等信息。
  • 签名 (Signature):用于验证 JWT 的发送者就是它声称的发送者,同时也确保了 JWT 在传输过程中没有被篡改。

二、为什么使用JWT

使用 JWT 的原因主要有以下几点:

  • 安全性:JWT 通过签名来保证数据的完整性和防篡改性。如果有人试图修改 JWT 内容,签名会失效,接收方可以检测到这一行为。
  • 无状态性:JWT 是自包含的,这意味着不需要在服务器上保存会话状态。每个 JWT 都包含了所有必要的信息,从而减少了对服务器端存储的需求。
  • 跨域支持:JWT 可以轻松地在不同的域之间共享,这使得它非常适合微服务架构和分布式系统。
  • 性能提升:由于 JWT 是自包含的,所以服务器可以快速地验证 JWT,而无需查询数据库来获取用户信息,这提高了应用的响应速度。
  • 易于缓存和扩展:JWT 可以被缓存,并且因为它们是无状态的,所以可以很容易地扩展到多个服务器,而无需担心会话复制问题。
  • CSRF 防护:使用 JWT 可以帮助缓解跨站请求伪造(CSRF)攻击的风险,因为 JWT 不依赖于 cookie,也就不会随同 HTTP 请求自动发送。

总的来说,JWT 提供了一种高效、安全的方式来处理用户认证和授权,尤其是在需要跨域操作或构建无状态服务的情况下。

三、在django项目中如何应用JWT

JWT(JSON Web Token)是一种用于在网络应用中安全地传输信息的令牌。它通常用于身份验证和授权,特别是在单页应用(SPA)和API服务中。在Django中应用JWT,可以使用 djangorestframework-simplejwt。

1、安装djangorestframework-simplejwt库:

1
pip install djangorestframework-simplejwt

2、在settings.py中配置JWT认证:

在INSTALLED_APPS中添加rest_framework_simplejwt的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
INSTALLED_APPS = [
'blog',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt', # 添加 simplejwt 应用
'django_filters', # 注册条件查询
'mdeditor', # 注册markdown的应用
'drf_yasg2', # 接口文档
]

添加REST_FRAMEWORK的默认认证类为JWT认证

1
2
3
4
5
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}

添加SIMPLE_JWT的相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# JWT 相关设置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30), # 访问令牌的有效时间
'REFRESH_TOKEN_LIFETIME': timedelta(days=1), # 刷新令牌的有效时间
'ROTATE_REFRESH_TOKENS': False, # 是否允许刷新令牌循环
'BLACKLIST_AFTER_ROTATION': True, # 刷新令牌后是否加入黑名单
'UPDATE_LAST_LOGIN': False, # 登录时是否更新最后登录时间

'ALGORITHM': 'HS256', # 签名算法
'SIGNING_KEY': SECRET_KEY, # 签名密钥
'VERIFYING_KEY': None, # 验证密钥
'AUDIENCE': None, # 观众
'ISSUER': None, # 发行人
'JWK_URL': None, # JWK URL
'LEEWAY': 0, # 宽限期

'AUTH_HEADER_TYPES': ('Bearer',), # 授权头类型
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', # 授权头名称
'USER_ID_FIELD': 'id', # 用户 ID 字段
'USER_ID_CLAIM': 'user_id', # 用户 ID 声明
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',

'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), # 认证令牌类
'TOKEN_TYPE_CLAIM': 'token_type', # 令牌类型声明
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',

'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', # 滑动令牌刷新过期声明
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), # 滑动令牌有效时间
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), # 滑动令牌刷新有效时间
}

3、在urls.py中配置JWT的获取和刷新路由:

1
2
3
4
5
6
7
from django.urls import path
from rest_framework_simplejwt.views import (TokenObtainPairView, TokenRefreshView)
urlpatterns = [
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
# 其他路由...
]

4、在视图中需要认证的地方使用JWT认证
如下modelviweset中使用,对于查询方法如list,retrieve不做鉴权,对于其他方法需要鉴权。

1
2
3
4
5
6
7
8
9
10
11
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
if self.action in ['list', 'retrieve']:
# 对于list方法,返回AllowAny权限类,表示不需要鉴权
permission_classes = [AllowAny, ]
else:
# 对于其他方法,返回IsAuthenticated权限类,表示需要用户已认证
permission_classes = [IsAuthenticated, ]
return [permission() for permission in permission_classes]

四、JWT如何使用

通过上面的应用后,使用接口调用遇到需要鉴权的会提示需要认证。
如当我们调用删除接口时,如果没有获得鉴权,接口会返回需要认证的信息。
接口调用需要认证

那如何通过JWT进行认证呢?
JWT进行认证过程

下面通过postman来应用JWT的使用过程。

1、调用生成JWT的接口获取JWT

调用生成JWT的接口获取JWT

2、客户端保存JWT在调用接口时带上获取的JWT

调用接口时带上获取的JWT

至此,本文介绍了什么时JWT,为什么要使用JWT,通过django实现JWT,介绍了JWT的使用流程,最后以一个具体API接口实例的调用来说明JWT如何使用。后续将介绍VUE从前端登录获取JWT到JWT认证的实例。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!

django实现开发、测试、生产环境配置区分

发表于 2024-09-16 | 更新于: 2024-09-16 | 分类于 技术 | | 阅读次数:
字数统计: 1.2k | 阅读时长 ≈ 4

任何实际的软件项目中都要经过开发、测试、然后上生产的阶段,在开发、测试的过程中往往会要频繁的切换开发、测试、生产等不同的环境。每个环境的配置有可能不一样,本文介绍如何实现django项目配置环境变量实现开发、测试、生产灵活便捷的切换。

一、为什么要区分开发 (dev)、测试 (test) 和生产 (prod) 环境

在Django项目中区分开发、测试和生产环境是非常重要的,这主要是因为不同环境在多个方面存在显著差异。
● 开发环境:通常配置为易于开发和调试,比如开启调试模式(DEBUG = True),这样可以提供更详细的错误信息和页面跟踪,方便开发者定位问题。同时,开发环境可能使用本地数据库或内存数据库,以便快速启动和测试。
● 测试环境:配置为模拟生产环境,但用于自动化测试。测试环境需要确保测试的独立性和一致性,以便准确地评估应用程序在类似生产条件下的表现。测试环境通常使用与生产环境相同或相似的数据库设置,但数据是测试专用的。
● 生产环境:配置为高性能、高可用性和高安全性。关闭调试模式,优化数据库连接和缓存策略,确保应用程序能够处理大量并发请求和保障数据的安全。
通过区分这些环境,开发者可以更有效地管理django项目,确保每个环境都能满足其特定的需求,从而提高开发效率、保障数据安全和提升用户体验。

二、django项目如何通过配置实现环境配置的区分

对于django项目实现开发(dev)、测试(test)和生产(prod)环境的配置分离可以通过使用不同的设置文件实现。
下面,我们以一个实例来介绍在django的项目如何通过使用不同的设置文件的方式区分不同的环境。

1、针对不同的环境创建不同的设置文件settings.py

针对不同的环境创建不同的设置文件,如开发环境settings_dev.py ,测试环境setting_test.py,正式生产环境使用默认的setting.py,对于共性的配置可以抽取到base.py中,其他的设置文件可以继承base.py中的配置,只有实现各个环境特有的设置就可以了。
创建设置文件的目录结构如下图所示。

根据不同的环境创建不同的配置文件

2、在设置文件中根据需要进行配置区分

典型的,如开发环境settings_dev.py用的本地数据库并开启debug模式、测试环境setting_test.py用sqlite3数据库开启debug模式。
注意:其他的设置文件要继承base.py中的配置,需要加入from .base import *
参考配置如下:
setting_test.py

1
2
3
4
5
6
7
8
9
10
11
12
13
from .base import *

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'test_db.sqlite3'),
}
}

setting_dev.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from .base import *

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['127.0.0.1', 'localhost','ishareblog.com']

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'ishareblog_test',
'USER': 'ishareblog',
'PASSWORD': 'yourpassword',
'HOST': '127.0.0.1',
'PORT': '3306',
# 1、取消外键约束,否则多对多模型迁移报django.db.utils.IntegrityError: (1215, 'Cannot add foreign key constraint');
# 2、解决字符串4个字节的utf8编码的字符报错的问题
'OPTIONS': {
'charset': 'utf8mb4',"init_command": "SET foreign_key_checks = 0;",
}
}
}

3、根据不同的环境运行使用不同的设置文件

不同的环境有了不同的设置文件后,就可以在不同的环境运行和使用配置文件。
如生产环境使用的wsgi启动的服务,可以在wsgi.py文件中指定使用生产环境的配置文件

wsgi中使用生产环境配置文件

在开发环境中使用manage.py启动服务的,可以修改manage.py将设置文件改为setting_dev
开发环境在manage文件中使用开发设置文件

也可以通过命令行指定设置文件 python manage.py runserver --settings=ishareblog.settings_dev
如果时通过IDE启动,可以在IDE中指定使用的环境设置文件。
如下图所示:

在IDE中指定环境设置文件

如要使用测试环境的设置文件,可以配置成ishareblog.settings_test,或通过命令行指定设置文件 python manage.py runserver --settings=ishareblog.settings_test

至此,本文详细的介绍了通过不同的设置文件来区分配置开发、测试、生产环境的配置及如何根据不同的环境使用不同的设置文件。解决不同环境去频繁修改settings.py环境设置的问题。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!

vue3+elementplus的表格展示和分页实战

发表于 2024-09-02 | 更新于: 2024-09-02 | 分类于 技术 | | 阅读次数:
字数统计: 1.6k | 阅读时长 ≈ 8

Element Plus 是一个基于 Vue 3 的现代化 UI 组件库,旨在帮助开发者快速构建美观且功能丰富的 Web 应用程序。它提供了大量的 UI 组件,如按钮、表单、表格、弹出框、标签页、树形控件等,涵盖了 Web 应用开发中常见的大多数场景。本文通过一个实例来说明vue3+elementplus查询、展示和分页实战。

一、Element Plus的安装使用

要开始使用 Element Plus,首先需要在项目中安装它。如果你正在使用 Vue 3 的项目,可以通过 npm 或 yarn 安装 Element Plus:

1
npm install element-plus

然后可以在Vue 项目中全局引入 Element Plus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createApp } from 'vue'
import App from './App.vue'

// 导入路由
import Router from './components/tools/Router'
// 导入ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)
// 遍历ElementPlusIconsVue中的所有组件进行祖册
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
// 向应用实例中全局注册图标组件
app.component(key, component)
}
app.use(ElementPlus) // 使用ElementPlus
app.use(Router); // 使用路由
app.mount('#app')

二、el-table 表格组件

el-table 是Element Plus 中的一个重要组件,用于展示列表数据。可以通过 组件来定义表格中的每一列,包括列标题、列宽、对齐方式等,可以结合 el-pagination 可以实现分页功能。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<el-table
ref="multipleTable"
:data="postList"
tooltip-effect="dark"
style="width: 100%"
fit
:pagination="pagination"
@selection-change="handleSelectionChange" >
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
label="ID"
width="100"
prop="id">
</el-table-column>
<el-table-column
label="标题"
width="450"
prop="title">
</el-table-column>
<el-table-column
label="是否置顶"
width="100"
prop="isTop">
</el-table-column>
<el-table-column
label="热度"
width="100"
prop="viewsCount">
</el-table-column>
<el-table-column
label="发布时间"
width="200"
prop="pubTime">
</el-table-column>
<el-table-column
label="操作"
>
<template #default="scope">
<el-button size="mini" type="danger" @click="deleteItem(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>

其中
:data="postList" 绑定要显示在表格中的数据源,通常是一个对象数组
fit: 让表格宽度自动填充父容器。
:pagination="pagination" 绑定分页的数据对象

数据定义如下:

1
2
3
4
5
6
7
8
9
// 博客文章列表数据
postList:[],
// 分页
pagination: {
currentPage: 1, // 当前页
pageSize: 10, // 每页显示条数
total: 0, // 总条数
layout: 'total,sizes,prev, pager, next, jumper', // 分页布局
},

三、el-pagination 分页组件

el-pagination Element Plus 中用于实现分页功能的重要组件。它可以与 el-table 组件结合使用,实现数据的分页显示。示例代码如下:

1
2
3
4
5
6
7
8
9
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagination.currentPage"
:page-sizes="[10, 20, 30, 40]"
:page-size="pagination.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>

属性
● @size-change="handleSizeChange" 当每页显示数量变化时触发。
● @current-change="handleCurrentChange" 当当前页变化时触发。
● :current-page="currentPage" 设置当前页。
● :page-sizes="[10, 20, 30, 40]" 设置每页可选的数量。
● :page-size="pageSize" 设置每页显示的数量。
● layout="total, sizes, prev, pager, next, jumper" 设置分页布局。
● :total="tableData.length" 设置总数据量。
方法:
● handleSelectionChange(val) 处理行选择变化。
● deleteItem(index) 删除指定行。
● handleSizeChange(val) 处理每页显示数量变化。
● handleCurrentChange(val) 处理当前页变化。

四、全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
<template>
<div class="content-container" direction="vertical">
<!-- input -->
<div>
<el-container class="content-row">
<div class="input-tip">
文章标题:
</div>
<div class="input-field" style="width: 400px;">
<el-input v-model="queryParam.words"></el-input>
</div>
<el-button type="primary" @click="getBlogList">筛选</el-button>
<el-button type="danger" @click="clear">清空筛选</el-button>
</el-container>
</div>
<!-- list -->
<div>
<el-tabs type="card" @tab-click="handleClick">
<el-tab-pane label="全部"></el-tab-pane>
<el-tab-pane v-for="(item,index) in blogCategorys"
:key="index"
:label="item.title"
:name="item.id">
</el-tab-pane>
</el-tabs>
<el-table
ref="multipleTable"
:data="postList"
tooltip-effect="dark"
style="width: 100%"
fit
:pagination="pagination"
@selection-change="handleSelectionChange" >
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
label="ID"
width="100"
prop="id">
</el-table-column>
<el-table-column
label="标题"
width="450"
prop="title">
</el-table-column>
<el-table-column
label="是否置顶"
width="100"
prop="isTop">
</el-table-column>
<el-table-column
label="热度"
width="100"
prop="viewsCount">
</el-table-column>
<el-table-column
label="发布时间"
width="200"
prop="pubTime">
</el-table-column>
<el-table-column
label="操作"
>
<template #default="scope">
<el-button size="mini" type="danger" @click="deleteItem(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagination.currentPage"
:page-sizes="[10, 20, 30, 40]"
:page-size="pagination.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>
</div>
</div>
</div>
</template>

<style scoped>
.pagination-container {
margin-top: 20px;
text-align: center;
}
</style>

<script>
import {getBlogList,getBlogCategory} from '@/api'
export default {
data() {
return {
// 博客文章列表数据
postList:[],
// 筛选博客的参数
queryParam:{
words:"",
cateid:"",
tag:"",
search:"",
page:1,
size:10
},
// 分页
pagination: {
currentPage: 1, // 当前页
pageSize: 10, // 每页显示条数
total: 0, // 总条数
layout: 'total,sizes,prev, pager, next, jumper', // 分页布局
},
// 博客分类
blogCategorys:[],
// 当前选中的博客分类
selectCategory:"",
// 当前选中的博客文章
multipleSelection:[]
}
},
mounted () {
this.getBlogList();
this.getBlogCategory();
},
// 路由更新时刷新数据
beforeRouteUpdate (to) {
this.getBlogList();
this.getBlogCategory();
},
methods : {
// 获取博客文章列表数据
getBlogList() {
getBlogList(this.queryParam).then(res => {
this.postList = res.data.items
this.pagination.total = res.data.total
this.pagination.currentPage= res.data.page
console.log(res.data)
}).catch(err => {
console.log(err)
})
},
// 获取博客分类数据
getBlogCategory() {
getBlogCategory().then(res => {
this.blogCategorys = res.data
console.log(res)
}).catch(err => {
console.log(err)
})
},
// 改变分页大小
handleSizeChange(val) {
this.pagination.pageSize = val;
this.queryParam.size = val;
this.getBlogList();
},
// 跳到当前页
handleCurrentChange(val) {
this.pagination.currentPage = val;
this.queryParam.page = val;
this.getBlogList();
},
// 切换Tab 刷新数据
handleClick(tab) {
this.queryParam.cateid = tab.props.name
this.getBlogList();
},
// 清空筛选项
clear() {
this.queryParam.words=""
this.getBlogList();
},

}
}
</script>

五、效果

elementplus表格
表格展示及数据分页是前端开发常用的功能,通过vue3+elementplus能够快速是实现对数据的展示及分页。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注微信公众号,一起学习、成长!

vue3+vite配置环境变量实现开发、测试、生产的区分

发表于 2024-08-25 | 更新于: 2024-08-25 | 分类于 技术 | | 阅读次数:
字数统计: 973 | 阅读时长 ≈ 3

在vue的实际项目中都要经过开发、测试、然后上生产的阶段,在开发、测试的过程中往往会要频繁的切换开发、测试、生产等不同的环境。每个环境的配置有可能不一样,本文介绍如何通过vue3+vite配置环境变量实现开发、测试、生产灵活便捷的切换。

一、为什么需要区分 (dev)、测试 (test) 和生产 (prod) 环境

做过大型项目开发的都知道,每个项目都会要经历开发、测试、再到生产上线,一般在开发时候最常用到(development)开发环境、(production)生产环境、(test)测试环境。每个环境的配置可能都不太一样。

  • 开发环境:为开发人员提供一个安全的地方来进行编码和调试,不会影响到其他环境,一般来说开发人员在本地机器上运行和测试应用程序。
  • 测试环境:用于测试,模拟生产环境,确保新功能在部署前能够正常工作,并且不会影响现有功能。
  • 生产环境:是面向用户的最终环境,任何更改都必须经过严格的测试才能部署到这里,通常具有优化和最少的日志记录。

区分开发 (dev)、测试 (test) 和生产 (prod) 环境是软件开发中的一个最佳实践。不同的环境通常有不同的资源配置。典型的如vue所调用的后台接口数据,在开发平台可能是本地服务提供的接口、用于自动化测试可能是mock提供的数据、生产应该是正式环境提供的真实接口。

二、vue3的项目如何通过配置方式区分不同的环境

vue3的项目可以通过vite的环境变量配置来进行不同环境的配置,可以参考vite的官方文档《环境变量和模式》
接下来,我们以一个实例来介绍在vue3的项目如何通过vite配置方式区分不同的环境。

1、创建不同环境的.env文件

在vue3根目录下创建三个文件分别为.env.develoment、.env.test、.env.production
创建的文件需要.env开头

env文件

2、在不同的.env文件中配置相应的环境变量

1).env.develoment

1
2
3
NODE_ENV='development'
# API URL
VITE_APP_API_URL='http://localhost:8000'

2).env.test

1
2
3
NODE_ENV='test'
# API URL
VITE_APP_API_URL=''

3).env.production

1
2
3
NODE_ENV='production'
# API URL
VITE_APP_API_URL='http://iblog.ishareread.com'

这里我们主要通过VITE_APP_API_URL变量来区分不同的环境调用不同的接口,在开发环境调用本地接口http://localhost:8000 ,在测试环境用mock接口,在生产环境调用真实接口。

3、在项目中使用环境变量

使用import.meta.env.VITE_APP_API_URL在axios请求中使用环境变量的配置来调用不同的接口。

1
2
3
4
5
6
7
8
import axios from 'axios'
const api_rul = import.meta.env.VITE_APP_API_URL
// create an axios instance
const service = axios.create({
baseURL: api_rul,
timeout: 5000 // request timeout
})
export default service

查看调用的哪些环境变量,可以在在main.js打印console.log('环境变量:', import.meta.env);看一下
main.js

1
2
3
4
5
6
7
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'

console.log('环境变量:', import.meta.env);

createApp(App).mount('#app')

4、在package.json中定义运行项目的脚本命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"name": "mocktest",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"test": "vite --mode test",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.7.5",
"vue": "^3.4.29"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.5",
"mockjs": "^1.1.0",
"vite": "^5.3.1",
"vite-plugin-mock": "^3.0.2"
}
}

package.json

三、运行效果

开发环境运行npm run dev

npm run dev

测试环境运行npm run test

npm run dev

可以看出通过运行不同的命令通过环境变量区分了不同的运行环境,避免了不同环境去改代码去适配不同的环境。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注微信公众号,一起学习、成长!

vue3+vite+axios+mock从接口获取模拟数据实战

发表于 2024-08-24 | 更新于: 2024-08-25 | 分类于 技术 | | 阅读次数:
字数统计: 1.6k | 阅读时长 ≈ 7

在用Vue.js开发前端应用时通常要与后端服务进行交互,例如通过API接口获取数据,在后端服务接口还没有具备之前,可以通过mock(模拟)数据来进行开发。使用mock数据可以让前端开发人员独立于后端开发人员工作,加快开发速度。在没有真实数据的情况下,mock数据可以帮助开发者更快地看到UI的呈现效果和交互逻辑。

本文通过vue3+vite+axios+mock来介绍如何实现Vue.js的前端应用从接口获取模拟数据。

一、安装相关组件

1
2
npm install axios -S
npm install mockjs vite-plugin-mock -D

其中axios 是一个基于 Promise 非常强大且灵活的 HTTP 客户端,适用于 Vue.js 应用程序中的数据获取和后端交互。它可以简化 HTTP 请求的处理,并提供丰富的功能来满足不同的需求。我们用axios来实现与接口服务的http请求。

Mock.js 是一个用于生成随机数据的 JavaScript 库,它主要用于前端开发过程中模拟后端接口数据。Mock.js 提供了一套简洁易用的 API,可以帮助开发者快速生成符合特定规则的假数据,从而在没有后端支持的情况下进行前端开发和测试。

vite-plugin-mock 是一个专为 Vite 设计的插件,用于在 Vite 项目中模拟数据。它简化了使用 Mock.js 的过程,让开发者能够更加方便地管理模拟数据。

简单来说,就是mock.js提供mock数据,通过vite-plugin-mock,将管理mock发布成服务,通过axios通过http请求接口的方式获取mock数据。

安装相关组件后,在package.json中看到相关的组件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package.json
{
"name": "mocktest",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"test": "vite --mode test",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.7.5",
"vue": "^3.4.29"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.5",
"mockjs": "^1.1.0",
"vite": "^5.3.1",
"vite-plugin-mock": "^3.0.2"
}
}

二、在vite.config.js中配置vite-plugin-mock插件

● viteMockServe的相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// mock 数据的 dev环境
viteMockServe({
// supportTs: true, // 是否开启支持ts
mockPath: 'mock', // 设置mockPath为根目录下的mock目录
localEnabled: true, // 设置是否监视mockPath对应的文件夹内文件中的更改
prodEnabled: false, // 设置是否启用生产环境的mock服务
watchFiles: true, // 是否监视文件更改
logger: true //是否在控制台显示请求日志
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

在viteMockServe中指定了mockPath为mock也就是根目录下的mock目录,在该目录下的mock服务都会被发布成mock服务。

三、实现mock服务

在根目录下新建mock目录在mock目录下新建mock文件实现mock服务,如app.js、user.js

mock目录

app.js代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
export default [{
url: '/mock/api/getApiInfo',
method: 'get',
response: () => {
return {
code: 200,
title: 'mock api test.'
}
}
},
{
url: '/api/category',
type: 'get',
response: () => {
return {
code: 200,
data: [
{
id: 1,
title: 'JAVA',
href: '/category/java'
},
{
id: 2,
title: 'SpringBoot',
href: '/category/SpringBoot',
},
{
id: 3,
title: 'MySql',
href: '/category/MySql'
},
{
id: 4,
title: '随笔',
href: '/category/live'
}
]
}
}
}
]

在app.js中我们并没有用mock生产数据,只是实现了mock服务放到了mock文件目录通过viteMockServe发布出来,后面可以通过axios调用获取。
user.js代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Mock from 'mockjs';

// 通过Mock生成模拟数据
const userdata = Mock.mock({
'list|10': [
{
'id|+1': 1,
'name': '@cname',
'age|18-60': 1,
'email': '@email',
},
],
});

export default [
{
url: '/mock/api/getUserInfo',
method: 'get',
response: () => {
return {
code: 200,
data: userdata
}
}
},
]

在user.js中,我们通过Mock生成模拟的用户列表数据

四、调用api接口请求mock数据

方法一、直接使用axios 请求mock 数据

1
import axios from 'axios'

在方法中通过axios.get()方法直接获取请求数据

1
2
3
4
5
6
7
async getData() {
await axios.get('/mock/api/getApiInfo').then(res =>{
console.log(res.data)
this.msg = res.data.title
}
)
},

方法二、对axios进行封装统一请求mock数据

建立一个service.js对axios进行封装,让后通过service.js来统一请求mock数据,这样做的好处是在切到真实接口的时候可以更加灵活
service.js的代码如下

1
2
3
4
5
6
7
8
9
import axios from 'axios'
const api_rul = '' //mock 接口地址可以为空字符串,真实接口配置为真实的接口地址。
// create an axios instance
const service = axios.create({
baseURL: api_rul,
timeout: 5000 // request timeout
})

export default service

通过一个统一的调用接口文件请求mock数据
如mockapi.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import service from '@/utils/service'

export function getCategory() {
return service({
url: '/api/category',
method: 'get',
params: {}
})
}

export function getUserInfo() {
return service({
url: '/mock/api/getUserInfo',
method: 'get',
params: {}
})
}

在methods中进行方法的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 方法二:通过封装后的get方法获取数据
getUserInfo()
{
getUserInfo().then(res =>{
console.log(res.data)
this.userinfo = res.data.data
}
)
},
getCategory()
{
getCategory().then(res =>{
console.log(res.data)
this.categorys = res.data.data
}
)
}

在vue的组件中具体的调用和展示代码如下:
HelloWorld.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<template>
<h1>{{ msg }}</h1>
<div>
人员列表
</div>
<div>
<ul>
<li v-for="(user) in userinfo.list" :key="index">
{{ user.id }} : {{ user.name }} {{ user.age }} {{ user.email }}
</li>
</ul>
</div>
<div>
目录
</div>
<div>
<ul>
<li v-for="(category) in categorys" :key="index">
{{ category.id }} : {{ category.title }} {{ category.href }}
</li>
</ul>
</div>
</template>
<script >
import axios from 'axios'
import { getCategory,getUserInfo } from '../api/mockapi'

export default {
data() {
return {
msg: 'Welcome to Your Vue.js App',
userinfo: {},
categorys: []
};
},
mounted() {
this.getData()
this.getUserInfo()
this.getCategory()
},
methods: {
// 方法一:直接axios请求调用获取mock数据
async getData() {
await axios.get('/mock/api/getApiInfo').then(res =>{
console.log(res.data)
this.msg = res.data.title
}
)
},
// 方法二:通过封装后的get方法获取数据
getUserInfo()
{
getUserInfo().then(res =>{
console.log(res.data)
this.userinfo = res.data.data
}
)
},
getCategory()
{
getCategory().then(res =>{
console.log(res.data)
this.categorys = res.data.data
}
)
}
}
}
</script>

整个工程的目录结构说明如下:

mocktest工程目录

五、实际运行效果

mock效果
可以看到分别用两种方式获取mock数据的效果,其中人员列表中的数据是mock生成的模拟数。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注微信公众号,一起学习、成长!

不忘初心,方得始终,码农实现自我提升的心法与工具

发表于 2024-08-20 | 更新于: 2024-08-20 | 分类于 人生 | | 阅读次数:
字数统计: 1.7k | 阅读时长 ≈ 5

在信息技术飞速发展的今天,越来越多的人进入到IT行业,也使得这个行业越来越卷。随着信息化普及进入尾声以及全球经济下行,IT行业也像传统的房地产行业一样哀鸿遍野,码农也像农民工一样转行的转行失业的失业。想要在这个行业里站住脚不被淘汰只能是不断的提升学习提升自己的能力,让自己成为不可替代才行。

作为码农,如是今还有份稳定的工作,应该来说是幸运的,但也是很焦虑的。一方面不知道自己所负责的业务还能撑多久,公司还能撑多久;一方面还得要思考一旦失业不知道自己的技术水平还能不能找到合适的工作。作为社会人,只有有了对抗风险的本钱后,才会有底气,有了底气后才会不至于那么的焦虑。而底气来自两个方面一方面是有足够的钱;另一方面是有足够的能力。如果有足够的钱,像文艺复兴时期的大咖们被人包养,吃喝不愁,那我们可以不用焦虑,惬意的写代码那一定是很开心的;如果能力足够强,其实也不用担心,换个地方施展自己的能力而已。而我们的现状是既要幸幸苦苦赚钱维持自己的生活又要不断的学习来提升自己的能力适应这个不断发展的行业。

如何在繁忙琐碎的编码工作与个人成长之间找到平衡,下面以我个人的经历来谈谈我的思考。对于码农来说,面临最大的两个问题是如何更好的利用时间和如何通过学习来提升自我的问题。

一、如何更好的利用时间?

在李笑来老师的《和时间做朋友》的书中讲到,其实时间是不可以管理的,每个人每天只有24小时,能够管理的是自己的精力,也就是把自己的关注力放到哪里。要学习,就应该把学习精力、注意力投入足够多的时间。对于我们来说最大的挑战来自手机,下班后很多同学报复性的休息放松,想起自己上班工作了,休息的时候一定得要彻底的放松,拿着手机刷抖音、打游戏、聊天不知不觉就到了深夜。为了避免长时间的玩手机,我的做法是到了一定时间把手机所有的应用都关闭,给自己一段没有手机打挠的时间,很多手机都提供的“禅定模式”,开启“禅定模式”后手机上除了接打电话,其他的应用都暂时不能用,除非退出“禅定模式”,通过这种方式让自己的注意力从手机上移开,放到看书和学习上来。在这里我常用的专注力APP是”Forest 专注森林“,它可以设置一段专注力时间,比如60分钟,如果60分钟你没有动手机就成功的种下了一颗健康的树,如果中途心痒痒玩了手机这个树就会蔫掉。可以按时间周期统计你的专注的时间。

Forest 专注森林

当然专注力是一方面,其实更重要的是我们应该有自控力的意识,有自我提升学习的意识。有了意识以后才会有行动。

二、如何通过学习来提升自我?

焦虑是因为面对残酷的社会竞争压力以及对自身能力的不自信。所以很多东西都想学,机器学习、网络安全、英语、写作等等。正因为焦虑,所以面临的困惑是什么都想学,什么都想学的结果是什么都没有学会。相信大多数人都有这样的经历,心想着要不断的提升自己,桌上摆了很多书,今天看几页这本书,明天翻几页那本书,看上去天天在看书,实际上一本书都没有看进去。看英语的时候在想应该多花点时间看看专业书,看专业书的时候在想英语也很重要,要不看会英语。实际就是有限的时间精力和无限的需要学习的知识之间的矛盾。尤其是专业领域,看了很多书,不能学以致用,看了就忘,感觉就是学了个寂寞。

早几年我也有相同的困惑,认识到学习,目标非常重要,没有明确目标的学习到头来都是浪费时间。于是写了一篇《你有了一个目标,于是你有了一堆技能,而不是反过来》。其实道理大家都懂,要有目标,不忘初心,方得始终。但是对于个人来说目标其实并不好确定,大部分人来说其实也没有什么明确的目标,我的经验是在没有明确的目标的时候就把考证当做目标,以考促学。见《工作这么多年了,我为什么还在考证》。后来我意识到自我提升最核心的是要构建自己知识体系,我的目标是构建了自己的知识体系,能够利用自己的知识体系具备“成事”的能力。如何建立个人知识体系,借用网上的一张图。

个人知识体系

有了构建自己知识体系的大目标后,我把自己近几年需要巩固提升的方面做了一个学习地图,有了学习地图就像航海的地图一样,避免东一下西一下迷失方向。

学习地图

刘未鹏在他的《暗时间》中有个观点让我感触特别的深刻,意思是你所懂得的多少并不是在于你看了多少书而是取决于你思考有多深。“教是最好的学”、“书写是更好的思考”。为了践行“问题->输入->内化->输出”,笃信写是为了更好的思考,坚持写作,力争更好的思考。我开启了自己的博客持续在CSDN和自己的博客上进行写作输出,将自己碰到的问题、经验、思考写出来,即帮助了大家又促进了自己,希望能和大家一起成长。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!

node20+版本下hexo部署报错失败的解决办法

发表于 2024-08-15 | 更新于: 2024-08-17 | 分类于 技术 | | 阅读次数:
字数统计: 864 | 阅读时长 ≈ 4

最近升级了node.js的版本,升到了最新的稳定版本v20.16.0,结果发现在该版本下hexo部署报错失败。本文记录了node20+版本下hexo部署报错失败的解决办法。

一、报错信息

执行hexo的deploy部署命令

1
hexo d

具体报错信息如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
INFO  Deploying: git
INFO Clearing .deploy_git folder...
INFO Copying files from public folder...
FATAL Something's wrong. Maybe you can find the solution here: https://hexo.io/docs/troubleshooting.html
TypeError: mode must be int32 or null/undefined
at copyFile (node:fs:3020:11)
at tryCatcher (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\util.js:16:23)
at ret (eval at makeNodePromisifiedEval (C:\Users\xiejava\AppData\Roaming\npm\node_modules\hexo-cli\node_modules\bluebird\js\release\promisify.js:184:12), <anonymous>:13:39)
at D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\hexo-fs\lib\fs.js:144:39
at tryCatcher (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\util.js:16:23)
at Promise._settlePromiseFromHandler (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:547:31)
at Promise._settlePromise (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:604:18)
at Promise._settlePromise0 (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:649:10)
at Promise._settlePromises (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:729:18)
at Promise._fulfill (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:673:18)
at Promise._resolveCallback (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:466:57)
at Promise._settlePromiseFromHandler (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:559:17)
at Promise._settlePromise (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:604:18)
at Promise._settlePromise0 (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:649:10)
at Promise._settlePromises (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:729:18)
at Promise._fulfill (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:673:18)
at Promise._resolveCallback (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:466:57)
at Promise._settlePromiseFromHandler (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:559:17)
at Promise._settlePromise (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:604:18)
at Promise._settlePromise0 (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:649:10)
at Promise._settlePromises (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:729:18)
at Promise._fulfill (D:\CloudStation\personal\xiejavablog\myhexo\myblog\node_modules\bluebird\js\release\promise.js:673:18)

遇到FATAL Something's wrong TypeError: mode must be int32 or null/undefined这类错误通常是在使用Hexo生成静态文件时出现的。这个问题通常与Hexo的一些插件不兼容或配置不当有关。

二、解决办法

一般来说因为node.js和hexo存在版本适配的关系,所以当可能是一个版本过高一个版本过低导致的出现问题。
解决这个问题可以降低node.js的版本,可以将hexo更新至适配的高版本。这里介绍如何将hexo更新至适配的高版本。

1、查看哪些包需要更新

1
npm outdated

在这里插入图片描述

通过npm outdated 列出了所有需要升级的组件包,这里还给出了npm也需要升级的信息,需要将npm从10.8.1升级到10.8.2,具体的升级命令是 npm install -g npm@10.8.2
执行 npm install -g npm@10.8.2 完成npm的升级

2、升级更新需要升级的包

升级hexo和hexo-cli

1
npm update hexo-cli hexo --latest

在这里插入图片描述

重新执行npm outdated
可以看到hexo 的版本从4.2.0升级到了4.2.1
在这里插入图片描述

重新执行hexo d,发现不报错了,并且可以成功部署。
这里还有hexo-abbrlink和hexo-generator-search 不是适配的版本,虽然不影响hexo部署,但也可以用同样的方法将其升为最新适配的版本。

1
npm update hexo-abbrlink hexo-generator-search --latest

在这里插入图片描述


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注微信公众号,一起学习、成长!

django+markdown2+pygments实现markdown解析及代码高亮

发表于 2024-08-14 | 更新于: 2024-08-17 | 分类于 技术 | | 阅读次数:
字数统计: 1.1k | 阅读时长 ≈ 4

随着markdown的流行,web应用系统常常会要碰到有使用markdown编辑器进行富文本编辑,然后在前台web页面进行显示。常见的博客系统当然也需要支持markdown的编辑与显示。本文就通过一个真实的博客系统来说明django+markdown2+pygments实现markdown解析及代码高亮。

一、后台管理支持markdown编辑

django应用的后台管理支持markdown可以用django-mdeditor,它是一个Django应用,它集成了markdown-editor,允许你在Django项目中使用富文本编辑器编写Markdown格式的内容。这个插件通常用于博客、论坛或任何需要用户输入Markdown文本的场景。

1、安装依赖

首先,通过pip安装django-mdeditor

1
pip install django-mdeditor

2、添加应用到INSTALLED_APPS

在settings.py文件中,将django_mdeditor添加到INSTALLED_APPS列表中,参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
INSTALLED_APPS = [
'blog',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'django_filters', # 注册条件查询
'mdeditor', # 注册markdown的应用
'drf_yasg2', # 接口文档
]

3、设置MEDIA_URL和MEDIA_ROOT

django-mdeditor使用文件上传功能,因此需要在settings.py中正确设置

1
2
3
MEDIA_URL和MEDIA_ROOT:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')

4、在模型中使用MdEditorField

在Django模型中,使用MdEditorField替换标准的TextField:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'''博客文章'''
class BlogPost(BaseModel):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=200, verbose_name='文章标题', unique = True)
category = models.ForeignKey(BlogCategory, blank=True,null=True, verbose_name='文章分类', on_delete=models.DO_NOTHING)
isTop = models.BooleanField(default=False, verbose_name='是否置顶')
isHot = models.BooleanField(default=False, verbose_name='是否热门')
isShow = models.BooleanField(default=False, verbose_name='是否显示')
summary = models.TextField(max_length=500, verbose_name='内容摘要', default='')
content = MDTextField(verbose_name='内容')
viewsCount = models.IntegerField(default=0, verbose_name="查看数")
commentsCount = models.IntegerField(default=0, verbose_name="评论数")
tags = models.ManyToManyField(to=Tag, related_name="tag_post", blank=True, default=None, verbose_name="标签")
blogSource = models.CharField(max_length=200, blank=True, null=True, default='',verbose_name='文章来源')
pubTime = models.DateTimeField(blank=True, null=True, verbose_name='发布日期')

@property
def tag_list(self):
return ','.join([i.tag for i in self.tags.all()])

def __str__(self):
return self.title

class Meta:
verbose_name = '博客文章'
verbose_name_plural = '博客文章'

在博客文章的模型中,文章内容需要支持markdown,所以content = MDTextField(verbose_name='内容')

5、查看效果

用django自带的后台管理admin就可以看到效果了

django自带的后台管理admin

二、前台支持markdown解析及代码高亮

在后台支持markdown编辑后,前台页面的博客文章页面也得要支持对markdown得解析。

1、安装依赖

在Django项目中使用markdown2库实现代码高亮,安装markdown2和pygments这两个Python库。markdown2用于解析Markdown文本,而pygments用于代码高亮。

1
pip install markdown2 pygments

2、配置Markdown解析器

在Django视图中,需要导入markdown2模块,并使用它来解析Markdown文本。同时,要启用fenced-code-blocks扩展,以便markdown2能正确识别代码块。

1
import markdown2

markdown2的扩展说明见 https://github.com/trentm/python-markdown2/wiki/Extras
在这里加入了

1
2
extras=["code-color", "fenced-code-blocks", "cuddled-lists", "tables",
"with-toc", "code-friendly"]

视图实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 详情页视图实现.
def post_detail(request, id):
try:
post_obj = BlogPost.objects.get(id=id)
html_content = markdown2.markdown(post_obj.content,
extras=["code-color", "fenced-code-blocks", "cuddled-lists", "tables",
"with-toc", "code-friendly"])
html_content = html_content.replace('<table>', '<table class="table table-bordered">')
html_content = html_content.replace('<img src=', '<img style="max-width:100%;height:auto;" src=')
context = {"post_obj": post_obj,
"html_content": html_content,
"hot_posts": get_hot_posts(),
"tags": get_all_tags(),
"post_grouped_by_year": get_post_groped_by_year(),
'categories': get_categories(),
'social_infos': get_socialinfo()}
except BlogPost.DoesNotExist:
raise Http404("Post does not exist")
return render(request, "blog/post.html", context)

3、在模板中显示HTML

在Django模板中,直接输出转换后的HTML内容。使用|safe过滤器告诉模板引擎不要转义HTML代码。

1
2
3
<div class="lyear-arc-detail">
{{ html_content|safe }}
</div>

4、导出高亮的css文件并引入css

有了上面的步骤,只是可以解析了markdown成html并显示,最终代码的高亮是通过css 来控制显示的,执行以下命令将高亮的css文件导出。

1
pygmentize -S default -f html -a .codehilite > markdown_highlighy.css

pyments的官方文档 https://pygments.org/ 查看一共有多少种风格,可以参考网址 https://pygments.org/docs/styles/#getting-a-list-of-available-styles
将生成的markdown_highlighy.css文件拷入到static/blog/css下
在模板页面中引入css

1
<link rel="stylesheet" type="text/css" href="{% static 'blog/css/markdown_highlighy.css' %}" />

5、查看效果

可以看到markdown可以正常解析,代码也可以高亮显示了。

在这里插入图片描述

三、全套代码

https://gitee.com/xiejava/ishareblog


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!

12…20>
XieJava

XieJava

196 日志
11 分类
25 标签
RSS
GitHub
友情链接
  • 爱分享读书
  • CSDN
  • 豆瓣
© 2024 XieJava | Site words total count: 407.5k

主题 — NexT.Muse
0%