feat: add MyBatis CRUD and database integration
This commit is contained in:
1 parent
4222b7124c
commit
8a97a0b054
67 files changed
+2597
-102
No files matched your search
@@ -0,0 +1,72 @@
|
||||
# Hera Backend Agent Guide
|
||||
|
||||
本文件适用于本仓库全部目录。Codex 修改代码前必须先阅读本文件,并遵循阿里巴巴《Java 开发手册》的命名、分层、异常处理和数据库规范。
|
||||
|
||||
## 技术基线
|
||||
|
||||
- Java 8,Spring Boot 2.7.x,MyBatis 2.3.x,MySQL 8.x。
|
||||
- 构建工具为 Maven,编码统一使用 UTF-8。
|
||||
- 不得擅自升级 Java、Spring Boot 或数据库大版本。
|
||||
- 优先复用现有包结构、公共响应、异常和鉴权组件,避免引入功能重复的依赖。
|
||||
|
||||
## 修改流程
|
||||
|
||||
1. 修改前先检查 `git status`,保留用户已有的未提交改动。
|
||||
2. 阅读需求涉及的 Controller、Service、Mapper、实体和 SQL,不凭字段名猜测数据库结构。
|
||||
3. 数据库结构以 `src/main/resources/schema.sql` 和用户提供的最新 SQL 为准。
|
||||
4. 改动保持最小范围,不做与需求无关的格式化或重构。
|
||||
5. 完成后至少执行编译、`git diff --check` 和受影响功能验证。
|
||||
|
||||
## Java 编码规范
|
||||
|
||||
- 类名使用 UpperCamelCase,方法、参数和变量使用 lowerCamelCase,常量使用 UPPER_SNAKE_CASE。
|
||||
- Mapper 接口统一以 `Mapper` 结尾,Service 和 Controller 分别以 `Service`、`Controller` 结尾。
|
||||
- 包名全部小写;按 `controller/service/mapper/entity` 职责分层,禁止跨层绕过 Service 直接访问 Mapper。
|
||||
- 方法职责保持单一。复杂条件先拆分为有业务含义的方法,避免深层嵌套。
|
||||
- 禁止无含义的魔法值;有业务含义的状态值应提取为常量或枚举。
|
||||
- 字符串比较使用常量调用 `equals`,包装类型比较使用 `equals`,不得依赖 `==`。
|
||||
- 返回集合时优先返回空集合而不是 `null`。
|
||||
- 捕获异常后必须处理、转换或记录,禁止空 `catch`;不得把数据库或系统内部异常原文直接返回前端。
|
||||
- `ThreadLocal` 必须在请求结束时执行 `remove()`。
|
||||
- 日期时间使用 `java.time` API,不新增 `Date`/`Calendar` 业务代码。
|
||||
- 不写无意义注释;实体类、实体字段和公共 API 必须使用 `/** ... */` Javadoc 说明业务含义。
|
||||
|
||||
## Spring 与接口规范
|
||||
|
||||
- Controller 只负责参数接收、鉴权上下文和响应包装,业务逻辑放在 Service。
|
||||
- 写操作需要事务时在 Service 层使用 `@Transactional`。
|
||||
- 接口统一返回 `ApiResponse<T>`,业务错误统一抛出 `BusinessException`。
|
||||
- 新增 `/api/**` 接口默认需要 JWT;公开接口必须显式加入白名单并说明原因。
|
||||
- 请求参数必须设置合理的长度、范围和分页上限,不信任客户端传入的用户 ID 或状态值。
|
||||
- 密码、密钥、身份证信息等敏感字段不得出现在日志或接口响应中。
|
||||
|
||||
## MyBatis 规范
|
||||
|
||||
- Mapper 接口只声明方法并保留 `@Mapper`;业务 SQL 统一写在 `src/main/resources/mapper/*.xml`。
|
||||
- XML 文件名与 Mapper 接口名一致,`namespace` 必须是 Mapper 接口的完整类名。
|
||||
- 多参数方法必须使用 `@Param` 明确命名。
|
||||
- 查询必须显式列出字段,禁止使用 `SELECT *`。
|
||||
- 数据库字段使用 snake_case,Java 属性使用 lowerCamelCase;依赖 MyBatis 驼峰映射或显式 `resultMap`。
|
||||
- 新增记录需要回填主键时使用 `useGeneratedKeys="true" keyProperty="id"`。
|
||||
- 更新和删除必须带明确的主键或业务条件;禁止无条件全表更新、删除。
|
||||
- 分页参数必须在 Service 层限制范围;当前单页最大值为 100。
|
||||
- 唯一约束和外键冲突转换为可理解的业务响应,不向客户端暴露 SQL。
|
||||
|
||||
## 数据库规范
|
||||
|
||||
- 表名、字段名使用小写 snake_case;主键统一命名为 `id`。
|
||||
- 表和重要字段必须有业务注释;时间字段统一使用 `created_at`、`updated_at`。
|
||||
- 线上可重复执行的初始化脚本使用 `CREATE TABLE IF NOT EXISTS`,禁止在应用启动脚本中使用 `DROP TABLE`。
|
||||
- 修改表结构时考虑已有数据兼容性;不得通过删除重建表来完成普通字段变更。
|
||||
- 账号密码只保存安全哈希;真实姓名、身份证号等敏感信息按字段约定加密或哈希后存储。
|
||||
|
||||
## 验证命令
|
||||
|
||||
本机无全局 Maven 时,使用 IntelliJ 自带 Maven 和已安装的 JDK 8:
|
||||
|
||||
```bash
|
||||
env JAVA_HOME='/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home' \
|
||||
'/Applications/IntelliJ IDEA.app/Contents/plugins/maven/lib/maven3/bin/mvn' test
|
||||
```
|
||||
|
||||
涉及数据库 Mapper 时,还应在可用的测试数据库上至少验证对应的查询和写入链路;写入的临时数据必须在验证结束后清理。
|
||||
@@ -11,6 +11,9 @@ Spring Boot 后端,提供微信小程序登录、手机号验证码绑定、
|
||||
```bash
|
||||
export WECHAT_APP_ID=wxa76a2ef9e8ba612e
|
||||
export WECHAT_APP_SECRET=266bb34a7186a4b835186141516efb78
|
||||
export DB_URL='jdbc:mysql://101.43.139.56:3306/hera?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true'
|
||||
export DB_USERNAME=hera_user
|
||||
export DB_PASSWORD='请通过部署环境配置'
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
@@ -19,6 +22,21 @@ mvn spring-boot:run
|
||||
## 开发说明
|
||||
|
||||
- 当前已默认关闭微信 mock,并接入微信 `jscode2session`。
|
||||
- 数据持久层使用 MyBatis Mapper XML,首次启动会执行 `schema.sql` 创建缺失的数据表。
|
||||
- 数据库连接默认指向 `101.43.139.56:3306/hera`,生产部署建议通过 `DB_URL`、`DB_USERNAME`、`DB_PASSWORD` 覆盖。
|
||||
- `WECHAT_APP_SECRET` 请优先使用服务器环境变量配置,不建议提交到公开仓库。
|
||||
- `/api/auth/sms` 开发阶段返回 `mockCode`,接入短信厂商后移除返回值。
|
||||
- 正式上线前需要替换 `hera.jwt-secret`、短信服务实现和协议正文。
|
||||
|
||||
## 数据表 CRUD
|
||||
|
||||
以下接口均需要携带 `Authorization: Bearer <token>`,并支持统一操作:
|
||||
|
||||
- `GET /api/data/{resource}/{id}`:按主键查询
|
||||
- `GET /api/data/{resource}?offset=0&limit=20`:分页列表,`limit` 最大为 100
|
||||
- `POST /api/data/{resource}`:新增
|
||||
- `PUT /api/data/{resource}/{id}`:更新
|
||||
- `DELETE /api/data/{resource}/{id}`:删除
|
||||
|
||||
`resource` 可取 `nfc-chips`、`user-accounts`、`user-profiles`、`virtual-items`、
|
||||
`user-virtual-items`、`offerings`、`transfers`。
|
||||
@@ -27,12 +27,13 @@
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.hongzhen.hera.auth.AuthDtos.WxLoginResponse;
|
||||
import com.hongzhen.hera.common.BusinessException;
|
||||
import com.hongzhen.hera.security.JwtService;
|
||||
import com.hongzhen.hera.user.User;
|
||||
import com.hongzhen.hera.user.UserRepository;
|
||||
import com.hongzhen.hera.user.UserMapper;
|
||||
import com.hongzhen.hera.user.UserService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
@@ -17,23 +17,23 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class AuthService {
|
||||
|
||||
private final WechatClient wechatClient;
|
||||
private final LoginSessionRepository loginSessionRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final LoginSessionMapper loginSessionMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final SmsService smsService;
|
||||
private final JwtService jwtService;
|
||||
private final UserService userService;
|
||||
|
||||
public AuthService(
|
||||
WechatClient wechatClient,
|
||||
LoginSessionRepository loginSessionRepository,
|
||||
UserRepository userRepository,
|
||||
LoginSessionMapper loginSessionMapper,
|
||||
UserMapper userMapper,
|
||||
SmsService smsService,
|
||||
JwtService jwtService,
|
||||
UserService userService
|
||||
) {
|
||||
this.wechatClient = wechatClient;
|
||||
this.loginSessionRepository = loginSessionRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.loginSessionMapper = loginSessionMapper;
|
||||
this.userMapper = userMapper;
|
||||
this.smsService = smsService;
|
||||
this.jwtService = jwtService;
|
||||
this.userService = userService;
|
||||
@@ -48,25 +48,31 @@ public class AuthService {
|
||||
loginSession.setOpenId(wxSession.getOpenId());
|
||||
loginSession.setUnionId(wxSession.getUnionId());
|
||||
loginSession.setExpiresAt(LocalDateTime.now().plusMinutes(15));
|
||||
loginSessionRepository.save(loginSession);
|
||||
boolean phoneBound = userRepository.findByOpenId(wxSession.getOpenId()).map(user -> user.getPhone() != null).orElse(false);
|
||||
loginSessionMapper.insert(loginSession);
|
||||
boolean phoneBound = userMapper.findByOpenId(wxSession.getOpenId())
|
||||
.map(user -> user.getPhone() != null)
|
||||
.orElse(false);
|
||||
return new WxLoginResponse(sessionKey, phoneBound);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LoginResponse bindPhone(BindPhoneRequest request) {
|
||||
smsService.verify(request.getPhone(), request.getCaptcha());
|
||||
LoginSession session = loginSessionRepository.findBySessionKey(request.getSessionKey())
|
||||
LoginSession session = loginSessionMapper.findBySessionKey(request.getSessionKey())
|
||||
.orElseThrow(() -> new BusinessException("登录会话不存在"));
|
||||
if (session.getExpiresAt().isBefore(LocalDateTime.now())) {
|
||||
throw new BusinessException("登录会话已过期");
|
||||
}
|
||||
User user = userRepository.findByOpenId(session.getOpenId()).orElseGet(User::new);
|
||||
User user = userMapper.findByOpenId(session.getOpenId()).orElseGet(User::new);
|
||||
user.setOpenId(session.getOpenId());
|
||||
user.setUnionId(session.getUnionId());
|
||||
user.setPhone(request.getPhone());
|
||||
user.touch();
|
||||
userRepository.save(user);
|
||||
if (user.getId() == null) {
|
||||
userMapper.insert(user);
|
||||
} else {
|
||||
userMapper.update(user);
|
||||
}
|
||||
return new LoginResponse(jwtService.issue(user.getId()), userService.toProfile(user));
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,45 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
/**
|
||||
* 微信登录临时会话实体。
|
||||
*/
|
||||
public class LoginSession {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/**
|
||||
* 主键。
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
/**
|
||||
* 业务登录会话键。
|
||||
*/
|
||||
private String sessionKey;
|
||||
|
||||
@Column(nullable = false)
|
||||
/**
|
||||
* 微信 OpenID。
|
||||
*/
|
||||
private String openId;
|
||||
|
||||
/**
|
||||
* 微信 UnionID。
|
||||
*/
|
||||
private String unionId;
|
||||
|
||||
/**
|
||||
* 会话过期时间。
|
||||
*/
|
||||
private LocalDateTime expiresAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSessionKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface LoginSessionMapper {
|
||||
|
||||
int insert(LoginSession loginSession);
|
||||
|
||||
Optional<LoginSession> findBySessionKey(String sessionKey);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LoginSessionRepository extends JpaRepository<LoginSession, Long> {
|
||||
|
||||
Optional<LoginSession> findBySessionKey(String sessionKey);
|
||||
}
|
||||
@@ -1,23 +1,45 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
/**
|
||||
* 手机短信验证码实体。
|
||||
*/
|
||||
public class SmsCode {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/**
|
||||
* 主键。
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 接收验证码的手机号。
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 验证码。
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 验证码过期时间。
|
||||
*/
|
||||
private LocalDateTime expiresAt;
|
||||
|
||||
/**
|
||||
* 是否已经使用。
|
||||
*/
|
||||
private boolean used;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface SmsCodeMapper {
|
||||
|
||||
int insert(SmsCode smsCode);
|
||||
|
||||
Optional<SmsCode> findLatestUnused(
|
||||
@Param("phone") String phone,
|
||||
@Param("code") String code
|
||||
);
|
||||
|
||||
int markUsed(Long id);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.hongzhen.hera.auth;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface SmsCodeRepository extends JpaRepository<SmsCode, Long> {
|
||||
|
||||
Optional<SmsCode> findTopByPhoneAndCodeAndUsedFalseOrderByExpiresAtDesc(String phone, String code);
|
||||
}
|
||||
@@ -9,11 +9,11 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class SmsService {
|
||||
|
||||
private final SmsCodeRepository smsCodeRepository;
|
||||
private final SmsCodeMapper smsCodeMapper;
|
||||
private final Random random = new Random();
|
||||
|
||||
public SmsService(SmsCodeRepository smsCodeRepository) {
|
||||
this.smsCodeRepository = smsCodeRepository;
|
||||
public SmsService(SmsCodeMapper smsCodeMapper) {
|
||||
this.smsCodeMapper = smsCodeMapper;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -23,17 +23,18 @@ public class SmsService {
|
||||
smsCode.setPhone(phone);
|
||||
smsCode.setCode(code);
|
||||
smsCode.setExpiresAt(LocalDateTime.now().plusMinutes(5));
|
||||
smsCodeRepository.save(smsCode);
|
||||
smsCodeMapper.insert(smsCode);
|
||||
return code;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void verify(String phone, String code) {
|
||||
SmsCode smsCode = smsCodeRepository.findTopByPhoneAndCodeAndUsedFalseOrderByExpiresAtDesc(phone, code)
|
||||
SmsCode smsCode = smsCodeMapper.findLatestUnused(phone, code)
|
||||
.orElseThrow(() -> new BusinessException("验证码不正确"));
|
||||
if (smsCode.getExpiresAt().isBefore(LocalDateTime.now())) {
|
||||
throw new BusinessException("验证码已过期");
|
||||
}
|
||||
smsCode.setUsed(true);
|
||||
smsCodeMapper.markUsed(smsCode.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.hongzhen.hera.common;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 具有主键和审计时间的实体基类。
|
||||
*/
|
||||
public abstract class BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键。
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 创建时间。
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 最后更新时间。
|
||||
*/
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.hongzhen.hera.common;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
public abstract class CrudController<T extends BaseEntity> {
|
||||
|
||||
private final CrudService<T> service;
|
||||
|
||||
protected CrudController(CrudService<T> service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<T> get(@PathVariable Long id) {
|
||||
return ApiResponse.ok(service.get(id));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<T>> list(
|
||||
@RequestParam(defaultValue = "0") int offset,
|
||||
@RequestParam(defaultValue = "20") int limit
|
||||
) {
|
||||
return ApiResponse.ok(service.list(offset, limit));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<T> create(@RequestBody T entity) {
|
||||
return ApiResponse.ok(service.create(entity));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<T> update(@PathVariable Long id, @RequestBody T entity) {
|
||||
return ApiResponse.ok(service.update(id, entity));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> delete(@PathVariable Long id) {
|
||||
service.delete(id);
|
||||
return ApiResponse.ok(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.hongzhen.hera.common;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CrudMapper<T extends BaseEntity> {
|
||||
|
||||
Optional<T> findById(Long id);
|
||||
|
||||
List<T> findAll(int offset, int limit);
|
||||
|
||||
int insert(T entity);
|
||||
|
||||
int update(T entity);
|
||||
|
||||
int deleteById(Long id);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.hongzhen.hera.common;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
public abstract class CrudService<T extends BaseEntity> {
|
||||
|
||||
private static final int MAX_PAGE_SIZE = 100;
|
||||
|
||||
private final CrudMapper<T> mapper;
|
||||
private final String entityName;
|
||||
|
||||
protected CrudService(CrudMapper<T> mapper, String entityName) {
|
||||
this.mapper = mapper;
|
||||
this.entityName = entityName;
|
||||
}
|
||||
|
||||
public T get(Long id) {
|
||||
return mapper.findById(id).orElseThrow(() -> new BusinessException(entityName + "不存在"));
|
||||
}
|
||||
|
||||
public List<T> list(int offset, int limit) {
|
||||
int safeOffset = Math.max(0, offset);
|
||||
int safeLimit = Math.max(1, Math.min(limit, MAX_PAGE_SIZE));
|
||||
return mapper.findAll(safeOffset, safeLimit);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public T create(T entity) {
|
||||
mapper.insert(entity);
|
||||
return get(entity.getId());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public T update(Long id, T entity) {
|
||||
get(id);
|
||||
entity.setId(id);
|
||||
if (mapper.update(entity) == 0) {
|
||||
throw new BusinessException(entityName + "更新失败");
|
||||
}
|
||||
return get(id);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long id) {
|
||||
if (mapper.deleteById(id) == 0) {
|
||||
throw new BusinessException(entityName + "不存在");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.hongzhen.hera.common;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
@@ -21,4 +22,10 @@ public class GlobalExceptionHandler {
|
||||
public ApiResponse<Void> handleValidation(Exception exception) {
|
||||
return ApiResponse.fail("参数不正确");
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
public ApiResponse<Void> handleDataIntegrity(DataIntegrityViolationException exception) {
|
||||
return ApiResponse.fail("数据重复或关联关系不正确");
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
public class HeraProperties {
|
||||
|
||||
private String jwtSecret;
|
||||
private String uploadDir = "./uploads";
|
||||
private final Wx wx = new Wx();
|
||||
private final Sms sms = new Sms();
|
||||
|
||||
@@ -17,6 +18,14 @@ public class HeraProperties {
|
||||
this.jwtSecret = jwtSecret;
|
||||
}
|
||||
|
||||
public String getUploadDir() {
|
||||
return uploadDir;
|
||||
}
|
||||
|
||||
public void setUploadDir(String uploadDir) {
|
||||
this.uploadDir = uploadDir;
|
||||
}
|
||||
|
||||
public Wx getWx() {
|
||||
return wx;
|
||||
}
|
||||
|
||||
@@ -5,16 +5,21 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(HeraProperties.class)
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final AuthInterceptor authInterceptor;
|
||||
private final HeraProperties properties;
|
||||
|
||||
public WebConfig(AuthInterceptor authInterceptor) {
|
||||
public WebConfig(AuthInterceptor authInterceptor, HeraProperties properties) {
|
||||
this.authInterceptor = authInterceptor;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -26,6 +31,15 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(authInterceptor)
|
||||
.addPathPatterns("/api/**")
|
||||
.excludePathPatterns("/api/auth/**", "/api/content/**", "/h2-console/**");
|
||||
.excludePathPatterns("/api/auth/**", "/api/content/**");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
String uploadLocation = Paths.get(properties.getUploadDir()).toAbsolutePath().normalize().toUri().toString();
|
||||
if (!uploadLocation.endsWith("/")) {
|
||||
uploadLocation += "/";
|
||||
}
|
||||
registry.addResourceHandler("/uploads/**").addResourceLocations(uploadLocation);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import com.hongzhen.hera.daily.DailyDtos.FortuneCard;
|
||||
import com.hongzhen.hera.daily.DailyDtos.LifeTip;
|
||||
import com.hongzhen.hera.daily.DailyDtos.LuckyPanel;
|
||||
import com.hongzhen.hera.user.User;
|
||||
import com.hongzhen.hera.user.UserRepository;
|
||||
import com.hongzhen.hera.user.UserMapper;
|
||||
import com.nlf.calendar.Solar;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
@@ -19,10 +19,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class DailyService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
public DailyService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
public DailyService(UserMapper userMapper) {
|
||||
this.userMapper = userMapper;
|
||||
}
|
||||
|
||||
public DailyHomeResponse home(Long userId) {
|
||||
@@ -59,11 +59,12 @@ public class DailyService {
|
||||
String nextPinnedCardId = cardId.equals(user.getPinnedDailyCardId()) ? null : cardId;
|
||||
user.setPinnedDailyCardId(nextPinnedCardId);
|
||||
user.touch();
|
||||
userMapper.update(user);
|
||||
return nextPinnedCardId;
|
||||
}
|
||||
|
||||
private User user(Long userId) {
|
||||
return userRepository.findById(userId).orElseThrow(() -> new BusinessException("用户不存在"));
|
||||
return userMapper.findById(userId).orElseThrow(() -> new BusinessException("用户不存在"));
|
||||
}
|
||||
|
||||
private String lunarDate(LocalDate date) {
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.BaseEntity;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* NFC 芯片实体。
|
||||
*/
|
||||
public class NfcChip extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 芯片唯一标识。
|
||||
*/
|
||||
private String uid;
|
||||
|
||||
/**
|
||||
* 芯片型号。
|
||||
*/
|
||||
private String chipType;
|
||||
|
||||
/**
|
||||
* 芯片制造商。
|
||||
*/
|
||||
private String manufacturer;
|
||||
|
||||
/**
|
||||
* 用户可写容量,单位为字节。
|
||||
*/
|
||||
private Integer capacity;
|
||||
|
||||
/**
|
||||
* NDEF 内容,格式为 JSON 或文本。
|
||||
*/
|
||||
private String ndefContent;
|
||||
|
||||
/**
|
||||
* 手机支架产品类型。
|
||||
*/
|
||||
private Integer holderType;
|
||||
|
||||
/**
|
||||
* 预置虚拟物 ID。
|
||||
*/
|
||||
private Integer virtualItemId;
|
||||
|
||||
/**
|
||||
* 激活时间。
|
||||
*/
|
||||
private LocalDateTime activatedAt;
|
||||
|
||||
/**
|
||||
* 最后登录时间。
|
||||
*/
|
||||
private LocalDateTime lastLoginAt;
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getChipType() {
|
||||
return chipType;
|
||||
}
|
||||
|
||||
public void setChipType(String chipType) {
|
||||
this.chipType = chipType;
|
||||
}
|
||||
|
||||
public String getManufacturer() {
|
||||
return manufacturer;
|
||||
}
|
||||
|
||||
public void setManufacturer(String manufacturer) {
|
||||
this.manufacturer = manufacturer;
|
||||
}
|
||||
|
||||
public Integer getCapacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public void setCapacity(Integer capacity) {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public String getNdefContent() {
|
||||
return ndefContent;
|
||||
}
|
||||
|
||||
public void setNdefContent(String ndefContent) {
|
||||
this.ndefContent = ndefContent;
|
||||
}
|
||||
|
||||
public Integer getHolderType() {
|
||||
return holderType;
|
||||
}
|
||||
|
||||
public void setHolderType(Integer holderType) {
|
||||
this.holderType = holderType;
|
||||
}
|
||||
|
||||
public Integer getVirtualItemId() {
|
||||
return virtualItemId;
|
||||
}
|
||||
|
||||
public void setVirtualItemId(Integer virtualItemId) {
|
||||
this.virtualItemId = virtualItemId;
|
||||
}
|
||||
|
||||
public LocalDateTime getActivatedAt() {
|
||||
return activatedAt;
|
||||
}
|
||||
|
||||
public void setActivatedAt(LocalDateTime activatedAt) {
|
||||
this.activatedAt = activatedAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastLoginAt() {
|
||||
return lastLoginAt;
|
||||
}
|
||||
|
||||
public void setLastLoginAt(LocalDateTime lastLoginAt) {
|
||||
this.lastLoginAt = lastLoginAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/data/nfc-chips")
|
||||
public class NfcChipController extends CrudController<NfcChip> {
|
||||
|
||||
public NfcChipController(NfcChipService service) {
|
||||
super(service);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudMapper;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface NfcChipMapper extends CrudMapper<NfcChip> {
|
||||
|
||||
@Override
|
||||
Optional<NfcChip> findById(Long id);
|
||||
|
||||
@Override
|
||||
List<NfcChip> findAll(@Param("offset") int offset, @Param("limit") int limit);
|
||||
|
||||
@Override
|
||||
int insert(NfcChip entity);
|
||||
|
||||
@Override
|
||||
int update(NfcChip entity);
|
||||
|
||||
@Override
|
||||
int deleteById(Long id);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class NfcChipService extends CrudService<NfcChip> {
|
||||
|
||||
public NfcChipService(NfcChipMapper mapper) {
|
||||
super(mapper, "NFC芯片");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.hongzhen.hera.common.BaseEntity;
|
||||
|
||||
/**
|
||||
* 用户账号实体。
|
||||
*/
|
||||
public class UserAccount extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 登录用户名。
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户显示昵称。
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 登录密码哈希。
|
||||
*/
|
||||
private String passwordHash;
|
||||
|
||||
/**
|
||||
* 手机号。
|
||||
*/
|
||||
private String mobile;
|
||||
|
||||
/**
|
||||
* 电子邮箱。
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 已绑定的 NFC 标签 UID。
|
||||
*/
|
||||
private String nfcUid;
|
||||
|
||||
/**
|
||||
* 微信 OpenID。
|
||||
*/
|
||||
private String wechatOpenid;
|
||||
|
||||
/**
|
||||
* 微信 UnionID。
|
||||
*/
|
||||
private String wechatUnionid;
|
||||
|
||||
/**
|
||||
* 账号状态:1 正常,0 禁用。
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 注册国家代码。
|
||||
*/
|
||||
private String registerCountryCode;
|
||||
|
||||
/**
|
||||
* 注册地区代码。
|
||||
*/
|
||||
private String registerRegionCode;
|
||||
|
||||
/**
|
||||
* 注册城市。
|
||||
*/
|
||||
private String registerCity;
|
||||
|
||||
/**
|
||||
* 注册时区。
|
||||
*/
|
||||
private String registerTimezone;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public void setNickname(String nickname) {
|
||||
this.nickname = nickname;
|
||||
}
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
public String getPasswordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
public void setPasswordHash(String passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
public String getMobile() {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
public void setMobile(String mobile) {
|
||||
this.mobile = mobile;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getNfcUid() {
|
||||
return nfcUid;
|
||||
}
|
||||
|
||||
public void setNfcUid(String nfcUid) {
|
||||
this.nfcUid = nfcUid;
|
||||
}
|
||||
|
||||
public String getWechatOpenid() {
|
||||
return wechatOpenid;
|
||||
}
|
||||
|
||||
public void setWechatOpenid(String wechatOpenid) {
|
||||
this.wechatOpenid = wechatOpenid;
|
||||
}
|
||||
|
||||
public String getWechatUnionid() {
|
||||
return wechatUnionid;
|
||||
}
|
||||
|
||||
public void setWechatUnionid(String wechatUnionid) {
|
||||
this.wechatUnionid = wechatUnionid;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getRegisterCountryCode() {
|
||||
return registerCountryCode;
|
||||
}
|
||||
|
||||
public void setRegisterCountryCode(String registerCountryCode) {
|
||||
this.registerCountryCode = registerCountryCode;
|
||||
}
|
||||
|
||||
public String getRegisterRegionCode() {
|
||||
return registerRegionCode;
|
||||
}
|
||||
|
||||
public void setRegisterRegionCode(String registerRegionCode) {
|
||||
this.registerRegionCode = registerRegionCode;
|
||||
}
|
||||
|
||||
public String getRegisterCity() {
|
||||
return registerCity;
|
||||
}
|
||||
|
||||
public void setRegisterCity(String registerCity) {
|
||||
this.registerCity = registerCity;
|
||||
}
|
||||
|
||||
public String getRegisterTimezone() {
|
||||
return registerTimezone;
|
||||
}
|
||||
|
||||
public void setRegisterTimezone(String registerTimezone) {
|
||||
this.registerTimezone = registerTimezone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/data/user-accounts")
|
||||
public class UserAccountController extends CrudController<UserAccount> {
|
||||
|
||||
public UserAccountController(UserAccountService service) {
|
||||
super(service);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudMapper;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface UserAccountMapper extends CrudMapper<UserAccount> {
|
||||
|
||||
@Override
|
||||
Optional<UserAccount> findById(Long id);
|
||||
|
||||
@Override
|
||||
List<UserAccount> findAll(@Param("offset") int offset, @Param("limit") int limit);
|
||||
|
||||
@Override
|
||||
int insert(UserAccount entity);
|
||||
|
||||
@Override
|
||||
int update(UserAccount entity);
|
||||
|
||||
@Override
|
||||
int deleteById(Long id);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserAccountService extends CrudService<UserAccount> {
|
||||
|
||||
public UserAccountService(UserAccountMapper mapper) {
|
||||
super(mapper, "用户账号");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.hongzhen.hera.common.BaseEntity;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户资料实体。
|
||||
*/
|
||||
public class UserProfile extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 关联的用户账号 ID。
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 加密存储的真实姓名。
|
||||
*/
|
||||
private String realNameEncrypted;
|
||||
|
||||
/**
|
||||
* 性别:0 未知,1 男,2 女。
|
||||
*/
|
||||
private Integer gender;
|
||||
|
||||
/**
|
||||
* 出生日期。
|
||||
*/
|
||||
private LocalDate birthday;
|
||||
|
||||
/**
|
||||
* 出生时辰,取值范围为 0 至 11。
|
||||
*/
|
||||
private Integer birthShichen;
|
||||
|
||||
/**
|
||||
* 出生国家代码。
|
||||
*/
|
||||
private String birthCountryCode;
|
||||
|
||||
/**
|
||||
* 出生地区代码。
|
||||
*/
|
||||
private String birthRegionCode;
|
||||
|
||||
/**
|
||||
* 出生城市代码。
|
||||
*/
|
||||
private String birthCityCode;
|
||||
|
||||
/**
|
||||
* 出生区县代码。
|
||||
*/
|
||||
private String birthDistrictCode;
|
||||
|
||||
/**
|
||||
* 加密存储的身份证号。
|
||||
*/
|
||||
private String idCardNoEncrypted;
|
||||
|
||||
/**
|
||||
* 身份证号 SHA-256 哈希。
|
||||
*/
|
||||
private String idCardHash;
|
||||
|
||||
/**
|
||||
* 实名认证状态:0 未认证,1 已认证。
|
||||
*/
|
||||
private Integer idVerified;
|
||||
|
||||
/**
|
||||
* 实名认证时间。
|
||||
*/
|
||||
private LocalDateTime idVerifiedAt;
|
||||
|
||||
/**
|
||||
* 头像 URL。
|
||||
*/
|
||||
private String avatarUrl;
|
||||
|
||||
/**
|
||||
* 现居住省份。
|
||||
*/
|
||||
private String residenceProvince;
|
||||
|
||||
/**
|
||||
* 现居住城市。
|
||||
*/
|
||||
private String residenceCity;
|
||||
|
||||
/**
|
||||
* 现居住区县。
|
||||
*/
|
||||
private String residenceDistrict;
|
||||
|
||||
/**
|
||||
* 现居住详细地址。
|
||||
*/
|
||||
private String residenceAddress;
|
||||
|
||||
/**
|
||||
* 个性签名。
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
public String getRealNameEncrypted() {
|
||||
return realNameEncrypted;
|
||||
}
|
||||
|
||||
public void setRealNameEncrypted(String realNameEncrypted) {
|
||||
this.realNameEncrypted = realNameEncrypted;
|
||||
}
|
||||
|
||||
public Integer getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(Integer gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public LocalDate getBirthday() {
|
||||
return birthday;
|
||||
}
|
||||
|
||||
public void setBirthday(LocalDate birthday) {
|
||||
this.birthday = birthday;
|
||||
}
|
||||
|
||||
public Integer getBirthShichen() {
|
||||
return birthShichen;
|
||||
}
|
||||
|
||||
public void setBirthShichen(Integer birthShichen) {
|
||||
this.birthShichen = birthShichen;
|
||||
}
|
||||
|
||||
public String getBirthCountryCode() {
|
||||
return birthCountryCode;
|
||||
}
|
||||
|
||||
public void setBirthCountryCode(String birthCountryCode) {
|
||||
this.birthCountryCode = birthCountryCode;
|
||||
}
|
||||
|
||||
public String getBirthRegionCode() {
|
||||
return birthRegionCode;
|
||||
}
|
||||
|
||||
public void setBirthRegionCode(String birthRegionCode) {
|
||||
this.birthRegionCode = birthRegionCode;
|
||||
}
|
||||
|
||||
public String getBirthCityCode() {
|
||||
return birthCityCode;
|
||||
}
|
||||
|
||||
public void setBirthCityCode(String birthCityCode) {
|
||||
this.birthCityCode = birthCityCode;
|
||||
}
|
||||
|
||||
public String getBirthDistrictCode() {
|
||||
return birthDistrictCode;
|
||||
}
|
||||
|
||||
public void setBirthDistrictCode(String birthDistrictCode) {
|
||||
this.birthDistrictCode = birthDistrictCode;
|
||||
}
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
public String getIdCardNoEncrypted() {
|
||||
return idCardNoEncrypted;
|
||||
}
|
||||
|
||||
public void setIdCardNoEncrypted(String idCardNoEncrypted) {
|
||||
this.idCardNoEncrypted = idCardNoEncrypted;
|
||||
}
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
public String getIdCardHash() {
|
||||
return idCardHash;
|
||||
}
|
||||
|
||||
public void setIdCardHash(String idCardHash) {
|
||||
this.idCardHash = idCardHash;
|
||||
}
|
||||
|
||||
public Integer getIdVerified() {
|
||||
return idVerified;
|
||||
}
|
||||
|
||||
public void setIdVerified(Integer idVerified) {
|
||||
this.idVerified = idVerified;
|
||||
}
|
||||
|
||||
public LocalDateTime getIdVerifiedAt() {
|
||||
return idVerifiedAt;
|
||||
}
|
||||
|
||||
public void setIdVerifiedAt(LocalDateTime idVerifiedAt) {
|
||||
this.idVerifiedAt = idVerifiedAt;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
public String getResidenceProvince() {
|
||||
return residenceProvince;
|
||||
}
|
||||
|
||||
public void setResidenceProvince(String residenceProvince) {
|
||||
this.residenceProvince = residenceProvince;
|
||||
}
|
||||
|
||||
public String getResidenceCity() {
|
||||
return residenceCity;
|
||||
}
|
||||
|
||||
public void setResidenceCity(String residenceCity) {
|
||||
this.residenceCity = residenceCity;
|
||||
}
|
||||
|
||||
public String getResidenceDistrict() {
|
||||
return residenceDistrict;
|
||||
}
|
||||
|
||||
public void setResidenceDistrict(String residenceDistrict) {
|
||||
this.residenceDistrict = residenceDistrict;
|
||||
}
|
||||
|
||||
public String getResidenceAddress() {
|
||||
return residenceAddress;
|
||||
}
|
||||
|
||||
public void setResidenceAddress(String residenceAddress) {
|
||||
this.residenceAddress = residenceAddress;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/data/user-profiles")
|
||||
public class UserProfileController extends CrudController<UserProfile> {
|
||||
|
||||
public UserProfileController(UserProfileService service) {
|
||||
super(service);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudMapper;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface UserProfileMapper extends CrudMapper<UserProfile> {
|
||||
|
||||
@Override
|
||||
Optional<UserProfile> findById(Long id);
|
||||
|
||||
@Override
|
||||
List<UserProfile> findAll(@Param("offset") int offset, @Param("limit") int limit);
|
||||
|
||||
@Override
|
||||
int insert(UserProfile entity);
|
||||
|
||||
@Override
|
||||
int update(UserProfile entity);
|
||||
|
||||
@Override
|
||||
int deleteById(Long id);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserProfileService extends CrudService<UserProfile> {
|
||||
|
||||
public UserProfileService(UserProfileMapper mapper) {
|
||||
super(mapper, "用户资料");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.BaseEntity;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户持有虚拟物关系实体。
|
||||
*/
|
||||
public class UserVirtualItem extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 用户账号 ID。
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 虚拟物 ID。
|
||||
*/
|
||||
private Integer virtualItemId;
|
||||
|
||||
/**
|
||||
* 来源 NFC 芯片 UID。
|
||||
*/
|
||||
private String nfcUid;
|
||||
|
||||
/**
|
||||
* 用户自定义顺序,值越小越靠前。
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 持有状态:1 供奉中,0 已送神,2 已回向,3 已过期。
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 获得时间。
|
||||
*/
|
||||
private LocalDateTime obtainedAt;
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Integer getVirtualItemId() {
|
||||
return virtualItemId;
|
||||
}
|
||||
|
||||
public void setVirtualItemId(Integer virtualItemId) {
|
||||
this.virtualItemId = virtualItemId;
|
||||
}
|
||||
|
||||
public String getNfcUid() {
|
||||
return nfcUid;
|
||||
}
|
||||
|
||||
public void setNfcUid(String nfcUid) {
|
||||
this.nfcUid = nfcUid;
|
||||
}
|
||||
|
||||
public Integer getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
public void setSortOrder(Integer sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getObtainedAt() {
|
||||
return obtainedAt;
|
||||
}
|
||||
|
||||
public void setObtainedAt(LocalDateTime obtainedAt) {
|
||||
this.obtainedAt = obtainedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/data/user-virtual-items")
|
||||
public class UserVirtualItemController extends CrudController<UserVirtualItem> {
|
||||
|
||||
public UserVirtualItemController(UserVirtualItemService service) {
|
||||
super(service);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudMapper;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface UserVirtualItemMapper extends CrudMapper<UserVirtualItem> {
|
||||
|
||||
@Override
|
||||
Optional<UserVirtualItem> findById(Long id);
|
||||
|
||||
@Override
|
||||
List<UserVirtualItem> findAll(@Param("offset") int offset, @Param("limit") int limit);
|
||||
|
||||
@Override
|
||||
int insert(UserVirtualItem entity);
|
||||
|
||||
@Override
|
||||
int update(UserVirtualItem entity);
|
||||
|
||||
@Override
|
||||
int deleteById(Long id);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.BaseEntity;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户虚拟物供奉记录实体。
|
||||
*/
|
||||
public class UserVirtualItemOffering extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 用户虚拟物关系 ID。
|
||||
*/
|
||||
private Long userVirtualItemId;
|
||||
|
||||
/**
|
||||
* 用户账号 ID。
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 虚拟物 ID。
|
||||
*/
|
||||
private Integer virtualItemId;
|
||||
|
||||
/**
|
||||
* 供奉类型:1 请神,2 上香,3 祈愿,4 功德,5 送神。
|
||||
*/
|
||||
private Integer offeringType;
|
||||
|
||||
/**
|
||||
* 祈愿文字或功德备注。
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 功德值。
|
||||
*/
|
||||
private Integer meritValue;
|
||||
|
||||
/**
|
||||
* 记录状态:1 生效,0 失效。
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 供奉时间。
|
||||
*/
|
||||
private LocalDateTime offeredAt;
|
||||
|
||||
public Long getUserVirtualItemId() {
|
||||
return userVirtualItemId;
|
||||
}
|
||||
|
||||
public void setUserVirtualItemId(Long userVirtualItemId) {
|
||||
this.userVirtualItemId = userVirtualItemId;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Integer getVirtualItemId() {
|
||||
return virtualItemId;
|
||||
}
|
||||
|
||||
public void setVirtualItemId(Integer virtualItemId) {
|
||||
this.virtualItemId = virtualItemId;
|
||||
}
|
||||
|
||||
public Integer getOfferingType() {
|
||||
return offeringType;
|
||||
}
|
||||
|
||||
public void setOfferingType(Integer offeringType) {
|
||||
this.offeringType = offeringType;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Integer getMeritValue() {
|
||||
return meritValue;
|
||||
}
|
||||
|
||||
public void setMeritValue(Integer meritValue) {
|
||||
this.meritValue = meritValue;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getOfferedAt() {
|
||||
return offeredAt;
|
||||
}
|
||||
|
||||
public void setOfferedAt(LocalDateTime offeredAt) {
|
||||
this.offeredAt = offeredAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/data/offerings")
|
||||
public class UserVirtualItemOfferingController extends CrudController<UserVirtualItemOffering> {
|
||||
|
||||
public UserVirtualItemOfferingController(UserVirtualItemOfferingService service) {
|
||||
super(service);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudMapper;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface UserVirtualItemOfferingMapper extends CrudMapper<UserVirtualItemOffering> {
|
||||
|
||||
@Override
|
||||
Optional<UserVirtualItemOffering> findById(Long id);
|
||||
|
||||
@Override
|
||||
List<UserVirtualItemOffering> findAll(@Param("offset") int offset, @Param("limit") int limit);
|
||||
|
||||
@Override
|
||||
int insert(UserVirtualItemOffering entity);
|
||||
|
||||
@Override
|
||||
int update(UserVirtualItemOffering entity);
|
||||
|
||||
@Override
|
||||
int deleteById(Long id);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserVirtualItemOfferingService extends CrudService<UserVirtualItemOffering> {
|
||||
|
||||
public UserVirtualItemOfferingService(UserVirtualItemOfferingMapper mapper) {
|
||||
super(mapper, "供奉记录");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserVirtualItemService extends CrudService<UserVirtualItem> {
|
||||
|
||||
public UserVirtualItemService(UserVirtualItemMapper mapper) {
|
||||
super(mapper, "用户虚拟物");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.BaseEntity;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户虚拟物回向记录实体。
|
||||
*/
|
||||
public class UserVirtualItemTransfer extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 转出用户 ID。
|
||||
*/
|
||||
private Long fromUserId;
|
||||
|
||||
/**
|
||||
* 转出用户昵称快照。
|
||||
*/
|
||||
private String fromUserName;
|
||||
|
||||
/**
|
||||
* 接收用户 ID。
|
||||
*/
|
||||
private Long toUserId;
|
||||
|
||||
/**
|
||||
* 接收用户昵称快照。
|
||||
*/
|
||||
private String toUserName;
|
||||
|
||||
/**
|
||||
* 虚拟物 ID。
|
||||
*/
|
||||
private Integer virtualItemId;
|
||||
|
||||
/**
|
||||
* 转出方原持有关系 ID。
|
||||
*/
|
||||
private Long fromUserItemId;
|
||||
|
||||
/**
|
||||
* 接收方新持有关系 ID。
|
||||
*/
|
||||
private Long toUserItemId;
|
||||
|
||||
/**
|
||||
* 回向寄语或祝福语。
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 记录状态:1 生效,0 已撤销。
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 回向时间。
|
||||
*/
|
||||
private LocalDateTime transferredAt;
|
||||
|
||||
public Long getFromUserId() {
|
||||
return fromUserId;
|
||||
}
|
||||
|
||||
public void setFromUserId(Long fromUserId) {
|
||||
this.fromUserId = fromUserId;
|
||||
}
|
||||
|
||||
public String getFromUserName() {
|
||||
return fromUserName;
|
||||
}
|
||||
|
||||
public void setFromUserName(String fromUserName) {
|
||||
this.fromUserName = fromUserName;
|
||||
}
|
||||
|
||||
public Long getToUserId() {
|
||||
return toUserId;
|
||||
}
|
||||
|
||||
public void setToUserId(Long toUserId) {
|
||||
this.toUserId = toUserId;
|
||||
}
|
||||
|
||||
public String getToUserName() {
|
||||
return toUserName;
|
||||
}
|
||||
|
||||
public void setToUserName(String toUserName) {
|
||||
this.toUserName = toUserName;
|
||||
}
|
||||
|
||||
public Integer getVirtualItemId() {
|
||||
return virtualItemId;
|
||||
}
|
||||
|
||||
public void setVirtualItemId(Integer virtualItemId) {
|
||||
this.virtualItemId = virtualItemId;
|
||||
}
|
||||
|
||||
public Long getFromUserItemId() {
|
||||
return fromUserItemId;
|
||||
}
|
||||
|
||||
public void setFromUserItemId(Long fromUserItemId) {
|
||||
this.fromUserItemId = fromUserItemId;
|
||||
}
|
||||
|
||||
public Long getToUserItemId() {
|
||||
return toUserItemId;
|
||||
}
|
||||
|
||||
public void setToUserItemId(Long toUserItemId) {
|
||||
this.toUserItemId = toUserItemId;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getTransferredAt() {
|
||||
return transferredAt;
|
||||
}
|
||||
|
||||
public void setTransferredAt(LocalDateTime transferredAt) {
|
||||
this.transferredAt = transferredAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/data/transfers")
|
||||
public class UserVirtualItemTransferController extends CrudController<UserVirtualItemTransfer> {
|
||||
|
||||
public UserVirtualItemTransferController(UserVirtualItemTransferService service) {
|
||||
super(service);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudMapper;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface UserVirtualItemTransferMapper extends CrudMapper<UserVirtualItemTransfer> {
|
||||
|
||||
@Override
|
||||
Optional<UserVirtualItemTransfer> findById(Long id);
|
||||
|
||||
@Override
|
||||
List<UserVirtualItemTransfer> findAll(@Param("offset") int offset, @Param("limit") int limit);
|
||||
|
||||
@Override
|
||||
int insert(UserVirtualItemTransfer entity);
|
||||
|
||||
@Override
|
||||
int update(UserVirtualItemTransfer entity);
|
||||
|
||||
@Override
|
||||
int deleteById(Long id);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserVirtualItemTransferService extends CrudService<UserVirtualItemTransfer> {
|
||||
|
||||
public UserVirtualItemTransferService(UserVirtualItemTransferMapper mapper) {
|
||||
super(mapper, "回向记录");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.BaseEntity;
|
||||
|
||||
/**
|
||||
* 虚拟物字典实体。
|
||||
*/
|
||||
public class VirtualItem extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 虚拟物名称。
|
||||
*/
|
||||
private String itemName;
|
||||
|
||||
/**
|
||||
* 图标 URL。
|
||||
*/
|
||||
private String iconUrl;
|
||||
|
||||
/**
|
||||
* 虚拟物描述。
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 展示顺序,值越小越靠前。
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 启用状态:1 启用,0 禁用。
|
||||
*/
|
||||
private Integer enabled;
|
||||
|
||||
public String getItemName() {
|
||||
return itemName;
|
||||
}
|
||||
|
||||
public void setItemName(String itemName) {
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
public String getIconUrl() {
|
||||
return iconUrl;
|
||||
}
|
||||
|
||||
public void setIconUrl(String iconUrl) {
|
||||
this.iconUrl = iconUrl;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Integer getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
public void setSortOrder(Integer sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
public Integer getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Integer enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/data/virtual-items")
|
||||
public class VirtualItemController extends CrudController<VirtualItem> {
|
||||
|
||||
public VirtualItemController(VirtualItemService service) {
|
||||
super(service);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudMapper;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface VirtualItemMapper extends CrudMapper<VirtualItem> {
|
||||
|
||||
@Override
|
||||
Optional<VirtualItem> findById(Long id);
|
||||
|
||||
@Override
|
||||
List<VirtualItem> findAll(@Param("offset") int offset, @Param("limit") int limit);
|
||||
|
||||
@Override
|
||||
int insert(VirtualItem entity);
|
||||
|
||||
@Override
|
||||
int update(VirtualItem entity);
|
||||
|
||||
@Override
|
||||
int deleteById(Long id);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.hongzhen.hera.data;
|
||||
|
||||
import com.hongzhen.hera.common.CrudService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class VirtualItemService extends CrudService<VirtualItem> {
|
||||
|
||||
public VirtualItemService(VirtualItemMapper mapper) {
|
||||
super(mapper, "虚拟物");
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@ public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
|
||||
return true;
|
||||
}
|
||||
String auth = request.getHeader("Authorization");
|
||||
if (auth == null || !auth.startsWith("Bearer ")) {
|
||||
throw new BusinessException("请先登录");
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.hongzhen.hera.user;
|
||||
|
||||
import com.hongzhen.hera.common.BusinessException;
|
||||
import com.hongzhen.hera.config.HeraProperties;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Service
|
||||
public class AvatarStorageService {
|
||||
|
||||
private static final long MAX_AVATAR_SIZE = 5L * 1024L * 1024L;
|
||||
private final Path avatarDirectory;
|
||||
|
||||
public AvatarStorageService(HeraProperties properties) {
|
||||
this.avatarDirectory = Paths.get(properties.getUploadDir(), "avatars").toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
public String store(Long userId, MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new BusinessException("请选择头像图片");
|
||||
}
|
||||
if (file.getSize() > MAX_AVATAR_SIZE) {
|
||||
throw new BusinessException("头像图片不能超过5MB");
|
||||
}
|
||||
|
||||
String contentType = file.getContentType() == null ? "" : file.getContentType().toLowerCase(Locale.ROOT);
|
||||
String extension;
|
||||
if ("image/jpeg".equals(contentType) || "image/jpg".equals(contentType)) {
|
||||
extension = ".jpg";
|
||||
} else if ("image/png".equals(contentType)) {
|
||||
extension = ".png";
|
||||
} else if ("image/webp".equals(contentType)) {
|
||||
extension = ".webp";
|
||||
} else {
|
||||
throw new BusinessException("仅支持 JPG、PNG 或 WebP 头像");
|
||||
}
|
||||
|
||||
String filename = userId + "-" + UUID.randomUUID().toString().replace("-", "") + extension;
|
||||
Path target = avatarDirectory.resolve(filename).normalize();
|
||||
if (!target.startsWith(avatarDirectory)) {
|
||||
throw new BusinessException("头像文件名不合法");
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createDirectories(avatarDirectory);
|
||||
try (InputStream input = file.getInputStream()) {
|
||||
Files.copy(input, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
throw new BusinessException("头像保存失败,请稍后重试");
|
||||
}
|
||||
return "/uploads/avatars/" + filename;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public class ProfileDtos {
|
||||
private Long id;
|
||||
private String nickname;
|
||||
private String phone;
|
||||
private String avatarUrl;
|
||||
private String gender;
|
||||
private LocalDate birthday;
|
||||
private String birthHour;
|
||||
@@ -21,11 +22,12 @@ public class ProfileDtos {
|
||||
private int remainingEdits;
|
||||
private ElementReport elementReport;
|
||||
|
||||
public ProfileResponse(Long id, String nickname, String phone, String gender, LocalDate birthday, String birthHour,
|
||||
public ProfileResponse(Long id, String nickname, String phone, String avatarUrl, String gender, LocalDate birthday, String birthHour,
|
||||
String province, String city, String district, int points, int remainingEdits, ElementReport elementReport) {
|
||||
this.id = id;
|
||||
this.nickname = nickname;
|
||||
this.phone = phone;
|
||||
this.avatarUrl = avatarUrl;
|
||||
this.gender = gender;
|
||||
this.birthday = birthday;
|
||||
this.birthHour = birthHour;
|
||||
@@ -49,6 +51,10 @@ public class ProfileDtos {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public String getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
@@ -1,44 +1,106 @@
|
||||
package com.hongzhen.hera.user;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "hera_user")
|
||||
/**
|
||||
* 小程序业务用户实体。
|
||||
*/
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/**
|
||||
* 主键。
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
/**
|
||||
* 微信 OpenID。
|
||||
*/
|
||||
private String openId;
|
||||
|
||||
/**
|
||||
* 微信 UnionID。
|
||||
*/
|
||||
private String unionId;
|
||||
|
||||
/**
|
||||
* 已绑定手机号。
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 头像 URL。
|
||||
*/
|
||||
private String avatarUrl;
|
||||
|
||||
/**
|
||||
* 用户昵称。
|
||||
*/
|
||||
private String nickname = "道道3338";
|
||||
|
||||
/**
|
||||
* 用户性别。
|
||||
*/
|
||||
private String gender = "男";
|
||||
|
||||
/**
|
||||
* 出生日期。
|
||||
*/
|
||||
private LocalDate birthday = LocalDate.of(2008, 12, 28);
|
||||
|
||||
/**
|
||||
* 出生时辰。
|
||||
*/
|
||||
private String birthHour = "寅时";
|
||||
|
||||
/**
|
||||
* 所在省份。
|
||||
*/
|
||||
private String province = "河南省";
|
||||
|
||||
/**
|
||||
* 所在城市。
|
||||
*/
|
||||
private String city = "洛阳市";
|
||||
|
||||
/**
|
||||
* 所在区县。
|
||||
*/
|
||||
private String district = "老城区";
|
||||
|
||||
/**
|
||||
* 当前吉分。
|
||||
*/
|
||||
private int points = 0;
|
||||
|
||||
/**
|
||||
* 已置顶的每日卡片 ID。
|
||||
*/
|
||||
private String pinnedDailyCardId;
|
||||
|
||||
/**
|
||||
* 当前周期内资料修改次数。
|
||||
*/
|
||||
private int profileEditCount = 0;
|
||||
|
||||
/**
|
||||
* 创建时间。
|
||||
*/
|
||||
private LocalDateTime createdAt = LocalDateTime.now();
|
||||
|
||||
/**
|
||||
* 最后更新时间。
|
||||
*/
|
||||
private LocalDateTime updatedAt = LocalDateTime.now();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getOpenId() {
|
||||
return openId;
|
||||
}
|
||||
@@ -63,6 +125,14 @@ public class User {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
@@ -147,10 +217,18 @@ public class User {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public void touch() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@@ -5,20 +5,26 @@ import com.hongzhen.hera.security.CurrentUser;
|
||||
import com.hongzhen.hera.user.ProfileDtos.ProfileResponse;
|
||||
import com.hongzhen.hera.user.ProfileDtos.UpdateProfileRequest;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
private final AvatarStorageService avatarStorageService;
|
||||
|
||||
public UserController(UserService userService) {
|
||||
public UserController(UserService userService, AvatarStorageService avatarStorageService) {
|
||||
this.userService = userService;
|
||||
this.avatarStorageService = avatarStorageService;
|
||||
}
|
||||
|
||||
@GetMapping("/profile")
|
||||
@@ -30,4 +36,10 @@ public class UserController {
|
||||
public ApiResponse<ProfileResponse> update(@Valid @RequestBody UpdateProfileRequest request) {
|
||||
return ApiResponse.ok(userService.updateProfile(CurrentUser.id(), request));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ApiResponse<ProfileResponse> uploadAvatar(@RequestParam("file") MultipartFile file) {
|
||||
String avatarUrl = avatarStorageService.store(CurrentUser.id(), file);
|
||||
return ApiResponse.ok(userService.updateAvatar(CurrentUser.id(), avatarUrl));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.hongzhen.hera.user;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface UserMapper {
|
||||
|
||||
Optional<User> findById(Long id);
|
||||
|
||||
Optional<User> findByOpenId(String openId);
|
||||
|
||||
int insert(User user);
|
||||
|
||||
int update(User user);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.hongzhen.hera.user;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
Optional<User> findByOpenId(String openId);
|
||||
}
|
||||
@@ -11,16 +11,16 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final UserMapper userMapper;
|
||||
private final FortuneService fortuneService;
|
||||
|
||||
public UserService(UserRepository userRepository, FortuneService fortuneService) {
|
||||
this.userRepository = userRepository;
|
||||
public UserService(UserMapper userMapper, FortuneService fortuneService) {
|
||||
this.userMapper = userMapper;
|
||||
this.fortuneService = fortuneService;
|
||||
}
|
||||
|
||||
public User get(Long userId) {
|
||||
return userRepository.findById(userId).orElseThrow(() -> new BusinessException("用户不存在"));
|
||||
return userMapper.findById(userId).orElseThrow(() -> new BusinessException("用户不存在"));
|
||||
}
|
||||
|
||||
public ProfileResponse profile(Long userId) {
|
||||
@@ -42,6 +42,16 @@ public class UserService {
|
||||
user.setDistrict(request.getDistrict());
|
||||
user.setProfileEditCount(user.getProfileEditCount() + 1);
|
||||
user.touch();
|
||||
userMapper.update(user);
|
||||
return toProfile(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ProfileResponse updateAvatar(Long userId, String avatarUrl) {
|
||||
User user = get(userId);
|
||||
user.setAvatarUrl(avatarUrl);
|
||||
user.touch();
|
||||
userMapper.update(user);
|
||||
return toProfile(user);
|
||||
}
|
||||
|
||||
@@ -51,6 +61,7 @@ public class UserService {
|
||||
user.getId(),
|
||||
user.getNickname(),
|
||||
user.getPhone(),
|
||||
user.getAvatarUrl(),
|
||||
user.getGender(),
|
||||
user.getBirthday(),
|
||||
user.getBirthHour(),
|
||||
|
||||
@@ -5,22 +5,27 @@ spring:
|
||||
application:
|
||||
name: hera
|
||||
datasource:
|
||||
url: jdbc:h2:mem:hera;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1
|
||||
username: sa
|
||||
password:
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
open-in-view: false
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: "${DB_URL:jdbc:mysql://101.43.139.56:3306/hera?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}"
|
||||
username: "${DB_USERNAME:hera_user}"
|
||||
password: "${DB_PASSWORD:}"
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
encoding: UTF-8
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 5MB
|
||||
max-request-size: 5MB
|
||||
|
||||
mybatis:
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
|
||||
hera:
|
||||
jwt-secret: "replace-this-development-secret-replace-this-development-secret"
|
||||
upload-dir: "${HERA_UPLOAD_DIR:./uploads}"
|
||||
wx:
|
||||
app-id: "${WECHAT_APP_ID:wxa76a2ef9e8ba612e}"
|
||||
app-secret: "${WECHAT_APP_SECRET:}"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.auth.LoginSessionMapper">
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO login_session (session_key, open_id, union_id, expires_at)
|
||||
VALUES (#{sessionKey}, #{openId}, #{unionId}, #{expiresAt})
|
||||
</insert>
|
||||
|
||||
<select id="findBySessionKey" resultType="com.hongzhen.hera.auth.LoginSession">
|
||||
SELECT id, session_key, open_id, union_id, expires_at
|
||||
FROM login_session
|
||||
WHERE session_key = #{sessionKey}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.data.NfcChipMapper">
|
||||
|
||||
<sql id="BaseColumnList">
|
||||
id, uid, chip_type, manufacturer, capacity, ndef_content, holder_type, virtual_item_id,
|
||||
activated_at, last_login_at, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultType="com.hongzhen.hera.data.NfcChip">
|
||||
SELECT <include refid="BaseColumnList"/> FROM nfc_chip WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultType="com.hongzhen.hera.data.NfcChip">
|
||||
SELECT <include refid="BaseColumnList"/>
|
||||
FROM nfc_chip
|
||||
ORDER BY id DESC
|
||||
LIMIT #{offset}, #{limit}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO nfc_chip (
|
||||
uid, chip_type, manufacturer, capacity, ndef_content, holder_type, virtual_item_id,
|
||||
activated_at, last_login_at
|
||||
) VALUES (
|
||||
#{uid}, #{chipType}, #{manufacturer}, #{capacity}, #{ndefContent}, #{holderType},
|
||||
#{virtualItemId}, #{activatedAt}, #{lastLoginAt}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE nfc_chip
|
||||
SET uid = #{uid},
|
||||
chip_type = #{chipType},
|
||||
manufacturer = #{manufacturer},
|
||||
capacity = #{capacity},
|
||||
ndef_content = #{ndefContent},
|
||||
holder_type = #{holderType},
|
||||
virtual_item_id = #{virtualItemId},
|
||||
activated_at = #{activatedAt},
|
||||
last_login_at = #{lastLoginAt}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">DELETE FROM nfc_chip WHERE id = #{id}</delete>
|
||||
</mapper>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.auth.SmsCodeMapper">
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO sms_code (phone, code, expires_at, used)
|
||||
VALUES (#{phone}, #{code}, #{expiresAt}, #{used})
|
||||
</insert>
|
||||
|
||||
<select id="findLatestUnused" resultType="com.hongzhen.hera.auth.SmsCode">
|
||||
SELECT id, phone, code, expires_at, used
|
||||
FROM sms_code
|
||||
WHERE phone = #{phone}
|
||||
AND code = #{code}
|
||||
AND used = FALSE
|
||||
ORDER BY expires_at DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<update id="markUsed">
|
||||
UPDATE sms_code
|
||||
SET used = TRUE
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.data.UserAccountMapper">
|
||||
|
||||
<sql id="BaseColumnList">
|
||||
id, username, nickname, password_hash, mobile, email, nfc_uid, wechat_openid, wechat_unionid,
|
||||
status, register_country_code, register_region_code, register_city, register_timezone,
|
||||
created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultType="com.hongzhen.hera.data.UserAccount">
|
||||
SELECT <include refid="BaseColumnList"/> FROM user_account WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultType="com.hongzhen.hera.data.UserAccount">
|
||||
SELECT <include refid="BaseColumnList"/>
|
||||
FROM user_account
|
||||
ORDER BY id DESC
|
||||
LIMIT #{offset}, #{limit}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO user_account (
|
||||
username, nickname, password_hash, mobile, email, nfc_uid, wechat_openid, wechat_unionid,
|
||||
status, register_country_code, register_region_code, register_city, register_timezone
|
||||
) VALUES (
|
||||
#{username}, #{nickname}, #{passwordHash}, #{mobile}, #{email}, #{nfcUid}, #{wechatOpenid},
|
||||
#{wechatUnionid}, COALESCE(#{status}, 1), #{registerCountryCode}, #{registerRegionCode},
|
||||
#{registerCity}, #{registerTimezone}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE user_account
|
||||
SET username = #{username},
|
||||
nickname = #{nickname},
|
||||
password_hash = COALESCE(#{passwordHash}, password_hash),
|
||||
mobile = #{mobile},
|
||||
email = #{email},
|
||||
nfc_uid = #{nfcUid},
|
||||
wechat_openid = #{wechatOpenid},
|
||||
wechat_unionid = #{wechatUnionid},
|
||||
status = COALESCE(#{status}, status),
|
||||
register_country_code = #{registerCountryCode},
|
||||
register_region_code = #{registerRegionCode},
|
||||
register_city = #{registerCity},
|
||||
register_timezone = #{registerTimezone}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">DELETE FROM user_account WHERE id = #{id}</delete>
|
||||
</mapper>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.user.UserMapper">
|
||||
|
||||
<sql id="BaseColumnList">
|
||||
id, open_id, union_id, phone, avatar_url, nickname, gender, birthday, birth_hour,
|
||||
province, city, district, points, pinned_daily_card_id, profile_edit_count, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultType="com.hongzhen.hera.user.User">
|
||||
SELECT <include refid="BaseColumnList"/>
|
||||
FROM hera_user
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findByOpenId" resultType="com.hongzhen.hera.user.User">
|
||||
SELECT <include refid="BaseColumnList"/>
|
||||
FROM hera_user
|
||||
WHERE open_id = #{openId}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO hera_user (
|
||||
open_id, union_id, phone, avatar_url, nickname, gender, birthday, birth_hour,
|
||||
province, city, district, points, pinned_daily_card_id, profile_edit_count, created_at, updated_at
|
||||
) VALUES (
|
||||
#{openId}, #{unionId}, #{phone}, #{avatarUrl}, #{nickname}, #{gender}, #{birthday}, #{birthHour},
|
||||
#{province}, #{city}, #{district}, #{points}, #{pinnedDailyCardId}, #{profileEditCount},
|
||||
#{createdAt}, #{updatedAt}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE hera_user
|
||||
SET union_id = #{unionId},
|
||||
phone = #{phone},
|
||||
avatar_url = #{avatarUrl},
|
||||
nickname = #{nickname},
|
||||
gender = #{gender},
|
||||
birthday = #{birthday},
|
||||
birth_hour = #{birthHour},
|
||||
province = #{province},
|
||||
city = #{city},
|
||||
district = #{district},
|
||||
points = #{points},
|
||||
pinned_daily_card_id = #{pinnedDailyCardId},
|
||||
profile_edit_count = #{profileEditCount},
|
||||
updated_at = #{updatedAt}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.data.UserProfileMapper">
|
||||
|
||||
<sql id="BaseColumnList">
|
||||
id, user_id, real_name_encrypted, gender, birthday, birth_shichen, birth_country_code,
|
||||
birth_region_code, birth_city_code, birth_district_code, id_card_no_encrypted, id_card_hash,
|
||||
id_verified, id_verified_at, avatar_url, residence_province, residence_city, residence_district,
|
||||
residence_address, signature, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultType="com.hongzhen.hera.data.UserProfile">
|
||||
SELECT <include refid="BaseColumnList"/> FROM user_profile WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultType="com.hongzhen.hera.data.UserProfile">
|
||||
SELECT <include refid="BaseColumnList"/>
|
||||
FROM user_profile
|
||||
ORDER BY id DESC
|
||||
LIMIT #{offset}, #{limit}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO user_profile (
|
||||
user_id, real_name_encrypted, gender, birthday, birth_shichen, birth_country_code,
|
||||
birth_region_code, birth_city_code, birth_district_code, id_card_no_encrypted, id_card_hash,
|
||||
id_verified, id_verified_at, avatar_url, residence_province, residence_city, residence_district,
|
||||
residence_address, signature
|
||||
) VALUES (
|
||||
#{userId}, #{realNameEncrypted}, COALESCE(#{gender}, 0), #{birthday}, #{birthShichen},
|
||||
#{birthCountryCode}, #{birthRegionCode}, #{birthCityCode}, #{birthDistrictCode},
|
||||
#{idCardNoEncrypted}, #{idCardHash}, COALESCE(#{idVerified}, 0), #{idVerifiedAt}, #{avatarUrl},
|
||||
#{residenceProvince}, #{residenceCity}, #{residenceDistrict}, #{residenceAddress}, #{signature}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE user_profile
|
||||
SET user_id = #{userId},
|
||||
real_name_encrypted = #{realNameEncrypted},
|
||||
gender = COALESCE(#{gender}, gender),
|
||||
birthday = #{birthday},
|
||||
birth_shichen = #{birthShichen},
|
||||
birth_country_code = #{birthCountryCode},
|
||||
birth_region_code = #{birthRegionCode},
|
||||
birth_city_code = #{birthCityCode},
|
||||
birth_district_code = #{birthDistrictCode},
|
||||
id_card_no_encrypted = #{idCardNoEncrypted},
|
||||
id_card_hash = #{idCardHash},
|
||||
id_verified = COALESCE(#{idVerified}, id_verified),
|
||||
id_verified_at = #{idVerifiedAt},
|
||||
avatar_url = #{avatarUrl},
|
||||
residence_province = #{residenceProvince},
|
||||
residence_city = #{residenceCity},
|
||||
residence_district = #{residenceDistrict},
|
||||
residence_address = #{residenceAddress},
|
||||
signature = #{signature}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">DELETE FROM user_profile WHERE id = #{id}</delete>
|
||||
</mapper>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.data.UserVirtualItemMapper">
|
||||
|
||||
<sql id="BaseColumnList">
|
||||
id, user_id, virtual_item_id, nfc_uid, sort_order, status, obtained_at, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultType="com.hongzhen.hera.data.UserVirtualItem">
|
||||
SELECT <include refid="BaseColumnList"/> FROM user_virtual_item WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultType="com.hongzhen.hera.data.UserVirtualItem">
|
||||
SELECT <include refid="BaseColumnList"/>
|
||||
FROM user_virtual_item
|
||||
ORDER BY id DESC
|
||||
LIMIT #{offset}, #{limit}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO user_virtual_item (user_id, virtual_item_id, nfc_uid, sort_order, status, obtained_at)
|
||||
VALUES (
|
||||
#{userId}, #{virtualItemId}, #{nfcUid}, COALESCE(#{sortOrder}, 0), COALESCE(#{status}, 1),
|
||||
COALESCE(#{obtainedAt}, CURRENT_TIMESTAMP)
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE user_virtual_item
|
||||
SET user_id = #{userId},
|
||||
virtual_item_id = #{virtualItemId},
|
||||
nfc_uid = #{nfcUid},
|
||||
sort_order = COALESCE(#{sortOrder}, sort_order),
|
||||
status = COALESCE(#{status}, status),
|
||||
obtained_at = COALESCE(#{obtainedAt}, obtained_at)
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">DELETE FROM user_virtual_item WHERE id = #{id}</delete>
|
||||
</mapper>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.data.UserVirtualItemOfferingMapper">
|
||||
|
||||
<sql id="BaseColumnList">
|
||||
id, user_virtual_item_id, user_id, virtual_item_id, offering_type, content, merit_value,
|
||||
status, offered_at, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultType="com.hongzhen.hera.data.UserVirtualItemOffering">
|
||||
SELECT <include refid="BaseColumnList"/> FROM user_virtual_item_offering WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultType="com.hongzhen.hera.data.UserVirtualItemOffering">
|
||||
SELECT <include refid="BaseColumnList"/>
|
||||
FROM user_virtual_item_offering
|
||||
ORDER BY id DESC
|
||||
LIMIT #{offset}, #{limit}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO user_virtual_item_offering (
|
||||
user_virtual_item_id, user_id, virtual_item_id, offering_type, content, merit_value,
|
||||
status, offered_at
|
||||
) VALUES (
|
||||
#{userVirtualItemId}, #{userId}, #{virtualItemId}, #{offeringType}, #{content}, #{meritValue},
|
||||
COALESCE(#{status}, 1), COALESCE(#{offeredAt}, CURRENT_TIMESTAMP)
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE user_virtual_item_offering
|
||||
SET user_virtual_item_id = #{userVirtualItemId},
|
||||
user_id = #{userId},
|
||||
virtual_item_id = #{virtualItemId},
|
||||
offering_type = #{offeringType},
|
||||
content = #{content},
|
||||
merit_value = #{meritValue},
|
||||
status = COALESCE(#{status}, status),
|
||||
offered_at = COALESCE(#{offeredAt}, offered_at)
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">DELETE FROM user_virtual_item_offering WHERE id = #{id}</delete>
|
||||
</mapper>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.data.UserVirtualItemTransferMapper">
|
||||
|
||||
<sql id="BaseColumnList">
|
||||
id, from_user_id, from_user_name, to_user_id, to_user_name, virtual_item_id,
|
||||
from_user_item_id, to_user_item_id, content, status, transferred_at, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultType="com.hongzhen.hera.data.UserVirtualItemTransfer">
|
||||
SELECT <include refid="BaseColumnList"/> FROM user_virtual_item_transfer WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultType="com.hongzhen.hera.data.UserVirtualItemTransfer">
|
||||
SELECT <include refid="BaseColumnList"/>
|
||||
FROM user_virtual_item_transfer
|
||||
ORDER BY id DESC
|
||||
LIMIT #{offset}, #{limit}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO user_virtual_item_transfer (
|
||||
from_user_id, from_user_name, to_user_id, to_user_name, virtual_item_id,
|
||||
from_user_item_id, to_user_item_id, content, status, transferred_at
|
||||
) VALUES (
|
||||
#{fromUserId}, #{fromUserName}, #{toUserId}, #{toUserName}, #{virtualItemId},
|
||||
#{fromUserItemId}, #{toUserItemId}, #{content}, COALESCE(#{status}, 1),
|
||||
COALESCE(#{transferredAt}, CURRENT_TIMESTAMP)
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE user_virtual_item_transfer
|
||||
SET from_user_id = #{fromUserId},
|
||||
from_user_name = #{fromUserName},
|
||||
to_user_id = #{toUserId},
|
||||
to_user_name = #{toUserName},
|
||||
virtual_item_id = #{virtualItemId},
|
||||
from_user_item_id = #{fromUserItemId},
|
||||
to_user_item_id = #{toUserItemId},
|
||||
content = #{content},
|
||||
status = COALESCE(#{status}, status),
|
||||
transferred_at = COALESCE(#{transferredAt}, transferred_at)
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">DELETE FROM user_virtual_item_transfer WHERE id = #{id}</delete>
|
||||
</mapper>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.hongzhen.hera.data.VirtualItemMapper">
|
||||
|
||||
<sql id="BaseColumnList">
|
||||
id, item_name, icon_url, description, sort_order, enabled, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultType="com.hongzhen.hera.data.VirtualItem">
|
||||
SELECT <include refid="BaseColumnList"/> FROM virtual_item WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultType="com.hongzhen.hera.data.VirtualItem">
|
||||
SELECT <include refid="BaseColumnList"/>
|
||||
FROM virtual_item
|
||||
ORDER BY sort_order, id
|
||||
LIMIT #{offset}, #{limit}
|
||||
</select>
|
||||
|
||||
<insert id="insert">
|
||||
INSERT INTO virtual_item (id, item_name, icon_url, description, sort_order, enabled)
|
||||
VALUES (
|
||||
#{id}, #{itemName}, #{iconUrl}, #{description}, COALESCE(#{sortOrder}, 0), COALESCE(#{enabled}, 1)
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE virtual_item
|
||||
SET item_name = #{itemName},
|
||||
icon_url = #{iconUrl},
|
||||
description = #{description},
|
||||
sort_order = COALESCE(#{sortOrder}, sort_order),
|
||||
enabled = COALESCE(#{enabled}, enabled)
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">DELETE FROM virtual_item WHERE id = #{id}</delete>
|
||||
</mapper>
|
||||
@@ -0,0 +1,190 @@
|
||||
CREATE TABLE IF NOT EXISTS hera_user (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
open_id VARCHAR(128) NOT NULL,
|
||||
union_id VARCHAR(128) NULL,
|
||||
phone VARCHAR(32) NULL,
|
||||
avatar_url VARCHAR(512) NULL,
|
||||
nickname VARCHAR(64) NOT NULL,
|
||||
gender VARCHAR(16) NULL,
|
||||
birthday DATE NULL,
|
||||
birth_hour VARCHAR(32) NULL,
|
||||
province VARCHAR(64) NULL,
|
||||
city VARCHAR(64) NULL,
|
||||
district VARCHAR(64) NULL,
|
||||
points INT NOT NULL DEFAULT 0,
|
||||
pinned_daily_card_id VARCHAR(64) NULL,
|
||||
profile_edit_count INT NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_hera_user_open_id (open_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS login_session (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
session_key VARCHAR(64) NOT NULL,
|
||||
open_id VARCHAR(128) NOT NULL,
|
||||
union_id VARCHAR(128) NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_login_session_key (session_key),
|
||||
KEY idx_login_session_open_id (open_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sms_code (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
phone VARCHAR(32) NOT NULL,
|
||||
code VARCHAR(16) NOT NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
used TINYINT(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_sms_code_lookup (phone, code, used, expires_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS nfc_chip (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
uid VARCHAR(64) NOT NULL,
|
||||
chip_type VARCHAR(50) NULL,
|
||||
manufacturer VARCHAR(50) NULL,
|
||||
capacity INT UNSIGNED NULL,
|
||||
ndef_content TEXT NULL,
|
||||
holder_type TINYINT UNSIGNED NULL,
|
||||
virtual_item_id TINYINT UNSIGNED NULL,
|
||||
activated_at DATETIME NULL,
|
||||
last_login_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_uid (uid),
|
||||
KEY idx_holder_type (holder_type),
|
||||
KEY idx_virtual_item_id (virtual_item_id),
|
||||
KEY idx_activated_at (activated_at),
|
||||
KEY idx_last_login_at (last_login_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_account (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
username VARCHAR(50) NOT NULL,
|
||||
nickname VARCHAR(50) NULL,
|
||||
password_hash VARCHAR(100) NOT NULL,
|
||||
mobile VARCHAR(20) NULL,
|
||||
email VARCHAR(100) NULL,
|
||||
nfc_uid VARCHAR(64) NULL,
|
||||
wechat_openid VARCHAR(64) NULL,
|
||||
wechat_unionid VARCHAR(64) NULL,
|
||||
status TINYINT NOT NULL DEFAULT 1,
|
||||
register_country_code CHAR(2) NULL,
|
||||
register_region_code VARCHAR(20) NULL,
|
||||
register_city VARCHAR(100) NULL,
|
||||
register_timezone VARCHAR(50) NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_username (username),
|
||||
UNIQUE KEY uk_mobile (mobile),
|
||||
UNIQUE KEY uk_email (email),
|
||||
UNIQUE KEY uk_nfc_uid (nfc_uid),
|
||||
UNIQUE KEY uk_wechat_openid (wechat_openid),
|
||||
KEY idx_wechat_unionid (wechat_unionid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_profile (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
real_name_encrypted VARCHAR(128) NULL,
|
||||
gender TINYINT NOT NULL DEFAULT 0,
|
||||
birthday DATE NULL,
|
||||
birth_shichen TINYINT UNSIGNED NULL,
|
||||
birth_country_code CHAR(2) NULL,
|
||||
birth_region_code VARCHAR(20) NULL,
|
||||
birth_city_code VARCHAR(20) NULL,
|
||||
birth_district_code VARCHAR(20) NULL,
|
||||
id_card_no_encrypted VARCHAR(128) NULL,
|
||||
id_card_hash CHAR(64) NULL,
|
||||
id_verified TINYINT NOT NULL DEFAULT 0,
|
||||
id_verified_at DATETIME NULL,
|
||||
avatar_url VARCHAR(255) NULL,
|
||||
residence_province VARCHAR(50) NULL,
|
||||
residence_city VARCHAR(50) NULL,
|
||||
residence_district VARCHAR(50) NULL,
|
||||
residence_address VARCHAR(255) NULL,
|
||||
signature VARCHAR(500) NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_user_id (user_id),
|
||||
UNIQUE KEY uk_id_card_hash (id_card_hash),
|
||||
KEY idx_id_verified (id_verified),
|
||||
CONSTRAINT fk_profile_user FOREIGN KEY (user_id) REFERENCES user_account (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS virtual_item (
|
||||
id INT UNSIGNED NOT NULL,
|
||||
item_name VARCHAR(50) NOT NULL,
|
||||
icon_url VARCHAR(255) NULL,
|
||||
description VARCHAR(255) NULL,
|
||||
sort_order SMALLINT NOT NULL DEFAULT 0,
|
||||
enabled TINYINT NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_enabled_sort (enabled, sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_virtual_item (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
virtual_item_id TINYINT UNSIGNED NOT NULL,
|
||||
nfc_uid VARCHAR(64) NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
status TINYINT NOT NULL DEFAULT 1,
|
||||
obtained_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_user_item (user_id, virtual_item_id),
|
||||
KEY idx_user_sort (user_id, sort_order),
|
||||
KEY idx_virtual_item_id (virtual_item_id),
|
||||
KEY idx_nfc_uid (nfc_uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_virtual_item_offering (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_virtual_item_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
virtual_item_id TINYINT UNSIGNED NOT NULL,
|
||||
offering_type TINYINT NOT NULL,
|
||||
content VARCHAR(500) NULL,
|
||||
merit_value INT NULL,
|
||||
status TINYINT NOT NULL DEFAULT 1,
|
||||
offered_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_user_id (user_id),
|
||||
KEY idx_user_virtual_item_id (user_virtual_item_id),
|
||||
KEY idx_virtual_item_id (virtual_item_id),
|
||||
KEY idx_offering_type (offering_type),
|
||||
KEY idx_offered_at (offered_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_virtual_item_transfer (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
from_user_id BIGINT UNSIGNED NOT NULL,
|
||||
from_user_name VARCHAR(50) NULL,
|
||||
to_user_id BIGINT UNSIGNED NOT NULL,
|
||||
to_user_name VARCHAR(50) NULL,
|
||||
virtual_item_id TINYINT UNSIGNED NOT NULL,
|
||||
from_user_item_id BIGINT UNSIGNED NULL,
|
||||
to_user_item_id BIGINT UNSIGNED NULL,
|
||||
content VARCHAR(500) NULL,
|
||||
status TINYINT NOT NULL DEFAULT 1,
|
||||
transferred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_from_user_id (from_user_id),
|
||||
KEY idx_to_user_id (to_user_id),
|
||||
KEY idx_virtual_item_id (virtual_item_id),
|
||||
KEY idx_transferred_at (transferred_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
Reference in new issue
Block a user