Ver Fonte

add python 脚本解析

18339543638 há 3 anos atrás
pai
commit
3623de53a0

+ 488 - 0
coffee-admin/src/main/resources/python/xmltodict.py

@@ -0,0 +1,488 @@
+#!/usr/bin/env python
+"Makes working with XML feel like you are working with JSON"
+
+try:
+    from defusedexpat import pyexpat as expat
+except ImportError:
+    from xml.parsers import expat
+from xml.sax.saxutils import XMLGenerator
+from xml.sax.xmlreader import AttributesImpl
+try:  # pragma no cover
+    from cStringIO import StringIO
+except ImportError:  # pragma no cover
+    try:
+        from StringIO import StringIO
+    except ImportError:
+        from io import StringIO
+
+from collections import OrderedDict
+
+try:  # pragma no cover
+    _basestring = basestring
+except NameError:  # pragma no cover
+    _basestring = str
+try:  # pragma no cover
+    _unicode = unicode
+except NameError:  # pragma no cover
+    _unicode = str
+
+__author__ = 'Martin Blech'
+__version__ = '0.12.0'
+__license__ = 'MIT'
+
+
+class ParsingInterrupted(Exception):
+    pass
+
+
+class _DictSAXHandler(object):
+    def __init__(self,
+                 item_depth=0,
+                 item_callback=lambda *args: True,
+                 xml_attribs=True,
+                 attr_prefix='@',
+                 cdata_key='#text',
+                 force_cdata=False,
+                 cdata_separator='',
+                 postprocessor=None,
+                 dict_constructor=OrderedDict,
+                 strip_whitespace=True,
+                 namespace_separator=':',
+                 namespaces=None,
+                 force_list=None):
+        self.path = []
+        self.stack = []
+        self.data = []
+        self.item = None
+        self.item_depth = item_depth
+        self.xml_attribs = xml_attribs
+        self.item_callback = item_callback
+        self.attr_prefix = attr_prefix
+        self.cdata_key = cdata_key
+        self.force_cdata = force_cdata
+        self.cdata_separator = cdata_separator
+        self.postprocessor = postprocessor
+        self.dict_constructor = dict_constructor
+        self.strip_whitespace = strip_whitespace
+        self.namespace_separator = namespace_separator
+        self.namespaces = namespaces
+        self.namespace_declarations = OrderedDict()
+        self.force_list = force_list
+
+    def _build_name(self, full_name):
+        if not self.namespaces:
+            return full_name
+        i = full_name.rfind(self.namespace_separator)
+        if i == -1:
+            return full_name
+        namespace, name = full_name[:i], full_name[i+1:]
+        short_namespace = self.namespaces.get(namespace, namespace)
+        if not short_namespace:
+            return name
+        else:
+            return self.namespace_separator.join((short_namespace, name))
+
+    def _attrs_to_dict(self, attrs):
+        if isinstance(attrs, dict):
+            return attrs
+        return self.dict_constructor(zip(attrs[0::2], attrs[1::2]))
+
+    def startNamespaceDecl(self, prefix, uri):
+        self.namespace_declarations[prefix or ''] = uri
+
+    def startElement(self, full_name, attrs):
+        name = self._build_name(full_name)
+        attrs = self._attrs_to_dict(attrs)
+        if attrs and self.namespace_declarations:
+            attrs['xmlns'] = self.namespace_declarations
+            self.namespace_declarations = OrderedDict()
+        self.path.append((name, attrs or None))
+        if len(self.path) > self.item_depth:
+            self.stack.append((self.item, self.data))
+            if self.xml_attribs:
+                attr_entries = []
+                for key, value in attrs.items():
+                    key = self.attr_prefix+self._build_name(key)
+                    if self.postprocessor:
+                        entry = self.postprocessor(self.path, key, value)
+                    else:
+                        entry = (key, value)
+                    if entry:
+                        attr_entries.append(entry)
+                attrs = self.dict_constructor(attr_entries)
+            else:
+                attrs = None
+            self.item = attrs or None
+            self.data = []
+
+    def endElement(self, full_name):
+        name = self._build_name(full_name)
+        if len(self.path) == self.item_depth:
+            item = self.item
+            if item is None:
+                item = (None if not self.data
+                        else self.cdata_separator.join(self.data))
+
+            should_continue = self.item_callback(self.path, item)
+            if not should_continue:
+                raise ParsingInterrupted()
+        if len(self.stack):
+            data = (None if not self.data
+                    else self.cdata_separator.join(self.data))
+            item = self.item
+            self.item, self.data = self.stack.pop()
+            if self.strip_whitespace and data:
+                data = data.strip() or None
+            if data and self.force_cdata and item is None:
+                item = self.dict_constructor()
+            if item is not None:
+                if data:
+                    self.push_data(item, self.cdata_key, data)
+                self.item = self.push_data(self.item, name, item)
+            else:
+                self.item = self.push_data(self.item, name, data)
+        else:
+            self.item = None
+            self.data = []
+        self.path.pop()
+
+    def characters(self, data):
+        if not self.data:
+            self.data = [data]
+        else:
+            self.data.append(data)
+
+    def push_data(self, item, key, data):
+        if self.postprocessor is not None:
+            result = self.postprocessor(self.path, key, data)
+            if result is None:
+                return item
+            key, data = result
+        if item is None:
+            item = self.dict_constructor()
+        try:
+            value = item[key]
+            if isinstance(value, list):
+                value.append(data)
+            else:
+                item[key] = [value, data]
+        except KeyError:
+            if self._should_force_list(key, data):
+                item[key] = [data]
+            else:
+                item[key] = data
+        return item
+
+    def _should_force_list(self, key, value):
+        if not self.force_list:
+            return False
+        if isinstance(self.force_list, bool):
+            return self.force_list
+        try:
+            return key in self.force_list
+        except TypeError:
+            return self.force_list(self.path[:-1], key, value)
+
+
+def parse(xml_input, encoding=None, expat=expat, process_namespaces=False,
+          namespace_separator=':', disable_entities=True, **kwargs):
+    """Parse the given XML input and convert it into a dictionary.
+
+    `xml_input` can either be a `string` or a file-like object.
+
+    If `xml_attribs` is `True`, element attributes are put in the dictionary
+    among regular child elements, using `@` as a prefix to avoid collisions. If
+    set to `False`, they are just ignored.
+
+    Simple example::
+
+        >>> import xmltodict
+        >>> doc = xmltodict.parse(\"\"\"
+        ... <a prop="x">
+        ...   <b>1</b>
+        ...   <b>2</b>
+        ... </a>
+        ... \"\"\")
+        >>> doc['a']['@prop']
+        u'x'
+        >>> doc['a']['b']
+        [u'1', u'2']
+
+    If `item_depth` is `0`, the function returns a dictionary for the root
+    element (default behavior). Otherwise, it calls `item_callback` every time
+    an item at the specified depth is found and returns `None` in the end
+    (streaming mode).
+
+    The callback function receives two parameters: the `path` from the document
+    root to the item (name-attribs pairs), and the `item` (dict). If the
+    callback's return value is false-ish, parsing will be stopped with the
+    :class:`ParsingInterrupted` exception.
+
+    Streaming example::
+
+        >>> def handle(path, item):
+        ...     print('path:%s item:%s' % (path, item))
+        ...     return True
+        ...
+        >>> xmltodict.parse(\"\"\"
+        ... <a prop="x">
+        ...   <b>1</b>
+        ...   <b>2</b>
+        ... </a>\"\"\", item_depth=2, item_callback=handle)
+        path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:1
+        path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:2
+
+    The optional argument `postprocessor` is a function that takes `path`,
+    `key` and `value` as positional arguments and returns a new `(key, value)`
+    pair where both `key` and `value` may have changed. Usage example::
+
+        >>> def postprocessor(path, key, value):
+        ...     try:
+        ...         return key + ':int', int(value)
+        ...     except (ValueError, TypeError):
+        ...         return key, value
+        >>> xmltodict.parse('<a><b>1</b><b>2</b><b>x</b></a>',
+        ...                 postprocessor=postprocessor)
+        OrderedDict([(u'a', OrderedDict([(u'b:int', [1, 2]), (u'b', u'x')]))])
+
+    You can pass an alternate version of `expat` (such as `defusedexpat`) by
+    using the `expat` parameter. E.g:
+
+        >>> import defusedexpat
+        >>> xmltodict.parse('<a>hello</a>', expat=defusedexpat.pyexpat)
+        OrderedDict([(u'a', u'hello')])
+
+    You can use the force_list argument to force lists to be created even
+    when there is only a single child of a given level of hierarchy. The
+    force_list argument is a tuple of keys. If the key for a given level
+    of hierarchy is in the force_list argument, that level of hierarchy
+    will have a list as a child (even if there is only one sub-element).
+    The index_keys operation takes precendence over this. This is applied
+    after any user-supplied postprocessor has already run.
+
+        For example, given this input:
+        <servers>
+          <server>
+            <name>host1</name>
+            <os>Linux</os>
+            <interfaces>
+              <interface>
+                <name>em0</name>
+                <ip_address>10.0.0.1</ip_address>
+              </interface>
+            </interfaces>
+          </server>
+        </servers>
+
+        If called with force_list=('interface',), it will produce
+        this dictionary:
+        {'servers':
+          {'server':
+            {'name': 'host1',
+             'os': 'Linux'},
+             'interfaces':
+              {'interface':
+                [ {'name': 'em0', 'ip_address': '10.0.0.1' } ] } } }
+
+        `force_list` can also be a callable that receives `path`, `key` and
+        `value`. This is helpful in cases where the logic that decides whether
+        a list should be forced is more complex.
+    """
+    handler = _DictSAXHandler(namespace_separator=namespace_separator,
+                              **kwargs)
+    if isinstance(xml_input, _unicode):
+        if not encoding:
+            encoding = 'utf-8'
+        xml_input = xml_input.encode(encoding)
+    if not process_namespaces:
+        namespace_separator = None
+    parser = expat.ParserCreate(
+        encoding,
+        namespace_separator
+    )
+    try:
+        parser.ordered_attributes = True
+    except AttributeError:
+        # Jython's expat does not support ordered_attributes
+        pass
+    parser.StartNamespaceDeclHandler = handler.startNamespaceDecl
+    parser.StartElementHandler = handler.startElement
+    parser.EndElementHandler = handler.endElement
+    parser.CharacterDataHandler = handler.characters
+    parser.buffer_text = True
+    if disable_entities:
+        try:
+            # Attempt to disable DTD in Jython's expat parser (Xerces-J).
+            feature = "http://apache.org/xml/features/disallow-doctype-decl"
+            parser._reader.setFeature(feature, True)
+        except AttributeError:
+            # For CPython / expat parser.
+            # Anything not handled ends up here and entities aren't expanded.
+            parser.DefaultHandler = lambda x: None
+            # Expects an integer return; zero means failure -> expat.ExpatError.
+            parser.ExternalEntityRefHandler = lambda *x: 1
+    if hasattr(xml_input, 'read'):
+        parser.ParseFile(xml_input)
+    else:
+        parser.Parse(xml_input, True)
+    return handler.item
+
+
+def _process_namespace(name, namespaces, ns_sep=':', attr_prefix='@'):
+    if not namespaces:
+        return name
+    try:
+        ns, name = name.rsplit(ns_sep, 1)
+    except ValueError:
+        pass
+    else:
+        ns_res = namespaces.get(ns.strip(attr_prefix))
+        name = '{}{}{}{}'.format(
+            attr_prefix if ns.startswith(attr_prefix) else '',
+            ns_res, ns_sep, name) if ns_res else name
+    return name
+
+
+def _emit(key, value, content_handler,
+          attr_prefix='@',
+          cdata_key='#text',
+          depth=0,
+          preprocessor=None,
+          pretty=False,
+          newl='\n',
+          indent='\t',
+          namespace_separator=':',
+          namespaces=None,
+          full_document=True):
+    key = _process_namespace(key, namespaces, namespace_separator, attr_prefix)
+    if preprocessor is not None:
+        result = preprocessor(key, value)
+        if result is None:
+            return
+        key, value = result
+    if (not hasattr(value, '__iter__')
+            or isinstance(value, _basestring)
+            or isinstance(value, dict)):
+        value = [value]
+    for index, v in enumerate(value):
+        if full_document and depth == 0 and index > 0:
+            raise ValueError('document with multiple roots')
+        if v is None:
+            v = OrderedDict()
+        elif isinstance(v, bool):
+            if v:
+                v = _unicode('true')
+            else:
+                v = _unicode('false')
+        elif not isinstance(v, dict):
+            v = _unicode(v)
+        if isinstance(v, _basestring):
+            v = OrderedDict(((cdata_key, v),))
+        cdata = None
+        attrs = OrderedDict()
+        children = []
+        for ik, iv in v.items():
+            if ik == cdata_key:
+                cdata = iv
+                continue
+            if ik.startswith(attr_prefix):
+                ik = _process_namespace(ik, namespaces, namespace_separator,
+                                        attr_prefix)
+                if ik == '@xmlns' and isinstance(iv, dict):
+                    for k, v in iv.items():
+                        attr = 'xmlns{}'.format(':{}'.format(k) if k else '')
+                        attrs[attr] = _unicode(v)
+                    continue
+                if not isinstance(iv, _unicode):
+                    iv = _unicode(iv)
+                attrs[ik[len(attr_prefix):]] = iv
+                continue
+            children.append((ik, iv))
+        if pretty:
+            content_handler.ignorableWhitespace(depth * indent)
+        content_handler.startElement(key, AttributesImpl(attrs))
+        if pretty and children:
+            content_handler.ignorableWhitespace(newl)
+        for child_key, child_value in children:
+            _emit(child_key, child_value, content_handler,
+                  attr_prefix, cdata_key, depth+1, preprocessor,
+                  pretty, newl, indent, namespaces=namespaces,
+                  namespace_separator=namespace_separator)
+        if cdata is not None:
+            content_handler.characters(cdata)
+        if pretty and children:
+            content_handler.ignorableWhitespace(depth * indent)
+        content_handler.endElement(key)
+        if pretty and depth:
+            content_handler.ignorableWhitespace(newl)
+
+
+def unparse(input_dict, output=None, encoding='utf-8', full_document=True,
+            short_empty_elements=False,
+            **kwargs):
+    """Emit an XML document for the given `input_dict` (reverse of `parse`).
+
+    The resulting XML document is returned as a string, but if `output` (a
+    file-like object) is specified, it is written there instead.
+
+    Dictionary keys prefixed with `attr_prefix` (default=`'@'`) are interpreted
+    as XML node attributes, whereas keys equal to `cdata_key`
+    (default=`'#text'`) are treated as character data.
+
+    The `pretty` parameter (default=`False`) enables pretty-printing. In this
+    mode, lines are terminated with `'\n'` and indented with `'\t'`, but this
+    can be customized with the `newl` and `indent` parameters.
+
+    """
+    if full_document and len(input_dict) != 1:
+        raise ValueError('Document must have exactly one root.')
+    must_return = False
+    if output is None:
+        output = StringIO()
+        must_return = True
+    if short_empty_elements:
+        content_handler = XMLGenerator(output, encoding, True)
+    else:
+        content_handler = XMLGenerator(output, encoding)
+    if full_document:
+        content_handler.startDocument()
+    for key, value in input_dict.items():
+        _emit(key, value, content_handler, full_document=full_document,
+              **kwargs)
+    if full_document:
+        content_handler.endDocument()
+    if must_return:
+        value = output.getvalue()
+        try:  # pragma no cover
+            value = value.decode(encoding)
+        except AttributeError:  # pragma no cover
+            pass
+        return value
+
+
+if __name__ == '__main__':  # pragma: no cover
+    import sys
+    import marshal
+    try:
+        stdin = sys.stdin.buffer
+        stdout = sys.stdout.buffer
+    except AttributeError:
+        stdin = sys.stdin
+        stdout = sys.stdout
+
+    (item_depth,) = sys.argv[1:]
+    item_depth = int(item_depth)
+
+    def handle_item(path, item):
+        marshal.dump((path, item), stdout)
+        return True
+
+    try:
+        root = parse(stdin,
+                     item_depth=item_depth,
+                     item_callback=handle_item,
+                     dict_constructor=dict)
+        if item_depth == 0:
+            handle_item([], root)
+    except KeyboardInterrupt:
+        pass

+ 32 - 0
coffee-admin/src/main/test/java/com/coffee/admin/BusHospitalTest.java

@@ -1,9 +1,11 @@
 package com.coffee.admin;
 
 import cn.dev33.satoken.stp.StpUtil;
+import com.coffee.bus.bean.Script;
 import com.coffee.bus.controller.BusHospitalController;
 import com.coffee.bus.entity.BusHospitalEntity;
 import com.coffee.bus.service.LocalBusHospitalService;
+import com.coffee.common.result.R;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -55,4 +57,34 @@ public class BusHospitalTest {
         boolean b = busHospitalService.removeById(1505789328745721857L);
         System.out.println(b);
     }
+
+    @Test
+    public void script(){
+        String id="";
+        String str = "# -*- coding: utf-8 -*-\n" +
+                "import sys\n" +
+                "reload(sys)\n" +
+                "sys.setdefaultencoding('utf-8')\n" +
+                "import json\n" +
+                "import xmltodict as xmltodict\n" +
+                "\n" +
+                "\n" +
+                "def parse(str):\n" +
+                "    json_dict = xmltodict.parse(str, encoding='utf-8')\n" +
+                "    json_str = json.dumps(json_dict, indent=2)\n" +
+                "    return json_str";
+        BusHospitalEntity busHospitalEntity = new BusHospitalEntity();
+        busHospitalEntity.setId("1505789859765604353");
+        Script script = new Script();
+        script.setId("python");
+        script.setContent(str);
+        busHospitalEntity.setScript(script);
+        busHospitalService.updateById(busHospitalEntity);
+    }
+
+    @Test
+    public void debug(){
+        R debug = busHospitalController.debug("1505789859765604353", "{\"ceshi\":\"测试\" , \"age\":\"20\" }");
+        System.out.println(debug);
+    }
 }

+ 3 - 0
coffee-common/src/main/java/com/coffee/common/config/websocket/DefaultWebSocketMsgHandler.java

@@ -10,6 +10,8 @@ import com.coffee.common.config.websocket.handler.WsHandler;
 import com.coffee.common.result.R;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 import org.tio.core.ChannelContext;
 import org.tio.core.Tio;
@@ -24,6 +26,7 @@ import java.util.stream.Collectors;
 @Component
 @Slf4j
 @AllArgsConstructor
+@AutoConfigureAfter(RedisTemplate.class)
 public class DefaultWebSocketMsgHandler implements IWsMsgHandler {
     private final List<WsHandler> messageHandlers;
     @Override

+ 7 - 7
coffee-framework/src/main/java/com/coffee/framework/config/mybatisplus/MybatisPlusConfig.java

@@ -145,13 +145,13 @@ public class MybatisPlusConfig {
                 if(url.toString().endsWith("/login")){
                     return true;
                 }
-//                return true;
-                //判断当前有用户是否为系统级用户,若是,则忽略逻辑隔离
-                LoginUser loginUser = (LoginUser) StpUtil.getTokenSession().get(Constants.LOGIN_USER_KEY);
-                if(1==loginUser.getIsSys()){
-                    return true;
-                }
-                return CollectionUtil.isEmpty(ignoreTableName)||ignoreTableName.contains(tableName);
+                return true;
+//                //判断当前有用户是否为系统级用户,若是,则忽略逻辑隔离
+//                LoginUser loginUser = (LoginUser) StpUtil.getTokenSession().get(Constants.LOGIN_USER_KEY);
+//                if(1==loginUser.getIsSys()){
+//                    return true;
+//                }
+//                return CollectionUtil.isEmpty(ignoreTableName)||ignoreTableName.contains(tableName);
             }
 
             @Override

+ 4 - 0
coffee-system/pom.xml

@@ -16,6 +16,10 @@
             <groupId>com.coffee</groupId>
             <artifactId>coffee-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.python</groupId>
+            <artifactId>jython-standalone</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 18 - 0
coffee-system/src/main/java/com/coffee/bus/bean/Script.java

@@ -0,0 +1,18 @@
+package com.coffee.bus.bean;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName Script.java
+ * @Description TODO
+ * @createTime 2022年03月28日 14:19:00
+ */
+@Data
+public class Script implements Serializable {
+    private String id;
+    private String content;
+}

+ 28 - 2
coffee-system/src/main/java/com/coffee/bus/controller/BusHospitalController.java

@@ -1,14 +1,20 @@
 package com.coffee.bus.controller;
 
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.mapper.Mapper;
+import com.coffee.bus.bean.Script;
 import com.coffee.bus.entity.BusHospitalEntity;
+import com.coffee.bus.script.ScriptManager;
 import com.coffee.bus.service.LocalBusHospitalService;
 import com.coffee.common.crud.BaseService;
 import com.coffee.common.crud.controller.BaseCrudController;
+import com.coffee.common.result.R;
 import io.swagger.annotations.Api;
+import io.swagger.v3.oas.annotations.Operation;
 import lombok.AllArgsConstructor;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import lombok.Getter;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * @author lifang
@@ -24,6 +30,7 @@ import org.springframework.web.bind.annotation.RestController;
 public class BusHospitalController extends BaseCrudController<BusHospitalEntity, String> {
     private final LocalBusHospitalService hospitalService;
 
+    private final ScriptManager scriptManager;
 
     /**
      * 权限控制前缀
@@ -38,4 +45,23 @@ public class BusHospitalController extends BaseCrudController<BusHospitalEntity,
     public BaseService<? extends Mapper<BusHospitalEntity>, BusHospitalEntity, String> getService() {
         return hospitalService;
     }
+
+    @PostMapping("/{id}/script")
+    @Operation(summary = "保存医院信息解析脚本")
+    public R script(@PathVariable("id") String id,@RequestBody Script script){
+        BusHospitalEntity busHospitalEntity = new BusHospitalEntity();
+        busHospitalEntity.setId(id);
+        busHospitalEntity.setScript(script);
+        if (this.getService()
+                .updateById(busHospitalEntity)) {
+            scriptManager.resetScript(id,script.getId(),script.getContent());
+        }
+        return R.success();
+    }
+
+    @PostMapping("/{id}/debug")
+    @Operation(summary = "执行医院解析脚本")
+    public R debug(@PathVariable("id") String id,@RequestBody String param){
+        return R.success( scriptManager.lookUpHospital(id).exec(param));
+    }
 }

+ 6 - 4
coffee-system/src/main/java/com/coffee/bus/entity/BusHospitalEntity.java

@@ -1,10 +1,8 @@
 package com.coffee.bus.entity;
 
-import com.baomidou.mybatisplus.annotation.FieldFill;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.*;
 import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
+import com.coffee.bus.bean.Script;
 import com.coffee.common.config.mybatis.DateToBigIntHandler;
 import com.coffee.common.entity.GenericEntity;
 import com.coffee.common.entity.RecordCreationEntity;
@@ -92,6 +90,10 @@ public class BusHospitalEntity extends GenericEntity<String> implements RecordMo
     @TableField(fill = FieldFill.INSERT)
     @TableLogic(value = "0",delval = "1")
     private Integer isDelete;
+
+    @TableField(typeHandler = FastjsonTypeHandler.class)
+    private Script script;
+
     @Data
     public static class  GeoPoint{
         private String lon;

+ 38 - 0
coffee-system/src/main/java/com/coffee/bus/script/DefaultParse.java

@@ -0,0 +1,38 @@
+package com.coffee.bus.script;
+
+import cn.hutool.json.JSON;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName DefaultParse.java
+ * @Description TODO
+ * @createTime 2022年03月28日 14:27:00
+ */
+public class DefaultParse implements ScriptParse{
+    @Override
+    public String getId() {
+        return "default";
+    }
+
+    @Override
+    public String functionName() {
+        return null;
+    }
+
+    @Override
+    public void reset(String script) {
+
+    }
+
+    @Override
+    public ExecuteResult exec(String params) {
+        ExecuteResult executeResult = new ExecuteResult();
+        executeResult.setSuccess(true);
+        executeResult.setUseTime(0);
+        executeResult.setResult(JSONUtil.parse(params));
+        return executeResult;
+    }
+}

+ 106 - 0
coffee-system/src/main/java/com/coffee/bus/script/ExecuteResult.java

@@ -0,0 +1,106 @@
+package com.coffee.bus.script;
+
+import cn.hutool.json.JSON;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import javax.script.ScriptException;
+import java.util.function.Supplier;
+@Data
+public class ExecuteResult {
+    private boolean success;
+
+    private JSON result;
+
+    private String message;
+
+    private transient Exception exception;
+
+    private long useTime;
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public void setSuccess(boolean success) {
+        this.success = success;
+    }
+
+    /**
+     * use {@link this#get()} or {@link this#getIfSuccess()}
+     *
+     * @return
+     */
+    @Deprecated
+    public JSON getResult() {
+        return result;
+    }
+
+    public void setResult(JSON result) {
+        this.result = result;
+    }
+
+    public String getMessage() {
+        if (message == null && exception != null) {
+            message = exception.getMessage();
+        }
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public Exception getException() {
+        return exception;
+    }
+
+    public void setException(Exception exception) {
+        this.exception = exception;
+    }
+
+    public long getUseTime() {
+        return useTime;
+    }
+
+    public void setUseTime(long useTime) {
+        this.useTime = useTime;
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(this.getResult());
+    }
+
+    public Object get() {
+        return result;
+    }
+
+    @JsonIgnore
+    public JSON getIfSuccess() throws Exception {
+        if (!success) {
+            if (exception != null) {
+                throw exception;
+            } else{
+                throw new ScriptException(message);
+            }
+        }
+        return result;
+    }
+
+    public JSON getIfSuccess(JSON defaultValue) {
+        if (!success) {
+            return defaultValue;
+        }
+        return result;
+    }
+
+    public Object getIfSuccess(Supplier<?> supplier) {
+        if (!success) {
+            return supplier.get();
+        }
+        return result;
+    }
+
+}

+ 104 - 0
coffee-system/src/main/java/com/coffee/bus/script/PythonParse.java

@@ -0,0 +1,104 @@
+package com.coffee.bus.script;
+
+import cn.hutool.json.JSON;
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.python.core.*;
+import org.python.util.PythonInterpreter;
+import org.springframework.stereotype.Component;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Properties;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName PythonParse.java
+ * @Description TODO
+ * @createTime 2022年03月28日 10:58:00
+ */
+@Slf4j
+@Component
+public class PythonParse implements ScriptParse {
+    private final PythonInterpreter interpreter;
+    private PyFunction pyFunction;
+
+    public PythonParse() {
+        Properties p = new Properties();
+        p.setProperty("python.console.encoding", "UTF-8");
+        PySystemState systemState = Py.getSystemState();
+        PyString xml = new PyString(Thread.currentThread().getContextClassLoader().getResource("python").getPath());
+        systemState.path.append(xml);
+        PythonInterpreter.initialize(System.getProperties(), p, new String[] {});
+        interpreter = new PythonInterpreter();
+    }
+
+    @Override
+    public String getId() {
+        return "python";
+    }
+
+
+    @Override
+    public String functionName() {
+        return "parse";
+    }
+
+    @Override
+    public void reset(String script){
+        interpreter.exec(Py.newStringUTF8(script));
+        pyFunction = interpreter.get(functionName(), PyFunction.class);
+    }
+
+    @Override
+    public ExecuteResult exec(String param) {
+        ExecuteResult executeResult = new ExecuteResult();
+        try {
+            long start = System.currentTimeMillis();
+            PyObject result = pyFunction.__call__(Py.newStringUTF8(param));
+            long end = System.currentTimeMillis();
+            executeResult.setSuccess(true);
+            executeResult.setUseTime(end-start);
+            executeResult.setResult( JSONUtil.parse(result));
+        }catch (Exception e){
+            executeResult.setSuccess(false);
+            executeResult.setException(e);
+        }
+        return executeResult;
+
+    }
+
+    public static void main(String[] args) throws UnsupportedEncodingException {
+        Properties p = new Properties();
+        p.setProperty("python.console.encoding", "UTF-8");
+        PySystemState systemState = Py.getSystemState();
+        PyString xml = new PyString("E:\\software\\coffee-boot\\coffee-admin\\src\\main\\resources\\python");
+//        PyString xml = new PyString("_classpath_:python");
+        systemState.path.append(xml);
+
+        PythonInterpreter.initialize(System.getProperties(), p, new String[] {});
+        PythonInterpreter pythonInterpreter = new PythonInterpreter();
+        String str = "# -*- coding: utf-8 -*-\n" +
+                "import sys\n" +
+                "reload(sys)\n" +
+                "sys.setdefaultencoding('utf-8')\n" +
+                "import json\n" +
+                "import xmltodict as xmltodict\n" +
+                "\n" +
+                "\n" +
+                "def parse(str):\n" +
+                "\n" +
+                "    json_result={}\n" +
+                "    json_obj=  json.loads(str)\n" +
+                "\n" +
+                "    json_result['1']=json_obj['name']\n" +
+                "    return json_result";
+        pythonInterpreter.exec(Py.newStringUTF8(str));
+        PyFunction parse = pythonInterpreter.get("parse", PyFunction.class);
+        PyObject pyObject = parse.__call__(Py.newStringUTF8( "{\"name\": \"小明\", \"age\": \"12\"}"));
+        JSON parse1 = JSONUtil.parse(pyObject);
+        pythonInterpreter.close();
+        System.out.println(pyObject);
+        System.out.println(parse1);
+    }
+}

+ 85 - 0
coffee-system/src/main/java/com/coffee/bus/script/ScriptManager.java

@@ -0,0 +1,85 @@
+package com.coffee.bus.script;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.coffee.bus.bean.Script;
+import com.coffee.bus.entity.BusHospitalEntity;
+import com.coffee.bus.service.LocalBusHospitalService;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ScriptManager.java
+ * @Description 医院脚本解析管理器
+ * @createTime 2022年03月27日 17:23:00
+ */
+@Component
+public class ScriptManager implements CommandLineRunner {
+
+    @Resource
+    private List<ScriptParse> scriptParseList;
+
+    private Map<String,ScriptParse> scriptParseMap=new HashMap<>();
+
+    private Map<String,ScriptParse> hospital=new HashMap<>();
+
+    @Resource
+    private LocalBusHospitalService hospitalService;
+    @PostConstruct
+    public void init(){
+        if(CollectionUtil.isNotEmpty(scriptParseList)){
+            scriptParseList.stream()
+                    .collect(Collectors.groupingBy(ScriptParse::getId))
+                    .forEach((k,v)-> scriptParseMap.put(k,v.get(0)));
+        }
+    }
+
+
+    public ScriptParse getById(String id){
+        ScriptParse scriptParse = scriptParseMap.get(id);
+        if(scriptParse==null){
+            throw new RuntimeException("不支持解析[{"+id+"}]脚本");
+        }
+        return scriptParse;
+    }
+
+    public ScriptParse lookUpHospital(String id){
+        return Optional.ofNullable(hospital.get(id)).orElse(new DefaultParse());
+    }
+
+    public void resetScript(String hospitalId,String scriptId,String script){
+        ScriptParse scriptParse = Optional.ofNullable(hospital.get(hospitalId)).orElse(createScript(scriptId));
+        scriptParse.reset(script);
+        hospital.put(hospitalId,scriptParse);
+    }
+
+    public ScriptParse createScript(String scriptId ){
+        return new PythonParse();
+    }
+
+    @Override
+    public void run(String... args) {
+        List<BusHospitalEntity> hospitals = hospitalService.list(new QueryWrapper<BusHospitalEntity>().lambda().isNotNull(BusHospitalEntity::getScript));
+        //将脚本初始化
+        if(CollectionUtil.isNotEmpty(hospitals)){
+            hospitals.stream().filter(hospitalEntity -> ObjectUtil.isNotNull(hospitalEntity.getScript())).forEach(hospital -> {
+
+                Script script = hospital.getScript();
+                resetScript(hospital.getId(),script.getId(),script.getContent());
+            });
+        }
+    }
+}

+ 38 - 0
coffee-system/src/main/java/com/coffee/bus/script/ScriptParse.java

@@ -0,0 +1,38 @@
+package com.coffee.bus.script;
+
+import cn.hutool.json.JSON;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ScriptParse.java
+ * @Description TODO
+ * @createTime 2022年03月28日 10:55:00
+ */
+public interface ScriptParse {
+    /**
+     * 获取脚本id
+     * @return
+     */
+    String getId();
+
+
+    /**
+     * 获取脚本解析函数名
+     * @return
+     */
+    String functionName();
+
+    /**
+     * 重置脚本内容
+     * @param script 脚本内容
+     */
+    void reset(String script);
+
+    /**
+     * 执行脚本
+     * @param params 脚本参数
+     * @return
+     */
+    ExecuteResult exec(String params);
+}

+ 7 - 0
pom.xml

@@ -35,6 +35,7 @@
         <satoken.version>1.29.0</satoken.version>
         <springfox.version>3.0.0</springfox.version>
         <tio.version>3.6.0.v20200315-RELEASE</tio.version>
+        <jython.version>2.7.1</jython.version>
     </properties>
 
     <modules>
@@ -63,6 +64,12 @@
     <!-- 依赖管理 -->
     <dependencyManagement>
         <dependencies>
+            <!-- https://mvnrepository.com/artifact/org.python/jython-standalone -->
+            <dependency>
+                <groupId>org.python</groupId>
+                <artifactId>jython-standalone</artifactId>
+                <version>${jython.version}</version>
+            </dependency>
             <!-- spring boot -->
             <dependency>
                 <groupId>org.springframework.boot</groupId>