关于flask:python-flask初体验

引言以前,学习前端的时候用过nodejs做后端,过后用if...else...写门路判断写的很臃肿。当初想试试其余语言,就选了python试试水。从nodejs到flask相对是个微小的挑战,尤其是对想我这样很久没有看过js的人来说。之前用的nodejs+react,当初有点看不懂了,感叹年老真好。 选型python的支流web框架有django,flask,tornado,twisted。。。年老、轻量、顺口,所以我选用flask。对于什么是web框架能够看下图。wsgi解释一套大家约定的接口标准;wsgi有很多能够抉择的web framework就是各个语言本人规定的一套规范,旨在不便开发人员开发业务相干;http server了解为解决网络协议相干的服务。 看文档的时候,很多web framework也能提供http server。不过,他们也说了,大部分用来debug用,不倡议生产上应用 装置装置flask我应用了centos 8 stream。应用yum命令。 yum install flask装置uWSGI官网:uwsgi官网上述地址是uwsgi对于疾速动手python的文档。装置相干节选如下截图:你很容易看到via pip; pip install uwsgi;。 那么首先,须要取得pip,这意味着咱们须要先装置python(哈哈哈,当然,咱们搞的就是python的web框架) 看一下咱们能装置什么版本 yum list *python* 装置一个能装置的最新的版本 yum install python39 当初,咱们应该能够看到像python3,python3.9,pip等python相干的命令了。 但咱们执行pip install uwsgi的时候会报错。我忘了截图了。不要慌乱,在一堆红字中,他提醒咱们Exception: you need a C compiler to build uWSGI 装置一个c compile yum install gcc但咱们执行pip install uwsgi的时候会报错。我忘了截图了。不要慌乱,在一堆红字中,他提醒咱们fatal error: Python.h: No such file or directory 查看Python development headers yum list *python*devel 找一个和python版本对应的版本,装置Python development headers yum install python39-devel这些都装置实现后,执行pip install uwsgi,应该没有问题了。我装置的时候比拟卡,多执行几次,总能胜利的。其实,uwsgi的文档中写了。再回到这张截图,看下高亮的句子“uWSGI is a (big) C application, so you need a C compiler (like gcc or clang) and the Python development headers.” ...

July 4, 2023 · 1 min · jiezi

关于flask:SpringBoot-如何保证接口安全老鸟们都是这么玩的

为什么要保障接口平安对于互联网来说,只有你零碎的接口裸露在外网,就防止不了接口平安问题。 如果你的接口在外网裸奔,只有让黑客晓得接口的地址和参数就能够调用,那几乎就是劫难。举个例子:你的网站用户注册的时候,须要填写手机号,发送手机验证码,如果这个发送验证码的接口没有通过非凡平安解决,那这个短信接口早就被人盗刷不晓得节约多少钱了。那如何保障接口平安呢?一般来说,裸露在外网的api接口须要做到防篡改和防重放能力称之为平安的接口。防篡改咱们晓得http 是一种无状态的协定,服务端并不知道客户端发送的申请是否非法,也并不知道申请中的参数是否正确。举个例子, 当初有个充值的接口,调用后能够给用户减少对应的余额。http://localhost/api/user/recharge?user_id=1001&amount=10复制代码如果非法用户通过抓包获取到接口参数后,批改user_id 或 amount的值就能够实现给任意账户增加余额的目标。如何解决采纳https协定能够将传输的明文进行加密,然而黑客依然能够截获传输的数据包,进一步伪造申请进行重放攻打。如果黑客应用非凡伎俩让申请方设施应用了伪造的证书进行通信,那么https加密的内容也会被解密。个别的做法有2种: 采纳https形式把接口的数据进行加密传输,即使是被黑客破解,黑客也破费大量的工夫和精力去破解。接口后盾对接口的申请参数进行验证,避免被黑客篡改; 步骤1:客户端应用约定好的秘钥对传输的参数进行加密,失去签名值sign1,并且将签名值也放入申请的参数中,发送申请给服务端步骤2:服务端接管到客户端的申请,而后应用约定好的秘钥对申请的参数再次进行签名,失去签名值sign2。步骤3:服务端比对sign1和sign2的值,如果不统一,就认定为被篡改,非法申请。 防重放防重放也叫防复用。简略来说就是我获取到这个申请的信息之后什么也不改,,间接拿着接口的参数去 反复申请这个充值的接口。此时我的申请是非法的, 因为所有参数都是跟非法申请截然不同的。重放攻打会造成两种结果: 针对插入数据库接口:重放攻打,会呈现大量反复数据,甚至垃圾数据会把数据库撑爆。针对查问的接口:黑客个别是重点攻打慢查问接口,例如一个慢查问接口1s,只有黑客发动重放攻打,就必然造成零碎被拖垮,数据库查问被阻塞死。 对于重放攻打个别有两种做法:基于timestamp的计划每次HTTP申请,都须要加上timestamp参数,而后把timestamp和其余参数一起进行数字签名。因为一次失常的HTTP申请,从收回达到服务器个别都不会超过60s,所以服务器收到HTTP申请之后,首先判断工夫戳参数与以后工夫比拟,是否超过了60s,如果超过了则认为是非法申请。个别状况下,黑客从抓包重放申请耗时远远超过了60s,所以此时申请中的timestamp参数曾经生效了。 如果黑客批改timestamp参数为以后的工夫戳,则sign1参数对应的数字签名就会生效,因为黑客不晓得签名秘钥,没有方法生成新的数字签名。 然而这种形式的破绽也是不言而喻,如果在60s之内进行重放攻打,那就没方法了,所以这种形式不能保障申请仅一次无效。 老鸟们个别会采取上面这种计划,既能够解决接口重放问题,又能够解决一次申请无效的问题。基于nonce + timestamp 的计划nonce的意思是仅一次无效的随机字符串,要求每次申请时该参数要保障不同。理论应用用户信息+工夫戳+随机数等信息做个哈希之后,作为nonce参数。此时服务端的解决流程如下: 去 redis 中查找是否有 key 为 nonce:{nonce} 的 string如果没有,则创立这个 key,把这个 key 生效的工夫和验证 timestamp 生效的工夫统一,比方是 60s。如果有,阐明这个 key 在 60s 内曾经被应用了,那么这个申请就能够判断为重放申请。 这种计划nonce和timestamp参数都作为签名的一部分传到后端,基于timestamp计划能够让黑客只能在60s内进行重放攻打,加上nonce随机数当前能够保障接口只能被调用一次,能够很好的解决重放攻打问题。代码实现接下来以SpringBoot我的项目为例看看如何实现接口的防篡改和防重放性能。1、构建申请头对象@Data@Builderpublic class RequestHeader {   private String sign ;   private Long timestamp ;   private String nonce;}复制代码2、工具类从HttpServletRequest获取申请参数@Slf4j@UtilityClasspublic class HttpDataUtil {    /**     * post申请解决:获取 Body 参数,转换为SortedMap     *     * @param request     */    public  SortedMap<String, String> getBodyParams(final HttpServletRequest request) throws IOException {        byte[] requestBody = StreamUtils.copyToByteArray(request.getInputStream());        String body = new String(requestBody);        return JsonUtil.json2Object(body, SortedMap.class);   }    /**     * get申请解决:将URL申请参数转换成SortedMap     */    public static SortedMap<String, String> getUrlParams(HttpServletRequest request) {        String param = "";        SortedMap<String, String> result = new TreeMap<>();        if (StringUtils.isEmpty(request.getQueryString())) {            return result;       }        try {            param = URLDecoder.decode(request.getQueryString(), "utf-8");       } catch (UnsupportedEncodingException e) {            e.printStackTrace();       }        String[] params = param.split("&");        for (String s : params) {            String[] array=s.split("=");            result.put(array[0], array[1]);       }        return result;   }}复制代码这里的参数放入SortedMap中对其进行字典排序,前端构建签名时同样须要对参数进行字典排序。3、签名验证工具类@Slf4j@UtilityClasspublic class SignUtil {    /**     * 验证签名     * 验证算法:把timestamp + JsonUtil.object2Json(SortedMap)合成字符串,而后MD5     */    @SneakyThrows    public  boolean verifySign(SortedMap<String, String> map, RequestHeader requestHeader) {        String params = requestHeader.getNonce() + requestHeader.getTimestamp() + JsonUtil.object2Json(map);        return verifySign(params, requestHeader);   }    /**     * 验证签名     */    public boolean verifySign(String params, RequestHeader requestHeader) {        log.debug("客户端签名: {}", requestHeader.getSign());        if (StringUtils.isEmpty(params)) {            return false;       }        log.info("客户端上传内容: {}", params);        String paramsSign = DigestUtils.md5DigestAsHex(params.getBytes()).toUpperCase();        log.info("客户端上传内容加密后的签名后果: {}", paramsSign);        return requestHeader.getSign().equals(paramsSign);   }}复制代码4、HttpServletRequest包装类public class SignRequestWrapper extends HttpServletRequestWrapper {    //用于将流保留下来    private byte[] requestBody = null;    public SignRequestWrapper(HttpServletRequest request) throws IOException {        super(request);        requestBody = StreamUtils.copyToByteArray(request.getInputStream());   }    @Override    public ServletInputStream getInputStream() throws IOException {        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);        return new ServletInputStream() {            @Override            public boolean isFinished() {                return false;           }            @Override            public boolean isReady() {                return false;           }            @Override            public void setReadListener(ReadListener readListener) {           }            @Override            public int read() throws IOException {                return bais.read();           }       };   }    @Override    public BufferedReader getReader() throws IOException {        return new BufferedReader(new InputStreamReader(getInputStream()));   }}复制代码防篡改和防重放咱们会通过SpringBoot Filter来实现,而编写的filter过滤器须要读取request数据流,然而request数据流只能读取一次,须要本人实现HttpServletRequestWrapper对数据流包装,目标是将request流保留下来。5、创立过滤器实现平安校验@Configurationpublic class SignFilterConfiguration {    @Value("${sign.maxTime}")    private String signMaxTime;    //filter中的初始化参数    private Map<String, String> initParametersMap =  new HashMap<>();    @Bean    public FilterRegistrationBean contextFilterRegistrationBean() {        initParametersMap.put("signMaxTime",signMaxTime);        FilterRegistrationBean registration = new FilterRegistrationBean();        registration.setFilter(signFilter());        registration.setInitParameters(initParametersMap);        registration.addUrlPatterns("/sign/*");        registration.setName("SignFilter");        // 设置过滤器被调用的程序        registration.setOrder(1);        return registration;   }    @Bean    public Filter signFilter() {        return new SignFilter();   }}复制代码@Slf4jpublic class SignFilter implements Filter {    @Resource    private RedisUtil redisUtil;    //从fitler配置中获取sign过期工夫    private Long signMaxTime;    private static final String NONCE_KEY = "x-nonce-";    @Override    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;        log.info("过滤URL:{}", httpRequest.getRequestURI());        HttpServletRequestWrapper requestWrapper = new SignRequestWrapper(httpRequest);        //构建申请头        RequestHeader requestHeader = RequestHeader.builder()               .nonce(httpRequest.getHeader("x-Nonce"))               .timestamp(Long.parseLong(httpRequest.getHeader("X-Time")))               .sign(httpRequest.getHeader("X-Sign"))               .build();        //验证申请头是否存在        if(StringUtils.isEmpty(requestHeader.getSign()) || ObjectUtils.isEmpty(requestHeader.getTimestamp()) || StringUtils.isEmpty(requestHeader.getNonce())){            responseFail(httpResponse, ReturnCode.ILLEGAL_HEADER);            return;       }        /*         * 1.重放验证         * 判断timestamp工夫戳与以后工夫是否操过60s(过期工夫依据业务状况设置),如果超过了就提醒签名过期。         */        long now = System.currentTimeMillis() / 1000;        if (now - requestHeader.getTimestamp() > signMaxTime) {            responseFail(httpResponse,ReturnCode.REPLAY_ERROR);            return;       }        //2. 判断nonce        boolean nonceExists = redisUtil.hasKey(NONCE_KEY + requestHeader.getNonce());        if(nonceExists){            //申请反复            responseFail(httpResponse,ReturnCode.REPLAY_ERROR);            return;       }else {            redisUtil.set(NONCE_KEY+requestHeader.getNonce(), requestHeader.getNonce(), signMaxTime);       }        boolean accept;        SortedMap<String, String> paramMap;        switch (httpRequest.getMethod()){            case "GET":                paramMap = HttpDataUtil.getUrlParams(requestWrapper);                accept = SignUtil.verifySign(paramMap, requestHeader);                break;            case "POST":                paramMap = HttpDataUtil.getBodyParams(requestWrapper);                accept = SignUtil.verifySign(paramMap, requestHeader);                break;            default:                accept = true;                break;       }        if (accept) {            filterChain.doFilter(requestWrapper, servletResponse);       } else {            responseFail(httpResponse,ReturnCode.ARGUMENT_ERROR);            return;       }   }    private void responseFail(HttpServletResponse httpResponse, ReturnCode returnCode) {        ResultData<Object> resultData = ResultData.fail(returnCode.getCode(), returnCode.getMessage());        WebUtils.writeJson(httpResponse,resultData);   }    @Override    public void init(FilterConfig filterConfig) throws ServletException {        String signTime = filterConfig.getInitParameter("signMaxTime");        signMaxTime = Long.parseLong(signTime);   }}复制代码6、Redis工具类@Componentpublic class RedisUtil {    @Resource    private RedisTemplate<String, Object> redisTemplate;    /**     * 判断key是否存在     * @param key 键     * @return true 存在 false不存在     */    public boolean hasKey(String key) {        try {            return Boolean.TRUE.equals(redisTemplate.hasKey(key));       } catch (Exception e) {            e.printStackTrace();            return false;       }   }    /**     * 一般缓存放入并设置工夫     * @param key   键     * @param value 值     * @param time 工夫(秒) time要大于0 如果time小于等于0 将设置无限期     * @return true胜利 false 失败     */    public boolean set(String key, Object value, long time) {        try {            if (time > 0) {                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);           } else {                set(key, value);           }            return true;       } catch (Exception e) {            e.printStackTrace();            return false;       }   }    /**     * 一般缓存放入     * @param key   键     * @param value 值     * @return true胜利 false失败     */    public boolean set(String key, Object value) {        try {            redisTemplate.opsForValue().set(key, value);            return true;       } catch (Exception e) {            e.printStackTrace();            return false;       }   }} ...

March 13, 2023 · 7 min · jiezi

关于flask:flask-112-移植指南openEuler-2003-LTS-SP1

介绍简要介绍Flask是一个应用 Python 编写的轻量级 Web 利用框架。其 WSGI 工具箱采纳 Werkzeug ,模板引擎则应用 Jinja2 。本案例应用x86_64架构虚拟机,通过评估工具x2openEuler评估flask 1.1.2软件移植到openEuler操作系统的兼容性,并依据评估后果实现软件移植。 开发语言:Python 开源协定:BSD 倡议的版本倡议应用版本为flask 1.1.2。 阐明:本文档实用于flask 1.1.2,其余版本的flask移植步骤也可参考本文档。环境要求操作系统要求操作系统版本openEuler20.03 LTS SP1CentOS7.6装置操作系统如果是全新装置操作系统,装置形式倡议不要应用最小化装置,否则很多软件包须要手动装置,可抉择“Server with GUI”装置形式。装置openEuler操作系统请参考:https://openeuler.org/zh/docs/20.03_LTS_SP1/docs/Installation/installation.html。 兼容性评估获取flask的RPM包wget https://download-ib01.fedoraproject.org/pub/epel/7/x86_64/Packages/p/python36-flask-1.1.2-4.el7.noarch.rpm下载x2openEuler工具下载指引:https://www.openeuler.org/zh/other/migration/部署工具rpm -ivh x2openEuler-2.0.0-1.x86_64.rpm留神:装置rpm时须要应用root用户,且目前须要网络(用于下载安装依赖)留神:依据提醒装置依赖包如bzip2-devel等su x2openEulerx2openEuler redis-db -init顺次录入redis数据库的ip:127.0.0.1端口:6379数据库索引号(0-16):0明码(工具会对明码加密解决):如果redis明码没有设置或者为空时,间接回车即可x2openEuler init source_centos7.6-openEuler20.03-LTS-SP1.tar.gz备注:x2openEuler应用rpm装置实现后会在/opt/x2openEuler目录下带有source_centos7.6-openEuler20.03-LTS-SP1.tar.gz这个默认资源包须要反对centos8.2到openEuler20.03-LTS-SP1的评估,则需获取对应的动态资源包导入,如对应的资源包为source_centos8.2-openEuler20.03-LTS-SP1.tar.gz,导入此包命令:x2openEuler init source_centos8.2-openEuler20.03-LTS-SP1.tar.gz,请示状况抉择对应的资源包扫描软件x2openEuler scan python36-flask-1.1.2-4.el7.noarch.rpm留神要剖析的移植文件须要有可能让x2openEuler用户能够读取的权限扫描实现后会在/opt/x2openEuler/output目录生成html格局的报告查看评估后果软件兼容性评估报告分三块内容展现软件兼容性,别离是依赖包兼容性、C/C++接口兼容性、java接口兼容性,依赖包兼容性反映了软件包装置过程中的间接依赖,非100%表明无奈正确装置;接口兼容性反映的是单个软件运行过程中对其余软件包、动静库或零碎接口的调用变动,非100%表明在某个性能调用时可能会触发异样,未调用到时可能体现失常;局部后果倡议人工复核,最终软件包应用建优先级倡议 openEuler已移植包>openEuler上人工重编译包>centos软件包。 后果:通过报告可知内部接口兼容性100%,依赖包兼容性人工复核后确认缺失,因为该软件包属于python包,倡议应用pip3形式装置依赖及python-flask对应版本。 装置flaskrpm装置因为兼容性报告显示依赖包缺失,尝试间接用下载的rpm包装置做个验证。 [[email protected] test]# yum install -y python36-flask-1.1.2-4.el7.noarch.rpmLast metadata expiration check: 1:39:08 ago on Mon 22 Mar 2021 10:35:29 AM CST.Error: Problem: conflicting requests - nothing provides python36-setuptools needed by python36-flask-1.1.2-4.el7.noarch - nothing provides python(abi) = 3.6 needed by python36-flask-1.1.2-4.el7.noarch - nothing provides python36-click >= 5.1 needed by python36-flask-1.1.2-4.el7.noarch - nothing provides python36-itsdangerous >= 0.24 needed by python36-flask-1.1.2-4.el7.noarch - nothing provides python36-jinja2 >= 2.10.1 needed by python36-flask-1.1.2-4.el7.noarch - nothing provides python36-werkzeug >= 0.15 needed by python36-flask-1.1.2-4.el7.noarch(try to add '--skip-broken' to skip uninstallable packages or '--nobest' to use not only best candidate packages)因为依赖起因未能装置。 ...

November 8, 2022 · 2 min · jiezi

关于flask:Flask-Echarts-制作-仪表盘

下载https://echarts.apache.org/examples/zh/index.html 代码展现html{% extends 'layout.html' %}{% block xx %} <blockquote class="layui-elem-quote"> 看板开发ing <a class="layui-font-blue" href="" target="_blank">other dashboards</a> 的页面,敬请期待。 </blockquote> <fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;"> <legend> Dashboards</legend> </fieldset> <div class="layui-row"> <div class="layui-col-md6"> <div class="grid-demo grid-demo-bg1"> <div id="contest" style="width: 600px;height:400px;"> </div> </div> </div> <div class="layui-col-md6"> <div class="grid-demo"> <div class="layui-bg-gray" style="padding: 30px;"> <div class="layui-row layui-col-space15"> <div class="layui-col-md6"> <div class="layui-card"> <div class="layui-card-header">在线人数 </div> <div class="layui-card-body"> <br> <br> <br> <h1 style="height: 90px;width: 90px;"> {{ login_count_online }}</h1> <br> <br> <br> <br> </div> </div> </div> </div> </div> </div> </div> </div> <div class="layui-row"> <div class="layui-col-xs6"> <div id="main" style="width: 500px;height:400px;" class="grid-demo grid-demo-bg1"></div> </div> <div class="layui-col-xs6"> <div id='tests' style='width:500px;height:400px;' class="grid-demo"></div> </div> </div>{% endblock %}{% block js %} <script type="text/javascript"> // 基于筹备好的dom,初始化echarts实例 var myChart = echarts.init(document.getElementById('contest')); // 指定图表的配置项和数据 var option = { title: { text: 'Resource percentage analysis chart', subtext: 'Resource Data', left: 'center' }, tooltip: { trigger: 'item' }, legend: { orient: 'vertical', left: 'left' }, series: [ { name: 'Access From', type: 'pie', radius: '50%', data: [ {% for data in data_js %} {value:{{ data.value }}, name: '{{data.name}}'}, {% endfor %} ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] }; myChart.setOption(option); </script> <script type="text/javascript"> // 基于筹备好的dom,初始化echarts实例 var myChart = echarts.init(document.getElementById('main')); // 指定图表的配置项和数据 var option = { title: { text: 'Resource histogram chart' }, tooltip: {}, legend: { data: ['数量'] }, xAxis: { data: {{ data_name|safe }} }, yAxis: {}, series: [{ name: '资产类型', type: 'bar', data: {{ data_value|safe }} }] }; myChart.setOption(option); </script> <script> var myChart = echarts.init(document.getElementById('tests')); var option = { title: {text: '浏览量统计'}, tooltip: {}, legend: {data: ['浏览量']}, xAxis: { data:{{ date|safe }} }, yAxis: {}, series: [{ name: '浏览量', type: 'line', data:{{ value|safe }} }] }; myChart.setOption(option); </script>{% endblock %}flask@dashboard.route('/', methods=['GET'])@login_requireddef index1(): login_count_online = get_keys_pattern_count('login') print('/ wecome! hello', '##login_count_online:', login_count_online, type(login_count_online)) date_list1 = date_list(7) count_login = [] for i in date_list1: login_count = user_login.query.filter(user_login.date_time.like("%%{tdate}%%".format(tdate=i))).count() count_login.append(login_count) resource_dic = db.session.query(func.count(Resources.id), Select.select_name).filter( Resources.parent_id == Select.select_id).group_by(Resources.parent_id).all() # [(2, 'ip'), (5, 'conferenceroomname'), (1, 'server'), (1, 'printer')] data_js = [] data_value = [] data_name = [] for j in resource_dic: l2 = {} l2['value'] = j[0] data_value.append(j[0]) l2["name"] = j[1] data_name.append(j[1]) data_js.append(l2) print("###data_js:", data_js) db.session.close() print("##date_list1:", date_list1, "##count_login:", count_login) print("##data_js", data_js) print("##data_value", data_value) print("##data_name", data_name) print("##login_count_online", login_count_online) return render_template('index.html', date=date_list1, value=count_login, data_js=data_js, data_value=data_value, data_name=data_name, login_count_online=login_count_online)print/ wecome! hello ##login_count_online: 2 <class 'int'>['2022-08-31', '2022-09-01', '2022-09-02', '2022-09-03', '2022-09-04', '2022-09-05', '2022-09-06']###data_js: [{'value': 2, 'name': 'instancehost'}, {'value': 5, 'name': 'conferenceroomname'}, {'value': 1, 'name': 'server'}, {'value': 1, 'name': 'printer'}]##date_list1: ['2022-08-31', '2022-09-01', '2022-09-02', '2022-09-03', '2022-09-04', '2022-09-05', '2022-09-06'] ##count_login: [1, 0, 1, 0, 0, 0, 0]##data_js [{'value': 2, 'name': 'instancehost'}, {'value': 5, 'name': 'conferenceroomname'}, {'value': 1, 'name': 'server'}, {'value': 1, 'name': 'printer'}]##data_value [2, 5, 1, 1]##data_name ['instancehost', 'conferenceroomname', 'server', 'printer']##login_count_online 2 ...

September 6, 2022 · 3 min · jiezi

关于flask:flask-layui-联动选择框-实例

图1图2图3 ormclass Select(db.Model): __tablename__ = 'select_project' # 表名 select_id = db.Column(db.Integer, primary_key=True, autoincrement=True) select_name = db.Column(db.String(200), nullable=False) # 分类名称、不能为空 roles = db.Column(db.Integer, nullable=False) # 级别、不能为空 def single_to_dict(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} def to_dict(self): model_dict = dict(self.__dict__) del model_dict['_sa_instance_state'] return model_dictclass Resources(db.Model): __tablename__ = 'resources' # 表名 id = db.Column(db.Integer, primary_key=True, autoincrement=True) resources_name = db.Column(db.String(200), nullable=False) # 分类名称、不能为空 parent_id = db.Column(db.Integer, nullable=False) # 父级id、不能为空。###obj转jsondef to_json_all(msg: list): import json data = {} if type(msg) == list: for i in range(len(msg)): temp_dict = {} j = 0 for k, v in msg[i].__dict__.items(): if j > 0: temp_dict[k] = v j += 1 data[i] = temp_dict else: temp_dict = {} j = 0 for k, v in msg.__dict__.items(): if j > 0: temp_dict[k] = v j += 1 data[0] = temp_dict return json.dumps(data, ensure_ascii=False) ...

August 25, 2022 · 2 min · jiezi

关于flask:flask-FlaskAPScheduler-配置计划任务

pip install Flask-APScheduler办法一:__init__.py from flask import Flask# 援用 APSchedulefrom flask_apscheduler import APScheduler# 援用 congfig 配置from config import Config, APSchedulerJobConfig app = Flask(__name__) # 定时工作,导入配置# APSchedulerJobConfig 就是在 config.py文件中的 类 名称。app.config.from_object(APSchedulerJobConfig) # 初始化Flask-APScheduler,定时工作scheduler = APScheduler()scheduler.init_app(app)scheduler.start()config.py#crontabclass APSchedulerJobConfig(object): SCHEDULER_API_ENABLED = True JOBS = [ { 'id': 'No1', # 工作惟一ID 'func': 'app.task_schedule:test_cron', # 执行工作的function名称,app.test 就是 app上面的`test.py` 文件,`shishi` 是办法名称。文件模块和办法之间用冒号":",而不是用英文的"." 'args': '', #如果function须要参数,就在这里增加 'trigger': { 'type': 'cron', # 类型 # 'day_of_week': "0-6", # 可定义具体哪几天要执行 # 'hour': '*', # 小时数 # 'minute': '1', 'second': '17' # "*/3" 示意每3秒执行一次,独自一个"3" 示意每分钟的3秒。当初就是每一分钟的第3秒时循环执行。 } } ]task_schedule.pyimport time, osdef test_cron(): print('Job 17 executed') task_time = time.strftime("%Y-%m-%d %H:%M:%S") ll = 'echo {test1} >> time_test.txt'.format(test1=task_time) os.system(ll) ...

August 23, 2022 · 2 min · jiezi

关于flask:Python-Flask构建微信小程序订餐系统

download:Python Flask构建微信小程序订餐零碎Golang | 模块引入与疾速实现表格的读写业务介绍在很多管理系统下都有不少让后端进行表格进行操作的业务需要,本期就带大家了解一下Golang中如何使用模块引入的,以及讲解怎么疾速的使用excelize库,对表格进行读写创建的。注释配置模块引入环境咱们在期望在vscode终端中也可能使用模块引入,它是 Go 1.11后新版模块治理形式。go env -w GO111MODULE=auto复制代码其 GO111MODULE 可能传送: auto:在其外层且根目录里有 go.mod 文件时,则开启模块反对,否者无模块反对。 on:开启模块反对。 off:无模块反对。 而后,初始化这个我的项目,就会生成一个 go.mod 文件。go mod init excel-demo复制代码 go.mod 是Go 1.11版本引入的官网的包管理工具(之前为 gopath 来治理),它可能理解为前端开发中的 npm 的作用,次要是为理解决没有记录依赖包具体版本查阅艰巨的问题,也极大程度上便利了依赖包的治理。 引入excelize库excelize 是一个用于读写 Microsoft Excel™2007 及更高版本生成的电子表格文档(XLAM / XLSM / XLSX / XLTM / XLTX)的 Go 语言库,而且更新保护频繁且非常好用。引入excelizego get github.com/xuri/excelize/v2复制代码这里因为站点是国外的所以常常会因无法访问而超时。此时,不要慌,咱们换一个国内的代理就好了。go env -w GOPROXY=https://goproxy.cn复制代码创建表格package main import ( "fmt""github.com/xuri/excelize/v2") func createExcel(){ // 创建表格文件f := excelize.NewFile()// 在Sheet1设置A1项的值f.SetCellValue("Sheet1", "A1", "这是Sheet1的A1项")// 创建新的Sheet,命名为Sheet2selectIndex := f.NewSheet("Sheet2")// 在Sheet2设置B2项的值f.SetCellValue("Sheet2", "B2", "这是Sheet2的B2项")// 切换到Sheet2f.SetActiveSheet(selectIndex)// 保存文件if err := f.SaveAs("test.xlsx"); err != nil { fmt.Println(err)}} ...

July 23, 2022 · 2 min · jiezi

关于flask:flask-在后台消费rocketmq

from flask import Flask, requestfrom flask_redis import FlaskRedisimport time, requests, jsonifyfrom rocketmq.client import PushConsumer, ConsumeStatusfrom celery import Celeryimport all_mqfrom concurrent.futures import ThreadPoolExecutor#executor = ThreadPoolExecutor(2)mq = all_mq.RocketMQ()class FlaskApp(Flask): def __init__(self, *args, **kwargs): super(FlaskApp, self).__init__(*args, **kwargs) self._activate_background_job() def _activate_background_job(self): def run_job(): mq.onMessage() t1 = threading.Thread(target=run_job) t1.start()app = FlaskApp(__name__)import json, timefrom rocketmq.client import PushConsumer, dll, ConsumeStatusimport tracebackimport loggingclass RocketMQ(): def __init__(self): logging.basicConfig(level=logging.CRITICAL, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') self.logger = logging.getLogger(__name__) self.consumer = PushConsumer("itxxxconsumer") self.consumer.set_name_server_address("rxx-xxx.com:9876") self.topic_name = "itxx" #缩小日志输入 dll.SetPushConsumerLogLevel("rexxx.com:9876".encode('utf-8'), 1) def callback(self,msg): sdp_status_json = msg.body sdp_status_json1 = sdp_status_json.decode('utf-8') print(sdp_status_json1, msg.id) return ConsumeStatus.CONSUME_SUCCESS def onMessage(self): self.consumer.subscribe(self.topic_name, self.callback) self.consumer.start() while True: time.sleep(2) self.consumer.shutdown() def my_func(test_body): print(test_body) ...

July 11, 2022 · 1 min · jiezi

关于flask:Flask中SQLAlchemy配置SQLite

在泛滥的数据库抉择中,除了mysql频繁被应用外,SQLite也是会被提到的。置信很多人对这种数据库还不是太相熟,所以本篇对配置SQLite的办法做了一个残缺的梳理。大家在装置好SQLAlchemy后,也能够同时实现跟SQLite数据库的连贯操作。上面咱们就具体的配置办法开展详解。 1、应用 pip 装置 Flask-SQLAlchemy: $ pip install flask-sqlalchemy2、接下来,咱们配置一个简略的 SQLite 数据库: $ cat app.py# -*- coding: utf-8 -*- from flask import Flaskfrom flask_sqlalchemy import SQLAlchemy app = Flask(__name__)app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db/users.db'app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Truedb = SQLAlchemy(app) class User(db.Model): """定义数据模型""" __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True) email = db.Column(db.String(120), unique=True) def __init__(self, username, email): self.username = username self.email = email def __repr__(self): return '<User %r>' % self.username3、这里有几点须要留神: ...

July 11, 2022 · 1 min · jiezi

关于flask:flask中模板引擎怎么用

在咱们对flask的一些引擎应用时,就不得不提到其中的一个默认引擎了。有些初学flask的人对Jinja2还没有应用过,所以不晓得该从何下手。本篇对于这种默认的引擎应用进行了整顿,有对flask模板引擎感兴趣的,能够跟着咱们一起来看看Jinja2的根底操作,具体的内容如下开展。 1、flask默认的模板引擎是Jinja2 目录构造: /application.py/templates /oscuser.html2、实例 application.py#coding=utf-8__author__ = 'duanpeng' import MySQLdbfrom flask import Flask,request,render_template,session, redirect, url_for, escapeapp = Flask(__name__,static_folder='static',static_url_path='/static') #定义首页@app.route('/')def hello_world(): user_agent = request.headers.get('User-Agent') return 'welcom! ,you browser is %s' % user_agent #定义404谬误页面@app.errorhandler(404)def not_found(error): return render_template('error.html'), 404 #定义动静页面@app.route('/user/<username>')def show_user_profile(username): # show the user profile for that user return 'User %s' % username #限度申请形式@app.route('/sayHello',methods=['post'])def sayHello(): return "hello,who are you?"#限度申请只能为get形式@app.route('/touch',methods=['get'])def touch(): return render_template('bank.html') #我的账号页面,与数据库交互,实现动静数据处理@app.route('/myaccount',methods=['get'])def mydata(): try: #加载驱动 连贯数据库 host ->ip port->端口 conn = MySQLdb.connect(host='192.168.1.124',user='root',passwd='abcdef',db='abcdef',port=3306,charset='gb2312') cursor = conn.cursor() cursor.execute("select * from osc_users t where t.login_name = 'rainbow07693'") result = cursor.fetchone() print(result[4]) cursor.close() conn.close() return render_template('oscuser.html',userinfo=result) except MySQLdb.Error,e: print e if __name__ == '__main__':app.run(debug=True)以上就是flask中模板引擎的应用,置信大家曾经对Jinja2的用法有了肯定的意识。平时在课后也能够找无关的材料进行深刻学习。 ...

July 11, 2022 · 1 min · jiezi

关于flask:flask中如何对数据库进行管理

在flask框架里,有许多数据库须要咱们频繁的解决,这样会造成很大的工作工作。咱们能够应用flask-migrate对数据库进行对立的治理,这样就省去了不少人工解决的工夫,上面咱们简略对flask-migrate办法进行理解,而后带来flask中对数据库进行治理的实例代码,具体内容如下。 1、flask-migrate办法 (1)flask-migrate提供了一个能够附加到flask-script的Manager类实例的ManagerCommand类。 (2)应用add_command()增加一个shell命令,并将db、app和user连贯到上下文中。 2、实例 在本文中,咱们应用了flask-script来治理数据库,另外,flask-migrate还反对flask-script的命令行界面,因而能够应用flask-script来对立治理。 #-*- coding:utf-8 -*-#filename: manage.pyfrom flask import Flaskfrom flask_sqlalchemy import SQLAlchemyfrom flask_script import Manager, Shellfrom flask_migrate import Migrate, MigrateCommand app = Flask(__name__)app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///lrh.db' db = SQLAlchemy(app)migrate = Migrate(app, db) manager = Manager(app)manager.add_command('db', MigrateCommand) class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(128)) def make_shell_context(): return dict(app=app, db=db, User=User) manager.add_command("shell", Shell(make_context=make_shell_context)) if __name__ == '__main__':manager.run()以上就是flask中对数据库进行治理的办法,能够发现通过整顿的数据库,在应用上更为方面,大家学会后也快点对数据库进行治理吧。

July 11, 2022 · 1 min · jiezi

关于flask:flask中Login的使用

Flask-Login,简略的来说,就是一个用户登录页面的设置,能够说通过这种办法,咱们能够对沉闷用户和不沉闷用户授予不同的权限。同时在会话的平安上有所保障。上面咱们就Login的一些应用益处进行介绍,而后带来具体的Login应用实例供大家学习,一起来看看具体的内容吧。 1、Login的益处 会话中积攒的沉闷用户能够轻松登录。 能够限度未登录的用户拜访页面。 解决记住我的性能。 爱护对话cookie不被小偷偷走。 轻松集成到Flask-Principal或其余许可扩大。 2、应用实例 将须要提供一个user_loader 回调。这个回调用于通过在会话中存储的ID来加载用户对象,它应该应用用户的 unicode ID ,并返回绝对应的用户对象。例如: @login_manager.user_loaderdef load_user(userid):return User.get(userid)它应该返回 None ( 不要抛出一个异样) 如果ID有效. (在这种状况下,ID应该手动的进行删除而后解决为持续运行。) 一旦用户认证通过,你能够通过函数 login_user 进行登入,例如: @app.route("/login", methods=["GET", "POST"])def login():form = LoginForm()if form.validate_on_submit():# login and validate the user...login_user(user)flash("Logged in successfully.")return redirect(request.args.get("next") or url_for("index"))return render_template("login.html", form=form)它是如此简略。 你能够通过 current_user 代理获取用户,这个代理在整个模板中都是无效的: {% if current_user.is_authenticated() %} Hi {{ current_user.name }}!{% endif %}页面如果须要用户登录才能够拜访能够应用 login_required 装璜器: @app.route("/settings")@login_requireddef settings():pass当用户须要登出时: @app.route("/logout")@login_requireddef logout():logout_user()return redirect(somewhere)它们行将登出,会话中的cookie将被全副革除。 以上就是flask中Login的应用,如果大家没有据说的Login,无妨先就它进行一些根底的理解,而后再来领会它的用途。

July 11, 2022 · 1 min · jiezi

关于flask:Flask中login如何定制登陆过程

一般来说,用户对于本人可能设置的页面,还是充斥极大的趣味。就拿Flask中login的登录过程来说,是能够依据本人的需要,做一些应用上的调整的。鉴于很多人会对定制的登录过程感兴趣,这里为大家进行了具体的流程梳理,想要同样实现这种个性化的定制,一起往下看看办法吧。 默认状况下,当用户登录到须要login_required身份验证的页面,但此时用户没有登录时,Flask-Login将闪动一条音讯,并将它们导航到登录视图(如果未设置登录视图,它将报告401谬误) 1、登录视图的名称能够应用登录管理器来设置,例如: login_manager.login_view = "users.login"2、默认的闪现音讯时请登陆后再查看该页面。 如果定制该信息,请应用, LoginManager.login_message: login_manager.login_message = u"Bonvolu ensaluti por uzi tio paĝo."3、定制信息的目录,请应用LoginManager.login_message_category : login_manager.login_message_category = "info"视图中有1个next选项指向您想查看的页面,当登入后,它会间接跳转到您要查看的页面。 4、如果您想进一步定制该流程,请应用函数 LoginManager.unauthorized_handler: @login_manager.unauthorized_handlerdef unauthorized():# do stuffreturn a_response以上就是Flask中login定制登陆过程的办法,基本上跟着流程走不会有太大的问题,看完后也赶快入手来试一试吧。

July 11, 2022 · 1 min · jiezi

关于flask:Flask中Jinja2是什么

在Flask框架里,有一种模板大家看起来十分眼生,那就是Jinja2。如果在django有接触过模板,那么对这Jinja2模板的上手就十分轻松了,在应用方面的学习也能够多多少少有所继承。上面咱们就Flask中Jinja2先进行介绍的阐明,理解其一些的有些后,正式进行应用的实例。 1、阐明 Jinja2是Flask作者开发的模板零碎。它最后是一个模拟django模板的模板引擎,为Flask提供模板反对。它因其灵活性、速度和安全性而失去广泛应用。 2、长处 绝对于Template,jinja2更加灵便,它提供了控制结构,表达式和继承等。 绝对于Mako,jinja2仅有控制结构,不容许在模板中编写太多的业务逻辑。 绝对于Django模板,jinja2性能更好。 Jinja2模板的可读性很棒。 3、实例 test.py # 1.导入依赖包from flask import Flask, render_template # 2.实例化app app = Flask(__name__) # 3.映射,默认状况下flask从templates文件夹中寻找模板文件(index.html) @app.route('/') def index(): return render_template('index.html') @app.route('/user/<param>') def user(name): return render_template('index.html', param=param)以上就是Flask中Jinja2的无关介绍,如果在之前没有接触过模板,能够先就相干的知识点进行了解,学会后运行代码局部就能够领会Jinja2的应用。

July 11, 2022 · 1 min · jiezi

关于flask:Flask-Web-极简教程四-Flask-WTF-Froms

一、表单表单在页面中次要负责数据采集,一个表单有三个根本组成部分: 表单标签:这外面蕴含了解决表单数据所用CGI程序的URL以及数据提交到服务器的办法。 表单域:蕴含了文本框明码框、暗藏域多行文本框、复选框单选框下拉抉择框和文件上传框等。表单按钮:包含提交按钮、复位按钮和个别按钮;用于将数据传送到服务器上的CGI脚本或者勾销输出,还能够用表单按钮来管制其余定义了解决脚本的解决工作 常见的表单有注册表单、登录表单、搜寻表单等视图函数中获取表单数据的形式有两种: GET申请提交的表单:request.args.get('name', None)POST申请提交的表单:request.from.get('age', None) 二、WTF表单WTF 表单是一个第三方的库,能够通过Python代码生成表单,而Flask-WTF则是Flask集成了WTF表单性能的实现。 Flask-WTF能够实现这些性能, 集成 wtforms。带有 csrf 令牌的平安表单。全局的 csrf 爱护。反对验证码(Recaptcha)。与 Flask-Uploads 一起反对文件上传。国际化集成。 更多信息能够查看 Flask-WTF 官网。Flask-WTF须要通过装置才能够应用。pip3 install Flask-WTF 在Pycharm中创立新的Flask我的项目flask-wtf,要应用Flask-WTF须要在app.py中创立Flask对象之后增加如下配置, 配置WTF的CSRF,Value能够是任意的字符串app.config['WTF_CSRF_SECRET_KEY'] = 'abc21231fafae2' 第一个表单模型在我的项目目录下新建一个form.py文件,专门用来编写表单模型,以登录表单为例,新增一个LoginForm对象,并减少相应的属性from flask_wtf import FlaskFormfrom wtforms import StringField, PasswordField, SubmitField class LoginForm(FlaskForm): username = StringField(label='用户名')password = PasswordField(label='明码')submit = SubmitField(label='提交')表单字段的罕用外围属性如下 属性名属性作用labelform表单中的label标签,如输入框前的文字描述default表单中输入框的默认值validators表单验证规定widget定制界面的显示方式description帮忙文字在app.py中减少视图函数from flask import Flask, render_templatefrom form import LoginForm @app.route('/form')def form(): login_form = LoginForm()# 返回login_form表单模型,在form.html中进行渲染return render_template('form.html', login_form=login_form)在templates减少form.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"/><title>Form</title></head><body><h3>登录</h3><form action=""> {# 渲染LoginForm表单模型中username字段的label属性#}{{ login_form.username.label }}:{{ login_form.username }} <br>{{ login_form.password.label }}:{{ login_form.password }} <br>{{ login_form.submit }}</form></body></html> ...

July 1, 2022 · 1 min · jiezi

关于flask:flask-配置ldap模块来校验AD用户登陆

参考flask_simpleldap模块的文档: https://github.com/alexferl/f... 从下面github下载代码,放到我的项目中 此处正文能够查看其他人的用户名明码 “”“谨慎不要外传”“”from flask import Flask, gfrom flask_simpleldap import LDAPapp = Flask(__name__)app.config["LDAP_HOST"] = "10.x1.1xx.11" # defaults to localhostapp.config["LDAP_BASE_DN"] = "OU=xxhu-sh,DC=xiaxxxhu,DC=sh"app.config["LDAP_USERNAME"] = "CN=ixxp,OU=Sxxxount,DC=xxxxxu,DC=sh"app.config["LDAP_PASSWORD"] = "uxxxP"ldap = LDAP(app)@app.route("/")@ldap.basic_auth_requireddef index(): return "Welcome, {0}!".format(g.ldap_username)if __name__ == "__main__": app.run(host="0.0.0.0", port=9000)

April 14, 2022 · 1 min · jiezi

关于flask:如何在Flask中集成Dash应用

背景Plotly Dash 是一个Python web利用框架,它能帮忙咱们疾速建设难看的,响应式的,可交互的,数据可视化页面。惋惜的是,一个只能展现数据的利用并不总是非常有用。如果咱们须要一个残缺的web利用,那就不得不想方法利用它的后端 Flask. 问题只管Dash借了Flask的壳,但这个Flask运行在sandbox里,而且和一般的Flask相比也少了很多性能。比方以下的性能要不没有要不须要降级到Dash enterprise版本。 数据库集成认证多页面、多路由反对自定义格调等等设计为了克服以上的所有问题,与其利用Dash自带的Flask,咱们齐全能够创立一个根底的Flask利用,而后将咱们的Dash利用置于其上。 准则灵便 -- 可能用Dash和Flask创立任意类型的利用。全功能 -- Dash和Flask都必须是全功能的,不能有性能上的限度。可定制 -- 可能自定义利用的款式。简洁 -- 可能简略快捷的创立一个Dash利用。解决方案总的来说,咱们能够有两种形式实现目标。 __子利用__: 创立一个根底的Flask利用,用这个Flask作为parent server初始化Dash利用,将Dash利用用自定义路由注册为子利用。__iframe__: 创立一个根底的Flask利用,将Dash利用放在一个iframe中,再用Flask去加载这些iframe.哪个更好和将Dash利用放在iframe中相比,只管这种形式看起来最简略,然而因为iframe齐全隔离的个性,反而会引入其余问题: __难于定制__: iframe不能通过css/js扭转iframe内的利用。__不统一__: iframe因为和main frame有着不同的路由零碎,点击一个iframe外部的链接并不会触发main frame的跳转。__无奈扩大__: iframe计划不反对多页面的Dash利用。基于以上理由,咱们认为应用子利用是一个更灵便且更普适的计划。 代码构造根底Flask的代码构造如下: ├── app│ ├── dash_apps -- 所有的Dash利用都在这个目录│ │ ├── custom_dash_app.py│ │ ├── experiment_detail_dash_app.py│ │ ├── experiment_list_dash_app.py│ │ └── __init__.py│ ├── __init__.py│ ├── static│ │ └── styles.css -- Custom CSS styles│ ├── templates│ │ ├── dash_layout.html -- Dash利用的layout│ │ ├── header.html -- Header│ │ ├── index.html -- 主页面│ │ └── layout.html -- 主页面的layout│ └── views.py -- Flask路由和主页面导航菜单定义├── config.py├── poetry.lock├── poetry.toml├── pyproject.toml├── README.md├── scripts│ ├── fix.sh│ ├── run.sh│ └── setup.sh├── setup.cfg└── wsgi.py残缺demo实现请参考github。 ...

March 11, 2022 · 2 min · jiezi

关于flask:无脑吹FastAPI性能碾压Flask关于网上不合适的性能对比以及让我糊涂的自测结果

不止一次的听过,有个FastAPI框架,性能碾压Flask,直追Golang,不过始终没有测试过,明天闲着没事测试一下看看后果。不晓得是哪里出了问题,后果大跌眼镜。 测试之前为了偷懒,天然想先从网上找找前人的测试代码以作为参照。百度前几名对于FastAPI和Flask性能测试又带了代码的有上面几个: FastAPI、Flask、Golang性能测试Flask、Django、Tornado、FastAPI 之 Python Web 并发测试flask,tornado,fastapi 压测比拟(web框架)有点纳闷简略看了一下,没明确,为什么都用了unicorn启动FastAPI,却只用Flask自带的启动形式,为什么不必其余WSGI服务器? 我感觉这样应该是有问题的,且不说原本二者都不是同一档次的框架(FastAPI是基于Starlette的,这才是应该和Flask比照的框架),就算比照,也应该用差不多的启动形式吧? unicorn是个第三方ASGI服务器,Flask应该用一个第三方WSGI服务器来启动才失常吧?感觉用它自带的WSGI服务器比可能不太偏心。 我原本想用gunicorn来启动Flask进行比照的,后果发现不兼容Windows,所以换了个waitress,差不多的WSGI框架。 开始测试网上都是用AB测试的,我电脑没装Apache,就用了另一个测试工具siege。测试形式为不限连接数,测试10秒,命令如下: ./siege.exe -b -t10s http://127.0.0.1:5000/测试代码和之前搜到的一样,用二者官网的例子,输入HelloWorld,略作批改,把启动代码写进文件内,就不必应用命令行启动了。 Flask from flask import Flaskfrom waitress import serveapp = Flask(__name__)@app.route('/')def index(): return {'message': 'hello world'}if __name__ == '__main__': app.run(host='0.0.0.0') # serve(app, host='0.0.0.0', port=5000)FastAPI from fastapi import FastAPIimport uvicornapp = FastAPI()@app.get("/")async def read_root(): return {"Hello": "World"}if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=5000)测试后果鉴于网上的文章在那摆着,所以我也测试了一下应用Flask自带启动形式的后果。 除此之外,还测试了FastAPI应用异步的后果(就加了个async,理论应该什么没用的,文档中明确说了,只有函数外部应用了异步函数且须要同步返回时,也就是须要在外部用await时,才须要定义async)。 后果如下: flask Transactions: 4579 hitsAvailability: 100.00 %Elapsed time: 9.15 secsData transferred: 0.11 MBResponse time: 0.03 secsTransaction rate: 500.66 trans/secThroughput: 0.01 MB/secConcurrency: 14.93Successful transactions: 4579Failed transactions: 0Longest transaction: 0.10Shortest transaction: 0.02flask + waitress ...

December 5, 2021 · 2 min · jiezi

关于flask:如何编写一个-Python-Web-应用-一Flask

对于 Flask 最外围的有三点: Application Context: 将整个利用连成一体.View Function & CLI Command: 利用暴漏给外界的操作接口Blueprints (蓝图): 实现模块化开发当然还有其余细节,但有了这三点,就能够编写一个残缺的 Web 援用了 Application Context参考: Flask 2.0.x 我更习惯应用 工厂模式 创立 Context: 在 __init__.py 中写 create_app(): from flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()def create_app(test_config=None): # create and configure the app app = Flask(__name__, instance_relative_config=True) configure(app, test_config) db.init_app(app) from faq.blueprints import review app.register_blueprint(review.bp) return app当在命令行输出命令 flask run 时, flask 将主动寻找 create_app 函数来执行, 以创立 Application Context. PS: flask run 便是一个默认的 CLI Command ...

August 19, 2021 · 2 min · jiezi

关于flask:Flask快速入门后台写接口API

Flask是一个应用Python编写的轻量级Web利用框架。 其WSGI工具箱采纳Werkzeug,模板引擎则应用Jinja2。Flask应用BSD受权。Flask也被称为“microframework”,因为它应用简略的外围,用extension减少其余性能。首先介绍Flask装置:进入创立的虚拟环境,在虚拟环境中应用pip install xxx 进行flask装置。应用flask进行输入hello world!输入后果中127.0.0.1代表本地,本人电脑中运行。5000代表端口号,点击链接输入hello world!。批改路由:绑定路由为/index!凋谢其余主机对其进行拜访,增加host=“0.0.0.0” 绑定路由,<>中内容默认格局为字符串类型,可输出英文、中文、数字等,但输入类型也为字符串类型,设置变量为username,应用%s占位输入hey flask。 批改定义变量类型为int(还可批改为float、path、uuid等类型)。批改为int类型后可进行变量的运算。Flask重定向,应用redirect()函数能够重定向,拜访对应路由即可进入想转入的网站,如下图拜访路由:/易华录开发者社区即转入开发者社区官网。 装置Postman,Postman 是一种罕用的接口测试工具,能够发送简直所有类型的HTTP申请。下图为其主页面。这里能够抉择申请类型,如下图展现其中局部:四种根本申请:1、GET申请会向数据库发索取数据的申请,从而来获取信息,该申请就像数据库的select操作一样,只是用来查问一下数据,不会批改、减少数据,不会影响资源的内容,即该申请不会产生副作用。无论进行多少次操作,后果都是一样的。查看:GET /url/xxx 2、与GET不同的是,PUT申请是向服务器端发送数据的,从而扭转信息,该申请就像数据库的update操作一样,用来批改数据的内容,然而不会减少数据的品种等,也就是说无论进行多少次PUT操作,其后果并没有不同。更新:PUT /url/xxx 3、POST申请同PUT申请相似,都是向服务器端发送数据的,然而该申请会扭转数据的品种等资源,就像数据库的insert操作一样,会创立新的内容。简直目前所有的提交操作都是用POST申请的。创立:POST /url 4、DELETE申请顾名思义,就是用来删除某一个资源的,该申请就像数据库的delete操作。删除:DELETE /url/xxx应用GET申请,输出想要拜访的路由地址,send进行拜访,例如咱们拜访易华录开发者社区官网:POST申请: 输入后果:若想返回后果为json格局,咱们要导入jsonify可实现返回格局为json,如下图:当输入报错时,咱们能够将报错起因进行输入,如下图将age参数删除,咱们可失去“短少参数”的反馈。当咱们将age类型写为str类型,无奈进行运算,咱们将失去“出错”反馈。session模仿简略登录、退出登录、查看登陆状态。登录:首先导入session包,设置session密钥在此设置username和password固定值,当输出username和password值均正确时,反馈登陆胜利。当账号或明码谬误时,反馈“账号或明码谬误”。查看登录状态: 退出登录:当咱们登录实现后,查看登陆状态,反馈失去username。当咱们退出登陆后,再查看一遍登陆状态。 到此,咱们就简略的模仿了账户的登录、查看登录状!

August 18, 2021 · 1 min · jiezi

关于flask:flask关于跨域的问题

拜访后端域名提醒:The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:8080, *', but only one is allowed. response header能够看到Access-Control-Allow-Origin有俩个,这是因为web框架中也设置了跨域返回导致的,把flask_cors插件去掉就是了

July 20, 2021 · 1 min · jiezi

关于flask:FlaskLimiter详细使用说明

本文首发于:行者AI速率限度通常作为服务的进攻措施予以施行。服务须要爱护本身免得适度应用(无论是无意还是无心),从而放弃服务可用性。在Flask我的项目开发过程中,遇到了须要对接口进行限度的需要,又不想去造轮子,这时候就须要用到Flask-Limiter这个三方库。本文将对Flask-Limiter的应用进行具体阐明。 1. 装置装置依赖环境。 pip install Flask==1.1.1 Flask-Limiter==1.42. 疾速开始有两种形式示意速率限度: "100 per day"、"20 per hour"、"5 per minute"、"1 per second""100/day"、"20/hour"、"5/minute"、"1/second"速率限度能够设置全局配置,针对所有接口进行限度;也能够通过装璜器进行部分限度;对于不想限度的接口,能够通过装璜器@limiter.exempt进行解除限制。示例代码如下所示: app = Flask(__name__)# 该配置为全局配置、实用于所有接口limiter = Limiter(app, key_func=get_remote_address, default_limits=["100 per day", "10/hour"])# @limiter.limit: 将笼罩全局limiter配置@app.route("/slow")@limiter.limit("1 per day")def slow(): return ":("# override_defaults: 示意该limiter是否笼罩全局limiter限度,默认为True@app.route("/medium")@limiter.limit("1/second", override_defaults=False)def medium(): return ":|"# 残缺继承全局limiter配置@app.route("/fast")def fast(): return ":)"# @limiter.exempt: 被装璜的视图不受全局速率限度@app.route("/ping")@limiter.exemptdef ping(): return "PONG"3. 装璜器依据集体爱好和应用场景,有以下几种形式:繁多润饰:限度字符串能够是单个限度,也能够是定界符分隔的字符串。 @app.route("....")@limiter.limit("100/day;10/hour;1/minute")def my_route() ...多个装璜器:限度字符串能够是单个限度,也能够是定界符分隔的字符串,也能够是两者的组合。 @app.route("....")@limiter.limit("100/day")@limiter.limit("10/hour")@limiter.limit("1/minute")def my_route(): ...自定义性能:默认状况下,依据Limiter实例初始化时所应用的要害性能来利用速率限度。开发者能够实现本人的性能。 def my_key_func(): ...@app.route("...")@limiter.limit("100/day", my_key_func)def my_route(): ...动静加载限度的字符串:在某些状况下,须要从代码内部的源(数据库,近程api等)中检索速率限度。这能够通过向装璜器提供可调用对象来实现。 留神:所装璜的路由上每个申请都会调用提供的可调用对象,对于低廉的检索,请思考缓存响应。 def rate_limit_from_config(): return current_app.config.get("CUSTOM_LIMIT", "10/s")@app.route("...")@limiter.limit(rate_limit_from_config)def my_route(): ...4. 限度域指依据什么进行限度,对应的参数为key_func,flask_limiter.util提供了两种形式: ...

June 15, 2021 · 1 min · jiezi

关于flask:flask-web-实现-harbor-早期版本的镜像-tag-批量-可视化-删除

性能展现 代码展现app.py#! /usr/bin/env python3# -*- coding:utf-8 -*-from flask import Flask, request, render_template, redirect, url_for, make_response ,escape, session, flash, g, current_app, abort, jsonify, send_fileimport requests#from werkzeug.utils import secure_filenameimport os,time, platformfrom flask_wtf import Form#from wtforms import TextField#from shell import os_scrpict, harbor#import sqlite3#from pathlib import Pathfrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)# 本脚本只是harbor页面tags清理# cd /usr/local/src/harbor && docker-compose stop #敞开harbor# 物理清理:docker run -it --name gc --rm --volumes-from registry goharbor/registry-photon:v2.6.2-v1.6.3 garbage-collect /etc/registry/config.yml#docker-compose up -d #启动class RequestClient(object): def __init__(self, login_url, username, password): self.username = username self.password = password self.login_url = login_url self.session = requests.Session() self.login() def login(self): requests.packages.urllib3.disable_warnings() self.session.post(self.login_url, params={"principal": self.username, "password": self.password}, verify=False)class ClearHarbor(object): def __init__(self, harbor_domain, username, password, num, schema="http", ): self.num = num self.schema = schema self.harbor_domain = harbor_domain self.harbor_url = self.schema + "://" + self.harbor_domain self.login_url = self.harbor_url + "/login" self.api_url = self.harbor_url + "/api" self.pro_url = self.api_url + "/projects" self.repos_url = self.api_url + "/repositories" self.username = username self.password = password self.client = RequestClient(self.login_url, self.username, self.password) def __fetch_pros_obj(self): # 获取所有项目名称 self.pros_obj = self.client.session.get(self.pro_url).json() # for n in self.pros_obj: # print("分组:",n.get("name")) print(self.pros_obj) return self.pros_obj def get_pros_id(self): # 获取所有我的项目ID self.pros_id = [] pro_res = self.__fetch_pros_obj() for i in pro_res: self.pros_id.append(i['project_id']) # print("所有我的项目ID:",self.pros_id) return self.pros_id def get_del_repos_name(self, pro_id): # 镜像tag数量大于self.num的镜像仓库名称 self.del_repos_name = [] repos_res = self.client.session.get(self.repos_url, params={"project_id": pro_id}) # print("我的项目信息:",repos_res.json()) for repo in repos_res.json(): if repo["tags_count"] > self.num: # print("镜像仓库名称:",repo['name']) self.del_repos_name.append(repo['name']) return self.del_repos_name def fetch_del_repos(self, repo_name): # 删除镜像仓库tag self.del_res = [] tag_url = self.repos_url + "/" + repo_name + "/tags" # 我的项目镜像仓库的所有tags,按创立工夫排序 tags = self.client.session.get(tag_url).json() tags_sort = sorted(tags, key=lambda a: a["created"]) # print(len(tags_sort),tags_sort) # 除了最新的self.num个,其余的tag都增加到待删除列表del_tags del_tags = tags_sort[0:len(tags_sort) - self.num] # print(del_tags) for tag in del_tags: del_repo_tag_url = tag_url + "/" + tag['name'] # print(del_repo_tag_url) del_res = self.client.session.delete(del_repo_tag_url) self.del_res.append("镜像: %s 删除状态: %s" % (del_repo_tag_url,del_res)) return self.del_res def work(self): # 遍历project id for i in self.get_pros_id(): # 获取所有tag超过self.num的repos repos = self.get_del_repos_name(i) if repos: for repo in repos: del_repos = self.fetch_del_repos(repo) print(del_repos) print(repo) def list_repositories(self, pro_id): # 镜像tag数量大于self.num的镜像仓库名称 self.del_repos_name1 = [] repos_res = self.client.session.get(self.repos_url, params={"project_id": pro_id}) # print("我的项目信息:",repos_res.json()) for repo in repos_res.json(): #if repo["tags_count"] > self.num: #print(repo,"镜像仓库名称:",repo['name']) #self.del_repos_name1.append({"name": repo["name"], "tags_count": repo["tags_count"]}) self.del_repos_name1.append({"name": repo["name"], "tags_count": repo["tags_count"],"project_id": repo["project_id"]}) #print(self.del_repos_name1) return self.del_repos_name1 def list_pros(self): # 获取所有我的项目ID self.pros_idname = [] pro_res = self.__fetch_pros_obj() for i in pro_res: self.pros_idname.append({"project_id": i["project_id"], "name": i["name"], "repo_count": i["repo_count"] }) #print(self.pros_idname) #print(self.pros_idname[0]['name']) return self.pros_idname def work_list(self, pros_id_list): # 遍历project id for i in pros_id_list: # 获取所有tag超过self.num的repos repos = self.get_del_repos_name(i) if repos: for repo in repos: # del_repos = self.fetch_del_repos(repo) # print(del_repos) print(repo)@app.route('/harbor/', methods = ['GET', 'POST'])def harbor_list(): del_images_tag = ClearHarbor(harbor_domain="172.16.167.11", username="admin", password="Harbor12345", num=10) harbor_project = del_images_tag.list_pros() print("###########",harbor_project) return render_template('harbor.html', harbor_project1 = harbor_project)@app.route('/del_harbor_project_all/')def del_harbor_project_all1(): del_images_tag = ClearHarbor(harbor_domain="172.16.167.11", username="admin", password="Harbor12345", num=10) del_images_tag.work() return redirect('/harbor/')@app.route('/del_harbor_project/<int:project_id>')def del_harbor_project(project_id): del_images_tag = ClearHarbor(harbor_domain="172.16.167.11", username="admin", password="Harbor12345", num=10) del_reposname = del_images_tag.get_del_repos_name(project_id) print(del_reposname,"#####################",project_id) del_reposname_status = del_images_tag.fetch_del_repos(del_reposname) print(del_reposname_status) return redirect('/harbor/')@app.route('/del_harbor_project_list/', methods = ['GET', 'POST'])def del_harbor_project_list(): if request.method == 'POST': project_id_list = request.form.getlist('vals') #print(project_id_list) vals_list2 = [] for i in project_id_list: if i == '': print("i is none") else: vals_list2.append(i) del_images_tag = ClearHarbor(harbor_domain="172.16.167.11", username="admin", password="Harbor12345", num=10) del_images_tag.work_list(vals_list2) return redirect('/harbor/')@app.route('/harbor_repositories/<int:project_id>')def harbor_repositoriest(project_id): del_images_tag = ClearHarbor(harbor_domain="192.168.1.17", username="admin", password="xxxxxx", num=10) harbor_project = del_images_tag.list_repositories(project_id) print("###########",harbor_project) return render_template('harbor_repositories.html', harbor_project1 = harbor_project)@app.route('/del_repositories_url/<int:project_id>/<path:project_name>')def del_repositories_url(project_id,project_name): del_images_tag = ClearHarbor(harbor_domain="192.168.1.17", username="admin", password="xxxxxx", num=10) del_reposname_status = del_images_tag.fetch_del_repos(project_name) print(del_reposname_status) return redirect('/harbor_repositories/{tproject_id}'.format(tproject_id=project_id))if __name__ == '__main__': app.run() #app.run(host="172.16.117.33",port=5888)harbor.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body> <script src="/static/jquery-1.9.1.min.js"></script> <script> $('#out').click(function () { $.ajax({ url: '/dns_list/', type: 'GET', data: {}, success: function (data) { //期待解决完结主动调用,data-返回值 console.log(data); window.location.href = "url" } }) }); $('#back').click(function () { $.ajax({ url: '/dns_list/', type: 'GET', data: {}, success: function (data) { window.location.href = "/" } }) });function getValues_del_project() { var valArr=[]; var ones=document.getElementsByName('item'); for (var i=0;i<ones.length;i++){ if (ones[i].checked==true){ valArr[i]=ones[i].value } } if (valArr.length!=0){ // var vals = valArr.join(','); // alert(valArr); // var url2 = '/app_intstall/' $.ajax({ url:"/del_harbor_project_list/", type:'POST', contenType:'application/json',// #不加这个,ajax会将后果后边加个[],例如{'vals[]':[4,6,8]} traditional:true,// #不加这个,会报服务器终止了一个在运行的程序 async: false, data:{ 'vals':valArr }, success:function(){ alert("批量革除project 超过10个 tag 胜利"); // window.location.href =url2 }, error:function(){ alert("批量革除project 超过10个 tag 失败"); } }) } else { var error_m="请抉择数据"; alert(error_m); }} </script> <div class="container" style="position: absolute;left: -2%;width: 1590px;height: 850px;margin-left: 95px;"> <h2>harbor</h2> <div style="float: left; width:190px;height:70px"> <ul class='myul'> <li><a class="btn btn-success input-sm" href="/harbor/" onclick="getValues_del_project()"> 批量革除选中我的项目images </a> </li> </ul> </div> <div style="float: left; width:290px;height:40px;display:block;overflow-y:auto;"> <ul class='myul'> <li> <a class="btn btn-primary input-sm" href="/del_harbor_project_all/"> 革除all project >10 tag images </a> </li> </ul> </div> {# <div style="position: absolute;top: 12%;left: 65%;width: 590px;height: 650px;">#} {# <p>text: <textarea rows="41" cols="80" name="conftext"> {{ conflook.conflook }} </textarea></p>#} {# </div>#} <table class="table table-condensed" id="table1"> <thead> <tr> <th width="25"><input id="checkAll" onclick="checkAll()" type="checkbox"></th> <th>ID</th> <th>我的项目</th> <th>镜像仓库数</th> <th>操作</th> </tr> </thead> <tbody> {% for ele in harbor_project1 %} <tr> <td><input name="item" onclick="checkOne()" type="checkbox" value="{{ele.project_id}}"></td> <td>{{ ele.project_id }}</td> <td><a href="/harbor_repositories/{{ ele.project_id }}"> {{ ele.name }}</a></td> <td>{{ ele.repo_count }} </td> <td><a href="/del_harbor_project/{{ ele.project_id }}"> 革除 </a></td> </tr> {% endfor %} </tbody> </table> </div></body></html>harbor_repositories.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body> <script src="/static/jquery-1.9.1.min.js"></script> <script> $('#out').click(function () { $.ajax({ url: '/dns_list/', type: 'GET', data: {}, success: function (data) { //期待解决完结主动调用,data-返回值 console.log(data); window.location.href = "url" } }) }); $('#back').click(function () { $.ajax({ url: '/dns_list/', type: 'GET', data: {}, success: function (data) { window.location.href = "/" } }) });function getValues_del_repositories() { var valArr=[]; var ones=document.getElementsByName('item'); for (var i=0;i<ones.length;i++){ if (ones[i].checked==true){ valArr[i]=ones[i].value } } if (valArr.length!=0){ // var vals = valArr.join(','); // alert(valArr); // var url2 = '/app_intstall/' $.ajax({ url:"/del_harbor_repositories_list/", type:'POST', contenType:'application/json',// #不加这个,ajax会将后果后边加个[],例如{'vals[]':[4,6,8]} traditional:true,// #不加这个,会报服务器终止了一个在运行的程序 async: false, data:{ 'vals':valArr }, success:function(){ alert("批量革除image 超过10个 tag 胜利"); // window.location.href =url2 }, error:function(){ alert("批量革除image 超过10个 tag 失败"); } }) } else { var error_m="请抉择数据"; alert(error_m); }} </script> <div class="container" style="position: absolute;left: -2%;width: 1590px;height: 850px;margin-left: 95px;display:block;overflow-y:auto;"> <h2>harbor</h2> {# <div style="position: absolute;top: 12%;left: 65%;width: 590px;height: 650px;">#} {# <p>text: <textarea rows="41" cols="80" name="conftext"> {{ conflook.conflook }} </textarea></p>#} {# </div>#} <table class="table table-condensed" id="table1" > <thead> <tr> <th width="25"><input id="checkAll" onclick="checkAll()" type="checkbox"></th> <th>我的项目</th> <th>标签数</th> <th>操作</th> </tr> </thead> <tbody> {% for ele in harbor_project1 %} <tr> <td><input name="item" onclick="checkOne()" type="checkbox" value="{{ele.name}}"></td> <td> {{ ele.name }}</td> <td>{{ ele.tags_count }} </td> <td><a href="/del_repositories_url/{{ ele.project_id }}/{{ ele.name }}"> 革除 </a></td> </tr> {% endfor %} </tbody> </table> </div></body></html>

April 27, 2021 · 5 min · jiezi

关于flask:flask-web-实现文件上传-下载-浏览-编辑

性能展现 代码展现app.ypfrom flask import Flask, request, render_template, redirect, url_for, make_response ,escape, session, flash, g, current_app, abort, jsonify, send_fileimport requestsfrom werkzeug.utils import secure_filenameimport os,time, platformfrom flask_wtf import Formfrom wtforms import TextFieldfrom shell import os_scrpict, harborimport sqlite3from pathlib import Pathfrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)#app.pyapp.secret_key = 'development key'app.config['UPLOAD_FOLDER'] = 'upload/'filepath = Path('upload/')@app.route('/upload')def upload_file(): return render_template('dir_view.html')@app.route('/uploader', methods = ['GET', 'POST'])@app.route('/uploader/<path:filepath>/', methods = ['GET', 'POST'])def uploader(filepath=' '): if request.method == 'POST': repath = 'upload/' + filepath + '/' print(repath) f = request.files['file'] #f.save(os.path.join(app.config['UPLOAD_FOLDER'],secure_filename(f.filename))) f.save(os.path.join(repath, secure_filename(f.filename))) #return 'file uploaded successfully' return redirect('/scan/{tfilepath}'.format(tfilepath = filepath))@app.route('/download/<path:fullname>', )def download_file(fullname): #current_app.logger.debug(fullname) print(fullname) return send_file(fullname)@app.route('/look/<path:fullname>', )def look_file(fullname): f = open(fullname, encoding='UTF-8') resp = make_response(f.read()) #resp = make_response(open(fullname, encoding='UTF-8').read()) resp.headers["Content-type"] = "application/json;charset=UTF-8" # f = open(fullname, "r+",encoding='UTF-8',newline="\n") # str = f.read() # fullname1 = str(fullname).replace('\\', '/') # with open(fullname, "r+",encoding='UTF-8',newline="\n") as f: # 默认模式为‘r',只读模式 # contents = f.read() f.close() return resp@app.route('/scan/')@app.route('/scan/<path:ortherpath>/', )def index(ortherpath=''): file_ele_list = list() dir_ele_list = list() #for f in (Path('upload/'+ ortherpath).iterdir()): for f in (Path('upload') / Path(ortherpath)).iterdir(): #for f in os_scrpict.allfile('upload/'): if f.is_file(): fullname = str(f).replace('\\', '/') file_ele_list.append({'is_dir': 0, 'filesize': os.path.getsize(f) / 1000, 'last_modify_time': time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getmtime(fullname))), 'look_url': url_for('look_file', fullname=fullname), 'download_url': url_for('download_file', fullname = fullname), 'fullname': fullname}) if f.is_dir(): fullname = str(f).replace('\\', '/') dirname = fullname.split('/') dir_ele_list.append({'is_dir': 1, 'filesize': 0, 'last_modify_time': time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(os.path.getmtime(fullname))), 'look_url': url_for('look_file', fullname = dirname[1]), 'download_url': url_for('index', ortherpath = dirname[1]), 'fullname': fullname}) #print(dirname[1],"########",dir_ele_list) #print(fullname,"fullname####",fullname[1:]) print(dir_ele_list + file_ele_list) repath = request.path.split('scan') print(request.path,"#############request.path##########",repath[1]) if 'look' in request.path: look_file1 = request.path.split('look/') return render_template('dir_view.html', ele_list=dir_ele_list + file_ele_list,path1 = {"path1": repath[1]},conflook = {"conflook": look_file(look_file1[1])}) else: return render_template('dir_view.html', ele_list=dir_ele_list + file_ele_list, path1={"path1": repath[1]}, conflook = {"conflook": "hello kugou"})@app.route('/get_configfile/<path:configname>', )def get_configfile(configname): print(configname) f = open(configname, "r+",encoding='UTF-8',newline="\n") str = f.read() f.close() return render_template('get_configfile.html', conflook = {"conflook": str,"configname": configname})@app.route('/edit_configfile/<path:configname>/', methods = ['GET', 'POST'])def edit_configfile(configname): if request.method == 'POST': fileconfig = request.form.get('conftext') print(configname) print(fileconfig) f = open(configname, 'w', encoding='utf8') # 写入文件内容 f.write(fileconfig) f.close() return redirect('/scan/')if __name__ == '__main__': app.run() #app.run(host="172.16.117.33",port=5888)dir_view.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><div class="container" style="position: absolute;left: -2%;width: 1590px;height: 850px;margin-left: 95px;display:block;overflow-y:auto;"> <div style="width: 590px;height: 80px;"> <form action="http://localhost:5000/uploader{{ path1.path1 }}" method="POST" enctype="multipart/form-data"> <input type="file" name="file" accept="*.*"/> <input type="submit"/> </form> </div> <div style="float: left; width:938px;height:70px"><a> dir_path : {{ path1.path1 }} </a></div> <table class="table table-condensed" id="table1"> <thead> <tr> <th>名称</th> <th>最初批改工夫</th> <th>文件大小</th> <th>下载文件</th> <th>操作</th> </tr> </thead> <tbody> {% for ele in ele_list %} {% if ele.is_dir %} <tr class="warning"> <td><a> 目录 {{ ele.fullname }}</a></td> <td>{{ ele.last_modify_time }}</td> <td>{{ ele.filesize }} M</td> <td><a href="{{ ele.download_url }}"> 进入 {{ ele.fullname }}</a></td> <td><a href="{{ ele.download_url }}"> 关上目录 </a></td> {% else %} <tr class="success"> <td><a href="{{ ele.look_url }}"> {{ ele.fullname }}</a></td> <td>{{ ele.last_modify_time }}</td> <td>{{ ele.filesize }} Kb</td> <td><a href="{{ ele.download_url }}" download={{ ele.fullname }}>下载 {{ ele.fullname }}</a></td> <td> <a href="/get_configfile/{{ ele.fullname }}"><font size="2" face="arial" color="blue"> <i class="fa fa-pencil" aria-hidden="true"></i> 编辑</font></a> </td> {% endif %} {# <td><a href="{{ ele.look_url }}" >预览 {{ ele.fullname }}</a></td>#} {# <td>{{ ele.last_modify_time }}</td>#} {# <td>{{ ele.filesize }} M</td>#} {# <td><a href="{{ ele.download_url }}" download={{ ele.fullname }}>下载 {{ ele.fullname }}</a></td>#} </tr> {% endfor %} </tbody> </table></div></body></html>get_configfile.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body> <h2>Nginx_config</h2> <form method="POST" action="/edit_configfile/{{ conflook.configname }}"> <div style="position: absolute;left: 4%;width: 690px;height: 850px;"> <p> nginx_config-text: <textarea rows="35" cols="120" name="conftext"> {{ conflook.conflook }} </textarea> </p> </div> <div style="position: absolute;top: 800px;left: 77%;width: 150px;height: 150px;" "> <input type="submit" value="提交"/> <a href="/scan/"><font size="2" face="arial" color="blue" class="btn btn-default"> <i class="fa fa-pencil" aria-hidden="true"></i> home </font></a> </div> </form></body></html>款式 模板 写的有点糙,就不给你们了

April 27, 2021 · 3 min · jiezi

关于flask:Flutter-文本框初始化是显示默认值

刚开始做Flutter文本框时候,应用的是TextField。仿佛大多数状况下都没有问题。代码模式如下: class _FooState extends State<Foo> { TextEditingController _controller; @override void initState() { super.initState(); _controller = new TextEditingController(text: '初始化内容'); } @override Widget build(BuildContext context) { return new Column( children: <Widget>[ new TextField( // 当TextField 第一次创立时,controller会蕴含初始值, // 当用户批改文本框内容时,会批改controller的值。 controller: _controller, ), new RaisedButton( onPressed: () { // 通过clear()能够清空controller的值。 _controller.clear(); }, child: new Text('清空'), ), ], ); }}问题1:动态创建文本框初始值个别状况下,间接应用这种形式,没有任何问题。然而当初有一种状况: 问题1: 当页面文本框中的初始值是动静的,从后盾获取到的时候,应该怎么办呢?这种状况下,阐明创立TextEditingController时,并不知道文本内容。这个时候如果动静批改controller的话,会报错,基本没法应用。 这种状况我基本没遇到过,然而我感觉Flutter必定有解决办法。所以我去找了一下Flutter的文档,总算是没有白找,找到了一个(https://api.flutter.dev/flutt...[组件] TextFormField。 文档中有一句: If a controller is not specified, initialValue can be used to give the automatically generated controller an initial value.意思就是说,当不指定controller时,initialValue 就能够主动生成controller的初始值。 ...

March 15, 2021 · 1 min · jiezi

关于flask:一寸宕机一寸血十万容器十万兵Win10Mac系统下基于k8s搭建GunicornFlask高可用Web集群

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_185 2021年,君不言容器技术则已,欲言容器则必称Docker,毫无疑问,它是当今最风行的容器技术之一,然而当咱们面对海量的镜像与容器时,怎么疾速精准的对海量容器进行治理和编排就又成了新的课题,此时,由Google开源的Kubernetes(读音[kub'netis],业界也有称其k8s的,但k8s其实就是文盲版的Kubernetes,只是因为k和s之间有8个字母)就应时而生了,它是一个开源的用于多个主机虚构成一个云平台后进行容器资源管理和利用编排引擎,致力于让部署容器化利用简略并且高效,提供了利用的全生命周期治理,如利用部署,布局,更新,保护等机制。本次咱们尝试在Win10/Mac零碎下,利用Kubernetes部署Gunicorn+Flask高可用Web集群我的项目。 首先,Kubernetes基于Docker-desktop,所以下载Docker-desktop安装包:https://www.docker.com/produc... 这里咱们应用的就是Docker官网最新版3.1.0,外部集成的Kubernetes版本是1.19.3,在装置之前有两点要阐明下,如果是Windows用户,须要确保零碎版本为专业版: 第二,在专业版的根底上,开启零碎的Hyper-v虚拟化性能: 所以Windows用户想要对Kubernetes一亲芳泽的话,同时确保下面两点即可,而Mac用户则无特殊要求。 双击安装包进行装置,默认装置在C盘目录,胜利后,启动Docker-desktop,个别状况下,Docker很容易启动胜利,然而Kubernetes往往会卡在启动界面,这是因为一些学术问题导致无奈下载Kubernetes的依赖镜像,此时咱们须要另辟蹊径,采纳一些开源的三方库曲折帮咱们下载这些镜像,这里举荐这个开源我的项目:https://github.com/AliyunCont... 输出命令拉取我的项目: git clone https://github.com/AliyunContainerService/k8s-for-docker-desktop.git进入我的项目的目录内,而后查看本人的Kubernetes版本号,该我的项目默认拉取的就是1.19.3的依赖镜像,如果您装置的Kubernetes是老版本,须要自行切换版本进行拉取操作: 如Kubernetes版本为 v1.18.8, 请应用上面命令切换 v1.18.8 分支 git checkout v1.18.8 如Kubernetes版本为 v1.18.6, 请应用上面命令切换 v1.18.6 分支 git checkout v1.18.6 如Kubernetes版本为 v1.18.3, 请应用上面命令切换 v1.18.3 分支 git checkout v1.18.3 如Kubernetes版本为 v1.16.5, 请应用上面命令切换 v1.16.5 分支 git checkout v1.16.5 如Kubernetes版本为 v1.15.5, 请应用上面命令切换 v1.15.5 分支 git checkout v1.15.5 如Kubernetes版本为 v1.15.4, 请应用上面命令切换 v1.15.4 分支 git checkout v1.15.4 如Kubernetes版本为 v1.14.8, 请应用上面命令切换 v1.14.8 分支 git checkout v1.14.8 如Kubernetes版本为 v1.14.7, 请应用上面命令切换 v1.14.7 分支 git checkout v1.14.7 如Kubernetes版本为 v1.14.6, 请应用上面命令切换 v1.14.6 分支 git checkout v1.14.6 如Kubernetes版本为 v1.14.3, 请应用上面命令切换 v1.14.3 分支 git checkout v1.14.3 如Kubernetes版本为 v1.14.1, 请应用上面命令切换 v1.14.1 分支 git checkout v1.14.1 如Kubernetes版本为 v1.13.0, 请应用上面命令切换 v1.13.0 分支 git checkout v1.13.0 如Kubernetes版本为 v1.10.11, 请应用上面命令切换 v1.10.11 分支 git checkout v1.10.11随后,如果是Mac用户间接执行shell脚本: ...

February 4, 2021 · 3 min · jiezi

关于flask:FlaskAPScheduler详细配置使用附带API调用

谁再不提前告诉我,就间接复制我的文章而后发表,全家生孩子没屁眼。原文链接:https://segmentfault.com/a/11... 作者:SyntaxError 1.配置from App.tasks.DatabaseTask import send_ding_test # 我的工作函数from flask_apscheduler.auth import HTTPBasicAuthfrom apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStoreclass Config(object): JOBS = [ # interval定时执行(从start_date到end_date,距离20s,蕴含首尾) # func也能够写字符串模式,例如:'App.tasks.DatabaseTask:send_ding_test' { 'id': 'job2', 'func': send_ding_test, 'trigger': 'interval', 'start_date': '2021-01-27 13:31:00', 'end_date': '2021-01-27 13:33:00', 'seconds': 20, 'replace_existing': True # 从新执行程序时,会将jobStore中的工作替换掉 }, # date一次执行 { 'id': 'job1', 'func': send_ding_test, 'trigger': 'date', 'run_date': '2021-01-30 11:22:00', 'replace_existing': True }, # cron式定时调度,相似linux的crontab { 'id': 'job3', 'func': send_ding_test, 'trigger': 'cron', 'day_of_week': '0-6', 'month': '*', 'hour': '6', 'minute': '0', 'second': '0', 'replace_existing': True } ] # 存储定时工作(默认是存储在内存中) SCHEDULER_JOBSTORES = {'default':SQLAlchemyJobStore(url='mysql+pymysql://xxx/xx')} # 设置时区,时区不统一会导致定时工作的工夫谬误 SCHEDULER_TIMEZONE = 'Asia/Shanghai' # 肯定要开启API性能,这样才能够用api的形式去查看和批改定时工作 SCHEDULER_API_ENABLED = True # api前缀(默认是/scheduler) SCHEDULER_API_PREFIX = '/scheduler' # 配置容许执行定时工作的主机名 SCHEDULER_ALLOWED_HOSTS = ['*'] # auth验证。默认是敞开的, SCHEDULER_AUTH = HTTPBasicAuth() # 设置定时工作的执行器(默认是最大执行数量为10的线程池) SCHEDULER_EXECUTORS = {'default': {'type': 'threadpool', 'max_workers': 10}} # 另外flask-apscheduler内有日志记录器。name为apscheduler.scheduler和apscheduler.executors.default。如果须要保留日志,则须要对此日志记录器进行配置    ...

January 28, 2021 · 2 min · jiezi

关于flask:3Flask构建弹幕微电影网站安装mysql数据库及配置

【百度云搜寻,搜各种材料:http://www.lqkweb.com】【搜网盘,搜各种材料:http://www.swpan.cn】Flask 构建微电影视频网站已上线演示地址: http://movie.tbquan.cn 装置数据库连贯依赖包安装包flask-sqlalchemy pip install flask-sqlalchemypip listPackage Version---------------- -------Click 7.0Flask 1.0.2Flask-SQLAlchemy 2.3.2itsdangerous 0.24Jinja2 2.10MarkupSafe 1.0pip 18.1setuptools 40.4.3SQLAlchemy 1.2.12Werkzeug 0.14.1wheel 0.32.1中文文档 http://www.pythondoc.com/flas... 查看配置键: http://www.pythondoc.com/flas... 连贯格局 MySQL: mysql://username:password@localhost/mydatabase 创立mysql数据库下载mysqlhttps://dev.mysql.com/downloa... 这儿间接用的最新版8.0 MySQL Community Server 学习应用下载社区版就能够 装置mysql 关上开始菜单的MySQL 8.0 Command Line Client验证是否失常装置 Enter password: rootWelcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 11Server version: 8.0.12 MySQL Community Server - GPLCopyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> \s--------------C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe Ver 8.0.12 for Win64 on x86_64 (MySQL Community Server - GPL)Connection id: 11Current database:Current user: root@localhostSSL: Cipher in use is DHE-RSA-AES128-GCM-SHA256Using delimiter: ;Server version: 8.0.12 MySQL Community Server - GPLProtocol version: 10Connection: localhost via TCP/IPServer characterset: utf8mb4Db characterset: utf8mb4Client characterset: gbkConn. characterset: gbkTCP port: 3306Uptime: 2 min 20 secThreads: 2 Questions: 23 Slow queries: 0 Opens: 147 Flush tables: 2 Open tables: 123 Queries per second avg: 0.164--------------如果cmd中mysql命令不论用,须要增加环境变量,在Path中增加C:\Program Files\MySQL\MySQL Server 8.0\bin\,也就是mysql的装置目录 ...

December 12, 2020 · 1 min · jiezi

关于flask:2Flask构建弹幕微电影网站使用蓝图构建项目目录

【百度云搜寻,搜各种材料:http://ww.bdyss.cn】【搜网盘,搜各种材料:http://www.swpan.cn】Flask 构建微电影视频网站已上线演示地址: http://movie.tbquan.cn 蓝图构建我的项目目录什么是蓝图一个利用中或者跨利用制作利用组件和反对通用的模式 蓝图的作用将不同的性能模块化构建大型利用优化我的项目构造加强可读性、易于保护构建办法·定义注册调用定义蓝图(app/admin/__init__.py) from flask import Blueprintadmin = Blueprint("admin", __name__)import views注册蓝图(app/__init__.py) from admin import admin as admin_blueprintapp.register_blueprint(admin_blueprint, url_prefix="/admin")调用蓝图(app/admin/views.py) from . import admin@admin.route("/")开始创立我的项目定义蓝图app/home/__init__.py from flask import Blueprinthome = Blueprint('home', __name__)import app.home.viewsapp/admin/__init__.py from flask import Blueprintadmin = Blueprint('admin', __name__)import app.admin.views调用蓝图定义视图 app/home/views.py from . import home@home.route("/")def index(): return "<h1 style='color:blue'>前台</h1>"app/admin/views.py from . import admin@admin.route("/")def index(): return "<h1 style='color:red'>后盾</h1>"注册蓝图app/__init__.py from flask import Flaskapp = Flask(__name__) # 实例化flaskapp.debug = True # 开启调试模式from app.home import home as home_blueprint # 导入from app.admin import admin as admin_blueprintapp.register_blueprint(home_blueprint)app.register_blueprint(admin_blueprint, url_prefix="/admin")运行服务编写入口脚本,使整个我的项目启动起来 ...

December 12, 2020 · 1 min · jiezi

关于flask:从零做网站开发基于Flask和JQuery实现表格管理平台

摘要:本文将为大家带来基于Flask框架和JQuery实现治理平台网站的开发性能。【写在后面】你要开发网站? 嗯。。 会Flask吗? 什么货色,没听过。。。 会JQuery吗? 是python的库吗 ? 那你会什么? 我会F12关上网站 好吧,那咱们来写个简略的表格治理平台。 基于Flask框架和JQuery实现治理平台网站的开发性能,我代码编写用了2天的工夫 ,从零开始写;又对具体实现流程,本人断断续续地整顿总结了近半个月。从自我感觉来说,整个过程和后果的实现都让我很称心。 【成果如下】 【第一步】理解Flask框架1、理解python 支流的web框架(1)Django:简略来说就是重武器,是最全能的开发框架,你想要的性能它都有;然而比拟沉重,适宜企业级的web开发; (2)Flask:属于web开发微框架,玲珑灵便,相干的第三方库丰盛,适宜开发小型web; (3)Tornado:天生异步,性能强悍,然而框架提供的性能少,须要开发者本人实现; 因而,本文代码实现次要是基于Flask实现的。 2、理解Flask框架(1)HelloWorld实现:简直所有的编程都是基于“hello world”实现的,因而也大抵讲下helloworld波及的内容。 from flask import Flaskapp = Flask(__name__)@app.route('/')def hello_world(): return "Hello world!"if name == '__main__': app.run()先申明一个Flask框架的对象,而后定义路由'/',即URL地址为 http://127.0.0.1:5000/;如果定义门路为‘/new’,那对应的拜访地址须要改为http://127.0.0.1:5000/new。另外,@app.route是个装璜器。 (2)如何实现具体IP地址或者端口的拜访呢?app.run(debug=True,host="x.x.x.x",port="1234") 通过对app.run()办法的参数定义,别离实现了调试性能,拜访URL变更为 http://x.x.x.x:1234 这里调试性能还是很好用的,岂但能够帮忙开发者从新加载网页,而且会打印具体的错误信息,帮助定位。 (3)如何在web上显示本人特定的html模板from flask import Flask,render_templateapp = Flask(__name__)@app.route('/')def hello_world(): return render_template('test.html') if name == '__main__': app.run()只须要导入render_template库,并且在函数返回时改成对应的办法即可。 不过 这 里要 留神,test.html必须保留在工程目录与下template文件下,否则会报错。(这是因为render_template办法定义时默认写了template门路 ) 【第二步】理解Sqlite3数据库web数据交互离不开后盾数据库的治理,本章节重点解释python自带的sqlite3数据库。相比拟于其余“正规”的数据库,如mongo、solr、MySQL等,sqlite3相当简略,属于轻量级的数据库。 1、sqlite3数据库的装置和创立 用pip命令能够下载安装sqlite3数据库 创立数据库: con = sqlite3.connect('material.db') 如果有数据库material.db,则进行连贯数据库的操作;如果没有此数据库,则先创立数据库再进行连贯; 2、创立数据表 label = ['ID','网络IP','地址','责任人','联系方式']content = ['1','10.10.10.10','杭州滨江','鹏哥','123456']def create(): ...

December 2, 2020 · 4 min · jiezi

关于flask:设计一个基于flask的高并发高可用的查询ip的http服务

结构设计基础架构为flask+gunicorn+负载平衡,负载平衡分为阿里云硬件负载平衡服务和软负载nginx。gunicorn应用supervisor进行治理。 应用nginx软件负载结构图 应用阿里云硬件负载平衡服务结构图 因为flask app须要在内存中保留ip树以及国家、省份、城市相干的字典,因而占用内存较高。gunicorn的1个worker须要占用300M内存,nginx的4个worker内存占用较小(不到100M),因而占用1.3G的内存(即须要一个2G内存的服务器)。当gunicorn任意一个节点挂断或者降级时,另外一个节点依然在应用,不会影响整体服务 <!--more--> ip数据库IP库(也叫IP地址数据库),是由业余技术人员通过长时间通过多种技术手段收集而来的,并且长期有业余人员进行更新、保护、补充。 ip数据库解析查问代码 基于二叉查找树实现 import structfrom socket import inet_aton, inet_ntoaimport osimport syssys.setrecursionlimit(1000000)_unpack_V = lambda b: struct.unpack("<L", b)_unpack_N = lambda b: struct.unpack(">L", b)_unpack_C = lambda b: struct.unpack("B", b)class IpTree: def __init__(self): self.ip_dict = {} self.country_codes = {} self.china_province_codes = {} self.china_city_codes = {} def load_country_codes(self, file_name): try: path = os.path.abspath(file_name) with open(path, "rb") as f: for line in f.readlines(): data = line.split('\t') self.country_codes[data[0]] = data[1] # print self.country_codes except Exception as ex: print "cannot open file %s: %s" % (file, ex) print ex.message exit(0) def load_china_province_codes(self, file_name): try: path = os.path.abspath(file_name) with open(path, "rb") as f: for line in f.readlines(): data = line.split('\t') provinces = data[2].split('\r') self.china_province_codes[provinces[0]] = data[0] # print self.china_province_codes except Exception as ex: print "cannot open file %s: %s" % (file, ex) print ex.message exit(0) def load_china_city_codes(self, file_name): try: path = os.path.abspath(file_name) with open(path, "rb") as f: for line in f.readlines(): data = line.split('\t') cities = data[3].split('\r') self.china_city_codes[cities[0]] = data[0] except Exception as ex: print "cannot open file %s: %s" % (file, ex) print ex.message exit(0) def loadfile(self, file_name): try: ipdot0 = 254 path = os.path.abspath(file_name) with open(path, "rb") as f: local_binary0 = f.read() local_offset, = _unpack_N(local_binary0[:4]) local_binary = local_binary0[4:local_offset] # 256 nodes while ipdot0 >= 0: middle_ip = None middle_content = None lis = [] # offset begin_offset = ipdot0 * 4 end_offset = (ipdot0 + 1) * 4 # index start_index, = _unpack_V(local_binary[begin_offset:begin_offset + 4]) start_index = start_index * 8 + 1024 end_index, = _unpack_V(local_binary[end_offset:end_offset + 4]) end_index = end_index * 8 + 1024 while start_index < end_index: content_offset, = _unpack_V(local_binary[start_index + 4: start_index + 7] + chr(0).encode('utf-8')) content_length, = _unpack_C(local_binary[start_index + 7]) content_offset = local_offset + content_offset - 1024 content = local_binary0[content_offset:content_offset + content_length] if middle_content != content and middle_content is not None: contents = middle_content.split('\t') lis.append((middle_ip, (contents[0], self.lookup_country_code(contents[0]), contents[1], self.lookup_china_province_code(contents[1]), contents[2], self.lookup_china_city_code(contents[2]), contents[3], contents[4]))) middle_content, = content, middle_ip = inet_ntoa(local_binary[start_index:start_index + 4]) start_index += 8 self.ip_dict[ipdot0] = self.generate_tree(lis) ipdot0 -= 1 except Exception as ex: print "cannot open file %s: %s" % (file, ex) print ex.message exit(0) def lookup_country(self, country_code): try: for item_country, item_country_code in self.country_codes.items(): if country_code == item_country_code: return item_country, item_country_code return 'None', 'None' except KeyError: return 'None', 'None' def lookup_country_code(self, country): try: return self.country_codes[country] except KeyError: return 'None' def lookup_china_province(self, province_code): try: for item_province, item_province_code, in self.china_province_codes.items(): if province_code == item_province_code: return item_province, item_province_code return 'None', 'None' except KeyError: return 'None', 'None' def lookup_china_province_code(self, province): try: return self.china_province_codes[province.encode('utf-8')] except KeyError: return 'None' def lookup_china_city(self, city_code): try: for item_city, item_city_code in self.china_city_codes.items(): if city_code == item_city_code: return item_city, item_city_code return 'None', 'None' except KeyError: return 'None', 'None' def lookup_china_city_code(self, city): try: return self.china_city_codes[city] except KeyError: return 'None' def lookup(self, ip): ipdot = ip.split('.') ipdot0 = int(ipdot[0]) if ipdot0 < 0 or ipdot0 > 255 or len(ipdot) != 4: return None try: d = self.ip_dict[int(ipdot[0])] except KeyError: return None if d is not None: return self.lookup1(inet_aton(ip), d) else: return None def lookup1(self, net_ip, (net_ip1, content, lefts, rights)): if net_ip < net_ip1: if lefts is None: return content else: return self.lookup1(net_ip, lefts) elif net_ip > net_ip1: if rights is None: return content else: return self.lookup1(net_ip, rights) else: return content def generate_tree(self, ip_list): length = len(ip_list) if length > 1: lefts = ip_list[:length / 2] rights = ip_list[length / 2:] (ip, content) = lefts[length / 2 - 1] return inet_aton(ip), content, self.generate_tree(lefts), self.generate_tree(rights) elif length == 1: (ip, content) = ip_list[0] return inet_aton(ip), content, None, None else: returnif __name__ == "__main__": import sys reload(sys) sys.setdefaultencoding('utf-8') ip_tree = IpTree() ip_tree.load_country_codes("doc/country_list.txt") ip_tree.load_china_province_codes("doc/china_province_code.txt") ip_tree.load_china_city_codes("doc/china_city_code.txt") ip_tree.loadfile("doc/mydata4vipday2.dat") print ip_tree.lookup('123.12.23.45')http申请提供ip查问服务的GET申请和POST申请 ...

November 11, 2020 · 7 min · jiezi

关于flask:如何做好DDoS防御F5给出四点建议

DDoS攻打是黑客手中的利器,是所有运维者的心头痛,也是任何公司听闻后都将大惊失色的弱小对手。DDoS全称Distributed Denial of Service,中文意思为“分布式拒绝服务”。是利用大量非法的分布式服务器对指标发送申请,从而导致服务器拥塞而无奈对外提供失常服务,只能发表game over。 从企业客户角度来看,DDoS进攻是一个漫长的过程,攻打伎俩和工具会一直刷新,那么如何如何做好DDoS进攻?F5给出四点倡议! 第一点倡议,用户须要意识到网络攻防和合规是两回事,平安部署不应该以合规为指标,而要重点思考攻防,将攻防搁置于首位。 第二点倡议,用户须要扭转对立安全策略,对于要害利用而言,须要对利用做完流量精分之后,再依据不同灰度的流量进行安全策略配置。F5产品+服务的攻防模型曾经被泛滥用户证实是无效的,可供参考。 第三点倡议,不要将平安防御能力全副寄希望于全自动平安模式,从另一个角度而言,全自动也意味着平安危险,最好的解决方法是要做可控的风险管理。 第四点倡议,审慎抉择通明模式和Bypass模式!很多客户被攻打就是因为这两个模式,现在的DDoS攻打终极目标其实并不是让业务瘫痪,而是为了在彻底打穿透明模式和Bypass模式之后,覆盖不法分子如入无人之境般窃取外围数据。对此F5给出的解决之道是构建一个超高弹性的全代理架构,做古代攻防的解决。 在F5的Advanced WAF(API 平安——新一代 WAF)+服务的框架下,通过大数据采集、机器学习、智能基线判断,最初进行预案全副工作都由F5实现,客户本人来判断是否通过“一键切换”进入进攻状态。“F5通过机器学习的办法来去判断失常流量、异样流量和用户行为,而后通过AIOps的形式去做解决,最初给客户一键选择权。 攻防是第一指标,流量精分是实现办法。F5心愿实现的成果是通过机器学习之后的一键操作,而不是简略的全自动,最终构建出残缺的DDoS进攻体系。 

October 10, 2020 · 1 min · jiezi

关于flask:Flask从零到一-2-flask相关参数配置

上一篇文章介绍了学习flask之前须要的筹备工作,也就是Git工具的下载和虚拟环境的配置,还介绍了如何编写一个最简略的flask程序,还捎带讲了一下有对于动态目录和模板目录的常识,这篇文章会深刻解说一下flask一些参数的配置。 app初始化参数上一篇文章咱们提及过在以后模块的根目录下会默认存在一个static,当然这是咱们不在网页加任何润饰的状况。但如果咱们须要将一些动态文件展现到网页中,咱们就须要创立一个static文件夹来保留这些动态文件,它应该与以后模块处于同级目录。 之前咱们通过拜访绑定的url调用相应的视图函数,从而将返回值传至客户端页面,咱们也能够通过拜访动态文件夹static中的html文件,间接将html文件中的内容展现在客户端,首先咱们须要做的就是在的static中创立一个html文件,内容能够依照本人的情意填写。 关上浏览器输出网址,门路须要是 根地址/static/.html 格局,这样就能够拜访对应的html文件。 但在咱们实例化app对象中,也能够设置一些初始化参数: 1、import_name:寻找动态目录和模板目录地位2、static_url_path:拜访动态文件时url的前缀3、static_folder:动态文件目录,默认为static4、template_floder:模板文件目录,默认为templatesimport_name参数如果传入__name__,就示意在以后模块目录中寻找动态和模板目录,当然也能够传入其余参数,但这里倡议用__name__。而第二个参数是用来自定义动态文件url前缀的,默认就为static,但如果咱们更改了app对象中的这个参数: app = Flask(__name__, static_url_path='/index')在拜访同一个动态文件时必须将url中的static更改为index,能力胜利拜访: 这个前缀也是比拟重要的,因为咱们之前说过通过视图函数也能够有同样的成果,这个前缀也是证实你在拜访一个动态文件,而不是一个视图函数。 而剩下两个参数不设置的话就会在以后模块目录下寻找名为static和templates的文件夹,而如果进行了设置,就会到你设置的目录下寻找对应的动态和模板目录。 debug参数配置咱们平时编程的时候应该都用过Debug性能调试代码,"万能的Debug一下",当咱们在写Flask程序时,如果代码局部呈现了谬误,运行之后客户端会给出一个很泛泛的谬误提醒,比方HTTP状态码。如果咱们在代码中加一个除零谬误,运行之后客户端的页面如下: 这样咱们只是晓得代码中有谬误,却不晓得错在哪,找BUG难上加难。而Flask中也是有DEBUG这个参数的,咱们须要做的就是对这个参数进行配置,办法大抵有以下四种: 1、通过配置文件2、通过对象配置3、间接在config字典中设置4、在run办法中设置办法一首先咱们在文件的同级目录下创立一个名为 config.cfg 的文件,并在文件中增加上面这行语句: DEBUG = True而后回到代码文件中,通过在app对象上进行配置,代码如下: app.config.from_pyfile('config.cfg')办法二因为Python中所有皆对象,类也是一个对象,所以咱们能够通过创立一个类,而后将DEBUG设置为这个类中的一个属性: class Config(): DEBUG = True而后也是在app对象上进行配置,只不过从文件配置改成从对象配置: app.config.from_object(Config)办法三app利用对象中的config能够了解成一个字典对象,咱们也能够间接在这个字典上进行配置debug参数: app.config["DEBUG"] = True须要留神的是配置大量参数能够应用这种办法,而参数过多则会导致代码量多,写起来简单,代码可读性也会升高。 对于config这个字典对象,咱们也能够依据配置参数中已知的键来查问对应的值: print(app.config.get("已知键"))办法四第四种办法应该是最简略的,run()办法的作用就是运行flask程序,外面也有一个debug参数,默认为False,当咱们设置为True时,Debug性能开启: app.run(debug=True)run()办法中也有一些其余参数可供配置,比方主门路、端口号等等,这里不再过多介绍,有须要的搭档能够查问官网文档。 这四种办法咱们任选其一配置好DEBUG参数后,再次运行程序,Pycharm运行栏里的信息通知咱们DEBUG性能曾经开启: 而后回到浏览器刷新页面,会发现客户端会给出了精确的代码谬误,通知你这是一个除零谬误,所以咱们只须要去找无关代码即可: 综上为本文全部内容,次要介绍了flask程序中两个比拟重要的参数:app利用对象的初始参数和debug参数,以及四种配置debug参数的根本办法。 本文参考资料:[1].《Flask入门教程》.李辉著[2].https://www.bilibili.com/vide...[3].Flask中英文档如果你对这个系列感兴趣,欢送关注公众号【奶糖猫】第一工夫跟进后续更新~

August 14, 2020 · 1 min · jiezi

关于flask:flaskpython-实时视频流输出到前台

问题形容: 1。调用摄像头获取视频流 2。将视频流解决并传递给浏览器 3。不是录制后处理,而是边录制边解决,边传递 4。 flash后盾进行解决,而不是在前端解决 问题解决: 办法起源:外汇名词解释https://www.fx61.com/definitions server.py from flask import Flask, render_template, Responseimport cv2 class VideoCamera(object): def __init__(self): # 通过opencv获取实时视频流 self.video = cv2.VideoCapture(0) def __del__(self): self.video.release() def get_frame(self): success, image = self.video.read() # 在这里解决视频帧 cv2.putText(image, "hello",(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8,(0, 255, 0)) # 因为opencv读取的图片并非jpeg格局,因而要用motion JPEG模式须要先将图片转码成jpg格局图片 ret, jpeg = cv2.imencode('.jpg', image) return jpeg.tobytes() app = Flask(__name__,static_folder='./static') @app.route('/') # 主页def index(): # jinja2模板,具体格局保留在index.html文件中 return render_template('index.html') def gen(camera): while True: frame = camera.get_frame() # 应用generator函数输入视频流, 每次申请输入的content类型是image/jpeg yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n') @app.route('/video_feed') # 这个地址返回视频流响应def video_feed(): return Response(gen(VideoCamera()), mimetype='multipart/x-mixed-replace; boundary=frame') if __name__ == '__main__': app.run(host='0.0.0.0', debug=True, port=5000) index.html ...

August 12, 2020 · 1 min · jiezi

关于flask:flaskpython-实时视频流输出到前台

问题形容: 1。调用摄像头获取视频流 2。将视频流解决并传递给浏览器 3。不是录制后处理,而是边录制边解决,边传递 4。 flash后盾进行解决,而不是在前端解决 问题解决: 办法起源:外汇名词解释https://www.fx61.com/definitions server.py from flask import Flask, render_template, Responseimport cv2 class VideoCamera(object): def __init__(self): # 通过opencv获取实时视频流 self.video = cv2.VideoCapture(0) def __del__(self): self.video.release() def get_frame(self): success, image = self.video.read() # 在这里解决视频帧 cv2.putText(image, "hello",(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8,(0, 255, 0)) # 因为opencv读取的图片并非jpeg格局,因而要用motion JPEG模式须要先将图片转码成jpg格局图片 ret, jpeg = cv2.imencode('.jpg', image) return jpeg.tobytes() app = Flask(__name__,static_folder='./static') @app.route('/') # 主页def index(): # jinja2模板,具体格局保留在index.html文件中 return render_template('index.html') def gen(camera): while True: frame = camera.get_frame() # 应用generator函数输入视频流, 每次申请输入的content类型是image/jpeg yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n') @app.route('/video_feed') # 这个地址返回视频流响应def video_feed(): return Response(gen(VideoCamera()), mimetype='multipart/x-mixed-replace; boundary=frame') if __name__ == '__main__': app.run(host='0.0.0.0', debug=True, port=5000) index.html ...

August 12, 2020 · 1 min · jiezi

关于flask:Flask从零到一-1-虚拟环境和第一个flask程序

大概还有二十天假期工夫,这二十天我筹备跟进一个Flask入门系列,大抵会分为10-12篇文章。尽管我当前不想做开发,然而Web开发热度还是挺高的,所以就用了一段时间学习了一下,下学期也会有与Web无关的课程,如果对Flask感兴趣的话,能够继续跟进,心愿这个系列能够帮到搭档们。 第一篇次要包含两个方面,一方面是学习Flask之前的筹备工作,比方配置相应的环境以及下载一些工具;另一方面就是编写第一个Flask简易程序。 Flask是反对Python2 和 Python3两个版本的,然而在我接触Python的时候曾经更新到3.6了,所以我也没有关注Python2。我置信搭档们应该也大多都在用Python3,所以这个系列会以Python3为根底,Flask的版本应用最新的即可。 这里只对Flask做一个简要的介绍:Flask是一个微框架,自身相当于一个内核,只保留了外围性能:申请响应解决和模板渲染。这两个性能别离有Werkzeug和Jinja实现,Flask自身也包装了这两个依赖,而后Flask还领有很多扩大包,用户能够依据本人的需要导入扩大包实现相应的性能,这也是Flask框架灵便的起因。 Git下载首先对于Windows用户,举荐下载Git这个工具,尽管很多性能咱们在终端也能够实现,但在某些方面上讲应用Git的命令会更加不便,而且它也能够用来记录编写程序的源码和文件的变动状况,Git的下载安装教程能够参考这篇博客:Windows零碎装置教程。 下载安装实现之后,在搜寻栏找到Git Bash,运行时候输出git --version,如果呈现对应的版本信息则代表装置胜利。 配置虚拟环境虚拟环境是一种独立于Python全局环境的Python解释器环境,比方一个虚拟环境中的解释器版本能够为Python2,另一个的解释器版本能够为Python3,它们之间是不会互相烦扰的,而且也不会受你电脑中Python环境的影响。 配置虚拟环境是很必要的操作,因为不同的程序依赖语言的版本可能会不同,但如果在一台电脑中下载多个版本Python解释器,就会导致全局环境芜杂,虚拟环境很好的解决了这个问题,也便于管理咱们的程序。 Python3中内置的venv模块能够创立虚拟环境,首先在零碎自带的cmd中通过cd指令进入到指定的文件中,须要留神的是这个门路不能蕴含中文,而后应用上面指令创立一个虚拟环境。 python -m venv env 其中env为虚拟环境的名称,能够本人拟定。这时会在当前目录下生成一个蕴含了Python解释器的虚拟环境文件夹,而后在Scripts文件下有两个.bat文件,这两个文件就管制着激活虚拟环境和退出虚拟环境。 通过上面指令就能够激活虚拟环境和退出虚拟环境: env\Scripts\activate#激活env\Scripts\deactivate#退出或者也能够间接进入Scripts目录下间接输出activate或者deactivae即可,当目录前呈现上面这种小括号模式就代表激活虚拟环境胜利。 (env) 而后就能够通过pip在虚拟环境中装置Flask,不必指定版本,默认装置最新版本。 pip install flask第一个flask程序编写一个flask程序是非常简单的,仅仅几行代码就能够实现,流程大抵能够分为以下五步: 1、从flask包中导入Flask类2、实例化Flask类,创立一个利用对象3、定义视图函数4、为视图函数增加装璜器(定义路由)5、启动flask程序#从flask包中导入Flask类from flask import Flask#创立flask的利用对象app = Flask(__name__)@app.route('/')def hello(): """定义视图函数""" return 'Hello NaiTangMao'if __name__ == "__main__": #启动flask程序 app.run()运行下面这段程序会给出上面的后果,咱们先关注红字局部的网址,会发现这是咱们本地主机的IP地址,在运行一个flask程序后,他会默认监听主机的5000端口。 Copy一下网址利用浏览器关上就会呈现咱们视图函数中返回的信息: 这个flask程序处理过程如下: 1、首先用户启动程序,并拜访对应的网址。2、服务器解析申请,辨认装璜器中绑定的URL。3、匹配URL并调用对应的视图函数。4、获取视图函数的返回值,返回至客户端。对于下面这几行代码须要把握几个知识点,首先你应该明确app为Flask类实例化后创立的一个利用对象,但外面的__name__有什么作用呢? __name__为以后模块名,因为flask程序在运行的时候须要动态文件和模板文件辅助,在你设置__name__之后,flask就会以以后模块(文件)所在目录为根目录,默认这个目录中的static为动态目录,templates为模板目录,因为是默认存在的,所以在根目录中并不会体现进去。不了解不要紧,前面还会波及这两方面常识。 视图函数临时不须要过多介绍,须要留神的是视图下面的装璜器app.route(),这个装璜器能够为视图函数绑定一个URL,当用户拜访这个URL时会触发对应的视图函数,其中'/'代表根地址,咱们还能够在根地址后设置额定门路,比方app.route('/hello')。 这时如果咱们在拜访原来那个网址就会呈现Not Found的谬误提醒,此时必须要拜访http://127.0.0.1:5000/hello能力触发视图函数。一个视图函数也能够同时绑定多个URL,这通过为视图函数增加多个装璜器实现: @app.route('/')@app.route('/hello')def hello(): """定义视图函数""" return 'Hello NaiTangMao'也就是说当初不管咱们拜访http://127.0.0.1:5000/ 还是 http://127.0.0.1:5000/hello都能够触发这个视图函数。 综上为Flask入门系列的第一篇,次要介绍虚拟环境的配置以及繁难flask程序形成局部的简要解析。 本文参考资料:[1].《Flask入门教程》.李辉著[2].https://www.bilibili.com/vide...[3].Flask中英文档如果你对这个系列感兴趣,欢送关注公众号【奶糖猫】第一工夫跟进后续更新~

August 6, 2020 · 1 min · jiezi

关于flask:Fastjson到了说再见的时候了

生命太短暂,不要去做一些基本没有人想要的货色。本文已被 https://www.yourbatman.cn 收录,外面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以收费学习。关注公众号【BAT的乌托邦】一一击破,深刻把握,回绝浅尝辄止。 前言各位小伙伴大家好,我是A哥。停更1个月后回归啦,明天咱们聊聊一个比拟有意思的话题:是否真的须要跟Fastjson说再见了? 我的态度我在CSDN写过好些篇对于JSON的文章,特地是2020年专门写了一个付费专栏:享学Jackson这个专栏“销量”在我心目中还对付,4个月“卖出”200份的样子(虽不值一提,但我很满足了????),小小的一个JSON库而已,热度可见一斑。专栏里不可避免的提到了Jackson和Fastjson的比拟,我自己始终持中立态度,次要起因有二: 两者都很风行(国内Fastjson风行度甚至超过Jackson),因而平时开发中我两者都用(须要随大流嘛)国产开源软件是须要被反对的,即便当初还存在差距(联想下最后的国产手机和苹果手机的差距,再看看当初呢?)当然,本文不一样了,必须得加点料。态度中立并不代表没有偏差:很显著我偏差于应用Jackson作为你的 惟一 JSON库。 从本文起,我将把CSDN里该付费专栏全部内容搬到公众号,收费助你轻松拥抱世界上最好的JSON库:Jackson。从本文起,我将把CSDN里该付费专栏全部内容搬到公众号,收费助你轻松拥抱世界上最好的JSON库:Jackson。从本文起,我将把CSDN里该付费专栏全部内容搬到公众号,收费助你轻松拥抱世界上最好的JSON库:Jackson。 市面上并无成体系介绍Jackson的教程(官网都木有),独此一家哦。当然喽,这必将伤害到我的CSDN专栏售卖权利(小钱也是钱嘛????),所以心愿你关注公众号,关注此专栏,而后学到手我就感觉值了2020-05-30阿里云应急响应核心监测到Fastjson暴发新的反序列化近程代码执行破绽,黑客利用破绽,可绕过autoType限度,间接近程执行任意命令攻打服务器,危险极大(话外音:此bug必须Fix)。侥幸的是,官网的响应速度十分快:还记得上一次Fastjson 高级别危险 安全漏洞是什么时候吗?是的,它就产生在2019-09-04,两次相距着实不远,不满你说我还历历在目呢,我司安全部门发的邮件还能找到????。 当然,之前也有些破绽问题,但关注度不如这两次。次要是这两次工夫相近,危险级别十分高影响面很大,所以社区反馈较为强烈这两次“相邻”的安全漏洞着实把Fastjson推到了风口浪尖,吃瓜大众一波接一波,一时间 “弃用Fastjson,拥抱Jackson/Gson” 的声音不绝于耳。这很容易了解,因为谁都不愿意时不时收到公司安全部门的这种邮件:针对此破绽,虽说咱们Fix起来步骤简略:降级Fastjson的版本,而后重启利用。看起来毫不费力,实则是个大坑。你是否曾想过这个问题:假使有上百个、几百个Java利用呢?且不谈你操作上的工夫和人力老本有多高,单单治理起来的工作量也不容小觑。所以如果你是技术Leader,胸中的怒火开释一下是在情理之中的。 置信很少有部门/团队把Spring Boot利用做成Jar包拆散的模式的吧~因而大概率都须要通过升版本 -> 提交代码 -> 合代码 -> 上pre -> 上线 -> 验证等步骤,so还是比拟麻烦的你为何用Fastjson?这个问题你能够问本人,也能够问身边的共事。汇总一下就是答案,这才是来自用户最实在的声音嘛。我针对此也简略“考察”过,把我听到的理解到的汇总为如下三点: API简略(static办法间接应用),上手快,对开发者敌对阿里巴巴出品,背靠大厂值得信赖.社区绝对沉闷,保护降级有保障容我猜猜,这3个理由大概率命中了你心中所想吧?????有大厂做背书天然能给产品加分,但本身优良才是硬道理。尽管起因有三点,但我认为让很多人决定去应用它、赞它的最最最次要起因其实就一点:API简略,static办法间接调用对开发者敌对。 我感觉对于大多数Java Coder(特地对于初学者)来说,应用时会有这样的一种情节在外面:静态方法的逼格比实例办法高。而实际上不应该是这样子的,初学者(初/中级选手)酷爱应用静态方法,而高手在设计一个库/框架时应在静态方法+实例办法间运用自如。一味地、过多地应用静态方法只会让你的的思维偏向于面向过程,而非更好的利用Java 面向对象 的个性,因而高下立判。 没有孰优孰劣,适宜的才是最好的发现了没,应用Fastjson的起因中,咱们至始至终都没有提到性能高/速度快等字眼,但这却是Fastjson最最最为外围的个性,堪称是它能立足于泛滥JSON库中、“怀才不遇”的立身之本。岂不怪哉,咱们应用它竟不是因为它最外围的个性有多好,那这是为何呢? 你为何仍在用Fastjson?起因能够说出5678种,总而言之言而总之,你不(敢)切换的起因或者只有一个:Fastjson的静态方法调用用着省心;最重要的是,对其它JSON库(如Jackson/Gson)并不相熟不敢切换。 我认为胆怯来自于未知不可否认Jackson/Gson的应用门槛确实比Fastjson高那么一丢丢,但这绝不是你回绝去应用它的理由。受Fastjson这“间断”两次高危破绽影响,A哥更加动摇了把Jackson当作 惟一 JSON库的信心,甚至在团队内严令禁止应用Fastjson。大家对立了语言/工具,更能进步生产力~ 如果你也是因为不太理解Jackson而不敢来到温室,那么看到本文就很侥幸了,本系列会收费带你拥抱Jackson这个高级JSON库,性能上比Fastjson强了不止一点点。 注释坊间在某坛里看到这样一句舆论:若你还依赖于应用Fastjson,那么你大概率还只是初/中级程度。这句话必然让Fastjson的忠诚用户火冒三丈,抄起家伙嘎嘎就是干。话出必然有因,那么这句话是否真的言过于词呢?接下来就絮叨絮叨 我很违心用存在即正当准则来表白一个观点:Fastjson出个bug就能有这么高的关注度,不可否认这自身就是一种胜利。 误区形容:“正当”请不要误读为“合乎情理”之类,而是当做“理由”来讲。“存在即正当”正确理解为:所有存在的事物都有它存在的理由任何技术可能流行起来,well-known被咱们所熟知必然有它的劣势,哪怕这个过人之处只有一个。上面咱们来看看为何Fastjson能一步步被钟爱,它的魔力到底在哪? 技术选型不应该像相亲:必定你只须要一个理由,而否定你却能... Why Fastjson?尽管最近Fastjson因为呈现安全漏洞,社区舆论一边倒。即使如此,简直没人间接否定过Fastjson自身的优良,特地是当你晓得这个应用宽泛的库简直全副来自于一人之手时。他就是匠人温少: 值得一提的是:温少另一个开源我的项目Druid是国内最风行的(甚至没有之一)数据库连接池产品,广受好评成人只看利弊,小孩才分对错。为何要应用它能风行开来,那必然是因为它优良。它优秀品质在其官网可一览无遗:这些“长处”用中文形容进去更加直(震)观(撼): 1、速度快fastjson绝对其余JSON库的特点是快,从2011年fastjson公布1.1.x版本之后,其性能从未被其余Java实现的JSON库超过。 话外音:速度/性能这一块,Fastjson始终拿捏得死死的 2、应用宽泛fastjson在阿里巴巴大规模应用,在数万台服务器上部署,fastjson在业界被宽泛承受。在2012年被开源中国评比为最受欢迎的国产开源软件之一。 话外音:阿里巴巴数以万计的大规模集群实例做规模背书,说服力杠杠的 3、测试齐备fastjson有十分多的testcase,在1.2.11版本中,testcase超过3321个。每次公布都会进行回归测试,保证质量稳固。 话外音:单测覆盖率高,代码健壮性有保障 4、应用简略fastjson的API非常简洁。 String text = JSON.toJSONString(obj); //序列化VO vo = JSON.parseObject("{...}", VO.class); //反序列化话外音:不论你是小白还是小小白,轻松上手,应用起来都无障碍 5、性能齐备反对泛型,反对流解决超大文本,反对枚举,反对序列化和反序列化扩大。 话外音:我这一家就够了,你要的,这都有 Why Not Fastjson?文首有表态本文我是有态度和有偏差的,因而不来几点起因实则不妥。那么我就针对于官网列出的5点(见上),给出个人观点供以参考。是否言过于辞,咱们拿出另外一个JSON库做出比照,本文以Jackson为例。 ...

July 20, 2020 · 1 min · jiezi

FlaskSocketIO部署遇到的问题

我的情况是服务端用Flask,前端用Vue.js。问题都出在Flask上。 CORS跨域问题这个问题令我很不愉快,是因为写法的问题导致的。实际上我在实例化SocketIO时已经传入cors_allowed_origins的参数为*,但是最后的问题出在*要用单引号,不能用双引号。我觉得这可能是前后传参符号不一致导致的,应该不是必须要求写单引号。 # 错误的写法socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet')# 正确的写法socketio = SocketIO(app, cors_allowed_origins='*', async_mode='eventlet')400错误这个需要配置Nginx,参考了一篇帖子,配置如下 location /{ proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";}其中第一行是告诉nginx使用HTTP/1.1通信协议,这是websoket必须要使用的协议。 第二行和第三行告诉nginx,当它想要使用WebSocket时,响应http升级请求。 参考资料:400错误解决方法400错误官方issue

June 21, 2020 · 1 min · jiezi

Flask项目中用户密码登陆跳转页面

在功能文件夹中注册蓝图,使用render_template包进行渲染页面 渲染路径与存放路径一致 一定要导入蓝图使用,要不然无法启动 **启动flask项目访问 ..../user/login就会渲染进入到login.html页面当中。** 现在想输入登陆用户名与密码并进行跳转到指定的页面。 现在点击登陆看怎么获取用户名和密码 注意前端用户名和密码的命名规范,一定保持一致。 输入用户名密码,点击登陆查看是否能获取到数据 成功获取到数据 一般项目中前端会去判断用户名密码是否正确或者为空。但是后端也要一定去判断,所有的用户输入的数据都是变化因素。要严谨的看待。 再试试不填写用户名或者密码时看看返回的json数据 在数据库当中增加用户名和密码,试试从数据库中对比数据进行验证返回。 添加测试数据 项目链接数据库 数据库字段表明构建 验证密码时,先对密码进行加密 导入User表以及加密文件 然后进行测试当用户名输入错误或者没填时会返回 当密码输入错误或者没输时返回 只有全部输入成功以及正确的时候,才能将用户名密码返回得到!

June 5, 2020 · 1 min · jiezi

建立Flask项目初始框架

在写flask项目的时候,框架配置尤为重要。大致框架思路如下: 在application.py中封装flask的类 manager.py中引入封装的类 在配置文件中,设置数据库配置,Debug配置,post配置等。 基本配置完成,可以在controllers文件夹中定义功能展示。基本展示页面的内容。 先注册蓝图 初始化导入蓝图使用 在Terminal中开启Flask项目python manager.py runserver配置文件中设置的端口号是多少,链接就会显示多少 成功显示页面内容!

June 4, 2020 · 1 min · jiezi

Flask实战从后台管理到人脸识别六款优质Flask开源项目介绍

Flask 是一个微型的 Python 开发的 Web 框架,基于 Werkzeug WSGI 工具箱和 Jinja2 模板引擎。 Flask 使用 BSD 授权。 Flask 也被称为 “microframework”,因为它使用简单的核心,用 extension 增加其他功能。Flask 没有默认使用的数据库、窗体验证工具。然而,Flask 保留了扩增的弹性,可以用 Flask-extension 加入这些功能:ORM、窗体验证工具、文件上传、各种开放式身份验证技术等。 今天为大家介绍的就是 Gitee 上六款优质的 Flask 项目,想要学习 Flask 的同学千万不要错过。 picbed项目作者:staugur 开源许可协议:BSD-3Clause 项目地址:https://gitee.com/staugur/picbed 基于Flask的Web自建图床,默认存储在本地,内置支持存储到又拍云、七牛云、阿里云OSS、腾讯云COS、GitHub、Gitee。 authbase项目作者:David 项目地址:https://gitee.com/zhujf21st/authbase 基于Python的Flask WEB框架实现后台权限管理系统,内容包含:用户管理、角色管理、资源管理和机构管理。前端页面参考了sypro。 PyFly项目作者:981764793 开源许可协议:MIT 项目地址:https://gitee.com/981764793/PyFly Flask + Layui Fly Template实现的一个社区项目,使用flask-admin实现了简单的后台管理功能,数据库使用Mongodb,前台实现功能:用户注册、登录、邮件激活、发帖、回帖、点赞、回复、采纳、删帖、结贴等功能。 easy-flask-json-mvc-socketio项目作者:水漫门廷 开源许可协议:Apache-2.0 项目地址:https://gitee.com/huashiyuting/flask 一套基于flask,vue的前后端分离的解决方案。 flask-ansible项目作者:shijiange 项目地址:https://gitee.com/shijiange/flask-ansible Flask实现Ansible和Ansible-Playbook的配置+部署系统。同时带有简单的服务器管理系统和认证系统。 face-recognition-service项目作者:westinyang 开源许可协议:Apache-2.0 项目地址:https://gitee.com/westinyang/face-recognition-service 使用 Face Recognition & Flask 构建的人脸比对服务,提供HTTP接口,Pyinstaller打包项目为可独立运行的exe程序。 ...

June 2, 2020 · 1 min · jiezi

再遇CORS-自定义HTTP-header的导致跨域

指路牌后端配置好了跨域,但是前端在HTTP header添加token后,又产生跨域的问题Flask、Vue(Axios)、跨域适用场景前后端分离,想要使用token来管理登录状态,或调用后台接口环境平台无关参考博客axios 在header中配置token信息后,向后端请求会报跨域的问题。但是用postman测试的时候没有什么问题。这个问题的回答其实没有给出直接性的帮助,甚至回答的有点奇怪,但是帮我打开了思路。背景出于多种考虑,放弃了使用类似WordPress这种现成博客解决方案,准备自己搭建一个博客系统,技术选型为:后端:Flask,前端:Vue。登录状态管理放弃cookie,采用token。开发进行到路由保护处时出现了CORS的问题,具体情形是Vue将从后台获取的token添加到HTTP请求的header中,调用相应接口时出现跨域。 在此次跨域出现前实际上已经在Flask通过flask_cors配置了跨域解决方案,因此跨域的产生是让我十分不解的,又由于问题比较奇特在搜索引擎中没有找到很好的解决方案(也可能是我不知道怎么描述,没有搜出来),因此自己重新研究可以一下跨域,又有了一些新收获。 分析相信使用前后端分离的开发者在开发之初就会碰到跨域的问题,跨域的解决方案有很多种,我选择通过后台解决。 跨域是浏览器同源策略导致的问题,网上相关文章很多,此处不赘述。备注一点:postman不会产生跨域。Flask解决跨域的方案非常简单,以下代码即可完成。 from flask_cors import CORSCORS(app,supports_credentials=True@app.after_requestdef after_request(resp): resp = make_response(resp) resp.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8080' resp.headers['Access-Control-Allow-Methods'] = 'GET,POST' resp.headers['Access-Control-Allow-Headers'] = 'x-requested-with,content-type' return resp配置完成后,一直也没有没有出过问题,因此也就没有去进一步了解其配置的含义,直到这一次被卡住,让我不得不去了解一下跨域我究竟配置了什么东西? 其实这个问题的关键点就在于那三条配置:Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers.他们到底代表什么含义? 首先Access-Control-Allow-Origin,字面上的意思,配置这个可以允许相应的源来访问后台资源,网上大多在此处的写的是*,也即允许所有源,这样很不安全,由于我此处是本地开发阶段,Vue的启动端口是8080,所以我写的是http://127.0.0.1:8080,根据你的开发需要改成自己需要的三元组即可。 其次Access-Control-Allow-Methods,也是字面上的意思,允许用的HTTP的Method有哪些,GET,POST是最常见的,此处只写了两个,如果你需要使用其他Method,在这里要写进来,否则也会会出现跨域问题。 以上两个配置都没有问题,问题在了最后一部分: Access-Control-Allow-Headers,和上面两个一样,字面的意思,之所以是她出问题了,是因为我们在前端给HTTP请求添加了一个自定义的字段token,而这不在许可范围内(许可的只有x-requested-with和content-type ),因此被判定为了不符合同源策略的非法请求,所以我们只需要将自定义的header添加进去即可。答案已经出来了。 继续挖一下,这个字段的两个含义分别还是什么?x-requested-with,content-type. x-requested-with是一个用来判断客户端请求是否由Ajax发起的,所以和Axios有什么关系?答案是:没有关系...可以直接删了。贴上这段代码的人或者是默认了发起请求使用的是Ajax,又或者没有分析字段含义,所以很直接贴了这段代码,但是对于使用Axios的开发者来说,这个字段不是必然的。 content-type: 指明POST请求的数据格式以及编码方式。数据格式最常见的莫过于josn,其形式如下:application/json在后端打印出POST请求的HTTP Header,就会发现有下面这条和数据。 Content-Type: application/json;charset=UTF-8解决方案在Access-Control-Allow-Headers中添加上自定义的header名称,整体如下: from flask_cors import CORSCORS(app,supports_credentials=True@app.after_requestdef after_request(resp): resp = make_response(resp) resp.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8080' resp.headers['Access-Control-Allow-Methods'] = 'GET,POST' resp.headers['Access-Control-Allow-Headers'] = 'content-type,token' return resp其实直接删掉Access-Control-Allow-Headers这条配置,也能解决问题,但是枚举出所有固定情形当然是更安全的。要获取更多Haytham原创文章,请关注公众号"许聚龙":

September 10, 2019 · 1 min · jiezi

宜信开源漏洞管理平台『洞察』部署指南

『洞察』是一个集成应用系统资产管理、漏洞全生命周期管理、安全知识库管理三位一体的管理平台。 『洞察』使用了Python语言进行开发,利用Flask框架+MySQL+Docker部署实现。 一、部署和启动mysqldocker pull mysql:5.7.13docker run -d -p 127.0.0.1:6606:3306 \--name open_source_mysqldb \-e MYSQL_ROOT_PASSWORD=root \mysql:5.7.13二、创建数据库和账号权限配置$ mysql -h 127.0.0.1 -P 6606 -u root -pEnter password:rootmysql> CREATE DATABASE IF NOT EXISTS vuldb DEFAULT CHARSET utf8 COLLATE utf8_general_ci;mysql> grant all on vuldb.* to vuluser@'%' identified by 'vulpassword';mysql> flush privileges;mysql> quit三、部署和启动APP1、下载源码$ git clone https://github.com/creditease-sec/insight.git2、修改srcpm/config.py 配置文件1)修改公司邮箱后缀 #公司邮箱后缀限制,只能使用公司邮箱注册账号。CORP_MAIL = '@qq.com'2)修改邮件CC抄送列表 #平台发送的每封邮件的邮件抄送列表,可以设置发送给安全部邮箱列表,可自行修改,也可以保持不变为空 ''' 示例: CC_EMAIL = ['xxx1@creditease.cn', 'xxx2@creditease.cn', ] ''' CC_EMAIL = [ ]3)修改开发模式的邮箱服务器和发件邮箱配置 ...

July 15, 2019 · 2 min · jiezi

Flask学习笔记

1 认识Flask1.1 Flask简介Flask诞生于2010年,是Armin ronacher(人名)用Python语言基于Werkzeug工具编写的轻量级Web开发框架,它主要面向需求简单的应用。Flask本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login),都需要第三方的扩展来实现。比如,可以用Flask-extension加入ORM、窗体验证工具,文件上传、身份验证等。Flask没有使用默认的数据库,你可以选择MySQL,也可以选择NoSQL,其WSGI工具箱采用Werkzeug(路由模块),模板使用Jinjia2。可以说Flask框架的核心就是Werkzeug和Jinjia2。Python最出名的框架要属Django,此外,还有Flask、Tornado等框架。虽然Flask框架不是最出名的框架,但是Flask应该是最灵活的框架之一,这也是Flask广受开发者喜欢的原因。 1.2 与django对比django提供了: django-admin快速提供了创建工程目录manage.py管理工程目录orm模型(数据库抽象层)admin 后台管理站点缓存机制文件存储系统用户认证系统1.3 Flask扩展包Flask-SQLalchemy:操作数据库Flask-migrate:管理迁移数据库Flask-script:插入脚本Flask-Login:认证用户状态Flask-RESTful:开发REST API的工具Flask-Bootstrap:集成前端Twitter Bootstrap框架Flask-Moment:本地化日期和时间2 Flask:Hello World一个最简单的flask程序: from flask import Flask# 创建flask的应用对象app = Flask(__name__) # __name__表示当前的模块名字,flask以这个模块所在的目录为总目录,默认这个目录中的static为静态目录,templates为模板目录@app.route("/")def index(): """定义视图函数""" return "hello flask"if __name__ == "__main__": """启动falsk程序""" app.run(debug=True)

July 2, 2019 · 1 min · jiezi

第6天在Flask应用中添加个人主页

原文: http://www.catonlinepy.tech/声明: 原创不易,未经许可,不得转载 1. 你将学会什么今天的教程主要给大家介绍,如何在Flask应用中添加个人主页以及在个人主页中如何上传用户头像。教程中的代码都会托管到github上,猫姐一如既往的强调,在学习本课内容时一定要亲自动手实现代码,遇到问题再到github上查看代码,如果实在不知道如何解决,可以在日志下方留言。 2. 个人主页的实现2.1 项目目录的创建在创建个人主页之前,先来创建今天的项目目录,猫姐直接将第5天的day5目录复制后改成day6,然后程序里面的userauth_demo目录改成userprofile_demo目录,并将代码中的userauth_demo改为userprofile_demo。在此基础上,我们还需要创建如下文件和目录: # 注意:以下所有的操作都必须在虚拟环境中进行# 在userprofile_demo新建文件utils.py文件,此文件用来保存一些功能独立的小函数(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo$ touch utils.py# 在userprofile_demo目录下新建static目录,此目录用来保存css,js及图片文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo$ mkdir static# cd到static目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo$ cd static# 在static目录中新建profile目录,用户上传的头像图片将保存到该目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo/static$ mkdir profile# 在templates目录中新建account.html文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo/templates$ touch account.html最终,我们得到今天项目的目录结构如下(使用tree命令得到): (miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ tree day6day6├── run.py└── userprofile_demo ├── config.py ├── database.db ├── forms.py ├── __init__.py ├── models.py ├── routes.py ├── static │   └── profile │   └── default.jpg ├── templates │   ├── account.html │   ├── index.html │   ├── layout.html │   ├── login.html │   └── register.html └── utils.py2.2 为个人主页添加入口链接通常,在用户登录后,在主页导航栏中会有一个用户名的超链接,当用户点击这个超链接时,就会跳转到用户的个人主页。下面,我们在layout.html文件的导航栏中添加个人主页的入口: ...

June 27, 2019 · 3 min · jiezi

关于Flask-Schedule

Flask ScheduleFlask-APSchedulera Flask extension supported for the APScheduler which is a Task scheduling library for Python.how to usefrom flask import Flaskfrom flask_apscheduler import APSchedulerclass Config(object): JOBS = [ { 'id': 'job1', 'func': '__main__:job1', 'args': (1, 2), 'trigger': 'interval', 'seconds': 10 } ]def job1(a, b): print(str(a) + ' ' + str(b))if __name__ == '__main__': app = Flask(__name__) app.config.from_object(Config()) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() app.run()APScheduler Jobadd jobdate 日期触发: 一次性指定日期 ...

June 21, 2019 · 1 min · jiezi

怎么选择确定个人博客网站的主题

除非你的博客完全是为了满足自己的乐趣,否则你肯定希望获得读者。因此,考虑别人可能喜欢的内容非常重要。多年来我一直在关注博客圈,在吸引读者方面,有些方法确实非常有效,下面是一些选择博客主题的实用技巧。 读者想要什么1. 读者想要解决问题 人们对此感到沮丧吗?你有解决方案吗?这是大多数博主成功的方式。很多人非常喜欢写作,但对如何建设一个博客网站毫无头绪,所以我分享了一系列文章、提示、工具和教程来帮助解决这个问题。 2. 读者想要减轻他们的恐惧 人们害怕什么?你怎样能帮助缓解这些恐惧?也许有些人曾经失去过人生中最重要的东西,分享你的希望和治愈故事对很多人都非常有帮助。此外,你还可以为那些面临失业或金融灾难的人提供帮助。 3. 读者想要学习新东西 如果有一样知识或技能,只有你知道,那你会怎么做?为什么不把自己擅长的东西交给别人呢?也许你是编织高手,也许你有写作诀窍,或者你有一种独特的教学方式。很多人都有一些难以解决的烦恼,但不知道从哪里开始,教他们吧。 4. 读者希望达成目标 人们有哪些共同的目标?你有没有设定并达到一些重要目标?你是如何做到这一点的?分享出来并激励他们吧。最容易想到的可能是健身、减肥、摆脱债务。过于远大的目标可能会令人沮丧和孤独,但有些人确实一直在创造奇迹。 5. 读者想要放松一下 你有一个引人入胜的故事吗?你过着有趣的生活吗?你是一个搞笑的人吗?每个人都需要停下来休息,大量博客纯粹是为了娱乐。不过,我认为这是一条棘手的道路,因为在互联网、杂志和电视上,娱乐的内容并不缺乏,但这并不意味着这条路完全不可行。关键是提供一些独特的东西,当然,作为奖励,你和别人都可以获得快乐。例如,如果你家里养了一头骆驼,谈谈你是怎样养骆驼的。 博客主题提示现在你已经了解了其他人正在寻找的内容,这里列出了一些提示,可以帮助你形成一个更好的博客主题。 我的博客主题是好的吗? 在开始撰写文章之前,你可能要先考虑自己的博客主题是否是一个好主意,例如: 是否会有人阅读?这个主题是否能赚钱?这可能是两个最常见的问题,毕竟,谁想把时间和精力浪费在没有访客,或者无法赚钱的事物上呢? 事实上,几乎每个你能想到的主题都能建立一个博客,从尴尬的家庭照片,到美味的佳肴食谱,以及介于两者之间的一切内容。简而言之,话题本身通常并不是问题,大多数主题都能吸引访客,并让你从中赚到一些钱。 主要的问题在于: 你对这个话题的热情是否具有感染性,那些感兴趣的人会被你吸引,但那些不感兴趣的人也会变成这样吗?你能够持之以恒地写下去吗?大多数博客需要很长时间才能获得收入,并产生动力。一旦到了这个时候,你就必须继续前进。所以,你必须长期参与其中。怎样在一个竞争市场中脱颖而出?互联网上有数以百万计的博客,有很多人可能正在撰写跟你主题相同(或非常相似)的文章,你如何保持独特性?世界各地的读者会喜欢你的文章,并分享它们吗?我的博客主题是否能够赚钱?博客的好处是几乎任何话题都可以产生收入,但是没有办法保证特定主题会产生多少收入。例如,有很多摄影博客赚取了大量利润,但更多的却没赚多少。 重要的不是主题,而是这个主题呈现的资源。以某种方式书写出来,吸引更多的读者阅读,然后与他们建立关系,创建或推广他们愿意购买的产品。 专业提示:有一种方法可以快速判断特定主题或 Niche 市场是否赚钱,那就是在 Google 、百度中搜索特定关键字。如果页面顶部拥有大量广告,那就说明这个市场非常有利可图。但需要注意的是,缺少广告并不一定表明这个市场无法赚钱,更深入的挖掘可能会让你另辟蹊径。 为别人而写很多新博主在创建博客时都没有超出自己的兴趣,毫无疑问,你的博客应该是你的延伸,但如果你的文章不是为别人带来好处,那么写日记可能更好。 选择一个Niche不要写出任何想到的东西,试着围绕一个特定的话题来写,这将成为你的 Niche。不仅实现起来更容易,也会为你带来更多读者。虽然不是必需的,但 Niche 提供了重点和方向,让你的博客更加易于理解。 选定的 Niche 太宽泛还是太具体?如果你的博客主题过于宽泛,那么与其他的博客和网站相比,你将很难脱颖而出。另一方面,如果你的主题太过狭隘,那么感兴趣的读者群体将非常小,导致你无法获得足够的收入。 例如,摄影是一个非常广泛的主题,但美国50人镇的摄影无法给你带来很多观众,黑白摄影更好,张家界黑白摄影则可能更好一些。你的目标是找到一个包含大量感兴趣的人,以及大量潜在子主题的主题,这需要做一些研究来缩小范围。 这个 Niche 是否已经饱和?回到互联网诞生之初,那个时候的在线博客寥寥无几,你几乎可以挑选任何主题。但发展到现在,几乎所有的 Niche 都已经被充分挖掘,因此想要占据一席之地非常困难。当你想到一个主题时,请去搜索关于这个主题的数十个热门博客,然后再决定自己的主题是否合适。 然而,仅仅因为一个Niche很大并不意味着你要避开,毕竟,一个巨大的 Niche 意味着它有一个不错的市场。如果你能从中脱颖而出,那么必将受益无穷。 这个 Niche 的读者是否愿意花钱?如果你希望创造不错的收入,那么这是一个重要的问题,想想你的 Niche 与读者之间的交集。例如,如果你希望推广高端服装产品,那么针对陷入困境的大学生可能不是一个好主意。 另一种看待这个问题的方式:这个 Niche 的其他博客赚钱吗?那些博客有广告商吗?他们是否活跃,充满吸引力并且在不断增长? 你有足够的内容可写吗?选择一个可以定期和永久撰写的主题,请记住,你很有可能需要长期处于这种状态。如果你每周发布一次,那么每年只有 52 篇文章;一周三次?156 篇;一周五次?260 篇…… 而这只是刚刚开始!不要选择一些过于狭窄的东西,以免在几周或几个月之后,发现没有内容可写。 有一个好方法可以测试这个问题:集体讨论与你的主题相关的文章或子主题,如果你能够轻松提出几十个关于如何分支的列表,那么这可能是一个好兆头。但如果你想不到很多,那可能就需要重新考虑这个主题。此外,解决此问题的另一种方法是多个作者一起来发布文章。 选择一个你真正热爱的话题如果你对所写的内容没有真正的兴趣,那将是一种可怕的拖累和负担。在现实生活中,如果你可以和朋友就一个话题谈论几个小时,那么这就是用于创建博客的一个很好的主题。 选择一个可以成为权威的 Niche 市场这里的关键是思考细分领域,或者选择一个较小的 Niche 市场,然后深入一个主题来讨论。尽管可能会有一小部分的潜在读者,但你也可以更快地获得更多关注。 ...

June 20, 2019 · 1 min · jiezi

第5天下篇在Flask应用中使用用户认证FlaskLogin

原文: http://www.catonlinepy.tech/声明: 原创不易,未经许可,不得转载 接第5天上篇的内容,我们接着介绍用户注册功能的实现3. 用户的注册3.1 注册表单及前端渲染根据经验,当我们在登录某个应用时,如果我们没有在平台注册过账号,平台会提醒我们注册一个账号,注册成功后方可进行登陆。与登录页面的实现类似,前端的注册页面也需要后台传入注册表单,然后Jinjia2将表单渲染成前端浏览器能解析的form表单。由于注册页面通常要求用户输入电子邮箱,用户名,密码以及确认密码,下面我们首先在forms.py文件中增加注册表单类registerForm: # forms.py文件中新增的内容# ..# 在wtforms中还需要导入StringField,ValidationError字段from wtforms import PasswordField, BooleanField, SubmitField, StringField,ValidationError# 在validators验证器中还需要导入EqualTo, InputRequired验证器from wtforms.validators import DataRequired, Length, EqualTo, InputRequired# 在def两个函数中使用到User模型,需要导入from userauth_demo.models import User# ..class registerForm(FlaskForm): username = StringField(u'用户名', validators=[DataRequired(), Length(min=2, max=20)]) email = EmailField(u"邮箱", validators=[DataRequired(), Length(min=2, max=20)]) password = PasswordField(u'密码', validators=[InputRequired(), EqualTo(u"confirm_password", message="两次输入的密码不一致!")]) confirm_password = PasswordField(u'确认密码', validators=[DataRequired()]) submit = SubmitField(u'注册') def validate_username(self, username): user = User.query.filter_by(username=username.data).first() if user is not None: raise ValidationError('该用户名已被注册!') def validate_email(self, email): user = User.query.filter_by(email=email.data).first() if user is not None: raise ValidationError('该邮箱已被注册!')在上面的注册表单类registerForm中,为了减少输入错误的风险,使用到了password和confirm_password,password密码中用到了EqualTo验证器,它需要与confirm_password中输入的密码一致才通过验证。这个注册表单中还使用到了两个自定义函数,它们以validate开头,后面接上_<fieldname>,这里的filedname都是WTForms提供的自定义字段,当前端用户点提交按键时,就会触发这两个函数的调用。在这里,当用户提供了注册信息后,会在数据库中的user表中进行查询,如果用户名和邮箱不存在,则可顺利的完成注册,否则,就会执行if语句内的错误提醒程序,这些错误提醒会在浏览器页面渲染出来。 ...

June 17, 2019 · 3 min · jiezi

第5天上篇在Flask应用中使用用户认证FlaskLogin

原文: http://www.catonlinepy.tech/声明: 原创不易,未经许可,不得转载 1. 你将学会什么今天的课程主要给大家介绍一款非常好用的Flask插件—Flask_Login,该插件主要用来管理用户的登陆状态。通过今天的学习,你将学会如何维护用户的登录、登出状态。教程中的代码都会托管到github上,猫姐一再强调,在学习本课内容时一定要亲自动手实现代码,遇到问题再到github上查看代码,如果实在不知道如何解决,可以在日志下方留言。 2. 使用Flask_Login2.1 项目目录结构的创建当用户登录某个应用时,应用需要记住该用户的登陆状态。在开发过程中,如果我们自己造轮子,去实现管理用户登录状态的代码,就会浪费大量时间。但现实中,我们往往需要聚焦在业务上的开发,Flask_Login就是前人造好的管理用户登录状态的轮子,并且这个插件上手起来毫无难度。照旧,在正式使用Flask_Login插件之前,还是先建立今天的项目目录,如下: # 进入到虚拟环境目录,激活虚拟环境maojie@Thinkpad:~/flask-plan/$ source miao_venv/bin/activate# 到flask-course-primary目录下创建第五天的课程day5目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ mkdir day5# 进入day5目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ cd day5# 新建userauth_demo目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5$ mkdir userauth_demo# 进入到userauth_demo目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5$ cd userauth_demo/# 在userauth_demo目录中新建__init__.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo$ touch __init__.py# 在userauth_demo包中新建routes.py路由文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo$ touch routes.py# 在userauth_demo包中新建models.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo$ touch models.py# 在userauth_demo包中新建forms.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo$ touch forms.py# 在userauth_demo包中新建config.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo$ touch config.py# 在userauth_demo包中新建templates目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo$ mkdir templates# 进入到templates目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo$ cd templates/# 在templates目录中新建layout.html(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo/templates$ touch layout.html# 在templates目录中新建login.html(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo/templates$ touch login.html# 在templates目录中新建register.html(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo/templates$ touch register.html# 在templates目录中新建index.html(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/userauth_demo/templates$ touch index.html# 在day5目录下新建run.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5/$ touch run.py最终,我们得到今天项目的目录结构如下(使用tree命令得到): ...

June 17, 2019 · 3 min · jiezi

聊聊-Python-的内置电池

本文原创并首发于公众号【Python猫】,未经授权,请勿转载。 原文地址:https://mp.weixin.qq.com/s/XzCqoCvcpFJt4A-E4WMqaA (一) 最近,我突然想到一个问题:相比其它语言,有哪些概念或习惯叫法是 Python 特有的? 在朋友圈提出这个问题后,我得到最多的回复是——Pythonic 。这个回复一点都不意外,名字中自带 Python 的,当然是特有的啦,与它相似的,还有 Pythonista 。 这两个词是啥意思呢?Python 圈内流传着一个说法“人生苦短,我用 Python”,人们相信存在着最佳的实践方式,采用这种方式是最美的、最高效的、最优雅的,也即是 Pythonic ,而这样做的人(或以此为追求的人)则自称是 Pythonista。这个称号是有别于 Pythoner 或者 Pythonist 的,简单地说就是,它更有追求、更有逼格。 除了以上两个,Python 还有众多独特的叫法,例如终生仁慈独裁者、装饰器、上下文管理器、推导式与生成式、鸭子类型、猴子补丁、魔术方法、GIL、内置电池,等等。它们有的并不是 Python 所原创或独有,但是却因为它才广为人知,它们在 Python 中是代表性的存在物。 (二) 这些内容都很有意思,本文唯独想聊聊它——内置电池 。 Batteries Included 这个叫法是 Python 特有的,它指的是 Python 拥有“内置电池”,也就是自带丰富多样的标准库,开箱即用,动力十足。 在《PEP 206 -- Python Advanced Library》中,它提出了“内置电池的哲学”(Batteries Included Philosophy):拥有丰富而通用的标准库,无需用户单独下载就能立即使用。还说这使得 Python 领先于很多项目。 根据官方文档显示,Python 内置了 200 多个标准库,类型丰富多样,包括字符处理、数据类型、数值计算、文件处理、并发执行、网络通信、多媒体服务、图形界面、调试与开发、以及操作系统专有服务等等。 内置电池为 Python 提供了一种自给自足的能力(self-sufficient),在大多数情况下,用户不需要再去下载和安装单独的软件包,因此也免去一大堆的依赖问题的折磨。 (三) 某些编程语言中也有内置电池的概念,例如 Perl、Ruby、PHP等等,还有的语言会强调自己内置了强大的功能,例如 Erlang(一切皆进程)、Go(goroutine 机制)。 然而,这个叫法在 Python 中被叫得最响,也被推广到了技术生态中的其它项目里,几乎成了 Python 的专有名词。 在维基百科上搜索“Batteries Included”,该条目有 4 个解释,其中之一表明它是 Python 的 Motto ,这个词的意思是座右铭、格言、箴言,足见分量之重了吧。 ...

June 15, 2019 · 1 min · jiezi

第4天在Flask应用中使用数据库FlaskSQLAlchemy

原文: http://www.catonlinepy.tech/声明:原创不易,未经许可,不得转载 1. 你将学会什么接第3天表单的使用课程,今天的课程主要涉及到与数据库相关的两个插件的使用,一个是Flask_SQLAlchemy,另外一个是Flask_Migrate。通过今天的学习,你将学会如何对数据库进行基本的操作,以及如何完成数据库的迁移。教程中的代码都会托管到github上,猫姐不厌其烦地强调,在学习本课内容时一定要自己尝试手敲代码,遇到问题再到github上查看代码,如果实在不知道如何解决,可以在日志下方留言。 2.使用Flask_SQLAlchemy管理数据库2.1 Flask_SQLAlchemy的安装对于web后台开发工作,必须要掌握的一项技能便是对数据库的CRUD(create, read, update, delete)操作,如果开发过程中直接使用原生的sql语句对数据库进行操作,将是非常痛苦的事件(毕竟sql语句有很多反人类的设计)。Flask_SQLAlchemy插件将开发人员从这个泥潭中解救出来了,我们只需要使用Python的类就能轻松的完成对表的增删改查操作,并且该插件还支持多种数据库类型,如MySQL、PostgreSQL、和SQLite等。 在进入正式学习之前,我们照旧要建立今天的项目目录,如下: # 进入到虚拟环境目录,激活虚拟环境maojie@Thinkpad:~/flask-plan/$ source miao_venv/bin/activate# 到flask-course-primary目录下创建第四天的课程day4目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ mkdir day4# 进入day4目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ cd day4# 新建database_demo目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ mkdir database_demo# 进入到database_demo目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ cd database_demo/# 在database_demo目录中新建__init__.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ touch __init__.py# 在database_demo包中新建routes.py路由文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ touch routes.py# 在day4目录下新建run.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/$ touch run.py安装Flask_SQLAlchemy插件还是使用pip命令,如下:# 注意一定要在虚拟环境中(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ pip install Flask_SQLAlchemy2.2 配置Flask_SQLAlchemy我们的教程中使用的数据库是SQLite(Linux),主要原因是SQLite足够简单,不需要进行任何配置便可使用,十分适用于入门。下面在__init__.py文件中配置数据库,如下所示: # 在__init__.py文件中的内容from flask import Flask# 从flask_sqlalchemy导入SQLAlchemy类from flask_sqlalchemy import SQLAlchemyimport os# 通过Flask创建一个app实例app = Flask(__name__)basedir = os.path.abspath(os.path.dirname(__file__))# Flask_SQLAlchemy插件从SQLALCHEMY_DATABASE_URI配置的变量中获取应用的数据库位置app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'database.db')# 通过SQLAlchemy创建db实例,表示程序使用的数据库,并且db能够使用Flask_SQLAlchemy的所有功能db = SQLAlchemy(app)2.3 构建数据库模型数据模型通常用来定义数据库中的表及表中的字段。下面代码中的User类和Post类就代表了数据库中的两张表(官方叫法是数据模型)。后面我们会看到,通过这里定义的两个Python类,我们就可以非常容易的完成表的增删改查,以下是routes.py文件中的内容。 ...

June 11, 2019 · 4 min · jiezi

喵星在线Flask博客上线布署过程

原文:http://www.catonlinepy.tech/声明:原创不易,未经许可,不得转载 这篇文章,猫姐将讨论如何将flask程序部署到ubuntu服务器上。其中,部署过程涉及到Flask,Nginx,Gunicorn,Supervisor的安装使用。通过学习今天的文章,你将学会部署自己开发的Flask web程序,真正让自己开发的应用上线运行。 0. Flask Web应用处理请求的过程 1. 安装相应的依赖环境使用ssh username@ip 进入云主机的shell环境,并在云主机上安装需要依赖环境,使用如下命令安装: sudo apt-get install nginx supervisor python-pip python3-venvssh的使用方法:ssh命令,加上@前面是云主机服务器的用户名,@后面是云主机服务器的ip地址。 2. 创建虚拟环境如果云主机中没有创建虚拟环境,那么我们有必要创建一个。虚拟开发环境的主要作用是为了将web项目所用的各种库与操作系统自带的python库隔离开来,这样做的好处是开发环境与系统环境隔离,环境之间不会相互影响,特别是对于多人协作的大型项目开发,建立虚拟环境是非常有必要的。下面开始创建虚拟环境: # 使用下面命令,创建一个虚拟开发环境$ python3 -m venv <虚拟环境的名字># 激活刚才建立的虚拟开发环境,(这里我们创建一个名为miao_venv的虚拟环境)$ python3 -m venv miao_venv$ source maio_venv/bin/activate# 安装flask web程序所需要的python包$ pip installl -r requirement.txt激活虚拟环境后,进入run.py文件的目录,在终端中输入python run.py,就可以将程序运行起来了。但是,这样生产环境中将flask程序运行起来是不安装的,并且处理用户请求的能力也非常不足。所以,我们应该去使用gunicorn这个工具去运行我们的Flask web程序。 3. 设置GunicornGunicorn(“Green Unicorn”)是一个unix上被广泛使用的高性能的Python WSGI UNIX HTTP Server。和大多数的web框架兼容,并具有实现简单,轻量级,自动管理多个worker进程,高性能等多个特点。 采用如下命令安装gunicorn: pip install gunicorngunicorn运行Flask web程序的方法也非常简单,如下命令即可达到目的: gunicorn filename(run):变量名(app) -b localhost:端口号 &# 解释:filename指的是能够将flask程序运行起来的python文件,如run.py的文件名run。冒号后面的是flask实例化的app变量。端口号是在网页中输入的url的端口号查看端口服务是否生效,可以使用下面的命令: # 在命令行中输入:$ ps aux|grep gunicorn# 下面是效果(miao_venv) root@vultr:~/flaskblog# ps aux|grep gunicornroot 4124 0.0 0.0 11020 980 pts/0 S+ 10:35 0:00 grep --color=auto gunicornroot 11117 0.0 2.1 33308 21400 ? S May28 0:10 /root/blog_venv/bin/python3 /root/blog_venv/bin/gunicorn run:app -b localhost:5006root 11120 0.0 7.6 160528 77160 ? S May28 0:17 /root/blog_venv/bin/python3 /root/blog_venv/bin/gunicorn run:app -b localhost:5006这样就能够使用gunicorn进程去监听5006的端口。现在gunicorn进程是工作在后台的,但只有一个gunicorn进程会处理web请求,当用户访问量过大时,网站的并发处理能力非常弱。最理想的方法是,使用supervisor工具去启动并监控多个gunicorn进程。 ...

June 8, 2019 · 1 min · jiezi

sqlalchemy使用count时遇到的坑

在用flask-sqlalchemy对一个千万级别表进行count操作时,出现了耗时严重、内存飙升的情况。要统计出一天内车辆访问次数,原代码如下: car_visit_counts = CarVisit.query.filter( CarVisit.park == car_visit.park, CarVisit.plate_number == car_visit.plate_number, CarVisit.visited_at >= today_start_time(),).count()发现代码运行特别慢,所以把生成的sql打印出来看一下: SELECT COUNT(*) AS count_1FROM ( SELECT car_visits.id AS car_visits_id , car_visits.park_id AS car_visits_park_id , car_visits.store_id AS car_visits_store_id , car_visits.car_id AS car_visits_car_id , car_visits.brand_id AS car_visits_brand_id , ... FROM car_visits WHERE %(param_1)s = car_visits.park_id AND car_visits.plate_number = %(plate_number_1)s AND car_visits.visited_at >= %(visited_at_1)s ) AS anon_1可以发现进行了一次子查询,这样的话会生成临时表,效率低下,将原语句改变一下: car_visit_counts = db.session.query(func.count(CarVisit.id)).filter( CarVisit.park == car_visit.park, CarVisit.plate_number == car_visit.plate_number, CarVisit.visited_at >= today_start_time(),).scalar()此时在看一下打印的sql语句: ...

June 7, 2019 · 1 min · jiezi

Gunicron-gevent-Mongodb数据库连接一直增加不释放

问题描述使用Flask开发的Web服务,部署在服务器上使用的是gunicorn manage:app -k gevent -w 4 某日告警,说浏览器崩了,当时急急忙忙的重启,搞好了,因为所有的服务都正常运行,后面查看日志,也没有发现什么特别的地方,最终感觉因该是MongoDB连接数满了,本地测试发现确实是连接数一直增加,不会释放。解决过程关于Gunicron什么是Gunicron:是一个unix上被广泛使用的高性能的Python WSGI UNIX HTTP Server。和大多数的web框架兼容,并具有实现简单,轻量级,高性能等特点。 深入理解uwsgi和gunicorn网络模型为什么要使用Gunicron:用于接受http请求并转换为WSGI协议,以供实现了WSGI协议的flask使用,并且gunicorn得益于gevent等技术,大幅度提高了性能,在生产环境以替代框架自带的WSGI server。生产环境配置gevent==1.3.6greenlet==0.4.14gunicorn==19.9.0pymongo==3.7.0 mongodb连接代码 def __init__(self): config_name = os.getenv('FLASK_CONFIG') or 'default' base_config = config[config_name] # object self.client = MongoClient(host=base_config.MONGO_HOST, port=base_config.MONGO_PORT) db_name = base_config.MONGO_NAME self.db = self.client[db_name]修改方案参考pymongo: MongoClient opened before fork错误排解fork是启动新进程的方法,由于MongoClient不是进程安全的,所以不可以将该实例从父进程中复制到子进程当中。在这个flask应用中,flask使用gunicorn作为网关接口,在启动的时候会启动一个主进程和多个子进程,也就是master/workers,这个时候就出现了MongoClient实例在进程之间的传递。为了解决这个问题,在实例化MongoClient对象的时候要加上connect=False参数。 def __init__(self): config_name = os.getenv('FLASK_CONFIG') or 'default' base_config = config[config_name] # object self.client = MongoClient(host=base_config.MONGO_HOST, port=base_config.MONGO_PORT, maxIdleTimeMS=300000, connect=False) db_name = base_config.MONGO_NAME self.db = self.client[db_name]参考用gevent来host wsgi server,mysql能否长连接python – Mongo连接从未发布 – Django和Mongoengine在gevent上使用gunicorn

June 4, 2019 · 1 min · jiezi

sqlalchemy-配置多连接读写库后的关系设置

前言一般来说,解决sqlalchemy 连接多个库的最简单的方式是新建两个或多个db.session 相互没有关联,modle配置不同的db.session来连接,这样的话,relationship正常配置就行,不用特殊配置.如果这样解决的话,也就不用看下面的配置了 # -*- coding:utf-8 -*-import flaskfrom flask_sqlalchemy import SQLAlchemy # Flask-SQLAlchemy 2.3.2from datetime import datetimefrom sqlalchemy.orm import backref, foreign # SQLAlchemy 1.3.1app = flask.Flask(__name__)app.config['DEBUG'] = Trueapp.config['SQLALCHEMY_BINDS'] = { 'read_db': 'mysql://reader:test@127.0.0.1:3306/test?charset=utf8', 'write_db': 'mysql://writer:test@127.0.0.2:3306/test?charset=utf8'}app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Falseapp.config['SQLALCHEMY_ECHO'] = Falsedb = SQLAlchemy(app)class RDriver(db.Model): __bind_key__ = 'read_db' __tablename__ = 'driver' # __table_args__ = {'schema': 'test'} # 不可以加上 id = db.Column(db.Integer, primary_key=True, autoincrement=True) fk_user_id = db.Column(db.Integer, db.ForeignKey("user.id")) driver_name = db.Column(db.String(7)) create_time = db.Column(db.TIMESTAMP, default=datetime.now)class RUser(db.Model): __bind_key__ = 'read_db' __tablename__ = 'user' # __table_args__ = {'schema': 'test'} id = db.Column(db.Integer, primary_key=True, autoincrement=True) user_name = db.Column(db.String(32), index=True, unique=True) user_password = db.Column(db.String(32)) create_time = db.Column(db.TIMESTAMP, default=datetime.now) update_time = db.Column(db.TIMESTAMP, default=datetime.now) # 如下的五种方式都是可以的 # driver_fk = db.relationship("RDriver", foreign_keys='RDriver.fk_user_id') # driver_fk = db.relationship("RDriver", primaryjoin=lambda: RDriver.fk_user_id == RUser.id, viewonly=True) # driver_fk = db.relationship("RDriver", primaryjoin=RDriver.fk_user_id == id) fk_driver = db.relationship("RDriver", primaryjoin='RDriver.fk_user_id == RUser.id') # driver_fk = db.relationship("RDriver", backref=db.backref('user', lazy=True), # primaryjoin=lambda: RDriver.fk_user_id == RUser.id, viewonly=True)class WDriver(db.Model): __bind_key__ = 'write_db' __tablename__ = 'driver' __table_args__ = {'schema': 'test', 'extend_existing': True} # 这个配置很关键 id = db.Column(db.Integer, primary_key=True, autoincrement=True) fk_user_id = db.Column(db.Integer, db.ForeignKey("test.user.id")) # test.user.id很关键 plate = db.Column(db.String(7)) create_at = db.Column(db.TIMESTAMP, default=datetime.now)class WUser(db.Model): __bind_key__ = 'write_db' __tablename__ = 'user' __table_args__ = {'schema': 'test', 'extend_existing': True} # 这个配置很关键 id = db.Column(db.Integer, primary_key=True, autoincrement=True) hash = db.Column(db.String(256), nullable=False) user_no = db.Column(db.String(32), index=True, unique=True) # 用户工号 create_time = db.Column(db.TIMESTAMP, default=datetime.now) update_time = db.Column(db.TIMESTAMP, default=datetime.now) # 以下五种方式都是可以的 # fk_driver = db.relationship("WDriver", foreign_keys='WDriver.fk_user_id', uselist=False) # fk_driver = db.relationship("WDriver", primaryjoin=lambda: WDriver.fk_user_id == WUser.id) fk_driver = db.relationship("WDriver", primaryjoin=WDriver.fk_user_id == id) # fk_driver = db.relationship("WDriver", primaryjoin='WDriver.fk_user_id == WUser.id') # fk_driver = db.relationship("WDriver", backref=db.backref('test.user', lazy=True), # primaryjoin=lambda: WDriver.fk_user_id == WUser.id)r_user_obj = RUser.query.filter_by().first()print("r_user_obj:", r_user_obj)print("r_user_obj.driver_fk:", r_user_obj.fk_driver)w_user_obj = WUser.query.filter_by(id=2188).first()print("w_user_obj:", w_user_obj)print("w_user_obj.driver_fk:", w_user_obj.fk_driver)参考文档:* https://docs.sqlalchemy.org/en/13/orm/relationship_api.html # 值得细看* https://www.osgeo.cn/sqlalchemy/orm/relationship_api.html # 同上,中文* https://www.cnblogs.com/srd945/p/9851227.html* extend_existing: (False)当表已经存在于元数据中时,如果元数据中存在与column_list中的列同名的列,column_list中同名的列会替换掉元数据中已经有的列* useexisting已被废弃, 新版本使用extend_existing总结关系配置参数真的很多,如下,很容易就会出错,需要多读读官方文档,还有就是建立modle时候尽量简洁,风格统一,不要在数据库层建立外键. ...

June 4, 2019 · 2 min · jiezi

第三天在Flask-Web中如何使用表单

原文:http://www.catonlinepy.tech/声明:原创不易,未经许可,不得转载 1. 你将学会什么通过第三天的学习内容,你将对表单有所了解。知道使用插件来处理应用中的表单,以后遇到表单能够更熟练的使用Flask_WTF插件,并完美的将应用运行起来。今天的学习内容涉及到的代码都会托管到github上,在学习本课内容时,一定要自己尝试手敲代码,遇到问题再到猫姐的github上去查看代码,如果实在不知道如何去解决问题,可以在日志下面留言说明具体情况。 2. 表单的插件简介WTForms作为处理Web表单的插件,是一款支持多个web框架的form组件。Flask-WTF插件对其类WTForms进行封装后以便它能够与Flask完美的结合。在第三天的内容中,我们将引入第一个Flask插件,后续也会对其它的插件进行引入。要知道,插件用得好,可以使web的开发的过程快到飞起。 大家需知道,所有Flask插件都是属于Python的三方包,因此都可以使用pip来进行安装。照旧,我们先进入miao_venv的虚拟环境目录中,将虚拟环境进行激活,然后安装Flask_WTF插件。安装步骤如下: # 进入到虚拟环境目录,激活虚拟环境maojie@Thinkpad:~/flask-plan/$ source miao_venv/bin/activate# 再安装Flask_WTF插件(miao_venv) maojie@Thinkpad:~/flask-plan$ pip install Flask_WTF3. 表单插件的使用今天的代码组织结构如下: # 使用tree命令查看(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day3$ tree.├── form_demo│ ├── __init__.py│ ├── routes.py│ └── templates│ ├── form.html│ └── layout.html└── run.py现在猫姐来创建今天的课程目录,步骤如下: # 在flask-course-primary目录下创建第三天的课程day3目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ mkdir day3# 进入day3目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ cd day3# 新建form_demo(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day3$ mkdir form_demo# 进入到form_demo包(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day3$ cd form_demo/# 在form_demo包中新建__init__.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day3/form_demo$ touch __init__.py# 在form_demo新建routes.py路由文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day3/form_demo$ touch routes.py# 在day3目录下新建run.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day3/$ touch run.py上面将课程目录创建好后,现在我们开始表单的创建。在创建表单之前,猫姐先在__init__.py文件中对Flask_WTF进行配置,如下所示: # __init__.py文件中的内容from flask import Flask # 从flask包中导入Flask类app = Flask(__name__) # 通过Flask类创建一个app实例app.config['SECRET_KEY'] = 'miaojie is great!' # 对Flask_WTF进行配置from form_demo import routes# 解释:对Flask_WTF进行配置主要是为了防跨站请求伪造保护。因为在默认的情况下,Flask_WTF能够保护所有的表单免受跨站请求伪造的攻击(Cross-Site Request Forgery,CSRF),但是在特殊情况下,一些恶意网站会把请求发送到被攻击者已登录的其它网站时就会发生CSRF攻击。为了实现CSRF的保护,Flask_WTF需要程序设置一个密钥,然后它使用密钥去生成一个加密令牌,再用令牌验证请求中的表单数据的真伪。(这里不懂也没事,问题不大!)下面开始一个简单的Web 表单,猫姐开始利用Flask_WTF插件来创建Web表单,表单内容写入到routes.py文件中,如下所示: ...

June 3, 2019 · 2 min · jiezi

第二天在Flask-Web应用中使用模板

原文:http://www.catonlinepy.tech/声明:原创不易,未经许可,不得转载 1. 你将学会什么通过学习第二天的内容,你将会对模板有所了解,并且知道为什么要使用模板,以及使用模板有什么好处。这一天的学习内容涉及到的代码都会托管到github上,猫姐再次强调,在学习本课内容时一定要自己尝试手敲代码,遇到问题再到github上去查看代码,如果实在不知道如何去解决问题,可以在日志下面留言说明具体情况。 2. 什么是模板视图函数主要有两个作用,一个是处理业务逻辑,另一个是给用户返回相关内容。在大型应用中,如果把业务逻辑和返回响应内容放在一起的话,这样会增加代码的复杂度,并且也不好维护。所以模板它就承担了视图函数的另外一个作用:返回响应内容。这样就可以实现业务逻辑和响应内容的分离,将所有的html代码都存放到模板中,而视图函数中只需要专心处理好业务逻辑即可。 下面猫姐通过一个简单的例子来展示为什么要引入模板,以下是这个例子的代码组织结构: (miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ tree.├── day2│ ├── run.py│ └── template_demo│ ├── __init__.py│ └── routes.py└── README.md首先来建立第二天的代码目录,与第一天的课程类似,先激活虚拟环境miao_venv,创建方法都是一样的: # 进入到虚拟环境目录,激活虚拟环境maojie@Thinkpad:~/flask-plan/$source miao_venv/bin/activate# 再到flask-course-primary目录下创建第二天的课程day2目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ mkdir day2# 进入day2目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ cd day2# 新建template_demo目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day2$ mkdir template_demo# 进入到template_demo目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day2$ cd template_demo/# 在template_demo目录中新建__init__.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day2/template_demo$ touch __init__.py# 在template_demo包中新建routes.py路由文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day2/template_demo$ touch routes.py# 在day2目录下新建run.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day2/$ touch run.py在template_demo包的__init__.py中输入如下代码: # 以下是__init__.py文件中的代码,我们逐行进行解释from flask import Flask # 从flask包中导入Flask类app = Flask(__name__) # 通过Flask类创建一个app实例from template_demo import routes # 从template_demo包中导入routes文件里的所有代码在routes.py文件中输入如下代码,包括响应内容中的HTML代码: ...

June 1, 2019 · 2 min · jiezi

第一天你的第一个Flask-Web应用你好喵星在线

原文:http:catonlinepy.tech/ 声明:原创不易,未经许可,不得转载 1. 你将学会什么通过学习第一天的内容,你将学会如何创建你的第一个flask web应用,并且在自己的电脑上运行它。这是第一天的学习内容,所有内容的代码都将托管在github上,猫姐强烈建议各位同学在学习本课内容时,先跟着教程自己尝试手敲代码,遇到问题后再去查看猫姐github上的代码,如果问题实在不知道如何解决,可以在日志下面留言具体说明情况。 2. 运行环境准备在开始写代码前,我们需要准备好flask web应用的运行环境,因此我们需要做3件事:1. 安装python;2. 安装虚拟开发环境;3. 安装flask框架。下面我们详细介绍一下这3个步骤的操作过程: 2.1 第一步:安装python首先是安装Python环境,下面提供了适用于不同操作系统的python安装包链接,大家根据自己的情况安装即可: python for windowspython for Linux/Unix(一般已经安装了python解释器)python for Mac OS X安装python的过程就不详细说明,相信大家都不会有什么问题,猫姐在这里强烈推荐各位同学使用Linux发行版进行学习,虽然开始时可能会遇到一些操作上的问题,但是在使用Linux发行版操作系统时,你会学到更多的其它软件开发的知识,毕竟Linux才是专門为软件开发人员准备的系统。猫姐使用的操作系统是ubuntu18.04,大家也可以使用deepin或centos等其它Linux发行版操作系统。查看python环境是否安装成功的命令如下(打开命令行ctrl+alt+T,输入python): $ pythonPython 2.7.15rc1 (default, Nov 12 2018, 14:31:15) [GCC 7.3.0] on linux2Type "help", "copyright", "credits" or "license" for more information.>>> 如上我们会看到三个大于号(>>>),这表示我们已经进入python交互环境中。我们可以在python交互环境中输入各种python语句,进行python基础知识的学习。如果想要退出python交互环境,只需可以输入exit()即可;在Linux或Mac OS X操作系统中,通过ctrl+d快捷键也可以退出python交互环境。 2.2 第二步:安装虚拟开发环境对于不了解python的同学来说,可能不知道虚拟环境是什么东西,不用担心!如果你能坚持学习完成后面几天的内容,你将会对虚拟环境有更深入的理解。这里猫姐简单解释一下,虚拟开发环境的主要作用是为了将web开发项目所用的各种库与操作系统自带的python库隔离开来,这样做的好处是开发环境与系统环境隔离,环境之间不会相互影响,特别是对于多人协作的大型项目的开发,建立虚拟环境是非常有必要的。猫姐说了这么多“废话”,其实建立虚拟开发环境的过程很简单,主要完成下面几个步骤(猫姐强烈推荐同学们使用python3进行学习): # 安装python3的虚拟环境包管理模块$ sudo apt-get install python3-venv# 使用下面命令,创建一个虚拟开发环境$ python3 -m venv <虚拟环境的名字># 激活刚才建立的虚拟开发环境,(这里我们创建一个名为miao_venv的虚拟环境)$ python3 -m venv miao_venv$ source maio_venv/bin/activate2.3 第三步:安装Flask web框架前面两步,我们已经安装了python开发环境、创建了虚拟开发环境,并激活了虚拟开发环境。大家需要记住,后面我们所有python包的安装都要在虚拟开发环境中进行。如下,当我们激活虚拟开发环境后,会看到命令行前面出现一个括号,括号中的内容为虚拟开发环境的名字: ...

May 31, 2019 · 2 min · jiezi

Python猫荐书系列之七Python入门书籍有哪些

本文原创并首发于公众号【Python猫】,未经授权,请勿转载。 原文地址:https://mp.weixin.qq.com/s/ArN-6mLPzPT8Zoq0Na_tsg 最近,猫哥的 Python 技术学习群里进来了几位比较特殊的同学:一位初三的以编程为兴趣的女生、一位在大学里刚开始执教 Python 的老师、一位四十多岁仍在编程一线的工程师。 自从写公众号以来,我就遇到了各色各样的人,比如,一位代替小学生儿子来加群的牙医父亲、一位多年自由职业每天炒股的前黑客、一位来咨询课程的自学编程的听障人士…… 其实,这些人都是极少数的个例,读者里绝大部分应该都是在校学生、程序员或即将转行成为程序员的人,但是,这些身份特殊的少数人群却触动了我。 一方面,我看到了 Python 的强大吸引力,另一方面,我也看到了 Python 学习群体的多元化。 近些年,为什么各类培训机构会大行其道呢?也许正是因为这庞大而多元的学习人群,想要挤上通往 Python 引力中心的桥梁啊! 我以前总是有意无意地忽略了这些读者的存在。前几天,我接了极客时间的一个专栏推广,在跟一些读者的互动中,以及在一些现象的观察中,我加深了对这些非主流人群的认识。 意识到了这一点后,我想,或许我也能为他们做点什么?至少以后在写文章的时候,应该设法做到兼顾吧。 正好,最近又有几位不同身份的初学者来咨询,要我推荐几本入门书籍,而我们荐书系列已经停更了两个多月,所以,本期荐书就来推荐一些入门书籍吧。 为了准备这期荐书,我专门搜集了 40 本 Python 入门书籍,现在全部加入到了一份豆瓣豆列里,方便大家查看。 先给大家看看完整的书单吧。 豆列:https://www.douban.com/doulist/114507342/ 《“笨办法”学Python》 https://book.douban.com/subje... 《python学习手册(原书第5版)》https://book.douban.com/subje... 《Head First Python(中文版)》https://book.douban.com/subje... 《Python基础教程(第3版)》https://book.douban.com/subje... 《Python编程无师自通》https://book.douban.com/subje... 《从Python开始学编程》https://book.douban.com/subje... 《Python编程之美:最佳实践指南》https://book.douban.com/subje... 《Python语言及其应用》 https://book.douban.com/subje... 《Python编程:从入门到实践》 https://book.douban.com/subje... 《像计算机科学家一样思考Python (第2版)》https://book.douban.com/subje... 《Python编程快速上手》 https://book.douban.com/subje... 《Python游戏编程快速上手》https://book.douban.com/subje... 《爱上Python》https://book.douban.com/subje... 《Python编程初学者指南》 https://book.douban.com/subje... 《Python语言程序设计基础(第2版)》https://book.douban.com/subje... 《Python语言程序设计》https://book.douban.com/subje... 《Python入门经典》https://book.douban.com/subje... 《Python入门经典》https://book.douban.com/subje... 《Python编程导论(第2版)》https://book.douban.com/subje... 《计算机编程导论—Python程序设计》https://book.douban.com/subje... 《趣学Python编程》 https://book.douban.com/subje... 《Python带我起飞:入门、进阶、商业实战》https://book.douban.com/subje... 《Python趣味编程入门》https://book.douban.com/subje... 《从问题到程序-用Python学编程和计算》https://book.douban.com/subje... 《跟老齐学Python》https://book.douban.com/subje... 《零基础学Python》https://book.douban.com/subje... 《Python程序设计入门到实战》https://book.douban.com/subje... 《从零开始学Python网络爬虫》https://book.douban.com/subje... 《零基础学Python图文版》https://book.douban.com/subje... ...

May 26, 2019 · 1 min · jiezi

Flaskpython3supervisorredisdockernginx技术架构web项目docker化二

背景手里有一个web项目,代码按照前端代码库、后端代码库分别在GitHub上,分散带来的结果是,不容易持续集成,比如你可能需要很多的job去保证一个项目的正常运作,但是这个项目也不是特别大,所以尝试将代码融合,于此同时将代码docker化,用于持续部署。 技术架构原来的代码使用gunicorn+gevent+supervisor+flask+DB的架构;具体的细节如下: 本地服务器搭建了一个nginx域名服务器,里面区分PC端还是手机端;访问域名通过nginx,访问前端静态页面的内容静态页面中加载指定地址的数据,提供数据的服务由flask后端提供接口;后端提供的接口,通过访问redis缓存和mongodb数据库,返回相应的数据; docker-compose上篇文章说了flask项目是怎么拆分和组合的,但是上次仅仅是使用docker,多个容器之间使用的--link连接起来的,本篇文章将介绍如何使用docker-compose代替原来的多个docker命令; docker compose是什么可以自行搜索,我直接上我的docker-compose.yml version: '3' services: flask: image: "flask:latest" restart: always depends_on: - mongoDB - redisDB tty: true stdin_open: false environment: SLEEP_SECOND: 10 container_name: flask logging: driver: "json-file" options: max-size: "200M" max-file: "10" command: "gunicorn manage:app -k gevent -w 4 -b 0.0.0.0" volumes: - $HOME/logs:/app/logs networks: - inline-network ports: - "8000:8000" matrix: image: "flask:latest" restart: always depends_on: - mongoDB - redisDB tty: true stdin_open: false environment: SLEEP_SECOND: 10 container_name: matrix command: "flask matrix" volumes: - $HOME/logs:/app/logs - /etc/localtime:/etc/localtime networks: - inline-network broadcast: image: "flask:latest" restart: always depends_on: - mongoDB - redisDB tty: true stdin_open: false environment: SLEEP_SECOND: 10 container_name: broadcast command: "flask broadcast" volumes: - $HOME/logs:/app/logs - /etc/localtime:/etc/localtime networks: - inline-network redisDB: image: "redis:latest" container_name: redis restart: always networks: inline-network: aliases: - redis ports: - "6379:6379" mongoDB: image: "mongo:latest" restart: always container_name: mongo ports: - "27017:27017" volumes: - /var/lib/mongo:/data/db networks: inline-network: aliases: - mongo networks: inline-network: driver: "bridge" ```解释:所有的启动的dontainer都在inline-network网络环境中,所以可以直接使用aliases指定的名字作为数据库连接时候的host,本来是不打算将数据库的端口的,只给flask用,但是后面由于切换的时候是现切换数据库,在切换后段flask的镜像,所以就将数据库端口和宿主机绑定了。其中flask、matrix、broadcast,都是之前代码中的功能,使用supervisor启动的,现在单独启动三个docker进程去完成。前端docker前端的PC端和移动端,都使用npm构建成dist文件,然后通过nginx定向到指定的dist文件内容就可以,所以我们对前端的代码也进行了docker化,使用的是nginx; ...

May 25, 2019 · 2 min · jiezi

1构建docker的flask镜像

基于alpine镜像构建自己的flask镜像python官方镜像地址: http://hub.docker.com/_/python 拉取官方的python镜像 docker pull python:3.7-alpine交互式方式生成一个python容器 docker run -it --name python37 --rm python:3.7-alpine /bin/sh进入交互式容器,查看当前python版本 python --version设置pip的阿里云镜像源 mkdir $HOME/.pip/tee $HOME/.pip/pip.conf <<-'EOF'[global]trusted-host=mirrors.aliyun.comindex-url=https://mirrors.aliyun.com/pypi/simpleEOF确认一下是否配置成功 cat $HOME/.pip/pip.conf我们的python使用的是alpine系统 alpine使用的是apk包管理器 命令如: apk addapk updateapk delalpine默认的镜像源也比较慢,我们也换成国内的 设置alpine镜像源 echo http://mirrors.ustc.edu.cn/alpine/v3.7/main > /etc/apk/repositoriesecho http://mirrors.ustc.edu.cn/alpine/v3.7/community >> /etc/apk/repositories设置后要执行 apk update && apk upgrade安装flask python -m pip install -U flask编写test.py from flask import Flaskapp = Flask(__name__)@app.route('/')def hello(): return 'Hello World!'@app.route('/abc')def abc(): return 'Hello abc'if __name__ == '__main__': app.run()第一种执行这个py文件方式 python test.py然后在另一个终端以交互式方式进入这个python容器 ...

May 22, 2019 · 1 min · jiezi

flask基础01

Flask基础Python版本管理器(pyenv)安装pyenv1: sudo apt-get install curl git 2: sudo curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash3: 将提示的三行代码复制 # 会出现提示的三行代码,格式如下 eg: export PATH="/root/.pyenv/bin:$PATH" eval "$(pyenv init -)" eval "$(pyenv virtualenv-init -)" 4: sudo vim ~/.bashrc 5: 追加刚才复制的三行代码至最后 eg: export PATH="/root/.pyenv/bin:$PATH" eval "$(pyenv init -)" eval "$(pyenv virtualenv-init -)" 6: sudo source ~/.bashrc 7: sudo echo $PATH # 如果出现 .pyenv/shims 说明成功8: pyenv update # 更新卸载pyenv1: sudo rm -fr ~/.pyenv2: 同时删除 .bashrc 下面这些内容 export PATH="~/.pyenv/bin:$PATH" eval "$(pyenv init -)" eval "$(pyenv virtualenv-init -)"安装python1:安装依赖 sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm sudo apt-get install libc6-dev gcc-5 2:cd ~/.pyenv3: mkdir -p cache4: 将下载的 python包 放到cache下 5: pyenv install 3.6.4 -v 6: pyenv rehash # 更新pyenv库Python管理1:列出pyenv已经安装的python版本 pyenv versions2: python版本切换 pyenv global 3.6.4 3:卸载python pyenv uninstall 3.6.4虚拟环境管理第一种1:Python自带环境创建 # 创建环境 python -m venv <envname> # 激活环境 source ....pathon<envname>/bin/activate # 环境的路径 # 退出环境 deactivate第二种1: pip install virtualenvwrapper 2: sudovim ~/.bashrc 3: 最后一行加入 # 注意修改主机名,和python版本 if [ -f /home/dq/.pyenv/versions/3.6.4/bin/virtualenvwrapper.sh ]; then export WORKON_HOME=$HOME/.virtualenvs export VIRTUALENVWRAPPER_PYTHON=/home/dq/.pyenv/versions/3.6.4/bin/python export VIRTUALENVWRAPPER_VIRTUALENV=/home/dq/.pyenv/versions/3.6.4/bin/virtualenv source /home/dq/.pyenv/versions/3.6.4/bin/virtualenvwrapper.sh fi4:source ~/.bashrc # 配置文件生效5: 基本使用: # 创建环境 mkvirtualenv <envname> # 激活环境 workon <envname> # 退出环境 deactivate # 列出所有的虚拟环境 lsvirtualenv # 删除虚拟环境 rmvirtualenv <envname> # 虚拟环境存放位置 ~/.virtualenvs/第三种1: pip install virtualenv2: mkdir -p /data/blog3: cd /data/blog/4: 基本使用 # 创建环境 pyenv virtualenv <envname> # 激活环境 pyenv activate <envname> # 退出环境 pyenv deactivate <envname> # 虚拟存放位置 ~/.pyenv/versions/第四种1:pycharm编辑器自带创建虚拟环境功能Flask依赖包安装flaskpip install flask包名作用Werkzeug用于实现 WSGI ,应用和服务之间的标准 Python 接口Jinja用于渲染页面的模板语言MarkupSafe与 Jinja 共用,在渲染页面时用于避免不可信的输入,防止注入攻击ItsDangerous保证数据完整性的安全标志数据,用于保护 Flask 的 session cookieClick是一个命令行应用的框架。用于提供 flask 命令,并允许添加自定义 管理命令创建第一个Flask应用文件内容from flask import Flask# 导入Flask类app = Flask(__name__)# 创建Flask类的实例@app.route('/')# 使用 route() 装饰器来告诉 Flask 触发函数的 URLdef hello_world(): # 函数名称被用于生成相关联的 URL 。函数最后返回需要在用户浏览器中显示的信息 return 'Hello, World!'项目启动1:手动启动: 1:终端里导出 FLASK_APP 环境变量: export FLASK_APP=app.py 2:启动项目: flask run 3:浏览器访问 127.0.0.0:50002:pycharm中启动: python app.py runserver # 文件内容中要有以下代码: if __name__ == '__main__': app.run() -p # 端口号 -h # 主机名 -r # 修改代码重载 -d # 调试模式 路由1:变量规则: # 关键字参数 @app.route('/user/<username>') def show_user_profile(username): return 'User %s' % username 127.0.0.1:5000/user/cross # 关键字加限定类型 @app.route('/post/<int:post_id>') def show_post(post_id): return 'Post %d' % post_id2: 参数类型: 1:string # 接受任何不包含斜杠的文本 2: int # 接受正整数 3: float # 接受正浮点数 4: path # 类似 string ,但可以包含斜杠 5: uuid # 接受 UUID 字符串3:唯一url和重定向行为: 1:@app.route('/projects/') 2:@app.route('/about') # 如果浏览器访问资源路径后面一个斜杠不加的话,1路由请求不会报错,flask会自动重定向帮你加上斜杠,2:路由也不会报错 # 如果浏览器访问资源路径后面添加上一个斜杠的话2路由会报404错误 HTTP方法1:route装饰器中如果不声明请求方法默认是GET方法 # 如果想要使用post方法,需要声明 methods=['POST']2:eg: from flask import request @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': return do_the_login() else: return show_the_login_form()Flask第一次封装项目结构├── App # 项目应用│   ├── __init__.py # 初始化文件│   ├── models.py # 模型 创建数据库模型 orm对象关系映射│   ├── static # 静态资源 css js img 等等│   ├── templates # 模板,动态资源,html│   └── views.py # 路由 试图函数├── app.py # 整个项目文件,项目入口 命令行管理项目(manager.py)1:pip install flask-script2: 基本使用 from flask import Flask from flask_script import Manager app = Flask(__name__) manager = Manager(app=app) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': manager.run()3:修改项目入口文件为manager.py 4: 测试: python manager.py runserver初始化Flask类(__init__.py)1:init文件封装函数实例化Flask类 from flask import Flask def create_app(): app = Flask(__name__) return app2:manager.py 导入函数实例化类 from flask_script import Manager from App import create_app app = create_app() ..................蓝图管理路由(views.py)1: 蓝图(blutprint): 1:好处: Flask蓝图提供了模块化管理程序路由的功能,使程序结构清晰、简单易懂,项目模块化,方便管理2:安装: pip install flask-blueprint3: 蓝图使用 1:实例化蓝图 from flask import Blueprint blue = Blueprint('first', __name__) # first为蓝图名字,多为反向解析时使用 2:装饰函数 @blue.route('/') def hello_world(): return 'Hello World!' 3:注册蓝图(manager.py) from App.views import blue app.register_blueprint(blue) 或者 app.register_blueprint(blueprint=blue) request方法返回值eg:request.method返回请求方法GET/POSTrequest.base_url去掉get参数的urlhttp://127.0.0.1:5000/testrequest/request.host_url只有主机和端口号的urlhttp://127.0.0.1:5000/request.url完整的请求地址http://127.0.0.1:5000/testrequest/request.mote_addr请求的客服端地址127.0.0.1request.args.get('参数')GET请求获取参数crossrequest.form.get('参数')POST请求获取参数crossrequest.files文件上传。。。。request.headers请求头。。。。request.path路由中的路径。。。。request.cookies请求中的cookie。。。。session与request类似 也是一个内置对象 可以直接打印 print(session)。。。。request.form['参数']POST请求获取参数crossrequest.args['参数']GET请求获取参数crossresponse返回类型1:string # 字符串 @blue.route('/testresponse/') def testresponse(): return '过去从未消亡,它甚至从未过去'2:reder_template # 模板渲染 @blue.route('/testresponse1/') def testresponse1(): return render_template('/index.html/')3:make_response @blue.route('/testresponse2/') def testresponse2(): res = make_response('<h3>过去从未消亡,它甚至从未过去</h3>') print(type(res)) return res4:redirect # 重定向 @blue.route('/testresponse3/') def testresponse3(): res = redirect(url_for('first.testresponse1')) print(type(res)) return res5:Response @blue.route('/testresponse4/') def testresponse4(): name = request.args.get('name') res = Response('你真的认识我吗?%s' % name) return res 会话技术

May 4, 2019 · 3 min · jiezi

flask数据库练习原创

图解如下:注意:增删改都是通过db.session来操作的 查的话是通过User.query.filter().all() sqlachemy程序以及测试数据如下:from flask import Flaskfrom flask_script import Managerfrom flask_sqlalchemy import SQLAlchemy app = Flask(__name__)app.config['DEBUG']=True manage = Manager(app)app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/flask_test'app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) class Role(db.Model): __tablename__ = 'roles'id = db.Column(db.Integer,primary_key=True)name = db.Column(db.String(64),unique = True)us = db.relationship('User',backref='role')def __repr__(self): return 'Role %s'% self.nameclass User(db.Model): __tablename__ = 'users'id = db.Column(db.Integer,primary_key=True)name = db.Column(db.String(64),unique=True,index=True)email = db.Column(db.String(64),unique=True)password=db.Column(db.String(64))role_id = db.Column(db.Integer,db.ForeignKey('roles.id'))def __repr__(self): return 'User:%s'%self.nameif name == '__main__': db.drop_all()db.create_all()# app.run(debug=True)# 创建测试数据ro1 = Role(name='admin')db.session.add(ro1)db.session.commit()# 再次插入一条数据ro2 = Role(name='user')db.session.add(ro2)db.session.commit()# 多条用户数据us1 = User(name='wang', email='wang@163.com', password='123456', role_id=ro1.id)us2 = User(name='zhang', email='zhang@189.com', password='201512', role_id=ro2.id)us3 = User(name='chen', email='chen@126.com', password='987654', role_id=ro2.id)us4 = User(name='zhou', email='zhou@163.com', password='456789', role_id=ro1.id)us5 = User(name='tang', email='tang@itheima.com', password='158104', role_id=ro2.id)us6 = User(name='wu', email='wu@gmail.com', password='5623514', role_id=ro2.id)us7 = User(name='qian', email='qian@gmail.com', password='1543567', role_id=ro1.id)us8 = User(name='liu', email='liu@itheima.com', password='867322', role_id=ro1.id)us9 = User(name='li', email='li@163.com', password='4526342', role_id=ro2.id)us10 = User(name='sun', email='sun@163.com', password='235523', role_id=ro2.id)db.session.add_all([us1, us2, us3, us4, us5, us6, us7, us8, us9, us10])db.session.commit()manage.run(default_command='runserver')测试指令如下: ...

April 29, 2019 · 1 min · jiezi

Flask-Response

flask jsonify()函数返回json响应 app = Flask(__name__)@app.route('/json/<name>')def index(name): return jsonify({'Hello':name})这时候content-Type=application/json 如果用python的json.dumps()函数 @app.route('/dumps/<name>')def py(name): return json.dumps({'Hello':name})这时候content-Type=text/html; charset=utf-8。如果选择响应的格式的话,return json.dumps({'Hello':name}),{'Content-Type':'application/json'} 那么content-Type=application/json就会响应json格式 flask Response响应类实际上来自Werzeug中的Response类,它继承的是python的BaseResponse类 我们可以自定义响应 >>> from flask import Flask>>> app = Flask(__name__)>>> app.make_response(("<h1>Hello word</h1>",201))<Response 16 bytes [201 CREATED]>make_response接收一个参数,返回信息和状态码都在一个元组里 Response类定义: class Response: charset = 'utf-8' default_status = 200 default_mimetype = 'text/html' def __init__(self, response=None, status=None, headers=None, mimetype=None, content_type=None, direct_passthrough=False): pass @classmethod def force_type(cls, response, environ=None): pass我们可以自定义Response的子类,对他的行为做出一些改变,Flask类的response_class属性可以改变响应类。 from falsk import FLask, Responsecalss MyResponse(Response): default_mimetype = 'application/xml' #修改内容类型 class Myfalsk(Flask): response_class = Myresponse @app.route('/')def index(): return '''<?xml version='1.0'encoding="UTF-8"?> <person> <name>Yang</name> </person> '''如果想要其他的内容类型,可以设置Content-Type的值:return "{'name':'yang'}",{'Content-Type'='application/json'}重写Response类来过滤Json格式的内容格式: ...

April 28, 2019 · 1 min · jiezi

Flask注册视图函数

那天莫名其妙出了个错。。就顺便看了看Flask路由 在flask存储路由函数是以函数名为键,函数对象为值 class Flask: def __init__(self, *args, **kwargs): #所有视图函数的注册将被放在字典。键是函数名,值是函数对象,函数名也用于生成URL。注册一个视图函数,用route装饰器。 self.view_functions= {}app.route装饰器注册视图函数 def route(self, rule, **options): #用来给给定的URL注册视图函数的装饰器,也可以用add_url_rule函数来注册。endpoint关键字参数默认是视图函数的名字 def decorator(f): endpoint = options.pop('endpoint', None) #pop删除endpoint的值没有为None并返回它赋值给endpoint self.add_url_rule(rule, endpoint, f, **options) #调用add_url_rule函数 return f return decoratoradd_url_rule函数 def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options): if endpoint is None: endpoint = _endpoint_from_view_func(view_func) #_endpoint_from_view_fun函数返回视图函数名view_fun.__name__ options['endpoint'] = endpoint #...... if view_func is not None: old_func = self.view_functions.get(endpoint) if old_func is not None and old_func != view_func: raise AssertionError('View function mapping is overwriting an ' 'existing endpoint function: %s' % endpoint) #old_func对象从储存视图函数的字典中取出,如果它不为空并且不等于视图函数那么就会报错视图函数覆盖当前端点函数,如果有同名函数可以通过修改endpoint值来避免这个错误。 self.view_functions[endpoint] = view_func #函数名作为键,函数对象作为值存储到view_functions中。 获取储存视图函数字典中的函数对象 ...

April 28, 2019 · 1 min · jiezi

sum-函数性能堪忧列表降维有何良方

本文原创并首发于公众号【Python猫】,未经授权,请勿转载。 原文地址:https://mp.weixin.qq.com/s/mK1nav2vKykZaKw_TY-rtw Python 的内置函数 sum() 可以接收两个参数,当第一个参数是二维列表,第二个参数是一维列表的时候,它可以实现列表降维的效果。 在上一篇《如何给列表降维?sum()函数的妙用》中,我们介绍了这个用法,还对 sum() 函数做了扩展的学习。 那篇文章发布后,猫哥收到了一些很有价值的反馈,不仅在知识面上获得了扩充,在思维能力上也得到了一些启发,因此,我决定再写一篇文章,继续跟大家聊聊 sum() 函数以及列表降维。若你读后有所启发,欢迎留言与我交流。 有些同学表示,没想到 sum() 函数竟然可以这么用,涨见识了!猫哥最初在交流群里看到这种用法时,也有同样的想法。整理成文章后,能得到别人的认可,我非常开心。 学到新东西,进行分享,最后令读者也有所获,这鼓舞了我——应该每日精进,并把所学分享出去。 也有的同学早已知道 sum() 的这个用法,还指出它的性能并不好,不建议使用。这是我不曾考虑到的问题,但又不得不认真对待。 是的,sum() 函数做列表降维有奇效,但它性能堪忧,并不是最好的选择。 因此,本文想继续探讨的话题是:(1)sum() 函数的性能到底差多少,为什么会差?(2)既然 sum() 不是最好的列表降维方法,那是否有什么替代方案呢? 在 stackoverflow 网站上,有人问了个“How to make a flat list out of list of lists”问题,正是我们在上篇文章中提出的问题。在回答中,有人分析了 7 种方法的时间性能。 先看看测试代码: import functoolsimport itertoolsimport numpyimport operatorimport perfplotdef forfor(a): return [item for sublist in a for item in sublist]def sum_brackets(a): return sum(a, [])def functools_reduce(a): return functools.reduce(operator.concat, a)def functools_reduce_iconcat(a): return functools.reduce(operator.iconcat, a, [])def itertools_chain(a): return list(itertools.chain.from_iterable(a))def numpy_flat(a): return list(numpy.array(a).flat)def numpy_concatenate(a): return list(numpy.concatenate(a))perfplot.show( setup=lambda n: [list(range(10))] * n, kernels=[ forfor, sum_brackets, functools_reduce, functools_reduce_iconcat, itertools_chain, numpy_flat, numpy_concatenate ], n_range=[2**k for k in range(16)], logx=True, logy=True, xlabel='num lists' )代码囊括了最具代表性的 7 种解法,使用了 perfplot (注:这是该测试者本人开发的库)作可视化,结果很直观地展示出,随着数据量的增加,这几种方法的效率变化。 ...

April 27, 2019 · 2 min · jiezi

我是如何自学-Python-的

不少初学 Python 或者准备学习 Python 的小伙伴问我如何学习 Python。今天就说说我当时是怎么学习的。 缘起 我大学专业是电气工程,毕业后做的是自动化方面的工作。对于高级语言编程基本是 0 基础,那时刚毕业在车间做设备调试,工资也只有三四千块钱。2014年底在知乎看到搞 IT 的薪资动辄 10k 起步,所以我也动了学习编程的念头。 当时 Python 已经开始流行。虽然远没有今天热度这么高,但是已经有一些大V在鼓励大家开始学习 Python了。对我影响最大的是知乎ID为:"萧井陌"的大神。我觉得他至少影响了上万人学习 Python 。那时候他的《编程入门指南》很火,而且一直在鼓励初学编程的人去学习 Python。其中他的这个回答对我影响最大,因为这个回答特别笃定,把步骤写好了,照做就是了。 然后我买了他推荐的这本书,现在已经出第二版了。当时看第二遍时还是糊里糊涂的,因为你学了 Python 基础后,还要了解 WEB 开发的一些概念,包括数据库的基本用法。所以当时又看了 WEB 方面包括 HTML/CSS/JS,和 HTTP协议一些知识。买了本 SQL 必知必会来了解简单的 SQL 语句。总之是 Flask 这本书看了三遍,对书中所写的项目理解了80%左右吧。到这里基本算是入门吧,之后就开始做 IT 相关工作了。 学习方法如果是 0 基础学习,还是推荐《笨办法学Python》这本小册子开始。很直白,没有上来就讲语法,仅仅是照着敲就行了。这个小册子看完后我当时看的是《Python核心编程-第二版》上面讲的还是 Python 2.5。现在出了第三版,但是已经不推荐初学者去看了。现在你可以直接去看人民邮电出版社的《Python编程从入门到实践》,这本书我简单翻过,内容还是很不错的,包括大量的实际案例,可以亲手做出一点好玩的应用来。 除了 Python 外还要了解基本的 HTML/CSS/JS。这些东西花几天时间在 W3School 看一看就差不多了。在这个过程中可以到网上看看别人都用 Python 来做哪些好玩的事情,可以跟着学学。知乎上有很多好的问题和答案,非常值得学习。 在学习过程中不必要求 100% 掌握,一些高级用法不理解没关系,等代码写的多了就懂了。上面基础知识看完后就要选择一个方向了,比如 WEB,数据分析等。做 WEB 的话 Python 最流行的两个框架 Django 和 Flask 选一个深入学一下就好了,我当时学的是 Flask,不过 Django 是一个大而全的框架,不需要你去找各种第三方模块来使用,文档也很全面,都很适合来学习。 ...

April 24, 2019 · 1 min · jiezi

windows10下,零基础学习VUE(8)-- 小练习,知识图谱可视化(ltp抽取文本关系,echarts展示图谱)

新坑!echarts的引入graph的设置data.nodesdata.linksltp安装文本解析及节点和关系的json格式生成图谱展示

April 17, 2019 · 1 min · jiezi

机器学习(七)-基于KNN分类的约会网站配对改进算法

1 项目介绍某APP用户一直使用在线约会软件寻找适合自己的约会对象。尽管约会网站会推荐不同的人选,但她并不是喜欢每一个人。经过一番总结,她发现曾交往过三种类型的人:不喜欢的人(3)魅力一般的人(2)极具魅力的人(1)某APP用户希望分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外还可以收集了约会软件未曾记录的数据信息,她认为这些数据更有助于匹配对象的归类。收集的部分信息如下图所示:数据集下载样本主要包含以下3种特征:每年获得的飞行常客里程数玩视频游戏所耗时间百分比每周消费的冰淇淋公升数2 准备数据:从文本文件中解析数据在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。import numpy as npdef file2matrix(filename): """ :param filename: APP用户收集的约会数据的文件名 :return: returnMat: 用户提供的每行数据信息,三列, 分别是每年获得的飞行常客里程数, 玩视频游戏所耗时间百分比, 每周消费的冰淇淋公升数 classLabelVetor: 用户的评价信息, 一般分为3类(1,2,3) """ fr = open(filename) arrayOfLines = fr.readlines() # print(arrayOfLines) # 获得文件行数; numerOfLines = len(arrayOfLines) # 创建要返回的Numpy矩阵; returnMat = np.zeros((numerOfLines, 3)) # 解析文件数据到矩阵中; classLabelVetor = [] index = 0 for line in arrayOfLines: line = line.strip() listFromLine = line.split(’\t’) returnMat[index, :] = listFromLine[0:3] classLabelVetor.append(listFromLine[-1]) index += 1 return returnMat, classLabelVetorprint(file2matrix(‘data/datingTestSet2’))返回的值显示: 3 分析数据:使用 Matplotlib 创建散点图使用Matplotlib库图形化清晰地标识了三个不同的样本分类区域,具有不同爱好的人其类别区域也不同。def draw_pic(datingDataMat, datingLabels): """ 每年获取的飞行常客里程数与每周所消费的冰淇淋公升数”构成的散点图。 :param datingDataMat: :param datingLabels: :return: """ # 中文显示乱码问题; myfont = font_manager.FontProperties(fname="/usr/share/fonts/cjkuni-uming/uming.ttc", size=12) # 创建画布 fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(datingDataMat[:, 0], datingDataMat[:, 2], 15 * datingLabels, datingLabels) plt.xlabel(“每年的飞行里程数”, fontproperties=myfont) plt.ylabel(“每周消费的冰淇淋公升数”, fontproperties=myfont) plt.grid(alpha=0.5) plt.show()效果展示4 准备数据:归一化数值计算样本3和样本4之间的距离:问题: 飞行常客里程数对于计算结果的影响将远远大于其他两个特征的影响解决方式: 处理不同取值范围的特征值时,通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。归一化公式: newValue = oldValue / maxdef autoNorm(dataSet): """ 归一化数值, :param dataSet:用户提供的每行数据信息,三列; :return: normDataSet: 归一化的特征信息; maxVals:每个特征数据的最大值; """ # 获取每个特征数据的最大值; maxVals = dataSet.max(0) # 获取样本个数; m = dataSet.shape[0] # 根据公式生成归一化的特征信息; normDataSet = dataSet / np.tile(maxVals, (m, 1)) return normDataSet, maxVals4 实施 kNN 算法对未知类别属性的数据集中的每个点依次执行以下操作, 与上一个案例代码相同:(1) 计算已知类别数据集中的点与当前点之间的距离;(2) 按照距离递增次序排序;(3) 选取与当前点距离最小的k个点;(4) 确定前k个点所在类别的出现频率;(5) 返回前k个点出现频率最高的类别作为当前点的预测分类。def classify(inX, dataSet, labels, k): """ :param inX: 要预测的数据 :param dataSet: 我们要传入的已知数据集 :param labels: 我们要传入的标签 :param k: KNN里的k, 也就是说我们要选几个近邻 :return: 排序的结果 """ dataSetSize = dataSet.shape[0] # (6,2) 6 # tile会重复inX, 把他重复成(datasetsize, 1)型的矩阵 # print(inX) # (x1 - y1), (x2- y2) diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet # 平方 sqDiffMat = diffMat ** 2 # 相加, axis=1 行相加 sqDistance = sqDiffMat.sum(axis=1) # 开根号 distances = sqDistance ** 0.5 # print(distances) # 排序 输出的是序列号index,并不是值 sortedDistIndicies = distances.argsort() # print(sortedDistIndicies) classCount = {} for i in range(k): voteLabel = labels[sortedDistIndicies[i]] classCount[voteLabel] = classCount.get(voteLabel, 0) + 1 # print(classCount) sortedClassCount = sorted(classCount.items(), key=lambda d: float(d[1]), reverse=True) return sortedClassCount[0]5 测试算法:作为完整程序验证分类器如果分类器的正确率满足要求,就可以使用这个软件来处理约会网站提供的约会名单了。机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。def datingClassTest(): """ 分类器针对约会网站的测试代码, 获取错误率; :return: """ hoRatio = 0.10 datingDataMat, datingLabels = file2matrix(‘data/datingTestSet2’) normDataSet, maxVals = autoNorm(datingDataMat) # 样本个数 m = normDataSet.shape[0] # 测试集个数; numTestVecs = int(mhoRatio) errorCount = 0.0 for i in range(numTestVecs): classiferResult = classify(normDataSet[i, :], normDataSet[numTestVecs:m, :], datingLabels[numTestVecs:m], 3) # print(classiferResult) if classiferResult != datingLabels[i]: errorCount += 1 print(“正确结果:”, datingLabels[i]) print(“预测结果:”, classiferResult) # print(“错误个数:”, errorCount) return errorCount执行效果展示:6 使用算法:构建完整可用的预测系统def classifyPerson(Person): """ 使用这个分类器为某APP用户来对人们分类。 :param Person: :return: """ datingDataMat, datingLabels = file2matrix(‘data/datingTestSet2’) normDataSet, maxVals = autoNorm(datingDataMat) classiferResult = classify(Person / maxVals, normDataSet, datingLabels, 3) if classiferResult == ‘1’: print(“不喜欢”) elif classiferResult == ‘2’: print(“有一点喜欢”) else: print(“非常喜欢”)完整代码# encoding:utf-8"““KNN实现,基于KNN分类的约会网站配对改进算法”““import numpy as npimport matplotlib.pyplot as pltfrom matplotlib import font_managerdef file2matrix(filename): "”” :param filename: APP用户收集的约会数据的文件名 :return: returnMat: 用户提供的每行数据信息,三列, 分别是每年获得的飞行常客里程数, 玩视频游戏所耗时间百分比, 每周消费的冰淇淋公升数 classLabelVetor: 用户的评价信息, 一般分为3类(1,2,3) "”" fr = open(filename) arrayOfLines = fr.readlines() # print(arrayOfLines) # 获得文件行数; numerOfLines = len(arrayOfLines) # 创建要返回的Numpy矩阵; returnMat = np.zeros((numerOfLines, 3)) # 解析文件数据到矩阵中; classLabelVetor = [] index = 0 for line in arrayOfLines: line = line.strip() listFromLine = line.split(’\t’) returnMat[index, :] = listFromLine[0:3] classLabelVetor.append(listFromLine[-1]) index += 1 return returnMat, classLabelVetordef autoNorm(dataSet): """ 归一化数值, 计算样本3和样本4之间的距离: [(0-67)**2 + (20000 - 32 000)**2 + (1.1 - 0.1)**2]**0.5 问题: 飞行常客里程数对于计算结果的影响将远远大于其他两个特征的影响 解决方式: 处理不同取值范围的特征值时, 通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。 归一化公式: newValue = oldValue / max :param dataSet:用户提供的每行数据信息,三列; :return: normDataSet: 归一化的特征信息; maxVals:每个特征数据的最大值; """ # 获取每个特征数据的最大值; maxVals = dataSet.max(0) # 获取样本个数; m = dataSet.shape[0] # 根据公式生成归一化的特征信息; normDataSet = dataSet / np.tile(maxVals, (m, 1)) return normDataSet, maxValsdef draw_pic(datingDataMat, datingLabels): """ 每年获取的飞行常客里程数与每周所消费的冰淇淋公升数”构成的散点图。 :param datingDataMat: :param datingLabels: :return: """ # 中文显示乱码问题; myfont = font_manager.FontProperties(fname="/usr/share/fonts/cjkuni-uming/uming.ttc", size=12) # 创建画布 fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(datingDataMat[:, 0], datingDataMat[:, 2], 15 * datingLabels, datingLabels) plt.xlabel(“每年的飞行里程数”, fontproperties=myfont) plt.ylabel(“每周消费的冰淇淋公升数”, fontproperties=myfont) plt.grid(alpha=0.5) plt.show()def classify(inX, dataSet, labels, k): """ :param inX: 要预测的数据 :param dataSet: 我们要传入的已知数据集 :param labels: 我们要传入的标签 :param k: KNN里的k, 也就是说我们要选几个近邻 :return: 排序的结果 """ dataSetSize = dataSet.shape[0] # (6,2) 6 # tile会重复inX, 把他重复成(datasetsize, 1)型的矩阵 # print(inX) # (x1 - y1), (x2- y2) diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet # 平方 sqDiffMat = diffMat ** 2 # 相加, axis=1 行相加 sqDistance = sqDiffMat.sum(axis=1) # 开根号 distances = sqDistance ** 0.5 # print(distances) # 排序 输出的是序列号index,并不是值 sortedDistIndicies = distances.argsort() # print(sortedDistIndicies) classCount = {} for i in range(k): voteLabel = labels[sortedDistIndicies[i]] classCount[voteLabel] = classCount.get(voteLabel, 0) + 1 # print(classCount) sortedClassCount = sorted(classCount.items(), key=lambda d: float(d[1]), reverse=True) return sortedClassCount[0][0]def datingClassTest(): """ 分类器针对约会网站的测试代码, 获取错误率; :return: """ hoRatio = 0.10 datingDataMat, datingLabels = file2matrix(‘data/datingTestSet2’) normDataSet, maxVals = autoNorm(datingDataMat) # 样本个数 m = normDataSet.shape[0] # 测试集个数; numTestVecs = int(mhoRatio) errorCount = 0.0 for i in range(numTestVecs): classiferResult = classify(normDataSet[i, :], normDataSet[numTestVecs:m, :], datingLabels[numTestVecs:m], 3) # print(classiferResult) if classiferResult != datingLabels[i]: errorCount += 1 print(“正确结果:”, datingLabels[i]) print(“预测结果:”, classiferResult) # print(“错误个数:”, errorCount) return errorCountdef classifyPerson(Person): """ 使用这个分类器为某APP用户来对人们分类。 :param Person: :return: """ datingDataMat, datingLabels = file2matrix(‘data/datingTestSet2’) normDataSet, maxVals = autoNorm(datingDataMat) classiferResult = classify(Person / maxVals, normDataSet, datingLabels, 3) if classiferResult == ‘1’: print(“不喜欢”) elif classiferResult == ‘2’: print(“有一点喜欢”) else: print(“非常喜欢”)if name == ‘main’: # personData = [30000, 10, 1.3] personData = [40920, 8.326976, 0.953952] classifyPerson(personData)执行结果 ...

April 17, 2019 · 4 min · jiezi

windows10下,零基础学习VUE(5)-- 小练习,搭建页面实现利用resnet50,识别imagenet中1000类图像

!!!!等待编辑中,先写个大纲简单看下结果使用 el-upload上传组件使用el-progress进度条使用v-for显示元素一些基本的css格式后台日志效果图.]

April 10, 2019 · 1 min · jiezi

windows10下,零基础学习VUE(3)-- axios+flask构建前后端通信demo

当前为大纲的内容:待完善中1. axiosaxios 安装npm install axios参考文档https://www.kancloud.cn/yunye…示例demohttps://cn.vuejs.org/v2/cookb…2. flask构建api:from flask import Flask, render_template,jsonifyfrom flask_cors import CORSapp = Flask(name, static_folder = “./dist/static”, template_folder = “./dist”)cors = CORS(app, resources={"/api/": {“origins”: “”}})from random import *@app.route(’/api/random’)def random_number(): response = { ‘randomNumber’: randint(1, 100) } return jsonify(response)在jupyter_notebook中运行服务from werkzeug.serving import run_simplerun_simple(‘0.0.0.0’, 9001, app)3. 在vue中调用,并显示模板部分<template> <div> 欢迎来到首个测试页面 <p>Random number from backend: {{ randomNumber }}</p> <button @click=“getRandom”>New random number</button> <p>{{orgdata}}</p> <foot-nav v-bind:class="{‘isIndex’:isIndexNow}"></foot-nav> </div></template> js脚本部分<script> import FootNav from “../components/footer.vue”; import axios from ‘axios’ export default { components: { FootNav }, methods: { getRandomInt (min, max) { min = Math.ceil(min) max = Math.floor(max) return Math.floor(Math.random() * (max - min + 1)) + min }, getRandom () { // this.randomNumber = this.getRandomInt(1, 100) this.randomNumber = this.getRandomFromBackend() }, getRandomFromBackend () { const path = http://localhost:9001/api/random axios.get(path) .then(response => { this.randomNumber = response.data.randomNumber this.orgdata = response.data }) .catch(error => { console.log(error) }) } }, data(){ return{ isIndexNow: true, randomNumber: 0, orgdata:null } } }</script> css脚本部分,未学习,不过就是页面丑一点而已,问题不大,回头再学把。 ...

April 9, 2019 · 1 min · jiezi

part-one-microservices

microservices微服务架构提供一个拆分大型应用为较小可相互影响通信的服务的手段。从整体拆分,每个服务可独立交付, 使得每个服务可独立的部署, 升级 , 缩小, 和替代。服务间通信通常通过网络连接HTTP 调研(request/response). Web sockets,message queues , pub/sub, 和 remote procedure calls(RPC) 也可以被用于连接独立组建。每个独立服务专注于一个单一任务, 通常按业务单元分离, 由 RESTful 协议管理。课程目标是详细介绍微服务的方式开发应用。 少谈为什么, 专注怎么做。 微服务很难。 它有大量的挑战和问题很难解决。 开始拆分大型应用前记住这一点。利关注隔离服务清晰的分离使开发更专注他们的特长领域,比如语言,框架,依赖, 工具和构建管道。例如, 一个前端 JavaScript 工程师可开发面向客户的视图,不需要理解后端 API 的代码实现。 可自由选择语言和框架,只需要通过 AJAX 请求来消费 RESTful API来与后端通信。换句话说, 因为通过 APIs 通信开发者可以将一个服务看作一个黑箱。实际的实现和复杂被隐藏。也就是说, 这是一个好注意来创建一些组织标准来确保每个团队可以一起发挥作用。例如代码质量,风格检查,代码检查, API 设计。清晰的分离意味着错误可最大限度的定位到开发者所工作的服务。使你可以安排初级开发到较不严格的服务即使他挂掉了对应服务, 剩余整体应用不受影响。可独立部署意味着更少的耦合使得规模化更容易。 也有助于消除一个团队堆另一个团队依赖完成的等待。更小的代码库不需要理解整个系统,小代码库更容易理解。只要有固定的必要 API 设计, 微服务栈的应用可以更快部署,更容易测试, 重构, 和规模化。服务保持一致的开发标准很重要,开发可以更容易从一个服务到另外一个。加速反馈回路在微服务中,开发通常掌握应用从立项到交付的整个生命周期。使团队不需要绑定特定的技术栈–像客户端UI,服务端等–团队可以更聚焦产品。自己为交付应用到用户负责。 因此, 应用如何在真实环境运行更清晰可见。这加速反馈循环,更容易修复 bug 和迭代。弊端设计复杂决定拆分应用为微服务并不是个轻松的任务。 通常在整个大项目更容易重构成独立模块。一旦拆分一个服务就无法回头。网络复杂通常一个大型应用所有事情发生于同一线程。 不需要每次调用其它服务。只要你把应用拆分成微服务, 你会发现你将不得不进行网络调用, 之前你只需要调用某一函数。这可能导致问题,特别是多个应用需要和另外一个通信, 导致乒乓效应。 不得不说明服务全面下降的原因。基础设施多服务将代码库复杂度转移到了平台和基础设施。 这可能很昂贵。另外你不得不使用正确的工具和适当的人力资源管理。数据持久化多数应用有状态曾, 像数据库和任务队列。 微服务站也需要记录服务部署地点和实例数量。 当一个实际服务实例启动,可合适的分配路由流量。 这通常称为 service discovery.由于我们处理容器,我们需要特别关注如何处理状态容器,因为他们不应下降。隔离特定服务的状态使得它分享和复制机器困难。你通常不得不处理不同来源且频繁调整的情况,这归结为设计原因。集成测试通常, 使用微服务架构开发应用, 你无法完整的测试所有服务知道你部署到一个预发或生产服务器。这获得反馈的时间太长。 幸运的是, Docker 能通过更容易的连接本地独立小应用服务来帮助加速这一个进程。日志,监控, 和调试也更难了。

April 8, 2019 · 1 min · jiezi

microservices-with-docker-flask-and-react 简介

在第一部分, 你学到如何使用 Docker 来创建一个基于python, postgres, 和 flask web 框架的 RESTful API 可重用开发环境. 在 app 启动本地运行后, 学习如何在 Amazon EC2 实例上部署。前置条件这不是一个入门课程。 此课程为至少有六个月网站开发经验的高级入门者设置。在开始之前, 你需要熟悉以下主题。 点击链接查看更多内容。主题资源Docker Docker Compose Docker Machine Flask 目标这部分结束,具备以下能力。。。使用 Flask 和 python 开发 RESTful API实践测试驱动开发本地使用 Docker 配置运行服务利用卷挂载代码到容器在 Docker 容器中进行单元和集成测试不同容器内的服务通信在 Docker 容器中使用 python 和 Flask在 Amazon EC2 实例中安装 Flask, Nginx, 和 Gunicorn使用 Docker Machine 部署到 EC2App最终 app:图例略彻底检查以下接口。。。接口HTTP 方法CRUD 方法结果/usersGETREADget all users/users/:idGETREADget single user/usersPOSTCREATEadd a user/users/pingGETREADsanity check本质上, app 运行在三个容器中– Flask, Postgres, Nginx.第一部分结束时, 你将完成部署上面的 app. 再接下来的部分我们添加权限和其他服务。第一部分完整代码: 依赖第一部分依赖Python v3.7.2Flask v1.0.2Docker v18.09.0Docker Compose v1.23.2Docker Machine v0.16.0Docker Compose file v3.7Postgres v11.1Flask-SQLAlchemy v2.3.2psycopg2 v2.7.6.1Flask-Testing v0.7.1Gunicorn v19.9.0Nginx v1.15.8Bulma 0.7.2耗时一章需要几个小时到一整天。 空余大块时间来完成一章, 特别是5,6,7. 这些较难的部分。 ...

April 8, 2019 · 1 min · jiezi

Flask:上传文件到服务器

Flask 上传文件到服务器<!– .html –><form action="/upload_file/" method=“POST” enctype=‘multipart/form-data’ > <input type=“file” class=“input-file " name=“file”> <button>上传</button></form>注意: 在使用包含文件上传控件的 form 时,必须添加enctype=‘multipart/form-data’用于发送二进制的文件, 不对字符编码。# .pyfrom flask import Flask,render_template,request,redirect,url_forimport osapp = Flask(name)@app.route(’/upload_file’, methods=[‘POST’])def upload_file(): if request.method == ‘POST’: files = request.files.get(‘file’) f = request.files[‘file’] upload_path = os.path.join(os.path.dirname(file), ‘imgs’, f.filename) f.save(upload_path) return redirect(url_for(‘upload_file’)) return render_template(‘upload.html’)

April 7, 2019 · 1 min · jiezi

微信账单数据可视化项目

微信账单数据可视化项目哈喽各位小伙伴们Night 微信账单分析来袭!让数据说话! Demo网址http://www.dnnnns.com话不多说,导出你的微信账单分析吧,走边了多少个城市,吃遍了多少个馆子,最爱消费的商铺等等…. 小Bill帮你的账单数据可视化。还支持导出账单小票操作吆,又可以装B了。(友商合作可重点看最后一图) 此外本项目完全开源,项目上传GitHub https://github.com/hltfaith/w…废话不多说直接上图,走起

April 2, 2019 · 1 min · jiezi

Web安全防范

简单总结一下日常web开发中会出现的一些安全问题,以Flask框架为例注入攻击(Injection)注入攻击主要包括系统命令注入,SQL注入,NoSQL注入,和ORM注入等,这里我们简单介绍一下SQL注入攻击原理在编写SQL语句时,如果直接将用户传入的输入作为参数使用字符串拼接的方式插入到SQL查询中的话,攻击者则可以利用SQL语句篡改,窃取数据库的信息。攻击实例@app.route(’/student’)def get_table(): password = request.args.get(‘password’) cur = db.execute(‘SELECT * FROM students WHERE password=’%s;’ % password) results = cur.fetchall() return results如果攻击者输入的值为"or 1=1–",这意味着会返回students表中的所有数据;或者输入"; drop table users; –",会删除students表中的所有数据。主要防范方法使用ORM验证输入类型转义特殊字符XSS攻击XSS(Cross-Site Scripting)即跨站脚本攻击,为了避免与CSS层叠样式表产生命名冲突,用X来表示Cross(交叉)攻击原理XSS其实是注入攻击的一种,通过将代码注入被攻击者的网站中,用户一旦访问便会执行被注入的恶意脚本。其中,XSS攻击主要分为反射型XSS攻击和存储型XSS攻击两种。攻击示例反射型XSS攻击反射型XSS又称为非持久型XSS,当某个站点存在XSS漏洞时,可以通过URL注入恶意脚本,当用户访问这个URL时,就会执行攻击脚本。@app.route(‘hello’)def hello(): name = request.args.get(’name’) response = ‘<h1>Hello, %s</h1>’ % name return response如果用户输入一段javascript代码,如访问http://example.com/hello/<script>alert(‘hahah’);</script>,客户端介绍到响应后,浏览器就会执行这段代码,当然这个示例代码不会构成任何威胁,但这意味着可以执行任意的js代码,鬼知道攻击者会做些什么!存储型XSS攻击存储型XSS也被称为持久型XSS,它和反射型XSS的行为类似,不过会把植入的恶意代码存储到数据库中,如在留言区写入一段重定向代码,这会导致用户在访问留言区页面时被重定向到一些恶意站点。防范措施HTML转义验证用户输入CSRF攻击CSRF(Cross Site Request Forgery)指跨域请求伪造,这是一种近年来才逐渐被大众了解的攻击方式。攻击原理攻击者利用用户在浏览器中保存的认证信息,向对应的站点发送伪造请求。如用户登录了A网站,认证信息保存在cookie中,当用户访问恶意网站B时,攻击者通过B网站发送一个伪造的请求提交到A网站服务器上,这会让A网站误以为请求来自于自己的网站,从而可以让攻击者成功篡改某些信息。攻击示例假设服务器端删除用户账户的视图操作为@app.route(’/account/delete’)def delete_account(): if not current_user.authenticated: abort(401) current_user.delete() return ‘deleted!‘当用户登录后,只要访问https://example.com/account/delete就会删除账户,那么在攻击者的网站上,只要创建一个显示图片的img,其中的src的属性加入删除账户的URL,当用户访问B网站时,浏览器解析网页会自动向该链接发送请求,此时你的登录信息就保存在浏览器的cookie中,因此,仅仅是访问B网站就会导致删除账户。防范措施正确使用HTTP方法CSRF令牌效验其中CSRF令牌是比较常用的方法,具体做法是在表单提交中添加一些伪随机数,即CSRF令牌(token),这里我们就不详细展开。

March 31, 2019 · 1 min · jiezi

别开心太早,Python 官方文档的翻译差远了

近几天,很多公众号发布了 Python 官方文档的消息。然而,一个特别奇怪的现象就发生了,让人啼笑皆非。Python 文档的中文翻译工作一直是“默默无闻”,几个月前,我还吐槽过这件事《再聊聊Python中文社区的翻译》,当时我们的进度是 10.3%,远远落后于日本和法国,甚至落后于巴西!这次所谓的中文版,当然是未完成翻译的残品。刚查了下,整体进度是 19.7%。有的公众号在发布消息的时候,说明了这不是官宣、不是正式发布版,还指出了中文版的访问地址是隐藏入口。这都是忠于事实的。然而,怪异的事情就在于,还有一些公众号在发布时,不知怎么误传,这个消息变成了官方正式发布、全部翻译完成、激动人心期盼已久,至于这个隐藏入口跳转问题、下载的文档为何是英文版的问题,则完全无法解释。这带来了极大的误导。由于曾搜集过 PEP 文档的翻译,我无意中也了解到关于翻译官方文档的一些情况。有以下几个现状吧:1、人员分散,缺乏核心。就我所见,在V站、华蟒邮件组、简书、知乎,分别有不同的人发起过翻译召集或者咨询,然而应者无几,并没有形成过足够大的核心组织。2、官方的翻译?Python 官方在 2017 年的 PEP-545 中推出了一种翻译模式,各国语言的翻译在协作平台Transifex 上进行。实际上,这才是官方认可的版本,也是最终发布的依据。前文说的进度,就是指在这个平台上的进度。3、野生的翻译?所谓野生,这里指的是不在Transifex 上的翻译。网上能看到有人零星地翻译了一些部分,但成果没有合入到官方平台上。社区内的译者还是挺多的,能力也有,只是太分散了。邮件组里就有位大佬,他说翻译过 40 多个标准库以及 C 模块的文档,但懒得组织。有人尝试组织过,时间久远的不说,就在去年夏天,某位在 PHP 界知名的站长开了个 Python 社区,召集了一批译者。他们译出了 Python 3.7 官方文档的入门教程部分,然而,后续内容的计划,似乎被放弃了。关于对待翻译的态度,似乎多数人表示:感兴趣,但是时间少,希望有人牵头组织,可以参与作贡献。我本人也怀着同样的想法。作为参与者、见证者、沾光者就好了,谁愿意花费那么多精力,承担重任,周旋策划,最后可能还讨不到好呢?写文章是重口难调,翻译文档更是如此,碰上质疑翻译水平的,还可商榷一下,而遇到下面这种杠精,只能是破坏心情。前面提到的那位站长,提出在他的社区维护一份长久维护的版本。事实上,他们真的做出了点实事,除了入门教程,还完成了两本经典书籍的翻译。然而,他们也招到了非议:不当的“官方文档”措辞、不合入官方使用的平台、网站的商业化运营……空谈的人总是有他们的理,不对事情做贡献,还无视别人的贡献。诚然,宣称“官方”中文文档,确实不妥,这只是个人/社区的行为,改正就好了;至于合入官方的途径,只需有翻译成果,也不难做到;最后,一个站点接些贴片广告,哪有什么不妥?我所了解到的社区翻译情况,大致如上。总体上,分裂分散现象严重,随性自由之处跟 Python 这语言倒挺像,而各怀能力各出成绩的现象,也跟为数众多的三方模块神似。也有默默在做事的人。从 4 个月前的 10% ,增长到现在的 20%,我们的翻译进度暴涨,这背后不知有几人在持续作出贡献?而他们还不为人知。距离官方文档全部译出,还有大步路要走,现实情况得认清。我总体上是乐观的。所以,最后聊个题外话。这几天,有个热得不行的话题——996.ICU ,才仅仅一周,Github star 数已经破 10 万,绝对创造纪录了。程序员发起的活动,就是有如此大的力量。就在本文写作过程中,Python 之父也给了这个项目 star ,而且发推声援。在官方文档的翻译事情上,或许我们是有点脱轨了,不过不要紧,在使用全球最大的同性交友平台上,我们是与国际接轨的。还有啊,等过完了愚人节,我们还有个节日也是与国际接轨的——国际劳动节,纪念 1886 年芝加哥工人大罢工,确立每日 8 小时工作制的节日。相关链接: 翻译进度:https://www.transifex.com/python-doc/python-newestV站话题:https://neue.v2ex.com/t/477400#reply147邮件列表:https://groups.google.com/forum/#!topic/python-cn/8H4qhhI6khw

March 30, 2019 · 1 min · jiezi

联科首个开源项目启动!未来可期,诚邀加入!

OpenEA开源组织是广州市联科软件有限公司旗下的一个“开放·共享·全球化”的开源组织。OpenEA全称“Open+Enterprise+Application”,意为开放的企业应用,致力让所有企业都能轻松用上流程应用开发平台。2019年联科将逐步开源基于流程应用的快速开发平台,以“专业·高效·创新·自由·开放·回馈”为理念,以“开源之林”为目标,构建开放的技术生态圈。最近【流程设计器组件FlowDesigner】作为第一个开源项目,主要用于设计和控制流程各个运转过程,欢迎广大技术好友前来码云交流探讨!流程设计器组件FlowDesigner前言FlowDesigner来源于Linkey BPM中的流程设计器,作用于流程运行过程中的图形描述。它的操作简捷轻巧,能快速绘制出流程图。组件单独也可以使用,并能嵌入到任何需要该组件的系统中。分享,是“开源”的真谛。机不可失失不再来,准备好加入我们了吗?立即前往码云Fork项目吧,地址:https://gitee.com/openEA/Flow…

March 25, 2019 · 1 min · jiezi

手把手教你如何用Crawlab构建技术文章聚合平台(一)

背景说到爬虫,大多数程序员想到的是scrapy这样受人欢迎的框架。scrapy的确不错,而且有很强大的生态圈,有gerapy等优秀的可视化界面。但是,它还是有一些不能做到的事情,例如在页面上做翻页点击操作、移动端抓取等等。对于这些新的需求,可以用Selenium、Puppeteer、Appium这些自动化测试框架绕开繁琐的动态内容,直接模拟用户操作进行抓取。可惜的是,这些框架不是专门的爬虫框架,不能对爬虫进行集中管理,因此对于一个多达数十个爬虫的大型项目来说有些棘手。Crawlab是一个基于Celery的分布式通用爬虫管理平台,擅长将不同编程语言编写的爬虫整合在一处,方便监控和管理。Crawlab有精美的可视化界面,能对多个爬虫进行运行和管理。任务调度引擎是本身支持分布式架构的Celery,因此Crawlab可以天然集成分布式爬虫。有一些朋友认为Crawlab只是一个任务调度引擎,其实这样认为并不完全正确。Crawlab是类似Gerapy这样的专注于爬虫的管理平台。本文将介绍如何使用Crawlab和Puppeteer抓取主流的技术博客文章,然后用Flask+Vue搭建一个小型的技术文章聚合平台。Crawlab在前一篇文章《分布式通用爬虫管理平台Crawlab》已介绍了Crawlab的架构以及安装使用,这里快速介绍一下如何安装、运行、使用Crawlab。安装到Crawlab的Github Repo用克隆一份到本地。git clone https://github.com/tikazyq/crawlab安装相应的依赖包和库。cd crawlab# 安装python依赖pip install -r crawlab/requirements# 安装前端依赖cd frontendnpm install安装mongodb和redis-server。Crawlab将用MongoDB作为结果集以及运行操作的储存方式,Redis作为Celery的任务队列,因此需要安装这两个数据库。运行在运行之前需要对Crawlab进行一些配置,配置文件为config.py。# project variablesPROJECT_SOURCE_FILE_FOLDER = ‘/Users/yeqing/projects/crawlab/spiders’ # 爬虫源码根目录PROJECT_DEPLOY_FILE_FOLDER = ‘/var/crawlab’ # 爬虫部署根目录PROJECT_LOGS_FOLDER = ‘/var/logs/crawlab’ # 日志目录PROJECT_TMP_FOLDER = ‘/tmp’ # 临时文件目录# celery variablesBROKER_URL = ‘redis://192.168.99.100:6379/0’ # 中间者URL,连接redisCELERY_RESULT_BACKEND = ‘mongodb://192.168.99.100:27017/’ # CELERY后台URLCELERY_MONGODB_BACKEND_SETTINGS = { ‘database’: ‘crawlab_test’, ’taskmeta_collection’: ’tasks_celery’,}CELERY_TIMEZONE = ‘Asia/Shanghai’CELERY_ENABLE_UTC = True# flower variablesFLOWER_API_ENDPOINT = ‘http://localhost:5555/api’ # Flower服务地址# database variablesMONGO_HOST = ‘192.168.99.100’MONGO_PORT = 27017MONGO_DB = ‘crawlab_test’# flask variablesDEBUG = TrueFLASK_HOST = ‘127.0.0.1’FLASK_PORT = 8000启动后端API,也就是一个Flask App,可以直接启动,或者用gunicorn代替。cd ../crawlabpython app.py启动Flower服务(抱歉目前集成Flower到App服务中,必须单独启动来获取节点信息,后面的版本不需要这个操作)。python ./bin/run_flower.py启动本地Worker。在其他节点中如果想只是想执行任务的话,只需要启动这一个服务就可以了。python ./bin/run_worker.py启动前端服务器。cd ../frontendnpm run serve使用首页Home中可以看到总任务数、总爬虫数、在线节点数和总部署数,以及过去30天的任务运行数量。点击侧边栏的Spiders或者上方到Spiders数,可以进入到爬虫列表页。这些是爬虫源码根目录PROJECT_SOURCE_FILE_FOLDER下的爬虫。Crawlab会自动扫描该目录下的子目录,将子目录看作一个爬虫。Action列下有一些操作选项,点击部署Deploy按钮将爬虫部署到所有在线节点中。部署成功后,点击运行Run按钮,触发抓取任务。这时,任务应该已经在执行了。点击侧边栏的Tasks到任务列表,可以看到已经调度过的爬虫任务。基本使用就是这些,但是Crawlab还能做到更多,大家可以进一步探索,详情请见Github。PuppeteerPuppeteer是谷歌开源的基于Chromium和NodeJS的自动化测试工具,可以很方便的让程序模拟用户的操作,对浏览器进行程序化控制。Puppeteer有一些常用操作,例如点击,鼠标移动,滑动,截屏,下载文件等等。另外,Puppeteer很类似Selenium,可以定位浏览器中网页元素,将其数据抓取下来。因此,Puppeteer也成为了新的爬虫利器。相对于Selenium,Puppeteer是新的开源项目,而且是谷歌开发,可以使用很多新的特性。对于爬虫来说,如果前端知识足够的话,写数据抓取逻辑简直不能再简单。正如其名字一样,我们是在操作木偶人来帮我们抓取数据,是不是很贴切?掘金上已经有很多关于Puppeteer的教程了(爬虫利器 Puppeteer 实战、Puppeteer 与 Chrome Headless —— 从入门到爬虫),这里只简单介绍一下Puppeteer的安装和使用。安装安装很简单,就一行npm install命令,npm会自动下载Chromium并安装,这个时间会比较长。为了让安装好的puppeteer模块能够被所有nodejs爬虫所共享,我们在PROJECT_DEPLOY_FILE_FOLDER目录下安装node的包。# PROJECT_DEPLOY_FILE_FOLDER变量值cd /var/crawlab# 安装puppeteernpm i puppeteer# 安装mongodbnpm i mongodb安装mongodb是为了后续的数据库操作。使用以下是Copy/Paste的一段用Puppeteer访问简书然后截屏的代码,非常简洁。const puppeteer = require(‘puppeteer’);(async () => { const browser = await (puppeteer.launch()); const page = await browser.newPage(); await page.goto(‘https://www.jianshu.com/u/40909ea33e50'); await page.screenshot({ path: ‘jianshu.png’, type: ‘png’, // quality: 100, 只对jpg有效 fullPage: true, // 指定区域截图,clip和fullPage两者只能设置一个 // clip: { // x: 0, // y: 0, // width: 1000, // height: 40 // } }); browser.close();})();关于Puppeteer的常用操作,请移步《我常用的puppeteer爬虫api》。编写爬虫啰嗦了这么久,终于到了万众期待的爬虫时间了。Talk is cheap, show me the code!咦?我们不是已经Show了不少代码了么…由于我们的目标是建立一个技术文章聚合平台,我们需要去各大技术网站抓取文章。资源当然是越多越好。作为展示用,我们将抓取下面几个具有代表性的网站:掘金SegmentFaultCSDN研究发现这三个网站都是由Ajax获取文章列表,生成动态内容以作为传统的分页替代。这对于Puppeteer来说很容易处理,因为Puppeteer绕开了解析Ajax这一部分,浏览器会自动处理这样的操作和请求,我们只着重关注数据获取就行了。三个网站的抓取策略基本相同,我们以掘金为例着重讲解。掘金首先是引入Puppeteer和打开网页。const puppeteer = require(‘puppeteer’);const MongoClient = require(‘mongodb’).MongoClient;(async () => { // browser const browser = await (puppeteer.launch({ headless: true })); // define start url const url = ‘https://juejin.im’; // start a new page const page = await browser.newPage(); … })();headless设置为true可以让浏览器以headless的方式运行,也就是指浏览器不用在界面中打开,它会在后台运行,用户是看不到浏览器的。browser.newPage()将新生成一个标签页。后面的操作基本就围绕着生成的page来进行。接下来我们让浏览器导航到start url。 … // navigate to url try { await page.goto(url, {waitUntil: ‘domcontentloaded’}); await page.waitFor(2000); } catch (e) { console.error(e); // close browser browser.close(); // exit code 1 indicating an error happened code = 1; process.emit(“exit “); process.reallyExit(code); return } …这里try catch的操作是为了处理浏览器访问超时的错误。当访问超时时,设置exit code为1表示该任务失败了,这样Crawlab会将该任务状态设置为FAILURE。然后我们需要下拉页面让浏览器可以读取下一页。 … // scroll down to fetch more data for (let i = 0; i < 100; i++) { console.log(‘Pressing PageDown…’); await page.keyboard.press(‘PageDown’, 200); await page.waitFor(100); } …翻页完毕后,就开始抓取数据了。 … // scrape data const results = await page.evaluate(() => { let results = []; document.querySelectorAll(’.entry-list > .item’).forEach(el => { if (!el.querySelector(’.title’)) return; results.push({ url: ‘https://juejin.com’ + el.querySelector(’.title’).getAttribute(‘href’), title: el.querySelector(’.title’).innerText }); }); return results; }); …page.evaluate可以在浏览器Console中进行JS操作。这段代码其实可以直接在浏览器Console中直接运行。调试起来是不是方便到爽?前端工程师们,开始欢呼吧!获取了数据,接下来我们需要将其储存在数据库中。 … // open database connection const client = await MongoClient.connect(‘mongodb://192.168.99.100:27017’); let db = await client.db(‘crawlab_test’); const colName = process.env.CRAWLAB_COLLECTION || ‘results_juejin’; const taskId = process.env.CRAWLAB_TASK_ID; const col = db.collection(colName); // save to database for (let i = 0; i < results.length; i++) { // de-duplication const r = await col.findOne({url: results[i]}); if (r) continue; // assign taskID results[i].task_id = taskId; // insert row await col.insertOne(results[i]); } …这样,我们就将掘金最新的文章数据保存在了数据库中。其中,我们用url字段做了去重处理。CRAWLAB_COLLECTION和CRAWLAB_TASK_ID是Crawlab传过来的环境变量,分别是储存的collection和任务ID。任务ID需要以task_id为键保存起来,这样在Crawlab中就可以将数据与任务关联起来了。整个爬虫代码如下。const puppeteer = require(‘puppeteer’);const MongoClient = require(‘mongodb’).MongoClient;(async () => { // browser const browser = await (puppeteer.launch({ headless: true })); // define start url const url = ‘https://juejin.im’; // start a new page const page = await browser.newPage(); // navigate to url try { await page.goto(url, {waitUntil: ‘domcontentloaded’}); await page.waitFor(2000); } catch (e) { console.error(e); // close browser browser.close(); // exit code 1 indicating an error happened code = 1; process.emit(“exit “); process.reallyExit(code); return } // scroll down to fetch more data for (let i = 0; i < 100; i++) { console.log(‘Pressing PageDown…’); await page.keyboard.press(‘PageDown’, 200); await page.waitFor(100); } // scrape data const results = await page.evaluate(() => { let results = []; document.querySelectorAll(’.entry-list > .item’).forEach(el => { if (!el.querySelector(’.title’)) return; results.push({ url: ‘https://juejin.com’ + el.querySelector(’.title’).getAttribute(‘href’), title: el.querySelector(’.title’).innerText }); }); return results; }); // open database connection const client = await MongoClient.connect(‘mongodb://192.168.99.100:27017’); let db = await client.db(‘crawlab_test’); const colName = process.env.CRAWLAB_COLLECTION || ‘results_juejin’; const taskId = process.env.CRAWLAB_TASK_ID; const col = db.collection(colName); // save to database for (let i = 0; i < results.length; i++) { // de-duplication const r = await col.findOne({url: results[i]}); if (r) continue; // assign taskID results[i].task_id = taskId; // insert row await col.insertOne(results[i]); } console.log(results.length: ${results.length}); // close database connection client.close(); // shutdown browser browser.close();})();SegmentFault & CSDN这两个网站的爬虫代码基本与上面的爬虫一样,只是一些参数不一样而已。我们的爬虫项目结构如下。运行爬虫在Crawlab中打开Spiders,我们可以看到刚刚编写好的爬虫。点击各个爬虫的View查看按钮,进入到爬虫详情。在Execute Command中输入爬虫执行命令。对掘金爬虫来说,是node juejin_spider.js。输入完毕后点击Save保存。然后点击Deploy部署爬虫。最后点击Run运行爬虫。点击左上角到刷新按钮可以看到刚刚运行的爬虫任务已经在运行了。点击Create Time后可以进入到任务详情。Overview标签中可以看到任务信息,Log标签可以看到日志信息,Results信息中可以看到抓取结果。目前在Crawlab结果列表中还不支持数据导出,但是不久的版本中肯定会将导出功能加入进来。总结在这一小节,我们已经能够将Crawlab运行起来,并且能用Puppeteer编写抓取三大网站技术文章的爬虫,并且能够用Crawlab运行爬虫,并且读取抓取后的数据。下一节,我们将用Flask+Vue做一个简单的技术文章聚合网站。能看到这里的都是有耐心的好同学,赞一个。Github: tikazyq/crawlab如果感觉Crawlab还不错的话,请加作者微信拉入开发交流群,大家一起交流关于Crawlab的使用和开发。 ...

March 15, 2019 · 3 min · jiezi

在TCP测试工具中发送HTTP信息

前言大家都知道,HTTP作为应用层协议,其实现主要还得感谢传输层提供端到端服务的TCP协议,TCP的三次握手和四次挥手完美的为HTTP提供稳定的传输服务.然而我们大多同学只是单纯的使用过这两种协议,今天笔者就小小尝试下在TCP测试工具中传输HTTP信息.环境及工具:Postman http测试工具SocketTool tcp测试工具一个可以接受请求及发送请求的后端服务,最好是运行在本地的源码项目,能具体查看数据信息.工具的下载地址(地址可能失效,必应上能轻松找到):Postman下载SocketTool下载开始先在本地运行一个python后端接口项目postmain测试接口改写成HTTP请求:POST /sensor/list HTTP/1.1Content-Type:application/jsonAuthorization:1.MTU1MjE5MDQ1MS41OTE4MTE3在SocketTool中创建tcp连接发送HTTP请求其中返回了响应头,头部字段.到此测试了用TCP工具发送HTTP请求的方法

March 10, 2019 · 1 min · jiezi

微信小程序端用户授权处理

taro1.安装 tarojsnpm install -g @tarojs/cli2.初始化项目taro init taro-login3.进入目录cd taro-login4.运行编译npm run dev:weapp5.修改文件 /src/app.js 代码…class App extends Component { config = { pages: [ ‘pages/user/user’, // new ‘pages/index/index’, ],6.微信登录需求如果我们需要用户一进入就取得用户的授权,以便于进行某些记录用户信息的操作,而微信又要求用户去点页面上的某个按钮才能获取信息,那怎么办呢?只能把一个按钮放在用户不能不点的地方,那就只有弹窗了。微信 wx.showModal 不能满足我们的需求,只能自己造一个,在用户第一次进来的时候弹窗,再次进来的时候则不显示。为了让这个组件具有拓展性,我们根据传入的值来修改 确认 位置按钮的属性,如果是授权的弹窗就改按钮属性为 openType=‘getUserInfo’。(摘自Taro 多端开发实现原理与项目实战)7.新建文件夹和modal.js文件/src/components/modal/modal.jsimport Taro, { Component } from ‘@tarojs/taro’import { View, Button } from ‘@tarojs/components’import ‘./modal.scss’class Modal extends Component { constructor() { super(…arguments) this.state = {} } onConfirmClick = () => { this.props.onConfirmCallback() } onCancelClick = () => { this.props.onCancelCallback() } onAuthConfirmClick = (e) => { this.props.onConfirmCallback(e.detail) } preventTouchMove = (e) => { e.stopPropagation() } render() { const { title, contentText, cancelText, confirmText, isAuth } = this.props return ( <View className=‘toplife_modal’ onTouchMove={this.preventTouchMove}> <View className=‘toplife_modal_content’> <View className=‘toplife_modal_title’>{title}</View> <View className=‘toplife_modal_text’>{contentText}</View> <View className=‘toplife_modal_btn’> <Button className=‘toplife_modal_btn_cancel’ onClick={this.onCancelClick}>{cancelText}</Button> {!isAuth ? <Button className=‘toplife_modal_btn_confirm’ onClick={this.onConfirmClick}>{confirmText}</Button> : <Button className=‘toplife_modal_btn_confirm’ openType=‘getUserInfo’ onGetUserInfo={this.onAuthConfirmClick}>授权</Button>} </View> </View> </View> ) }}Modal.defaultProps = { title: ‘’, contentText: ‘’, cancelText: ‘取消’, confirmText: ‘确定’, isAuth: false, onCancelCallback: () => { }, onConfirmCallback: () => { }}export default ModalModal 组件还算比较简单,组件的属性:字段说明title提示的标题contentText提示的描述cancelText取消按钮的文案cancelCallback取消回调的函数confirmText确认按钮的文案confirmCallback确认回调函数isAuth标记是否为授权按钮在内部设置了一个函数 preventTouchMove,其作用是弹窗出现蒙层的时候,阻止在蒙版上的滑动手势 onTouchMove。另外一个函数 authConfirmClick, 当 isAuth 为真时,确认按钮为取得个人信息的授权按钮,此时把个人信息当值传递给调用的函数。(摘自Taro 多端开发实现原理与项目实战)8.添加modal.scss文件/postcss-pxtransform rn eject enable/.toplife_modal { position: fixed; width: 100%; height: 100%; left: 0; top: 0; background-color: rgba(0, 0, 0, .8); z-index: 100; &_content { position: absolute; left: 50%; top: 50%; width: 600px; height: 320px; transform: translate(-50%, -50%); background-color: #fff; color: #232321; text-align: center; border-radius: 30px; } &_title { margin-top: 40px; font-size: 32px; } &_text { margin-top: 40px; font-size: 24px; } &_btn { position: absolute; bottom: 0; left: 0; width: 100%; height: 88px; border-top: 2px solid #eee; &_cancel { color: #8c8c8c; border-radius: 0; border: 0; border-right: 2px solid #eee; border-bottom-left-radius: 30px; } &_confirm { color: #666; border-radius: 0; border: 0; border-bottom-right-radius: 30px; } button { display: block; float: left; width: 50%; height: 88px; text-align: center; line-height: 88px; font-size: 32px; box-sizing: border-box; background-color: #fff; &::after { border: 0; } } }}9.新建文件/src/page/user/user.js,在user.js中引用该Modal组件import Taro, { Component } from ‘@tarojs/taro’;import { View, Image, Text } from ‘@tarojs/components’;import classnames from ‘classnames’import Modal from ‘../../components/modal/modal’;import { setGlobalData } from ‘../../utils/globalData’;import { getUserInfo, getIsAuth } from ‘../../utils/getUser’;class Info extends Component { config = { navigationBarTitleText: ‘TARO商城’, enablePullDownRefresh: true, backgroundTextStyle: ‘dark’, disableScroll: true } constructor() { super(…arguments) this.state = { animationClass: ‘’, showAuthModal: false, shouldIndexHidden: false, } this.env = process.env.TARO_ENV } hideAuthModal() { this.setState({ showAuthModal: false }) Taro.setStorage({ key: ‘isHomeLongHideAuthModal’, data: true }) } onProcessAuthResult = (userData) => { Taro.setStorage({ key: ‘isHomeLongHideAuthModal’, data: true }) if (userData.userInfo) { setGlobalData(‘userData’, userData) } this.setState({ showAuthModal: false }) getIsAuth() } async onPullDownRefresh() { if (this.state.shouldIndexHidden) { Taro.stopPullDownRefresh() // 停止下拉刷新 } else { await this.props.onFetchIndexList() Taro.stopPullDownRefresh() // 停止下拉刷新 } } componentDidMount() { if (this.env === ‘weapp’) { // 用类名来控制动画 setTimeout(async () => { const userData = await getUserInfo(); Taro.getStorage({ key: ‘isHomeLongHideAuthModal’, success: (res) => { const isHomeLongHideAuthModal = res.data; let showAuthModal if (!userData && !this.state.showAuthModal && !isHomeLongHideAuthModal) { showAuthModal = true } else { showAuthModal = false } this.setState({ animationClass: ‘animation’, showAuthModal }) }, fail: () => { let showAuthModal if (!userData && !this.state.showAuthModal) { showAuthModal = true } else { showAuthModal = false } this.setState({ animationClass: ‘animation’, showAuthModal }) } }) }, 1000) getIsAuth() } else if (this.env === ‘h5’ || this.env === ‘rn’) { console.log(‘h5登录’) } } render() { const { animationClass, shouldIndexHidden, showAuthModal } = this.state const { loginname, avatar_url } = this.props; const indexClassNames = classnames(‘container’, ‘index’, animationClass, { hidden: shouldIndexHidden }) return ( <View className={indexClassNames}> <View className=‘login-head’> <Image className=‘login-head-back’ src={require(’../../assets/img/loginBack.jpg’)} /> <Image className=‘login-head-head’ src={avatar_url ? avatar_url : require(’../../assets/img/head.png’)} /> {loginname ? <Text classnames=‘login-head-name’>{loginname}</Text> : null} </View> {showAuthModal && <Modal title=‘授权提示’ contentText=‘诚邀您完成授权,尊享畅游体验’ onCancelCallback={this.hideAuthModal.bind(this)} onConfirmCallback={this.onProcessAuthResult.bind(this)} isAuth />} </View> ) }}export default Info我们是如何保证这个应用只有一次授权弹窗呢? 关键代码是 Taro.setStorageSync(‘isHomeLongHideAuthModal’, true) ,如果弹出了一次,就在本地存一个标记已经弹过授权框,下一次弹窗之前可以根据此判断。至此我们完成了授权处理,但如果可以的话还是要优雅一些,在需要的时候才征求用户授权,保证用户体验。(摘自Taro 多端开发实现原理与项目实战)10.新建几个辅助文件/src/utils/globalData.jsconst globalData = {}export function setGlobalData(key, val) { globalData[key] = val}export function getGlobalData(key) { return globalData[key]}/src/utils/request.jsimport Taro from ‘@tarojs/taro’;import ‘@tarojs/async-await’;export function getJSON(url, data) { Taro.showLoading(); return Taro.request({ url: url, data: data, method: ‘GET’ }).then(result => { Taro.hideLoading(); return result; })}export function postJSON(url, data) { Taro.showLoading() return Taro.request({ header: { ‘content-type’: ‘application/json’ }, url: url, data: data, method: ‘POST’ }).then(result => { Taro.hideLoading(); return result; });}/src/constants/apiconst rootPath = ‘http://127.0.0.1:5000/v1’;const apiObject = { registerclient: rootPath + ‘/client/register’, //注册用户 getusertoken: rootPath + ‘/token’, // 登录成功之后获取用户token checkusertoken: rootPath + ‘/token/secret’, //验证用户token getuserinfo: rootPath + ‘/user’, //获取用户信息}export default apiObject;11. 新建一个登录获取token的函数/src/utils/getUser.jsimport Taro from ‘@tarojs/taro’import { getGlobalData } from ‘./globalData’import api from ‘../constants/api’;import { postJSON } from ‘../utils/request’;async function getUserInfo() { const userData = getGlobalData(‘userData’) if (userData) { return userData } try { const userData = await Taro.getUserInfo() return userData } catch (err) { console.log(err) console.log(‘微信登录或用户接口故障’) return null }}async function getIsAuth() { const loginRes = await Taro.login() let { userInfo } = await getUserInfo() let isAuth = false if (userInfo) { // 使用微信注册新用户 let result = await postJSON(api.registerclient, { “avatar”: userInfo.avatarUrl, “sex”: userInfo.gender, “nickname”: userInfo.nickName, “account”: loginRes.code, “type”: 200 }); if (result.data.error_code == 0) { // 登录用户,获取token,缓存到前端 const tokenRes = await Taro.login() let auth_token = await postJSON(api.getusertoken, { “account”: tokenRes.code, “type”: 200 }) if (auth_token.statusCode == 201) { Taro.setStorage({ key: ’token’, data: auth_token.data.token })// 设置到缓存 Taro.showToast({ title: ‘授权成功’ }) userInfo.isAuth = true isAuth = true } } else { Taro.showToast({ title: ‘授权失败,请稍后再试’, icon: ’none’ }) } } else { userInfo = { isAuth: false } } console.log(‘isAuth: ‘, isAuth) return isAuth}export { getUserInfo, getIsAuth}flask1.文件目录├── app│ ├── init.py│ ├── api│ │ ├── init.py│ │ └── v1│ │ ├── init.py│ │ ├── client.py│ │ ├── token.py│ │ └── user.py│ ├── apps.py│ ├── config│ │ ├── secure.py│ │ └── settings.py│ ├── libs│ │ ├── enums.py│ │ ├── error.py│ │ ├── error_code.py│ │ ├── format_time.py│ │ ├── get_openid.py│ │ ├── redprint.py│ │ ├── scope.py│ │ └── token_auth.py│ ├── models│ │ ├── init.py│ │ ├── pycache│ │ │ ├── init.cpython-36.pyc│ │ │ ├── base.cpython-36.pyc│ │ │ └── user.cpython-36.pyc│ │ ├── base.py│ │ └── user.py│ └── validators│ ├── init.py│ ├── base.py│ └── forms.py├── manage.py└── requirements.txt2.新建虚拟环境略过requirements.txtFlaskFlask-SQLAlchemypsycopg2-binarycymysqlFlask-Testingcoverageflake8flask-debugtoolbarflask-corsflask-migrateflask-bcryptpyjwtgunicornrequestsflask-httpauthflask-wtf3. 新建app目录和__init.py文件和apps.py# File: /app/apps.py# -- coding: utf-8 --from flask import Flask as _Flaskfrom flask.json import JSONEncoder as _JSONEncoderfrom app.libs.error_code import ServerErrorfrom datetime import dateclass JSONEncoder(_JSONEncoder): def default(self, o): if hasattr(o, ‘keys’) and hasattr(o, ‘getitem’): return dict(o) if isinstance(o, date): return o.strftime(’%Y-%m-%d’) raise ServerError()class Flask(_Flask): json_encoder = JSONEncoder# File: /app/init.py# -- coding: utf-8 --from .apps import Flaskfrom flask_debugtoolbar import DebugToolbarExtensionfrom flask_cors import CORSfrom flask_migrate import Migratefrom flask_bcrypt import Bcryptfrom app.models.base import db# instantiate 实例化toolbar = DebugToolbarExtension()migrate = Migrate(db=db)bcrypt = Bcrypt()def create_app(): # instantiate the app app = Flask(name) # enable CORS CORS(app) # set config app.config.from_object(‘app.config.settings’) app.config.from_object(‘app.config.secure’) # set up extensions toolbar.init_app(app) migrate.init_app(app, db) bcrypt.init_app(app) # register blueprints register_blueprints(app) register_plugin(app) # shell context for flask cli @app.shell_context_processor def ctx(): return {‘app’: app, ‘db’: db} return appdef register_blueprints(app): from app.api.v1 import create_blueprint_v1 app.register_blueprint(create_blueprint_v1(), url_prefix=’/v1’)def register_plugin(app): db.init_app(app) with app.app_context(): db.create_all()4.新建配置文件/app/config/目录,在这个目录下新建两个文件settings.py和secure.py# File: /app/config/settings.py# -- coding: utf-8 --# TOKEN_EXPIRATION = 30 * 24 * 3600DEBUG = ’true’TOKEN_EXPIRATION_DAYS = 30TOKEN_EXPIRATION_SECONDS = 0# encryption的复杂程度,默认值为12BCRYPT_LOG_ROUNDS = 4# File: /app/config/secure.py# -- coding: utf-8 --SQLALCHEMY_DATABASE_URI = \ ‘mysql+cymysql://root:root1234@localhost/flask-rest’SECRET_KEY = ‘‘SQLALCHEMY_TRACK_MODIFICATIONS = TrueMINA_APP = { ‘AppID’: ‘’, ‘AppSecret’: ‘’}5.在根目录下新建一个 manage.py#File: /manage.py# -- coding: utf-8 --from werkzeug.exceptions import HTTPExceptionfrom app import create_appfrom app.libs.error import APIExceptionfrom app.libs.error_code import ServerErrorapp = create_app()@app.errorhandler(Exception)def framework_error(e): “““全局拦截异常””” if isinstance(e, APIException): return e if isinstance(e, HTTPException): code = e.code msg = e.description error_code = 1007 return APIException(msg, code, error_code) else: if app.config[‘DEBUG’]: return ServerError() else: raise eif name == ‘main’: app.run()6.配置全局错误处理新建文件夹 /app/libs/#File: /app/libs/error.py# -- coding: utf-8 --“““自定义错误文件”““from flask import request, jsonfrom werkzeug.exceptions import HTTPExceptionclass APIException(HTTPException): “““自定义api请求错误,返回的json格式””” code = 500 msg = ‘抱歉,后台发生了错误 ( ̄︶ ̄)!’ error_code = 999 def init(self, msg=None, code=None, error_code=None, headers=None): if code: self.code = code if error_code: self.error_code = error_code if msg: self.msg = msg super(APIException, self).init(msg, None) def get_body(self, environ=None): body = dict( msg=self.msg, error_code=self.error_code, request=request.method + ’ ’ + self.get_url_no_param() ) text = json.dumps(body) return text def get_headers(self, environ=None): return [(‘Content-Type’, ‘application/json’)] @staticmethod def get_url_no_param(): full_path = str(request.full_path) main_path = full_path.split(’?’) return main_path[0]#File: /app/libs/error_code.py# -- coding: utf-8 --from werkzeug.exceptions import HTTPExceptionfrom app.libs.error import APIExceptionclass Success(APIException): code = 201 msg = ‘success’ error_code = 0class DeleteSuccess(Success): code = 202 error_code = 1class ServerError(APIException): code = 500 msg = ‘抱歉,后台发生了错误 ( ̄︶ ̄)!’ error_code = 999class ClientTypeError(APIException): code = 400 msg = ‘未检测到客户端类型’ error_code = 1006class ParameterException(APIException): code = 400 msg = ‘无效参数’ error_code = 1000class NotFound(APIException): code = 404 msg = ‘没有找到对应的资源 O__O…’ error_code = 1001class AuthFailed(APIException): code = 401 error_code = 1005 msg = ‘认证失败’class Forbidden(APIException): code = 403 error_code = 1004 msg = ‘禁止访问,不在对应权限内’class SingleLogin(APIException): code = 400 error_code = 2002 msg = ‘请重新登录’class DuplicateAct(APIException): code = 400 error_code = 2001 msg = ‘请勿重复操作'7.自定义红图#File: /app/libs/redprint.py# -- coding: utf-8 --class Redprint: def init(self, name): self.name = name self.mound = [] def route(self, rule, **options): def decorator(f): self.mound.append((f, rule, options)) return f return decorator def register(self, bp, url_prefix=None): if url_prefix is None: url_prefix = ‘/’ + self.name for f, rule, options in self.mound: endpoint = self.name + ‘+’ + \ options.pop(“endpoint”, f.name) bp.add_url_rule(url_prefix + rule, endpoint, f, **options)8.新建/app/api/v1/ 文件夹#File: /app/api/v1/init.py# -- coding: utf-8 --from flask import Blueprintfrom app.api.v1 import user, client, tokendef create_blueprint_v1(): bp_v1 = Blueprint(‘v1’, name) user.api.register(bp_v1) client.api.register(bp_v1) token.api.register(bp_v1) return bp_v19.新建注册接口client.py#File: /app/api/v1/client.py# -- coding: utf-8 --from app.libs.error_code import Success, ParameterExceptionfrom app.libs.redprint import Redprintfrom app.models.user import Userfrom app.validators.forms import ClientForm, UserEmailForm, MinaFormfrom app.libs.enums import ClientTypeEnumfrom app.libs.get_openid import get_openidapi = Redprint(‘client’)@api.route(’/register’, methods=[‘POST’])def create_client(): form = ClientForm().validate_for_api() promise = { ClientTypeEnum.USER_EMAIL: __register_user_by_email, ClientTypeEnum.USER_MINA: __register_user_by_mina, } promiseform.type.data return Success()def __register_user_by_email(): form = UserEmailForm().validate_for_api() User.register_by_email(form.nickname.data, form.account.data, form.secret.data)def __register_user_by_mina(): form = MinaForm().validate_for_api() account = get_openid(form.account.data) if account is None: raise ParameterException else: User.register_by_mina(form.nickname.data, account, form.sex.data, form.avatar.data)10.登录apitoken.py#File: /app/api/v1/token.py# -- coding: utf-8 --import jwtimport datetimefrom flask import current_app, jsonifyfrom app.libs.enums import ClientTypeEnumfrom app.libs.error_code import AuthFailedfrom app.libs.redprint import Redprintfrom app.models.user import Userfrom app.validators.forms import ClientForm, TokenFormfrom app.libs.format_time import get_format_timestampapi = Redprint(’token’)@api.route(’’, methods=[‘POST’])def get_token(): “““登录功能,认证成功返回token””” form = ClientForm().validate_for_api() promise = { ClientTypeEnum.USER_EMAIL: User.verify, ClientTypeEnum.USER_MINA: User.mina_login, } identity = promise[ClientTypeEnum(form.type.data)]( form.account.data, form.secret.data ) # Token token = generate_auth_token(identity[‘uid’], form.type.data, identity[’login_time’], identity[‘scope’]) t = {’token’: token.decode(‘ascii’)} return jsonify(t), 201@api.route(’/secret’, methods=[‘POST’])def get_token_info(): “““获取令牌信息””” form = TokenForm().validate_for_api() auth_token = form.token.data try: data = jwt.decode(auth_token, current_app.config[‘SECRET_KEY’]) except jwt.ExpiredSignatureError: raise AuthFailed(msg=‘token is expired’, error_code=1003) except jwt.InvalidTokenError: raise AuthFailed(msg=‘token is invalid’, error_code=1002) r = { ‘scope’: data[‘scope’], ‘create_at’: get_format_timestamp(data[‘iat’]), ’expire_in’: get_format_timestamp(data[’exp’]), ‘uid’: data[‘uid’], ’login_time’: get_format_timestamp(data[’login_time’]) } return jsonify(r)def generate_auth_token(uid, ac_type, login_time, scope=None): “““生成令牌””” try: payload = { ’exp’: datetime.datetime.utcnow() + datetime.timedelta( days=current_app.config[‘TOKEN_EXPIRATION_DAYS’], seconds=current_app.config[‘TOKEN_EXPIRATION_SECONDS’], ), ‘iat’: datetime.datetime.utcnow(), ‘uid’: uid, ’type’: ac_type.value, ’login_time’: login_time, ‘scope’: scope, } return jwt.encode( payload, current_app.config[‘SECRET_KEY’], algorithm=‘HS256’ ) except Exception as e: return e11.用户接口user.py#File: /app/api/v1/user.py# -- coding: utf-8 --from flask import jsonify, gfrom app.libs.error_code import DeleteSuccessfrom app.libs.redprint import Redprintfrom app.libs.token_auth import authfrom app.models.base import dbfrom app.models.user import Userapi = Redprint(‘user’)@api.route(’/<int:uid>’, methods=[‘GET’])@auth.login_requireddef super_get_user(uid): user = User.query.filter_by(id=uid).first_or_404() return jsonify(user)@api.route(’’, methods=[‘GET’])@auth.login_requireddef get_user(): uid = g.user.uid user = User.query.filter_by(id=uid).first_or_404() return jsonify(user)@api.route(’/<int:uid>’, methods=[‘DELETE’])def super_delete_user(uid): with db.auto_commit(): user = User.query.filter_by(id=uid).first_or_404() user.delete() return DeleteSuccess()@api.route(’’, methods=[‘DELETE’])@auth.login_requireddef delete_user(): uid = g.user.uid with db.auto_commit(): user = User.query.filter_by(id=uid).first_or_404() user.delete() return DeleteSuccess()@api.route(’’, methods=[‘PUT’])def update_user(): return ‘update'12.新建用户models#File: /app/models/base.py# -- coding: utf-8 --from datetime import datetimefrom flask_sqlalchemy import SQLAlchemy as _SQLAlchemy, BaseQueryfrom sqlalchemy import inspect, Column, Integer, SmallInteger, ormfrom contextlib import contextmanagerfrom app.libs.error_code import NotFoundclass SQLAlchemy(_SQLAlchemy): @contextmanager def auto_commit(self): try: yield self.session.commit() except Exception as e: db.session.rollback() raise eclass Query(BaseQuery): def filter_by(self, **kwargs): if ‘status’ not in kwargs.keys(): kwargs[‘status’] = 1 return super(Query, self).filter_by(**kwargs) def get_or_404(self, ident): rv = self.get(ident) if not rv: raise NotFound() return rv def first_or_404(self): rv = self.first() if not rv: raise NotFound() return rvdb = SQLAlchemy(query_class=Query)class Base(db.Model): abstract = True create_time = Column(Integer) status = Column(SmallInteger, default=1) def init(self): self.create_time = int(datetime.now().timestamp()) def getitem(self, item): return getattr(self, item) @property def create_datetime(self): if self.create_time: return datetime.fromtimestamp(self.create_time) else: return None def set_attrs(self, attrs_dict): for key, value in attrs_dict.items(): if hasattr(self, key) and key != ‘id’: setattr(self, key, value) def delete(self): “““删除用户,注销用户””” self.status = 0 def active(self): “““激活用户””” self.status = 1 def update(self): “““更新数据库的表内容””” try: db.session.commit() except Exception as e: db.session.rollback() return str(e) def keys(self): return self.fields def hide(self, keys): for key in keys: self.fields.remove(key) return self def append(self, keys): for key in keys: self.fields.append(key) return selfclass MixinJSONSerializer: @orm.reconstructor def init_on_load(self): self._fields = [] # self._include = [] self._exclude = [] self._set_fields() self.__prune_fields() def _set_fields(self): pass def __prune_fields(self): columns = inspect(self.class).columns if not self._fields: all_columns = set(columns.keys()) self._fields = list(all_columns - set(self._exclude)) def hide(self, args): for key in args: self._fields.remove(key) return self def keys(self): return self._fields def getitem(self, key): return getattr(self, key)#File: /app/models/user.py# -- coding: utf-8 --from datetime import datetimefrom flask import current_appfrom sqlalchemy import Column, Integer, String, SmallIntegerfrom app import bcryptfrom app.libs.error_code import AuthFailedfrom app.models.base import Base, dbfrom app.libs.format_time import get_current_timestampfrom app.libs.get_openid import get_openidfrom app.libs.error_code import ParameterExceptionclass User(Base): id = Column(Integer, primary_key=True) nickname = Column(String(24), unique=True) email = Column(String(24), unique=True) mobile = Column(String(11), unique=True) sex = Column(Integer, default=0) # 1男2女 avatar = Column(String(200)) # 头像 register_ip = Column(String(100)) # 注册ip auth = Column(SmallInteger, default=1) # 权限 openid = Column(String(80), unique=True) _password = Column(‘password’, String(100)) login_time = Column(Integer, default=int(datetime.now().timestamp())) @property def login_datetime(self): if self.login_time: return datetime.fromtimestamp(self.login_time) else: return None def keys(self): return [‘id’, ’nickname’, ’email’, ‘auth’] @property def password(self): return self._password @password.setter def password(self, raw): self._password = bcrypt.generate_password_hash( raw, current_app.config[‘BCRYPT_LOG_ROUNDS’]).decode(‘utf-8’) @staticmethod def register_by_email(nickname, account, secret): “““通过邮箱注册””” with db.auto_commit(): user = User() user.nickname = nickname user.email = account user.password = secret db.session.add(user) @staticmethod def verify(email, password): “““通过邮箱登录””” user = User.query.filter_by(email=email).first_or_404() if not user.check_password(password): raise AuthFailed() scope = ‘AdminScope’ if user.auth == 2 else ‘UserScope’ login_time = get_current_timestamp() user.login_time = login_time User.update(User) return {‘uid’: user.id, ‘scope’: scope, ’login_time’: login_time} def check_password(self, raw): if not self._password: return False return bcrypt.check_password_hash(self._password, raw) @staticmethod def register_by_mina(nickname, account, sex, avatar): “““通过小程序注册””” with db.auto_commit(): user = User() user.nickname = nickname user.openid = account user.sex = sex user.avatar = avatar db.session.add(user) @staticmethod def mina_login(account, secret): “““通过小程序登录””” openid = get_openid(account) # 通过code来来获取openid if openid is None: raise ParameterException user = User.query.filter_by(openid=openid).first_or_404() scope = ‘AdminScope’ if user.auth == 2 else ‘UserScope’ login_time = get_current_timestamp() user.login_time = login_time User.update(User) return {‘uid’: user.id, ‘scope’: scope, ’login_time’: login_time}13.添加自定义的函数枚举登录类型# File: /app/libs/enums.py# -- coding: utf-8 --from enum import Enumclass ClientTypeEnum(Enum): USER_EMAIL = 100 USER_MOBILE = 101 # 微信小程序 USER_MINA = 200 # 微信公众号 USER_WX = 201 14.时间辅助函数#File: /app/libs/format_time.py# -- coding: utf-8 --import datetimedef get_current_date(): “““获取当前时间””” return datetime.datetime.now()def get_current_timestamp(): “““获取当前时间的时间戳””” return int(datetime.datetime.now().timestamp())def get_format_date(date=None, format_time="%Y-%m-%d %H:%M:%S”): “““获取格式化时间””” if date is None: date = datetime.datetime.now() return date.strftime(format_time)def get_format_timestamp(date=None, format_time="%Y-%m-%d %H:%M:%S”): “““格式化时间戳””” if date is None: date = datetime.datetime.now() return datetime.datetime.fromtimestamp(date).strftime(format_time)15.获取微信openid的函数#File: /app/libs/get_openid.py# -- coding: utf-8 --import requestsimport jsonfrom flask import current_appdef get_openid(code): api = ‘https://api.weixin.qq.com/sns/jscode2session' params = ‘appid={0}&secret={1}&js_code={2}&grant_type=authorization_code’ \ .format(current_app.config[‘MINA_APP’][‘AppID’], current_app.config[‘MINA_APP’][‘AppSecret’], code) url = api + ‘?’ + params response = requests.get(url=url) res = json.loads(response.text) openid = None if ‘openid’ in res: openid = res[‘openid’] return openid16.scope.py权限管理函数#File: /app/libs/scope.py# -- coding: utf-8 --class Scope: allow_api = [] allow_module = [] forbidden = [] def add(self, other): “““重载加号运算符””” self.allow_api = self.allow_api + other.allow_api self.allow_api = list(set(self.allow_api)) self.allow_module = self.allow_module + other.allow_module self.allow_module = list(set(self.allow_module)) self.forbidden = self.forbidden + other.forbidden self.forbidden = list(set(self.forbidden)) return selfclass AdminScope(Scope): allow_module = [‘v1.user’] def init(self): passclass UserScope(Scope): forbidden = [‘v1.user+super_get_user’, ‘v1.user+super_delete_user’] def init(self): self + AdminScope()def is_in_scope(scope, endpoint): # 把类名的字符串实例化 scope = globals()scope splits = endpoint.split(’+’) red_name = splits[0] if endpoint in scope.forbidden: return False if endpoint in scope.allow_api: return True if red_name in scope.allow_module: return True else: return False17.jwt生成token和验证token#File: /app/libs/token_auth.py# -- coding: utf-8 --import jwtfrom collections import namedtuplefrom flask import current_app, g, requestfrom flask_httpauth import HTTPBasicAuthfrom app.models.user import User as Userfrom app.libs.scope import is_in_scopefrom app.libs.error_code import AuthFailed, Forbidden, SingleLoginauth = HTTPBasicAuth()User = namedtuple(‘User’, [‘uid’, ‘ac_type’, ‘scope’, ’login_time’])@auth.verify_passworddef verify_password(token, password): user_info = verify_auth_token(token) if not user_info: return False else: g.user = user_info return Truedef verify_auth_token(token): try: data = jwt.decode(token, current_app.config[‘SECRET_KEY’]) except jwt.ExpiredSignatureError: raise AuthFailed(msg=‘token is expired’, error_code=1003) except jwt.InvalidTokenError: raise AuthFailed(msg=‘token is invalid’, error_code=1002) uid = data[‘uid’] ac_type = data[’type’] scope = data[‘scope’] login_time = data[’login_time’] user = User.query.filter_by(id=uid).first_or_404() if login_time != user.login_time: raise SingleLogin() # request 视图函数 allow = is_in_scope(scope, request.endpoint) if not allow: raise Forbidden() return User(uid, ac_type, scope, login_time)18.验证函数validators#File: /app/validators/base.py# -- coding: utf-8 --from flask import requestfrom wtforms import Formfrom app.libs.error_code import ParameterExceptionclass BaseForm(Form): def init(self): data = request.get_json(silent=True) args = request.args.to_dict() super(BaseForm, self).init(data=data, **args) def validate_for_api(self): valid = super(BaseForm, self).validate() if not valid: raise ParameterException(msg=self.errors) return self#File: /app/validators/forms.py# -- coding: utf-8 --from wtforms import StringField, IntegerFieldfrom wtforms.validators import DataRequired, length, Email, Regexpfrom wtforms import ValidationErrorfrom app.libs.enums import ClientTypeEnumfrom app.models.user import Userfrom app.validators.base import BaseForm as Formclass ClientForm(Form): account = StringField(validators=[ DataRequired(message=‘不允许为空’), length(min=5, max=32)]) secret = StringField() type = IntegerField(validators=[DataRequired()]) def validate_type(self, value): try: client = ClientTypeEnum(value.data) except ValueError as e: raise e self.type.data = clientclass UserEmailForm(ClientForm): account = StringField(validators=[Email(message=‘invalidate email’)]) secret = StringField(validators=[ DataRequired(), # password can only include letters , numbers and “” Regexp(r’^[A-Za-z0-9&$#@]{6,22}$’) ]) nickname = StringField(validators=[DataRequired(), length(min=2, max=22)]) def validate_account(self, value): if User.query.filter_by(email=value.data).first(): raise ValidationError()class TokenForm(Form): token = StringField(validators=[DataRequired()])class MinaForm(Form): account = StringField(validators=[ DataRequired(message=‘不允许为空’), length(min=10, max=80)]) nickname = StringField(validators=[DataRequired()]) sex = IntegerField(validators=[DataRequired()]) avatar = StringField(validators=[DataRequired()]) type = IntegerField(validators=[DataRequired()]) def validate_type(self, value): try: client = ClientTypeEnum(value.data) except ValueError as e: raise e self.type.data = client19. error_code.mderror_codemsg0创建成功1删除成功999未知错误 - 后台发生了错误1000无效参数1001没有找到对应的资源1002token is invalid1003token is expired1004禁止访问,不在对应权限内1005认证失败1006未检测到客户端类型2001请勿重复操作2002请重新登录20. jwt登录图片Flask + PyJWT 实现基于Json Web Token的用户认证授权endpointHTTP MethodAuthenticated?Resultjson Body/v1/client/registerPOSTNO注册用户{“account”:“666@qq.com”,“secret”:“123456”,“type”:100,“nickname”:“666”}/v1/tokenPOSTNO获取token{“account”:“666@qq.com”,“secret”:“123456”,“type”:100,“nickname”:“666”}/v1/userGETNO用户详情空/v1/user/2GETYES管理员获取用户详情空/v1/token/secretPOSTNOtoken详情{“token”:”"}通过 jwt 的 paylod 携带 login_time,和数据库 User 表中的 login_time 进行匹配作为单点登录修改数据库cd usersflask db migrateflask db upgrade运行结果注册成功注册失败登录成功登录失败成功获取用户信息用户重新登录,token变更,原token无法获取用户信息不带token请求,无法获取用户信息token过期,无法获取用户信息有权限获取其他用户信息无权限获取其他用户信息学习资料:慕课网-Python Flask构建可扩展的RESTful API慕课网-掌握Taro多端框架 快速上手小程序/H5开发Taro 多端开发实现原理与项目实战 ...

March 6, 2019 · 13 min · jiezi

个推微服务网关架构实践

作者:个推应用平台基础架构高级研发工程师 阿飞在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用N个微服务的接口完成一个用户请求。因此,在客户端和服务端之间增加一个API网关成为多数微服务架构的必然选择。在个推的微服务实践中,API网关也起着至关重要的作用。一方面,API网关是个推微服务体系对外的唯一入口;另一方面,API网关中实现了很多后端服务的共性需求,避免了重复建设。个推微服务网关的设计与实现个推微服务主要是基于Docker和Kubernetes进行实践的。在整个微服务架构中,最底层的是个推私有部署的Kubernetes集群,在集群之上,部署了应用服务。个推的应用服务体系共分为三层,最上一层是网关层,接着是业务层,最下面是基础层服务。在部署应用服务时,我们使用了Kubernetes的命名空间对不同产品线的产品进行隔离。除了应用服务外, Kubernetes集群上还部署了Consul来实现配置的管理、Kube-DNS实现服务注册与发现,以及一些辅助系统来进行应用和集群的管理。下图是个推微服务体系的架构图。个推对API网关的功能需求主要有以下几方面:要支持配置多个产品,为不同的产品提供不同的端口;动态路由;URI的重写;服务的注册与发现;负载均衡;安全相关的需求,如session校验等;流量控制;链路追踪;A/B Testing。在对市面上已有的网关产品进行调研后,我们的技术团队发现,它们并不太适合应用于个推的微服务体系。第一,个推配置的管理都是基于Consul实现的,而大部分网关产品都需要基于一些DB存储,来进行配置的管理;第二,大部分的网关产品提供的功能比较通用,也比较完善,这同时也降低了配置的复杂度以及灵活性;第三,大部分的网关产品很难直接融入到个推的微服务架构体系中。最终,个推选择使用了OperResty和Lua进行自研网关,在自研的过程中,我们也借鉴了其他网关产品的一些设计,如Kong和Orange的插件机制等。个推的API网关的插件设计如下图所示。OpenResty对请求的处理分为多个阶段。个推API网关的插件主要是在Set、Rewrite、Access、Header_filter、Body_filter、Log这六个阶段做相应的处理,其中,每一个插件都可以在一个或多个阶段起到相应的作用。在一个请求到达API网关之后,网关会根据配置为该请求选择插件,然后根据每个插件的规则,进一步过滤出匹配规则的插件,最后对插件进行实例化,对流量进行相应的处理。我们可以通过举例来理解这个过程,如上图所示,localhost:8080/api/demo/test/hello这个请求到达网关后,网关会根据host和端口确定产品信息,并提取出URI(/api/demo/test/hello),然后根据产品的具体配置,筛选出需要使用的插件——Rewrite_URI、Dyups和Auth,接下来根据每个插件的规则配置进行过滤,过滤后,只有Rewrite_URI和Dyups两个插件被选中。之后实例化这两个插件,在各个阶段对请求进行处理。请求被转发到后端服务时,URI就被rewrite为“/demo/test/hello”,upstream也被设置为“prod1-svc1”。请求由后端服务处理之后,响应会经网关返回给客户端,这就是整个插件的设计和工作的流程。为了优化性能,我们将插件的实例化延缓到了请求真正开始处理时,在此之前,网关会通过产品配置和规则,过滤掉不需要执行的插件。从图中也可以看出,每个插件的规则配置都很简单,并且没有统一的格式,这也确保了插件配置的简单灵活。网关的配置均为热更新,通过Consul和Consul-Template来实现,配置在Consul上进行更新后,Consul-Template会将其实时地拉取下来,然后通过以下两种方式进行更新。(1)通过调用Update API,将配置更新到shared-dict中。(2)更新配置文件,利用Reload OpenResty实现配置文件的更新。个推微服务网关提供的主要功能1.动态路由动态路由主要涉及到三个方面:服务注册、服务发现和请求转发。如下图所示,服务的注册和发现是基于Kubernetes的Service和Kube-DNS实现的,在Consul中,会维持一个服务的映射表,应用的每一个微服务都对应Kubernetes上的一个Service,每创建一个Service都会在Consul上的服务映射表中添加一项(会被实时更新到网关的共享内存中)。网关每收到一个请求都会从服务映射表中查询到具体的后端服务(即Kubernetes中的Service名),并进行动态路由。Kube-DNS可以将Service的域名解析成Kubernetes内部的ClusterIP,而Service代理了多个Pod,会将流量均衡地转发到不同的Pod上。2.流量控制流量控制主要是通过一个名为“Counter”的后端服务和网关中的流控插件实现的。Counter负责存储请求的访问次数和限值,并且支持按时间维度进行计数。流控插件负责拦截流量,调用Counter的接口进行超限查询,如果Counter返回请求超限,网关就会直接拒绝访问,实现限次的功能,再结合时间维度就可以实现限频的需求。同时流控插件通过输出日志信息到fluent-bit,由fluent-bit聚合计次来更新Counter中的计数。3.链路追踪整个微服务体系的链路追踪是基于分布式的链路追踪系统Zipkin来实现的。通过在网关安装Zipkin插件和在后端服务中引入Zipkin中间件,实现最终的链路追踪功能。具体架构如下图所示。4. A/B测试在A/B测试的实现中,有以下几个关键点:(1)所有的策略信息都配置在Consul上,并通过Consul-Template实时生效到各个微服务的内存中;(2)每条策略均有指明,调用一个微服务时应调用A还是B(默认为A);(3)网关中实现A/B插件,在请求到达网关时,通过A/B插件配置的规则,即可确定请求适用的A/B策略;(4)网关会将请求适用的A/B策略通过URL参数传递下去;(5)每个微服务通过传递下来的策略,选择正确的服务进行访问。下图给出了两种场景下的调用链路。总结以上就是个推微服务网关的设计和主要功能的实现。之后,个推的技术团队会不断提升API网关的弹性设计,使其能够在故障出现时,缩小故障的影响范围;同时,我们也会继续将网关与DevOps平台做进一步地结合,以确保网关在迭代更新时,能够有更多的自动化测试来保证质量,实现更快速地部署。

March 5, 2019 · 1 min · jiezi

整理了一周的Python资料,包含各阶段所需网站、项目,收藏了慢慢来

这周应该有不少学校已经开学了,那么同学们都该动起来了,把家里面的那些懒习惯给扔掉了可以。不知怎么的,最近不少关注我的读者都开始私信我怎么学好python?零基础转行是不是合适,还有希望吗?今年30了,还能不能转IT?其实关于零基础转型的,我以前写过一篇文章,没有看过的都可以看看:「零基础非科班如何成长为五百强Arch」,另外还有一篇知乎点赞1k+关于如何学习python的也建议都看下:「万字谏言,给那些想学Python的人,建议收藏后细看!」。今天就把剩余板块给一一填充,意在做成一个系列,让大家看了这个系列后,明白自己选择了IT这条路后,应该干什么,怎么干。相信大家看完以上两篇文章后多少都会有个问号,除了我推荐的「笨办法」外,就没什么资料的,而很多新手村玩家都喜欢问一个问题:有什么资料可以参考的吗?有什么实战项目可以借鉴的吗?今天这篇文章,我花了一周的时间搜索、整理、调研、筛选,最后定稿。希望能够帮助到大家,减少在起步阶段的油耗,集中精神突破技术。我把链接一起贴出来,大家收藏后,可以去电脑上打开,比较方便。虽然强调过很多次了,但是还是要多提一句,不要看python2.x,如果你是零基础过来的,请直接开始你的py3.x 之路。建议3.6,3.7的一些特性可能对你不是很重要。1.初出茅庐我不会推荐你们去看官方文档的,因为我知道,你们不会去看的1.廖雪峰老师,包括我自己,我相信很多读者应该都多少看过:「廖雪峰的官方网站」:https://www.liaoxuefeng.com/w…2.Vamei老师在cnblogs上的一个目录,我基本都看过,内容比较基础,很适合零基础的同学看:「python快速教程」:http://www.cnblogs.com/vamei/…3.实验楼,这个网站其实做得蛮好的,虽然是收费项目,但是说实话,如果你想认真学习的话,这些学费该付的就付吧。网站主要是将python的知识点和小项目结合起来了:「Python基础+项目实战课程」:https://www.shiyanlou.com/cou…4.我一直推荐的「笨办法学 Python」现在有了在线版,只不过是英文版的,别和我说英语看不懂!你这是要我去接英语广告吗……,当然他还有收费的课程,看你自己喜欢咯:「learnpythonthehardway」:https://learnpythonthehardway…5.这个网站我是订阅了的,每周都会有更新,内容的话基本都和Python相关:文章、教程、演讲、书籍、项目、工作等都有:「Python Weekly」:https://www.pythonweekly.com/6.「Pycrumbs」是搜集了各种免费Python的资料,你可以收藏后慢慢看:「Pycrumbs」:https://github.com/kirang89/p…2.小试牛刀经过以上内容的学习,基本的语法、函数、类的定义和调用应该都掌握了,接下来就是找些小练习,试试自己的三把刷子了。1.首当其冲的就是很多人都会推荐的「Python challenge」,现在已经到33关了,可以看看自己能到第几关哦:「pythonchallenge」:http://www.pythonchallenge.com2.对于很多人来说,英文看不懂,我又没接到英语广告的,我帮你们找了一个中文网站:「Python中文学习大本营」:http://www.pythondoc.com/3.再给大家推荐一个爬虫er必看的博客,我们的崔大系列:「崔庆才的个人博客」:https://cuiqingcai.com/4.虽然已经不更新了,但是已有的联系够大家琢磨半天了,建议认真的去把每一题都做了:「每天一点小练习」:https://github.com/Yixiaohan/…3.登堂入室经过小试牛刀后,我相信现在的你应该已经跃跃欲试了,心里一句话:还有谁。那么是时候开始找些完整项目跟着抄了,哦不,是临摹,是跟着敲。你们千万别ctrl c,ctrl v 的把内容拷过来了,这没有任何效果。1.简书上我找到一个非常棒的「Django By Example」的中文翻译系列,推荐给大家,「Django By Example」本身就是一本非常不错的Django实战书:「Django By Example」:https://www.jianshu.com/p/058…2.再给大家推荐一个Flask写网站的教程,我推荐给很多人过,非常好,跟着作者一步步学习如何用flask开发一款属于自己的博客管理系统:「Flask 10天开发一个网站」:https://zhuanlan.zhihu.com/p/…3.在学习爬虫的路上,你一定会遇到一个叫scrapy的怪物,别人都告诉你要用它,所以你就去搜了,发现全特么是英文,又溜了。我给大家找了一份「Scrapy Cookbook」的中文版:「Scrapy Cookbook」:https://scrapy-cookbook.readt…4.「超级马里奥第一关」用pygame写的,这个就比较有意思了,大家有兴趣的可以跟着敲敲:「Super Mario Bros Level 1」:https://github.com/justinmeis…4.游刃有余再经过以上环节后,你必须要开始修炼心法了,练武之人,必修内功,否则就是花拳绣腿,形如:1.「LeetCode」想必大家都知道,也有一些读者刷过,但有多少人坚持下来了?LeetCode可以说是Python内功的九阴真经,哦不,是九阳神功。在平时写代码的时候你不一定会用到,但是他却是你解决问题的思想源泉:「LeetCode China」:https://leetcode-cn.com/2.如果说算法是一个程序员的九阳神功,那么设计模式就是你的乾坤大挪移。设计模式在日常工作中会经常用到,对于不同的场景会需要用到不同的模式。「python-patterns」是我觉得非常棒的一个项目,如果你可以跟着他一个个学的话,你回头看看昨天写的Django Example里的代码,会有种“哪个傻逼写的垃圾代码啊”的错觉:「python-pattern」:https://github.com/faif/pytho…我有一个项目,上面有大多数的练习代码,包含:leet-code刷题,设计模式练习,爬虫项目,小应用,微信机器人等等。关注公众后,后台回复关键字:python资料,获取项目包本篇文章对不同阶段的人群都适用,别再说Python怎么学,没有实战项目了。撸袖子干呗,别墨迹了。

March 1, 2019 · 1 min · jiezi

ETM: “黄金时代”不会结束,而是重启

在《希腊的黄金时代》一书中,作者W·杜兰认为,雅典的民主政治“是历史上最狭隘也是最充实的:最狭隘,系指其享受民主权利的人数之少,最充实,是谓全体公民在管制立法及治理公众事务上的直接与平等权利”。曾经的比特币,也经历过这样一个黄金时代。数字黄金比特币:极客们的伊甸园2009年1月,第一个数字货币——比特币正式上线运行。谁也没有意识到,与此同时,一个最具含金量的职业-矿工诞生了。不同于现代国家发行的货币,比特币由分布式网络基于数学计算产生,所有交易由全网节点共同记账确保其不可篡改。也就是说,它的本质,其实是⼀个公开的、不可篡改的分布式账本。但是,记账需要付出⼤量的计算能力,需要硬件和电费的投入,全网如何才能参与进来争当记账人呢?创始人中本聪天才地设计出了矿工奖励制度——每一次记账成功,就相当于比特币进行了一次“印钞”动作,而且这新出的“钞票”只归记账成功者所有。因为比特币的总量是恒定的,越到后来就会越少,犹如黄金一样,因此,这个过程被形象地称为“挖矿”。一开始谁也没当回事,毕竟知道的人不多,知道的人中感兴趣的也不多,而那些少数感兴趣的极客,也更多是出于一种情怀——他们和中本聪一样,厌倦了金融操纵者的翻云覆雨,厌倦了经济危机下中心机构的欺骗,便以行动支持那些试图建立某种金融乌托邦的尝试。哈尔 · 芬尼是典型的例子,他是密码圈成名已久的大牛,也是第一笔比特币转账的接收者,更是最早时期中本聪以外唯一的挖矿者——以此向中本聪反馈系统运行中可能有的一些错误和改进建议。比特币挖矿的早期阶段,单个区块奖励为50 BTC,且全网算力很小。那时候没有算力竞赛,且挖矿难度很低,遍地黄金,俯拾皆是。只靠普通电脑CPU挖矿的芬尼,有时一天便能挖出100多枚比特币。这简直就是极客们理想中的伊甸园。但这世上本没有一个地方是真正的人间乐土,世外桃源。比特币,也终究不是伊甸园。最悲哀的事,杀死恶龙的英雄变成了恶龙在缅甸,有这么一个传说:有一条恶龙,每年要求村庄献祭一个处女,每年这个村庄都会有一个少年英雄去与恶龙搏斗,但无人生还。又一个英雄出发时,有人悄悄尾随。龙穴铺满金银财宝,英雄用剑刺死恶龙,然后坐在尸身上,看着闪烁的珠宝,慢慢地长出鳞片、尾巴和触角,最终变成恶龙。非常戏剧性的是,恶龙的故事在比特币身上重演了。2010年5月22日,一位名叫Laszlo Hanyecz的程序员用1万枚比特币购买了两个披萨,被广泛认为是用比特币进行的首笔交易。如果那个程序员知道比特币如今的价格,估计肠子都悔青了!但对于比特币来说,这却是个至关重要的时刻。比特币的价值由此从极客圈向外扩散,一升再升,单枚比特币最高涨到了两万多美元。这也意味着,挖矿名副其实的成为了印钞。利益驱使下,资本蜂拥而至,挖矿从最初的个人电脑挖矿、显卡挖矿、个人用矿机在家里挖矿,迅速发展成为集群化、专业化的大规模挖矿。算力被垄断,普通人挖矿越来越成为传说。这何尝不是一件悲哀的事?中本聪的初始想法,是希望更多的节点、更多的个体参与进来,获取原始财富,站到相同的起跑线,共同形成一个真正的去中心化生态,公平参与到区块链的建设中来。而那些后来者们,却以继承者的面目,以改革者的说辞,把自己重新打造成中心化的“恶龙”,携中心化之力量,剥夺普通人的财富获取通道。矿场矿池主如比特大陆等如是,纵容矿池矿场的以太坊等数字货币发行者亦如是。“黄金时代”不会结束,而是重启资本是如此所向披靡。留给我们普通人的,似乎就是偶尔幻想下回到2009那种美好时光,个人电脑随便开着,顺便就把矿挖了,顺便就实现车厘子自由了,顺便就成隐形富豪了。但真的只能是幻想吗?有一群较真的学院派,还真就这么干了。他们有诺贝尔奖获得者两名,有名校教授学者若干,好吧,这不重要,重要的是——他们用一年的时间,经过反复探索和测试,创造出了一种完全非主流的挖矿机制。说它非主流,是因为在算力不断提升的主流趋势下,它却选择了在保障安全的基础上,大幅降低算力需求,让大众能够参与进来。在BTC、ETH等主流币被矿场把持的背景下,它支持个人电脑——独立显卡高性能个人计算机、视频工作室闲置服务器、实验室高性能服务器、带 GPU 算力的云服务器等等,都是它的代理矿工候选者。它——就是ETM。一年多来,ETM的科学家们穿越黑洞,突破迷障,摸索出了全新的投票与分红机制,以确保整个体系不会被寡头把持,不会成为少数人的游戏。它设立的优选机制,杜绝了纯资本运作对系统的过分干预,选优淘劣,又同时保证了系统的稳定与高效,大众而不失品质。ETM逆流而上,只为重回本来的河道——在那条河道上,沿着中本聪开辟的道路,我们抵抗资本的侵袭,抵抗包括算力在内的一切垄断。那条河道有我们关于资产与自由的梦想。那条河道,通往黄金时代。这个黄金时代并不遥远 就在眼前距离ETN Testnet 3.0全球有奖公测启动还有 1 天ETM TestNet 3.0 亮点抢先看

February 28, 2019 · 1 min · jiezi

Flask出现Error code 400, message Bad request syntax异常

有一个小坑,所以记录下。请求api是出现Error code 400, message Bad request syntax,然后后面有一串乱码其实这个问题最大的原因就是请求时用的https,然后flask服务没有配置ssl证书,所有报错了。换成http请求即可。

February 25, 2019 · 1 min · jiezi

[译]PEP 380--子生成器的语法

导语: PEP(Python增强提案)几乎是 Python 社区中最重要的文档,它们提供了公告信息、指导流程、新功能的设计及使用说明等内容。对于学习者来说,PEP 是非常值得一读的第一手材料,学习中遇到的大部分难题,都能在 PEP 中找到答案或者解决思路。我翻译了几篇 PEP,这么做的目的一方面是为了加强学习,另一方面也是为了锻炼自己的英文水平。Python 与 English,都是如此重要。翻译能将两者巧妙地结合起来,真是一举两得。本文介绍了子生成器的语法,即 yield from 语法。其它与生成器相关的 PEP 有 3 篇,翻译的结果附在了本文末尾。若有对翻译感兴趣的同学,可在 Github 上关注下我创建的项目 peps-cn 。PEP原文 : https://www.python.org/dev/pe...PEP标题: Syntax for Delegating to a SubgeneratorPEP作者: Gregory Ewing创建日期: 2009-02-13合入版本: 3.3译者 :豌豆花下猫(Python猫 公众号作者)目录摘要PEP接受动机提议StopIteration 的增强形式语义基本原理重构原则结束方式作为线程的生成器语法优化使用StopIteration来返回值被拒绝的建议批评可选的提案附加材料参考资料版权摘要为生成器提出了一种新的语法,用于将部分的操作委派给其它的生成器。这使得一部分包含“yield”的代码段,可以被分离并放置到其它生成器中。与此同时,子生成器会返回一个值,交给委派生成器(delegating generator)使用。当一个生成器再次 yield 被另一个生成器生成的值时,该语法还创造了一些优化的可能。PEP接受Guido 于 2011 年 6 月 26 日正式接受本 PEP。动机Python 的生成器是一种协程,但有一个限制,它只能返回值给直接的调用者。这意味着包含了 yield 的代码段不能像其它代码段一样,被拆分并放入到单独的函数中。如果做了这样的分解,就会导致被调用的函数本身成为一个生成器,并且必须显式地迭代这个生成器,以便重新 yield 它产生的所有值。如果只关心生成值的过程,那么可以不费劲地使用如下的循环:for v in g: yield v但是,如果在调用send(),throw()和close()的情况下,要使子生成器与调用者正确地交互,就相当困难。如后面所说,必要的代码非常复杂,因此想要正确地处理所有特殊情况,将会非常棘手。一种新的语法被提出来解决此问题。在最简单的用例中,它等同于上面的 for-循环,并且可以处理生成器的所有的行为,同时还能用简单而直接的方式进行重构。提议以下的新的生成器语法将被允许在生成器的内部使用:yield from <expr>其中 <expr> 表达式作用于可迭代对象,从迭代器中提取元素。该迭代器会遍历到耗尽,在此期间,它直接向包含 yield from 表达式的调用者生成器(即“委托生成器”)生成和接收值。此外,当该迭代器是一个生成器时,则此生成器可以执行 return 语句返回一个值,而该值将成为 yield from 表达式的值。yield from 表达式的完整语义可通过生成器协议来描述如下:迭代器返回的任何值都直接传给调用者。使用 send() 发送给委托生成器的任何值都直接传给迭代器。如果发送的值是 None,则调用迭代器的 next() 方法。如果发送的值不是 None,则调用迭代器的 send() 方法。如果调用引发了 StopIteration,则恢复委托生成器。任何其它异常都会传递给委托生成器。除 GeneratorExit 以外,任何传给委托生成器的异常都会传给迭代器的 throw() 方法。如果调用引发 StopIteration,则恢复委托生成器。任何其它异常都会传递给委托生成器。如果传给委托生成器的是 GeneratorExit 异常,或者调用委托生成器的 close() 方法,则迭代器的 close() 方法会被调用(如果有)。如果调用时出现异常,则会传给委托生成器。否则的话,在委托生成器中抛出 GeneratorExit。yield from 表达式的值是迭代器终止时引发的 StopIteration 异常的第一个参数。生成器里的 return expr 导致从生成器退出时引发 StopIteration(expr)。StopIteration的增强功能为方便起见,StopIteration 异常被赋予了一个 value 属性,来保存它的第一个参数,若无参数,则为 None。正式的语义本节使用 Python 3语法。1、RESULT = yield from EXPR 语句等同于以下语句:_i = iter(EXPR)try: _y = next(_i)except StopIteration as _e: _r = _e.valueelse: while 1: try: _s = yield _y except GeneratorExit as _e: try: _m = _i.close except AttributeError: pass else: _m() raise _e except BaseException as _e: _x = sys.exc_info() try: _m = _i.throw except AttributeError: raise _e else: try: _y = _m(*_x) except StopIteration as _e: _r = _e.value break else: try: if _s is None: _y = next(_i) else: _y = _i.send(_s) except StopIteration as _e: _r = _e.value breakRESULT = _r2、在生成器中,return value 语句在语义上等同于 raise StopIteration(value) ,除了一点,当前返回的生成器中的 except 子句无法捕获该异常。3、 StopIteration 异常的行为就像这样定义:class StopIteration(Exception): def init(self, *args): if len(args) > 0: self.value = args[0] else: self.value = None Exception.init(self, *args)基本原理重构原则上面提到的大多数语义,其背后的基本原理源于一种对生成器代码进行重构的愿望。即希望可以将包含一个或多个 yield 表达式的代码段,分离进一个单独的函数中(使用常规手段来处理作用域范围内的变量引用,等等),并通过 yield from 表达式来调用该函数。在合理可行的情况下,这种复合而成的生成器的行为应该跟原始的非分离的生成器完全相同,包括调用 __next () 、send()、throw() 和 close() 。子迭代器(而非生成器)的语义被选择成为生成器案例的合理泛化(generalization)。所提出的语义在重构方面具有如下限制:一个捕获了 GenetatorExit 却不重新抛出的代码块,不能在完全保留相同行为的情况下被分离出去。如果将 StopIteration 异常抛进了委托生成器中,则分离的生成器的行为跟原始代码的行为可能会不同。由于这些用例几乎不存在,因此不值得为支持它们而考虑额外的复杂性。结束方式当在 yield from 处挂起时,并且使用 close() 方法显式地终止委托生成器时,关于是否要一并终止子迭代器,存在一些争议。一个反对的论据是,如果在别处存在对子迭代器的引用,这样做会导致过早结束它。对非引用计数型的 Python 实现的考虑,导致了应该显式地结束的结论,以便在所有类型的 Python 实现上,显式地结束子迭代器与非重构的迭代器,能具有相同的效果。这里做的假设是,在大多数用例中,子迭代器不会被共享。在子迭代器被共享的稀有情况下,可通过一个阻塞调用 throw() 和 close() 的装饰器来实现,或者使用除 yield from 以外的方法来调用子迭代器。作为线程的生成器使生成器能够 return 值的动机,还考虑到使用生成器来实现轻量级的线程。当以这种方式使用生成器时,将轻量级线程的计算扩散到许多函数上就会是合理的。人们希望能够像调用普通函数一样调用子生成器,传递给它参数并接收返回值。使用提议的语法,像以下的表达式y = f(x)其中 f 是一个普通的函数,就可以被转化成一个委托调用y = yield from g(x)其中 g 是生成器。通过把 g 想象成一个普通的能被 yield 语句挂起的函数,人们可以推断出结果代码的行为。当以这种方式把生成器作为线程使用时,通常人们不会对 yield 所传入或传出的值感兴趣。但是,也有一些例子,线程可以作为 item 的生产者或消费者。yield from 表达式允许线程的逻辑被扩散到所需的尽可能多的函数中,item 的生产与消费发生在任意的子函数中,并且这些 item 会自动路由到/去它们的最终来源/目的地。对于 throw() 与 close() ,可以合理地预期,如果从外部向线程内抛入了一个异常,那么首先应该在线程挂起处的最内部的生成器中引发,再从那里向外传递;而如果线程是从外部调用 close() 来终结的,那也应该从最内部往外地终止处于活动态的生成器链。语法所提出的特定语法被选中,像它的含义所暗示,并没有引入任何新的关键词,且清晰地突出了它与普通 yield 的不同。优化当存在一长串生成器时,使用专门的语法就为优化提供了可能性。这种生成器链可能存在,例如,当递归遍历树结构时。在链上传递 next() 的调用与 yield 返回值,可能造成 O(n) 开销,最坏情况下会是 O(n**2)。可能的策略是向生成器对象添加一个槽(slot)来保存委派给它的生成器。当在生成器上调用 next() 或 send() 时,首先检查该槽,如果非空,则它引用的生成器将会被激活。如果引发了 StopIteration,该槽会被清空,并且主生成器会被激活。这将减少一系列 C 函数调用的委托开销,并不涉及 Python 代码的执行。一种可能的增强方法是在循环中遍历整个生成器链,并直接激活最后一个生成器,尽管 StopIteration 的处理会比较复杂。使用StopIteration来返回值有多种方法可以将生成器的返回值传回。也有一些替代的方法,例如将其存储为生成器-迭代器对象的属性,或将其作为子生成器的 close() 方法的调用值返回。然而,本 PEP 提议的机制很有吸引力,有如下理由:使用泛化的 StopIteration 异常,可以使其它类型的迭代器轻松地加入协议,而不必增加额外的属性或 close() 方法。它简化了实现,因为子生成器的返回值变得可用的点与引发异常的点相同。延迟到任意时间都需要在某处存储返回值。被拒绝的建议一些想法被讨论并且拒绝了。建议:应该有一些方法可以避免对__next() 的调用,或者用带有指定值的 send() 调用来替换它,目的是支持对生成器作装饰,以便可以自动地执行初始的 next() 。决议:超出本提案的范围。这种生成器不该与 yield from 一起使用。建议:如果关闭一个子迭代器时,引发了带返回值的 StopIteration 异常,则将该值从 close() 调用中返回给委托生成器。此功能的动机是为了通过关闭生成器,传信号给传入生成器的最后的值。被关闭的生成器会捕获 GeneratorExit ,完成其计算并返回一个结果,该结果最终成为 close() 调用的返回值。决议:close() 与 GeneratorExit 的这种用法,将与当前的退出(bail-out)与清理机制的角色不兼容。这要求在关闭子生成器后、关闭一个委托生成器时,该委托生成器可以被恢复,而不是重新引发 GeneratorExit。但这是不可接受的,因为调用 close() 进行清理的意图,无法保证委托生成器能正确地终止。通过其它方式,可以更好地处理向消费者告知(signal)最后的值的问题,例如发送一个哨兵值(sentinel value)或者抛入一个被生产者与消费者都认可的异常。然后,消费者可以检查该哨兵或异常,通过完成其计算并正常地返回,来作响应。这种方案在存在委托的情况下表现正确。建议:如果 close() 不返回值,如果出现 StopIteration 中带有非 None 的值,则抛出一个异常。决议:没有明确的理由如此做。忽略返回值在 Python 中的任何其它地方,都不会被视为错误。批评根据本提案,yield from 表达式的值将以跟普通 yield 表达式非常不同的方式得出。这意味着其它不包含 yield 表达式的语法可能会更合适,但到目前为止,还没有提出可接受的替代方案。被拒绝的替代品包括 call、delegate 和 gcall。有人提议,应该使用子生成器中除 return 以外的某些机制,来处理 yield from 表达式的返回值。但是,这会干扰将子生成器视为可挂起函数的目的,因为它不能像其它函数一样 return 值。有人批评,说使用异常来传递返回值是“滥用异常”,却没有任何具体的理由来证明它。无论如何,这只是一种实现的建议;其它机制可以在不丢失本提案的任何关键特性的情况下使用。有人建议,使用与 StopIteration 不同的异常来返回值,例如 GeneratorReturn。但是,还没有令人信服的实际理由被提出,并且向 StopIteration 添加 value 属性减轻了从异常(该异常可能存在也可能不存在)中提取返回值的所有困难。此外,使用不同的异常意味着,与普通函数不同,生成器中不带值的 return,将不等同于 return None 。可选的提案之前已经提到了类似的提议,有些语法使用 yield 而不是 yield from。虽然 yield 更简洁,但是有争议的是,它看起来与普通的 yield 太相似了,可能在阅读代码时会忽视了其中的差异。据作者所知,之前的提案只关注于 yield 产生值,因此遭受到了批评,即他们所替代的两行 for 循环并没有足够令人厌烦,不足以让人为新的语法辩护。通过处理完整的生成器协议,本提案提供了更多的好处。附加材料本提案的语法的一些用例已经被提供出来,并且基于上面概括的第一个优化的原型也已实现。Examples and Implementation可以从跟踪器问题的 issue 11682 中获得针对 Python 3.3 实现的升级版本。参考资料[1] https://mail.python.org/pipermail/python-dev/2011-June/112010.html [2] http://www.cosc.canterbury.ac.nz/greg.ewing/python/yield-from/ [3] http://bugs.python.org/issue11682版权本文档已经放置在公共领域。源文档:https://github.com/python/pep…————-(译文完)————-相关链接: PEP背景知识 :学习Python,怎能不懂点PEP呢?PEP翻译计划 :https://github.com/chinesehua…[[译] PEP 255–简单的生成器](https://mp.weixin.qq.com/s/vj...[[译] PEP 342–增强型生成器:协程](https://mp.weixin.qq.com/s/M7...[[译] PEP 525–异步生成器](https://mp.weixin.qq.com/s/fy…公众号【Python猫】, 专注Python技术、数据科学和深度学习,力图创造一个有趣又有用的学习分享平台。本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、优质英文推荐与翻译等等,欢迎关注哦。PS:后台回复“爱学习”,免费获得一份学习大礼包。 ...

February 16, 2019 · 2 min · jiezi

Web 开发学习笔记(5) --- 抽象出 Page 类

回顾通过前几篇文章的内容, 我们已经搭建了基于 Flask 框架的一个简单的 Web 应用, server.py 的代码如下from flask import Flask, make_responsefrom flask.views import MethodViewapp = Flask(name)class IndexHandler(MethodView): def get(self): resp = make_response(‘It is a GET request’) resp.headers[‘Strict-Transport-Security’] = ‘max-age=15768000; includeSubDomains; preload’ return respif name == ‘main’: app.add_url_rule(’/’, view_func=IndexHandler.as_view(‘index’)) context = (’./server.cer’, ‘./server.key’) app.run(port=443, host=‘0.0.0.0’, debug=True, threaded=True, ssl_context=context)抽象出 Page 类由于我们在以后的每个页面的 Handler 中都要用到 HSTS, 以及其他一些共用的特性, 我们将其提取出来, 抽象成一个 Page 类, 修改 server.py 如下from flask import Flask, make_responsefrom flask.views import MethodViewapp = Flask(name)class Page(MethodView): def render(self, resp): resp.headers[‘Strict-Transport-Security’] = ‘max-age=15768000; includeSubDomains; preload’ return respclass IndexHandler(Page): def get(self): content = ‘It is a GET request’ return self.render(content)if name == ‘main’: app.add_url_rule(’/’, view_func=IndexHandler.as_view(‘index’)) context = (’./server.cer’, ‘./server.key’) app.run(port=443, host=‘0.0.0.0’, debug=True, threaded=True, ssl_context=context)提取其他共用特性get_args()args 一般出现在 GET 请求中, 比如 Google 搜索时地址栏出现的 https://www.google.com/search?q=awesome, 其中 ? 后面的内容就是 args, 以 key-value 的形式出现. 对应刚才的例子, key 就是 q, value 就是 awesome. 如果有多组 key-value, 中间就用 & 连接, 如 ?q=awesome&type=server. 这种形式很容易让我们联想到 Python 的字典结构 dict. 根据 文档, Flask 是通过 ImmutableMultiDict 来存储 args 的. 我们可以在 Page 类中增加 get_args() 方法from flask import requestclass Page(MethodView): def get_args(self, key): return request.args.get(key)这样, 我们就可以通过调用 get_args() 来获取用户请求中的参数了, 举个栗子class AwesomeHandler(Page): def get(self): if self.get_args(‘q’) == ‘awesome’: # do something return ‘This is an awesome page!‘同样地, 我们可以在 Page 类中增加其他特性, 如 get_date(), get_referer(), get_cookies() 等等, 编写好的 Page 类如下from flask import requestfrom datetime import datetime, dateclass Page(MethodView): def render(self, resp): resp.headers[‘Strict-Transport-Security’] = ‘max-age=15768000; includeSubDomains; preload’ return resp def get_args(self, key): return request.args.get(key) def get_date(self, year=0, month=0, day=0): if year and month and day: return date(year, month, day) else: return date.today() def get_referer(self): return request.headers.get(‘referer’) def get_cookies(self): return request.cookies参考资料http://flask.pocoo.org/docs/1… ...

February 16, 2019 · 2 min · jiezi

SQLflow:基于python开发的分布式机器学习平台, 支持通过写sql的方式,运行spark, 机器学习算法, 爬虫

项目git:https://github.com/lqkweb/sql…SQLflow (python3+)Sqlflow based on python development, support to Spark, Flink, etc as the underlying distributed computing engine, through a set of unified configuration file to complete the batch, flow calculation, the Rest service development.2019-01-22 更新界面,近期会规范一下代码,写一些注释并加入读写功能主页:结果页面:项目目标基于python开发的分布式机器学习平台, 支持通过写sql的方式,运行spark, 机器学习算法, 爬虫。安装python3环境, 执行项目git clone https://github.com/lqkweb/sql...pip install -r requirements.txt(sqlflow/sqlflow/execute/main.py 中的data.csv需要修改成你电脑中的绝对路径,数据文件在sqlflow/data/中)python manage.py打开http://127.0.0.1:5000 就可以测试了。项目测试在http://127.0.0.1:5000/demo输入框输入:测试1:select * from A limit 3;测试2:select * from A limit 3 as B;新开一个http://127.0.0.1:5000/demo网页, 直接就可以查询数据表B了:select * from B limit 2;as B 相当于创建了一个B临时表。是不是很简单。正在新增sql版机器学习算法功能, 谢谢支持。正在新增sql版爬虫功能, 谢谢支持。记得给个start鼓励一下!Thanks♪(・・)ノ

February 15, 2019 · 1 min · jiezi

Flask 学习笔记(3) --- 申请和部署HTTPS证书

简介现在已经进入 HTTPS 的时代, HTTPS 证书 目前应用广泛, 发展迅速. 相较于明文传输的 HTTP, HTTPS 更加安全. HTTPS 即 Hypertext Transfer Protocol Secure, 由于其安全层使用的是 TLS/SSL, 因此 HTTPS 也可以称为 HTTP over TLS 或 HTTP over SSL. 关于 HTTPS 证书的分类, 可以参考这篇博客HTTPS 证书 需要向国际公认的证书证书认证机构 Certificate Authority (CA) 申请.接下来, 我们将使用自动化证书管理工具 acme.sh 为我们的域名申请 Let’s Encrypt 颁发的 HTTPS 证书, 然后将其部署在我们的网站上.本文假设我们的域名为 www.awesome.com开发环境在前文的基础上, 我们只需增加 acme.sh 2.8.0 这个工具. 它的中文文档在这里. 安装 acme.sh 的过程很简单, 在 Terminal 中输入如下命令 acme.sh 即可.curl https://get.acme.sh | sh生成证书我们使用 http 方式来验证我们对域名的所有权.如果只申请单域名证书(Single Domain Certificate, www.awesome.com), 那么在 Terminal 中运行如下命令即可acme.sh –issue -d www.awesome.com –standaloneacme 会在当前目录生成一个验证文件, 然后运行一个监听 80 端口的 server, 如果 Let’s Encrypt 成功地通过域名下载了这个文件, 就验证了我们对域名的所有权, 就可以签发证书了.我们也可以运行一个 file server 监听 80 端口cd ~/webapppython3 -m http.server 80然后在另一个 Terminal 里输入如下命令cd ~acme.sh –issue -d www.awesome.com –webroot ~/webapp如果要申请通配符证书(Wildcard Certificate, .awesome.com), 则需要用 dns 方式. 首先我们在 Godaddy 上申请开发者 API key & secret, 然后参考 acme.sh 的文档 readme 和 dnsapi, 执行如下命令export GD_Key=“xxxxxxxx"export GD_Secret=“yyyyy"acme.sh –issue –dns dns_gd -d “.awesome.com” -d awesome.com如果 DNS txt record 记录添加成功, acme 会先等待 120s 以待其生效, 然后验证我们对域名的所有权.下一节, 我们将讲述如何安装和部署证书安装和部署证书对于单域名证书, 根据 acme 的文档, 我们需要执行以下命令, 将证书和公钥放到 ~/ssl/ 文件夹中acme.sh –installcert -d www.awesome.com –key-file ~/ssl/server.key –fullchain-file ~/ssl/server.cer对于通配符证书, 操作也是类似的, 把域名换成 “.awesome.com” 就好了acme.sh –installcert -d “.awesome.com” –key-file /ssl/server.key –fullchain-file /sslwebsite/server.cer然后, 在之前编写的 server 中, 我们需要引入证书和公钥, 从而将明文的消息用 ssl/tls 包裹起来. 根据 Stack Overflow, 这篇文章下面的 Comments, 以及 werkzeug docs, 我们只需要在 app.run() 中加上 ssl_context=(’/ssl/server.cer’, ‘/ssl/server.key’) 参数即可# class IndexHandler(…):# …if name == ‘main’: app.add_url_rule(’/’, view_func=IndexHandler.as_view(‘index’)) context = (’./server.cer’, ‘./server.key’) app.run(port=443, host=‘0.0.0.0’, debug=True, threaded=True, ssl_context=context)至此, 我们的 HTTPS 证书已经申请和部署完成了. 但是我们的 server 目前还存在一个问题, 就是只能访问 https://www.awesome.com, 而原来的 http://www.awesome.com 已经无法访问了, 因为我们的 server 现在只能监听 443 端口而不能监听 80 端口. 下一篇文章, 我们将解决这个问题, 也就是另外写一个 server 来监听 80 端口, 并将 http 服务重定向为 https. 同时, 我们还将学习如何使用 HSTS, 使浏览器默认以更安全的 https 的方式访问我们的网站.参考链接https://imququ.com/post/letse…https://github.com/Neilpang/a...https://github.com/Neilpang/a...https://github.com/Neilpang/a...https://letsencrypt.org/https://stackoverflow.com/que...http://flask.pocoo.org/docs/1...http://flask.pocoo.org/snippe...http://werkzeug.pocoo.org/doc...http://werkzeug.pocoo.org/doc...https://jjayyyyyyy.github.io/… ...

February 15, 2019 · 2 min · jiezi

Flask 学习笔记(1) --- 搭建你的第一个 web server

开发环境Ubuntu 16.04Python 3.5Flask 1.0.2命令如下sudo apt-get upgradesudo apt-get install python3-setuptoolssudo apt-get install python3-devsudo apt-get install python3-pipsudo pip3 install pip –upgradesudo pip3 install flask第一个 server首先我们创建一个文件夹 webapp, 并在其中新建一个 main.py 文件mkdir ~/webappcd ~/webapptouch main.py接着, 我们打开 main.py, 按照 Flask Quickstart 的示例, 开始编写第一个 serverfrom flask import Flaskapp = Flask(name)@app.route(’/’, methods=[‘GET’])# methods 默认是 GET 因此可以简写为如下形式# @app.route(’/’)def hello(): return ‘Hello’if name == ‘main’: app.run(host=‘0.0.0.0’, port=8080, debug=True)保存文件后, 在 Terminal 中输入如下命令, 即可运行 webapp. 我们在浏览器中输入 http://server_ip:8080 即可访问网站, 页面的内容就是 Hellopython3 main.py编写 IndexHandler在上一节中, 我们使用了 @ decorator 来指定某个路由对应的处理函数, 这样的写法非常方便. 同时, 我们也可以编写我们自己的 Handler 来处理各个不同的页面(路径). 比如, 对于首页 Index, 即 http://server_ip:8080/, 我们可以编写一个 class IndexHandler, 注意这是一个 MethodView 的子类, 也就是说这是一个 View Handlerfrom flask import Flaskfrom flask.views import MethodViewapp = Flask(name)class IndexHandler(MethodView): def init(self, name): print(name) def get(self): return ‘It is a GET request’ def post(self): return ‘It is a POST request’if name == ‘main’: app.add_url_rule(’/’, view_func=IndexHandler.as_view(‘index’)) app.run(port=8080, host=‘0.0.0.0’, debug=True)根据 flask docs, 传给 as_view() 的参数 name 会转发给构造函数, 我们暂时用不到这个参数 name , 但是为了保持命名的一致性, 我们将其设置为 index保存文件后, 在 Terminal 中输入如下命令, 即可运行 webapp. 我们在浏览器中输入 http://server_ip:8080 即可访问网站, 页面的内容是 It is a GET requestpython3 main.py参考资料quickstart, flask docsas_view, flask docsflask/flask/views.py ...

February 13, 2019 · 1 min · jiezi

Flask在Windows环境下的部署

背景由于目前在用的Flask项目涉及到一部分依赖Windows的处理,还无法迁移到linux平台,那么在windows环境下,要怎么部署呢?思路根据Flask官网介绍,由于Flask内置的服务器性能不佳,推荐的主要的部署方式有如下几种:mod_wsgi (Apache)独立 WSGI 容器GunicornTornadoGeventuWSGIFastCGICGI上述这些部署方式,仅Tornado是支持在windows情况下部署的,配合上Nginx可以达到比较好的效果。可已参考Nginx与tornado框架的并发评测。但是在实际使用中发现,tornado 的稳定性虽然很高,但是在tornado上部署Flask,并不会有异步的效果。实际上还是单进程阻塞运行的,即使在Flask中配置了threaded = True也无法实现多线程使用。Flask多线程情况配置启用多线程:# manage.pyfrom flask_script import Serverserver = Server(host=“0.0.0.0”, threaded=True)在Flask中配置两条测试路由import time@main.route(’/test’)def maintest(): return ‘hello world’ @main.route(’/sleep’)def mainsleep(): time.sleep(60) return ‘wake up’先用浏览器访问\sleep:随即立刻访问\test:可见两次访问是不同的线程处理的,不会出现堵塞的情况。tornado + Flask多线程情况使用tornado托管:from tornado.wsgi import WSGIContainerfrom tornado.httpserver import HTTPServerfrom tornado.ioloop import IOLoopfrom yourapplication import apphttp_server = HTTPServer(WSGIContainer(app))http_server.listen(5000)IOLoop.instance().start()先用浏览器访问\sleep:随即立刻访问\test:可以发现,虽然tornado框架是支持异步的,但是由于实际上后台的处理是同步的,从而无法实现异步的处理的效果。如果想后台的处理也异步,则需要直接使用tornado来开发。那么为什么使用tornado来托管flask呢?Tornado 是一个开源的可伸缩的、非阻塞式的 web 服务器和工具集,它驱动了FriendFeed 。因为它使用了 epoll 模型且是非阻塞的,它可以处理数以千计的并发固定连接,这意味着它对实时 web 服务是理想的。把 Flask 集成这个服务是直截了当的根据官网描述,其实也是为了弥足flask自带服务器不稳定的问题。Flask高并发下的表现使用tsung进行压测,压力500:Namehighest 10sec meanlowest 10sec meanHighest RateMean RateMeanCountconnect34.30 msec31.91 msec506 / sec356.60 / sec33.19 msec103908page0.42 sec0.29 sec505 / sec356.32 / sec0.39 sec103782request0.42 sec0.29 sec505 / sec356.32 / sec0.39 sec103782session1mn 24sec10.64 sec11.4 / sec1.21 / sec14.24 sec362CodeHighest RateMean RateTotal number200505 / sec356.32 / sec104792NameHighest RateTotal numbererror_abort0.5 / sec1error_abort_max_conn_retries11.7 / sec362error_connect_econnrefused58.6 / sec1667可见,在500的并发下,效果不佳,有很多的链接拒绝。Flask + Nginx在高并发下的表现使用tsung进行压测,压力500:Namehighest 10sec meanlowest 10sec meanHighest RateMean RateMeanCountconnect0.20 sec30.95 msec1810.5 / sec626.43 / sec0.11 sec189853page0.68 sec0.17 sec1810.1 / sec625.72 / sec0.40 sec189581request0.68 sec0.17 sec1810.1 / sec625.72 / sec0.40 sec189581CodeHighest RateMean RateTotal number200906.4 / sec196.08 / sec606895021443.9 / sec430.02 / sec129006NameHighest RateTotal numbererror_abort0.5 / sec1情况差不多,Flask服务器表现还算稳定,那么尝试增加后台Flask服务器数量(通过多端口实现):python manage.py runserver –port=8001python manage.py runserver –port=8002python manage.py runserver –port=8003python manage.py runserver –port=8004使用tsung进行压测,压力500,4个Flask服务器:Namehighest 10sec meanlowest 10sec meanHighest RateMean RateMeanCountconnect0.18 sec32.57 msec3510.1 / sec639.92 / sec0.11 sec195154page0.49 sec85.30 msec3512.1 / sec639.07 / sec0.35 sec194856request0.49 sec85.30 msec3512.1 / sec639.07 / sec0.35 sec194856CodeHighest RateMean RateTotal number2003510.1 / sec639.50 / sec194986NameHighest RateTotal numbererror_abort0.333333333333333 / sec1这个效果妥妥的。使用tsung进行压测,压力1000,4个Flask服务器:Namehighest 10sec meanlowest 10sec meanHighest RateMean RateMeanCountconnect0.20 sec32.63 msec2983.8 / sec492.94 / sec98.56 msec150793page0.57 sec90.00 msec2976.4 / sec491.31 / sec0.40 sec150275request0.57 sec90.00 msec2976.4 / sec491.31 / sec0.40 sec150275CodeHighest RateMean RateTotal number2002981.4 / sec488.92 / sec14955650292.5 / sec4.02 / sec925NameHighest RateTotal numbererror_abort0.333333333333333 / sec1开始有一些502的超时错误了。使用tsung进行压测,压力1000,4个tornado服务器:Namehighest 10sec meanlowest 10sec meanHighest RateMean RateMeanCountconnect0.18 sec86.24 msec2052.1 / sec693.82 / sec0.14 sec208786page0.52 sec0.24 sec2060.7 / sec693.34 / sec0.45 sec208606request0.52 sec0.24 sec2060.7 / sec693.34 / sec0.45 sec208606CodeHighest RateMean RateTotal number2002056.6 / sec693.67 / sec208703在并发1000的情况下,是否使用tornado托管Flask效果差不多。结论根据上述测试,直接使用Flask服务器的话,由于并发处理较弱,会有各种超时或者连接拒绝的错误。通过搭配Nginx来进行缓冲,通过增加后端服务器数来提供并发处理量。所以最终选择了Nginx+后台4个Flask服务器的方式。由于目前Flask项目全体用户只有几千,目前并发情况很低,该方式完全满足使用。如果在更大型项目中,并发上万,建议还是考虑想办法迁移至Liunx环境,通过官方建议的方式部署。 ...

February 11, 2019 · 2 min · jiezi

如何优雅的在flask中记录log

背景记录日志,在任何项目中,都是很重要的。在Flask项目中,即有Flask提供的logger可以用来记录log,也可以通过直接使用Python的logging模块自定义logger来记录。那么这两者是什么关系,又该怎么使用呢?思路Python的logging模块先看下对于logging模块的官方介绍Loggers have the following attributes and methods. Note that Loggers are never instantiated directly, but always through the module-level function logging.getLogger(name). Multiple calls to getLogger() with the same name will always return a reference to the same Logger object.The name is potentially a period-separated hierarchical value, like foo.bar.baz (though it could also be just plain foo, for example). Loggers that are further down in the hierarchical list are children of loggers higher up in the list. For example, given a logger with a name of foo, loggers with names of foo.bar, foo.bar.baz, and foo.bam are all descendants of foo. The logger name hierarchy is analogous to the Python package hierarchy, and identical to it if you organise your loggers on a per-module basis using the recommended construction logging.getLogger(name). That’s because in a module, name is the module’s name in the Python package namespace.https://docs.python.org/3/lib…上面主要告诉我们两点,可以通过logging.getLogger(name)来获取一个logger,相同名字的logger,其实是同一个logger。logger是通过name进行继承的,比如foo.bar就是foo 的子logger。就可以是实现我们通过配置一个rootLogger,然后直接使用rootLogger.sublogger来记录一下内容,而不需要单独再配置一遍。当使用logging.getLogger(name)时,__name__就是这个模块所在的python package的namespace。flask提供的logger再看下flask中的logging模块:Flask uses standard Python logging. All Flask-related messages are logged under the ‘flask’ logger namespace.Flask.logger returns the logger named ‘flask.app’, and can be used to log messages for your application.Depending on the situation, an extension may choose to log to app.logger or its own named logger. Consult each extension’s documentation for details.http://flask.pocoo.org/docs/1…我们可以知道flask的logger就是一个标准的Python logging,它的命名是flask。我们既可以使用app.logger,也可以自己定义一个logger。那么如何使用app.logger呢?有两种方式:直接调用logger = logging.getLogger(‘flask.app’)logger.info(‘flask.app’)使用Flask提供的接口from flask import current_appcurrent_app.logger.info(’logged by current_app from main’)这里推荐还是使用第二种,current_app是一个单例,可以直接引用到app.logger。通过修改app.logger的name,可以实现子logger的继承么?答案是否定的。修改app.logger的name:# app/init.pyapp.logger.name = ‘app’然后在子模块中定义一个app.module的logger来记录:from flask import current_appimport logginglogger = logging.getLogger(‘app.module’)@module.route(’/test’, methods=[‘GET’])def test(): logger.info(’logged by app.module’) current_app.logger.info(’logged by current_app.logger’)输出结果:2019-02-01 10:56:01,877 - Thread-2 - app - INFO - logged by current_app.logger只有current_app.logger的输出。修改app.logger的name是不是无效呢?我们把子模块中的logger的name修改为flask.app.module:from flask import current_appimport logginglogger = logging.getLogger(‘flask.app.module’)@module.route(’/test’, methods=[‘GET’])def test(): logger.info(’logged by flask.app.module’) current_app.logger.info(’logged by current_app.logger’)输出结果:2019-02-01 11:00:10,944 - Thread-2 - flask.app.module - INFO - logged by flask.app.module2019-02-01 11:00:10,946 - Thread-2 - app - INFO - logged by current_app.logger两个logger均输出了。可见,通过修改app.logger.name可以在记录的时候显示为我们设置的名称,但实际上这个logger还是flask.app。__name__的使用在自定义logger的情况下,为了方便起见,我们可以利用__name__这个参数。前面说到:当使用logging.getLogger(name)时,__name__就是这个模块所在的python package的namespace。一般Flask的工厂模式结构如下:app├── init.py├── main│ ├── init.py│ ├── functions.py│ └── views.py└── module ├── init.py ├── functions.py └── views.py那么我们在先在app.__init__中定义rootLogger,然后再在app.module.functions.py中定义子Logger,均使用logging.getLogger(name):# app.init.py 初始化rootloggerrootLogger = logging.getLogger(name) rootLogger.setLevel(logging.DEBUG) socketHandler = logging.handlers.SocketHandler(’localhost’,logging.handlers.DEFAULT_TCP_LOGGING_PORT) rootLogger.addHandler(socketHandler) rootLogger.setLevel(logging.DEBUG)# app.module.functions.pyimport logginglogger = logging.getLogger(name)def record_from_logging(): logger.info(’logged by logging from name’)输出:2019-02-01 12:18:34,743 - MainThread - app - INFO - register root logger by __name__2019-02-01 12:19:24,954 - Thread-4 - app.module.functions - INFO - logged by logging from name__可以发现输出的logger.name就是所在的文件目录,logger之间的继承关系与整个程序包保持一致。总结根据上面分析,那么怎么优雅的记录logger呢?如果没有对模块进行分logger记录要求的话。可以直接使用在程序初始化的时候配置app.logger(可以自行设置logger.name)。在模块中通过import current_app来记录:# app.init.pydef register_logging(app): app.logger.name = ‘app’ # logstash_handler stashHandler = logstash.LogstashHandler(‘app.config.get(‘ELK_HOST’)’, ‘app.config.get(‘ELK_PORT’)’) app.logger.addHandler(stashHandler) # socket_handler socketHandler = logging.handlers.SocketHandler(’localhost’, logging.handlers.DEFAULT_TCP_LOGGING_PORT) app.logger.addHandler(socketHandler) # app.module.function.pyfrom flask import current_app@module.route(’/test’, methods=[‘GET’])def test(): current_app.logger.info(’logging someting’) return ’logged by current_app.logger’输出效果:2019-02-01 13:49:28,998 - Thread-2 - app - INFO - logged by current_app from main2019-02-01 13:49:38,346 - Thread-3 - app - INFO - logged by current_app of functions__注意: 对于current_app.logger的引用不能通过如下方式,会有RuntimeError的报错。from flask import current_applogger = current_app.logger## 异常 raise RuntimeError(_app_ctx_err_msg)RuntimeError: Working outside of application context.This typically means that you attempted to use functionality that neededto interface with the current application object in some way. To solvethis, set up an application context with app.app_context(). See thedocumentation for more information.如果希望按自己的实际需求,对模块进行分logger记录要求的话。那么建议自己设置logger。# app.init.pydef register_logging(): # set own root logger rootLogger = logging.getLogger(name) rootLogger.setLevel(logging.DEBUG) # socketHandler socketHandler = logging.handlers.SocketHandler(’localhost’,logging.handlers.DEFAULT_TCP_LOGGING_PORT) rootLogger.addHandler(socketHandler) # logstash_handler stashHandler = logstash.LogstashHandler(‘app.config.get(‘ELK_HOST’)’, ‘app.config.get(‘ELK_PORT’)’) rootLogger.addHandler(stashHandler) rootLogger.setLevel(logging.DEBUG)# app.module.function.pyimport logginglogger = logging.getLogger(name)@module.route(’/test’, methods=[‘GET’])def test(): logger.info(’logging someting’) return ’logged by logging module’输出效果:2019-02-01 13:49:49,297 - Thread-5 - app.module.views - INFO - logged by flask.app.module2019-02-01 13:50:01,013 - Thread-7 - app.module.functions - INFO - logged by logging module of functions完整代码可参考:https://github.com/keejo125/flask_logging_demo注意关于python中logging的配置可参考官网:https://docs.python.org/3/lib…在配置handler时,经常会希望日志可以按时间分割(TimedRotatingFileHandler)或者按大小分割(RotatingFileHandler).但是在flask项目中,尤其开启多线程之后,在分割日志(doRollover())时会有文件读写的异常:WindowsError: [Error 32]建议使用SocketHandler,将日志发送给单独的LogServer来进行二次处理。简易的接收socketlog的LogServer可参考:https://github.com/keejo125/f…或者现在流行的stashHandler,将日志发送给ELK来进行二次处理。 ...

February 1, 2019 · 3 min · jiezi

一篇文章入门Flask

Flask本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login),都需要用第三方的扩展来实现。Flask对WSGI (路由)的实现,是采用 Werkzeug,而模板引擎(业务视图) 则使用 Jinja2。这两个是Flask框架的核心。Flask核心就是作为一个Webapp框架的两个基础部分:CGI (WSGI) 路由:是由Werkzeug实现,即将网络请求翻译成Python语言Template 业务模版:是由Python流行的Jinja2实现除此之外,Flask其它一切的都是由第三方插件实现:包括:Flask-SQLalchemy:操作数据库; Flask-migrate:管理迁移数据库; Flask-Mail:邮件; Flask-WTF:表单; Flask-Bable:提供国际化和本地化支持,翻译; Flask-script:插入脚本; Flask-Login:认证用户状态; Flask-OpenID:认证; Flask-RESTful:开发REST API的工具; Flask-Bootstrap:集成前端Twitter Bootstrap框架; Flask-Moment:本地化日期和时间; Flask-Admin:简单而可扩展的管理接口的框架参考Flask扩展列表:http://flask.pocoo.org/extens…Flask安装安装很简单:$ pip install flask建议在Virtualenv虚拟环境下安装,因为Flask需要一系列的依赖,最好给Flask生成一个专用的生产环境,并生产requirement.txt依赖列表:# 生产虚拟环境$ virtualenv ./flask_env# 启动虚拟环境$ ./flask_env/bin/active# 生产依赖列表$ pip freeze > requirements.txtHello World一个最简单的Flask程序,只需要三步:生成Flask类的实例,即一个APP指定目录的路由规则,即不同路径可以实现不同的操作。运行app:app.run(),hello-world.py:from flask import Flask# 生产一个Flask APP 实例,并指向当前文件(模块)app = Flask(name)# 指定"/“根目录的路由规则@app.route(’/’)def index(): return ‘Hello World’# 开始运行appapp.run()Flask中,路由的实现是用装饰器:@app.route(”/")这种方式来做到的。Flask路由规则页面显示指定内容:@app.route(’/’)def index(): return ‘<h1> 欢迎访问此页面 </h1>‘页面返回指定内容、状态码、headers等:@app.route(’/’)def index(): body = ‘<h1> 欢迎访问此页面 </h1>’ status_code = 200 headers = {‘Cache-Control’:’no-cache’, ‘Connection’:‘keep-alive’} return body, status_code, headers指定接收的请求方式:@app.route(’/’, method=‘GET’)# …给路由传参数:@app.route(’/orders/<order_id>’)def hello_itheima(order_id): return ‘The ID of order is: %d’ % order_id限制路由参数的数据类型:@app.route(’/orders/<int:order_id>’)# …其中int:order_id是指定参数order_id必须能转换成int整数,否则这个请求就会自动被拒绝。比如用户请求http://xyz.com/orders/HAHAHAH,这就不成功。而http://xyz.com/orders/210这样的就成功。中断请求:# …from flask import abort@app.route(’/wrong-page’)def hello(): # 中断请求,并返回404状态码 abort(404)处理错误请求:# …@app.errorhandler(404)def handel_404_error(): return ‘<h1> The page does not exist </h1>‘其中,如果路由中调用了abort(404)中断函数,或是其它产生404错误的方法,这个app.errorhandler都会被自动调用。返回“渲染”过的模版(即把动态的模版渲染成静态的HTML):#…from flask import render_template@app.route(’/’)def index(): return render_template(‘index.html’)Flask 路由分割如果所有路径的路由都定义在一个文件里,会很难维护,所以必定要把各个路径的路由分拆到不同的文件里。这种分拆很简单:正常情况下,在index.py主模块中,我们还是一样正常的定义路由:#…@app.route(’/’)def index(): pass然后我们可以把其它路由的处理函数分别放在别的文件里,比如:register.py中定义“普通函数”:def reg(): pass以及login.py中定义一个普通函数:def signin(): pass然后回到主模块index.py中,我们可以导入这些函数,并显式的将这些函数注册到路由上:from flask import Flaskfrom register import regfrom login import signinapp = Flask(name)app.route(’/register’)(reg)app.route(’/login’)(signin)@app.route(’/’)def index(): pass然后我们可以用app.url_map获得当前定义过的所有路由:print( app.url_map )Flask Blueprint 蓝图我们手动分割路由处理函数,然后分别导入,这样虽然也简单,但是不不好的地方是,主模块外定义的各个处理函数,本身很难看出来处理的是什么路由逻辑。为此,Flask提供了另一种路由分割的方法:即Blueprint类。而这个Blueprint类生成的对象,是在子模块中代替了之前我们所使用的Flask类生成的app对象。也就是说:主模块还是用app,但是子模块中用蓝图blueprint。假设我们现在有一个子模块order.py定义"/order"路径的路由,那么文件中定义如下:from flask import Blueprint# 生成蓝图实例:参数中一个是蓝图名称,一个是主模块名称app_orders = Bluepint(‘blueprint_orders’, name)# 将路由添加到蓝图里@app_orders.route(’/orders’)def get_orders(): pass然后回到主模块index.py中,把蓝图注册到主路由上:#…from orders import app_ordersapp = Flask(name)app.register_blueprint( app_orders )#…Hooks 钩子事件Flask提供一个完整请求至回应的事件流,其中包括:@app.before_first_request: 接受第一次请求之前执行@app.before_request: 接受请求前,每次请求之前都执行。@app.route(): 处理请求@app.after_request: 请求之后执行,但前提是请求中没有出现异常-@app.teardown_request: 关闭请求时,即每次请求是否异常都会被执行以下是钩子的用法:#…@app.before_first_requestdef handle_before_first_request(): pass@app.route(’/’)def index(): pass@app….def …Flask上下文请求对象 flask.current_apprequest.current_app 是Flask特有的一种request请求处理方式,不同于flask.request对象的处理方式,它是能区分多个请求的。在我们常用的flask.request对象中,会有一个很严重的问题:即它是一个全局变量。也就是说,如果服务器在处理并发请求时使用的是在同一个进程里的多线程,那么不同用户的请求也许会使用同一个flask.request对象!这时候request中的请求信息就会出现混淆!所以Flask引入了request.current_app这个对象,即它能够根据上下文来区分不同人的请求。这是怎么做到的呢?其实很简单,它只是把request变为一个局部变量而已。这样一来,每次的request请求,都是各自独立的局部对象。返回响应信息 flask.make_response除了我们自己定义返回的信息外,Flask提供了一个内置的make_response对象,便于处理返回信息。返回全文信息:from flask import make_response@app.route(’/’)def index(): resp = make_response(’<h1> 欢迎访问此页面 </h1>’) resp.status = 200 resp.headers[‘Cache-Control’] = ’no-cache’ return resp设置cookies:from flask import make_response@app.route(’/’)def index(): resp = make_response(’<h1> 此页面会设置你的cookies :) </h1>’) resp.set_cookie(‘uuid’, ‘1230sfjdlsj3uu’) resp.set_cookie(’name’, ‘Jason’, max_age=360) return resp其中,max_age是cookie的存活时间,以s秒为单位。不设置的话,默认是临时cookies,即浏览器关闭后立马失效。删除cookie:resp.delete_cookie(‘uuid’)。注意,这里的删除并不是立马删除浏览器中用户的cookie,而只是把max_age设置为0,即浏览器关闭后立马失效。获取请求信息 flask.requestFlask中有一个request对象,接收了一切对当前模块的请求数据。使用的话,直接在@app.route后面的函数中用def index(request)接收来自装饰器的请求对象即可使用。request参数类型:常用的各种类型操作如下:from flask import request app = Flask(name)@app.route(’/’, method=‘POST’)def index(request): # Get uploaded file afile = request.files.get(‘pic’) with open(’./pic.jpg’, ‘w’) as f: f.write( afile.read() ) # Get a form form = request.from # Dict类型 name = form.get(’name’) age = form.get(‘age’) # Get cookies uuid = request.cookies.get(‘uuid’) name = request.cookies.get(’name’)会话处理 flask.session在登录页设置session,并在index页根据session判断是否登录:from flask import session#…app.config[‘SECRET_KEY’] = ‘asdlkjflaj23jrsdjf任意字符串作为密钥kaljdsl;fkja;j’@app.route(’/login’)def login(): # 设置sessions session[‘uuid’] = ‘123abadsf’ return ‘<p> 登录成功 </p>’@app.route(’/’)def index(): # 获取sessions uuid = session[‘uuid’] # 判别session是否存在 if uuid: return ‘<p> 之前已登录过 </p>’ else: return ‘<p> 未登录,请重新登录 </p>‘其中,Flask默认情况下,会利用app.config[‘SECRET_KEY’] 的值作为一个密钥,来加密你手动设置的session,然后把这个信息转换为名叫session的cookie存在浏览器中。这个是Flask特别的一点。但是把敏感的session数据保存到谁都能访问的cookie中,即使加密了也不是很安全。所以一般我们还是会手动把session数据存到服务器后台的数据库中,而不是存到cookie中。每次验证再与数据库进行对比。表单处理 request.form动态网页必须要的就是Form表单。Flask中有自带的form表单处理方法。不过我们也可以用第三方插件Flask-WTF实现。这里我们先只讲自带的处理方式。Flask自带表单处理假设我们有一个表单模版form.html:<form method=“post”> 用户名:<input type=“text” name=“username”> 密码: <input type=“password” name=“password”> 确认密码: <input type=“password” name=“password2”> <input type=“submit” value=“提交”><br> {% for message in get_flashed_messages() %} {{ message }} {% endfor %} </form>当用户点击submit提交时,整个form信息就会用POST方式提交到Flask的路由文件abc.py中。我们进行处理如下:from flask import Flaskfrom flask import render_templatefrom flask import requestapp.secret_key = ‘abc123'@app.route(’/’, methods=[‘GET’, ‘POST’])def hello(): if request.method == ‘POST’: # 获取参数, 并效验参数完整性, 如果有问题就进行flash username = request.form.get(‘username’) password = request.form.get(‘password’) password2 = request.form.get(‘password2’) if not all([username, password, password2]): flash(‘params error’) elif password != password2: flash(‘password error’) else: print username return ‘success’ return render_template(‘Congratulations.html’)Flask的HTTP Server一般我们在开发调试过程中,可以用Flask自带的WSGI和一个小HTTP Server来实现整个App正常运转。但是生产环境中,这两个自带的组件就效率很低了。所以我们需要用效率更高的独立的CGI和独立的HTTP Server服务器来部署真正的生产环境一般常见的选项有:HTTP Server -> 首推NginxCGI翻译器 -> Gunicorn (Python开发,实现了WSGI翻译)所以,我们一般采用Nginx + Gunicorn + Flask来部署网络应用。Gunicorn的使用:# 安装$ pip install gunicorn# 进入Flask app的主目录cd ./myFlask# 用gunicorn服务器启动Flask app$ gunicorn -w 4 -b 127.0.0.1:8080 main:app这个时候,flask就在gunicorn的HTTP服务器上运行了,可以通过127.0.0.1:8080访问到app。 ...

January 24, 2019 · 2 min · jiezi

一篇文章搞懂Jinja2 Template Engine 模版引擎

Flask和Django,以及其它很多Python框架,都默认使用Jinja2来作为模版引擎。在Python中,什么是模版?就是在一个静态HTML加入一些类似变量的标签,然后引擎在渲染这个HTML时候会动态的把变量填入内容,生成一个最终的HTML。什么是模版引擎?其实就是一种能解析类似Python语言的标记语言的解释器。比如我们在HTML模版中输入一个<p> {{ post.title }} </p>,显然这不是真正的HTML语法。但是当Jinja2解释器读取到{{ …}}后知道里面是一个变量,那么就把这个变量替换为真正的值,最后翻译出来就变成了<p> 大标题 </p>这样的HTML内容。Jinja2是一个模版语言,只是类似Python,比较符合Python语法,但不完全相同!所有的模版引擎,实际上都差不多,不管是基于VBS语言的ASP模版,还是基于PHP语言的PHP模版,都不是与原本语言一摸一样,而只是做到尽量一样而已。Jinja2语言基础注意:Jinja2模版语言,是不区分缩进的,和纯python不同。实际上所有模版语言都不区分缩紧。常用标记:注释:{# 这是注释 #}变量:{{ post.title }},或字典元素{{your_dict[‘key’]}},或列表{{your_list[0]}}多行代码块:{% 开始 %} HTML标签 {% 结束 %}示例:{% if user %} {{ user }}{% else %} hello! {% for index in indexs %} {{ index }} {% endfor %}Jinja2 Filter 过滤器 (即函数)一个filter过滤器的本质就是一个function函数。使用格式为:变量名 | 函数。它做到的就是,把变量传给函数,然后再把函数返回值作为这个代码块的值。如:<!– 带参数的 –>{{变量 | 函数名(*args)}}<!– 不带参数可以省略括号 –>{{变量 | 函数名}}链式调用(管道式):和命令行的pipline管道一样,可以一次调用多个函数(过滤器),如:{{ “hello world” | reverse | upper }}文本块调用(将中间的所有文字都作为变量内容传入到过滤器中):{% filter upper %} 一大堆文字{% endfilter %}Jinja2常用内置函数(过滤器)字符串操作:safe:禁用转义<p>{{ ‘<em>hello</em>’ | safe }}</p>capitalize:把变量值的首字母转成大写,其余字母转小写<p>{{ ‘hello’ | capitalize }}</p>lower:把值转成小写<p>{{ ‘HELLO’ | lower }}</p>upper:把值转成大写<p>{{ ‘hello’ | upper }}</p>title:把值中的每个单词的首字母都转成大写<p>{{ ‘hello’ | title }}</p>reverse:字符串反转<p>{{ ‘olleh’ | reverse }}</p>format:格式化输出<p>{{ ‘%s is %d’ | format(’name’,17) }}</p>striptags:渲染之前把值中所有的HTML标签都删掉<p>{{ ‘<em>hello</em>’ | striptags }}</p>truncate: 字符串截断<p>{{ ‘hello every one’ | truncate(9)}}</p>列表操作:first:取第一个元素<p>{{ [1,2,3,4,5,6] | first }}</p>last:取最后一个元素<p>{{ [1,2,3,4,5,6] | last }}</p>length:获取列表长度<p>{{ [1,2,3,4,5,6] | length }}</p>sum:列表求和<p>{{ [1,2,3,4,5,6] | sum }}</p>sort:列表排序<p>{{ [6,2,3,1,5,4] | sort }}</p>Jinja2 Macro 宏 (自定义函数)Jinja2是允许自定义函数的,这样在模版中可以重复利用这个自定义函数。Jinja2称之为Macro宏。定义方法:{% macro 函数名(参数) %} 具体的HTML内容{% endmacro %}<!– 使用 –>{{ 函数名(参数) }}<!– 或作为过滤器 –>{{ 变量 | 函数名(参数) }}关于Jinja2自定义函数的context上下文和环境变量的问题:Jinja2的自定义函数“宏”,本身是没法像filter过滤器函数一样使用上下文和环境变量的。不过可以加上@contextfilter装饰器达到同样的效果。导入另一个文件的自定义函数“宏”:假设在macro.html文件中我们定义了一个函数func()。那么现在我们可以在另一个文件reference.html中像python导入模块一样导入它:{% import ‘macro.html’ as module %}{{ module.func() }}Include 模版引用Include是我们常用的操作,即定义一个框架模版(父模版),然后一个一个指定性的把子模版引入进来。框架模版frame.html如下:{% include ‘header.html’ %}{% include ‘body.html’ %}{% include ‘footer.html’ %}Extend 模版继承我们可以在一个父模版中定义一个block代码块,然后在另一个子模版中“继承”这个父模版,并重写这个block代码块。不过一般模版中的父模版,都只是留出一个block空位,里面不写东西,特意等子模版来实现。假设现在有一个父模版parent.html:{% block HEADER %} 页头部分的HTML内容。{% endblock HEADER %}{% block BODY %} 正文部分的HTML内容。{% endblock BODY %}{% block FOOTER %} 页脚部分的HTML内容。{% endblock FOOTER %}其中定义了三个block,页头、正文和页脚。然后我们就可以定义一个模版child.html来继承父模版,并且只重写BODY部分:{% extends ‘parent.html’ %}{% block BODY %} 由子页面重写改写的的HTML内容,替换父页面的BODY。。。{% endblock BODY %}扩展完成后,我们最终得到的结果是:{% block HEADER %} 页头部分的HTML内容。{% endblock HEADER %}{% block BODY %} 由子页面重写改写的的HTML内容,替换父页面的BODY。。。{% endblock BODY %}{% block FOOTER %} 页脚部分的HTML内容。{% endblock FOOTER %}Jinja2模版引用Flask路由中的内容在Flask应用Jinja2模版时,在模版中可以直接调用Flask app中的一些公用变量和方法。引用Flask的request对象:<p> {{ request.url }} </p><p> {{ request.form.get(’name’) }} </p>引用Flask的url_for(…)方法:<!– 它会返回我们定义的路由app.route('/index')所对应的URL –><p> {{ url_for(‘index’) }} </p><!– 它会返回我们定义的路由app.route('/post/{post_id}')所对应的URL –><p> {{ url_for(‘post’, post_id=‘127’) }} </p>在模版中,我们可以引用get_flashed_messages()方法,获取Flask路由传来的闪现信息:{% for msg in get_flashed_messages() %} <p> {{ msg }} </p>{% endfor %}这种闪现信息是从Flask路由中传来的,只要在路由中发一个flash(‘hello’)信息,相当于弹了一个alert()。然后我们可以在Jinja2的模版中用get_flashed_messages()获得flash过来的信息列表。 ...

January 24, 2019 · 2 min · jiezi

flask-sqlalchemy操作(基础)

以下内容介绍了Sqlalchemy的基础查询语句,下篇文章将介绍其高级查询(聚合、自关联、连接、子查询等)模型类# 用户表class User(db.Model): tablename = ‘user’ uid = db.Column(db.String(32), primary_key=True, nullable=False) username = db.Column(db.String(20), nullable=True) password = db.Column(db.String(128), nullable=True) email = db.Column(db.String(30), nullable=True) addresses = db.relationship(‘Address’, backref=‘user’)# 地址信息class Address(db.Model): tablename = ‘address’ aid = db.Column(db.String(32), primary_key=True, nullable=False) name = db.Column(db.String(32), nullable=True) site = db.Column(db.String(100), nullable=True) phone = db.Column(db.Integer, nullable=True) uid = db.Column(db.String(32), db.ForeignKey(‘user.uid’))# 关联表OrderItem = db.Table( ‘orderitem’, db.Column(‘gid’, db.String(32), nullable=True), db.Column(‘product_id’, db.String(32), db.ForeignKey(‘product.pid’)), db.Column(‘order_id’, db.String(32), db.ForeignKey(‘order.oid’)))# 商品信息class Product(db.Model): tablename = ‘product’ pid = db.Column(db.String(32), nullable=False, primary_key=True) pname = db.Column(db.String(50), nullable=True) market_price = db.Column(db.Float, nullable=True) shop_price = db.Column(db.Float, nullable=True) pimage = db.Column(db.String(200), nullable=True) pdate = db.Column(db.Date, nullable=True) is_hot = db.Column(db.Integer, nullable=True) pdesc = db.Column(db.String(255), nullable=True) pflag = db.Column(db.Integer, nullable=True) order = db.relationship(‘Order’, secondary=OrderItem)# 订单表class Order(db.Model): tablename = ‘order’ oid = db.Column(db.String(32), nullable=False, primary_key=True) count = db.Column(db.Integer, nullable=True) subtotal = db.Column(db.Float, nullable=True) ordertime = db.Column(db.DateTime, nullable=True) flag = db.Column(db.String(10), nullable=True)增一对一order = models.Orders(oid=orderid, ordertime=datetime.now(), total=pcount, uid=pid) # 构建对象models.db.session.add(order) # 添加对象models.db.session.commit() # 提交事务一对多p = models.User(uid=‘122’, username=‘hello’, password=‘123456’, email='1@qq.com’) # 主表c1 = models.Address(aid=‘1111111111’,site=‘xxxxxxxxxx’) # 子表c2 = models.Address(aid=‘2222222222’, site=‘yyyyyyyyyy’) # 子表p.addresses = [c1, c2] # 赋值对象models.db.session.add(p) # 添加models.db.session.commit() # 提交多对多p = models.Product(pid=‘1’,pname=‘hello’) # 生成或获取商品对象o = models.Order(oid=‘1’, ) # 生成订单对象p.order = [o] # 订单表与商品表关联models.db.session.add(p) # 添加models.db.session.commit() # 提交查一对一models.Product.query.get(pid) # 根据主键返回一个对象,必须查询主键models.Product.query.all() # 返回全部对象models.Product.query.filter_by(pid=pid).first() # 返回第一个对象,检索任何值均可models.Product.query.filter_by(cid=‘1’).limit(4).all() # 限制返回对象一对多# 根据用户获取地址p = models.user.query.get(uid) # 根据主键返回一个对象,必须查询主键p.addresses # 一对多获取对象# 根据地址获取用户u = models.Address.query.get(1)print(u.user)多对多p = models.Product.query.get(1) # 正向查询print(p.order)o = models.Order.query.get(1) # 逆向查询print(o.product)改一对一good = models.Product.query.filter_by(pid=pid).first() # 获取good.pflag = 6 # 修改models.db.session.commit() # 提交一对多u = models.User.query.get(1) for i in u.addresses: i.name = ‘Gage’models.db.session.commit()多对多o = models.Order.query.get(1) for i in o.product: i.pname = ‘Gage’models.db.session.commit()删一对一add = models.Address.query.filter_by(aid=aid).first() # 获取models.db.session.delete(add) # 添加models.db.session.commit() # 提交一对多# 根据用户删除地址# cascade=‘all’ 添加此属性会级联删除# 用户一个用户对应多个地址,因此需要循环遍历,实际开发需使用filter_by 或 filter进行过滤特定删除u = models.User.query.get(1) for i in u.addresses: print(i.aid)models.db.session.delete(i)models.db.session.commit()# 根据地址删除用户a = models.Address.query.get(1)models.db.session.delete(a.user)models.db.session.commit()多对多# 实际开发也是如此,即只删除依赖# 根据商品删除所依赖的订单p = models.Product.query.get(1)o = models.Order.query.get(1)p.order.remove(o)models.db.session.commit()# 根据订单删除所关联的商品p = models.Product.query.get(1)o = models.Order.query.get(1)o.product.remove(p)models.db.session.commit() ...

January 15, 2019 · 2 min · jiezi

flask在Windows上的安装

flask是python的一个框架,因为其轻便而备受欢迎,在电脑中装好python后,进行flask的安装。1.首先要在电脑中安装pip,进入https://pypi.org/project/pip/#downloads进入网站后点击Download files,然后下载画线的文件下载完成后进行解压。 2.以管理员身份进入cmd,将目录切换到解压后的pip文件所在的目录,然后输入python setup.py install等待安装完成后,输入pip,发现命令可用 3.这时输入pip install flask等待安装完成即可。 4.如果在安装过程中出现网络超时报错,输入pip install flask -i http://pypi.douban.com/simple –trusted-host pypi.douban.com即可。

January 13, 2019 · 1 min · jiezi

做了一个技术博客聚合站,大家来提交自己的博客鸭

抛 https://www.kewangst.com/提交网站说明 https://www.kewangst.com/page…友链说明 https://www.kewangst.com/page…

January 9, 2019 · 1 min · jiezi

current_app这个坑

今天同学问了我一个关于current_app的问题,我之前也看了一下慕课七月老师讲的flask项目,正好碰到过,所以也给大家分享一下。【current_app】之前在写项目的时候也有用到current_app,老师讲的是代表了当前项目的app,当然写完项目也没有任何问题,但是通过这次学习,我才发现,好坑呐,真怕不用就忘了。大家看一下下面这段代码from flask import Flask, current_appapp = Flask(name)print(app) #输出结果:<Flask ‘1_current_app’>相信大家对这段代码是没有问题的,也不知道有啥作用,那么请看下面这段代码from flask import Flask, current_appapp = Flask(name)print(app) #输出结果:<Flask ‘1_current_app’>app2 = current_appprint(app2) #输出结果:竟然报了一大堆的错误错误信息是这样子的:RuntimeError: Working outside of application context.意思是说我们在应用上下文之外运行的,这究竟是咋回事,请看下面这幅图原来在flask内部维护者两个线程隔离的栈,current_app指向了AppContext(应用上下文)中的栈顶,request指向了RequestContext(请求上下文)栈顶,当请求进入的时候,Request对象被压入栈,从而request有了指向处理请求,接下来会判断AppContext栈顶是否为空,若为空则向栈中压入一个AppContext对象,即app,从而current_app就有了指向,所以我们在项目中使用是没有报错的,而我们上面的代码不是在请求中实现的所以AppContext栈顶为空,current_app并没有指向一个AppContext对象,怎样解决呢?from flask import Flask, current_appapp = Flask(name)print(app) #输出结果:<Flask ‘1_current_app’>with app.app_context(): app2 = current_app print(app2) #输出结果:<Flask ‘1_current_app’>这里我们使用了with,其app_context()返回一个AppContext对象,而其又实现了__enter__与__exit__分别让AppContext对象,即app入栈和出栈,完成了此操作。

January 8, 2019 · 1 min · jiezi

Flask之flask-mail邮件发送

为什么要使用flask-mail发送邮件? 因为python自带的email包比较底层,使用起来比较麻烦。安装Flask-Mail:pip install flask-mail配置 Flask-MailMAIL_SERVER : 默认为 ‘localhost’ #这里我配置QQ邮箱服务器:smtp.qq.comMAIL_PORT : 默认为 25 #QQ邮箱服务器端口:465MAIL_USE_TLS : 默认为 False #使用的是TLS协议,所以为TrueMAIL_USE_SSL : 默认为 FalseMAIL_USERNAME : 默认为 None #这里配置自己的QQ邮箱MAIL_PASSWORD : 默认为 None #这里的密码不是邮箱密码,是需要去开启的客户端授权密码MAIL_DEFAULT_SENDER : 默认为 None #这里我配置自己的QQ邮箱MAIL_MAX_EMAILS : 默认为 None #一次性发邮件的最大数量,即recipients列表长度MAIL_ASCII_ATTACHMENTS : 默认为 False #文件名将会转换成 ASCII 的。 当文件名是以 UTF-8 编码的时候,使用邮件转发的时候会修改邮件内容并且混淆 Content-Disposition 描述,这个时候 MAIL_ASCII_ATTACHMENTS 配置项是十分有用的注册 Mail【app/init.py】#和其他的扩展一样,我们要先注册到appfrom flask import Flaskfrom flask_mail import Mailapp = Flask(name)mail = Mail() #测试时可以直接在Mail()中写入app对象mail.init_app(app) #这种方式是开发的时候常用的,因为我们要在其他模块中使用mail对象发送简单邮件【app/e_mail.py】from flask_mail import Messagefrom . import maildef send_email(subject,to,content): message=Message(subject,sender=current_app.config[“MAIL_DEFAULT_SENDER “],body=content,recipients=[to]) mail.send(message)if name == “main”: send_email(“jim给你发邮件啦”,11111@qq.com,“嘿嘿,这是我使用flask-mail给你发送的邮件哦!")当然上面只是我们一个简单的发邮件程序,我们还可以使用模板发送固定形式的数据。发送模板邮件【templates/mail.html】欢迎你观看此文章,您的邮箱账号为:<span style=“color:blue”>{{ to_mail }}</span>这个程序在上述文件上进行编写from flask import render_templatedef send_template_data(subject,to): message=Message(subject,sender=current_app.config[“MAIL_DEFAULT_SENDER “],recipients=[to]) message.html=render_template(“mail.html”,to_mail=to) mail.send(message)if name == “main”: send_email(“jim给你发邮件啦”,11111@qq.com)还可以发送附件def send_template_data(subject,to): message=Message(subject,sender=current_app.config[“MAIL_DEFAULT_SENDER “],recipients=[to]) with open(‘img.png’) as f: message.attach(img.png,image/png,f.read()) mail.send(message) ...

January 7, 2019 · 1 min · jiezi

为什么range不是迭代器?range到底是什么类型?

迭代器是 23 种设计模式中最常用的一种(之一),在 Python 中随处可见它的身影,我们经常用到它,但是却不一定意识到它的存在。在关于迭代器的系列文章中(链接见文末),我至少提到了 23 种生成迭代器的方法。有些方法是专门用于生成迭代器的,还有一些方法则是为了解决别的问题而“暗中”使用到迭代器。在系统学习迭代器之前,我一直以为 range() 方法也是用于生成迭代器的,现在却突然发现,它生成的只是可迭代对象,而并不是迭代器! (PS:Python2 中 range() 生成的是列表,本文基于Python3,生成的是可迭代对象)于是,我有了这样的疑问:为什么 range() 不生成迭代器呢?在查找答案的过程中,我发现自己对 range 类型的认识存在一些误区。因此,本文将和大家全面地认识一下 range ,期待与你共同学习进步。1、range() 是什么?它的语法:range(start, stop [,step]) ;start 指的是计数起始值,默认是 0;stop 指的是计数结束值,但不包括 stop ;step 是步长,默认为 1,不可以为 0 。range() 方法生成一段左闭右开的整数范围。>>> a = range(5) # 即 range(0,5)>>> arange(0, 5)>>> len(a)5>>> for x in a:>>> print(x,end=" “)0 1 2 3 4对于 range() 函数,有几个注意点:(1)它表示的是左闭右开区间;(2)它接收的参数必须是整数,可以是负数,但不能是浮点数等其它类型;(3)它是不可变的序列类型,可以进行判断元素、查找元素、切片等操作,但不能修改元素;(4)它是可迭代对象,却不是迭代器。# (1)左闭右开>>> for i in range(3, 6):>>> print(i,end=” “)3 4 5# (2)参数类型>>> for i in range(-8, -2, 2):>>> print(i,end=” “)-8 -6 -4>>> range(2.2)—————————-TypeError Traceback (most recent call last)…TypeError: ‘float’ object cannot be interpreted as an integer# (3)序列操作>>> b = range(1,10)>>> b[0]1>>> b[:-3]range(1, 7)>>> b[0] = 2TypeError Traceback (most recent call last)…TypeError: ‘range’ object does not support item assignment# (4)不是迭代器>>> hasattr(range(3),’iter’)True>>> hasattr(range(3),’next’)False>>> hasattr(iter(range(3)),’next’)True2、 为什么range()不生产迭代器?可以获得迭代器的内置方法很多,例如 zip() 、enumerate()、map()、filter() 和 reversed() 等等,但是像 range() 这样仅仅得到的是可迭代对象的方法就绝无仅有了(若有反例,欢迎告知)。这就是我存在知识误区的地方。在 for-循环 遍历时,可迭代对象与迭代器的性能是一样的,即它们都是惰性求值的,在空间复杂度与时间复杂度上并无差异。我曾概括过两者的差别是“一同两不同”:相同的是都可惰性迭代,不同的是可迭代对象不支持自遍历(即next()方法),而迭代器本身不支持切片(即__getitem__() 方法)。虽然有这些差别,但很难得出结论说它们哪个更优。现在微妙之处就在于,为什么给 5 种内置方法都设计了迭代器,偏偏给 range() 方法设计的就是可迭代对象呢?把它们都统一起来,不是更好么?事实上,Pyhton 为了规范性就干过不少这种事,例如,Python2 中有 range() 和 xrange() 两种方法,而 Python3 就干掉了其中一种,还用了“李代桃僵”法。为什么不更规范点,令 range() 生成的是迭代器呢?关于这个问题,我没找到官方解释,以下纯属个人观点 。zip() 等方法都需要接收确定的可迭代对象的参数,是对它们的一种再加工的过程,因此也希望马上产出确定的结果来,所以 Python 开发者就设计了这个结果是迭代器。这样还有一个好处,即当作为参数的可迭代对象发生变化的时候,作为结果的迭代器因为是消耗型的,不会被错误地使用。而 range() 方法就不同了,它接收的参数不是可迭代对象,本身是一种初次加工的过程,所以设计它为可迭代对象,既可以直接使用,也可以用于其它再加工用途。例如,zip() 等方法就完全可以接收 range 类型的参数。>>> for i in zip(range(1,6,2), range(2,7,2)):>>> print(i, end=”")(1, 2)(3, 4)(5, 6)也就是说,range() 方法作为一种初级生产者,它生产的原料本身就有很大用途,早早把它变为迭代器的话,无疑是一种画蛇添足的行为。对于这种解读,你是否觉得有道理呢?欢迎就这个话题与我探讨。3、range 类型是什么?以上是我对“为什么range()不产生迭代器”的一种解答。顺着这个思路,我研究了一下它产生的 range 对象,一研究就发现,这个 range 对象也并不简单。首先奇怪的一点就是,它竟然是不可变序列!我从未注意过这一点。虽然说,我从未想过修改 range() 的值,但这一不可修改的特性还是令我惊讶。翻看文档,官方是这样明确划分的——有三种基本的序列类型:列表、元组和范围(range)对象。(There are three basic sequence types: lists, tuples, and range objects.) 这我倒一直没注意,原来 range 类型居然跟列表和元组是一样地位的基础序列!我一直记挂着字符串是不可变的序列类型,不曾想,这里还有一位不可变的序列类型呢。那 range 序列跟其它序列类型有什么差异呢? 普通序列都支持的操作有 12 种,在《你真的知道Python的字符串是什么吗?》这篇文章里提到过。range 序列只支持其中的 10 种,不支持进行加法拼接与乘法重复。>>> range(2) + range(3)—————————————–TypeError Traceback (most recent call last)…TypeError: unsupported operand type(s) for +: ‘range’ and ‘range’>>> range(2)*2—————————————–TypeError Traceback (most recent call last)…TypeError: unsupported operand type(s) for *: ‘range’ and ‘int’那么问题来了:同样是不可变序列,为什么字符串和元组就支持上述两种操作,而偏偏 range 序列不支持呢?虽然不能直接修改不可变序列,但我们可以将它们拷贝到新的序列上进行操作啊,为何 range 对象连这都不支持呢?且看官方文档的解释:…due to the fact that range objects can only represent sequences that follow a strict pattern and repetition and concatenation will usually violate that pattern.原因是 range 对象仅仅表示一个遵循着严格模式的序列,而重复与拼接通常会破坏这种模式…问题的关键就在于 range 序列的 pattern,仔细想想,其实它表示的就是一个等差数列啊(喵,高中数学知识没忘…),拼接两个等差数列,或者重复拼接一个等差数列,想想确实不妥,这就是为啥 range 类型不支持这两个操作的原因了。由此推论,其它修改动作也会破坏等差数列结构,所以统统不给修改就是了。4、小结回顾全文,我得到了两个偏冷门的结论:range 是可迭代对象而不是迭代器;range 对象是不可变的等差序列。 若单纯看结论的话,你也许没有感触,或许还会说这没啥了不得啊。但如果我追问,为什么 range 不是迭代器呢,为什么 range 是不可变序列呢?对这俩问题,你是否还能答出个自圆其说的设计思想呢?(PS:我决定了,若有机会面试别人,我必要问这两个问题的嘿~)由于 range 对象这细微而有意思的特性,我觉得这篇文章写得值了。本文是作为迭代器系列文章的一篇来写的,所以对于迭代器的基础知识介绍不多,欢迎查看之前的文章。另外,还有一种特殊的迭代器也值得单独成文,那就是生成器了,敬请期待后续推文哦~猜你想读:Python进阶:迭代器与迭代器切片Python进阶:设计模式之迭代器模式你真的知道Python的字符串是什么吗?官方文档:http://t.cn/EGMzJt8—————–本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。 ...

January 5, 2019 · 2 min · jiezi