diff --git a/db/alter_account_add_createby_updateby.sql b/db/alter_account_add_createby_updateby.sql new file mode 100644 index 0000000..66facc5 --- /dev/null +++ b/db/alter_account_add_createby_updateby.sql @@ -0,0 +1,7 @@ +-- 给account表添加create_by和update_by字段 +-- 作者: 王志维 +-- 创建时间: 2026-01-26 + +ALTER TABLE `account` +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; diff --git a/db/alter_all_tables_add_createby_updateby.sql b/db/alter_all_tables_add_createby_updateby.sql new file mode 100644 index 0000000..832ca88 --- /dev/null +++ b/db/alter_all_tables_add_createby_updateby.sql @@ -0,0 +1,90 @@ +-- 为所有表添加create_by和update_by字段 +-- 作者: 王志维 +-- 创建时间: 2026-01-26 + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- 1. 账户表 +ALTER TABLE `account` +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 2. 积分账户表 +ALTER TABLE `points_account` +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 3. 客户表 +ALTER TABLE `customer` +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 4. 内容购买记录表 +ALTER TABLE `content_purchase` +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 5. 用户角色关联表 +ALTER TABLE `sys_user_role` +ADD COLUMN `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' AFTER `user_id`, +ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`, +ADD COLUMN `delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 :0 未删除,1已删除' AFTER `update_time`, +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 6. 系统菜单表 +ALTER TABLE `sys_menu` +ADD COLUMN `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' AFTER `delete_flag`, +ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`, +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 7. 系统角色表 +ALTER TABLE `sys_role` +ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`, +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 8. 支付订单表 +ALTER TABLE `payment_order` +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 9. 系统日志表 +ALTER TABLE `sys_log` +ADD COLUMN `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' AFTER `note`, +ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`, +ADD COLUMN `delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 :0 未删除,1已删除' AFTER `update_time`, +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 10. 系统用户表 +ALTER TABLE `sys_user` +ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`, +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 11. 供应商表 +ALTER TABLE `supplier` +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 12. 系统字典表 +ALTER TABLE `sys_dict` +ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`, +ADD COLUMN `delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 :0 未删除,1已删除' AFTER `update_time`, +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 13. 内容点赞记录表 +ALTER TABLE `cms_content_like` +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +-- 14. 内容查看记录表 +ALTER TABLE `cms_content_view` +ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`, +ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/db/cms_content_like.sql b/db/cms_content_like.sql new file mode 100644 index 0000000..8919c8e --- /dev/null +++ b/db/cms_content_like.sql @@ -0,0 +1,14 @@ +-- 创建cms_content_like表(用户点赞记录) +CREATE TABLE `cms_content_like` ( + `like_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '点赞记录ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `user_name` varchar(50) NOT NULL COMMENT '用户名', + `content_id` bigint(20) NOT NULL COMMENT '内容ID', + `content_title` varchar(200) NOT NULL COMMENT '内容标题', + `like_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '点赞时间', + `delete_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标志:0未删除,1已删除', + PRIMARY KEY (`like_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_content_id` (`content_id`), + KEY `idx_like_time` (`like_time`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='CMS内容点赞记录表'; \ No newline at end of file diff --git a/db/cms_content_view.sql b/db/cms_content_view.sql new file mode 100644 index 0000000..f2f3cd6 --- /dev/null +++ b/db/cms_content_view.sql @@ -0,0 +1,14 @@ +-- 创建cms_content_view表(用户查看记录) +CREATE TABLE `cms_content_view` ( + `view_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '查看记录ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `user_name` varchar(50) NOT NULL COMMENT '用户名', + `content_id` bigint(20) NOT NULL COMMENT '内容ID', + `content_title` varchar(200) NOT NULL COMMENT '内容标题', + `view_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '查看时间', + `delete_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标志:0未删除,1已删除', + PRIMARY KEY (`view_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_content_id` (`content_id`), + KEY `idx_view_time` (`view_time`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='CMS内容查看记录表'; \ No newline at end of file diff --git a/pom.xml b/pom.xml index 03ee0fb..c1d4c02 100644 --- a/pom.xml +++ b/pom.xml @@ -240,6 +240,13 @@ commons-pool2 2.12.0 + + + + org.dromara.sms4j + sms4j-spring-boot-starter + 3.3.5 + @@ -270,6 +277,7 @@ -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -parameters diff --git a/src/main/java/com/kexue/skills/common/Assert.java b/src/main/java/com/kexue/skills/common/Assert.java new file mode 100644 index 0000000..c91f051 --- /dev/null +++ b/src/main/java/com/kexue/skills/common/Assert.java @@ -0,0 +1,299 @@ +package com.kexue.skills.common; + +import com.kexue.skills.exception.BizException; + +import java.util.Collection; +import java.util.Map; + +/** + * 断言工具类,用于封装业务异常的抛出 + * 参考continue-admin的实现方式 + * + * @author 王志维 + * @since 2026-01-26 + */ +public class Assert { + + private Assert() { + // 私有构造方法,禁止实例化 + } + + /** + * 断言表达式为true,如果为false则抛出BizException + * + * @param expression 表达式 + * @param message 异常信息 + */ + public static void isTrue(boolean expression, String message) { + if (!expression) { + throw new BizException(message); + } + } + + /** + * 断言表达式为true,如果为false则抛出BizException + * + * @param expression 表达式 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void isTrue(boolean expression, long errorCode, String message) { + if (!expression) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言表达式为false,如果为true则抛出BizException + * + * @param expression 表达式 + * @param message 异常信息 + */ + public static void isFalse(boolean expression, String message) { + if (expression) { + throw new BizException(message); + } + } + + /** + * 断言表达式为false,如果为true则抛出BizException + * + * @param expression 表达式 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void isFalse(boolean expression, long errorCode, String message) { + if (expression) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言对象不为null,如果为null则抛出BizException + * + * @param object 对象 + * @param message 异常信息 + */ + public static void notNull(Object object, String message) { + if (object == null) { + throw new BizException(message); + } + } + + /** + * 断言对象不为null,如果为null则抛出BizException + * + * @param object 对象 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void notNull(Object object, long errorCode, String message) { + if (object == null) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言对象为null,如果不为null则抛出BizException + * + * @param object 对象 + * @param message 异常信息 + */ + public static void isNull(Object object, String message) { + if (object != null) { + throw new BizException(message); + } + } + + /** + * 断言对象为null,如果不为null则抛出BizException + * + * @param object 对象 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void isNull(Object object, long errorCode, String message) { + if (object != null) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言字符串不为空,如果为空则抛出BizException + * + * @param str 字符串 + * @param message 异常信息 + */ + public static void notEmpty(String str, String message) { + if (str == null || str.isEmpty()) { + throw new BizException(message); + } + } + + /** + * 断言字符串不为空,如果为空则抛出BizException + * + * @param str 字符串 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void notEmpty(String str, long errorCode, String message) { + if (str == null || str.isEmpty()) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言字符串不为空白,如果为空白则抛出BizException + * + * @param str 字符串 + * @param message 异常信息 + */ + public static void notBlank(String str, String message) { + if (str == null || str.trim().isEmpty()) { + throw new BizException(message); + } + } + + /** + * 断言字符串不为空白,如果为空白则抛出BizException + * + * @param str 字符串 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void notBlank(String str, long errorCode, String message) { + if (str == null || str.trim().isEmpty()) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言集合不为空,如果为空则抛出BizException + * + * @param collection 集合 + * @param message 异常信息 + */ + public static void notEmpty(Collection collection, String message) { + if (collection == null || collection.isEmpty()) { + throw new BizException(message); + } + } + + /** + * 断言集合不为空,如果为空则抛出BizException + * + * @param collection 集合 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void notEmpty(Collection collection, long errorCode, String message) { + if (collection == null || collection.isEmpty()) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言Map不为空,如果为空则抛出BizException + * + * @param map Map + * @param message 异常信息 + */ + public static void notEmpty(Map map, String message) { + if (map == null || map.isEmpty()) { + throw new BizException(message); + } + } + + /** + * 断言Map不为空,如果为空则抛出BizException + * + * @param map Map + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void notEmpty(Map map, long errorCode, String message) { + if (map == null || map.isEmpty()) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言数组不为空,如果为空则抛出BizException + * + * @param array 数组 + * @param message 异常信息 + */ + public static void notEmpty(Object[] array, String message) { + if (array == null || array.length == 0) { + throw new BizException(message); + } + } + + /** + * 断言数组不为空,如果为空则抛出BizException + * + * @param array 数组 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void notEmpty(Object[] array, long errorCode, String message) { + if (array == null || array.length == 0) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言两个对象相等,如果不相等则抛出BizException + * + * @param obj1 对象1 + * @param obj2 对象2 + * @param message 异常信息 + */ + public static void equals(Object obj1, Object obj2, String message) { + if (!obj1.equals(obj2)) { + throw new BizException(message); + } + } + + /** + * 断言两个对象相等,如果不相等则抛出BizException + * + * @param obj1 对象1 + * @param obj2 对象2 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void equals(Object obj1, Object obj2, long errorCode, String message) { + if (!obj1.equals(obj2)) { + throw new BizException(errorCode, message); + } + } + + /** + * 断言两个对象不相等,如果相等则抛出BizException + * + * @param obj1 对象1 + * @param obj2 对象2 + * @param message 异常信息 + */ + public static void notEquals(Object obj1, Object obj2, String message) { + if (obj1.equals(obj2)) { + throw new BizException(message); + } + } + + /** + * 断言两个对象不相等,如果相等则抛出BizException + * + * @param obj1 对象1 + * @param obj2 对象2 + * @param errorCode 错误码 + * @param message 异常信息 + */ + public static void notEquals(Object obj1, Object obj2, long errorCode, String message) { + if (obj1.equals(obj2)) { + throw new BizException(errorCode, message); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/config/FilterConfig.java b/src/main/java/com/kexue/skills/config/FilterConfig.java new file mode 100644 index 0000000..e7470ee --- /dev/null +++ b/src/main/java/com/kexue/skills/config/FilterConfig.java @@ -0,0 +1,31 @@ +package com.kexue.skills.config; + +import com.kexue.skills.interceptor.CorsFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 过滤器配置类 + * 用于注册自定义过滤器 + * + * @author 王志维 + * @since 2026-01-26 + */ +@Configuration +public class FilterConfig { + + /** + * 注册CORS过滤器 + * + * @return FilterRegistrationBean + */ + @Bean + public FilterRegistrationBean corsFilterRegistrationBean() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new CorsFilter()); + registrationBean.addUrlPatterns("/*"); + registrationBean.setOrder(1); + return registrationBean; + } +} diff --git a/src/main/java/com/kexue/skills/config/WebMvcConfig.java b/src/main/java/com/kexue/skills/config/WebMvcConfig.java index 299000d..03d1223 100644 --- a/src/main/java/com/kexue/skills/config/WebMvcConfig.java +++ b/src/main/java/com/kexue/skills/config/WebMvcConfig.java @@ -1,11 +1,17 @@ package com.kexue.skills.config; import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.nio.charset.StandardCharsets; +import java.util.List; + /** mvc配置,例如:资源映射、视图解析、拦截器等 * **/ @@ -53,6 +59,17 @@ public class WebMvcConfig implements WebMvcConfigurer { registry.addViewController("/submitJournalUpload").setViewName("submitJournalUpload");*/ } + @Override + public void configureMessageConverters(List> converters) { + // 配置字符串转换器,使用UTF-8编码 + StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8); + converters.add(stringConverter); + + // 配置JSON转换器,使用UTF-8编码 + MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(); + jsonConverter.setDefaultCharset(StandardCharsets.UTF_8); + converters.add(jsonConverter); + } @Override public void addInterceptors(InterceptorRegistry registry) { diff --git a/src/main/java/com/kexue/skills/controller/CaptchaController.java b/src/main/java/com/kexue/skills/controller/CaptchaController.java index 9dace26..9784a10 100644 --- a/src/main/java/com/kexue/skills/controller/CaptchaController.java +++ b/src/main/java/com/kexue/skills/controller/CaptchaController.java @@ -79,7 +79,7 @@ public class CaptchaController { */ @PostMapping("/verify") @Operation(summary = "验证验证码", description = "验证验证码") - public CommonResult verifyCaptcha(@RequestParam String captchaId, @RequestParam String captchaValue) { + public CommonResult verifyCaptcha(@RequestParam("captchaId") String captchaId, @RequestParam("captchaValue") String captchaValue) { // 验证码开关逻辑:只有启用时才验证验证码 if (!captchaConfig.isEnabled()) { return CommonResult.success(true); diff --git a/src/main/java/com/kexue/skills/controller/CmsContentController.java b/src/main/java/com/kexue/skills/controller/CmsContentController.java index 5e2b7c3..061a81c 100644 --- a/src/main/java/com/kexue/skills/controller/CmsContentController.java +++ b/src/main/java/com/kexue/skills/controller/CmsContentController.java @@ -106,11 +106,11 @@ public class CmsContentController { @PostMapping("/updateAuditStatus") @Operation(summary = "更新审核状态", description = "更新审核状态") @RequireAuth - public CommonResult updateAuditStatus(@RequestParam Long contentId, - @RequestParam Integer auditStatus, - @RequestParam Long reviewerId, - @RequestParam String reviewerName, - @RequestParam(required = false) String auditComment) { + public CommonResult updateAuditStatus(@RequestParam("contentId") Long contentId, + @RequestParam("auditStatus") Integer auditStatus, + @RequestParam("reviewerId") Long reviewerId, + @RequestParam("reviewerName") String reviewerName, + @RequestParam(value = "auditComment", required = false) String auditComment) { return CommonResult.success(cmsContentService.updateAuditStatus(contentId, auditStatus, reviewerId, reviewerName, auditComment, reviewerName) > 0); } @@ -126,10 +126,10 @@ public class CmsContentController { @PostMapping("/updatePublishStatus") @Operation(summary = "更新发布状态", description = "更新发布状态") @RequireAuth - public CommonResult updatePublishStatus(@RequestParam Long contentId, - @RequestParam Integer publishStatus, - @RequestParam(required = false) String publishTime, - @RequestParam String updateBy) { + public CommonResult updatePublishStatus(@RequestParam("contentId") Long contentId, + @RequestParam("publishStatus") Integer publishStatus, + @RequestParam(value = "publishTime", required = false) String publishTime, + @RequestParam("updateBy") String updateBy) { return CommonResult.success(cmsContentService.updatePublishStatus(contentId, publishStatus, publishTime, updateBy) > 0); } diff --git a/src/main/java/com/kexue/skills/controller/LoginController.java b/src/main/java/com/kexue/skills/controller/LoginController.java index 40b0031..4b19bd1 100644 --- a/src/main/java/com/kexue/skills/controller/LoginController.java +++ b/src/main/java/com/kexue/skills/controller/LoginController.java @@ -2,9 +2,9 @@ package com.kexue.skills.controller; import com.kexue.skills.annotation.PreventDuplicateSubmission; import com.kexue.skills.common.CommonResult; -import com.kexue.skills.entity.SysUser; import com.kexue.skills.entity.request.LoginDto; import com.kexue.skills.entity.request.LoginUserDto; +import com.kexue.skills.entity.request.PhoneLoginDto; import com.kexue.skills.service.SysUserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -35,7 +35,7 @@ public class LoginController { * @param loginDto 登录信息 * @return 登录结果 */ - @PostMapping("/do") + @PostMapping("/accountLogin") @Operation(summary = "用户登录", description = "用户登录") @PreventDuplicateSubmission public CommonResult login(@RequestBody LoginDto loginDto) { @@ -47,13 +47,42 @@ public class LoginController { * * @return 登出结果 */ - @PostMapping("/logout") + @PostMapping("/accountLogout") @Operation(summary = "用户登出", description = "用户登出") public CommonResult logout() { // 使用Sa-Token登出 cn.dev33.satoken.stp.StpUtil.logout(); return CommonResult.success("登出成功"); } + + /** + * 发送手机验证码 + * + * @param phone 手机号 + * @return 发送结果 + */ + @PostMapping("/sendCode") + @Operation(summary = "发送手机验证码", description = "向指定手机号发送登录验证码") + public CommonResult sendCode(@RequestParam("phone") String phone) { + boolean success = sysUserService.sendSmsCode(phone); + if (success) { + return CommonResult.success("验证码发送成功"); + } else { + return CommonResult.failed("验证码发送失败"); + } + } + + /** + * 手机号登录 + * + * @param phoneLoginDto 手机号登录信息 + * @return 登录结果 + */ + @PostMapping("/phoneLogin") + @Operation(summary = "手机号登录", description = "使用手机号和验证码登录") + public CommonResult phoneLogin(@RequestBody PhoneLoginDto phoneLoginDto) { + return CommonResult.success(sysUserService.phoneLogin(phoneLoginDto)); + } } diff --git a/src/main/java/com/kexue/skills/entity/Account.java b/src/main/java/com/kexue/skills/entity/Account.java index 3b2302a..f42726d 100644 --- a/src/main/java/com/kexue/skills/entity/Account.java +++ b/src/main/java/com/kexue/skills/entity/Account.java @@ -46,4 +46,10 @@ public class Account extends BaseEntity implements Serializable { @Schema(description ="是否删除 :0 未删除,1已删除") private Integer deleteFlag; + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/CmsContentLike.java b/src/main/java/com/kexue/skills/entity/CmsContentLike.java new file mode 100644 index 0000000..2bb4af7 --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/CmsContentLike.java @@ -0,0 +1,129 @@ +package com.kexue.skills.entity; + +import java.util.Date; + +/** + * CMS内容点赞记录实体类 + * + * @author 王志维 + * @since 2026-01-26 + */ +public class CmsContentLike { + /** + * 点赞记录ID + */ + private Long likeId; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String userName; + + /** + * 内容ID + */ + private Long contentId; + + /** + * 内容标题 + */ + private String contentTitle; + + /** + * 点赞时间 + */ + private Date likeTime; + + /** + * 删除标志:0未删除,1已删除 + */ + private Integer deleteFlag; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + // getter和setter方法 + public Long getLikeId() { + return likeId; + } + + public void setLikeId(Long likeId) { + this.likeId = likeId; + } + + 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 Long getContentId() { + return contentId; + } + + public void setContentId(Long contentId) { + this.contentId = contentId; + } + + public String getContentTitle() { + return contentTitle; + } + + public void setContentTitle(String contentTitle) { + this.contentTitle = contentTitle; + } + + public Date getLikeTime() { + return likeTime; + } + + public void setLikeTime(Date likeTime) { + this.likeTime = likeTime; + } + + public Integer getDeleteFlag() { + return deleteFlag; + } + + public void setDeleteFlag(Integer deleteFlag) { + this.deleteFlag = deleteFlag; + } + + public String getCreateBy() { + return createBy; + } + + public void setCreateBy(String createBy) { + this.createBy = createBy; + } + + public String getUpdateBy() { + return updateBy; + } + + public void setUpdateBy(String updateBy) { + this.updateBy = updateBy; + } +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/CmsContentView.java b/src/main/java/com/kexue/skills/entity/CmsContentView.java new file mode 100644 index 0000000..895de63 --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/CmsContentView.java @@ -0,0 +1,129 @@ +package com.kexue.skills.entity; + +import java.util.Date; + +/** + * CMS内容查看记录实体类 + * + * @author 王志维 + * @since 2026-01-26 + */ +public class CmsContentView { + /** + * 查看记录ID + */ + private Long viewId; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String userName; + + /** + * 内容ID + */ + private Long contentId; + + /** + * 内容标题 + */ + private String contentTitle; + + /** + * 查看时间 + */ + private Date viewTime; + + /** + * 删除标志:0未删除,1已删除 + */ + private Integer deleteFlag; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新人 + */ + private String updateBy; + + // getter和setter方法 + public Long getViewId() { + return viewId; + } + + public void setViewId(Long viewId) { + this.viewId = viewId; + } + + 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 Long getContentId() { + return contentId; + } + + public void setContentId(Long contentId) { + this.contentId = contentId; + } + + public String getContentTitle() { + return contentTitle; + } + + public void setContentTitle(String contentTitle) { + this.contentTitle = contentTitle; + } + + public Date getViewTime() { + return viewTime; + } + + public void setViewTime(Date viewTime) { + this.viewTime = viewTime; + } + + public Integer getDeleteFlag() { + return deleteFlag; + } + + public void setDeleteFlag(Integer deleteFlag) { + this.deleteFlag = deleteFlag; + } + + public String getCreateBy() { + return createBy; + } + + public void setCreateBy(String createBy) { + this.createBy = createBy; + } + + public String getUpdateBy() { + return updateBy; + } + + public void setUpdateBy(String updateBy) { + this.updateBy = updateBy; + } +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/ContentPurchase.java b/src/main/java/com/kexue/skills/entity/ContentPurchase.java index 9dc1757..dc7018d 100644 --- a/src/main/java/com/kexue/skills/entity/ContentPurchase.java +++ b/src/main/java/com/kexue/skills/entity/ContentPurchase.java @@ -61,4 +61,10 @@ public class ContentPurchase extends BaseEntity implements Serializable { @Schema(description ="是否删除 :0 未删除,1已删除") private Integer deleteFlag; + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/Customer.java b/src/main/java/com/kexue/skills/entity/Customer.java index 76c342f..88a090a 100644 --- a/src/main/java/com/kexue/skills/entity/Customer.java +++ b/src/main/java/com/kexue/skills/entity/Customer.java @@ -50,4 +50,10 @@ public class Customer extends BaseEntity implements Serializable { @Schema(description ="是否删除 :0 未删除,1已删除") private Integer deleteFlag; + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/PaymentOrder.java b/src/main/java/com/kexue/skills/entity/PaymentOrder.java index 11ba924..f8aeb91 100644 --- a/src/main/java/com/kexue/skills/entity/PaymentOrder.java +++ b/src/main/java/com/kexue/skills/entity/PaymentOrder.java @@ -84,4 +84,10 @@ public class PaymentOrder extends BaseEntity implements Serializable { @Schema(description ="是否删除 :0 未删除,1已删除") private Integer deleteFlag; + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/PointsAccount.java b/src/main/java/com/kexue/skills/entity/PointsAccount.java index a5df0d7..c10f32f 100644 --- a/src/main/java/com/kexue/skills/entity/PointsAccount.java +++ b/src/main/java/com/kexue/skills/entity/PointsAccount.java @@ -48,4 +48,10 @@ public class PointsAccount extends BaseEntity implements Serializable { @Schema(description ="是否删除 :0 未删除,1已删除") private Integer deleteFlag; + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/Supplier.java b/src/main/java/com/kexue/skills/entity/Supplier.java index 232d8f6..9be09a2 100644 --- a/src/main/java/com/kexue/skills/entity/Supplier.java +++ b/src/main/java/com/kexue/skills/entity/Supplier.java @@ -50,4 +50,10 @@ public class Supplier extends BaseEntity implements Serializable { @Schema(description ="是否删除 :0 未删除,1已删除") private Integer deleteFlag; + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/SysDict.java b/src/main/java/com/kexue/skills/entity/SysDict.java index 5dd5ed1..8554850 100644 --- a/src/main/java/com/kexue/skills/entity/SysDict.java +++ b/src/main/java/com/kexue/skills/entity/SysDict.java @@ -44,4 +44,17 @@ public class SysDict extends BaseEntity implements Serializable { @Schema(description ="创建时间") private Date createTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description ="更新时间") + private Date updateTime; + + @Schema(description ="是否删除 :0 未删除,1已删除") + private Integer deleteFlag; + + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } diff --git a/src/main/java/com/kexue/skills/entity/SysLog.java b/src/main/java/com/kexue/skills/entity/SysLog.java index bf0cf4c..69050bf 100644 --- a/src/main/java/com/kexue/skills/entity/SysLog.java +++ b/src/main/java/com/kexue/skills/entity/SysLog.java @@ -4,6 +4,7 @@ package com.kexue.skills.entity; import java.io.Serializable; import com.kexue.skills.entity.base.BaseEntity; import com.kexue.skills.entity.base.BaseQueryDto; +import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -45,4 +46,21 @@ public class SysLog extends BaseEntity implements Serializable { @Schema(description ="备注") private String note; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description ="创建时间") + private Date createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description ="更新时间") + private Date updateTime; + + @Schema(description ="是否删除 :0 未删除,1已删除") + private Integer deleteFlag; + + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } diff --git a/src/main/java/com/kexue/skills/entity/SysMenu.java b/src/main/java/com/kexue/skills/entity/SysMenu.java index 9d96ad5..4fb711b 100644 --- a/src/main/java/com/kexue/skills/entity/SysMenu.java +++ b/src/main/java/com/kexue/skills/entity/SysMenu.java @@ -4,6 +4,7 @@ package com.kexue.skills.entity; import java.io.Serializable; import com.kexue.skills.entity.base.BaseEntity; import com.kexue.skills.entity.base.BaseQueryDto; +import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -45,4 +46,18 @@ public class SysMenu extends BaseEntity implements Serializable { @Schema(description ="删除标记") private Object deleteFlag; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description ="创建时间") + private Date createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description ="更新时间") + private Date updateTime; + + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } diff --git a/src/main/java/com/kexue/skills/entity/SysRole.java b/src/main/java/com/kexue/skills/entity/SysRole.java index 93c7650..8f7c087 100644 --- a/src/main/java/com/kexue/skills/entity/SysRole.java +++ b/src/main/java/com/kexue/skills/entity/SysRole.java @@ -32,10 +32,20 @@ public class SysRole extends BaseEntity implements Serializable { @Schema(description ="创建时间") private Date createTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description ="更新时间") + private Date updateTime; + @Schema(description ="备注") private String remark; @Schema(description ="删除标记") private String deleteFlag; + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } diff --git a/src/main/java/com/kexue/skills/entity/SysUser.java b/src/main/java/com/kexue/skills/entity/SysUser.java index 7887f48..e0933f2 100644 --- a/src/main/java/com/kexue/skills/entity/SysUser.java +++ b/src/main/java/com/kexue/skills/entity/SysUser.java @@ -47,10 +47,20 @@ public class SysUser extends BaseEntity implements Serializable { @Schema(description ="创建时间") private Date createTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description ="更新时间") + private Date updateTime; + @Schema(description ="是否启用(1启用,2禁用)") private Integer enable; @Schema(description ="是否删除 :0 未删除,1已删除") private Integer deleteFlag; + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } diff --git a/src/main/java/com/kexue/skills/entity/SysUserRole.java b/src/main/java/com/kexue/skills/entity/SysUserRole.java index 15b77d1..cf32ce2 100644 --- a/src/main/java/com/kexue/skills/entity/SysUserRole.java +++ b/src/main/java/com/kexue/skills/entity/SysUserRole.java @@ -2,6 +2,8 @@ package com.kexue.skills.entity; import java.io.Serializable; +import java.util.Date; + import com.kexue.skills.entity.base.BaseEntity; import com.kexue.skills.entity.base.BaseQueryDto; import com.fasterxml.jackson.annotation.JsonFormat; @@ -24,4 +26,21 @@ public class SysUserRole extends BaseEntity implements Serializable { @Schema(description ="用户ID") private Long userId; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description ="创建时间") + private Date createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Schema(description ="更新时间") + private Date updateTime; + + @Schema(description ="是否删除 :0 未删除,1已删除") + private Integer deleteFlag; + + @Schema(description ="创建人") + private String createBy; + + @Schema(description ="更新人") + private String updateBy; + } diff --git a/src/main/java/com/kexue/skills/entity/request/LoginUser.java b/src/main/java/com/kexue/skills/entity/request/LoginUser.java index 44fd783..e0d6e44 100644 --- a/src/main/java/com/kexue/skills/entity/request/LoginUser.java +++ b/src/main/java/com/kexue/skills/entity/request/LoginUser.java @@ -44,4 +44,24 @@ public class LoginUser { * token */ private String token; + + /** + * 最近点赞的20条记录 + */ + private List favorites; + + /** + * 最近查看的20条记录 + */ + private List history; + + /** + * 最近创建的20条记录 + */ + private List create; + + /** + * 最近购买的20条记录 + */ + private List has; } diff --git a/src/main/java/com/kexue/skills/entity/request/LoginUserDto.java b/src/main/java/com/kexue/skills/entity/request/LoginUserDto.java index aa24603..6f9d86b 100644 --- a/src/main/java/com/kexue/skills/entity/request/LoginUserDto.java +++ b/src/main/java/com/kexue/skills/entity/request/LoginUserDto.java @@ -4,6 +4,8 @@ import com.kexue.skills.entity.SysUser; import io.swagger.annotations.ApiModel; import lombok.Data; +import java.util.List; + /** * @author 维哥 * @Description @@ -13,4 +15,24 @@ import lombok.Data; @ApiModel(value = "登录用户信息") public class LoginUserDto extends SysUser { String token; + + /** + * 最近点赞的20条记录 + */ + List favorites; + + /** + * 最近查看的20条记录 + */ + List history; + + /** + * 最近创建的20条记录 + */ + List create; + + /** + * 最近购买的20条记录 + */ + List has; } diff --git a/src/main/java/com/kexue/skills/entity/request/PhoneLoginDto.java b/src/main/java/com/kexue/skills/entity/request/PhoneLoginDto.java new file mode 100644 index 0000000..885bce7 --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/request/PhoneLoginDto.java @@ -0,0 +1,26 @@ +package com.kexue.skills.entity.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 手机号登录请求DTO + * + * @author 王志维 + * @since 2026-01-22 + */ +@Data +@ApiModel(value = "手机号登录信息") +public class PhoneLoginDto implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description ="手机号") + private String phone; + + @Schema(description ="验证码") + private String code; +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/interceptor/CorsFilter.java b/src/main/java/com/kexue/skills/interceptor/CorsFilter.java index af7cc4a..5c7e877 100644 --- a/src/main/java/com/kexue/skills/interceptor/CorsFilter.java +++ b/src/main/java/com/kexue/skills/interceptor/CorsFilter.java @@ -12,6 +12,11 @@ public class CorsFilter implements Filter { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; + // 设置字符编码 + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json;charset=UTF-8"); + // 允许所有来源 response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); diff --git a/src/main/java/com/kexue/skills/mapper/CmsContentLikeMapper.java b/src/main/java/com/kexue/skills/mapper/CmsContentLikeMapper.java new file mode 100644 index 0000000..d16391e --- /dev/null +++ b/src/main/java/com/kexue/skills/mapper/CmsContentLikeMapper.java @@ -0,0 +1,74 @@ +package com.kexue.skills.mapper; + +import com.kexue.skills.entity.CmsContentLike; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * CMS内容点赞记录Mapper接口 + * + * @author 王志维 + * @since 2026-01-26 + */ +@Mapper +public interface CmsContentLikeMapper { + /** + * 新增点赞记录 + * + * @param cmsContentLike 点赞记录对象 + * @return 受影响的行数 + */ + int insert(CmsContentLike cmsContentLike); + + /** + * 删除点赞记录 + * + * @param likeId 点赞记录ID + * @return 受影响的行数 + */ + int deleteById(Long likeId); + + /** + * 更新点赞记录 + * + * @param cmsContentLike 点赞记录对象 + * @return 受影响的行数 + */ + int update(CmsContentLike cmsContentLike); + + /** + * 查询点赞记录 + * + * @param likeId 点赞记录ID + * @return 点赞记录对象 + */ + CmsContentLike queryById(Long likeId); + + /** + * 查询用户是否已点赞该内容 + * + * @param userId 用户ID + * @param contentId 内容ID + * @return 点赞记录对象,不存在则返回null + */ + CmsContentLike queryByUserIdAndContentId(@Param("userId") Long userId, @Param("contentId") Long contentId); + + /** + * 查询用户最近的点赞记录 + * + * @param userId 用户ID + * @param limit 查询数量 + * @return 点赞记录列表 + */ + List queryRecentLikesByUserId(@Param("userId") Long userId, @Param("limit") int limit); + + /** + * 查询内容的点赞记录数量 + * + * @param contentId 内容ID + * @return 点赞记录数量 + */ + int countByContentId(Long contentId); +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java b/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java index 5b379f8..9afb8f2 100644 --- a/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java +++ b/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java @@ -108,4 +108,13 @@ public interface CmsContentMapper { * @return 影响行数 */ int increaseViewCount(Long contentId); + + /** + * 查询用户最近创建的内容ID列表 + * + * @param userId 用户ID + * @param limit 查询数量 + * @return 内容ID列表 + */ + List queryRecentCreatedByUserId(@Param("userId") Long userId, @Param("limit") int limit); } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/mapper/CmsContentViewMapper.java b/src/main/java/com/kexue/skills/mapper/CmsContentViewMapper.java new file mode 100644 index 0000000..ca5e751 --- /dev/null +++ b/src/main/java/com/kexue/skills/mapper/CmsContentViewMapper.java @@ -0,0 +1,74 @@ +package com.kexue.skills.mapper; + +import com.kexue.skills.entity.CmsContentView; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * CMS内容查看记录Mapper接口 + * + * @author 王志维 + * @since 2026-01-26 + */ +@Mapper +public interface CmsContentViewMapper { + /** + * 新增查看记录 + * + * @param cmsContentView 查看记录对象 + * @return 受影响的行数 + */ + int insert(CmsContentView cmsContentView); + + /** + * 删除查看记录 + * + * @param viewId 查看记录ID + * @return 受影响的行数 + */ + int deleteById(Long viewId); + + /** + * 更新查看记录 + * + * @param cmsContentView 查看记录对象 + * @return 受影响的行数 + */ + int update(CmsContentView cmsContentView); + + /** + * 查询查看记录 + * + * @param viewId 查看记录ID + * @return 查看记录对象 + */ + CmsContentView queryById(Long viewId); + + /** + * 查询用户最近的查看记录 + * + * @param userId 用户ID + * @param limit 查询数量 + * @return 查看记录列表 + */ + List queryRecentViewsByUserId(@Param("userId") Long userId, @Param("limit") int limit); + + /** + * 查询内容的查看记录数量 + * + * @param contentId 内容ID + * @return 查看记录数量 + */ + int countByContentId(Long contentId); + + /** + * 查询用户是否已查看该内容 + * + * @param userId 用户ID + * @param contentId 内容ID + * @return 查看记录对象,不存在则返回null + */ + CmsContentView queryByUserIdAndContentId(@Param("userId") Long userId, @Param("contentId") Long contentId); +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/mapper/SysUserMapper.java b/src/main/java/com/kexue/skills/mapper/SysUserMapper.java index 8e993a7..29b33f5 100644 --- a/src/main/java/com/kexue/skills/mapper/SysUserMapper.java +++ b/src/main/java/com/kexue/skills/mapper/SysUserMapper.java @@ -83,5 +83,7 @@ public interface SysUserMapper { int deleteById(Long userId); SysUser getByUsername(String userName); + + SysUser getByTel(String tel); } diff --git a/src/main/java/com/kexue/skills/service/SysUserService.java b/src/main/java/com/kexue/skills/service/SysUserService.java index 5014202..1b868aa 100644 --- a/src/main/java/com/kexue/skills/service/SysUserService.java +++ b/src/main/java/com/kexue/skills/service/SysUserService.java @@ -5,6 +5,7 @@ import com.kexue.skills.entity.SysUser; import com.kexue.skills.entity.dto.SysUserDto; import com.kexue.skills.entity.request.LoginDto; import com.kexue.skills.entity.request.LoginUserDto; +import com.kexue.skills.entity.request.PhoneLoginDto; import com.kexue.skills.entity.request.ResetPasswordDto; import java.util.List; @@ -83,4 +84,20 @@ public interface SysUserService extends BaseService { * @return 是否成功 */ boolean resetPwd(Long userId, String newPassword, String operator); + + /** + * 发送手机验证码 + * + * @param phone 手机号 + * @return 是否发送成功 + */ + boolean sendSmsCode(String phone); + + /** + * 手机号登录 + * + * @param phoneLoginDto 手机号登录信息 + * @return 登录结果 + */ + LoginUserDto phoneLogin(PhoneLoginDto phoneLoginDto); } diff --git a/src/main/java/com/kexue/skills/service/impl/AccountServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/AccountServiceImpl.java index 3331ed2..2651673 100644 --- a/src/main/java/com/kexue/skills/service/impl/AccountServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/AccountServiceImpl.java @@ -5,6 +5,7 @@ import com.github.pagehelper.PageInfo; import com.kexue.skills.entity.Account; import com.kexue.skills.entity.AccountTransaction; import com.kexue.skills.entity.dto.AccountDto; +import com.kexue.skills.common.Assert; import com.kexue.skills.exception.BizException; import com.kexue.skills.mapper.AccountMapper; import com.kexue.skills.mapper.AccountTransactionMapper; @@ -177,14 +178,10 @@ public class AccountServiceImpl implements AccountService { public int reduceBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark) { // 1. 查询账户信息 Account account = this.queryByUserId(userId); - if (account == null) { - throw new BizException("账户不存在"); - } + Assert.notNull(account, "账户不存在"); // 2. 检查余额是否足够 - if (account.getBalance().compareTo(amount) < 0) { - throw new BizException("账户余额不足"); - } + Assert.isTrue(account.getBalance().compareTo(amount) >= 0, "账户余额不足"); // 3. 保存交易记录 AccountTransaction transaction = new AccountTransaction(); diff --git a/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java index cad649a..f9fc381 100644 --- a/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java @@ -5,6 +5,7 @@ import com.github.pagehelper.PageInfo; import com.kexue.skills.entity.CmsContent; import com.kexue.skills.entity.ContentPurchase; import com.kexue.skills.entity.dto.ContentPurchaseDto; +import com.kexue.skills.common.Assert; import com.kexue.skills.exception.BizException; import com.kexue.skills.mapper.ContentPurchaseMapper; import com.kexue.skills.service.AccountService; @@ -169,15 +170,10 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService { public ContentPurchase purchaseContent(Long userId, Long contentId, Integer payType) { // 1. 查询内容信息 CmsContent content = this.cmsContentService.queryById(contentId); - if (content == null) { - throw new BizException("内容不存在"); - } + Assert.notNull(content, "内容不存在"); // 2. 检查内容是否需要付费 - if (content.getIsPaid() == 0) { - // 免费内容,无需购买 - throw new BizException("该内容为免费内容,无需购买"); - } + Assert.isTrue(content.getIsPaid() != 0, "该内容为免费内容,无需购买"); // 3. 检查用户是否已经购买过该内容 ContentPurchase existingPurchase = this.queryByUserIdAndContentId(userId, contentId); @@ -209,9 +205,7 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService { this.accountService.reduceBalance(userId, price, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle()); } else if (payType == 2) { // 积分支付 - if (content.getSupportPointsPay() != 1) { - throw new BizException("该内容不支持积分支付"); - } + Assert.isTrue(content.getSupportPointsPay() == 1, "该内容不支持积分支付"); Integer points = content.getRequiredPoints(); purchase.setAmount(BigDecimal.ZERO); @@ -220,7 +214,7 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService { // 扣除用户积分 this.pointsAccountService.reducePoints(userId, points, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle()); } else { - throw new BizException("不支持的支付方式"); + Assert.isTrue(false, "不支持的支付方式"); } // 6. 更新购买记录状态 diff --git a/src/main/java/com/kexue/skills/service/impl/PointsAccountServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/PointsAccountServiceImpl.java index cf299eb..c0516b6 100644 --- a/src/main/java/com/kexue/skills/service/impl/PointsAccountServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/PointsAccountServiceImpl.java @@ -5,6 +5,7 @@ import com.github.pagehelper.PageInfo; import com.kexue.skills.entity.PointsAccount; import com.kexue.skills.entity.PointsTransaction; import com.kexue.skills.entity.dto.PointsAccountDto; +import com.kexue.skills.common.Assert; import com.kexue.skills.exception.BizException; import com.kexue.skills.mapper.PointsAccountMapper; import com.kexue.skills.mapper.PointsTransactionMapper; @@ -179,14 +180,10 @@ public class PointsAccountServiceImpl implements PointsAccountService { public int reducePoints(Long userId, Integer points, String transactionNo, Long businessId, String businessType, String remark) { // 1. 查询积分账户信息 PointsAccount account = this.queryByUserId(userId); - if (account == null) { - throw new BizException("积分账户不存在"); - } + Assert.notNull(account, "积分账户不存在"); // 2. 检查积分是否足够 - if (account.getAvailablePoints() < points) { - throw new BizException("积分不足"); - } + Assert.isTrue(account.getAvailablePoints() >= points, "积分不足"); // 3. 保存积分交易记录 PointsTransaction transaction = new PointsTransaction(); diff --git a/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java index 34709ed..8323f1d 100644 --- a/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java @@ -4,33 +4,30 @@ import cn.hutool.core.bean.BeanUtil; import com.alicp.jetcache.anno.CacheInvalidate; import com.alicp.jetcache.anno.CachePenetrationProtect; import com.alicp.jetcache.anno.Cached; -import com.kexue.skills.common.CacheManager; -import com.kexue.skills.common.Const; -import com.kexue.skills.entity.*; -import com.kexue.skills.entity.dto.ContentPurchaseDto; -import com.kexue.skills.entity.request.LoginUser; -import com.kexue.skills.entity.request.LoginUserDto; -import com.kexue.skills.entity.request.ResetPasswordDto; -import com.kexue.skills.mapper.*; -import com.kexue.skills.utils.MD5Util; -import com.kexue.skills.entity.dto.SysUserDto; -import com.kexue.skills.entity.request.LoginDto; -import com.kexue.skills.exception.BizException; -import com.kexue.skills.service.SysUserService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; +import com.kexue.skills.common.Assert; +import com.kexue.skills.common.CacheManager; +import com.kexue.skills.common.Const; +import com.kexue.skills.config.CaptchaConfig; +import com.kexue.skills.entity.*; +import com.kexue.skills.entity.dto.ContentPurchaseDto; +import com.kexue.skills.entity.dto.SysUserDto; +import com.kexue.skills.entity.request.*; +import com.kexue.skills.mapper.*; +import com.kexue.skills.service.SysUserService; +import com.kexue.skills.utils.MD5Util; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.SmsBlend; +import org.dromara.sms4j.core.factory.SmsFactory; +import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Objects; - -import com.kexue.skills.config.CaptchaConfig; -import com.kexue.skills.service.AccountService; -import com.kexue.skills.service.PointsAccountService; +import java.util.Random; /** * (SysUser)表服务实现类 @@ -62,6 +59,15 @@ public class SysUserServiceImpl implements SysUserService { @Resource private ContentPurchaseMapper contentPurchaseMapper; + + @Resource + private CmsContentLikeMapper cmsContentLikeMapper; + + @Resource + private CmsContentViewMapper cmsContentViewMapper; + + @Resource + private CmsContentMapper cmsContentMapper; /** * 分页查询数据 @@ -119,9 +125,7 @@ public class SysUserServiceImpl implements SysUserService { @Override public SysUser insert(SysUser sysUser) { SysUser byUsername = getByUsername(sysUser.getUserName()); - if (Objects.nonNull(byUsername)){ - throw new BizException("用户名已存在"); - } + Assert.isNull(byUsername, "用户名已存在"); sysUser.setEnable(Const.USER_STATUS_NORMAL); sysUser.setDeleteFlag(Const.DELETE_FLAG_NO); //写一个salt生成方法 @@ -173,9 +177,7 @@ public class SysUserServiceImpl implements SysUserService { // 校验用户名是否已经存在 if (sysUser.getUserName() != null && !sysUser.getUserName().isEmpty()) { SysUser existingUser = sysUserMapper.getByUsername(sysUser.getUserName()); - if (existingUser != null && !existingUser.getUserId().equals(sysUser.getUserId())) { - throw new BizException("用户名已存在"); - } + Assert.isTrue(existingUser == null || existingUser.getUserId().equals(sysUser.getUserId()), "用户名已存在"); } sysUserMapper.update(sysUser); }else { @@ -196,9 +198,7 @@ public class SysUserServiceImpl implements SysUserService { SysUser sysUser = queryById(userId); //管理员不允许删除 - if (Const.ADMIN_USER_LIST.contains(sysUser.getUserName().toLowerCase())){ - throw new BizException("该用户是管理员,不能删除"); - } + Assert.isFalse(Const.ADMIN_USER_LIST.contains(sysUser.getUserName().toLowerCase()), "该用户是管理员,不能删除"); // 删除用户名缓存 redisTemplate.delete("sysUser:username:" + sysUser.getUserName()); @@ -208,148 +208,332 @@ public class SysUserServiceImpl implements SysUserService { @Override public LoginUserDto login(LoginDto loginDto) { - if (Objects.isNull(loginDto.getUsername())){ - throw new BizException("用户名不能位空"); - } - if (Objects.isNull(loginDto.getPassword())){ - throw new BizException("密码不能位空"); - } + // 参数验证 + validateLoginParams(loginDto); - // 验证码开关逻辑:只有启用时才验证验证码 + // 验证码验证 + validateCaptcha(loginDto); + + // 查询用户 + SysUser sysUser = getUserByUsername(loginDto.getUsername()); + + // 密码验证 + validatePassword(loginDto.getPassword(), sysUser); + + // 生成token + String token = generateToken(sysUser.getUserId()); + + // 构建登录用户信息 + LoginUser loginUser = buildLoginUser(sysUser, token); + + // 存储登录信息到Redis + saveLoginUserToRedis(token, loginUser); + + // 构建返回对象 + LoginUserDto sysUserDto = buildLoginUserDto(loginUser); + + log.info("用户:{}登录成功,token:{}", loginDto.getUsername(), token); + + return sysUserDto; + } + + /** + * 参数验证 + * + * @param loginDto 登录请求参数 + */ + private void validateLoginParams(LoginDto loginDto) { + Assert.notNull(loginDto.getUsername(), "用户名不能位空"); + Assert.notNull(loginDto.getPassword(), "密码不能位空"); + } + + /** + * 验证码验证 + * + * @param loginDto 登录请求参数 + */ + private void validateCaptcha(LoginDto loginDto) { if (captchaConfig.isEnabled()) { - if (Objects.isNull(loginDto.getCaptchaId()) || Objects.isNull(loginDto.getCaptchaValue())) { - throw new BizException("验证码不能为空"); - } + Assert.notNull(loginDto.getCaptchaId(), "验证码不能为空"); + Assert.notNull(loginDto.getCaptchaValue(), "验证码不能为空"); - // 验证验证码 String captchaKey = "captcha:" + loginDto.getCaptchaId(); String captchaText = redisTemplate.opsForValue().get(captchaKey); - if (captchaText == null) { - throw new BizException("验证码已过期"); - } - if (!captchaText.equals(loginDto.getCaptchaValue().toLowerCase())) { - throw new BizException("验证码错误"); - } + Assert.notNull(captchaText, "验证码已过期"); + Assert.equals(captchaText, loginDto.getCaptchaValue().toLowerCase(), "验证码错误"); - // 验证成功后,删除验证码 redisTemplate.delete(captchaKey); } - - SysUser sysUser = sysUserMapper.getByUsername(loginDto.getUsername()); - if (Objects.isNull(sysUser)) { - throw new BizException("用户名不存在"); - } - + } + + /** + * 根据用户名查询用户 + * + * @param username 用户名 + * @return 用户对象 + */ + private SysUser getUserByUsername(String username) { + SysUser sysUser = sysUserMapper.getByUsername(username); + Assert.notNull(sysUser, "用户名不存在"); + return sysUser; + } + + /** + * 密码验证 + * + * @param password 密码 + * @param sysUser 用户对象 + */ + private void validatePassword(String password, SysUser sysUser) { try { // 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证 - // 这样密码在传输过程中不是明文,提高安全性 - String encryptedPwd = MD5Util.doubleEncrypt(loginDto.getPassword(), sysUser.getSalt()); - if (encryptedPwd.equals(sysUser.getPwd())) { - // 使用Sa-Token登录,生成token - cn.dev33.satoken.stp.StpUtil.login(sysUser.getUserId()); - // 获取生成的token - String token = cn.dev33.satoken.stp.StpUtil.getTokenValue(); - - // 创建LoginUser对象 - LoginUser loginUser = new LoginUser(); - - // 设置用户基本信息 - sysUser.setPwd(null); - loginUser.setUserInfo(sysUser); - - // 查询用户角色列表 - List roles = new java.util.ArrayList<>(); - try { - // 创建查询条件,查询用户的角色关联记录 - SysUserRole userRole = new SysUserRole(); - userRole.setUserId(sysUser.getUserId()); - List userRoles = sysUserRoleMapper.queryAll(userRole); - - // 遍历角色关联记录,获取角色名称 - // 注意:这里需要根据角色ID查询角色名称,当前系统中没有直接的方法,所以暂时使用角色ID作为角色名称 - // 实际项目中应该查询sys_role表获取角色名称 - for (SysUserRole ur : userRoles) { - roles.add("ROLE_" + ur.getRoleId()); - } - } catch (Exception e) { - log.error("查询用户角色列表失败:{}", e.getMessage()); - roles = java.util.Collections.emptyList(); - } - loginUser.setRoles(roles); - - // 查询用户已购买的内容ID列表 - List purchasedContentIds = new java.util.ArrayList<>(); - try { - // 创建查询条件,查询用户的购买记录 - ContentPurchaseDto purchaseDto = new ContentPurchaseDto(); - purchaseDto.setUserId(sysUser.getUserId()); - List purchases = contentPurchaseMapper.getList(purchaseDto); - - // 遍历购买记录,获取内容ID - for (ContentPurchase purchase : purchases) { - purchasedContentIds.add(purchase.getContentId()); - } - } catch (Exception e) { - log.error("查询用户已购买内容列表失败:{}", e.getMessage()); - purchasedContentIds = java.util.Collections.emptyList(); - } - loginUser.setPurchasedContentIds(purchasedContentIds); - - // 查询用户账户信息 - Account account = accountMapper.queryByUserId(sysUser.getUserId()); - loginUser.setAccount(account); - - // 查询用户积分信息 - PointsAccount pointsAccount = pointsAccountMapper.queryByUserId(sysUser.getUserId()); - loginUser.setPointsAccount(pointsAccount); - - // 设置token - loginUser.setToken(token); - - // 将LoginUser对象存储到Redis缓存中,使用token作为key - redisTemplate.opsForValue().set("loginUser:" + token, cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS); - - // 创建LoginUserDto返回对象 - LoginUserDto sysUserDto = new LoginUserDto(); - BeanUtil.copyProperties(sysUser, sysUserDto); - sysUserDto.setToken(token); - - log.info("用户:{}登录成功,token:{}",loginDto.getUsername(),token); - - // 密码匹配成功 - return sysUserDto; - }else { - throw new BizException("密码不正确"); - } - } catch (RuntimeException e) { - throw new RuntimeException(e); + String encryptedPwd = MD5Util.doubleEncrypt(password, sysUser.getSalt()); + Assert.equals(encryptedPwd, sysUser.getPwd(), "密码不正确"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } + + /** + * 生成token + * + * @param userId 用户ID + * @return token + */ + private String generateToken(Long userId) { + cn.dev33.satoken.stp.StpUtil.login(userId); + return cn.dev33.satoken.stp.StpUtil.getTokenValue(); + } + + /** + * 构建登录用户信息 + * + * @param sysUser 用户对象 + * @param token token + * @return 登录用户信息 + */ + private LoginUser buildLoginUser(SysUser sysUser, String token) { + LoginUser loginUser = new LoginUser(); + + // 设置用户基本信息 + sysUser.setPwd(null); + loginUser.setUserInfo(sysUser); + + // 查询并设置用户角色列表 + loginUser.setRoles(queryUserRoles(sysUser.getUserId())); + + // 查询并设置用户已购买的内容ID列表 + loginUser.setPurchasedContentIds(queryPurchasedContentIds(sysUser.getUserId())); + + // 查询并设置用户账户信息 + loginUser.setAccount(queryUserAccount(sysUser.getUserId())); + + // 查询并设置用户积分信息 + loginUser.setPointsAccount(queryUserPointsAccount(sysUser.getUserId())); + + // 查询并设置用户最近点赞记录 + loginUser.setFavorites(queryRecentFavorites(sysUser.getUserId())); + + // 查询并设置用户最近查看记录 + loginUser.setHistory(queryRecentViews(sysUser.getUserId())); + + // 查询并设置用户最近创建记录 + loginUser.setCreate(queryRecentCreatedContent(sysUser.getUserId())); + + // 查询并设置用户最近购买记录 + loginUser.setHas(queryRecentPurchases(sysUser.getUserId())); + + // 设置token + loginUser.setToken(token); + + return loginUser; + } + + /** + * 查询用户角色列表 + * + * @param userId 用户ID + * @return 角色列表 + */ + private List queryUserRoles(Long userId) { + List roles = new java.util.ArrayList<>(); + try { + SysUserRole userRole = new SysUserRole(); + userRole.setUserId(userId); + List userRoles = sysUserRoleMapper.queryAll(userRole); + + for (SysUserRole ur : userRoles) { + roles.add("ROLE_" + ur.getRoleId()); + } + } catch (Exception e) { + log.error("查询用户角色列表失败:{}", e.getMessage()); + roles = java.util.Collections.emptyList(); + } + return roles; + } + + /** + * 查询用户已购买的内容ID列表 + * + * @param userId 用户ID + * @return 内容ID列表 + */ + private List queryPurchasedContentIds(Long userId) { + List purchasedContentIds = new java.util.ArrayList<>(); + try { + ContentPurchaseDto purchaseDto = new ContentPurchaseDto(); + purchaseDto.setUserId(userId); + List purchases = contentPurchaseMapper.getList(purchaseDto); + + for (ContentPurchase purchase : purchases) { + purchasedContentIds.add(purchase.getContentId()); + } + } catch (Exception e) { + log.error("查询用户已购买内容列表失败:{}", e.getMessage()); + purchasedContentIds = java.util.Collections.emptyList(); + } + return purchasedContentIds; + } + + /** + * 查询用户账户信息 + * + * @param userId 用户ID + * @return 账户信息 + */ + private Account queryUserAccount(Long userId) { + return accountMapper.queryByUserId(userId); + } + + /** + * 查询用户积分信息 + * + * @param userId 用户ID + * @return 积分信息 + */ + private PointsAccount queryUserPointsAccount(Long userId) { + return pointsAccountMapper.queryByUserId(userId); + } + + /** + * 查询用户最近点赞记录 + * + * @param userId 用户ID + * @return 最近点赞记录 + */ + private List queryRecentFavorites(Long userId) { + try { + return cmsContentLikeMapper.queryRecentLikesByUserId(userId, 20); + } catch (Exception e) { + log.error("查询用户最近点赞记录失败:{}", e.getMessage()); + return java.util.Collections.emptyList(); + } + } + + /** + * 查询用户最近查看记录 + * + * @param userId 用户ID + * @return 最近查看记录 + */ + private List queryRecentViews(Long userId) { + try { + return cmsContentViewMapper.queryRecentViewsByUserId(userId, 20); + } catch (Exception e) { + log.error("查询用户最近查看记录失败:{}", e.getMessage()); + return java.util.Collections.emptyList(); + } + } + + /** + * 查询用户最近创建记录 + * + * @param userId 用户ID + * @return 最近创建记录 + */ + private List queryRecentCreatedContent(Long userId) { + try { + return cmsContentMapper.queryRecentCreatedByUserId(userId, 20); + } catch (Exception e) { + log.error("查询用户最近创建记录失败:{}", e.getMessage()); + return java.util.Collections.emptyList(); + } + } + + /** + * 查询用户最近购买记录 + * + * @param userId 用户ID + * @return 最近购买记录 + */ + private List queryRecentPurchases(Long userId) { + List has = new java.util.ArrayList<>(); + try { + ContentPurchaseDto purchaseDto = new ContentPurchaseDto(); + purchaseDto.setUserId(userId); + List purchases = contentPurchaseMapper.getList(purchaseDto); + if (purchases != null && !purchases.isEmpty()) { + java.util.LinkedHashSet contentIdSet = new java.util.LinkedHashSet<>(); + for (ContentPurchase purchase : purchases) { + if (contentIdSet.size() < 20) { + contentIdSet.add(purchase.getContentId()); + } else { + break; + } + } + has.addAll(contentIdSet); + } + } catch (Exception e) { + log.error("查询用户最近购买记录失败:{}", e.getMessage()); + has = java.util.Collections.emptyList(); + } + return has; + } + + /** + * 存储登录信息到Redis + * + * @param token token + * @param loginUser 登录用户信息 + */ + private void saveLoginUserToRedis(String token, LoginUser loginUser) { + redisTemplate.opsForValue().set( + "loginUser:" + token, + cn.hutool.json.JSONUtil.toJsonStr(loginUser), + 3600, + java.util.concurrent.TimeUnit.SECONDS + ); + } + + /** + * 构建登录用户返回对象 + * + * @param loginUser 登录用户信息 + * @return 登录用户返回对象 + */ + private LoginUserDto buildLoginUserDto(LoginUser loginUser) { + LoginUserDto sysUserDto = new LoginUserDto(); + BeanUtil.copyProperties(loginUser.getUserInfo(), sysUserDto); + sysUserDto.setToken(loginUser.getToken()); + sysUserDto.setFavorites(loginUser.getFavorites()); + sysUserDto.setHistory(loginUser.getHistory()); + sysUserDto.setCreate(loginUser.getCreate()); + sysUserDto.setHas(loginUser.getHas()); + return sysUserDto; + } @Override public boolean resetPassword(ResetPasswordDto resetPasswordDto) { - if (Objects.isNull(resetPasswordDto.getUserName())) { - throw new BizException("用户名不能位空"); - } - if (Objects.isNull(resetPasswordDto.getOldPassword())) { - throw new BizException("旧密码不能位空"); - } - if (Objects.isNull(resetPasswordDto.getNewPassword())) { - throw new BizException("新密码不能位空"); - } + Assert.notNull(resetPasswordDto.getUserName(), "用户名不能位空"); + Assert.notNull(resetPasswordDto.getOldPassword(), "旧密码不能位空"); + Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空"); SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName()); - if (Objects.isNull(sysUser)) { - throw new BizException("用户名不存在"); - } + Assert.notNull(sysUser, "用户名不存在"); try { String oldMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getOldPassword() + sysUser.getSalt()); - if (!oldMd5Pwd.equals(sysUser.getPwd())) { - throw new BizException("旧密码不正确"); - } + Assert.equals(oldMd5Pwd, sysUser.getPwd(), "旧密码不正确"); String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt()); sysUser.setPwd(newMd5Pwd); @@ -366,17 +550,11 @@ public class SysUserServiceImpl implements SysUserService { @Override public boolean resetPasswordByAdmin(ResetPasswordDto resetPasswordDto) { - if (Objects.isNull(resetPasswordDto.getUserName())) { - throw new BizException("用户名不能位空"); - } - if (Objects.isNull(resetPasswordDto.getNewPassword())) { - throw new BizException("新密码不能位空"); - } + Assert.notNull(resetPasswordDto.getUserName(), "用户名不能位空"); + Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空"); SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName()); - if (Objects.isNull(sysUser)) { - throw new BizException("用户名不存在"); - } + Assert.notNull(sysUser, "用户名不存在"); try { String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt()); @@ -409,15 +587,11 @@ public class SysUserServiceImpl implements SysUserService { @Override public boolean resetPwd(Long userId, String newPassword, String operator) { // 检查操作人是否为管理员 - if (!Const.ADMIN_USER_LIST.contains(operator.toLowerCase())) { - throw new BizException("只有管理员才能执行此操作"); - } + Assert.isTrue(Const.ADMIN_USER_LIST.contains(operator.toLowerCase()), "只有管理员才能执行此操作"); // 查询用户是否存在 SysUser sysUser = sysUserMapper.queryById(userId); - if (sysUser == null) { - throw new BizException("用户不存在"); - } + Assert.notNull(sysUser, "用户不存在"); try { // 生成新的加密密码 @@ -435,4 +609,208 @@ public class SysUserServiceImpl implements SysUserService { throw new RuntimeException(e); } } + + /** + * 发送手机验证码 + * + * @param phone 手机号 + * @return 是否发送成功 + */ + @Override + public boolean sendSmsCode(String phone) { + try { + // 参数校验 + Assert.notBlank(phone, "手机号不能为空"); + + // 生成6位随机验证码 + String code = generateSmsCode(); + + // 存储验证码到Redis,有效期5分钟 + redisTemplate.opsForValue().set("sms_code:" + phone, code, 300, java.util.concurrent.TimeUnit.SECONDS); + + // 获取默认的短信发送器 + SmsBlend sms = SmsFactory.getSmsBlend(); + + Assert.notNull(sms, "短信服务未正确配置"); + log.info("获取短信发送器成功"); + + // 发送验证码 - 使用模板方式发送,避免直接文本发送可能导致的参数缺失 + java.util.Map params = new java.util.HashMap<>(); + params.put("code", code); // 参数名需要与阿里云模板中的变量名匹配 + + // 发送短信,使用配置文件中定义的模板 + // 根据API文档和用户需求,使用单个手机号发送的方法:sendMessage(String phone, Map params) + // 系统会自动从配置中读取template-id + sms.sendMessage(phone, code); + + log.info("向手机号 {} 发送验证码:{}", phone, code); + return true; + } catch (Exception e) { + log.error("向手机号 {} 发送验证码失败:{}", phone, e.getMessage()); + log.error("详细错误信息:", e); + + // 如果短信服务不可用,可以记录错误并返回false而不是抛出异常 + // 或者根据业务需求决定是否继续 + Assert.isTrue(false, "发送验证码失败:" + e.getMessage()); + } + return false; + } + + /** + * 生成6位随机验证码 + * + * @return 6位随机验证码 + */ + private String generateSmsCode() { + Random random = new Random(); + return String.format("%06d", random.nextInt(1000000)); + } + + /** + * 手机号登录 + * + * @param phoneLoginDto 手机号登录信息 + * @return 登录结果 + */ + @Override + public LoginUserDto phoneLogin(PhoneLoginDto phoneLoginDto) { + String phone = phoneLoginDto.getPhone(); + String code = phoneLoginDto.getCode(); + + Assert.notBlank(phone, "手机号不能为空"); + Assert.notBlank(code, "验证码不能为空"); + + // 验证验证码 + String cachedCode = redisTemplate.opsForValue().get("sms_code:" + phone); + Assert.notNull(cachedCode, "验证码已过期"); + Assert.equals(cachedCode, code, "验证码错误"); + + // 验证成功后,删除验证码 + redisTemplate.delete("sms_code:" + phone); + + // 查询用户是否存在 + SysUser sysUser = sysUserMapper.getByTel(phone); + + // 如果用户不存在,自动创建账号 + if (sysUser == null) { + sysUser = createUserByPhone(phone); + } + + // 使用Sa-Token登录,生成token + cn.dev33.satoken.stp.StpUtil.login(sysUser.getUserId()); + // 获取生成的token + String token = cn.dev33.satoken.stp.StpUtil.getTokenValue(); + + // 创建LoginUser对象 + LoginUser loginUser = new LoginUser(); + + // 设置用户基本信息 + sysUser.setPwd(null); + loginUser.setUserInfo(sysUser); + + // 查询用户角色列表 + List roles = new java.util.ArrayList<>(); + try { + // 创建查询条件,查询用户的角色关联记录 + SysUserRole userRole = new SysUserRole(); + userRole.setUserId(sysUser.getUserId()); + List userRoles = sysUserRoleMapper.queryAll(userRole); + + // 遍历角色关联记录,获取角色名称 + for (SysUserRole ur : userRoles) { + roles.add("ROLE_" + ur.getRoleId()); + } + } catch (Exception e) { + log.error("查询用户角色列表失败:{}", e.getMessage()); + roles = java.util.Collections.emptyList(); + } + loginUser.setRoles(roles); + + // 查询用户已购买的内容ID列表 + List purchasedContentIds = new java.util.ArrayList<>(); + try { + // 创建查询条件,查询用户的购买记录 + ContentPurchaseDto purchaseDto = new ContentPurchaseDto(); + purchaseDto.setUserId(sysUser.getUserId()); + List purchases = contentPurchaseMapper.getList(purchaseDto); + + // 遍历购买记录,获取内容ID + for (ContentPurchase purchase : purchases) { + purchasedContentIds.add(purchase.getContentId()); + } + } catch (Exception e) { + log.error("查询用户已购买内容列表失败:{}", e.getMessage()); + purchasedContentIds = java.util.Collections.emptyList(); + } + loginUser.setPurchasedContentIds(purchasedContentIds); + + // 查询用户账户信息 + Account account = accountMapper.queryByUserId(sysUser.getUserId()); + loginUser.setAccount(account); + + // 查询用户积分信息 + PointsAccount pointsAccount = pointsAccountMapper.queryByUserId(sysUser.getUserId()); + loginUser.setPointsAccount(pointsAccount); + + // 查询并设置用户最近点赞记录 + loginUser.setFavorites(queryRecentFavorites(sysUser.getUserId())); + + // 查询并设置用户最近查看记录 + loginUser.setHistory(queryRecentViews(sysUser.getUserId())); + + // 查询并设置用户最近创建记录 + loginUser.setCreate(queryRecentCreatedContent(sysUser.getUserId())); + + // 查询并设置用户最近购买记录 + loginUser.setHas(queryRecentPurchases(sysUser.getUserId())); + + // 设置token + loginUser.setToken(token); + + // 将LoginUser对象存储到Redis缓存中,使用token作为key + redisTemplate.opsForValue().set("loginUser:" + token, cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS); + + // 创建LoginUserDto返回对象 + LoginUserDto sysUserDto = buildLoginUserDto(loginUser); + + log.info("用户:{} 手机号登录成功,token:{}", phone, token); + + return sysUserDto; + } + + /** + * 根据手机号创建用户 + * + * @param phone 手机号 + * @return 创建的用户对象 + */ + private SysUser createUserByPhone(String phone) { + SysUser sysUser = new SysUser(); + + // 设置用户名(使用手机号作为用户名) + sysUser.setUserName(phone); + sysUser.setTel(phone); + + // 生成随机密码 + String randomPassword = generateRandomPassword(); + sysUser.setPwd(randomPassword); + + // 调用insert方法创建用户 + return insert(sysUser); + } + + /** + * 生成随机密码 + * + * @return 随机密码 + */ + private String generateRandomPassword() { + String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuilder sb = new StringBuilder(12); + for (int i = 0; i < 12; i++) { + sb.append(characters.charAt(random.nextInt(characters.length()))); + } + return sb.toString(); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6deed3f..0e8192f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -17,6 +17,11 @@ spring: mode: HTML prefix: classpath:/templates/ suffix: .html + http: + encoding: + charset: UTF-8 + enabled: true + force: true servlet: multipart: max-request-size: 20MB @@ -44,3 +49,23 @@ pagehelper: supportMethodsArguments: true params: count=countSql +# SMS4J 短信发送配置 +sms: + # 从 YAML 读取配置 + config-type: YAML + http-log: true + is-print: false + blends: + alibaba: + # 短信厂商 + supplier: alibaba + requestUrl: dysmsapi.aliyuncs.com + access-key-id: LTAI5t8rsaBDrno4xd4F6EwE + access-key-secret: QNmjxatrgzBYukAKr8BZ4r7gd37SHw + signature: 武汉可学智能科技 + # 阿里云短信模板ID,需要替换为实际申请的模板ID + template-id: SMS_493720484 # 实际使用时请替换为真实模板ID + # 短信模板变量名,对应验证码的变量 + template-param-name: code + template-cache: true + diff --git a/src/main/resources/mapper/CmsContentLikeMapper.xml b/src/main/resources/mapper/CmsContentLikeMapper.xml new file mode 100644 index 0000000..eaac1b2 --- /dev/null +++ b/src/main/resources/mapper/CmsContentLikeMapper.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + INSERT INTO cms_content_like ( + user_id, user_name, content_id, content_title, like_time, delete_flag + ) VALUES ( + #{userId}, #{userName}, #{contentId}, #{contentTitle}, NOW(), #{deleteFlag, jdbcType=INTEGER} + ) + + + + UPDATE cms_content_like + + user_id = #{userId}, + user_name = #{userName}, + content_id = #{contentId}, + content_title = #{contentTitle}, + like_time = #{likeTime}, + delete_flag = #{deleteFlag}, + + WHERE like_id = #{likeId} + + + + DELETE FROM cms_content_like WHERE like_id = #{likeId} + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/CmsContentMapper.xml b/src/main/resources/mapper/CmsContentMapper.xml index dba6420..eafb90b 100644 --- a/src/main/resources/mapper/CmsContentMapper.xml +++ b/src/main/resources/mapper/CmsContentMapper.xml @@ -269,5 +269,13 @@ set view_count = view_count + 1 where content_id = #{contentId} + + + \ No newline at end of file diff --git a/src/main/resources/mapper/CmsContentViewMapper.xml b/src/main/resources/mapper/CmsContentViewMapper.xml new file mode 100644 index 0000000..dfe301a --- /dev/null +++ b/src/main/resources/mapper/CmsContentViewMapper.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + INSERT INTO cms_content_view ( + user_id, user_name, content_id, content_title, view_time, delete_flag + ) VALUES ( + #{userId}, #{userName}, #{contentId}, #{contentTitle}, NOW(), #{deleteFlag, jdbcType=INTEGER} + ) + + + + UPDATE cms_content_view + + user_id = #{userId}, + user_name = #{userName}, + content_id = #{contentId}, + content_title = #{contentTitle}, + view_time = #{viewTime}, + delete_flag = #{deleteFlag}, + + WHERE view_id = #{viewId} + + + + DELETE FROM cms_content_view WHERE view_id = #{viewId} + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/ContentPurchaseMapper.xml b/src/main/resources/mapper/ContentPurchaseMapper.xml index caa1021..a16ca19 100644 --- a/src/main/resources/mapper/ContentPurchaseMapper.xml +++ b/src/main/resources/mapper/ContentPurchaseMapper.xml @@ -107,6 +107,7 @@ and delete_flag = #{deleteFlag} + order by purchase_time desc diff --git a/src/main/resources/mapper/SysUserMapper.xml b/src/main/resources/mapper/SysUserMapper.xml index 331dbd1..ed68249 100644 --- a/src/main/resources/mapper/SysUserMapper.xml +++ b/src/main/resources/mapper/SysUserMapper.xml @@ -195,5 +195,14 @@ and delete_flag = 0 limit 1 + +