Explorar el Código

feat: 投票活动功能.

eric hace 2 años
padre
commit
432a7dd1b6
Se han modificado 25 ficheros con 1134 adiciones y 1 borrados
  1. 26 0
      pom.xml
  2. 94 0
      src/main/java/com/finikes/oc/vote/ApiResponse.java
  3. 11 0
      src/main/java/com/finikes/oc/vote/LockService.java
  4. 19 0
      src/main/java/com/finikes/oc/vote/LockServiceImpl.java
  5. 97 0
      src/main/java/com/finikes/oc/vote/controller/VoteController.java
  6. 43 0
      src/main/java/com/finikes/oc/vote/dao/BaseDao.java
  7. 11 0
      src/main/java/com/finikes/oc/vote/dao/ChoiceDao.java
  8. 13 0
      src/main/java/com/finikes/oc/vote/dao/OptionDao.java
  9. 16 0
      src/main/java/com/finikes/oc/vote/dao/VoteActivityDao.java
  10. 9 0
      src/main/java/com/finikes/oc/vote/dao/VoteDao.java
  11. 30 0
      src/main/java/com/finikes/oc/vote/dto/ChoiceDto.java
  12. 61 0
      src/main/java/com/finikes/oc/vote/dto/VoteActivityCreateDto.java
  13. 62 0
      src/main/java/com/finikes/oc/vote/dto/VoteActivitySearchDto.java
  14. 72 0
      src/main/java/com/finikes/oc/vote/dto/VoteActivityUpdateDto.java
  15. 62 0
      src/main/java/com/finikes/oc/vote/dto/VoteActivityViewDto.java
  16. 62 0
      src/main/java/com/finikes/oc/vote/dto/VoteCreateDto.java
  17. 4 0
      src/main/java/com/finikes/oc/vote/dto/VoteUpdateDto.java
  18. 27 0
      src/main/java/com/finikes/oc/vote/exception/BusinessException.java
  19. 15 0
      src/main/java/com/finikes/oc/vote/mapper/VoteActivityMapper.java
  20. 18 0
      src/main/java/com/finikes/oc/vote/mapper/VoteMapper.java
  21. 46 0
      src/main/java/com/finikes/oc/vote/service/VoteService.java
  22. 215 0
      src/main/java/com/finikes/oc/vote/service/VoteServiceImpl.java
  23. 3 1
      src/main/resources/application.yml
  24. 42 0
      src/main/resources/mapper/VoteActivityMapper.xml
  25. 76 0
      src/test/java/com/finikes/oc/vote/dao/VoteActivityDaoTest.java

+ 26 - 0
pom.xml

@@ -61,6 +61,32 @@
             <artifactId>commons-lang3</artifactId>
             <version>3.12.0</version>
         </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+            <version>4.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+            <version>1.2.5</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 94 - 0
src/main/java/com/finikes/oc/vote/ApiResponse.java

@@ -0,0 +1,94 @@
+package com.finikes.oc.vote;
+
+/**
+ * 接口返回对象
+ *
+ * @param <T> 内容数据类型
+ */
+public class ApiResponse<T> {
+    private String code;
+    private String message;
+    private T content;
+
+    private ApiResponse(String code, String message, T content) {
+        this.code = code;
+        this.message = message;
+        this.content = content;
+    }
+
+    @Override
+    public String toString() {
+        return "ApiResponse{" +
+                "code='" + code + '\'' +
+                ", message='" + message + '\'' +
+                ", content=" + content +
+                '}';
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public T getContent() {
+        return content;
+    }
+
+    public void setContent(T content) {
+        this.content = content;
+    }
+
+    /**
+     * 成功
+     *
+     * @return 接口返回对象
+     */
+    public static <T> ApiResponse<T> successful() {
+        return new ApiResponse<>("1", null, null);
+    }
+
+    /**
+     * 成功
+     *
+     * @param content 内容
+     * @param <T>     内容类型
+     * @return 接口返回对象
+     */
+    public static <T> ApiResponse<T> successful(T content) {
+        return new ApiResponse<>("1", null, content);
+    }
+
+    /**
+     * 失败
+     *
+     * @param message 信息
+     * @return 接口返回对象
+     */
+    public static ApiResponse<Void> fail(String message) {
+        return new ApiResponse<>("0", message, null);
+    }
+
+    /**
+     * 自定义
+     *
+     * @param code    代号
+     * @param message 信息
+     * @param content 内容
+     * @param <T>     内容类型
+     * @return 接口返回对象
+     */
+    public static <T> ApiResponse<T> custom(String code, String message, T content) {
+        return new ApiResponse<>(code, message, content);
+    }
+}

+ 11 - 0
src/main/java/com/finikes/oc/vote/LockService.java

@@ -0,0 +1,11 @@
+package com.finikes.oc.vote;
+
+import java.util.concurrent.TimeUnit;
+
+public interface LockService {
+
+    boolean lock(String resourceId, String ownerId, long time, TimeUnit unit);
+
+    void unlock(String resourceId, String ownerId);
+
+}

+ 19 - 0
src/main/java/com/finikes/oc/vote/LockServiceImpl.java

@@ -0,0 +1,19 @@
+package com.finikes.oc.vote;
+
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class LockServiceImpl implements LockService {
+
+    @Override
+    public boolean lock(String resourceId, String ownerId, long time, TimeUnit unit) {
+        return false;
+    }
+
+    @Override
+    public void unlock(String resourceId, String ownerId) {
+
+    }
+}

+ 97 - 0
src/main/java/com/finikes/oc/vote/controller/VoteController.java

@@ -0,0 +1,97 @@
+package com.finikes.oc.vote.controller;
+
+import com.finikes.oc.vote.ApiResponse;
+import com.finikes.oc.vote.dto.*;
+import com.finikes.oc.vote.service.VoteService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 投票控制器
+ */
+@RestController
+public class VoteController {
+
+    private final VoteService voteService;
+
+    public VoteController(VoteService voteService) {
+        this.voteService = voteService;
+    }
+
+    /**
+     * 创建投票活动
+     *
+     * @param dto 投票活动创建数据传输对象
+     * @return 接口返回对象
+     */
+    @PutMapping("vote_activity")
+    public ApiResponse<Integer> createVoteActivity(@RequestBody VoteActivityCreateDto dto) {
+        int voteActivityId = voteService.createVoteActivity(dto);
+        return ApiResponse.successful(voteActivityId);
+    }
+
+    /**
+     * 更新投票活动
+     *
+     * @param dto 投票活动创建数据传输对象
+     * @return 接口返回对象
+     */
+    @PostMapping("vote_activity")
+    public ApiResponse<Integer> updateVoteActivity(@RequestBody VoteActivityUpdateDto dto) {
+        voteService.updateVoteActivity(dto);
+        return ApiResponse.successful();
+    }
+
+    /**
+     * 搜索投票活动
+     *
+     * @param pageNum      页码
+     * @param pageCapacity 页容量
+     * @param key          活动名称
+     * @param startTime    开始时间
+     * @param endTime      结束时间
+     * @return 接口返回对象
+     */
+    @GetMapping("vote_activities/section")
+    public ApiResponse<List<VoteActivityViewDto>> searchVoteActivity(
+            @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
+            @RequestParam(value = "pageCapacity", defaultValue = "10") int pageCapacity,
+            @RequestParam(value = "key") String key, @RequestParam(value = "startTime") Date startTime,
+            @RequestParam(value = "endTime") Date endTime) {
+        VoteActivitySearchDto dto = new VoteActivitySearchDto();
+        dto.setPageNum(pageNum);
+        dto.setPageCapacity(pageCapacity);
+        dto.setKey(key);
+        dto.setStartTime(startTime);
+        dto.setEndTime(endTime);
+
+        List<VoteActivityViewDto> list = voteService.searchVoteActivity(dto);
+        return ApiResponse.successful(list);
+    }
+
+    /**
+     * 投票
+     *
+     * @param dto 投票数据传输对象
+     * @return 接口返回对象
+     */
+    @PutMapping("choice")
+    public ApiResponse<Void> choice(@RequestBody @Valid ChoiceDto dto) {
+        voteService.madeChoice(dto);
+        return ApiResponse.successful();
+    }
+
+    /**
+     * 异常处理器
+     *
+     * @param exception 异常
+     * @return 接口返回对象
+     */
+    @ExceptionHandler
+    public ApiResponse<?> exceptionHandler(Exception exception) {
+        return ApiResponse.fail(exception.getMessage());
+    }
+}

+ 43 - 0
src/main/java/com/finikes/oc/vote/dao/BaseDao.java

@@ -0,0 +1,43 @@
+package com.finikes.oc.vote.dao;
+
+/**
+ * 基础数据访问对象
+ *
+ * @param <T> 实体对象
+ * @param <I> 主键类型
+ */
+public interface BaseDao<T, I> {
+
+    /**
+     * 新增
+     *
+     * @param entity 实体
+     * @return 影响行数
+     */
+    int insert(T entity);
+
+    /**
+     * 更新
+     *
+     * @param entity 实体
+     * @return 影响行数
+     */
+    int update(T entity);
+
+    /**
+     * 通过主键获取实体
+     *
+     * @param primaryKey 主键
+     * @return 实体
+     */
+    T selectByPrimaryKey(I primaryKey);
+
+    /**
+     * 通过主键删除实体
+     *
+     * @param primaryKey 主键
+     * @return 影响行数
+     */
+    int deleteByPrimaryKey(I primaryKey);
+
+}

+ 11 - 0
src/main/java/com/finikes/oc/vote/dao/ChoiceDao.java

@@ -0,0 +1,11 @@
+package com.finikes.oc.vote.dao;
+
+import com.finikes.oc.vote.entity.Choice;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ChoiceDao extends BaseDao<Choice, Long> {
+
+
+
+}

+ 13 - 0
src/main/java/com/finikes/oc/vote/dao/OptionDao.java

@@ -0,0 +1,13 @@
+package com.finikes.oc.vote.dao;
+
+import com.finikes.oc.vote.entity.Option;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface OptionDao extends BaseDao<Option, Integer> {
+
+    int bulkInsert(List<Option> entities);
+
+}

+ 16 - 0
src/main/java/com/finikes/oc/vote/dao/VoteActivityDao.java

@@ -0,0 +1,16 @@
+package com.finikes.oc.vote.dao;
+
+import com.finikes.oc.vote.entity.VoteActivity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface VoteActivityDao extends BaseDao<VoteActivity, Integer> {
+
+    List<VoteActivity> selectByTitleAndStartTimeAndEndTime(@Param("title") String title,
+                                                           @Param("startTime") Long startTime,
+                                                           @Param("endTime")  Long time);
+
+}

+ 9 - 0
src/main/java/com/finikes/oc/vote/dao/VoteDao.java

@@ -0,0 +1,9 @@
+package com.finikes.oc.vote.dao;
+
+import com.finikes.oc.vote.entity.Vote;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface VoteDao extends BaseDao<Vote, Integer>{
+
+}

+ 30 - 0
src/main/java/com/finikes/oc/vote/dto/ChoiceDto.java

@@ -0,0 +1,30 @@
+package com.finikes.oc.vote.dto;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 投票数据传输对象
+ */
+public class ChoiceDto {
+
+    /**
+     * 选项 ID
+     */
+    @NotNull
+    private Integer optionId;
+
+    @Override
+    public String toString() {
+        return "ChoiceDto{" +
+                "optionId=" + optionId +
+                '}';
+    }
+
+    public Integer getOptionId() {
+        return optionId;
+    }
+
+    public void setOptionId(Integer optionId) {
+        this.optionId = optionId;
+    }
+}

+ 61 - 0
src/main/java/com/finikes/oc/vote/dto/VoteActivityCreateDto.java

@@ -0,0 +1,61 @@
+package com.finikes.oc.vote.dto;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+public class VoteActivityCreateDto {
+    @NotBlank
+    @Max(256)
+    private String title;
+    @NotBlank
+    @Max(256)
+    private String content;
+    @NotNull
+    private Date launchTime;
+    @NotNull
+    private Date deadline;
+
+    @Override
+    public String toString() {
+        return "VoteActivityCreateDto{" +
+                "title='" + title + '\'' +
+                ", content='" + content + '\'' +
+                ", startTime=" + launchTime +
+                ", endTime=" + deadline +
+                '}';
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Date getLaunchTime() {
+        return launchTime;
+    }
+
+    public void setLaunchTime(Date launchTime) {
+        this.launchTime = launchTime;
+    }
+
+    public Date getDeadline() {
+        return deadline;
+    }
+
+    public void setDeadline(Date deadline) {
+        this.deadline = deadline;
+    }
+}

+ 62 - 0
src/main/java/com/finikes/oc/vote/dto/VoteActivitySearchDto.java

@@ -0,0 +1,62 @@
+package com.finikes.oc.vote.dto;
+
+import java.util.Date;
+
+public class VoteActivitySearchDto {
+    private int pageNum;
+    private int pageCapacity;
+    private String key;
+    private Date startTime;
+    private Date endTime;
+
+    @Override
+    public String toString() {
+        return "VoteActivityPageDto{" +
+                "pageNum=" + pageNum +
+                ", pageCapacity=" + pageCapacity +
+                ", key='" + key + '\'' +
+                ", startTime=" + startTime +
+                ", endTime=" + endTime +
+                '}';
+    }
+
+    public int getPageNum() {
+        return pageNum;
+    }
+
+    public void setPageNum(int pageNum) {
+        this.pageNum = pageNum;
+    }
+
+    public int getPageCapacity() {
+        return pageCapacity;
+    }
+
+    public void setPageCapacity(int pageCapacity) {
+        this.pageCapacity = pageCapacity;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+}

+ 72 - 0
src/main/java/com/finikes/oc/vote/dto/VoteActivityUpdateDto.java

@@ -0,0 +1,72 @@
+package com.finikes.oc.vote.dto;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+public class VoteActivityUpdateDto {
+    @NotNull
+    private Integer id;
+    @NotBlank
+    @Max(256)
+    private String title;
+    @NotBlank
+    @Max(256)
+    private String content;
+    @NotNull
+    private Date launchTime;
+    @NotNull
+    private Date deadline;
+
+    @Override
+    public String toString() {
+        return "VoteActivityUpdateDto{" +
+                "id=" + id +
+                ", title='" + title + '\'' +
+                ", content='" + content + '\'' +
+                ", startTime=" + launchTime +
+                ", endTime=" + deadline +
+                '}';
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Date getLaunchTime() {
+        return launchTime;
+    }
+
+    public void setLaunchTime(Date launchTime) {
+        this.launchTime = launchTime;
+    }
+
+    public Date getDeadline() {
+        return deadline;
+    }
+
+    public void setDeadline(Date deadline) {
+        this.deadline = deadline;
+    }
+}

+ 62 - 0
src/main/java/com/finikes/oc/vote/dto/VoteActivityViewDto.java

@@ -0,0 +1,62 @@
+package com.finikes.oc.vote.dto;
+
+import java.util.Date;
+
+public class VoteActivityViewDto {
+    private Integer id;
+    private String title;
+    private String content;
+    private String launchTime;
+    private String deadline;
+
+    @Override
+    public String toString() {
+        return "VoteActivityViewDto{" +
+                "id=" + id +
+                ", title='" + title + '\'' +
+                ", content='" + content + '\'' +
+                ", launchTime='" + launchTime + '\'' +
+                ", deadline='" + deadline + '\'' +
+                '}';
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getLaunchTime() {
+        return launchTime;
+    }
+
+    public void setLaunchTime(String launchTime) {
+        this.launchTime = launchTime;
+    }
+
+    public String getDeadline() {
+        return deadline;
+    }
+
+    public void setDeadline(String deadline) {
+        this.deadline = deadline;
+    }
+}

+ 62 - 0
src/main/java/com/finikes/oc/vote/dto/VoteCreateDto.java

@@ -0,0 +1,62 @@
+package com.finikes.oc.vote.dto;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+public class VoteCreateDto {
+    @NotNull
+    private Integer activityId;
+    @NotBlank
+    @Max(256)
+    private String title;
+    @NotBlank
+    @Max(256)
+    private String content;
+    @NotEmpty
+    private List<String> options;
+
+    @Override
+    public String toString() {
+        return "VoteCreateDto{" +
+                "activityId=" + activityId +
+                ", title='" + title + '\'' +
+                ", content='" + content + '\'' +
+                ", options=" + options +
+                '}';
+    }
+
+    public Integer getActivityId() {
+        return activityId;
+    }
+
+    public void setActivityId(Integer activityId) {
+        this.activityId = activityId;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public List<String> getOptions() {
+        return options;
+    }
+
+    public void setOptions(List<String> options) {
+        this.options = options;
+    }
+}

+ 4 - 0
src/main/java/com/finikes/oc/vote/dto/VoteUpdateDto.java

@@ -0,0 +1,4 @@
+package com.finikes.oc.vote.dto;
+
+public class VoteUpdateDto {
+}

+ 27 - 0
src/main/java/com/finikes/oc/vote/exception/BusinessException.java

@@ -0,0 +1,27 @@
+package com.finikes.oc.vote.exception;
+
+/**
+ * 业务异常
+ */
+public class BusinessException extends RuntimeException{
+    public BusinessException(String message) {
+        super(message);
+    }
+
+    public BusinessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BusinessException(Throwable cause) {
+        super(cause);
+    }
+
+    public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    @Override
+    public synchronized Throwable fillInStackTrace() {
+        return null;
+    }
+}

+ 15 - 0
src/main/java/com/finikes/oc/vote/mapper/VoteActivityMapper.java

@@ -0,0 +1,15 @@
+package com.finikes.oc.vote.mapper;
+
+import com.finikes.oc.vote.dto.VoteActivityViewDto;
+import com.finikes.oc.vote.entity.VoteActivity;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface VoteActivityMapper {
+
+    VoteActivityMapper INSTANCE = Mappers.getMapper(VoteActivityMapper.class);
+
+    VoteActivityViewDto voteActivityToVoteActivityViewDto(VoteActivity voteActivity);
+
+}

+ 18 - 0
src/main/java/com/finikes/oc/vote/mapper/VoteMapper.java

@@ -0,0 +1,18 @@
+package com.finikes.oc.vote.mapper;
+
+import com.finikes.oc.vote.dto.VoteCreateDto;
+import com.finikes.oc.vote.dto.VoteUpdateDto;
+import com.finikes.oc.vote.entity.Vote;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface VoteMapper {
+
+    VoteMapper INSTANCE = Mappers.getMapper(VoteMapper.class);
+
+    Vote voteCreateDtoToVote(VoteCreateDto dto);
+
+    Vote voteUpdateDtoToVote(VoteUpdateDto dto);
+
+}

+ 46 - 0
src/main/java/com/finikes/oc/vote/service/VoteService.java

@@ -0,0 +1,46 @@
+package com.finikes.oc.vote.service;
+
+import com.finikes.oc.vote.dto.*;
+
+import java.util.List;
+
+/**
+ * 投票服务
+ */
+public interface VoteService {
+
+    /**
+     * 创建投票活动
+     *
+     * @param dto 创建投票活动数据传输对象
+     * @return 主键
+     */
+    int createVoteActivity(VoteActivityCreateDto dto);
+
+    /**
+     * 更新投票活动
+     *
+     * @param dto 更新投票活动数据传输对象
+     */
+    void updateVoteActivity(VoteActivityUpdateDto dto);
+
+    /**
+     * 检索投票活动
+     *
+     * @param dto 投票活动检索数据传输对象
+     * @return 投票活动视图数据传输对象列表
+     */
+    List<VoteActivityViewDto> searchVoteActivity(VoteActivitySearchDto dto);
+
+    int createVote(VoteCreateDto dto);
+
+    void updateVote(VoteUpdateDto dto);
+
+    /**
+     * 投票
+     *
+     * @param dto 投票数据传输对象
+     */
+    void madeChoice(ChoiceDto dto);
+
+}

+ 215 - 0
src/main/java/com/finikes/oc/vote/service/VoteServiceImpl.java

@@ -0,0 +1,215 @@
+package com.finikes.oc.vote.service;
+
+import com.finikes.oc.vote.LockService;
+import com.finikes.oc.vote.dao.ChoiceDao;
+import com.finikes.oc.vote.dao.OptionDao;
+import com.finikes.oc.vote.dao.VoteActivityDao;
+import com.finikes.oc.vote.dao.VoteDao;
+import com.finikes.oc.vote.dto.*;
+import com.finikes.oc.vote.entity.Choice;
+import com.finikes.oc.vote.entity.Option;
+import com.finikes.oc.vote.entity.Vote;
+import com.finikes.oc.vote.entity.VoteActivity;
+import com.finikes.oc.vote.exception.BusinessException;
+import com.finikes.oc.vote.mapper.VoteActivityMapper;
+import com.finikes.oc.vote.mapper.VoteMapper;
+import com.github.pagehelper.Page;
+import com.github.pagehelper.PageHelper;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Service
+public class VoteServiceImpl implements VoteService {
+
+    private final ChoiceDao choiceDao;
+
+    private final OptionDao optionDao;
+
+    private final VoteActivityDao voteActivityDao;
+
+    private final VoteDao voteDao;
+
+    private final LockService lockService;
+
+    public VoteServiceImpl(ChoiceDao choiceDao, OptionDao optionDao, VoteActivityDao voteActivityDao, VoteDao voteDao,
+                           LockService lockService) {
+        this.choiceDao = choiceDao;
+        this.optionDao = optionDao;
+        this.voteActivityDao = voteActivityDao;
+        this.voteDao = voteDao;
+        this.lockService = lockService;
+    }
+
+    @Override
+    public int createVoteActivity(VoteActivityCreateDto dto) {
+        voteActivityDateCheck(dto.getLaunchTime(), dto.getDeadline());
+
+        VoteActivity voteActivity = new VoteActivity();
+        voteActivity.setTitle(dto.getTitle());
+        voteActivity.setContent(dto.getContent());
+        voteActivity.setStartTime(dto.getLaunchTime().getTime());
+        voteActivity.setEndTime(dto.getDeadline().getTime());
+        voteActivityDao.insert(voteActivity);
+        return voteActivity.getId();
+    }
+
+    @Override
+    public void updateVoteActivity(VoteActivityUpdateDto dto) {
+        voteActivityDateCheck(dto.getLaunchTime(), dto.getDeadline());
+
+        VoteActivity voteActivity = new VoteActivity();
+        voteActivity.setId(dto.getId());
+        voteActivity.setTitle(dto.getTitle());
+        voteActivity.setContent(dto.getContent());
+        voteActivity.setStartTime(dto.getLaunchTime().getTime());
+        voteActivity.setEndTime(dto.getDeadline().getTime());
+        int effectRow = voteActivityDao.update(voteActivity);
+        if (effectRow != 1) {
+            throw new BusinessException("更新失败");
+        }
+    }
+
+    private String timestampToStrDate(long timestamp) {
+        Date date = new Date(timestamp);
+        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+        return simpleDateFormat.format(date);
+    }
+
+    @Override
+    public List<VoteActivityViewDto> searchVoteActivity(VoteActivitySearchDto dto) {
+        List<VoteActivity> activities;
+
+        try (Page<VoteActivity> page = PageHelper.startPage(dto.getPageNum(), dto.getPageCapacity(), false)){
+            if (dto.getStartTime() == null || dto.getEndTime() == null) {
+                activities = voteActivityDao.selectByTitleAndStartTimeAndEndTime(dto.getKey(),
+                        null, null);
+            } else {
+                activities = voteActivityDao.selectByTitleAndStartTimeAndEndTime(dto.getKey(),
+                        dto.getStartTime().getTime(), dto.getEndTime().getTime());
+            }
+        }
+
+        return activities.stream().map(i -> {
+            VoteActivityViewDto viewDto = VoteActivityMapper.INSTANCE.voteActivityToVoteActivityViewDto(i);
+            viewDto.setLaunchTime(timestampToStrDate(i.getStartTime()));
+            viewDto.setDeadline(timestampToStrDate(i.getEndTime()));
+            return viewDto;
+        }).collect(Collectors.toList());
+    }
+
+    @Override
+    public int createVote(VoteCreateDto dto) {
+        VoteActivity activity = voteActivityDao.selectByPrimaryKey(dto.getActivityId());
+        if (activity == null) {
+            throw new BusinessException("投票活动不存在");
+        }
+        Vote vote = VoteMapper.INSTANCE.voteCreateDtoToVote(dto);
+        voteDao.insert(vote);
+
+        List<Option> options = dto.getOptions().stream()
+                .map(value -> {
+                    Option option = new Option();
+                    option.setVoteId(vote.getId());
+                    option.setValue(value);
+                    return option;
+                }).collect(Collectors.toList());
+
+        optionDao.bulkInsert(options);
+
+        return vote.getId();
+    }
+
+    @Override
+    public void updateVote(VoteUpdateDto dto) {
+
+    }
+
+    /**
+     * 投票活动日期检查
+     *
+     * @param startDate 开始日期
+     * @param endDate   结束日期
+     */
+    private void voteActivityDateCheck(Date startDate, Date endDate) {
+
+        if (startDate == null || endDate == null) {
+            throw new BusinessException("开始日期和结束日期不可为空");
+        }
+
+        if (startDate.getTime() == endDate.getTime()) {
+            throw new BusinessException("开始日期和结束日期不可为同一天");
+        }
+
+        if (endDate.getTime() < startDate.getTime()) {
+            throw new BusinessException("结束日期不可早于开始日期");
+        }
+
+        Calendar calendar = Calendar.getInstance(Locale.CHINA);
+        calendar.set(Calendar.HOUR, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        Date today = calendar.getTime();
+
+        if (endDate.getTime() <= today.getTime()) {
+            throw new BusinessException("结束日期不可为当前日期");
+        }
+    }
+
+    @Override
+    public void madeChoice(ChoiceDto dto) {
+        int voterId = 0;
+        // TODO 需要方法获取登录用户信息
+
+        // TODO 这里需要锁住 用户ID 防止单个用户的并发调用, 锁住用户具有的房产主键已达到一房一票的需求.
+        String resourceId = "vote:" + voterId;
+        String ownerId = UUID.randomUUID().toString();
+        boolean locked = lockService.lock(resourceId, ownerId, 60, TimeUnit.SECONDS);
+        if (!locked) {
+            throw new BusinessException("请稍后再试");
+        }
+
+        try {
+            long now = System.currentTimeMillis();
+
+            Option option = optionDao.selectByPrimaryKey(dto.getOptionId());
+            if (option == null) {
+                throw new BusinessException("参与的投票不存在");
+            }
+
+            Vote vote = voteDao.selectByPrimaryKey(option.getVoteId());
+            if (vote == null) {
+                throw new BusinessException("参与的投票不存在");
+            }
+
+            VoteActivity activity = voteActivityDao.selectByPrimaryKey(vote.getActivityId());
+            if (activity == null) {
+                throw new BusinessException("参与的投票不存在");
+            }
+
+            if (now < activity.getStartTime()) {
+                throw new BusinessException("投票日期未到");
+            }
+
+            if (now > activity.getEndTime()) {
+                throw new BusinessException("投票日期已过");
+            }
+
+            // TODO 检查此登录人员的房产下的绑定人员是否参与过此抽奖.
+
+            Choice choice = new Choice();
+            choice.setAssigneeId(voterId);
+            choice.setOptionId(dto.getOptionId());
+            choice.setProxy(false);
+            choice.setChooseTime(now);
+
+            choiceDao.insert(choice);
+        } finally {
+            lockService.unlock(resourceId, ownerId);
+        }
+    }
+}

+ 3 - 1
src/main/resources/application.yml

@@ -3,10 +3,12 @@ spring:
     host: 10.18.10.106
     port: 6379
   datasource:
-    url: jdbc:mysql://121.5.58.50:3306
+    url: jdbc:mysql://121.5.58.50:3306/community?useSSL=false&useUnicode=true&characterEncoding=utf-8&useAffectedRows=true&serverTimezone=UTC
     username: root
     password: 6bbd00bb4777fe30
     driver-class-name: com.mysql.cj.jdbc.Driver
+  main:
+    allow-circular-references: true
 mybatis:
   mapper-locations: classpath:mapper/*.xml
   configuration:

+ 42 - 0
src/main/resources/mapper/VoteActivityMapper.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.finikes.oc.vote.dao.VoteActivityDao">
+
+    <insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id"
+            parameterType="com.finikes.oc.vote.entity.VoteActivity">
+        INSERT INTO t_vote_activity (title, content, start_time, end_time)
+        VALUES (#{title}, #{content}, #{startTime}, #{endTime})
+    </insert>
+
+    <insert id="update" parameterType="com.finikes.oc.vote.entity.VoteActivity">
+        UPDATE t_vote_activity
+        SET title      = #{title},
+            content    = #{content},
+            start_time = #{startTime},
+            end_time   = #{endTime}
+        WHERE id = #{id}
+    </insert>
+
+    <select id="selectByPrimaryKey" parameterType="integer" resultType="com.finikes.oc.vote.entity.VoteActivity">
+        SELECT id, title, content, start_time, end_time
+        FROM t_vote_activity
+        WHERE id = #{primaryKey}
+    </select>
+
+    <select id="selectByTitleAndStartTimeAndEndTime" resultType="com.finikes.oc.vote.entity.VoteActivity">
+        SELECT id, title, content, start_time, end_time
+        FROM t_vote_activity WHERE 1 = 1
+        <if test="#{title != ''}">
+            AND title like CONCAT('%', #{title})
+        </if>
+        <if test="startTime != null and endTime != null">
+            AND start_time &gt;= #{startTime} AND start_time &lt;= #{endTime}
+        </if>
+    </select>
+
+    <delete id="deleteByPrimaryKey" parameterType="integer">
+        DELETE
+        FROM t_vote_activity
+        WHERE id = #{primaryKey}
+    </delete>
+</mapper>

+ 76 - 0
src/test/java/com/finikes/oc/vote/dao/VoteActivityDaoTest.java

@@ -0,0 +1,76 @@
+package com.finikes.oc.vote.dao;
+
+import com.finikes.oc.App;
+import com.finikes.oc.vote.entity.VoteActivity;
+import org.junit.Assert;
+import org.junit.jupiter.api.*;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = App.class)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+class VoteActivityDaoTest {
+
+    @Autowired
+    private VoteActivityDao voteActivityDao;
+
+    private static Integer voteActivityId;
+
+    @Order(1)
+    @Test
+    void insert() {
+        VoteActivity voteActivity = new VoteActivity();
+        voteActivity.setTitle(UUID.randomUUID().toString());
+        voteActivity.setContent(UUID.randomUUID().toString());
+        voteActivity.setStartTime(System.currentTimeMillis());
+        voteActivity.setEndTime(System.currentTimeMillis());
+        voteActivityDao.insert(voteActivity);
+        Assertions.assertNotEquals(voteActivity.getId(), 0);
+
+        voteActivityId = voteActivity.getId();
+    }
+
+
+    @Order(2)
+    @Test
+    void selectByPrimaryKey() {
+        VoteActivity activity = voteActivityDao.selectByPrimaryKey(voteActivityId);
+        Assertions.assertNotNull(activity);
+    }
+
+    @Order(3)
+    @Test
+    void update() {
+        VoteActivity activity = voteActivityDao.selectByPrimaryKey(voteActivityId);
+        activity.setTitle(UUID.randomUUID().toString());
+        activity.setContent(UUID.randomUUID().toString());
+
+        int effectRow = voteActivityDao.update(activity);
+        Assertions.assertEquals(effectRow, 1);
+    }
+
+
+    @Order(4)
+    @Test
+    void deleteByPrimaryKey() {
+        int effectRow = voteActivityDao.deleteByPrimaryKey(voteActivityId);
+        Assertions.assertEquals(effectRow, 1);
+    }
+
+    @Order(5)
+    @Test
+    void selectByTitleAndStartTimeAndEndTime() {
+        Date start = new Date(1680074706249L);
+        Date end = new Date(1680085342000L);
+        List<VoteActivity> list = voteActivityDao.selectByTitleAndStartTimeAndEndTime("pig", start.getTime(), end.getTime());
+        Assertions.assertEquals(list.size(), 1);
+    }
+
+}