· 10 years ago · May 10, 2016, 01:00 PM
1diff --git a/app.yaml b/app.yaml
2index 2c26c84..617e4d4 100644
3--- a/app.yaml
4+++ b/app.yaml
5@@ -31,13 +31,16 @@ services:
6 # 1. In yoda/settings.py, `ASYNC_ENABLED` is True
7 # 2. Config items in yoda/celeryconfig.py configured
8 #
9-# Celery related config items:
10-# >> celery_settings: yoda.celeryconfig
11-# >> async_workers:
12-# >> - name: clair.yoda
13-# >> size: 1
14-# >> type: celery
15-#
16+# Celery related config items:
17+celery_settings: yoda.celeryconfig
18+async_workers:
19+ - name: clair.yoda
20+ size: 8
21+ type: celery
22+ queues:
23+ - clair.yoda.celery.carrier.tag.queue
24+ - clair.yoda.celery.merchant.tag.queue
25+
26 #
27 # If mysql worker is your choice, uncomment related config items
28 # and make sure:
29@@ -65,15 +68,6 @@ services:
30 # one broker.
31 #
32 message_consumer:
33- #- broker_urls: yoda.settings:RMQ_BROKER_URLS
34- #process_num: 3
35- #handler_type: 1
36- #queues:
37- #- queue_name: clair.yoda.order.queue
38- #handler: yoda.message.handlers:assign_order
39- #auto_ack: true
40- #on_error: yoda.message.handlers:assign_order_error
41-
42 - broker_urls: yoda.settings:RMQ_BROKER_URLS
43 process_num: 3
44 handler_type: 1
45diff --git a/database/schema.sql b/database/schema.sql
46index 50d306f..826b473 100644
47--- a/database/schema.sql
48+++ b/database/schema.sql
49@@ -10,6 +10,7 @@ create table tb_carrier (
50 PRIMARY KEY(id)
51 );
52
53+
54 create table tb_direct_strategy (
55 id serial NOT NULL,
56 title varchar(255) NOT NULL,
57@@ -21,6 +22,13 @@ create table tb_direct_strategy (
58 PRIMARY KEY(id)
59 );
60 CREATE UNIQUE INDEX uk_tb_direct_strategy_platform_merchant_id on tb_direct_strategy (platform_id, merchant_id);
61+COMMENT on table tb_direct_strategy is '定å‘ç–略表';
62+COMMENT on COLUMN tb_direct_strategy.id is '主键id';
63+COMMENT on COLUMN tb_direct_strategy.title is '定å‘åç§°';
64+COMMENT on COLUMN tb_direct_strategy.platform_id is 'å¹³å°id';
65+COMMENT on COLUMN tb_direct_strategy.merchant_id is '商检id';
66+COMMENT on COLUMN tb_direct_strategy.rule is '定å‘ç–ç•¥';
67+
68
69 create table tb_grid_strategy (
70 id serial NOT NULL,
71@@ -35,6 +43,14 @@ create table tb_grid_strategy (
72 CREATE INDEX gix_tb_grid_strategy_range ON tb_grid_strategy USING GIST (range);
73 CREATE UNIQUE INDEX uk_tb_grid_strategy_grid_id on tb_grid_strategy (grid_id);
74
75+COMMENT ON TABLE tb_grid_strategy is '网络ç–略表';
76+COMMENT ON COLUMN tb_grid_strategy.id is '主键id';
77+COMMENT ON COLUMN tb_grid_strategy.grid_id is 'ç½‘æ ¼id';
78+COMMENT on COLUMN tb_grid_strategy.title is 'ç½‘æ ¼åç§°';
79+COMMENT on COLUMN tb_grid_strategy.range is 'ç½‘æ ¼èŒƒå›´';
80+COMMENT on COLUMN tb_grid_strategy.rule is 'ç½‘æ ¼ç–ç•¥';
81+
82+
83 create table tb_plan (
84 id bigserial NOT NULL,
85 order_id varchar(128) NOT NULL,
86@@ -51,4 +67,72 @@ create table tb_plan (
87
88 COMMENT ON TABLE tb_plan is '订å•分æµplan表';
89 COMMENT ON COLUMN tb_plan.id is '主键id';
90+COMMENT ON COLUMN tb_plan.order_id is 'è¿å•id';
91 COMMENT ON COLUMN tb_plan.state is 'plan的当å‰çжæ€;0: æ— æ•ˆçŠ¶æ€; 1:已推å•; 2:æŽ¨å•æˆåŠŸ; 3:æ‹’å•失败; 4: 超时失败';
92+COMMENT ON COLUMN tb_plan.remark is '备注';
93+COMMENT ON COLUMN tb_plan.current_carrier is '当å‰é…é€å•†';
94+COMMENT ON COLUMN tb_plan.strategy_index is '当å‰ç–ç•¥æ¸¸æ ‡';
95+COMMENT ON COLUMN tb_plan.logs is 'planæ‰§è¡Œæµæ°´æ—¥å¿—';
96+COMMENT ON COLUMN tb_plan.created_at is '创建时间';
97+COMMENT ON COLUMN tb_plan.updated_at is 'æ›´æ–°æ—¶é—´';
98+
99+
100+alter table tb_grid_strategy rename column rule to rules;
101+alter table tb_grid_strategy add column city_id character varying(255) not null;
102+alter table tb_grid_strategy add column owner character varying(255) not null;
103+drop index uk_tb_grid_strategy_grid_id;
104+create unique index uk_tb_grid_stategy_owner_grid_id on tb_grid_strategy (owner , grid_id);
105+
106+COMMENT ON COLUMN tb_grid_strategy.city_id is '城市id';
107+COMMENT ON COLUMN tb_grid_strategy.owner is '拥有者';
108+
109+
110+create table tb_merchant_tag (
111+ id bigserial NOT NULL,
112+ grid_id varchar(256) NOT NULL,
113+ merchant_id varchar(256) NOT NULL,
114+ tags text NOT NULL,
115+ PRIMARY KEY(id)
116+);
117+create index idx_tb_merchant_tag_grid_id on tb_merchant_tag (grid_id);
118+create unique index uk_tb_merchant_tag_grid_mechant on tb_merchant_tag (grid_id, merchant_id);
119+
120+COMMENT ON TABLE tb_merchant_tag is '商家画åƒè¡¨';
121+COMMENT ON COLUMN tb_merchant_tag.grid_id is 'ç½‘æ ¼id';
122+COMMENT ON COLUMN tb_merchant_tag.id is '主键id';
123+COMMENT ON COLUMN tb_merchant_tag.merchant_id is '商家id';
124+COMMENT ON COLUMN tb_merchant_tag.tags is '商家画åƒé›†';
125+
126+create table tb_carrier_tag (
127+ id bigserial NOT NULL,
128+ grid_id varchar(256) NOT NULL,
129+ carrier_id varchar(256) NOT NULL,
130+ tags text NOT NULL,
131+ PRIMARY KEY(id)
132+);
133+
134+create index idx_tb_carrier_tag_grid on tb_carrier_tag(grid);
135+create unique index uk_tb_carrier_tag_grid_carrier on tb_carrier_tag (grid_id, carrier_id);
136+
137+COMMENT ON TABLE tb_carrier_tag is 'é…é€å•†ç”»åƒè¡¨';
138+COMMENT ON COLUMN tb_carrier_tag.grid_id is 'ç½‘æ ¼id';
139+COMMENT ON COLUMN tb_carrier_tag.id is '主键id';
140+COMMENT ON COLUMN tb_carrier_tag.carrier_id is 'é…é€å•†id';
141+COMMENT ON COLUMN tb_carrier_tag.tags is 'é…é€å•†ç”»åƒé›†';
142+
143+
144+CREATE table tb_timer_task_marker (
145+ id serial NOT NULL,
146+ task_id varchar(256) NOT NULL,
147+ status integer NOT NULL DEFAULT 0,
148+ created_at timestamp with time zone NOT NULL DEFAULT now(),
149+ PRIMARY KEY(id)
150+);
151+create index idx_tb_timer_task_marker_task on tb_timer_task_marker(task_id);
152+create index idx_tb_timer_task_marker_created_at on tb_timer_task_marker(created_at);
153+
154+COMMENT ON TABLE tb_timer_task_marker is '定时任务执行情况登记表';
155+COMMENT ON COLUMN tb_timer_task_marker.id is '主键id';
156+COMMENT ON COLUMN tb_timer_task_marker.task_id is '定时任务åç§°';
157+COMMENT ON COLUMN tb_timer_task_marker.status is '定时任务结果1:执行æˆåŠŸ, 2: 执行失败';
158+COMMENT ON COLUMN tb_timer_task_marker.created_at is '任务创建时间';
159diff --git a/requirements.txt b/requirements.txt
160index f1bce0d..c401b2e 100644
161--- a/requirements.txt
162+++ b/requirements.txt
163@@ -1,2 +1,2 @@
164-zeus_core==0.13.1
165+zeus_core==0.13.3.3
166 GeoAlchemy2==0.2.6
167diff --git a/test.py b/test.py
168new file mode 100644
169index 0000000..953749a
170--- /dev/null
171+++ b/test.py
172@@ -0,0 +1,20 @@
173+from yoda import thrift_file
174+from yoda.dispatcher import Dispatcher
175+
176+locs = []
177+locs.append(thrift_file.Location('1', '2'))
178+locs.append(thrift_file.Location('2', '2'))
179+locs.append(thrift_file.Location('1', '1'))
180+locs.append(thrift_file.Location('1', '2'))
181+grid = thrift_file.Grid('b', '1', 'first grid', locs, '1')
182+
183+strategies = []
184+strategies.append(thrift_file.Strategy('price', '-1'))
185+strategies.append(thrift_file.Strategy('price', '-2'))
186+strategies.append(thrift_file.Strategy('cc', '-2'))
187+grid_strategy = thrift_file.GridStrategy('a', '1', strategies)
188+
189+d = Dispatcher()
190+# d.create_grid(grid)
191+# d.create_grid_strategy(grid_strategy)
192+print(d.query_grid_strategies('c', '1'))
193diff --git a/yoda/celeryconfig.py b/yoda/celeryconfig.py
194index cf4b650..d3b45ba 100644
195--- a/yoda/celeryconfig.py
196+++ b/yoda/celeryconfig.py
197@@ -1,15 +1,21 @@
198 # -*- coding: utf-8 -*-
199-# from datetime import timedelta
200-#
201-# BROKER_URL = "amqp://clair:clair@192.168.80.117/clair"
202-# CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
203-# CELERY_DEFAULT_EXCHANGE = 'zeus'
204-#
205-#
206-# CELERYBEAT_SCHEDULE = {
207-# 'test_schedule_task': {
208-# 'task': 'zeus_core.async.async_api',
209-# 'schedule': timedelta(seconds=1),
210-# 'args': ('clair.yoda', 'ping')
211-# }
212-# }
213+from datetime import timedelta
214+
215+from yoda.settings import AMQP_DNS, CACHE_DSN
216+BROKER_URL = AMQP_DNS
217+CELERY_RESULT_BACKEND = CACHE_DSN
218+CELERY_DEFAULT_EXCHANGE = 'zeus'
219+
220+CELERYBEAT_SCHEDULE = {
221+ 'sync_carrier_tags_task': {
222+ 'task': 'zeus_core.async.async_api',
223+ 'schedule': timedelta(minutes=61),
224+ 'args': ('clair.yoda', 'sync_carrier_tags_task')
225+ },
226+ 'sync_merchant_tags_task': {
227+ 'task': 'zeus_core.async.async_api',
228+ 'schedule': timedelta(minutes=61),
229+ 'args': ('clair.yoda', 'sync_merchant_tags_task')
230+ }
231+
232+}
233diff --git a/yoda/dispatcher.py b/yoda/dispatcher.py
234index 1b37841..4514f60 100644
235--- a/yoda/dispatcher.py
236+++ b/yoda/dispatcher.py
237@@ -2,7 +2,12 @@
238
239 import logging
240 from .service import service
241-from . import strategy, thrift_file
242+from . import (
243+ strategy,
244+ thrift_file,
245+ tasks
246+)
247+
248 from .util import geo_util
249 logger = logging.getLogger(__name__)
250
251@@ -23,15 +28,30 @@ class Dispatcher(object):
252 def create_direct_strategy(self, direct_strategy):
253 strategy.create_direct_strategy(direct_strategy)
254
255- def create_grid_strategy(self, grid_strategy):
256+ def create_grid(self, grid):
257 points = []
258- for point in grid_strategy.polygon:
259+ for point in grid.polygon:
260 points.append(location_to_wgs(point))
261- grid_strategy.polygon = points
262- strategy.create_grid_strategy(grid_strategy)
263+ grid.polygon = points
264+ strategy.create_grid(grid)
265+
266+ def add_grid_strategies(self, grid_strategy):
267+ strategy.add_grid_strategies(grid_strategy)
268+
269+ def remove_grid_strategies(self, grid_strategy):
270+ strategy.remove_grid_strategies(grid_strategy)
271+
272+ def query_grid_strategies(self, owner, grid_id):
273+ return strategy.query_grid_strategies(owner, grid_id)
274
275 def make_plan(self, order):
276 order.merchant_location = location_to_wgs(order.merchant_location)
277 order.customer_location = location_to_wgs(order.customer_location)
278 rules = strategy.make_plan(order)
279 return service.thrift.Plan(rules)
280+
281+ def sync_carrier_tags(self):
282+ tasks.sync_carrier_tags()
283+
284+ def sync_merchant_tags(self):
285+ tasks.sync_merchant_tags()
286diff --git a/yoda/exc.py b/yoda/exc.py
287index 730c9d4..1214e5e 100644
288--- a/yoda/exc.py
289+++ b/yoda/exc.py
290@@ -10,6 +10,9 @@ TRANSLATIONS = {
291 YodaErrorCode.UNKNOWN_ERROR: u'系统异常,请ç¨åŽå†è¯•',
292 YodaErrorCode.DATABASE_ERROR: u'æ•°æ®åº“错误',
293 YodaErrorCode.TOO_BUSY_ERROR: u'系统ç¹å¿™ï¼Œè¯·ç¨åŽå†è¯•',
294+
295+ YodaErrorCode.INVALID_RULE_ERROR: u'éžæ³•规则',
296+ YodaErrorCode.GRID_NOT_EXIST_ERROR: u'ç½‘æ ¼ä¸å˜åœ¨',
297 }
298
299
300diff --git a/yoda/executor/plan.py b/yoda/executor/plan.py
301index d3f7f07..057817f 100644
302--- a/yoda/executor/plan.py
303+++ b/yoda/executor/plan.py
304@@ -23,6 +23,7 @@ from yoda.exc import YodaErrorCode, raise_system_exc
305 from zeus_core.cache import Cache, cache_mixin, Lock
306 from yoda import settings
307
308+
309 DBSession = db_manager.get_session('yoda')
310 cache_manager = Cache('yoda', settings.CACHE_NAMESPACE)
311 region = cache_manager.make_region()
312@@ -35,6 +36,7 @@ auto_commit = gen_commit_deco(DBSession, raise_system_exc,
313 YodaErrorCode.DATABASE_ERROR)
314
315
316+# TODO add a json-serializable method
317 class PlanLog(object):
318 def __init__(self, operation, carrier, state):
319 self.carrier = None
320@@ -44,7 +46,7 @@ class PlanLog(object):
321
322
323 class Plan(DeclarativeBase, CacheMixin):
324- __tablename__ = 'tb_grid_strategy'
325+ __tablename__ = 'tb_plan'
326 ENABLE_NEW_CACHE = True
327
328 StateGeneratePlan = 0
329@@ -63,20 +65,20 @@ class Plan(DeclarativeBase, CacheMixin):
330 logs = Column(JSON, default=[])
331 remark = Column(String, default='')
332 current_carrier = Column(String, default='')
333- expire_at = Column(DateTime, default=datetime.datetime.now)
334 created_at = Column(DateTime, default=datetime.datetime.now)
335 updated_at = Column(DateTime, default=datetime.datetime.now)
336
337 def __init__(self, order, strategy):
338- self.order_id = order.id
339+ self.order_id = order.tracking_id
340 self.strategy = strategy
341+ self.logs = []
342 self.current_carrier = "ghost carrier"
343- self.add_log("new Plan")
344+ # self.add_log("new Plan")
345
346 @classmethod
347 @auto_commit
348 def create(cls, order, strategy):
349- plan = cls(order, strategy)
350+ plan = cls(order, strategy or [])
351 DBSession().add(plan)
352 return plan
353
354diff --git a/yoda/grid/__init__.py b/yoda/grid/__init__.py
355new file mode 100644
356index 0000000..e69de29
357diff --git a/yoda/grid/grid.py b/yoda/grid/grid.py
358new file mode 100644
359index 0000000..0726c8e
360--- /dev/null
361+++ b/yoda/grid/grid.py
362@@ -0,0 +1,216 @@
363+#!/usr/bin/env python
364+# -*- coding: utf-8 -*-
365+###############################################################################
366+#
367+# Copyright (c) Yabin Ping
368+# Mail(Forever) yabping@gmail.com
369+#
370+# Created Time: 2016-04-01 13:32:40
371+###############################################################################
372+
373+
374+import json
375+import random
376+import logging
377+from sqlalchemy import (
378+ Column,
379+ DateTime,
380+ Integer,
381+ String,
382+ func,
383+)
384+from sqlalchemy.dialects.postgresql import JSON
385+from geoalchemy2 import Geometry
386+from zeus_core.db import db_manager, gen_commit_deco, model_base
387+from yoda.exc import YodaErrorCode, raise_system_exc, raise_user_exc
388+from zeus_core.cache import Cache, cache_mixin
389+from zeus_core.decorators import gen_no_lock_cache_deco
390+from yoda import settings
391+from yoda import thrift_file
392+
393+DBSession = db_manager.get_session('yoda')
394+cache_manager = Cache('yoda', settings.CACHE_NAMESPACE)
395+region = cache_manager.make_region()
396+table_region = cache_manager.make_client()
397+no_lock_cache = gen_no_lock_cache_deco(region.client)
398+
399+DeclarativeBase = model_base()
400+CacheMixin = cache_mixin(table_region, DBSession, pub=False)
401+
402+auto_commit = gen_commit_deco(DBSession, raise_system_exc,
403+ YodaErrorCode.DATABASE_ERROR)
404+
405+logger = logging.getLogger(__name__)
406+
407+
408+def point_to_wtx(point):
409+ return 'SRID=4326;POINT({} {})'.\
410+ format(point.longitude, point.latitude)
411+
412+
413+def polygon_to_wtx(points):
414+ wtx = ','.join('{} {}'.format(point.longitude, point.latitude)
415+ for point in points)
416+ return 'SRID=4326;POLYGON(({}))'.format(wtx)
417+
418+
419+def translate_rules(config):
420+ start, rules, total = 0, {}, 0
421+ for carrier, val in config.iteritems():
422+ if val <= 0:
423+ raise_user_exc(YodaErrorCode.INVALID_RULE_ERROR)
424+ rules[carrier] = (start, start + val)
425+ start += val
426+ total += val
427+ if total != 100:
428+ raise_user_exc(YodaErrorCode.INVALID_RULE_ERROR)
429+ return rules
430+
431+
432+def strategy_serialize(strategies):
433+ rules = {}
434+ for strategy in strategies:
435+ rules[strategy.key] = strategy.val
436+ return rules
437+
438+
439+def strategy_deserialize(rules):
440+ strategies = []
441+ rules = json.loads(rules)
442+ for key, val in rules.iteritems():
443+ strategies.append(thrift_file.Strategy(key, val))
444+ return strategies
445+
446+
447+def flush_cache(func):
448+ def wrapper(cls, strategy):
449+ func(cls, strategy)
450+ cls.query_global_rule.flush()
451+ cls.query_grid_strategies.flush(strategy.owner, strategy.grid_id)
452+ return wrapper
453+
454+
455+class GridStrategy(DeclarativeBase):
456+ __tablename__ = 'tb_grid_strategy'
457+ GLOBAL_GRID_ID = 'global_grid_id'
458+ id = Column(Integer, primary_key=True)
459+ grid_id = Column(String, default='')
460+ owner = Column(String, default='')
461+ title = Column(String, default='')
462+ range = Column(Geometry('POLYGON', srid=4326))
463+ rules = Column(JSON, default={})
464+ city_id = Column(String, default='')
465+ created_at = Column(DateTime, default=func.now())
466+ updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
467+
468+ @classmethod
469+ @flush_cache
470+ @auto_commit
471+ def add_grid_strategies(cls, strategy):
472+ '''如果å˜åœ¨åˆ™æ›´æ–°è®°å½•,å¦åˆ™æŠ¥é”™ '''
473+ s = DBSession()
474+ grid = (s.query(cls).filter(cls.owner == strategy.owner)
475+ .filter(cls.grid_id == strategy.grid_id).first())
476+ if grid is None:
477+ raise_user_exc(YodaErrorCode.GRID_NOT_EXIST_ERROR)
478+
479+ rules = json.loads(grid.rules)
480+ # add new rule if not exists, otherwise update it
481+ new_rules = strategy_serialize(strategy.strategies)
482+ for key, val in new_rules.iteritems():
483+ rules[key] = val
484+ grid.rules = rules
485+ s.add(grid)
486+
487+ @classmethod
488+ @flush_cache
489+ @auto_commit
490+ def remove_grid_strategies(cls, strategy):
491+ '''如果å˜åœ¨åˆ™æ›´æ–°è®°å½•,å¦åˆ™æŠ¥é”™ '''
492+ s = DBSession()
493+ grid = (s.query(cls).filter(cls.owner == strategy.owner)
494+ .filter(cls.grid_id == strategy.grid_id).first())
495+ if grid is None:
496+ raise_user_exc(YodaErrorCode.GRID_NOT_EXIST_ERROR)
497+
498+ rules = json.loads(grid.rules)
499+ # remove the rule if exists, otherwise do nothing
500+ to_remove_rules = strategy_serialize(strategy.strategies)
501+ for key, val in to_remove_rules.iteritems():
502+ rules.pop(key, None)
503+ grid.rules = rules
504+ s.add(grid)
505+
506+ @classmethod
507+ @flush_cache
508+ @auto_commit
509+ def create_grid(cls, grid):
510+ '''如果å˜åœ¨åˆ™æ›´æ–°è®°å½•,å¦åˆ™æ–°å»ºä¸€ä¸ªå¯¹è±¡ '''
511+ s = DBSession()
512+ _grid = (s.query(cls).filter(cls.owner == grid.owner)
513+ .filter(cls.grid_id == grid.grid_id).first())
514+ if _grid is None:
515+ _grid = cls()
516+
517+ _grid.owner = grid.owner
518+ _grid.grid_id = grid.grid_id
519+ _grid.title = grid.title or "default title"
520+ _grid.range = polygon_to_wtx(grid.polygon)
521+ _grid.city_id = grid.city_id
522+ _grid.rules = {}
523+ s.add(_grid)
524+
525+ @classmethod
526+ @no_lock_cache(5 * 60 * 60)
527+ def query_grid_strategies(cls, owner, grid_id):
528+ '''如果å˜åœ¨åˆ™è¿”回查询结果,å¦åˆ™æŠ¥é”™ '''
529+ s = DBSession()
530+ grid = (s.query(cls).filter(cls.owner == owner)
531+ .filter(cls.grid_id == grid_id).first())
532+ if grid is None:
533+ raise_user_exc(YodaErrorCode.GRID_NOT_EXIST_ERROR)
534+
535+ return strategy_deserialize(grid.rules)
536+
537+ @classmethod
538+ @no_lock_cache(5 * 60 * 60)
539+ def query_global_rule(cls):
540+ record = DBSession().query(cls).\
541+ filter(cls.grid_id == cls.GLOBAL_GRID_ID).first()
542+ return None if record is None else record.get_rules()
543+
544+ @classmethod
545+ @no_lock_cache(5 * 60)
546+ def query_rule_by_point(cls, point):
547+ s = DBSession()
548+ point_wtx = point_to_wtx(point)
549+ record = s.query(cls).\
550+ filter(cls.grid_id != cls.GLOBAL_GRID_ID).\
551+ filter(func.ST_Contains(cls.range, point_wtx)).first()
552+ return None if record is None else record.get_rules()
553+
554+ @classmethod
555+ def strategy(cls, order):
556+ rules = cls.query_rule_by_point(order.merchant_location)
557+ # if grid rule not set, use global rule
558+ if rules is None:
559+ rules = cls.query_global_rule()
560+ # if grid rule not set, write error log
561+ if rules is None:
562+ logger.error('global grid rules not set')
563+ return None
564+ count, carrier_order, rules = 0, [], rules
565+ # 100 is a magic number; To make sure each number will be selected once
566+ while count < 1000:
567+ choice = random.randint(0, 100)
568+ count += 1
569+ for carrier, interval in rules.iteritems():
570+ if carrier not in carrier_order:
571+ if interval[0] <= choice and interval[1] > choice:
572+ carrier_order.append(carrier)
573+ return carrier_order
574+
575+ def get_rules(self):
576+ if isinstance(self.rules, list):
577+ return self.rules
578+ return json.loads(self.rules)
579diff --git a/yoda/message/handlers.py b/yoda/message/handlers.py
580index 0b20573..12649d2 100644
581--- a/yoda/message/handlers.py
582+++ b/yoda/message/handlers.py
583@@ -8,7 +8,10 @@
584 # Created Time: 2016-04-04 13:19:50
585 ###############################################################################
586 import logging
587+from yoda import thrift_file
588 from yoda.rpc import client_manager
589+from yoda.executor.plan import Plan
590+from yoda import strategy
591
592 logger = logging.getLogger(__name__)
593
594@@ -17,21 +20,27 @@ def assign_order(payload, msg_meta):
595 logger.info(payload)
596 payload = payload['payload']
597 tracking_id = payload['message']['tracking_id']
598- # platform_id = payload['message']['platform_id']
599- # merchant_id = payload['message']['platform_merchant_id']
600+ platform_id = payload['message']['platform_id']
601+ merchant_id = payload['message']['platform_merchant_id']
602
603 client = client_manager['apollo.mgtservice']
604- shipping_order_primary_info = client.getShippingOrderPrimaryInfo([tracking_id]) # noqa
605- logger.info(shipping_order_primary_info)
606- # merchant_location, customer_location = .
607- # order = thrift_file.Order(
608- # tracking_id=tracking_id,
609- # platform_id=platform_id,
610- # merchant_id=merchant_id,
611- # merchant_location=,
612- # customer_location=,
613- # )
614- # return order
615+ shipping_order_primary_info = (
616+ client.getShippingOrderPrimaryInfo([tracking_id])
617+ )
618+ merchant_location, customer_location = (
619+ shipping_order_primary_info[0].consumerLocation,
620+ shipping_order_primary_info[0].merchantLocation
621+ )
622+ order = thrift_file.Order(
623+ tracking_id=tracking_id,
624+ platform_id=str(platform_id),
625+ merchant_id=str(merchant_id),
626+ merchant_location=merchant_location,
627+ customer_location=customer_location,
628+ )
629+
630+ rule = strategy.make_plan(order)
631+ Plan.create(order, rule)
632
633
634 def assign_order_error(payload, msg_meta, exc):
635diff --git a/yoda/message/producer.py b/yoda/message/producer.py
636new file mode 100644
637index 0000000..39a20d6
638--- /dev/null
639+++ b/yoda/message/producer.py
640@@ -0,0 +1,52 @@
641+import uuid
642+import time
643+from zeus_core.message import TypedMessageSender
644+from yoda.settings import AMQP_SETTINGS
645+
646+
647+class Producer(TypedMessageSender):
648+ ''' order producer for test '''
649+
650+ def _send_message(self, key, message):
651+ payload = {
652+ 'id': str(uuid.uuid4()),
653+ 'utc': int(time.time() * 1000),
654+ 'app_id': AMQP_SETTINGS["appid"],
655+ 'message': message,
656+ }
657+ self.send(payload, key, type=message['type'])
658+
659+ def send_hello(self):
660+ hello_key = self.keys["YODA_MESSAGE_KEY"]
661+ message = {
662+ "type": "helloworld",
663+ "content": "hello world"
664+ }
665+ self._send_message(hello_key, message)
666+
667+ def send_order(self):
668+ key = self.keys["YODA_MESSAGE_KEY"]
669+ message = {
670+ 'tracking_id': 0,
671+ 'platform_id': 0,
672+ 'platform_tracking_id': 'a',
673+ 'platform_merchant_id': 'a',
674+ 'assign_type': 0,
675+ 'pre_carrier_id': 0,
676+ }
677+ payload = {
678+ 'utc': int(time.time() * 1000),
679+ 'message': message,
680+ 'id': str(uuid.uuid4()),
681+ 'app_id': AMQP_SETTINGS['appid'],
682+ }
683+ order = {
684+ 'type': 0,
685+ 'payload': payload,
686+ }
687+ self.send(order, key, type=order['type'])
688+
689+
690+producer = Producer(AMQP_SETTINGS["exchange"], AMQP_SETTINGS['dns'],
691+ AMQP_SETTINGS["exchange_type"],
692+ keys=AMQP_SETTINGS["routing_keys"])
693diff --git a/yoda/rpc/stub/order.thrift b/yoda/rpc/stub/order.thrift
694index 924f805..cb96b4e 100644
695--- a/yoda/rpc/stub/order.thrift
696+++ b/yoda/rpc/stub/order.thrift
697@@ -1,15 +1,15 @@
698 namespace java me.ele.managementService.api
699
700 struct TLocation {
701- 1: double longitude
702- 2: double latitude
703+ 1: double longitude
704+ 2: double latitude
705 }
706
707 struct TShippingOrderPrimaryInfo {
708- 1: i64 trackingId
709- 2: TLocation merchantLocation
710- 3: i32 shippingState
711- 4: TLocation consumerLocation
712+ 1: i64 trackingId
713+ 2: TLocation merchantLocation
714+ 3: i32 shippingState
715+ 4: TLocation consumerLocation
716 }
717
718 exception ServiceException {
719@@ -25,5 +25,5 @@ service ForDispatchService {
720 list<TShippingOrderPrimaryInfo> getShippingOrderPrimaryInfo(1: set<i64> trackingIds) throws (
721 1: ServiceException service_exception
722 )
723-
724+
725 }
726diff --git a/yoda/settings.py b/yoda/settings.py
727index 4a9598b..bdd30da 100644
728--- a/yoda/settings.py
729+++ b/yoda/settings.py
730@@ -12,6 +12,7 @@ from zeus_core.huskar import get_config_manager
731 # set_huskar_options(local_mode=False) # enable huskar in development
732
733 config_manager = get_config_manager()
734+AMQP_DNS = config_manager.get('RMQ_BROKER_URLS')
735
736
737 # ========== Cache Settings ==========
738@@ -40,27 +41,24 @@ DB_SETTINGS = {
739
740
741 # ========== RMQ Settings ==========
742-
743-AMQP_DNS = config_manager.get('RMQ_BROKER_URLS')
744 RMQ_BROKER_URLS = [AMQP_DNS]
745 MESSAGE_WORKER_RETRY_TIMES = 0
746 MESSAGE_WORKER_RETRY_INTERVALS = 1
747
748 # ========== Async Settings ==========
749 # Enable async feature by uncomment following line, see also: `app.yaml`
750-ASYNC_ENABLED = False
751+ASYNC_ENABLED = True
752
753 # ========== AMQP Settings ==========
754-
755-# AMQP_SETTINGS = {
756-# 'dns': AMQP_DNS,
757-# 'exchange': 'clair.yoda.exchange',
758-# 'exchange_type': 'topic',
759-# "appid": 'clair.yoda',
760-# 'routing_keys': {
761-# 'YODA_MESSAGE_KEY': 'yoda.message.hello',
762-# }
763-# }
764+AMQP_SETTINGS = {
765+ 'dns': AMQP_DNS,
766+ 'exchange': 'clair.yoda.exchange',
767+ 'exchange_type': 'topic',
768+ 'appid': 'clair.yoda',
769+ 'routing_keys': {
770+ 'YODA_MESSAGE_KEY': 'yoda.message.test.order',
771+ }
772+}
773
774 # ========== Python SOA Call Settings =======
775 CURRENT_PATH = os.path.dirname(__file__)
776diff --git a/yoda/strategy/__init__.py b/yoda/strategy/__init__.py
777index 1ab4a5d..800a3c7 100644
778--- a/yoda/strategy/__init__.py
779+++ b/yoda/strategy/__init__.py
780@@ -8,8 +8,8 @@
781 # Created Time: 2016-04-01 13:31:25
782 ###############################################################################
783
784-from direct import DirectStrategy
785-from grid import GridStrategy
786+from yoda.strategy.direct import DirectStrategy
787+from yoda.grid.grid import GridStrategy
788
789 active_strategy = [DirectStrategy, GridStrategy]
790
791@@ -18,8 +18,20 @@ def create_direct_strategy(strategy):
792 DirectStrategy.create(strategy)
793
794
795-def create_grid_strategy(strategy):
796- GridStrategy.create(strategy)
797+def add_grid_strategies(strategy):
798+ GridStrategy.add_grid_strategies(strategy)
799+
800+
801+def create_grid(grid):
802+ GridStrategy.create_grid(grid)
803+
804+
805+def remove_grid_strategies(strategy):
806+ GridStrategy.remove_grid_strategies(strategy)
807+
808+
809+def query_grid_strategies(owner, grid_id):
810+ return GridStrategy.query_grid_strategies(owner, grid_id)
811
812
813 def make_plan(order):
814diff --git a/yoda/strategy/direct.py b/yoda/strategy/direct.py
815index 0cd785c..b20c8a0 100644
816--- a/yoda/strategy/direct.py
817+++ b/yoda/strategy/direct.py
818@@ -49,22 +49,32 @@ class DirectStrategy(DeclarativeBase, CacheMixin):
819 updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
820
821 @classmethod
822- @auto_commit
823 def create(cls, strategy):
824- _strategy = cls.query_rule_by_platform_merchant_id(
825+ @auto_commit
826+ def _update(_strategy):
827+ _strategy.platform_id = strategy.platform_id
828+ _strategy.merchant_id = strategy.merchant_id
829+ _strategy.rule = strategy.rules
830+ _strategy.title = strategy.title or "defaut title"
831+ s = DBSession()
832+ s.add(_strategy)
833+
834+ _strategy = cls._query_rule_by_platform_merchant_id(
835 strategy.platform_id, strategy.merchant_id)
836+
837 if _strategy is None:
838 _strategy = DirectStrategy()
839- _strategy.platform_id = strategy.platform_id
840- _strategy.merchant_id = strategy.merchant_id
841- _strategy.rule = strategy.rules
842- _strategy.title = strategy.title or "defaut title"
843- s = DBSession()
844- s.add(_strategy)
845+ _update(_strategy)
846+ # TODO:refresh cache data
847+ # some code here
848
849 @classmethod
850- @no_lock_cache(15*60)
851+ @no_lock_cache(5*60)
852 def query_rule_by_platform_merchant_id(cls, platform_id, merchant_id):
853+ return cls._query_rule_by_platform_merchant_id(platform_id, merchant_id)
854+
855+ @classmethod
856+ def _query_rule_by_platform_merchant_id(cls, platform_id, merchant_id):
857 s = DBSession()
858 record = s.query(cls).filter(cls.platform_id == platform_id).\
859 filter(cls.merchant_id == merchant_id).first()
860diff --git a/yoda/strategy/grid.py b/yoda/strategy/grid.py
861deleted file mode 100644
862index 6c7d169..0000000
863--- a/yoda/strategy/grid.py
864+++ /dev/null
865@@ -1,130 +0,0 @@
866-#!/usr/bin/env python
867-# -*- coding: utf-8 -*-
868-###############################################################################
869-#
870-# Copyright (c) Yabin Ping
871-# Mail(Forever) yabping@gmail.com
872-#
873-# Created Time: 2016-04-01 13:32:40
874-###############################################################################
875-
876-
877-import json
878-import random
879-import logging
880-from sqlalchemy import (
881- Column,
882- DateTime,
883- Integer,
884- String,
885- func,
886-)
887-from sqlalchemy.dialects.postgresql import JSON
888-from geoalchemy2 import Geometry
889-from zeus_core.db import db_manager, gen_commit_deco, model_base
890-from yoda.exc import YodaErrorCode, raise_system_exc
891-from zeus_core.cache import Cache, cache_mixin
892-from zeus_core.decorators import gen_no_lock_cache_deco
893-from yoda import settings
894-
895-DBSession = db_manager.get_session('yoda')
896-cache_manager = Cache('yoda', settings.CACHE_NAMESPACE)
897-region = cache_manager.make_region()
898-table_region = cache_manager.make_client()
899-no_lock_cache = gen_no_lock_cache_deco(region.client)
900-
901-DeclarativeBase = model_base()
902-CacheMixin = cache_mixin(table_region, DBSession, pub=False)
903-
904-auto_commit = gen_commit_deco(DBSession, raise_system_exc,
905- YodaErrorCode.DATABASE_ERROR)
906-
907-logger = logging.getLogger(__name__)
908-
909-
910-def point_to_wtx(point):
911- return 'SRID=4326;POINT({} {})'.\
912- format(point.longitude, point.latitude)
913-
914-
915-def polygon_to_wtx(points):
916- wtx = ','.join('{} {}'.format(point.longitude, point.latitude)
917- for point in points)
918- return 'SRID=4326;POLYGON(({}))'.format(wtx)
919-
920-
921-def translate_rules(config):
922- start, rules = 0, {}
923- for carrier, val in config.iteritems():
924- rules[carrier] = (start, start + val)
925- start += val
926- return rules
927-
928-
929-class GridStrategy(DeclarativeBase):
930- __tablename__ = 'tb_grid_strategy'
931- GLOBAL_GRID_ID = 'global_grid_id'
932- id = Column(Integer, primary_key=True)
933- grid_id = Column(String, default='')
934- title = Column(String, default='')
935- range = Column(Geometry('POLYGON', srid=4326))
936- rule = Column(JSON, default=[])
937- created_at = Column(DateTime, default=func.now())
938- updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
939-
940- @classmethod
941- @auto_commit
942- def create(cls, strategy):
943- '''如果å˜åœ¨åˆ™æ›´æ–°è®°å½•,å¦åˆ™æ›´æ–°å·²æœ‰è®°å½• '''
944- s = DBSession()
945- grid = s.query(cls).filter(cls.grid_id == strategy.grid_id).first()
946- if grid is None:
947- grid = cls()
948- grid.grid_id = strategy.grid_id
949- grid.range = polygon_to_wtx(strategy.polygon)
950- grid.rule = translate_rules(strategy.rules)
951- grid.title = strategy.title or "defaut title"
952- s.add(grid)
953-
954- @classmethod
955- @no_lock_cache(15 * 60)
956- def query_global_rule(cls):
957- record = DBSession().query(cls).\
958- filter(cls.grid_id == cls.GLOBAL_GRID_ID).first()
959- return None if record is None else record.get_rules()
960-
961- @classmethod
962- @no_lock_cache(15 * 60)
963- def query_rule_by_point(cls, point):
964- s = DBSession()
965- point_wtx = point_to_wtx(point)
966- record = s.query(cls).\
967- filter(cls.grid_id != cls.GLOBAL_GRID_ID).\
968- filter(func.ST_Contains(cls.range, point_wtx)).first()
969- return None if record is None else record.get_rules()
970-
971- @classmethod
972- def strategy(cls, order):
973- rules = cls.query_rule_by_point(order.merchant_location)
974- # if grid rule not set, use global rule
975- if rules is None:
976- rules = cls.query_global_rule()
977- # if grid rule not set, write error log
978- if rules is None:
979- logger.error('global grid rules not set')
980- return None
981- count, carrier_order, rules = 0, [], rules
982- # 100 is a magic number; To make sure each number will be selected once
983- while count < 1000:
984- choice = random.randint(0, 100)
985- count += 1
986- for carrier, interval in rules.iteritems():
987- if carrier not in carrier_order:
988- if interval[0] <= choice and interval[1] > choice:
989- carrier_order.append(carrier)
990- return carrier_order
991-
992- def get_rules(self):
993- if isinstance(self.rule, list):
994- return self.rule
995- return json.loads(self.rule)
996diff --git a/yoda/tasks/__init__.py b/yoda/tasks/__init__.py
997new file mode 100644
998index 0000000..22e34c9
999--- /dev/null
1000+++ b/yoda/tasks/__init__.py
1001@@ -0,0 +1,27 @@
1002+import logging
1003+import functools
1004+
1005+from .models import CarrierTag, MerchantTag
1006+
1007+
1008+logger = logging.getLogger(__name__)
1009+
1010+
1011+def task_logger(func):
1012+ @functools.wraps(func)
1013+ def wrap(*args, **kwargs):
1014+ logging.info("Task: {} started.".format(func.func_name))
1015+ ret = func(*args, **kwargs)
1016+ logging.info("Task: {} finished".format(func.func_name))
1017+ return ret
1018+ return wrap
1019+
1020+
1021+@task_logger
1022+def sync_carrier_tags():
1023+ CarrierTag.sync_carrier_tags()
1024+
1025+
1026+@task_logger
1027+def sync_merchant_tags():
1028+ MerchantTag.sync_merchant_tags()
1029diff --git a/yoda/tasks/models.py b/yoda/tasks/models.py
1030new file mode 100644
1031index 0000000..0641461
1032--- /dev/null
1033+++ b/yoda/tasks/models.py
1034@@ -0,0 +1,161 @@
1035+#!/usr/bin/env python
1036+# -*- coding: utf-8 -*-
1037+###############################################################################
1038+#
1039+# Copyright (c) Yabin Ping
1040+# Mail(Forever) yabping@gmail.com
1041+#
1042+# Created Time: 2016-05-10 11:34:05
1043+###############################################################################
1044+
1045+import datetime
1046+import logging
1047+
1048+from sqlalchemy import (
1049+ Column,
1050+ Integer,
1051+ DateTime,
1052+ BigInteger,
1053+ String,
1054+ func,
1055+)
1056+from zeus_core.db import db_manager, gen_commit_deco, model_base
1057+from zeus_core.cache import Cache
1058+from zeus_core.decorators import gen_cache_decorator
1059+from zeus_core.async import send_task
1060+from yoda.exc import YodaErrorCode, raise_system_exc
1061+from yoda import settings, thrift_file
1062+
1063+
1064+DBSession = db_manager.get_session('yoda')
1065+DeclarativeBase = model_base()
1066+auto_commit = gen_commit_deco(DBSession, raise_system_exc,
1067+ YodaErrorCode.DATABASE_ERROR)
1068+
1069+cache_manager = Cache('yoda', settings.CACHE_NAMESPACE)
1070+region = cache_manager.make_region()
1071+lock_cache = gen_cache_decorator(region)
1072+logger = logging.getLogger(__name__)
1073+
1074+
1075+class MerchantTag(DeclarativeBase):
1076+ __tablename__ = 'tb_merchant_tag'
1077+
1078+ id = Column(BigInteger, primary_key=True)
1079+ grid_id = Column(String, default='')
1080+ merchant_id = Column(String, default='')
1081+ tags = Column(String, default='')
1082+
1083+ @classmethod
1084+ @lock_cache(1 * 60 * 60)
1085+ def sync_merchant_tags(cls):
1086+ '''åŒæ¥å½“天的tag'''
1087+
1088+ task_id = cls.__tablename__
1089+ today = datetime.date.today()
1090+ # 检查大数æ®ç»„å½“å¤©çš„æ•°æ®æ˜¯å¦æœ‰æ›´æ–°ï¼Œ 没有直接返回
1091+ if not TimerTaskMarker.has_record(task_id, 1, today):
1092+ # TODO: do metrics
1093+ return
1094+
1095+ # 检查yoda当天是å¦å·²ç»æ›´æ–°è¿‡æ‰€æœ‰tag
1096+ task_id = '{}-{}'.format(cls.__tablename__,
1097+ cls.sync_merchant_tags.func_name)
1098+ if TimerTaskMarker.has_record(task_id, 1, today):
1099+ return
1100+
1101+ query = DBSession().query(cls)
1102+ for o in query:
1103+ strategy = cls.construct_strategy(o.grid_id, o.merchant_id, o.tags)
1104+ send_task("clair.yoda", "add_grid_strategies", strategy,
1105+ timeout=5,
1106+ queue='clair.yoda.celery.merchant.tag.queue')
1107+ TimerTaskMarker.add(task_id, 1)
1108+
1109+ @classmethod
1110+ def construct_strategy(cls, grid_id, merchant_id, tags):
1111+ strategies = thrift_file.Strategy(
1112+ key='merchant_tag:{}'.format(merchant_id),
1113+ val=tags)
1114+ return thrift_file.GridStrategy(owner='yoda',
1115+ grid_id=grid_id,
1116+ strategies=[strategies])
1117+
1118+
1119+class CarrierTag(DeclarativeBase):
1120+ __tablename__ = 'tb_carrier_tag'
1121+
1122+ id = Column(BigInteger, primary_key=True)
1123+ grid_id = Column(String, default='')
1124+ carrier_id = Column(String, default='')
1125+ # 开始时间-ç»“æŸæ—¶é—´ï¼š 准时é€è¾¾çއ,é…逿—¶æ•ˆ,异常å•率,投诉率
1126+ tags = Column(String, default='')
1127+
1128+ @classmethod
1129+ @lock_cache(1 * 60 * 60)
1130+ def sync_carrier_tags(cls):
1131+ '''åŒæ¥å½“天的tag'''
1132+
1133+ task_id = cls.__tablename__
1134+ today = datetime.date.today()
1135+ # 检查大数æ®ç»„å½“å¤©çš„æ•°æ®æ˜¯å¦æœ‰æ›´æ–°ï¼Œ 没有直接返回
1136+ if not TimerTaskMarker.has_record(task_id, 1, today):
1137+ # TODO: do metrics
1138+ return
1139+
1140+ # 检查yoda当天是å¦å·²ç»æ›´æ–°è¿‡æ‰€æœ‰tag
1141+ task_id = '{}-{}'.format(cls.__tablename__,
1142+ cls.sync_carrier_tags.func_name)
1143+ if TimerTaskMarker.has_record(task_id, 1, today):
1144+ return
1145+
1146+ mapping = {
1147+ '5': 'TD',
1148+ '6': 'ZB',
1149+ '9': 'DWD',
1150+ '10': 'SHBJ',
1151+ }
1152+ query = DBSession().query(cls)
1153+ for o in query:
1154+ if mapping.get(o.carrier_id, None) is None:
1155+ logger.error(u"大数æ®ç»„åŒæ¥é”™è¯¯é…é€å•†ç¼–å·:{}".format(o.carrier_id)) # noqa
1156+ continue
1157+ strategy = cls.construct_strategy(o.grid_id, o.carrier_id, o.tags)
1158+ send_task("clair.yoda", "add_grid_strategies", strategy,
1159+ timeout=5,
1160+ queue='clair.yoda.celery.carrier.tag.queue')
1161+ TimerTaskMarker.add(task_id, 1)
1162+
1163+ @classmethod
1164+ def construct_strategy(cls, grid_id, carrier_id, tags):
1165+ strategies = thrift_file.Strategy(
1166+ key='carrier_tag:{}'.format(carrier_id),
1167+ val=tags)
1168+ return thrift_file.GridStrategy(owner='yoda',
1169+ grid_id=grid_id,
1170+ strategies=[strategies])
1171+
1172+
1173+class TimerTaskMarker(DeclarativeBase):
1174+ __tablename__ = 'tb_timer_task_marker'
1175+
1176+ id = Column(Integer, primary_key=True)
1177+ task_id = Column(String, default='')
1178+ status = Column(Integer, default=0)
1179+ created_at = Column(DateTime, default=func.now())
1180+
1181+ @classmethod
1182+ @auto_commit
1183+ def add(cls, task_id, status):
1184+ obj = cls()
1185+ obj.task_id = task_id
1186+ obj.status = status
1187+ DBSession().add(obj)
1188+
1189+ @classmethod
1190+ def has_record(cls, task_id, status, sync_date):
1191+ query = DBSession().query(cls).\
1192+ filter(cls.task_id == task_id).\
1193+ filter(cls.status == status).\
1194+ filter(cls.created_at > sync_date)
1195+ return query.count() > 0
1196diff --git a/yoda/yoda.thrift b/yoda/yoda.thrift
1197index 123cdf4..db8bf7d 100644
1198--- a/yoda/yoda.thrift
1199+++ b/yoda/yoda.thrift
1200@@ -2,6 +2,8 @@
1201 * Types and Structures
1202 */
1203 typedef i64 Timestamp
1204+typedef string JSON
1205+const string GLOBAL_GRID_ID = "global_grid_id"
1206
1207 /**
1208 * Exceptions
1209@@ -10,6 +12,9 @@ enum YodaErrorCode {
1210 UNKNOWN_ERROR = 0,
1211 DATABASE_ERROR = 1,
1212 TOO_BUSY_ERROR = 2,
1213+
1214+ INVALID_RULE_ERROR = 1000,
1215+ GRID_NOT_EXIST_ERROR = 1001,
1216 }
1217
1218 exception YodaUserException {
1219@@ -51,14 +56,25 @@ struct DirectStrategy {
1220 4: optional string title,
1221 }
1222
1223-const string GLOBAL_GRID_ID = "global_grid_id"
1224-struct GridStrategy {
1225- 1: required string grid_id,
1226+struct Strategy {
1227+ 1: required string key,
1228+ 2: required JSON val,
1229+}
1230+
1231+struct Grid {
1232+ 1: required string owner,
1233+ 2: required string grid_id,
1234+ 3: required string title,
1235 /* ç½‘æ ¼å¤šè¾¹å½¢é—环, éžè‡ªäº¤ */
1236- 2: required list<Location> polygon,
1237- /* é…é€å•†ç™¾åˆ†æ¯”列表 */
1238- 3: required map<string, i32> rules,
1239- 4: optional string title,
1240+ 4: required list<Location> polygon,
1241+ 5: required string city_id,
1242+}
1243+
1244+struct GridStrategy {
1245+ 1: required string owner,
1246+ 2: required string grid_id,
1247+ /* é…é€å•†é…置列表项 */
1248+ 3: required list<Strategy> strategies,
1249 }
1250
1251 struct Plan {
1252@@ -80,8 +96,26 @@ service YodaService {
1253 2: YodaSystemException system_exception,
1254 3: YodaUnknownException unknown_exception,)
1255
1256- /* æ·»åŠ ä¸€æ¡ç½‘æ ¼è§„åˆ™; å¦‚æžœæ˜¯å…¨å±€è§„åˆ™ä¼ å…¥æŒ‡å®šçš„GLOBAL_GRID_ID, é…é€åŒºåŸŸä¸ºå…¨å±€æœ€å¤§åŒºåŸŸ */
1257- void create_grid_strategy(1: GridStrategy strategy)
1258+ /* æ·»åŠ æˆ–è€…æ›´æ–°ä¸€ä¸ªç½‘æ ¼ */
1259+ void create_grid(1: Grid grid)
1260+ throws (1: YodaUserException user_exception,
1261+ 2: YodaSystemException system_exception,
1262+ 3: YodaUnknownException unknown_exception,)
1263+
1264+ /* æ·»åŠ ç½‘æ ¼è§„åˆ™; å¦‚æžœæ˜¯å…¨å±€è§„åˆ™ä¼ å…¥æŒ‡å®šçš„GLOBAL_GRID_ID */
1265+ void add_grid_strategies(1: GridStrategy strategy)
1266+ throws (1: YodaUserException user_exception,
1267+ 2: YodaSystemException system_exception,
1268+ 3: YodaUnknownException unknown_exception,)
1269+
1270+ /* ç§»é™¤ç½‘æ ¼è§„åˆ™; å¦‚æžœæ˜¯å…¨å±€è§„åˆ™ä¼ å…¥æŒ‡å®šçš„GLOBAL_GRID_ID */
1271+ void remove_grid_strategies(1: GridStrategy strategy)
1272+ throws (1: YodaUserException user_exception,
1273+ 2: YodaSystemException system_exception,
1274+ 3: YodaUnknownException unknown_exception,)
1275+
1276+ /* æŸ¥è¯¢ç½‘æ ¼æ‰€æœ‰çš„é…置项 */
1277+ list<Strategy> query_grid_strategies(1: string owner, 2: string grid_id)
1278 throws (1: YodaUserException user_exception,
1279 2: YodaSystemException system_exception,
1280 3: YodaUnknownException unknown_exception,)
1281@@ -91,4 +125,10 @@ service YodaService {
1282 throws (1: YodaUserException user_exception,
1283 2: YodaSystemException system_exception,
1284 3: YodaUnknownException unknown_exception,)
1285+
1286+
1287+ /* 定时检查大数æ®ç»„是å¦åŒæ¥æ›´æ–°äº†ç½‘æ ¼ç”»åƒæ•°æ® */
1288+ void sync_carrier_tags_task()
1289+ void sync_merchant_tags_task()
1290+
1291 }