diff --git a/pom.xml b/pom.xml index f8716c9..a9a1031 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,13 @@ spring-boot-starter-web + + + org.projectlombok + lombok + true + + org.springframework.boot @@ -39,10 +46,10 @@ - mysql - mysql-connector-java - 8.0.33 + com.mysql + mysql-connector-j + org.mybatis.spring.boot @@ -64,6 +71,14 @@ org.springframework.boot spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + diff --git a/src/main/java/com/example/mini_program/common/Result.java b/src/main/java/com/example/mini_program/common/Result.java new file mode 100644 index 0000000..f2f6c0d --- /dev/null +++ b/src/main/java/com/example/mini_program/common/Result.java @@ -0,0 +1,33 @@ +package com.example.mini_program.common; + +import lombok.Data; + +@Data +public class Result { + + private int code; + private String message; + private T data; + + public static Result success(T data) { + Result result = new Result<>(); + result.setCode(0); + result.setMessage("success"); + result.setData(data); + return result; + } + + public static Result error(String message) { + Result result = new Result<>(); + result.setCode(-1); + result.setMessage(message); + return result; + } + + public static Result error(String code, String message) { + Result result = new Result<>(); + result.setCode(-1); + result.setMessage(code + ": " + message); + return result; + } +} diff --git a/src/main/java/com/example/mini_program/config/WxMiniAppConfig.java b/src/main/java/com/example/mini_program/config/WxMiniAppConfig.java index 1c12774..a6fc3a0 100644 --- a/src/main/java/com/example/mini_program/config/WxMiniAppConfig.java +++ b/src/main/java/com/example/mini_program/config/WxMiniAppConfig.java @@ -1,28 +1,14 @@ package com.example.mini_program.config; +import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +@Data @Component @ConfigurationProperties(prefix = "wx.miniapp") public class WxMiniAppConfig { private String appid; private String secret; - - public String getAppid() { - return appid; - } - - public void setAppid(String appid) { - this.appid = appid; - } - - public String getSecret() { - return secret; - } - - public void setSecret(String secret) { - this.secret = secret; - } } diff --git a/src/main/java/com/example/mini_program/controller/AppointmentController.java b/src/main/java/com/example/mini_program/controller/AppointmentController.java new file mode 100644 index 0000000..5c59d7a --- /dev/null +++ b/src/main/java/com/example/mini_program/controller/AppointmentController.java @@ -0,0 +1,90 @@ +package com.example.mini_program.controller; + +import com.example.mini_program.common.Result; +import com.example.mini_program.entity.VisitApplication; +import com.example.mini_program.service.AppointmentService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/wx-mini/appointment") +@RequiredArgsConstructor +public class AppointmentController { + + private final AppointmentService appointmentService; + + /** + * 根据openid获取最新的一条预约记录 + */ + @GetMapping("/latest") + public Result getLatest(@RequestParam String openid) { + if (openid == null || openid.trim().isEmpty()) { + return Result.error("openid不能为空"); + } + return Result.success(appointmentService.getLatest(openid)); + } + + /** + * 获取用户所有预约记录(按创建时间倒序) + */ + @GetMapping("/list") + public Result> getList(@RequestParam String openid) { + if (openid == null || openid.trim().isEmpty()) { + return Result.error("openid不能为空"); + } + return Result.success(appointmentService.getList(openid)); + } + + /** + * 创建预约记录 + */ + @PostMapping("/create") + public Result create(@RequestBody VisitApplication record) { + if (record.getOpenid() == null || record.getOpenid().trim().isEmpty()) { + return Result.error("openid不能为空"); + } + if (record.getName() == null || record.getName().trim().isEmpty()) { + return Result.error("访客姓名不能为空"); + } + if (record.getPhone() == null || record.getPhone().trim().isEmpty()) { + return Result.error("联系电话不能为空"); + } + try { + return Result.success(appointmentService.create(record)); + } catch (Exception e) { + return Result.error("创建预约失败: " + e.getMessage()); + } + } + + /** + * 取消预约(仅pending状态可取消,需校验openid) + */ + @PutMapping("/cancel") + public Result cancel(@RequestParam String id, @RequestParam String openid) { + if (id == null || id.trim().isEmpty()) { + return Result.error("id不能为空"); + } + if (openid == null || openid.trim().isEmpty()) { + return Result.error("openid不能为空"); + } + boolean success = appointmentService.cancel(id, openid); + return success ? Result.success(true) : Result.error("取消失败,无权限或状态不允许"); + } + + /** + * 审批预约(通过/拒绝) + */ + @PutMapping("/approve") + public Result approve(@RequestParam String id, @RequestParam String status) { + if (id == null || id.trim().isEmpty()) { + return Result.error("id不能为空"); + } + if (!"approved".equals(status) && !"rejected".equals(status)) { + return Result.error("status只能为approved或rejected"); + } + boolean success = appointmentService.approve(id, status); + return success ? Result.success(true) : Result.error("审批失败,记录不存在或状态不允许"); + } +} diff --git a/src/main/java/com/example/mini_program/controller/WxLoginController.java b/src/main/java/com/example/mini_program/controller/WxLoginController.java index e73bcd9..63aed8f 100644 --- a/src/main/java/com/example/mini_program/controller/WxLoginController.java +++ b/src/main/java/com/example/mini_program/controller/WxLoginController.java @@ -1,28 +1,20 @@ package com.example.mini_program.controller; -import com.example.mini_program.entity.VisitApplication; -import com.example.mini_program.service.AppointmentService; +import com.example.mini_program.common.Result; import com.example.mini_program.service.WxLoginService; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/wx-mini") +@RequiredArgsConstructor public class WxLoginController { private final WxLoginService wxLoginService; - private final AppointmentService appointmentService; - - public WxLoginController(WxLoginService wxLoginService, AppointmentService appointmentService) { - this.wxLoginService = wxLoginService; - this.appointmentService = appointmentService; - } /** * 微信小程序登录接口 * 接收wx.login的code,换取openid - * - * @param code 小程序wx.login获取的code - * @return openid等信息 */ @GetMapping("/login") public Result login(@RequestParam String code) { @@ -38,74 +30,4 @@ public class WxLoginController { return Result.error(result.getErrcode(), result.getErrmsg()); } } - - /** - * 根据openid获取最新的一条预约记录 - * - * @param openid 微信用户openid - * @return 最新的一条预约记录 - */ - @GetMapping("/appointment/latest") - public Result getLatestAppointment(@RequestParam String openid) { - if (openid == null || openid.trim().isEmpty()) { - return Result.error("openid不能为空"); - } - VisitApplication appointment = appointmentService.getLatest(openid); - return Result.success(appointment); - } - - /** - * 统一返回结果类 - */ - public static class Result { - private int code; - private String message; - private T data; - - public static Result success(T data) { - Result result = new Result<>(); - result.setCode(0); - result.setMessage("success"); - result.setData(data); - return result; - } - - public static Result error(String message) { - Result result = new Result<>(); - result.setCode(-1); - result.setMessage(message); - return result; - } - - public static Result error(String code, String message) { - Result result = new Result<>(); - result.setCode(-1); - result.setMessage(code + ": " + message); - return result; - } - - public int getCode() { - return code; - } - - public void setCode(int code) { - this.code = code; - } - - 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; - } - } } diff --git a/src/main/java/com/example/mini_program/entity/VisitApplication.java b/src/main/java/com/example/mini_program/entity/VisitApplication.java index 24bbc61..f4191a0 100644 --- a/src/main/java/com/example/mini_program/entity/VisitApplication.java +++ b/src/main/java/com/example/mini_program/entity/VisitApplication.java @@ -1,5 +1,9 @@ package com.example.mini_program.entity; +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.Data; + +@Data public class VisitApplication { private String id; @@ -7,116 +11,17 @@ public class VisitApplication { private String phone; private String company; private String reason; + + @JsonAlias("date") private String visitDate; + + @JsonAlias("time") private String visitTime; + private String hostName; private String area; private String status; private String statusText; private String openid; private String createTime; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - - public String getCompany() { - return company; - } - - public void setCompany(String company) { - this.company = company; - } - - public String getReason() { - return reason; - } - - public void setReason(String reason) { - this.reason = reason; - } - - public String getVisitDate() { - return visitDate; - } - - public void setVisitDate(String visitDate) { - this.visitDate = visitDate; - } - - public String getVisitTime() { - return visitTime; - } - - public void setVisitTime(String visitTime) { - this.visitTime = visitTime; - } - - public String getHostName() { - return hostName; - } - - public void setHostName(String hostName) { - this.hostName = hostName; - } - - public String getArea() { - return area; - } - - public void setArea(String area) { - this.area = area; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public String getStatusText() { - return statusText; - } - - public void setStatusText(String statusText) { - this.statusText = statusText; - } - - public String getOpenid() { - return openid; - } - - public void setOpenid(String openid) { - this.openid = openid; - } - - public String getCreateTime() { - return createTime; - } - - public void setCreateTime(String createTime) { - this.createTime = createTime; - } } diff --git a/src/main/java/com/example/mini_program/mapper/VisitApplicationMapper.java b/src/main/java/com/example/mini_program/mapper/VisitApplicationMapper.java index fe41ed9..3a0231f 100644 --- a/src/main/java/com/example/mini_program/mapper/VisitApplicationMapper.java +++ b/src/main/java/com/example/mini_program/mapper/VisitApplicationMapper.java @@ -4,6 +4,8 @@ import com.example.mini_program.entity.VisitApplication; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import java.util.List; + @Mapper public interface VisitApplicationMapper { @@ -14,4 +16,56 @@ public interface VisitApplicationMapper { * @return 最新的一条记录,没有则返回null */ VisitApplication selectLatestByOpenid(@Param("openid") String openid); + + /** + * 根据openid查询所有预约记录(按创建时间倒序) + * + * @param openid 微信用户openid + * @return 预约记录列表 + */ + List selectListByOpenid(@Param("openid") String openid); + + /** + * 新增预约记录 + * + * @param record 预约记录 + * @return 影响行数 + */ + int insert(VisitApplication record); + + /** + * 根据ID和openid取消预约(仅pending状态可取消) + * + * @param id 记录ID + * @param openid 用户openid + * @return 影响行数 + */ + int updateStatusToCancelled(@Param("id") String id, @Param("openid") String openid); + + /** + * 根据ID和openid查询记录(用于权限校验) + * + * @param id 记录ID + * @param openid 用户openid + * @return 预约记录 + */ + VisitApplication selectByIdAndOpenid(@Param("id") String id, @Param("openid") String openid); + + /** + * 根据ID查询记录 + * + * @param id 记录ID + * @return 预约记录 + */ + VisitApplication selectById(@Param("id") String id); + + /** + * 更新审批状态 + * + * @param id 记录ID + * @param status 状态值 + * @param statusText 状态文本 + * @return 影响行数 + */ + int updateStatus(@Param("id") String id, @Param("status") String status, @Param("statusText") String statusText); } diff --git a/src/main/java/com/example/mini_program/service/AppointmentService.java b/src/main/java/com/example/mini_program/service/AppointmentService.java index 25f074c..2140b66 100644 --- a/src/main/java/com/example/mini_program/service/AppointmentService.java +++ b/src/main/java/com/example/mini_program/service/AppointmentService.java @@ -2,35 +2,105 @@ package com.example.mini_program.service; import com.example.mini_program.entity.VisitApplication; import com.example.mini_program.mapper.VisitApplicationMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -@Service -public class AppointmentService { +import java.util.List; +import java.util.UUID; - private static final Logger logger = LoggerFactory.getLogger(AppointmentService.class); +@Slf4j +@Service +@RequiredArgsConstructor +public class AppointmentService { private final VisitApplicationMapper visitApplicationMapper; - public AppointmentService(VisitApplicationMapper visitApplicationMapper) { - this.visitApplicationMapper = visitApplicationMapper; - } - /** * 根据openid获取最新的一条预约记录 - * - * @param openid 微信用户openid - * @return 最新的一条预约记录,没有则返回null */ public VisitApplication getLatest(String openid) { - logger.info("查询用户最新预约记录, openid: {}", openid); + log.info("查询用户最新预约记录, openid: {}", openid); VisitApplication result = visitApplicationMapper.selectLatestByOpenid(openid); if (result != null) { - logger.info("找到预约记录, id: {}", result.getId()); + log.info("找到预约记录, id: {}", result.getId()); } else { - logger.info("未找到预约记录"); + log.info("未找到预约记录"); } return result; } + + /** + * 获取用户所有预约记录(按创建时间倒序) + */ + public List getList(String openid) { + log.info("查询用户预约列表, openid: {}", openid); + List list = visitApplicationMapper.selectListByOpenid(openid); + log.info("查询到 {} 条预约记录", list.size()); + return list; + } + + /** + * 创建预约记录 + */ + public VisitApplication create(VisitApplication record) { + record.setId(UUID.randomUUID().toString().replace("-", "")); + record.setStatus("pending"); + record.setStatusText("待审核"); + visitApplicationMapper.insert(record); + log.info("创建预约记录成功, id: {}, openid: {}", record.getId(), record.getOpenid()); + return record; + } + + /** + * 取消预约(仅pending状态可取消,需校验openid) + */ + public boolean cancel(String id, String openid) { + log.info("取消预约, id: {}, openid: {}", id, openid); + + VisitApplication existing = visitApplicationMapper.selectByIdAndOpenid(id, openid); + if (existing == null) { + log.warn("预约记录不存在或不属于该用户, id: {}, openid: {}", id, openid); + return false; + } + if (!"pending".equals(existing.getStatus())) { + log.warn("预约状态不允许取消, id: {}, status: {}", id, existing.getStatus()); + return false; + } + + int rows = visitApplicationMapper.updateStatusToCancelled(id, openid); + if (rows > 0) { + log.info("取消预约成功, id: {}", id); + return true; + } + log.warn("取消预约失败, id: {}", id); + return false; + } + + /** + * 审批预约(通过/拒绝) + */ + public boolean approve(String id, String status) { + log.info("审批预约, id: {}, status: {}", id, status); + + VisitApplication existing = visitApplicationMapper.selectById(id); + if (existing == null) { + log.warn("预约记录不存在, id: {}", id); + return false; + } + if (!"pending".equals(existing.getStatus())) { + log.warn("预约状态不允许审批, id: {}, currentStatus: {}", id, existing.getStatus()); + return false; + } + + String statusText = "approved".equals(status) ? "已通过" : "已拒绝"; + int rows = visitApplicationMapper.updateStatus(id, status, statusText); + if (rows <= 0) { + log.warn("审批更新失败, id: {}", id); + return false; + } + + log.info("审批成功, id: {}, status: {}", id, statusText); + return true; + } } diff --git a/src/main/java/com/example/mini_program/service/WxLoginService.java b/src/main/java/com/example/mini_program/service/WxLoginService.java index 969c0b4..3f2cf5c 100644 --- a/src/main/java/com/example/mini_program/service/WxLoginService.java +++ b/src/main/java/com/example/mini_program/service/WxLoginService.java @@ -1,6 +1,8 @@ package com.example.mini_program.service; import com.example.mini_program.config.WxMiniAppConfig; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,7 +13,8 @@ import org.springframework.web.client.RestTemplate; public class WxLoginService { private static final Logger logger = LoggerFactory.getLogger(WxLoginService.class); - private static final String JSCODE2SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"; + private static final String JSCODE2SESSION_URL = + "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"; private final WxMiniAppConfig wxMiniAppConfig; private final RestTemplate restTemplate; @@ -23,9 +26,6 @@ public class WxLoginService { /** * 调用微信接口用code换取openid - * - * @param code 小程序调用wx.login获取的code - * @return 登录结果,包含openid等信息 */ public WxLoginResult code2Session(String code) { String url = String.format(JSCODE2SESSION_URL, @@ -33,17 +33,16 @@ public class WxLoginService { wxMiniAppConfig.getSecret(), code); - logger.info("调用微信jscode2session接口,code: {}", code); + logger.info("调用微信jscode2session接口, code: {}", code); try { - // 微信返回text/plain,手动解析JSON字符串 String response = restTemplate.getForObject(url, String.class); logger.info("微信返回原始结果: {}", response); JSONObject json = new JSONObject(response); WxLoginResult result = new WxLoginResult(); result.setOpenid(json.optString("openid")); - result.setSession_key(json.optString("session_key")); + result.setSessionKey(json.optString("session_key")); result.setUnionid(json.optString("unionid")); result.setErrcode(json.optString("errcode")); result.setErrmsg(json.optString("errmsg")); @@ -60,52 +59,15 @@ public class WxLoginService { } } + @Data public static class WxLoginResult { - private String session_key; + @JsonProperty("session_key") + private String sessionKey; + private String unionid; private String openid; private String errcode; private String errmsg; - - public String getSession_key() { - return session_key; - } - - public void setSession_key(String session_key) { - this.session_key = session_key; - } - - public String getUnionid() { - return unionid; - } - - public void setUnionid(String unionid) { - this.unionid = unionid; - } - - public String getOpenid() { - return openid; - } - - public void setOpenid(String openid) { - this.openid = openid; - } - - public String getErrcode() { - return errcode; - } - - public void setErrcode(String errcode) { - this.errcode = errcode; - } - - public String getErrmsg() { - return errmsg; - } - - public void setErrmsg(String errmsg) { - this.errmsg = errmsg; - } } } diff --git a/src/main/java/com/example/mini_program/service/WxSubscribeMessageService.java b/src/main/java/com/example/mini_program/service/WxSubscribeMessageService.java new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/mapper/VisitApplicationMapper.xml b/src/main/resources/mapper/VisitApplicationMapper.xml index 789c813..cae9a87 100644 --- a/src/main/resources/mapper/VisitApplicationMapper.xml +++ b/src/main/resources/mapper/VisitApplicationMapper.xml @@ -18,16 +18,60 @@ + + id, name, phone, company, reason, + DATE_FORMAT(visit_date, '%Y-%m-%d') AS visit_date, + DATE_FORMAT(visit_time, '%H:%i') AS visit_time, + host_name, area, status, status_text, openid, + DATE_FORMAT(create_time, '%Y-%m-%dT%H:%i:%s.000+00:00') AS create_time + + + + + + + + INSERT INTO visit_application (id, name, phone, company, reason, + visit_date, visit_time, host_name, area, + status, status_text, openid, create_time) + VALUES (#{id}, #{name}, #{phone}, #{company}, #{reason}, + #{visitDate}, #{visitTime}, #{hostName}, #{area}, + #{status}, #{statusText}, #{openid}, NOW()) + + + + UPDATE visit_application + SET status = 'cancelled', status_text = '已取消' + WHERE id = #{id} AND openid = #{openid} AND status = 'pending' + + + + + + UPDATE visit_application + SET status = #{status}, status_text = #{statusText} + WHERE id = #{id} AND status = 'pending' + +