diff --git a/backend/src/main/java/com/linhelp/community/CommunityController.java b/backend/src/main/java/com/linhelp/community/CommunityController.java new file mode 100644 index 0000000..6a7618d --- /dev/null +++ b/backend/src/main/java/com/linhelp/community/CommunityController.java @@ -0,0 +1,26 @@ +package com.linhelp.community; + +import com.linhelp.common.api.ApiResponse; +import com.linhelp.common.security.RequireRole; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class CommunityController { + private final CommunityService communityService; + + public CommunityController(CommunityService communityService) { + this.communityService = communityService; + } + + @GetMapping("/api/mini/community") + public ApiResponse miniCurrent() { + return ApiResponse.ok(communityService.currentCommunity()); + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @GetMapping("/api/admin/community") + public ApiResponse adminCurrent() { + return ApiResponse.ok(communityService.currentCommunity()); + } +} diff --git a/backend/src/main/java/com/linhelp/community/CommunityResponse.java b/backend/src/main/java/com/linhelp/community/CommunityResponse.java new file mode 100644 index 0000000..54f70c3 --- /dev/null +++ b/backend/src/main/java/com/linhelp/community/CommunityResponse.java @@ -0,0 +1,69 @@ +package com.linhelp.community; + +import java.util.Map; + +public class CommunityResponse { + private Long id; + private Long tenantId; + private String name; + private String address; + private String contactName; + private String contactPhone; + private Map modules; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getContactName() { + return contactName; + } + + public void setContactName(String contactName) { + this.contactName = contactName; + } + + public String getContactPhone() { + return contactPhone; + } + + public void setContactPhone(String contactPhone) { + this.contactPhone = contactPhone; + } + + public Map getModules() { + return modules; + } + + public void setModules(Map modules) { + this.modules = modules; + } +} diff --git a/backend/src/main/java/com/linhelp/community/CommunityService.java b/backend/src/main/java/com/linhelp/community/CommunityService.java new file mode 100644 index 0000000..9a9f238 --- /dev/null +++ b/backend/src/main/java/com/linhelp/community/CommunityService.java @@ -0,0 +1,32 @@ +package com.linhelp.community; + +import org.springframework.stereotype.Service; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Service +public class CommunityService { + + public CommunityResponse currentCommunity() { + CommunityResponse response = new CommunityResponse(); + response.setId(1L); + response.setTenantId(1L); + response.setName("邻小帮示范小区"); + response.setAddress("示范路 1 号"); + response.setContactName("社区管理员"); + response.setContactPhone("13800000000"); + response.setModules(defaultModules()); + return response; + } + + private Map defaultModules() { + Map modules = new LinkedHashMap(); + modules.put("GOODS", true); + modules.put("EXPRESS", true); + modules.put("GROUP_BUY", true); + modules.put("SECOND_HAND", true); + modules.put("NOTICE", true); + return modules; + } +} diff --git a/backend/src/main/java/com/linhelp/notice/BannerController.java b/backend/src/main/java/com/linhelp/notice/BannerController.java new file mode 100644 index 0000000..a08f23b --- /dev/null +++ b/backend/src/main/java/com/linhelp/notice/BannerController.java @@ -0,0 +1,64 @@ +package com.linhelp.notice; + +import com.linhelp.common.api.ApiResponse; +import com.linhelp.common.security.AuthService; +import com.linhelp.common.security.CurrentUser; +import com.linhelp.common.security.RequireRole; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.List; + +@RestController +public class BannerController { + private final BannerService bannerService; + private final AuthService authService; + + public BannerController(BannerService bannerService, AuthService authService) { + this.bannerService = bannerService; + this.authService = authService; + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @GetMapping("/api/admin/banners") + public ApiResponse> adminList() { + CurrentUser user = authService.currentUser(); + return ApiResponse.ok(bannerService.listAdmin(user.getCommunityId())); + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @PostMapping("/api/admin/banners") + public ApiResponse create(@Valid @RequestBody BannerRequest request) { + CurrentUser user = authService.currentUser(); + request.setTenantId(user.getTenantId()); + request.setCommunityId(user.getCommunityId()); + return ApiResponse.ok(bannerService.create(request)); + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @PutMapping("/api/admin/banners/{id}") + public ApiResponse update(@PathVariable Long id, @Valid @RequestBody BannerRequest request) { + CurrentUser user = authService.currentUser(); + request.setTenantId(user.getTenantId()); + request.setCommunityId(user.getCommunityId()); + return ApiResponse.ok(bannerService.update(id, request)); + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @DeleteMapping("/api/admin/banners/{id}") + public ApiResponse delete(@PathVariable Long id) { + bannerService.delete(id); + return ApiResponse.ok(); + } + + @GetMapping("/api/mini/banners") + public ApiResponse> miniList() { + return ApiResponse.ok(bannerService.listEnabled(1L)); + } +} diff --git a/backend/src/main/java/com/linhelp/notice/BannerRequest.java b/backend/src/main/java/com/linhelp/notice/BannerRequest.java new file mode 100644 index 0000000..e4e471b --- /dev/null +++ b/backend/src/main/java/com/linhelp/notice/BannerRequest.java @@ -0,0 +1,83 @@ +package com.linhelp.notice; + +import javax.validation.constraints.NotBlank; + +public class BannerRequest { + private Long tenantId; + private Long communityId; + + @NotBlank + private String title; + + @NotBlank + private String imageUrl; + + private String linkType; + private String linkValue; + private int sortNo; + private boolean enabled = true; + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getCommunityId() { + return communityId; + } + + public void setCommunityId(Long communityId) { + this.communityId = communityId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getLinkType() { + return linkType; + } + + public void setLinkType(String linkType) { + this.linkType = linkType; + } + + public String getLinkValue() { + return linkValue; + } + + public void setLinkValue(String linkValue) { + this.linkValue = linkValue; + } + + public int getSortNo() { + return sortNo; + } + + public void setSortNo(int sortNo) { + this.sortNo = sortNo; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/backend/src/main/java/com/linhelp/notice/BannerResponse.java b/backend/src/main/java/com/linhelp/notice/BannerResponse.java new file mode 100644 index 0000000..46d3aa9 --- /dev/null +++ b/backend/src/main/java/com/linhelp/notice/BannerResponse.java @@ -0,0 +1,85 @@ +package com.linhelp.notice; + +public class BannerResponse { + private Long id; + private Long tenantId; + private Long communityId; + private String title; + private String imageUrl; + private String linkType; + private String linkValue; + private int sortNo; + private boolean enabled; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getCommunityId() { + return communityId; + } + + public void setCommunityId(Long communityId) { + this.communityId = communityId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getLinkType() { + return linkType; + } + + public void setLinkType(String linkType) { + this.linkType = linkType; + } + + public String getLinkValue() { + return linkValue; + } + + public void setLinkValue(String linkValue) { + this.linkValue = linkValue; + } + + public int getSortNo() { + return sortNo; + } + + public void setSortNo(int sortNo) { + this.sortNo = sortNo; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/backend/src/main/java/com/linhelp/notice/BannerService.java b/backend/src/main/java/com/linhelp/notice/BannerService.java new file mode 100644 index 0000000..4e31e42 --- /dev/null +++ b/backend/src/main/java/com/linhelp/notice/BannerService.java @@ -0,0 +1,81 @@ +package com.linhelp.notice; + +import com.linhelp.common.exception.BizException; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +@Service +public class BannerService { + private final AtomicLong ids = new AtomicLong(1L); + private final Map banners = new LinkedHashMap(); + + public synchronized BannerResponse create(BannerRequest request) { + BannerResponse banner = new BannerResponse(); + banner.setId(ids.getAndIncrement()); + copy(request, banner); + banners.put(banner.getId(), banner); + return banner; + } + + public synchronized List listAdmin(Long communityId) { + List result = new ArrayList(); + for (BannerResponse banner : banners.values()) { + if (communityId.equals(banner.getCommunityId())) { + result.add(banner); + } + } + return result; + } + + public synchronized List listEnabled(Long communityId) { + List result = new ArrayList(); + for (BannerResponse banner : banners.values()) { + if (communityId.equals(banner.getCommunityId()) && banner.isEnabled()) { + result.add(banner); + } + } + Collections.sort(result, new Comparator() { + @Override + public int compare(BannerResponse left, BannerResponse right) { + return Integer.compare(left.getSortNo(), right.getSortNo()); + } + }); + return result; + } + + public synchronized BannerResponse update(Long id, BannerRequest request) { + BannerResponse banner = require(id); + copy(request, banner); + return banner; + } + + public synchronized void delete(Long id) { + banners.remove(id); + } + + private BannerResponse require(Long id) { + BannerResponse banner = banners.get(id); + if (banner == null) { + throw new BizException(404, "Banner不存在"); + } + return banner; + } + + private void copy(BannerRequest request, BannerResponse banner) { + banner.setTenantId(request.getTenantId()); + banner.setCommunityId(request.getCommunityId()); + banner.setTitle(request.getTitle()); + banner.setImageUrl(request.getImageUrl()); + banner.setLinkType(request.getLinkType()); + banner.setLinkValue(request.getLinkValue()); + banner.setSortNo(request.getSortNo()); + banner.setEnabled(request.isEnabled()); + } +} diff --git a/backend/src/main/java/com/linhelp/notice/NoticeController.java b/backend/src/main/java/com/linhelp/notice/NoticeController.java new file mode 100644 index 0000000..385cdf3 --- /dev/null +++ b/backend/src/main/java/com/linhelp/notice/NoticeController.java @@ -0,0 +1,79 @@ +package com.linhelp.notice; + +import com.linhelp.common.api.ApiResponse; +import com.linhelp.common.security.AuthService; +import com.linhelp.common.security.CurrentUser; +import com.linhelp.common.security.RequireRole; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.List; + +@RestController +public class NoticeController { + private final NoticeService noticeService; + private final AuthService authService; + + public NoticeController(NoticeService noticeService, AuthService authService) { + this.noticeService = noticeService; + this.authService = authService; + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @GetMapping("/api/admin/notices") + public ApiResponse> adminList() { + CurrentUser user = authService.currentUser(); + return ApiResponse.ok(noticeService.listAdmin(user.getCommunityId())); + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @PostMapping("/api/admin/notices") + public ApiResponse create(@Valid @RequestBody NoticeRequest request) { + CurrentUser user = authService.currentUser(); + request.setTenantId(user.getTenantId()); + request.setCommunityId(user.getCommunityId()); + return ApiResponse.ok(noticeService.create(request)); + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @PutMapping("/api/admin/notices/{id}") + public ApiResponse update(@PathVariable Long id, @Valid @RequestBody NoticeRequest request) { + return ApiResponse.ok(noticeService.update(id, request)); + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @PutMapping("/api/admin/notices/{id}/publish") + public ApiResponse publish(@PathVariable Long id) { + return ApiResponse.ok(noticeService.publish(id)); + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @PutMapping("/api/admin/notices/{id}/offline") + public ApiResponse offline(@PathVariable Long id) { + return ApiResponse.ok(noticeService.offline(id)); + } + + @RequireRole({"PLATFORM_ADMIN", "COMMUNITY_ADMIN"}) + @DeleteMapping("/api/admin/notices/{id}") + public ApiResponse delete(@PathVariable Long id) { + noticeService.delete(id); + return ApiResponse.ok(); + } + + @GetMapping("/api/mini/notices") + public ApiResponse> miniList() { + return ApiResponse.ok(noticeService.listPublished(1L)); + } + + @GetMapping("/api/mini/notices/{id}") + public ApiResponse miniDetail(@PathVariable Long id) { + return ApiResponse.ok(noticeService.detail(id)); + } +} diff --git a/backend/src/main/java/com/linhelp/notice/NoticeRequest.java b/backend/src/main/java/com/linhelp/notice/NoticeRequest.java new file mode 100644 index 0000000..67f2485 --- /dev/null +++ b/backend/src/main/java/com/linhelp/notice/NoticeRequest.java @@ -0,0 +1,67 @@ +package com.linhelp.notice; + +import javax.validation.constraints.NotBlank; + +public class NoticeRequest { + private Long tenantId; + private Long communityId; + + @NotBlank + private String title; + + @NotBlank + private String content; + + @NotBlank + private String category; + + private boolean pinned; + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getCommunityId() { + return communityId; + } + + public void setCommunityId(Long communityId) { + this.communityId = communityId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public boolean isPinned() { + return pinned; + } + + public void setPinned(boolean pinned) { + this.pinned = pinned; + } +} diff --git a/backend/src/main/java/com/linhelp/notice/NoticeResponse.java b/backend/src/main/java/com/linhelp/notice/NoticeResponse.java new file mode 100644 index 0000000..6567662 --- /dev/null +++ b/backend/src/main/java/com/linhelp/notice/NoticeResponse.java @@ -0,0 +1,87 @@ +package com.linhelp.notice; + +import java.time.LocalDateTime; + +public class NoticeResponse { + private Long id; + private Long tenantId; + private Long communityId; + private String title; + private String content; + private String category; + private boolean pinned; + private String status; + private LocalDateTime publishedAt; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getCommunityId() { + return communityId; + } + + public void setCommunityId(Long communityId) { + this.communityId = communityId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public boolean isPinned() { + return pinned; + } + + public void setPinned(boolean pinned) { + this.pinned = pinned; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public LocalDateTime getPublishedAt() { + return publishedAt; + } + + public void setPublishedAt(LocalDateTime publishedAt) { + this.publishedAt = publishedAt; + } +} diff --git a/backend/src/main/java/com/linhelp/notice/NoticeService.java b/backend/src/main/java/com/linhelp/notice/NoticeService.java new file mode 100644 index 0000000..74161f2 --- /dev/null +++ b/backend/src/main/java/com/linhelp/notice/NoticeService.java @@ -0,0 +1,122 @@ +package com.linhelp.notice; + +import com.linhelp.common.exception.BizException; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +@Service +public class NoticeService { + public static final String STATUS_DRAFT = "DRAFT"; + public static final String STATUS_PUBLISHED = "PUBLISHED"; + public static final String STATUS_OFFLINE = "OFFLINE"; + + private final AtomicLong ids = new AtomicLong(1L); + private final Map notices = new LinkedHashMap(); + + public synchronized NoticeResponse create(NoticeRequest request) { + NoticeResponse notice = new NoticeResponse(); + notice.setId(ids.getAndIncrement()); + notice.setTenantId(request.getTenantId()); + notice.setCommunityId(request.getCommunityId()); + copy(request, notice); + notice.setStatus(STATUS_DRAFT); + notices.put(notice.getId(), notice); + return notice; + } + + public synchronized List listAdmin(Long communityId) { + List result = new ArrayList(); + for (NoticeResponse notice : notices.values()) { + if (communityId.equals(notice.getCommunityId())) { + result.add(notice); + } + } + return result; + } + + public synchronized NoticeResponse update(Long id, NoticeRequest request) { + NoticeResponse notice = require(id); + copy(request, notice); + return notice; + } + + public synchronized NoticeResponse publish(Long id) { + NoticeResponse notice = require(id); + notice.setStatus(STATUS_PUBLISHED); + notice.setPublishedAt(LocalDateTime.now()); + return notice; + } + + public synchronized NoticeResponse offline(Long id) { + NoticeResponse notice = require(id); + notice.setStatus(STATUS_OFFLINE); + return notice; + } + + public synchronized void delete(Long id) { + notices.remove(id); + } + + public synchronized NoticeResponse detail(Long id) { + return require(id); + } + + public synchronized List listPublished(Long communityId) { + List result = new ArrayList(); + for (NoticeResponse notice : notices.values()) { + if (communityId.equals(notice.getCommunityId()) && STATUS_PUBLISHED.equals(notice.getStatus())) { + result.add(notice); + } + } + Collections.sort(result, new Comparator() { + @Override + public int compare(NoticeResponse left, NoticeResponse right) { + if (left.isPinned() != right.isPinned()) { + return left.isPinned() ? -1 : 1; + } + LocalDateTime leftTime = left.getPublishedAt(); + LocalDateTime rightTime = right.getPublishedAt(); + if (leftTime == null && rightTime == null) { + return 0; + } + if (leftTime == null) { + return 1; + } + if (rightTime == null) { + return -1; + } + return rightTime.compareTo(leftTime); + } + }); + return result; + } + + private NoticeResponse require(Long id) { + NoticeResponse notice = notices.get(id); + if (notice == null) { + throw new BizException(404, "公告不存在"); + } + return notice; + } + + private void copy(NoticeRequest request, NoticeResponse notice) { + notice.setTitle(request.getTitle()); + notice.setContent(request.getContent()); + notice.setCategory(request.getCategory()); + notice.setPinned(request.isPinned()); + if (request.getTenantId() != null) { + notice.setTenantId(request.getTenantId()); + } + if (request.getCommunityId() != null) { + notice.setCommunityId(request.getCommunityId()); + } + } +} diff --git a/backend/src/main/java/com/linhelp/user/AddressRequest.java b/backend/src/main/java/com/linhelp/user/AddressRequest.java new file mode 100644 index 0000000..a9e3258 --- /dev/null +++ b/backend/src/main/java/com/linhelp/user/AddressRequest.java @@ -0,0 +1,98 @@ +package com.linhelp.user; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +public class AddressRequest { + private Long tenantId; + private Long communityId; + + @NotBlank + private String contactName; + + @NotBlank + private String phone; + + @NotBlank + private String building; + + @NotBlank + private String room; + + private String detail; + private boolean defaultAddress; + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getCommunityId() { + return communityId; + } + + public void setCommunityId(Long communityId) { + this.communityId = communityId; + } + + public String getContactName() { + return contactName; + } + + public void setContactName(String contactName) { + this.contactName = contactName; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getBuilding() { + return building; + } + + public void setBuilding(String building) { + this.building = building; + } + + public String getRoom() { + return room; + } + + public void setRoom(String room) { + this.room = room; + } + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public boolean isDefault() { + return defaultAddress; + } + + public void setDefault(boolean defaultAddress) { + this.defaultAddress = defaultAddress; + } + + @NotNull + public Long requireTenantId(Long fallback) { + return tenantId == null ? fallback : tenantId; + } + + @NotNull + public Long requireCommunityId(Long fallback) { + return communityId == null ? fallback : communityId; + } +} diff --git a/backend/src/main/java/com/linhelp/user/AddressResponse.java b/backend/src/main/java/com/linhelp/user/AddressResponse.java new file mode 100644 index 0000000..f4c22cf --- /dev/null +++ b/backend/src/main/java/com/linhelp/user/AddressResponse.java @@ -0,0 +1,94 @@ +package com.linhelp.user; + +public class AddressResponse { + private Long id; + private Long tenantId; + private Long communityId; + private Long userId; + private String contactName; + private String phone; + private String building; + private String room; + private String detail; + private boolean defaultAddress; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public Long getCommunityId() { + return communityId; + } + + public void setCommunityId(Long communityId) { + this.communityId = communityId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getContactName() { + return contactName; + } + + public void setContactName(String contactName) { + this.contactName = contactName; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getBuilding() { + return building; + } + + public void setBuilding(String building) { + this.building = building; + } + + public String getRoom() { + return room; + } + + public void setRoom(String room) { + this.room = room; + } + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public boolean isDefault() { + return defaultAddress; + } + + public void setDefault(boolean defaultAddress) { + this.defaultAddress = defaultAddress; + } +} diff --git a/backend/src/main/java/com/linhelp/user/UserAddressController.java b/backend/src/main/java/com/linhelp/user/UserAddressController.java new file mode 100644 index 0000000..fc57d11 --- /dev/null +++ b/backend/src/main/java/com/linhelp/user/UserAddressController.java @@ -0,0 +1,61 @@ +package com.linhelp.user; + +import com.linhelp.common.api.ApiResponse; +import com.linhelp.common.security.AuthService; +import com.linhelp.common.security.CurrentUser; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.List; + +@RestController +@RequestMapping("/api/mini/addresses") +public class UserAddressController { + private final UserAddressService addressService; + private final AuthService authService; + + public UserAddressController(UserAddressService addressService, AuthService authService) { + this.addressService = addressService; + this.authService = authService; + } + + @GetMapping + public ApiResponse> listMine() { + CurrentUser user = authService.currentUser(); + return ApiResponse.ok(addressService.listMine(user.getUserId())); + } + + @PostMapping + public ApiResponse create(@Valid @RequestBody AddressRequest request) { + CurrentUser user = authService.currentUser(); + request.setTenantId(request.requireTenantId(user.getTenantId())); + request.setCommunityId(request.requireCommunityId(user.getCommunityId())); + return ApiResponse.ok(addressService.create(user.getUserId(), request)); + } + + @PutMapping("/{id}") + public ApiResponse update(@PathVariable Long id, @Valid @RequestBody AddressRequest request) { + CurrentUser user = authService.currentUser(); + return ApiResponse.ok(addressService.update(user.getUserId(), id, request)); + } + + @DeleteMapping("/{id}") + public ApiResponse delete(@PathVariable Long id) { + CurrentUser user = authService.currentUser(); + addressService.delete(user.getUserId(), id); + return ApiResponse.ok(); + } + + @PutMapping("/{id}/default") + public ApiResponse setDefault(@PathVariable Long id) { + CurrentUser user = authService.currentUser(); + return ApiResponse.ok(addressService.setDefault(user.getUserId(), id)); + } +} diff --git a/backend/src/main/java/com/linhelp/user/UserAddressService.java b/backend/src/main/java/com/linhelp/user/UserAddressService.java new file mode 100644 index 0000000..5471353 --- /dev/null +++ b/backend/src/main/java/com/linhelp/user/UserAddressService.java @@ -0,0 +1,87 @@ +package com.linhelp.user; + +import com.linhelp.common.exception.BizException; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +@Service +public class UserAddressService { + private final AtomicLong ids = new AtomicLong(1L); + private final Map addresses = new LinkedHashMap(); + + public synchronized AddressResponse create(Long userId, AddressRequest request) { + AddressResponse response = new AddressResponse(); + response.setId(ids.getAndIncrement()); + response.setTenantId(request.getTenantId()); + response.setCommunityId(request.getCommunityId()); + response.setUserId(userId); + copy(request, response); + if (request.isDefault()) { + clearDefaults(userId); + response.setDefault(true); + } + addresses.put(response.getId(), response); + return response; + } + + public synchronized List listMine(Long userId) { + List result = new ArrayList(); + for (AddressResponse address : addresses.values()) { + if (userId.equals(address.getUserId())) { + result.add(address); + } + } + return result; + } + + public synchronized AddressResponse update(Long userId, Long id, AddressRequest request) { + AddressResponse address = requireMine(userId, id); + copy(request, address); + if (request.isDefault()) { + clearDefaults(userId); + address.setDefault(true); + } + return address; + } + + public synchronized void delete(Long userId, Long id) { + requireMine(userId, id); + addresses.remove(id); + } + + public synchronized AddressResponse setDefault(Long userId, Long id) { + AddressResponse address = requireMine(userId, id); + clearDefaults(userId); + address.setDefault(true); + return address; + } + + private void clearDefaults(Long userId) { + for (AddressResponse address : addresses.values()) { + if (userId.equals(address.getUserId())) { + address.setDefault(false); + } + } + } + + private AddressResponse requireMine(Long userId, Long id) { + AddressResponse address = addresses.get(id); + if (address == null || !userId.equals(address.getUserId())) { + throw new BizException(404, "地址不存在"); + } + return address; + } + + private void copy(AddressRequest request, AddressResponse response) { + response.setContactName(request.getContactName()); + response.setPhone(request.getPhone()); + response.setBuilding(request.getBuilding()); + response.setRoom(request.getRoom()); + response.setDetail(request.getDetail()); + } +} diff --git a/backend/src/test/java/com/linhelp/notice/NoticeServiceTests.java b/backend/src/test/java/com/linhelp/notice/NoticeServiceTests.java new file mode 100644 index 0000000..183b3b9 --- /dev/null +++ b/backend/src/test/java/com/linhelp/notice/NoticeServiceTests.java @@ -0,0 +1,37 @@ +package com.linhelp.notice; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class NoticeServiceTests { + + @Test + void miniListOnlyContainsPublishedNoticesWithPinnedFirst() { + NoticeService noticeService = new NoticeService(); + NoticeResponse normal = noticeService.create(request("普通公告", false)); + NoticeResponse pinned = noticeService.create(request("置顶公告", true)); + noticeService.create(request("草稿公告", false)); + + noticeService.publish(normal.getId()); + noticeService.publish(pinned.getId()); + + List notices = noticeService.listPublished(1L); + + assertThat(notices).extracting(NoticeResponse::getTitle) + .containsExactly("置顶公告", "普通公告"); + } + + private NoticeRequest request(String title, boolean pinned) { + NoticeRequest request = new NoticeRequest(); + request.setTenantId(1L); + request.setCommunityId(1L); + request.setTitle(title); + request.setContent(title + "内容"); + request.setCategory("物业通知"); + request.setPinned(pinned); + return request; + } +} diff --git a/backend/src/test/java/com/linhelp/user/UserAddressServiceTests.java b/backend/src/test/java/com/linhelp/user/UserAddressServiceTests.java new file mode 100644 index 0000000..51d49d4 --- /dev/null +++ b/backend/src/test/java/com/linhelp/user/UserAddressServiceTests.java @@ -0,0 +1,34 @@ +package com.linhelp.user; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class UserAddressServiceTests { + + @Test + void settingOneAddressAsDefaultClearsOtherDefaultsForSameUser() { + UserAddressService addressService = new UserAddressService(); + addressService.create(1L, request("张三", "13800000000", "1栋", "101", true)); + AddressResponse second = addressService.create(1L, request("张三", "13800000000", "1栋", "102", true)); + + List addresses = addressService.listMine(1L); + + assertThat(addresses).filteredOn(AddressResponse::isDefault).singleElement() + .extracting(AddressResponse::getId).isEqualTo(second.getId()); + } + + private AddressRequest request(String contactName, String phone, String building, String room, boolean isDefault) { + AddressRequest request = new AddressRequest(); + request.setTenantId(1L); + request.setCommunityId(1L); + request.setContactName(contactName); + request.setPhone(phone); + request.setBuilding(building); + request.setRoom(room); + request.setDefault(isDefault); + return request; + } +}