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
+
+