diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/ActiveRecordField.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/ActiveRecordField.java new file mode 100644 index 0000000..ff24aac --- /dev/null +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/ActiveRecordField.java @@ -0,0 +1,9 @@ +package com.my.graphiteDigesterBg.diframe; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface ActiveRecordField { +} diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiActiveRecord.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiActiveRecord.java index 612e674..c6b4aa0 100644 --- a/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiActiveRecord.java +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiActiveRecord.java @@ -1,12 +1,181 @@ package com.my.graphiteDigesterBg.diframe; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.my.graphiteDigesterBg.diframe.mapper.DiActiveRecordMapper; -abstract public class DiActiveRecord { +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +public class DiActiveRecord { + // is new record + @JsonIgnore + public Boolean isNewRecord = true; + + // save model + public void save() { + var context = DiApplicationContextProvider.getContext(); + DiActiveRecordMapper mapper = context.getBean(DiActiveRecordMapper.class); + String tableName = DiActiveRecord.getTableNameFromModelClass(this.getClass()); + Map data = DiActiveRecord.exportModelData(this); + if ( this.isNewRecord ) { + mapper.insert(tableName, data); + } else { + mapper.update(tableName, (Integer)data.get("id"), data); + } + } + + // delete model + public void delete() { + var context = DiApplicationContextProvider.getContext(); + DiActiveRecordMapper mapper = context.getBean(DiActiveRecordMapper.class); + String tableName = DiActiveRecord.getTableNameFromModelClass(this.getClass()); + Map data = DiActiveRecord.exportModelData(this); + mapper.delete(tableName, (Integer)data.get("id")); + } + + // set attributes + public void setAttributes( Map attributes ) { + for ( Map.Entry entry : attributes.entrySet() ) { + String key = entry.getKey(); + Field field = null; + try { + field = this.getClass().getDeclaredField(key); + } catch (NoSuchFieldException e) { + continue ; + } + + Class fieldType = field.getType(); + if ( !fieldType.isAssignableFrom(entry.getValue().getClass()) ) { + throw new RuntimeException("Attribute type mismatch: " + key); + } + + field.setAccessible(true); + try { + field.set(this, entry.getValue()); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + // find by id - public static void findOne(Integer id) { + public static T findOne( Class modelClass, Integer id) { + return DiActiveRecord.findOne(modelClass, Map.of("id", id)); + } + + // find one by conditions + public static T findOne( Class modelClass, Map conditions) { + var criteria = new DiActiveRecordCriteria(); + criteria.tableName = DiActiveRecord.getTableNameFromModelClass(modelClass); + criteria.conditions = conditions; + criteria.limit = 1; + + var context = DiApplicationContextProvider.getContext(); + DiActiveRecordMapper mapper = context.getBean(DiActiveRecordMapper.class); + var rows = mapper.find(criteria); + if (rows.isEmpty()) { + return null; + } + + T model = DiActiveRecord.fillModel(modelClass, rows.get(0)); + model.isNewRecord = false; + return model; + } + + // find all by criteria + public static List find( Class modelClass, DiActiveRecordCriteria criteria ) { + criteria.tableName = DiActiveRecord.getTableNameFromModelClass(modelClass); var context = DiApplicationContextProvider.getContext(); DiActiveRecordMapper mapper = context.getBean(DiActiveRecordMapper.class); - var row = mapper.findById("di_app_users", id); -// // @TODO : 这里要根据配置参数计算 ~~~ - System.out.println("DiActiveRecord.findById"); + var rows = mapper.find(criteria); + + List models = new ArrayList<>(); + for ( var row : rows ) { + T model = DiActiveRecord.fillModel(modelClass, row); + model.isNewRecord = false; + models.add(model); + } + return models; + } + + // get table name from model class + private static String getTableNameFromModelClass( Class modelClass) { + Method tableNameGetter = null; + try { + tableNameGetter = modelClass.getMethod("getTableName"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + String tableName = null; + try { + tableName = (String)tableNameGetter.invoke(null); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + return tableName; + } + + // fill model + private static T fillModel(Class modelClass, Map data) { + Constructor modelConstructor = null; + try { + modelConstructor = modelClass.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + T model = null; + try { + model = (T)modelConstructor.newInstance(); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + + for ( Map.Entry entry : data.entrySet() ) { + String key = entry.getKey(); + Field field = null; + try { + field = model.getClass().getDeclaredField(key); + } catch (NoSuchFieldException e) { + continue ; + } + + Class fieldType = field.getType(); + if ( !fieldType.isAssignableFrom(entry.getValue().getClass()) ) { + throw new RuntimeException("Attribute type mismatch: " + key); + } + + field.setAccessible(true); + try { + field.set(model, entry.getValue()); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + return model; + } + + // model to map + private static Map exportModelData(Object model) { + Map data = new HashMap<>(); + var fields = model.getClass().getDeclaredFields(); + for ( var field : fields ) { + var annotation = field.getAnnotation(ActiveRecordField.class); + if ( annotation == null ) { + continue ; + } + + field.setAccessible(true); + try { + data.put(field.getName(), field.get(model)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + return data; } } diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiActiveRecordCriteria.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiActiveRecordCriteria.java new file mode 100644 index 0000000..8a22a42 --- /dev/null +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiActiveRecordCriteria.java @@ -0,0 +1,10 @@ +package com.my.graphiteDigesterBg.diframe; +import java.util.Map; +public class DiActiveRecordCriteria { + // table name + public String tableName; + // conditions + public Map conditions; + // limit + public Integer limit; +} diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiApiControllerBase.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiApiControllerBase.java index 222ad43..e46f55b 100644 --- a/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiApiControllerBase.java +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiApiControllerBase.java @@ -1,4 +1,10 @@ package com.my.graphiteDigesterBg.diframe; + +import com.my.graphiteDigesterBg.diframe.model.DiMdbUser; +import jakarta.servlet.http.HttpServletRequest; + +import java.util.Map; + abstract public class DiApiControllerBase { /** * success response @@ -51,4 +57,13 @@ abstract public class DiApiControllerBase { response.code = code; return response; } + + // get user from request + protected DiMdbUser getUserFromRequest(HttpServletRequest request) { + String accessToken = request.getHeader("App-Access-Token"); + if ( null == accessToken ) { + return null; + } + return DiActiveRecord.findOne(DiMdbUser.class, Map.of("accessToken", accessToken)); + } } diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/api/DiApiUser.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/api/DiApiUser.java index 5ec660d..35ad279 100644 --- a/src/src/main/java/com/my/graphiteDigesterBg/diframe/api/DiApiUser.java +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/api/DiApiUser.java @@ -1,18 +1,107 @@ package com.my.graphiteDigesterBg.diframe.api; +import com.my.graphiteDigesterBg.diframe.DiActiveRecord; +import com.my.graphiteDigesterBg.diframe.DiActiveRecordCriteria; import com.my.graphiteDigesterBg.diframe.DiApiControllerBase; import com.my.graphiteDigesterBg.diframe.DiApiResponse; import com.my.graphiteDigesterBg.diframe.model.DiMdbUser; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; +import java.util.Map; +import java.util.UUID; @Controller public class DiApiUser extends DiApiControllerBase { @ResponseBody @RequestMapping("/api/user/login") - public DiApiResponse login() { - DiMdbUser.findOne(1); + public DiApiResponse login(@RequestBody Map params) { + Integer id = (Integer)params.get("id"); + String password = (String)params.get("password"); + + var user = DiActiveRecord.findOne(DiMdbUser.class, id); + if ( null == user || !user.matchPassword(password) ) { + return this.error("无效的账号或密码"); + } + + user.accessToken = UUID.randomUUID().toString(); + user.accessTokenExpiredAt = (int)(System.currentTimeMillis() / 1000) + 3600 * 24; + user.save(); + return this.success(Map.of( + "accessToken", user.accessToken, + "accessTokenExpiredAt", user.accessTokenExpiredAt + )); + } + + @ResponseBody + @RequestMapping("/api/user/logout") + public DiApiResponse logout( HttpServletRequest request ) { + String accessToken = request.getHeader("App-Access-Token"); + var user = DiActiveRecord.findOne(DiMdbUser.class, Map.of("accessToken", accessToken)); + if ( null == user ) { + return this.success(); + } + + user.accessToken = ""; + user.accessTokenExpiredAt = 0; + user.save(); + return this.success(); + } + + @ResponseBody + @PostMapping("/api/user/save") + public DiApiResponse save( HttpServletRequest request, @RequestBody Map params ) { + DiMdbUser curUser = this.getUserFromRequest(request); + Integer id = (Integer)params.get("id"); + Map data = (Map)params.get("data"); + + var user = new DiMdbUser(); + user.salt = UUID.randomUUID().toString().substring(0, 8); + user.createdAt = (int)(System.currentTimeMillis() / 1000); + user.createdBy = curUser.id; + if ( null != id ) { + user = DiActiveRecord.findOne(DiMdbUser.class, id); + } + + user.setAttributes(data); + user.save(); return this.success(); } - public void logout() {} + @ResponseBody + @PostMapping("/api/user/delete") + public DiApiResponse delete( @RequestBody Map params ) { + Integer id = (Integer)params.get("id"); + var user = DiActiveRecord.findOne(DiMdbUser.class, id); + if ( null == user ) { + return this.success(); + } + + user.delete(); + return this.success(); + } + + @ResponseBody + @PostMapping("/api/user/updatePassword") + public DiApiResponse updatePassword( @RequestBody Map params ) { + Integer id = (Integer)params.get("id"); + String password = (String)params.get("password"); + var user = DiActiveRecord.findOne(DiMdbUser.class, id); + if ( null == user ) { + return this.error("无效的用户"); + } + user.password = user.hashPassword(password); + user.save(); + return this.success(); + } + + @ResponseBody + @RequestMapping("/api/user/list") + public DiApiResponse list() { + var criteria = new DiActiveRecordCriteria(); + criteria.limit = 10; + var users = DiActiveRecord.find(DiMdbUser.class, criteria); + return this.success(Map.of("list",users)); + } } diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/mapper/DiActiveRecordMapper.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/mapper/DiActiveRecordMapper.java index 3b2d4e6..3e27035 100644 --- a/src/src/main/java/com/my/graphiteDigesterBg/diframe/mapper/DiActiveRecordMapper.java +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/mapper/DiActiveRecordMapper.java @@ -1,9 +1,45 @@ package com.my.graphiteDigesterBg.diframe.mapper; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Select; +import com.my.graphiteDigesterBg.diframe.DiActiveRecordCriteria; +import org.apache.ibatis.annotations.*; + +import java.util.List; import java.util.Map; @Mapper public interface DiActiveRecordMapper { + @Select( + "" + ) + List> find(DiActiveRecordCriteria criteria); + @Select("SELECT * FROM ${tableName} WHERE id = #{id}") Map findById(String tableName, Integer id); + + @Insert( + "" + ) + Integer insert(String tableName, Map data); + + @Update( + "" + ) + Integer update(String tableName, Integer id, Map data); + + @Delete("DELETE FROM ${tableName} WHERE id = #{id}") + Integer delete(String tableName, Integer id); } diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/model/DiMdbUser.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/model/DiMdbUser.java index b1c4d0a..6a70d2e 100644 --- a/src/src/main/java/com/my/graphiteDigesterBg/diframe/model/DiMdbUser.java +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/model/DiMdbUser.java @@ -1,5 +1,54 @@ package com.my.graphiteDigesterBg.diframe.model; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.my.graphiteDigesterBg.diframe.ActiveRecordField; import com.my.graphiteDigesterBg.diframe.DiActiveRecord; +import org.springframework.util.DigestUtils; public class DiMdbUser extends DiActiveRecord { + @ActiveRecordField + public Integer id; + @ActiveRecordField + public String account; + + @JsonIgnore + @ActiveRecordField + public String password; + + @JsonIgnore + @ActiveRecordField + public String salt; + + @ActiveRecordField + public Integer roleId; + + @ActiveRecordField + public Integer createdAt; + + @ActiveRecordField + public Integer createdBy; + + @JsonIgnore + @ActiveRecordField + public String accessToken; + + @JsonIgnore + @ActiveRecordField + public Integer accessTokenExpiredAt; + + // get table name + public static String getTableName() { + return "app_users"; + } + + // check if password matches + public Boolean matchPassword(String password) { + String hash = this.hashPassword(password); + return this.password.equals(hash); + } + + // hash password + public String hashPassword(String password) { + String salt = this.salt; + return DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()); + } } diff --git a/src/src/main/resources/application.yml b/src/src/main/resources/application.yml index d0910e1..dd42fd0 100644 --- a/src/src/main/resources/application.yml +++ b/src/src/main/resources/application.yml @@ -5,6 +5,10 @@ spring: password: 1 driver-class-name: org.sqlite.JDBC +mybatis: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + device : connection : class : com.my.graphiteDigesterBg.diframe.connection.DiConSerialPort