如何利用FME 创建自己的功能软件

目录

前言

一、概述

二、开发思路

1.前端开发

(1)登录页面

(1)功能页面

(1)下载页面

 2.后端开发

二、发布上线

总结



前言

经过几个月修炼,之前更新的一篇利用django和fme实现模板继承的研究现在终于开花结果,今天悟空就来分享一下利用fme作为后台计算模型搭建数据分析web应用,创建自己的功能软件,让用户彻底脱离fme环境,创建属于自己的ui操作界面。


一、概述

1、编程语言:JavaScript,python

2、开发框架:前端VUE3 ,后端django

3、数据库:数据表PostgreSql,进程管理数据库redis

4、开发工具:webstorm,pycharm

5、计算模型api:fme模板

二、开发思路

1.前端开发

本次开发主要采用前端后端分离式开发,前端使用vue3+element-plus搭建web应用页面,主要页面为登录页面、功能页面、成果下载页面。

(1)登录页面

 这个没啥好说的,主要注意的是要做一个导航守卫,登录成果后让系统从后台api拿到token值,根据是否有token值来判断页面是否跳转,如果没拿到token值则用户不能进入主页面。这部分比较通用,几乎任何应用都类似。

export default defineComponent({

 
    setup() {
          const loginForm={
            username:'',
              password:'',
  };

         let router=useRouter();
         let store=useStore();
        return {
          router,
          store,
          loginForm,
        };
    },
    mounted() {
    let loginForm = JSON.parse(localStorage.getItem("loginForm"));
    if (loginForm) {
      this.loginForm = loginForm;
    }
  },
  methods: {
       onSubmit() {
             let api = 'http://192.168.0.179:8000/lg/login/'
             let router=this.router
             window.localStorage.setItem("loginForm",this.loginForm.username);
              axios.post(api,this.loginForm,{
       timeout: 2000,
      }).then(function(res:any){
              alert('登录成功');
              window.localStorage.setItem("token",res.data.token);
              router.push({path:"/"});
              //重新触发路由
                location.reload()

(2)功能页面

 功能页面简洁大方为主,目前上线了3个功能集,都是以自定义表单的形式提交参数到后台,并得到成果,后续上线其他功能只需要简单新增子路由即可。

 以下是其中一个子页面代码,需要注意的如果是某些大文件传输,需要将文件二进制流打散,然后后端接受组合成完整文件,后期会考虑增加断点续传等功能。

<template>
 
    <div class="page-box">
  <vxe-toolbar>
  </vxe-toolbar>

  <el-form ref="form" :model="form" label-width="80px">
  <el-row>
  <el-col :span="24">

  <div class="pagetit">尖锐角修复</div>
  </el-col>
  </el-row>

  <el-row>
  <el-col :span="10">
    <el-form-item label="角度">
  <el-input v-model="forms.angel" placeholder="尖角判定最大角度"></el-input>
  </el-form-item>
  </el-col>
  </el-row>

  <el-row>
  <el-col :span="10">
    <el-form-item label="面积">
  <el-input v-model="forms.area" placeholder="尖角部分面积改变大小"></el-input>
  </el-form-item>
  </el-col>
  </el-row>
  <el-row>
   <el-col :span="18">
     <el-form-item label="文件上传">
            <el-upload

                multiple
                ref="mYupload"
              :on-change="fileget"
              class="upload-demo"
              action="111"

              :auto-upload="false"
            >
              <template #trigger>
                <el-button type="primary">需要修复的shp文件</el-button>
              </template>

              <el-button type="primary" @click="clearFiles">
                清空

              </el-button>

            </el-upload>

       </el-form-item>
   </el-col>
  </el-row>

  <el-row>
      <el-col :span="18">
   <el-form-item>
      <el-button type="primary" @click="onSubmit">提交数据</el-button>

    </el-form-item>
  </el-col>
  </el-row>

    </el-form>
    </div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";

import axios from "axios";
export default defineComponent({
    setup() {
  var fileList=[];
  var file= {};
  var dialogImageUrl= '';
       var   dialogVisible= false;
  const forms=ref({

   angel:'',
   area:'',


  });


    return {
     file:{},
     fileList:[],
   forms,

    };
    },
     methods: {
      fileget(file){
        this.fileList.push(file.raw)


        console.log(this.fileList)
      },

      clearFiles() {
        this.$refs["mYupload"].clearFiles();
      },

      onSubmit() {
     let api = 'http://192.168.0.179:8000/api/test/';
      let fd = new FormData();
      //let username= window.username
       let name = window.localStorage.getItem('loginForm');

      for(var i=0;i<this.fileList.length;i++){
         fd.append('file',this.fileList[i]);;
      }
      //传文件
         fd.append('angel',this.forms.angel);//传其他参数
         fd.append('area',this.forms.area);
         fd.append('name',name);
      axios.post(api,fd,{
       headers: {'Content-Type': 'multipart/form-data'},
       timeout: 200000,
      }).then(function(res){
              alert('提交成功');
      })
      this.fileList=[];
      this.$refs["mYupload"].clearFiles();
      this.forms={
   angel:'',
   area:'',
  };

      // return false//屏蔽了action的默认上传
    },

        },
   
})
</script>
<style lang="scss" scoped>
.up-load{
    width: 50vw;
    margin: 50px auto;
    >div{
        margin-top: 20px;
    }
    .up-title{
        text-align: center;
        font-size: 30px;
        font-weight: 600;
    }
    .up-one{
        height: 70px;
        overflow: hidden;
    }
    .property{
        padding: 0 50px;
        text-align: center;
        >div{
            padding: 10px;
            font-weight: 600;
        }
    }
}
/deep/.vxe-button--content{color: #fff;}
 /deep/.vxe-button.type--button:hover,.vxe-button.type--button.active{background: #2D94FC !important; border-color: #2D94FC!important;}
</style>

(3)下载页面

我们所有表单提交都采用ajax异步请求,请求完成后,后端接收前端数据,储存数据库,触发fme计算模型。fme模板运行完毕后会和后端交互,自动生成下载链接,并渲染到前端。

表单提交后会立即生成任务,后台计算完成后会自动更新任务状态。

fme模板执行成功后自动渲染下载链接

fme模板运行失败,也会更新任务状态让用户从新检查自己的提交参数

点击下载链接,成功下载计算后成果。

点击删除按钮则自动删除该任务在数据库的位置,以及对应的下载文件。

 2.后端开发

后端开发采用django和django的扩展库drf来快速生成接口,采用redis和celery来管理fme进程以便于应付高并发任务队列情况。

值得一提的是celery这个python库,能实现百万级的进程管理,成功的让我们的后台模板有序高效的执行。经过测试,celery在处理高请求量且较为复杂的fme模板甚至有媲美fmeserver的效率。

 当然唯一的缺陷也非常明显,就是fmedesktop最多只支持8进程同时运行,不过要克服这一难题也不是没有办法,可以组建分布式celery,让多台机器为一个django提供计算服务。要组建分布式的话,所涉及的技术也更加的难,要考虑资源分配,任务调度,以及多台机器的连接稳定。redis和数据库以及本地磁盘都得建立多台机内网共享。

后端还需要攻克的一点则是 输出成果的渲染,因为我们的用户的多个,他们执行的任务是不同的,后端需要写逻辑,根据前端的反馈的用户名信息,从数据库调用该用户的任务来反馈给前端,不然就会导致任务共享,用户则不知道自己执行的任务是哪一个。

以下是部分后端代码。


from rest_framework.views import APIView
from rest_framework.response import Response
from books.models import Books
from books.serializer import BooksSerializer
from rest_framework import status
import os
import uuid
import shutil

class BooksViewSet(APIView):
    def get(self,request):
        name=request.GET['name']
        path_list = Books.objects.filter(name=name)
        path_list1=BooksSerializer(instance=path_list,many=True)
        return Response(path_list1.data)

    def post(self, request):
        """添加一条数据"""
        uid=uuid.uuid1()
        # 接收客户端提交的数据
        name=request.data['name']
        type='尖锐角修复'
        an=request.data['angel']
        area=request.data['area']
        url='计算中...'
        import time
        now = time.localtime()
        time = str(time.strftime("%Y-%m-%d %H:%M:%S", now))
        Books.objects.create(
            name=name,
            path=url,
            type=type,
            uid=uid,
            date=time
        )
        myfile = request.FILES.getlist("file", None)
        #print(myfile)
        path = os.makedirs(r'G:\\temp' + './{}'.format(uid))
        aaA = str('G:\\temp' + '\\{}'.format(uid))
        for ff in myfile:
            road = os.path.join(aaA, ff.name)
            f = open(road, 'wb+')
            for chunk in ff.chunks():
               f.write(chunk)
            f.close()
        from celery_tasks.api.tasks import angel


        try:
            angel.delay(road,an,area,uid,name)
        except:
            aa=Books.objects.filter(uid=uid)
            aa.update(path='计算失败,请查看提交数据是否规范')
            pass
        return Response(status=status.HTTP_201_CREATED)

class reViewSet(APIView):
    def post(self, request):
        data = request.data['removeRecords'][0]
        pk=data['id']
        uid=data['uid']
        course = Books.objects.get(pk=pk)
        course.delete()
        shutil.rmtree(r'G:\django1\media\{}'.format(uid))
        return Response(status=status.HTTP_201_CREATED)
class gdViewSet(APIView):
    def post(self, request):
        """添加一条数据"""
        uid=uuid.uuid1()
        # 接收客户端提交的数据
        name=request.data['name']
        type='耕地质量分类'
        xzq=request.data['xzq']
        url='计算中...'
        import time
        now = time.localtime()
        time = str(time.strftime("%Y-%m-%d %H:%M:%S", now))
        Books.objects.create(
            name=name,
            path=url,
            type=type,
            uid=uid,
            date=time
        )
        myfile = request.FILES.getlist("file", None)
        os.makedirs(r'G:\\temp' + './{}'.format(uid))
        aaA = str('G:\\temp' + '\\{}'.format(uid))
        for ff in myfile:
            road = os.path.join(aaA, ff.name)
            f = open(road, 'wb+')
            for chunk in ff.chunks():
               f.write(chunk)
            f.close()
        from celery_tasks.api.zippp import upzip
        from celery_tasks.api.tasks import gd
        path = 'G:\\temp\\{}'.format(uid)
        for i in os.listdir(path):
            file_name, file_extension = os.path.splitext(i)
            if file_extension == '.zip':
                upzip(dirpath=str(path + '\\' + i), outpath=path)
                gdbroad = str(path + '\\' + file_name)
            if file_extension == '.shp':
                xzqroad = str(path + '\\' + i)

        try:
            gd.delay(xzqroad,xzq,gdbroad,uid,name)
        except:
            aa=Books.objects.filter(uid=uid)
            aa.update(path='计算失败,请查看提交数据是否规范')
            pass

        return Response(status=status.HTTP_201_CREATED)

 3.FME模板调用

调用模板采用python触发dos指令来传递前端参数,并调用fme模板,需要注意传统os调用会出问题,因为传统os调用是异步携程执行。这样会和celery出现冲突,我们需要subprocess库来调用模板,同步执行。

二、发布上线

采用iis服务器发布django任务,具体可以参考该博主的博客。Django--4 Django项目在Win10 + IIS服务器部署上线运行_liuning2008的博客-CSDN博客_django项目部署到服务器 windowszDjango--4 Django项目在Win10 + IIS服务器部署上线运行_liuning2008的博客-CSDN博客_django项目部署到服务器 windows

整体项目框架就是这样,temp存放临时缓存文件,fmw存放模板文件。我设置了windos用户计划,定期清空临时文件防止磁盘爆满。


总结

爆肝多个月终于是将全套功能实现了,看似简单的功能,简单的逻辑,其实涉及了庞大的技术量,中途踩的坑那是一个比一个多,尤其是vue的生命周期和路由分发,耗费了我大量时间才勉强实现效果。有兴趣的小伙伴可以先从前端js,html5,css3开始学起,再掌握一门后端框架,推荐django和flask。最终的成果是让我非常满意的,同时也感谢我的前端大佬小洋人,给我恶补了不少vue的知识点。该研究最大的特点就是可以让用户脱离fme环境,脱离配置各种python库的烦恼,脱离电脑设备跟不上的困境,只需要一个浏览器,就可以完成大量复杂且耗费人工的工作,后期我会陆续上线功能集,并准备把我之前的搞深度学习涉及的不少模板也上线了。

猜你喜欢

转载自blog.csdn.net/weixin_57664381/article/details/125417057