├── src ├── main │ ├── resources │ │ ├── application.properties │ │ ├── static │ │ │ └── css │ │ │ │ └── common.css │ │ └── templates │ │ │ └── home.html │ └── java │ │ └── jie │ │ └── cf │ │ ├── core │ │ ├── utils │ │ │ ├── exception │ │ │ │ └── CfException.java │ │ │ ├── stereotype │ │ │ │ └── CfDemo.java │ │ │ ├── CfConstants.java │ │ │ └── CfUtils.java │ │ ├── dao │ │ │ ├── ICampaignDAO.java │ │ │ ├── ICampaignRuleDAO.java │ │ │ ├── ICampaignEventDAO.java │ │ │ ├── ICampaignCouponDAO.java │ │ │ ├── ICampaignResponseDAO.java │ │ │ └── ICampaignCouponItemDAO.java │ │ ├── api │ │ │ └── ICfCampaignSvs.java │ │ ├── dto │ │ │ ├── CampaignEvent.java │ │ │ ├── CampaignRule.java │ │ │ ├── CampaignRequest.java │ │ │ ├── Campaign.java │ │ │ ├── CampaignCoupon.java │ │ │ ├── CampaignResponse.java │ │ │ └── CampaignCouponItem.java │ │ └── service │ │ │ ├── CfCampaignSvs.java │ │ │ └── CampaignExecution.java │ │ ├── CfApplication.java │ │ ├── demo │ │ ├── dao │ │ │ ├── DemoCampaignDAO.java │ │ │ ├── DemoCampaignEventDAO.java │ │ │ ├── DemoCampaignRuleDAO.java │ │ │ ├── DemoCampaignCouponDAO.java │ │ │ ├── DemoCampaignResponseDAO.java │ │ │ └── DemoCampaignCouponItemDAO.java │ │ └── Demo.java │ │ └── controller │ │ └── HomeController.java └── test │ └── java │ └── jie │ └── cf │ └── CfApplicationTests.java ├── dev-book ├── uml │ ├── Architecture.png │ └── SequenceDiagram.png └── cf-mysql-schema.sql ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .gitignore ├── pom.xml ├── mvnw.cmd ├── README.md └── mvnw /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.thymeleaf.cache=false -------------------------------------------------------------------------------- /dev-book/uml/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ujsleo/cf/HEAD/dev-book/uml/Architecture.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ujsleo/cf/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /dev-book/uml/SequenceDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ujsleo/cf/HEAD/dev-book/uml/SequenceDiagram.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip 2 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/utils/exception/CfException.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.utils.exception; 2 | 3 | /** 4 | * CF异常 5 | * 6 | * @author Jie 7 | * 8 | */ 9 | public class CfException extends Exception { 10 | public CfException(String msg) { 11 | super(msg); 12 | } 13 | 14 | public CfException(Throwable e) { 15 | super(e); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /src/test/java/jie/cf/CfApplicationTests.java: -------------------------------------------------------------------------------- 1 | package jie.cf; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class CfApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/CfApplication.java: -------------------------------------------------------------------------------- 1 | package jie.cf; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.ApplicationContext; 6 | 7 | import jie.cf.core.utils.CfUtils; 8 | 9 | @SpringBootApplication 10 | public class CfApplication { 11 | public static void main(String[] args) { 12 | ApplicationContext context = SpringApplication.run(CfApplication.class, args); 13 | // 持有Spring应用上下文 14 | CfUtils.setContext(context); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/static/css/common.css: -------------------------------------------------------------------------------- 1 | header, footer, nav, section, article { 2 | margin: 5px; 3 | background-color: write; 4 | padding: 5px; 5 | } 6 | 7 | header, footer { 8 | color: white; 9 | background-color: black; 10 | text-align: center; 11 | } 12 | 13 | section { 14 | background-color: #ddd; 15 | } 16 | 17 | nav { 18 | background-color: #D3D3D3; 19 | } 20 | 21 | span.red { 22 | color: red; 23 | } 24 | 25 | button { 26 | background-color: #D43C33; 27 | color: white; 28 | border: none; 29 | font-size: 16px; 30 | padding: 8px 28px; 31 | } -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/utils/stereotype/CfDemo.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.utils.stereotype; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * CfDemo注解 12 | * 13 | * @author Jie 14 | * 15 | */ 16 | @Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Component 19 | public @interface CfDemo { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dao/ICampaignDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dao; 2 | 3 | import jie.cf.core.dto.Campaign; 4 | 5 | /** 6 | * Data access layer for the Campaign 7 | * 8 | * @author Jie 9 | * 10 | */ 11 | public interface ICampaignDAO { 12 | /** 13 | * 持久化:CREATE or UPDATE 14 | * 15 | * @param campaign 16 | */ 17 | void saveOne(Campaign campaign); 18 | 19 | /** 20 | * 21 | * @param id 22 | * 活动ID 23 | * @return 24 | */ 25 | Campaign findOne(Long id); 26 | 27 | /** 28 | * 29 | * @param code 30 | * 活动CODE 31 | * @return 32 | */ 33 | Campaign findOne(String code); 34 | 35 | /** 36 | * 通过code查找ID。唯一性 37 | * 38 | * @param code 39 | * @return 40 | */ 41 | Long findId(String code); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dao/ICampaignRuleDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dao; 2 | 3 | import java.util.List; 4 | 5 | import jie.cf.core.dto.CampaignRule; 6 | 7 | /** 8 | * Data access layer for the CampaignRule 9 | * 10 | * @author Jie 11 | * 12 | */ 13 | public interface ICampaignRuleDAO { 14 | /** 15 | * 持久化:CREATE or UPDATE 16 | * 17 | * @param campaignRule 18 | */ 19 | void saveOne(CampaignRule campaignRule); 20 | 21 | /** 22 | * 23 | * @param id 24 | * 活动规则ID 25 | * @return 26 | */ 27 | CampaignRule findOne(Long id); 28 | 29 | /** 30 | * 通过活动事件ID和规则类型查找相关的活动规则列表 31 | * 32 | * @param campaignEventId 33 | * @param type 34 | * @return 35 | */ 36 | List findByParentId(Long campaignEventId, String type); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dao/ICampaignEventDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dao; 2 | 3 | import jie.cf.core.dto.CampaignEvent; 4 | 5 | /** 6 | * Data access layer for the CampaignEvent 7 | * 8 | * @author Jie 9 | * 10 | */ 11 | public interface ICampaignEventDAO { 12 | /** 13 | * 持久化:CREATE or UPDATE 14 | * 15 | * @param campaignEvent 16 | */ 17 | void saveOne(CampaignEvent campaignEvent); 18 | 19 | /** 20 | * 21 | * @param id 22 | * 活动事件ID 23 | * @return 24 | */ 25 | CampaignEvent findOne(Long id); 26 | 27 | /** 28 | * 29 | * @param code 30 | * 活动事件CODE 31 | * @return 32 | */ 33 | CampaignEvent findOne(String code); 34 | 35 | /** 36 | * 通过code查找ID。唯一性 37 | * 38 | * @param code 39 | * @return 40 | */ 41 | Long findId(String code); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dao/ICampaignCouponDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dao; 2 | 3 | import java.util.List; 4 | 5 | import jie.cf.core.dto.CampaignCoupon; 6 | 7 | /** 8 | * Data access layer for the CampaignCoupon 9 | * 10 | * @author Jie 11 | * 12 | */ 13 | public interface ICampaignCouponDAO { 14 | /** 15 | * 持久化:CREATE or UPDATE 16 | * 17 | * @param campaignCoupon 18 | */ 19 | void saveOne(CampaignCoupon campaignCoupon); 20 | 21 | /** 22 | * 23 | * @param id 24 | * 票券池ID 25 | * @return 26 | */ 27 | CampaignCoupon findOne(Long id); 28 | 29 | /** 30 | * 31 | * @param code 32 | * 票券池CODE 33 | * @return 34 | */ 35 | CampaignCoupon findOne(String code); 36 | 37 | /** 38 | * 通过活动ID查找相关的活动票券池列表 39 | * 40 | * @param campaignId 41 | * @return 42 | */ 43 | List findByParentId(Long campaignId); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/api/ICfCampaignSvs.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.api; 2 | 3 | import jie.cf.core.dto.CampaignRequest; 4 | import jie.cf.core.utils.exception.CfException; 5 | 6 | /** 7 | * Campaign service for the CF 8 | * 9 | * @author Jie 10 | * 11 | */ 12 | public interface ICfCampaignSvs { 13 | /** 14 | * 参加活动事件 15 | * 16 | * @param campaignRequest 17 | * 活动请求 18 | * @throws CfException 19 | */ 20 | void campaignEvent(CampaignRequest campaignRequest) throws CfException; 21 | 22 | /** 23 | * 派发票券 24 | * 25 | * @param campaignRequest 26 | * @throws CfException 27 | */ 28 | void distribute(CampaignRequest campaignRequest) throws CfException; 29 | 30 | /** 31 | * 使用票券 32 | * 33 | * @param campaignRequest 34 | * @throws CfException 35 | */ 36 | void use(CampaignRequest campaignRequest) throws CfException; 37 | 38 | /** 39 | * 兑换票券(凭兑换码换票券) 40 | * 41 | * @param campaignRequest 42 | * @throws CfException 43 | */ 44 | void exchange(CampaignRequest campaignRequest) throws CfException; 45 | 46 | /** 47 | * 参加微信“好友帮砍价”活动事件 48 | * 49 | * @param campaignRequest 50 | * @throws CfException 51 | */ 52 | void wechatCampaignEvent(CampaignRequest campaignRequest) throws CfException; 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dto/CampaignEvent.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dto; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | /** 7 | * 活动事件 8 | * 9 | * @author Jie 10 | * 11 | */ 12 | public class CampaignEvent { 13 | private Long id; 14 | private Long campaignId; // 活动ID 15 | private String code; // 活动事件CODE。UNIQUE 16 | private String desc; // 描述 17 | 18 | @Override 19 | public String toString() { 20 | ObjectMapper objectMapper = new ObjectMapper(); 21 | try { 22 | return objectMapper.writeValueAsString(this); 23 | } catch (JsonProcessingException e) { 24 | e.printStackTrace(); 25 | } 26 | return null; 27 | } 28 | 29 | public Long getId() { 30 | return id; 31 | } 32 | 33 | public void setId(Long id) { 34 | this.id = id; 35 | } 36 | 37 | public Long getCampaignId() { 38 | return campaignId; 39 | } 40 | 41 | public void setCampaignId(Long campaignId) { 42 | this.campaignId = campaignId; 43 | } 44 | 45 | public String getCode() { 46 | return code; 47 | } 48 | 49 | public void setCode(String code) { 50 | this.code = code; 51 | } 52 | 53 | public String getDesc() { 54 | return desc; 55 | } 56 | 57 | public void setDesc(String desc) { 58 | this.desc = desc; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dao/ICampaignResponseDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dao; 2 | 3 | import java.util.List; 4 | 5 | import jie.cf.core.dto.CampaignResponse; 6 | 7 | /** 8 | * Data access layer for the CampaignResponse 9 | * 10 | * @author Jie 11 | * 12 | */ 13 | public interface ICampaignResponseDAO { 14 | /** 15 | * 持久化:CREATE or UPDATE 16 | * 17 | * @param campaignResponse 18 | */ 19 | void saveOne(CampaignResponse campaignResponse); 20 | 21 | /** 22 | * 23 | * @param id 24 | * 活动响应ID 25 | * @return 26 | */ 27 | CampaignResponse findOne(Long id); 28 | 29 | /** 30 | * 通过任一指定参数查找相关的活动响应列表 31 | * 32 | * @param campaignEventId 33 | * 活动事件ID. required 34 | * @param clientId 35 | * 客户ID. optional 36 | * @param mobile 37 | * 手机号. optional 38 | * @return 39 | */ 40 | List findByCampaignRequest(Long campaignEventId, Long clientId, String mobile); 41 | 42 | /** 43 | * 通过微信OpenId查找相关的活动响应列表 44 | * 45 | * @param campaignEventId 46 | * 活动事件ID. required 47 | * @param openId 48 | * 参与人微信OpenId. required 49 | * @param targetOpenId 50 | * 目标发起人微信OpenId. optional 51 | * @return 52 | */ 53 | List findByWxOpenId(Long campaignEventId, String openId, String targetOpenId); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dao/ICampaignCouponItemDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dao; 2 | 3 | import java.util.List; 4 | 5 | import jie.cf.core.dto.CampaignCouponItem; 6 | import jie.cf.core.utils.stereotype.CfDemo; 7 | 8 | /** 9 | * Data access layer for the CampaignCouponItem 10 | * 11 | * @author Jie 12 | * 13 | */ 14 | public interface ICampaignCouponItemDAO { 15 | /** 16 | * 持久化:CREATE or UPDATE 17 | * 18 | * @param campaignCouponItem 19 | */ 20 | void saveOne(CampaignCouponItem campaignCouponItem); 21 | 22 | /** 23 | * 24 | * @param id 25 | * 票券ID 26 | * @return 27 | */ 28 | CampaignCouponItem findOne(Long id); 29 | 30 | /** 31 | * 通过活动事件ID和序列号查找一张活动票券 32 | * 33 | * @param campaignEventId 34 | * @param serialNumber 35 | * @return 36 | */ 37 | CampaignCouponItem findOneBySerialNumber(Long campaignEventId, String serialNumber); 38 | 39 | /** 40 | * 通过任一指定参数查找相关的票券列表 41 | * 42 | * @param clientId 43 | * 客户ID. optional 44 | * @param mobile 45 | * 手机号. optional 46 | * @return 47 | */ 48 | List findByUser(Long clientId, String mobile); 49 | 50 | /** 51 | * 通过活动事件ID查找一张AVAILABLE活动票券 52 | * 53 | * @param campaignEventId 54 | * @return 55 | */ 56 | @CfDemo 57 | @Deprecated 58 | CampaignCouponItem findOneAvailable(Long campaignEventId); 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/demo/dao/DemoCampaignDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.demo.dao; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import jie.cf.core.dao.ICampaignDAO; 7 | import jie.cf.core.dto.Campaign; 8 | import jie.cf.core.utils.stereotype.CfDemo; 9 | 10 | @CfDemo 11 | public class DemoCampaignDAO implements ICampaignDAO { 12 | private Map repo = new HashMap(); 13 | private static Long id = 1L; 14 | 15 | @Override 16 | public void saveOne(Campaign campaign) { 17 | if (campaign.getId() == null) 18 | campaign.setId(id++); 19 | String key = campaign.getId() + ":" + campaign.getCode(); 20 | repo.put(key, campaign); 21 | } 22 | 23 | @Override 24 | public Campaign findOne(Long id) { 25 | for (String key : repo.keySet()) { 26 | String[] items = key.split(":"); 27 | if (items[0].equals(id.toString())) 28 | return (Campaign) repo.get(key); 29 | } 30 | return null; 31 | } 32 | 33 | @Override 34 | public Campaign findOne(String code) { 35 | for (String key : repo.keySet()) { 36 | String[] items = key.split(":"); 37 | if (items[1].equals(code)) 38 | return (Campaign) repo.get(key); 39 | } 40 | return null; 41 | } 42 | 43 | @Override 44 | public Long findId(String code) { 45 | for (String key : repo.keySet()) { 46 | String[] items = key.split(":"); 47 | if (items[1].equals(code)) 48 | return Long.parseLong(items[0]); 49 | } 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package jie.cf.controller; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.json.JSONObject; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestMethod; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | import org.springframework.web.bind.annotation.ResponseBody; 12 | 13 | import jie.cf.core.utils.CfUtils; 14 | 15 | /** 16 | * 控制台 - 执行Groovy脚本 17 | * 18 | * @author Jie 19 | * 20 | */ 21 | @Controller 22 | @RequestMapping("/") 23 | public class HomeController { 24 | @RequestMapping(method = RequestMethod.GET) 25 | public String home() { 26 | return "home"; 27 | } 28 | 29 | /** 30 | * 执行Groovy脚本 31 | * 32 | * @param script 33 | * @return 34 | * @throws Exception 35 | */ 36 | @RequestMapping(value = "/eval", method = RequestMethod.GET) 37 | public @ResponseBody String eval(@RequestParam(value = "script") String script) throws Exception { 38 | Object obj = CfUtils.executeGroovyScript(script, param()); 39 | JSONObject jsonObject = new JSONObject(); 40 | jsonObject.put("ret", obj); 41 | return jsonObject.toString(); 42 | } 43 | 44 | /** 构造Groovy脚本可用的参数 */ 45 | private Map param() { 46 | Map ret = new HashMap(); 47 | ret.put("context", CfUtils.getContext()); // Spring应用上下文 48 | return ret; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/demo/dao/DemoCampaignEventDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.demo.dao; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import jie.cf.core.dao.ICampaignEventDAO; 7 | import jie.cf.core.dto.CampaignEvent; 8 | import jie.cf.core.utils.stereotype.CfDemo; 9 | 10 | @CfDemo 11 | public class DemoCampaignEventDAO implements ICampaignEventDAO { 12 | private Map repo = new HashMap(); 13 | private static Long id = 1L; 14 | 15 | @Override 16 | public void saveOne(CampaignEvent campaignEvent) { 17 | if (campaignEvent.getId() == null) 18 | campaignEvent.setId(id++); 19 | String key = campaignEvent.getId() + ":" + campaignEvent.getCode(); 20 | repo.put(key, campaignEvent); 21 | } 22 | 23 | @Override 24 | public CampaignEvent findOne(Long id) { 25 | for (String key : repo.keySet()) { 26 | String[] items = key.split(":"); 27 | if (items[0].equals(id.toString())) 28 | return (CampaignEvent) repo.get(key); 29 | } 30 | return null; 31 | } 32 | 33 | @Override 34 | public CampaignEvent findOne(String code) { 35 | for (String key : repo.keySet()) { 36 | String[] items = key.split(":"); 37 | if (items[1].equals(code)) 38 | return (CampaignEvent) repo.get(key); 39 | } 40 | return null; 41 | } 42 | 43 | @Override 44 | public Long findId(String code) { 45 | for (String key : repo.keySet()) { 46 | String[] items = key.split(":"); 47 | if (items[1].equals(code)) 48 | return Long.parseLong(items[0]); 49 | } 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/demo/dao/DemoCampaignRuleDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.demo.dao; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import jie.cf.core.dao.ICampaignRuleDAO; 9 | import jie.cf.core.dto.CampaignRule; 10 | import jie.cf.core.utils.stereotype.CfDemo; 11 | 12 | @CfDemo 13 | public class DemoCampaignRuleDAO implements ICampaignRuleDAO { 14 | private Map repo = new HashMap(); 15 | private static Long id = 1L; 16 | 17 | @Override 18 | public void saveOne(CampaignRule campaignRule) { 19 | if (campaignRule.getId() == null) 20 | campaignRule.setId(id++); 21 | String key = campaignRule.getId() + ":" + campaignRule.getCampaignEventId(); 22 | repo.put(key, campaignRule); 23 | } 24 | 25 | @Override 26 | public CampaignRule findOne(Long id) { 27 | for (String key : repo.keySet()) { 28 | String[] items = key.split(":"); 29 | if (items[0].equals(id.toString())) 30 | return (CampaignRule) repo.get(key); 31 | } 32 | return null; 33 | } 34 | 35 | @Override 36 | public List findByParentId(Long campaignEventId, String type) { 37 | List ret = new ArrayList(); 38 | for (String key : repo.keySet()) { 39 | String[] items = key.split(":"); 40 | if (items[1].equals(campaignEventId.toString())) { 41 | CampaignRule tmp = (CampaignRule) repo.get(key); 42 | if (tmp.getType().equals(type)) 43 | ret.add(tmp); 44 | } 45 | } 46 | return ret; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Compaign Framework(营销活动框架) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 31 | 32 | 33 | 34 | 35 |
36 |

Compaign Framework(营销活动框架)

37 |
38 | 39 | 42 | 43 |
44 |

Groovy script

45 | 56 |
57 | 58 |
59 | 60 |
61 |
62 | 63 |
64 |

Jie © 2017. All rights reserved.

65 |
66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/demo/dao/DemoCampaignCouponDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.demo.dao; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import jie.cf.core.dao.ICampaignCouponDAO; 9 | import jie.cf.core.dto.CampaignCoupon; 10 | import jie.cf.core.utils.stereotype.CfDemo; 11 | 12 | @CfDemo 13 | public class DemoCampaignCouponDAO implements ICampaignCouponDAO { 14 | private Map repo = new HashMap(); 15 | private static Long id = 1L; 16 | 17 | @Override 18 | public void saveOne(CampaignCoupon campaignCoupon) { 19 | if (campaignCoupon.getId() == null) 20 | campaignCoupon.setId(id++); 21 | String key = campaignCoupon.getId() + ":" + campaignCoupon.getCode(); 22 | repo.put(key, campaignCoupon); 23 | } 24 | 25 | @Override 26 | public CampaignCoupon findOne(Long id) { 27 | for (String key : repo.keySet()) { 28 | String[] items = key.split(":"); 29 | if (items[0].equals(id.toString())) 30 | return (CampaignCoupon) repo.get(key); 31 | } 32 | return null; 33 | } 34 | 35 | @Override 36 | public CampaignCoupon findOne(String code) { 37 | for (String key : repo.keySet()) { 38 | String[] items = key.split(":"); 39 | if (items[1].equals(code)) 40 | return (CampaignCoupon) repo.get(key); 41 | } 42 | return null; 43 | } 44 | 45 | @Override 46 | public List findByParentId(Long campaignId) { 47 | List ret = new ArrayList(); 48 | for (Map.Entry entry : repo.entrySet()) { 49 | CampaignCoupon tmp = (CampaignCoupon) entry.getValue(); 50 | if (tmp.getCampaignId().equals(campaignId)) 51 | ret.add(tmp); 52 | } 53 | return ret; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dto/CampaignRule.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dto; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | /** 9 | * 活动规则 10 | * 11 | * @author Jie 12 | * 13 | */ 14 | public class CampaignRule { 15 | private Long id; 16 | private Long campaignEventId; // 活动事件ID 17 | private String type; // 活动规则类型 18 | private String groovyCondition; // 判断条件 19 | private String groovyBody; // 执行代码 20 | private Date startDate; // 活动规则开始时间 21 | private Date endDate; // 活动规则结束时间 22 | 23 | @Override 24 | public String toString() { 25 | ObjectMapper objectMapper = new ObjectMapper(); 26 | try { 27 | return objectMapper.writeValueAsString(this); 28 | } catch (JsonProcessingException e) { 29 | e.printStackTrace(); 30 | } 31 | return null; 32 | } 33 | 34 | public Long getId() { 35 | return id; 36 | } 37 | 38 | public void setId(Long id) { 39 | this.id = id; 40 | } 41 | 42 | public Long getCampaignEventId() { 43 | return campaignEventId; 44 | } 45 | 46 | public void setCampaignEventId(Long campaignEventId) { 47 | this.campaignEventId = campaignEventId; 48 | } 49 | 50 | public String getType() { 51 | return type; 52 | } 53 | 54 | public void setType(String type) { 55 | this.type = type; 56 | } 57 | 58 | public String getGroovyCondition() { 59 | return groovyCondition; 60 | } 61 | 62 | public void setGroovyCondition(String groovyCondition) { 63 | this.groovyCondition = groovyCondition; 64 | } 65 | 66 | public String getGroovyBody() { 67 | return groovyBody; 68 | } 69 | 70 | public void setGroovyBody(String groovyBody) { 71 | this.groovyBody = groovyBody; 72 | } 73 | 74 | public Date getStartDate() { 75 | return startDate; 76 | } 77 | 78 | public void setStartDate(Date startDate) { 79 | this.startDate = startDate; 80 | } 81 | 82 | public Date getEndDate() { 83 | return endDate; 84 | } 85 | 86 | public void setEndDate(Date endDate) { 87 | this.endDate = endDate; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dto/CampaignRequest.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dto; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | /** 7 | * 活动请求 8 | * 9 | * @author Jie 10 | * 11 | */ 12 | public class CampaignRequest { 13 | private String campaignEventCode; // 活动事件CODE 14 | private Long clientId; // 客户ID 15 | private String mobile; // 手机号 16 | private String openId; // 微信openId 17 | private String ruleType; // 规则类型(选填) 18 | private String serialNumber; // 兑换票券 19 | private String reserved; // 预留。建议JSON 20 | 21 | @Override 22 | public String toString() { 23 | ObjectMapper objectMapper = new ObjectMapper(); 24 | try { 25 | return objectMapper.writeValueAsString(this); 26 | } catch (JsonProcessingException e) { 27 | e.printStackTrace(); 28 | } 29 | return null; 30 | } 31 | 32 | public String getCampaignEventCode() { 33 | return campaignEventCode; 34 | } 35 | 36 | public void setCampaignEventCode(String campaignEventCode) { 37 | this.campaignEventCode = campaignEventCode; 38 | } 39 | 40 | public Long getClientId() { 41 | return clientId; 42 | } 43 | 44 | public void setClientId(Long clientId) { 45 | this.clientId = clientId; 46 | } 47 | 48 | public String getMobile() { 49 | return mobile; 50 | } 51 | 52 | public void setMobile(String mobile) { 53 | this.mobile = mobile; 54 | } 55 | 56 | public String getOpenId() { 57 | return openId; 58 | } 59 | 60 | public void setOpenId(String openId) { 61 | this.openId = openId; 62 | } 63 | 64 | public String getRuleType() { 65 | return ruleType; 66 | } 67 | 68 | public void setRuleType(String ruleType) { 69 | this.ruleType = ruleType; 70 | } 71 | 72 | public String getSerialNumber() { 73 | return serialNumber; 74 | } 75 | 76 | public void setSerialNumber(String serialNumber) { 77 | this.serialNumber = serialNumber; 78 | } 79 | 80 | public String getReserved() { 81 | return reserved; 82 | } 83 | 84 | public void setReserved(String reserved) { 85 | this.reserved = reserved; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /dev-book/cf-mysql-schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE CfCampaign ( 2 | Id INT(8) NOT NULL AUTO_INCREMENT, 3 | Code VARCHAR(64) NOT NULL, 4 | Name VARCHAR(64), 5 | Status VARCHAR(64), 6 | StatusDate DATETIME, 7 | StartDate DATETIME, 8 | EndDate DATETIME, 9 | Description VARCHAR(1024), 10 | UNIQUE(Code), 11 | CONSTRAINT pk PRIMARY KEY (Id) 12 | ); 13 | CREATE TABLE CfCampaignEvent ( 14 | Id INT(8) NOT NULL AUTO_INCREMENT, 15 | CampaignId INT(8), 16 | Code VARCHAR(64) NOT NULL, 17 | Name VARCHAR(64), 18 | Description VARCHAR(1024), 19 | UNIQUE(Code), 20 | CONSTRAINT pk PRIMARY KEY (Id) 21 | ); 22 | CREATE TABLE CfCampaignRule ( 23 | Id INT(8) NOT NULL AUTO_INCREMENT, 24 | CampaignId INT(8), 25 | CampaignEventId INT(8), 26 | Name VARCHAR(64), 27 | Type VARCHAR(64), 28 | GroovyCondition VARCHAR(1024), 29 | GroovyBody VARCHAR(1024), 30 | StartDate DATETIME, 31 | EndDate DATETIME, 32 | CONSTRAINT pk PRIMARY KEY (Id) 33 | ); 34 | CREATE TABLE CfCampaignResponse ( 35 | Id INT(8) NOT NULL AUTO_INCREMENT, 36 | CampaignId INT(8), 37 | CampaignEventId INT(8), 38 | CampaignRuleId INT(8), 39 | ClientId INT(8), 40 | Mobile VARCHAR(64), 41 | OpenId VARCHAR(128), 42 | Status VARCHAR(64), 43 | StatusDate DATETIME, 44 | Reserved VARCHAR(1024), 45 | CONSTRAINT pk PRIMARY KEY (Id) 46 | ); 47 | CREATE TABLE CfCampaignCoupon ( 48 | Id INT(8) NOT NULL AUTO_INCREMENT, 49 | CampaignId INT(8), 50 | CampaignEventId INT(8), 51 | Code VARCHAR(64) NOT NULL, 52 | Name VARCHAR(64), 53 | Type VARCHAR(64), 54 | TotalAmount INT(8), 55 | AvailableAmount INT(8), 56 | StartDate DATETIME, 57 | EndDate DATETIME, 58 | Description VARCHAR(1024), 59 | UNIQUE(Code), 60 | CONSTRAINT pk PRIMARY KEY (Id) 61 | ); 62 | CREATE TABLE CfCampaignCouponItem ( 63 | Id INT(8) NOT NULL AUTO_INCREMENT, 64 | CampaignId INT(8), 65 | CampaignEventId INT(8), 66 | CampaignCouponId INT(8), 67 | Type VARCHAR(64), 68 | SerialNumber VARCHAR(128), 69 | Status VARCHAR(64), 70 | StatusDate DATETIME, 71 | ClientId INT(8), 72 | Mobile VARCHAR(64), 73 | OpenId VARCHAR(128), 74 | BindingDate DATETIME, 75 | StartDate DATETIME, 76 | EndDate DATETIME, 77 | Description VARCHAR(1024), 78 | CONSTRAINT pk PRIMARY KEY (Id) 79 | ); 80 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/demo/dao/DemoCampaignResponseDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.demo.dao; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import jie.cf.core.dao.ICampaignResponseDAO; 11 | import jie.cf.core.dto.CampaignResponse; 12 | import jie.cf.core.utils.stereotype.CfDemo; 13 | 14 | @CfDemo 15 | public class DemoCampaignResponseDAO implements ICampaignResponseDAO { 16 | private Map repo = new HashMap(); 17 | private static Long id = 1L; 18 | 19 | @Override 20 | public void saveOne(CampaignResponse campaignResponse) { 21 | if (campaignResponse.getId() == null) 22 | campaignResponse.setId(id++); 23 | String key = campaignResponse.getId() + ":" + campaignResponse.getCampaignEventId(); 24 | repo.put(key, campaignResponse); 25 | } 26 | 27 | @Override 28 | public CampaignResponse findOne(Long id) { 29 | for (String key : repo.keySet()) { 30 | String[] items = key.split(":"); 31 | if (items[0].equals(id.toString())) 32 | return (CampaignResponse) repo.get(key); 33 | } 34 | return null; 35 | } 36 | 37 | @Override 38 | public List findByCampaignRequest(Long campaignEventId, Long clientId, String mobile) { 39 | List ret = new ArrayList(); 40 | for (String key : repo.keySet()) { 41 | String[] items = key.split(":"); 42 | if (items[1].equals(campaignEventId.toString())) { 43 | CampaignResponse tmp = (CampaignResponse) repo.get(key); 44 | if (tmp.getClientId() == clientId || tmp.getMobile().equals(mobile)) 45 | ret.add(tmp); 46 | } 47 | } 48 | return ret; 49 | } 50 | 51 | @Override 52 | public List findByWxOpenId(Long campaignEventId, String openId, String targetOpenId) { 53 | List ret = new ArrayList(); 54 | for (String key : repo.keySet()) { 55 | String[] items = key.split(":"); 56 | if (items[1].equals(campaignEventId.toString())) { 57 | CampaignResponse tmp = (CampaignResponse) repo.get(key); 58 | if (tmp.getOpenId().equals(openId) || StringUtils.equals(targetOpenId, tmp.getReserved())) 59 | ret.add(tmp); 60 | } 61 | } 62 | return ret; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dto/Campaign.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dto; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | /** 9 | * 活动 10 | * 11 | * @author Jie 12 | * 13 | */ 14 | public class Campaign { 15 | private Long id; 16 | private String code; // 活动CODE。UNIQUE 17 | private String name; // 活动名 18 | private String status; // 活动状态 19 | private Date statusDate; // 活动状态的更新时间 20 | private Date startDate; // 活动开始时间 21 | private Date endDate; // 活动结束时间 22 | private String desc; // 描述 23 | 24 | /** 活动状态 */ 25 | public enum CampaignStatus { 26 | NOT_START, // 未开始 27 | IN_PROCESS, // 进行中 28 | END, // 已结束 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | ObjectMapper objectMapper = new ObjectMapper(); 34 | try { 35 | return objectMapper.writeValueAsString(this); 36 | } catch (JsonProcessingException e) { 37 | e.printStackTrace(); 38 | } 39 | return null; 40 | } 41 | 42 | public Long getId() { 43 | return id; 44 | } 45 | 46 | public void setId(Long id) { 47 | this.id = id; 48 | } 49 | 50 | public String getCode() { 51 | return code; 52 | } 53 | 54 | public void setCode(String code) { 55 | this.code = code; 56 | } 57 | 58 | public String getName() { 59 | return name; 60 | } 61 | 62 | public void setName(String name) { 63 | this.name = name; 64 | } 65 | 66 | public String getStatus() { 67 | return status; 68 | } 69 | 70 | public void setStatus(String status) { 71 | this.status = status; 72 | } 73 | 74 | public Date getStatusDate() { 75 | return statusDate; 76 | } 77 | 78 | public void setStatusDate(Date statusDate) { 79 | this.statusDate = statusDate; 80 | } 81 | 82 | public Date getStartDate() { 83 | return startDate; 84 | } 85 | 86 | public void setStartDate(Date startDate) { 87 | this.startDate = startDate; 88 | } 89 | 90 | public Date getEndDate() { 91 | return endDate; 92 | } 93 | 94 | public void setEndDate(Date endDate) { 95 | this.endDate = endDate; 96 | } 97 | 98 | public String getDesc() { 99 | return desc; 100 | } 101 | 102 | public void setDesc(String desc) { 103 | this.desc = desc; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/utils/CfConstants.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.utils; 2 | 3 | public class CfConstants { 4 | /** 票券有效期(天) */ 5 | public static final int CampainCouponExpireDays = 30; 6 | 7 | private static final String CONTEXT_IMPORTS = "import org.springframework.context.ApplicationContext;"; 8 | private static final String DAO_IMPORTS = "import jie.cf.core.dao.ICampaignDAO;" 9 | + "import jie.cf.core.dao.ICampaignEventDAO;" + "import jie.cf.core.dao.ICampaignRuleDAO;" 10 | + "jie.cf.core.dao.ICampaignCouponDAO;" + "jie.cf.core.dao.ICampaignCouponItemDAO;"; 11 | private static final String SERVICE_IMPORTS = "import jie.cf.core.service.CampaignExecution;" 12 | + "import jie.cf.core.utils.CfUtils;"; 13 | private static final String DTO_IMPORTS = "import jie.cf.core.dto.CampaignRequest;" 14 | + "import jie.cf.core.dto.CampaignResponse;" + "import jie.cf.core.dto.CampaignCouponItem;"; 15 | private static final String CONSTANTS_IMPORTS = "import jie.cf.core.utils.CfConstants;"; 16 | private static final String UTILS_IMPORTS = "import org.apache.commons.lang3.StringUtils;"; 17 | /** Groovy脚本import包 */ 18 | public static final String GROOVY_IMPORTS = CONTEXT_IMPORTS + DAO_IMPORTS + SERVICE_IMPORTS + DTO_IMPORTS 19 | + CONSTANTS_IMPORTS + UTILS_IMPORTS; 20 | 21 | // === 活动规则类型、活动响应状态 === 22 | /** 活动规则类型、活动响应状态:参与 */ 23 | public static final String PARTICIPATED = "PARTICIPATED"; 24 | /** 活动规则类型、活动响应状态:兑换票券 */ 25 | public static final String EXCHANGE = "EXCHANGE"; 26 | /** 活动规则类型、活动响应状态:派发票券 */ 27 | public static final String DISTRIBUTED = "DISTRIBUTED"; 28 | /** 活动规则类型、活动响应状态:使用票券 */ 29 | public static final String USED = "USED"; 30 | /** 活动规则类型、活动响应状态:微信朋友帮砍价活动 - 发起人 */ 31 | public static final String WX_INITIATE = "WX_INITIATE"; 32 | /** 活动规则类型、活动响应状态:微信朋友帮砍价活动 - 参与人 */ 33 | public static final String WX_PARTICIPATE = "WX_PARTICIPATE"; 34 | 35 | // === 解析参数类型 === 36 | /** 解析参数类型:派发票券 */ 37 | public static final int RESOLVE_TYPE_DISTRIBUTE = 2; 38 | /** 解析参数类型:微信“好友帮砍价” */ 39 | public static final int RESOLVE_TYPE_WX = 16; 40 | 41 | // === 解析参数 === 42 | /** 解析参数:票券池CODE */ 43 | public static final String RESOLVE_campaignCouponCode = "campaignCouponCode"; 44 | /** 解析参数:票券状态 */ 45 | public static final String RESOLVE_campaignCouponItemStatus = "campaignCouponItemStatus"; 46 | /** 解析参数:票券数量 */ 47 | public static final String RESOLVE_campaignCouponItemAmount = "campaignCouponItemAmount"; 48 | /** 解析参数:目标微信OpenId */ 49 | public static final String RESOLVE_targetOpenId = "targetOpenId"; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/demo/dao/DemoCampaignCouponItemDAO.java: -------------------------------------------------------------------------------- 1 | package jie.cf.demo.dao; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import jie.cf.core.dao.ICampaignCouponItemDAO; 11 | import jie.cf.core.dto.CampaignCouponItem; 12 | import jie.cf.core.dto.CampaignCouponItem.CampaignCouponItemStatus; 13 | import jie.cf.core.utils.stereotype.CfDemo; 14 | 15 | @CfDemo 16 | public class DemoCampaignCouponItemDAO implements ICampaignCouponItemDAO { 17 | private Map repo = new HashMap(); 18 | private static Long id = 1L; 19 | 20 | @Override 21 | public void saveOne(CampaignCouponItem campaignCouponItem) { 22 | if (campaignCouponItem.getId() == null) 23 | campaignCouponItem.setId(id++); 24 | String key = campaignCouponItem.getId() + ":" + campaignCouponItem.getCampaignEventId(); 25 | repo.put(key, campaignCouponItem); 26 | } 27 | 28 | @Override 29 | public CampaignCouponItem findOne(Long id) { 30 | for (String key : repo.keySet()) { 31 | String[] items = key.split(":"); 32 | if (items[0].equals(id.toString())) 33 | return (CampaignCouponItem) repo.get(key); 34 | } 35 | return null; 36 | } 37 | 38 | @Override 39 | public CampaignCouponItem findOneBySerialNumber(Long campaignEventId, String serialNumber) { 40 | CampaignCouponItem ret = null; 41 | for (String key : repo.keySet()) { 42 | String[] items = key.split(":"); 43 | if (items[1].equals(campaignEventId.toString())) { 44 | CampaignCouponItem tmp = (CampaignCouponItem) repo.get(key); 45 | if (StringUtils.equals(serialNumber, tmp.getSerialNumber())) 46 | return tmp; 47 | } 48 | } 49 | return ret; 50 | } 51 | 52 | @Override 53 | public List findByUser(Long clientId, String mobile) { 54 | List ret = new ArrayList(); 55 | for (String key : repo.keySet()) { 56 | CampaignCouponItem tmp = (CampaignCouponItem) repo.get(key); 57 | if (tmp.getClientId() == clientId || StringUtils.equals(mobile, tmp.getMobile())) 58 | ret.add(tmp); 59 | } 60 | return ret; 61 | } 62 | 63 | @Override 64 | public CampaignCouponItem findOneAvailable(Long campaignEventId) { 65 | CampaignCouponItem ret = null; 66 | for (String key : repo.keySet()) { 67 | String[] items = key.split(":"); 68 | if (items[1].equals(campaignEventId.toString())) { 69 | CampaignCouponItem tmp = (CampaignCouponItem) repo.get(key); 70 | if (StringUtils.equals(CampaignCouponItemStatus.AVAILABLE.toString(), tmp.getStatus())) 71 | return tmp; 72 | } 73 | } 74 | return ret; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | jie 7 | cf 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | cf 12 | Campaign Framework(营销活动框架) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.9.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.7 25 | 26 | 3.3.1 27 | 4.1 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-thymeleaf 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | test 45 | 46 | 47 | 48 | 49 | org.apache.commons 50 | commons-lang3 51 | ${apache.lang.version} 52 | 53 | 54 | org.apache.commons 55 | commons-collections4 56 | ${apache.collections.version} 57 | 58 | 59 | 60 | 61 | org.codehaus.groovy 62 | groovy 63 | 64 | 65 | org.codehaus.groovy 66 | groovy-jsr223 67 | 68 | 69 | 70 | 71 | 72 | org.json 73 | json 74 | 75 | 76 | 77 | 78 | 79 | 80 | org.springframework.boot 81 | spring-boot-maven-plugin 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/utils/CfUtils.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | import java.util.Map; 7 | 8 | import javax.script.Bindings; 9 | import javax.script.ScriptEngine; 10 | import javax.script.ScriptEngineManager; 11 | 12 | import org.apache.commons.lang3.time.DateUtils; 13 | import org.springframework.context.ApplicationContext; 14 | 15 | import jie.cf.core.utils.exception.CfException; 16 | 17 | public class CfUtils { 18 | /** 持有Spring应用上下文 */ 19 | private static ApplicationContext context; 20 | private static ScriptEngine engine = null; 21 | private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 22 | 23 | public static ApplicationContext getContext() { 24 | return context; 25 | } 26 | 27 | public static void setContext(ApplicationContext context) { 28 | CfUtils.context = context; 29 | } 30 | 31 | /** 32 | * 执行Groovy脚本 33 | * 34 | * @param script 35 | * @param param 36 | * @return 37 | * @throws Exception 38 | */ 39 | public static Object executeGroovyScript(String script, Map param) throws Exception { 40 | checkNotNull(script, "Groovy script can NOT be null"); 41 | // Groovy脚本import包 + 执行脚本 42 | String evalScript = CfConstants.GROOVY_IMPORTS + script; 43 | 44 | ScriptEngine engine = groovyScriptEngine(); 45 | Bindings bindings = engine.createBindings(); 46 | bindings.putAll(param); 47 | return engine.eval(evalScript, bindings); 48 | } 49 | 50 | /** 51 | * 获取ScriptEngine 52 | * 53 | * @return ScriptEngine 54 | */ 55 | private static ScriptEngine groovyScriptEngine() { 56 | if (engine == null) { 57 | ScriptEngineManager engineManager = new ScriptEngineManager(); 58 | engine = engineManager.getEngineByName("groovy"); 59 | } 60 | return engine; 61 | } 62 | 63 | /** 64 | * checkNotNull 65 | * 66 | * @param reference 67 | * @param errorMessage 68 | * @throws CfException 69 | */ 70 | public static T checkNotNull(T reference, String errorMessage) throws CfException { 71 | if (reference == null) 72 | throw new CfException(errorMessage); 73 | return reference; 74 | } 75 | 76 | public static Date convert2Date(Object rhs) throws Exception { 77 | if (rhs == null) 78 | return null; 79 | return sdf.parse(rhs.toString()); 80 | } 81 | 82 | /** 83 | * 判断是否在指定有效期内 84 | * 85 | * @param startDate 86 | * @param endDate 87 | * @return false - 已过期 88 | */ 89 | public static boolean hasNotExpired(Date startDate, Date endDate) { 90 | Date now = new Date(); 91 | return (DateUtils.truncatedCompareTo(startDate, now, Calendar.SECOND) < 0) 92 | && (DateUtils.truncatedCompareTo(now, endDate, Calendar.SECOND) < 0); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/service/CfCampaignSvs.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.service; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Component; 6 | 7 | import jie.cf.core.api.ICfCampaignSvs; 8 | import jie.cf.core.dto.CampaignRequest; 9 | import jie.cf.core.utils.CfConstants; 10 | import jie.cf.core.utils.CfUtils; 11 | import jie.cf.core.utils.exception.CfException; 12 | 13 | @Component 14 | public class CfCampaignSvs implements ICfCampaignSvs { 15 | @Autowired 16 | private CampaignExecution campaignExecution; 17 | 18 | @Override 19 | public void campaignEvent(CampaignRequest campaignRequest) throws CfException { 20 | // 校验参加活动事件的请求 21 | checkCampaignRequest(campaignRequest); 22 | campaignExecution.campaignEvent(campaignRequest); 23 | } 24 | 25 | @Override 26 | public void distribute(CampaignRequest campaignRequest) throws CfException { 27 | // 校验兑换票券的请求 28 | checkCampaignRequest(campaignRequest); 29 | campaignExecution.distributeCampaignCoupon(campaignRequest); 30 | } 31 | 32 | @Override 33 | public void use(CampaignRequest campaignRequest) throws CfException { 34 | // 校验使用票券的请求 35 | String serialNumber = campaignRequest.getSerialNumber(); 36 | CfUtils.checkNotNull(serialNumber, "serialNumber can NOT be NULL"); 37 | // 参加使用票券的活动事件 38 | campaignRequest.setRuleType(CfConstants.USED); 39 | campaignEvent(campaignRequest); 40 | } 41 | 42 | @Override 43 | public void exchange(CampaignRequest campaignRequest) throws CfException { 44 | // 校验兑换票券的请求 45 | String serialNumber = campaignRequest.getSerialNumber(); 46 | CfUtils.checkNotNull(serialNumber, "serialNumber can NOT be NULL"); 47 | // 参加兑换票券的活动事件 48 | campaignRequest.setRuleType(CfConstants.EXCHANGE); 49 | campaignEvent(campaignRequest); 50 | } 51 | 52 | @Override 53 | public void wechatCampaignEvent(CampaignRequest campaignRequest) throws CfException { 54 | // 校验参加微信“好友帮砍价”活动事件的请求 55 | CfUtils.checkNotNull(campaignRequest.getOpenId(), "wechat openId can NOT be null"); 56 | String campaignRuleType = campaignRequest.getRuleType(); 57 | if (!StringUtils.equalsIgnoreCase(CfConstants.WX_INITIATE, campaignRuleType) 58 | && !StringUtils.equalsIgnoreCase(CfConstants.WX_PARTICIPATE, campaignRuleType)) 59 | throw new CfException("WRONG campaignRuleType: " + campaignRuleType); 60 | // 参加微信“好友帮砍价”活动事件 61 | campaignEvent(campaignRequest); 62 | } 63 | 64 | /** 65 | * 校验活动请求 66 | * 67 | * @param campaignRequest 68 | * @throws CfException 69 | */ 70 | private void checkCampaignRequest(CampaignRequest campaignRequest) throws CfException { 71 | CfUtils.checkNotNull(campaignRequest, "campaignRequest can NOT be null"); 72 | if ((campaignRequest.getClientId() == null) && StringUtils.isEmpty(campaignRequest.getMobile()) 73 | && StringUtils.isEmpty(campaignRequest.getOpenId())) 74 | throw new CfException("clientId, mobile, openId can NOT be ALL null"); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dto/CampaignCoupon.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dto; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | /** 9 | * 票券池 10 | * 11 | * @author Jie 12 | * 13 | */ 14 | public class CampaignCoupon { 15 | private Long id; 16 | private Long campaignId; // 活动ID 17 | private Long campaignEventId; // 活动事件ID 18 | private String code; // 票券池CODE。UNIQUE 19 | private String name; // 票券池名 20 | private String type; // 票券类型 21 | private String desc; // 描述 22 | private Long totalAmount; // 总票券数 23 | private Long availableAmount; // 可用票券数 24 | private Date startDate; // 票券默认开始时间 25 | private Date endDate; // 票券默认结束时间 26 | 27 | /** 票券类型 */ 28 | public enum CampaignCouponType { 29 | COUPON, // 内部票券 30 | TICKET, // 外部票券(如电影票)兑换码 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | ObjectMapper objectMapper = new ObjectMapper(); 36 | try { 37 | return objectMapper.writeValueAsString(this); 38 | } catch (JsonProcessingException e) { 39 | e.printStackTrace(); 40 | } 41 | return null; 42 | } 43 | 44 | public Long getId() { 45 | return id; 46 | } 47 | 48 | public void setId(Long id) { 49 | this.id = id; 50 | } 51 | 52 | public Long getCampaignId() { 53 | return campaignId; 54 | } 55 | 56 | public void setCampaignId(Long campaignId) { 57 | this.campaignId = campaignId; 58 | } 59 | 60 | public Long getCampaignEventId() { 61 | return campaignEventId; 62 | } 63 | 64 | public void setCampaignEventId(Long campaignEventId) { 65 | this.campaignEventId = campaignEventId; 66 | } 67 | 68 | public String getCode() { 69 | return code; 70 | } 71 | 72 | public void setCode(String code) { 73 | this.code = code; 74 | } 75 | 76 | public String getName() { 77 | return name; 78 | } 79 | 80 | public void setName(String name) { 81 | this.name = name; 82 | } 83 | 84 | public String getType() { 85 | return type; 86 | } 87 | 88 | public void setType(String type) { 89 | this.type = type; 90 | } 91 | 92 | public String getDesc() { 93 | return desc; 94 | } 95 | 96 | public void setDesc(String desc) { 97 | this.desc = desc; 98 | } 99 | 100 | public Long getTotalAmount() { 101 | return totalAmount; 102 | } 103 | 104 | public void setTotalAmount(Long totalAmount) { 105 | this.totalAmount = totalAmount; 106 | } 107 | 108 | public Long getAvailableAmount() { 109 | return availableAmount; 110 | } 111 | 112 | public void setAvailableAmount(Long availableAmount) { 113 | this.availableAmount = availableAmount; 114 | } 115 | 116 | public Date getStartDate() { 117 | return startDate; 118 | } 119 | 120 | public void setStartDate(Date startDate) { 121 | this.startDate = startDate; 122 | } 123 | 124 | public Date getEndDate() { 125 | return endDate; 126 | } 127 | 128 | public void setEndDate(Date endDate) { 129 | this.endDate = endDate; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dto/CampaignResponse.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dto; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | import jie.cf.core.utils.CfConstants; 9 | 10 | /** 11 | * 活动响应 12 | * 13 | * @author Jie 14 | * 15 | */ 16 | public class CampaignResponse { 17 | private Long id; 18 | private Long campaignId; // 活动ID 19 | private Long campaignEventId; // 活动事件ID 20 | private Long campaignRuleId; // 活动规则ID 21 | private Long clientId; // 客户ID 22 | private String mobile; // 手机号 23 | private String openId; // 微信openId 24 | private String status; // 活动响应状态。同CampaignRule.type 25 | private Date statusDate; // 活动响应状态的更新时间 26 | private String reserved; // 预留。建议JSON 27 | 28 | public CampaignResponse() { 29 | } 30 | 31 | public CampaignResponse(CampaignRequest campaignRequest) { 32 | clientId = campaignRequest.getClientId(); 33 | mobile = campaignRequest.getMobile(); 34 | openId = campaignRequest.getOpenId(); 35 | status = CfConstants.PARTICIPATED; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | ObjectMapper objectMapper = new ObjectMapper(); 41 | try { 42 | return objectMapper.writeValueAsString(this); 43 | } catch (JsonProcessingException e) { 44 | e.printStackTrace(); 45 | } 46 | return null; 47 | } 48 | 49 | public Long getId() { 50 | return id; 51 | } 52 | 53 | public void setId(Long id) { 54 | this.id = id; 55 | } 56 | 57 | public Long getCampaignId() { 58 | return campaignId; 59 | } 60 | 61 | public void setCampaignId(Long campaignId) { 62 | this.campaignId = campaignId; 63 | } 64 | 65 | public Long getCampaignEventId() { 66 | return campaignEventId; 67 | } 68 | 69 | public void setCampaignEventId(Long campaignEventId) { 70 | this.campaignEventId = campaignEventId; 71 | } 72 | 73 | public Long getCampaignRuleId() { 74 | return campaignRuleId; 75 | } 76 | 77 | public void setCampaignRuleId(Long campaignRuleId) { 78 | this.campaignRuleId = campaignRuleId; 79 | } 80 | 81 | public Long getClientId() { 82 | return clientId; 83 | } 84 | 85 | public void setClientId(Long clientId) { 86 | this.clientId = clientId; 87 | } 88 | 89 | public String getMobile() { 90 | return mobile; 91 | } 92 | 93 | public void setMobile(String mobile) { 94 | this.mobile = mobile; 95 | } 96 | 97 | public String getOpenId() { 98 | return openId; 99 | } 100 | 101 | public void setOpenId(String openId) { 102 | this.openId = openId; 103 | } 104 | 105 | public String getStatus() { 106 | return status; 107 | } 108 | 109 | public void setStatus(String status) { 110 | this.status = status; 111 | } 112 | 113 | public Date getStatusDate() { 114 | return statusDate; 115 | } 116 | 117 | public void setStatusDate(Date statusDate) { 118 | this.statusDate = statusDate; 119 | } 120 | 121 | public String getReserved() { 122 | return reserved; 123 | } 124 | 125 | public void setReserved(String reserved) { 126 | this.reserved = reserved; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/dto/CampaignCouponItem.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.dto; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | /** 9 | * 票券 10 | * 11 | * @author Jie 12 | * 13 | */ 14 | public class CampaignCouponItem { 15 | private Long id; 16 | private Long campaignId; // 活动ID 17 | private Long campaignEventId; // 活动事件ID 18 | private Long campaignCouponId; // 票券池ID 19 | private String type; // 票券类型。同CampaignCoupon.type 20 | private String serialNumber; // 票券序列号 21 | private String status; // 票券状态 22 | private Date statusDate; // 票券状态的更新时间 23 | private String desc; // 描述 24 | private Long clientId; // 客户ID 25 | private String mobile; // 手机号 26 | private String openId; // 微信openId 27 | private Date bindingDate; // 票券绑定到用户的时间 28 | private Date startDate; // 票券开始时间 29 | private Date endDate; // 票券结束时间 30 | 31 | public CampaignCouponItem() { 32 | } 33 | 34 | public CampaignCouponItem(CampaignCoupon campaignCoupon) { 35 | campaignId = campaignCoupon.getCampaignId(); 36 | campaignEventId = campaignCoupon.getCampaignEventId(); 37 | campaignCouponId = campaignCoupon.getId(); 38 | type = campaignCoupon.getType(); 39 | desc = campaignCoupon.getDesc(); 40 | startDate = campaignCoupon.getStartDate(); 41 | endDate = campaignCoupon.getEndDate(); 42 | status = CampaignCouponItemStatus.AVAILABLE.toString(); 43 | } 44 | 45 | /** 票券状态 */ 46 | public enum CampaignCouponItemStatus { 47 | AVAILABLE, // 可用 48 | PREPARED, // 待派发 49 | DISTRIBUTED, // 已派发 50 | USED, // 已使用 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | ObjectMapper objectMapper = new ObjectMapper(); 56 | try { 57 | return objectMapper.writeValueAsString(this); 58 | } catch (JsonProcessingException e) { 59 | e.printStackTrace(); 60 | } 61 | return null; 62 | } 63 | 64 | public Long getId() { 65 | return id; 66 | } 67 | 68 | public void setId(Long id) { 69 | this.id = id; 70 | } 71 | 72 | public Long getCampaignId() { 73 | return campaignId; 74 | } 75 | 76 | public void setCampaignId(Long campaignId) { 77 | this.campaignId = campaignId; 78 | } 79 | 80 | public Long getCampaignEventId() { 81 | return campaignEventId; 82 | } 83 | 84 | public void setCampaignEventId(Long campaignEventId) { 85 | this.campaignEventId = campaignEventId; 86 | } 87 | 88 | public Long getCampaignCouponId() { 89 | return campaignCouponId; 90 | } 91 | 92 | public void setCampaignCouponId(Long campaignCouponId) { 93 | this.campaignCouponId = campaignCouponId; 94 | } 95 | 96 | public String getType() { 97 | return type; 98 | } 99 | 100 | public void setType(String type) { 101 | this.type = type; 102 | } 103 | 104 | public String getSerialNumber() { 105 | return serialNumber; 106 | } 107 | 108 | public void setSerialNumber(String serialNumber) { 109 | this.serialNumber = serialNumber; 110 | } 111 | 112 | public String getStatus() { 113 | return status; 114 | } 115 | 116 | public void setStatus(String status) { 117 | this.status = status; 118 | } 119 | 120 | public Date getStatusDate() { 121 | return statusDate; 122 | } 123 | 124 | public void setStatusDate(Date statusDate) { 125 | this.statusDate = statusDate; 126 | } 127 | 128 | public String getDesc() { 129 | return desc; 130 | } 131 | 132 | public void setDesc(String desc) { 133 | this.desc = desc; 134 | } 135 | 136 | public Long getClientId() { 137 | return clientId; 138 | } 139 | 140 | public void setClientId(Long clientId) { 141 | this.clientId = clientId; 142 | } 143 | 144 | public String getMobile() { 145 | return mobile; 146 | } 147 | 148 | public void setMobile(String mobile) { 149 | this.mobile = mobile; 150 | } 151 | 152 | public String getOpenId() { 153 | return openId; 154 | } 155 | 156 | public void setOpenId(String openId) { 157 | this.openId = openId; 158 | } 159 | 160 | public Date getBindingDate() { 161 | return bindingDate; 162 | } 163 | 164 | public void setBindingDate(Date bindingDate) { 165 | this.bindingDate = bindingDate; 166 | } 167 | 168 | public Date getStartDate() { 169 | return startDate; 170 | } 171 | 172 | public void setStartDate(Date startDate) { 173 | this.startDate = startDate; 174 | } 175 | 176 | public Date getEndDate() { 177 | return endDate; 178 | } 179 | 180 | public void setEndDate(Date endDate) { 181 | this.endDate = endDate; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CF - Campaign Framework, 营销活动框架 2 | (Jie © 2017)
3 | ## 1 分层架构设计 4 | CF(营销活动框架,Campaign Framework)基于模板方法和策略模式设计。将“变化的部分”通过Groovy脚本来实现并与业务代码解耦合,脚本通过Groovy Bindings可使用CF框架基础服务bean、获取业务数据(**CampaignRequest**)、修改活动响应数据(**CampaignResponse**)等。
5 | ![CF分层架构](/dev-book/uml/Architecture.png)
6 | ### 1.1 时序图 7 | ![时序图](/dev-book/uml/SequenceDiagram.png)
8 | ### 1.2 Quick start 9 | Spring Boot App - Run
10 | http://localhost:8080/ 执行Groovy脚本 11 | #### 1.2.1 参加活动 12 | campaignApi.campaignEvent(campaignRequest); 13 | > campaignRequest参数中campaignEventCode必填;clientId, mobile, openId不能都为null 14 | 15 | GroovyCondition 16 | ``` 17 | // 先判断用户是否满足活动条件 18 | if (!活动条件) 19 | throw new Exception("不满足活动条件!"); 20 | 21 | // 统计用户参加活动的次数 22 | if (campaignExecution.countCampaignResponse(campaignRequest) > 0) 23 | throw new Exception("你已经参加过活动了!"); 24 | 25 | return true; 26 | ``` 27 | GroovyBody 28 | ``` 29 | // 修改活动响应数据 30 | campaignResponse.setStatus("PARTICIPATED"); 31 | ``` 32 | #### 1.2.2 使用票券 33 | campaignApi.use(campaignRequest); 34 | > campaignRequest参数中campaignEventCode, serialNumber必填;clientId, mobile, openId不能都为null 35 | 36 | GroovyCondition 37 | ``` 38 | // 获取对应的票券对象 39 | CampaignCouponItem campaignCouponItem = campaignCouponItemDAO.findOneBySerialNumber(campaignEventDAO.findId(campaignEventCode), serialNumber); 40 | // 校验票券 41 | if (!StringUtils.equalsIgnoreCase(campaignCouponItem.getStatus(), "DISTRIBUTED")) 42 | throw new Exception("serialNumber = " + serialNumber + " is NOT abailable"); 43 | if (!StringUtils.equals (campaignCouponItem.getMobile(), mobile)) 44 | throw new Exception("serialNumber = " + serialNumber + "所有者信息不匹配!"); 45 | return true; 46 | ``` 47 | GroovyBody 48 | ``` 49 | // 获取对应的票券对象 50 | CampaignCouponItem campaignCouponItem = campaignCouponItemDAO.findOneBySerialNumber(campaignEventDAO.findId(campaignEventCode), serialNumber); 51 | // 使用票券,修改活动响应数据 52 | campaignExecution.bindCampaignCoupon(campaignCouponItem, campaignRequest); 53 | campaignResponse.setStatus("USED"); 54 | ``` 55 | #### 1.2.3 兑换票券 56 | campaignApi.exchange(campaignRequest); 57 | > campaignRequest参数中campaignEventCode, serialNumber必填;reserved字段转JSON = {"campaignCouponCode":"必填","campaignCouponItemStatus":"选填","campaignCouponItemAmount":"选填"};clientId, mobile, openId不能都为null 58 | 59 | GroovyCondition 60 | ``` 61 | // 获取对应的票券对象 62 | CampaignCouponItem campaignCouponItem = campaignCouponItemDAO.findOneBySerialNumber(campaignEventDAO.findId(campaignEventCode), serialNumber); 63 | // 校验票券 64 | if (!StringUtils.equalsIgnoreCase(campaignCouponItem.getStatus(), "AVAILABLE")) 65 | throw new Exception("serialNumber = " + serialNumber + " is NOT abailable"); 66 | return true; 67 | ``` 68 | GroovyBody 69 | ``` 70 | // 获取对应的票券对象 71 | CampaignCouponItem campaignCouponItem = campaignCouponItemDAO.findOneBySerialNumber(campaignEventDAO.findId(campaignEventCode), serialNumber); 72 | // 使用兑换码 73 | campaignExecution.bindCampaignCoupon(campaignCouponItem, campaignRequest); 74 | // 派发指定数目的票券,修改活动响应数据 75 | campaignExecution.distributeCampaignCoupon(campaignRequest); 76 | campaignResponse.setStatus("EXCHANGE"); 77 | 78 | ``` 79 | #### 1.2.4 微信“好友帮砍价”活动 80 | campaignApi.wechatCampaignEvent(campaignRequest); 81 | > campaignRequest参数中campaignEventCode, openId, ruleType必填;参与人reserved字段转JSON = {"targetOpenId":"发起人OpenId"} 82 | 83 | GroovyCondition 84 | ``` 85 | // 先判断用户是否满足活动条件 86 | if (campaignExecution.hasParticipatedWxCampaign(campaignRequest)) 87 | throw new Exception("你已经参加过微信组团活动了!"); 88 | return true; 89 | ``` 90 | GroovyBody 91 | ``` 92 | // 修改活动响应数据 93 | if (StringUtils.equalsIgnoreCase(campaignRequest.getRuleType(), CfConstants.WX_INITIATE)) { 94 | // 发起人 95 | campaignResponse.setStatus(CfConstants.WX_INITIATE); 96 | } else if (StringUtils.equalsIgnoreCase(campaignRequest.getRuleType(), CfConstants.WX_PARTICIPATE)) { 97 | // 参与人 98 | campaignResponse.setReserved(campaignExecution.resolveCampaignRequest(campaignRequest, CfConstants.RESOLVE_TYPE_WX).get(CfConstants.RESOLVE_targetOpenId)); 99 | campaignResponse.setStatus(CfConstants.WX_PARTICIPATE); 100 | } 101 | ``` 102 | ## 2 CfCampaignSvs服务 103 | CfCampaignSvs服务负责校验活动请求、读取查询并执行活动规则等。若执行成功,则记录一条活动响应;否则抛异常。 104 | ### 2.1 Campaign类:活动 105 | 域 | 描述 | 备注 106 | ---|---|--- 107 | code | 活动CODE | 活动CODE 108 | name | 活动名 | 109 | status | 活动状态 | **NOT_START** 未开始 **IN_PROCESS** 进行中 **END** 已结束 110 | statusDate | 活动状态的更新时间 | 111 | startDate | 活动开始时间 | 活动的有效期 112 | endDate | 活动结束时间 | 113 | desc | 描述 | 114 | 115 | ### 2.2 CampaignEvent类:活动事件 116 | 域 | 描述 | 备注 117 | ---|---|--- 118 | campaignId | 活动ID | 活动CODE 119 | code | 活动事件CODE | UNIQUE 120 | desc | 描述 | 121 | 122 | ### 2.3 CampaignRule类:活动规则 123 | 域 | 描述 | 备注 124 | ---|---|--- 125 | campaignId | campaignId | 126 | campaignEventId | 活动事件ID | 127 | name | 活动规则名 | 128 | type | 活动规则类型 | **PARTICIPATED** 参与 **EXCHANGE** 兑换票券 **DISTRIBUTED** 派发票券 **USED** 使用票券 129 | groovyCondition | 判断条件 | Groovy脚本 130 | groovyBody | 执行代码 | Groovy脚本 131 | startDate | 活动规则开始时间 | 132 | endDate | 活动规则结束时间 | 133 | 134 | ### 2.4 CampaignResponse类:活动响应 135 | 域 | 描述 | 备注 136 | ---|---|--- 137 | campaignId | 活动ID | 138 | campaignEventId | 活动事件ID | 139 | campaignRuleId | 活动规则ID | 140 | clientId | 客户ID | 141 | mobile | 手机号 | 142 | openId | 微信openId | 143 | status | 活动响应状态 | 同CampaignRule.type 144 | statusDate | 活动响应状态的更新时间 | 145 | reserved | 预留 | 建议JSON 146 | 147 | ### 2.5 CampaignCoupon类:票券池 148 | 域 | 描述 | 备注 149 | ---|---|--- 150 | | | 151 | campaignId | 活动ID | 152 | campaignEventId | 活动事件ID | 153 | code | 票券池CODE | UNIQUE 154 | name | 票券池名 | 155 | type | 票券类型 | **COUPON** 内部票券 **TICKET** 外部票券(如电影票)兑换码 156 | desc | 描述 | 157 | totalAmount | 总票券数 | 158 | availableAmount | 可用票券数 | 159 | startDate | 票券默认开始时间 | 160 | endDate | 票券默认结束时间 | 161 | 162 | ### 2.6 CampaignCouponItem类:票券 163 | 域 | 描述 | 备注 164 | ---|---|--- 165 | | | 166 | campaignId | 活动ID | 167 | campaignEventId | 活动事件ID | 168 | campaignCouponId | 票券池ID | 169 | type | 票券类型 | 同CampaignCoupon.type 170 | serialNumber | 票券序列号 | 171 | status | 票券状态 | **AVAILABLE** 可用 **PREPARED** 待派发 **DISTRIBUTED** 已派发 **USED** 已使用 172 | statusDate | 票券状态的更新时间 | 173 | desc | 描述 | 174 | clientId | 客户ID | 175 | mobile | 手机号 | 176 | openId | 微信openId | 177 | bindingDate | 票券绑定到用户的时间 | 178 | startDate | 票券开始时间 | 179 | endDate | 票券结束时间 | 180 | 181 | ## 3 附录 182 | ### 3.1 Groovy Bindings 183 | 变量名 | 变量类型 | 备注 184 | ---|---|--- 185 | context | ApplicationContext | Spring应用上下文 bean 186 | campaignDAO | ICampaignDAO | 活动DAO bean 187 | campaignEventDAO | ICampaignEventDAO | 活动事件DAO bean 188 | campaignRuleDAO | ICampaignRuleDAO | 活动规则DAO bean 189 | campaignCouponDAO | ICampaignCouponDAO | 活动票券池DAO bean 190 | campaignCouponItemDAO | ICampaignCouponItemDAO | 活动票券DAO bean 191 | campaignExecution | CampaignExecution | 活动常用服务bean 192 | campaignRequest | CampaignRequest | 活动请求的参数 193 | campaignEventCode | String | 活动事件CODE 194 | clientId | Long | 客户ID 195 | mobile | String | 手机号 196 | serialNumber | String | 兑换码 197 | campaignResponse | CampaignResponse | 活动响应 198 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/demo/Demo.java: -------------------------------------------------------------------------------- 1 | package jie.cf.demo; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.List; 6 | import java.util.UUID; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.apache.commons.lang3.time.DateUtils; 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | 14 | import jie.cf.core.api.ICfCampaignSvs; 15 | import jie.cf.core.dao.ICampaignCouponDAO; 16 | import jie.cf.core.dao.ICampaignCouponItemDAO; 17 | import jie.cf.core.dao.ICampaignDAO; 18 | import jie.cf.core.dao.ICampaignEventDAO; 19 | import jie.cf.core.dao.ICampaignResponseDAO; 20 | import jie.cf.core.dao.ICampaignRuleDAO; 21 | import jie.cf.core.dto.Campaign; 22 | import jie.cf.core.dto.Campaign.CampaignStatus; 23 | import jie.cf.core.dto.CampaignCoupon; 24 | import jie.cf.core.dto.CampaignCoupon.CampaignCouponType; 25 | import jie.cf.core.dto.CampaignCouponItem; 26 | import jie.cf.core.dto.CampaignCouponItem.CampaignCouponItemStatus; 27 | import jie.cf.core.dto.CampaignEvent; 28 | import jie.cf.core.dto.CampaignRequest; 29 | import jie.cf.core.dto.CampaignResponse; 30 | import jie.cf.core.dto.CampaignRule; 31 | import jie.cf.core.utils.CfConstants; 32 | import jie.cf.core.utils.exception.CfException; 33 | import jie.cf.core.utils.stereotype.CfDemo; 34 | 35 | @CfDemo 36 | public class Demo { 37 | @Autowired 38 | private ICampaignDAO campaignDAO; 39 | @Autowired 40 | private ICampaignEventDAO campaignEventDAO; 41 | @Autowired 42 | private ICampaignRuleDAO campaignRuleDAO; 43 | @Autowired 44 | private ICampaignCouponDAO campaignCouponDAO; 45 | @Autowired 46 | private ICampaignCouponItemDAO campaignCouponItemDAO; 47 | @Autowired 48 | private ICampaignResponseDAO campaignResponseDAO; 49 | @Autowired 50 | private ICfCampaignSvs campaignSvs; 51 | 52 | /** 参加活动 */ 53 | public List campaign() throws CfException { 54 | CampaignRequest campaignRequest = createCampaignRequest(); 55 | campaignSvs.campaignEvent(campaignRequest); 56 | return response(campaignRequest); 57 | } 58 | 59 | /** 派发票券 */ 60 | public List distribute() throws CfException, JSONException { 61 | CampaignRequest campaignRequest = createCampaignRequest(); 62 | // reserved参数 63 | JSONObject jsonObject = new JSONObject(); 64 | jsonObject.put(CfConstants.RESOLVE_campaignCouponCode, "COUPON"); 65 | jsonObject.put(CfConstants.RESOLVE_campaignCouponItemAmount, "2"); 66 | campaignRequest.setReserved(jsonObject.toString()); 67 | campaignSvs.distribute(campaignRequest); 68 | return campaignCouponItemDAO.findByUser(campaignRequest.getClientId(), campaignRequest.getMobile()); 69 | } 70 | 71 | /** 使用票券 */ 72 | public List use() throws CfException { 73 | CampaignRequest campaignRequest = createCampaignRequest(); 74 | // 使用DISTRIBUTED票券 75 | for (CampaignCouponItem coupon : campaignCouponItemDAO.findByUser(campaignRequest.getClientId(), 76 | campaignRequest.getMobile())) 77 | if (StringUtils.equals(CampaignCouponItemStatus.DISTRIBUTED.toString(), coupon.getStatus())) 78 | campaignRequest.setSerialNumber(coupon.getSerialNumber()); 79 | campaignSvs.use(campaignRequest); 80 | return response(campaignRequest); 81 | } 82 | 83 | /** 兑换票券 */ 84 | public List exchange() throws CfException, JSONException { 85 | CampaignRequest campaignRequest = createCampaignRequest(); 86 | // 使用AVAILABLE票券 87 | CampaignCouponItem coupon = campaignCouponItemDAO 88 | .findOneAvailable(campaignEventDAO.findId(campaignRequest.getCampaignEventCode())); 89 | campaignRequest.setSerialNumber(coupon.getSerialNumber()); 90 | JSONObject jsonObject = new JSONObject(); 91 | jsonObject.put(CfConstants.RESOLVE_campaignCouponCode, "COUPON"); 92 | jsonObject.put(CfConstants.RESOLVE_campaignCouponItemAmount, "2"); 93 | campaignRequest.setReserved(jsonObject.toString()); 94 | campaignSvs.exchange(campaignRequest); 95 | return response(campaignRequest); 96 | } 97 | 98 | /** 微信“好友帮砍价”- 发起 */ 99 | public List wechatCampaignInitiate() throws CfException { 100 | CampaignRequest campaignRequest = createCampaignRequest(); 101 | campaignRequest.setRuleType(CfConstants.WX_INITIATE); 102 | campaignRequest.setOpenId("wx10086"); 103 | campaignSvs.wechatCampaignEvent(campaignRequest); 104 | return response(campaignRequest); 105 | } 106 | 107 | /** 微信“好友帮砍价”- 参与 */ 108 | public List wechatCampaignParticipate() throws CfException, JSONException { 109 | CampaignRequest campaignRequest = createCampaignRequest(); 110 | campaignRequest.setRuleType(CfConstants.WX_PARTICIPATE); 111 | campaignRequest.setOpenId("wx10010"); 112 | JSONObject jsonObject = new JSONObject(); 113 | jsonObject.put(CfConstants.RESOLVE_targetOpenId, "wx10086"); 114 | campaignRequest.setReserved(jsonObject.toString()); 115 | campaignSvs.wechatCampaignEvent(campaignRequest); 116 | return response(campaignRequest); 117 | } 118 | 119 | private List response(CampaignRequest campaignRequest) { 120 | return campaignResponseDAO.findByCampaignRequest( 121 | campaignEventDAO.findId(campaignRequest.getCampaignEventCode()), campaignRequest.getClientId(), 122 | campaignRequest.getMobile()); 123 | } 124 | 125 | /** 活动请求数据准备 */ 126 | public static CampaignRequest createCampaignRequest() { 127 | CampaignRequest ret = new CampaignRequest(); 128 | ret.setCampaignEventCode("CampaignEventCode"); 129 | ret.setClientId(10086L); 130 | ret.setMobile("13888888888"); 131 | return ret; 132 | } 133 | 134 | /** 测试数据准备,写入数据库 */ 135 | public void createData() { 136 | // 活动数据 137 | Campaign campaign = createCampaign(); 138 | campaignDAO.saveOne(campaign); 139 | Long campaignId = campaignDAO.findId(campaign.getCode()); 140 | // 活动事件数据 141 | CampaignEvent campaignEvent = createCampaignEvent(campaignId); 142 | campaignEventDAO.saveOne(campaignEvent); 143 | Long campaignEventId = campaignEventDAO.findId(campaignEvent.getCode()); 144 | // 活动规则数据 145 | CampaignRule campaignRuleParticipated = createCampaignRuleParticipated(campaignEventId); 146 | campaignRuleDAO.saveOne(campaignRuleParticipated); 147 | CampaignRule campaignRuleUse = createCampaignRuleUse(campaignEventId); 148 | campaignRuleDAO.saveOne(campaignRuleUse); 149 | CampaignRule campaignRuleExchange = createCampaignRuleExchange(campaignEventId); 150 | campaignRuleDAO.saveOne(campaignRuleExchange); 151 | CampaignRule campaignRuleWxInitiate = createCampaignRuleWx(campaignEventId); 152 | campaignRuleWxInitiate.setType(CfConstants.WX_INITIATE); 153 | campaignRuleDAO.saveOne(campaignRuleWxInitiate); 154 | CampaignRule campaignRuleWxParticipate = createCampaignRuleWx(campaignEventId); 155 | campaignRuleWxParticipate.setType(CfConstants.WX_PARTICIPATE); 156 | campaignRuleDAO.saveOne(campaignRuleWxParticipate); 157 | // 活动票券池数据 158 | CampaignCoupon compaignCoupon = createCampaignCoupon(campaignId, campaignEventId); 159 | campaignCouponDAO.saveOne(compaignCoupon); 160 | CampaignCoupon savedCampaignCoupon = campaignCouponDAO.findByParentId(campaignId).get(0); 161 | // 活动票券数据 162 | List campaignCouponItemList = createCampaignCouponItem(savedCampaignCoupon); 163 | for (CampaignCouponItem campaignCouponItem : campaignCouponItemList) 164 | campaignCouponItemDAO.saveOne(campaignCouponItem); 165 | } 166 | 167 | private static Campaign createCampaign() { 168 | Campaign ret = new Campaign(); 169 | Date now = new Date(); 170 | ret.setCode("CampaignCode"); 171 | ret.setName("活动"); 172 | ret.setStatus(CampaignStatus.IN_PROCESS.toString()); 173 | ret.setStatusDate(now); 174 | ret.setStartDate(now); 175 | ret.setEndDate(DateUtils.addDays(now, 7)); 176 | ret.setDesc("活动"); 177 | return ret; 178 | } 179 | 180 | private static CampaignEvent createCampaignEvent(Long campaignId) { 181 | CampaignEvent ret = new CampaignEvent(); 182 | ret.setCampaignId(campaignId); 183 | ret.setCode("CampaignEventCode"); 184 | ret.setDesc("活动事件"); 185 | return ret; 186 | } 187 | 188 | private static CampaignRule createCampaignRuleParticipated(Long campaignEventId) { 189 | String PARTICIPATED_Condition = "if (campaignExecution.countCampaignResponse(campaignRequest) > 0) throw new Exception(\"你已经参加过活动了!\"); return true;"; 190 | String PARTICIPATED_Body = "campaignResponse.setStatus(\"PARTICIPATED\");"; 191 | 192 | CampaignRule ret = new CampaignRule(); 193 | Date now = new Date(); 194 | ret.setCampaignEventId(campaignEventId); 195 | ret.setType(CfConstants.PARTICIPATED); 196 | ret.setGroovyCondition(PARTICIPATED_Condition); 197 | ret.setGroovyBody(PARTICIPATED_Body); 198 | ret.setStartDate(now); 199 | ret.setEndDate(DateUtils.addDays(now, 7)); 200 | return ret; 201 | } 202 | 203 | private static CampaignRule createCampaignRuleUse(Long campaignEventId) { 204 | String EXCHANGE_Condition = "CampaignCouponItem campaignCouponItem = campaignCouponItemDAO.findOneBySerialNumber(campaignEventDAO.findId(campaignEventCode), serialNumber); if (!StringUtils.equalsIgnoreCase(campaignCouponItem.getStatus(), \"DISTRIBUTED\")) throw new Exception(\"serialNumber = \" + serialNumber + \" is NOT abailable\"); if (!StringUtils.equals (campaignCouponItem.getMobile(), mobile)) throw new Exception(\"serialNumber = \" + serialNumber + \"所有者信息不匹配!\"); return true;"; 205 | String EXCHANGE_Body = "CampaignCouponItem campaignCouponItem = campaignCouponItemDAO.findOneBySerialNumber(campaignEventDAO.findId(campaignEventCode), serialNumber); campaignExecution.bindCampaignCoupon(campaignCouponItem, campaignRequest); campaignResponse.setStatus(\"USED\");"; 206 | 207 | CampaignRule ret = new CampaignRule(); 208 | Date now = new Date(); 209 | ret.setCampaignEventId(campaignEventId); 210 | ret.setType(CfConstants.USED); 211 | ret.setGroovyCondition(EXCHANGE_Condition); 212 | ret.setGroovyBody(EXCHANGE_Body); 213 | ret.setStartDate(now); 214 | ret.setEndDate(DateUtils.addDays(now, 7)); 215 | return ret; 216 | } 217 | 218 | private static CampaignRule createCampaignRuleExchange(Long campaignEventId) { 219 | String EXCHANGE_Condition = "CampaignCouponItem campaignCouponItem = campaignCouponItemDAO.findOneBySerialNumber(campaignEventDAO.findId(campaignEventCode), serialNumber); if (!StringUtils.equalsIgnoreCase(campaignCouponItem.getStatus(), \"AVAILABLE\")) throw new Exception(\"serialNumber = \" + serialNumber + \" is NOT abailable\"); return true;"; 220 | String EXCHANGE_Body = "CampaignCouponItem campaignCouponItem = campaignCouponItemDAO.findOneBySerialNumber(campaignEventDAO.findId(campaignEventCode), serialNumber); campaignExecution.bindCampaignCoupon(campaignCouponItem, campaignRequest); campaignExecution.distributeCampaignCoupon(campaignRequest); campaignResponse.setStatus(\"EXCHANGE\");"; 221 | 222 | CampaignRule ret = new CampaignRule(); 223 | Date now = new Date(); 224 | ret.setCampaignEventId(campaignEventId); 225 | ret.setType(CfConstants.EXCHANGE); 226 | ret.setGroovyCondition(EXCHANGE_Condition); 227 | ret.setGroovyBody(EXCHANGE_Body); 228 | ret.setStartDate(now); 229 | ret.setEndDate(DateUtils.addDays(now, 7)); 230 | return ret; 231 | } 232 | 233 | private static CampaignRule createCampaignRuleWx(Long campaignEventId) { 234 | String WX_Condition = "if (campaignExecution.hasParticipatedWxCampaign(campaignRequest)) throw new Exception(\"你已经参加过微信组团活动了!\"); return true;"; 235 | String WX_Body = "if (StringUtils.equalsIgnoreCase(campaignRequest.getRuleType(), CfConstants.WX_INITIATE)) { campaignResponse.setStatus(CfConstants.WX_INITIATE); } else if (StringUtils.equalsIgnoreCase(campaignRequest.getRuleType(), CfConstants.WX_PARTICIPATE)) { campaignResponse.setReserved(campaignExecution.resolveCampaignRequest(campaignRequest, CfConstants.RESOLVE_TYPE_WX).get(CfConstants.RESOLVE_targetOpenId)); campaignResponse.setStatus(CfConstants.WX_PARTICIPATE); }"; 236 | 237 | CampaignRule ret = new CampaignRule(); 238 | Date now = new Date(); 239 | ret.setCampaignEventId(campaignEventId); 240 | ret.setGroovyCondition(WX_Condition); 241 | ret.setGroovyBody(WX_Body); 242 | ret.setStartDate(now); 243 | ret.setEndDate(DateUtils.addDays(now, 7)); 244 | return ret; 245 | } 246 | 247 | private static CampaignCoupon createCampaignCoupon(Long campaignId, Long campaignEventId) { 248 | CampaignCoupon ret = new CampaignCoupon(); 249 | Date now = new Date(); 250 | ret.setCampaignId(campaignId); 251 | ret.setCampaignEventId(campaignEventId); 252 | ret.setCode("COUPON"); 253 | ret.setName("活动票券"); 254 | ret.setType(CampaignCouponType.COUPON.toString()); 255 | ret.setDesc("活动票券"); 256 | ret.setTotalAmount(10L); 257 | ret.setAvailableAmount(10L); 258 | ret.setStartDate(now); 259 | ret.setEndDate(DateUtils.addDays(now, CfConstants.CampainCouponExpireDays)); 260 | return ret; 261 | } 262 | 263 | private static List createCampaignCouponItem(CampaignCoupon campaignCoupon) { 264 | List ret = new ArrayList(); 265 | Date now = new Date(); 266 | for (Long i = 0L; i != campaignCoupon.getTotalAmount(); i++) { 267 | CampaignCouponItem tmp = new CampaignCouponItem(campaignCoupon); 268 | tmp.setSerialNumber(UUID.randomUUID().toString()); 269 | tmp.setStatusDate(now); 270 | ret.add(tmp); 271 | } 272 | return ret; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/main/java/jie/cf/core/service/CampaignExecution.java: -------------------------------------------------------------------------------- 1 | package jie.cf.core.service; 2 | 3 | import java.util.Date; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.UUID; 8 | 9 | import org.apache.commons.collections4.MapUtils; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.apache.commons.lang3.time.DateUtils; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.util.CollectionUtils; 15 | 16 | import com.fasterxml.jackson.databind.ObjectMapper; 17 | 18 | import jie.cf.core.dao.ICampaignCouponDAO; 19 | import jie.cf.core.dao.ICampaignCouponItemDAO; 20 | import jie.cf.core.dao.ICampaignDAO; 21 | import jie.cf.core.dao.ICampaignEventDAO; 22 | import jie.cf.core.dao.ICampaignResponseDAO; 23 | import jie.cf.core.dao.ICampaignRuleDAO; 24 | import jie.cf.core.dto.Campaign; 25 | import jie.cf.core.dto.Campaign.CampaignStatus; 26 | import jie.cf.core.dto.CampaignCoupon; 27 | import jie.cf.core.dto.CampaignCouponItem; 28 | import jie.cf.core.dto.CampaignCouponItem.CampaignCouponItemStatus; 29 | import jie.cf.core.dto.CampaignEvent; 30 | import jie.cf.core.dto.CampaignRequest; 31 | import jie.cf.core.dto.CampaignResponse; 32 | import jie.cf.core.dto.CampaignRule; 33 | import jie.cf.core.utils.CfConstants; 34 | import jie.cf.core.utils.CfUtils; 35 | import jie.cf.core.utils.exception.CfException; 36 | 37 | /** 38 | * 活动执行器 39 | * 40 | * @author Jie 41 | * 42 | */ 43 | @Component 44 | public class CampaignExecution { 45 | @Autowired 46 | private ICampaignDAO campaignDAO; 47 | @Autowired 48 | private ICampaignEventDAO campaignEventDAO; 49 | @Autowired 50 | private ICampaignRuleDAO campaignRuleDAO; 51 | @Autowired 52 | private ICampaignResponseDAO campaignResponseDAO; 53 | @Autowired 54 | private ICampaignCouponDAO campaignCouponDAO; 55 | @Autowired 56 | private ICampaignCouponItemDAO campaignCouponItemDAO; 57 | 58 | private ObjectMapper objectMapper = new ObjectMapper(); 59 | 60 | /** 61 | * 参加活动事件 62 | * 63 | * @param campaignRequest 64 | * @throws CfException 65 | */ 66 | public void campaignEvent(CampaignRequest campaignRequest) throws CfException { 67 | String campaignEventCode = campaignRequest.getCampaignEventCode(); 68 | CfUtils.checkNotNull(campaignEventCode, "campaignEventCode can NOT be null"); 69 | // 获取活动事件Code对应的活动事件对象 70 | CampaignEvent campaignEvent = campaignEventDAO.findOne(campaignEventCode); 71 | CfUtils.checkNotNull(campaignEvent, "campaignEventCode = " + campaignEventCode + " NOT found"); 72 | 73 | // 校验活动是否在有效期内 74 | Campaign campaign = campaignDAO.findOne(campaignEvent.getCampaignId()); 75 | checkCampaign(campaign); 76 | 77 | // 获取活动事件对象相关的活动规则列表 78 | String ruleType = StringUtils.isEmpty(campaignRequest.getRuleType()) ? CfConstants.PARTICIPATED 79 | : campaignRequest.getRuleType(); 80 | List rules = campaignRuleDAO.findByParentId(campaignEvent.getId(), ruleType); 81 | if (CollectionUtils.isEmpty(rules)) 82 | throw new CfException("campaignEventCode = " + campaignEventCode + " NOT found any campaignRules"); 83 | // 执行活动规则 84 | for (CampaignRule rule : rules) { 85 | executeCampaignRule(rule, campaignRequest); 86 | } 87 | } 88 | 89 | /** 90 | * 派发票券 91 | * 92 | * @param campaignRequest 93 | * @throws CfException 94 | */ 95 | public void distributeCampaignCoupon(CampaignRequest campaignRequest) throws CfException { 96 | // 解析并校验派发票券的请求 97 | Map param = resolveCampaignRequest(campaignRequest, CfConstants.RESOLVE_TYPE_DISTRIBUTE); 98 | 99 | // 校验票券池是否在有效期内 100 | String campaignCouponCode = param.get(CfConstants.RESOLVE_campaignCouponCode); 101 | CampaignCoupon campaignCoupon = campaignCouponDAO.findOne(campaignCouponCode); 102 | checkCampaignCoupon(campaignCoupon); 103 | 104 | // 派发票券 105 | String campaignCouponItemStatus = StringUtils.isEmpty(param.get(CfConstants.RESOLVE_campaignCouponItemStatus)) 106 | ? CampaignCouponItemStatus.DISTRIBUTED.toString() 107 | : param.get(CfConstants.RESOLVE_campaignCouponItemStatus); 108 | Long campaignCouponItemAmount = StringUtils.isEmpty(param.get(CfConstants.RESOLVE_campaignCouponItemAmount)) 109 | ? 1L : Long.parseLong(param.get(CfConstants.RESOLVE_campaignCouponItemAmount)); 110 | Date now = new Date(); 111 | for (int i = 0; i != campaignCouponItemAmount.intValue(); ++i) { 112 | CampaignCouponItem tmp = new CampaignCouponItem(campaignCoupon); 113 | tmp.setSerialNumber(UUID.randomUUID().toString()); 114 | tmp.setStatus(campaignCouponItemStatus); 115 | tmp.setStatusDate(now); 116 | tmp.setClientId(campaignRequest.getClientId()); 117 | tmp.setMobile(campaignRequest.getMobile()); 118 | tmp.setBindingDate(now); 119 | campaignCouponItemDAO.saveOne(tmp); 120 | } 121 | } 122 | 123 | /** 124 | * 执行一条活动规则 125 | * 126 | * @param campaignRule 127 | * @param campaignRequest 128 | * @throws CfException 129 | */ 130 | private void executeCampaignRule(CampaignRule campaignRule, CampaignRequest campaignRequest) throws CfException { 131 | // 校验活动规则是否在有效期内 132 | checkCampaignRule(campaignRule); 133 | // 构造Groovy脚本可用的参数 134 | Map param = param(campaignRequest); 135 | try { 136 | // 执行GroovyCondition 137 | Boolean condition = (Boolean) CfUtils.executeGroovyScript(campaignRule.getGroovyCondition(), param); 138 | if (condition) { 139 | CampaignResponse campaignResponse = new CampaignResponse(campaignRequest); 140 | campaignResponse.setCampaignEventId(campaignRule.getCampaignEventId()); 141 | campaignResponse.setCampaignRuleId(campaignRule.getId()); 142 | // Groovy脚本可用的业务数据 143 | param.put("campaignResponse", campaignResponse); 144 | 145 | CfUtils.executeGroovyScript(campaignRule.getGroovyBody(), param); 146 | 147 | campaignResponse.setStatusDate(new Date()); 148 | campaignResponseDAO.saveOne(campaignResponse); 149 | } 150 | } catch (Exception e) { 151 | throw new CfException(e); 152 | } 153 | } 154 | 155 | /** 156 | * 构造Groovy脚本可用的参数 157 | * 158 | * @param campaignRequest 159 | * @return 160 | */ 161 | private Map param(CampaignRequest campaignRequest) { 162 | Map ret = new HashMap(); 163 | // Groovy脚本可用的bean 164 | ret.put("context", CfUtils.getContext()); 165 | ret.put("campaignDAO", campaignDAO); 166 | ret.put("campaignEventDAO", campaignEventDAO); 167 | ret.put("campaignRuleDAO", campaignRuleDAO); 168 | ret.put("campaignCouponDAO", campaignCouponDAO); 169 | ret.put("campaignCouponItemDAO", campaignCouponItemDAO); 170 | ret.put("campaignExecution", this); 171 | // Groovy脚本可用的业务数据 172 | ret.put("campaignRequest", campaignRequest); 173 | ret.put("campaignEventCode", campaignRequest.getCampaignEventCode()); 174 | ret.put("clientId", campaignRequest.getClientId()); 175 | ret.put("mobile", campaignRequest.getMobile()); 176 | ret.put("serialNumber", campaignRequest.getSerialNumber()); 177 | return ret; 178 | } 179 | 180 | /** 181 | * 解析并校验活动请求的reserved参数 182 | * 183 | * @param campaignRequest 184 | * @param type 185 | * @return Map 186 | * @throws CfException 187 | */ 188 | private Map resolveCampaignRequest(CampaignRequest campaignRequest, int type) throws CfException { 189 | try { 190 | String reserved = campaignRequest.getReserved(); 191 | CfUtils.checkNotNull(reserved, "campaignRequest.reserved can NOT be null"); 192 | Map param = objectMapper.readValue(reserved, Map.class); 193 | if (MapUtils.isEmpty(param)) 194 | throw new CfException("campaignRequest.reserved resolve failed"); 195 | 196 | switch (type) { 197 | // 派发票券 198 | case CfConstants.RESOLVE_TYPE_DISTRIBUTE: 199 | String campaignCouponCode = param.get(CfConstants.RESOLVE_campaignCouponCode); 200 | CfUtils.checkNotNull(campaignCouponCode, "campaignCouponCode can NOT be null"); 201 | break; 202 | // 微信“好友帮砍价” 203 | case CfConstants.RESOLVE_TYPE_WX: 204 | String targetOpenId = param.get(CfConstants.RESOLVE_targetOpenId); 205 | CfUtils.checkNotNull(targetOpenId, "targetOpenId can NOT be null"); 206 | break; 207 | default: 208 | break; 209 | } 210 | return param; 211 | } catch (Exception e) { 212 | throw new CfException(e); 213 | } 214 | } 215 | 216 | /** 217 | * 校验活动是否在有效期内 218 | * 219 | * @param campaign 220 | * @throws CfException 221 | */ 222 | private void checkCampaign(Campaign campaign) throws CfException { 223 | CfUtils.checkNotNull(campaign, "campaign can NOT be null"); 224 | if (!StringUtils.equalsIgnoreCase(campaign.getStatus(), CampaignStatus.IN_PROCESS.toString())) 225 | throw new CfException("campaignCode = " + campaign.getCode() + " NOT IN_PROCESS"); 226 | if (!CfUtils.hasNotExpired(campaign.getStartDate(), campaign.getEndDate())) 227 | throw new CfException("campaignCode = " + campaign.getCode() + " has expired, startDate = " 228 | + campaign.getStartDate() + " endDate = " + campaign.getEndDate()); 229 | } 230 | 231 | /** 232 | * 校验活动规则是否在有效期内 233 | * 234 | * @param campaignRule 235 | * @throws CfException 236 | */ 237 | private void checkCampaignRule(CampaignRule campaignRule) throws CfException { 238 | CfUtils.checkNotNull(campaignRule, "campaignRule can NOT be null"); 239 | if (!CfUtils.hasNotExpired(campaignRule.getStartDate(), campaignRule.getEndDate())) 240 | throw new CfException("campaignRuleId = " + campaignRule.getId() + " has expired, startDate = " 241 | + campaignRule.getStartDate() + " endDate = " + campaignRule.getEndDate()); 242 | } 243 | 244 | /** 245 | * 校验票券池是否在有效期内 246 | * 247 | * @param campaignCoupon 248 | * @throws CfException 249 | */ 250 | private void checkCampaignCoupon(CampaignCoupon campaignCoupon) throws CfException { 251 | CfUtils.checkNotNull(campaignCoupon, "campaignCoupon can NOT be null"); 252 | if (!CfUtils.hasNotExpired(campaignCoupon.getStartDate(), campaignCoupon.getEndDate())) 253 | throw new CfException("campaignCouponCode = " + campaignCoupon.getCode() + " has expired, startDate = " 254 | + campaignCoupon.getStartDate() + " endDate = " + campaignCoupon.getEndDate()); 255 | } 256 | 257 | /** 258 | * 统计活动事件的响应次数 259 | * 260 | * @param campaignRequest 261 | * @return 262 | * @throws CfException 263 | */ 264 | public long countCampaignResponse(CampaignRequest campaignRequest) throws CfException { 265 | Long campaignEventId = campaignEventDAO.findId(campaignRequest.getCampaignEventCode()); 266 | List lst = campaignResponseDAO.findByCampaignRequest(campaignEventId, 267 | campaignRequest.getClientId(), campaignRequest.getMobile()); 268 | if (CollectionUtils.isEmpty(lst)) 269 | return 0; 270 | else 271 | return lst.size(); 272 | } 273 | 274 | /** 275 | * 绑定票券 276 | * 277 | * @param campaignCouponItem 278 | * @param campaignRequest 279 | * @throws CfException 280 | */ 281 | public void bindCampaignCoupon(CampaignCouponItem campaignCouponItem, CampaignRequest campaignRequest) 282 | throws CfException { 283 | CfUtils.checkNotNull(campaignCouponItem, "campaignCouponItem can NOT be null"); 284 | Date now = new Date(); 285 | campaignCouponItem.setStatus(convertRuleType2CouponItemStatus(campaignRequest.getRuleType())); 286 | campaignCouponItem.setStatusDate(now); 287 | campaignCouponItem.setClientId(campaignRequest.getClientId()); 288 | campaignCouponItem.setMobile(campaignRequest.getMobile()); 289 | campaignCouponItem.setOpenId(campaignRequest.getOpenId()); 290 | campaignCouponItem.setBindingDate(now); 291 | campaignCouponItem.setStartDate(now); 292 | campaignCouponItem.setEndDate(DateUtils.addDays(now, CfConstants.CampainCouponExpireDays)); 293 | campaignCouponItemDAO.saveOne(campaignCouponItem); 294 | } 295 | 296 | /** 297 | * 是否已参加过微信“好友帮砍价”活动 298 | * 299 | * @param campaignRequest 300 | * @return boolean 301 | * @throws CfException 302 | */ 303 | public boolean hasParticipatedWxCampaign(CampaignRequest campaignRequest) throws CfException { 304 | Long campaignEventId = campaignEventDAO.findId(campaignRequest.getCampaignEventCode()); 305 | String targetOpenId = null; 306 | 307 | if (StringUtils.equalsIgnoreCase(campaignRequest.getRuleType(), CfConstants.WX_INITIATE)) { 308 | // 发起人 309 | } else if (StringUtils.equalsIgnoreCase(campaignRequest.getRuleType(), CfConstants.WX_PARTICIPATE)) { 310 | // 参与人 311 | Map param = resolveCampaignRequest(campaignRequest, CfConstants.RESOLVE_TYPE_WX); 312 | targetOpenId = param.get(CfConstants.RESOLVE_targetOpenId); 313 | // 校验目标OpenId是否已发起 314 | if (CollectionUtils.isEmpty(campaignResponseDAO.findByWxOpenId(campaignEventId, targetOpenId, null))) 315 | throw new CfException("targetOpenId = " + targetOpenId + " NOT available"); 316 | } 317 | 318 | List lst = campaignResponseDAO.findByWxOpenId(campaignEventId, campaignRequest.getOpenId(), 319 | targetOpenId); 320 | if (CollectionUtils.isEmpty(lst)) 321 | return false; 322 | else 323 | return true; 324 | } 325 | 326 | /** 327 | * CampaignRuleType映射到CampaignCouponItemStatus 328 | * 329 | * @param campaignRuleType 330 | * @return CampaignCouponItemStatus 331 | * @throws CfException 332 | */ 333 | private String convertRuleType2CouponItemStatus(String campaignRuleType) throws CfException { 334 | String ret = null; 335 | if (StringUtils.equalsIgnoreCase(CfConstants.DISTRIBUTED, campaignRuleType) 336 | || StringUtils.equalsIgnoreCase(CfConstants.USED, campaignRuleType)) 337 | ret = campaignRuleType; 338 | else { 339 | if (StringUtils.equalsIgnoreCase(CfConstants.EXCHANGE, campaignRuleType)) 340 | ret = CampaignCouponItemStatus.USED.toString(); 341 | else 342 | throw new CfException("WRONG campaignRuleType: " + campaignRuleType); 343 | } 344 | return ret; 345 | } 346 | } 347 | --------------------------------------------------------------------------------