This commit is contained in:
王子琦
2026-01-21 10:23:12 +08:00
parent 358b121e56
commit 996c6ce750
59 changed files with 4876 additions and 0 deletions

66
backend/pom.xml Normal file
View File

@@ -0,0 +1,66 @@
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<relativePath/>
</parent>
<groupId>com.community</groupId>
<artifactId>community-activities</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>community-activities</name>
<description>Community solar-term activity system</description>
<properties>
<java.version>17</java.version>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.38.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

48
backend/schema.sql Normal file
View File

@@ -0,0 +1,48 @@
create database if not exists community_activity default character set utf8mb4 collate utf8mb4_general_ci;
use community_activity;
create table if not exists sys_user (
id bigint primary key auto_increment,
username varchar(64) not null unique,
password_hash varchar(255) not null,
nickname varchar(64) not null,
phone varchar(32),
role varchar(32) not null default 'user',
created_at datetime not null,
updated_at datetime not null
);
create table if not exists activity (
id bigint primary key auto_increment,
title varchar(255) not null,
term varchar(64) not null,
summary varchar(255),
content text,
location varchar(255) not null,
start_time datetime not null,
end_time datetime not null,
signup_start datetime not null,
signup_end datetime not null,
quota int not null,
status varchar(32) not null,
cover_url varchar(255),
created_by bigint,
created_at datetime not null,
updated_at datetime not null,
index idx_activity_status (status),
index idx_activity_time (start_time)
);
create table if not exists activity_signup (
id bigint primary key auto_increment,
activity_id bigint not null,
user_id bigint not null,
status varchar(32) not null,
checkin_status varchar(32) not null,
signed_at datetime not null,
canceled_at datetime,
checkin_at datetime,
unique key uk_activity_user (activity_id, user_id),
index idx_activity (activity_id),
index idx_user (user_id)
);

View File

@@ -0,0 +1,13 @@
package com.community.app;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.community.app.mapper")
public class CommunityApplication {
public static void main(String[] args) {
SpringApplication.run(CommunityApplication.class, args);
}
}

View File

@@ -0,0 +1,33 @@
package com.community.app.config;
import com.community.app.dto.ApiResponse;
import cn.dev33.satoken.exception.NotLoginException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalStateException.class)
public ApiResponse<Void> handleIllegalState(IllegalStateException ex) {
return ApiResponse.fail(ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse<Void> handleValidation(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().isEmpty()
? "参数错误"
: ex.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
return ApiResponse.fail(message);
}
@ExceptionHandler(NotLoginException.class)
public ApiResponse<Void> handleNotLogin(NotLoginException ex) {
return ApiResponse.fail("未登录或登录已失效");
}
@ExceptionHandler(Exception.class)
public ApiResponse<Void> handleOther(Exception ex) {
return ApiResponse.fail("服务器异常");
}
}

View File

@@ -0,0 +1,32 @@
package com.community.app.config;
import cn.dev33.satoken.stp.StpInterface;
import com.community.app.entity.User;
import com.community.app.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collections;
import java.util.List;
@Configuration
public class SaTokenConfig {
@Bean
public StpInterface stpInterface(UserService userService) {
return new StpInterface() {
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return Collections.emptyList();
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
User user = userService.findById(Long.valueOf(loginId.toString()));
if (user == null) {
return Collections.emptyList();
}
return Collections.singletonList(user.getRole());
}
};
}
}

View File

@@ -0,0 +1,35 @@
package com.community.app.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.context.SaHolder;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(false)
.maxAge(3600);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handle -> {
String method = SaHolder.getRequest().getMethod();
if ("OPTIONS".equalsIgnoreCase(method)) {
return;
}
StpUtil.checkLogin();
}))
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**", "/api/public/**");
}
}

View File

@@ -0,0 +1,105 @@
package com.community.app.controller;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.stp.StpUtil;
import com.community.app.dto.ActivityRequest;
import com.community.app.dto.ActivityView;
import com.community.app.dto.ApiResponse;
import com.community.app.entity.User;
import com.community.app.service.ActivityService;
import com.community.app.service.SignupService;
import com.community.app.service.UserService;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/activities")
public class ActivityController {
private final ActivityService activityService;
private final SignupService signupService;
private final UserService userService;
public ActivityController(ActivityService activityService, SignupService signupService, UserService userService) {
this.activityService = activityService;
this.signupService = signupService;
this.userService = userService;
}
@GetMapping
public ApiResponse<List<ActivityView>> list(@RequestParam(required = false) String status,
@RequestParam(required = false) String keyword) {
User user = currentUser();
if (user == null || !"admin".equals(user.getRole())) {
status = "published";
}
return ApiResponse.ok(activityService.list(status, keyword));
}
@GetMapping("/{id}")
public ApiResponse<ActivityView> detail(@PathVariable Long id) {
ActivityView view = activityService.getView(id);
if (view == null) {
return ApiResponse.fail("活动不存在");
}
User user = currentUser();
if (user == null || !"admin".equals(user.getRole())) {
if (!"published".equals(view.getStatus())) {
return ApiResponse.fail("活动未发布");
}
}
return ApiResponse.ok(view);
}
@SaCheckRole("admin")
@PostMapping
public ApiResponse<Void> create(@Valid @RequestBody ActivityRequest request) {
Long userId = Long.valueOf(StpUtil.getLoginId().toString());
activityService.create(request, userId);
return ApiResponse.ok("创建成功", null);
}
@SaCheckRole("admin")
@PutMapping("/{id}")
public ApiResponse<Void> update(@PathVariable Long id, @Valid @RequestBody ActivityRequest request) {
activityService.update(id, request);
return ApiResponse.ok("更新成功", null);
}
@SaCheckRole("admin")
@PostMapping("/{id}/publish")
public ApiResponse<Void> publish(@PathVariable Long id) {
activityService.publish(id);
return ApiResponse.ok("已发布", null);
}
@SaCheckRole("admin")
@PostMapping("/{id}/close")
public ApiResponse<Void> close(@PathVariable Long id) {
activityService.close(id);
return ApiResponse.ok("已结束", null);
}
@PostMapping("/{id}/signup")
public ApiResponse<Void> signup(@PathVariable Long id) {
Long userId = Long.valueOf(StpUtil.getLoginId().toString());
signupService.signup(id, userId);
return ApiResponse.ok("报名成功", null);
}
@PostMapping("/{id}/cancel")
public ApiResponse<Void> cancel(@PathVariable Long id) {
Long userId = Long.valueOf(StpUtil.getLoginId().toString());
signupService.cancel(id, userId);
return ApiResponse.ok("已取消报名", null);
}
private User currentUser() {
if (!StpUtil.isLogin()) {
return null;
}
Long userId = Long.valueOf(StpUtil.getLoginId().toString());
return userService.findById(userId);
}
}

View File

@@ -0,0 +1,44 @@
package com.community.app.controller;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.community.app.dto.AdminSignupView;
import com.community.app.dto.ApiResponse;
import com.community.app.entity.User;
import com.community.app.service.SignupService;
import com.community.app.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/admin")
@SaCheckRole("admin")
public class AdminController {
private final SignupService signupService;
private final UserService userService;
public AdminController(SignupService signupService, UserService userService) {
this.signupService = signupService;
this.userService = userService;
}
@GetMapping("/activities/{id}/signups")
public ApiResponse<List<AdminSignupView>> listSignups(@PathVariable Long id) {
return ApiResponse.ok(signupService.listByActivity(id));
}
@PostMapping("/signups/{id}/checkin")
public ApiResponse<Void> checkin(@PathVariable Long id) {
signupService.checkin(id);
return ApiResponse.ok("签到成功", null);
}
@GetMapping("/users")
public ApiResponse<List<User>> listUsers() {
return ApiResponse.ok(userService.listAll());
}
}

View File

@@ -0,0 +1,59 @@
package com.community.app.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.community.app.dto.ApiResponse;
import com.community.app.dto.AuthRequest;
import com.community.app.dto.AuthResponse;
import com.community.app.dto.RegisterRequest;
import com.community.app.entity.User;
import com.community.app.service.UserService;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final UserService userService;
public AuthController(UserService userService) {
this.userService = userService;
}
@PostMapping("/login")
public ApiResponse<AuthResponse> login(@Valid @RequestBody AuthRequest request) {
User user = userService.findByUsername(request.getUsername());
if (user == null || !userService.verifyPassword(user, request.getPassword())) {
return ApiResponse.fail("用户名或密码错误");
}
StpUtil.login(user.getId());
AuthResponse response = new AuthResponse();
response.setToken(StpUtil.getTokenValue());
response.setUserId(user.getId());
response.setUsername(user.getUsername());
response.setNickname(user.getNickname());
response.setRole(user.getRole());
return ApiResponse.ok("登录成功", response);
}
@PostMapping("/register")
public ApiResponse<AuthResponse> register(@Valid @RequestBody RegisterRequest request) {
User user = userService.register(request.getUsername(), request.getPassword(), request.getNickname(), request.getPhone());
StpUtil.login(user.getId());
AuthResponse response = new AuthResponse();
response.setToken(StpUtil.getTokenValue());
response.setUserId(user.getId());
response.setUsername(user.getUsername());
response.setNickname(user.getNickname());
response.setRole(user.getRole());
return ApiResponse.ok("注册成功", response);
}
@PostMapping("/logout")
public ApiResponse<Void> logout() {
StpUtil.logout();
return ApiResponse.ok("已退出", null);
}
}

View File

@@ -0,0 +1,36 @@
package com.community.app.controller;
import com.community.app.dto.ActivityView;
import com.community.app.dto.ApiResponse;
import com.community.app.service.ActivityService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/public")
public class PublicController {
private final ActivityService activityService;
public PublicController(ActivityService activityService) {
this.activityService = activityService;
}
@GetMapping("/activities")
public ApiResponse<List<ActivityView>> list(@RequestParam(required = false) String keyword) {
return ApiResponse.ok(activityService.list("published", keyword));
}
@GetMapping("/activities/{id}")
public ApiResponse<ActivityView> detail(@PathVariable Long id) {
ActivityView view = activityService.getView(id);
if (view == null || !"published".equals(view.getStatus())) {
return ApiResponse.fail("活动不存在或未发布");
}
return ApiResponse.ok(view);
}
}

View File

@@ -0,0 +1,37 @@
package com.community.app.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.community.app.dto.ApiResponse;
import com.community.app.dto.SignupView;
import com.community.app.entity.User;
import com.community.app.service.SignupService;
import com.community.app.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/me")
public class UserController {
private final UserService userService;
private final SignupService signupService;
public UserController(UserService userService, SignupService signupService) {
this.userService = userService;
this.signupService = signupService;
}
@GetMapping
public ApiResponse<User> me() {
Long userId = Long.valueOf(StpUtil.getLoginId().toString());
return ApiResponse.ok(userService.findById(userId));
}
@GetMapping("/signups")
public ApiResponse<List<SignupView>> mySignups() {
Long userId = Long.valueOf(StpUtil.getLoginId().toString());
return ApiResponse.ok(signupService.listByUser(userId));
}
}

View File

@@ -0,0 +1,124 @@
package com.community.app.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
public class ActivityRequest {
@NotBlank
private String title;
@NotBlank
private String term;
private String summary;
private String content;
@NotBlank
private String location;
@NotNull
private LocalDateTime startTime;
@NotNull
private LocalDateTime endTime;
@NotNull
private LocalDateTime signupStart;
@NotNull
private LocalDateTime signupEnd;
@NotNull
private Integer quota;
private String status;
private String coverUrl;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTerm() {
return term;
}
public void setTerm(String term) {
this.term = term;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public LocalDateTime getSignupStart() {
return signupStart;
}
public void setSignupStart(LocalDateTime signupStart) {
this.signupStart = signupStart;
}
public LocalDateTime getSignupEnd() {
return signupEnd;
}
public void setSignupEnd(LocalDateTime signupEnd) {
this.signupEnd = signupEnd;
}
public Integer getQuota() {
return quota;
}
public void setQuota(Integer quota) {
this.quota = quota;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCoverUrl() {
return coverUrl;
}
public void setCoverUrl(String coverUrl) {
this.coverUrl = coverUrl;
}
}

View File

@@ -0,0 +1,132 @@
package com.community.app.dto;
import java.time.LocalDateTime;
public class ActivityView {
private Long id;
private String title;
private String term;
private String summary;
private String content;
private String location;
private LocalDateTime startTime;
private LocalDateTime endTime;
private LocalDateTime signupStart;
private LocalDateTime signupEnd;
private Integer quota;
private String status;
private String coverUrl;
private Long signupCount;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTerm() {
return term;
}
public void setTerm(String term) {
this.term = term;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public LocalDateTime getSignupStart() {
return signupStart;
}
public void setSignupStart(LocalDateTime signupStart) {
this.signupStart = signupStart;
}
public LocalDateTime getSignupEnd() {
return signupEnd;
}
public void setSignupEnd(LocalDateTime signupEnd) {
this.signupEnd = signupEnd;
}
public Integer getQuota() {
return quota;
}
public void setQuota(Integer quota) {
this.quota = quota;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCoverUrl() {
return coverUrl;
}
public void setCoverUrl(String coverUrl) {
this.coverUrl = coverUrl;
}
public Long getSignupCount() {
return signupCount;
}
public void setSignupCount(Long signupCount) {
this.signupCount = signupCount;
}
}

View File

@@ -0,0 +1,105 @@
package com.community.app.dto;
import java.time.LocalDateTime;
public class AdminSignupView {
private Long id;
private Long activityId;
private Long userId;
private String username;
private String nickname;
private String phone;
private String status;
private String checkinStatus;
private LocalDateTime signedAt;
private LocalDateTime canceledAt;
private LocalDateTime checkinAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getActivityId() {
return activityId;
}
public void setActivityId(Long activityId) {
this.activityId = activityId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCheckinStatus() {
return checkinStatus;
}
public void setCheckinStatus(String checkinStatus) {
this.checkinStatus = checkinStatus;
}
public LocalDateTime getSignedAt() {
return signedAt;
}
public void setSignedAt(LocalDateTime signedAt) {
this.signedAt = signedAt;
}
public LocalDateTime getCanceledAt() {
return canceledAt;
}
public void setCanceledAt(LocalDateTime canceledAt) {
this.canceledAt = canceledAt;
}
public LocalDateTime getCheckinAt() {
return checkinAt;
}
public void setCheckinAt(LocalDateTime checkinAt) {
this.checkinAt = checkinAt;
}
}

View File

@@ -0,0 +1,52 @@
package com.community.app.dto;
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
public ApiResponse() {
}
public ApiResponse(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(true, "success", data);
}
public static <T> ApiResponse<T> ok(String message, T data) {
return new ApiResponse<>(true, message, data);
}
public static <T> ApiResponse<T> fail(String message) {
return new ApiResponse<>(false, message, null);
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}

View File

@@ -0,0 +1,26 @@
package com.community.app.dto;
import jakarta.validation.constraints.NotBlank;
public class AuthRequest {
@NotBlank
private String username;
@NotBlank
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,49 @@
package com.community.app.dto;
public class AuthResponse {
private String token;
private Long userId;
private String username;
private String nickname;
private String role;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}

View File

@@ -0,0 +1,32 @@
package com.community.app.dto;
import java.util.List;
public class PageResponse<T> {
private long total;
private List<T> list;
public PageResponse() {
}
public PageResponse(long total, List<T> list) {
this.total = total;
this.list = list;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
}

View File

@@ -0,0 +1,45 @@
package com.community.app.dto;
import jakarta.validation.constraints.NotBlank;
public class RegisterRequest {
@NotBlank
private String username;
@NotBlank
private String password;
@NotBlank
private String nickname;
private String phone;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}

View File

@@ -0,0 +1,114 @@
package com.community.app.dto;
import java.time.LocalDateTime;
public class SignupView {
private Long id;
private Long activityId;
private String activityTitle;
private String term;
private String location;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String status;
private String checkinStatus;
private LocalDateTime signedAt;
private LocalDateTime canceledAt;
private LocalDateTime checkinAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getActivityId() {
return activityId;
}
public void setActivityId(Long activityId) {
this.activityId = activityId;
}
public String getActivityTitle() {
return activityTitle;
}
public void setActivityTitle(String activityTitle) {
this.activityTitle = activityTitle;
}
public String getTerm() {
return term;
}
public void setTerm(String term) {
this.term = term;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCheckinStatus() {
return checkinStatus;
}
public void setCheckinStatus(String checkinStatus) {
this.checkinStatus = checkinStatus;
}
public LocalDateTime getSignedAt() {
return signedAt;
}
public void setSignedAt(LocalDateTime signedAt) {
this.signedAt = signedAt;
}
public LocalDateTime getCanceledAt() {
return canceledAt;
}
public void setCanceledAt(LocalDateTime canceledAt) {
this.canceledAt = canceledAt;
}
public LocalDateTime getCheckinAt() {
return checkinAt;
}
public void setCheckinAt(LocalDateTime checkinAt) {
this.checkinAt = checkinAt;
}
}

View File

@@ -0,0 +1,150 @@
package com.community.app.entity;
import java.time.LocalDateTime;
public class Activity {
private Long id;
private String title;
private String term;
private String summary;
private String content;
private String location;
private LocalDateTime startTime;
private LocalDateTime endTime;
private LocalDateTime signupStart;
private LocalDateTime signupEnd;
private Integer quota;
private String status;
private String coverUrl;
private Long createdBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTerm() {
return term;
}
public void setTerm(String term) {
this.term = term;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public LocalDateTime getSignupStart() {
return signupStart;
}
public void setSignupStart(LocalDateTime signupStart) {
this.signupStart = signupStart;
}
public LocalDateTime getSignupEnd() {
return signupEnd;
}
public void setSignupEnd(LocalDateTime signupEnd) {
this.signupEnd = signupEnd;
}
public Integer getQuota() {
return quota;
}
public void setQuota(Integer quota) {
this.quota = quota;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCoverUrl() {
return coverUrl;
}
public void setCoverUrl(String coverUrl) {
this.coverUrl = coverUrl;
}
public Long getCreatedBy() {
return createdBy;
}
public void setCreatedBy(Long createdBy) {
this.createdBy = createdBy;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,78 @@
package com.community.app.entity;
import java.time.LocalDateTime;
public class ActivitySignup {
private Long id;
private Long activityId;
private Long userId;
private String status;
private String checkinStatus;
private LocalDateTime signedAt;
private LocalDateTime canceledAt;
private LocalDateTime checkinAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getActivityId() {
return activityId;
}
public void setActivityId(Long activityId) {
this.activityId = activityId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCheckinStatus() {
return checkinStatus;
}
public void setCheckinStatus(String checkinStatus) {
this.checkinStatus = checkinStatus;
}
public LocalDateTime getSignedAt() {
return signedAt;
}
public void setSignedAt(LocalDateTime signedAt) {
this.signedAt = signedAt;
}
public LocalDateTime getCanceledAt() {
return canceledAt;
}
public void setCanceledAt(LocalDateTime canceledAt) {
this.canceledAt = canceledAt;
}
public LocalDateTime getCheckinAt() {
return checkinAt;
}
public void setCheckinAt(LocalDateTime checkinAt) {
this.checkinAt = checkinAt;
}
}

View File

@@ -0,0 +1,80 @@
package com.community.app.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.time.LocalDateTime;
public class User {
private Long id;
private String username;
@JsonIgnore
private String passwordHash;
private String nickname;
private String phone;
private String role;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(String passwordHash) {
this.passwordHash = passwordHash;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,19 @@
package com.community.app.mapper;
import com.community.app.dto.ActivityView;
import com.community.app.entity.Activity;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ActivityMapper {
int insert(Activity activity);
int update(Activity activity);
Activity findById(@Param("id") Long id);
ActivityView findViewById(@Param("id") Long id);
List<ActivityView> list(@Param("status") String status, @Param("keyword") String keyword);
}

View File

@@ -0,0 +1,28 @@
package com.community.app.mapper;
import com.community.app.dto.AdminSignupView;
import com.community.app.dto.SignupView;
import com.community.app.entity.ActivitySignup;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface SignupMapper {
ActivitySignup findByActivityAndUser(@Param("activityId") Long activityId, @Param("userId") Long userId);
ActivitySignup findById(@Param("id") Long id);
int insert(ActivitySignup signup);
int updateStatus(@Param("id") Long id, @Param("status") String status, @Param("canceledAt") java.time.LocalDateTime canceledAt);
int resign(@Param("id") Long id, @Param("status") String status, @Param("signedAt") java.time.LocalDateTime signedAt);
int checkin(@Param("id") Long id, @Param("checkinStatus") String checkinStatus, @Param("checkinAt") java.time.LocalDateTime checkinAt);
long countSignedByActivity(@Param("activityId") Long activityId);
List<SignupView> listByUser(@Param("userId") Long userId);
List<AdminSignupView> listByActivity(@Param("activityId") Long activityId);
}

View File

@@ -0,0 +1,16 @@
package com.community.app.mapper;
import com.community.app.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
User findById(@Param("id") Long id);
User findByUsername(@Param("username") String username);
int insert(User user);
List<User> listAll();
}

View File

@@ -0,0 +1,110 @@
package com.community.app.service;
import com.community.app.dto.ActivityRequest;
import com.community.app.dto.ActivityView;
import com.community.app.entity.Activity;
import com.community.app.mapper.ActivityMapper;
import com.community.app.mapper.SignupMapper;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class ActivityService {
private final ActivityMapper activityMapper;
private final SignupMapper signupMapper;
public ActivityService(ActivityMapper activityMapper, SignupMapper signupMapper) {
this.activityMapper = activityMapper;
this.signupMapper = signupMapper;
}
public ActivityView getView(Long id) {
return activityMapper.findViewById(id);
}
public Activity getEntity(Long id) {
return activityMapper.findById(id);
}
public List<ActivityView> list(String status, String keyword) {
return activityMapper.list(status, keyword);
}
public Activity create(ActivityRequest request, Long creatorId) {
validateTimes(request);
Activity activity = new Activity();
applyRequest(activity, request);
activity.setCreatedBy(creatorId);
if (activity.getStatus() == null || activity.getStatus().isEmpty()) {
activity.setStatus("draft");
}
activityMapper.insert(activity);
return activity;
}
public void update(Long id, ActivityRequest request) {
validateTimes(request);
Activity activity = activityMapper.findById(id);
if (activity == null) {
throw new IllegalStateException("活动不存在");
}
activity.setId(id);
applyRequest(activity, request);
if (activity.getStatus() == null || activity.getStatus().isEmpty()) {
activity.setStatus("draft");
}
activityMapper.update(activity);
}
public void publish(Long id) {
Activity activity = activityMapper.findById(id);
if (activity == null) {
throw new IllegalStateException("活动不存在");
}
activity.setStatus("published");
activityMapper.update(activity);
}
public void close(Long id) {
Activity activity = activityMapper.findById(id);
if (activity == null) {
throw new IllegalStateException("活动不存在");
}
activity.setStatus("closed");
activityMapper.update(activity);
}
public long countSigned(Long activityId) {
return signupMapper.countSignedByActivity(activityId);
}
private void validateTimes(ActivityRequest request) {
LocalDateTime start = request.getStartTime();
LocalDateTime end = request.getEndTime();
LocalDateTime signupStart = request.getSignupStart();
LocalDateTime signupEnd = request.getSignupEnd();
if (start != null && end != null && end.isBefore(start)) {
throw new IllegalStateException("活动结束时间不能早于开始时间");
}
if (signupStart != null && signupEnd != null && signupEnd.isBefore(signupStart)) {
throw new IllegalStateException("报名结束时间不能早于开始时间");
}
}
private void applyRequest(Activity activity, ActivityRequest request) {
activity.setTitle(request.getTitle());
activity.setTerm(request.getTerm());
activity.setSummary(request.getSummary());
activity.setContent(request.getContent());
activity.setLocation(request.getLocation());
activity.setStartTime(request.getStartTime());
activity.setEndTime(request.getEndTime());
activity.setSignupStart(request.getSignupStart());
activity.setSignupEnd(request.getSignupEnd());
activity.setQuota(request.getQuota());
activity.setStatus(request.getStatus());
activity.setCoverUrl(request.getCoverUrl());
}
}

View File

@@ -0,0 +1,93 @@
package com.community.app.service;
import com.community.app.dto.AdminSignupView;
import com.community.app.dto.SignupView;
import com.community.app.entity.Activity;
import com.community.app.entity.ActivitySignup;
import com.community.app.mapper.ActivityMapper;
import com.community.app.mapper.SignupMapper;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class SignupService {
private final SignupMapper signupMapper;
private final ActivityMapper activityMapper;
public SignupService(SignupMapper signupMapper, ActivityMapper activityMapper) {
this.signupMapper = signupMapper;
this.activityMapper = activityMapper;
}
public void signup(Long activityId, Long userId) {
Activity activity = activityMapper.findById(activityId);
if (activity == null) {
throw new IllegalStateException("活动不存在");
}
if (!"published".equals(activity.getStatus())) {
throw new IllegalStateException("活动未发布或已结束");
}
LocalDateTime now = LocalDateTime.now();
if (activity.getSignupStart() != null && now.isBefore(activity.getSignupStart())) {
throw new IllegalStateException("报名尚未开始");
}
if (activity.getSignupEnd() != null && now.isAfter(activity.getSignupEnd())) {
throw new IllegalStateException("报名已结束");
}
long count = signupMapper.countSignedByActivity(activityId);
if (activity.getQuota() != null && count >= activity.getQuota()) {
throw new IllegalStateException("报名名额已满");
}
ActivitySignup existing = signupMapper.findByActivityAndUser(activityId, userId);
if (existing == null) {
ActivitySignup signup = new ActivitySignup();
signup.setActivityId(activityId);
signup.setUserId(userId);
signup.setStatus("SIGNED");
signup.setCheckinStatus("NOT");
signup.setSignedAt(now);
signupMapper.insert(signup);
return;
}
if ("SIGNED".equals(existing.getStatus())) {
throw new IllegalStateException("已报名该活动");
}
signupMapper.resign(existing.getId(), "SIGNED", now);
}
public void cancel(Long activityId, Long userId) {
ActivitySignup existing = signupMapper.findByActivityAndUser(activityId, userId);
if (existing == null || !"SIGNED".equals(existing.getStatus())) {
throw new IllegalStateException("未找到可取消的报名");
}
if ("CHECKED".equals(existing.getCheckinStatus())) {
throw new IllegalStateException("已签到的报名不可取消");
}
signupMapper.updateStatus(existing.getId(), "CANCELED", LocalDateTime.now());
}
public void checkin(Long signupId) {
ActivitySignup existing = signupMapper.findById(signupId);
if (existing == null) {
throw new IllegalStateException("报名记录不存在");
}
if (!"SIGNED".equals(existing.getStatus())) {
throw new IllegalStateException("该报名已取消,无法签到");
}
if ("CHECKED".equals(existing.getCheckinStatus())) {
throw new IllegalStateException("已完成签到");
}
signupMapper.checkin(signupId, "CHECKED", LocalDateTime.now());
}
public List<SignupView> listByUser(Long userId) {
return signupMapper.listByUser(userId);
}
public List<AdminSignupView> listByActivity(Long activityId) {
return signupMapper.listByActivity(activityId);
}
}

View File

@@ -0,0 +1,48 @@
package com.community.app.service;
import com.community.app.entity.User;
import com.community.app.mapper.UserMapper;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
private final UserMapper userMapper;
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User findById(Long id) {
return userMapper.findById(id);
}
public User findByUsername(String username) {
return userMapper.findByUsername(username);
}
public User register(String username, String rawPassword, String nickname, String phone) {
if (userMapper.findByUsername(username) != null) {
throw new IllegalStateException("用户名已存在");
}
User user = new User();
user.setUsername(username);
user.setPasswordHash(encoder.encode(rawPassword));
user.setNickname(nickname);
user.setPhone(phone);
user.setRole("user");
userMapper.insert(user);
return user;
}
public boolean verifyPassword(User user, String rawPassword) {
return encoder.matches(rawPassword, user.getPasswordHash());
}
public List<User> listAll() {
return userMapper.listAll();
}
}

View File

@@ -0,0 +1,25 @@
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3307/community_activity?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: qq5211314
driver-class-name: com.mysql.cj.jdbc.Driver
jackson:
time-zone: Asia/Shanghai
date-format: yyyy-MM-dd HH:mm:ss
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
sa-token:
token-name: satoken
timeout: 2592000
is-concurrent: true
is-share: true
token-style: uuid
active-timeout: 1800

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.community.app.mapper.ActivityMapper">
<resultMap id="ActivityMap" type="com.community.app.entity.Activity">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="term" property="term"/>
<result column="summary" property="summary"/>
<result column="content" property="content"/>
<result column="content" property="content"/>
<result column="location" property="location"/>
<result column="start_time" property="startTime"/>
<result column="end_time" property="endTime"/>
<result column="signup_start" property="signupStart"/>
<result column="signup_end" property="signupEnd"/>
<result column="quota" property="quota"/>
<result column="status" property="status"/>
<result column="cover_url" property="coverUrl"/>
<result column="created_by" property="createdBy"/>
<result column="created_at" property="createdAt"/>
<result column="updated_at" property="updatedAt"/>
</resultMap>
<resultMap id="ActivityViewMap" type="com.community.app.dto.ActivityView">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="term" property="term"/>
<result column="summary" property="summary"/>
<result column="location" property="location"/>
<result column="start_time" property="startTime"/>
<result column="end_time" property="endTime"/>
<result column="signup_start" property="signupStart"/>
<result column="signup_end" property="signupEnd"/>
<result column="quota" property="quota"/>
<result column="status" property="status"/>
<result column="cover_url" property="coverUrl"/>
<result column="signup_count" property="signupCount"/>
</resultMap>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into activity (title, term, summary, content, location, start_time, end_time,
signup_start, signup_end, quota, status, cover_url, created_by, created_at, updated_at)
values (#{title}, #{term}, #{summary}, #{content}, #{location}, #{startTime}, #{endTime},
#{signupStart}, #{signupEnd}, #{quota}, #{status}, #{coverUrl}, #{createdBy}, now(), now())
</insert>
<update id="update">
update activity
set title = #{title},
term = #{term},
summary = #{summary},
content = #{content},
location = #{location},
start_time = #{startTime},
end_time = #{endTime},
signup_start = #{signupStart},
signup_end = #{signupEnd},
quota = #{quota},
status = #{status},
cover_url = #{coverUrl},
updated_at = now()
where id = #{id}
</update>
<select id="findById" resultMap="ActivityMap">
select * from activity where id = #{id}
</select>
<select id="findViewById" resultMap="ActivityViewMap">
select a.*, coalesce(sum(case when s.status = 'SIGNED' then 1 else 0 end), 0) as signup_count
from activity a
left join activity_signup s on a.id = s.activity_id
where a.id = #{id}
group by a.id
</select>
<select id="list" resultMap="ActivityViewMap">
select a.*, coalesce(sum(case when s.status = 'SIGNED' then 1 else 0 end), 0) as signup_count
from activity a
left join activity_signup s on a.id = s.activity_id
<where>
<if test="status != null and status != ''">
a.status = #{status}
</if>
<if test="keyword != null and keyword != ''">
and (a.title like concat('%', #{keyword}, '%') or a.term like concat('%', #{keyword}, '%'))
</if>
</where>
group by a.id
order by a.start_time desc
</select>
</mapper>

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.community.app.mapper.SignupMapper">
<resultMap id="SignupMap" type="com.community.app.entity.ActivitySignup">
<id column="id" property="id"/>
<result column="activity_id" property="activityId"/>
<result column="user_id" property="userId"/>
<result column="status" property="status"/>
<result column="checkin_status" property="checkinStatus"/>
<result column="signed_at" property="signedAt"/>
<result column="canceled_at" property="canceledAt"/>
<result column="checkin_at" property="checkinAt"/>
</resultMap>
<resultMap id="SignupViewMap" type="com.community.app.dto.SignupView">
<id column="id" property="id"/>
<result column="activity_id" property="activityId"/>
<result column="activity_title" property="activityTitle"/>
<result column="term" property="term"/>
<result column="location" property="location"/>
<result column="start_time" property="startTime"/>
<result column="end_time" property="endTime"/>
<result column="status" property="status"/>
<result column="checkin_status" property="checkinStatus"/>
<result column="signed_at" property="signedAt"/>
<result column="canceled_at" property="canceledAt"/>
<result column="checkin_at" property="checkinAt"/>
</resultMap>
<resultMap id="AdminSignupViewMap" type="com.community.app.dto.AdminSignupView">
<id column="id" property="id"/>
<result column="activity_id" property="activityId"/>
<result column="user_id" property="userId"/>
<result column="username" property="username"/>
<result column="nickname" property="nickname"/>
<result column="phone" property="phone"/>
<result column="status" property="status"/>
<result column="checkin_status" property="checkinStatus"/>
<result column="signed_at" property="signedAt"/>
<result column="canceled_at" property="canceledAt"/>
<result column="checkin_at" property="checkinAt"/>
</resultMap>
<select id="findByActivityAndUser" resultMap="SignupMap">
select * from activity_signup where activity_id = #{activityId} and user_id = #{userId}
</select>
<select id="findById" resultMap="SignupMap">
select * from activity_signup where id = #{id}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into activity_signup (activity_id, user_id, status, checkin_status, signed_at)
values (#{activityId}, #{userId}, #{status}, #{checkinStatus}, #{signedAt})
</insert>
<update id="updateStatus">
update activity_signup set status = #{status}, canceled_at = #{canceledAt} where id = #{id}
</update>
<update id="resign">
update activity_signup set status = #{status}, signed_at = #{signedAt}, canceled_at = null
where id = #{id}
</update>
<update id="checkin">
update activity_signup set checkin_status = #{checkinStatus}, checkin_at = #{checkinAt}
where id = #{id}
</update>
<select id="countSignedByActivity" resultType="long">
select count(1) from activity_signup where activity_id = #{activityId} and status = 'SIGNED'
</select>
<select id="listByUser" resultMap="SignupViewMap">
select s.id, s.activity_id, a.title as activity_title, a.term, a.location, a.start_time, a.end_time,
s.status, s.checkin_status, s.signed_at, s.canceled_at, s.checkin_at
from activity_signup s
join activity a on s.activity_id = a.id
where s.user_id = #{userId}
order by s.signed_at desc
</select>
<select id="listByActivity" resultMap="AdminSignupViewMap">
select s.id, s.activity_id, s.user_id, u.username, u.nickname, u.phone,
s.status, s.checkin_status, s.signed_at, s.canceled_at, s.checkin_at
from activity_signup s
join sys_user u on s.user_id = u.id
where s.activity_id = #{activityId}
order by s.signed_at desc
</select>
</mapper>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.community.app.mapper.UserMapper">
<resultMap id="UserMap" type="com.community.app.entity.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password_hash" property="passwordHash"/>
<result column="nickname" property="nickname"/>
<result column="phone" property="phone"/>
<result column="role" property="role"/>
<result column="created_at" property="createdAt"/>
<result column="updated_at" property="updatedAt"/>
</resultMap>
<select id="findById" resultMap="UserMap">
select * from sys_user where id = #{id}
</select>
<select id="findByUsername" resultMap="UserMap">
select * from sys_user where username = #{username}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into sys_user (username, password_hash, nickname, phone, role, created_at, updated_at)
values (#{username}, #{passwordHash}, #{nickname}, #{phone}, #{role}, now(), now())
</insert>
<select id="listAll" resultMap="UserMap">
select * from sys_user order by id desc
</select>
</mapper>