SpringCloud微信投票小程序毕业设计(一)

1.后端创建

创建最最外层的微服务框架名为 vote_cloud

image-20260108233113953

删去多余的文件,保留.idea和pom.xml即可,修改pom配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<groupId>org.kangdd</groupId>
<artifactId>vote_cloud</artifactId>

<modules>
<module>common-module</module>
<module>user-service</module>
<module>gateway-service</module>
<module>exam-service</module>
<module>vote-service</module>
</modules>
<version>1.0-SNAPSHOT</version>

<properties>
<!-- 若使用Java 11,需对应Spring Boot 2.x版本 -->
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- 版本统一管理 -->
<spring-boot.version>2.7.18</spring-boot.version>
<spring-cloud.version>2021.0.9</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- Spring Cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- Spring Cloud Alibaba 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- Spring Boot 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

刷新maven创建公共模块

2.创建公共模块

2.1 common-module

2.1.1 公共返回类型R

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package com.kangdd.commonmodule.common;

import lombok.Data;

import java.io.Serializable;

/**
* 统一响应结果类
* @author kangdidi
* @date 2025年12月14日
*/
@Data
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;

/**
* 响应码:200表示成功,其他表示失败
*/
private Integer code;

/**
* 响应消息
*/
private String message;

/**
* 响应数据
*/
private T data;

public R() {
}

public R(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}

/**
* 成功响应(无数据)
*/
public static <T> R<T> success() {
return new R<>(200, "操作成功", null);
}

/**
* 成功响应(带数据)
*/
public static <T> R<T> success(T data) {
return new R<>(200, "操作成功", data);
}

/**
* 成功响应(自定义消息)
*/
public static <T> R<T> success(String message) {
return new R<>(200, message, null);
}

/**
* 成功响应(自定义消息和数据)
*/
public static <T> R<T> success(String message, T data) {
return new R<>(200, message, data);
}

/**
* 失败响应(自定义消息)
*/
public static <T> R<T> error(String message) {
return new R<>(500, message, null);
}

/**
* 失败响应(自定义码和消息)
*/
public static <T> R<T> error(Integer code, String message) {
return new R<>(code, message, null);
}

/**
* 失败响应(自定义码、消息和数据)
*/
public static <T> R<T> error(Integer code, String message, T data) {
return new R<>(code, message, data);
}

/**
* 判断是否成功
*/
public boolean isSuccess() {
return this.code != null && this.code == 200;
}
}

2.1.2 消息处理器

config.RestTemplateConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.kangdd.commonmodule.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class RestTemplateConfig {

@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();

// 创建新的消息转换器列表
List<HttpMessageConverter<?>> converters = new ArrayList<>();

// 找到Jackson转换器并修改其支持的媒体类型
MappingJackson2HttpMessageConverter jacksonConverter = null;
for (HttpMessageConverter<?> converter : restTemplate.getMessageConverters()) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
jacksonConverter = (MappingJackson2HttpMessageConverter) converter;
break;
}
}

// 如果找到Jackson转换器,修改其支持的媒体类型
if (jacksonConverter != null) {
// 创建新的可修改的媒体类型列表
List<MediaType> mediaTypes = new ArrayList<>(jacksonConverter.getSupportedMediaTypes());
mediaTypes.add(MediaType.TEXT_PLAIN);
jacksonConverter.setSupportedMediaTypes(mediaTypes);
converters.add(jacksonConverter);
}

// 添加其他转换器
for (HttpMessageConverter<?> converter : restTemplate.getMessageConverters()) {
if (!(converter instanceof MappingJackson2HttpMessageConverter)) {
converters.add(converter);
}
}

restTemplate.setMessageConverters(converters);
return restTemplate;
}
}

2.1.3 字段自动填充处理器

handler.MyMetaObjectHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.kangdd.commonmodule.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
* MyBatis-Plus字段自动填充处理器
* 用于自动填充创建时间和更新时间
* @author kangdidi
* @date 2025年12月14日
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 新增操作时填充字段
*/
@Override
public void insertFill(MetaObject metaObject) {
// 填充createTime为当前时间
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
// 填充updateTime为当前时间(新增时也需要)
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}

/**
* 更新操作时填充字段
*/
@Override
public void updateFill(MetaObject metaObject) {
// 填充updateTime为当前时间
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}

2.1.4 实体对象

pojo.user

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.kangdd.commonmodule.pojo;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

import java.time.LocalDateTime;

/**
* @description:
* @author: kangdidi
* @data 2025年12月13日 19:22
*/
@Data
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Integer uid;
@TableField("openid")
private String openid;
@TableField("nickname")
private String userName;
@TableField("avatar_url")
private String userImage;
// 新增时填充、更新时填充
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

pojo.vote

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.kangdd.commonmodule.pojo;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

/**
* 投票主表实体类(对应数据库vote表)
* @author kangdidi
* @date 2025年12月14日
*/
@Data
@TableName("vote")
public class Vote {
/**
* 投票主键ID(自增)
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;

/**
* 投票标题
*/
@TableField("title")
private String title;

/**
* 投票类型:1-单选,2-多选
*/
@TableField("type")
private Byte type; // 数据库是TINYINT,对应Java的Byte

/**
* 创建者ID(关联user表的id)
*/
@TableField("user_id")
private Long userId;

/**
* 投票创建时间(MyBatis-Plus自动填充)
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;

/**
* 投票截止时间
*/
@TableField("dead_time")
private LocalDateTime deadTime;

/**
* 投票状态:1-未开始,2-进行中,3-已结束,4-已删除
*/
@TableField("status")
private Byte status;

/**
* 是否匿名投票:1-是,0-否
*/
@TableField("is_anonymous")
private Byte isAnonymous;
@TableField("is_public")
private Byte isPublic;
/**
* 分享码(用于链接分享,16位字符串)
*/
@TableField("share")
private String share;

/**
* 当前投票总票数(选项票数之和,非持久化字段)
*/
@TableField(exist = false)
private Integer voteCount;
}

pojo.vote_potion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.kangdd.commonmodule.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
* 投票选项表实体类(对应数据库vote_option表)
* @author kangdidi
* @date 2025年12月14日
*/
@Data
@TableName("vote_option")
public class VoteOption {
/**
* 选项主键ID(自增)
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;

/**
* 关联的投票ID(vote表的id)
*/
@TableField("vote_id")
private Long voteId;

/**
* 选项内容
*/
@TableField("content")
private String content;

/**
* 选项排序(数字越小越靠前)
*/
@TableField("sort")
private Integer sort;

/**
* 该选项的投票数(冗余字段)
*/
@TableField("vote_count")
private Integer voteCount;
}

pojo.vote_record

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.kangdd.commonmodule.pojo;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

/**
* 投票记录表实体类(对应数据库vote_record表)
* @author kangdidi
* @date 2025年12月14日
*/
@Data
@TableName("vote_record")
public class VoteRecord {
/**
* 记录主键ID(自增)
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;

/**
* 关联的投票ID(vote表的id)
*/
@TableField("vote_id")
private Long voteId;

/**
* 投票用户ID(user表的id)
*/
@TableField("user_id")
private Long userId;

/**
* 关联的选项ID(vote_option表的id)
*/
@TableField("option_id")
private Long optionId;

/**
* 投票时间(MyBatis-Plus自动填充)
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

3. gateway网关

稍后更新见(二)