瀏覽代碼

Merge branch 'dev-meta-user' of http://git.izouma.com/xiongzhu/raex_back into dev

 Conflicts:
	src/main/java/com/izouma/nineth/security/WebSecurityConfig.java
	src/main/java/com/izouma/nineth/service/AssetService.java
	src/main/vue/package-lock.json
	src/main/vue/src/router.js
sunkean 3 年之前
父節點
當前提交
01915cbe8a
共有 52 個文件被更改,包括 2478 次插入95 次删除
  1. 5 0
      pom.xml
  2. 0 3
      src/main/java/com/izouma/nineth/config/CacheConfig.java
  3. 2 0
      src/main/java/com/izouma/nineth/config/Constants.java
  4. 15 0
      src/main/java/com/izouma/nineth/config/WebSocketConfig.java
  5. 28 0
      src/main/java/com/izouma/nineth/domain/MetaAdvertRecord.java
  6. 60 0
      src/main/java/com/izouma/nineth/domain/MetaMMOLoginInfo.java
  7. 36 0
      src/main/java/com/izouma/nineth/domain/MetaTask.java
  8. 37 0
      src/main/java/com/izouma/nineth/domain/MetaTaskToUser.java
  9. 3 3
      src/main/java/com/izouma/nineth/domain/MetaUser.java
  10. 29 0
      src/main/java/com/izouma/nineth/dto/MMOMessage.java
  11. 20 0
      src/main/java/com/izouma/nineth/dto/MMOSingleMessage.java
  12. 27 0
      src/main/java/com/izouma/nineth/dto/MetaBonusSceneDTO.java
  13. 18 0
      src/main/java/com/izouma/nineth/enums/MetaTaskStatus.java
  14. 21 0
      src/main/java/com/izouma/nineth/enums/MetaTaskType.java
  15. 2 5
      src/main/java/com/izouma/nineth/repo/AssetRepo.java
  16. 18 0
      src/main/java/com/izouma/nineth/repo/MetaAdvertRecordRepo.java
  17. 14 0
      src/main/java/com/izouma/nineth/repo/MetaBonusSceneRepo.java
  18. 14 0
      src/main/java/com/izouma/nineth/repo/MetaMMOLoginInfoRepo.java
  19. 18 0
      src/main/java/com/izouma/nineth/repo/MetaTaskRepo.java
  20. 23 0
      src/main/java/com/izouma/nineth/repo/MetaTaskToUserRepo.java
  21. 11 2
      src/main/java/com/izouma/nineth/repo/MetaUserRepo.java
  22. 3 0
      src/main/java/com/izouma/nineth/security/WebSecurityConfig.java
  23. 3 3
      src/main/java/com/izouma/nineth/service/AssetService.java
  24. 20 0
      src/main/java/com/izouma/nineth/service/MetaAdvertRecordService.java
  25. 32 0
      src/main/java/com/izouma/nineth/service/MetaBonusSceneService.java
  26. 20 0
      src/main/java/com/izouma/nineth/service/MetaTaskService.java
  27. 75 0
      src/main/java/com/izouma/nineth/service/MetaTaskToUserService.java
  28. 20 0
      src/main/java/com/izouma/nineth/service/MetaUserService.java
  29. 98 0
      src/main/java/com/izouma/nineth/service/UserHoldCountCache.java
  30. 23 74
      src/main/java/com/izouma/nineth/service/UserHoldCountService.java
  31. 76 0
      src/main/java/com/izouma/nineth/web/MetaAdvertRecordController.java
  32. 24 2
      src/main/java/com/izouma/nineth/web/MetaBonusSceneController.java
  33. 8 1
      src/main/java/com/izouma/nineth/web/MetaPlayerInfoController.java
  34. 60 0
      src/main/java/com/izouma/nineth/web/MetaTaskController.java
  35. 47 0
      src/main/java/com/izouma/nineth/web/MetaTaskToUserController.java
  36. 68 0
      src/main/java/com/izouma/nineth/web/MetaUserController.java
  37. 0 2
      src/main/java/com/izouma/nineth/web/UserController.java
  38. 9 0
      src/main/java/com/izouma/nineth/web/UserHoldCountController.java
  39. 20 0
      src/main/java/com/izouma/nineth/web/UserHoldCountScheduleTask.java
  40. 441 0
      src/main/java/com/izouma/nineth/websocket/WebSocket.java
  41. 1 0
      src/main/resources/genjson/MetaAdvertRecord.json
  42. 1 0
      src/main/resources/genjson/MetaTask.json
  43. 1 0
      src/main/resources/genjson/MetaUser.json
  44. 56 0
      src/main/vue/src/router.js
  45. 120 0
      src/main/vue/src/views/MetaAdvertRecordEdit.vue
  46. 153 0
      src/main/vue/src/views/MetaAdvertRecordList.vue
  47. 99 0
      src/main/vue/src/views/MetaBonusSceneList.vue
  48. 132 0
      src/main/vue/src/views/MetaTaskEdit.vue
  49. 162 0
      src/main/vue/src/views/MetaTaskList.vue
  50. 120 0
      src/main/vue/src/views/MetaUserEdit.vue
  51. 175 0
      src/main/vue/src/views/MetaUserList.vue
  52. 10 0
      src/main/vue/src/views/UserHoldCountList.vue

+ 5 - 0
pom.xml

@@ -481,6 +481,11 @@
             <artifactId>bucket4j-core</artifactId>
             <version>7.5.0</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 0 - 3
src/main/java/com/izouma/nineth/config/CacheConfig.java

@@ -142,9 +142,6 @@ public class CacheConfig {
         cacheNamesConfigurationMap.put("blindBoxRare", RedisCacheConfiguration.defaultCacheConfig()
                 .entryTtl(Duration.ofSeconds(2))
                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
-        cacheNamesConfigurationMap.put("userHoldList", RedisCacheConfiguration.defaultCacheConfig()
-                .entryTtl(Duration.ofHours(1))
-                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
 
         RedisCacheManager redisCacheManager = RedisCacheManager.builder()
                 .cacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory()))

+ 2 - 0
src/main/java/com/izouma/nineth/config/Constants.java

@@ -23,6 +23,8 @@ public interface Constants {
 
     String ALIPAY_URL_SCHEME = "alipays://platformapi/startapp?saId=10000007&qrcode=";
 
+    String USER_HOLD_CACHE_KEY = "userHoldList";
+
     interface PayChannel {
         String SAND = "sandPay";
         String HM   = "hmPay";

+ 15 - 0
src/main/java/com/izouma/nineth/config/WebSocketConfig.java

@@ -0,0 +1,15 @@
+package com.izouma.nineth.config;
+
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketConfig {
+
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+}

+ 28 - 0
src/main/java/com/izouma/nineth/domain/MetaAdvertRecord.java

@@ -0,0 +1,28 @@
+package com.izouma.nineth.domain;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@ApiModel("元宇宙广告")
+public class MetaAdvertRecord extends BaseEntity {
+
+    @ApiModelProperty("广告图片")
+    private String pic;
+
+    @ApiModelProperty("链接地址")
+    private String link;
+
+    @ApiModelProperty("是否使用")
+    private boolean used;
+
+}

+ 60 - 0
src/main/java/com/izouma/nineth/domain/MetaMMOLoginInfo.java

@@ -0,0 +1,60 @@
+package com.izouma.nineth.domain;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import java.time.LocalDateTime;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "meta_mmo_login_info")
+@ApiModel("元宇宙玩家登陆信息")
+@Builder
+public class MetaMMOLoginInfo extends BaseEntity {
+
+    @ApiModelProperty("昵称")
+    private String nickname;
+
+    @ApiModelProperty("用户id")
+    private Long  userId;
+
+    @ApiModelProperty("区域id")
+    private Long  regionId;
+
+    @ApiModelProperty("城市id")
+    private Long  cityId;
+
+    @ApiModelProperty("上线时间")
+    private LocalDateTime onLineTime;
+
+    @ApiModelProperty("离线时间")
+    private LocalDateTime offLineTime;
+
+    @ApiModelProperty("UUID")
+    private String UUID;
+
+    @ApiModelProperty("角色")
+    private String role;
+
+    private Float axisX;
+
+    private Float axisY;
+
+    private Float axisZ;
+
+    private Float eulerX;
+
+    private Float eulerY;
+
+    private Float eulerZ;
+
+}

+ 36 - 0
src/main/java/com/izouma/nineth/domain/MetaTask.java

@@ -0,0 +1,36 @@
+package com.izouma.nineth.domain;
+
+
+import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.enums.MetaTaskType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@ApiModel("元宇宙任务")
+public class MetaTask extends BaseEntity{
+
+    @ApiModelProperty("任务名称")
+    @Searchable
+    private String name;
+
+    @ApiModelProperty("任务")
+    @Column(columnDefinition = "TEXT")
+    private String detail;
+
+    @ApiModelProperty("任务类型")
+    @Enumerated(EnumType.STRING)
+    private MetaTaskType type;
+
+}

+ 37 - 0
src/main/java/com/izouma/nineth/domain/MetaTaskToUser.java

@@ -0,0 +1,37 @@
+package com.izouma.nineth.domain;
+
+import com.izouma.nineth.enums.MetaTaskStatus;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import java.time.LocalDateTime;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@ApiModel("元宇宙玩家领取任务情况")
+public class MetaTaskToUser extends BaseEntity{
+
+    @ApiModelProperty("用户id")
+    private Long userId;
+
+    @ApiModelProperty("领取时间")
+    private LocalDateTime getTime;
+
+    @ApiModelProperty("完成时间")
+    private LocalDateTime finishTime;
+
+    @ApiModelProperty("任务id")
+    private Long taskId;
+
+    @ApiModelProperty("当前状态")
+    @Enumerated(EnumType.STRING)
+    private MetaTaskStatus status;
+}

+ 3 - 3
src/main/java/com/izouma/nineth/domain/MetaUser.java

@@ -1,5 +1,6 @@
 package com.izouma.nineth.domain;
 
+import com.izouma.nineth.annotations.Searchable;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
@@ -7,17 +8,16 @@ import lombok.Data;
 import lombok.NoArgsConstructor;
 
 import javax.persistence.Entity;
-import javax.persistence.Id;
 
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 @Entity
 @ApiModel("元宇宙内测用户")
-public class MetaUser extends BaseEntityNoID {
+public class MetaUser extends BaseEntity {
 
     @ApiModelProperty("用户手机号")
-    @Id
+    @Searchable
     private Long phone;
 
     @ApiModelProperty("是否允许登陆")

+ 29 - 0
src/main/java/com/izouma/nineth/dto/MMOMessage.java

@@ -0,0 +1,29 @@
+package com.izouma.nineth.dto;
+
+import com.izouma.nineth.domain.MetaMMOLoginInfo;
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ApiModel("单个消息")
+public class MMOMessage {
+
+    /**
+     * 1.地图玩家信息
+     * 2.移动
+     * 3.下线
+     * 4.玩家加入
+     * 5.自生位置信息
+     */
+    private Integer messageType;
+
+    private MetaMMOLoginInfo message;
+
+    private List<MetaMMOLoginInfo> map;
+}

+ 20 - 0
src/main/java/com/izouma/nineth/dto/MMOSingleMessage.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ApiModel("单个消息")
+public class MMOSingleMessage {
+
+    @ApiModelProperty("消息类型")
+    private Integer messageType;
+
+    @ApiModelProperty("消息体")
+    private String message;
+}

+ 27 - 0
src/main/java/com/izouma/nineth/dto/MetaBonusSceneDTO.java

@@ -0,0 +1,27 @@
+package com.izouma.nineth.dto;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class MetaBonusSceneDTO {
+
+    @ExcelProperty("用户id")
+    @ApiModelProperty("用户id")
+    private Long userId;
+
+    @ExcelProperty("个数")
+    @ApiModelProperty("触发个数")
+    private int num;
+
+    @ExcelProperty("完成时间")
+    @ApiModelProperty("触发时间")
+    private Date createdAt;
+}

+ 18 - 0
src/main/java/com/izouma/nineth/enums/MetaTaskStatus.java

@@ -0,0 +1,18 @@
+package com.izouma.nineth.enums;
+
+public enum MetaTaskStatus {
+
+    GET("领取"),
+
+    FINISH("完成");
+
+    private final String description;
+
+    MetaTaskStatus(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

+ 21 - 0
src/main/java/com/izouma/nineth/enums/MetaTaskType.java

@@ -0,0 +1,21 @@
+package com.izouma.nineth.enums;
+
+
+public enum MetaTaskType {
+
+    ONLY_ONE("一次领取任务"),
+
+    MULTIPLE("多次领取任务"),
+
+    DAILY("每日任务");
+
+    private final String description;
+
+    MetaTaskType(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

+ 2 - 5
src/main/java/com/izouma/nineth/repo/AssetRepo.java

@@ -104,11 +104,8 @@ public interface AssetRepo extends JpaRepository<Asset, Long>, JpaSpecificationE
 
     List<Asset> findAllByUserIdAndTypeAndOpenedAndCompanyId(Long userId, CollectionType type, Boolean opened, Long companyId);
 
-    @Query(nativeQuery = true, value = "SELECT asset.user_id userId,user.nickname nickname,user.username username,user.avatar avatar,asset.name,asset.prefix_name prefixName,count(*) num FROM asset left join user on asset.user_id = user.id where asset.user_id not in (1435297,4273750, 56302) and asset.status in ('NORMAL','TRADING','GIFTING','MINTING','AUCTIONING') GROUP BY asset.user_id ORDER BY count(*) DESC limit ?1,?2")
-    List<Map<String, String>> findByPage(int start, int end);
-
-    @Query(nativeQuery = true, value = "SELECT count(distinct user_id) FROM asset where status in ('NORMAL','TRADING','GIFTING','MINTING','AUCTIONING') ")
-    int totalElements();
+    @Query(nativeQuery = true, value = "SELECT * FROM (SELECT asset.user_id userId,user.nickname nickname,user.username username,user.avatar avatar,asset.name,asset.prefix_name prefixName,count(*) num FROM asset left join user on asset.user_id = user.id where asset.user_id not in (1435297,4273750, 56302, 7209) and asset.status in ('NORMAL','TRADING','GIFTING','MINTING','AUCTIONING') GROUP BY asset.user_id) a WHERE a.num > 10")
+    List<Map<String, String>> findAllUserHold();
 
     List<Asset> findAllByUserIdAndStatusIn(Long userId, List<AssetStatus> status);
 

+ 18 - 0
src/main/java/com/izouma/nineth/repo/MetaAdvertRecordRepo.java

@@ -0,0 +1,18 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.MetaAdvertRecord;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
+
+public interface MetaAdvertRecordRepo extends JpaRepository<MetaAdvertRecord, Long>, JpaSpecificationExecutor<MetaAdvertRecord> {
+    @Query("update MetaAdvertRecord t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    MetaAdvertRecord findByUsedAndDel(boolean used, boolean del);
+}

+ 14 - 0
src/main/java/com/izouma/nineth/repo/MetaBonusSceneRepo.java

@@ -3,9 +3,12 @@ package com.izouma.nineth.repo;
 import com.izouma.nineth.domain.MetaBonusScene;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
 
+import javax.transaction.Transactional;
 import java.util.List;
+import java.util.Map;
 
 public interface MetaBonusSceneRepo extends JpaRepository<MetaBonusScene, Long>, JpaSpecificationExecutor<MetaBonusScene> {
 
@@ -15,4 +18,15 @@ public interface MetaBonusSceneRepo extends JpaRepository<MetaBonusScene, Long>,
 
     @Query(nativeQuery = true, value = "select count(*) from (select count(*) num from meta_bonus_scene group by user_id )a where a.num >= 15")
     long countNum();
+
+    @Query("update MetaBonusScene t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    @Query(nativeQuery = true, value= "SELECT user_id userId,count(*) num,max( created_at ) createdAt FROM meta_bonus_scene GROUP BY user_id ORDER BY count(*) desc, max( created_at  ) desc limit ?1,?2 ")
+    List<Map<String, String>> findByPage(int start, int end);
+
+    @Query(nativeQuery = true, value = "SELECT count(a.user_id) FROM ( SELECT user_id FROM meta_bonus_scene GROUP BY user_id) a")
+    int totalElements();
 }

+ 14 - 0
src/main/java/com/izouma/nineth/repo/MetaMMOLoginInfoRepo.java

@@ -0,0 +1,14 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.MetaMMOLoginInfo;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+
+public interface MetaMMOLoginInfoRepo extends JpaRepository<MetaMMOLoginInfo, Long>, JpaSpecificationExecutor<MetaMMOLoginInfo> {
+
+    @Query(value = "select * from meta_mmo_login_info a where a.user_id = ?1 order by a.created_at desc limit 1",nativeQuery = true)
+    MetaMMOLoginInfo findLastByUserId(Long userId);
+
+    MetaMMOLoginInfo findByUUID(String UUID);
+}

+ 18 - 0
src/main/java/com/izouma/nineth/repo/MetaTaskRepo.java

@@ -0,0 +1,18 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.MetaTask;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
+
+public interface MetaTaskRepo extends JpaRepository<MetaTask, Long>, JpaSpecificationExecutor<MetaTask> {
+    @Query("update MetaTask t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    MetaTask findByIdAndDel(Long taskId, boolean del);
+}

+ 23 - 0
src/main/java/com/izouma/nineth/repo/MetaTaskToUserRepo.java

@@ -0,0 +1,23 @@
+package com.izouma.nineth.repo;
+
+
+import com.izouma.nineth.domain.MetaTaskToUser;
+import com.izouma.nineth.enums.MetaTaskStatus;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public interface MetaTaskToUserRepo extends JpaRepository<MetaTaskToUser, Long>, JpaSpecificationExecutor<MetaTaskToUser> {
+
+    MetaTaskToUser findByUserIdAndTaskIdAndDel(Long userId, Long taskId, boolean del);
+
+    @Query(value = "select * from meta_task_to_user where user_id = ?1 and task_id = ?2 and del = false and get_time > ?3", nativeQuery = true)
+    MetaTaskToUser findByCondition(Long userId, Long taskId, LocalDateTime startTime);
+
+    MetaTaskToUser findByIdAndDel(Long id, boolean del);
+
+    List<MetaTaskToUser> findAllByUserIdAndStatusAndDel(Long userId, MetaTaskStatus status, boolean del);
+}

+ 11 - 2
src/main/java/com/izouma/nineth/repo/MetaUserRepo.java

@@ -3,8 +3,17 @@ package com.izouma.nineth.repo;
 import com.izouma.nineth.domain.MetaUser;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
 
 public interface MetaUserRepo extends JpaRepository<MetaUser, Long>, JpaSpecificationExecutor<MetaUser> {
+    @Query("update MetaUser t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    MetaUser findByPhoneAndDel(Long phone, boolean del);
 
-    MetaUser findByPhone(Long phone);
-}
+}

+ 3 - 0
src/main/java/com/izouma/nineth/security/WebSecurityConfig.java

@@ -149,6 +149,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .antMatchers("/metaPlayerWear/**").permitAll()
                 .antMatchers("/userHold/app/top").permitAll()
                 .antMatchers("/company/get/*").permitAll()
+                .antMatchers("/websocket/**").permitAll()
+                .antMatchers("/metaAdvertRecord/metaQuery").permitAll()
+                .antMatchers("/metaTask/**").permitAll()
                 // all other requests need to be authenticated
                 .anyRequest().authenticated().and()
                 // make sure we use stateless session; session won't be used to

+ 3 - 3
src/main/java/com/izouma/nineth/service/AssetService.java

@@ -260,9 +260,9 @@ public class AssetService {
 
     public void publicShow(Long id) {
         Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
-//        if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
-//            throw new BusinessException("此藏品不属于你");
-//        }
+        if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
+            throw new BusinessException("此藏品不属于你");
+        }
         if (asset.getLockTo() != null && asset.getLockTo().isAfter(LocalDateTime.now())) {
             throw new BusinessException("已锁仓,不能上架展示");
         }

+ 20 - 0
src/main/java/com/izouma/nineth/service/MetaAdvertRecordService.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.domain.MetaAdvertRecord;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.repo.MetaAdvertRecordRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class MetaAdvertRecordService {
+
+    private MetaAdvertRecordRepo metaAdvertRecordRepo;
+
+    public Page<MetaAdvertRecord> all(PageQuery pageQuery) {
+        return metaAdvertRecordRepo.findAll(JpaUtils.toSpecification(pageQuery, MetaAdvertRecord.class), JpaUtils.toPageRequest(pageQuery));
+    }
+}

+ 32 - 0
src/main/java/com/izouma/nineth/service/MetaBonusSceneService.java

@@ -0,0 +1,32 @@
+package com.izouma.nineth.service;
+
+import com.alibaba.fastjson.JSONArray;
+import com.izouma.nineth.dto.MetaBonusSceneDTO;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.dto.PageWrapper;
+import com.izouma.nineth.repo.MetaBonusSceneRepo;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+@Service
+@AllArgsConstructor
+public class MetaBonusSceneService {
+
+    private MetaBonusSceneRepo metaBonusSceneRepo;
+
+    public PageWrapper<MetaBonusSceneDTO> all(PageQuery pageQuery) {
+        int page = pageQuery.getPage();
+        int size = pageQuery.getSize();
+        int start = page * size;
+        List<Map<String, String>> map = metaBonusSceneRepo.findByPage(start, size);
+        JSONArray jsonArray = new JSONArray();
+        jsonArray.addAll(map);
+        List<MetaBonusSceneDTO> metaBonusScenes = jsonArray.toJavaList(MetaBonusSceneDTO.class);
+        int totalElements = metaBonusSceneRepo.totalElements();
+        return new PageWrapper<>(metaBonusScenes, page,
+                size, totalElements);
+    }
+}

+ 20 - 0
src/main/java/com/izouma/nineth/service/MetaTaskService.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.domain.MetaTask;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.repo.MetaTaskRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class MetaTaskService {
+
+    private MetaTaskRepo metaTaskRepo;
+
+    public Page<MetaTask> all(PageQuery pageQuery) {
+        return metaTaskRepo.findAll(JpaUtils.toSpecification(pageQuery, MetaTask.class), JpaUtils.toPageRequest(pageQuery));
+    }
+}

+ 75 - 0
src/main/java/com/izouma/nineth/service/MetaTaskToUserService.java

@@ -0,0 +1,75 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.domain.MetaTask;
+import com.izouma.nineth.domain.MetaTaskToUser;
+import com.izouma.nineth.dto.MetaRestResult;
+import com.izouma.nineth.enums.MetaTaskStatus;
+import com.izouma.nineth.repo.MetaTaskRepo;
+import com.izouma.nineth.repo.MetaTaskToUserRepo;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+@Service
+@AllArgsConstructor
+@Slf4j
+public class MetaTaskToUserService {
+
+    private MetaTaskToUserRepo metaTaskToUserRepo;
+
+    private MetaTaskRepo metaTaskRepo;
+
+
+    public MetaRestResult<MetaTaskToUser> getTask(MetaTaskToUser metaTaskToUser) {
+        if (Objects.nonNull(metaTaskToUser.getId())) {
+            return MetaRestResult.returnError("参数不合法:id必须为空");
+        }
+        if (Objects.isNull(metaTaskToUser.getTaskId())) {
+            return MetaRestResult.returnError("参数不合法:任务id不可为空");
+        }
+        if (Objects.isNull(metaTaskToUser.getUserId())) {
+            return MetaRestResult.returnError("参数不合法:用户id不可为空");
+        }
+        MetaTask metaTask = metaTaskRepo.findByIdAndDel(metaTaskToUser.getTaskId(), false);
+        if (Objects.isNull(metaTask)) {
+            String errMsg = String.format("根据任务id[%S]查询不到任务详情", metaTaskToUser.getTaskId());
+            log.info(errMsg);
+            return MetaRestResult.returnError(errMsg);
+        }
+        if (Objects.isNull(metaTask.getType())) {
+            String errMsg = String.format("任务[%S]缺少任务类型", metaTaskToUser.getTaskId());
+            log.info(errMsg);
+            return MetaRestResult.returnError(errMsg);
+        }
+        switch (metaTask.getType()) {
+            case ONLY_ONE:
+                MetaTaskToUser dbMetaTaskToUser = metaTaskToUserRepo.findByUserIdAndTaskIdAndDel(metaTaskToUser.getUserId(), metaTaskToUser.getTaskId(), false);
+                if (Objects.nonNull(dbMetaTaskToUser)) {
+                    String errMsg = String.format("任务[%S]只允许领取一次,当前用户已在[%S]领取过该任务", metaTask.getName(), dbMetaTaskToUser.getGetTime());
+                    log.info(errMsg);
+                    return MetaRestResult.returnError(errMsg);
+                }
+                break;
+            case DAILY:
+                LocalDateTime now = LocalDateTime.now();
+                LocalDateTime startTime = now.withHour(0).withMinute(0).withSecond(0);
+                MetaTaskToUser dailyMetaTaskToUser = metaTaskToUserRepo.findByCondition(metaTaskToUser.getUserId(), metaTaskToUser.getTaskId(), startTime);
+                if (Objects.nonNull(dailyMetaTaskToUser)) {
+                    String errMsg = String.format("任务[%S]每天只允许领取一次,当前用户已在[%S]领取过该任务", metaTask.getName(), dailyMetaTaskToUser.getGetTime());
+                    log.info(errMsg);
+                    return MetaRestResult.returnError(errMsg);
+                }
+                break;
+            case MULTIPLE:
+                break;
+            default:
+                return MetaRestResult.returnError(String.format("不存在的任务类型[%S]", metaTask.getType()));
+        }
+        metaTaskToUser.setGetTime(LocalDateTime.now());
+        metaTaskToUser.setStatus(MetaTaskStatus.GET);
+        return MetaRestResult.returnSuccess(metaTaskToUserRepo.save(metaTaskToUser));
+    }
+}

+ 20 - 0
src/main/java/com/izouma/nineth/service/MetaUserService.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.domain.MetaUser;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.repo.MetaUserRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class MetaUserService {
+
+    private MetaUserRepo metaUserRepo;
+
+    public Page<MetaUser> all(PageQuery pageQuery) {
+        return metaUserRepo.findAll(JpaUtils.toSpecification(pageQuery, MetaUser.class), JpaUtils.toPageRequest(pageQuery));
+    }
+}

+ 98 - 0
src/main/java/com/izouma/nineth/service/UserHoldCountCache.java

@@ -0,0 +1,98 @@
+package com.izouma.nineth.service;
+
+import com.alibaba.fastjson.JSONArray;
+import com.izouma.nineth.config.Constants;
+import com.izouma.nineth.domain.Asset;
+import com.izouma.nineth.domain.Collection;
+import com.izouma.nineth.dto.UserHoldDTO;
+import com.izouma.nineth.enums.AssetStatus;
+import com.izouma.nineth.repo.AssetRepo;
+import com.izouma.nineth.repo.CollectionRepo;
+import lombok.AllArgsConstructor;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+@Service
+@AllArgsConstructor
+public class UserHoldCountCache {
+
+    private CollectionRepo collectionRepo;
+
+    private AssetRepo assetRepo;
+
+    private RedisTemplate<String, Object> redisTemplate;
+
+    public void allUserHold() {
+        // 清理用户持仓缓存
+        redisTemplate.delete(Constants.USER_HOLD_CACHE_KEY);
+
+        Map<String, BigDecimal> minPriceMap = new HashMap<>();
+        List<Map<String, String>> assets = assetRepo.findAllUserHold();
+        JSONArray jsonArray = new JSONArray();
+        jsonArray.addAll(assets);
+        List<UserHoldDTO> userHoldDTOS = jsonArray.toJavaList(UserHoldDTO.class);
+        userHoldDTOS.forEach(userHoldDTO -> {
+            List<Asset> userAssets = assetRepo.findAllByUserIdAndStatusIn(userHoldDTO.getUserId(), new ArrayList<>(Arrays.asList(AssetStatus.NORMAL, AssetStatus.TRADING, AssetStatus.GIFTING, AssetStatus.MINTING, AssetStatus.AUCTIONING)));
+            // 分类计算各资产寄售最低价
+            userAssets.forEach(asset -> {
+                if (StringUtils.isBlank(asset.getPrefixName())) {
+                    return;
+                }
+                if (asset.getName().contains(Constants.Rarity.SR_LIKE) && !asset.getName().contains(Constants.Rarity.SSR_LIKE)) {
+                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.SR_LIKE, Constants.Rarity.SSR_LIKE);
+                    return;
+                }
+                if (asset.getName().contains(Constants.Rarity.U_LIKE)) {
+                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.U_LIKE, Constants.Rarity.R_LIKE);
+                    return;
+                }
+                if (asset.getName().contains(Constants.Rarity.R_LIKE) && !asset.getName().contains(Constants.Rarity.SR_LIKE)) {
+                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.R_LIKE, Constants.Rarity.SR_LIKE);
+                    return;
+                }
+                if (asset.getName().contains(Constants.Rarity.SSR_LIKE)) {
+                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.SSR_LIKE, Constants.Rarity.U_LIKE);
+                    return;
+                }
+                calculatePrice(userHoldDTO, asset, minPriceMap, null, null);
+            });
+        });
+        if (CollectionUtils.isNotEmpty(userHoldDTOS)) {
+            userHoldDTOS.sort(Comparator.comparing(UserHoldDTO::getPrice).reversed());
+        }
+        // 重新缓存持仓信息
+        redisTemplate.opsForValue().set(Constants.USER_HOLD_CACHE_KEY, userHoldDTOS);
+    }
+
+    private void calculatePrice(UserHoldDTO userHoldDTO, Asset asset, Map<String, BigDecimal> minPriceMap, String nameLike, String nameNotLike) {
+        BigDecimal minPrice;
+        String minPriceMapKey = StringUtils.isBlank(nameLike) ? asset.getPrefixName() : asset.getPrefixName().concat(nameLike);
+        if (minPriceMap.containsKey(minPriceMapKey)) {
+            minPrice = minPriceMap.get(minPriceMapKey);
+        } else {
+            // 该系列寄售最低价
+            if (StringUtils.isBlank(nameLike)) {
+                minPrice = collectionRepo.findMinPriceByPrefixName(asset.getPrefixName());
+            } else {
+                minPrice = collectionRepo.findMinPriceByNameAndPrefixName(asset.getPrefixName(), nameLike, nameNotLike);
+            }
+            // 该系列没有寄售的话取originalPrice
+            if (Objects.isNull(minPrice)) {
+                Collection collection = collectionRepo.findById(asset.getCollectionId()).orElse(null);
+                if (Objects.isNull(collection)) {
+                    minPrice = BigDecimal.ZERO;
+                } else {
+                    minPrice = collection.getOriginalPrice();
+                }
+            }
+//            minPrice = Objects.isNull(minPrice) ? BigDecimal.ZERO : minPrice;
+            minPriceMap.put(minPriceMapKey, minPrice);
+        }
+        userHoldDTO.setPrice(userHoldDTO.getPrice().add(minPrice));
+    }
+}

+ 23 - 74
src/main/java/com/izouma/nineth/service/UserHoldCountService.java

@@ -1,97 +1,46 @@
 package com.izouma.nineth.service;
 
-import com.alibaba.fastjson.JSONArray;
 import com.izouma.nineth.config.Constants;
-import com.izouma.nineth.domain.Asset;
-import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.PageWrapper;
 import com.izouma.nineth.dto.UserHoldDTO;
-import com.izouma.nineth.enums.AssetStatus;
-import com.izouma.nineth.repo.AssetRepo;
-import com.izouma.nineth.repo.CollectionRepo;
+import com.izouma.nineth.exception.BusinessException;
 import lombok.AllArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.cache.annotation.Cacheable;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
-import java.math.BigDecimal;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
 
 @Service
 @AllArgsConstructor
 public class UserHoldCountService {
+    private RedisTemplate<String, Object> redisTemplate;
 
-    private AssetRepo assetRepo;
-
-    private CollectionRepo collectionRepo;
-
-    @Cacheable(value = "userHoldList", key = "#pageQuery.hashCode()")
     public PageWrapper<UserHoldDTO> all(PageQuery pageQuery) {
+        if (Boolean.FALSE.equals(redisTemplate.hasKey(Constants.USER_HOLD_CACHE_KEY))) {
+            throw new BusinessException("key失效,请刷新!如果已经刷新,请等待!");
+        }
         int page = pageQuery.getPage();
         int size = pageQuery.getSize();
         int start = page * size;
-        Map<String, BigDecimal> minPriceMap = new HashMap<>();
-        int totalElements = assetRepo.totalElements();
-        List<Map<String, String>> assets = assetRepo.findByPage(start, size);
-        JSONArray jsonArray = new JSONArray();
-        jsonArray.addAll(assets);
-        List<UserHoldDTO> userHoldDTOS = jsonArray.toJavaList(UserHoldDTO.class);
-        userHoldDTOS.forEach(userHoldDTO -> {
-            List<Asset> userAssets = assetRepo.findAllByUserIdAndStatusIn(userHoldDTO.getUserId(), new ArrayList<>(Arrays.asList(AssetStatus.NORMAL, AssetStatus.TRADING, AssetStatus.GIFTING, AssetStatus.MINTING, AssetStatus.AUCTIONING)));
-            // 分类计算各资产寄售最低价
-            userAssets.forEach(asset -> {
-                if (StringUtils.isBlank(asset.getPrefixName())) {
-                    return;
-                }
-                if (asset.getName().contains(Constants.Rarity.SR_LIKE) && !asset.getName().contains(Constants.Rarity.SSR_LIKE)) {
-                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.SR_LIKE, Constants.Rarity.SSR_LIKE);
-                    return;
-                }
-                if (asset.getName().contains(Constants.Rarity.U_LIKE)) {
-                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.U_LIKE, Constants.Rarity.R_LIKE);
-                    return;
-                }
-                if (asset.getName().contains(Constants.Rarity.R_LIKE) && !asset.getName().contains(Constants.Rarity.SR_LIKE)) {
-                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.R_LIKE, Constants.Rarity.SR_LIKE);
-                    return;
-                }
-                if (asset.getName().contains(Constants.Rarity.SSR_LIKE)) {
-                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.SSR_LIKE, Constants.Rarity.U_LIKE);
-                    return;
-                }
-                calculatePrice(userHoldDTO, asset, minPriceMap, null, null);
-            });
-        });
-        return new PageWrapper<>(userHoldDTOS, page,
-                size, totalElements);
-    }
-
-    private void calculatePrice(UserHoldDTO userHoldDTO, Asset asset, Map<String, BigDecimal> minPriceMap, String nameLike, String nameNotLike) {
-        BigDecimal minPrice;
-        String minPriceMapKey = StringUtils.isBlank(nameLike) ? asset.getPrefixName() : asset.getPrefixName().concat(nameLike);
-        if (minPriceMap.containsKey(minPriceMapKey)) {
-            minPrice = minPriceMap.get(minPriceMapKey);
+        List<UserHoldDTO> userHoldDTOS = (List<UserHoldDTO>) redisTemplate.opsForValue().get(Constants.USER_HOLD_CACHE_KEY);
+        int totalElements;
+        if (CollectionUtils.isEmpty(userHoldDTOS)) {
+            totalElements = 0;
+            return new PageWrapper<>(new ArrayList<>(), page,
+                    size, totalElements);
+        }
+        totalElements = userHoldDTOS.size();
+        List<UserHoldDTO> newUserHoldList;
+        if (userHoldDTOS.size() < start + size) {
+            newUserHoldList = userHoldDTOS.subList(start, userHoldDTOS.size());
         } else {
-            // 该系列寄售最低价
-            if (StringUtils.isBlank(nameLike)) {
-                minPrice = collectionRepo.findMinPriceByPrefixName(asset.getPrefixName());
-            } else {
-                minPrice = collectionRepo.findMinPriceByNameAndPrefixName(asset.getPrefixName(), nameLike, nameNotLike);
-            }
-            // 该系列没有寄售的话取originalPrice
-            if(Objects.isNull(minPrice)) {
-                Collection collection = collectionRepo.findById(asset.getCollectionId()).orElse(null);
-                if (Objects.isNull(collection)) {
-                    minPrice = BigDecimal.ZERO;
-                } else {
-                    minPrice = collection.getOriginalPrice();
-                }
-            }
-//            minPrice = Objects.isNull(minPrice) ? BigDecimal.ZERO : minPrice;
-            minPriceMap.put(minPriceMapKey, minPrice);
+            newUserHoldList = userHoldDTOS.subList(start, start + size);
         }
-        userHoldDTO.setPrice(userHoldDTO.getPrice().add(minPrice));
+        return new PageWrapper<>(newUserHoldList, page,
+                size, totalElements);
     }
 
 }

+ 76 - 0
src/main/java/com/izouma/nineth/web/MetaAdvertRecordController.java

@@ -0,0 +1,76 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.domain.MetaAdvertRecord;
+import com.izouma.nineth.dto.MetaRestResult;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.MetaAdvertRecordRepo;
+import com.izouma.nineth.service.MetaAdvertRecordService;
+import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/metaAdvertRecord")
+@AllArgsConstructor
+public class MetaAdvertRecordController extends BaseController {
+    private MetaAdvertRecordService metaAdvertRecordService;
+    private MetaAdvertRecordRepo metaAdvertRecordRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public MetaAdvertRecord save(@RequestBody MetaAdvertRecord record) {
+
+        MetaAdvertRecord metaAdvertRecord = metaAdvertRecordRepo.findByUsedAndDel(true, false);
+        if (Objects.nonNull(metaAdvertRecord) && !Objects.equals(metaAdvertRecord.getId(), record.getId())) {
+            throw new BusinessException("当前以存在使用中的广告,请先取消使用中的广告!");
+        }
+        if (record.getId() != null) {
+            MetaAdvertRecord orig = metaAdvertRecordRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return metaAdvertRecordRepo.save(orig);
+        }
+        return metaAdvertRecordRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<MetaAdvertRecord> all(@RequestBody PageQuery pageQuery) {
+        return metaAdvertRecordService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public MetaAdvertRecord get(@PathVariable Long id) {
+        return metaAdvertRecordRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        metaAdvertRecordRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<MetaAdvertRecord> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+
+    @GetMapping("/metaQuery")
+    public MetaRestResult<MetaAdvertRecord> queryForMeta() {
+        MetaAdvertRecord metaAdvertRecord = metaAdvertRecordRepo.findByUsedAndDel(true, false);
+        if (Objects.isNull(metaAdvertRecord)) {
+            return MetaRestResult.returnError("当前未配置广告,请先配置!");
+        }
+        return MetaRestResult.returnSuccess(metaAdvertRecord);
+    }
+}
+

+ 24 - 2
src/main/java/com/izouma/nineth/web/MetaBonusSceneController.java

@@ -2,11 +2,18 @@ package com.izouma.nineth.web;
 
 
 import com.izouma.nineth.domain.MetaBonusScene;
+import com.izouma.nineth.dto.MetaBonusSceneDTO;
 import com.izouma.nineth.dto.MetaRestResult;
+import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.repo.MetaBonusSceneRepo;
+import com.izouma.nineth.service.MetaBonusSceneService;
+import com.izouma.nineth.utils.excel.ExcelUtils;
 import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 import java.util.List;
 import java.util.Objects;
 
@@ -15,6 +22,8 @@ import java.util.Objects;
 @AllArgsConstructor
 public class MetaBonusSceneController {
 
+    private MetaBonusSceneService metaBonusSceneService;
+
     private MetaBonusSceneRepo metaBonusSceneRepo;
 
     @GetMapping("/{userId}/find")
@@ -32,11 +41,11 @@ public class MetaBonusSceneController {
             return MetaRestResult.returnError("参数错误:缺少用户id", false);
         }
         if (!(0 < metaBonusScene.getButton())) {
-            return  MetaRestResult.returnError("参数错误:缺少按钮信息", false);
+            return MetaRestResult.returnError("参数错误:缺少按钮信息", false);
         }
         MetaBonusScene dbMetaBonusScene = metaBonusSceneRepo.findByUserIdAndButton(metaBonusScene.getUserId(), metaBonusScene.getButton());
         if (Objects.nonNull(dbMetaBonusScene)) {
-          return MetaRestResult.returnError("该玩家已经触发过同一个彩蛋,无法多次触发", false);
+            return MetaRestResult.returnError("该玩家已经触发过同一个彩蛋,无法多次触发", false);
         }
         metaBonusSceneRepo.save(metaBonusScene);
         return MetaRestResult.returnSuccess("触发成功", true);
@@ -47,4 +56,17 @@ public class MetaBonusSceneController {
         long num = metaBonusSceneRepo.countNum();
         return MetaRestResult.returnSuccess(Integer.parseInt(String.valueOf(num)));
     }
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<MetaBonusSceneDTO> all(@RequestBody PageQuery pageQuery) {
+        return metaBonusSceneService.all(pageQuery).toPage();
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<MetaBonusSceneDTO> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
 }

+ 8 - 1
src/main/java/com/izouma/nineth/web/MetaPlayerInfoController.java

@@ -3,6 +3,7 @@ package com.izouma.nineth.web;
 import cn.hutool.core.collection.CollectionUtil;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.MetaRestResult;
+import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.service.MetaPlayerInfoService;
 import lombok.AllArgsConstructor;
@@ -89,10 +90,16 @@ public class MetaPlayerInfoController {
 
     @GetMapping("/{phone}/allowLogin")
     public MetaRestResult<Boolean> allowLogin(@PathVariable Long phone) {
-        MetaUser metaUser = metaUserRepo.findByPhone(phone);
+        MetaUser metaUser = metaUserRepo.findByPhoneAndDel(phone, Boolean.FALSE);
         if (Objects.isNull(metaUser)) {
             return MetaRestResult.returnSuccess(Boolean.FALSE);
         }
         return MetaRestResult.returnSuccess(metaUser.isAllowLogin());
     }
+
+    @GetMapping("/{userId}/useCollectionPic")
+    public MetaRestResult<Boolean> useCollectionPic(@PathVariable Long userId) {
+        User user = userRepo.findById(userId).orElseThrow(new BusinessException(String.format("根据userId: [%S]查询不到玩家信息", userId)));
+        return MetaRestResult.returnSuccess(user.isUseCollectionPic());
+    }
 }

+ 60 - 0
src/main/java/com/izouma/nineth/web/MetaTaskController.java

@@ -0,0 +1,60 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.domain.MetaTask;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.MetaTaskRepo;
+import com.izouma.nineth.service.MetaTaskService;
+import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/metaTask")
+@AllArgsConstructor
+public class MetaTaskController extends BaseController {
+    private MetaTaskService metaTaskService;
+    private MetaTaskRepo metaTaskRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public MetaTask save(@RequestBody MetaTask record) {
+        if (record.getId() != null) {
+            MetaTask orig = metaTaskRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return metaTaskRepo.save(orig);
+        }
+        return metaTaskRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<MetaTask> all(@RequestBody PageQuery pageQuery) {
+        return metaTaskService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public MetaTask get(@PathVariable Long id) {
+        return metaTaskRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        metaTaskRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<MetaTask> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 47 - 0
src/main/java/com/izouma/nineth/web/MetaTaskToUserController.java

@@ -0,0 +1,47 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.domain.MetaTaskToUser;
+import com.izouma.nineth.dto.MetaRestResult;
+import com.izouma.nineth.enums.MetaTaskStatus;
+import com.izouma.nineth.repo.MetaTaskToUserRepo;
+import com.izouma.nineth.service.MetaTaskToUserService;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/metaTask")
+@AllArgsConstructor
+public class MetaTaskToUserController {
+
+    private MetaTaskToUserService metaTaskToUserService;
+
+    private MetaTaskToUserRepo metaTaskToUserRepo;
+
+    @PostMapping("/getTask")
+    public MetaRestResult<MetaTaskToUser> getTask(@RequestBody MetaTaskToUser metaTaskToUser) {
+        return metaTaskToUserService.getTask(metaTaskToUser);
+    }
+
+    @PostMapping("/{id}/finishTask")
+    public MetaRestResult<MetaTaskToUser> finishTask(@PathVariable Long id) {
+        MetaTaskToUser metaTaskToUser = metaTaskToUserRepo.findByIdAndDel(id, false);
+        if (Objects.isNull(metaTaskToUser)) {
+            return MetaRestResult.returnError("");
+        }
+        if (MetaTaskStatus.FINISH.equals(metaTaskToUser.getStatus())) {
+            return MetaRestResult.returnError("当前任务已经完成");
+        }
+        metaTaskToUser.setFinishTime(LocalDateTime.now());
+        metaTaskToUser.setStatus(MetaTaskStatus.FINISH);
+        return MetaRestResult.returnSuccess(metaTaskToUserRepo.save(metaTaskToUser));
+    }
+
+    @GetMapping("/{userId}/{status}/findByStatus")
+    public MetaRestResult<List<MetaTaskToUser>> findByStatus(@PathVariable Long userId, @PathVariable MetaTaskStatus status) {
+        return MetaRestResult.returnSuccess(metaTaskToUserRepo.findAllByUserIdAndStatusAndDel(userId, status, false));
+    }
+}

+ 68 - 0
src/main/java/com/izouma/nineth/web/MetaUserController.java

@@ -0,0 +1,68 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.domain.MetaUser;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.MetaUserRepo;
+import com.izouma.nineth.service.MetaUserService;
+import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/metaUser")
+@AllArgsConstructor
+public class MetaUserController extends BaseController {
+    private MetaUserService metaUserService;
+    private MetaUserRepo metaUserRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public MetaUser save(@RequestBody MetaUser record) {
+        if (Objects.isNull(record.getPhone())) {
+            throw new BusinessException("手机号不可为空");
+        }
+        MetaUser metaUser = metaUserRepo.findByPhoneAndDel(record.getPhone(), false);
+        if (Objects.nonNull(metaUser) && !Objects.equals(metaUser.getId(), record.getId())) {
+            throw new BusinessException("已经存在该手机号,如需调整内测资格请编辑!");
+        }
+        if (record.getId() != null) {
+            MetaUser orig = metaUserRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return metaUserRepo.save(orig);
+        }
+        return metaUserRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<MetaUser> all(@RequestBody PageQuery pageQuery) {
+        return metaUserService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public MetaUser get(@PathVariable Long id) {
+        return metaUserRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        metaUserRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<MetaUser> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 0 - 2
src/main/java/com/izouma/nineth/web/UserController.java

@@ -25,9 +25,7 @@ import lombok.AllArgsConstructor;
 import me.chanjar.weixin.common.error.WxErrorException;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.ObjectUtils;
-import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.BeanUtils;
 import org.springframework.data.domain.Page;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;

+ 9 - 0
src/main/java/com/izouma/nineth/web/UserHoldCountController.java

@@ -3,6 +3,7 @@ package com.izouma.nineth.web;
 import com.alibaba.excel.util.CollectionUtils;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.UserHoldDTO;
+import com.izouma.nineth.service.UserHoldCountCache;
 import com.izouma.nineth.service.UserHoldCountService;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import lombok.AllArgsConstructor;
@@ -21,6 +22,8 @@ public class UserHoldCountController {
 
     private UserHoldCountService userHoldCountService;
 
+    private UserHoldCountCache userHoldCountCache;
+
     @PostMapping("/all")
     public Page<UserHoldDTO> all(@RequestBody PageQuery pageQuery) {
         return userHoldCountService.all(pageQuery).toPage();
@@ -59,4 +62,10 @@ public class UserHoldCountController {
         List<UserHoldDTO> data = all(pageQuery).getContent();
         ExcelUtils.export(response, data);
     }
+
+    @GetMapping("/refresh")
+    public void refresh() {
+        // 重新缓存
+        userHoldCountCache.allUserHold();
+    }
 }

+ 20 - 0
src/main/java/com/izouma/nineth/web/UserHoldCountScheduleTask.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.web;
+
+
+import lombok.AllArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+@Configuration
+@EnableScheduling
+@AllArgsConstructor
+public class UserHoldCountScheduleTask {
+
+    private UserHoldCountController userHoldCountController;
+
+    @Scheduled(cron = "0 0 22 ? * SUN")
+    private void configureTasks() {
+        userHoldCountController.refresh();
+    }
+}

+ 441 - 0
src/main/java/com/izouma/nineth/websocket/WebSocket.java

@@ -0,0 +1,441 @@
+package com.izouma.nineth.websocket;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.nineth.domain.MetaMMOLoginInfo;
+import com.izouma.nineth.dto.MMOMessage;
+import com.izouma.nineth.dto.MMOSingleMessage;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.MetaMMOLoginInfoRepo;
+import com.izouma.nineth.utils.ApplicationContextUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+@Service
+@ServerEndpoint(value = "/websocket/mmo/{nickName}/{userId}")
+@Slf4j
+public class WebSocket {
+
+    /**
+     * 当前在线的客户端map
+     */
+    private static Map<String, WebSocket> clients = new ConcurrentHashMap();
+
+    /**
+     * session
+     */
+    private Session session;
+
+    /**
+     * 用户id
+     */
+    private String userId;
+
+    /**
+     * uuid
+     */
+    private String uuid;
+
+    /**
+     * key
+     */
+    private String key;
+
+    /**
+     * cityId
+     */
+    private Long cityId;
+
+    /**
+     * 区域id
+     */
+    private Long regionId;
+
+    private final RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
+
+    Session getSession() {
+        return this.session;
+    }
+
+    private MetaMMOLoginInfoRepo metaMMOLoginInfoRepo;
+
+    public void init() {
+        if (this.metaMMOLoginInfoRepo == null) {
+            metaMMOLoginInfoRepo = (MetaMMOLoginInfoRepo) ApplicationContextUtil.getBean("metaMMOLoginInfoRepo");
+        }
+    }
+
+    @OnOpen
+    public void onOpen(@PathParam("nickName") String nickName, @PathParam("userId") String userId, Session session) {
+        init();
+        try {
+            // 判断当前玩家是否在线
+            if (clients.containsKey(userId)) {
+                WebSocket webSocket = clients.get(userId);
+                // 关闭当前的链接
+                MMOSingleMessage mmoSingleMessage = new MMOSingleMessage();
+                mmoSingleMessage.setMessageType(6);
+                mmoSingleMessage.setMessage("您已在其他地方登录");
+                webSocket.sendMessageTo(JSON.toJSONString(mmoSingleMessage), userId);
+                webSocket.getSession().close();
+            }
+            // 获取上次登录的信息
+            MetaMMOLoginInfo metaMMOLoginInfo = metaMMOLoginInfoRepo.findLastByUserId(Long.parseLong(userId));
+            if (Objects.isNull(metaMMOLoginInfo)) {
+                // 首次登陆 初始化玩家信息
+                metaMMOLoginInfo = new MetaMMOLoginInfo();
+                metaMMOLoginInfo.setUserId(Long.parseLong(userId));
+                metaMMOLoginInfo.setNickname(nickName);
+                metaMMOLoginInfo.setRegionId(0L);
+                metaMMOLoginInfo.setCityId(0L);
+                metaMMOLoginInfo.setAxisX(0F);
+                metaMMOLoginInfo.setAxisY(0F);
+                metaMMOLoginInfo.setAxisZ(0F);
+                metaMMOLoginInfo.setEulerX(0F);
+                metaMMOLoginInfo.setEulerY(0F);
+                metaMMOLoginInfo.setEulerZ(0F);
+            }
+            log.info("现在来连接的sessionId:" + session.getId() + "玩家id:" + userId + "玩家昵称" + nickName);
+            this.userId = userId;
+            this.session = session;
+            // 生成缓存key 规则 --> cityId:regionId
+            this.key = String.valueOf(metaMMOLoginInfo.getCityId()).concat(":").concat(String.valueOf(metaMMOLoginInfo.getRegionId()));
+            this.cityId = metaMMOLoginInfo.getCityId();
+            this.regionId = metaMMOLoginInfo.getRegionId();
+            // 玩家信息存入websocket
+            clients.put(userId, this);
+            MMOMessage mmoMessage = new MMOMessage();
+            mmoMessage.setMessageType(5);
+            mmoMessage.setMessage(metaMMOLoginInfo);
+            sendMessageTo(JSON.toJSONString(mmoMessage), userId);
+            this.uuid = UUID.randomUUID().toString();
+            // 玩家信息入库
+            MetaMMOLoginInfo newMetaMMOLoginInfo = new MetaMMOLoginInfo();
+            newMetaMMOLoginInfo.setNickname(metaMMOLoginInfo.getNickname());
+            newMetaMMOLoginInfo.setUserId(metaMMOLoginInfo.getUserId());
+            newMetaMMOLoginInfo.setCityId(metaMMOLoginInfo.getCityId());
+            newMetaMMOLoginInfo.setRegionId(metaMMOLoginInfo.getRegionId());
+            newMetaMMOLoginInfo.setAxisX(metaMMOLoginInfo.getAxisX());
+            newMetaMMOLoginInfo.setAxisY(metaMMOLoginInfo.getAxisY());
+            newMetaMMOLoginInfo.setAxisZ(metaMMOLoginInfo.getAxisZ());
+            newMetaMMOLoginInfo.setEulerX(metaMMOLoginInfo.getEulerX());
+            newMetaMMOLoginInfo.setEulerY(metaMMOLoginInfo.getEulerY());
+            newMetaMMOLoginInfo.setEulerZ(metaMMOLoginInfo.getEulerZ());
+            newMetaMMOLoginInfo.setOnLineTime(LocalDateTime.now());
+            newMetaMMOLoginInfo.setUUID(uuid);
+            metaMMOLoginInfoRepo.save(newMetaMMOLoginInfo);
+        } catch (Exception e) {
+            String errMsg = String.format("[%S]上线的时候发生了错误,错误信息[%S]", userId, e.getMessage());
+            log.info(errMsg);
+            throw new BusinessException(errMsg);
+        }
+    }
+
+    @OnError
+    public void onError(Session session, Throwable error) {
+        // 异常处理
+        log.info(String.format("服务端发生了错误:[%S]", error.getMessage()));
+    }
+
+
+    @OnClose
+    public void onClose() {
+        init();
+        // 查询地图中玩家信息
+        MetaMMOLoginInfo metaMMOLoginInfo = (MetaMMOLoginInfo) redisTemplate.opsForValue().get(this.userId);
+        if (Objects.isNull(metaMMOLoginInfo)) {
+            // 如果缓存中玩家信息为空,根据UUID查询数据库
+            MetaMMOLoginInfo dbMetaMMOLoginInfo = metaMMOLoginInfoRepo.findByUUID(uuid);
+            dbMetaMMOLoginInfo.setOffLineTime(LocalDateTime.now());
+            // 更新离线时间
+            metaMMOLoginInfoRepo.save(dbMetaMMOLoginInfo);
+            clients.remove(userId);
+            return;
+        }
+        String key = String.valueOf(metaMMOLoginInfo.getCityId()).concat(":").concat(String.valueOf(metaMMOLoginInfo.getRegionId()));
+        List<String> userIds = redisTemplate.opsForList().range(key, 0, -1);
+        // 分发下线消息给区域内其他玩家
+        buildMessageForSendingToAllOther(userIds, 3, metaMMOLoginInfo);
+        MetaMMOLoginInfo dbMetaMMOLoginInfo = metaMMOLoginInfoRepo.findLastByUserId(metaMMOLoginInfo.getUserId());
+        dbMetaMMOLoginInfo.setOffLineTime(LocalDateTime.now());
+        metaMMOLoginInfoRepo.save(dbMetaMMOLoginInfo);
+        clients.remove(userId);
+        // 删除redis中自己的信息
+        redisTemplate.delete(userId);
+        // 移除地图中自己的信息
+        redisTemplate.opsForList().remove(key, 0, userId);
+    }
+
+
+    @OnMessage
+    public void onMessage(String message, Session session) {
+        init();
+        log.info("来自客户端消息:" + message + "客户端的id是:" + session.getId());
+        if (StringUtils.isBlank(message)) {
+            log.info("message is null");
+            return;
+        }
+        JSONObject jsonObject = JSON.parseObject(message);
+        if (Objects.isNull(jsonObject.getString("type"))) {
+            String errMsg = "操作类型为空";
+            log.error(errMsg);
+            throw new BusinessException(errMsg);
+        }
+        if (Objects.isNull(jsonObject.getString("cityId"))) {
+            String errMsg = "cityId为空";
+            log.error(errMsg);
+            throw new BusinessException(errMsg);
+        }
+        if (Objects.isNull(jsonObject.getString("regionId"))) {
+            String errMsg = "regionId为空";
+            log.error(errMsg);
+            throw new BusinessException(errMsg);
+        }
+        int type = Integer.parseInt(jsonObject.getString("type"));
+        String cityId = jsonObject.getString("cityId");
+        String regionId = jsonObject.getString("regionId");
+        String key = cityId.concat(":").concat(regionId);
+        // 从当前地图中获取其他玩家信息
+        List<String> userIds = redisTemplate.opsForList().range(key, 0, -1);
+        // 进入地图 把自己位置信息通知别人,获取其他人的位置信息
+        if (type == 1) {
+            MetaMMOLoginInfo metaMMOLoginInfo = buildMetaMMOLoginInfo(jsonObject, Long.parseLong(cityId), Long.parseLong(regionId));
+            // 判断地图中是否存在玩家
+            if (CollectionUtils.isNotEmpty(userIds)) {
+                List<MetaMMOLoginInfo> metaMMOLoginInfos = redisTemplate.opsForValue().multiGet(userIds);
+                if (CollectionUtils.isNotEmpty(metaMMOLoginInfos)) {
+                    // 过滤自己
+                    metaMMOLoginInfos = remove(metaMMOLoginInfos);
+                    if (CollectionUtils.isNotEmpty(metaMMOLoginInfos)) {
+                        // 分发区域内其他玩家信息给自己
+                        buildMessageForSendingToUser(userId, 1, metaMMOLoginInfos);
+                        // 分发自己信息给区域内其他玩家
+                        buildMessageForSendingToAllOther(userIds, 4, metaMMOLoginInfo);
+                    }
+                }
+            }
+            // 将自己信息存到redis中
+            redisTemplate.opsForValue().set(userId, metaMMOLoginInfo);
+            redisTemplate.opsForList().leftPush(key, userId);
+        }
+        // 如果是区域直接切换区域
+        if (type == 2) {
+            MetaMMOLoginInfo metaMMOLoginInfo = buildMetaMMOLoginInfo(jsonObject, Long.parseLong(cityId), Long.parseLong(regionId));
+            if (CollectionUtils.isNotEmpty(userIds)) {
+                // 分发玩家信息给区域内其他玩家
+                buildMessageForSendingToAllOther(userIds, 4, metaMMOLoginInfo);
+                // 分发区域内其他玩家信息给自己
+                List<MetaMMOLoginInfo> metaMMOLoginInfos = redisTemplate.opsForValue().multiGet(userIds);
+                if (CollectionUtils.isNotEmpty(metaMMOLoginInfos)) {
+                    // 过滤自己
+                    metaMMOLoginInfos = remove(metaMMOLoginInfos);
+                    if (CollectionUtils.isNotEmpty(metaMMOLoginInfos)) {
+                        buildMessageForSendingToUser(userId, 1, metaMMOLoginInfos);
+                    }
+                }
+            }
+            // 分发玩家信息给之前区域内其他玩家
+            MetaMMOLoginInfo oldMetaMMOLoginInfo = (MetaMMOLoginInfo) redisTemplate.opsForValue().get(userId);
+            if (Objects.isNull(oldMetaMMOLoginInfo)) {
+                String errMsg = "缺失玩家上次区域的地图缓存信息";
+                log.error(errMsg);
+                throw new BusinessException(errMsg);
+            }
+            String oldKey = String.valueOf(oldMetaMMOLoginInfo.getCityId()).concat(":").concat(String.valueOf(oldMetaMMOLoginInfo.getRegionId()));
+            List<String> oldUserIds = redisTemplate.opsForList().range(oldKey, 0, -1);
+            if (CollectionUtils.isEmpty(oldUserIds)) {
+                String errMsg = "查询不到上次所进入的区域的玩家信息";
+                log.error(errMsg);
+                throw new BusinessException(errMsg);
+            }
+            // 分发消息给之前区域的玩家
+            buildMessageForSendingToAllOther(oldUserIds, 3, oldMetaMMOLoginInfo);
+            // 清除玩家上次缓存的地图信息
+            redisTemplate.opsForList().remove(oldKey, 0, userId);
+            // 缓存玩家新的地图信息
+            redisTemplate.opsForValue().set(userId, metaMMOLoginInfo);
+            redisTemplate.opsForList().leftPush(key, userId);
+        }
+        // 玩家在地图内移动
+        if (type == 3) {
+            MetaMMOLoginInfo metaMMOLoginInfo = (MetaMMOLoginInfo) redisTemplate.opsForValue().get(userId);
+            if (Objects.isNull(metaMMOLoginInfo)) {
+                String errMsg = "缓存中不存在本玩家信息";
+                log.error(errMsg);
+                throw new BusinessException(errMsg);
+            }
+            // 分发玩家信息给区域内其他玩家
+            buildMessageForSendingToAllOther(userIds, 2, metaMMOLoginInfo);
+            // 更新缓存中玩家位置信息
+            buildMetaMMOLoginInfo(metaMMOLoginInfo, jsonObject);
+            redisTemplate.opsForValue().set(userId, metaMMOLoginInfo);
+        }
+        // 进入大厅
+        if (type == 4) {
+            MetaMMOLoginInfo metaMMOLoginInfo = (MetaMMOLoginInfo) redisTemplate.opsForValue().get(userId);
+            if (Objects.isNull(metaMMOLoginInfo)) {
+                String errMsg = "缓存中不存在本玩家信息";
+                log.error(errMsg);
+                throw new BusinessException(errMsg);
+            }
+            // 分发玩家信息给区域内其他玩家
+            buildMessageForSendingToAllOther(userIds, 3, metaMMOLoginInfo);
+            // 更新库中玩家位置信息
+            MetaMMOLoginInfo dbMetaMMOLoginInfo = metaMMOLoginInfoRepo.findByUUID(uuid);
+            dbMetaMMOLoginInfo.setAxisX(metaMMOLoginInfo.getAxisX());
+            dbMetaMMOLoginInfo.setAxisY(metaMMOLoginInfo.getAxisY());
+            dbMetaMMOLoginInfo.setAxisZ(metaMMOLoginInfo.getAxisZ());
+            dbMetaMMOLoginInfo.setEulerX(metaMMOLoginInfo.getEulerX());
+            dbMetaMMOLoginInfo.setEulerY(metaMMOLoginInfo.getEulerY());
+            dbMetaMMOLoginInfo.setEulerZ(metaMMOLoginInfo.getEulerZ());
+            dbMetaMMOLoginInfo.setCityId(metaMMOLoginInfo.getCityId());
+            dbMetaMMOLoginInfo.setRegionId(metaMMOLoginInfo.getRegionId());
+            metaMMOLoginInfoRepo.save(dbMetaMMOLoginInfo);
+        }
+    }
+
+    /**
+     * 分发玩家信息给区域内其他玩家
+     *
+     * @param userIds          玩家id集合
+     * @param messageType      消息类型
+     * @param metaMMOLoginInfo 消息体
+     */
+    private void buildMessageForSendingToAllOther(List<String> userIds, int messageType, MetaMMOLoginInfo metaMMOLoginInfo) {
+        MMOMessage mmoMessage = new MMOMessage();
+        mmoMessage.setMessageType(messageType);
+        mmoMessage.setMessage(metaMMOLoginInfo);
+        sendMessageAllOther(userIds, JSON.toJSONString(mmoMessage));
+    }
+
+    /**
+     * 分发区域内其他玩家信息给自己
+     *
+     * @param userId            玩家id
+     * @param messageType       消息类型
+     * @param metaMMOLoginInfos 消息体(其他玩家信息)
+     */
+    private void buildMessageForSendingToUser(String userId, int messageType, List<MetaMMOLoginInfo> metaMMOLoginInfos) {
+        MMOMessage mmoMessage = new MMOMessage();
+        mmoMessage.setMessageType(messageType);
+        mmoMessage.setMap(metaMMOLoginInfos);
+        sendMessageTo(JSON.toJSONString(mmoMessage), userId);
+    }
+
+    /**
+     * 移除自己信息
+     *
+     * @param metaMMOLoginInfos 信息集合
+     * @return 非自己信息集合
+     */
+    private List<MetaMMOLoginInfo> remove(List<MetaMMOLoginInfo> metaMMOLoginInfos) {
+        return metaMMOLoginInfos.stream()
+                .filter(mmoLoginInfo -> !Objects.equals(mmoLoginInfo.getUserId(), userId)).collect(Collectors.toList());
+    }
+
+    /**
+     * 给指定用户分发消息
+     *
+     * @param message  消息体
+     * @param toUserId 用户id
+     */
+    public void sendMessageTo(String message, String toUserId) {
+        clients.values().forEach(webSocket -> {
+            if (webSocket.userId.equals(toUserId)) {
+                log.info(String.format("给指定的在线用户发送消息,当前在线人员为[%S]。消息:[%S]", webSocket.userId.toString(), message));
+                try {
+                    webSocket.session.getBasicRemote().sendText(message);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+    }
+
+
+    /**
+     * 分发下线消息给当前城市当前区域中其他玩家
+     *
+     * @param userIds 其他玩家id集合
+     * @param message 消息体
+     */
+    public void sendMessageAllOther(List<String> userIds, String message) {
+        clients.values().forEach(webSocket -> {
+            if (!Objects.equals(webSocket.userId, userId) && userIds.contains(webSocket.userId)) {
+                log.info(String.format("服务器给所有当前区域内在线用户发送消息,当前在线人员为[%S]。消息:[%S]", webSocket.userId.toString(), message));
+                try {
+                    webSocket.session.getBasicRemote().sendText(message);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+    }
+
+    /**
+     * 分发消息给所有玩家
+     *
+     * @param message 消息体
+     */
+    public void sendMessageAll(String message) {
+        clients.values().forEach(webSocket -> {
+            log.info(String.format("服务器给所有当前区域内在线用户发送消息,当前在线人员为[%S]。消息:[%S]", webSocket.userId.toString(), message));
+            try {
+                webSocket.session.getBasicRemote().sendText(message);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    private MetaMMOLoginInfo buildMetaMMOLoginInfo(JSONObject jsonObject, Long cityId, Long regionId) {
+        // 拿到自己进入地图市时的信息通知给其他人,获取当前区域内其他玩家信息
+        String role = jsonObject.getString("role");
+        // 获取到进入地图时自己的信息
+        MetaMMOLoginInfo metaMMOLoginInfo = new MetaMMOLoginInfo();
+        buildMetaMMOLoginInfo(metaMMOLoginInfo, jsonObject);
+        metaMMOLoginInfo.setUserId(Long.parseLong(userId));
+        metaMMOLoginInfo.setCityId(cityId);
+        metaMMOLoginInfo.setRegionId(regionId);
+        if (StringUtils.isNotBlank(role)) {
+            metaMMOLoginInfo.setRole(role);
+        }
+        return metaMMOLoginInfo;
+    }
+
+    private void buildMetaMMOLoginInfo(MetaMMOLoginInfo metaMMOLoginInfo, JSONObject jsonObject) {
+        if (Objects.nonNull(jsonObject.getString("axisX"))) {
+            metaMMOLoginInfo.setAxisX(Float.parseFloat(jsonObject.getString("axisX")));
+        }
+        if (Objects.nonNull(jsonObject.getString("axisY"))) {
+            metaMMOLoginInfo.setAxisY(Float.parseFloat(jsonObject.getString("axisY")));
+        }
+        if (Objects.nonNull(jsonObject.getString("axisZ"))) {
+            metaMMOLoginInfo.setAxisZ(Float.parseFloat(jsonObject.getString("axisZ")));
+        }
+        if (Objects.nonNull(jsonObject.getString("eulerX"))) {
+            metaMMOLoginInfo.setEulerX(Float.parseFloat(jsonObject.getString("eulerX")));
+        }
+        if (Objects.nonNull(jsonObject.getString("eulerY"))) {
+            metaMMOLoginInfo.setEulerY(Float.parseFloat(jsonObject.getString("eulerY")));
+        }
+        if (Objects.nonNull(jsonObject.getString("eulerZ"))) {
+            metaMMOLoginInfo.setEulerZ(Float.parseFloat(jsonObject.getString("eulerZ")));
+        }
+        if (Objects.nonNull(jsonObject.getString("nickName"))) {
+            metaMMOLoginInfo.setNickname(jsonObject.getString("nickName"));
+        }
+    }
+}

+ 1 - 0
src/main/resources/genjson/MetaAdvertRecord.json

@@ -0,0 +1 @@
+{"tableName":"MetaAdvertRecord","className":"MetaAdvertRecord","remark":"元宇宙广告","genTable":true,"genClass":true,"genList":true,"genForm":true,"genRouter":true,"javaPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/java/com/izouma/nineth","viewPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/vue/src/views","routerPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/vue/src","resourcesPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/resources","dataBaseType":"Mysql","fields":[{"name":"pic","modelName":"pic","remark":"图片","showInList":true,"showInForm":true,"formType":"singleImage","required":true},{"name":"link","modelName":"link","remark":"地址","showInList":true,"showInForm":true,"formType":"singleLineText","required":true}],"readTable":false,"dataSourceCode":"dataSource","genJson":"","subtables":[],"update":false,"basePackage":"com.izouma.nineth","tablePackage":"com.izouma.nineth.domain.MetaAdvertRecord"}

+ 1 - 0
src/main/resources/genjson/MetaTask.json

@@ -0,0 +1 @@
+{"tableName":"MetaTask","className":"MetaTask","remark":"元宇宙任务","genTable":true,"genClass":true,"genList":true,"genForm":true,"genRouter":true,"javaPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/java/com/izouma/nineth","viewPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/vue/src/views","routerPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/vue/src","resourcesPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/resources","dataBaseType":"Mysql","fields":[{"name":"name","modelName":"name","remark":"任务名称","showInList":true,"showInForm":true,"formType":"singleLineText","required":true},{"name":"description","modelName":"description","remark":"任务详情","showInList":true,"showInForm":true,"formType":"singleLineText","required":true},{"name":"type","modelName":"type","remark":"任务类型","showInList":true,"showInForm":true,"formType":"select","required":true,"apiFlag":"1","optionsValue":"[{\"label\":\"一次领取任务\",\"value\":\"ONLY_ONE\"},{\"label\":\"多次领取任务\",\"value\":\"MULTIPLE\"},{\"label\":\"每日任务\",\"value\":\"DAILY\"}]"}],"readTable":false,"dataSourceCode":"dataSource","genJson":"","subtables":[],"update":false,"basePackage":"com.izouma.nineth","tablePackage":"com.izouma.nineth.domain.MetaTask"}

+ 1 - 0
src/main/resources/genjson/MetaUser.json

@@ -0,0 +1 @@
+{"tableName":"MetaUser","className":"MetaUser","remark":"元宇宙内测用户","genTable":true,"genClass":true,"genList":true,"genForm":true,"genRouter":true,"javaPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/java/com/izouma/nineth","viewPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/vue/src/views","routerPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/vue/src","resourcesPath":"/Users/xiaohuoban/IdeaProjects/raex_back/src/main/resources","dataBaseType":"Mysql","fields":[{"name":"phone","modelName":"phone","remark":"用户手机号","showInList":true,"showInForm":true,"formType":"number","required":true},{"name":"allowLogin","modelName":"allowLogin","remark":"是否允许登陆","showInList":true,"showInForm":true,"formType":"switch","required":true}],"readTable":false,"dataSourceCode":"dataSource","genJson":"","subtables":[],"update":false,"basePackage":"com.izouma.nineth","tablePackage":"com.izouma.nineth.domain.MetaUser"}

+ 56 - 0
src/main/vue/src/router.js

@@ -1077,6 +1077,62 @@ const router = new Router({
                         title: '盲盒'
                     }
                 },
+                {
+                    path: '/metaUserEdit',
+                    name: 'MetaUserEdit',
+                    component: () => import(/* webpackChunkName: "metaUserEdit" */ '@/views/MetaUserEdit.vue'),
+                    meta: {
+                        title: '元宇宙内测用户编辑',
+                    },
+                },
+                {
+                    path: '/metaUserList',
+                    name: 'MetaUserList',
+                    component: () => import(/* webpackChunkName: "metaUserList" */ '@/views/MetaUserList.vue'),
+                    meta: {
+                        title: '元宇宙内测用户',
+                    },
+                },
+                {
+                    path: '/metaBonusSceneList',
+                    name: 'MetaBonusSceneList',
+                    component: () => import(/* webpackChunkName: "metaBonusSceneList" */ '@/views/MetaBonusSceneList.vue'),
+                    meta: {
+                        title: '元宇宙彩蛋',
+                    },
+                },
+                {
+                    path: '/metaAdvertRecordEdit',
+                    name: 'MetaAdvertRecordEdit',
+                    component: () => import(/* webpackChunkName: "metaAdvertRecordEdit" */ '@/views/MetaAdvertRecordEdit.vue'),
+                    meta: {
+                        title: '元宇宙广告编辑',
+                    },
+                },
+                {
+                    path: '/metaAdvertRecordList',
+                    name: 'MetaAdvertRecordList',
+                    component: () => import(/* webpackChunkName: "metaAdvertRecordList" */ '@/views/MetaAdvertRecordList.vue'),
+                    meta: {
+                        title: '元宇宙广告',
+                    },
+                },
+                {
+                    path: '/metaTaskEdit',
+                    name: 'MetaTaskEdit',
+                    component: () => import(/* webpackChunkName: "metaTaskEdit" */ '@/views/MetaTaskEdit.vue'),
+                    meta: {
+                        title: '元宇宙任务编辑',
+                    },
+                },
+                {
+                    path: '/metaTaskList',
+                    name: 'MetaTaskList',
+                    component: () => import(/* webpackChunkName: "metaTaskList" */ '@/views/MetaTaskList.vue'),
+                    meta: {
+                        title: '元宇宙任务',
+                    },
+                }
                 /**INSERT_LOCATION**/
             ]
         },

+ 120 - 0
src/main/vue/src/views/MetaAdvertRecordEdit.vue

@@ -0,0 +1,120 @@
+<template>
+    <div class="edit-view">
+        <page-title>
+            <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+            <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+                删除
+            </el-button>
+            <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
+        </page-title>
+        <div class="edit-view__content-wrapper">
+            <div class="edit-view__content-section">
+                <el-form :model="formData" :rules="rules" ref="form" label-width="82px" label-position="right"
+                    size="small" style="max-width: 500px;">
+                    <el-form-item prop="pic" label="图片">
+                        <single-upload v-model="formData.pic"></single-upload>
+                    </el-form-item>
+                    <el-form-item prop="link" label="地址">
+                        <el-input v-model="formData.link"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="used" label="是否使用">
+						<el-switch v-model="formData.used"> </el-switch>
+					</el-form-item>
+                    <el-form-item class="form-submit">
+                        <el-button @click="onSave" :loading="saving" type="primary">
+                            保存
+                        </el-button>
+                        <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+                            删除
+                        </el-button>
+                        <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    name: 'MetaAdvertRecordEdit',
+    created() {
+        if (this.$route.query.id) {
+            this.$http
+                .get('metaAdvertRecord/get/' + this.$route.query.id)
+                .then(res => {
+                    this.formData = res;
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        }
+    },
+    data() {
+        return {
+            saving: false,
+            formData: {
+            },
+            rules: {
+                pic: [
+                    {
+                        required: true,
+                        message: '请输入图片',
+                        trigger: 'blur'
+                    },
+                ],
+                link: [
+                    {
+                        required: true,
+                        message: '请输入地址',
+                        trigger: 'blur'
+                    },
+                ],
+            },
+        }
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate((valid) => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            let data = { ...this.formData };
+
+            this.saving = true;
+            this.$http
+                .post('/metaAdvertRecord/save', data, { body: 'json' })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        },
+        onDelete() {
+            this.$confirm('删除将无法恢复,确认要删除么?', '警告', { type: 'error' }).then(() => {
+                return this.$http.post(`/metaAdvertRecord/del/${this.formData.id}`)
+            }).then(() => {
+                this.$message.success('删除成功');
+                this.$router.go(-1);
+            }).catch(e => {
+                if (e !== 'cancel') {
+                    console.log(e);
+                    this.$message.error((e || {}).error || '删除失败');
+                }
+            })
+        },
+    }
+}
+</script>
+<style lang="less" scoped>
+</style>

+ 153 - 0
src/main/vue/src/views/MetaAdvertRecordList.vue

@@ -0,0 +1,153 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button @click="addRow" type="primary" icon="el-icon-plus" :disabled="fetchingData || downloading"
+                class="filter-item">
+                新增
+            </el-button>
+        </page-title>
+        <el-table :data="tableData" row-key="id" ref="table" header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell" row-class-name="table-row" cell-class-name="table-cell"
+            :height="tableHeight" v-loading="fetchingData">
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50">
+            </el-table-column>
+            <el-table-column prop="id" label="ID" width="100">
+            </el-table-column>
+            <el-table-column prop="createdAt" label="时间">
+            </el-table-column>
+            <el-table-column prop="pic" label="图片">
+                <template slot-scope="{row}">
+                    <el-image style="width: 30px; height: 30px" :src="row.pic" fit="cover"
+                        :preview-src-list="[row.pic]"></el-image>
+                </template>
+            </el-table-column>
+            <el-table-column prop="link" label="链接地址">
+            </el-table-column>
+            <el-table-column prop="used" label="是否使用">
+				<template slot-scope="{ row }">
+					<el-tag :type="row.used ? '' : 'info'"> {{ row.used }} </el-tag>
+				</template>
+			</el-table-column>
+            <el-table-column label="操作" align="center" fixed="right" width="150">
+                <template slot-scope="{row}">
+                    <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
+                    <el-button @click="deleteRow(row)" type="danger" size="mini" plain>删除</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <!-- <div class="multiple-mode-wrapper">
+                <el-button v-if="!multipleMode" @click="toggleMultipleMode(true)">批量编辑</el-button>
+                <el-button-group v-else>
+                    <el-button @click="operation1">批量操作1</el-button>
+                    <el-button @click="operation2">批量操作2</el-button>
+                    <el-button @click="toggleMultipleMode(false)">取消</el-button>
+                </el-button-group>
+            </div> -->
+            <el-pagination background @size-change="onSizeChange" @current-change="onCurrentChange" :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]" :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper" :total="totalElements">
+            </el-pagination>
+        </div>
+
+    </div>
+</template>
+<script>
+import { mapState } from "vuex";
+import pageableTable from "@/mixins/pageableTable";
+
+export default {
+    name: 'MetaAdvertRecordList',
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: "",
+            url: "/metaAdvertRecord/all",
+            downloading: false,
+        }
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        beforeGetData() {
+            return { search: this.search, query: { del: false } };
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: "/metaAdvertRecordEdit",
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: "/metaAdvertRecordEdit",
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get("/metaAdvertRecord/excel", {
+                    responseType: "blob",
+                    params: { size: 10000 }
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement("a");
+                    link.href = downloadUrl;
+                    link.setAttribute(
+                        "download",
+                        res.headers["content-disposition"].split("filename=")[1]
+                    );
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        deleteRow(row) {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' }).then(() => {
+                return this.$http.post(`/metaAdvertRecord/del/${row.id}`)
+            }).then(() => {
+                this.$message.success('删除成功');
+                this.getData();
+            }).catch(e => {
+                if (e !== 'cancel') {
+                    this.$message.error(e.error);
+                }
+            })
+        },
+    }
+}
+</script>
+<style lang="less" scoped>
+</style>

+ 99 - 0
src/main/vue/src/views/MetaBonusSceneList.vue

@@ -0,0 +1,99 @@
+<template>
+	<div class="list-view">
+		<page-title>
+			<el-button
+				@click="download"
+				icon="el-icon-upload2"
+				:loading="downloading"
+				:disabled="fetchingData"
+				class="filter-item"
+			>
+				导出
+			</el-button>
+		</page-title>
+		<el-table
+			:data="tableData"
+			row-key="id"
+			ref="table"
+			header-row-class-name="table-header-row"
+			header-cell-class-name="table-header-cell"
+			row-class-name="table-row"
+			cell-class-name="table-cell"
+			:height="tableHeight"
+			v-loading="fetchingData"
+		>
+			<el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+			<el-table-column prop="userId" label="userId"> </el-table-column>
+			<el-table-column prop="num" label="触发彩蛋个数"> </el-table-column>
+			<el-table-column prop="createdAt" label="触发时间"> </el-table-column>
+		</el-table>
+		<div class="pagination-wrapper">
+			<el-pagination
+				background
+				@size-change="onSizeChange"
+				@current-change="onCurrentChange"
+				:current-page="page"
+				:page-sizes="[10, 20, 30, 40, 50]"
+				:page-size="pageSize"
+				layout="total, sizes, prev, pager, next, jumper"
+				:total="totalElements"
+			>
+			</el-pagination>
+		</div>
+	</div>
+</template>
+<script>
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+
+export default {
+	name: 'MetaBonusSceneList',
+	mixins: [pageableTable],
+	data() {
+		return {
+			multipleMode: false,
+			url: '/metaBonusScene/all',
+			downloading: false
+		};
+	},
+	computed: {
+		selection() {
+			return this.$refs.table.selection.map(i => i.id);
+		}
+	},
+	methods: {
+		toggleMultipleMode(multipleMode) {
+			this.multipleMode = multipleMode;
+			if (!multipleMode) {
+				this.$refs.table.clearSelection();
+			}
+		},
+		download() {
+			this.downloading = true;
+			this.$axios
+				.get('/metaBonusScene/excel', {
+					responseType: 'blob',
+					params: { size: 10000 }
+				})
+				.then(res => {
+					console.log(res);
+					this.downloading = false;
+					const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+					const link = document.createElement('a');
+					link.href = downloadUrl;
+					link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+					document.body.appendChild(link);
+					link.click();
+					link.remove();
+				})
+				.catch(e => {
+					console.log(e);
+					this.downloading = false;
+					this.$message.error(e.error);
+				});
+		}
+	}
+};
+</script>
+<style lang="less" scoped>
+</style>

+ 132 - 0
src/main/vue/src/views/MetaTaskEdit.vue

@@ -0,0 +1,132 @@
+<template>
+    <div class="edit-view">
+        <page-title>
+            <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+            <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+                删除
+            </el-button>
+            <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
+        </page-title>
+        <div class="edit-view__content-wrapper">
+            <div class="edit-view__content-section">
+                <el-form :model="formData" :rules="rules" ref="form" label-width="80px" label-position="right"
+                    size="small" style="max-width: 500px;">
+                    <el-form-item prop="name" label="任务名称">
+                        <el-input v-model="formData.name"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="detail" label="详情" style="width: calc(100vw - 450px)">
+                        <rich-text v-model="formData.detail"></rich-text>
+                    </el-form-item>
+                    <el-form-item prop="type" label="任务类型">
+                        <el-select v-model="formData.type" clearable filterable placeholder="请选择">
+                            <el-option v-for="item in typeOptions" :key="item.value" :label="item.label"
+                                :value="item.value">
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item class="form-submit">
+                        <el-button @click="onSave" :loading="saving" type="primary">
+                            保存
+                        </el-button>
+                        <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+                            删除
+                        </el-button>
+                        <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    name: 'MetaTaskEdit',
+    created() {
+        if (this.$route.query.id) {
+            this.$http
+                .get('metaTask/get/' + this.$route.query.id)
+                .then(res => {
+                    this.formData = res;
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        }
+    },
+    data() {
+        return {
+            saving: false,
+            formData: {
+            },
+            rules: {
+                name: [
+                    {
+                        required: true,
+                        message: '请输入任务名称',
+                        trigger: 'blur'
+                    },
+                ],
+                description: [
+                    {
+                        required: true,
+                        message: '请输入任务详情',
+                        trigger: 'blur'
+                    },
+                ],
+                type: [
+                    {
+                        required: true,
+                        message: '请输入任务类型',
+                        trigger: 'blur'
+                    },
+                ],
+            },
+            typeOptions: [{ "label": "一次领取任务", "value": "ONLY_ONE" }, { "label": "多次领取任务", "value": "MULTIPLE" }, { "label": "每日任务", "value": "DAILY" }],
+        }
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate((valid) => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            let data = { ...this.formData };
+
+            this.saving = true;
+            this.$http
+                .post('/metaTask/save', data, { body: 'json' })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        },
+        onDelete() {
+            this.$confirm('删除将无法恢复,确认要删除么?', '警告', { type: 'error' }).then(() => {
+                return this.$http.post(`/metaTask/del/${this.formData.id}`)
+            }).then(() => {
+                this.$message.success('删除成功');
+                this.$router.go(-1);
+            }).catch(e => {
+                if (e !== 'cancel') {
+                    console.log(e);
+                    this.$message.error((e || {}).error || '删除失败');
+                }
+            })
+        },
+    }
+}
+</script>
+<style lang="less" scoped>
+</style>

+ 162 - 0
src/main/vue/src/views/MetaTaskList.vue

@@ -0,0 +1,162 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button @click="addRow" type="primary" icon="el-icon-plus" :disabled="fetchingData || downloading"
+                class="filter-item">
+                新增
+            </el-button>
+            <el-button @click="download" icon="el-icon-upload2" :loading="downloading" :disabled="fetchingData"
+                class="filter-item">
+                导出
+            </el-button>
+        </page-title>
+        <div class="filters-container">
+            <el-input placeholder="搜索..." v-model="search" clearable class="filter-item search"
+                @keyup.enter.native="getData">
+                <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+            </el-input>
+        </div>
+        <el-table :data="tableData" row-key="id" ref="table" header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell" row-class-name="table-row" cell-class-name="table-cell"
+            :height="tableHeight" v-loading="fetchingData">
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50">
+            </el-table-column>
+            <el-table-column prop="id" label="ID" width="100">
+            </el-table-column>
+            <el-table-column prop="name" label="任务名称">
+            </el-table-column>
+            <el-table-column prop="createdAt" label="创建时间">
+            </el-table-column>
+            <el-table-column prop="type" label="任务类型" :formatter="typeFormatter">
+            </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right" width="150">
+                <template slot-scope="{row}">
+                    <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
+                    <el-button @click="deleteRow(row)" type="danger" size="mini" plain>删除</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <!-- <div class="multiple-mode-wrapper">
+                <el-button v-if="!multipleMode" @click="toggleMultipleMode(true)">批量编辑</el-button>
+                <el-button-group v-else>
+                    <el-button @click="operation1">批量操作1</el-button>
+                    <el-button @click="operation2">批量操作2</el-button>
+                    <el-button @click="toggleMultipleMode(false)">取消</el-button>
+                </el-button-group>
+            </div> -->
+            <el-pagination background @size-change="onSizeChange" @current-change="onCurrentChange" :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]" :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper" :total="totalElements">
+            </el-pagination>
+        </div>
+
+    </div>
+</template>
+<script>
+import { mapState } from "vuex";
+import pageableTable from "@/mixins/pageableTable";
+
+export default {
+    name: 'MetaTaskList',
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: "",
+            url: "/metaTask/all",
+            downloading: false,
+            typeOptions: [{ "label": "一次领取任务", "value": "ONLY_ONE" }, { "label": "多次领取任务", "value": "MULTIPLE" }, { "label": "每日任务", "value": "DAILY" }],
+        }
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        typeFormatter(row, column, cellValue, index) {
+            let selectedOption = this.typeOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        beforeGetData() {
+            return { search: this.search, query: { del: false } };
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: "/metaTaskEdit",
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: "/metaTaskEdit",
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get("/metaTask/excel", {
+                    responseType: "blob",
+                    params: { size: 10000 }
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement("a");
+                    link.href = downloadUrl;
+                    link.setAttribute(
+                        "download",
+                        res.headers["content-disposition"].split("filename=")[1]
+                    );
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        deleteRow(row) {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' }).then(() => {
+                return this.$http.post(`/metaTask/del/${row.id}`)
+            }).then(() => {
+                this.$message.success('删除成功');
+                this.getData();
+            }).catch(e => {
+                if (e !== 'cancel') {
+                    this.$message.error(e.error);
+                }
+            })
+        },
+    }
+}
+</script>
+<style lang="less" scoped>
+</style>

+ 120 - 0
src/main/vue/src/views/MetaUserEdit.vue

@@ -0,0 +1,120 @@
+<template>
+	<div class="edit-view">
+		<page-title>
+			<el-button @click="$router.go(-1)" :disabled="saving"> 取消 </el-button>
+			<el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id"> 删除 </el-button>
+			<el-button @click="onSave" :loading="saving" type="primary"> 保存 </el-button>
+		</page-title>
+		<div class="edit-view__content-wrapper">
+			<div class="edit-view__content-section">
+				<el-form
+					:model="formData"
+					:rules="rules"
+					ref="form"
+					label-width="108px"
+					label-position="right"
+					size="small"
+					style="max-width: 500px"
+				>
+					<el-form-item prop="phone" label="用户手机号">
+						<el-input type="number" v-model="formData.phone"> </el-input>
+					</el-form-item>
+					<el-form-item prop="allowLogin" label="是否允许登陆">
+						<el-switch v-model="formData.allowLogin"> </el-switch>
+					</el-form-item>
+					<el-form-item class="form-submit">
+						<el-button @click="onSave" :loading="saving" type="primary"> 保存 </el-button>
+						<el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+							删除
+						</el-button>
+						<el-button @click="$router.go(-1)" :disabled="saving"> 取消 </el-button>
+					</el-form-item>
+				</el-form>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+export default {
+	name: 'MetaUserEdit',
+	created() {
+		if (this.$route.query.id) {
+			this.$http
+				.get('metaUser/get/' + this.$route.query.id)
+				.then(res => {
+					this.formData = res;
+				})
+				.catch(e => {
+					console.log(e);
+					this.$message.error(e.error);
+				});
+		}
+	},
+	data() {
+		return {
+			saving: false,
+			formData: {},
+			rules: {
+				phone: [
+                    {
+                        required: true,
+                        message: '请输入手机号',
+                        trigger: 'blur'
+                    },
+                    {
+                        pattern: /^1[3-9]\d{9}$/,
+                        message: '请输入正确的手机号',
+                        trigger: 'blur'
+                    }
+                ]
+			}
+		};
+	},
+	methods: {
+		onSave() {
+			this.$refs.form.validate(valid => {
+				if (valid) {
+					this.submit();
+				} else {
+					return false;
+				}
+			});
+		},
+		submit() {
+			let data = { ...this.formData };
+
+			this.saving = true;
+			this.$http
+				.post('/metaUser/save', data, { body: 'json' })
+				.then(res => {
+					this.saving = false;
+					this.$message.success('成功');
+					this.$router.go(-1);
+				})
+				.catch(e => {
+					console.log(e);
+					this.saving = false;
+					this.$message.error(e.error);
+				});
+		},
+		onDelete() {
+			this.$confirm('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+				.then(() => {
+					return this.$http.post(`/metaUser/del/${this.formData.id}`);
+				})
+				.then(() => {
+					this.$message.success('删除成功');
+					this.$router.go(-1);
+				})
+				.catch(e => {
+					if (e !== 'cancel') {
+						console.log(e);
+						this.$message.error((e || {}).error || '删除失败');
+					}
+				});
+		}
+	}
+};
+</script>
+<style lang="less" scoped>
+</style>

+ 175 - 0
src/main/vue/src/views/MetaUserList.vue

@@ -0,0 +1,175 @@
+<template>
+	<div class="list-view">
+		<page-title>
+			<el-button
+				@click="addRow"
+				type="primary"
+				icon="el-icon-plus"
+				:disabled="fetchingData || downloading"
+				class="filter-item"
+			>
+				新增
+			</el-button>
+			<!-- <el-button @click="download" icon="el-icon-upload2" :loading="downloading" :disabled="fetchingData" class="filter-item">
+                导出
+            </el-button> -->
+		</page-title>
+		<div class="filters-container">
+			<el-input
+				placeholder="搜索..."
+				v-model="search"
+				clearable
+				class="filter-item search"
+				@keyup.enter.native="getData"
+			>
+				<el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+			</el-input>
+		</div>
+		<el-table
+			:data="tableData"
+			row-key="id"
+			ref="table"
+			header-row-class-name="table-header-row"
+			header-cell-class-name="table-header-cell"
+			row-class-name="table-row"
+			cell-class-name="table-cell"
+			:height="tableHeight"
+			v-loading="fetchingData"
+		>
+			<el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+			<el-table-column prop="id" label="ID" width="100"> </el-table-column>
+			<el-table-column prop="phone" label="用户手机号"> </el-table-column>
+			<el-table-column prop="allowLogin" label="是否允许登陆">
+				<template slot-scope="{ row }">
+					<el-tag :type="row.allowLogin ? '' : 'info'"> {{ row.allowLogin }} </el-tag>
+				</template>
+			</el-table-column>
+			<el-table-column label="操作" align="center" fixed="right" width="150">
+				<template slot-scope="{ row }">
+					<el-button @click="editRow(row)" type="primary" size="mini" plain> 编辑 </el-button>
+					<el-button @click="deleteRow(row)" type="danger" size="mini" plain> 删除 </el-button>
+				</template>
+			</el-table-column>
+		</el-table>
+		<div class="pagination-wrapper">
+			<!-- <div class="multiple-mode-wrapper">
+                <el-button v-if="!multipleMode" @click="toggleMultipleMode(true)"> 批量编辑 </el-button>
+                <el-button-group v-else>
+                    <el-button @click="operation1"> 批量操作1 </el-button>
+                    <el-button @click="operation2"> 批量操作2 </el-button>
+                    <el-button @click="toggleMultipleMode(false)"> 取消 </el-button>
+                </el-button-group>
+            </div> -->
+			<el-pagination
+				background
+				@size-change="onSizeChange"
+				@current-change="onCurrentChange"
+				:current-page="page"
+				:page-sizes="[10, 20, 30, 40, 50]"
+				:page-size="pageSize"
+				layout="total, sizes, prev, pager, next, jumper"
+				:total="totalElements"
+			>
+			</el-pagination>
+		</div>
+	</div>
+</template>
+<script>
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+
+export default {
+	name: 'MetaUserList',
+	mixins: [pageableTable],
+	data() {
+		return {
+			multipleMode: false,
+			search: '',
+			url: '/metaUser/all',
+			downloading: false
+		};
+	},
+	computed: {
+		selection() {
+			return this.$refs.table.selection.map(i => i.id);
+		}
+	},
+	methods: {
+		beforeGetData() {
+			return { search: this.search, query: { del: false } };
+		},
+		toggleMultipleMode(multipleMode) {
+			this.multipleMode = multipleMode;
+			if (!multipleMode) {
+				this.$refs.table.clearSelection();
+			}
+		},
+		addRow() {
+			this.$router.push({
+				path: '/metaUserEdit',
+				query: {
+					...this.$route.query
+				}
+			});
+		},
+		editRow(row) {
+			this.$router.push({
+				path: '/metaUserEdit',
+				query: {
+					id: row.id
+				}
+			});
+		},
+		download() {
+			this.downloading = true;
+			this.$axios
+				.get('/metaUser/excel', {
+					responseType: 'blob',
+					params: { size: 10000 }
+				})
+				.then(res => {
+					console.log(res);
+					this.downloading = false;
+					const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+					const link = document.createElement('a');
+					link.href = downloadUrl;
+					link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+					document.body.appendChild(link);
+					link.click();
+					link.remove();
+				})
+				.catch(e => {
+					console.log(e);
+					this.downloading = false;
+					this.$message.error(e.error);
+				});
+		},
+		operation1() {
+			this.$notify({
+				title: '提示',
+				message: this.selection
+			});
+		},
+		operation2() {
+			this.$message('操作2');
+		},
+		deleteRow(row) {
+			this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+				.then(() => {
+					return this.$http.post(`/metaUser/del/${row.id}`);
+				})
+				.then(() => {
+					this.$message.success('删除成功');
+					this.getData();
+				})
+				.catch(e => {
+					if (e !== 'cancel') {
+						this.$message.error(e.error);
+					}
+				});
+		}
+	}
+};
+</script>
+<style lang="less" scoped>
+</style>

+ 10 - 0
src/main/vue/src/views/UserHoldCountList.vue

@@ -10,6 +10,9 @@
             >
                 导出
             </el-button>
+            <el-button @click="refresh" type="primary" icon="el-icon-refresh" :loading="downloading" class="filter-item">
+                刷新
+            </el-button>
         </page-title>
         <el-table
             :data="tableData"
@@ -90,6 +93,13 @@ export default {
                     this.downloading = false;
                     this.$message.error(e.error);
                 });
+        },
+        refresh() {
+            this.$axios.get('/userHold/refresh')
+            this.$notify({
+                title: '提示',
+                message: '当前正在刷新中,请等待!'
+            });
         }
     }
 };