Open duangsuse opened 5 years ago
目前 GeekApk 的控制器路径模板已经在 add-controller-template 分支上开发
填充完所有模板并合并到 master 后,下一个目标 branch 是 add-controller-tests
其时 v1b 的第一个版本所有非控制器的编程需求都没有了(因为之前都写完了,感谢 SpringBoot 使得我不用写那么多难看并且耗时间的 XML 配置文件),接下来就是测试驱动编程 implement-interfaces 喽
Test-driven development 的一个好处就是出来的肯定是能用的成品,因为它通过了验收测试(Acceptance test)
然后就是写前端
[DuangSUSE@duangsuse]~/文档/geekapk_spring% node code_writer.js
serverDetail() -> object:detailMap = /serverDetail
serverVersion() -> plain = /serverVersion
serverDescription() -> plain = /serverDescription
serverBoot() -> datetime = /serverBoot
POST@createUser(username:String) -> object:GeekUser = /admin/makeUser
PUT@resetSharedHash(uid-path:UserId, shash:String?) -> plain = /admin/resetMetaHash/{uid}
DELETE@deleteUser(uid-path:UserId) -> object:GeekUser = /admin/dropUser/{uid}
PUT@flagUser(uid-path:UserId, flag:Int) -> object:GeekUser = /admin/flagUser/{uid}
POST@createCategory(name:String) -> object:Category = /admin/makeCategory
PUT@renameCategory(id-path:CategoryId, name:String) -> object:Category = /admin/nameCategory/{id}
DELETE@deleteCategory(id-path:CategoryId) -> object:Category = /admin/dropCategory/{id}
DELETE@deleteApp(aid-path:AppId) -> object:App = /admin/dropApp/{aid}
PUT@transferAppCategory(aid-path:AppId, cid:CategoryId) -> [$aid:number, $old:number, $new:number] = /admin/moveApp/{aid}
PUT@transferAppOwner(aid-path:AppId, uid:UserId) -> [$aid:number, $old:number, $new:number] = /admin/transferApp/{aid}
DELETE@deleteAppUpdate(aid-path:AppId, rev-path:Int) -> object:AppUpdate = /admin/dropAppUpdate/{aid}/{rev}
DELETE@deleteComment(cid-path:CommentId) -> [$cid:number, $deletedSubComments:number] = /admin/dropComment/{cid}
categoryList() -> array:Category = category/all
categoryName(id-path:CategoryId) -> plain = category/{id}
readUser(id-path:UserId) -> object:GeekUser = user/{id}
updateUser(id-path:UserId, prop:String, value-body:String) -> object:string = user/{id}
resetHash(id-path:UserId, shash:String, hash:String) -> object:string = user/{id}/hash
checkHash(id-path:UserId, hash:String) -> object:string = user/{id}/checkHash
listUser(sort:String?, sliceFrom:UserSize?, sliceTo:UserSize?) -> array:GeekUser = user/all
listMetaUser(sort:String?, sliceFrom:UserSize?, sliceTo:UserSize?) -> array:GeekUser = user/allHasMetaApp
searchUser(type:String?, kw-path:String, sort:String?) -> array:GeekUser = user/search/{kw}
updateOnlineTime(id-path:UserId) = user/{id}/online
[ { method: 'GET', name: 'serverDetail', args: [], return: { type: 'object', of: 'detailMap' }, url: '/serverDetail' }, { method: 'GET', name: 'serverVersion', args: [], return: 'plain', url: '/serverVersion' }, { method: 'GET', name: 'serverDescription', args: [], return: 'plain', url: '/serverDescription' }, { method: 'GET', name: 'serverBoot', args: [], return: 'datetime', url: '/serverBoot' }, { method: 'POST', name: 'createUser', args: [ [Object] ], return: { type: 'object', of: 'GeekUser' }, url: '/admin/makeUser' }, { method: 'PUT', name: 'resetSharedHash', args: [ [Object], [Object] ], return: 'plain', url: '/admin/resetMetaHash/{uid}' }, { method: 'DELETE', name: 'deleteUser', args: [ [Object] ], return: { type: 'object', of: 'GeekUser' }, url: '/admin/dropUser/{uid}' }, { method: 'PUT', name: 'flagUser', args: [ [Object], [Object] ], return: { type: 'object', of: 'GeekUser' }, url: '/admin/flagUser/{uid}' }, { method: 'POST', name: 'createCategory', args: [ [Object] ], return: { type: 'object', of: 'Category' }, url: '/admin/makeCategory' }, { method: 'PUT', name: 'renameCategory', args: [ [Object], [Object] ], return: { type: 'object', of: 'Category' }, url: '/admin/nameCategory/{id}' }, { method: 'DELETE', name: 'deleteCategory', args: [ [Object] ], return: { type: 'object', of: 'Category' }, url: '/admin/dropCategory/{id}' }, { method: 'DELETE', name: 'deleteApp', args: [ [Object] ], return: { type: 'object', of: 'App' }, url: '/admin/dropApp/{aid}' }, { method: 'PUT', name: 'transferAppCategory', args: [ [Object], [Object] ], return: [ [Object], [Object], [Object] ], url: '/admin/moveApp/{aid}' }, { method: 'PUT', name: 'transferAppOwner', args: [ [Object], [Object] ], return: [ [Object], [Object], [Object] ], url: '/admin/transferApp/{aid}' }, { method: 'DELETE', name: 'deleteAppUpdate', args: [ [Object], [Object] ], return: { type: 'object', of: 'AppUpdate' }, url: '/admin/dropAppUpdate/{aid}/{rev}' }, { method: 'DELETE', name: 'deleteComment', args: [ [Object] ], return: [ [Object], [Object] ], url: '/admin/dropComment/{cid}' }, { method: 'GET', name: 'categoryList', args: [], return: { type: 'array', of: 'Category' }, url: 'category/all' }, { method: 'GET', name: 'categoryName', args: [ [Object] ], return: 'plain', url: 'category/{id}' }, { method: 'GET', name: 'readUser', args: [ [Object] ], return: { type: 'object', of: 'GeekUser' }, url: 'user/{id}' }, { method: 'GET', name: 'updateUser', args: [ [Object], [Object], [Object] ], return: { type: 'object', of: 'string' }, url: 'user/{id}' }, { method: 'GET', name: 'resetHash', args: [ [Object], [Object], [Object] ], return: { type: 'object', of: 'string' }, url: 'user/{id}/hash' }, { method: 'GET', name: 'checkHash', args: [ [Object], [Object] ], return: { type: 'object', of: 'string' }, url: 'user/{id}/checkHash' }, { method: 'GET', name: 'listUser', args: [ [Object], [Object], [Object] ], return: { type: 'array', of: 'GeekUser' }, url: 'user/all' }, { method: 'GET', name: 'listMetaUser', args: [ [Object], [Object], [Object] ], return: { type: 'array', of: 'GeekUser' }, url: 'user/allHasMetaApp' }, { method: 'GET', name: 'searchUser', args: [ [Object], [Object], [Object] ], return: { type: 'array', of: 'GeekUser' }, url: 'user/search/{kw}' }, { method: 'GET', name: 'updateOnlineTime', args: [ [Object] ], return: null, url: 'user/{id}/online' } ]
GET /serverDetail: serverDetail() Returning object of detailMap
GET /serverVersion: serverVersion() Returning plain
GET /serverDescription: serverDescription() Returning plain
GET /serverBoot: serverBoot() Returning datetime
POST /admin/makeUser: createUser(username:String!) Returning object of GeekUser
PUT /admin/resetMetaHash/{uid}: resetSharedHash(uid-path:UserId!,shash:String?) Returning plain
DELETE /admin/dropUser/{uid}: deleteUser(uid-path:UserId!) Returning object of GeekUser
PUT /admin/flagUser/{uid}: flagUser(uid-path:UserId!,flag:Int!) Returning object of GeekUser
POST /admin/makeCategory: createCategory(name:String!) Returning object of Category
PUT /admin/nameCategory/{id}: renameCategory(id-path:CategoryId!,name:String!) Returning object of Category
DELETE /admin/dropCategory/{id}: deleteCategory(id-path:CategoryId!) Returning object of Category
DELETE /admin/dropApp/{aid}: deleteApp(aid-path:AppId!) Returning object of App
PUT /admin/moveApp/{aid}: transferAppCategory(aid-path:AppId!,cid:CategoryId!) Returning number:aid,number:old,number:new
PUT /admin/transferApp/{aid}: transferAppOwner(aid-path:AppId!,uid:UserId!) Returning number:aid,number:old,number:new
DELETE /admin/dropAppUpdate/{aid}/{rev}: deleteAppUpdate(aid-path:AppId!,rev-path:Int!) Returning object of AppUpdate
DELETE /admin/dropComment/{cid}: deleteComment(cid-path:CommentId!) Returning number:cid,number:deletedSubComments
GET category/all: categoryList() Returning array of Category
GET category/{id}: categoryName(id-path:CategoryId!) Returning plain
GET user/{id}: readUser(id-path:UserId!) Returning object of GeekUser
GET user/{id}: updateUser(id-path:UserId!,prop:String!,value-body:String!) Returning object of string
GET user/{id}/hash: resetHash(id-path:UserId!,shash:String!,hash:String!) Returning object of string
GET user/{id}/checkHash: checkHash(id-path:UserId!,hash:String!) Returning object of string
GET user/all: listUser(sort:String?,sliceFrom:UserSize?,sliceTo:UserSize?) Returning array of GeekUser
GET user/allHasMetaApp: listMetaUser(sort:String?,sliceFrom:UserSize?,sliceTo:UserSize?) Returning array of GeekUser
GET user/search/{kw}: searchUser(type:String?,kw-path:String!,sort:String?) Returning array of GeekUser
GET user/{id}/online: updateOnlineTime(id-path:UserId!) Returning noting [DuangSUSE@duangsuse]~/文档/geekapk_spring%
找到了个很有意思的名词:Dao(Data Access Object) 是这样的么?
Branch "add-tests" excluded per configuration.
贡献者 duangsuse 已经启用很无聊的 Commit GnuPG 签名,除了让 GitHub 开心外没有什么卵用。
百 Commit 纪念
Travis GitHub App Installed :wrench:
[In reply to duangsuse::Echo] git diff --stat fb0242a475e84fe12911840de5d7740b8fd075e0 abb5d6c47cb011c5059e8e27c4e569e87b58b50b
11 files changed, 343 insertions(+), 34 deletions(-)
在半个小时里,GeekSpec 自动生成了 300 多行模板代码,换算成人就是十分钟写 100 行啊,都是这种代码:
@PutMapping("/{aid}")
@ResponseBody
fun updateApp(@PathVariable("aid") aid: AppId, @RequestParam("attr") attr: String/* Maybe package or icon or name or screenshots or readme */,
@RequestBody value: String): Map<String, String> /* attr: String *//* oldVal: String */ {
TODO()
}
不无聊么? 上面的代码,为什么不可以这么写呢?
PUT@updateApp(aid-path:AppId, attr:String{package, icon, name, screenshots, readme}, val-body:String)
-> [$attr:String, $oldVal:String]
= /app/{aid}
于是我居然花了一天时间弄了个 GeekApk 自己替换 Swagger 的,虽然没啥复杂算法... 但不得不说,真的很有用。 『磨刀不误砍柴工』
... CS 看多了又想到偏序理论... 头疼
🤔 GeekSpec 对 GeekApk Spring 的工程有多大的辅助作用呢?自 Spec 文件写完 https://github.com/duangsuse/GeekApk/commit/fb0242a475e84fe12911840de5d7740b8fd075e0 之后,自动生成解决了 300 多行模板代码的编写任务,从写完 7:34 到完成任务合并主分支 8:08 只花费了半个小时左右,可谓是一瞬生成了(
哇,除了 DAO,我又认识了一大堆那么专业的术语名词,听起来真是好大佬呢,好厉害呢!不明觉厉呢!哇!原来这就是 IT 啊,我要打十个!
MVC、MVP、MVVP、Databinding、ViewHolder、AOP、Provider、Resolver、Adaptor、Pattern、List 视图、PAJAX、AJAX、Reverse AJAX、Bean、CDI、Transactional、ACID、Functional、GC、Memory Leak、static field、obfuscation、anti-reversing、Machine learning、解耦合、Component、POJO、DSL、Parametric Types、Encryption、Cipher、Observer、Stream、Future、Dependencies、Box Model、Dispatcher、Handler、Scheduler、OOP、FP、Xml
SICP、TaPL、FoPL、CSAPP、SASP、…
你们说我是不是可以去写十个酷安了?(笑)
懒得用,因为我觉得不好看、不是。因为酷安的 API 客户端验证就用到了此类、DDG 上一搜全都是,并且我也看过 Oracle 的文档,我不可能看不到。实际上我妄图写了点 Kotlin/multiplatform 代码都用到了此类。
PostgreSQL 更成熟,而且我们的建模是关系型的,目前没有 lower 化模型的打算 芒果数据库(不准喷翻译)是文档型数据库,其实它这个接口很适合熟悉 Java collections API 的程序员使用。我们说关系的本质是图,但是图在电子计算机上往往使用 Map 和 (对于每个节点之间关系箭头的建立)Set 来构建。MongoDB 提供了易用的 API。 GeekApk 的模型很大,我很难一次做好(是的,即使我出过很多版本的文档) 不像隔壁,它的模型就比较简单(是的,而且他们用的 service 层又是别的… 我们为了速度先用的是手里有包含实际例子的 SpringBoot。 LBA 架构么?总是说术语… 无聊,而且这就像拼音和五笔一样,明明都差不多,各有所好因为各种有不同的经历)
要喷这个,请大人您先考虑一下 GeekApk v1b 的模型,和上面那个比起来哪个大。注意我们(GA Spring 和 Rebase-server)的权限验证都没有用外部的 CDI 库。
不说那些基本就是非常简单的 proxy (和设计模式无关) extract 的东西(比如那些单纯包装外部 procedure 的类、静态方法、静态字段)了,咱看看这个:
@Constraint(validatedBy = { CategoryKey.Validator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface CategoryKey {
int min() default 1;
int max() default Globals.SIZE_CATEGORY;
String message() default "{org.hibernate.validator.constraints.Length.message}";
// Validator implementation omitted
80% 的模板代码,而且三个文件中可以说这些代码都是重复的。我看一遍这些模板代码使用的接口也就大概了解了,请问这算什么?不算 Java 8 语法的部分,我只需要知道 @Constraint(validatedBy: Class<?>[]) min max message org.hibernate.validator.constraints.Length.message Class<?>[] groups() Class<? extends Payload>[] payload() Validator implements ConstraintValidator<CategoryKey, String> { initialize (annotation); boolean validate/*update: isValid*/(value, context); }
,就算学会了这知识。何况 IDEA 现在那么方便,应该比谁更会用特性和快捷键(e.g. Ctrl N)才是。
我看完这些就是满脑子名词,Resources、Provider… XXXXs、YYYYMapper…
然而,比起理解递归、解决各种动态规划甚至线性代数问题,这些接口了解性质的东西真的可谓是『了解』而不能算作是学习… 这点概念哪里配得上『学习』这个词… 配得上机器学习里的学习这个词
我很心虚,觉得自己的代码不好,就去看别人的取长补短。我是菜🐔别人是大佬不行吗?
* 那么多人用 J2SE 的 UUID 类,你为什么不用,非得自己写?是不是你连 JSE 有这个都不知道?
....
行了... 好无聊啊,其实要看的话,要比较的话也没有可比性,那个设计模式更多一些(),但它不能证明什么,因为这些代码也可以抄,目前本项目的代码都是我抄书上抄来的。
我目前的任务就是弄完 GeekApk,其他的再说。
duangsuse 有时候会有点神经质,因为我对技术看得太重... 如有不便还请大家谅解。刚才想要不要彻底分析一下刚才那个项目,顺便熟悉一下工程的说(因为我平时工程做得比较少,可以作为一种提升,虽然也模式化但比这个 Spring 的稍微好一点),可是因为 GA 最好还是先弄完(包括前端)就算了。
:pray: :boy:
https://github.com/Codingpedia/demo-rest-jersey-spring
emmmm DataAccessObject 和 Entity 居然不是一样的 🤔 #Backend #web
不过我现在几乎没有看到 DAO、大概是后端的书不够多...
https://github.com/imufun/rebase-server/tree/master/src/main/java/com/drakeet/rebase/api/tool
我已经正常分析了此项目并且理解了所有 Extracts(Authorizations, Globals, Hashes, Log, MongoDBs, RebaseAsserts, Responses, URIs)、Mapper(ConstraintViolationExceptionMapper, WebExceptionMapper)、Provider(GsonBodyProvider)、Resolver(ValidationConfigurationContextResolver)、Implementation(ObjectIdSerializer)... 还有他们自己的用途、模式、运行时的数据流和控制流,并和我之前的知识相结合,并且在正常的时间内完成,不会崩坏了。thx。GeekApk 会照常以自己的风格完成开发。
since 目前我写了第一个含写的接口业务逻辑测试,结果写了半天 200 多行代码... 而且很多代码... 呃... 很难看,因为 JUnit 的原因,貌似没办法保持一定顺序执行(因为我这个是要求某些操作顺序的,之间互相有依赖关系),而且这个测试质量很差... 而且单独去给所有接口写测试是非常淡疼的。我决定写保护 add-tests 分支,跳过测试编写,直接开始编写业务逻辑。而且我希望尽快完成任务,,, 所有 v1b 的 Boot Server 可能不会弄 test-driven development,emmm....
https://github.com/duangsuse/GeekApk/commit/58a32fbc127df3cbd4b6c30e624c88231b58bb47 割肉删掉了测试.. 以后自动化测试会由 Ruby 执行
♥\_(ㇱ)_/✐
:point_left: 疯狂暗示用爱写代码 hhhh :rofl:
Ruby 的 Spectrum 打算先暂时 JSON,因为我最近有点... 不想写 Ruby?
其实是嫌浪费时间了
[ Photo ] 累死,算是学习(确信)了一下 Ruby 和测试了 Spectrum... 感觉解耦合做得不是很好,response mapper 根本一点用都没有(因为要很多 mapper 必须要拿到一个 ClientShowcase 实例),另外给 ClientShowcase 添加一个 Hash 表来放诸如密码这样的东西隐式传递不错),考虑重构。
[In reply to duangsuse::Echo] Times 是之前老 GeekApkR 里唯一一个能用的后端服务... Ruby + Sinatra 写的,其实现在看来都很不良实践(跑) 凑字数(代码行数)吧(
不过它的测试套件也算完整,虽然持久化层的不是 Mock,但也可以用,缺点是如果在生产环境用了很可能导致数据丢失
Times 没有使用 DBMS,而是,这是因为我以为它不会有太大的数据集性能要求,我觉得很可以重构(比方说,使用类似 MongoDB 的面向文档数据库替换纯手动)... 尤其是难看的 API...
值得注意的是我以前智商还低一些的时候,连常用的序列化这种弱智能力都没有... JSON 序列化(Times 的数据持久化方式)当时非常的混乱,我非常智障的套模板(或许,因为我想我那时就是瞎搅合...)
我没有么写:
[foo, bar, baz].to_json
而是:
[foo, bar, baz].map { |o| o.to_json }.to_json
然后反序列也一样... rofl.
[In reply to duangsuse::Echo] 回来了(因为身体素质真的开始有点 🌶🐔 了,而且非常需要休息,然后老是熬夜容易变丑,虽然其实长的就不咋地,大嘘),顺便添了一些任务
部署文档添加 Systemd 低端口权限配置提示(服务程序部署安全,这样如果我们的 upstream 有严重的,例如 RCE、可以拿 Shell 的安全问题别人不至于一下子迫真 rm -rf / 殃及池鱼) :heavy_check_mark:
把所有 Endpoint mapping 的 Int?
换成 java.lang.Integer?
(要不然错误,说 int 不能是 optional 的,虽然本来有默认值 0)(这个打脸了,我真的不是不了解 Kotlin... 我真的只是被 Spring 的提示误导了... emmm) :heavy_check_mark:
为了 Clients 数据交换开心,添加 PATCH Json API(现在创建用户之后可以直接拿 JSON 去 patch 属性了,不用一个一个属性去 update) :heavy_check_mark: (现在还不是 RESTful 的)
添加用户和管理员权限验证的 Middleware(WebFilter)、其中用户的权限验证和 Rate Limit 一起做(GeekHits API 也一起开放了)
为 CI Intergation 添加 Node.JS 和 MRI Ruby 依赖,并且开始使用外围的 Spectrum Ruby 客户端接口测试(从 JUnit 启动服务器和 RSpec 客户端自动化测试程序) :heavy_check_mark:
GeekApk v1b 完成后可能有别的打算(包括 Android 客户端,不准喷我不会什么设计模式)(有些人不准喷我菜...)(虽然我有时候的确很菜,但我自己觉得我很有前途,嗯嗯。)
希望以后都是艳阳高照。冻死了天又黑...
下一步操作是 WebHooks(GeekPush)支持、Topics 等和 MessageHeader 的支持、包括工具的其他平台移植
== 晚点
== 发布之后
TODO
getXXXXCount()
Interfaces for client update event checkTODO later
"NOT"
TODO