由于项目需要,需要在基于 asp.net mvc 的 Web 项目框架中做权限的控制,于是才有了这个权限控制组件,最初只是支持 netframework,后来 dotnetcore 2.0 发布了之后添加了对 asp.net core 的支持,在 dotnetcore 3.0 发布之后也增加了对 asp.net core 3.0 的支持(1.9.0及之后版本),目前对于 asp.net core 支持的更多一些,asp.net core 可以使用 TagHelper 来控制页面上元素的权限访问,也可以通过 Policy 来控制权限访问,同时支持通过中间件也可以实现对静态资源的访问。
安装 nuget 包 WeihanLi.AspNetMvc.AccessControlHelper
Copydotnet add package WeihanLi.AspNetMvc.AccessControlHelper
以下代码定义了一个简单的访问策略,需要登录且拥有 Admin 角色,可以根据自己需要调整优化
Copypublic class AdminPermissionRequireStrategy : IResourceAccessStrategy
{
private readonly IHttpContextAccessor _accessor;
public AdminPermissionRequireStrategy(IHttpContextAccessor accessor)
{
_accessor=accessor;
}
public bool IsCanAccess(string accessKey)
{
var user=_accessor.HttpContext.User;
return user.Identity.IsAuthenticated && user.IsInRole("Admin");
}
public IActionResult DisallowedCommonResult=> new ContentResult
{
Content="No Permission",
ContentType="text/plain",
StatusCode=403
};
public IActionResult DisallowedAjaxResult=> new JsonResult(new JsonResultModel
{
ErrorMsg="No Permission",
Status=JsonResultStatus.NoPermission
});
}
定义页面元素/控件访问策略:
Copypublic class AdminOnlyControlAccessStrategy : IControlAccessStrategy
{
private readonly IHttpContextAccessor _accessor;
public AdminOnlyControlAccessStrategy(IHttpContextAccessor httpContextAccessor)=> _accessor=httpContextAccessor;
public bool IsControlCanAccess(string accessKey)
{
if ("Never".Equals(accessKey, System.StringComparison.OrdinalIgnoreCase))
{
return false;
}
var user=_accessor.HttpContext.User;
return user.Identity.IsAuthenticated && user.IsInRole("Admin");
}
}
在 Startup 里注册服务:
Copyservices.AddAccessControlHelper()
.AddResourceAccessStrategy<AdminPermissionRequireStrategy>()
.AddControlAccessStrategy<AdminOnlyControlAccessStrategy>()
;
如果你只是 web api ,不涉及到页面元素的权限控制可以只注册 ResourceAccessStrategy
Copyservices.AddAccessControlHelper()
.AddResourceAccessStrategy<AdminPermissionRequireStrategy>();
默认访问策略的生命周期是单例的,如果需要注册为Scoped,可以指定默认的生命周期
Copyservices.AddAccessControlHelper()
.AddResourceAccessStrategy<AdminPermissionRequireStrategy>(ServiceLifetime.Scoped);
对于 asp.net core 应用推荐使用 Policy 来控制权限的访问,可以在需要权限控制的 Action 或者 Controller 上设置 [Authorize("AccessControl")] 或者 [Authorize(AccessControlHelperConstants.PolicyName)]
Copy[Authorize(AccessControlHelperConstants.PolicyName)]
public class SystemSettingsController : AdminBaseController
{
// ...
}
Copy[Authorize(AccessControlHelperConstants.PolicyName)]
public ActionResult UserList()
{
return View();
}
在 Views 目录下的 _ViewImports.cshtml 文件中导入 AccessControlHelper 的 TagHelper
Copy@using ActivityReservation
@using WeihanLi.AspNetMvc.AccessControlHelper
@using WeihanLi.AspNetMvc.MvcSimplePager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, WeihanLi.AspNetMvc.AccessControlHelper
在需要权限控制的元素上增加 asp-access 的 attribute 就可以了,如果需要 access-key 通过 asp-access-key 来配置
Copy<ul class="list-group" asp-access asp-access-key="AdminOnly">
<li role="separator" class="list-unstyled">
<br />
</li>
<li class="list-group-item">@Html.ActionLink("用户管理", "UserList", "Account")</li>
<li class="list-group-item">@Html.ActionLink("操作日志查看", "Index", "OperationLog")</li>
<li class="list-group-item">@Html.ActionLink("系统设置管理", "Index", "SystemSettings")</li>
<li class="list-group-item">
@Html.ActionLink("微信设置管理", "Index", new {
controller="Config",
area="Wechat"
})
</li>
</ul>
这样就可以了,有权限访问的时候才会正常渲染,没有权限访问的时候,这一段 ul 并不会渲染输出,在客户端浏览器查看源代码也不会看到对应的代码
FormMaking表单设计器是一款与行业无关的通用表单设计器前端解决方案,表单设计器包含设计器(MakingForm)和生成器(GenerateForm)两个模块,设计器主要负责表单的动态设计,可以通过拖拽的形式设计生成表单页面,生成器则负责对表单页面的渲染和数据的回写。
如果你对FormMaking不太了解可能会把FormMaking当成是单纯页面设计器,只为了减少重复的表单,稍微有点经验的前端开发,通过简单的Copy Paste就能很快做出一个固定的表单页面。而FormMaking做的是一整套与表单相关的前端方案,包含了表单的动态设计,表单渲染,数据回写,数据校验,事件支持,css自定义,动作面板等丰富的功能,而这些所有的功能只需要一个import 命令将MakingForm和GenerateForm引入到项目中,你的项目就可以拥有这些功能。
FormMaking基础功能可以看之前都介绍。
本文我们将介绍如何使用 FormMaking 来引入自己的高级组件,并可以通过设计器进行配置,处理事件等操作
封装分页数据表格组件
我们将封装 一个分页数据表格组件,组件采用 ElementPlus TableV2
<template>
<div>
<div style="height: 400px">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
:columns="columns"
:data="data"
:width="width"
:height="height"
fixed
/>
</template>
</el-auto-resizer>
</div>
<el-pagination
background
layout="prev, pager, next"
:total="1000"
v-model:current-page="currentPage"
@current-change="loadPageData"
/>
</div>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue'
const props=defineProps({
modelValue: {
type: Array,
default: ()=> []
},
columns: {
type: Array,
default: ()=> []
}
})
const emit=defineEmits(['on-load'])
const data=ref(props.modelValue)
const currentPage=ref(1)
const loadPageData=(index)=> {
// 通过 emit,可以将事件抛出到设计器中进行配置
emit('on-load', index)
}
onMounted(()=> {
emit('on-load', currentPage.value)
})
watch(()=> props.modelValue, (val)=> {
data.value=val
})
</script>
引入到设计器
注册组件
首先应该在自己项目中进行组件的注册。
import CustomPaginationTable from 'PaginationTable.vue'
app.use(FormMakingV3, {
components: [{
name: 'custom-pagination-table',
component: CustomPaginationTable // 自定义组件
}]
})
也可以使用Vue.component 进行组件的注册
app.component('custom-pagination-table', CustomPaginationTable)
设计器配置
<template>
<fm-making-form
:custom-fields="customFields"
>
</fm-making-form>
</template>
<script>
export default {
data() {
return {
customFields: [
{
name: '分页数据列表',
el: 'custom-pagination-table',
options: {
defaultValue: [],
labelWidth: 0,
isLabelWidth: false,
hidden: false,
dataBind: true,
validator: '',
extendProps: {
columns: [] // 用于配置表格的列
}
},
events: {
onLoad: '' // 定义设计器可以配置的事件,处理组件 emit 的事件
}
}
]
}
}
}
</script>
然后在自定义字段中就展示出来了
设计器界面
配置组件表格
表格列配置
在字段属性中对扩展属性配置 进行设置
数据加载
数据加载的 on-load事件,我们在自定义组件中通过emit抛出到设计器中进行配置,在字段属性-动作设置中添加onLoad事件配置如下:
效果展示
我们来查看下最后的效果
效果图
更多关于FormMaking详情可移步:
可视化低代码表单设计器 - FormMaking
宝页面比较复杂,含有各种请求参数和加密参数,如果直接请求或者分析Ajax将会非常繁琐。Selenium是一个自动化测试工具,可以驱动浏览器去完成各种工作,比如模拟点击、输入和下拉等多种功能,这样我们只需关心操作,不需要关心后台发生了怎么样的请求下面对具体操作步骤进行详述
实现流程
1.搜索关键字:利用Selenium驱动浏览器搜索关键字,得到查询后的商品列表
2.分析页码并翻页:得到商品页码数,模拟翻页,得到后续页面的商品列表
3.分析提取商品内容:利用PyQuery分析源码,解析得到商品列表
4.存储至MongoDB:将商品列表信息存储到数据库MongoDB
具体实现
1.首先需要声明一个browser用来操作,我的是chrome。这里的wait是在后面的判断元素是否出现时使用,第二个参数为等待最长时间,超过该值则抛出异常
#创建一个浏览器对象 browser=webdriver.Chrome() #十秒内寻找元素失败,将报出TimeoutException异常 wait=WebDriverWait(browser, 10)
2.声明好之后就需要进行打开网页、进行搜索的操作
def search(): """ 作用:通过关键字搜索得到搜索内容第一页产品信息,并返回搜索内容共计页码 """ print('正在搜索') try: browser.get('https://www.taobao.com') print(browser.page_source) # 获取input输入框对象 input=wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "#q")) )# # 获取button提交对象 submit=wait.until( EC.element_to_be_clickable( (By.CSS_SELECTOR, "#J_TSearchForm > div.search-button > button") ) ) # KEYWORD是config.py文件中定义的搜索关键字,可修改 input.send_keys(KEYWORD) # 点击button提交按钮 submit.click() # 通过CSS_SELECTOR选择器获取total页码 total_page=wait.until( EC.presence_of_element_located( (By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.total") ) ) # 解析页面内容,并将数据保存至MongoDB中 get_products() return total_page.text except TimeoutException: # 出现超时访问就重新调用一次search return search()
3.第一个页面操作之后,我们需要进行翻页操作,如下:
def next_page(page_number): """ 作用:作人为翻页功能,并得到该页产品信息 page_number:循环的页码,将页码写入input框中 """ print('正在翻页', page_number) try: # 获取input输入框对象 input=wait.until( EC.presence_of_element_located( (By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input") ) ) # 获取button提交对象 submit=wait.until( EC.element_to_be_clickable( (By.CSS_SELECTOR, "div > div > div > div.form > span.btn.J_Submit") ) ) # 将input框清空 input.clear() # 向input框中输入页码 input.send_keys(page_number) # 点击button提交按钮 submit.click() wait.until( # 验证输入框内的页码是否为当前高亮页码,如果是,则继续执行 # text参数是否在该span元素内,此处需要注意text的参数位置 EC.text_to_be_present_in_element( (By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > ul > li.item.active > span'), str(page_number) ) ) # 获取产品信息并保存至MongoDB get_products() except TimeoutException: # 超时异常,则继续解析该页 next_page(page_number)
4.写完搜索操作和翻页操作后,我们需要完成对每个页面的商品信息获取功能
def get_products(): """ 作用:通过页面源代码,将产品信息保存至MongoDB中 """ # 通过CSS_SELECTOR选择器,验证页面中是否存在产品对象 wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-itemlist .items .item")) ) # 拿到页面源码 html=browser.page_source # 通过pyquery找到产品对象 doc=pq(html) items=doc('#mainsrp-itemlist .items .item').items() # 遍历每个产品对象,组建数据内容 for item in items: product={ 'image': item.find('.pic .img').attr('src'), 'price': item.find('.price').text(), 'deal': item.find('.deal-cnt').text()[:-3], 'title': item.find('.title').text(), 'shop': item.find('.shop').text(), 'location': item.find('.location').text() } # print(product) # 将组建好的每个产品信息保存至MongoDB save_to_mongo(product)
5.获取信息之后则需要对信息进行存储,将数据库关键参数定义在config.py文件中
#!/usr/bin/env python # coding=utf-8 # 本地的数据库 MONGO_URL='localhost' # 数据库的名称 MONGO_DB='taobao' # 数据库的表名 MONGO_TABLE='products'
6.将组建好的每个产品信息保存至MongoDB
# 创建一个MongoDB对象 client=pymongo.MongoClient(MONGO_URL) # 创建一个数据库,注意是[]而不是() db=client[MONGO_DB] def save_to_mongo(result): """ 作用:将信息保存至MongoDB中 """ try: if db[MONGO_TABLE].insert(result): print('Successfully save to MongoDB', result) except Exception: print('Failed save to MongoDB', result)
7.爬取调度器
def TbMeishi_Spider(): """ 作用:淘宝美食爬取调度器 """ try: total_page=search() # 通过正则将search返回的total页码内容解析成数字形式 pattern=re.compile('(\d+)') total_page=int(pattern.search(total_page).group(1)) # print(total_page) # 遍历页码,传入next_page函数中,做整站爬取 for i in range(2, total_page + 1): next_page(i) except Exception: print('有错误产生...') finally: browser.close()
8.代码完成到这里已经可以结束了,但是使用Selenium模拟会发现每次运行程序都会打开一个浏览器,然后自动操作,可以最直接地观察到代码的运行,但是假如不想弹出浏览器的话,可以使用PhantomJS来实现一个无界面的webkit浏览器。虽然没有界面,但dom渲染、js运行、网络访问、canvas/svg绘制等功能都很完备,在页面抓取、页面输出、自动化测试等方面有广泛的应用。下载PhantomJS地址:http://phantomjs.org/download.html,下载完成后解压缩,将PhantomJS添加到环境变量中即可
在config.py文件中加上
# 关闭load-images的功能,加快运行速度,默认开启,开启disk-cache缓存,默认关闭 SERVICE_ARGS=['--load-images=false', '--disk-cache=true'] # 无界面浏览器,将browser改为 browser=webdriver.PhantomJS(service_args=SERVICE_ARGS) # 设置PlantomJS的窗口大小,可能会影响内容下载 browser.set_window_size(1400, 900)
总结思考
1.目标网址不匹配
一开始使用显示等待访问,通过CSS_SELECTOR的id选择器找到网页元素,可最后程序都执行异常,每次都是TimeoutExceptions的异常错误。经过反复排查,才发现淘宝网有很多个站点
我在程序中使用的是:http://www.taobao.com
但是在进行网页分析的时候随意用百度搜了一个淘宝站点,它的站点是:https://uland.taobao.com/sem/tbsearch
两个站点地址不一样,网页元素也不一样,自己竟然会犯这种错误,应牢记,正确的选择元素应该是下面这几行
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC input=WebDriverWait(browser, 10).until( EC.presence_of_element_located((By.ID, 'q')) ) button=WebDriverWait(browser, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')) )
2.text参数是否在该span元素内,此处需要注意text的参数位置
# 验证text参数是否与选择器选择的元素文本内容一致 text_to_be_present_in_element(locator, text_)
3.完整源码地址:https://github.com/XiaoFei-97/TbMeishi_Spider\
*请认真填写需求信息,我们会在24小时内与您取得联系。