QQ交流群:298405437
本人QQ:4206359
具体视频地址:
1、前端对axios的封装理解和基本使用ruoyi的前端对axios进行了封装,让我们发get请求或者是post请求更加方便了。
ruoyi对axios的封装在下面文件中:
打开文件,可以看到它有三个显眼的方法,分别是request拦截器、response拦截器和通用下载方法。
request拦截器
1.1 Getrequest拦截器对我们发送的请求进行了封装,当我们发送Get请求,那么我们携带参数的时候应该用param。,对应下面的源码。
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
通过此这简单的代码,可以让get请求自动变为我们熟悉的形式[:port/aaa?key1=value1&key2=value2]。如需要详细了解,同学们可以利用单元测试的方法测试即可。
来看一个get请求的案例:
// 获取验证码 不带参数
export function getCodeImg() {
return request({
url: '/captchaImage',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}
// 查询在线用户列表 带参数
export function list(query) {
return request({
url: '/monitor/online/list',
method: 'get',
params: query
})
}
当我们查看在线用户的时候:发现确实用的param,而且确实请求变成了:port/aaa?key1=value1&key2=value2的样子,江哥你没有骗我:
话说回来,这个在线用户的功能怎么实现的呢?
直接告诉大家结论:ruoyi的在线用户存在redis中的,每次一个人登录,就会把他的登录信息存在redis中,当我们去查询在线用户,无非就是去redis中取一下有哪一些用户罢了!
具体而言,它使用redis的查询巧妙实现的(视频中debug),详细见视频。
我们还发现登录人具有一个TTL(保持登录状态的有效期),还发现登录人具有他的许许多多的信息......
1.2 Post-Post请求带参数使用data具体代码也差不多:
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}
}
}
响应拦截器,用于每一个响应的拦截过滤处理。
对不同状态码进行判断给出处理逻辑,并不是特别重要。
通用下载方法
ruoyi许多地方用到下载,比如说“字典管理”,里面可以导出,我们来演示一下。
【演示字典管理的下载,查看type_xxxx.xlsx的生成,方法的执行主要是三个参数。】
2、前端向后端传参的方式本节课主要讲述ruoyi中存在的restful风格在路径中传参和直接通过get或post携带的param、data传参。
2.1RESTful以参数管理为例子,用过configId查询它用到了restful风格的传参方式:
@PreAuthorize("@ss.hasPermi('system:config:query')") @GetMapping(value = "/{configId}") public AjaxResult getInfo(@PathVariable Long configId) {return AjaxResult.success(configService.selectConfigById(configId)); }Java
以上的/{configId}就是一种在路径中传参数的例子。
2.2 普通方式第二种直接在get的param中写,或者在post的data中写即可。
3、开关原理(验证码开关、IP开关) 3.1开关原理(验证码开关)我们的项目具有验证码功能,旧版不支持关闭,新版已经支持关闭了。
我们打开页面“参数管理”,所谓参数管理,就是在系统运行起来的时候,可以动态修改一些值,这些值会被系统实时修改,下次如果需要读值,则会是最新的值。
对于验证码而言,可以将下面的参数值修改为false即可:
重新登录发现确实没有验证码了??????真神奇呢??
我们直接看一下为啥没有了,上源码!
前端关键代码:
getCode() {
getCodeImg().then(res => {
this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
if (this.captchaOnOff) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
}
});
},
后端关键代码:
@GetMapping("/captchaImage") public AjaxResult getCode(HttpServletResponse response) throws IOException { AjaxResult ajax = AjaxResult.success(); boolean captchaOnOff = configService.selectCaptchaOnOff(); ajax.put("captchaOnOff", captchaOnOff); if (!captchaOnOff) { return ajax; } // 保存验证码信息 String uuid = IdUtils.simpleUUID(); String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; String capStr = null, code = null; BufferedImage image = null; // 生成验证码 String captchaType = RuoYiConfig.getCaptchaType(); if ("math".equals(captchaType)) { String capText = captchaProducerMath.createText(); capStr = capText.substring(0, capText.lastIndexOf("@")); code = capText.substring(capText.lastIndexOf("@") + 1); image = captchaProducerMath.createImage(capStr); } else if ("char".equals(captchaType)) { capStr = code = captchaProducer.createText(); image = captchaProducer.createImage(capStr); } redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try { ImageIO.write(image, "jpg", os); } catch (IOException e) { return AjaxResult.error(e.getMessage()); } ajax.put("uuid", uuid); ajax.put("img", Base64.encode(os.toByteArray())); return ajax; }Java
3.2开关原理(IP开关)上一种验证码开关是在参数设置里面设置的,随着程序运行之后,也能随时改。主要通过redis实现。
那么,IP开关呢?IP开关在YML,主要是用来看日志的时候能不能看到公网IP。
IP开关通过YML文件中写死实现,也就是说,一旦程序启动,就改不了了。
4、前端页面的布局详解 4.1 Vue-Devtools工具安装关于此工具的安装,两种方法。第一种通过别人已经下好的文件,应用在自己的浏览器上面即可(演示谷歌浏览器)。
第二种方法是直接在浏览器的插件库寻找,搜索vue一般就能出来(演示火狐浏览器)。
4.2 前端页面的布局 4.2.1情况一:不依赖Layout的情况ruoyi的前端页面目前存在两种情况,一种是依赖layout组件,一种是不依赖layout组件。
比如说登录页面,注册页面,404页面就不依赖于layout组件,layout组件实际上就是一个vue的组件。
这种组件比较简单,就是跟写HTML基本也没啥两样。
关于login.vue页面的对应组件树如下:
那么,我们要不要试着写一个类似的页面呢?来上手试试!
4.2.2 情况二:依赖Layout组件当我们成功登录,接下来基本上每一个页面都是出于Layout组件之下,也就是说,它是layout组件的子组件!
当我们登录之后,会自动到首页:
此时此刻的组件树如上。
Sidebar:对应左边菜单区。
Navbar:导航区。
TagsView:页签区。
RightPanel:右面板区,平常不可见。
AppMain:内容页面区,我们一般业务页面会出现在此区域。
下面我们写一个小小的案例来具体讲解一下如何新增自己的业务界面在AppMain区域中。
5、天气小案例 5.1 新增带Layout组件的页面直接在views文件夹下面新增weather.vue。然后随便写一个123,现在先让我们页面能跳过去先。
让页面能跳过去,有好几种方法:
1、在菜单管理自己添加一个菜单,然后把菜单分配给某个角色,再把该角色分给某个人。
【然而超级管理员什么时候都能看到此菜单,因为超级管理员能无视一切权限问题】
2、在路由文件(router/index.js直接写相关路由),然后可以手动切换浏览器网址进入该路由。
本次例子利用使用自己添加菜单的方法,这样比较简单。简单如下图:
组件路径一定要写对,写不对直接进不去相应的组件。路由地址可以乱写,但是起码也要有点“path”的样子吧?
先随便在weather.vue写一句话来测试一下:
发现页面也出来了:
现在我们可以开始专注页面了。
5.2专注weather业务首先.vue文件的代码如下:
<template> <div> <el-row style="margin-top: 30px;" :gutter="20"> <el-col :offset="10" :span="4"> <el-button type="success" @click="handleWeather">当前城市天气</el-button> </el-col> </el-row> <el-row :gutter="20" v-if="city.length>0"> <el-col :offset="2" :span="20"> <el-descriptions title="当前实时天气"> <el-descriptions-item label="当前城市">{{ city }}</el-descriptions-item> <el-descriptions-item label="温度">{{ weather.realtime.temperature }}℃</el-descriptions-item> <el-descriptions-item label="风向">{{ weather.realtime.direct }}</el-descriptions-item> <el-descriptions-item label="风力">{{ weather.realtime.power }}</el-descriptions-item> <el-descriptions-item label="湿度">{{ weather.realtime.humidity }}%</el-descriptions-item> <el-descriptions-item label="天气状况">{{ weather.realtime.info }}</el-descriptions-item> </el-descriptions> </el-col> </el-row> <el-row v-for="item in weather.future" :key="item.date" style="margin-top: 30px;" :gutter="20"> <el-col :offset="2" :span="20"> <el-descriptions :title="item.date" :column="4"> <el-descriptions-item label="风向">{{ item.direct }}</el-descriptions-item> <el-descriptions-item label="温度">{{ item.temperature }}</el-descriptions-item> <el-descriptions-item label="天气情况">{{ item.weather }}</el-descriptions-item> </el-descriptions> </el-col> </el-row> </div> </template> <script> import {getWeather} from "@/api/gzh/weather"; export default { name: "weather", data() { return { city: "", weather: { realtime: {}, future: [] } } }, methods: { handleWeather() { getWeather().then(res => { const weatherInfo = JSON.parse(res.msg); this.city = weatherInfo.result.city this.weather.realtime = weatherInfo.result.realtime this.weather.future = weatherInfo.result.future console.log(weatherInfo) }).catch(err => { console.error(err) }) } } } </script> <style scoped></style>
代码片段:可切换语言,无法单独设置文字格式
然后是前端api调用代码:
import request from "@/utils/request"; // 查询参数列表export function getWeather() { return request({ url: '/getWeatherByLocalIP', method: 'get', })}代码片段:可切换语言,无法单独设置文字格式
接下来是后端的工具类代码:
package com.ruoyi.web.controller.gzh; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.http.HttpUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @RestController public class WeatherController { @GetMapping("/getWeatherByLocalIP") public AjaxResult getWeather() throws UnsupportedEncodingException { AjaxResult result = AjaxResult.success(); String localCityName = GetLocationAndIP.getLocalCityName(); //调用天气API String encodeCity = URLEncoder.encode(localCityName, "UTF-8"); System.out.println(encodeCity); String url = "?city=" + encodeCity + "&key=81fe33a6077267b2e4ae2967af47eeb7"; String weatherInfo = HttpUtils.sendGet(url); result.put("msg", weatherInfo); return result; }}
代码片段:可切换语言,无法单独设置文字格式
然后是后端接口的代码:
package com.ruoyi.web.controller.gzh; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class GetLocationAndIP { private static String readAll(BufferedReader rd) throws IOException { StringBuilder sb = new StringBuilder(); int cp; while ((cp = rd.read()) != -1) { sb.append((char) cp); } return sb.toString(); } public static JSONObject readJsonFromUrl(String url) throws IOException, JSONException { try (InputStream is = new URL(url).openStream()) { BufferedReader rd = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); String jsonText = readAll(rd); return new JSONObject(jsonText); } } public Map<String, Object> getAddress() { String ip = ""; // 这个网址似乎不能了用了 // String chinaz = ""; // 改用了太平洋的一个网址 String chinaz = ""; StringBuilder inputLine = new StringBuilder(); String read = ""; URL url = null; HttpURLConnection urlConnection = null; BufferedReader in = null; Map<String, Object> map = new HashMap<>(); try { url = new URL(chinaz); urlConnection = (HttpURLConnection) url.openConnection(); // 如有乱码的,请修改相应的编码集,这里是 gbk in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "gbk")); while ((read = in.readLine()) != null) { inputLine.append(read).append("\r\n"); } } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } // 这个是之前的正则表达式, // Pattern p = Pattern.compile("\\<dd class\\=\"fz24\">(.*?)\\<\\/dd>"); // 通过正则表达式匹配我们想要的内容,根据拉取的网页内容不同,正则表达式作相应的改变 Pattern p = Pattern.compile("显示IP地址为(.*?)的位置信息"); Matcher m = p.matcher(inputLine.toString()); if (m.find()) { String ipstr = m.group(0); // 这里根据具体情况,来截取想要的内容 ip = ipstr.substring(ipstr.indexOf("为") + 2, ipstr.indexOf("的") - 1); map.put("ip", ip); } JSONObject json = null; try { // 这里调用百度的ip定位api服务 详见 json = readJsonFromUrl("?ak=laOQElaF53xGGBjscGtrd10nN4j1zGki&ip=" + ip); //city = (((JSONObject) ((JSONObject) json.get("content")).get("address_detail")).get("city")).toString(); map.put("city", ((JSONObject) ((JSONObject) json.get("content")).get("address_detail")).get("city").toString()); } catch (Exception e) { e.printStackTrace(); } return map; } public static String getLocalCityName() { GetLocationAndIP getLocationANDIp = new GetLocationAndIP(); Map<String, Object> map = getLocationANDIp.getAddress(); String city = map.get("city").toString(); return city.substring(0, city.length() - 1); } public static void main(String[] args) { GetLocationAndIP getLocationANDIp = new GetLocationAndIP(); Map<String, Object> map = getLocationANDIp.getAddress(); String city = map.get("city").toString(); String city_1 = city.substring(0, city.length() - 1); System.out.println(city_1); } }代码片段:可切换语言,无法单独设置文字格式
由此,天气小demo就跑起来了,效果图如下: